mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
metadata collecting
routes renaming permissions
This commit is contained in:
+31
-10
@@ -3122,10 +3122,10 @@ $collections = [
|
||||
],
|
||||
]
|
||||
],
|
||||
'video_renditions' => [
|
||||
'videos_renditions' => [
|
||||
'$collection' => Database::METADATA,
|
||||
'$id' => 'video_renditions',
|
||||
'$name' => 'Video_renditions',
|
||||
'$id' => 'videos_renditions',
|
||||
'$name' => 'Videos_renditions',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'videoId',
|
||||
@@ -3347,10 +3347,10 @@ $collections = [
|
||||
],
|
||||
]
|
||||
],
|
||||
'video_profiles' => [
|
||||
'videos_profiles' => [
|
||||
'$collection' => Database::METADATA,
|
||||
'$id' => 'video_profiles',
|
||||
'$name' => 'Video_profiles',
|
||||
'$id' => 'videos_profiles',
|
||||
'$name' => 'Videos_profiles',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'name',
|
||||
@@ -3429,10 +3429,10 @@ $collections = [
|
||||
],
|
||||
]
|
||||
],
|
||||
'video_subtitles' => [
|
||||
'videos_subtitles' => [
|
||||
'$collection' => Database::METADATA,
|
||||
'$id' => 'video_subtitles',
|
||||
'$name' => 'Video_subtitles',
|
||||
'$id' => 'videos_subtitles',
|
||||
'$name' => 'Videos_subtitles',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'videoId',
|
||||
@@ -3467,6 +3467,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' => 'name',
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -3496,7 +3507,17 @@ $collections = [
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'default' => false,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],[
|
||||
'$id' => 'status',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
|
||||
@@ -186,12 +186,12 @@ return [
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
],
|
||||
'video' => [
|
||||
'key' => 'video',
|
||||
'name' => 'Video',
|
||||
'subtitle' => 'Appwrite\'s Video Endpoint',
|
||||
'description' => 'Video Endpoint',
|
||||
'controller' => 'api/video.php',
|
||||
'videos' => [
|
||||
'key' => 'videos',
|
||||
'name' => 'Videos',
|
||||
'subtitle' => 'Appwrite\'s Videos Endpoint',
|
||||
'description' => 'Videos Endpoint',
|
||||
'controller' => 'api/videos.php',
|
||||
'sdk' => false,
|
||||
'docs' => false,
|
||||
'docsUrl' => '',
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -155,10 +155,10 @@ App::post('/v1/projects')
|
||||
$dbForProject->createCollection($key, $attributes, $indexes);
|
||||
}
|
||||
|
||||
if ($dbForProject->exists($dbForProject->getDefaultDatabase(), 'video_profiles')) {
|
||||
foreach (Config::getParam('video-profiles', []) as $profile) {
|
||||
if ($dbForProject->exists($dbForProject->getDefaultDatabase(), 'videos_profiles')) {
|
||||
foreach (Config::getParam('videos-profiles', []) as $profile) {
|
||||
Authorization::skip(function () use ($project, $profile, $dbForProject) {
|
||||
return $dbForProject->createDocument('video_profiles', new Document([
|
||||
return $dbForProject->createDocument('videos_profiles', new Document([
|
||||
'name' => $profile['name'],
|
||||
'videoBitrate' => $profile['videoBitrate'],
|
||||
'audioBitrate' => $profile['audioBitrate'],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\ClamAV\Network;
|
||||
|
||||
use Appwrite\Event\Audit;
|
||||
use Appwrite\Event\Audit as EventAudit;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
@@ -44,7 +44,7 @@ use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\Swoole\Request;
|
||||
use Streaming\Representation;
|
||||
|
||||
|
||||
/**
|
||||
* Validate file Permissions
|
||||
@@ -52,36 +52,29 @@ use Streaming\Representation;
|
||||
* @param Database $dbForProject
|
||||
* @param string $bucketId
|
||||
* @param string $fileId
|
||||
* @param array|null $read
|
||||
* @param array|null $write
|
||||
* @param string $mode
|
||||
* @return Document $file
|
||||
* @throws Exception
|
||||
*/
|
||||
function validateFilePermissions(Database $dbForProject, string $bucketId, string $fileId, string $mode, Document $user, ?array $read, ?array $write): Document
|
||||
function validateFilePermissions(Database $dbForProject, string $bucketId, string $fileId, string $mode, Document $user): Document
|
||||
{
|
||||
/** @var Utopia\Database\Document $project */
|
||||
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
if (
|
||||
$bucket->isEmpty()
|
||||
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)
|
||||
) {
|
||||
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())) {
|
||||
$validator = new Authorization('read');
|
||||
if (!$validator->isValid($bucket->getRead())) {
|
||||
throw new Exception('Unauthorized file 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 ?? [];
|
||||
$read = !$user->isEmpty() ? ['user:' . $user->getId()] : []; // By default set read permissions for user
|
||||
|
||||
// Users can only add their roles to files, API keys and Admin users can add any
|
||||
$roles = Authorization::getRoles();
|
||||
@@ -92,11 +85,6 @@ function validateFilePermissions(Database $dbForProject, string $bucketId, strin
|
||||
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') {
|
||||
@@ -109,15 +97,15 @@ function validateFilePermissions(Database $dbForProject, string $bucketId, strin
|
||||
return $file;
|
||||
}
|
||||
|
||||
App::get('/v1/video/profiles')
|
||||
->alias('/v1/video/video/profiles', [])
|
||||
App::get('/v1/videos/profiles')
|
||||
->alias('/v1/videos/videos/profiles', [])
|
||||
->desc('Get all video profiles')
|
||||
->groups(['api', 'video'])
|
||||
->label('scope', 'files.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'video')
|
||||
->label('sdk.method', 'getProfiles')
|
||||
->label('sdk.description', '/docs/references/video/get-profiles.md') // TODO: Create markdown
|
||||
->label('sdk.description', '/docs/references/videos/get-profiles.md') // TODO: Create markdown
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_VIDEO_PROFILE_LIST)
|
||||
@@ -125,36 +113,40 @@ App::get('/v1/video/profiles')
|
||||
->inject('dbForProject')
|
||||
->action(function (Response $response, Database $dbForProject) {
|
||||
|
||||
$profiles = Authorization::skip(fn () => $dbForProject->find('video_profiles', [], 12, 0, [], ['ASC']));
|
||||
$profiles = Authorization::skip(fn () => $dbForProject->find('videos_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),
|
||||
'total' => $dbForProject->count('videos_profiles', [], APP_LIMIT_COUNT),
|
||||
'profiles' => $profiles,
|
||||
]), Response::MODEL_VIDEO_PROFILE_LIST);
|
||||
});
|
||||
|
||||
|
||||
App::post('/v1/video/:videoId/subtitles')
|
||||
->alias('/v1/video/:videoId/subtitles', [])
|
||||
->desc('Link a subtitle file to a video')
|
||||
App::post('/v1/videos/:videoId/subtitles')
|
||||
->alias('/v1/videos/:videoId/subtitles', [])
|
||||
->desc('Attach a subtitle file to a video')
|
||||
->groups(['api', 'video'])
|
||||
->label('scope', 'files.write')
|
||||
// TODO: Add sdk labels
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'video')
|
||||
->label('sdk.method', 'addSubtitles')
|
||||
->label('sdk.description', '/docs/references/videos/add-subtitles.md') // TODO: Create markdown
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_NONE)
|
||||
->param('videoId', null, new UID(), 'Video unique ID.')
|
||||
->param('bucketId', '', new CustomId(), 'Subtitle bucket unique ID.')
|
||||
->param('fileId', '', new CustomId(), 'Subtitle file unique ID.')
|
||||
->param('name', '', new Text(128), 'Subtitle name.')
|
||||
->param('code', '', new Text(128), 'Subtitle code name.')
|
||||
->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)
|
||||
->param('default', false, new Boolean(true), 'Default subtitle.')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('user')
|
||||
->inject('audits')
|
||||
->inject('usage')
|
||||
@@ -162,26 +154,26 @@ App::post('/v1/video/:videoId/subtitles')
|
||||
->inject('mode')
|
||||
->inject('deviceFiles')
|
||||
->inject('deviceLocal')
|
||||
->action(action: function (string $videoId, string $bucketId, string $fileId, string $name, string $code, ?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 */
|
||||
->action(action: function (string $videoId, string $bucketId, string $fileId, string $name, string $code, bool $default, Request $request, Response $response, Database $dbForProject, Document $user, Audit $audits, Stats $usage, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal) {
|
||||
|
||||
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [new Query('_uid', Query::TYPE_EQUAL, [$videoId])]));
|
||||
|
||||
if ($video->isEmpty()) {
|
||||
if (empty($video)) {
|
||||
throw new Exception('Video not found', 400, Exception::VIDEO_NOT_FOUND);
|
||||
}
|
||||
|
||||
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode, $user, $read, $write);
|
||||
validateFilePermissions($dbForProject, $bucketId, $fileId, $mode, $user, $read, $write);
|
||||
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode, $user);
|
||||
validateFilePermissions($dbForProject, $bucketId, $fileId, $mode, $user);
|
||||
|
||||
try {
|
||||
$subtitle = Authorization::skip(function () use ($dbForProject, $videoId, $bucketId, $fileId, $name, $code) {
|
||||
return $dbForProject->createDocument('video_subtitles', new Document([
|
||||
Authorization::skip(function () use ($dbForProject, $videoId, $bucketId, $fileId, $name, $code, $default) {
|
||||
return $dbForProject->createDocument('videos_subtitles', new Document([
|
||||
'videoId' => $videoId,
|
||||
'bucketId' => $bucketId,
|
||||
'fileId' => $fileId,
|
||||
'name' => $name,
|
||||
'code' => $code,
|
||||
'default' => $default,
|
||||
]));
|
||||
});
|
||||
} catch (StructureException $exception) {
|
||||
@@ -192,26 +184,22 @@ App::post('/v1/video/:videoId/subtitles')
|
||||
});
|
||||
|
||||
|
||||
App::post('/v1/video/buckets/:bucketId/files/:fileId')
|
||||
App::post('/v1/video')
|
||||
->desc('Create Video')
|
||||
->groups(['api', 'video'])
|
||||
->label('scope', 'files.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'video')
|
||||
->label('sdk.method', 'create')
|
||||
->label('sdk.description', '/docs/references/video/create.md') // TODO: Create markdown
|
||||
->label('sdk.description', '/docs/references/videos/create.md') // TODO: Create markdown
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_VIDEO)
|
||||
// ->label('event', 'buckets.[bucketId].files.[fileId].create')
|
||||
->param('bucketId', 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('fileId', '', 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')
|
||||
@@ -219,10 +207,10 @@ App::post('/v1/video/buckets/:bucketId/files/:fileId')
|
||||
->inject('mode')
|
||||
->inject('deviceFiles')
|
||||
->inject('deviceLocal')
|
||||
->action(action: function (string $bucketId, string $fileId, ?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) {
|
||||
->action(action: function (string $bucketId, string $fileId, Request $request, Response $response, Database $dbForProject, Document $user, Audit $audits, Stats $usage, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal) {
|
||||
/** @var Utopia\Database\Document $project */
|
||||
|
||||
$file = validateFilePermissions($dbForProject, $bucketId, $fileId, $mode, $user, $read, $write);
|
||||
$file = validateFilePermissions($dbForProject, $bucketId, $fileId, $mode, $user);
|
||||
|
||||
try {
|
||||
$video = Authorization::skip(function () use ($dbForProject, $bucketId, $file) {
|
||||
@@ -245,22 +233,20 @@ App::post('/v1/video/buckets/:bucketId/files/:fileId')
|
||||
});
|
||||
|
||||
|
||||
App::post('/v1/video/:videoId/rendition/:profileId')
|
||||
->alias('/v1/video/:videoId/rendition/:profileId', [])
|
||||
App::post('/v1/videos/:videoId/rendition')
|
||||
->alias('/v1/videos/:videoId/rendition', [])
|
||||
->desc('Start transcoding video rendition')
|
||||
->groups(['api', 'video'])
|
||||
->label('scope', 'files.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'video')
|
||||
->label('sdk.method', 'createTranscoding')
|
||||
->label('sdk.description', '/docs/references/video/create-transcoding.md') // TODO: Create markdown
|
||||
->label('sdk.description', '/docs/references/videos/create-transcoding.md') // TODO: Create markdown
|
||||
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
|
||||
->label('sdk.response.model', Response::MODEL_NONE)
|
||||
// ->label('event', 'buckets.[bucketId].files.[fileId].create')
|
||||
->param('videoId', null, new UID(), 'Video unique ID.')
|
||||
->param('profileId', '', new CustomId(), 'Profile unique ID.')
|
||||
->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')
|
||||
@@ -272,7 +258,7 @@ App::post('/v1/video/:videoId/rendition/:profileId')
|
||||
->inject('mode')
|
||||
->inject('deviceFiles')
|
||||
->inject('deviceLocal')
|
||||
->action(action: function (string $videoId, string $profileId, ?array $read, ?array $write, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Audit $audits, Stats $usage, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal) {
|
||||
->action(action: function (string $videoId, string $profileId, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Audit $audits, Stats $usage, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal) {
|
||||
|
||||
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [new Query('_uid', Query::TYPE_EQUAL, [$videoId])]));
|
||||
|
||||
@@ -280,9 +266,9 @@ App::post('/v1/video/:videoId/rendition/:profileId')
|
||||
throw new Exception('Video not found', 400, Exception::VIDEO_NOT_FOUND);
|
||||
}
|
||||
|
||||
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode, $user, $read, $write);
|
||||
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode, $user);
|
||||
|
||||
$profile = Authorization::skip(fn() => $dbForProject->findOne('video_profiles', [new Query('_uid', Query::TYPE_EQUAL, [$profileId])]));
|
||||
$profile = Authorization::skip(fn() => $dbForProject->findOne('videos_profiles', [new Query('_uid', Query::TYPE_EQUAL, [$profileId])]));
|
||||
|
||||
if (!$profile) {
|
||||
throw new Exception('Video profile not found', 400, Exception::PROFILES_NOT_FOUND);
|
||||
@@ -300,29 +286,26 @@ App::post('/v1/video/:videoId/rendition/:profileId')
|
||||
});
|
||||
|
||||
|
||||
App::get('/v1/video/:videoId/:stream/renditions')
|
||||
->alias('/v1/video/:videoId/renditions', [])
|
||||
App::get('/v1/videos/:videoId/:stream/renditions')
|
||||
->alias('/v1/videos/:videoId/renditions', [])
|
||||
->desc('Get File renditions')
|
||||
->groups(['api', 'video'])
|
||||
->label('scope', 'files.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'video')
|
||||
->label('sdk.method', 'getRenditions')
|
||||
->label('sdk.description', '/docs/references/video/get-renditions.md') // TODO: Create markdown
|
||||
->label('sdk.description', '/docs/references/videos/get-renditions.md') // TODO: Create markdown
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_VIDEO_RENDITIONS_LIST)
|
||||
->param('videoId', null, new UID(), 'Video unique ID.')
|
||||
->param('stream', '', new WhiteList(['hls', 'mpeg-dash']), 'stream protocol name')
|
||||
->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('response')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('usage')
|
||||
->inject('mode')
|
||||
->inject('user')
|
||||
->action(function (string $videoId, string $stream, ?array $read, ?array $write, Response $response, Database $dbForProject, Document $project, Stats $usage, string $mode, Document $user) {
|
||||
->action(function (string $videoId, string $stream, Response $response, Database $dbForProject, Stats $usage, string $mode, Document $user) {
|
||||
|
||||
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [new Query('_uid', Query::TYPE_EQUAL, [$videoId])]));
|
||||
|
||||
@@ -330,7 +313,7 @@ App::get('/v1/video/:videoId/:stream/renditions')
|
||||
throw new Exception('Video not found', 400, Exception::VIDEO_NOT_FOUND);
|
||||
}
|
||||
|
||||
$file = validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode, $user, $read, $write);
|
||||
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode, $user);
|
||||
|
||||
$queries = [
|
||||
new Query('videoId', Query::TYPE_EQUAL, [$video->getId()]),
|
||||
@@ -339,24 +322,23 @@ App::get('/v1/video/:videoId/:stream/renditions')
|
||||
new Query('stream', Query::TYPE_EQUAL, [$stream]),
|
||||
];
|
||||
|
||||
$renditions = Authorization::skip(fn () => $dbForProject->find('video_renditions', $queries, 12, 0, [], ['ASC']));
|
||||
$renditions = Authorization::skip(fn () => $dbForProject->find('videos_renditions', $queries, 12, 0, [], ['ASC']));
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'total' => $dbForProject->count('video_renditions', $queries, APP_LIMIT_COUNT),
|
||||
'total' => $dbForProject->count('videos_renditions', $queries, APP_LIMIT_COUNT),
|
||||
'renditions' => $renditions,
|
||||
]), Response::MODEL_VIDEO_RENDITIONS_LIST);
|
||||
});
|
||||
|
||||
|
||||
|
||||
App::get('/v1/video/:videoId/:stream/:profile/:fileName')
|
||||
->alias('/v1/video/:videoId/:stream/:profile/:fileName', [])
|
||||
App::get('/v1/videos/:videoId/:stream/:profile/:fileName')
|
||||
->alias('/v1/videos/:videoId/:stream/:profile/:fileName', [])
|
||||
->desc('Get video playlist manifests')
|
||||
->groups(['api', 'video'])
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'video')
|
||||
->label('sdk.method', 'getPlaylist')
|
||||
->label('sdk.description', '/docs/references/video/get-playlist.md') // TODO: Create markdown
|
||||
->label('sdk.description', '/docs/references/videos/get-playlist.md') // TODO: Create markdown
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
// TODO: Response model
|
||||
@@ -365,29 +347,25 @@ App::get('/v1/video/:videoId/:stream/:profile/:fileName')
|
||||
->param('stream', '', new WhiteList(['hls', 'mpeg-dash']), 'stream protocol name')
|
||||
->param('profile', '', new Text(18), 'folder name')
|
||||
->param('fileName', '', new Text(128), 'playlist file name')
|
||||
->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('response')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('videosDevice')
|
||||
->inject('usage')
|
||||
->inject('mode')
|
||||
->inject('user')
|
||||
->action(function (string $videoId, string $stream, string $profile, string $fileName, ?array $read, ?array $write, Response $response, Database $dbForProject, Document $project, Device $videosDevice, Stats $usage, string $mode, Document $user) {
|
||||
|
||||
->action(function (string $videoId, string $stream, string $profile, string $fileName, Response $response, Database $dbForProject, Device $videosDevice, Stats $usage, string $mode, Document $user) {
|
||||
|
||||
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [
|
||||
new Query('_uid', Query::TYPE_EQUAL, [$videoId])
|
||||
]));
|
||||
|
||||
if (empty($video)){
|
||||
if (empty($video)) {
|
||||
throw new Exception('Video not found', 400, Exception::VIDEO_NOT_FOUND);
|
||||
}
|
||||
|
||||
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode, $user, $read, $write);
|
||||
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode, $user);
|
||||
|
||||
$renditions = Authorization::skip(fn () => $dbForProject->find('video_renditions', [
|
||||
$renditions = Authorization::skip(fn () => $dbForProject->find('videos_renditions', [
|
||||
new Query('videoId', Query::TYPE_EQUAL, [$video->getId()]),
|
||||
new Query('endedAt', Query::TYPE_GREATER, [0]),
|
||||
new Query('status', Query::TYPE_EQUAL, ['ready']),
|
||||
@@ -398,28 +376,30 @@ App::get('/v1/video/:videoId/:stream/:profile/:fileName')
|
||||
throw new Exception('Renditions not found'); // TODO: Proper error code
|
||||
}
|
||||
|
||||
$ct['m3u8'] = 'application/x-mpegurl';
|
||||
$ct['mpd'] = 'application/dash+xml';
|
||||
$ct['ts'] = 'video/MP2T';
|
||||
$ct['m4s'] = 'video/iso.segment';
|
||||
$contentType['m3u8'] = 'application/x-mpegurl';
|
||||
$contentType['mpd'] = 'application/dash+xml';
|
||||
$contentType['ts'] = 'video/MP2T';
|
||||
$contentType['m4s'] = 'video/iso.segment';
|
||||
$contentType['vtt'] = 'text/vtt';
|
||||
$ext = pathinfo($fileName, PATHINFO_EXTENSION);
|
||||
$baseUrl = 'http://127.0.0.1/v1/video/' . $videoId . '/' . $stream . '/' ;
|
||||
$baseUrl = 'http://127.0.0.1/v1/videos/' . $videoId . '/' . $stream . '/' ;
|
||||
|
||||
if ($profile === 'master') {
|
||||
if ($stream === 'hls') {
|
||||
$subtitles = Authorization::skip(fn () => $dbForProject->find('video_subtitles', [new Query('videoId', Query::TYPE_EQUAL, [$video->getId()])], 12, 0, [], ['ASC']));
|
||||
$subtitles = Authorization::skip(fn () => $dbForProject->find('videos_subtitles', [new Query('videoId', Query::TYPE_EQUAL, [$video->getId()])], 12, 0, [], ['ASC']));
|
||||
$paramsSubtitles = [];
|
||||
foreach ($subtitles as $subtitle) {
|
||||
$paramsSubtitles[] = [
|
||||
'name' => $subtitle->getAttribute('name'),
|
||||
'code' => $subtitle->getAttribute('code'),
|
||||
'uri' => $baseUrl . $videoId . '_subtitles_' . $subtitle->getAttribute('code') . '.m3u8',
|
||||
'default' => !empty($subtitle->getAttribute('default')) ? 'YES' : 'NO',
|
||||
'uri' => $baseUrl . $subtitle->getAttribute('path') . '/' . $videoId . '_subtitles_' . $subtitle->getAttribute('code') . '.m3u8',
|
||||
];
|
||||
}
|
||||
$paramsRenditions = [];
|
||||
foreach ($renditions as $rendition) {
|
||||
$paramsRenditions[] = [
|
||||
'bandwidth' => ($rendition->getAttribute('videoBitrate') + $rendition->getAttribute('audioBitrate')),
|
||||
'bandwidth' => ($rendition->getAttribute('videoBitrate') + $rendition->getAttribute('audioBitrate')),
|
||||
'resolution' => $rendition->getAttribute('width') . 'X' . $rendition->getAttribute('height'),
|
||||
'name' => $rendition->getAttribute('name'),
|
||||
'uri' => $baseUrl . $rendition->getAttribute('name') . '/' . $rendition->getAttribute('videoId') . '_' . $rendition->getAttribute('height') . 'p.m3u8',
|
||||
@@ -427,7 +407,7 @@ App::get('/v1/video/:videoId/:stream/:profile/:fileName')
|
||||
];
|
||||
}
|
||||
|
||||
$template = new View(__DIR__ . '/../../views/video/hls.phtml');
|
||||
$template = new View(__DIR__ . '/../../views/videos/hls.phtml');
|
||||
$template->setParam('paramsSubtitles', $paramsSubtitles);
|
||||
$template->setParam('paramsRenditions', $paramsRenditions);
|
||||
$output = $template->render(false);
|
||||
@@ -445,15 +425,14 @@ App::get('/v1/video/:videoId/:stream/:profile/:fileName')
|
||||
}
|
||||
}
|
||||
|
||||
$template = new View(__DIR__ . '/../../views/video/dash.phtml');
|
||||
$template = new View(__DIR__ . '/../../views/videos/dash.phtml');
|
||||
$template->setParam('params', $adaptations);
|
||||
$response->setContentType($ct[$ext]);
|
||||
$response->setContentType($contentType[$ext]);
|
||||
$output = $template->render(false);
|
||||
}
|
||||
} else {
|
||||
$output = $videosDevice->read($videosDevice->getRoot() . '/' . $videoId . '/' . $profile . '/' . $fileName);
|
||||
}
|
||||
|
||||
$response->setContentType($ct[$ext])
|
||||
->send($output);
|
||||
$response->setContentType($contentType[$ext])->send($output);
|
||||
});
|
||||
+3
-3
@@ -98,7 +98,7 @@ const APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH = 1073741824; // 2^32 bits / 4 bi
|
||||
const APP_STORAGE_UPLOADS = '/storage/uploads';
|
||||
const APP_STORAGE_FUNCTIONS = '/storage/functions';
|
||||
const APP_STORAGE_BUILDS = '/storage/builds';
|
||||
const APP_STORAGE_VIDEO = '/storage/videos';
|
||||
const APP_STORAGE_VIDEOS = '/storage/videos';
|
||||
const APP_STORAGE_CACHE = '/storage/cache';
|
||||
const APP_STORAGE_CERTIFICATES = '/storage/certificates';
|
||||
const APP_STORAGE_CONFIG = '/storage/config';
|
||||
@@ -174,7 +174,7 @@ Config::load('roles', __DIR__ . '/config/roles.php'); // User roles and scopes
|
||||
Config::load('scopes', __DIR__ . '/config/scopes.php'); // User roles and scopes
|
||||
Config::load('services', __DIR__ . '/config/services.php'); // List of services
|
||||
Config::load('variables', __DIR__ . '/config/variables.php'); // List of env variables
|
||||
Config::load('video-profiles', __DIR__ . '/config/video-profiles.php');
|
||||
Config::load('videos-profiles', __DIR__ . '/config/videos-profiles.php');
|
||||
Config::load('avatar-browsers', __DIR__ . '/config/avatars/browsers.php');
|
||||
Config::load('avatar-credit-cards', __DIR__ . '/config/avatars/credit-cards.php');
|
||||
Config::load('avatar-flags', __DIR__ . '/config/avatars/flags.php');
|
||||
@@ -915,7 +915,7 @@ App::setResource('deviceFiles', function ($project) {
|
||||
}, ['project']);
|
||||
|
||||
App::setResource('videosDevice', function ($project) {
|
||||
return getDevice(APP_STORAGE_VIDEO . '/app-' . $project->getId());
|
||||
return getDevice(APP_STORAGE_VIDEOS . '/app-' . $project->getId());
|
||||
}, ['project']);
|
||||
|
||||
App::setResource('deviceFunctions', function ($project) {
|
||||
|
||||
+5
-5
@@ -42,8 +42,8 @@ foreach (
|
||||
}
|
||||
}
|
||||
|
||||
$preloader
|
||||
->paths(realpath(__DIR__ . '/../app/config'))
|
||||
->paths(realpath(__DIR__ . '/../app/controllers'))
|
||||
->paths(realpath(__DIR__ . '/../src'))
|
||||
->load();
|
||||
//$preloader
|
||||
//->paths(realpath(__DIR__ . '/../app/config'))
|
||||
//->paths(realpath(__DIR__ . '/../app/controllers'))
|
||||
//->paths(realpath(__DIR__ . '/../src'))
|
||||
//->load();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#EXTM3U
|
||||
#EXT-X-VERSION:3
|
||||
<?php foreach ($this->getParam('paramsSubtitles', []) as $subtitle): ?>
|
||||
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="<?php echo $subtitle['name']; ?>",DEFAULT=YES,AUTOSELECT=NO,FORCED=NO,LANGUAGE="<?php echo $subtitle['code']; ?>",URI="<?php echo $subtitle['uri']; ?>"<?php echo PHP_EOL?>
|
||||
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="<?php echo $subtitle['name']; ?>",DEFAULT=<?php echo $subtitle['default']; ?>,AUTOSELECT=YES,FORCED=NO,LANGUAGE="<?php echo $subtitle['code']; ?>",URI="<?php echo $subtitle['uri']; ?>"<?php echo PHP_EOL?>
|
||||
<?php endforeach; ?>
|
||||
<?php foreach ($this->getParam('paramsRenditions', []) as $rendition): ?>
|
||||
#EXT-X-STREAM-INF:BANDWIDTH="<?php echo $rendition['bandwidth']; ?>" ,RESOLUTION="<?php echo $rendition['resolution']; ?>",NAME="<?php echo $rendition['name']; ?>"<?php if($rendition['subs'] !== null): echo $rendition['subs']; endif?><?php echo PHP_EOL?>
|
||||
#EXT-X-STREAM-INF:BANDWIDTH="<?php echo $rendition['bandwidth']; ?>",RESOLUTION="<?php echo $rendition['resolution']; ?>",NAME="<?php echo $rendition['name']; ?>"<?php if($rendition['subs'] !== null): echo $rendition['subs']; endif?><?php echo PHP_EOL?>
|
||||
<?php echo $rendition['uri'] ?><?php echo PHP_EOL?>
|
||||
<?php endforeach; ?>
|
||||
@@ -616,7 +616,7 @@ class DeletesV1 extends Worker
|
||||
$device->deletePath($document->getId());
|
||||
|
||||
$dbForProject->deleteCollection('bucket_' . $document->getInternalId() . '_video_renditions');
|
||||
$device = $this->getDevice(APP_STORAGE_VIDEO . '/app-' . $projectId);
|
||||
$device = $this->getDevice(APP_STORAGE_VIDEOS . '/app-' . $projectId);
|
||||
$device = $this->getDevice(APP_STORAGE_UPLOADS . '/app-' . $projectId);
|
||||
|
||||
$device->deletePath($document->getId());
|
||||
|
||||
+88
-34
@@ -14,6 +14,8 @@ use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use FFMpeg\FFProbe\DataMapping\StreamCollection;
|
||||
use FFMpeg\FFProbe\DataMapping\AbstractData;
|
||||
use FFMpeg\FFProbe\DataMapping\Stream;
|
||||
use Utopia\Storage\Compression\Algorithms\GZIP;
|
||||
use Captioning\Format\SubripFile;
|
||||
|
||||
@@ -27,15 +29,17 @@ 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 STATUS_START = 'started';
|
||||
const STATUS_END = 'ended';
|
||||
const STATUS_UPLOADING = 'uploading';
|
||||
const STATUS_READY = 'ready';
|
||||
const STATUS_ERROR = 'error';
|
||||
|
||||
const STREAM_HLS = 'hls';
|
||||
const STREAM_MPEG_DASH = 'mpeg-dash';
|
||||
|
||||
const BASE_URL_ENDPOINT = 'http://127.0.0.1/v1/videos';
|
||||
|
||||
//protected string $basePath = '/tmp/';
|
||||
protected string $basePath = '/usr/src/code/tests/tmp/';
|
||||
|
||||
@@ -64,7 +68,7 @@ class TranscodingV1 extends Worker
|
||||
$this->outDir = $this->basePath . '/out/';
|
||||
@mkdir($this->inDir, 0755, true);
|
||||
@mkdir($this->outDir, 0755, true);
|
||||
$this->outPath = $this->outDir . $this->args['videoId']; /** TODO figure a way to write dir tree without this **/
|
||||
$this->outPath = $this->outDir . $this->args['videoId'];
|
||||
}
|
||||
|
||||
public function run(): void
|
||||
@@ -77,7 +81,7 @@ class TranscodingV1 extends Worker
|
||||
throw new Exception('Video not found');
|
||||
}
|
||||
|
||||
$profile = Authorization::skip(fn() => $this->database->findOne('video_profiles', [new Query('_uid', Query::TYPE_EQUAL, [$this->args['profileId']])]));
|
||||
$profile = Authorization::skip(fn() => $this->database->findOne('videos_profiles', [new Query('_uid', Query::TYPE_EQUAL, [$this->args['profileId']])]));
|
||||
if (empty($profile)) {
|
||||
throw new Exception('profile not found');
|
||||
}
|
||||
@@ -87,9 +91,9 @@ class TranscodingV1 extends Worker
|
||||
$data = $this->getFilesDevice($project->getId())->read($file->getAttribute('path'));
|
||||
$fileName = basename($file->getAttribute('path'));
|
||||
$inPath = $this->inDir . $fileName;
|
||||
$collection = 'video_renditions';
|
||||
$collection = 'videos_renditions';
|
||||
|
||||
if (!empty($file->getAttribute('openSSLCipher'))) { // Decrypt
|
||||
if (!empty($file->getAttribute('openSSLCipher'))) {
|
||||
$data = OpenSSL::decrypt(
|
||||
$data,
|
||||
$file->getAttribute('openSSLCipher'),
|
||||
@@ -130,7 +134,7 @@ class TranscodingV1 extends Worker
|
||||
}
|
||||
}
|
||||
|
||||
$general = $this->getVideoInfo($ffprobe->streams($inPath));
|
||||
$general = $this->getVideoSourceInfo($ffprobe->streams($inPath));
|
||||
if (!empty($general)) {
|
||||
foreach ($general as $key => $value) {
|
||||
$sourceVideo->setAttribute($key, $value);
|
||||
@@ -147,14 +151,26 @@ class TranscodingV1 extends Worker
|
||||
$this->setRenditionName($profile);
|
||||
|
||||
$subs = [];
|
||||
$subtitles = Authorization::skip(fn () => $this->database->find('video_subtitles', [new Query('videoId', Query::TYPE_EQUAL, [$this->args['videoId']])], 12, 0, [], ['ASC']));
|
||||
$subtitles = Authorization::skip(fn () => $this->database->find('videos_subtitles', [
|
||||
new Query('status', Query::TYPE_EQUAL, ['']),
|
||||
new Query('videoId', Query::TYPE_EQUAL, [$this->args['videoId']])
|
||||
], 12, 0, [], ['ASC']));
|
||||
|
||||
foreach ($subtitles as $subtitle) {
|
||||
$subtitle->setAttribute('status', self::STATUS_START);
|
||||
$subtitle->setAttribute('path', $this->getRenditionName());
|
||||
Authorization::skip(fn() => $this->database->updateDocument(
|
||||
'videos_subtitles',
|
||||
$subtitle->getId(),
|
||||
$subtitle
|
||||
));
|
||||
|
||||
$subtitleBucket = Authorization::skip(fn() => $this->database->getDocument('buckets', $subtitle->getAttribute('bucketId')));
|
||||
$subtitleFile = Authorization::skip(fn() => $this->database->getDocument('bucket_' . $subtitleBucket->getInternalId(), $subtitle->getAttribute('fileId')));
|
||||
$subtitleData = $this->getFilesDevice($project->getId())->read($subtitleFile->getAttribute('path'));
|
||||
$subtitleFileName = basename($subtitleFile->getAttribute('path'));
|
||||
|
||||
if (!empty($subtitleFile->getAttribute('openSSLCipher'))) { // Decrypt
|
||||
if (!empty($subtitleFile->getAttribute('openSSLCipher'))) {
|
||||
$subtitleData = OpenSSL::decrypt(
|
||||
$subtitleData,
|
||||
$subtitleFile->getAttribute('openSSLCipher'),
|
||||
@@ -172,10 +188,12 @@ class TranscodingV1 extends Worker
|
||||
|
||||
$this->getFilesDevice($project->getId())->write($this->inDir . $subtitleFileName, $subtitleData, $subtitleFile->getAttribute('mimeType'));
|
||||
$ext = pathinfo($subtitleFileName, PATHINFO_EXTENSION);
|
||||
|
||||
if ($ext === 'srt') {
|
||||
$srt = new SubripFile($this->inDir . $subtitleFileName);
|
||||
$srt->convertTo('webvtt')->save($this->inDir . $this->args['videoId'] . '.vtt');
|
||||
}
|
||||
|
||||
$subs[] = [
|
||||
'name' => $subtitle->getAttribute('name'),
|
||||
'code' => $subtitle->getAttribute('code'),
|
||||
@@ -189,7 +207,7 @@ class TranscodingV1 extends Worker
|
||||
'profileId' => $profile->getId(),
|
||||
'name' => $this->getRenditionName(),
|
||||
'startedAt' => time(),
|
||||
'status' => self::STATUS_TRANSCODE_START,
|
||||
'status' => self::STATUS_START,
|
||||
'stream' => $profile['stream'],
|
||||
]));
|
||||
});
|
||||
@@ -212,7 +230,6 @@ class TranscodingV1 extends Worker
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
list($general, $metadata) = $this->transcode($profile['stream'], $video, $format, $representation, $subs);
|
||||
if (!empty($metadata)) {
|
||||
$query->setAttribute('metadata', json_encode($metadata));
|
||||
@@ -224,7 +241,7 @@ class TranscodingV1 extends Worker
|
||||
}
|
||||
}
|
||||
|
||||
$query->setAttribute('status', self::STATUS_TRANSCODE_END);
|
||||
$query->setAttribute('status', self::STATUS_END);
|
||||
$query->setAttribute('endedAt', time());
|
||||
Authorization::skip(fn() => $this->database->updateDocument(
|
||||
$collection,
|
||||
@@ -232,6 +249,16 @@ class TranscodingV1 extends Worker
|
||||
$query
|
||||
));
|
||||
|
||||
if (!empty($subtitles)) {
|
||||
foreach ($subtitles as $subtitle) {
|
||||
$subtitle->setAttribute('status', self::STATUS_READY);
|
||||
Authorization::skip(fn() => $this->database->updateDocument(
|
||||
'videos_subtitles',
|
||||
$subtitle->getId(),
|
||||
$subtitle
|
||||
));
|
||||
}
|
||||
}
|
||||
/** Upload & remove files **/
|
||||
$start = 0;
|
||||
$fileNames = scandir($this->outDir);
|
||||
@@ -249,9 +276,9 @@ class TranscodingV1 extends Worker
|
||||
$devicePath = $deviceFiles->getPath($this->args['videoId']);
|
||||
$data = $this->getFilesDevice($project->getId())->read($this->outDir . $fileName);
|
||||
$to = $devicePath . '/' . $this->getRenditionName() . '/';
|
||||
if (str_contains($fileName, "_subtitles_") || str_contains($fileName, ".vtt")) {
|
||||
$to = $devicePath . '/';
|
||||
}
|
||||
// if (str_contains($fileName, "_subtitles_") || str_contains($fileName, ".vtt")) {
|
||||
// $to = $devicePath . '/';
|
||||
// }
|
||||
|
||||
$this->getVideoDevice($project->getId())->write($to . $fileName, $data, \mime_content_type($this->outDir . $fileName));
|
||||
if ($start === 0) {
|
||||
@@ -276,7 +303,7 @@ class TranscodingV1 extends Worker
|
||||
//@unlink($this->outDir . $fileName);
|
||||
}
|
||||
|
||||
$query->setAttribute('status', self::STATUS_PACKAGE_END);
|
||||
$query->setAttribute('status', self::STATUS_READY);
|
||||
Authorization::skip(fn() => $this->database->updateDocument(
|
||||
$collection,
|
||||
$query->getId(),
|
||||
@@ -302,7 +329,8 @@ class TranscodingV1 extends Worker
|
||||
* @param $video Media
|
||||
* @param $format StreamFormat
|
||||
* @param $representation Representation
|
||||
* @return array
|
||||
* @param array $subtitles
|
||||
* @return string|array
|
||||
*/
|
||||
private function transcode(string $stream, Media $video, StreamFormat $format, Representation $representation, array $subtitles): string | array
|
||||
{
|
||||
@@ -313,12 +341,12 @@ class TranscodingV1 extends Worker
|
||||
'-vf', 'scale=iw:-2:force_original_aspect_ratio=increase,setsar=1:1'
|
||||
];
|
||||
|
||||
$segementSize = 10;
|
||||
$segmentSize = 10;
|
||||
|
||||
if ($stream === 'mpeg-dash') {
|
||||
$dash = $video->dash()
|
||||
->setFormat($format)
|
||||
->setSegDuration($segementSize)
|
||||
->setSegDuration($segmentSize)
|
||||
->addRepresentation($representation)
|
||||
->setAdditionalParams($additionalParams)
|
||||
->save($this->outPath);
|
||||
@@ -327,7 +355,7 @@ class TranscodingV1 extends Worker
|
||||
file_get_contents($this->outDir . $this->args['videoId'] . '.mpd')
|
||||
);
|
||||
|
||||
$general = $this->getVideoInfo($dash->metadata()->getVideoStreams());
|
||||
$general = $this->getVideoStreamInfo($dash->metadata()->export());
|
||||
$general['width'] = $representation->getWidth();
|
||||
$general['height'] = $representation->getHeight();
|
||||
return [
|
||||
@@ -336,27 +364,26 @@ class TranscodingV1 extends Worker
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
$hls = $video->hls();
|
||||
|
||||
|
||||
foreach ($subtitles as $subtitle) {
|
||||
$sub = new HLSSubtitle($subtitle['path'], $subtitle['name'], $subtitle['code']);
|
||||
$sub->default();
|
||||
$sub->setM3u8Uri($this->getHlsBaseUri(false) . $this->args['videoId'] . '_subtitles_' . $subtitle['code'] . '.m3u8');
|
||||
$sub->setM3u8Uri($this->getHlsBaseUri() . $this->args['videoId'] . '_subtitles_' . $subtitle['code'] . '.m3u8');
|
||||
$hls->subtitle($sub);
|
||||
}
|
||||
|
||||
|
||||
$hls->setFormat($format)
|
||||
->setHlsTime($segementSize)
|
||||
->setHlsTime($segmentSize)
|
||||
->setHlsAllowCache(false)
|
||||
->addRepresentation($representation)
|
||||
->setAdditionalParams($additionalParams)
|
||||
->setHlsBaseUrl($this->getHlsBaseUri())
|
||||
->save($this->outPath);
|
||||
|
||||
$general = $this->getVideoInfo($hls->metadata()->getVideoStreams());
|
||||
|
||||
$general = $this->getVideoStreamInfo($hls->metadata()->export());
|
||||
var_dump($general);
|
||||
$general['videoBitrate'] = $representation->getKiloBitrate() * 1024;
|
||||
$general['audioBitrate'] = $representation->getAudioKiloBitrate() * 1024;
|
||||
$general['width'] = $representation->getWidth();
|
||||
@@ -377,7 +404,7 @@ class TranscodingV1 extends Worker
|
||||
*/
|
||||
private function getHlsBaseUri(bool $nest = true): string
|
||||
{
|
||||
$uri = 'http://127.0.0.1/v1/video/' . $this->args['videoId'] . '/' . self::STREAM_HLS . '/';
|
||||
$uri = self::BASE_URL_ENDPOINT . '/' . $this->args['videoId'] . '/' . self::STREAM_HLS . '/';
|
||||
|
||||
if (empty($nest)) {
|
||||
return $uri;
|
||||
@@ -398,7 +425,8 @@ class TranscodingV1 extends Worker
|
||||
str_contains($line, ".vtt") ||
|
||||
str_contains($line, ".m3u8")
|
||||
) {
|
||||
$newLine = $this->getHlsBaseUri(str_contains($line, ".vtt") ?? false) . $newLine;
|
||||
//$newLine = $this->getHlsBaseUri(str_contains($line, ".vtt") ?? false) . $newLine;
|
||||
$newLine = $this->getHlsBaseUri() . $newLine;
|
||||
}
|
||||
fwrite($destination, $newLine . PHP_EOL);
|
||||
}
|
||||
@@ -412,10 +440,8 @@ class TranscodingV1 extends Worker
|
||||
* @param $streams StreamCollection
|
||||
* @return array
|
||||
*/
|
||||
private function getVideoInfo(StreamCollection $streams): array
|
||||
private function getVideoSourceInfo(StreamCollection $streams): array
|
||||
{
|
||||
//var_dump($streams->videos()->first()->get('bit_rate'));
|
||||
//var_dump($streams->audios()->first()->get('bit_rate'));
|
||||
return [
|
||||
'duration' => !empty($streams->videos()) ? $streams->videos()->first()->get('duration') : '0',
|
||||
'height' => !empty($streams->videos()) ? $streams->videos()->first()->get('height') : 0,
|
||||
@@ -429,6 +455,34 @@ class TranscodingV1 extends Worker
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $metadata array
|
||||
* @return array
|
||||
*/
|
||||
private function getVideoStreamInfo(array $metadata): array
|
||||
{
|
||||
$info = [];
|
||||
// if (!empty($metadata['stream']['resolutions'][0])) {
|
||||
// $general = $metadata['stream']['resolutions'][0];
|
||||
// $info['resolution'] = $general['dimension'];
|
||||
// }
|
||||
|
||||
if (!empty($metadata['video']['streams'])) {
|
||||
foreach ($metadata['video']['streams'] as $streams) {
|
||||
if ($streams['codec_type'] === 'video') {
|
||||
$info['duration'] = !empty($streams['duration']) ? $streams['duration'] : '0';
|
||||
$info['videoCodec'] = !empty($streams['codec_name']) ? $streams['codec_name'] . ',' . $streams['codec_tag_string'] : '';
|
||||
$info['videoBitrate'] = !empty($streams['bit_rate']) ? (int)$streams['bit_rate'] : 0;
|
||||
$info['videoFramerate'] = !empty($streams['avg_frame_rate']) ? $streams['avg_frame_rate'] : '';
|
||||
} elseif ($streams['codec_type'] === 'audio') {
|
||||
$info['audioCodec'] = !empty($streams['codec_name']) ? $streams['codec_name'] . ',' . $streams['codec_tag_string'] : '' ;
|
||||
$info['audioBitrate'] = !empty($streams['sample_rate']) ? (int)$streams['sample_rate'] : 0;
|
||||
$info['audioBitrate'] = !empty($streams['bit_rate']) ? (int)$streams['bit_rate'] : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
|
||||
private function setRenditionName($profile)
|
||||
{
|
||||
|
||||
@@ -274,7 +274,7 @@ abstract class Worker
|
||||
*/
|
||||
protected function getVideoDevice($projectId): Device
|
||||
{
|
||||
return $this->getDevice(APP_STORAGE_VIDEO . '/app-' . $projectId);
|
||||
return $this->getDevice(APP_STORAGE_VIDEOS . '/app-' . $projectId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+24
-46
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Storage;
|
||||
namespace Tests\E2E\Services\Videos;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
@@ -10,7 +10,6 @@ use Tests\E2E\Scopes\SideServer;
|
||||
|
||||
class VideoCustomServerTest extends Scope
|
||||
{
|
||||
use StorageBase;
|
||||
use ProjectCustom;
|
||||
use VideoCustom;
|
||||
use SideServer;
|
||||
@@ -18,18 +17,18 @@ class VideoCustomServerTest extends Scope
|
||||
public function testTranscodeWithSubs(): array
|
||||
{
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/video/buckets/' . $this->getBucket()['$id'] . '/files/' . $this->getVideo()['$id'], [
|
||||
$response = $this->client->call(Client::METHOD_POST, '/videos', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'read' => ['role:all'],
|
||||
'write' => ['role:all']
|
||||
'bucketId' => $this->getBucket()['$id'],
|
||||
'fileId' => $this->getVideo()['$id']
|
||||
]);
|
||||
|
||||
$videoId = $response['body']['$id'];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/video/' . $videoId . '/subtitles', [
|
||||
$response = $this->client->call(Client::METHOD_POST, '/videos/' . $videoId . '/subtitles', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
@@ -38,11 +37,9 @@ class VideoCustomServerTest extends Scope
|
||||
'fileId' => $this->getSubtitle()['$id'],
|
||||
'name' => 'hebrew',
|
||||
'code' => 'heb',
|
||||
'read' => ['role:all'],
|
||||
'write' => ['role:all']
|
||||
]);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/video/' . $videoId . '/subtitles', [
|
||||
$response = $this->client->call(Client::METHOD_POST, '/videos/' . $videoId . '/subtitles', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
@@ -51,11 +48,10 @@ class VideoCustomServerTest extends Scope
|
||||
'fileId' => $this->getSubtitle()['$id'],
|
||||
'name' => 'english',
|
||||
'code' => 'eng',
|
||||
'read' => ['role:all'],
|
||||
'write' => ['role:all']
|
||||
'default' => true,
|
||||
]);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/video/profiles', [
|
||||
$response = $this->client->call(Client::METHOD_GET, '/videos/profiles', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
@@ -63,15 +59,15 @@ class VideoCustomServerTest extends Scope
|
||||
|
||||
|
||||
$profileId = $response['body']['profiles'][0]['$id'];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/video/' . $videoId . '/rendition/' . $profileId, [
|
||||
$response = $this->client->call(Client::METHOD_POST, '/videos/' . $videoId . '/rendition', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'read' => ['role:all'],
|
||||
'write' => ['role:all']
|
||||
'profileId' => $profileId,
|
||||
]);
|
||||
|
||||
|
||||
return [
|
||||
'videoId' => $videoId,
|
||||
'stream' => ''
|
||||
@@ -82,18 +78,17 @@ class VideoCustomServerTest extends Scope
|
||||
public function testTranscodingRendition(): array
|
||||
{
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/video/buckets/' . $this->getBucket()['$id'] . '/files/' . $this->getVideo()['$id'], [
|
||||
$response = $this->client->call(Client::METHOD_POST, '/video', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'read' => ['role:all'],
|
||||
'write' => ['role:all']
|
||||
'bucketId' => $this->getBucket()['$id'],
|
||||
'fileId' => $this->getVideo()['$id']
|
||||
]);
|
||||
|
||||
$videoId = $response['body']['$id'];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/video/profiles', [
|
||||
$response = $this->client->call(Client::METHOD_GET, '/videos/profiles', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
@@ -102,15 +97,13 @@ class VideoCustomServerTest extends Scope
|
||||
|
||||
foreach ($response['body']['profiles'] as $profile) {
|
||||
|
||||
$profileId = $profile['$id'];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/video/' . $videoId . '/rendition/' . $profileId, [
|
||||
$profileId = $profile['$id'];
|
||||
$response = $this->client->call(Client::METHOD_POST, '/videos/' . $videoId . '/rendition', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'read' => ['role:all'],
|
||||
'write' => ['role:all']
|
||||
'profileId' => $profileId,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -127,13 +120,10 @@ class VideoCustomServerTest extends Scope
|
||||
|
||||
sleep(30);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/video/' . $data['videoId'] . '/hls/renditions', [
|
||||
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $data['videoId'] . '/hls/renditions', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'read' => ['role:all'],
|
||||
'write' => ['role:all']
|
||||
]);
|
||||
|
||||
$this->assertNotEmpty($response['body']['renditions']);
|
||||
@@ -160,35 +150,26 @@ class VideoCustomServerTest extends Scope
|
||||
{
|
||||
sleep(20);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/video/' . $data['videoId'] . '/' . $data['stream'] . '/master/' . $data['videoId'] . '.m3u8', [
|
||||
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $data['videoId'] . '/' . $data['stream'] . '/master/' . $data['videoId'] . '.m3u8', [
|
||||
'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']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/video/' . $data['videoId'] . '/' . $data['stream'] . '/' . $data['profileName'] . '/' . $data['videoId'] . '_360p.m3u8', [
|
||||
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $data['videoId'] . '/' . $data['stream'] . '/' . $data['profileName'] . '/' . $data['videoId'] . '_360p.m3u8', [
|
||||
'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']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/video/' . $data['videoId'] . '/' . $data['stream'] . '/' . $data['profileName'] . '/' . $data['videoId'] . '_360p_0000.ts', [
|
||||
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $data['videoId'] . '/' . $data['stream'] . '/' . $data['profileName'] . '/' . $data['videoId'] . '_360p_0000.ts', [
|
||||
'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']);
|
||||
@@ -201,13 +182,10 @@ class VideoCustomServerTest extends Scope
|
||||
// {
|
||||
// sleep(20);
|
||||
//
|
||||
// $response = $this->client->call(Client::METHOD_GET, '/video/' . $data['videoId'] . '/mpeg-dash/master/' . $data['videoId'] . '.mpd', [
|
||||
// $response = $this->client->call(Client::METHOD_GET, '/videos/' . $data['videoId'] . '/mpeg-dash/master/' . $data['videoId'] . '.mpd', [
|
||||
// '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']);
|
||||
Reference in New Issue
Block a user