Skip to content

Commit

Permalink
Support for new transcription management (subtitles) in Opencast 15 (#…
Browse files Browse the repository at this point in the history
…373)

* Update transcriptions for OC 15, fixes #368

* fixed checkers

* overwrite info

* reorder lang file

* fix code checker warning
  • Loading branch information
ferishili authored Dec 3, 2024
1 parent 0adefe5 commit 4b631b0
Show file tree
Hide file tree
Showing 7 changed files with 366 additions and 168 deletions.
109 changes: 93 additions & 16 deletions classes/local/attachment_helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,16 @@ class attachment_helper {
const ATTACHMENT_TYPE_TRANSCRIPTION = 'transcription';

/** @var string transcription flavor */
const TRANSCRIPTION_FLAVOR_TYPE = 'captions/vtt';
const TRANSCRIPTION_FLAVOR_TYPE = 'captions';

/** @var string transcription manual subflavor */
const TRANSCRIPTION_MANUAL_SUBFLAVOR_TYPE = 'vtt';

/** @var array transcription subflavor types */
const TRANSCRIPTION_SUBFLAVOR_TYPES = ['vtt', 'delivery', 'prepared'];

/** @var string transcription mediatype */
const TRANSCRIPTION_MEDIATYPE = 'text/vtt';

/**
* Saves the attachment upload job.
Expand Down Expand Up @@ -179,6 +188,7 @@ protected function upload_job_transcriptions($attachmentjob, $uploadjob, $video)
$mediapackagestr = $apibridge->get_event_media_package($video->identifier);

$transcriptionstoupload = json_decode($attachmentjob->files);
$mainmanualflavor = self::TRANSCRIPTION_FLAVOR_TYPE . '/' . self::TRANSCRIPTION_MANUAL_SUBFLAVOR_TYPE;
foreach ($transcriptionstoupload as $transcription) {
// Get file first.
$fs = get_file_storage();
Expand All @@ -189,7 +199,7 @@ protected function upload_job_transcriptions($attachmentjob, $uploadjob, $video)
continue;
}
// Prepare flavor based on the flavor code.
$flavor = self::TRANSCRIPTION_FLAVOR_TYPE . "+{$transcription->flavor}";
$flavor = $mainmanualflavor . "+{$transcription->flavor}";

// Compile and add attachment/track.
$mediapackagestr = self::perform_add_attachment($ocinstanceid, $video->identifier,
Expand Down Expand Up @@ -273,16 +283,25 @@ protected function cleanup_attachment_job($job) {
*/
private static function perform_add_attachment($ocinstanceid, $identifier, $mediapackagestr, $file, $flavor) {
// Remove existing attachments or media with the same flavor.
$mediapackagestr = self::remove_existing_flavor_from_mediapackage($ocinstanceid, $mediapackagestr, 'type', $flavor);
list($mediapackagestr, $removedmediaids, $removedattachmentids) = self::remove_existing_flavor_from_mediapackage(
$ocinstanceid, $mediapackagestr, 'type', $flavor);
$apibridge = apibridge::get_instance($ocinstanceid);
$filestream = $apibridge->get_upload_filestream($file, 'file');
// We do a version check here to perform addTrack instead of addAttachment.
$opencastversion = $apibridge->get_opencast_version();
// We do a version check here to perform the add track feature specifically for transcriptions added in Opencast version 13.
if (version_compare($opencastversion, '13.0.0', '>=') && strpos($flavor, self::TRANSCRIPTION_FLAVOR_TYPE) !== false) {
$apibridge->event_add_track($identifier, $flavor, $filestream);
// We need to get the mediapackage again.
$mediapackagestr = $apibridge->get_event_media_package($identifier);
$mainmanualflavor = self::TRANSCRIPTION_FLAVOR_TYPE . '/' . self::TRANSCRIPTION_MANUAL_SUBFLAVOR_TYPE;
if (version_compare($opencastversion, '13.0.0', '>=') && strpos($flavor, $mainmanualflavor) !== false) {
$trackisadded = $apibridge->event_add_track($identifier, $flavor, $filestream);
if ($trackisadded) {
$mediapackagestr = $apibridge->get_event_media_package($identifier);
// We need to perform extracting the existing media items again, because overwrite existing in add track endpoint
// does not work as expected!
foreach ($removedmediaids as $mediaid) {
list($mediapackagestr, $unusedmediaids, $unusedattachmentids) = self::remove_existing_flavor_from_mediapackage(
$ocinstanceid, $mediapackagestr, 'id', $mediaid);
}
}
} else {
$mediapackagestr = $apibridge->ingest_add_attachment($mediapackagestr, $flavor, $filestream);
}
Expand All @@ -297,23 +316,24 @@ private static function perform_add_attachment($ocinstanceid, $identifier, $medi
* @param string $attributetype the attribute type to check againts.
* @param string $value the targeted attribute's value.
*
* @return string mediapackage
* @return array [$mediapackage, $mediaids, $attachmentids] the mediapackage string as well as removed media and attachment ids.
*/
private static function remove_existing_flavor_from_mediapackage($ocinstanceid, $mediapackagestr, $attributetype, $value) {
$mediapackage = simplexml_load_string($mediapackagestr);
// We loop through the attackments, to get rid of any duplicates.
self::remove_attachment_from_xml($mediapackage, $attributetype, $value);
$attachmentids = self::remove_attachment_from_xml($mediapackage, $attributetype, $value);

// Get the opencast version to make sure everything gets removed.
$apibridge = apibridge::get_instance($ocinstanceid);
$opencastversion = $apibridge->get_opencast_version();
// As of opencast 13 we need to check the media for transcriptions as well.
$mediaids = [];
if (version_compare($opencastversion, '13.0.0', '>=')) {
// We loop through the media tracks, to get rid of any duplicates.
self::remove_media_from_xml($mediapackage, $attributetype, $value);
$mediaids = self::remove_media_from_xml($mediapackage, $attributetype, $value);
}

return $mediapackage->asXML();
return [$mediapackage->asXML(), $mediaids, $attachmentids];
}

/**
Expand All @@ -322,20 +342,25 @@ private static function remove_existing_flavor_from_mediapackage($ocinstanceid,
* @param SimpleXMLElement $mediapackage the mediapackage XML object.
* @param string $attributetype the type of attribute to check against.
* @param string $value the value of attribute to match with.
*
* @return array to remove ids.
*/
private static function remove_attachment_from_xml(&$mediapackage, $attributetype, $value) {
$i = 0;
$toremove = [];
$ids = [];
foreach ($mediapackage->attachments->attachment as $item) {
if ($item->attributes()[$attributetype] == $value) {
$toremove[] = $i;
$ids[] = (string) $item->attributes()['id'];
}
$i++;
}
$toremove = array_reverse($toremove);
foreach ($toremove as $i) {
unset($mediapackage->attachments->attachment[$i]);
}
return $ids;
}

/**
Expand All @@ -344,20 +369,25 @@ private static function remove_attachment_from_xml(&$mediapackage, $attributetyp
* @param SimpleXMLElement $mediapackage the mediapackage XML object.
* @param string $attributetype the type of attribute to check against.
* @param string $value the value of attribute to match with.
*
* @return array to remove ids.
*/
private static function remove_media_from_xml(&$mediapackage, $attributetype, $value) {
$i = 0;
$toremove = [];
$ids = [];
foreach ($mediapackage->media->track as $item) {
if ($item->attributes()[$attributetype] == $value) {
$toremove[] = $i;
$ids[] = (string) $item->attributes()['id'];
}
$i++;
}
$toremove = array_reverse($toremove);
foreach ($toremove as $i) {
unset($mediapackage->media->track[$i]);
}
return $ids;
}

/**
Expand Down Expand Up @@ -391,16 +421,21 @@ private static function perform_finalize_upload_attachment($ocinstanceid, $media
*
* @param string $ocinstanceid id of opencast instance
* @param string $eventidentifier id of the video
* @param string $transcriptionidentifier id of transcription
* @param stdClass $transcriptionobj transcription publication object.
* @param string $publicationtype the type of publication to look for.
*
* @return boolean the result of deletion.
*/
public static function delete_transcription($ocinstanceid, $eventidentifier, $transcriptionidentifier) {
public static function delete_transcription($ocinstanceid, $eventidentifier, $transcriptionobj, $publicationtype) {
$success = false;
$apibridge = apibridge::get_instance($ocinstanceid);
$mediapackagestr = $apibridge->get_event_media_package($eventidentifier);
$mediapackagestr = self::remove_existing_flavor_from_mediapackage($ocinstanceid,
$mediapackagestr, 'id', $transcriptionidentifier);

$transcriptionidentifier = self::extract_transcription_id_from_mediapackage($mediapackagestr, $transcriptionobj,
$publicationtype);

list($mediapackagestr, $removedmediaids, $removedattachmentids) = self::remove_existing_flavor_from_mediapackage(
$ocinstanceid, $mediapackagestr, 'id', $transcriptionidentifier);
try {
$ingested = $apibridge->ingest($mediapackagestr,
get_config('block_opencast', 'deletetranscriptionworkflow_' . $ocinstanceid));
Expand All @@ -426,7 +461,8 @@ public static function delete_transcription($ocinstanceid, $eventidentifier, $tr
public static function upload_single_transcription($file, $flavorservice, $ocinstanceid, $eventidentifier) {
$apibridge = apibridge::get_instance($ocinstanceid);
$mediapackagestr = $apibridge->get_event_media_package($eventidentifier);
$flavor = self::TRANSCRIPTION_FLAVOR_TYPE . "+{$flavorservice}";
$mainmanualflavor = self::TRANSCRIPTION_FLAVOR_TYPE . '/' . self::TRANSCRIPTION_MANUAL_SUBFLAVOR_TYPE;
$flavor = $mainmanualflavor . "+{$flavorservice}";
// Compile and add attachment.
$mediapackagestr = self::perform_add_attachment($ocinstanceid, $eventidentifier, $mediapackagestr, $file, $flavor);
// Finalizing the attachment upload.
Expand Down Expand Up @@ -454,4 +490,45 @@ public static function remove_single_transcription_file($fileitemid) {
}
$files->close();
}

/**
* Gets the mediapackage id based on comparing the actual publication object.
*
* @param string $mediapackagestr the event mediapackage.
* @param object $transcriptionobj the transcription publication object.
* @param string $pubtype the publication type, either media or attachements.
*
* @return string|null the medispackage id or null if it could not be found.
*/
public static function extract_transcription_id_from_mediapackage($mediapackagestr, $transcriptionobj, $pubtype = 'media') {
$mediapackagexml = simplexml_load_string($mediapackagestr);
$pubsubtype = $pubtype == 'media' ? 'track' : 'attachment';
if (property_exists($mediapackagexml, $pubtype)) {
foreach ($mediapackagexml->$pubtype->$pubsubtype as $item) {
$itemobj = json_decode(json_encode((array) $item));
if ($itemobj->mimetype == $transcriptionobj->mediatype &&
$itemobj->{'@attributes'}->type == $transcriptionobj->flavor) {
// As of Opencast 15, subtitles are all about tags, therefore we need to go through tags one by one.
if (property_exists($itemobj, 'tags')) {
$itemtags = $itemobj->tags->tag;
if (!is_array($itemtags)) {
$itemtags = [$itemtags];
}
if (count($transcriptionobj->tags) === count($itemtags)) {
$alltagsmatch = true;
foreach ($itemtags as $tag) {
if (!in_array($tag, $transcriptionobj->tags)) {
$alltagsmatch = false;
}
}
if ($alltagsmatch) {
return $itemobj->{'@attributes'}->id;
}
}
}
}
}
}
return null;
}
}
32 changes: 30 additions & 2 deletions deletetranscription.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
require_capability('block/opencast:addvideo', $coursecontext);

$apibridge = apibridge::get_instance($ocinstanceid);
$video = $apibridge->get_opencast_video($videoidentifier);
$video = $apibridge->get_opencast_video($videoidentifier, true);
if ($video->error || $video->video->processing_state != 'SUCCEEDED' ||
empty(get_config('block_opencast', 'transcriptionworkflow_' . $ocinstanceid)) ||
empty(get_config('block_opencast', 'deletetranscriptionworkflow_' . $ocinstanceid))) {
Expand All @@ -69,7 +69,35 @@
}

if (($action == 'delete') && confirm_sesskey()) {
$deleted = attachment_helper::delete_transcription($ocinstanceid, $videoidentifier, $identifier);
// In order to remove the transcription from the media package we need to look it up in publication first,
// then we find it in the media package based on flavor and tags, because we maintain the compatibility of both ways.

// 1. Find it in publications.
$transcriptiontodelete = null;
$publicationtype = 'media'; // For opencast 13 and above, this would be always media, unless intentianly configured otherwise.
foreach ($video->video->publications as $publication) {
// Search the attachments.
foreach ($publication->attachments as $attachment) {
if ($attachment->id == $identifier) {
$transcriptiontodelete = $attachment;
$publicationtype = 'attachments';
break 2;
}
}
// Search the media.
foreach ($publication->media as $media) {
if ($media->id == $identifier) {
$transcriptiontodelete = $media;
break 2;
}
}
}

$deleted = false;
if (!empty($transcriptiontodelete)) {
$deleted = attachment_helper::delete_transcription($ocinstanceid, $videoidentifier, $transcriptiontodelete,
$publicationtype);
}

$message = get_string('transcriptiondeletionsucceeded', 'block_opencast');
$status = notification::NOTIFY_SUCCESS;
Expand Down
Loading

0 comments on commit 4b631b0

Please sign in to comment.