This commit is contained in:
shimon
2022-11-22 11:15:48 +02:00
parent 5546de13e3
commit 19ad9914a3
5 changed files with 70 additions and 76 deletions
+15 -15
View File
@@ -158,8 +158,8 @@ App::put('/v1/videos/:videoId')
}
$file = validateFilePermissions($dbForProject, $bucketId, $fileId, $mode);
$video = Authorization::skip(function () use ($dbForProject, $videoId, $bucketId, $file) {
return $dbForProject->updateDocument('videos', $videoId, new Document([
$video = Authorization::skip(fn() =>
$dbForProject->updateDocument('videos', $videoId, new Document([
'bucketId' => $bucketId,
'fileId' => $file->getId(),
'size' => $file->getAttribute('sizeOriginal'),
@@ -172,8 +172,7 @@ App::put('/v1/videos/:videoId')
'audioCodec' => null,
'audioBitrate' => null,
'audioSamplerate' => null,
]));
});
])));
$response->dynamic($video, Response::MODEL_VIDEO);
});
@@ -273,16 +272,15 @@ App::post('/v1/videos/:videoId/subtitles')
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode);
validateFilePermissions($dbForProject, $bucketId, $fileId, $mode);
$subtitle = Authorization::skip(function () use ($dbForProject, $videoId, $bucketId, $fileId, $name, $code, $default) {
return $dbForProject->createDocument('videos_subtitles', new Document([
$subtitle = Authorization::skip(fn() =>
$dbForProject->createDocument('videos_subtitles', new Document([
'videoId' => $videoId,
'bucketId' => $bucketId,
'fileId' => $fileId,
'name' => $name,
'code' => $code,
'default' => $default,
]));
});
])));
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($subtitle, Response::MODEL_SUBTITLE);
@@ -465,6 +463,7 @@ App::delete('/v1/videos/:videoId/renditions/:renditionId')
->action(function (string $videoId, string $renditionId, Response $response, Database $dbForProject, string $mode, Device $deviceVideos) {
$video = Authorization::skip(fn() => $dbForProject->getDocument('videos', $videoId));
if ($video->isEmpty()) {
throw new Exception(Exception::VIDEO_NOT_FOUND);
}
@@ -509,6 +508,7 @@ App::get('/v1/videos/:videoId/renditions/:renditionId')
->action(function ($videoId, $renditionId, Response $response, Database $dbForProject, string $mode) {
$video = Authorization::skip(fn() => $dbForProject->getDocument('videos', $videoId));
if ($video->isEmpty()) {
throw new Exception(Exception::VIDEO_NOT_FOUND);
}
@@ -516,6 +516,7 @@ App::get('/v1/videos/:videoId/renditions/:renditionId')
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode);
$rendition = Authorization::skip(fn() => $dbForProject->getDocument('videos_renditions', $renditionId));
if ($rendition->isEmpty()) {
throw new Exception('Video rendition not found', 404, Exception::VIDEO_RENDITION_NOT_FOUND);
}
@@ -541,6 +542,7 @@ App::get('/v1/videos/:videoId/renditions')
->action(function (string $videoId, Response $response, Database $dbForProject, string $mode) {
$video = Authorization::skip(fn() => $dbForProject->getDocument('videos', $videoId));
if ($video->isEmpty()) {
throw new Exception(Exception::VIDEO_NOT_FOUND);
}
@@ -578,6 +580,7 @@ App::get('/v1/videos/:videoId/protocols/:protocolId')
->action(function (string $videoId, string $protocolId, Response $response, Database $dbForProject, string $mode) {
$video = Authorization::skip(fn() => $dbForProject->getDocument('videos', $videoId));
if ($video->isEmpty()) {
throw new Exception(Exception::VIDEO_NOT_FOUND);
}
@@ -730,7 +733,7 @@ App::get('/v1/videos/:videoId/protocols/:protocolId/renditions/:renditionId/stre
// TODO: Response model
->label('scope', 'videos.read')
->param('videoId', null, new UID(), 'Video unique ID.')
->param('protocolId', '', new WhiteList(['hls']), 'protocol name')
->param('protocolId', '', new WhiteList(['hls']), 'protocol name.')
->param('renditionId', '', new UID(), 'Rendition unique ID.')
->param('streamId', '', new Range(0, 10), 'Stream id.')
->inject('response')
@@ -760,7 +763,7 @@ App::get('/v1/videos/:videoId/protocols/:protocolId/renditions/:renditionId/stre
Query::equal('streamId', [$streamId]),
]));
if ($segments->isEmpty() || empty($segments)) {
if (empty($segments)) {
throw new Exception(Exception::VIDEO_RENDITION_SEGMENT_NOT_FOUND);
}
@@ -779,7 +782,6 @@ App::get('/v1/videos/:videoId/protocols/:protocolId/renditions/:renditionId/stre
->send($template->render(false));
});
App::get('/v1/videos/:videoId/protocols/:protocolId/renditions/:renditionId/segments/:segmentId')
->desc('Get video rendition segment')
->groups(['api', 'video'])
@@ -816,7 +818,6 @@ App::get('/v1/videos/:videoId/protocols/:protocolId/renditions/:renditionId/segm
}
});
App::get('/v1/videos/:videoId/protocols/:protocolId/subtitles/:subtitleId')
->desc('Get video subtitle')
->groups(['api', 'video'])
@@ -858,7 +859,7 @@ App::get('/v1/videos/:videoId/protocols/:protocolId/subtitles/:subtitleId')
Query::equal('subtitleId', [$subtitleId]),
]));
if ($segments->isEmpty() || empty($segments)) {
if (empty($segments)) {
throw new Exception(Exception::VIDEO_SUBTITLE_SEGMENT_NOT_FOUND);
}
@@ -882,7 +883,6 @@ App::get('/v1/videos/:videoId/protocols/:protocolId/subtitles/:subtitleId')
}
});
App::get('/v1/videos/:videoId/protocols/:protocolId/subtitles/:subtitleId/segments/:segmentId')
->desc('Get video subtitle segment')
->groups(['api', 'video'])
@@ -1040,7 +1040,7 @@ App::get('/v1/videos/profiles')
->action(function (Response $response, Database $dbForProject) {
$profiles = Authorization::skip(fn () => $dbForProject->find('videos_profiles'));
if ($profiles->isEmpty() || empty($profiles)) {
if (empty($profiles)) {
throw new Exception(Exception::VIDEO_PROFILE_NOT_FOUND);
}
+4 -2
View File
@@ -34,8 +34,10 @@ foreach (
realpath(__DIR__ . '/../vendor/matomo'),
realpath(__DIR__ . '/../vendor/symfony'),
realpath(__DIR__ . '/../vendor/mongodb'),
realpath(__DIR__ . '/../vendor/utopia-php/websocket'), // TODO: remove workerman autoload
realpath(__DIR__ . '/../vendor/utopia-php/cache'), // TODO: remove memcache autoload
realpath(__DIR__ . '/../vendor/utopia-php/websocket'),
realpath(__DIR__ . '/../vendor/utopia-php/cache'),
realpath(__DIR__ . '/../vendor/alchemy'),
realpath(__DIR__ . '/../vendor/php-ffmpeg'),
] as $key => $value
) {
if ($value !== false) {
+15 -10
View File
@@ -668,7 +668,12 @@ class DeletesV1 extends Worker
$dbForProject = $this->getProjectDB($projectId);
$dbForProject->deleteCollection('bucket_' . $document->getInternalId());
$dbForProject->deleteCollection('bucket_' . $document->getInternalId() . '_video_renditions');
$dbForProject->deleteCollection('videos_' . $document->getInternalId());
$dbForProject->deleteCollection('videos_' . $document->getInternalId() . '_renditions');
$dbForProject->deleteCollection('videos_' . $document->getInternalId() . '_renditions_segments');
$dbForProject->deleteCollection('videos_' . $document->getInternalId() . '_subtitles');
$dbForProject->deleteCollection('videos_' . $document->getInternalId() . '_subtitles_segments');
$dbForProject->deleteCollection('videos_' . $document->getInternalId() . '_profiles');
$device = $this->getFilesDevice($projectId);
$device->deletePath($document->getId());
@@ -686,25 +691,25 @@ class DeletesV1 extends Worker
$videoId = $document->getId();
$dbForProject = $this->getProjectDB($projectId);
$renditions = Authorization::skip(fn() => $dbForProject->find('videos_renditions', [
new Query('videoId', Query::TYPE_EQUAL, [$videoId])]));
$renditions = $dbForProject->find('videos_renditions', [
new Query('videoId', Query::TYPE_EQUAL, [$videoId])]);
foreach ($renditions as $rendition) {
$subtitles = Authorization::skip(fn() => $dbForProject->find('videos_subtitles', [
new Query('videoId', Query::TYPE_EQUAL, [$videoId])]));
$subtitles = $dbForProject->find('videos_subtitles', [
new Query('videoId', Query::TYPE_EQUAL, [$videoId])]);
foreach ($subtitles as $subtitle) {
$segments = Authorization::skip(fn() => $dbForProject->find('videos_subtitles_segments', [
new Query('subtitleId', Query::TYPE_EQUAL, [$subtitle->getId()])]));
$segments = fn() => $dbForProject->find('videos_subtitles_segments', [
new Query('subtitleId', Query::TYPE_EQUAL, [$subtitle->getId()])]);
foreach ($segments as $segment) {
Authorization::skip(fn() => $dbForProject->deleteDocument('videos_subtitles_segments', $segment->getId()));
$dbForProject->deleteDocument('videos_subtitles_segments', $segment->getId());
}
Authorization::skip(fn() => $dbForProject->deleteDocument('videos_subtitles', $subtitle->getId()));
$dbForProject->deleteDocument('videos_subtitles', $subtitle->getId());
}
$this->deleteByGroup('videos_renditions_segments', [
new Query('renditionId', Query::TYPE_EQUAL, [$rendition->getId()])
], $dbForProject);
Authorization::skip(fn() => $dbForProject->deleteDocument('videos_renditions', $rendition->getId()));
$dbForProject->deleteDocument('videos_renditions', $rendition->getId());
}
$videosDevice = $this->getVideoDevice($projectId);
+35 -48
View File
@@ -1,5 +1,6 @@
<?php
ini_set('memory_limit', -1);
use Appwrite\Extend\Exception;
use Appwrite\OpenSSL\OpenSSL;
use Appwrite\Resque\Worker;
@@ -14,7 +15,6 @@ use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Storage\Compression\Algorithms\GZIP;
use Utopia\Storage\Compression\Algorithms\Zstd;
use Captioning\Format\SubripFile;
@@ -56,7 +56,7 @@ class TranscodingV1 extends Worker
public function getName(): string
{
return "Transcoding";
return "Transcoder v1";
}
public function init(): void
@@ -75,34 +75,28 @@ class TranscodingV1 extends Worker
$project = new Document($this->args['project']);
$this->database = $this->getProjectDB($project->getId());
$sourceVideo = Authorization::skip(fn() => $this->database->findOne('videos', [
$sourceVideo = $this->database->findOne('videos', [
Query::equal('_uid', [$this->args['videoId']]),
]));
]);
if (empty($sourceVideo)) {
throw new Exception(Exception::VIDEO_NOT_FOUND);
}
$profile = Authorization::skip(fn() => $this->database->findOne('videos_profiles', [
$profile = $this->database->findOne('videos_profiles', [
Query::equal('_uid', [$this->args['profileId']]),
]));
]);
if (empty($profile)) {
throw new Exception(Exception::VIDEO_PROFILE_NOT_FOUND);
}
$bucket = Authorization::skip(
fn() => $this->database->getDocument('buckets', $sourceVideo->getAttribute('bucketId'))
);
$file = Authorization::skip(
fn() => $this->database->getDocument('bucket_' . $bucket->getInternalId(), $sourceVideo->getAttribute('fileId'))
);
$bucket = $this->database->getDocument('buckets', $sourceVideo->getAttribute('bucketId'));
$file = $this->database->getDocument('bucket_' . $bucket->getInternalId(), $sourceVideo->getAttribute('fileId'));
$path = basename($file->getAttribute('path'));
$inPath = $this->inDir . $path;
$result = $this->writeData($project, $file);
if (empty($result)) {
throw new Exception(Exception::GENERAL_UNKNOWN);
}
@@ -126,6 +120,7 @@ class TranscodingV1 extends Worker
$audioStreamCount = $ffprobe->streams($inPath)->audios()->count();
$videoStreamCount = $ffprobe->streams($inPath)->videos()->count();
$streams = $ffprobe->streams($inPath);
$sourceVideo
->setAttribute('duration', $videoStreamCount > 0 ? $streams->videos()->first()->get('duration') : null)
->setAttribute('height', $videoStreamCount > 0 ? $streams->videos()->first()->get('height') : null)
@@ -137,36 +132,32 @@ class TranscodingV1 extends Worker
->setAttribute('audioSamplerate', $audioStreamCount > 0 ? $streams->audios()->first()->get('sample_rate') : null)
->setAttribute('audioBitrate', $audioStreamCount > 0 ? $streams->audios()->first()->get('bit_rate') : null)
;
Authorization::skip(fn() => $this->database->updateDocument(
$this->database->updateDocument(
'videos',
$sourceVideo->getId(),
$sourceVideo
));
);
$video = $ffmpeg->open($inPath);
$this->setRenditionName($profile);
$subs = [];
$subtitles = Authorization::skip(fn() => $this->database->find('videos_subtitles', [
$subtitles = $this->database->find('videos_subtitles', [
Query::equal('videoId', [$this->args['videoId']]),
Query::equal('status', ['']),
]));
]);
foreach ($subtitles as $subtitle) {
$subtitle->setAttribute('status', self::STATUS_START);
Authorization::skip(fn() => $this->database->updateDocument(
$this->database->updateDocument(
'videos_subtitles',
$subtitle->getId(),
$subtitle
));
$bucket = Authorization::skip(
fn() => $this->database->getDocument('buckets', $subtitle->getAttribute('bucketId'))
);
$file = Authorization::skip(
fn() => $this->database->getDocument('bucket_' . $bucket->getInternalId(), $subtitle->getAttribute('fileId'))
);
$bucket = $this->database->getDocument('buckets', $subtitle->getAttribute('bucketId'));
$file = $this->database->getDocument('bucket_' . $bucket->getInternalId(), $subtitle->getAttribute('fileId'));
$path = basename($file->getAttribute('path'));
$this->writeData($project, $file);
@@ -185,8 +176,7 @@ class TranscodingV1 extends Worker
];
}
$query = Authorization::skip(function () use ($profile) {
return $this->database->createDocument('videos_renditions', new Document([
$query = $this->database->createDocument('videos_renditions', new Document([
'videoId' => $this->args['videoId'],
'profileId' => $profile->getId(),
'name' => $this->getRenditionName(),
@@ -194,7 +184,6 @@ class TranscodingV1 extends Worker
'status' => self::STATUS_START,
'protocol' => $profile->getAttribute('protocol'),
]));
});
$renditionRootPath = $this->getVideoDevice($project->getId())->getPath($this->args['videoId']) . '/';
$renditionPath = $renditionRootPath . $this->getRenditionName() . '-' . $query->getId() . '/';
@@ -210,11 +199,11 @@ class TranscodingV1 extends Worker
$format->on('progress', function ($video, $format, $percentage) use ($query) {
if ($percentage % 3 === 0) {
$query->setAttribute('progress', (string)$percentage);
Authorization::skip(fn() => $this->database->updateDocument(
$this->database->updateDocument(
'videos_renditions',
$query->getId(),
$query
));
);
}
});
@@ -231,15 +220,13 @@ class TranscodingV1 extends Worker
$m3u8 = $this->getHlsSegments($this->outDir . $stream['path']);
if (!empty($m3u8['segments'])) {
foreach ($m3u8['segments'] as $segment) {
Authorization::skip(function () use ($segment, $project, $query, $renditionPath, $stream) {
return $this->database->createDocument('videos_renditions_segments', new Document([
$this->database->createDocument('videos_renditions_segments', new Document([
'renditionId' => $query->getId(),
'streamId' => (int)$stream['id'],
'fileName' => $segment['fileName'],
'path' => $renditionPath,
'duration' => $segment['duration'],
]));
});
}
}
@@ -250,15 +237,13 @@ class TranscodingV1 extends Worker
$mpd = $this->getDashSegments($this->outPath . '.mpd');
if (!empty($mpd['segments'])) {
foreach ($mpd['segments'] as $segment) {
Authorization::skip(function () use ($segment, $project, $query, $renditionPath) {
return $this->database->createDocument('videos_renditions_segments', new Document([
$this->database->createDocument('videos_renditions_segments', new Document([
'renditionId' => $query->getId(),
'streamId' => $segment['streamId'],
'fileName' => $segment['fileName'],
'path' => $renditionPath,
'isInit' => $segment['isInit'],
]));
});
}
}
@@ -269,20 +254,18 @@ class TranscodingV1 extends Worker
$query->setAttribute('status', self::STATUS_END);
$query->setAttribute('endedAt', DateTime::now());
Authorization::skip(fn() => $this->database->updateDocument('videos_renditions', $query->getId(), $query));
$this->database->updateDocument('videos_renditions', $query->getId(), $query);
foreach ($subtitles ?? [] as $subtitle) {
if ($profile->getAttribute('protocol') === 'hls') {
$m3u8 = $this->getHlsSegments($this->outPath . '_subtitles_' . $subtitle['code'] . '.m3u8');
foreach ($m3u8['segments'] ?? [] as $segment) {
Authorization::skip(function () use ($segment, $project, $subtitle, $renditionRootPath) {
return $this->database->createDocument('videos_subtitles_segments', new Document([
$this->database->createDocument('videos_subtitles_segments', new Document([
'subtitleId' => $subtitle->getId(),
'fileName' => $segment['fileName'],
'path' => $renditionRootPath ,
'duration' => $segment['duration'],
]));
});
}
$subtitle->setAttribute('targetDuration', $m3u8['targetDuration']);
} else {
@@ -291,11 +274,11 @@ class TranscodingV1 extends Worker
$subtitle->setAttribute('status', self::STATUS_READY);
$subtitle->setAttribute('path', $renditionRootPath);
Authorization::skip(fn() => $this->database->updateDocument(
$this->database->updateDocument(
'videos_subtitles',
$subtitle->getId(),
$subtitle
));
);
}
/** Upload & cleanup **/
@@ -318,14 +301,14 @@ class TranscodingV1 extends Worker
$query->setAttribute('progress', '100');
$query->setAttribute('status', self::STATUS_UPLOADING);
$query->setAttribute('path', $renditionPath);
Authorization::skip(fn() => $this->database->updateDocument('videos_renditions', $query->getId(), $query));
$this->database->updateDocument('videos_renditions', $query->getId(), $query);
$start = 1;
}
//@unlink($this->outDir . $fileName);
}
$query->setAttribute('status', self::STATUS_READY);
Authorization::skip(fn() => $this->database->updateDocument('videos_renditions', $query->getId(), $query));
$this->database->updateDocument('videos_renditions', $query->getId(), $query);
} catch (\Throwable $th) {
$query->setAttribute('metadata', json_encode([
'code' => $th->getCode(),
@@ -333,7 +316,7 @@ class TranscodingV1 extends Worker
]));
$query->setAttribute('status', self::STATUS_ERROR);
Authorization::skip(fn() => $this->database->updateDocument('videos_renditions', $query->getId(), $query));
$this->database->updateDocument('videos_renditions', $query->getId(), $query);
throw new Exception($th->getMessage(), 500, Exception::GENERAL_UNKNOWN);
}
}
@@ -348,6 +331,7 @@ class TranscodingV1 extends Worker
*/
private function transcode(string $protocol, Media $video, StreamFormat $format, Representation $representation, array $subtitles): string | array
{
// $video->filters()
// ->framerate(new FFMpeg\Coordinate\FrameRate(24), 2)
// ;
@@ -401,6 +385,7 @@ class TranscodingV1 extends Worker
*/
private function getDashSegments(string $path): array
{
$segments = [];
$metadata = null;
$handle = fopen($path, "r");
@@ -437,6 +422,7 @@ class TranscodingV1 extends Worker
*/
private function getHlsSegmentsUrls(string $path): array
{
$files = [];
$handle = fopen($path, "r");
if ($handle) {
@@ -482,6 +468,7 @@ class TranscodingV1 extends Worker
*/
private function getHlsSegments(string $path): array
{
$segments = [];
$targetDuration = 0;
$handle = fopen($path, "r");
@@ -518,6 +505,7 @@ class TranscodingV1 extends Worker
*/
private function getVideoStreamInfo(array $metadata, RepresentationInterface $representation): array
{
$info = [];
// if (!empty($metadata['stream']['resolutions'][0])) {
// $general = $metadata['stream']['resolutions'][0];
@@ -549,7 +537,6 @@ class TranscodingV1 extends Worker
private function writeData(Document $project, Document $file): bool
{
$fullPath = $file->getAttribute('path');
$path = basename($file->getAttribute('path'));