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(video): add support for video/mp4 to both v1 and v2 (DSP-1204) #1891

Merged
merged 2 commits into from Jul 20, 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
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