Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat(video): add support for video/mp4 to both v1 and v2 (DSP-1204) (#…
…1891)

* feat(videoFileValue): add support for video/mp4 to both v1 and v2

* feat: add sparql statements and tests
  • Loading branch information
SepidehAlassi committed Jul 20, 2021
1 parent 17601a7 commit 83fb4b8
Show file tree
Hide file tree
Showing 21 changed files with 662 additions and 24 deletions.
4 changes: 3 additions & 1 deletion docs/03-apis/api-v2/editing-values.md
Expand Up @@ -233,7 +233,9 @@ DSP-API v2 currently supports using Sipi to store the following types of files:

* Images: JPEG, JPEG2000, TIFF, or PNG which are stored internally as JPEG2000
* Documents: PDF
* Text files: XML or CSV
* Audio: MPEG, MP4, or Waveform audio file format (.wav, .x-wav, .vnd.wave)
* Text files: TXT, XML, or CSV
* Video files: MP4

Support for other types of files will be added in the future.

Expand Down
4 changes: 2 additions & 2 deletions knora-ontologies/knora-base.ttl
Expand Up @@ -33,7 +33,7 @@

:attachedToProject knora-admin:SystemProject ;

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



Expand Down Expand Up @@ -2159,7 +2159,7 @@
] ,
[ rdf:type owl:Restriction ;
owl:onProperty :fps ;
owl:cardinality "1"^^xsd:nonNegativeInteger
owl:maxCardinality "1"^^xsd:nonNegativeInteger
] ,
[ rdf:type owl:Restriction ;
owl:onProperty :duration ;
Expand Down
19 changes: 19 additions & 0 deletions sipi/scripts/file_info.lua
Expand Up @@ -24,6 +24,7 @@ TEXT = "text"
IMAGE = "image"
DOCUMENT = "document"
AUDIO = "audio"
VIDEO = "video"

-------------------------------------------------------------------------------
-- Mimetype constants
Expand Down Expand Up @@ -52,6 +53,7 @@ local APPLICATION_ZIP = "application/zip"
local APPLICATION_TAR = "application/x-tar"
local APPLICATION_ISO = "application/x-iso9660-image"
local APPLICATION_GZIP = "application/gzip"
local VIDEO_MP4 = "video/mp4"


local image_mime_types = {
Expand Down Expand Up @@ -89,6 +91,10 @@ local document_mime_types = {
APPLICATION_PPTX
}

local video_mime_types = {
VIDEO_MP4
}

local audio_extensions = {
"mp3",
"mp4",
Expand Down Expand Up @@ -117,6 +123,10 @@ local document_extensions = {
"pptx"
}

local video_extensions = {
"mp4"
}

function make_image_file_info(extension)
return {
media_type = IMAGE,
Expand All @@ -135,6 +145,13 @@ function make_audio_file_info(extension)
end
end

function make_video_file_info(extension)
return {
media_type = VIDEO,
extension = extension
}
end

function make_text_file_info(extension)
if not table.contains(text_extensions, extension) then
return nil
Expand Down Expand Up @@ -176,6 +193,8 @@ function get_file_info(filename, mimetype)
return make_image_file_info(extension)
elseif table.contains(audio_mime_types, mimetype) then
return make_audio_file_info(extension)
elseif table.contains(video_mime_types, mimetype) then
return make_video_file_info(extension)
elseif table.contains(text_mime_types, mimetype) then
return make_text_file_info(extension)
elseif table.contains(document_mime_types, mimetype) then
Expand Down
Binary file added test_data/test_route/files/testVideo.mp4
Binary file not shown.
Binary file added test_data/test_route/files/testVideo2.mp4
Binary file not shown.
2 changes: 1 addition & 1 deletion webapi/src/main/resources/application.conf
Expand Up @@ -413,7 +413,7 @@ app {
"application/x-iso9660-image",
]
text-mime-types = ["application/xml", "text/xml", "text/csv", "text/plain"]
video-mime-types = []
video-mime-types = ["video/mp4"]
audio-mime-types = ["audio/mpeg", "audio/mp4", "audio/wav", "audio/x-wav", "audio/vnd.wave"]
}

Expand Down
Expand Up @@ -60,7 +60,8 @@ case class GetFileMetadataResponse(originalFilename: Option[String],
width: Option[Int],
height: Option[Int],
pageCount: Option[Int],
duration: Option[BigDecimal])
duration: Option[BigDecimal],
fps: Option[BigDecimal])

/**
* Asks Sipi to move a file from temporary to permanent storage.
Expand Down
Expand Up @@ -1098,6 +1098,22 @@ object ConstructResponseUtilV2 {
.map(definedDuration => BigDecimal(definedDuration)),
comment = valueCommentOption
))
case OntologyConstants.KnoraBase.MovingImageFileValue =>
FastFuture.successful(
MovingImageFileValueContentV2(
ontologySchema = InternalSchema,
fileValue = fileValue,
dimX = valueObject.requireIntObject(OntologyConstants.KnoraBase.DimX.toSmartIri),
dimY = valueObject.requireIntObject(OntologyConstants.KnoraBase.DimY.toSmartIri),
fps = valueObject
.maybeStringObject(OntologyConstants.KnoraBase.Fps.toSmartIri)
.map(definedFps => BigDecimal(definedFps)),
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 @@ -90,6 +90,8 @@ class ValueUtilV1(private val settings: KnoraSettingsImpl) {
makeTextFileValue(valueProps, projectShortcode, responderManager, userProfile)
case OntologyConstants.KnoraBase.AudioFileValue =>
makeAudioFileValue(valueProps, projectShortcode, responderManager, userProfile)
case OntologyConstants.KnoraBase.MovingImageFileValue =>
makeVideoFileValue(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 @@ -140,6 +142,16 @@ class ValueUtilV1(private val settings: KnoraSettingsImpl) {
s"${settings.externalSipiIIIFGetUrl}/${audioFileValue.projectShortcode}/${audioFileValue.internalFilename}/file"
}

/**
* Creates a URL for accessing a video file via Sipi.
*
* @param videoFileValue the file value representing the video file.
* @return a Sipi URL.
*/
def makeSipiVideoFileGetUrlFromFilename(videoFileValue: MovingImageFileValueV1): String = {
s"${settings.externalSipiIIIFGetUrl}/${videoFileValue.projectShortcode}/${videoFileValue.internalFilename}/file"
}

// A Map of MIME types to Knora API v1 binary format name.
private val mimeType2V1Format = new ErrorHandlingMap(
Map(
Expand All @@ -162,13 +174,15 @@ class ValueUtilV1(private val settings: KnoraSettingsImpl) {
"application/xml" -> "XML",
"text/xml" -> "XML",
"text/csv" -> "CSV",
"text/plain" -> "TEXT",
"application/zip" -> "ZIP",
"application/x-compressed-zip" -> "ZIP",
"audio/mpeg" -> "AUDIO",
"audio/mp4" -> "AUDIO",
"audio/wav" -> "AUDIO",
"audio/x-wav" -> "AUDIO",
"audio/vnd.wave" -> "AUDIO"
"audio/vnd.wave" -> "AUDIO",
"video/mp4" -> "VIDEO"
), { key: String =>
s"Unknown MIME type: $key"
}
Expand Down Expand Up @@ -215,6 +229,13 @@ class ValueUtilV1(private val settings: KnoraSettingsImpl) {
path = makeSipiAudioFileGetUrlFromFilename(audioFileValue)
)

case videoFileValue: MovingImageFileValueV1 =>
LocationV1(
format_name = mimeType2V1Format(videoFileValue.internalMimeType),
origname = videoFileValue.originalFilename,
path = makeSipiVideoFileGetUrlFromFilename(videoFileValue)
)

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

case _: AudioFileValueV1 => basicObjectResponse

case _: MovingImageFileValueV1 => basicObjectResponse

case _: HierarchicalListValueV1 => basicObjectResponse

case _: ColorValueV1 => basicObjectResponse
Expand All @@ -409,7 +432,7 @@ class ValueUtilV1(private val settings: KnoraSettingsImpl) {
case _: UriValueV1 => basicObjectResponse

case other =>
throw new Exception(s"Resource creation response format not implemented for value type ${other.valueTypeIri}") // TODO: implement remaining types.
throw new Exception(s"Resource creation response format not implemented for value type ${other.valueTypeIri}")
}

ResourceCreateValueResponseV1(
Expand Down Expand Up @@ -878,7 +901,7 @@ 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]].
* @return a [[AudioFileValueV1]].
*/
private def makeAudioFileValue(
valueProps: ValueProps,
Expand All @@ -899,6 +922,36 @@ class ValueUtilV1(private val settings: KnoraSettingsImpl) {
))
}

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

Future(
MovingImageFileValueV1(
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,
dimX = predicates(OntologyConstants.KnoraBase.DimX).literals.head.toInt,
dimY = predicates(OntologyConstants.KnoraBase.DimY).literals.head.toInt,
fps = predicates.get(OntologyConstants.KnoraBase.Fps).map { giveLiteralValue =>
BigDecimal(giveLiteralValue.literals.head)
},
duration = predicates
.get(OntologyConstants.KnoraBase.Duration)
.map(valueLiterals => BigDecimal(valueLiterals.literals.head))
))
}

/**
* Converts a [[ValueProps]] into a [[LinkValueV1]].
*
Expand Down
Expand Up @@ -1698,7 +1698,11 @@ case class MovingImageFileValueV1(internalMimeType: String,
internalFilename: String,
originalFilename: Option[String],
originalMimeType: Option[String] = None,
projectShortcode: String)
projectShortcode: String,
dimX: Int,
dimY: Int,
fps: Option[BigDecimal] = None,
duration: Option[BigDecimal] = None)
extends FileValueV1 {

def valueTypeIri: IRI = OntologyConstants.KnoraBase.MovingImageFileValue
Expand Down Expand Up @@ -1736,7 +1740,19 @@ case class MovingImageFileValueV1(internalMimeType: String,
}

override def toFileValueContentV2: FileValueContentV2 = {
throw NotImplementedException("Moving image file values are not supported in Knora API v1")
MovingImageFileValueContentV2(
ontologySchema = InternalSchema,
fileValue = FileValueV2(
internalFilename = internalFilename,
internalMimeType = internalMimeType,
originalFilename = originalFilename,
originalMimeType = Some(internalMimeType)
),
dimX = dimX,
dimY = dimY,
fps = fps,
duration = duration
)
}
}

Expand Down Expand Up @@ -1879,7 +1895,7 @@ object ApiValueV1JsonProtocol extends SprayJsonSupport with DefaultJsonProtocol
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 movingImageFileValueV1Format: JsonFormat[MovingImageFileValueV1] = jsonFormat9(MovingImageFileValueV1)
implicit val valueVersionV1Format: JsonFormat[ValueVersionV1] = jsonFormat3(ValueVersionV1)
implicit val linkValueV1Format: JsonFormat[LinkValueV1] = jsonFormat4(LinkValueV1)
implicit val valueVersionHistoryGetResponseV1Format: RootJsonFormat[ValueVersionHistoryGetResponseV1] = jsonFormat1(
Expand Down

0 comments on commit 83fb4b8

Please sign in to comment.