Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for audio files (DSP-1343) #1818

Merged
merged 5 commits into from Feb 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions knora-ontologies/knora-base.ttl
Expand Up @@ -33,7 +33,7 @@

:attachedToProject knora-admin:SystemProject ;

:ontologyVersion "knora-base v10" .
:ontologyVersion "knora-base v11" .



Expand Down Expand Up @@ -1674,7 +1674,7 @@
rdfs:subClassOf :FileValue ,
[ rdf:type owl:Restriction ;
owl:onProperty :duration ;
owl:cardinality "1"^^xsd:nonNegativeInteger
owl:maxCardinality "1"^^xsd:nonNegativeInteger
] ;

rdfs:comment "Represents an audio file"@en .
Expand Down Expand Up @@ -2163,7 +2163,7 @@
] ,
[ rdf:type owl:Restriction ;
owl:onProperty :duration ;
owl:cardinality "1"^^xsd:nonNegativeInteger
owl:maxCardinality "1"^^xsd:nonNegativeInteger
] ;

rdfs:comment "Represents a moving image file"@en .
Expand Down
9 changes: 7 additions & 2 deletions sipi/scripts/file_info.lua
Expand Up @@ -23,6 +23,7 @@ require "util"
TEXT = "text"
IMAGE = "image"
DOCUMENT = "document"
AUDIO = "audio"

-------------------------------------------------------------------------------
-- Mimetype constants
Expand All @@ -37,7 +38,9 @@ local TEXT_XML = "text/xml"
local TEXT_PLAIN = "text/plain"
local AUDIO_MP3 = "audio/mpeg"
local AUDIO_MP4 = "audio/mp4"
local AUDIO_WAV = "audio/x-wav"
local AUDIO_WAV = "audio/wav"
local AUDIO_X_WAV = "audio/x-wav"
local AUDIO_VND_WAVE = "audio/vnd.wave"
local APPLICATION_PDF = "application/pdf"
local APPLICATION_DOC = "application/msword"
local APPLICATION_DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
Expand All @@ -61,7 +64,9 @@ local image_mime_types = {
local audio_mime_types = {
AUDIO_MP3,
AUDIO_MP4,
AUDIO_WAV
AUDIO_WAV,
AUDIO_X_WAV,
AUDIO_VND_WAVE
}

local text_mime_types = {
Expand Down
Binary file added test_data/test_route/files/minimal.wav
Binary file not shown.
Binary file added test_data/test_route/files/test.wav
Binary file not shown.
4 changes: 2 additions & 2 deletions webapi/src/main/resources/application.conf
Expand Up @@ -398,8 +398,8 @@ app {
"application/x-iso9660-image",
]
text-mime-types = ["application/xml", "text/xml", "text/csv", "text/plain"]
movie-mime-types = []
sound-mime-types = ["audio/mpeg", "audio/mp4", "audio/x-wav", "audio/vnd.wav"]
video-mime-types = []
audio-mime-types = ["audio/mpeg", "audio/mp4", "audio/wav", "audio/x-wav", "audio/vnd.wave"]
}

ark {
Expand Down
Expand Up @@ -51,14 +51,16 @@ case class GetFileMetadataRequest(fileUrl: String, requestingUser: UserADM) exte
* @param internalMimeType the file's internal MIME type. Always defined (https://dasch.myjetbrains.com/youtrack/issue/DSP-711).
* @param width the file's width in pixels, if applicable.
* @param height the file's height in pixels, if applicable.
* @param pageCount the number of pages in the file, if applicable.
* @param pageCount the number of pages in the file, if applicable.
* @param duration the duration of the file in seconds, if applicable.
*/
case class GetFileMetadataResponse(originalFilename: Option[String],
originalMimeType: Option[String],
internalMimeType: String,
width: Option[Int],
height: Option[Int],
pageCount: Option[Int])
pageCount: Option[Int],
duration: Option[BigDecimal])

/**
* Asks Sipi to move a file from temporary to permanent storage.
Expand Down
Expand Up @@ -1087,6 +1087,19 @@ object ConstructResponseUtilV2 {
fileValue = fileValue,
comment = valueCommentOption
))

case OntologyConstants.KnoraBase.AudioFileValue =>
FastFuture.successful(
AudioFileValueContentV2(
ontologySchema = InternalSchema,
fileValue = fileValue,
duration = valueObject
.maybeStringObject(OntologyConstants.KnoraBase.Duration.toSmartIri)
.map(definedDuration => BigDecimal(definedDuration)),
comment = valueCommentOption
))

case _ => throw InconsistentRepositoryDataException(s"Unexpected file value type: $valueType")
}
}

Expand Down
Expand Up @@ -88,6 +88,8 @@ class ValueUtilV1(private val settings: KnoraSettingsImpl) {
makeStillImageValue(valueProps, projectShortcode, responderManager, userProfile)
case OntologyConstants.KnoraBase.TextFileValue =>
makeTextFileValue(valueProps, projectShortcode, responderManager, userProfile)
case OntologyConstants.KnoraBase.AudioFileValue =>
makeAudioFileValue(valueProps, projectShortcode, responderManager, userProfile)
case OntologyConstants.KnoraBase.DocumentFileValue =>
makeDocumentFileValue(valueProps, projectShortcode, responderManager, userProfile)
case OntologyConstants.KnoraBase.LinkValue => makeLinkValue(valueProps, responderManager, userProfile)
Expand Down Expand Up @@ -122,16 +124,20 @@ class ValueUtilV1(private val settings: KnoraSettingsImpl) {
* Creates a URL for accessing a text file via Sipi.
*
* @param textFileValue the text file value representing the text file.
* @param external a flag denoting the type of URL that should be generated.
* @return a Sipi URL.
*/
def makeSipiTextFileGetUrlFromFilename(textFileValue: TextFileValueV1, external: Boolean = true): String = {
def makeSipiTextFileGetUrlFromFilename(textFileValue: TextFileValueV1): String = {
s"${settings.externalSipiBaseUrl}/${textFileValue.projectShortcode}/${textFileValue.internalFilename}"
}

if (external) {
s"${settings.externalSipiBaseUrl}/${textFileValue.projectShortcode}/${textFileValue.internalFilename}"
} else {
s"${settings.internalSipiBaseUrl}/${textFileValue.projectShortcode}/${textFileValue.internalFilename}"
}
/**
* Creates a URL for accessing an audio file via Sipi.
*
* @param audioFileValue the file value representing the audio file.
* @return a Sipi URL.
*/
def makeSipiAudioFileGetUrlFromFilename(audioFileValue: AudioFileValueV1): String = {
s"${settings.externalSipiIIIFGetUrl}/${audioFileValue.projectShortcode}/${audioFileValue.internalFilename}/file"
}

// A Map of MIME types to Knora API v1 binary format name.
Expand All @@ -158,9 +164,11 @@ class ValueUtilV1(private val settings: KnoraSettingsImpl) {
"text/csv" -> "CSV",
"application/zip" -> "ZIP",
"application/x-compressed-zip" -> "ZIP",
"audio/x-wav" -> "AUDIO",
"audio/mpeg" -> "AUDIO",
"audio/mp4" -> "AUDIO",
"audio/mpeg" -> "AUDIO"
"audio/wav" -> "AUDIO",
"audio/x-wav" -> "AUDIO",
"audio/vnd.wave" -> "AUDIO"
), { key: String =>
s"Unknown MIME type: $key"
}
Expand Down Expand Up @@ -199,6 +207,14 @@ class ValueUtilV1(private val settings: KnoraSettingsImpl) {
origname = textFileValue.originalFilename,
path = makeSipiTextFileGetUrlFromFilename(textFileValue)
)

case audioFileValue: AudioFileValueV1 =>
LocationV1(
format_name = mimeType2V1Format(audioFileValue.internalMimeType),
origname = audioFileValue.originalFilename,
path = makeSipiAudioFileGetUrlFromFilename(audioFileValue)
)

case otherType => throw NotImplementedException(s"Type not yet implemented: ${otherType.valueTypeIri}")
}
}
Expand Down Expand Up @@ -364,12 +380,14 @@ class ValueUtilV1(private val settings: KnoraSettingsImpl) {

case _: LinkV1 => basicObjectResponse

case _: StillImageFileValueV1 => basicObjectResponse // TODO: implement this.
case _: StillImageFileValueV1 => basicObjectResponse

case _: TextFileValueV1 => basicObjectResponse

case _: DocumentFileValueV1 => basicObjectResponse

case _: AudioFileValueV1 => basicObjectResponse

case _: HierarchicalListValueV1 => basicObjectResponse

case _: ColorValueV1 => basicObjectResponse
Expand Down Expand Up @@ -856,6 +874,31 @@ class ValueUtilV1(private val settings: KnoraSettingsImpl) {
))
}

/**
* Converts a [[ValueProps]] into a [[AudioFileValueV1]].
*
* @param valueProps a [[ValueProps]] representing the SPARQL query results to be converted.
* @return a [[DocumentFileValueV1]].
*/
private def makeAudioFileValue(
valueProps: ValueProps,
projectShortcode: String,
responderManager: ActorRef,
userProfile: UserADM)(implicit timeout: Timeout, executionContext: ExecutionContext): Future[ApiValueV1] = {
val predicates = valueProps.literalData

Future(
AudioFileValueV1(
internalMimeType = predicates(OntologyConstants.KnoraBase.InternalMimeType).literals.head,
internalFilename = predicates(OntologyConstants.KnoraBase.InternalFilename).literals.head,
originalFilename = predicates.get(OntologyConstants.KnoraBase.OriginalFilename).map(_.literals.head),
projectShortcode = projectShortcode,
duration = predicates
.get(OntologyConstants.KnoraBase.Duration)
.map(valueLiterals => BigDecimal(valueLiterals.literals.head))
))
}

/**
* Converts a [[ValueProps]] into a [[LinkValueV1]].
*
Expand Down
Expand Up @@ -1638,6 +1638,62 @@ case class DocumentFileValueV1(internalMimeType: String,
}
}

case class AudioFileValueV1(internalMimeType: String,
internalFilename: String,
originalFilename: Option[String],
originalMimeType: Option[String] = None,
projectShortcode: String,
duration: Option[BigDecimal] = None)
extends FileValueV1 {

def valueTypeIri: IRI = OntologyConstants.KnoraBase.AudioFileValue

def toJsValue: JsValue = ApiValueV1JsonProtocol.audioFileValueV1Format.write(this)

override def toString: String = internalFilename

/**
* Checks if a new moving image file value would duplicate an existing moving image file value.
*
* @param other another [[ValueV1]].
* @return `true` if `other` is a duplicate of `this`.
*/
override def isDuplicateOfOtherValue(other: ApiValueV1): Boolean = {
other match {
case audioFileValueV1: AudioFileValueV1 => audioFileValueV1 == this
case otherValue =>
throw InconsistentRepositoryDataException(s"Cannot compare a $valueTypeIri to a ${otherValue.valueTypeIri}")
}
}

/**
* Checks if a new version of a moving image file value would be redundant given the current version of the value.
*
* @param currentVersion the current version of the value.
* @return `true` if this [[UpdateValueV1]] is redundant given `currentVersion`.
*/
override def isRedundant(currentVersion: ApiValueV1): Boolean = {
currentVersion match {
case audioFileValueV1: AudioFileValueV1 => audioFileValueV1 == this
case other =>
throw InconsistentRepositoryDataException(s"Cannot compare a $valueTypeIri to a ${other.valueTypeIri}")
}
}

override def toFileValueContentV2: FileValueContentV2 = {
AudioFileValueContentV2(
ontologySchema = InternalSchema,
fileValue = FileValueV2(
internalFilename = internalFilename,
internalMimeType = internalMimeType,
originalFilename = originalFilename,
originalMimeType = Some(internalMimeType)
),
duration = duration
)
}
}

case class MovingImageFileValueV1(internalMimeType: String,
internalFilename: String,
originalFilename: Option[String],
Expand Down Expand Up @@ -1822,6 +1878,7 @@ object ApiValueV1JsonProtocol extends SprayJsonSupport with DefaultJsonProtocol
implicit val stillImageFileValueV1Format: JsonFormat[StillImageFileValueV1] = jsonFormat7(StillImageFileValueV1)
implicit val documentFileValueV1Format: JsonFormat[DocumentFileValueV1] = jsonFormat8(DocumentFileValueV1)
implicit val textFileValueV1Format: JsonFormat[TextFileValueV1] = jsonFormat5(TextFileValueV1)
implicit val audioFileValueV1Format: JsonFormat[AudioFileValueV1] = jsonFormat6(AudioFileValueV1)
implicit val movingImageFileValueV1Format: JsonFormat[MovingImageFileValueV1] = jsonFormat5(MovingImageFileValueV1)
implicit val valueVersionV1Format: JsonFormat[ValueVersionV1] = jsonFormat3(ValueVersionV1)
implicit val linkValueV1Format: JsonFormat[LinkValueV1] = jsonFormat4(LinkValueV1)
Expand Down