diff --git a/Dockerfile b/Dockerfile
index 2f6cb455ee..0b70fb7b19 100755
--- a/Dockerfile
+++ b/Dockerfile
@@ -126,9 +126,8 @@ ENV _APP_SERVER=swoole \
RUN \
apk update \
- && apk add --no-cache --virtual .deps \
+ && apk add --no-cache\
ffmpeg \
- && apk del .deps \
&& rm -rf /var/cache/apk/*
RUN \
diff --git a/app/config/videos-profiles.php b/app/config/videos-profiles.php
index e68b3dbbb7..f1b0bcff0a 100644
--- a/app/config/videos-profiles.php
+++ b/app/config/videos-profiles.php
@@ -1,6 +1,32 @@
'360p',
+ 'videoBitrate' => 890, //video bitrate in Kbps
+ 'audioBitrate' => 64, //audio bitrate in Kbps
+ 'width' => 640, //width resolution in px
+ 'height' => 360, //height resolution in px
+ 'output' => 'hls'
+ ],
+ [
+ 'name' => '576p',
+ 'videoBitrate' => 2538,
+ 'audioBitrate' => 128,
+ 'width' => 1024,
+ 'height' => 576,
+ 'output' => 'hls'
+ ],
+ [
+ 'name' => '720p',
+ 'videoBitrate' => 3551,
+ 'audioBitrate' => 128,
+ 'width' => 1280,
+ 'height' => 720,
+ 'output' => 'hls'
+ ],
+
[
'name' => '360p',
@@ -27,4 +53,5 @@ return [
'output' => 'dash'
],
+
];
diff --git a/app/controllers/api/videos.php b/app/controllers/api/videos.php
index 24a0bf9ec1..6a36ad862e 100644
--- a/app/controllers/api/videos.php
+++ b/app/controllers/api/videos.php
@@ -1059,9 +1059,6 @@ App::get('/v1/videos/profiles')
->action(function (Response $response, Database $dbForProject) {
$profiles = Authorization::skip(fn () => $dbForProject->find('videos_profiles'));
- if (empty($profiles)) {
- throw new Exception(Exception::VIDEO_PROFILE_NOT_FOUND);
- }
$response->dynamic(new Document([
'total' => $dbForProject->count('videos_profiles', [], APP_LIMIT_COUNT),
diff --git a/app/init.php b/app/init.php
index b895d92aa8..2b1ed21d46 100644
--- a/app/init.php
+++ b/app/init.php
@@ -889,7 +889,7 @@ App::setResource('project', function ($dbForConsole, $request, $console) {
/** @var Utopia\Database\Document $console */
$projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', 'console'));
- $projectId = '63f22d4b78b296b2d47d';
+ //$projectId = '63f22d4b78b296b2d47d';
if ($projectId === 'console') {
return $console;
}
diff --git a/app/workers/transcoding.php b/app/workers/transcoding.php
index d6c94a31d9..fdcec0f7c7 100644
--- a/app/workers/transcoding.php
+++ b/app/workers/transcoding.php
@@ -512,9 +512,9 @@ class TranscodingV1 extends Worker
$general = $metadata['stream']['resolutions'][0] ?? [];
$parts = explode('X', $general['dimension']);
$info['width'] = $parts[0] ?? $representation->getWidth();
- $info['height'] = isset($parts[1]) ?? $representation->getHeight();
- $info['videoBitrate'] = isset($general['video_kilo_bitrate']) ?? '0';
- $info['audioBitrate'] = isset($general['audio_kilo_bitrate']) ?? '0';
+ $info['height'] = $parts[1] ?? $representation->getHeight();
+ $info['videoBitrate'] = $general['video_kilo_bitrate'] ?? '0';
+ $info['audioBitrate'] = $general['audio_kilo_bitrate'] ?? '0';
foreach ($metadata['video']['streams'] ?? [] as $streams) {
if ($streams['codec_type'] === 'video') {
diff --git a/composer.json b/composer.json
index e1d1231924..63727cc7c9 100644
--- a/composer.json
+++ b/composer.json
@@ -59,7 +59,7 @@
"utopia-php/messaging": "0.1.*",
"utopia-php/orchestration": "0.6.*",
"utopia-php/registry": "0.5.*",
- "utopia-php/storage": "0.13.*",
+ "utopia-php/storage": "dev-feat-transfer as 0.13.2",
"utopia-php/swoole": "0.5.*",
"utopia-php/websocket": "0.1.*",
"resque/php-resque": "1.3.6",
diff --git a/composer.lock b/composer.lock
index f478c3ffc4..c3bd4d8a2a 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "1577b45260165bbdf1367d5a24e4e79f",
+ "content-hash": "b636b84edd26b787ae58b3dbdc752da4",
"packages": [
{
"name": "adhocore/jwt",
@@ -3798,16 +3798,16 @@
},
{
"name": "utopia-php/storage",
- "version": "0.13.2",
+ "version": "dev-feat-transfer",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/storage.git",
- "reference": "ad1c00f24ca56e73888acc2af3deee4919b1194b"
+ "reference": "00b26ef3d04b04f2df64a64636f6b81f8ae1a166"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/utopia-php/storage/zipball/ad1c00f24ca56e73888acc2af3deee4919b1194b",
- "reference": "ad1c00f24ca56e73888acc2af3deee4919b1194b",
+ "url": "https://api.github.com/repos/utopia-php/storage/zipball/00b26ef3d04b04f2df64a64636f6b81f8ae1a166",
+ "reference": "00b26ef3d04b04f2df64a64636f6b81f8ae1a166",
"shasum": ""
},
"require": {
@@ -3847,9 +3847,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/storage/issues",
- "source": "https://github.com/utopia-php/storage/tree/0.13.2"
+ "source": "https://github.com/utopia-php/storage/tree/feat-transfer"
},
- "time": "2022-12-20T11:11:35+00:00"
+ "time": "2022-11-02T07:42:34+00:00"
},
{
"name": "utopia-php/swoole",
@@ -6506,9 +6506,17 @@
"time": "2023-02-08T07:49:20+00:00"
}
],
- "aliases": [],
+ "aliases": [
+ {
+ "package": "utopia-php/storage",
+ "version": "dev-feat-transfer",
+ "alias": "0.13.2",
+ "alias_normalized": "0.13.2.0"
+ }
+ ],
"minimum-stability": "stable",
"stability-flags": {
+ "utopia-php/storage": 20,
"aminyazdanpanah/php-ffmpeg-video-streaming": 20,
"appwrite/sdk-generator": 20
},
diff --git a/phpunit.xml b/phpunit.xml
index 5b4bcdb99c..5c5c7f593c 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -31,6 +31,7 @@
./tests/e2e/Services/Teams
./tests/e2e/Services/Users
./tests/e2e/Services/Webhooks
+ ./tests/e2e/Services/Videos
./tests/e2e/Services/Functions/FunctionsBase.php
./tests/e2e/Services/Functions/FunctionsCustomServerTest.php
./tests/e2e/Services/Functions/FunctionsCustomClientTest.php
diff --git a/tests/e2e/Scopes/VideoCustom.php b/tests/e2e/Scopes/VideoCustom.php
index a3275d0ae5..a6bd22a4db 100644
--- a/tests/e2e/Scopes/VideoCustom.php
+++ b/tests/e2e/Scopes/VideoCustom.php
@@ -4,9 +4,10 @@ namespace Tests\E2E\Scopes;
use Tests\E2E\Client;
use Utopia\Database\DateTime;
-use Utopia\Database\ID;
-use Utopia\Database\Permission;
-use Utopia\Database\Role;
+use Utopia\Database\Helpers\ID;
+use Utopia\Database\Helpers\Permission;
+use Utopia\Database\validator\DateTimeValidator;
+use Utopia\Database\Helpers\Role;
trait VideoCustom
{
diff --git a/tests/e2e/Services/Videos/VideosCustomClientTest.php b/tests/e2e/Services/Videos/VideosCustomClientTest.php
new file mode 100644
index 0000000000..c577367989
--- /dev/null
+++ b/tests/e2e/Services/Videos/VideosCustomClientTest.php
@@ -0,0 +1,293 @@
+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(6, $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']);
+ }
+
+ public function testTranscodeWithSubs()
+ {
+ /**
+ * Create video
+ */
+ $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']
+ ]);
+
+ $this->assertEquals(201, $response['headers']['status-code']);
+ $this->assertNotEmpty($response['body']);
+ $this->assertNotEmpty($response['body']['$id']);
+
+ $videoId = $response['body']['$id'];
+
+ /**
+ * Create 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'],
+ ], [
+ 'bucketId' => $this->getBucket()['$id'],
+ 'fileId' => $this->getSubtitle()['$id'],
+ 'name' => 'English',
+ 'code' => 'Eng',
+ 'default' => true,
+ ]);
+
+ $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' => 'Italian',
+ 'code' => 'It',
+ ]);
+
+ $this->assertEquals(201, $response['headers']['status-code']);
+ $this->assertNotEmpty($response['body']);
+ $this->assertNotEmpty($response['body']['$id']);
+
+ /**
+ * Create profiles
+ */
+ $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 A',
+ 'videoBitrate' => 770,
+ 'audioBitrate' => 64,
+ 'width' => 600,
+ 'height' => 400,
+ 'output' => '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,
+ 'output' => 'dash',
+ ]);
+
+ $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']);
+ $this->assertNotEmpty($response['body']['profiles']);
+ $profiles = $response['body']['profiles'];
+
+ 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' => $profile['$id'],
+ ]);
+
+ $this->assertEquals(204, $response['headers']['status-code']);
+ }
+ sleep(30);
+ $response = $this->client->call(Client::METHOD_GET, '/videos/' . $videoId . '/outputs/hls', [
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders());
+
+ $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][1];
+ $renditionUri = $match[0][2];
+
+ $response = $this->client->call(Client::METHOD_GET, $renditionUri, [
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders());
+
+ $this->assertEquals(200, $response['headers']['status-code']);
+ preg_match_all('#\b/videos[^,\s()<>]+(?:\([\w\d]+\)|([^,[:punct:]\s]|/))#', $response['body'], $match);
+ $this->assertEquals(10, 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'],
+ ], $this->getHeaders());
+
+ $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'],
+ ], $this->getHeaders());
+
+ 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'],
+ ], $this->getHeaders());
+
+ $this->assertEquals(200, $response['headers']['status-code']);
+ $this->assertEquals(1508, strlen($response['body']));
+
+ $response = $this->client->call(Client::METHOD_GET, '/videos/' . $videoId . '/outputs/dash', [
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders());
+
+ $this->assertEquals(200, $response['headers']['status-code']);
+
+ $xml = simplexml_load_string($response['body']);
+
+ $this->assertEquals("PT1M32.7S", $xml->attributes()->mediaPresentationDuration);
+ $this->assertEquals("PT10.0S", $xml->attributes()->maxSegmentDuration);
+ $this->assertEquals("PT20.0S", $xml->attributes()->minBufferTime);
+ $subsCount = 0;
+ $isVideo = false;
+ $isAudio = false;
+ $subs[] = ['id' => '2', 'lang' => 'Eng',];
+ $subs[] = ['id' => '3', 'lang' => 'It',];
+ foreach ($xml->Period->AdaptationSet as $adaptation) {
+ if ((string)$adaptation['contentType'] === 'video') {
+ $isVideo = true;
+ $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'],
+ ], $this->getHeaders());
+
+ $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'],
+ ], $this->getHeaders());
+
+ $this->assertEquals(200, $response['headers']['status-code']);
+ $this->assertGreaterThan(0, strlen($response['body']));
+ }
+ } elseif ((string)$adaptation['contentType'] === 'audio') {
+ $isAudio = true;
+ foreach ($adaptation->Representation as $representation) {
+ $this->assertEquals("audio/mp4", $representation['mimeType']);
+ $this->assertEquals("mp4a.40.2", $representation['codecs']);
+ $this->assertEquals(10, $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'],
+ ], $this->getHeaders());
+
+ $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'],
+ ], $this->getHeaders());
+
+ $this->assertEquals(200, $response['headers']['status-code']);
+ $this->assertGreaterThan(0, strlen($response['body']));
+ }
+ } elseif ((string)$adaptation['mimeType'] === 'text/vtt') {
+ $this->assertEquals($subs[$subsCount]['id'], $adaptation['id']);
+ $this->assertEquals($subs[$subsCount]['lang'], $adaptation['lang']);
+ $subsCount++;
+ }
+ }
+
+ $this->assertEquals($subsCount, 2);
+ $this->assertTrue($isVideo);
+ $this->assertTrue($isAudio);
+ }
+}
diff --git a/tests/e2e/Services/Videos/VideoCustomServerTest.php b/tests/e2e/Services/Videos/VideosCustomServerTest.php
similarity index 81%
rename from tests/e2e/Services/Videos/VideoCustomServerTest.php
rename to tests/e2e/Services/Videos/VideosCustomServerTest.php
index f17117d016..3acfd49db0 100644
--- a/tests/e2e/Services/Videos/VideoCustomServerTest.php
+++ b/tests/e2e/Services/Videos/VideosCustomServerTest.php
@@ -7,15 +7,47 @@ use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\VideoCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideServer;
-use Utopia\Database\Permission;
-use Utopia\Database\Role;
-class VideoCustomServerTest extends Scope
+class VideosCustomServerTest extends Scope
{
use ProjectCustom;
use VideoCustom;
use SideServer;
+ 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(6, $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']);
+ }
+
public function testCreateProfile(): string
{
$response = $this->client->call(Client::METHOD_POST, '/videos/profiles', [
@@ -28,21 +60,20 @@ class VideoCustomServerTest extends Scope
'audioBitrate' => 120,
'width' => 600,
'height' => 400,
- 'protocol' => 'hls',
+ 'output' => 'hls',
]);
- $profileId = $response['body']['$id'];
- $this->assertEquals(201, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['$id']);
+ $this->assertEquals(201, $response['headers']['status-code']);
- return $profileId;
+ return $response['body']['$id'];
}
/**
* @depends testCreateProfile
*/
- public function testUpdateProfile(string $profileId)
+ public function testUpdateProfile(string $profileId): string
{
$response = $this->client->call(Client::METHOD_PATCH, '/videos/profiles/' . $profileId, [
@@ -55,16 +86,18 @@ class VideoCustomServerTest extends Scope
'audioBitrate' => 120,
'width' => 300,
'height' => 400,
- 'protocol' => 'dash',
+ 'output' => 'dash',
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']['$id']);
+
+ return $response['body']['$id'];
}
/**
- * @depends testCreateProfile
+ * @depends testUpdateProfile
*/
public function testGetProfile(string $profileId): string
{
@@ -74,74 +107,21 @@ class VideoCustomServerTest extends Scope
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
- $profileId = $response['body']['$id'];
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']);
+ $this->assertNotEmpty($response['body']['$id']);
$this->assertEquals('My updated test profile', $response['body']['name']);
$this->assertEquals(300, $response['body']['width']);
$this->assertEquals(400, $response['body']['height']);
+ $this->assertEquals('dash', $response['body']['output']);
- return $profileId;
+ return $response['body']['$id'];
}
+
/**
* @depends testGetProfile
*/
- public function testGetVideoWithUserPermissions(string $profileId)
- {
-
- $source = __DIR__ . "/../../../resources/disk-a/video-srt.mp4";
- $totalSize = \filesize($source);
- $chunkSize = 5 * 1024 * 1024;
- $handle = @fopen($source, "rb");
- $fileId = 'unique()';
- $mimeType = mime_content_type($source);
- $counter = 0;
- $size = filesize($source);
- $headers = [
- 'content-type' => 'multipart/form-data',
- 'x-appwrite-project' => $this->getProject()['$id']
- ];
- $id = '';
-
- while (!feof($handle)) {
- $curlFile = new \CURLFile('data:' . $mimeType . ';base64,' . base64_encode(@fread($handle, $chunkSize)), $mimeType, 'video-srt.mp4');
- $headers['content-range'] = 'bytes ' . ($counter * $chunkSize) . '-' . min(((($counter * $chunkSize) + $chunkSize) - 1), $size) . '/' . $size;
-
- if (!empty($id)) {
- $headers['x-appwrite-id'] = $id;
- }
-
- $_file = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $this->getBucket()['$id'] . '/files', array_merge($headers, $this->getHeaders()), [
- 'fileId' => $fileId,
- 'file' => $curlFile,
- 'fileSecurity' => true,
- 'permissions' => [
- Permission::read(Role::user($this->getUser()['$id'])),
- Permission::update(Role::user($this->getUser()['$id'])),
- Permission::delete(Role::user($this->getUser()['$id'])),
- ],
-
- ]);
- $counter++;
- $fileId = $_file['body']['$id'];
- }
- @fclose($handle);
-
- $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' => $fileId
- ]);
-
- }
-
- /**
- * @depends testCreateProfile
- */
public function testDeleteProfile(string $profileId)
{
@@ -171,46 +151,46 @@ class VideoCustomServerTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']);
- $this->assertEquals(3, $response['body']['total']);
+ $this->assertEquals(0, $response['body']['total']);
}
- public function testDeleteAllProfiles()
+ public function testCreateProfiles(): void
{
-
- $response = $this->client->call(Client::METHOD_GET, '/videos/profiles', [
+ $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' => 'My test profile',
+ 'videoBitrate' => 570,
+ 'audioBitrate' => 120,
+ 'width' => 600,
+ 'height' => 400,
+ 'output' => 'hls',
]);
- $this->assertEquals(200, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']);
- $this->assertEquals(3, $response['body']['total']);
+ $this->assertNotEmpty($response['body']['$id']);
+ $this->assertEquals(201, $response['headers']['status-code']);
- $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'], [
+ $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' => '576p',
+ 'videoBitrate' => 2538,
+ 'audioBitrate' => 128,
+ 'width' => 1024,
+ 'height' => 576,
+ 'output' => 'hls',
]);
- $this->assertEquals(404, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']);
- $this->assertEquals('Video profile not found.', $response['body']['message']);
+ $this->assertNotEmpty($response['body']['$id']);
+ $this->assertEquals(201, $response['headers']['status-code']);
}
- /**
- * @depends testDeleteAllProfiles
- */
public function testCreateVideo(): string
{
$response = $this->client->call(Client::METHOD_POST, '/videos', [
@@ -375,6 +355,27 @@ class VideoCustomServerTest extends Scope
*/
public function testTranscodeWithSubs($videoId): string
{
+
+ $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(2, $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_POST, '/videos/' . $videoId . '/subtitles', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -431,7 +432,7 @@ class VideoCustomServerTest extends Scope
'audioBitrate' => 64,
'width' => 600,
'height' => 400,
- 'protocol' => 'hls',
+ 'output' => 'hls',
]);
$this->assertEquals(201, $response['headers']['status-code']);
@@ -445,7 +446,7 @@ class VideoCustomServerTest extends Scope
'audioBitrate' => 64,
'width' => 300,
'height' => 200,
- 'protocol' => 'dash',
+ 'output' => 'dash',
]);
$this->assertEquals(201, $response['headers']['status-code']);
@@ -534,7 +535,7 @@ class VideoCustomServerTest extends Scope
/**
* @depends testGetRenditions
*/
- public function testProtocolWithSubs($videoId): string
+ public function testOutputWithSubs($videoId): string
{
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $videoId . '/outputs/hls', [
@@ -544,9 +545,9 @@ class VideoCustomServerTest extends Scope
]);
$this->assertEquals(200, $response['headers']['status-code']);
preg_match_all('#\b/videos[^,\s()<>]+(?:\([\w\d]+\)|([^,[:punct:]\s]|/))#', $response['body'], $match);
- $this->assertEquals(4, count($match[0]));
+ $this->assertEquals(3, count($match[0]));
$subtitleUri = $match[0][1];
- $renditionUri = $match[0][3];
+ $renditionUri = $match[0][2];
$response = $this->client->call(Client::METHOD_GET, $renditionUri, [
'content-type' => 'application/json',
@@ -555,7 +556,7 @@ class VideoCustomServerTest extends Scope
]);
$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]));
+ $this->assertEquals(10, count($match[0]));
$segmentUri = $match[0][0];
$response = $this->client->call(Client::METHOD_GET, $segmentUri, [
@@ -595,8 +596,8 @@ class VideoCustomServerTest extends Scope
$xml = simplexml_load_string($response['body']);
$this->assertEquals("PT1M32.7S", $xml->attributes()->mediaPresentationDuration);
- $this->assertEquals("PT8.0S", $xml->attributes()->maxSegmentDuration);
- $this->assertEquals("PT20.5S", $xml->attributes()->minBufferTime);
+ $this->assertEquals("PT10.0S", $xml->attributes()->maxSegmentDuration);
+ $this->assertEquals("PT20.0S", $xml->attributes()->minBufferTime);
$subsCount = 0;
$isVideo = false;
$isAudio = false;
@@ -605,13 +606,13 @@ class VideoCustomServerTest extends Scope
foreach ($xml->Period->AdaptationSet as $adaptation) {
if ((string)$adaptation['contentType'] === 'video') {
$isVideo = true;
- $this->assertEquals("24/1", $adaptation['frameRate']);
+ $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.640014", $representation['codecs']);
+ $this->assertEquals("avc1.640015", $representation['codecs']);
$this->assertEquals("300", $representation['width']);
$this->assertEquals("200", $representation['height']);
$this->assertEquals("20:17", $representation['sar']);
@@ -643,7 +644,7 @@ class VideoCustomServerTest extends Scope
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());
+ $this->assertEquals(10, $representation->SegmentList->SegmentURL->count());
$audioSegmentBaseUrl = (string)$representation->BaseURL;
$audioSegmentInitialization = (string)$representation->SegmentList->Initialization['sourceURL'];
$audioSegmentId = (string)$representation->SegmentList->SegmentURL['media'];
@@ -682,7 +683,7 @@ class VideoCustomServerTest extends Scope
/**
- * @depends testProtocolWithSubs
+ * @depends testOutputWithSubs
*/
public function testDeleteVideo($videoId): string
{
@@ -701,7 +702,7 @@ class VideoCustomServerTest extends Scope
/**
* @depends testDeleteVideo
*/
- public function testProtocolWithSubsAgain($videoId): string
+ public function testOutputWithSubsAgain($videoId): string
{
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $videoId . '/outputs/hls', [
@@ -769,19 +770,20 @@ class VideoCustomServerTest extends Scope
/**
* @depends testTranscodeWithoutSubs
*/
- public function testProtocolWithoutSubs($videoId)
+ public function testOutputWithoutSubs($videoId): string
{
-
- sleep(50);
+ sleep(30);
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $videoId . '/outputs/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(2, count($match[0]));
+ $this->assertEquals(1, count($match[0]));
$renditionUri = $match[0][0];
$response = $this->client->call(Client::METHOD_GET, $renditionUri, [
@@ -791,7 +793,7 @@ class VideoCustomServerTest extends Scope
]);
$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]));
+ $this->assertEquals(10, count($match[0]));
$segmentUri = $match[0][0];
$response = $this->client->call(Client::METHOD_GET, $segmentUri, [
@@ -813,20 +815,20 @@ class VideoCustomServerTest extends Scope
$xml = simplexml_load_string($response['body']);
$this->assertEquals("PT1M32.7S", $xml->attributes()->mediaPresentationDuration);
- $this->assertEquals("PT8.0S", $xml->attributes()->maxSegmentDuration);
- $this->assertEquals("PT20.5S", $xml->attributes()->minBufferTime);
+ $this->assertEquals("PT10.0S", $xml->attributes()->maxSegmentDuration);
+ $this->assertEquals("PT20.0S", $xml->attributes()->minBufferTime);
$isVideo = false;
foreach ($xml->Period->AdaptationSet as $adaptation) {
if ((string)$adaptation['contentType'] === 'video') {
$isVideo = true;
- $this->assertEquals("24/1", $adaptation['frameRate']);
+ $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.640014", $representation['codecs']);
+ $this->assertEquals("avc1.640015", $representation['codecs']);
$this->assertEquals("300", $representation['width']);
$this->assertEquals("200", $representation['height']);
$this->assertEquals("20:17", $representation['sar']);
@@ -858,7 +860,7 @@ class VideoCustomServerTest extends Scope
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());
+ $this->assertEquals(10, $representation->SegmentList->SegmentURL->count());
$audioSegmentBaseUrl = (string)$representation->BaseURL;
$audioSegmentInitialization = (string)$representation->SegmentList->Initialization['sourceURL'];
$audioSegmentId = (string)$representation->SegmentList->SegmentURL['media'];
@@ -886,11 +888,46 @@ class VideoCustomServerTest extends Scope
$this->assertTrue($isVideo);
$this->assertTrue($isAudio);
+
+ return $videoId;
}
-
- public function testGetVideos()
+ /**
+ * @depends testTranscodeWithoutSubs
+ */
+ public function testDeleteRendition($videoId)
{
+ /**
+ * Delete one rendition
+ */
+ $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->assertNotEmpty($response['body']['renditions']);
+ $this->assertEquals(2, $response['body']['total']);
+
+ $response = $this->client->call(Client::METHOD_DELETE, '/videos/' . $videoId . '/renditions/' . $response['body']['renditions'][0]['$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 . '/renditions', [
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ 'x-appwrite-key' => $this->getProject()['apiKey'],
+ ]);
+
+ $this->assertNotEmpty($response['body']['renditions']);
+ $this->assertEquals(1, $response['body']['total']);
+
+ /**
+ * Get videos
+ */
$response = $this->client->call(Client::METHOD_GET, '/videos', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -899,9 +936,67 @@ class VideoCustomServerTest extends Scope
], [
'queries' => [ 'limit(10)' ]
]);
+ $this->assertEquals(200, $response['headers']['status-code']);
+ $this->assertEquals(1, $response['body']['total']);
+ $this->assertEquals(1, count($response['body']['videos']));
+
+ /**
+ * Delete the video
+ */
+ $response = $this->client->call(Client::METHOD_DELETE, '/videos/' . $videoId, [
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ 'x-appwrite-key' => $this->getProject()['apiKey'],
+ ]);
+
+ $this->assertEquals(204, $response['headers']['status-code']);
+
+ /**
+ * Get videos again
+ */
+ $response = $this->client->call(Client::METHOD_GET, '/videos', [
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ 'x-appwrite-key' => $this->getProject()['apiKey'],
+
+ ], [
+ 'queries' => [ 'limit(10)' ]
+ ]);
+ $this->assertEquals(200, $response['headers']['status-code']);
+ $this->assertEquals(0, $response['body']['total']);
+ $this->assertEquals(0, count($response['body']['videos']));
+
+ /**
+ * Delete all profiles (cleanup)
+ */
+ $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->assertGreaterThan(0, $response['body']['total']);
- $this->assertGreaterThan(0, count($response['body']['videos']));
+ $this->assertNotEmpty($response['body']);
+ $this->assertEquals(2, $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']);
}
}