sync with 0.16

This commit is contained in:
shimon
2022-09-07 11:16:38 +03:00
parent aef8c1b207
commit baea836574
7 changed files with 145 additions and 209 deletions
+1 -1
View File
@@ -4060,7 +4060,7 @@ $collections = [
],
'indexes' => [
[
'$id' => '_key_unique',
'$id' => '_key_profile',
'type' => Database::INDEX_KEY,
'attributes' => ['protocol', 'width', 'height', 'videoBitrate', 'audioBitrate' ],
'lengths' => [],
+35
View File
@@ -549,4 +549,39 @@ return [
'description' => 'Domain verification for the requested domain has failed.',
'code' => 401,
],
Exception::VIDEO_PROFILE_NOT_FOUND => [
'name' => Exception::VIDEO_PROFILE_NOT_FOUND,
'description' => 'Video profile not found.',
'code' => 404,
],
Exception::VIDEO_NOT_VALID => [
'name' => Exception::VIDEO_NOT_VALID,
'description' => 'Video not valid.',
'code' => 400,
],
Exception::VIDEO_RENDITION_NOT_FOUND => [
'name' => Exception::VIDEO_RENDITION_NOT_FOUND,
'description' => 'Video rendition not found.',
'code' => 404,
],
Exception::VIDEO_SUBTITLE_NOT_FOUND => [
'name' => Exception::VIDEO_SUBTITLE_NOT_FOUND,
'description' => 'Video subtitle not found.',
'code' => 404,
],
Exception::VIDEO_NOT_FOUND => [
'name' => Exception::VIDEO_NOT_FOUND,
'description' => 'Video not found.',
'code' => 404,
],
Exception::VIDEO_RENDITION_SEGMENT_NOT_FOUND => [
'name' => Exception::VIDEO_RENDITION_SEGMENT_NOT_FOUND,
'description' => 'Video rendition segment not found',
'code' => 404,
],
Exception::VIDEO_SUBTITLE_SEGMENT_NOT_FOUND => [
'name' => Exception::VIDEO_SUBTITLE_SEGMENT_NOT_FOUND,
'description' => 'Video subtitle segment not found',
'code' => 404,
],
];
+89 -179
View File
@@ -4,6 +4,7 @@ use Appwrite\Auth\Auth;
use Appwrite\Event\Delete;
use Appwrite\Event\Transcoding;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries\Files;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\View;
use Utopia\App;
@@ -78,6 +79,7 @@ App::post('/v1/videos')
->action(action: function (string $bucketId, string $fileId, Request $request, Response $response, Database $dbForProject, Document $user, string $mode) {
$file = validateFilePermissions($dbForProject, $bucketId, $fileId, $mode, $user);
$video = Authorization::skip(function () use ($dbForProject, $bucketId, $file) {
return $dbForProject->createDocument('videos', new Document([
'bucketId' => $bucketId,
@@ -85,6 +87,7 @@ App::post('/v1/videos')
'size' => $file->getAttribute('sizeOriginal'),
]));
});
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($video, Response::MODEL_VIDEO);
});
@@ -108,10 +111,9 @@ App::delete('/v1/videos/:videoId')
->inject('deletes')
->action(function (string $videoId, Response $response, Document $project, Database $dbForProject, string $mode, Document $user, Delete $deletes) {
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [new Query('_uid', Query::TYPE_EQUAL, [$videoId])]));
$video = Authorization::skip(fn() => $dbForProject->getDocument('videos', $videoId));
if ($video->isEmpty()) {
throw new Exception('Video not found', 404, Exception::VIDEO_NOT_FOUND);
throw new Exception(Exception::VIDEO_NOT_FOUND);
}
$deleted = $dbForProject->deleteDocument('videos', $videoId);
@@ -147,14 +149,12 @@ App::put('/v1/videos/:videoId')
->inject('user')
->action(function (string $videoId, $bucketId, $fileId, Response $response, Document $project, Database $dbForProject, string $mode, Document $user) {
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [new Query('_uid', Query::TYPE_EQUAL, [$videoId])]));
$video = Authorization::skip(fn() => $dbForProject->getDocument('videos', $videoId));
if ($video->isEmpty()) {
throw new Exception('Video not found', 404, Exception::VIDEO_NOT_FOUND);
throw new Exception(Exception::VIDEO_NOT_FOUND);
}
$file = validateFilePermissions($dbForProject, $bucketId, $fileId, $mode, $user);
$video = Authorization::skip(function () use ($dbForProject, $videoId, $bucketId, $file) {
return $dbForProject->updateDocument('videos', $videoId, new Document([
'bucketId' => $bucketId,
@@ -192,9 +192,8 @@ App::get('/v1/videos/:videoId')
->action(function (string $videoId, Response $response, Database $dbForProject) {
$video = Authorization::skip(fn() => $dbForProject->getDocument('videos', $videoId));
if ($video->isEmpty()) {
throw new Exception('Video not found', 404, Exception::VIDEO_NOT_FOUND);
throw new Exception(Exception::VIDEO_NOT_FOUND);
}
$response->dynamic($video, Response::MODEL_VIDEO);
@@ -211,36 +210,36 @@ App::get('/v1/videos')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_VIDEO)
->param('limit', 25, new Range(0, 100), 'Maximum number of files to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this param to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursor', '', new UID(), 'ID of the file used as the starting point for the query, excluding the file itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor, can be either \'before\' or \'after\'.', true)
->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
->param('queries', [], new Files(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Files::ALLOWED_ATTRIBUTES), true)
->inject('response')
->inject('dbForProject')
->action(function (int $limit, int $offset, string $cursor, string $cursorDirection, string $orderType, Response $response, Database $dbForProject) {
->action(function (array $queries, Response $response, Database $dbForProject) {
if (!empty($cursor)) {
$cursorFile = $dbForProject->getDocument('videos', $cursor);
if ($cursorFile->isEmpty()) {
throw new Exception("File '{$cursor}' for the 'cursor' value not found.", 400, Exception::GENERAL_CURSOR_NOT_FOUND);
$queries = Query::parseQueries($queries);
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE);
$cursor = reset($cursor);
if ($cursor) {
/** @var Query $cursor */
$fileId = $cursor->getValue();
$cursorDocument = Authorization::skip(fn() => $dbForProject->getDocument('videos', $fileId));
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "File '{$fileId}' for the 'cursor' value not found.");
}
$cursor->setValue($cursorDocument);
}
$videos = $dbForProject->find('videos', [], $limit, $offset, [], [$orderType], $cursorFile ?? null, $cursorDirection);
$videos = $dbForProject->find('videos', $queries);
if (empty($videos)) {
throw new Exception('Videos not found', 404, Exception::VIDEO_NOT_FOUND);
}
$response->dynamic(new Document([
'total' => $dbForProject->count('videos', [], APP_LIMIT_COUNT),
'total' => $dbForProject->count('videos', $queries, APP_LIMIT_COUNT),
'videos' => $videos,
]), Response::MODEL_VIDEO_LIST);
});
App::post('/v1/videos/:videoId/subtitles')
->desc('Add subtitle to video')
->groups(['api', 'video'])
@@ -265,10 +264,9 @@ App::post('/v1/videos/:videoId/subtitles')
->inject('mode')
->action(action: function (string $videoId, string $bucketId, string $fileId, string $name, string $code, bool $default, Request $request, Response $response, Database $dbForProject, Document $user, string $mode) {
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [new Query('_uid', Query::TYPE_EQUAL, [$videoId])]));
$video = Authorization::skip(fn() => $dbForProject->getDocument('videos', $videoId));
if (empty($video)) {
throw new Exception('Video not found', 404, Exception::VIDEO_NOT_FOUND);
throw new Exception(Exception::VIDEO_NOT_FOUND);
}
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode, $user);
@@ -289,7 +287,6 @@ App::post('/v1/videos/:videoId/subtitles')
$response->dynamic($subtitle, Response::MODEL_SUBTITLE);
});
App::patch('/v1/videos/:videoId/subtitles/:subtitleId')
->desc('Update video subtitle')
->groups(['api', 'video'])
@@ -313,9 +310,8 @@ App::patch('/v1/videos/:videoId/subtitles/:subtitleId')
->action(action: function (string $subtitleId, string $videoId, string $bucketId, string $fileId, string $name, string $code, bool $default, Response $response, Database $dbForProject) {
$subtitle = Authorization::skip(fn() => $dbForProject->getDocument('videos_subtitles', $subtitleId));
if ($subtitle->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::VIDEO_SUBTITLE_NOT_FOUND);
}
$subtitle->setAttribute('videoId', $videoId)
@@ -330,7 +326,6 @@ App::patch('/v1/videos/:videoId/subtitles/:subtitleId')
$response->dynamic($subtitle, Response::MODEL_SUBTITLE);
});
App::delete('/v1/videos/:videoId/subtitles/:subtitleId')
->desc('Delete video subtitle')
->groups(['api', 'video'])
@@ -349,16 +344,14 @@ App::delete('/v1/videos/:videoId/subtitles/:subtitleId')
->inject('mode')
->action(function (string $videoId, string $subtitleId, Response $response, Database $dbForProject, Document $user, string $mode) {
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [new Query('_uid', Query::TYPE_EQUAL, [$videoId])]));
if ($video->isEmpty()) {
throw new Exception('Video not found', 404, Exception::VIDEO_NOT_FOUND);
$video = Authorization::skip(fn() => $dbForProject->getDocument('videos', $videoId));
if (empty($video)) {
throw new Exception(Exception::VIDEO_NOT_FOUND);
}
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode, $user);
$subtitle = Authorization::skip(fn() => $dbForProject->getDocument('videos_subtitles', $subtitleId));
if ($subtitle->isEmpty()) {
throw new Exception('Video subtitle not found', 404, Exception::VIDEO_PROFILE_NOT_FOUND);
}
@@ -372,7 +365,6 @@ App::delete('/v1/videos/:videoId/subtitles/:subtitleId')
$response->noContent();
});
App::get('/v1/videos/:videoId/subtitles')
->desc('Get all video subtitles')
->groups(['api', 'video'])
@@ -389,19 +381,21 @@ App::get('/v1/videos/:videoId/subtitles')
->inject('dbForProject')
->action(function ($videoId, Response $response, Database $dbForProject) {
$subtitles = Authorization::skip(fn () => $dbForProject->find('videos_subtitles', [new Query('videoId', Query::TYPE_EQUAL, [$videoId])]));
$query = [
Query::equal('videoId', [$videoId]),
];
$subtitles = Authorization::skip(fn () => $dbForProject->find('videos_subtitles', $query));
if (empty($subtitles)) {
throw new Exception('Video subtitles not found', 404, Exception::VIDEO_SUBTITLE_NOT_FOUND);
throw new Exception(Exception::VIDEO_SUBTITLE_NOT_FOUND);
}
$response->dynamic(new Document([
'total' => $dbForProject->count('videos_subtitles', [], APP_LIMIT_COUNT),
'total' => $dbForProject->count('videos_subtitles', $query, APP_LIMIT_COUNT),
'subtitles' => $subtitles,
]), Response::MODEL_SUBTITLE_LIST);
});
App::post('/v1/videos/:videoId/rendition')
->alias('/v1/videos/:videoId/rendition', [])
->desc('Create video rendition')
@@ -423,23 +417,16 @@ App::post('/v1/videos/:videoId/rendition')
->inject('mode')
->action(action: function (string $videoId, string $profileId, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, string $mode) {
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [
Query::equal('_uid', [$videoId]),
]));
$video = Authorization::skip(fn() => $dbForProject->getDocument('videos', $videoId));
if (empty($video)) {
throw new Exception('Video not found', 404, Exception::VIDEO_NOT_FOUND);
throw new Exception(Exception::VIDEO_NOT_FOUND);
}
validateFilePermissions($dbForProject, $video->getAttribute('bucketId'), $video->getAttribute('fileId'), $mode, $user);
$profile = Authorization::skip(fn() => $dbForProject->findOne('videos_profiles', [
Query::equal('_uid', [$profileId]),
]));
$profile = Authorization::skip(fn() => $dbForProject->getDocument('videos_profiles', $profileId));
if (empty($profile)) {
throw new Exception('Video profile not found', 404, Exception::VIDEO_PROFILE_NOT_FOUND);
throw new Exception(Exception::VIDEO_PROFILE_NOT_FOUND);
}
$transcoder = new Transcoding();
@@ -453,7 +440,6 @@ App::post('/v1/videos/:videoId/rendition')
$response->noContent();
});
App::delete('/v1/videos/:videoId/renditions/:renditionId')
->desc('Delete video rendition')
->groups(['api', 'video'])
@@ -473,24 +459,22 @@ App::delete('/v1/videos/:videoId/renditions/:renditionId')
->inject('deviceVideos')
->action(function (string $videoId, string $renditionId, Response $response, Database $dbForProject, string $mode, Document $user, Device $deviceVideos) {
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [new Query('_uid', Query::TYPE_EQUAL, [$videoId])]));
if ($video->isEmpty()) {
throw new Exception('Video not found', 404, Exception::VIDEO_NOT_FOUND);
$video = Authorization::skip(fn() => $dbForProject->getDocument('videos', $videoId));
if (empty($video)) {
throw new Exception(Exception::VIDEO_NOT_FOUND);
}
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode, $user);
$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);
throw new Exception(Exception::VIDEO_RENDITION_NOT_FOUND);
}
$deleted = $dbForProject->deleteDocument('videos_renditions', $renditionId);
if (!$deleted) {
throw new Exception('Failed to remove video rendition from DB', 500, Exception::GENERAL_SERVER_ERROR);
throw new Exception(Exception::GENERAL_SERVER_ERROR);
}
Authorization::skip(fn() => $dbForProject->deleteDocument('videos_renditions', $rendition->getId()));
@@ -501,7 +485,6 @@ App::delete('/v1/videos/:videoId/renditions/:renditionId')
$response->noContent();
});
App::get('/v1/videos/:videoId/rendition/:renditionId')
->desc('Get all backet\'s videos')
->groups(['api', 'video'])
@@ -521,17 +504,14 @@ App::get('/v1/videos/:videoId/rendition/:renditionId')
->inject('user')
->action(function ($videoId, $renditionId, Response $response, Database $dbForProject, string $mode, Document $user) {
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [new Query('_uid', Query::TYPE_EQUAL, [$videoId])]));
$video = Authorization::skip(fn() => $dbForProject->getDocument('videos', $videoId));
if ($video->isEmpty()) {
throw new Exception('Video not found', 404, Exception::VIDEO_NOT_FOUND);
}
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode, $user);
$rendition = Authorization::skip(fn () => $dbForProject->findOne('videos_renditions', [new Query('_uid', Query::TYPE_EQUAL, [$renditionId])]));
$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);
}
@@ -539,7 +519,6 @@ App::get('/v1/videos/:videoId/rendition/:renditionId')
$response->dynamic($rendition, Response::MODEL_RENDITION);
});
App::get('/v1/videos/:videoId/renditions')
->desc('Get video renditions')
->groups(['api', 'video'])
@@ -558,18 +537,16 @@ App::get('/v1/videos/:videoId/renditions')
->inject('user')
->action(function (string $videoId, Response $response, Database $dbForProject, string $mode, 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', 404, Exception::VIDEO_NOT_FOUND);
$video = Authorization::skip(fn() => $dbForProject->getDocument('videos', $videoId));
if (empty($video)) {
throw new Exception(Exception::VIDEO_NOT_FOUND);
}
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode, $user);
$queries = [
new Query('videoId', Query::TYPE_EQUAL, [$video->getId()]),
new Query('endedAt', Query::TYPE_GREATER, [0]),
new Query('status', Query::TYPE_EQUAL, ['ready']),
Query::equal('videoId', [$video->getId()]),
Query::equal('status', ['ready']),
];
$renditions = Authorization::skip(fn () => $dbForProject->find('videos_renditions', $queries));
@@ -580,7 +557,6 @@ App::get('/v1/videos/:videoId/renditions')
]), Response::MODEL_RENDITION_LIST);
});
App::get('/v1/videos/:videoId/protocols/:protocolId')
->desc('Get video master renditions manifest')
->groups(['api', 'video'])
@@ -599,12 +575,9 @@ App::get('/v1/videos/:videoId/protocols/:protocolId')
->inject('user')
->action(function (string $videoId, string $protocolId, Response $response, Database $dbForProject, string $mode, Document $user) {
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [
Query::equal('_uid', [$videoId]),
]));
$video = Authorization::skip(fn() => $dbForProject->getDocument('videos', $videoId));
if (empty($video)) {
throw new Exception('Video not found', 404, Exception::VIDEO_NOT_FOUND);
throw new Exception(Exception::VIDEO_NOT_FOUND);
}
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode, $user);
@@ -616,7 +589,7 @@ App::get('/v1/videos/:videoId/protocols/:protocolId')
]));
if (empty($renditions)) {
throw new Exception('Renditions not found', 404, Exception::VIDEO_RENDITION_NOT_FOUND);
throw new Exception(Exception::VIDEO_RENDITION_NOT_FOUND);
}
$baseUrl = 'http://127.0.0.1/v1/videos/' . $videoId . '/protocols/' . $protocolId;
@@ -625,7 +598,7 @@ App::get('/v1/videos/:videoId/protocols/:protocolId')
]));
$_renditions = [];
$_subtitles = [];
$_subtitles = [];
if ($protocolId === 'hls') {
foreach ($subtitles ?? [] as $subtitle) {
@@ -693,10 +666,10 @@ App::get('/v1/videos/:videoId/protocols/:protocolId')
$representation['attributes'] = $attributes['@attributes'] ?? [];
$attributes = (array)$adaptation->Representation->SegmentList->attributes();
$representation['segmentList']['attributes'] = $attributes['@attributes'] ?? [];
$segments = Authorization::skip(fn() => $dbForProject->find('videos_renditions_segments', [
new Query('renditionId', Query::TYPE_EQUAL, [$rendition->getId()]),
new Query('streamId', Query::TYPE_EQUAL, [$streamId]),
], 1000, 0, ['streamId']));
$segments = Authorization::skip(fn () => $dbForProject->find('videos_renditions_segments', [
Query::equal('renditionId', [$rendition->getId()]),
Query::equal('streamId', [$streamId]),
]));
if (count($segments) === 0) {
continue;
@@ -743,7 +716,6 @@ App::get('/v1/videos/:videoId/protocols/:protocolId')
}
});
App::get('/v1/videos/:videoId/protocols/:protocolId/renditions/:renditionId/streams/:streamId')
->desc('Get video rendition manifest')
->groups(['api', 'video'])
@@ -765,21 +737,16 @@ App::get('/v1/videos/:videoId/protocols/:protocolId/renditions/:renditionId/stre
->inject('user')
->action(function (string $videoId, string $protocolId, string $renditionId, string $streamId, Response $response, Database $dbForProject, string $mode, Document $user) {
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [
Query::equal('_uid', [$videoId]),
]));
$video = Authorization::skip(fn() => $dbForProject->getDocument('videos', $videoId));
if (empty($video)) {
throw new Exception('Video not found', 404, Exception::VIDEO_NOT_FOUND);
throw new Exception(Exception::VIDEO_NOT_FOUND);
}
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode, $user);
$rendition = Authorization::skip(fn () => $dbForProject->findOne('videos_renditions', [
Query::equal('_uid', [$renditionId]),
Query::equal('videoId', [$video->getId()]),
Query::equal('status', ['ready']),
Query::equal('protocol', [$protocolId]),
]));
if (empty($rendition)) {
@@ -805,7 +772,7 @@ App::get('/v1/videos/:videoId/protocols/:protocolId/renditions/:renditionId/stre
$template = new View(__DIR__ . '/../../views/videos/hls.phtml');
$template->setParam('targetDuration', $rendition->getAttribute('targetDuration'));
$template->setParam('paramsSegments', $_segments);
$template->setParam('segments', $_segments);
$response->setContentType('application/x-mpegurl')
->send($template->render(false));
});
@@ -833,37 +800,9 @@ App::get('/v1/videos/:videoId/protocols/:protocolId/renditions/:renditionId/segm
->inject('user')
->action(function (string $videoId, string $protocolId, string $renditionId, string $segmentId, Response $response, Database $dbForProject, Device $deviceVideos, string $mode, Document $user) {
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [
Query::equal('_uid', [$videoId]),
]));
if (empty($video)) {
throw new Exception('Video not found', 404, Exception::VIDEO_NOT_FOUND);
}
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode, $user);
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [
Query::equal('_uid', [$videoId]),
]));
$rendition = Authorization::skip(fn () => $dbForProject->findOne('videos_renditions', [
Query::equal('_uid', [$renditionId]),
Query::equal('videoId', [$video->getId()]),
Query::equal('status', ['ready']),
Query::equal('protocol', [$protocolId]),
]));
if (empty($rendition)) {
throw new Exception('Rendition not found', 404, Exception::VIDEO_RENDITION_NOT_FOUND);
}
$segment = Authorization::skip(fn () => $dbForProject->findOne('videos_renditions_segments', [
Query::equal('_uid', [$segmentId]),
]));
$segment = Authorization::skip(fn() => $dbForProject->getDocument('videos_renditions_segments', $segmentId));
if (empty($segment)) {
throw new Exception('Rendition segments not found', 404, Exception::VIDEO_RENDITION_SEGMENT_NOT_FOUND);
throw new Exception('segment not found', 404, Exception::VIDEO_RENDITION_SEGMENT_NOT_FOUND);
}
$output = $deviceVideos->read($segment->getAttribute('path') . $segment->getAttribute('fileName'));
@@ -899,19 +838,16 @@ App::get('/v1/videos/:videoId/protocols/:protocolId/subtitles/:subtitleId')
->inject('user')
->action(function (string $videoId, string $protocolId, string $subtitleId, Response $response, Database $dbForProject, Device $deviceVideos, string $mode, Document $user) {
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [
new Query('_uid', Query::TYPE_EQUAL, [$videoId])
]));
$video = Authorization::skip(fn() => $dbForProject->getDocument('videos', $videoId));
if (empty($video)) {
throw new Exception('Video not found', 404, Exception::VIDEO_NOT_FOUND);
throw new Exception(Exception::VIDEO_NOT_FOUND);
}
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode, $user);
$subtitle = Authorization::skip(fn() => $dbForProject->findOne('videos_subtitles', [
new Query('_uid', Query::TYPE_EQUAL, [$subtitleId]),
new Query('status', Query::TYPE_EQUAL, ['ready'])
$subtitle = Authorization::skip(fn () => $dbForProject->find('videos_subtitles', [
Query::equal('_uid', [$subtitleId]),
Query::equal('status', ['ready']),
]));
if (empty($subtitle)) {
@@ -919,17 +855,14 @@ App::get('/v1/videos/:videoId/protocols/:protocolId/subtitles/:subtitleId')
}
if ($protocolId == 'hls') {
$segments = Authorization::skip(fn() => $dbForProject->find('videos_subtitles_segments', [
new Query('subtitleId', Query::TYPE_EQUAL, [$subtitleId]),
], 4000));
$segments = Authorization::skip(fn() => $dbForProject->getDocument('videos_subtitles_segments', $subtitleId));
if (empty($segments)) {
throw new Exception('Subtitle segments not found', 404, Exception::VIDEO_SUBTITLE_SEGMENT_NOT_FOUND);
}
$paramsSegments = [];
$_segments = [];
foreach ($segments as $segment) {
$paramsSegments[] = [
$_segments[] = [
'duration' => $segment->getAttribute('duration'),
'url' => 'http://127.0.0.1/v1/videos/' . $videoId . '/protocols/' . $protocolId . '/subtitles/' . $subtitleId . '/segments/' . $segment->getId(),
];
@@ -937,11 +870,13 @@ App::get('/v1/videos/:videoId/protocols/:protocolId/subtitles/:subtitleId')
$template = new View(__DIR__ . '/../../views/videos/hls-subtitles.phtml');
$template->setParam('targetDuration', $subtitle->getAttribute('targetDuration'));
$template->setParam('paramsSegments', $paramsSegments);
$response->setContentType('application/x-mpegurl')->send($template->render(false));
$template->setParam('paramsSegments', $_segments);
$response->setContentType('application/x-mpegurl')
->send($template->render(false));
} else {
$output = $deviceVideos->read($deviceVideos->getPath($subtitle->getAttribute('videoId')) . '/' . $subtitle->getId() . '.vtt');
$response->setContentType('text/vtt')->send($output);
$response->setContentType('text/vtt')
->send($output);
}
});
@@ -968,37 +903,23 @@ App::get('/v1/videos/:videoId/protocols/:protocolId/subtitles/:subtitleId/segmen
->inject('user')
->action(function (string $videoId, string $protocolId, string $subtitleId, string $segmentId, Response $response, Database $dbForProject, Device $deviceVideos, string $mode, Document $user) {
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [
new Query('_uid', Query::TYPE_EQUAL, [$videoId])
]));
$video = Authorization::skip(fn() => $dbForProject->getDocument('videos', $videoId));
if (empty($video)) {
throw new Exception('Video not found', 404, Exception::VIDEO_NOT_FOUND);
throw new Exception(Exception::VIDEO_NOT_FOUND);
}
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode, $user);
$subtitle = Authorization::skip(fn() => $dbForProject->findOne('videos_subtitles', [
new Query('_uid', Query::TYPE_EQUAL, [$subtitleId]),
new Query('status', Query::TYPE_EQUAL, ['ready'])
]));
if (empty($subtitle)) {
throw new Exception('subtitle not found', 404, Exception::VIDEO_SUBTITLE_NOT_FOUND);
}
$segment = Authorization::skip(fn () => $dbForProject->findOne('videos_subtitles_segments', [
new Query('_uid', Query::TYPE_EQUAL, [$segmentId])]));
$segment = Authorization::skip(fn() => $dbForProject->getDocument('videos_subtitles_segments', $segmentId));
if (empty($segment)) {
throw new Exception('Subtitle segments not found', 404, Exception::VIDEO_SUBTITLE_SEGMENT_NOT_FOUND);
throw new Exception(Exception::VIDEO_SUBTITLE_SEGMENT_NOT_FOUND);
}
$output = $deviceVideos->read($segment->getAttribute('path') . $segment->getAttribute('fileName'));
$response->setContentType('text/vtt')->send($output);
$response->setContentType('text/vtt')
->send($output);
});
App::post('/v1/videos/profiles')
->desc('Create video profile')
->groups(['api', 'video'])
@@ -1019,7 +940,7 @@ App::post('/v1/videos/profiles')
->inject('response')
->inject('dbForProject')
->action(action: function (string $name, string $videoBitrate, string $audioBitrate, string $width, string $height, string $protocol, Response $response, Database $dbForProject) {
try {
$profile = Authorization::skip(function () use ($dbForProject, $name, $videoBitrate, $audioBitrate, $width, $height, $protocol) {
return $dbForProject->createDocument('videos_profiles', new Document([
'name' => $name,
@@ -1030,15 +951,11 @@ App::post('/v1/videos/profiles')
'protocol' => $protocol,
]));
});
} catch (DuplicateException $exception) {
throw new Exception('Profile already exists', 409, Exception::VIDEO_PROFILE_ALREADY_EXISTS);
}
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($profile, Response::MODEL_PROFILE);
});
App::patch('/v1/videos/profiles/:profileId')
->desc('Update video profile')
->groups(['api', 'video'])
@@ -1062,9 +979,8 @@ App::patch('/v1/videos/profiles/:profileId')
->action(action: function (string $profileId, string $name, string $videoBitrate, string $audioBitrate, string $width, string $height, string $protocol, Response $response, Database $dbForProject) {
$profile = Authorization::skip(fn() => $dbForProject->getDocument('videos_profiles', $profileId));
;
if ($profile->isEmpty()) {
throw new Exception('Project not found', 404, Exception::PROJECT_NOT_FOUND);
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$profile->setAttribute('name', $name)
@@ -1079,7 +995,6 @@ App::patch('/v1/videos/profiles/:profileId')
$response->dynamic($profile, Response::MODEL_PROFILE);
});
App::get('/v1/videos/profiles/:profileId')
->desc('Get video profile')
->groups(['api', 'video'])
@@ -1097,15 +1012,13 @@ App::get('/v1/videos/profiles/:profileId')
->action(function (string $profileId, Response $response, Database $dbForProject) {
$profile = Authorization::skip(fn() => $dbForProject->getDocument('videos_profiles', $profileId));
if ($profile->isEmpty()) {
throw new Exception('Video profile not found', 404, Exception::VIDEO_PROFILE_NOT_FOUND);
throw new Exception(Exception::VIDEO_PROFILE_NOT_FOUND);
}
$response->dynamic($profile, Response::MODEL_PROFILE);
});
App::get('/v1/videos/profiles')
->desc('Get all video profiles')
->groups(['api', 'video'])
@@ -1121,10 +1034,9 @@ App::get('/v1/videos/profiles')
->inject('dbForProject')
->action(function (Response $response, Database $dbForProject) {
$profiles = Authorization::skip(fn () => $dbForProject->find('videos_profiles', [], 12, 0, [], ['ASC']));
$profiles = Authorization::skip(fn () => $dbForProject->find('videos_profiles'));
if (empty($profiles)) {
throw new Exception('Video profiles where not found', 404, Exception::VIDEO_PROFILE_NOT_FOUND);
throw new Exception(Exception::VIDEO_PROFILE_NOT_FOUND);
}
$response->dynamic(new Document([
@@ -1133,7 +1045,6 @@ App::get('/v1/videos/profiles')
]), Response::MODEL_PROFILE_LIST);
});
App::delete('/v1/videos/profiles/:profileId')
->desc('Delete video profile')
->groups(['api', 'video'])
@@ -1150,9 +1061,8 @@ App::delete('/v1/videos/profiles/:profileId')
->action(function (string $profileId, Response $response, Database $dbForProject) {
$profile = Authorization::skip(fn() => $dbForProject->getDocument('videos_profiles', $profileId));
if ($profile->isEmpty()) {
throw new Exception('Video profile not found', 404, Exception::VIDEO_PROFILE_NOT_FOUND);
throw new Exception(Exception::VIDEO_PROFILE_NOT_FOUND);
}
$deleted = $dbForProject->deleteDocument('videos_profiles', $profileId);
+1 -1
View File
@@ -3,7 +3,7 @@
#EXT-X-ALLOW-CACHE:NO
#EXT-X-TARGETDURATION:<?php echo $this->getParam('targetDuration', 0)?><?php echo PHP_EOL?>
#EXT-X-MEDIA-SEQUENCE:0
<?php foreach ($this->getParam('paramsSegments', []) as $segment): ?>
<?php foreach ($this->getParam('segments', []) as $segment): ?>
#EXTINF:<?php echo $segment['duration']; ?><?php echo PHP_EOL?>
<?php echo $segment['url']; ?><?php echo PHP_EOL?>
<?php endforeach; ?>
+15 -14
View File
@@ -80,7 +80,7 @@ class TranscodingV1 extends Worker
]));
if (empty($sourceVideo)) {
throw new Exception('Video not found', 400, Exception::VIDEO_NOT_FOUND);
throw new Exception(Exception::VIDEO_NOT_FOUND);
}
$profile = Authorization::skip(fn() => $this->database->findOne('videos_profiles', [
@@ -88,7 +88,7 @@ class TranscodingV1 extends Worker
]));
if (empty($profile)) {
throw new Exception('Video profile not found', 400, Exception::VIDEO_PROFILE_NOT_FOUND);
throw new Exception(Exception::VIDEO_PROFILE_NOT_FOUND);
}
$bucket = Authorization::skip(
@@ -104,7 +104,7 @@ class TranscodingV1 extends Worker
$result = $this->writeData($project, $file);
if (empty($result)) {
throw new Exception('File write failed', 500, Exception::GENERAL_UNKNOWN);
throw new Exception(Exception::GENERAL_UNKNOWN);
}
$ffprobe = FFMpeg\FFProbe::create();
@@ -114,7 +114,7 @@ class TranscodingV1 extends Worker
]);
if (!$ffprobe->isValid($inPath)) {
throw new Exception('Not an valid FFMpeg file "' . $inPath . '"');
throw new Exception('Not an valid Video file "' . $inPath . '"');
}
foreach ($ffprobe->streams($inPath)->audios()->getIterator() as $stream) {
@@ -185,16 +185,16 @@ class TranscodingV1 extends Worker
];
}
$query = Authorization::skip(function () use ($profile) {
return $this->database->createDocument('videos_renditions', new Document([
'videoId' => $this->args['videoId'],
'profileId' => $profile->getId(),
'name' => $this->getRenditionName(),
'startedAt' => DateTime::now(),
'status' => self::STATUS_START,
'protocol' => $profile->getAttribute('protocol'),
]));
});
$query = Authorization::skip(function () use ($profile) {
return $this->database->createDocument('videos_renditions', new Document([
'videoId' => $this->args['videoId'],
'profileId' => $profile->getId(),
'name' => $this->getRenditionName(),
'startedAt' => DateTime::now(),
'status' => self::STATUS_START,
'protocol' => $profile->getAttribute('protocol'),
]));
});
$renditionRootPath = $this->getVideoDevice($project->getId())->getPath($this->args['videoId']) . '/';
$renditionPath = $renditionRootPath . $this->getRenditionName() . '-' . $query->getId() . '/';
@@ -315,6 +315,7 @@ class TranscodingV1 extends Worker
$this->getVideoDevice($project->getId())->write($to . $fileName, $data, \mime_content_type($this->outDir . $fileName));
if ($start === 0) {
$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));
+1 -1
View File
@@ -179,7 +179,7 @@ class Exception extends \Exception
/** Video */
public const VIDEO_PROFILE_NOT_FOUND = 'video_profile_not_found';
public const VIDEO_PROFILE_ALREADY_EXISTS = 'video_profile_already_exists';
public const VIDEO_NOT_VALID = 'video_not_valid';
public const VIDEO_RENDITION_NOT_FOUND = 'video_rendition_not_found';
public const VIDEO_SUBTITLE_NOT_FOUND = 'video_subtitle_not_found';
public const VIDEO_NOT_FOUND = 'video_not_found';
@@ -31,16 +31,6 @@ class VideoCustomServerTest extends Scope
'protocol' => 'hls',
]);
$x = [
Permission::read(Role::any()),
Permission::create(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
];
var_dump($x);
exit;
$profileId = $response['body']['$id'];
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']);
@@ -115,7 +105,7 @@ class VideoCustomServerTest extends Scope
$this->assertEquals(404, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']);
$this->assertEquals('Video profile not found', $response['body']['message']);
$this->assertEquals('Video profile not found.', $response['body']['message']);
$response = $this->client->call(Client::METHOD_GET, '/videos/profiles', [
'content-type' => 'application/json',
@@ -159,7 +149,7 @@ class VideoCustomServerTest extends Scope
$this->assertEquals(404, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']);
$this->assertEquals('Video profile not found', $response['body']['message']);
$this->assertEquals('Video profile not found.', $response['body']['message']);
}
/**
@@ -360,7 +350,6 @@ class VideoCustomServerTest extends Scope
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['$id']);
/**
* Try to transcode with wrong profileId
*/
@@ -849,6 +838,7 @@ class VideoCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
'queries' => [ 'limit(1)' ],
]);
$this->assertEquals(200, $response['headers']['status-code']);