mirror of
https://github.com/alchemy-fr/Phraseanet.git
synced 2025-10-23 01:43:13 +00:00
Merge branch 'master' into PHRAS-3169_Prod_video_tools_vtt_with_number
This commit is contained in:
@@ -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
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Phrasea\Core\Event\Record;
|
||||
|
||||
use Alchemy\Phrasea\Model\RecordInterface;
|
||||
|
||||
class RecordAutoSubtitleEvent extends RecordEvent
|
||||
{
|
||||
private $languageSource;
|
||||
private $metaStructId;
|
||||
private $permalinkUrl;
|
||||
|
||||
public function __construct(RecordInterface $record, $permalinkUrl, $languageSource, $metaStructId)
|
||||
{
|
||||
parent::__construct($record);
|
||||
|
||||
$this->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;
|
||||
}
|
||||
}
|
@@ -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';
|
||||
|
@@ -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');
|
||||
});
|
||||
|
@@ -16,7 +16,6 @@ class Version
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
|
||||
private $number = '4.1.1';
|
||||
|
||||
/**
|
||||
|
150
lib/Alchemy/Phrasea/Model/Entities/WorkerJob.php
Normal file
150
lib/Alchemy/Phrasea/Model/Entities/WorkerJob.php
Normal file
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Phrasea\Model\Entities;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Gedmo\Mapping\Annotation as Gedmo;
|
||||
|
||||
/**
|
||||
* @ORM\Table(name="WorkerJob", indexes={@ORM\Index(name="worker_job_type", columns={"type"})})
|
||||
* @ORM\Entity(repositoryClass="Alchemy\Phrasea\Model\Repositories\WorkerJobRepository")
|
||||
*/
|
||||
class WorkerJob
|
||||
{
|
||||
const WAITING = "waiting";
|
||||
const RUNNING = "running";
|
||||
const FINISHED = "finished";
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", name="type")
|
||||
*/
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="json_array", name="data", nullable=false)
|
||||
*/
|
||||
private $data;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", name="status")
|
||||
*/
|
||||
private $status;
|
||||
|
||||
/**
|
||||
* @Gedmo\Timestampable(on="create")
|
||||
* @ORM\Column(type="datetime")
|
||||
*/
|
||||
private $created;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="datetime", nullable=true)
|
||||
*/
|
||||
private $started;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="datetime", nullable=true)
|
||||
*/
|
||||
private $finished;
|
||||
|
||||
/**
|
||||
* @return integer
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->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;
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Phrasea\Model\Repositories;
|
||||
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
|
||||
class WorkerJobRepository extends EntityRepository
|
||||
{
|
||||
public function getEntityManager()
|
||||
{
|
||||
return parent::getEntityManager();
|
||||
}
|
||||
|
||||
public function reconnect()
|
||||
{
|
||||
if($this->_em->getConnection()->ping() === false) {
|
||||
$this->_em->getConnection()->close();
|
||||
$this->_em->getConnection()->connect();
|
||||
}
|
||||
}
|
||||
}
|
@@ -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']);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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;
|
||||
})
|
||||
|
@@ -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,
|
||||
|
@@ -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';
|
||||
|
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Phrasea\WorkerManager\Subscriber;
|
||||
|
||||
use Alchemy\Phrasea\Core\Event\Record\RecordAutoSubtitleEvent;
|
||||
use Alchemy\Phrasea\Core\PhraseaEvents;
|
||||
use Alchemy\Phrasea\Model\Entities\WorkerJob;
|
||||
use Alchemy\Phrasea\Model\Repositories\WorkerJobRepository;
|
||||
use Alchemy\Phrasea\WorkerManager\Queue\MessagePublisher;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
class SubtitleSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
private $messagePublisher;
|
||||
|
||||
/** @var WorkerJobRepository $repoWorkerJob*/
|
||||
private $repoWorkerJob;
|
||||
|
||||
/** @var callable */
|
||||
private $repoWorkerJobLocator;
|
||||
|
||||
public function __construct(callable $repoWorkerJobLocator, MessagePublisher $messagePublisher)
|
||||
{
|
||||
$this->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();
|
||||
}
|
||||
}
|
49
lib/Alchemy/Phrasea/WorkerManager/Worker/MainQueueWorker.php
Normal file
49
lib/Alchemy/Phrasea/WorkerManager/Worker/MainQueueWorker.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Phrasea\WorkerManager\Worker;
|
||||
|
||||
use Alchemy\Phrasea\Model\Repositories\WorkerJobRepository;
|
||||
use Alchemy\Phrasea\WorkerManager\Queue\MessagePublisher;
|
||||
|
||||
class MainQueueWorker implements WorkerInterface
|
||||
{
|
||||
private $messagePublisher;
|
||||
|
||||
private $repoWorkerJob;
|
||||
|
||||
public function __construct(
|
||||
MessagePublisher $messagePublisher,
|
||||
WorkerJobRepository $repoWorkerJob
|
||||
)
|
||||
{
|
||||
$this->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);
|
||||
}
|
||||
}
|
||||
}
|
212
lib/Alchemy/Phrasea/WorkerManager/Worker/SubtitleWorker.php
Normal file
212
lib/Alchemy/Phrasea/WorkerManager/Worker/SubtitleWorker.php
Normal file
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Phrasea\WorkerManager\Worker;
|
||||
|
||||
use Alchemy\Phrasea\Core\Configuration\PropertyAccess;
|
||||
use Alchemy\Phrasea\Model\Entities\WorkerJob;
|
||||
use Alchemy\Phrasea\Model\Repositories\WorkerJobRepository;
|
||||
use GuzzleHttp\Client;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class SubtitleWorker implements WorkerInterface
|
||||
{
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
private $appboxLocator;
|
||||
|
||||
private $logger;
|
||||
private $conf;
|
||||
|
||||
/** @var WorkerJobRepository $repoWorkerJob*/
|
||||
private $repoWorkerJob;
|
||||
|
||||
public function __construct(WorkerJobRepository $repoWorkerJob, PropertyAccess $conf, callable $appboxLocator, LoggerInterface $logger)
|
||||
{
|
||||
$this->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();
|
||||
}
|
||||
}
|
@@ -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]
|
||||
|
||||
|
@@ -244,9 +244,9 @@
|
||||
<p class="item">
|
||||
<label>{{ "prod:videoeditor:subtitleRequestTab:label:: Source Audio language" | trans }}</label>
|
||||
<select name="subtitle_language_source" id="subtitle_language_source">
|
||||
<option value="fr">Fr</option>
|
||||
<option value="en">En</option>
|
||||
<option value="de">De</option>
|
||||
{% for videoTextTrackField in videoTextTrackFields %}
|
||||
<option value="{{ videoTextTrackField.meta_struct_id}}">{{ videoTextTrackField.label}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</p>
|
||||
<p class="item">
|
||||
@@ -354,4 +354,24 @@
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
Reference in New Issue
Block a user