diff --git a/Dockerfile b/Dockerfile index dd3020253c..0ec3324c38 100755 --- a/Dockerfile +++ b/Dockerfile @@ -185,7 +185,7 @@ RUN chmod +x /usr/local/bin/doctor && \ chmod +x /usr/local/bin/worker-builds && \ chmod +x /usr/local/bin/worker-mails && \ chmod +x /usr/local/bin/worker-messaging && \ - chmod +x /usr/local/bin/worker-transcoding && \ + chmod +x /usr/local/bin/worker-videos && \ chmod +x /usr/local/bin/worker-webhooks # Letsencrypt Permissions diff --git a/app/controllers/api/videos.php b/app/controllers/api/videos.php index c00967484d..7d09e18d1a 100644 --- a/app/controllers/api/videos.php +++ b/app/controllers/api/videos.php @@ -1,7 +1,7 @@ setAction('preview') ->setProject($project) ->setVideo($video) ->trigger(); - (new Transcoding()) + (new Video()) ->setAction('timeline') ->setProject($project) ->setVideo($video) @@ -360,7 +360,7 @@ App::post('/v1/videos/:videoId/preview') throw new Exception(Exception::VIDEO_SECOND_OUT_OF_RANGE); } - (new Transcoding()) + (new Video()) ->setAction('preview') ->setProject($project) ->setVideo($video) @@ -651,8 +651,7 @@ App::post('/v1/videos/:videoId/rendition') throw new Exception(Exception::VIDEO_PROFILE_NOT_FOUND); } - $transcoder = new Transcoding(); - $transcoder + (new Video()) ->setAction('encode') ->setProject($project) ->setVideo($video) @@ -843,7 +842,7 @@ App::get('/v1/videos/:videoId/outputs/:output') throw new Exception(Exception::VIDEO_RENDITION_NOT_FOUND); } - $baseUrl = 'http://127.0.0.1/v1/videos/' . $videoId . '/outputs/' . $output; + $baseUrl = TMP_HOST . 'v1/videos/' . $videoId . '/outputs/' . $output; $subtitles = Authorization::skip(fn() => $dbForProject->find('videos_subtitles', [ Query::equal('videoId', [$video->getId()]), ])); @@ -1019,7 +1018,7 @@ App::get('/v1/videos/:videoId/outputs/:output/renditions/:renditionId/streams/:s foreach ($segments as $segment) { $_segments[] = [ 'duration' => $segment->getAttribute('duration'), - 'url' => 'http://127.0.0.1/v1/videos/' . $videoId . '/outputs/' . $output . '/renditions/' . $renditionId . '/segments/' . $segment->getId(), + 'url' => TMP_HOST . 'v1/videos/' . $videoId . '/outputs/' . $output . '/renditions/' . $renditionId . '/segments/' . $segment->getId(), ]; } @@ -1115,7 +1114,7 @@ App::get('/v1/videos/:videoId/outputs/:output/subtitles/:subtitleId') foreach ($segments as $segment) { $_segments[] = [ 'duration' => $segment->getAttribute('duration'), - 'url' => 'http://127.0.0.1/v1/videos/' . $videoId . '/outputs/' . $output . '/subtitles/' . $subtitleId . '/segments/' . $segment->getId(), + 'url' => TMP_HOST . 'v1/videos/' . $videoId . '/outputs/' . $output . '/subtitles/' . $subtitleId . '/segments/' . $segment->getId(), ]; } diff --git a/app/init.php b/app/init.php index eebca4c04a..4338ecd813 100644 --- a/app/init.php +++ b/app/init.php @@ -176,7 +176,7 @@ const APP_AUTH_TYPE_KEY = 'Key'; const APP_AUTH_TYPE_ADMIN = 'Admin'; // Response related const MAX_OUTPUT_CHUNK_SIZE = 2 * 1024 * 1024; // 2MB - +const TMP_HOST = 'http://127.0.0.1/'; $register = new Registry(); App::setMode(App::getEnv('_APP_ENV', App::MODE_TYPE_PRODUCTION)); diff --git a/app/workers/transcoding.php b/app/workers/videos.php similarity index 98% rename from app/workers/transcoding.php rename to app/workers/videos.php index 38ce0783aa..542028c451 100644 --- a/app/workers/transcoding.php +++ b/app/workers/videos.php @@ -25,10 +25,10 @@ use Utopia\Storage\Device\Local; require_once __DIR__ . '/../init.php'; -Console::title('Transcoding V1 Worker'); -Console::success(APP_NAME . ' transcoding worker v1 has started'); +Console::title('Video V1 Worker'); +Console::success(APP_NAME . ' video worker v1 has started'); -class TranscodingV1 extends Worker +class VideosV1 extends Worker { /** * Rendition Status @@ -73,7 +73,7 @@ class TranscodingV1 extends Worker public function getName(): string { - return "Transcoding v1"; + return "Video v1"; } public function init(): void @@ -255,7 +255,9 @@ class TranscodingV1 extends Worker ]; $result = shell_exec(implode(" ", $cmd)); - /** Vtt creation**/ + /** + * Vtt creation* + */ if ($result !== false) { $size = explode('x', $timeline['size']); $counter = 0; @@ -270,7 +272,7 @@ class TranscodingV1 extends Worker 'path' => $path, ])); - $url = 'http://127.0.0.1/v1/videos/' . $this->video->getId() . '/preview/' . $sprite->getId(); + $url = TMP_HOST . 'v1/videos/' . $this->video->getId() . '/preview/' . $sprite->getId() . '/'; for ($col = 0; $col < $size[0]; $col++) { for ($row = 0; $row < $size[1]; $row++) { $data .= "\n" . gmdate("H:i:s", $counter * $interval) . " --> " . gmdate("H:i:s", ($counter + 1) * $interval) . "\n" . $url . "#xywh=" . ($row * $timeline['width']) . "," . ($col * $timeline['height']) . "," . $timeline['width'] . "," . $timeline['height']; diff --git a/bin/worker-transcoding b/bin/worker-videos similarity index 56% rename from bin/worker-transcoding rename to bin/worker-videos index 71d2674309..74a45e66ba 100644 --- a/bin/worker-transcoding +++ b/bin/worker-videos @@ -7,4 +7,4 @@ else REDIS_BACKEND="redis://${_APP_REDIS_USER}:${_APP_REDIS_PASS}@${_APP_REDIS_HOST}:${_APP_REDIS_PORT}" fi -INTERVAL=0.1 QUEUE='v1-transcoding' APP_INCLUDE='/usr/src/code/app/workers/transcoding.php' php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php \ No newline at end of file +INTERVAL=0.1 QUEUE='v1-videos' APP_INCLUDE='/usr/src/code/app/workers/videos.php' php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 083a5f76f4..0d35390d1e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -228,10 +228,10 @@ services: - _APP_LOGGING_PROVIDER - _APP_LOGGING_CONFIG - appwrite-worker-transcoding: - entrypoint: worker-transcoding + appwrite-worker-videos: + entrypoint: worker-videos <<: *x-logging - container_name: appwrite-worker-transcoding + container_name: appwrite-worker-videos build: context: . networks: diff --git a/src/Appwrite/Event/Event.php b/src/Appwrite/Event/Event.php index a6f9420ae2..ef8aa7b831 100644 --- a/src/Appwrite/Event/Event.php +++ b/src/Appwrite/Event/Event.php @@ -32,8 +32,8 @@ class Event public const BUILDS_QUEUE_NAME = 'v1-builds'; public const BUILDS_CLASS_NAME = 'BuildsV1'; - public const TRANSCODING_QUEUE_NAME = 'v1-transcoding'; - public const TRANSCODING_CLASS_NAME = 'TranscodingV1'; + public const VIDEO_QUEUE_NAME = 'v1-videos'; + public const VIDEO_CLASS_NAME = 'VideosV1'; public const MESSAGING_QUEUE_NAME = 'v1-messaging'; public const MESSAGING_CLASS_NAME = 'MessagingV1'; diff --git a/src/Appwrite/Event/Transcoding.php b/src/Appwrite/Event/Video.php similarity index 96% rename from src/Appwrite/Event/Transcoding.php rename to src/Appwrite/Event/Video.php index 6c34ab7ee8..2176243459 100644 --- a/src/Appwrite/Event/Transcoding.php +++ b/src/Appwrite/Event/Video.php @@ -5,7 +5,7 @@ namespace Appwrite\Event; use Resque; use Utopia\Database\Document; -class Transcoding extends Event +class Video extends Event { protected ?Document $video = null; protected ?Document $profile = null; @@ -15,7 +15,7 @@ class Transcoding extends Event public function __construct() { - parent::__construct(Event::TRANSCODING_QUEUE_NAME, Event::TRANSCODING_CLASS_NAME); + parent::__construct(Event::VIDEO_QUEUE_NAME, Event::VIDEO_CLASS_NAME); } /** diff --git a/tests/e2e/Services/Videos/VideosCustomClientTest.php b/tests/e2e/Services/Videos/VideosCustomClientTest.php index 120a988ed8..71ed8e7836 100644 --- a/tests/e2e/Services/Videos/VideosCustomClientTest.php +++ b/tests/e2e/Services/Videos/VideosCustomClientTest.php @@ -96,7 +96,7 @@ class VideosCustomClientTest extends Scope 'bucketId' => $this->getBucket()['$id'], 'fileId' => $this->getSubtitle()['$id'], 'name' => 'English', - 'code' => 'eng', + 'code' => 'Eng', 'default' => true, ]); @@ -117,6 +117,7 @@ class VideosCustomClientTest extends Scope $this->assertEquals(404, $response['headers']['status-code']); + $response = $this->client->call(Client::METHOD_POST, '/videos/' . $videoId . '/subtitles', [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -251,8 +252,8 @@ class VideosCustomClientTest extends Scope $subsCount = 0; $isVideo = false; $isAudio = false; - $subs[] = ['id' => '2', 'lang' => 'eng',]; - $subs[] = ['id' => '3', 'lang' => 'it',]; + $subs[] = ['id' => '2', 'lang' => 'Eng',]; + $subs[] = ['id' => '3', 'lang' => 'It',]; foreach ($xml->Period->AdaptationSet as $adaptation) { if ((string)$adaptation['contentType'] === 'video') { $isVideo = true; diff --git a/tests/e2e/Services/Videos/VideosCustomServerTest.php b/tests/e2e/Services/Videos/VideosCustomServerTest.php index a84d294343..695354c1c3 100644 --- a/tests/e2e/Services/Videos/VideosCustomServerTest.php +++ b/tests/e2e/Services/Videos/VideosCustomServerTest.php @@ -253,6 +253,7 @@ class VideosCustomServerTest extends Scope $this->assertNotEmpty($response['body']); $this->assertNotEmpty($response['body']['$id']); $this->assertEquals(92810, $response['body']['duration']); + /** * timeline vtt */ @@ -263,7 +264,33 @@ class VideosCustomServerTest extends Scope ]); $this->assertEquals(200, $response['headers']['status-code']); $this->assertNotEmpty($response['body']); - $this->assertEquals(2956, strlen($response['body'])); + preg_match_all('#\b/videos[^,\s()<>]+(?:\([\w\d]+\)|([^,[:punct:]\s]|/))#', $response['body'], $match); + $this->assertEquals(25, count($match[0])); + + /** + * timeline preview image + */ + $response = $this->client->call(Client::METHOD_GET, '/v1' . $match[0][0], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + $this->assertEquals('image/jpeg', $response['headers']['content-type']); + $this->assertEquals('miss', $response['headers']['x-appwrite-cache']); + + sleep(3); + + /** + * timeline preview image (response from cache) + */ + $response = $this->client->call(Client::METHOD_GET, '/v1' . $match[0][0], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + $this->assertEquals('image/jpeg', $response['headers']['content-type']); + $this->assertEquals('hit', $response['headers']['x-appwrite-cache']); + $response = $this->client->call(Client::METHOD_GET, '/v1/videos/' . $videoId, [ 'content-type' => 'application/json', @@ -333,7 +360,7 @@ class VideosCustomServerTest extends Scope 'bucketId' => $this->getBucket()['$id'], 'fileId' => $this->getSubtitle()['$id'], 'name' => 'English', - 'code' => 'Eng', + 'code' => 'eng', 'default' => true, ]); @@ -495,7 +522,7 @@ class VideosCustomServerTest extends Scope 'bucketId' => $this->getBucket()['$id'], 'fileId' => $this->getSubtitle()['$id'], 'name' => 'English', - 'code' => 'Eng', + 'code' => 'eng', 'default' => true, ]);