Skip to content

Commit

Permalink
feat: Add support for audio files (DSP-1343) (#1818)
Browse files Browse the repository at this point in the history
  • Loading branch information
Benjamin Geer committed Feb 25, 2021
1 parent f8f7591 commit 7497023
Show file tree
Hide file tree
Showing 21 changed files with 640 additions and 26 deletions.
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

0 comments on commit 7497023

Please sign in to comment.