From f8c5adbf98d1e209708a273f253e8ef9b456b4f2 Mon Sep 17 00:00:00 2001 From: aynsix Date: Thu, 2 Nov 2023 21:18:35 +0300 Subject: [PATCH 1/2] happyscribe autosubtitle --- .../Controller/Prod/ToolsController.php | 13 +- .../Event/Record/RecordAutoSubtitleEvent.php | 12 +- .../Subscriber/SubtitleSubscriber.php | 33 +- .../WorkerManager/Worker/SubtitleWorker.php | 500 +++++++++++------- .../prod/actions/Tools/videoEditor.html.twig | 20 +- 5 files changed, 349 insertions(+), 229 deletions(-) diff --git a/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php b/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php index 9c522a6ee5..1aeeb2554a 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php @@ -552,25 +552,14 @@ public function autoSubtitleAction(Request $request) (int)$request->request->get("record_id") ); - $permalinkUrl = ''; - $conf = $this->getConf(); - - // if subdef_source not set, by default use the preview permalink - $subdefSource = $conf->get(['externalservice', 'ginger', 'AutoSubtitling', 'subdef_source']) ?: 'preview'; - - if ($this->isPhysicallyPresent($record, $subdefSource) && ($previewLink = $record->get_subdef($subdefSource)->get_permalink()) != null) { - $permalinkUrl = $previewLink->get_url()->__toString(); - } $this->dispatch( PhraseaEvents::RECORD_AUTO_SUBTITLE, new RecordAutoSubtitleEvent( $record, - $permalinkUrl, $request->request->get("subtitle_language_source"), $request->request->get("meta_struct_id_source"), - $request->request->get("subtitle_language_destination"), - $request->request->get("meta_struct_id_destination") + json_decode($request->request->get("subtitle_destination"), true) ) ); diff --git a/lib/Alchemy/Phrasea/Core/Event/Record/RecordAutoSubtitleEvent.php b/lib/Alchemy/Phrasea/Core/Event/Record/RecordAutoSubtitleEvent.php index 307a54e474..706ad8a3fb 100644 --- a/lib/Alchemy/Phrasea/Core/Event/Record/RecordAutoSubtitleEvent.php +++ b/lib/Alchemy/Phrasea/Core/Event/Record/RecordAutoSubtitleEvent.php @@ -10,15 +10,12 @@ class RecordAutoSubtitleEvent extends RecordEvent private $metaStructureIdSource; private $languageDestination; private $metaStructureIdDestination; - private $permalinkUrl; public function __construct( RecordInterface $record, - $permalinkUrl, $languageSource, $metaStructureIdSource, - $languageDestination, - $metaStructureIdDestination + $languageDestination ) { parent::__construct($record); @@ -26,8 +23,6 @@ public function __construct( $this->languageSource = $languageSource; $this->metaStructureIdSource = $metaStructureIdSource; $this->languageDestination = $languageDestination; - $this->metaStructureIdDestination = $metaStructureIdDestination; - $this->permalinkUrl = $permalinkUrl; } public function getLanguageSource() @@ -49,9 +44,4 @@ public function getMetaStructureIdDestination() { return $this->metaStructureIdDestination; } - - public function getPermalinkUrl() - { - return $this->permalinkUrl; - } } diff --git a/lib/Alchemy/Phrasea/WorkerManager/Subscriber/SubtitleSubscriber.php b/lib/Alchemy/Phrasea/WorkerManager/Subscriber/SubtitleSubscriber.php index 83318bcbb4..0465e52e24 100644 --- a/lib/Alchemy/Phrasea/WorkerManager/Subscriber/SubtitleSubscriber.php +++ b/lib/Alchemy/Phrasea/WorkerManager/Subscriber/SubtitleSubscriber.php @@ -27,23 +27,26 @@ public function __construct(callable $repoWorkerJobLocator, MessagePublisher $me public function onRecordAutoSubtitle(RecordAutoSubtitleEvent $event) { - $data = [ - "databoxId" => $event->getRecord()->getDataboxId(), - "recordId" => $event->getRecord()->getRecordId(), - "permalinkUrl" => $event->getPermalinkUrl(), - "languageSource" => $event->getLanguageSource(), - "metaStructureIdSource" => $event->getMetaStructureIdSource(), - "languageDestination" => $event->getLanguageDestination(), - "metaStructureIdDestination" => $event->getMetaStructureIdDestination(), - "type" => MessagePublisher::SUBTITLE_TYPE // used to specify the final Q to publish message - ]; + if (!empty($event->getLanguageDestination())) { + $data = [ + "databoxId" => $event->getRecord()->getDataboxId(), + "recordId" => $event->getRecord()->getRecordId(), + "languageSource" => $event->getLanguageSource(), + "metaStructureIdSource" => $event->getMetaStructureIdSource(), + "languageDestination" => $event->getLanguageDestination(), + "type" => MessagePublisher::SUBTITLE_TYPE // used to specify the final Q to publish message + ]; - $payload = [ - 'message_type' => MessagePublisher::MAIN_QUEUE_TYPE, - 'payload' => $data - ]; + $payload = [ + 'message_type' => MessagePublisher::MAIN_QUEUE_TYPE, + 'payload' => $data + ]; + + $this->messagePublisher->publishMessage($payload, MessagePublisher::MAIN_QUEUE_TYPE); + } else { + $this->messagePublisher->pushLog("There is no language destination selected when trying do do autosubtitle!"); + } - $this->messagePublisher->publishMessage($payload, MessagePublisher::MAIN_QUEUE_TYPE); } public static function getSubscribedEvents() diff --git a/lib/Alchemy/Phrasea/WorkerManager/Worker/SubtitleWorker.php b/lib/Alchemy/Phrasea/WorkerManager/Worker/SubtitleWorker.php index 753b9c4c16..34e00b82fb 100644 --- a/lib/Alchemy/Phrasea/WorkerManager/Worker/SubtitleWorker.php +++ b/lib/Alchemy/Phrasea/WorkerManager/Worker/SubtitleWorker.php @@ -29,6 +29,14 @@ class SubtitleWorker implements WorkerInterface private $repoWorker; private $dispatcher; + /** + * @var Client + */ + private $happyscribeClient; + private $happyscribeToken; + private $extension; + private $workerRunningJob; + private $transcriptionsId; public function __construct(WorkerRunningJobRepository $repoWorker, PropertyAccess $conf, callable $appboxLocator, LoggerInterface $logger, EventDispatcherInterface $dispatcher) { @@ -41,16 +49,17 @@ public function __construct(WorkerRunningJobRepository $repoWorker, PropertyAcce public function process(array $payload) { - $gingaBaseurl = $this->conf->get(['externalservice', 'ginger', 'AutoSubtitling', 'service_base_url']); - $gingaToken = $this->conf->get(['externalservice', 'ginger', 'AutoSubtitling', 'token']); - $gingaTranscriptFormat = $this->conf->get(['externalservice', 'ginger', 'AutoSubtitling', 'transcript_format']); + $this->happyscribeToken = $this->conf->get(['externalservice', 'happyscribe', 'token']); + $organizationId = $this->conf->get(['externalservice', 'happyscribe', 'organization_id']); + $happyscribeTranscriptFormat = $this->conf->get(['externalservice', 'happyscribe', 'transcript_format'], 'vtt'); - if (!$gingaBaseurl || !$gingaToken || !$gingaTranscriptFormat) { + if (!$organizationId || !$this->happyscribeToken ) { $this->logger->error("External service Ginga not set correctly in configuration.yml"); return 0; } - $workerRunningJob = null; + + $this->workerRunningJob = null; $em = $this->repoWorker->getEntityManager(); $em->beginTransaction(); @@ -62,8 +71,8 @@ public function process(array $payload) ]; $date = new \DateTime(); - $workerRunningJob = new WorkerRunningJob(); - $workerRunningJob + $this->workerRunningJob = new WorkerRunningJob(); + $this->workerRunningJob ->setDataboxId($payload['databoxId']) ->setRecordId($payload['recordId']) ->setWork(MessagePublisher::SUBTITLE_TYPE) @@ -73,7 +82,7 @@ public function process(array $payload) ->setPayload($message) ; - $em->persist($workerRunningJob); + $em->persist($this->workerRunningJob); $em->flush(); $em->commit(); @@ -81,223 +90,165 @@ public function process(array $payload) $em->rollback(); } - switch ($gingaTranscriptFormat) { - case 'text/srt,': - $extension = 'srt'; - break; - case 'text/plain': - $extension = 'txt'; - break; - case 'application/json': - $extension = 'json'; - break; - case 'text/vtt': - default: - $extension = 'vtt'; - break; + if (in_array(strtolower($happyscribeTranscriptFormat), ['srt', 'txt', 'json', 'vtt'])) { + $this->extension = strtolower($happyscribeTranscriptFormat); + } else { + $this->extension = 'vtt'; } $languageSource = $this->getLanguageFormat($payload['languageSource']); - $languageDestination = $this->getLanguageFormat($payload['languageDestination']); $record = $this->getApplicationBox()->get_databox($payload['databoxId'])->get_record($payload['recordId']); - $languageSourceFieldName = $record->getDatabox()->get_meta_structure()->get_element($payload['metaStructureIdSource'])->get_name(); - - $subtitleSourceTemporaryFile = $this->getTemporaryFilesystem()->createTemporaryFile("subtitle", null, $extension); - $gingerClient = new Client(); - - // if the languageSourceFieldName do not yet exist, first generate subtitle for it - if ($payload['permalinkUrl'] != '' && !$record->get_caption()->has_field($languageSourceFieldName)) { - try { - $response = $gingerClient->post($gingaBaseurl.'/media/', [ - 'headers' => [ - 'Authorization' => 'token '.$gingaToken - ], - 'json' => [ - 'url' => $payload['permalinkUrl'], - 'language' => $languageSource - ] - ]); - } catch(\Exception $e) { - $this->logger->error($e->getMessage()); - $this->jobFinished($workerRunningJob); - - return 0; - } - - if ($response->getStatusCode() !== 201) { - $this->logger->error("response status /media/ : ". $response->getStatusCode()); - $this->jobFinished($workerRunningJob); - - return 0; - } - - $responseMediaBody = $response->getBody()->getContents(); - $responseMediaBody = json_decode($responseMediaBody,true); - - $checkStatus = true; - do { - // first wait 5 second before check subtitling status - sleep(5); - $this->logger->info("bigin to check status"); - try { - $response = $gingerClient->get($gingaBaseurl.'/task/'.$responseMediaBody['task_id'].'/', [ - 'headers' => [ - 'Authorization' => 'token '.$gingaToken - ] - ]); - } catch (\Exception $e) { - $checkStatus = false; - break; - } + // if subdef_source not set, by default use the preview permalink + $subdefSource = $this->conf->get(['externalservice', 'happyscribe', 'subdef_source']) ?: 'preview'; - if ($response->getStatusCode() !== 200) { - $checkStatus = false; + $tmpUrl = ''; + if ($this->isPhysicallyPresent($record, $subdefSource) && ($previewLink = $record->get_subdef($subdefSource)->get_permalink()) != null) { + $tmpUrl = $previewLink->get_url()->__toString(); + } - break; - } + $this->happyscribeClient = new Client(); - $responseTaskBody = $response->getBody()->getContents(); - $responseTaskBody = json_decode($responseTaskBody,true); + if (!$this->isRemoteFileExist($tmpUrl)) { + $fileName = $record->get_subdef($subdefSource)->get_file(); - } while($responseTaskBody['status'] != 'SUCCESS'); + // first get a signed URL + $responseUpload = $this->happyscribeClient->get('https://www.happyscribe.com/api/v1/uploads/new?filename=' . $fileName, [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $this->happyscribeToken + ] + ]); - if (!$checkStatus) { - $this->logger->error("can't check status"); - $this->jobFinished($workerRunningJob); + if ($responseUpload->getStatusCode() !== 200) { + $this->logger->error("error when uploading file to transcript,response status : ". $responseUpload->getStatusCode()); + $this->jobFinished(); return 0; } - try { - $response = $gingerClient->get($gingaBaseurl.'/media/'.$responseMediaBody['media']['uuid'].'/', [ - 'headers' => [ - 'Authorization' => 'token '.$gingaToken, - 'ACCEPT' => $gingaTranscriptFormat - ], - 'query' => [ - 'language' => $languageSource - ] - ]); - } catch (\Exception $e) { - $this->logger->error($e->getMessage()); - $this->jobFinished($workerRunningJob); + $responseUploadBody = $responseUpload->getBody()->getContents(); + $responseUploadBody = json_decode($responseUploadBody,true); - return 0; - } + $tmpUrl = $responseUploadBody['signedUrl']; + $res = $this->happyscribeClient->put($tmpUrl, [ + 'body' => fopen($record->get_subdef($subdefSource)->getRealPath(), 'r') + ]); - if ($response->getStatusCode() !== 200) { - $this->logger->error("response status /media/uuid : ". $response->getStatusCode()); - $this->jobFinished($workerRunningJob); + if ($res->getStatusCode() !== 200) { + $this->logger->error("error when uploading file to signed url,response status : ". $res->getStatusCode()); + $this->jobFinished(); return 0; } + } - $transcriptContent = $response->getBody()->getContents(); + // create a transcription + + $responseTranscription = $this->happyscribeClient->post('https://www.happyscribe.com/api/v1/transcriptions', [ + 'headers' => [ + 'Authorization' => 'Bearer '. $this->happyscribeToken + ], + 'json' => [ + 'transcription' => [ + 'name' => $record->get_title(), + 'is_subtitle' => true, + 'language' => $languageSource, + 'organization_id' => $organizationId, + 'tmp_url' => $tmpUrl + ] + ] + ]); + + if ($responseTranscription->getStatusCode() !== 200) { + $this->logger->error("error when creating transcript,response status : ". $responseTranscription->getStatusCode()); + $this->jobFinished(); - $transcriptContent = preg_replace('/WEBVTT/', 'WEBVTT - with cue identifier', $transcriptContent, 1); + return 0; + } - // save subtitle on temporary file to use to translate if needed - file_put_contents($subtitleSourceTemporaryFile, $transcriptContent); + $responseTranscriptionBody = $responseTranscription->getBody()->getContents(); + $responseTranscriptionBody = json_decode($responseTranscriptionBody,true); + $this->transcriptionsId[] = $transcriptionId = $responseTranscriptionBody['id']; - $metadatas[0] = [ - 'meta_struct_id' => (int)$payload['metaStructureIdSource'], - 'meta_id' => '', - 'value' => $transcriptContent - ]; + // check transcription status + $failureTranscriptMessage = ''; - try { - $record->set_metadatas($metadatas); + do { + // first wait 5 second before check transcript status + sleep(5); + $resCheckTranscript = $this->happyscribeClient->get('https://www.happyscribe.com/api/v1/transcriptions/' . $transcriptionId, [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $this->happyscribeToken + ] + ]); - // order to write meta in file - $this->dispatcher->dispatch(WorkerEvents::RECORDS_WRITE_META, - new RecordsWriteMetaEvent([$record->getRecordId()], $record->getDataboxId())); - } catch (\Exception $e) { - $this->logger->error($e->getMessage()); - $this->jobFinished($workerRunningJob); + if ($resCheckTranscript->getStatusCode() !== 200) { + $this->logger->error("error when checking transcript,response status : ". $responseTranscription->getStatusCode()); + $this->jobFinished(); return 0; } - $this->logger->info("Generate subtitle on language source SUCCESS"); - } elseif ($record->get_caption()->has_field($languageSourceFieldName)) { - // get the source subtitle and save it to a temporary file - $fieldValues = $record->get_caption()->get_field($languageSourceFieldName)->get_values(); - $fieldValue = array_pop($fieldValues); + $resCheckTranscriptBody = $resCheckTranscript->getBody()->getContents(); + $resCheckTranscriptBody = json_decode($resCheckTranscriptBody,true); + $transcriptStatus = $resCheckTranscriptBody['state']; + if (isset($resCheckTranscriptBody['failureMessage'])) { + $failureTranscriptMessage = $resCheckTranscriptBody['failureMessage']; + } + + } while(!in_array($transcriptStatus, ['automatic_done', 'locked', 'failed'])); + + if ($transcriptStatus != "automatic_done") { + $this->logger->error("error when checking transcript, : " . $failureTranscriptMessage); + $this->jobFinished(); - file_put_contents($subtitleSourceTemporaryFile, $fieldValue->getValue()); + return 0; } - if ($payload['metaStructureIdSource'] !== $payload['metaStructureIdDestination']) { - try { - $response = $gingerClient->post($gingaBaseurl.'/translate/', [ - 'headers' => [ - 'Authorization' => 'token '.$gingaToken, - 'ACCEPT' => $gingaTranscriptFormat - ], - 'multipart' => [ - [ - 'name' => 'transcript', - 'contents' => fopen($subtitleSourceTemporaryFile, 'r') - ], - [ - 'name' => 'transcript_format', - 'contents' => $gingaTranscriptFormat, - - ], - [ - 'name' => 'language_in', - 'contents' => $languageSource, - - ], - [ - 'name' => 'language_out', - 'contents' => $languageDestination, - - ] - ] - ]); - } catch(\Exception $e) { - $this->logger->error($e->getMessage()); - $this->jobFinished($workerRunningJob); - return 0; - } + $metadatas = []; - if ($response->getStatusCode() !== 200) { - $this->logger->error("response status /translate/ : ". $response->getStatusCode()); - $this->jobFinished($workerRunningJob); + foreach ($payload['languageDestination'] as $language => $metaStructureIdDestination) { + $languageDestination = strtolower($language); - return 0; + if (strtolower($payload['languageSource']) == $languageDestination) { + $metadatas[] = [ + 'meta_struct_id' => (int)$metaStructureIdDestination, + 'meta_id' => '', + 'value' => $this->exportTranscription($transcriptionId) + ]; + } else { + $metadatas[] = [ + 'meta_struct_id' => (int)$metaStructureIdDestination, + 'meta_id' => '', + 'value' => $this->translate($transcriptionId, $languageDestination) + ]; } + } - $transcriptContent = $response->getBody()->getContents(); - $transcriptContent = preg_replace('/WEBVTT/', 'WEBVTT - with cue identifier', $transcriptContent, 1); + try { + $record->set_metadatas($metadatas); - $metadatas[0] = [ - 'meta_struct_id' => (int)$payload['metaStructureIdDestination'], - 'meta_id' => '', - 'value' => $transcriptContent - ]; + // order to write meta in file + $this->dispatcher->dispatch(WorkerEvents::RECORDS_WRITE_META, + new RecordsWriteMetaEvent([$record->getRecordId()], $record->getDataboxId())); + } catch (\Exception $e) { + $this->logger->error($e->getMessage()); + $this->jobFinished(); - try { - $record->set_metadatas($metadatas); + return 0; + } - // order to write meta in file - $this->dispatcher->dispatch(WorkerEvents::RECORDS_WRITE_META, - new RecordsWriteMetaEvent([$record->getRecordId()], $record->getDataboxId())); - } catch (\Exception $e) { - $this->logger->error($e->getMessage()); - $this->jobFinished($workerRunningJob); + $this->logger->info("Translate subtitle on language destination SUCCESS"); - return 0; - } + // delete transcription - $this->logger->info("Translate subtitle on language destination SUCCESS"); + foreach ($this->transcriptionsId as $transcriptionId) { + $this->deleteTranscription($transcriptionId); } - $this->jobFinished($workerRunningJob); + $this->jobFinished(); return 0; } @@ -312,16 +263,16 @@ private function getApplicationBox() return $callable(); } - private function jobFinished(WorkerRunningJob $workerRunningJob) + private function jobFinished() { - if ($workerRunningJob != null) { - $workerRunningJob->setStatus(WorkerRunningJob::FINISHED) + if ($this->workerRunningJob != null) { + $this->workerRunningJob->setStatus(WorkerRunningJob::FINISHED) ->setFinished(new \DateTime('now')); $em = $this->repoWorker->getEntityManager(); $this->repoWorker->reconnect(); - $em->persist($workerRunningJob); + $em->persist($this->workerRunningJob); $em->flush(); } } @@ -338,4 +289,185 @@ private function getLanguageFormat($language) return 'fr-FR'; } } + + private function isRemoteFileExist($fileUrl) + { + $client = new Client(); + + try { + $client->head($fileUrl); + return true; + } catch (\Exception $e) { + return false; + } + } + + private function isPhysicallyPresent(\record_adapter $record, $subdefName) + { + try { + return $record->get_subdef($subdefName)->is_physically_present(); + } + catch (\Exception $e) { + unset($e); + } + + return false; + } + + private function exportTranscription($transcriptionId) + { + $subtitleTranscriptTemporaryFile = $this->getTemporaryFilesystem()->createTemporaryFile("subtitle", null, $this->extension); + + $resExport = $this->happyscribeClient->post('https://www.happyscribe.com/api/v1/exports', [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $this->happyscribeToken + ], + 'json' => [ + 'export' => [ + 'format' => $this->extension, + 'transcription_ids' => [ + $transcriptionId + ] + ] + ] + ]); + + if ($resExport->getStatusCode() !== 200) { + $this->logger->error("error when creating transcript export, response status : ". $resExport->getStatusCode()); + $this->jobFinished(); + + return 0; + } + + $resExportBody = $resExport->getBody()->getContents(); + $resExportBody = json_decode($resExportBody,true); + + $exportId = $resExportBody['id']; + $failureExportMessage = ''; + + // retrieve transcript export when ready + do { + sleep(3); + $resCheckExport = $this->happyscribeClient->get('https://www.happyscribe.com/api/v1/exports/' . $exportId, [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $this->happyscribeToken + ] + ]); + + if ($resCheckExport->getStatusCode() !== 200) { + $this->logger->error("error when checking transcript export ,response status : ". $resCheckExport->getStatusCode()); + $this->jobFinished(); + + return 0; + } + + $resCheckExportBody = $resCheckExport->getBody()->getContents(); + $resCheckExportBody = json_decode($resCheckExportBody,true); + $exportStatus = $resCheckExportBody['state']; + if (isset($resCheckExportBody['failureMessage'])) { + $failureExportMessage = $resCheckExportBody['failureMessage']; + } + + } while(!in_array($exportStatus, ['ready', 'expired', 'failed'])); + + + if ($exportStatus != 'ready') { + $this->logger->error("error when exporting transcript : " . $failureExportMessage); + $this->jobFinished(); + + return 0; + } + + $this->happyscribeClient->get($resCheckExportBody['download_link'], [ + 'sink' => $subtitleTranscriptTemporaryFile + ]); + + $transcriptContent = file_get_contents($subtitleTranscriptTemporaryFile); + + $transcriptContent = preg_replace('/WEBVTT/', 'WEBVTT - with cue identifier', $transcriptContent, 1); + + return $transcriptContent; + } + + private function translate($sourceTranscriptionId, $targetLanguage) + { + // translate + $resTranslate = $this->happyscribeClient->post('https://www.happyscribe.com/api/v1/task/transcription_translation', [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $this->happyscribeToken + ], + 'json' => [ + 'source_transcription_id' => $sourceTranscriptionId, + 'target_language' => strtolower($targetLanguage) + ] + ]); + + if ($resTranslate->getStatusCode() !== 200) { + $this->logger->error("error when translate, response status : ". $resTranslate->getStatusCode()); + $this->jobFinished(); + + return 0; + } + + $resTranslateBody = $resTranslate->getBody()->getContents(); + $resTranslateBody = json_decode($resTranslateBody,true); + + if ($resTranslateBody['state'] == 'failed') { + $this->logger->error("failed when translate, : " . $resTranslateBody['failureReason']); + $this->jobFinished(); + + return 0; + } + + $translateId = $resTranslateBody['id']; + $this->transcriptionsId[] = $translatedTranscriptionId = $resTranslateBody['translatedTranscriptionId']; + + // check translation + $failureTranslateMessage = ''; + + do { + sleep(5); + + $resCheckTranslate = $this->happyscribeClient->get('https://www.happyscribe.com/api/v1/task/transcription_translation/' . $translateId, [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $this->happyscribeToken + ] + ]); + + if ($resCheckTranslate->getStatusCode() !== 200) { + $this->logger->error("error when checking translation task ,response status : " . $resCheckTranslate->getStatusCode()); + $this->jobFinished(); + + return 0; + } + + $resCheckTranslateBody = $resCheckTranslate->getBody()->getContents(); + $resCheckTranslateBody = json_decode($resCheckTranslateBody,true); + $checkTranslateStatus = $resCheckTranslateBody['state']; + if (isset($resCheckTranslateBody['failureReason'])) { + $failureTranslateMessage = $resCheckTranslateBody['failureReason']; + } + + } while(!in_array($checkTranslateStatus, ['done', 'failed'])); + + if ($checkTranslateStatus != 'done') { + $this->logger->error("error when translate : " . $failureTranslateMessage); + $this->jobFinished(); + + return 0; + } + + // export the translation now + + return $this->exportTranscription($translatedTranscriptionId); + } + + private function deleteTranscription($transcriptionId) + { + $this->happyscribeClient->delete('https://www.happyscribe.com/api/v1/transcriptions/' . $transcriptionId, [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $this->happyscribeToken + ] + ]); + } } diff --git a/templates/web/prod/actions/Tools/videoEditor.html.twig b/templates/web/prod/actions/Tools/videoEditor.html.twig index 50719f5eb3..57ffcf75af 100644 --- a/templates/web/prod/actions/Tools/videoEditor.html.twig +++ b/templates/web/prod/actions/Tools/videoEditor.html.twig @@ -248,11 +248,9 @@

- + {% for videoTextTrackField in videoTextTrackFields %} + + {% endfor %}


@@ -359,6 +357,15 @@ $('#submit-subtitle-request').on('click', function (e) { e.preventDefault(); console.log("auto-subtitle process"); + + var destination = {}; + $.each($('input[name=subtitle_language_destination]'), function(k, input){ + let inputEl = $(input); + if (inputEl.prop('checked')) { + destination[inputEl.data('label')] = inputEl.val(); + } + }); + $.ajax({ type: 'POST', url: '/prod/tools/auto-subtitle/', @@ -368,8 +375,7 @@ record_id: {{ record.getRecordId }}, subtitle_language_source: $('#subtitle_language_source option:selected').text(), meta_struct_id_source: $('#subtitle_language_source').val(), - subtitle_language_destination: $('#subtitle_language_destination option:selected').text(), - meta_struct_id_destination: $('#subtitle_language_destination').val() + subtitle_destination: JSON.stringify(destination), }, success: function success(data) { console.log(data); From cc8c1f64622c040401ba6ed0057ccc8746896804 Mon Sep 17 00:00:00 2001 From: aynsix Date: Fri, 3 Nov 2023 18:11:39 +0300 Subject: [PATCH 2/2] list source lang --- .../Controller/Prod/ToolsController.php | 42 ++++++++- .../Event/Record/RecordAutoSubtitleEvent.php | 7 -- .../Subscriber/SubtitleSubscriber.php | 1 - .../WorkerManager/Worker/SubtitleWorker.php | 92 ++++++++++--------- .../prod/actions/Tools/videoEditor.html.twig | 9 +- 5 files changed, 93 insertions(+), 58 deletions(-) diff --git a/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php b/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php index 1aeeb2554a..d6eb522fb7 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php @@ -558,7 +558,6 @@ public function autoSubtitleAction(Request $request) new RecordAutoSubtitleEvent( $record, $request->request->get("subtitle_language_source"), - $request->request->get("meta_struct_id_source"), json_decode($request->request->get("subtitle_destination"), true) ) ); @@ -618,7 +617,8 @@ public function videoEditorAction(Request $request) 'videoEditorConfig' => $conf->get(['video-editor']), 'metadatas' => $metadatas, 'JSonFields' => json_encode($JSFields), - 'videoTextTrackFields' => $videoTextTrackFields + 'videoTextTrackFields' => $videoTextTrackFields, + 'languages' => $this->languageList() ]); } @@ -633,4 +633,42 @@ private function isPhysicallyPresent(record_adapter $record, $subdefName) return false; } + + private function languageList() + { + return [ + 'af-ZA' => 'af-ZA Afrikaans (South Africa)', 'am-ET' => 'am-ET Amharic (Ethiopia)', + 'ar-DZ' => 'ar-DZ Arabic (Algeria)', 'ar-BH' => 'ar-BH Arabic (Bahrain)', + 'ar-EG' => 'ar-EG Arabic (Egypt)', 'ar-IQ' => 'ar-IQ Arabic (Iraq)', + 'ar-IL' => 'ar-IL Arabic (Israel)', 'ar-YE' => 'ar-YE Arabic (Yemen)', + 'eu-ES' => 'eu-ES Basque (Spain)', 'bn-BD' => 'bn-BD Bengali (Bangladesh)', + 'bn-IN' => 'bn-IN Bengali (India)', 'bg-BG' => 'bg-BG Bulgarian (Bulgaria)', + 'ca-ES' => 'ca-ES Catalan (Spain)', 'yue-Hant-HK' => 'yue-Hant-HK Chinese, Cantonese (Traditional, Hong Kong)', + 'cmn-Hans-CN' => 'cmn-Hans-CN Chinese, Mandarin (Simplified, China)', 'hr-HR' => 'hr-HR Croatian (Croatia)', + 'cs-CZ' => 'cs-CZ Czech (Czech Republic)', 'da-DK' => 'da-DK Danish (Denmark)', + 'nl-NL' => 'nl-NL Dutch (Netherlands)', 'nl-BE' => 'nl-BE Dutch (Belgium)', + 'en-AU' => 'en-AU English (Australia)', 'en-CA' => 'en-CA English (Canada)', + 'en-GB' => 'en-GB English (United Kingdom)', 'en-US' => 'en-US English (United States)', + 'fr-CA' => 'fr-CA French (Canada)', 'fr-FR' => 'fr-FR French (France)', + 'fr-BE' => 'fr-BE French (Belgium)', 'fr-CH' => 'fr-CH French (Switzerland)', + 'ka-GE' => 'ka-GE Georgian (Georgia)', 'de-DE' => 'de-DE German (Germany)', + 'el-GR' => 'el-GR Greek (Greece)', 'he-IL' => 'he-IL Hebrew (Israel)', + 'hi-IN' => 'hi-IN Hindi (India)', 'hu-HU' => 'hu-HU Hungarian (Hungary)', + 'is-IS' => 'is-IS Icelandic (Iceland)', 'id-ID' => 'id-ID Indonesian (Indonesia)', + 'it-IT' => 'it-IT Italian (Italy)', 'ja-JP' => 'ja-JP Japanese (Japan)', + 'ko-KR' => 'ko-KR Korean (South Korea)', 'lo-LA' => 'lo-LA Lao (Laos)', + 'lt-LT' => 'lt-LT Lithuanian (Lithuania)', 'ms-MY' => 'ms-MY Malay (Malaysia)', + 'ne-NP' => 'ne-NP Nepali (Nepal)', 'nb-NO' => 'nb-NO Norwegian Bokmål (Norway)', + 'pl-PL' => 'pl-PL Polish (Poland)', 'pt-BR' => 'pt-BR Portuguese (Brazil)', + 'pt-PT' => 'pt-PT Portuguese (Portugal)', 'ro-RO' => 'ro-RO Romanian (Romania)', + 'ru-RU' => 'ru-RU Russian (Russia)', 'sr-RS' => 'sr-RS Serbian (Serbia)', + 'sk-SK' => 'sk-SK Slovak (Slovakia)', 'sl-SI' => 'sl-SI Slovenian (Slovenia)', + 'es-ES' => 'es-ES Spanish (Spain)', 'sv-SE' => 'sv-SE Swedish (Sweden)', + 'th-TH' => 'th-TH Thai (Thailand)', 'tr-TR' => 'tr-TR Turkish (Turkey)', + 'uk-UA' => 'uk-UA Ukrainian (Ukraine)', 'vi-VN' => 'vi-VN Vietnamese (Vietnam)', + 'et-EE' => 'et-EE Estonian (Estonia)', 'mn-MN' => 'mn-MN Mongolian (Mongolia)', + 'uz-UZ' => 'uz-UZ Uzbek (Uzbekistan)' + + ]; + } } diff --git a/lib/Alchemy/Phrasea/Core/Event/Record/RecordAutoSubtitleEvent.php b/lib/Alchemy/Phrasea/Core/Event/Record/RecordAutoSubtitleEvent.php index 706ad8a3fb..37b12fb4e0 100644 --- a/lib/Alchemy/Phrasea/Core/Event/Record/RecordAutoSubtitleEvent.php +++ b/lib/Alchemy/Phrasea/Core/Event/Record/RecordAutoSubtitleEvent.php @@ -14,14 +14,12 @@ class RecordAutoSubtitleEvent extends RecordEvent public function __construct( RecordInterface $record, $languageSource, - $metaStructureIdSource, $languageDestination ) { parent::__construct($record); $this->languageSource = $languageSource; - $this->metaStructureIdSource = $metaStructureIdSource; $this->languageDestination = $languageDestination; } @@ -30,11 +28,6 @@ public function getLanguageSource() return $this->languageSource; } - public function getMetaStructureIdSource() - { - return $this->metaStructureIdSource; - } - public function getLanguageDestination() { return $this->languageDestination; diff --git a/lib/Alchemy/Phrasea/WorkerManager/Subscriber/SubtitleSubscriber.php b/lib/Alchemy/Phrasea/WorkerManager/Subscriber/SubtitleSubscriber.php index 0465e52e24..b6ab7c4b28 100644 --- a/lib/Alchemy/Phrasea/WorkerManager/Subscriber/SubtitleSubscriber.php +++ b/lib/Alchemy/Phrasea/WorkerManager/Subscriber/SubtitleSubscriber.php @@ -32,7 +32,6 @@ public function onRecordAutoSubtitle(RecordAutoSubtitleEvent $event) "databoxId" => $event->getRecord()->getDataboxId(), "recordId" => $event->getRecord()->getRecordId(), "languageSource" => $event->getLanguageSource(), - "metaStructureIdSource" => $event->getMetaStructureIdSource(), "languageDestination" => $event->getLanguageDestination(), "type" => MessagePublisher::SUBTITLE_TYPE // used to specify the final Q to publish message ]; diff --git a/lib/Alchemy/Phrasea/WorkerManager/Worker/SubtitleWorker.php b/lib/Alchemy/Phrasea/WorkerManager/Worker/SubtitleWorker.php index 34e00b82fb..d170e6d694 100644 --- a/lib/Alchemy/Phrasea/WorkerManager/Worker/SubtitleWorker.php +++ b/lib/Alchemy/Phrasea/WorkerManager/Worker/SubtitleWorker.php @@ -96,8 +96,6 @@ public function process(array $payload) $this->extension = 'vtt'; } - $languageSource = $this->getLanguageFormat($payload['languageSource']); - $record = $this->getApplicationBox()->get_databox($payload['databoxId'])->get_record($payload['recordId']); // if subdef_source not set, by default use the preview permalink @@ -145,20 +143,28 @@ public function process(array $payload) // create a transcription - $responseTranscription = $this->happyscribeClient->post('https://www.happyscribe.com/api/v1/transcriptions', [ - 'headers' => [ - 'Authorization' => 'Bearer '. $this->happyscribeToken - ], - 'json' => [ - 'transcription' => [ - 'name' => $record->get_title(), - 'is_subtitle' => true, - 'language' => $languageSource, - 'organization_id' => $organizationId, - 'tmp_url' => $tmpUrl + try { + $responseTranscription = $this->happyscribeClient->post('https://www.happyscribe.com/api/v1/transcriptions', [ + 'headers' => [ + 'Authorization' => 'Bearer '. $this->happyscribeToken + ], + 'json' => [ + 'transcription' => [ + 'name' => $record->get_title(), + 'is_subtitle' => true, + 'language' => $payload['languageSource'], + 'organization_id' => $organizationId, + 'tmp_url' => $tmpUrl + ] ] - ] - ]); + ]); + + } catch (\Exception $e) { + $this->logger->error("error when creating transcript : " . $e->getMessage()); + $this->jobFinished(); + + return 0; + } if ($responseTranscription->getStatusCode() !== 200) { $this->logger->error("error when creating transcript,response status : ". $responseTranscription->getStatusCode()); @@ -212,7 +218,7 @@ public function process(array $payload) foreach ($payload['languageDestination'] as $language => $metaStructureIdDestination) { $languageDestination = strtolower($language); - if (strtolower($payload['languageSource']) == $languageDestination) { + if ($this->getTargetLanguageByCode($payload['languageSource']) == $languageDestination) { $metadatas[] = [ 'meta_struct_id' => (int)$metaStructureIdDestination, 'meta_id' => '', @@ -244,9 +250,9 @@ public function process(array $payload) // delete transcription - foreach ($this->transcriptionsId as $transcriptionId) { - $this->deleteTranscription($transcriptionId); - } +// foreach ($this->transcriptionsId as $transcriptionId) { +// $this->deleteTranscription($transcriptionId); +// } $this->jobFinished(); @@ -277,19 +283,6 @@ private function jobFinished() } } - private function getLanguageFormat($language) - { - switch ($language) { - case 'En': - return 'en-GB'; - case 'De': - return 'de-DE'; - case 'Fr': - default: - return 'fr-FR'; - } - } - private function isRemoteFileExist($fileUrl) { $client = new Client(); @@ -384,23 +377,29 @@ private function exportTranscription($transcriptionId) $transcriptContent = file_get_contents($subtitleTranscriptTemporaryFile); - $transcriptContent = preg_replace('/WEBVTT/', 'WEBVTT - with cue identifier', $transcriptContent, 1); - return $transcriptContent; } private function translate($sourceTranscriptionId, $targetLanguage) { // translate - $resTranslate = $this->happyscribeClient->post('https://www.happyscribe.com/api/v1/task/transcription_translation', [ - 'headers' => [ - 'Authorization' => 'Bearer ' . $this->happyscribeToken - ], - 'json' => [ - 'source_transcription_id' => $sourceTranscriptionId, - 'target_language' => strtolower($targetLanguage) - ] - ]); + try { + $resTranslate = $this->happyscribeClient->post('https://www.happyscribe.com/api/v1/task/transcription_translation', [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $this->happyscribeToken + ], + 'json' => [ + 'source_transcription_id' => $sourceTranscriptionId, + 'target_language' => strtolower($targetLanguage) + ] + ]); + } catch (\Exception $e) { + $this->logger->error("error when translate : ". $e->getMessage()); + $this->jobFinished(); + + return 0; + } + if ($resTranslate->getStatusCode() !== 200) { $this->logger->error("error when translate, response status : ". $resTranslate->getStatusCode()); @@ -470,4 +469,11 @@ private function deleteTranscription($transcriptionId) ] ]); } + + private function getTargetLanguageByCode($code) + { + $t = explode('-', $code); + + return $t[0]; + } } diff --git a/templates/web/prod/actions/Tools/videoEditor.html.twig b/templates/web/prod/actions/Tools/videoEditor.html.twig index 57ffcf75af..f3fc5ee52c 100644 --- a/templates/web/prod/actions/Tools/videoEditor.html.twig +++ b/templates/web/prod/actions/Tools/videoEditor.html.twig @@ -229,7 +229,7 @@

@@ -241,8 +241,8 @@

@@ -373,8 +373,7 @@ data: { databox_id: {{ record.getDataboxId }}, record_id: {{ record.getRecordId }}, - subtitle_language_source: $('#subtitle_language_source option:selected').text(), - meta_struct_id_source: $('#subtitle_language_source').val(), + subtitle_language_source: $('#subtitle_language_source').val(), subtitle_destination: JSON.stringify(destination), }, success: function success(data) {