re-working worker flow

This commit is contained in:
shimon
2023-05-17 11:37:46 +03:00
parent e28cbce5ee
commit e0f4673574
6 changed files with 283 additions and 374 deletions
-22
View File
@@ -3640,28 +3640,6 @@ $collections = [
'array' => false,
'filters' => [],
],
[
'$id' => 'previewId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'previewInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'size',
'type' => Database::VAR_INTEGER,
+28 -56
View File
@@ -108,12 +108,6 @@ App::post('/v1/videos')
]));
});
(new Video())
->setAction('preview')
->setProject($project)
->setVideo($video)
->trigger();
(new Video())
->setAction('timeline')
->setProject($project)
@@ -328,50 +322,6 @@ App::get('/v1/videos/:videoId/timeline')
->send($data);
});
App::post('/v1/videos/:videoId/preview')
->desc('Create Video preview from a given point in duration (second)')
->groups(['api', 'videos'])
->label('scope', 'videos.write')
->label('audits.event', 'preview.create')
->label('audits.resource', 'video/{response.$id}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'videos')
->label('sdk.method', 'createPreview')
->label('sdk.description', '/docs/references/videos/create-preview.md') // TODO: Create markdown
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_VIDEO)
->param('videoId', '', new UID(), 'Video unique ID.')
->param('second', 5, new Numeric(), 'Second from video duration.')
->inject('request')
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('mode')
->action(action: function (string $videoId, int $second, Request $request, Response $response, Document $project, Database $dbForProject, string $mode) {
$video = Authorization::skip(fn() => $dbForProject->getDocument('videos', $videoId));
if ($video->isEmpty()) {
throw new Exception(Exception::VIDEO_NOT_FOUND);
}
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode);
$range = new Range(1, ($video['duration'] / 1000), Validator::TYPE_INTEGER);
if (!$range->isValid($second)) {
throw new Exception(Exception::VIDEO_SECOND_OUT_OF_RANGE);
}
(new Video())
->setAction('preview')
->setProject($project)
->setVideo($video)
->setSecond($second)
->trigger();
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($video, Response::MODEL_VIDEO);
});
App::get('/v1/videos/:videoId/preview/:previewId')
->desc('Get video file preview')
@@ -466,9 +416,10 @@ App::post('/v1/videos/:videoId/subtitles')
->param('default', false, new Boolean(true), 'Default subtitle.')
->inject('request')
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('mode')
->action(action: function (string $videoId, string $bucketId, string $fileId, string $name, string $code, bool $default, Request $request, Response $response, Database $dbForProject, string $mode) {
->action(action: function (string $videoId, string $bucketId, string $fileId, string $name, string $code, bool $default, Request $request, Response $response, Document $project, Database $dbForProject, string $mode) {
$video = Authorization::skip(fn() => $dbForProject->getDocument('videos', $videoId));
@@ -496,6 +447,13 @@ App::post('/v1/videos/:videoId/subtitles')
'default' => $default,
])));
(new Video())
->setAction('subtitle')
->setProject($project)
->setVideo($video)
->setSubtitle($subtitle)
->trigger();
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($subtitle, Response::MODEL_SUBTITLE);
});
@@ -553,9 +511,10 @@ App::patch('/v1/videos/:videoId/subtitles/:subtitleId')
->param('code', '', new Text(3), 'Subtitle ISO 639-2 three letters alpha code.')
->param('default', false, new Boolean(true), 'Default subtitle.')
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('mode')
->action(action: function (string $subtitleId, string $videoId, string $bucketId, string $fileId, string $name, string $code, bool $default, Response $response, Database $dbForProject, string $mode) {
->action(action: function (string $subtitleId, string $videoId, string $bucketId, string $fileId, string $name, string $code, bool $default, Response $response, Document $project, Database $dbForProject, string $mode) {
$video = Authorization::skip(fn() => $dbForProject->getDocument('videos', $videoId));
@@ -597,6 +556,13 @@ App::patch('/v1/videos/:videoId/subtitles/:subtitleId')
$subtitle = Authorization::skip(fn() => $dbForProject->updateDocument('videos_subtitles', $subtitle->getId(), $subtitle));
(new Video())
->setAction('subtitle')
->setProject($project)
->setVideo($video)
->setSubtitle($subtitle)
->trigger();
$response->dynamic($subtitle, Response::MODEL_SUBTITLE);
});
@@ -639,6 +605,14 @@ App::delete('/v1/videos/:videoId/subtitles/:subtitleId')
throw new Exception('Failed to remove video subtitle', 500, Exception::GENERAL_SERVER_ERROR);
}
$segments = Authorization::skip(fn() => $dbForProject->find('videos_subtitles_segments', [
Query::equal('subtitleInternalId', [$subtitle->getInternalId()]),
]));
foreach ($segments as $segment) {
$dbForProject->deleteDocument('videos_subtitles_segments', $segment->getId());
}
$response->noContent();
});
@@ -878,8 +852,6 @@ App::get('/v1/videos/:videoId/outputs/:output/:fileName')
Query::equal('status', ['ready']),
]));
var_dump('$dbTime = ' . microtime(true) - $dbStartTime);
$_renditions = $_subtitles = [];
if ($output === 'hls') {
@@ -1157,7 +1129,7 @@ App::get('/v1/videos/:videoId/outputs/:output/subtitles/:subtitleId/:fileName')
}
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode);
//var_dump('$subtitleId=',$subtitleId);
$subtitle = Authorization::skip(fn () => $dbForProject->findOne('videos_subtitles', [
Query::equal('_uid', [$subtitleId]),
Query::equal('status', ['ready']),
@@ -1176,7 +1148,7 @@ App::get('/v1/videos/:videoId/outputs/:output/subtitles/:subtitleId/:fileName')
$segments = Authorization::skip(fn () => $dbForProject->find('videos_subtitles_segments', [
Query::equal('subtitleId', [$subtitleId]),
]));
//var_dump('$segments=',$segments);
if (empty($segments)) {
throw new Exception(Exception::VIDEO_SUBTITLE_SEGMENT_NOT_FOUND);
}
+1 -1
View File
@@ -907,7 +907,7 @@ App::setResource('project', function ($dbForConsole, $request, $console) {
/** @var Utopia\Database\Document $console */
$projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', 'console'));
$projectId = '645905a0594ef29a8680';
$projectId = '64635cdd5f809d98d3ad';
if ($projectId === 'console') {
return $console;
+231 -274
View File
@@ -54,6 +54,7 @@ class VideosV1 extends Worker
private string $renditionName;
private Database $database;
private Document $video;
private Document $subtitle;
private string $action;
private Document $profile;
private Document $project;
@@ -61,7 +62,7 @@ class VideosV1 extends Worker
private Document $bucket;
private FFProbe $ffprobe;
private FFMpeg $ffmpeg;
private bool $isVideo = false;
public function getName(): string
{
@@ -70,9 +71,11 @@ class VideosV1 extends Worker
public function init(): void
{
$this->video = new Document($this->args['video'] ?? []);
$this->profile = new Document($this->args['profile'] ?? []);
$this->project = new Document($this->args['project'] ?? []);
$this->video = new Document($this->args['video'] ?? []);
$this->profile = new Document($this->args['profile'] ?? []);
$this->project = new Document($this->args['project'] ?? []);
$this->subtitle = new Document($this->args['subtitle'] ?? []);
$this->action = $this->args['action'];
$this->basePath .= uniqid();
$this->inDir = $this->basePath . '/in/';
@@ -92,172 +95,22 @@ class VideosV1 extends Worker
$path = basename($this->file->getAttribute('path'));
$this->inPath = $this->inDir . $path;
/**
* Write original asset to tmp
*/
$result = $this->write($this->project, $this->file);
if (empty($result)) {
console::error('Storage transfer error');
}
$this->renditionName = $this->profile->getAttribute('width')
. 'X' . $this->profile->getAttribute('height')
. '@' . ($this->profile->getAttribute('videoBitRate') + $this->profile->getAttribute('audioBitRate'));
/**
* FFMpeg init
*/
$this->ffprobe = FFProbe::create();
$this->ffmpeg = FFMpeg::create([
'timeout' => 0,
'ffmpeg.threads' => 4
]);
if (!$this->ffprobe->isValid($this->inPath)) {
console::error('Not an valid Video file "' . $this->inPath . '"');
}
if (empty($this->video->getAttribute('duration'))) {
/**
* Original asset metadata
*/
$mediaInfo = new MediaInfo();
$mediaInfoContainer = $mediaInfo->getInfo($this->inPath);
$general = $mediaInfoContainer->getGeneral();
$this->video
->setAttribute('duration', $general->has('duration') ? $general->get('duration')->getMilliseconds() : 0)
->setAttribute('format', $general->has('format') ? $general->get('format')->getShortName() : '');
foreach ($mediaInfoContainer->getVideos() ?? [] as $video) {
$this->isVideo = true;
$this->video
->setAttribute('height', $video->has('height') ? $video->get('height')->getAbsoluteValue() : 0)
->setAttribute('width', $video->has('width') ? $video->get('width')->getAbsoluteValue() : 0)
->setAttribute('aspectRatio', $video->has('display_aspect_ratio') ? $video->get('display_aspect_ratio')->getTextValue() : '')
->setAttribute('videoFormat', $video->has('format') ? $video->get('format')->getShortName() : '')
->setAttribute('videoFormatProfile', $video->has('format_profile') ? $video->get('format_profile') : '')
->setAttribute('videoFrameRate', $video->has('frame_rate') ? strval($video->get('frame_rate')->getAbsoluteValue()) : '')
->setAttribute('videoFrameRateMode', $video->has('frame_rate_mode') ? $video->get('frame_rate_mode')->getFullName() : '')
->setAttribute('videoBitRate', $video->has('bit_rate') ? $video->get('bit_rate')->getAbsoluteValue() : 0);
}
foreach ($mediaInfoContainer->getAudios() ?? [] as $audio) {
$this->video
->setAttribute('audioFormat', $audio->has('format') ? strval($audio->get('format')->getShortName()) : '')
->setAttribute('audioSampleRate', $audio->has('sampling_rate') ? strval($audio->get('sampling_rate')->getAbsoluteValue()) : '')
->setAttribute('audioBitRate', $audio->has('bit_rate') ? $audio->get('bit_rate')->getAbsoluteValue() : 0);
}
console::info('Input video id: ' . $this->video->getId() . PHP_EOL .
'Input name: ' . $this->file->getAttribute('name') . PHP_EOL .
'Input width: ' . $this->video->getAttribute('width') . ' px' . PHP_EOL .
'Input height: ' . $this->video->getAttribute('height') . ' px' . PHP_EOL .
'Input duration: ' . ($this->video->getAttribute('duration') / 1000) . ' Sec' . PHP_EOL .
'Input size: ' . ($this->video->getAttribute('size') / 1024 / 1024) . ' MiB' . PHP_EOL .
'Input videoBitRate: ' . ($this->video->getAttribute('videoBitRate') / 1000) . ' kb/s' . PHP_EOL .
'Input audioBitRate: ' . ($this->video->getAttribute('audioBitRate') / 1000) . ' kb/s' . PHP_EOL);
$this->database->updateDocument(
'videos',
$this->video->getId(),
new document(
array_filter((array)$this->video, fn($value) => !is_null($value))
),
);
}
$this->video->getAttribute('videoBitRate');
$media = $this->ffmpeg->open($this->inPath);
match ($this->action) {
'preview' => $this->createPreview($media),
'timeline' => $this->createTimeLine($media),
default => $this->createOutput($media),
'timeline' => $this->createTimeLine(),
'subtitle' => $this->createSubtitle(),
default => $this->createOutput(),
};
unset($media);
}
/**
* @throws Authorization
* @throws Throwable
* @throws Structure
*/
private function createPreview($media): void
{
if (!$this->isVideo) {
return;
}
console::info('Creating preview image from second ' . $this->args['second']);
$path = $this->getVideoDevice($this->project->getId())->getPath($this->video->getId()) . '/preview/';
$name = 'preview.jpg';
$media
->filters()
->resize(new \FFMpeg\Coordinate\Dimension($this->video->getAttribute('width'), $this->video->getAttribute('height')))
->synchronize();
$media
->frame(\FFMpeg\Coordinate\TimeCode::fromSeconds($this->args['second']))
->save($this->outDir . $name);
/**
* Upload preview
*/
console::info('Uploading ' . $name);
$this->getVideoDevice($this->project->getId())->write(
$path . $name,
(new Local('/'))->read($this->outDir . $name),
mime_content_type($this->outDir . $name)
);
$preview = $this->database->findOne('videos_previews', [
Query::equal('videoInternalId', [$this->video->getInternalId()]),
Query::equal('type', [$this->action]),
Query::equal('name', [$name]),
]);
if (empty($preview)) {
$preview = $this->database->createDocument('videos_previews', new Document([
'videoId' => $this->video->getId(),
'videoInternalId' => $this->video->getInternalId(),
'type' => $this->action,
'name' => $name,
'path' => $path,
'second' => $this->args['second'],
]));
$this->video->setAttribute('previewId', $preview->getId());
$this->video->setAttribute('previewInternalId', $preview->getInternalId());
$this->database->updateDocument(
'videos',
$this->video->getId(),
new document(
array_filter((array)$this->video, fn($value) => !is_null($value))
),
);
} else {
$this->database->updateDocument(
'videos_previews',
$preview->getId(),
$preview->setAttribute('second', $this->args['second'])
);
/**
* Clean preview cache
*/
(new Delete())
->setType(DELETE_TYPE_CACHE_BY_RESOURCE)
->setResource('preview/' . $preview->getId())
->trigger();
}
}
/**
* Create timeline
*
* @throws Authorization
* @throws Structure
* @throws Throwable
*/
private function createTimeLine($media): void
private function createTimeLine(): void
{
/*
@@ -270,7 +123,14 @@ class VideosV1 extends Worker
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
* Text Domain: playerjs
*/
if (!$this->isVideo) {
$this->download($this->project, $this->file);
if (empty($this->video->getAttribute('duration'))) {
$this->createInfo();
}
if (empty($this->video->getAttribute('videoBitRate'))) {
return;
}
@@ -349,7 +209,7 @@ class VideosV1 extends Worker
console::info('Uploading timeline vtt');
/**
* Upload sprites*
* Upload sprites
*/
$dir = new DirectoryIterator($this->outDir);
foreach ($dir as $fileinfo) {
@@ -367,44 +227,106 @@ class VideosV1 extends Worker
}
/**
* Create subtitle
*
* @throws Authorization
* @throws Structure
* @throws Throwable
*/
private function createSubtitle(): void
{
if (empty($this->video->getAttribute('duration'))) {
$this->download($this->project, $this->file);
$this->createInfo();
}
$segments = $this->database->find('videos_subtitles_segments', [
Query::equal('subtitleInternalId', [$this->subtitle->getInternalId()]),
]);
foreach ($segments as $segment) {
$this->database->deleteDocument('videos_subtitles_segments', $segment->getId());
}
$this->subtitle->setAttribute('status', self::STATUS_START);
$this->database->updateDocument('videos_subtitles', $this->subtitle->getId(), $this->subtitle);
$bucket = $this->database->getDocument('buckets', $this->subtitle->getAttribute('bucketId'));
$file = $this->database->getDocument('bucket_' . $bucket->getInternalId(), $this->subtitle->getAttribute('fileId'));
$path = basename($file->getAttribute('path'));
$this->download($this->project, $file);
$ext = pathinfo($path, PATHINFO_EXTENSION);
$subtitlePath = $this->inDir . $this->subtitle->getId() . '.vtt';
if ($ext === 'srt') {
$srt = new SubripFile($this->inDir . $path);
$srt->convertTo('webvtt')->save($subtitlePath);
}
$path = $this->getVideoDevice($this->project->getId())->getPath($this->video->getId()) . '/subtitles/';
$this->database->createDocument('videos_subtitles_segments', new Document([
'subtitleId' => $this->subtitle->getId(),
'subtitleInternalId' => $this->subtitle->getInternalId(),
'fileName' => $this->subtitle->getId() . '.vtt',
'path' => $path,
'duration' => (string)number_format(($this->video->getAttribute('duration') / 1000), 1),
]));
/**
* Upload subtitle
*/
console::info('Uploading ' . $this->subtitle->getId() . '.vtt');
$this->getVideoDevice($this->project->getId())->write(
$path . $this->subtitle->getId() . '.vtt',
(new Local('/'))->read($this->inDir . $this->subtitle->getId() . '.vtt'),
'text/vtt'
);
$this->subtitle->setAttribute('targetDuration', (string)number_format(($this->video->getAttribute('duration') / 1000), 1));
$this->subtitle->setAttribute('status', self::STATUS_READY);
$this->subtitle->setAttribute('path', $path);
$this->database->updateDocument('videos_subtitles', $this->subtitle->getId(), $this->subtitle);
}
/**
* Create output
*
* @throws Authorization
* @throws Exception
* @throws Throwable
* @throws Structure
*/
private function createOutput($media): void
private function createOutput(): void
{
$subs = [];
$subtitles = $this->database->find('videos_subtitles', [
Query::equal('videoInternalId', [$this->video->getInternalId()]),
Query::equal('status', [''])
$this->download($this->project, $this->file);
if (empty($this->video->getAttribute('duration'))) {
$this->createInfo();
}
/**
* FFMpeg init
*/
$this->ffprobe = FFProbe::create();
$this->ffmpeg = FFMpeg::create([
'timeout' => 0,
'ffmpeg.threads' => 4
]);
foreach ($subtitles as $subtitle) {
$subtitle->setAttribute('status', self::STATUS_START);
$this->database->updateDocument('videos_subtitles', $subtitle->getId(), $subtitle);
$bucket = $this->database->getDocument('buckets', $subtitle->getAttribute('bucketId'));
$file = $this->database->getDocument('bucket_' . $bucket->getInternalId(), $subtitle->getAttribute('fileId'));
$path = basename($file->getAttribute('path'));
$this->write($this->project, $file);
console::info('Transferring subtitle from storage to ' . $this->inDir);
$ext = pathinfo($path, PATHINFO_EXTENSION);
$subtitlePath = $this->inDir . $subtitle->getId() . '.vtt';
if ($ext === 'srt') {
$srt = new SubripFile($this->inDir . $path);
$srt->convertTo('webvtt')->save($subtitlePath);
}
$subs[] = [
'name' => $subtitle->getAttribute('name'),
'code' => $subtitle->getAttribute('code'),
'path' => $subtitlePath,
];
if (!$this->ffprobe->isValid($this->inPath)) {
console::error('Not an valid Video file "' . $this->inPath . '"');
}
$media = $this->ffmpeg->open($this->inPath);
$this->renditionName = $this->profile->getAttribute('width')
. 'X' . $this->profile->getAttribute('height')
. '@' . ($this->profile->getAttribute('videoBitRate') + $this->profile->getAttribute('audioBitRate'));
$query = $this->database->createDocument('videos_renditions', new Document([
'videoId' => $this->video->getId(),
'videoInternalId' => $this->video->getInternalId(),
@@ -422,9 +344,6 @@ class VideosV1 extends Worker
$this->send($query);
$renditionRootPath = $this->getVideoDevice($this->project->getId())->getPath($this->video->getId()) . '/';
$renditionPath = $renditionRootPath . $this->renditionName . '-' . $query->getId() . '/';
try {
$representation = (new Representation())
->setKiloBitRate($this->profile->getAttribute('videoBitRate'))
@@ -448,12 +367,16 @@ class VideosV1 extends Worker
}
});
$this->transcode($media, $format, $representation, $subs);
$this->transcode($media, $format, $representation);
unset($media);
//exec('/usr/bin/ffmpeg -y -i /usr/src/code/tests/tmp/637f59c88f9ff0fe3b1f/637e1b82aeab8980400e/in/637f59ab5bce0e36d05e.mp4 -c:v libx264 -c:a aac -bf 1 -keyint_min 25 -g 250 -sc_threshold 40 -use_timeline 0 -use_template 0 -seg_duration 10 -hls_playlist 0 -f dash -dn -sn -vf scale=iw:-2:force_original_aspect_ratio=increase,setsar=1:1 -b_strategy 1 -bf 3 -force_key_frames "expr:gte(t,n_forced*2)" -map 0 -s:v:0 1024x576 -b:v:0 2538k -b:a:0 128k -strict -2 -threads 12 /usr/src/code/tests/tmp/637f59c88f9ff0fe3b1f/637e1b82aeab8980400e/out/637f59c88f9ff0fe3b1f.mpd2>&1', $o, $v);
//var_dump($o);
//var_dump($v);
$path = $this->getVideoDevice($this->project->getId())->getPath($this->video->getId()) . '/' . $this->renditionName . '-' . $query->getId() . '/';
if ($this->args['output'] === self::OUTPUT_HLS) {
$streams = $this->getHlsSegmentsUrls($this->outDir . 'master.m3u8');
foreach ($streams as $stream) {
@@ -465,7 +388,7 @@ class VideosV1 extends Worker
'renditionInternalId' => $query->getInternalId(),
'streamId' => (int)$stream['id'],
'fileName' => $segment['fileName'],
'path' => $renditionPath,
'path' => $path,
'duration' => $segment['duration'],
]));
}
@@ -484,7 +407,7 @@ class VideosV1 extends Worker
'renditionInternalId' => $query->getInternalId(),
'streamId' => $segment['streamId'],
'fileName' => $segment['fileName'],
'path' => $renditionPath,
'path' => $path,
'isInit' => $segment['isInit'],
]));
}
@@ -500,44 +423,20 @@ class VideosV1 extends Worker
$this->database->updateDocument('videos_renditions', $query->getId(), $query);
$this->send($query, 'update');
foreach ($subtitles ?? [] as $subtitle) {
if ($this->args['output'] === self::OUTPUT_HLS) {
$m3u8 = $this->getSegments($this->outPath . '_subtitles_' . $subtitle['code'] . '.m3u8');
foreach ($m3u8['segments'] ?? [] as $segment) {
$this->database->createDocument('videos_subtitles_segments', new Document([
'subtitleId' => $subtitle->getId(),
'subtitleInternalId' => $subtitle->getInternalId(),
'fileName' => $segment['fileName'],
'path' => $renditionRootPath . 'subtitles/',
'duration' => $segment['duration'],
]));
}
$subtitle->setAttribute('targetDuration', $m3u8['targetDuration']);
} else {
$this->getFilesDevice($this->project->getId())->transfer($this->inDir . $subtitle->getId() . '.vtt', $this->outDir . $subtitle->getId() . '.vtt', $this->getFilesDevice($this->project->getId()));
}
$subtitle->setAttribute('status', self::STATUS_READY);
$subtitle->setAttribute('path', $renditionRootPath . 'subtitles/');
$this->database->updateDocument('videos_subtitles', $subtitle->getId(), $subtitle);
}
console::info('Rendition ' . $query->getId() . ' conversion, done');
/** Upload **/
/**
* Upload
*/
$dir = new DirectoryIterator($this->outDir);
foreach ($dir as $fileinfo) {
if (!$fileinfo->isDot()) {
$data = (new Local('/'))->read($this->outDir . $fileinfo->getFilename());
$to = $renditionPath;
if (str_contains($fileinfo->getFilename(), "_subtitles_") || str_contains($fileinfo->getFilename(), ".vtt")) {
$to = $renditionRootPath . 'subtitles/';
}
console::info('Uploading ' . $fileinfo->getFilename());
$this->getVideoDevice($this->project->getId())->write(
$to . $fileinfo->getFilename(),
$path . $fileinfo->getFilename(),
$data,
mime_content_type($this->outDir . $fileinfo->getFilename())
);
@@ -545,7 +444,7 @@ class VideosV1 extends Worker
if ($fileinfo->key() === 0) {
$query->setAttribute('progress', '100');
$query->setAttribute('status', self::STATUS_UPLOADING);
$query->setAttribute('path', $renditionPath);
$query->setAttribute('path', $path);
$this->database->updateDocument('videos_renditions', $query->getId(), $query);
$this->send($query, 'update');
}
@@ -572,14 +471,70 @@ class VideosV1 extends Worker
}
}
/**
* Create media Info
*
* @throws Authorization
* @throws Structure
* @throws Throwable
*/
private function createInfo(): void
{
$mediaInfo = new MediaInfo();
$mediaInfoContainer = $mediaInfo->getInfo($this->inPath);
$general = $mediaInfoContainer->getGeneral();
$this->video
->setAttribute('duration', $general->has('duration') ? $general->get('duration')->getMilliseconds() : 0)
->setAttribute('format', $general->has('format') ? $general->get('format')->getShortName() : '');
foreach ($mediaInfoContainer->getVideos() ?? [] as $video) {
$this->video
->setAttribute('height', $video->has('height') ? $video->get('height')->getAbsoluteValue() : 0)
->setAttribute('width', $video->has('width') ? $video->get('width')->getAbsoluteValue() : 0)
->setAttribute('aspectRatio', $video->has('display_aspect_ratio') ? $video->get('display_aspect_ratio')->getTextValue() : '')
->setAttribute('videoFormat', $video->has('format') ? $video->get('format')->getShortName() : '')
->setAttribute('videoFormatProfile', $video->has('format_profile') ? $video->get('format_profile') : '')
->setAttribute('videoFrameRate', $video->has('frame_rate') ? strval($video->get('frame_rate')->getAbsoluteValue()) : '')
->setAttribute('videoFrameRateMode', $video->has('frame_rate_mode') ? $video->get('frame_rate_mode')->getFullName() : '')
->setAttribute('videoBitRate', $video->has('bit_rate') ? $video->get('bit_rate')->getAbsoluteValue() : 0);
}
foreach ($mediaInfoContainer->getAudios() ?? [] as $audio) {
$this->video
->setAttribute('audioFormat', $audio->has('format') ? strval($audio->get('format')->getShortName()) : '')
->setAttribute('audioSampleRate', $audio->has('sampling_rate') ? strval($audio->get('sampling_rate')->getAbsoluteValue()) : '')
->setAttribute('audioBitRate', $audio->has('bit_rate') ? $audio->get('bit_rate')->getAbsoluteValue() : 0);
}
console::info('Input video id: ' . $this->video->getId() . PHP_EOL .
'Input name: ' . $this->file->getAttribute('name') . PHP_EOL .
'Input width: ' . $this->video->getAttribute('width') . ' px' . PHP_EOL .
'Input height: ' . $this->video->getAttribute('height') . ' px' . PHP_EOL .
'Input duration: ' . ($this->video->getAttribute('duration') / 1000) . ' Sec' . PHP_EOL .
'Input size: ' . ($this->video->getAttribute('size') / 1024 / 1024) . ' MiB' . PHP_EOL .
'Input videoBitRate: ' . ($this->video->getAttribute('videoBitRate') / 1000) . ' kb/s' . PHP_EOL .
'Input audioBitRate: ' . ($this->video->getAttribute('audioBitRate') / 1000) . ' kb/s' . PHP_EOL);
$this->database->updateDocument(
'videos',
$this->video->getId(),
new document(
array_filter((array)$this->video, fn($value) => !is_null($value))
),
);
}
/**
* Transcode
*
* @param $media Media
* @param $format StreamFormat
* @param $representation Representation
* @param array $subtitles
* @return void
*/
private function transcode(Media $media, StreamFormat $format, Representation $representation, array $subtitles): void
private function transcode(Media $media, StreamFormat $format, Representation $representation): void
{
$additionalParams = [
@@ -602,14 +557,8 @@ class VideosV1 extends Worker
return;
}
$hls = $media->hls();
foreach ($subtitles as $subtitle) {
$sub = new HLSSubtitle($subtitle['path'], $subtitle['name'], $subtitle['code']);
$sub->default();
$hls->subtitle($sub);
}
$hls->setFormat($format)
$media->hls()
->setFormat($format)
->setHlsTime($segmentSize)
->addRepresentation($representation)
->setAdditionalParams($additionalParams)
@@ -617,6 +566,8 @@ class VideosV1 extends Worker
}
/**
* Get HLS|DASH segments
*
* @param string $path
* @return array
*/
@@ -685,6 +636,8 @@ class VideosV1 extends Worker
/**
* Get HLS segments urls
*
* @param string $path
* @return array
*/
@@ -757,11 +710,11 @@ class VideosV1 extends Worker
}
/**
* Send realtime updates
* Send
*
* @param string $action
* @param Document $payload
* @throws Exception
* @throws Exception|\Exception
*/
private function send(Document $payload, string $action = 'create')
{
@@ -790,57 +743,61 @@ class VideosV1 extends Worker
);
}
/**
* Write files
* Download
*
* @param $project Document
* @param $file Document
* @return boolean
* @throws \Exception
*/
private function write(Document $project, Document $file): bool
private function download(Document $project, Document $file)
{
console::info('Writing file to ' . $this->inDir);
$fullPath = $file->getAttribute('path');
$path = basename($file->getAttribute('path'));
console::info('Downloading file: ' . $path . ' to local storage ' . $this->inDir);
$local = new Local('/');
if (
!empty($file->getAttribute('openSSLCipher')) ||
$file->getAttribute('algorithm', 'none') !== 'none'
) {
$data = $this->getFilesDevice($project->getId())->read($fullPath);
if (!empty($file->getAttribute('openSSLCipher'))) {
$data = OpenSSL::decrypt(
$data,
$file->getAttribute('openSSLCipher'),
App::getEnv('_APP_OPENSSL_KEY_V' . $file->getAttribute('openSSLVersion')),
0,
\hex2bin($file->getAttribute('openSSLIV')),
\hex2bin($file->getAttribute('openSSLTag'))
);
}
try {
if (
!empty($file->getAttribute('openSSLCipher')) ||
$file->getAttribute('algorithm', 'none') !== 'none'
) {
$data = $this->getFilesDevice($project->getId())->read($fullPath);
if (!empty($file->getAttribute('openSSLCipher'))) {
$data = OpenSSL::decrypt(
$data,
$file->getAttribute('openSSLCipher'),
App::getEnv('_APP_OPENSSL_KEY_V' . $file->getAttribute('openSSLVersion')),
0,
\hex2bin($file->getAttribute('openSSLIV')),
\hex2bin($file->getAttribute('openSSLTag'))
);
}
$algorithm = $file->getAttribute('algorithm', 'none');
switch ($algorithm) {
case 'zstd':
$compressor = new Zstd();
$data = $compressor->decompress($data);
break;
case 'gzip':
$compressor = new GZIP();
$data = $compressor->decompress($data);
break;
$algorithm = $file->getAttribute('algorithm', 'none');
switch ($algorithm) {
case 'zstd':
$compressor = new Zstd();
$data = $compressor->decompress($data);
break;
case 'gzip':
$compressor = new GZIP();
$data = $compressor->decompress($data);
break;
}
$local->
write($this->inDir . $path, $data, $file->getAttribute('mimeType'));
} else {
$this->getFilesDevice(
$project->getId()
)->transfer($fullPath, $this->inDir . $path, $local);
}
$result = $local
->write($this->inDir . $path, $data, $file->getAttribute('mimeType'));
} else {
$result = $this->getFilesDevice(
$project->getId()
)->transfer($fullPath, $this->inDir . $path, $local);
} catch (\Throwable $th) {
console::error($th->getMessage() . ' on file: ' . $th->getFile() . ' line: ' . $th->getLine);
}
return $result;
}
private function cleanup(): bool
Generated
+8 -11
View File
@@ -135,7 +135,7 @@
"source": {
"type": "git",
"url": "https://github.com/shimonewman/PHP-FFmpeg-video-streaming.git",
"reference": "0d6013d6f69483fa03f4faef0b53998eef74854d"
"reference": "12d0871597d495d8741d457014144146a0c11018"
},
"require": {
"php": "^7.2 || ^8.0",
@@ -209,7 +209,7 @@
"video-streaming",
"video-streaming"
],
"time": "2023-05-01T06:57:01+00:00"
"time": "2023-05-07T11:00:25+00:00"
},
{
"name": "appwrite/php-clamav",
@@ -731,16 +731,16 @@
},
{
"name": "guzzlehttp/guzzle",
"version": "7.5.1",
"version": "7.6.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "b964ca597e86b752cd994f27293e9fa6b6a95ed9"
"reference": "733dd89533dd371a0987172727df15f500dab0ef"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/b964ca597e86b752cd994f27293e9fa6b6a95ed9",
"reference": "b964ca597e86b752cd994f27293e9fa6b6a95ed9",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/733dd89533dd371a0987172727df15f500dab0ef",
"reference": "733dd89533dd371a0987172727df15f500dab0ef",
"shasum": ""
},
"require": {
@@ -771,9 +771,6 @@
"bamarni-bin": {
"bin-links": true,
"forward-command": false
},
"branch-alias": {
"dev-master": "7.5-dev"
}
},
"autoload": {
@@ -839,7 +836,7 @@
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
"source": "https://github.com/guzzle/guzzle/tree/7.5.1"
"source": "https://github.com/guzzle/guzzle/tree/7.6.0"
},
"funding": [
{
@@ -855,7 +852,7 @@
"type": "tidelift"
}
],
"time": "2023-04-17T16:30:08+00:00"
"time": "2023-05-14T11:23:39+00:00"
},
{
"name": "guzzlehttp/promises",
+15 -10
View File
@@ -9,7 +9,7 @@ class Video extends Event
{
protected ?Document $video = null;
protected ?Document $profile = null;
protected int $second = 5;
protected ?Document $subtitle = null;
protected string $output = '';
protected string $action = '';
@@ -111,26 +111,26 @@ class Video extends Event
}
/**
* Sets second from duration.
* Sets subtitle.
*
* @param int $second
* @param Document $subtitle
* @return self
*/
public function setSecond(int $second): self
public function setSubtitle(Document $subtitle): self
{
$this->second = $second;
$this->subtitle = $subtitle;
return $this;
}
/**
* Returns second from duration.
* Returns Subtitle.
*
* @return int
* @return null|Document
*/
public function getSecond(): int
public function getSubtitle(): ?Document
{
return $this->second;
return $this->subtitle;
}
@@ -147,9 +147,14 @@ class Video extends Event
'project' => $this->project,
'user' => $this->user,
'video' => $this->video,
'second' => $this->getSecond(),
];
if (!empty($this->getSubtitle())) {
$keys = array_merge($keys, [
'subtitle' => $this->getSubtitle(),
]);
}
if (!empty($this->getOutput())) {
$keys = array_merge($keys, [
'output' => $this->getOutput(),