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

Support for new transcription management (subtitles) in Opencast 15 #373

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading