mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
added internalIds to collections
This commit is contained in:
@@ -23,7 +23,7 @@ _APP_DB_SCHEMA=appwrite
|
||||
_APP_DB_USER=user
|
||||
_APP_DB_PASS=password
|
||||
_APP_DB_ROOT_PASS=rootsecretpassword
|
||||
_APP_STORAGE_DEVICE=Local
|
||||
_APP_STORAGE_DEVICE=local
|
||||
_APP_STORAGE_S3_ACCESS_KEY=
|
||||
_APP_STORAGE_S3_SECRET=
|
||||
_APP_STORAGE_S3_REGION=
|
||||
@@ -31,7 +31,7 @@ _APP_STORAGE_S3_BUCKET=
|
||||
_APP_STORAGE_DO_SPACES_ACCESS_KEY=
|
||||
_APP_STORAGE_DO_SPACES_SECRET=
|
||||
_APP_STORAGE_DO_SPACES_REGION=fra1
|
||||
_APP_STORAGE_DO_SPACES_BUCKET=
|
||||
_APP_STORAGE_DO_SPACES_BUCKET=videos-test
|
||||
_APP_STORAGE_BACKBLAZE_ACCESS_KEY=
|
||||
_APP_STORAGE_BACKBLAZE_SECRET=
|
||||
_APP_STORAGE_BACKBLAZE_REGION=us-west-004
|
||||
|
||||
+133
-1
@@ -3219,6 +3219,17 @@ $collections = [
|
||||
'default' => null,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('bucketInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('name'),
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -3564,6 +3575,17 @@ $collections = [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'bucketInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'fileId',
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -3575,6 +3597,17 @@ $collections = [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'fileInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'previewId',
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -3586,6 +3619,17 @@ $collections = [
|
||||
'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,
|
||||
@@ -3767,6 +3811,17 @@ $collections = [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'videoInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'type',
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -3833,6 +3888,17 @@ $collections = [
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'videoInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
@@ -3849,6 +3915,17 @@ $collections = [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'profileInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'name',
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -4019,6 +4096,17 @@ $collections = [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'renditionInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'fileName',
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -4167,7 +4255,18 @@ $collections = [
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'videoInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
@@ -4183,6 +4282,17 @@ $collections = [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'bucketInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'fileId',
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -4194,6 +4304,17 @@ $collections = [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'fileInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'path',
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -4289,6 +4410,17 @@ $collections = [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'subtitleInternalId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'fileName',
|
||||
'type' => Database::VAR_STRING,
|
||||
|
||||
@@ -17,10 +17,24 @@ return [
|
||||
'height' => 576,
|
||||
],
|
||||
[
|
||||
'name' => '720p',
|
||||
'name' => '1080p',
|
||||
'videoBitRate' => 3551,
|
||||
'audioBitRate' => 128,
|
||||
'width' => 1280,
|
||||
'height' => 720,
|
||||
],
|
||||
[
|
||||
'name' => '720p',
|
||||
'videoBitRate' => 4800,
|
||||
'audioBitRate' => 128,
|
||||
'width' => 1920,
|
||||
'height' => 1080,
|
||||
],
|
||||
[
|
||||
'name' => '2160p',
|
||||
'videoBitRate' => 16000,
|
||||
'audioBitRate' => 356,
|
||||
'width' => 4096,
|
||||
'height' => 2160,
|
||||
],
|
||||
];
|
||||
|
||||
@@ -567,6 +567,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||
'$id' => $fileId,
|
||||
'$permissions' => $permissions,
|
||||
'bucketId' => $bucket->getId(),
|
||||
'bucketInternalId' => $bucket->getInternalId(),
|
||||
'name' => $fileName,
|
||||
'path' => $path,
|
||||
'signature' => $fileHash,
|
||||
@@ -616,6 +617,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||
'$id' => ID::custom($fileId),
|
||||
'$permissions' => $permissions,
|
||||
'bucketId' => $bucket->getId(),
|
||||
'bucketInternalId' => $bucket->getInternalId(),
|
||||
'name' => $fileName,
|
||||
'path' => $path,
|
||||
'signature' => '',
|
||||
|
||||
@@ -100,9 +100,11 @@ App::post('/v1/videos')
|
||||
|
||||
$video = Authorization::skip(function () use ($dbForProject, $bucketId, $file) {
|
||||
return $dbForProject->createDocument('videos', new Document([
|
||||
'bucketId' => $bucketId,
|
||||
'fileId' => $file->getId(),
|
||||
'size' => $file->getAttribute('sizeOriginal'),
|
||||
'bucketId' => $file->getAttribute('bucketId'),
|
||||
'bucketInternalId' => $file->getAttribute('bucketInternalId'),
|
||||
'fileId' => $file->getId(),
|
||||
'fileInternalId' => $file->getInternalId(),
|
||||
'size' => $file->getAttribute('sizeOriginal'),
|
||||
]));
|
||||
});
|
||||
|
||||
@@ -191,10 +193,13 @@ App::put('/v1/videos/:videoId')
|
||||
|
||||
$video = Authorization::skip(fn() =>
|
||||
$dbForProject->updateDocument('videos', $videoId, new Document([
|
||||
'bucketId' => $bucketId,
|
||||
'fileId' => $file->getId(),
|
||||
'bucketId' => $file->getAttribute('bucketId'),
|
||||
'bucketInternalId' => $file->getAttribute('bucketInternalId'),
|
||||
'fileId' => $file->getId(),
|
||||
'fileInternalId' => $file->getInternalId(),
|
||||
'size' => $file->getAttribute('sizeOriginal'),
|
||||
'previewId' => null,
|
||||
'previewInternalId' => null,
|
||||
'duration' => null,
|
||||
'width' => null,
|
||||
'height' => null,
|
||||
@@ -480,9 +485,12 @@ App::post('/v1/videos/:videoId/subtitles')
|
||||
|
||||
$subtitle = Authorization::skip(fn() =>
|
||||
$dbForProject->createDocument('videos_subtitles', new Document([
|
||||
'videoId' => $videoId,
|
||||
'bucketId' => $bucketId,
|
||||
'fileId' => $fileId,
|
||||
'videoId' => $video->getId(),
|
||||
'videoInternalId' => $video->getInternalId(),
|
||||
'bucketId' => $file->getAttribute('bucketId'),
|
||||
'bucketInternalId' => $file->getAttribute('bucketInternalId'),
|
||||
'fileId' => $file->getId(),
|
||||
'fileInternalId' => $file->getInternalId(),
|
||||
'name' => $name,
|
||||
'code' => $code,
|
||||
'default' => $default,
|
||||
@@ -546,7 +554,22 @@ App::patch('/v1/videos/:videoId/subtitles/:subtitleId')
|
||||
->param('default', false, new Boolean(true), 'Default subtitle.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(action: function (string $subtitleId, string $videoId, string $bucketId, string $fileId, string $name, string $code, bool $default, Response $response, Database $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) {
|
||||
|
||||
$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);
|
||||
|
||||
$file = validateFilePermissions($dbForProject, $bucketId, $fileId, $mode);
|
||||
|
||||
if (!in_array($file->getAttribute('mimeType'), ['text/vtt','text/plain'])) {
|
||||
throw new Exception(Exception::VIDEO_SUBTITLE_NOT_VALID);
|
||||
}
|
||||
|
||||
$code = strtolower($code);
|
||||
$languages = Config::getParam('locale-languages');
|
||||
@@ -562,9 +585,12 @@ App::patch('/v1/videos/:videoId/subtitles/:subtitleId')
|
||||
throw new Exception(Exception::VIDEO_SUBTITLE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$subtitle->setAttribute('videoId', $videoId)
|
||||
->setAttribute('bucketId', $bucketId)
|
||||
->setAttribute('fileId', $fileId)
|
||||
$subtitle->setAttribute('videoId', $video->getId())
|
||||
->setAttribute('videoInternalId', $video->getInternalId())
|
||||
->setAttribute('bucketId', $file->getAttribute('bucketId'))
|
||||
->setAttribute('bucketInternalId', $file->getAttribute('bucketInternalId'))
|
||||
->setAttribute('fileId', $file->getId())
|
||||
->setAttribute('fileInternalId', $file->getInternalId())
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('code', $code)
|
||||
->setAttribute('default', $default);
|
||||
@@ -1276,9 +1302,9 @@ App::patch('/v1/videos/profiles/:profileId')
|
||||
|
||||
$profile->setAttribute('name', $name)
|
||||
->setAttribute('videoBitRate', (int)$videoBitRate)
|
||||
->setAttribute('audioBitRate', (int)$audioBitRate)
|
||||
->setAttribute('width', (int)$width)
|
||||
->setAttribute('height', (int)$height);
|
||||
->setAttribute('audioBitRate', (int)$audioBitRate)
|
||||
->setAttribute('width', (int)$width)
|
||||
->setAttribute('height', (int)$height);
|
||||
|
||||
|
||||
$profile = Authorization::skip(fn() => $dbForProject->updateDocument('videos_profiles', $profile->getId(), $profile));
|
||||
|
||||
+1
-2
@@ -889,7 +889,7 @@ App::setResource('project', function ($dbForConsole, $request, $console) {
|
||||
/** @var Utopia\Database\Document $console */
|
||||
|
||||
$projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', 'console'));
|
||||
$projectId = '644a75323601130be4a8';
|
||||
$projectId = '64575eb69a8995fbde21';
|
||||
|
||||
if ($projectId === 'console') {
|
||||
return $console;
|
||||
@@ -936,7 +936,6 @@ App::setResource('console', function () {
|
||||
|
||||
App::setResource('dbForProject', function ($db, $cache, Document $project) {
|
||||
$cache = new Cache(new RedisCache($cache));
|
||||
|
||||
$database = new Database(new MariaDB($db), $cache);
|
||||
$database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
|
||||
$database->setNamespace("_{$project->getInternalId()}");
|
||||
|
||||
+241
-219
@@ -46,6 +46,7 @@ class VideosV1 extends Worker
|
||||
private string $basePath = '/tmp/';
|
||||
//private string $basePath = '/usr/src/code/tests/tmp/';
|
||||
private string $inDir;
|
||||
private string $inPath;
|
||||
private string $outDir;
|
||||
private string $outPath;
|
||||
private string $renditionName;
|
||||
@@ -81,20 +82,18 @@ class VideosV1 extends Worker
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$startTime = time();
|
||||
|
||||
$this->database = $this->getProjectDB($this->project->getId());
|
||||
$this->bucket = $this->database->getDocument('buckets', $this->video->getAttribute('bucketId'));
|
||||
$this->file = $this->database->getDocument('bucket_' . $this->bucket->getInternalId(), $this->video->getAttribute('fileId'));
|
||||
$path = basename($this->file->getAttribute('path'));
|
||||
$inPath = $this->inDir . $path;
|
||||
$this->inPath = $this->inDir . $path;
|
||||
|
||||
/**
|
||||
* Write original asset to tmp
|
||||
*/
|
||||
$result = $this->write($this->project, $this->file);
|
||||
|
||||
console::info('Transferring video from storage to ' . $this->inDir);
|
||||
|
||||
if (empty($result)) {
|
||||
console::error('Storage transfer error');
|
||||
}
|
||||
@@ -103,18 +102,17 @@ class VideosV1 extends Worker
|
||||
. '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' => 12
|
||||
'ffmpeg.threads' => 12
|
||||
]);
|
||||
|
||||
if (!$this->ffprobe->isValid($inPath)) {
|
||||
console::error('Not an valid Video file "' . $inPath . '"');
|
||||
if (!$this->ffprobe->isValid($this->inPath)) {
|
||||
console::error('Not an valid Video file "' . $this->inPath . '"');
|
||||
}
|
||||
|
||||
if (empty($this->video->getAttribute('duration'))) {
|
||||
@@ -122,7 +120,7 @@ class VideosV1 extends Worker
|
||||
* Original asset metadata
|
||||
*/
|
||||
$mediaInfo = new MediaInfo();
|
||||
$mediaInfoContainer = $mediaInfo->getInfo($inPath);
|
||||
$mediaInfoContainer = $mediaInfo->getInfo($this->inPath);
|
||||
$general = $mediaInfoContainer->getGeneral();
|
||||
$this->video
|
||||
->setAttribute('duration', $general->has('duration') ? $general->get('duration')->getMilliseconds() : 0)
|
||||
@@ -166,186 +164,208 @@ class VideosV1 extends Worker
|
||||
);
|
||||
}
|
||||
$this->video->getAttribute('videoBitRate');
|
||||
$media = $this->ffmpeg->open($inPath);
|
||||
$media = $this->ffmpeg->open($this->inPath);
|
||||
|
||||
if ($this->action === 'preview') {
|
||||
if (!$this->isVideo) {
|
||||
return;
|
||||
}
|
||||
console::info('Creating preview image from second ' . $this->args['second']);
|
||||
switch ($this->action) {
|
||||
case 'preview':
|
||||
$this->createPreview($media);
|
||||
break;
|
||||
|
||||
$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);
|
||||
case 'timeline':
|
||||
$this->createTimeLine($media);
|
||||
break;
|
||||
|
||||
/**
|
||||
* Upload preview
|
||||
*/
|
||||
console::info('Uploading ' . $name);
|
||||
default:
|
||||
$this->createOutput($media);
|
||||
break;
|
||||
}
|
||||
|
||||
$this->getVideoDevice($this->project->getId())->write(
|
||||
$path . $name,
|
||||
(new Local('/'))->read($this->outDir . $name),
|
||||
mime_content_type($this->outDir . $name)
|
||||
unset($media);
|
||||
}
|
||||
|
||||
|
||||
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('videoId', [$this->video->getId()]),
|
||||
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->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();
|
||||
}
|
||||
}
|
||||
|
||||
$preview = $this->database->findOne('videos_previews', [
|
||||
Query::equal('videoId', [$this->video->getId()]),
|
||||
Query::equal('type', [$this->action]),
|
||||
Query::equal('name', [$name]),
|
||||
]);
|
||||
|
||||
if (empty($preview)) {
|
||||
$preview = $this->database->createDocument('videos_previews', new Document([
|
||||
'videoId' => $this->video->getId(),
|
||||
'type' => $this->action,
|
||||
'name' => $name,
|
||||
'path' => $path,
|
||||
'second' => $this->args['second'],
|
||||
]));
|
||||
|
||||
$this->video->setAttribute('previewId', $preview->getId());
|
||||
$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();
|
||||
}
|
||||
private function createTimeLine($media): void
|
||||
{
|
||||
|
||||
/*
|
||||
* Title: PlayerJS Thumbnails & WebVTT Creator
|
||||
* URI: https://playerjs.com/docs/q=thumbnailsphpwebvtt
|
||||
* Version: 1.0
|
||||
* Author: Playerjs.com
|
||||
* Author URI: https://playerjs.com
|
||||
* License: GPL-2.0+
|
||||
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
|
||||
* Text Domain: playerjs
|
||||
*/
|
||||
if (!$this->isVideo) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->action === 'timeline') {
|
||||
/*
|
||||
* Title: PlayerJS Thumbnails & WebVTT Creator
|
||||
* URI: https://playerjs.com/docs/q=thumbnailsphpwebvtt
|
||||
* Version: 1.0
|
||||
* Author: Playerjs.com
|
||||
* Author URI: https://playerjs.com
|
||||
* License: GPL-2.0+
|
||||
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
|
||||
* Text Domain: playerjs
|
||||
*/
|
||||
if (!$this->isVideo) {
|
||||
return;
|
||||
$interval = 2;
|
||||
$ranges = [
|
||||
['from' => 120, 'to' => 600, 'interval' => 5],
|
||||
['from' => 600, 'to' => 1800, 'interval' => 10],
|
||||
['from' => 1800, 'to' => 3600, 'interval' => 20],
|
||||
['from' => 3600, 'to' => 99999, 'interval' => 30],
|
||||
];
|
||||
|
||||
foreach ($ranges as $range) {
|
||||
if (
|
||||
$this->video->getAttribute('duration') > $range['from'] &&
|
||||
$this->video->getAttribute('duration') <= $range['to']
|
||||
) {
|
||||
$interval = $range['interval'];
|
||||
break;
|
||||
}
|
||||
|
||||
$interval = 2;
|
||||
$ranges = [
|
||||
['from' => 120, 'to' => 600, 'interval' => 5],
|
||||
['from' => 600, 'to' => 1800 , 'interval' => 10],
|
||||
['from' => 1800, 'to' => 3600, 'interval' => 20],
|
||||
['from' => 3600, 'to' => 99999, 'interval' => 30],
|
||||
];
|
||||
|
||||
foreach ($ranges as $range) {
|
||||
if (
|
||||
$this->video->getAttribute('duration') > $range['from'] &&
|
||||
$this->video->getAttribute('duration') <= $range['to']
|
||||
) {
|
||||
$interval = $range['interval'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$timeline['aspect'] = $this->video->getAttribute('width') / $this->video->getAttribute('height');
|
||||
$timeline['width'] = 160;
|
||||
$timeline['height'] = round($timeline['width'] / $timeline['aspect']);
|
||||
$timeline['size'] = '5x5';
|
||||
|
||||
$cmd = [
|
||||
'/usr/bin/ffmpeg',
|
||||
'-i ' . $inPath,
|
||||
'-hide_banner',
|
||||
'-loglevel error',
|
||||
'-vsync vfr',
|
||||
'-vf \'select=isnan(prev_selected_t)+gte(t-prev_selected_t\,' . $interval . '),scale=' . $timeline['width'] . ':' . $timeline['height'] . ',tile=' . $timeline['size'] . '\'',
|
||||
'-qscale:v 3',
|
||||
$this->outDir . 'sprite%d.jpg',
|
||||
];
|
||||
$result = shell_exec(implode(" ", $cmd));
|
||||
|
||||
/**
|
||||
* Vtt creation*
|
||||
*/
|
||||
if ($result !== false) {
|
||||
$size = explode('x', $timeline['size']);
|
||||
$counter = 0;
|
||||
$images = ceil((($this->video->getAttribute('duration') / 1000) / $interval) / ($size[0] * $size[1]));
|
||||
$data = "WEBVTT";
|
||||
$path = $this->getVideoDevice($this->project->getId())->getPath($this->video->getId()) . '/timeline/';
|
||||
for ($image = 1; $image <= $images; $image++) {
|
||||
$sprite = $this->database->createDocument('videos_previews', new Document([
|
||||
'videoId' => $this->video->getId(),
|
||||
'type' => 'sprite',
|
||||
'name' => 'sprite' . $image . '.jpg',
|
||||
'path' => $path,
|
||||
]));
|
||||
|
||||
$url = TMP_HOST . 'v1/videos/' . $this->video->getId() . '/preview/' . $sprite->getId() . '/';
|
||||
for ($col = 0; $col < $size[0]; $col++) {
|
||||
for ($row = 0; $row < $size[1]; $row++) {
|
||||
$data .= "\n" . gmdate("H:i:s", $counter * $interval) . " --> " . gmdate("H:i:s", ($counter + 1) * $interval) . "\n" . $url . "#xywh=" . ($row * $timeline['width']) . "," . ($col * $timeline['height']) . "," . $timeline['width'] . "," . $timeline['height'];
|
||||
$counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($counter > 0) {
|
||||
/**
|
||||
* Upload vtt
|
||||
*/
|
||||
$this->getVideoDevice($this->project->getId())->write(
|
||||
$this->getVideoDevice($this->project->getId())->getPath($this->video->getId() . '/timeline/timeline.vtt'),
|
||||
$data,
|
||||
'text/vtt'
|
||||
);
|
||||
|
||||
console::info('Uploading timeline vtt');
|
||||
|
||||
/**
|
||||
* Upload sprites*
|
||||
*/
|
||||
$dir = new DirectoryIterator($this->outDir);
|
||||
foreach ($dir as $fileinfo) {
|
||||
if (!$fileinfo->isDot()) {
|
||||
console::info('Uploading ' . $fileinfo->getFilename());
|
||||
$this->getVideoDevice($this->project->getId())->write(
|
||||
$path . $fileinfo->getFilename(),
|
||||
(new Local('/'))->read($this->outDir . $fileinfo->getFilename()),
|
||||
mime_content_type($this->outDir . $fileinfo->getFilename())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$timeline['aspect'] = $this->video->getAttribute('width') / $this->video->getAttribute('height');
|
||||
$timeline['width'] = 160;
|
||||
$timeline['height'] = round($timeline['width'] / $timeline['aspect']);
|
||||
$timeline['size'] = '5x5';
|
||||
|
||||
$cmd = [
|
||||
'/usr/bin/ffmpeg',
|
||||
'-i ' . $this->inPath,
|
||||
'-hide_banner',
|
||||
'-loglevel error',
|
||||
'-vsync vfr',
|
||||
'-vf \'select=isnan(prev_selected_t)+gte(t-prev_selected_t\,' . $interval . '),scale=' . $timeline['width'] . ':' . $timeline['height'] . ',tile=' . $timeline['size'] . '\'',
|
||||
'-qscale:v 3',
|
||||
$this->outDir . 'sprite%d.jpg',
|
||||
];
|
||||
$result = shell_exec(implode(" ", $cmd));
|
||||
|
||||
/**
|
||||
* Vtt creation*
|
||||
*/
|
||||
if ($result !== false) {
|
||||
$size = explode('x', $timeline['size']);
|
||||
$counter = 0;
|
||||
$images = ceil((($this->video->getAttribute('duration') / 1000) / $interval) / ($size[0] * $size[1]));
|
||||
$data = "WEBVTT";
|
||||
$path = $this->getVideoDevice($this->project->getId())->getPath($this->video->getId()) . '/timeline/';
|
||||
for ($image = 1; $image <= $images; $image++) {
|
||||
$sprite = $this->database->createDocument('videos_previews', new Document([
|
||||
'videoId' => $this->video->getId(),
|
||||
'videoInternalId' => $this->video->getInternalId(),
|
||||
'type' => 'sprite',
|
||||
'name' => 'sprite' . $image . '.jpg',
|
||||
'path' => $path,
|
||||
]));
|
||||
|
||||
$url = TMP_HOST . 'v1/videos/' . $this->video->getId() . '/preview/' . $sprite->getId() . '/';
|
||||
for ($col = 0; $col < $size[0]; $col++) {
|
||||
for ($row = 0; $row < $size[1]; $row++) {
|
||||
$data .= "\n" . gmdate("H:i:s", $counter * $interval) . " --> " . gmdate("H:i:s", ($counter + 1) * $interval) . "\n" . $url . "#xywh=" . ($row * $timeline['width']) . "," . ($col * $timeline['height']) . "," . $timeline['width'] . "," . $timeline['height'];
|
||||
$counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($counter > 0) {
|
||||
/**
|
||||
* Upload vtt
|
||||
*/
|
||||
$this->getVideoDevice($this->project->getId())->write(
|
||||
$this->getVideoDevice($this->project->getId())->getPath($this->video->getId() . '/timeline/timeline.vtt'),
|
||||
$data,
|
||||
'text/vtt'
|
||||
);
|
||||
|
||||
console::info('Uploading timeline vtt');
|
||||
|
||||
/**
|
||||
* Upload sprites*
|
||||
*/
|
||||
$dir = new DirectoryIterator($this->outDir);
|
||||
foreach ($dir as $fileinfo) {
|
||||
if (!$fileinfo->isDot()) {
|
||||
console::info('Uploading ' . $fileinfo->getFilename());
|
||||
$this->getVideoDevice($this->project->getId())->write(
|
||||
$path . $fileinfo->getFilename(),
|
||||
(new Local('/'))->read($this->outDir . $fileinfo->getFilename()),
|
||||
mime_content_type($this->outDir . $fileinfo->getFilename())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function createOutput($media): void
|
||||
{
|
||||
$subs = [];
|
||||
$subtitles = $this->database->find('videos_subtitles', [
|
||||
$subtitles = $this->database->find('videos_subtitles', [
|
||||
Query::equal('videoId', [$this->video->getId()]),
|
||||
Query::equal('status', [''])
|
||||
]);
|
||||
@@ -354,7 +374,7 @@ class VideosV1 extends Worker
|
||||
$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'));
|
||||
$file = $this->database->getDocument('bucket_' . $bucket->getInternalId(), $subtitle->getAttribute('fileId'));
|
||||
$path = basename($file->getAttribute('path'));
|
||||
$this->write($this->project, $file);
|
||||
|
||||
@@ -369,40 +389,41 @@ class VideosV1 extends Worker
|
||||
}
|
||||
|
||||
$subs[] = [
|
||||
'name' => $subtitle->getAttribute('name'),
|
||||
'code' => $subtitle->getAttribute('code'),
|
||||
'path' => $subtitlePath,
|
||||
'name' => $subtitle->getAttribute('name'),
|
||||
'code' => $subtitle->getAttribute('code'),
|
||||
'path' => $subtitlePath,
|
||||
];
|
||||
}
|
||||
|
||||
$query = $this->database->createDocument('videos_renditions', new Document([
|
||||
'videoId' => $this->video->getId(),
|
||||
'profileId' => $this->profile->getId(),
|
||||
'name' => $this->renditionName,
|
||||
'startedAt' => DateTime::now(),
|
||||
'status' => self::STATUS_START,
|
||||
'width' => $this->profile->getAttribute('width'),
|
||||
'height' => $this->profile->getAttribute('height'),
|
||||
'videoBitRate' => $this->profile->getAttribute('videoBitRate'),
|
||||
'audioBitRate' => $this->profile->getAttribute('audioBitRate'),
|
||||
'output' => $this->args['output'],
|
||||
]));
|
||||
'videoId' => $this->video->getId(),
|
||||
'videoInternalId' => $this->video->getInternalId(),
|
||||
'profileId' => $this->profile->getId(),
|
||||
'profileInternalId' => $this->profile->getInternalId(),
|
||||
'name' => $this->renditionName,
|
||||
'startedAt' => DateTime::now(),
|
||||
'status' => self::STATUS_START,
|
||||
'width' => $this->profile->getAttribute('width'),
|
||||
'height' => $this->profile->getAttribute('height'),
|
||||
'videoBitRate' => $this->profile->getAttribute('videoBitRate'),
|
||||
'audioBitRate' => $this->profile->getAttribute('audioBitRate'),
|
||||
'output' => $this->args['output'],
|
||||
]));
|
||||
|
||||
$this->send($query);
|
||||
|
||||
$renditionRootPath = $this->getVideoDevice($this->project->getId())->getPath($this->video->getId()) . '/';
|
||||
$renditionPath = $renditionRootPath . $this->renditionName . '-' . $query->getId() . '/';
|
||||
$renditionPath = $renditionRootPath . $this->renditionName . '-' . $query->getId() . '/';
|
||||
|
||||
try {
|
||||
$representation = (new Representation())
|
||||
->setKiloBitRate($this->profile->getAttribute('videoBitRate'))
|
||||
->setAudioKiloBitRate($this->profile->getAttribute('audioBitRate'))
|
||||
->setResize($this->profile->getAttribute('width'), $this->profile->getAttribute('height'))
|
||||
;
|
||||
->setResize($this->profile->getAttribute('width'), $this->profile->getAttribute('height'));
|
||||
|
||||
console::info('Output video id:' . $this->video->getId() . PHP_EOL .
|
||||
'Output name: ' . $this->file->getAttribute('name') . PHP_EOL .
|
||||
'Output width: ' . $this->profile->getAttribute('width') . PHP_EOL .
|
||||
'Output width: ' . $this->profile->getAttribute('width') . PHP_EOL .
|
||||
'Output height: ' . $this->profile->getAttribute('height') . PHP_EOL .
|
||||
'Output video BitRate:' . $this->profile->getAttribute('videoBitRate') . PHP_EOL .
|
||||
'Output audio BitRate:' . $this->profile->getAttribute('audioBitRate') . PHP_EOL .
|
||||
@@ -419,7 +440,6 @@ class VideosV1 extends Worker
|
||||
|
||||
$this->transcode($media, $format, $representation, $subs);
|
||||
|
||||
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);
|
||||
@@ -431,12 +451,13 @@ class VideosV1 extends Worker
|
||||
if (!empty($m3u8['segments'])) {
|
||||
foreach ($m3u8['segments'] as $segment) {
|
||||
$this->database->createDocument('videos_renditions_segments', new Document([
|
||||
'renditionId' => $query->getId(),
|
||||
'streamId' => (int)$stream['id'],
|
||||
'fileName' => $segment['fileName'],
|
||||
'path' => $renditionPath,
|
||||
'duration' => $segment['duration'],
|
||||
]));
|
||||
'renditionId' => $query->getId(),
|
||||
'renditionInternalId' => $query->getInternalId(),
|
||||
'streamId' => (int)$stream['id'],
|
||||
'fileName' => $segment['fileName'],
|
||||
'path' => $renditionPath,
|
||||
'duration' => $segment['duration'],
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,13 +469,14 @@ class VideosV1 extends Worker
|
||||
|
||||
if (!empty($mpd['segments'])) {
|
||||
foreach ($mpd['segments'] as $segment) {
|
||||
$this->database->createDocument('videos_renditions_segments', new Document([
|
||||
'renditionId' => $query->getId(),
|
||||
'streamId' => $segment['streamId'],
|
||||
'fileName' => $segment['fileName'],
|
||||
'path' => $renditionPath,
|
||||
'isInit' => $segment['isInit'],
|
||||
]));
|
||||
$this->database->createDocument('videos_renditions_segments', new Document([
|
||||
'renditionId' => $query->getId(),
|
||||
'renditionInternalId' => $query->getInternalId(),
|
||||
'streamId' => $segment['streamId'],
|
||||
'fileName' => $segment['fileName'],
|
||||
'path' => $renditionPath,
|
||||
'isInit' => $segment['isInit'],
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,12 +494,13 @@ class VideosV1 extends Worker
|
||||
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(),
|
||||
'fileName' => $segment['fileName'],
|
||||
'path' => $renditionRootPath . 'subtitles/',
|
||||
'duration' => $segment['duration'],
|
||||
]));
|
||||
$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 {
|
||||
@@ -491,7 +514,7 @@ class VideosV1 extends Worker
|
||||
|
||||
console::info('Rendition ' . $query->getId() . ' conversion, done');
|
||||
|
||||
/** Upload**/
|
||||
/** Upload **/
|
||||
$dir = new DirectoryIterator($this->outDir);
|
||||
foreach ($dir as $fileinfo) {
|
||||
if (!$fileinfo->isDot()) {
|
||||
@@ -504,12 +527,12 @@ class VideosV1 extends Worker
|
||||
console::info('Uploading ' . $fileinfo->getFilename());
|
||||
|
||||
$this->getVideoDevice($this->project->getId())->write(
|
||||
$to . $fileinfo->getFilename(),
|
||||
$to . $fileinfo->getFilename(),
|
||||
$data,
|
||||
mime_content_type($this->outDir . $fileinfo->getFilename())
|
||||
);
|
||||
|
||||
if ($fileinfo->key() === 0) {
|
||||
if ($fileinfo->key() === 0) {
|
||||
$query->setAttribute('progress', '100');
|
||||
$query->setAttribute('status', self::STATUS_UPLOADING);
|
||||
$query->setAttribute('path', $renditionPath);
|
||||
@@ -522,8 +545,6 @@ class VideosV1 extends Worker
|
||||
$query->setAttribute('status', self::STATUS_READY);
|
||||
$this->database->updateDocument('videos_renditions', $query->getId(), $query);
|
||||
$this->send($query, 'update');
|
||||
|
||||
Console::warning('Job total time ' . (microtime(true) - $startTime) . ' seconds');
|
||||
} catch (\Throwable $th) {
|
||||
$query->setAttribute('metadata', json_encode([
|
||||
'code' => $th->getCode(),
|
||||
@@ -768,6 +789,7 @@ class VideosV1 extends Worker
|
||||
*/
|
||||
private function write(Document $project, Document $file): bool
|
||||
{
|
||||
console::info('Writing file to ' . $this->inDir);
|
||||
|
||||
$fullPath = $file->getAttribute('path');
|
||||
$path = basename($file->getAttribute('path'));
|
||||
|
||||
Reference in New Issue
Block a user