diff --git a/config/configuration.sample.yml b/config/configuration.sample.yml index 968d309c88..307b4ca827 100644 --- a/config/configuration.sample.yml +++ b/config/configuration.sample.yml @@ -328,6 +328,14 @@ workers: password: guest vhost: / +externalservice: + ginger: + AutoSubtitling: + service_base_url: https://base.uri + token: 39c6011d + transcript_format: text/vtt + subdef_source: preview + user_account: deleting_policies: email_confirmation: true diff --git a/lib/Alchemy/Phrasea/Application.php b/lib/Alchemy/Phrasea/Application.php index 60d8f2bf3e..a37c78339c 100644 --- a/lib/Alchemy/Phrasea/Application.php +++ b/lib/Alchemy/Phrasea/Application.php @@ -91,7 +91,6 @@ use Alchemy\Phrasea\WorkerManager\Provider\AlchemyWorkerServiceProvider; use Alchemy\Phrasea\WorkerManager\Provider\QueueWorkerServiceProvider; use Alchemy\QueueProvider\QueueServiceProvider; use Alchemy\WorkerProvider\WorkerServiceProvider; -use Doctrine\DBAL\Event\ConnectionEventArgs; use MediaVorus\Media\MediaInterface; use MediaVorus\MediaVorus; use Monolog\Handler\ErrorLogHandler; diff --git a/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php b/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php index 3343a05438..55c067b17b 100644 --- a/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php +++ b/lib/Alchemy/Phrasea/Controller/Prod/ToolsController.php @@ -15,8 +15,10 @@ use Alchemy\Phrasea\Application\Helper\FilesystemAware; use Alchemy\Phrasea\Application\Helper\SubDefinitionSubstituerAware; use Alchemy\Phrasea\Controller\Controller; use Alchemy\Phrasea\Controller\RecordsRequest; +use Alchemy\Phrasea\Core\Event\Record\RecordAutoSubtitleEvent; use Alchemy\Phrasea\Core\Event\Record\RecordEvents; use Alchemy\Phrasea\Core\Event\Record\SubdefinitionCreateEvent; +use Alchemy\Phrasea\Core\PhraseaEvents; use Alchemy\Phrasea\Exception\RuntimeException; use Alchemy\Phrasea\Metadata\PhraseanetMetadataReader; use Alchemy\Phrasea\Metadata\PhraseanetMetadataSetter; @@ -24,7 +26,6 @@ use Alchemy\Phrasea\Record\RecordWasRotated; use DataURI\Parser; use MediaAlchemyst\Alchemyst; use MediaVorus\MediaVorus; -use PHPExiftool\Reader; use Symfony\Component\HttpFoundation\Request; class ToolsController extends Controller @@ -45,7 +46,6 @@ class ToolsController extends Controller if (count($records) == 1) { /** @var \record_adapter $record */ $record = $records->first(); - $databox = $record->getDatabox(); /**Array list of subdefs**/ $listsubdef = array_keys($record-> get_subdefs()); @@ -88,14 +88,13 @@ class ToolsController extends Controller $metadatas = true; } } - $conf = $this->getConf(); return $this->render('prod/actions/Tools/index.html.twig', [ 'records' => $records, 'record' => $record, 'recordSubdefs' => $recordAccessibleSubdefs, 'metadatas' => $metadatas, - 'listsubdef' => $listsubdef + 'listsubdef' => $listsubdef ]); } @@ -118,6 +117,7 @@ class ToolsController extends Controller } foreach ($records as $record) { + /** @var \media_subdef $subdef */ foreach ($record->get_subdefs() as $subdef) { if ($subdef->get_type() !== \media_subdef::TYPE_IMAGE) { continue; @@ -146,6 +146,7 @@ class ToolsController extends Controller foreach ($selection as $record) { $substituted = false; + /** @var \media_subdef $subdef */ foreach ($record->get_subdefs() as $subdef) { if ($subdef->is_substituted()) { $substituted = true; @@ -362,14 +363,6 @@ class ToolsController extends Controller return $this->app->json($return); } - /** - * @return Reader - */ - private function getExifToolReader() - { - return $this->app['exiftool.reader']; - } - /** * @return Alchemyst */ @@ -449,13 +442,38 @@ class ToolsController extends Controller try { $record->set_metadatas($metadatas); } - catch (Exception $e) { + catch (\Exception $e) { return $this->app->json(['success' => false, 'errorMessage' => $e->getMessage()]); } return $this->app->json(['success' => true, 'errorMessage' => '']); } + public function autoSubtitleAction(Request $request) + { + $record = new \record_adapter($this->app, + (int)$request->request->get("databox_id"), + (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")) + ); + + return $this->app->json(["status" => "dispatch"]); + } + public function videoEditorAction(Request $request) { $records = RecordsRequest::fromRequest($this->app, $request, false); @@ -491,7 +509,7 @@ class ToolsController extends Controller $fieldValue = array_pop($fieldValues); $field['value'] = $fieldValue->getValue(); } - $videoTextTrackFields[] = $field; + $videoTextTrackFields[$meta->get_id()] = $field; unset($field); } } @@ -511,4 +529,15 @@ class ToolsController extends Controller 'videoTextTrackFields' => $videoTextTrackFields ]); } + + private function isPhysicallyPresent(\record_adapter $record, $subdefName) + { + try { + return $record->get_subdef($subdefName)->is_physically_present(); + } catch (\Exception $e) { + unset($e); + } + + return false; + } } diff --git a/lib/Alchemy/Phrasea/ControllerProvider/Prod/Tools.php b/lib/Alchemy/Phrasea/ControllerProvider/Prod/Tools.php index 8d6932aecf..07f8810ad4 100644 --- a/lib/Alchemy/Phrasea/ControllerProvider/Prod/Tools.php +++ b/lib/Alchemy/Phrasea/ControllerProvider/Prod/Tools.php @@ -72,6 +72,9 @@ class Tools implements ControllerProviderInterface, ServiceProviderInterface $controllers->post('/metadata/save/', 'controller.prod.tools:saveMetasAction') ->bind('prod_tools_metadata_save'); + $controllers->post('/auto-subtitle/', 'controller.prod.tools:autoSubtitleAction') + ->bind('prod_tools_auto_subtitle'); + $controllers->get('/videoEditor', 'controller.prod.tools:videoEditorAction'); return $controllers; diff --git a/lib/Alchemy/Phrasea/Core/Event/Record/RecordAutoSubtitleEvent.php b/lib/Alchemy/Phrasea/Core/Event/Record/RecordAutoSubtitleEvent.php new file mode 100644 index 0000000000..044303161a --- /dev/null +++ b/lib/Alchemy/Phrasea/Core/Event/Record/RecordAutoSubtitleEvent.php @@ -0,0 +1,36 @@ +languageSource = $languageSource; + $this->metaStructId = $metaStructId; + $this->permalinkUrl = $permalinkUrl; + } + + public function getLanguageSource() + { + return $this->languageSource; + } + + public function getMetaStructId() + { + return $this->metaStructId; + } + + public function getPermalinkUrl() + { + return $this->permalinkUrl; + } +} diff --git a/lib/Alchemy/Phrasea/Core/PhraseaEvents.php b/lib/Alchemy/Phrasea/Core/PhraseaEvents.php index 90adac69dd..1e1daf714b 100644 --- a/lib/Alchemy/Phrasea/Core/PhraseaEvents.php +++ b/lib/Alchemy/Phrasea/Core/PhraseaEvents.php @@ -54,6 +54,8 @@ final class PhraseaEvents const RECORD_EDIT = 'record.edit'; const RECORD_UPLOAD = 'record.upload'; + const RECORD_AUTO_SUBTITLE = 'record.auto-subtitle'; + const THESAURUS_IMPORTED = 'thesaurus.imported'; const THESAURUS_FIELD_LINKED = 'thesaurus.field-linked'; const THESAURUS_CANDIDATE_ACCEPTED_AS_CONCEPT = 'thesaurus.candidate-accepted-as-concept'; diff --git a/lib/Alchemy/Phrasea/Core/Provider/RepositoriesServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/RepositoriesServiceProvider.php index 72f64fa52c..b81835842e 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/RepositoriesServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/Provider/RepositoriesServiceProvider.php @@ -150,6 +150,9 @@ class RepositoriesServiceProvider implements ServiceProviderInterface $app['repo.worker-running-job'] = $app->share(function (PhraseaApplication $app) { return $app['orm.em']->getRepository('Phraseanet:WorkerRunningJob'); }); + $app['repo.worker-job'] = $app->share(function (PhraseaApplication $app) { + return $app['orm.em']->getRepository('Phraseanet:WorkerJob'); + }); $app['repo.worker-running-populate'] = $app->share(function (PhraseaApplication $app) { return $app['orm.em']->getRepository('Phraseanet:WorkerRunningPopulate'); }); diff --git a/lib/Alchemy/Phrasea/Core/Version.php b/lib/Alchemy/Phrasea/Core/Version.php index 9ac3908119..79fe15eb4b 100644 --- a/lib/Alchemy/Phrasea/Core/Version.php +++ b/lib/Alchemy/Phrasea/Core/Version.php @@ -16,7 +16,6 @@ class Version /** * @var string */ - private $number = '4.1.1'; /** diff --git a/lib/Alchemy/Phrasea/Model/Entities/WorkerJob.php b/lib/Alchemy/Phrasea/Model/Entities/WorkerJob.php new file mode 100644 index 0000000000..dbeb5d319a --- /dev/null +++ b/lib/Alchemy/Phrasea/Model/Entities/WorkerJob.php @@ -0,0 +1,150 @@ +id; + } + + public function setType($type) + { + $this->type = $type; + + return $this; + } + + public function getType() + { + return $this->type; + } + + /** + * @param array $data + * + * @return WorkerJob + */ + public function setData(array $data) + { + $this->data = $data; + + return $this; + } + + public function getData() + { + return $this->data; + } + + public function setStatus($status) + { + $this->status = $status; + + return $this; + } + + public function getStatus() + { + return $this->status; + } + + /** + * @return \DateTime + */ + public function getCreated() + { + return $this->created; + } + + /** + * @param \DateTime $finished + * @return $this + */ + public function setFinished(\DateTime $finished) + { + $this->finished = $finished; + + return $this; + } + + /** + * @return mixed + */ + public function getFinished() + { + return $this->finished; + } + + /** + * @param \DateTime $started + * @return $this + */ + public function setStarted(\DateTime $started) + { + $this->started = $started; + + return $this; + } + + /** + * @return mixed + */ + public function getStarted() + { + return $this->started; + } +} diff --git a/lib/Alchemy/Phrasea/Model/Repositories/WorkerJobRepository.php b/lib/Alchemy/Phrasea/Model/Repositories/WorkerJobRepository.php new file mode 100644 index 0000000000..dd20a9c95d --- /dev/null +++ b/lib/Alchemy/Phrasea/Model/Repositories/WorkerJobRepository.php @@ -0,0 +1,22 @@ +_em->getConnection()->ping() === false) { + $this->_em->getConnection()->close(); + $this->_em->getConnection()->connect(); + } + } +} diff --git a/lib/Alchemy/Phrasea/WorkerManager/Provider/AlchemyWorkerServiceProvider.php b/lib/Alchemy/Phrasea/WorkerManager/Provider/AlchemyWorkerServiceProvider.php index ecabd28649..2011ef9921 100644 --- a/lib/Alchemy/Phrasea/WorkerManager/Provider/AlchemyWorkerServiceProvider.php +++ b/lib/Alchemy/Phrasea/WorkerManager/Provider/AlchemyWorkerServiceProvider.php @@ -11,11 +11,13 @@ use Alchemy\Phrasea\WorkerManager\Worker\CreateRecordWorker; use Alchemy\Phrasea\WorkerManager\Worker\DeleteRecordWorker; use Alchemy\Phrasea\WorkerManager\Worker\ExportMailWorker; use Alchemy\Phrasea\WorkerManager\Worker\Factory\CallableWorkerFactory; +use Alchemy\Phrasea\WorkerManager\Worker\MainQueueWorker; use Alchemy\Phrasea\WorkerManager\Worker\PopulateIndexWorker; use Alchemy\Phrasea\WorkerManager\Worker\ProcessPool; use Alchemy\Phrasea\WorkerManager\Worker\PullAssetsWorker; use Alchemy\Phrasea\WorkerManager\Worker\Resolver\TypeBasedWorkerResolver; use Alchemy\Phrasea\WorkerManager\Worker\SubdefCreationWorker; +use Alchemy\Phrasea\WorkerManager\Worker\SubtitleWorker; use Alchemy\Phrasea\WorkerManager\Worker\WebhookWorker; use Alchemy\Phrasea\WorkerManager\Worker\WorkerInvoker; use Alchemy\Phrasea\WorkerManager\Worker\WriteMetadatasWorker; @@ -128,6 +130,14 @@ class AlchemyWorkerServiceProvider implements PluginProviderInterface return (new DeleteRecordWorker()) ->setApplicationBox($app['phraseanet.appbox']); })); + + $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']); + })); + + $app['alchemy_worker.type_based_worker_resolver']->addFactory(MessagePublisher::MAIN_QUEUE_TYPE, new CallableWorkerFactory(function () use ($app) { + return new MainQueueWorker($app['alchemy_worker.message.publisher'], $app['repo.worker-job']); + })); } /** diff --git a/lib/Alchemy/Phrasea/WorkerManager/Provider/QueueWorkerServiceProvider.php b/lib/Alchemy/Phrasea/WorkerManager/Provider/QueueWorkerServiceProvider.php index 7de8ef2604..6bdf421fbf 100644 --- a/lib/Alchemy/Phrasea/WorkerManager/Provider/QueueWorkerServiceProvider.php +++ b/lib/Alchemy/Phrasea/WorkerManager/Provider/QueueWorkerServiceProvider.php @@ -23,6 +23,7 @@ use Alchemy\Phrasea\WorkerManager\Subscriber\AssetsIngestSubscriber; use Alchemy\Phrasea\WorkerManager\Subscriber\ExportSubscriber; use Alchemy\Phrasea\WorkerManager\Subscriber\RecordSubscriber; use Alchemy\Phrasea\WorkerManager\Subscriber\SearchengineSubscriber; +use Alchemy\Phrasea\WorkerManager\Subscriber\SubtitleSubscriber; use Alchemy\Phrasea\WorkerManager\Subscriber\WebhookSubscriber; use Silex\Application; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -68,6 +69,7 @@ class QueueWorkerServiceProvider implements PluginProviderInterface $dispatcher->addSubscriber(new AssetsIngestSubscriber($app['alchemy_worker.message.publisher'])); $dispatcher->addSubscriber(new SearchengineSubscriber($app['alchemy_worker.message.publisher'])); $dispatcher->addSubscriber(new WebhookSubscriber($app['alchemy_worker.message.publisher'])); + $dispatcher->addSubscriber(new SubtitleSubscriber(new LazyLocator($app, 'repo.worker-job'), $app['alchemy_worker.message.publisher'])); return $dispatcher; }) diff --git a/lib/Alchemy/Phrasea/WorkerManager/Queue/AMQPConnection.php b/lib/Alchemy/Phrasea/WorkerManager/Queue/AMQPConnection.php index e1d6fed0ba..4508741ca7 100644 --- a/lib/Alchemy/Phrasea/WorkerManager/Queue/AMQPConnection.php +++ b/lib/Alchemy/Phrasea/WorkerManager/Queue/AMQPConnection.php @@ -29,7 +29,9 @@ class AMQPConnection MessagePublisher::CREATE_RECORD_TYPE => MessagePublisher::CREATE_RECORD_QUEUE, MessagePublisher::PULL_QUEUE => MessagePublisher::PULL_QUEUE, MessagePublisher::POPULATE_INDEX_TYPE => MessagePublisher::POPULATE_INDEX_QUEUE, - MessagePublisher::DELETE_RECORD_TYPE => MessagePublisher::DELETE_RECORD_QUEUE + MessagePublisher::DELETE_RECORD_TYPE => MessagePublisher::DELETE_RECORD_QUEUE, + MessagePublisher::MAIN_QUEUE_TYPE => MessagePublisher::MAIN_QUEUE, + MessagePublisher::SUBTITLE_TYPE => MessagePublisher::SUBTITLE_QUEUE ]; // the corresponding worker queues and retry queues, loop queue @@ -44,7 +46,6 @@ class AMQPConnection MessagePublisher::PULL_QUEUE => MessagePublisher::LOOP_PULL_QUEUE ]; - // default message TTL in retry queue in millisecond public static $defaultFailedQueues = [ MessagePublisher::WRITE_METADATAS_TYPE => MessagePublisher::FAILED_METADATAS_QUEUE, MessagePublisher::SUBDEF_CREATION_TYPE => MessagePublisher::FAILED_SUBDEF_QUEUE, diff --git a/lib/Alchemy/Phrasea/WorkerManager/Queue/MessagePublisher.php b/lib/Alchemy/Phrasea/WorkerManager/Queue/MessagePublisher.php index c191017bf8..1a236e2a08 100644 --- a/lib/Alchemy/Phrasea/WorkerManager/Queue/MessagePublisher.php +++ b/lib/Alchemy/Phrasea/WorkerManager/Queue/MessagePublisher.php @@ -18,6 +18,12 @@ class MessagePublisher const WEBHOOK_TYPE = 'webhook'; const POPULATE_INDEX_TYPE = 'populateIndex'; const PULL_ASSETS_TYPE = 'pullAssets'; + const SUBTITLE_TYPE = 'subtitle'; + const MAIN_QUEUE_TYPE = 'mainQueue'; + + + const MAIN_QUEUE = 'main-queue'; + const SUBTITLE_QUEUE = 'subtitle-queue'; // worker queue to be consumed, when no ack , it is requeued to the retry queue const EXPORT_QUEUE = 'export-queue'; diff --git a/lib/Alchemy/Phrasea/WorkerManager/Subscriber/SubtitleSubscriber.php b/lib/Alchemy/Phrasea/WorkerManager/Subscriber/SubtitleSubscriber.php new file mode 100644 index 0000000000..bed3a8eb4c --- /dev/null +++ b/lib/Alchemy/Phrasea/WorkerManager/Subscriber/SubtitleSubscriber.php @@ -0,0 +1,88 @@ +repoWorkerJobLocator = $repoWorkerJobLocator; + $this->messagePublisher = $messagePublisher; + } + + public function onRecordAutoSubtitle(RecordAutoSubtitleEvent $event) + { + $this->repoWorkerJob = $this->getRepoWorkerJob(); + + $em = $this->repoWorkerJob->getEntityManager(); + + $data = [ + "databoxId" => $event->getRecord()->getDataboxId(), + "recordId" => $event->getRecord()->getRecordId(), + "permalinkUrl" => $event->getPermalinkUrl(), + "langageSource" => $event->getLanguageSource(), + "metaStructureId" => $event->getMetaStructId() + ]; + + $this->repoWorkerJob->reconnect(); + $em->beginTransaction(); + + try { + $workerJob = new WorkerJob(); + $workerJob + ->setType(MessagePublisher::SUBTITLE_TYPE) + ->setData($data) + ->setStatus(WorkerJob::WAITING) + ; + + $em->persist($workerJob); + $em->flush(); + + $em->commit(); + + $data['workerId'] = $workerJob->getId(); + $data['type'] = MessagePublisher::SUBTITLE_TYPE; + + $payload = [ + 'message_type' => MessagePublisher::MAIN_QUEUE_TYPE, + 'payload' => $data + ]; + + $this->messagePublisher->publishMessage($payload, MessagePublisher::MAIN_QUEUE); + } catch (\Exception $e) { + $em->rollback(); + } + } + + public static function getSubscribedEvents() + { + return [ + PhraseaEvents::RECORD_AUTO_SUBTITLE => 'onRecordAutoSubtitle', + ]; + } + + /** + * @return WorkerJobRepository + */ + private function getRepoWorkerJob() + { + $callable = $this->repoWorkerJobLocator; + + return $callable(); + } +} diff --git a/lib/Alchemy/Phrasea/WorkerManager/Worker/MainQueueWorker.php b/lib/Alchemy/Phrasea/WorkerManager/Worker/MainQueueWorker.php new file mode 100644 index 0000000000..82471f2b88 --- /dev/null +++ b/lib/Alchemy/Phrasea/WorkerManager/Worker/MainQueueWorker.php @@ -0,0 +1,49 @@ +messagePublisher = $messagePublisher; + $this->repoWorkerJob = $repoWorkerJob; + } + + public function process(array $payload) + { + + // if needed do treatement here depending on the type + $queue = null; + $messageType = ''; + + switch ($payload['type']) { + case MessagePublisher::SUBTITLE_TYPE: + $queue = MessagePublisher::SUBTITLE_QUEUE; + $messageType = $payload['type']; + unset($payload['type']); + + break; + + } + + $data = [ + 'message_type' => $messageType, + 'payload' => $payload + ]; + + if ($queue != null) { + $this->messagePublisher->publishMessage($data, $queue); + } + } +} diff --git a/lib/Alchemy/Phrasea/WorkerManager/Worker/SubtitleWorker.php b/lib/Alchemy/Phrasea/WorkerManager/Worker/SubtitleWorker.php new file mode 100644 index 0000000000..e9d66a2c66 --- /dev/null +++ b/lib/Alchemy/Phrasea/WorkerManager/Worker/SubtitleWorker.php @@ -0,0 +1,212 @@ +repoWorkerJob = $repoWorkerJob; + $this->conf = $conf; + $this->appboxLocator = $appboxLocator; + $this->logger = $logger; + } + + 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']); + + if (!$gingaBaseurl || !$gingaToken || !$gingaTranscriptFormat) { + $this->logger->error("External service Ginga not set correctly in configuration.yml"); + + return 0; + } + + /** @var WorkerJob $workerJob */ + $workerJob = $this->repoWorkerJob->find($payload['workerId']); + if ($workerJob == null) { + $this->logger->error("WorkerId not found"); + + return 0; + } + + $workerJob->setStatus(WorkerJob::RUNNING) + ->setStarted(new \DateTime('now')); + + $em = $this->repoWorkerJob->getEntityManager(); + $this->repoWorkerJob->reconnect(); + $em->persist($workerJob); + $em->flush(); + + $record = $this->getApplicationBox()->get_databox($payload['databoxId'])->get_record($payload['recordId']); + + 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(); + + try { + $response = $gingerClient->post($gingaBaseurl.'/media/', [ + 'headers' => [ + 'Authorization' => 'token '.$gingaToken + ], + 'json' => [ + 'url' => $payload['permalinkUrl'], + 'language' => $language + ] + ]); + } catch(\Exception $e) { + $this->logger->error($e->getMessage()); + $this->jobFinished($workerJob); + + return 0; + } + + if ($response->getStatusCode() !== 201) { + $this->logger->error("response status /media/ : ". $response->getStatusCode()); + $this->jobFinished($workerJob); + + 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 ($response->getStatusCode() !== 200) { + $checkStatus = false; + + break; + } + + $responseTaskBody = $response->getBody()->getContents(); + $responseTaskBody = json_decode($responseTaskBody,true); + + } while($responseTaskBody['status'] != 'SUCCESS'); + + if (!$checkStatus) { + $this->logger->error("can't check status"); + $this->jobFinished($workerJob); + + return 0; + } + + try { + $response = $gingerClient->get($gingaBaseurl.'/media/'.$responseMediaBody['media']['uuid'].'/', [ + 'headers' => [ + 'Authorization' => 'token '.$gingaToken, + 'ACCEPT' => $gingaTranscriptFormat + ], + 'query' => [ + 'language' => $language + ] + ]); + } catch (\Exception $e) { + $this->logger->error($e->getMessage()); + $this->jobFinished($workerJob); + + return 0; + } + + if ($response->getStatusCode() !== 200) { + $this->logger->error("response status /media/uuid : ". $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['metaStructureId'], + '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("Auto subtitle SUCCESS"); + } + + $this->jobFinished($workerJob); + + return 0; + } + + /** + * @return \appbox + */ + private function getApplicationBox() + { + $callable = $this->appboxLocator; + + return $callable(); + } + + private function jobFinished(WorkerJob $workerJob) + { + $workerJob->setStatus(WorkerJob::FINISHED) + ->setFinished(new \DateTime('now')); + + $em = $this->repoWorkerJob->getEntityManager(); + $this->repoWorkerJob->reconnect(); + + $em->persist($workerJob); + $em->flush(); + } +} diff --git a/lib/conf.d/configuration.yml b/lib/conf.d/configuration.yml index 4013f8a44e..fd59be26f8 100644 --- a/lib/conf.d/configuration.yml +++ b/lib/conf.d/configuration.yml @@ -319,5 +319,15 @@ workers: user: '' password: '' vhost: / + +externalservice: + ginger: + AutoSubtitling: + service_base_url: https://base.uri + token: 39c6011d + transcript_format: text/vtt + subdef_source: preview + + Console_logger_enabled_environments: [test] diff --git a/package.json b/package.json index 4d4e4caab2..dae70c0368 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "normalize-css": "^2.1.0", "npm": "^6.0.0", "npm-modernizr": "^2.8.3", - "phraseanet-production-client": "0.34.255-d", + "phraseanet-production-client": "0.34.256-d", "requirejs": "^2.3.5", "tinymce": "^4.0.28", "underscore": "^1.8.3", diff --git a/templates/web/prod/actions/Tools/videoEditor.html.twig b/templates/web/prod/actions/Tools/videoEditor.html.twig index dc2d1bb0cd..b9920c7fb2 100644 --- a/templates/web/prod/actions/Tools/videoEditor.html.twig +++ b/templates/web/prod/actions/Tools/videoEditor.html.twig @@ -236,25 +236,23 @@

@@ -262,6 +260,9 @@
+

+ {{ "prod:videoeditor:subtitleRequestTab:: Request in process" | trans }} +

@@ -354,4 +355,25 @@ overlapChapters: {% if overlapChapters != NULL %}{{ overlapChapters }}{% else %}1{% endif %}, } }; + + $('#submit-subtitle-request').on('click', function (e) { + e.preventDefault(); + console.log("auto-subtitle process"); + $.ajax({ + type: 'POST', + url: '/prod/tools/auto-subtitle/', + dataType: 'json', + 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() + }, + success: function success(data) { + console.log(data); + $('#request-status').removeClass('hide'); + } + }); + }); + diff --git a/yarn.lock b/yarn.lock index 30158d604e..2a99f187cb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7678,10 +7678,10 @@ phraseanet-common@^0.4.5-d: js-cookie "^2.1.0" pym.js "^1.3.1" -phraseanet-production-client@0.34.255-d: - version "0.34.255-d" - resolved "https://registry.yarnpkg.com/phraseanet-production-client/-/phraseanet-production-client-0.34.255-d.tgz#40e9c97ebb5fd77dd117f2d766126a94437b63ff" - integrity sha512-UQpkjhkKLPF63107y1nG+cd1wxUq2jAmbO58yrp3tJLsfQt1nGAkUfvZb9VEJ3/zbvKcjRmjnHHFDa08RcViww== +phraseanet-production-client@0.34.256-d: + version "0.34.256-d" + resolved "https://registry.yarnpkg.com/phraseanet-production-client/-/phraseanet-production-client-0.34.256-d.tgz#93a6b103ba38fc78d2b7687fec47928e2c46f363" + integrity sha512-cPHofONltBWs6IUOPEsQ02gSN+7HZv1hy9DZ98UPGpqkJG0D7gZLuNxiEVq/OFQFyYDCBTemNm/+BCRm6Abr7A== dependencies: "@mapbox/mapbox-gl-language" "^0.9.2" "@turf/turf" "^5.1.6"