refactor worker flow

This commit is contained in:
shimon
2022-06-27 16:06:46 +03:00
parent c7534760a0
commit c89b028797
10 changed files with 469 additions and 130 deletions
+45 -34
View File
@@ -2872,6 +2872,17 @@ $collections = [
'array' => false,
'filters' => [],
],
[
'$id' => 'size',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'duration',
'type' => Database::VAR_STRING,
@@ -2928,7 +2939,7 @@ $collections = [
'filters' => [],
],
[
'$id' => 'videoFrameRate',
'$id' => 'videoFramerate',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
@@ -2961,7 +2972,7 @@ $collections = [
'filters' => [],
],
[
'$id' => 'audioSampleRate',
'$id' => 'audioSamplerate',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => Database::LENGTH_KEY,
@@ -2988,18 +2999,7 @@ $collections = [
'$name' => 'Video_renditions',
'attributes' => [
[
'$id' => 'bucketId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'fileId',
'$id' => 'videoId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
@@ -3024,7 +3024,7 @@ $collections = [
'$id' => 'name',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 2048,
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
@@ -3053,6 +3053,17 @@ $collections = [
'array' => false,
'filters' => [],
],
[
'$id' => 'path',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'duration',
'type' => Database::VAR_STRING,
@@ -3066,10 +3077,10 @@ $collections = [
],
[
'$id' => 'width',
'type' => Database::VAR_INTEGER,
'type' => Database::VAR_STRING,
'format' => '',
'size' => 0,
'signed' => false,
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
@@ -3077,10 +3088,10 @@ $collections = [
],
[
'$id' => 'height',
'type' => Database::VAR_INTEGER,
'type' => Database::VAR_STRING,
'format' => '',
'size' => 0,
'signed' => false,
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
@@ -3099,17 +3110,17 @@ $collections = [
],
[
'$id' => 'videoBitrate',
'type' => Database::VAR_INTEGER,
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => 0,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'videoFrameRate',
'$id' => 'videoFramerate',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
@@ -3132,23 +3143,23 @@ $collections = [
],
[
'$id' => 'audioBitrate',
'type' => Database::VAR_INTEGER,
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => 0,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'audioSampleRate',
'type' => Database::VAR_INTEGER,
'$id' => 'audioSamplerate',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => 0,
'default' => null,
'array' => false,
'filters' => [],
],
@@ -3156,7 +3167,7 @@ $collections = [
'$id' => 'metadata',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'size' => 2048,
'signed' => true,
'required' => false,
'default' => null,
@@ -3167,7 +3178,7 @@ $collections = [
'$id' => 'status',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 100,
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
@@ -3178,7 +3189,7 @@ $collections = [
'$id' => 'progress',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 4,
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
@@ -3189,7 +3200,7 @@ $collections = [
'$id' => 'stream',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 255,
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
@@ -3201,7 +3212,7 @@ $collections = [
[
'$id' => '_key_bucket_file_stream',
'type' => Database::INDEX_KEY,
'attributes' => ['bucketId', 'fileId', 'stream'],
'attributes' => ['videoId', 'stream'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
+3 -2
View File
@@ -160,8 +160,9 @@ App::post('/v1/projects')
'name' => $profile['name'],
'videoBitrate' => $profile['videoBitrate'],
'audioBitrate' => $profile['audioBitrate'],
'width' => $profile['width'],
'height' => $profile['height']
'width' => $profile['width'],
'height' => $profile['height'],
'stream' => 'hls'
]));
});
}
+132 -25
View File
@@ -44,9 +44,31 @@ use Utopia\Validator\WhiteList;
use Utopia\Swoole\Request;
use Streaming\Representation;
App::get('/v1/video/profiles')
->alias('/v1/video/video/profiles', [])
->desc('Get all video profiles')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->inject('response')
->inject('dbForProject')
->action(function (Response $response, Database $dbForProject) {
$profiles = Authorization::skip(fn () => $dbForProject->find('video_profiles', [], 12, 0, [], ['ASC']));
if(empty($profiles)) {
throw new Exception('Video profiles where not found', 404, Exception::PROFILES_NOT_FOUND);
}
$response->dynamic(new Document([
'total' => $dbForProject->count('video_profiles', [], APP_LIMIT_COUNT),
'profiles' => $profiles,
]), Response::MODEL_VIDEO_PROFILE_LIST);
});
App::post('/v1/video/buckets/:bucketId/files/:fileId')
->alias('/v1/video/files', ['bucketId' => 'default'])
->desc('Start transcoding video')
->desc('Create video')
->groups(['api', 'storage'])
->label('scope', 'files.write')
// ->label('event', 'buckets.[bucketId].files.[fileId].create')
@@ -106,36 +128,121 @@ App::post('/v1/video/buckets/:bucketId/files/:fileId')
}
}
$profiles = Authorization::skip(fn () => $dbForProject->find('video_profiles', [], 12, 0, [], ['ASC']));
if ($profiles->empty()) {
throw new Exception('No video profiles found', 400, Exception::PROFILES_NOT_FOUND);
if ($bucket->getAttribute('permission') === 'bucket') {
// skip authorization
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
} else {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
}
$queries = [
new Query('bucketId', Query::TYPE_EQUAL, [$bucketId]),
new Query('fileId', Query::TYPE_EQUAL, [$fileId]),
];
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [$queries]));
if($video->empty()) {
$video = Authorization::skip(function () use ($dbForProject) {
return $this->database->createDocument('videos', new Document([
'bucketId' => $this->args['bucketId'],
'fileId' => $this->args['fileId'],
try {
$video = Authorization::skip(function () use ($dbForProject, $bucket, $file) {
return $dbForProject->createDocument('videos', new Document([
'bucketId' => $bucket->getId(),
'fileId' => $file->getId(),
'size' => $file->getAttribute('sizeOriginal'),
]));
});
} catch (StructureException $exception) {
throw new Exception($exception->getMessage(), 400, Exception::DOCUMENT_INVALID_STRUCTURE);
}
$response->dynamic(new Document([
'$id' => $video->getId(),
'fileId' => $video['fileId'],
'bucketId' => $video['bucketId'],
'size' => $video['size'],
]), Response::MODEL_VIDEO);
});
App::post('/v1/video/:videoId/rendition/:profileId')
->alias('/v1/video/files', ['bucketId' => 'default'])
->desc('Start transcoding video rendition')
->groups(['api', 'storage'])
->label('scope', 'files.write')
// ->label('event', 'buckets.[bucketId].files.[fileId].create')
->param('videoId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('profileId', '', new CustomId(), 'File ID. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('read', null, new Permissions(), 'An array of strings with read permissions. By default only the current user is granted with read permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new Permissions(), 'An array of strings with write permissions. By default only the current user is granted with write permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
->inject('request')
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('user')
->inject('audits')
->inject('usage')
->inject('events')
->inject('mode')
->inject('deviceFiles')
->inject('deviceLocal')
->action(action: function (string $videoId, string $profileId, ?array $read, ?array $write, Request $request, Response $response, Database $dbForProject, $project, Document $user, Audit $audits, Stats $usage, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal) {
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Document $user */
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [new Query('_uid', Query::TYPE_EQUAL, [$videoId])]));
if($video->isEmpty()){
throw new Exception('Video not found', 400, Exception::VIDEO_NOT_FOUND);
}
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $video['bucketId']));
if (
$bucket->isEmpty()
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)
) {
throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND);
}
// Check bucket permissions when enforced
$permissionBucket = $bucket->getAttribute('permission') === 'bucket';
if ($permissionBucket) {
$validator = new Authorization('write');
if (!$validator->isValid($bucket->getWrite())) {
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
}
}
$read = (is_null($read) && !$user->isEmpty()) ? ['user:' . $user->getId()] : $read ?? []; // By default set read permissions for user
$write = (is_null($write) && !$user->isEmpty()) ? ['user:' . $user->getId()] : $write ?? [];
// Users can only add their roles to files, API keys and Admin users can add any
$roles = Authorization::getRoles();
if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles)) {
foreach ($read as $role) {
if (!Authorization::isRole($role)) {
throw new Exception('Read permissions must be one of: (' . \implode(', ', $roles) . ')', 400, Exception::USER_UNAUTHORIZED);
}
}
foreach ($write as $role) {
if (!Authorization::isRole($role)) {
throw new Exception('Write permissions must be one of: (' . \implode(', ', $roles) . ')', 400, Exception::USER_UNAUTHORIZED);
}
}
}
if ($bucket->getAttribute('permission') === 'bucket') {
// skip authorization
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $video['fileId']));
} else {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $video['fileId']);
}
$profile = Authorization::skip(fn() => $dbForProject->findOne('video_profiles', [new Query('_uid', Query::TYPE_EQUAL, [$profileId])]));
if(!$profile){
throw new Exception('Video profile not found', 400, Exception::PROFILES_NOT_FOUND);
}
$transcoder = new Transcoding();
foreach ($profiles as $profile) {
$transcoder
->setUser($user)
->setProject($project)
->setVideoId($video->getId())
->setProfileId($profile->getId())
->trigger();
}
$transcoder
->setUser($user)
->setProject($project)
->setVideoId($video->getId())
->setProfileId($profile->getId())
->trigger();
$response->json(['result' => 'ok']);
});
+75 -42
View File
@@ -23,6 +23,15 @@ Console::success(APP_NAME . ' transcoding worker v1 has started');
class TranscodingV1 extends Worker
{
/**
* Rendition Status
*/
const STATUS_TRANSCODE_START = 'started';
const STATUS_TRANSCODE_END = 'ended';
const STATUS_UPLOADING = 'uploading';
const STATUS_PACKAGE_END = 'ready';
const STATUS_ERROR = 'error';
const HLS_BASE_URL = '';
protected string $basePath = '/tmp/';
@@ -45,32 +54,36 @@ class TranscodingV1 extends Worker
public function init(): void
{
$this->basePath .= $this->args['fileId'] . '/' . $this->args['profileId'];
$this->basePath .= $this->args['videoId'] . '/' . $this->args['profileId'];
$this->inDir = $this->basePath . '/in/';
$this->outDir = $this->basePath . '/out/';
@mkdir($this->inDir, 0755, true);
@mkdir($this->outDir, 0755, true);
$this->outPath = $this->outDir . $this->args['fileId']; /** TODO figure a way to write dir tree without this **/
$this->outPath = $this->outDir . $this->args['videoId']; /** TODO figure a way to write dir tree without this **/
}
public function run(): void
{
$project = new Document($this->args['project']);
$this->database = $this->getProjectDB($project->getId());
$profile = Authorization::skip(fn() => $this->database->findOne('video_profiles', [new Query('_uid', Query::TYPE_EQUAL, [$this->args['profileId']])]));
if($profile->isEmpty()){
throw new Exception('No profile found');
$sourceVideo = Authorization::skip(fn() => $this->database->findOne('videos', [new Query('_uid', Query::TYPE_EQUAL, [$this->args['videoId']])]));
if(empty($sourceVideo)){
throw new Exception('Video not found');
}
$profile = Authorization::skip(fn() => $this->database->findOne('video_profiles', [new Query('_uid', Query::TYPE_EQUAL, [$this->args['profileId']])]));
if(empty($profile)){
throw new Exception('profile not found');
}
$user = new Document($this->args['user'] ?? []);
$bucket = Authorization::skip(fn() => $this->database->getDocument('buckets', $this->args['bucketId']));
$bucket = Authorization::skip(fn() => $this->database->getDocument('buckets', $sourceVideo['bucketId']));
if ($bucket->getAttribute('permission') === 'bucket') {
$file = Authorization::skip(fn() => $this->database->getDocument('bucket_' . $bucket->getInternalId(), $this->args['fileId']));
$file = Authorization::skip(fn() => $this->database->getDocument('bucket_' . $bucket->getInternalId(), $video['fileId']));
} else {
$file = $this->database->getDocument('bucket_' . $bucket->getInternalId(), $this->args['fileId']);
$file = $this->database->getDocument('bucket_' . $bucket->getInternalId(), $sourceVideo['fileId']);
}
$data = $this->getFilesDevice($project->getId())->read($file->getAttribute('path'));
@@ -103,35 +116,47 @@ class TranscodingV1 extends Worker
throw new Exception('Not an valid FFMpeg file "' . $inPath . '"');
}
//TODO Can you retranscode?
//Delete prev rendition
$queries = [
new Query('bucketId', Query::TYPE_EQUAL, [$this->args['bucketId']]),
new Query('fileId', Query::TYPE_EQUAL, [$this->args['fileId']])
new Query('videoId', Query::TYPE_EQUAL, [$sourceVideo->getId()]),
new Query('profileId', Query::TYPE_EQUAL, [$profile->getId()])
];
$renditions = Authorization::skip(fn() => $this->database->find($collection, $queries, 12, 0, [], ['ASC']));
if (!empty($renditions)) {
foreach ($renditions as $rendition) {
Authorization::skip(fn() => $this->database->deleteDocument($collection, $rendition->getId()));
}
$rendition = Authorization::skip(fn() => $this->database->findOne($collection, $queries));
if (!empty($rendition)) {
Authorization::skip(fn() => $this->database->deleteDocument($collection, $rendition->getId()));
$deviceFiles = $this->getVideoDevice($project->getId());
$devicePath = $deviceFiles->getPath($this->args['fileId']);
$devicePath = str_ireplace($deviceFiles->getRoot(), $deviceFiles->getRoot() . DIRECTORY_SEPARATOR . $bucket->getId(), $devicePath);
$deviceFiles->deletePath($devicePath);
if(!empty($rendition['path'])) {
$deviceFiles->deletePath($rendition['path']);
}
}
$sourceInfo = $this->getVideoInfo($ffprobe->streams($inPath));
$stream = !empty($rendition['stream']) ? $rendition['stream'] : 'hls';
$general = $this->getMetadataExport($ffprobe->streams($inPath));
var_dump($general);
if(!empty($general)) {
foreach ($general as $key => $value) {
$sourceVideo->setAttribute($key, $value);
}
Authorization::skip(fn() => $this->database->updateDocument(
'videos',
$sourceVideo->getId(),
$sourceVideo
));
}
$video = $ffmpeg->open($inPath);
foreach (['hls', 'dash'] as $stream) {
$query = Authorization::skip(function () use ($collection, $profile, $stream) {
$query = Authorization::skip(function () use ($collection, $profile, $stream) {
return $this->database->createDocument($collection, new Document([
'bucketId' => $this->args['bucketId'],
'fileId' => $this->args['fileId'],
'videoId' => $this->args['videoId'],
'profileId' => $profile->getId(),
'name' => $profile->getAttribute('name'),
'startedAt' => time(),
'status' => 'started',
'status' => self::STATUS_TRANSCODE_START,
'stream' => $stream,
]));
});
@@ -156,11 +181,18 @@ class TranscodingV1 extends Worker
list($general, $metadata) = $this->transcode($stream, $video, $format, $representation);
if (!empty($metadata)) {
$query->setAttribute('metadata', json_encode($metadata));
}
$query->setAttribute('status', 'ended');
if(!empty($general)) {
foreach ($general as $key => $value) {
$query->setAttribute($key, $value);
}
}
$query->setAttribute('status', self::STATUS_TRANSCODE_END);
$query->setAttribute('endedAt', time());
Authorization::skip(fn() => $this->database->updateDocument(
$collection,
@@ -182,13 +214,16 @@ class TranscodingV1 extends Worker
}
$deviceFiles = $this->getVideoDevice($project->getId());
$devicePath = $deviceFiles->getPath($this->args['fileId']);
$devicePath = $deviceFiles->getPath($this->args['videoId']);
$devicePath = str_ireplace($deviceFiles->getRoot(), $deviceFiles->getRoot() . DIRECTORY_SEPARATOR . $bucket->getId(), $devicePath);
$data = $this->getFilesDevice($project->getId())->read($this->outDir . $fileName);
$this->getVideoDevice($project->getId())->write($devicePath . DIRECTORY_SEPARATOR . $profile->getAttribute('name') . DIRECTORY_SEPARATOR . $fileName, $data, \mime_content_type($this->outDir . $fileName));
var_dump($devicePath . DIRECTORY_SEPARATOR . $profile->getAttribute('name') . DIRECTORY_SEPARATOR . $fileName);
$renditionDir = $profile->getAttribute('width') . 'X' . $profile->getAttribute('height') . '@' .$profile->getAttribute('videoBitrate');
$renditionPath = $devicePath . DIRECTORY_SEPARATOR . $renditionDir;
$this->getVideoDevice($project->getId())->write($renditionPath . DIRECTORY_SEPARATOR . $fileName, $data, \mime_content_type($this->outDir . $fileName));
if ($start === 0) {
$query->setAttribute('status', 'uploading');
$query->setAttribute('status', self::STATUS_UPLOADING);
$query->setAttribute('path', $renditionPath);
Authorization::skip(fn() => $this->database->updateDocument(
$collection,
$query->getId(),
@@ -208,7 +243,7 @@ class TranscodingV1 extends Worker
@unlink($this->outDir . $fileName);
}
$query->setAttribute('status', 'ready');
$query->setAttribute('status', self::STATUS_PACKAGE_END);
Authorization::skip(fn() => $this->database->updateDocument(
$collection,
$query->getId(),
@@ -220,14 +255,13 @@ class TranscodingV1 extends Worker
'message' => $th->getMessage(),
]));
$query->setAttribute('status', 'error');
$query->setAttribute('status', self::STATUS_ERROR);
Authorization::skip(fn() => $this->database->updateDocument(
$collection,
$query->getId(),
$query
));
}
}
}
/**
@@ -240,8 +274,7 @@ class TranscodingV1 extends Worker
if (!empty($metadata['stream']['resolutions'][0])) {
$general = $metadata['stream']['resolutions'][0];
var_dump($general);
$parts = explode("x", $general);
$parts = explode("X", $general['dimension']);
$info['width'] = $parts['0'];
$info['height'] = $parts['1'];
}
@@ -251,12 +284,12 @@ class TranscodingV1 extends Worker
if ($streams['codec_type'] === 'video') {
$info['duration'] = $streams['duration'];
$info['videoCodec'] = $streams['codec_name'] . ',' . $streams['codec_tag_string'];
$info['videoBitRate'] = $streams['bit_rate'];
$info['videoFrameRate'] = $streams['avg_frame_rate'];
$info['videoBitrate'] = $streams['bit_rate'];
$info['videoFramerate'] = $streams['avg_frame_rate'];
} elseif ($streams['codec_type'] === 'audio') {
$info['audioCodec'] = $streams['codec_name'] . ',' . $streams['codec_tag_string'];
$info['audioBitRate'] = $streams['sample_rate'];
$info['audioSamplRate'] = $streams['bit_rate'];
$info['audioBitrate'] = $streams['sample_rate'];
$info['audioSamplerate'] = $streams['bit_rate'];
}
}
}
@@ -295,7 +328,7 @@ class TranscodingV1 extends Worker
return [
$this->getMetadataExport($dash->metadata()->export()),
['dash' => !empty($xml) ? json_decode(json_encode((array)$xml), true) : []],
['mpeg-dash' => !empty($xml) ? json_decode(json_encode((array)$xml), true) : []],
];
}
@@ -307,7 +340,7 @@ class TranscodingV1 extends Worker
->setAdditionalParams($additionalParams)
->setHlsBaseUrl(self::HLS_BASE_URL)
->save($this->outPath);
var_dump($this->outPath);
return [
$this->getMetadataExport($hls->metadata()->export()), []
];
+1 -2
View File
@@ -74,8 +74,7 @@ class Transcoding extends Event
return Resque::enqueue($this->queue, $this->class, [
'project' => $this->project,
'user' => $this->user,
'bucketId' => $this->bucketId,
'fileId' => $this->fileId,
'videoId' => $this->videoId,
'profileId' => $this->profileId,
]);
}
+1
View File
@@ -164,6 +164,7 @@ class Exception extends \Exception
/** Video */
public const PROFILES_NOT_FOUND = 'profiles_not_found';
public const VIDEO_NOT_FOUND = 'video_not_found';
private $type = '';
+12 -1
View File
@@ -70,6 +70,8 @@ use Appwrite\Utopia\Response\Model\UsageProject;
use Appwrite\Utopia\Response\Model\UsageStorage;
use Appwrite\Utopia\Response\Model\UsageUsers;
use Appwrite\Utopia\Response\Model\FileRendition;
use Appwrite\Utopia\Response\Model\Video;
use Appwrite\Utopia\Response\Model\VideoProfile;
/**
* @method Response setStatusCode(int $code = 200)
@@ -128,6 +130,11 @@ class Response extends SwooleResponse
public const MODEL_FILE_LIST = 'fileList';
public const MODEL_BUCKET = 'bucket';
public const MODEL_BUCKET_LIST = 'bucketList';
//video
public const MODEL_VIDEO = 'video';
public const MODEL_VIDEO_PROFILE = 'videoProfile';
public const MODEL_VIDEO_PROFILE_LIST = 'videoProfileList';
public const MODEL_FILE_RENDITION = 'fileRendition';
public const MODEL_FILE_RENDITIONS_LIST = 'fileRenditionsList';
@@ -221,7 +228,6 @@ class Response extends SwooleResponse
->setModel(new BaseList('Sessions List', self::MODEL_SESSION_LIST, 'sessions', self::MODEL_SESSION))
->setModel(new BaseList('Logs List', self::MODEL_LOG_LIST, 'logs', self::MODEL_LOG))
->setModel(new BaseList('Files List', self::MODEL_FILE_LIST, 'files', self::MODEL_FILE))
->setModel(new BaseList('File Renditions List', self::MODEL_FILE_RENDITIONS_LIST, 'renditions', self::MODEL_FILE_RENDITION))
->setModel(new BaseList('Buckets List', self::MODEL_BUCKET_LIST, 'buckets', self::MODEL_BUCKET))
->setModel(new BaseList('Teams List', self::MODEL_TEAM_LIST, 'teams', self::MODEL_TEAM))
->setModel(new BaseList('Memberships List', self::MODEL_MEMBERSHIP_LIST, 'memberships', self::MODEL_MEMBERSHIP))
@@ -241,6 +247,9 @@ class Response extends SwooleResponse
->setModel(new BaseList('Currencies List', self::MODEL_CURRENCY_LIST, 'currencies', self::MODEL_CURRENCY))
->setModel(new BaseList('Phones List', self::MODEL_PHONE_LIST, 'phones', self::MODEL_PHONE))
->setModel(new BaseList('Metric List', self::MODEL_METRIC_LIST, 'metrics', self::MODEL_METRIC, true, false))
->setModel(new BaseList('File Renditions List', self::MODEL_FILE_RENDITIONS_LIST, 'renditions', self::MODEL_FILE_RENDITION))
->setModel(new BaseList('video profile List', self::MODEL_VIDEO_PROFILE_LIST, 'profiles', self::MODEL_VIDEO_PROFILE))
// Entities
->setModel(new Collection())
->setModel(new Attribute())
@@ -295,6 +304,8 @@ class Response extends SwooleResponse
->setModel(new UsageFunctions())
->setModel(new UsageProject())
->setModel(new FileRendition())
->setModel(new Video())
->setModel(new VideoProfile())
// Verification
// Recovery
@@ -0,0 +1,59 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class Video extends Model
{
public function __construct()
{
$this
->addRule('$id', [
'type' => self::TYPE_STRING,
'description' => 'ID.',
'default' => '',
'example' => '5e5ea5c16897e',
])
->addRule('bucketId', [
'type' => self::TYPE_STRING,
'description' => 'Bucket ID.',
'default' => '',
'example' => '5e5ea5c16897e',
])
->addRule('fileId', [
'type' => self::TYPE_STRING,
'description' => 'File ID.',
'default' => '',
'example' => 'd5fg5ehg1c168g7c',
])
->addRule('size', [
'type' => self::TYPE_INTEGER,
'description' => 'File SIZE.',
'default' => '',
'example' => 3567790,
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'Video entity';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_VIDEO;
}
}
@@ -0,0 +1,78 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class VideoProfile extends Model
{
public function __construct()
{
$this
->addRule('$id', [
'type' => self::TYPE_STRING,
'description' => 'ID.',
'default' => '',
'example' => '5e5ea5c16897e',
])
->addRule('name', [
'type' => self::TYPE_STRING,
'description' => 'Video profile name.',
'default' => '',
'example' => '360P',
])
->addRule('videoBitrate', [
'type' => self::TYPE_INTEGER,
'description' => 'Video bitrate.',
'default' => '',
'example' => 3,
])
->addRule('audioBitrate', [
'type' => self::TYPE_INTEGER,
'description' => 'Audio bitrate.',
'default' => '',
'example' => 3,
])
->addRule('width', [
'type' => self::TYPE_INTEGER,
'description' => 'Video width.',
'default' => '',
'example' => 300,
])
->addRule('height', [
'type' => self::TYPE_INTEGER,
'description' => 'Video height.',
'default' => '',
'example' => 400,
])
->addRule('stream', [
'type' => self::TYPE_STRING,
'description' => 'http video stream type.',
'default' => '',
'example' => 'mpeg-dash',
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'Video profile';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_VIDEO_PROFILE;
}
}
@@ -13,9 +13,8 @@ class VideoCustomServerTest extends Scope
use ProjectCustom;
use SideServer;
public function testTranscoding(): array
public function testCreateBucketFile(): array
{
$bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -61,40 +60,80 @@ class VideoCustomServerTest extends Scope
}
@fclose($handle);
$pid = $this->getProject()['$id'];
$key = $this->getProject()['apiKey'];
$fid = $id;
$bid = $bucket['body']['$id'];
return [
'bucketId' => $bucket['body']['$id'],
'fileId' => $id,
];
}
var_dump($pid);
var_dump($key);
var_dump($fid);
var_dump($bid);
/**
* @depends testCreateBucketFile
*/
public function testTranscodingRendition($data): array
{
//
// $pid = '62b1956c92e0e39ef577';
// $key = 'f9b8dd3800b93a3dd513cf7cbcbd436cd61850b2c101662210e8ee2f052f796b3d4c7a08149634ce90da6037f6164d7faa36b32b91b568524e6720014e149b83f7a970c28de1a14a97a69010be325d142d51ca51f0f1b29783a7c1f4689d1b90a42cf19a7b55ec9ea6dc51974a1740b67e71de9f80009c2d91c6f3c686aa616c';
// $fid = '62b1956e4f03f57a0f74';
// $bid = '62b1956d0c600d70c8f7';
//
$transcoding = $this->client->call(Client::METHOD_POST, '/video/buckets/' . $bid . '/files/' . $fid, [
$response = $this->client->call(Client::METHOD_POST, '/video/buckets/' . $data['bucketId'] . '/files/' . $data['fileId'], [
'content-type' => 'application/json',
'x-appwrite-project' => $pid,
'x-appwrite-key' => $key,
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'read' => ['role:all'],
'write' => ['role:all']
]);
$videoId = $response['body']['$id'];
$response = $this->client->call(Client::METHOD_GET, '/video/profiles', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$profileId = $response['body']['profiles'][0]['$id'];
$response = $this->client->call(Client::METHOD_POST, '/video/' . $videoId . '/rendition/' . $profileId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'read' => ['role:all'],
'write' => ['role:all']
]);
var_dump($response['body']);
// var_dump($pid);
// var_dump($key);
// var_dump($fid);
// var_dump($bid);
//
////
//// $pid = '62b1956c92e0e39ef577';
//// $key = 'f9b8dd3800b93a3dd513cf7cbcbd436cd61850b2c101662210e8ee2f052f796b3d4c7a08149634ce90da6037f6164d7faa36b32b91b568524e6720014e149b83f7a970c28de1a14a97a69010be325d142d51ca51f0f1b29783a7c1f4689d1b90a42cf19a7b55ec9ea6dc51974a1740b67e71de9f80009c2d91c6f3c686aa616c';
//// $fid = '62b1956e4f03f57a0f74';
//// $bid = '62b1956d0c600d70c8f7';
////
// $video = $this->client->call(Client::METHOD_POST, '/video/buckets/' . $bid . '/files/' . $fid, [
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $pid,
// 'x-appwrite-key' => $key,
// ], [
// 'read' => ['role:all'],
// 'write' => ['role:all']
// ]);
// var_dump($video);
return [
'projectId' => $pid,
'apiKey' => $key,
'bucketId' => $bid,
'fileId' => $fid,
'videoId' => $videoId,
'profileId' => $profileId,
];
}
/**
* @depends testCreateBucketFile
*/
public function testRenditions(): void
{