diff --git a/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php b/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php index 55c067b17b..af30331807 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php @@ -468,7 +468,14 @@ class ToolsController extends Controller $this->dispatch( PhraseaEvents::RECORD_AUTO_SUBTITLE, - new RecordAutoSubtitleEvent($record, $permalinkUrl, $request->request->get("subtitle_language_source"), $request->request->get("meta_struct_id")) + 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") + ) ); return $this->app->json(["status" => "dispatch"]); diff --git a/lib/Alchemy/Phrasea/Core/Event/Record/RecordAutoSubtitleEvent.php b/lib/Alchemy/Phrasea/Core/Event/Record/RecordAutoSubtitleEvent.php index 044303161a..307a54e474 100644 --- a/lib/Alchemy/Phrasea/Core/Event/Record/RecordAutoSubtitleEvent.php +++ b/lib/Alchemy/Phrasea/Core/Event/Record/RecordAutoSubtitleEvent.php @@ -7,16 +7,27 @@ use Alchemy\Phrasea\Model\RecordInterface; class RecordAutoSubtitleEvent extends RecordEvent { private $languageSource; - private $metaStructId; + private $metaStructureIdSource; + private $languageDestination; + private $metaStructureIdDestination; private $permalinkUrl; - public function __construct(RecordInterface $record, $permalinkUrl, $languageSource, $metaStructId) + public function __construct( + RecordInterface $record, + $permalinkUrl, + $languageSource, + $metaStructureIdSource, + $languageDestination, + $metaStructureIdDestination + ) { parent::__construct($record); - $this->languageSource = $languageSource; - $this->metaStructId = $metaStructId; - $this->permalinkUrl = $permalinkUrl; + $this->languageSource = $languageSource; + $this->metaStructureIdSource = $metaStructureIdSource; + $this->languageDestination = $languageDestination; + $this->metaStructureIdDestination = $metaStructureIdDestination; + $this->permalinkUrl = $permalinkUrl; } public function getLanguageSource() @@ -24,9 +35,19 @@ class RecordAutoSubtitleEvent extends RecordEvent return $this->languageSource; } - public function getMetaStructId() + public function getMetaStructureIdSource() { - return $this->metaStructId; + return $this->metaStructureIdSource; + } + + public function getLanguageDestination() + { + return $this->languageDestination; + } + + public function getMetaStructureIdDestination() + { + return $this->metaStructureIdDestination; } public function getPermalinkUrl() diff --git a/lib/Alchemy/Phrasea/WorkerManager/Provider/AlchemyWorkerServiceProvider.php b/lib/Alchemy/Phrasea/WorkerManager/Provider/AlchemyWorkerServiceProvider.php index 2011ef9921..b1690777e4 100644 --- a/lib/Alchemy/Phrasea/WorkerManager/Provider/AlchemyWorkerServiceProvider.php +++ b/lib/Alchemy/Phrasea/WorkerManager/Provider/AlchemyWorkerServiceProvider.php @@ -132,7 +132,9 @@ class AlchemyWorkerServiceProvider implements PluginProviderInterface })); $app['alchemy_worker.type_based_worker_resolver']->addFactory(MessagePublisher::SUBTITLE_TYPE, new CallableWorkerFactory(function () use ($app) { - return new SubtitleWorker($app['repo.worker-job'], $app['conf'], new LazyLocator($app, 'phraseanet.appbox'), $app['alchemy_worker.logger']); + return (new SubtitleWorker($app['repo.worker-job'], $app['conf'], new LazyLocator($app, 'phraseanet.appbox'), $app['alchemy_worker.logger'])) + ->setFileSystemLocator(new LazyLocator($app, 'filesystem')) + ->setTemporaryFileSystemLocator(new LazyLocator($app, 'temporary-filesystem')); })); $app['alchemy_worker.type_based_worker_resolver']->addFactory(MessagePublisher::MAIN_QUEUE_TYPE, new CallableWorkerFactory(function () use ($app) { diff --git a/lib/Alchemy/Phrasea/WorkerManager/Subscriber/SubtitleSubscriber.php b/lib/Alchemy/Phrasea/WorkerManager/Subscriber/SubtitleSubscriber.php index bed3a8eb4c..e67958165c 100644 --- a/lib/Alchemy/Phrasea/WorkerManager/Subscriber/SubtitleSubscriber.php +++ b/lib/Alchemy/Phrasea/WorkerManager/Subscriber/SubtitleSubscriber.php @@ -32,11 +32,13 @@ class SubtitleSubscriber implements EventSubscriberInterface $em = $this->repoWorkerJob->getEntityManager(); $data = [ - "databoxId" => $event->getRecord()->getDataboxId(), - "recordId" => $event->getRecord()->getRecordId(), - "permalinkUrl" => $event->getPermalinkUrl(), - "langageSource" => $event->getLanguageSource(), - "metaStructureId" => $event->getMetaStructId() + "databoxId" => $event->getRecord()->getDataboxId(), + "recordId" => $event->getRecord()->getRecordId(), + "permalinkUrl" => $event->getPermalinkUrl(), + "languageSource" => $event->getLanguageSource(), + "metaStructureIdSource" => $event->getMetaStructureIdSource(), + "languageDestination" => $event->getLanguageDestination(), + "metaStructureIdDestination" => $event->getMetaStructureIdDestination() ]; $this->repoWorkerJob->reconnect(); diff --git a/lib/Alchemy/Phrasea/WorkerManager/Worker/SubtitleWorker.php b/lib/Alchemy/Phrasea/WorkerManager/Worker/SubtitleWorker.php index e9d66a2c66..c0c9d56f61 100644 --- a/lib/Alchemy/Phrasea/WorkerManager/Worker/SubtitleWorker.php +++ b/lib/Alchemy/Phrasea/WorkerManager/Worker/SubtitleWorker.php @@ -2,6 +2,7 @@ namespace Alchemy\Phrasea\WorkerManager\Worker; +use Alchemy\Phrasea\Application\Helper\FilesystemAware; use Alchemy\Phrasea\Core\Configuration\PropertyAccess; use Alchemy\Phrasea\Model\Entities\WorkerJob; use Alchemy\Phrasea\Model\Repositories\WorkerJobRepository; @@ -10,6 +11,8 @@ use Psr\Log\LoggerInterface; class SubtitleWorker implements WorkerInterface { + use FilesystemAware; + /** * @var callable */ @@ -50,31 +53,40 @@ class SubtitleWorker implements WorkerInterface } $workerJob->setStatus(WorkerJob::RUNNING) - ->setStarted(new \DateTime('now')); + ->setStarted(new \DateTime('now')); $em = $this->repoWorkerJob->getEntityManager(); $this->repoWorkerJob->reconnect(); $em->persist($workerJob); $em->flush(); + 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; + } + + $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['meta_struct_id_source'])->get_name(); - if ($payload['permalinkUrl'] != '' && $payload['metaStructureId']) { - switch ($payload['langageSource']) { - case 'En': - $language = 'en-GB'; - break; - case 'De': - $language = 'de-DE'; - break; - case 'Fr': - default: - $language = 'fr-FR'; - break; - } - - $gingerClient = new Client(); + $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' => [ @@ -82,7 +94,7 @@ class SubtitleWorker implements WorkerInterface ], 'json' => [ 'url' => $payload['permalinkUrl'], - 'language' => $language + 'language' => $languageSource ] ]); } catch(\Exception $e) { @@ -144,7 +156,7 @@ class SubtitleWorker implements WorkerInterface 'ACCEPT' => $gingaTranscriptFormat ], 'query' => [ - 'language' => $language + 'language' => $languageSource ] ]); } catch (\Exception $e) { @@ -165,8 +177,11 @@ class SubtitleWorker implements WorkerInterface $transcriptContent = preg_replace('/WEBVTT/', 'WEBVTT - with cue identifier', $transcriptContent, 1); + // save subtitle on temporary file to use to translate if needed + file_put_contents($subtitleSourceTemporaryFile, $transcriptContent); + $metadatas[0] = [ - 'meta_struct_id' => (int)$payload['metaStructureId'], + 'meta_struct_id' => (int)$payload['metaStructureIdSource'], 'meta_id' => '', 'value' => $transcriptContent ]; @@ -180,7 +195,77 @@ class SubtitleWorker implements WorkerInterface return 0; } - $this->logger->info("Auto subtitle SUCCESS"); + $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); + + file_put_contents($subtitleSourceTemporaryFile, $fieldValue->getValue()); + } + + 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($workerJob); + + return 0; + } + + if ($response->getStatusCode() !== 200) { + $this->logger->error("response status /translate/ : ". $response->getStatusCode()); + $this->jobFinished($workerJob); + + return 0; + } + + $transcriptContent = $response->getBody()->getContents(); + $transcriptContent = preg_replace('/WEBVTT/', 'WEBVTT - with cue identifier', $transcriptContent, 1); + + $metadatas[0] = [ + 'meta_struct_id' => (int)$payload['metaStructureIdDestination'], + 'meta_id' => '', + 'value' => $transcriptContent + ]; + + try { + $record->set_metadatas($metadatas); + } catch (\Exception $e) { + $this->logger->error($e->getMessage()); + $this->jobFinished($workerJob); + + return 0; + } + + $this->logger->info("Translate subtitle on language destination SUCCESS"); } $this->jobFinished($workerJob); @@ -209,4 +294,17 @@ class SubtitleWorker implements WorkerInterface $em->persist($workerJob); $em->flush(); } + + private function getLanguageFormat($language) + { + switch ($language) { + case 'En': + return 'en-GB'; + case 'De': + return 'de-DE'; + case 'Fr': + default: + return 'fr-FR'; + } + } } diff --git a/templates/web/prod/actions/Tools/videoEditor.html.twig b/templates/web/prod/actions/Tools/videoEditor.html.twig index b9920c7fb2..93da3b2018 100644 --- a/templates/web/prod/actions/Tools/videoEditor.html.twig +++ b/templates/web/prod/actions/Tools/videoEditor.html.twig @@ -366,8 +366,10 @@ data: { databox_id: {{ record.getDataboxId }}, record_id: {{ record.getRecordId }}, - meta_struct_id: $('#subtitle_language_source').val(), - subtitle_language_source: $('#subtitle_language_source option:selected').text() + 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() }, success: function success(data) { console.log(data);