e2e tests

This commit is contained in:
shimon
2022-08-03 16:08:14 +03:00
parent 49f8eae3f6
commit fd3a4fd485
8 changed files with 544 additions and 9525 deletions
+5 -5
View File
@@ -3312,7 +3312,7 @@ $collections = [
'required' => false,
'default' => null,
'array' => false,
//'filters' => ['json'],
'filters' => ['json'],
],
[
'$id' => 'status',
@@ -3351,7 +3351,7 @@ $collections = [
'indexes' => [
[
'$id' => '_key_video_stream_profile',
'type' => Database::INDEX_UNIQUE,
'type' => Database::INDEX_KEY,
'attributes' => ['videoId', 'stream', 'profileId'],
'lengths' => [],
'orders' => [],
@@ -3433,8 +3433,8 @@ $collections = [
'$id' => '_key_renditionId',
'type' => Database::INDEX_KEY,
'attributes' => ['renditionId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
'lengths' => [],
'orders' => [],
],
]
],
@@ -3513,7 +3513,7 @@ $collections = [
'indexes' => [
[
'$id' => '_key_unique',
'type' => Database::INDEX_UNIQUE,
'type' => Database::INDEX_KEY,
'attributes' => ['stream', 'width', 'height', 'videoBitrate', 'audioBitrate' ],
'lengths' => [],
'orders' => [],
+31 -46
View File
@@ -1,6 +1,7 @@
<?php
use Appwrite\Auth\Auth;
use Appwrite\Event\Delete;
use Appwrite\Event\Transcoding;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Stats\Stats;
@@ -86,10 +87,10 @@ App::post('/v1/videos/profiles')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_VIDEO_PROFILE)
->param('name', null, new Text(128), 'Video profile name.')
->param('videoBitrate', '', new Range(64, 4000), 'Video profile bitrate in Kbps.')
->param('audioBitrate', '', new Range(64, 4000), 'Audio profile bit rate in Kbps.')
->param('width', '', new Range(100, 2000), 'Video profile width.')
->param('height', '', new Range(100, 2000), 'Video profile height.')
->param('videoBitrate', '', new Range(32, 5000), 'Video profile bitrate in Kbps.')
->param('audioBitrate', '', new Range(32, 5000), 'Audio profile bit rate in Kbps.')
->param('width', '', new Range(6, 3000), 'Video profile width.')
->param('height', '', new Range(6, 3000), 'Video profile height.')
->param('stream', false, new WhiteList(['hls', 'dash']), 'Video profile stream protocol.')
->inject('response')
->inject('dbForProject')
@@ -267,7 +268,7 @@ App::post('/v1/videos/:videoId/subtitles')
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [new Query('_uid', Query::TYPE_EQUAL, [$videoId])]));
if (empty($video)) {
throw new Exception('Video not found', 400, Exception::VIDEO_NOT_FOUND);
throw new Exception('Video not found', 404, Exception::VIDEO_NOT_FOUND);
}
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode, $user);
@@ -380,7 +381,7 @@ App::delete('/v1/videos/:videoId/subtitles/:subtitleId')
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [new Query('_uid', Query::TYPE_EQUAL, [$videoId])]));
if ($video->isEmpty()) {
throw new Exception('Video not found', 400, Exception::VIDEO_NOT_FOUND);
throw new Exception('Video not found', 404, Exception::VIDEO_NOT_FOUND);
}
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode, $user);
@@ -479,45 +480,24 @@ App::delete('/v1/videos/:videoId')
->inject('dbForProject')
->inject('mode')
->inject('user')
->inject('deviceVideos')
->action(function (string $videoId, string $renditionId, Response $response, Document $project, Database $dbForProject, string $mode, Document $user, Device $deviceVideos) {
->inject('deletes')
->action(function (string $videoId, string $renditionId, 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])]));
if ($video->isEmpty()) {
throw new Exception('Video not found', 400, Exception::VIDEO_NOT_FOUND);
throw new Exception('Video not found', 404, Exception::VIDEO_NOT_FOUND);
}
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode, $user);
$deleted = $dbForProject->deleteDocument('videos', $videoId);
if (!$deleted) {
throw new Exception('Failed to remove video from DB', 500, Exception::GENERAL_SERVER_ERROR);
}
$renditions = Authorization::skip(fn() => $dbForProject->find('videos_renditions', [
new Query('videoId', Query::TYPE_EQUAL, [$video->getId()]),
], 12, 0, [], ['ASC']));
foreach ($renditions as $rendition) {
Authorization::skip(fn() => $dbForProject->deleteDocument('videos_renditions', $rendition->getId()));
}
$subtitles = Authorization::skip(fn() => $dbForProject->find('videos_subtitles', [
new Query('videoId', Query::TYPE_EQUAL, [$video->getId()]),
], 12, 0, [], ['ASC']));
foreach ($subtitles as $subtitle) {
Authorization::skip(fn() => $dbForProject->deleteDocument('videos_subtitles', $subtitle->getId()));
}
foreach ($renditions as $rendition) {
Authorization::skip(fn() => $dbForProject->deleteDocument('videos_renditions', $rendition->getId()));
}
$videoPath = $this->getVideoDevice($project->getId())->getPath($this->args['videoId']);
$deviceVideos->deletePath($videoPath);
$deletes
->setType(DELETE_TYPE_DOCUMENT)
->setDocument($video);
$response->noContent();
});
@@ -546,16 +526,16 @@ App::post('/v1/videos/:videoId/rendition')
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [new Query('_uid', Query::TYPE_EQUAL, [$videoId])]));
if ($video->isEmpty()) {
throw new Exception('Video not found', 400, Exception::VIDEO_NOT_FOUND);
if (empty($video)) {
throw new Exception('Video not found', 404, Exception::VIDEO_NOT_FOUND);
}
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode, $user);
$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::VIDEO_PROFILE_NOT_FOUND);
if (empty($profile)) {
throw new Exception('Video profile not found', 404, Exception::VIDEO_PROFILE_NOT_FOUND);
}
$transcoder = new Transcoding();
@@ -592,7 +572,7 @@ App::get('/v1/videos/:videoId/rendition/:renditionId')
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [new Query('_uid', Query::TYPE_EQUAL, [$videoId])]));
if ($video->isEmpty()) {
throw new Exception('Video not found', 400, Exception::VIDEO_NOT_FOUND);
throw new Exception('Video not found', 404, Exception::VIDEO_NOT_FOUND);
}
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode, $user);
@@ -629,7 +609,7 @@ App::get('/v1/videos/:videoId/renditions')
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [new Query('_uid', Query::TYPE_EQUAL, [$videoId])]));
if ($video->isEmpty()) {
throw new Exception('Video not found', 400, Exception::VIDEO_NOT_FOUND);
throw new Exception('Video not found', 404, Exception::VIDEO_NOT_FOUND);
}
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode, $user);
@@ -640,7 +620,7 @@ App::get('/v1/videos/:videoId/renditions')
new Query('status', Query::TYPE_EQUAL, ['ready']),
];
$renditions = Authorization::skip(fn () => $dbForProject->find('videos_renditions', $queries, 18, 0, [], ['ASC']));
$renditions = Authorization::skip(fn () => $dbForProject->find('videos_renditions', $queries));
$response->dynamic(new Document([
'total' => $dbForProject->count('videos_renditions', $queries, APP_LIMIT_COUNT),
@@ -671,7 +651,7 @@ App::delete('/v1/videos/:videoId/renditions/:renditionId')
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [new Query('_uid', Query::TYPE_EQUAL, [$videoId])]));
if ($video->isEmpty()) {
throw new Exception('Video not found', 400, Exception::VIDEO_NOT_FOUND);
throw new Exception('Video not found', 404, Exception::VIDEO_NOT_FOUND);
}
validateFilePermissions($dbForProject, $video['bucketId'], $video['fileId'], $mode, $user);
@@ -730,10 +710,10 @@ App::get('/v1/videos/:videoId/streams/:streamId')
new Query('endedAt', Query::TYPE_GREATER, [0]),
new Query('status', Query::TYPE_EQUAL, ['ready']),
new Query('stream', Query::TYPE_EQUAL, [$streamId]),
], 12, 0, [], ['ASC']));
]));
if (empty($renditions)) {
throw new Exception('Rendition not found', 404, Exception::VIDEO_RENDITION_NOT_FOUND);
throw new Exception('Renditions not found', 404, Exception::VIDEO_RENDITION_NOT_FOUND);
}
$baseUrl = 'http://127.0.0.1/v1/videos/' . $videoId . '/streams/' . $streamId;
@@ -770,7 +750,10 @@ App::get('/v1/videos/:videoId/streams/:streamId')
$adaptationId = 0;
foreach ($renditions as $rendition) {
$metadata = $rendition->getAttribute('metadata');
$xml = simplexml_load_string($metadata['xml']);
$xml = simplexml_load_string($metadata['mpd']);
if (empty($xml)) {
continue;
}
$representationId = 0;
foreach ($xml->Period->AdaptationSet as $adaptation) {
$representation = [];
@@ -782,8 +765,10 @@ App::get('/v1/videos/:videoId/streams/:streamId')
$segments = Authorization::skip(fn() => $dbForProject->find('videos_renditions_segments', [
new Query('renditionId', Query::TYPE_EQUAL, [$rendition->getId()]),
new Query('representationId', Query::TYPE_EQUAL, [$representationId]),
], 1000, 0, ['representationId'], ['ASC']));
], 1000, 0, ['representationId']));
if (count($segments) === 0) {
continue;
}
foreach ($segments ?? [] as $segment) {
if ($segment->getAttribute('isInit')) {
$representation['SegmentList']['Initialization'] = $segment->getId();
+3 -2
View File
@@ -12,7 +12,7 @@ if (\file_exists(__DIR__ . '/../vendor/autoload.php')) {
require_once __DIR__ . '/../vendor/autoload.php';
}
ini_set('memory_limit', '512M');
ini_set('memory_limit', '1024M');
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
ini_set('default_socket_timeout', -1);
@@ -144,6 +144,7 @@ const DELETE_TYPE_USAGE = 'usage';
const DELETE_TYPE_REALTIME = 'realtime';
const DELETE_TYPE_BUCKETS = 'buckets';
const DELETE_TYPE_SESSIONS = 'sessions';
const DELETE_TYPE_VIDEOS = 'videos';
// Mail Types
const MAIL_TYPE_VERIFICATION = 'verification';
const MAIL_TYPE_MAGIC_SESSION = 'magicSession';
@@ -843,7 +844,7 @@ App::setResource('project', function ($dbForConsole, $request, $console) {
/** @var Utopia\Database\Database $dbForConsole */
/** @var Utopia\Database\Document $console */
$projectId = 'dev';
//$projectId = 'dev';
$projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', 'console'));
if ($projectId === 'console') {
+5 -5
View File
@@ -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();
+42
View File
@@ -68,6 +68,9 @@ class DeletesV1 extends Worker
case DELETE_TYPE_BUCKETS:
$this->deleteBucket($document, $project->getId());
break;
case DELETE_TYPE_VIDEOS:
$this->deleteVideo($document, $project->getId());
break;
default:
Console::error('No lazy delete operation available for document of type: ' . $document->getCollection());
break;
@@ -621,4 +624,43 @@ class DeletesV1 extends Worker
$device->deletePath($document->getId());
}
/**
* @param Document $document database document
* @param string $projectId
*/
protected function deleteVideo(Document $document, string $projectId): void
{
$videoId = $document->getId();
$dbForProject = $this->getProjectDB($projectId);
$renditions = Authorization::skip(fn() => $dbForProject->find('videos_renditions', [
new Query('videoId', Query::TYPE_EQUAL, [$videoId])]));
foreach ($renditions as $rendition) {
$subtitles = Authorization::skip(fn() => $dbForProject->find('videos_subtitles', [
new Query('videoId', Query::TYPE_EQUAL, [$videoId])]));
foreach ($subtitles as $subtitle) {
$segments = Authorization::skip(fn() => $dbForProject->find('videos_subtitles_segments', [
new Query('subtitleId', Query::TYPE_EQUAL, [$subtitle->getId()])]));
foreach ($segments as $segment) {
Authorization::skip(fn() => $dbForProject->deleteDocument('videos_subtitles_segments', $segment->getId()));
}
Authorization::skip(fn() => $dbForProject->deleteDocument('videos_subtitles', $subtitle->getId()));
}
$this->deleteByGroup('videos_renditions_segments', [
new Query('renditionId', Query::TYPE_EQUAL, [$rendition->getId()])
], $dbForProject);
Authorization::skip(fn() => $dbForProject->deleteDocument('videos_renditions', $rendition->getId()));
}
$videosDevice = $this->getVideoDevice($projectId);
$videosPath = $videosDevice->getPath($videoId);
if ($videosDevice->deletePath($videosPath)) {
Console::success('Deleted video directory: ' . $videosPath);
} else {
Console::error('Failed to delete video directory: ' . $videosPath);
}
}
}
+88 -96
View File
@@ -12,12 +12,12 @@ use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate as DuplicateException;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use FFMpeg\FFProbe\DataMapping\StreamCollection;
use Utopia\Storage\Compression\Algorithms\GZIP;
use Captioning\Format\SubripFile;
use Utopia\Storage\Device;
require_once __DIR__ . '/../init.php';
@@ -87,51 +87,46 @@ class TranscodingV1 extends Worker
$bucket = Authorization::skip(fn() => $this->database->getDocument('buckets', $sourceVideo['bucketId']));
$file = Authorization::skip(fn() => $this->database->getDocument('bucket_' . $bucket->getInternalId(), $sourceVideo['fileId']));
$data = $this->getFilesDevice($project->getId())->read($file->getAttribute('path'));
$fileName = basename($file->getAttribute('path'));
$inPath = $this->inDir . $fileName;
$collection = 'videos_renditions';
if (!empty($file->getAttribute('openSSLCipher'))) {
$data = OpenSSL::decrypt(
$data,
$file->getAttribute('openSSLCipher'),
App::getEnv('_APP_OPENSSL_KEY_V' . $file->getAttribute('openSSLVersion')),
0,
\hex2bin($file->getAttribute('openSSLIV')),
\hex2bin($file->getAttribute('openSSLTag'))
);
if (
!empty($file->getAttribute('openSSLCipher')) ||
!empty($file->getAttribute('algorithm', ''))
) {
$data = $this->getFilesDevice($project->getId())->read($file->getAttribute('path'));
if (!empty($file->getAttribute('openSSLCipher'))) {
$data = OpenSSL::decrypt(
$data,
$file->getAttribute('openSSLCipher'),
App::getEnv('_APP_OPENSSL_KEY_V' . $file->getAttribute('openSSLVersion')),
0,
\hex2bin($file->getAttribute('openSSLIV')),
\hex2bin($file->getAttribute('openSSLTag'))
);
}
if (!empty($file->getAttribute('algorithm', ''))) {
$compressor = new GZIP();
$data = $compressor->decompress($data);
}
$this->getFilesDevice($project->getId())->write($this->inDir . $fileName, $data, $file->getAttribute('mimeType'));
} else {
$this->getFilesDevice($project->getId())->transfer($file->getAttribute('path'), $this->inDir . $fileName, $this->getFilesDevice($project->getId()));
}
if (!empty($file->getAttribute('algorithm', ''))) {
$compressor = new GZIP();
$data = $compressor->decompress($data);
}
$this->getFilesDevice($project->getId())->write($this->inDir . $fileName, $data, $file->getAttribute('mimeType'));
$ffprobe = FFMpeg\FFProbe::create([]);
$ffmpeg = Streaming\FFMpeg::create([]);
$ffprobe = FFMpeg\FFProbe::create();
$ffmpeg = Streaming\FFMpeg::create([
'timeout' => 0,
'ffmpeg.threads' => 12
]);
if (!$ffprobe->isValid($inPath)) {
throw new Exception('Not an valid FFMpeg file "' . $inPath . '"');
}
//Delete prev rendition
$queries = [
new Query('videoId', Query::TYPE_EQUAL, [$sourceVideo->getId()]),
new Query('profileId', Query::TYPE_EQUAL, [$profile->getId()])
];
$rendition = Authorization::skip(fn() => $this->database->findOne($collection, $queries));
if (!empty($rendition)) {
Authorization::skip(fn() => $this->database->deleteDocument($collection, $rendition->getId()));
$deviceFiles = $this->getVideoDevice($project->getId());
if (!empty($rendition['path'])) {
$deviceFiles->deletePath($rendition['path']);
}
}
$general = $this->getVideoSourceInfo($ffprobe->streams($inPath));
if (!empty($general)) {
foreach ($general as $key => $value) {
@@ -152,11 +147,10 @@ class TranscodingV1 extends Worker
$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(),
@@ -165,26 +159,35 @@ class TranscodingV1 extends Worker
$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'))) {
$subtitleData = OpenSSL::decrypt(
$subtitleData,
$subtitleFile->getAttribute('openSSLCipher'),
App::getEnv('_APP_OPENSSL_KEY_V' . $subtitleFile->getAttribute('openSSLVersion')),
0,
\hex2bin($subtitleFile->getAttribute('openSSLIV')),
\hex2bin($subtitleFile->getAttribute('openSSLTag'))
);
if (
!empty($subtitleFile->getAttribute('openSSLCipher')) ||
!empty($subtitleFile->getAttribute('algorithm', ''))
) {
$subtitleData = $this->getFilesDevice($project->getId())->read($subtitleFile->getAttribute('path'));
if (!empty($subtitleFile->getAttribute('openSSLCipher'))) {
$subtitleData = OpenSSL::decrypt(
$subtitleData,
$subtitleFile->getAttribute('openSSLCipher'),
App::getEnv('_APP_OPENSSL_KEY_V' . $subtitleFile->getAttribute('openSSLVersion')),
0,
\hex2bin($subtitleFile->getAttribute('openSSLIV')),
\hex2bin($subtitleFile->getAttribute('openSSLTag'))
);
}
if (!empty($subtitleFile->getAttribute('algorithm', ''))) {
$compressor = new GZIP();
$subtitleData = $compressor->decompress($subtitleData);
}
$this->getFilesDevice($project->getId())->write($this->inDir . $subtitleFileName, $subtitleData, $subtitleFile->getAttribute('mimeType'));
} else {
$this->getFilesDevice($project->getId())->transfer($subtitleFile->getAttribute('path'), $this->inDir . $subtitleFileName, $this->getFilesDevice($project->getId()));
}
if (!empty($subtitleFile->getAttribute('algorithm', ''))) {
$compressor = new GZIP();
$subtitleData = $compressor->decompress($subtitleData);
}
$this->getFilesDevice($project->getId())->write($this->inDir . $subtitleFileName, $subtitleData, $subtitleFile->getAttribute('mimeType'));
$ext = pathinfo($subtitleFileName, PATHINFO_EXTENSION);
if ($ext === 'srt') {
@@ -198,7 +201,7 @@ class TranscodingV1 extends Worker
'path' => $this->inDir . $this->args['videoId'] . '.vtt',
];
}
try {
$query = Authorization::skip(function () use ($collection, $profile) {
return $this->database->createDocument($collection, new Document([
'videoId' => $this->args['videoId'],
@@ -209,10 +212,9 @@ class TranscodingV1 extends Worker
'stream' => $profile['stream'],
]));
});
} catch (DuplicateException $exception) {
throw new Exception('Video rendition already exists', 409, Exception::VIDEO_RENDITION_ALREADY_EXISTS);
}
$renditionRootPath = $this->getVideoDevice($project->getId())->getPath($this->args['videoId']) . '/';
$renditionPath = $renditionRootPath . $this->getRenditionName() . '-' . $query->getId() . '/';
try {
$representation = (new Representation())->
@@ -243,12 +245,12 @@ class TranscodingV1 extends Worker
$m3u8 = $this->parseM3u8($this->outPath . '_' . $representation->getHeight() . 'p.m3u8');
if (!empty($m3u8['segments'])) {
foreach ($m3u8['segments'] as $segment) {
Authorization::skip(function () use ($segment, $project, $query) {
Authorization::skip(function () use ($segment, $project, $query, $renditionPath) {
return $this->database->createDocument('videos_renditions_segments', new Document([
'renditionId' => $query->getId(),
'representationId' => 0,
'fileName' => $segment['fileName'],
'path' => $this->getVideoDevice($project->getId())->getPath($this->args['videoId']) . '/' . $this->getRenditionName() . '/',
'path' => $renditionPath,
'duration' => $segment['duration'],
]));
});
@@ -256,16 +258,15 @@ class TranscodingV1 extends Worker
}
$query->setAttribute('targetDuration', $m3u8['targetDuration']);
} else {
$mpd = $this->parseMpd($this->outPath . '.mpd');
if (!empty($mpd['segments'])) {
foreach ($mpd['segments'] as $segment) {
Authorization::skip(function () use ($segment, $project, $query) {
Authorization::skip(function () use ($segment, $project, $query, $renditionPath) {
return $this->database->createDocument('videos_renditions_segments', new Document([
'renditionId' => $query->getId(),
'representationId' => $segment['representationId'],
'fileName' => $segment['fileName'],
'path' => $this->getVideoDevice($project->getId())->getPath($this->args['videoId']) . '/' . $this->getRenditionName() . '/',
'path' => $renditionPath,
'isInit' => $segment['isInit'],
]));
});
@@ -273,7 +274,7 @@ class TranscodingV1 extends Worker
}
if (!empty($mpd['metadata'])) {
$query->setAttribute('metadata', json_encode(['xml' => $mpd['metadata']]));
$query->setAttribute('metadata', json_encode(['mpd' => $mpd['metadata']]));
}
}
@@ -286,11 +287,11 @@ class TranscodingV1 extends Worker
if ($profile['stream'] === 'hls') {
$m3u8 = $this->parseM3u8($this->outPath . '_subtitles_' . $subtitle['code'] . '.m3u8');
foreach ($m3u8['segments'] as $segment) {
Authorization::skip(function () use ($segment, $project, $subtitle) {
Authorization::skip(function () use ($segment, $project, $subtitle, $renditionRootPath) {
return $this->database->createDocument('videos_subtitles_segments', new Document([
'subtitleId' => $subtitle->getId(),
'fileName' => $segment['fileName'],
'path' => $this->getVideoDevice($project->getId())->getPath($this->args['videoId']) . '/' ,
'path' => $renditionRootPath ,
'duration' => $segment['duration'],
]));
});
@@ -299,6 +300,7 @@ class TranscodingV1 extends Worker
}
$subtitle->setAttribute('status', self::STATUS_READY);
$subtitle->setAttribute('path', $renditionRootPath);
Authorization::skip(fn() => $this->database->updateDocument(
'videos_subtitles',
$subtitle->getId(),
@@ -306,7 +308,7 @@ class TranscodingV1 extends Worker
));
}
}
/** Upload & remove files **/
/** Upload & cleanup **/
$start = 0;
$fileNames = scandir($this->outDir);
@@ -316,38 +318,31 @@ class TranscodingV1 extends Worker
continue;
}
$devicePath = $this->getVideoDevice($project->getId())->getPath($this->args['videoId']);
$data = $this->getFilesDevice($project->getId())->read($this->outDir . $fileName);
$to = $devicePath . '/' . $this->getRenditionName() . '/';
$to = $renditionPath;
if (str_contains($fileName, "_subtitles_") || str_contains($fileName, ".vtt")) {
$to = $devicePath . '/';
$to = $renditionRootPath;
}
$this->getVideoDevice($project->getId())->write($to . $fileName, $data, \mime_content_type($this->outDir . $fileName));
if ($start === 0) {
$query->setAttribute('status', self::STATUS_UPLOADING);
$query->setAttribute('path', $devicePath . '/' . $this->getRenditionName());
$query->setAttribute('path', $renditionPath);
Authorization::skip(fn() => $this->database->updateDocument($collection, $query->getId(), $query));
$start = 1;
}
//$metadata=[];
//$chunksUploaded = $deviceFiles->upload($file, $path, -1, 1, $metadata);
//var_dump($chunksUploaded);
// if (empty($chunksUploaded)) {
// throw new Exception('Failed uploading file', 500, Exception::GENERAL_SERVER_ERROR);
//}
// }
//@unlink($this->outDir . $fileName);
}
$query->setAttribute('status', self::STATUS_READY);
Authorization::skip(fn() => $this->database->updateDocument($collection, $query->getId(), $query));
} catch (\Throwable $th) {
var_dump($th->getCode());
var_dump($th->getMessage());
$query->setAttribute('metadata', json_encode([
'code' => $th->getCode(),
'message' => substr($th->getMessage(), 0, 2048),
'message' => substr($th->getMessage(), 0, 3800),
]));
$query->setAttribute('status', self::STATUS_ERROR);
@@ -365,14 +360,13 @@ class TranscodingV1 extends Worker
*/
private function transcode(string $stream, Media $video, StreamFormat $format, Representation $representation, array $subtitles): string | array
{
$additionalParams = [
'-dn',
'-sn',
'-vf', 'scale=iw:-2:force_original_aspect_ratio=increase,setsar=1:1'
'-vf', 'scale=iw:-2:force_original_aspect_ratio=increase,setsar=1:1',
];
$segmentSize = 10;
$segmentSize = 8;
if ($stream === 'dash') {
$dash = $video->dash()
@@ -481,15 +475,14 @@ class TranscodingV1 extends Worker
*/
private function getVideoSourceInfo(StreamCollection $streams): array
{
return [
'duration' => $streams->videos()->count() ? $streams->videos()->first()->get('duration') : '0',
'height' => $streams->videos()->count() ? $streams->videos()->first()->get('height') : 0,
'width' => $streams->videos()->count() ? $streams->videos()->first()->get('width') : 0,
'videoCodec' => $streams->videos()->count() ? $streams->videos()->first()->get('codec_name') . ',' . $streams->videos()->first()->get('codec_tag_string') : '',
'videoFramerate' => $streams->videos()->count() ? $streams->videos()->first()->get('avg_frame_rate') : '',
'duration' => $streams->videos()->count()> 0 ? $streams->videos()->first()->get('duration') : '0',
'height' => $streams->videos()->count()> 0 ? $streams->videos()->first()->get('height') : 0,
'width' => $streams->videos()->count() > 0 ? $streams->videos()->first()->get('width') : 0,
'videoCodec' => $streams->videos()->count() > 0 ? $streams->videos()->first()->get('codec_name') : '',
'videoFramerate' => $streams->videos()->count() > 0 ? $streams->videos()->first()->get('avg_frame_rate') : '',
'videoBitrate' => $streams->videos()->count() > 0 ? (int)$streams->videos()->first()->get('bit_rate') : 0,
'audioCodec' => $streams->audios()->count() > 0 ? $streams->audios()->first()->get('codec_name') . ',' . $streams->audios()->first()->get('codec_tag_string') : '',
'audioCodec' => $streams->audios()->count() > 0 ? $streams->audios()->first()->get('codec_name') : '',
'audioSamplerate' => $streams->audios()->count() > 0 ? (int)$streams->audios()->first()->get('sample_rate') : 0,
'audioBitrate' => $streams->audios()->count() > 0 ? (int)$streams->audios()->first()->get('bit_rate') : 0,
];
@@ -508,19 +501,18 @@ class TranscodingV1 extends Worker
// }
$info['width'] = $representation->getWidth();
$info['height'] = $representation->getHeight();
if (!empty($metadata['video']['streams'])) {
foreach ($metadata['video']['streams'] as $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'] : $representation->getKiloBitrate() * 1024;
$info['videoCodec'] = !empty($streams['codec_name']) ? $streams['codec_name'] : '';
$info['videoBitrate'] = !empty($streams['bit_rate']) ? (int)$streams['bit_rate'] : $representation->getKiloBitrate();
$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['audioCodec'] = !empty($streams['codec_name']) ? $streams['codec_name'] : '' ;
$info['audioSamplerate'] = !empty($streams['sample_rate']) ? (int)$streams['sample_rate'] : 0;
$info['audioBitrate'] = !empty($streams['bit_rate']) ? (int)$streams['bit_rate'] : $representation->getAudioKiloBitrate() * 1024;
$info['audioBitrate'] = !empty($streams['bit_rate']) ? (int)$streams['bit_rate'] : $representation->getAudioKiloBitrate();
}
}
}
return $info;
}
-9243
View File
File diff suppressed because it is too large Load Diff
@@ -14,9 +14,8 @@ class VideoCustomServerTest extends Scope
use VideoCustom;
use SideServer;
public function testCreateVideoProfile()
public function testCreateProfile(): string
{
$response = $this->client->call(Client::METHOD_POST, '/videos/profiles', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -30,11 +29,21 @@ class VideoCustomServerTest extends Scope
'stream' => 'hls',
]);
$profileId = $response['body']['$id'];
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['$id']);
$response = $this->client->call(Client::METHOD_PATCH, '/videos/profiles/' . $response['body']['$id'], [
return $profileId;
}
/**
* @depends testCreateProfile
*/
public function testUpdateProfile(string $profileId)
{
$response = $this->client->call(Client::METHOD_PATCH, '/videos/profiles/' . $profileId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
@@ -44,14 +53,20 @@ class VideoCustomServerTest extends Scope
'audioBitrate' => 120,
'width' => 300,
'height' => 400,
'stream' => 'hls',
'stream' => 'dash',
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['$id']);
}
$response = $this->client->call(Client::METHOD_GET, '/videos/profiles/' . $response['body']['$id'], [
/**
* @depends testCreateProfile
*/
public function testGetProfile(string $profileId)
{
$response = $this->client->call(Client::METHOD_GET, '/videos/profiles/' . $profileId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
@@ -63,8 +78,16 @@ class VideoCustomServerTest extends Scope
$this->assertNotEmpty($response['body']);
$this->assertEquals('My updated test profile', $response['body']['name']);
$this->assertEquals(300, $response['body']['width']);
$this->assertEquals(400, $response['body']['height']);
}
$response = $this->client->call(Client::METHOD_DELETE, '/videos/profiles/' . $response['body']['$id'], [
/**
* @depends testCreateProfile
*/
public function testDeleteProfile(string $profileId)
{
$response = $this->client->call(Client::METHOD_DELETE, '/videos/profiles/' . $profileId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
@@ -93,10 +116,45 @@ class VideoCustomServerTest extends Scope
$this->assertEquals(3, $response['body']['total']);
}
public function testCreateVideo(): string
public function testDeleteAllProfiles()
{
$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'],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']);
$this->assertEquals(3, $response['body']['total']);
$profiles = $response['body']['profiles'];
foreach ($profiles as $profile) {
$response = $this->client->call(Client::METHOD_DELETE, '/videos/profiles/' . $profile['$id'], [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(204, $response['headers']['status-code']);
}
$response = $this->client->call(Client::METHOD_GET, '/videos/profiles/' . $profile['$id'], [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(404, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']);
$this->assertEquals('Video profile not found', $response['body']['message']);
}
/**
* @depends testDeleteAllProfiles
*/
public function testCreateVideo(): string
{
$response = $this->client->call(Client::METHOD_POST, '/videos', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -116,9 +174,8 @@ class VideoCustomServerTest extends Scope
/**
* @depends testCreateVideo
*/
public function testCreateVideoSubtitle($videoId)
public function testCreateSubtitles($videoId)
{
$response = $this->client->call(Client::METHOD_POST, '/videos/' . $videoId . '/subtitles', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -135,6 +192,42 @@ class VideoCustomServerTest extends Scope
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['$id']);
$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'],
], [
'bucketId' => $this->getBucket()['$id'],
'fileId' => $this->getSubtitle()['$id'],
'name' => 'Italian',
'code' => 'It',
]);
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['$id']);
$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'],
], [
'bucketId' => $this->getBucket()['$id'],
'fileId' => $this->getSubtitle()['$id'],
'name' => 'Hebrew',
'code' => 'Heb',
]);
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['$id']);
}
/**
* @depends testCreateVideo
*/
public function testGetSubtitles($videoId): array
{
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $videoId . '/subtitles', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -143,12 +236,25 @@ class VideoCustomServerTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']);
$this->assertEquals(1, $response['body']['total']);
$this->assertEquals(3, $response['body']['total']);
$this->assertNotEmpty($response['body']['subtitles']);
$this->assertNotEmpty($response['body']['subtitles'][0]['$id']);
$this->assertEquals('English', $response['body']['subtitles'][0]['name']);
$this->assertEquals('Eng', $response['body']['subtitles'][0]['code']);
$this->assertEquals(true, $response['body']['subtitles'][0]['default']);
$response = $this->client->call(Client::METHOD_PATCH, '/videos/' . $videoId . '/subtitles/' . $response['body']['subtitles'][0]['$id'], [
return $response['body']['subtitles'];
}
/**
* @depends testGetSubtitles
*/
public function testUpdateSubtitle($subtitles): array
{
$subtitleId = $subtitles[1]['$id'];
$videoId = $subtitles[1]['videoId'];
$response = $this->client->call(Client::METHOD_PATCH, '/videos/' . $videoId . '/subtitles/' . $subtitleId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
@@ -157,7 +263,6 @@ class VideoCustomServerTest extends Scope
'fileId' => $this->getSubtitle()['$id'],
'name' => 'Polish',
'code' => 'Pol',
'default' => false,
]);
$this->assertEquals(200, $response['headers']['status-code']);
@@ -170,49 +275,48 @@ class VideoCustomServerTest extends Scope
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']);
$this->assertEquals(1, $response['body']['total']);
$this->assertEquals(3, $response['body']['total']);
$this->assertNotEmpty($response['body']['subtitles']);
$this->assertNotEmpty($response['body']['subtitles'][0]['$id']);
$this->assertEquals('Polish', $response['body']['subtitles'][0]['name']);
$this->assertNotEmpty($response['body']['subtitles'][1]['$id']);
$this->assertEquals('Pol', $response['body']['subtitles'][1]['code']);
$this->assertEquals('Polish', $response['body']['subtitles'][1]['name']);
$response = $this->client->call(Client::METHOD_DELETE, '/videos/' . $videoId . '/subtitles/' . $response['body']['subtitles'][0]['$id'], [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
return $response['body']['subtitles'];
}
$this->assertEquals(204, $response['headers']['status-code']);
/**
* @depends testGetSubtitles
*/
public function testDeleteSubtitle($subtitles)
{
$videoId = $subtitles[0]['videoId'];
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $videoId . '/subtitles', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
foreach ($subtitles as $subtitle) {
$response = $this->client->call(Client::METHOD_DELETE, '/videos/' . $subtitle['videoId'] . '/subtitles/' . $subtitle['$id'], [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(204, $response['headers']['status-code']);
}
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $videoId . '/subtitles', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(404, $response['headers']['status-code']);
}
/**
* @depends testCreateVideo
*/
public function testTranscodeWithSubs(): array
public function testTranscodeWithSubs($videoId): string
{
$response = $this->client->call(Client::METHOD_POST, '/videos', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'bucketId' => $this->getBucket()['$id'],
'fileId' => $this->getVideo()['$id']
]);
$videoId = $response['body']['$id'];
$response = $this->client->call(Client::METHOD_POST, '/videos/' . $videoId . '/subtitles', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -220,110 +324,144 @@ class VideoCustomServerTest extends Scope
], [
'bucketId' => $this->getBucket()['$id'],
'fileId' => $this->getSubtitle()['$id'],
'name' => 'hebrew',
'code' => 'heb',
]);
$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'],
], [
'bucketId' => $this->getBucket()['$id'],
'fileId' => $this->getSubtitle()['$id'],
'name' => 'english',
'code' => 'eng',
'name' => 'English',
'code' => 'Eng',
'default' => true,
]);
$subtitleId = $response['body']['$id'];
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['$id']);
$response = $this->client->call(Client::METHOD_GET, '/videos/profiles', [
$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'],
], [
'bucketId' => $this->getBucket()['$id'],
'fileId' => $this->getSubtitle()['$id'],
'name' => 'Italian',
'code' => 'It',
]);
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['$id']);
$profileId = $response['body']['profiles'][0]['$id'];
/**
* Try to transcode with wrong 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'],
], [
'profileId' => $profileId,
'profileId' => $videoId,
]);
$this->assertEquals(204, $response['headers']['status-code']);
$this->assertEquals(404, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']);
$this->assertEquals('Video profile not found', $response['body']['message']);
return [
'videoId' => $videoId,
'subtitleId' => $subtitleId
];
}
public function testTranscodingRendition(): array
{
$response = $this->client->call(Client::METHOD_POST, '/video', [
$response = $this->client->call(Client::METHOD_POST, '/videos/profiles', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'bucketId' => $this->getBucket()['$id'],
'fileId' => $this->getVideo()['$id']
'name' => 'Profile A',
'videoBitrate' => 770,
'audioBitrate' => 64,
'width' => 600,
'height' => 400,
'stream' => 'hls',
]);
$this->assertEquals(201, $response['headers']['status-code']);
$response = $this->client->call(Client::METHOD_POST, '/videos/profiles', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'name' => 'Profile B',
'videoBitrate' => 570,
'audioBitrate' => 64,
'width' => 300,
'height' => 200,
'stream' => 'dash',
]);
$videoId = $response['body']['$id'];
$this->assertEquals(201, $response['headers']['status-code']);
$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'],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(2, $response['body']['total']);
$profiles = $response['body']['profiles'];
foreach ($response['body']['profiles'] as $profile) {
$profileId = $profile['$id'];
/**
* Try to transcode with wrong videoId
*/
$response = $this->client->call(Client::METHOD_POST, '/videos/' . $response['body']['profiles'][0]['$id'] . '/rendition', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'profileId' => $response['body']['profiles'][0]['$id'],
]);
$this->assertEquals(404, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']);
$this->assertEquals('Video not found', $response['body']['message']);
foreach ($profiles as $profile) {
$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'],
], [
'profileId' => $profileId,
'profileId' => $profile['$id'],
]);
$this->assertEquals(204, $response['headers']['status-code']);
}
return [
'videoId' => $videoId,
];
return $videoId;
}
/**
* @depends testTranscodeWithSubs
*/
public function testGetRenditions(array $data): array
public function testGetRenditionsWithSubs(string $videoId): string
{
sleep(30);
sleep(50);
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $data['videoId'] . '/renditions', [
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $videoId . '/renditions', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(2, $response['body']['total']);
$this->assertNotEmpty($response['body']['renditions']);
$videoId = $response['body']['renditions'][0]['videoId'];
$renditionId = $response['body']['renditions'][0]['$id'];
$profileId = $response['body']['renditions'][0]['profileId'];
$profileName = $response['body']['renditions'][0]['name'];
$stream = $response['body']['renditions'][0]['stream'];
foreach ($response['body']['renditions'] as $rendition) {
$this->assertEquals('ready', $rendition['status']);
$this->assertEquals('99', $rendition['progress']);
$this->assertNotEmpty($rendition['videoBitrate']);
$this->assertNotEmpty($rendition['videoCodec']);
}
$this->assertEquals(200, $response['headers']['status-code']);
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $data['videoId'] . '/rendition/' . $renditionId, [
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $videoId . '/rendition/' . $renditionId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
@@ -331,16 +469,134 @@ class VideoCustomServerTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals($renditionId, $response['body']['$id']);
//var_dump($response['body']);
return [
'renditionId' => $renditionId,
'videoId' => $videoId,
'profileId' => $profileId,
'profileName' => $profileName,
'subtitleId' => $data['subtitleId'],
'stream' => $stream
];
return $videoId;
}
/**
* @depends testGetRenditionsWithSubs
*/
public function testStreamManifest($videoId): void
{
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $videoId . '/streams/hls', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(200, $response['headers']['status-code']);
preg_match_all('#\b/videos[^,\s()<>]+(?:\([\w\d]+\)|([^,[:punct:]\s]|/))#', $response['body'], $match);
$this->assertEquals(3, count($match[0]));
$subtitleUri = $match[0][0];
$renditionUri = $match[0][2];
$response = $this->client->call(Client::METHOD_GET, $renditionUri, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(200, $response['headers']['status-code']);
preg_match_all('#\b/videos[^,\s()<>]+(?:\([\w\d]+\)|([^,[:punct:]\s]|/))#', $response['body'], $match);
$this->assertEquals(12, count($match[0]));
$segmentUri = $match[0][0];
$response = $this->client->call(Client::METHOD_GET, $segmentUri, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertGreaterThan(0, strlen($response['body']));
$response = $this->client->call(Client::METHOD_GET, $subtitleUri, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
preg_match_all('#\b/videos[^,\s()<>]+(?:\([\w\d]+\)|([^,[:punct:]\s]|/))#', $response['body'], $match);
$segmentUri = $match[0][0];
$response = $this->client->call(Client::METHOD_GET, $segmentUri, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(1506, strlen($response['body']));
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $videoId . '/streams/dash', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$xml = simplexml_load_string($response['body']);
foreach ($xml->Period->AdaptationSet as $adaptation) {
if ((string) $adaptation['contentType'] === 'video') {
$this->assertEquals("50/1", $adaptation['frameRate']);
$this->assertEquals("300", $adaptation['maxWidth']);
$this->assertEquals("30:17", $adaptation['par']);
$this->assertEquals("und", $adaptation['lang']);
foreach ($adaptation->Representation as $representation) {
$this->assertEquals("video/mp4", $representation['mimeType']);
$this->assertEquals("avc1.640015", $representation['codecs']);
$this->assertEquals("300", $representation['width']);
$this->assertEquals("200", $representation['height']);
$this->assertEquals("20:17", $representation['sar']);
$this->assertEquals(10, $representation->SegmentList->SegmentURL->count());
$videoSegmentBaseUrl = (string)$representation->BaseURL;
$videoSegmentInitialization = (string)$representation->SegmentList->Initialization['sourceURL'];
$videoSegmentId = (string)$representation->SegmentList->SegmentURL['media'];
$response = $this->client->call(Client::METHOD_GET, $videoSegmentBaseUrl . $videoSegmentInitialization, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertGreaterThan(0, strlen($response['body']));
$response = $this->client->call(Client::METHOD_GET, $videoSegmentBaseUrl . $videoSegmentId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertGreaterThan(0, strlen($response['body']));
}
} elseif ((string) $adaptation['contentType'] === 'audio') {
foreach ($adaptation->Representation as $representation) {
$this->assertEquals("audio/mp4", $representation['mimeType']);
$this->assertEquals("mp4a.40.2", $representation['codecs']);
$this->assertEquals(12, $representation->SegmentList->SegmentURL->count());
$audioSegmentBaseUrl = (string)$representation->BaseURL;
$audioSegmentInitialization = (string)$representation->SegmentList->Initialization['sourceURL'];
$audioSegmentId = (string)$representation->SegmentList->SegmentURL['media'];
$response = $this->client->call(Client::METHOD_GET, $audioSegmentBaseUrl . $audioSegmentInitialization, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertGreaterThan(0, strlen($response['body']));
$response = $this->client->call(Client::METHOD_GET, $audioSegmentBaseUrl . $audioSegmentId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertGreaterThan(0, strlen($response['body']));
}
}
}
}
/**
@@ -356,67 +612,53 @@ class VideoCustomServerTest extends Scope
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
var_dump($response['body']);
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $data['videoId'] . $data['stream'] . '/renditions/' . $data['renditionId'], [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
var_dump($response['body']);
preg_match_all('#\b/videos[^,\s()<>]+(?:\([\w\d]+\)|([^,[:punct:]\s]|/))#', $response['body'], $match);
$segmentUrl = $match[0][0];
$response = $this->client->call(Client::METHOD_GET, $segmentUrl, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
//var_dump($response['body']);
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $data['videoId'] . '/streams/' . $data['stream'] . '/subtitles/' . $data['subtitleId'], [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
//var_dump($response['body']);
preg_match_all('#\b/videos[^,\s()<>]+(?:\([\w\d]+\)|([^,[:punct:]\s]|/))#', $response['body'], $match);
$segmentUrl = $match[0][0];
var_dump($segmentUrl);
$response = $this->client->call(Client::METHOD_GET, $segmentUrl, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
//var_dump($response['body']);
}
/**
* @depends testGetRenditions
*/
public function testDashStreamRender($data): void
{
sleep(20);
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $data['videoId'] . '/streams/dash', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
var_dump($response['body']);
}
// /**
// * @depends testGetRenditions
// */
// public function testDashStreamRender($data): void
// {
//
// sleep(20);
//
// $response = $this->client->call(Client::METHOD_GET, '/videos/' . $data['videoId'] . '/streams/dash', [
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// 'x-appwrite-key' => $this->getProject()['apiKey'],
// ]);
//
// var_dump($response['body']);
// }
}