view template

This commit is contained in:
shimon
2022-06-21 11:45:49 +03:00
parent 69c6798aa9
commit 100fb73323
15 changed files with 2228 additions and 163 deletions
+6 -1
View File
@@ -50,7 +50,8 @@ RUN \
yaml-dev \
imagemagick \
imagemagick-dev \
libmaxminddb-dev
libmaxminddb-dev \
ffmpeg
RUN docker-php-ext-install sockets
@@ -234,6 +235,7 @@ RUN \
docker-cli \
docker-compose \
libgomp \
ffmpeg \
&& docker-php-ext-install sockets opcache pdo_mysql \
&& apk del .deps \
&& rm -rf /var/cache/apk/*
@@ -264,12 +266,14 @@ COPY ./src /usr/src/code/src
# Set Volumes
RUN mkdir -p /storage/uploads && \
mkdir -p /storage/video && \
mkdir -p /storage/cache && \
mkdir -p /storage/config && \
mkdir -p /storage/certificates && \
mkdir -p /storage/functions && \
mkdir -p /storage/debug && \
chown -Rf www-data.www-data /storage/uploads && chmod -Rf 0755 /storage/uploads && \
chown -Rf www-data.www-data /storage/video && chmod -Rf 0755 /storage/video && \
chown -Rf www-data.www-data /storage/cache && chmod -Rf 0755 /storage/cache && \
chown -Rf www-data.www-data /storage/config && chmod -Rf 0755 /storage/config && \
chown -Rf www-data.www-data /storage/certificates && chmod -Rf 0755 /storage/certificates && \
@@ -297,6 +301,7 @@ RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/worker-functions && \
chmod +x /usr/local/bin/worker-builds && \
chmod +x /usr/local/bin/worker-mails && \
chmod +x /usr/local/bin/worker-transcoding && \
chmod +x /usr/local/bin/worker-webhooks
# Letsencrypt Permissions
+126
View File
@@ -2845,6 +2845,132 @@ $collections = [
],
]
],
'videos' => [
'$collection' => 'video_renditions',
'$id' => 'video_renditions',
'$name' => 'Video_renditions',
'attributes' => [
[
'$id' => 'bucketId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'fileId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'renditionId',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => 0,
'array' => false,
'filters' => [],
],
[
'$id' => 'renditionName',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 2048,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'startedAt',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'endedAt',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'metadata',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384, // https://tools.ietf.org/html/rfc4288#section-4.2
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['json'],
],
[
'$id' => 'status',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 100,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'progress',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 4,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'stream',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 255,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => '_key_bucket_file',
'type' => Database::INDEX_KEY,
'attributes' => ['bucketId','fileId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
]
],
];
return $collections;
+29
View File
@@ -0,0 +1,29 @@
<?php
return [
[
'id' => 1,
'name' => '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
],
[
'id' => 2,
'name' => '576p',
'videoBitrate' => 2538,
'audioBitrate' => 128,
'width' => 1024,
'height' => 576,
],
[
'id' => 3,
'name' => '720p',
'videoBitrate' => 3551,
'audioBitrate' => 128,
'width' => 1280,
'height' => 720,
],
];
+271
View File
@@ -0,0 +1,271 @@
<?php
use Appwrite\Auth\Auth;
use Appwrite\ClamAV\Network;
use Appwrite\Event\Audit;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Event\Transcoding;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\OpenSSL\OpenSSL;
use Appwrite\Stats\Stats;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\View;
use Utopia\App;
use Utopia\Cache\Adapter\Filesystem;
use Utopia\Cache\Cache;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate;
use Utopia\Database\Exception\Duplicate as DuplicateException;
use Utopia\Database\Exception\Structure as StructureException;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Permissions;
use Utopia\Database\Validator\UID;
use Appwrite\Extend\Exception;
use Utopia\Image\Image;
use Utopia\Storage\Compression\Algorithms\GZIP;
use Utopia\Storage\Device;
use Utopia\Storage\Device\Local;
use Utopia\Storage\Storage;
use Utopia\Storage\Validator\File;
use Utopia\Storage\Validator\FileExt;
use Utopia\Storage\Validator\FileSize;
use Utopia\Storage\Validator\Upload;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
use Utopia\Validator\HexColor;
use Utopia\Validator\Integer;
use Utopia\Validator\Range;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\Swoole\Request;
use Streaming\Representation;
App::post('/v1/video/buckets/:bucketId/files/:fileId')
->alias('/v1/video/files', ['bucketId' => 'default'])
->desc('Start transcoding video')
->groups(['api', 'storage'])
->label('scope', 'files.write')
// ->label('event', 'buckets.[bucketId].files.[fileId].create')
->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('fileId', '', new CustomId(), 'File ID. Choose your own unique ID or pass the string "unique()" to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('read', null, new Permissions(), 'An array of strings with read permissions. By default only the current user is granted with read permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
->param('write', null, new Permissions(), 'An array of strings with write permissions. By default only the current user is granted with write permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.', true)
->inject('request')
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('user')
->inject('audits')
->inject('usage')
->inject('events')
->inject('mode')
->inject('deviceFiles')
->inject('deviceLocal')
->action(action: function (string $bucketId, string $fileId, ?array $read, ?array $write, Request $request, Response $response, Database $dbForProject, $project, Document $user, Audit $audits, Stats $usage, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal) {
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Document $user */
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
if (
$bucket->isEmpty()
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)
) {
throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND);
}
// Check bucket permissions when enforced
$permissionBucket = $bucket->getAttribute('permission') === 'bucket';
if ($permissionBucket) {
$validator = new Authorization('write');
if (!$validator->isValid($bucket->getWrite())) {
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
}
}
$read = (is_null($read) && !$user->isEmpty()) ? ['user:' . $user->getId()] : $read ?? []; // By default set read permissions for user
$write = (is_null($write) && !$user->isEmpty()) ? ['user:' . $user->getId()] : $write ?? [];
// Users can only add their roles to files, API keys and Admin users can add any
$roles = Authorization::getRoles();
if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles)) {
foreach ($read as $role) {
if (!Authorization::isRole($role)) {
throw new Exception('Read permissions must be one of: (' . \implode(', ', $roles) . ')', 400, Exception::USER_UNAUTHORIZED);
}
}
foreach ($write as $role) {
if (!Authorization::isRole($role)) {
throw new Exception('Write permissions must be one of: (' . \implode(', ', $roles) . ')', 400, Exception::USER_UNAUTHORIZED);
}
}
}
$transcoder = new Transcoding();
$transcoder
->setUser($user)
->setProject($project)
->setBucketId($bucketId)
->setFileId($fileId)
->trigger();
$response->json(['result' => 'ok']);
});
App::get('/v1/video/buckets/:bucketId/files/:fileId/renditions')
->alias('/v1/storage/files/:fileId/renditions', ['bucketId' => 'default'])
->desc('Get File renditions')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'getFile')
->label('sdk.description', '/docs/references/storage/get-file.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FILE)
->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('fileId', '', new UID(), 'File ID.')
->inject('response')
->inject('dbForProject')
->inject('usage')
->inject('mode')
->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Stats $usage, string $mode) {
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
if (
$bucket->isEmpty()
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)
) {
throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND);
}
// Check bucket permissions when enforced
if ($bucket->getAttribute('permission') === 'bucket') {
$validator = new Authorization('read');
if (!$validator->isValid($bucket->getRead())) {
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
}
}
if ($bucket->getAttribute('permission') === 'bucket') {
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
} else {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
}
if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) {
throw new Exception('File not found', 404, Exception::STORAGE_FILE_NOT_FOUND);
}
$queries = [
new Query('bucketId', Query::TYPE_EQUAL, [$bucketId]),
new Query('fileId', Query::TYPE_EQUAL, [$fileId]),
new Query('stream', Query::TYPE_EQUAL, ['dash']),
];
$renditions = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId() . '_video_renditions', $queries, 12, 0, [], ['ASC']));
$response->dynamic(new Document([
'total' => $dbForProject->count('bucket_' . $bucket->getInternalId() . '_video_renditions', $queries, APP_LIMIT_COUNT),
'renditions' => $renditions,
]), Response::MODEL_FILE_RENDITIONS_LIST);
});
App::get('/v1/video/buckets/:bucketId/files/:stream/:fileId')
->alias('/v1/video/buckets/:bucketId/files/:stream/:fileId', [])
->desc('Get video playlist manifest')
->groups(['api', 'storage'])
->label('scope', 'files.read')
->param('bucketId', null, new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).')
->param('stream', '', new WhiteList(['hls', 'dash']), 'stream protocol name')
->param('fileId', '', new UID(), 'File ID.')
->inject('response')
->inject('dbForProject')
->inject('usage')
->inject('mode')
->action(function (string $bucketId, string $stream, string $fileId, Response $response, Database $dbForProject, Stats $usage, string $mode) {
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
if (
$bucket->isEmpty()
|| (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)
) {
throw new Exception('Bucket not found', 404, Exception::STORAGE_BUCKET_NOT_FOUND);
}
// Check bucket permissions when enforced
if ($bucket->getAttribute('permission') === 'bucket') {
$validator = new Authorization('read');
if (!$validator->isValid($bucket->getRead())) {
throw new Exception('Unauthorized permissions', 401, Exception::USER_UNAUTHORIZED);
}
}
if ($bucket->getAttribute('permission') === 'bucket') {
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
} else {
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
}
if ($file->isEmpty() || $file->getAttribute('bucketId') !== $bucketId) {
throw new Exception('File not found', 404, Exception::STORAGE_FILE_NOT_FOUND);
}
$queries = [
new Query('bucketId', Query::TYPE_EQUAL, [$bucketId]),
new Query('fileId', Query::TYPE_EQUAL, [$fileId]),
new Query('stream', Query::TYPE_EQUAL, [$stream]),
new Query('endedAt', Query::TYPE_GREATER, [0]),
new Query('status', Query::TYPE_EQUAL, ['ready'])
];
$renditions = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId() . '_video_renditions', $queries, 12, 0, [], ['ASC']));
if (empty($renditions)) {
throw new Exception('Renditions not found');
}
if ($stream === 'hls') {
foreach ($renditions as $rendition) {
$metadata = $rendition->getAttribute('metadata');
$fileId = $rendition->getAttribute('fileId');
$t['bandwidth'] = (($metadata['general']['video']['bitrate'] + $metadata['general']['audio']['bitrate']) * 1024);
$t['resolution'] = $metadata['general']['resolution'];
$t['name'] = $rendition->getAttribute('renditionName');
$t['path'] = $rendition->getAttribute('renditionName') . DIRECTORY_SEPARATOR . $fileId . '_' . $rendition->getAttribute('renditionName') . '.m3u8';
$params[] = $t;
}
$template = new View(__DIR__ . '/../../views/video/hls.phtml');
$template->setParam('params', $params);
$response->setContentType('application/x-mpegurl');
$response->send($template->render());
} else {
$adaptations = [];
foreach ($renditions as $rendition) {
$metadata = $rendition->getAttribute('metadata');
foreach ($metadata['dash']['Period']['AdaptationSet'] as $set) {
$adaption = $set['@attributes'];
$adaption['baseUrl'] = $rendition->getAttribute('renditionName') . DIRECTORY_SEPARATOR;
$adaption['representation'] = $set['Representation']['@attributes'];
$adaption['representation']['SegmentTemplate'] = $set['Representation']['SegmentTemplate']['@attributes'];
$adaption['representation']['segmentTemplate']['segmentTimeline'] = $set['Representation']['SegmentTemplate']['SegmentTimeline'];
$adaptations[] = $adaption;
}
}
$template = new View(__DIR__ . '/../../views/video/dash.phtml');
$template->setParam('params', $adaptations);
$response->setContentType('application/dash+xml');
$response->send($template->render());
}
});
+55
View File
@@ -0,0 +1,55 @@
<?php
$params = $this->getParam('params', []);
?>
<?php echo '<?xml version="1.0" encoding="utf-8"?>'; ?>
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xlink="http://www.w3.org/1999/xlink"
xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd"
profiles="urn:mpeg:dash:profile:isoff-live:2011" type="static" mediaPresentationDuration="PT13.2S" maxSegmentDuration="PT6.0S" minBufferTime="PT16.6S">
<ProgramInformation></ProgramInformation>
<ServiceDescription id="0"></ServiceDescription>
<Period id="0" start="PT0.0S">
<?php foreach ($this->getParam('params', []) as $param): ?>
<AdaptationSet
<?php if (isset($param['id'])):?> id="<?php echo $param['id']; ?>" <?php endif;?>
<?php if (isset($param['contentType'])): ?> contentType="<?php echo $param['contentType']; ?>" <?php endif;?>
<?php if (isset($param['startWithSAP'])): ?> startWithSAP="<?php echo $param['startWithSAP']; ?>" <?php endif;?>
<?php if (isset($param['segmentAlignment'])): ?> segmentAlignment="<?php echo $param['segmentAlignment']; ?>" <?php endif;?>
<?php if (isset($param['bitstreamSwitching'])): ?> bitstreamSwitching="<?php echo $param['bitstreamSwitching']; ?>" <?php endif;?>
<?php if (isset($param['frameRate'])): ?> frameRate="<?php echo $param['frameRate']; ?>" <?php endif;?>
<?php if (isset($param['maxWidth'])): ?> maxWidth="<?php echo $param['maxWidth']; ?>" <?php endif;?>
<?php if (isset($param['par'])): ?> par="<?php echo $param['par']; ?>" <?php endif;?>
<?php if (isset($param['lang'])): ?> lang="<?php echo $param['lang']; ?>" <?php endif;?>
>
<BaseURL><?php if (isset($param['baseUrl'])): echo $param['baseUrl']; endif;?></BaseURL>
<Representation
<?php if (isset($param['representation']['id'])):?> id="<?php echo $param['representation']['id']; ?>" <?php endif;?>
<?php if (isset($param['representation']['mimeType'])):?> mimeType="<?php echo $param['representation']['mimeType']; ?>" <?php endif;?>
<?php if (isset($param['representation']['codecs'])):?> codecs="<?php echo $param['representation']['codecs']; ?>" <?php endif;?>
<?php if (isset($param['representation']['width'])):?> width="<?php echo $param['representation']['width']; ?>" <?php endif;?>
<?php if (isset($param['representation']['height'])):?> height="<?php echo $param['representation']['height']; ?>" <?php endif;?>
<?php if (isset($param['representation']['sar'])):?> sar="<?php echo $param['representation']['sar']; ?>" <?php endif;?>
<?php if (isset($param['representation']['audioSamplingRate'])):?> audioSamplingRate="<?php echo $param['representation']['audioSamplingRate']; ?>" <?php endif;?>
>
<?php if ($param['contentType'] === 'audio'):?>
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2" />
<?php endif;?>
<SegmentTemplate
<?php if (isset($param['representation']['segmentTemplate']['timescale'] )):?> timescale="<?php echo ['representation']['segmentTemplate']['timescale'] ; ?>" <?php endif;?>
<?php if (isset($param['representation']['segmentTemplate']['initialization'] )):?> initialization="<?php echo ['representation']['segmentTemplate']['initialization'] ; ?>" <?php endif;?>
<?php if (isset($param['representation']['segmentTemplate']['media'] )):?> media="<?php echo ['representation']['segmentTemplate']['media'] ; ?>" <?php endif;?>
<?php if (isset($param['representation']['segmentTemplate']['startNumber'] )):?> startNumber="<?php echo ['representation']['segmentTemplate']['startNumber'] ; ?>" <?php endif;?>
>
<SegmentTimeline>
<?php foreach ($param['representation']['segmentTemplate']['segmentTimeline']['S'] as $s): ?>
<S
<?php if (isset($s['@attributes']['t'])):?> t="<?php echo $s['@attributes']['t']; ?> " <?php endif;?>
<?php if (isset($s['@attributes']['d'])):?> d="<?php echo $s['@attributes']['d']; ?>" <?php endif;?>
/>
<?php endforeach; ?>
</SegmentTimeline>
</SegmentTemplate>
</Representation>
</AdaptationSet>
<?php endforeach; ?>
</Period>
</MPD>
+7
View File
@@ -0,0 +1,7 @@
#EXTM3U
#EXT-X-VERSION:3
<?php foreach ($this->getParam('params', []) as $param): ?>
#EXT-X-STREAM-INF:BANDWIDTH="<?php echo $param['bandwidth']; ?>" ,RESOLUTION="<?php echo $param['resolution']; ?>" ,NAME="<?php echo $param['name']; ?>"<?php echo PHP_EOL?>
<?php echo $param['path'] ?>
<?php endforeach; ?>
}
+334
View File
@@ -0,0 +1,334 @@
<?php
use Appwrite\Extend\Exception;
use Appwrite\OpenSSL\OpenSSL;
use Appwrite\Resque\Worker;
use Streaming\Format\StreamFormat;
use Streaming\Media;
use Streaming\Representation;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use FFMpeg\FFProbe\DataMapping\StreamCollection;
use Utopia\Storage\Compression\Algorithms\GZIP;
require_once __DIR__ . '/../init.php';
Console::title('Transcoding V1 Worker');
Console::success(APP_NAME . ' transcoding worker v1 has started');
class TranscodingV1 extends Worker
{
const HLS_BASE_URL = '';
protected string $basePath = '/tmp/';
protected string $inDir;
protected string $outDir;
protected string $outPath;
protected Database $database;
public function getName(): string
{
return "Transcoding";
}
public function init(): void
{
$this->basePath .= $this->args['fileId'];
$this->inDir = $this->basePath . '/in/';
$this->outDir = $this->basePath . '/out/';
@mkdir($this->inDir, 0755, true);
@mkdir($this->outDir, 0755, true);
$this->outPath = $this->outDir . $this->args['fileId']; /** TODO figure a way to write dir tree without this **/
}
public function run(): void
{
$project = new Document($this->args['project']);
$user = new Document($this->args['user'] ?? []);
$this->database = $this->getProjectDB($project->getId());
$bucket = Authorization::skip(fn() => $this->database->getDocument('buckets', $this->args['bucketId']));
if ($bucket->getAttribute('permission') === 'bucket') {
$file = Authorization::skip(fn() => $this->database->getDocument('bucket_' . $bucket->getInternalId(), $this->args['fileId']));
} else {
$file = $this->database->getDocument('bucket_' . $bucket->getInternalId(), $this->args['fileId']);
}
$data = $this->getFilesDevice($project->getId())->read($file->getAttribute('path'));
$fileName = basename($file->getAttribute('path'));
$inPath = $this->inDir . $fileName;
$collection = 'bucket_' . $bucket->getInternalId() . '_video_renditions';
if (!empty($file->getAttribute('openSSLCipher'))) { // Decrypt
$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'));
$ffprobe = FFMpeg\FFProbe::create([]);
$ffmpeg = Streaming\FFMpeg::create([]);
if (!$ffprobe->isValid($inPath)) {
throw new Exception('Not an valid FFMpeg file "' . $inPath . '"');
}
//TODO Can you retranscode?
$queries = [
new Query('bucketId', Query::TYPE_EQUAL, [$this->args['bucketId']]),
new Query('fileId', Query::TYPE_EQUAL, [$this->args['fileId']])
];
$renditions = Authorization::skip(fn() => $this->database->find($collection, $queries, 12, 0, [], ['ASC']));
if (!empty($renditions)) {
foreach ($renditions as $rendition) {
Authorization::skip(fn() => $this->database->deleteDocument($collection, $rendition->getId()));
}
$deviceFiles = $this->getVideoDevice($project->getId());
$devicePath = $deviceFiles->getPath($this->args['fileId']);
$devicePath = str_ireplace($deviceFiles->getRoot(), $deviceFiles->getRoot() . DIRECTORY_SEPARATOR . $bucket->getId(), $devicePath);
$deviceFiles->deletePath($devicePath);
}
$sourceInfo = $this->getVideoInfo($ffprobe->streams($inPath));
$video = $ffmpeg->open($inPath);
foreach (Config::getParam('renditions', []) as $rendition) {
foreach (['hls', 'dash'] as $stream) {
$query = Authorization::skip(function () use ($collection, $rendition, $stream) {
return $this->database->createDocument($collection, new Document([
'bucketId' => $this->args['bucketId'],
'fileId' => $this->args['fileId'],
'renditionId' => $rendition['id'],
'renditionName' => $rendition['name'],
'startedAt' => time(),
'status' => 'started',
'stream' => $stream,
]));
});
try {
$representation = (new Representation())->
setKiloBitrate($rendition['videoBitrate'])->
setAudioKiloBitrate($rendition['audioBitrate'])->
setResize($rendition['width'], $rendition['height']);
$format = new Streaming\Format\X264();
$format->on('progress', function ($video, $format, $percentage) use ($query, $collection) {
if ($percentage % 3 === 0) {
$query->setAttribute('progress', (string)$percentage);
Authorization::skip(fn() => $this->database->updateDocument(
$collection,
$query->getId(),
$query
));
}
});
$metadata = $this->transcode($stream, $video, $format, $representation);
if (!empty($metadata)) {
$query->setAttribute('metadata', json_encode($metadata));
}
$query->setAttribute('status', 'ended');
$query->setAttribute('endedAt', time());
Authorization::skip(fn() => $this->database->updateDocument(
$collection,
$query->getId(),
$query
));
/** Upload & remove files **/
$start = 0;
$fileNames = scandir($this->outDir);
foreach ($fileNames as $fileName) {
if (
$fileName === '.' ||
$fileName === '..' ||
str_contains($fileName, '.json')
) {
continue;
}
$deviceFiles = $this->getVideoDevice($project->getId());
$devicePath = $deviceFiles->getPath($this->args['fileId']);
$devicePath = str_ireplace($deviceFiles->getRoot(), $deviceFiles->getRoot() . DIRECTORY_SEPARATOR . $bucket->getId(), $devicePath);
$data = $this->getFilesDevice($project->getId())->read($this->outDir . $fileName);
$this->getVideoDevice($project->getId())->write($devicePath . DIRECTORY_SEPARATOR . $rendition['name'] . DIRECTORY_SEPARATOR . $fileName, $data, \mime_content_type($this->outDir . $fileName));
if ($start === 0) {
$query->setAttribute('status', 'uploading');
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', 'ready');
Authorization::skip(fn() => $this->database->updateDocument(
$collection,
$query->getId(),
$query
));
} catch (\Throwable $th) {
$query->setAttribute('metadata', json_encode([
'code' => $th->getCode(),
'message' => $th->getMessage(),
]));
$query->setAttribute('status', 'error');
Authorization::skip(fn() => $this->database->updateDocument(
$collection,
$query->getId(),
$query
));
}
}
}
}
/**
* @param $metadata array
* @return array
*/
private function getMetadataExport(array $metadata): array
{
$info = [];
if (!empty($metadata['stream']['resolutions'][0])) {
$general = $metadata['stream']['resolutions'][0];
$info['resolution'] = $general['dimension'];
}
if (!empty($metadata['video']['streams'])) {
foreach ($metadata['video']['streams'] as $streams) {
if ($streams['codec_type'] === 'video') {
$info['duration'] = $streams['duration'];
$info['video']['codec'] = $streams['codec_name'] . ',' . $streams['codec_tag_string'];
$info['video']['bitRate'] = $streams['bit_rate'];
$info['video']['frameRate'] = $streams['avg_frame_rate'];
} elseif ($streams['codec_type'] === 'audio') {
$info['audio']['codec'] = $streams['codec_name'] . ',' . $streams['codec_tag_string'];
$info['audio']['bitRate'] = $streams['sample_rate'];
$info['audio']['samplRate'] = $streams['bit_rate'];
}
}
}
return $info;
}
/**
* @param string $stream
* @param $video Media
* @param $format StreamFormat
* @param $representation Representation
* @return array
*/
private function transcode(string $stream, Media $video, StreamFormat $format, Representation $representation): string | array
{
$additionalParams = [
'-sn',
'-vf', 'scale=iw:-2:force_original_aspect_ratio=increase,setsar=1:1'
];
$segementSize = 6;
if ($stream === 'dash') {
$dash = $video->dash()
->setFormat($format)
->setSegDuration($segementSize)
->addRepresentation($representation)
->setAdditionalParams($additionalParams)
->save($this->outPath);
$xml = simplexml_load_string(
file_get_contents($this->outDir . $this->args['fileId'] . '.mpd')
);
return [
'general' => $this->getMetadataExport($dash->metadata()->export()),
'dash' => !empty($xml) ? json_decode(json_encode((array)$xml), true) : [],
];
}
$hls = $video->hls()
->setFormat($format)
->setHlsTime($segementSize)
->addRepresentation($representation)
->setAdditionalParams($additionalParams)
->setHlsBaseUrl(self::HLS_BASE_URL)
->save($this->outPath);
return ['general' => $this->getMetadataExport($hls->metadata()->export())];
}
/**
* @param $streams StreamCollection
* @return array
*/
private function getVideoInfo(StreamCollection $streams): array
{
return [
'duration' => $streams->videos()->first()->get('duration'),
'height' => $streams->videos()->first()->get('height'),
'width' => $streams->videos()->first()->get('width'),
'frameRate' => $streams->videos()->first()->get('r_frame_rate'),
'bitrateKb' => $streams->videos()->first()->get('bit_rate') / 1000,
'bitrateMb' => $streams->videos()->first()->get('bit_rate') / 1000 / 1000,
];
}
private function cleanup(): bool
{
return \exec("rm -rf {$this->basePath}");
}
public function shutdown(): void
{
$this->cleanup();
}
}
+10
View File
@@ -0,0 +1,10 @@
#!/bin/sh
if [ -z "$_APP_REDIS_USER" ] && [ -z "$_APP_REDIS_PASS" ]
then
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
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
+4 -2
View File
@@ -51,7 +51,7 @@
"utopia-php/preloader": "0.2.*",
"utopia-php/domains": "1.1.*",
"utopia-php/swoole": "0.3.*",
"utopia-php/storage": "0.9.*",
"utopia-php/storage": "dev-main",
"utopia-php/websocket": "0.1.0",
"utopia-php/image": "0.5.*",
"utopia-php/orchestration": "0.4.*",
@@ -63,7 +63,9 @@
"chillerlan/php-qrcode": "4.3.3",
"adhocore/jwt": "1.1.2",
"slickdeals/statsd": "3.1.0",
"squizlabs/php_codesniffer": "^3.6"
"aminyazdanpanah/php-ffmpeg-video-streaming": "^1.2",
"squizlabs/php_codesniffer": "^3.6",
"ext-simplexml": "*"
},
"repositories": [
{
Generated
+1009 -116
View File
File diff suppressed because it is too large Load Diff
+68 -44
View File
@@ -7,38 +7,14 @@ x-logging: &x-logging
logging:
driver: 'json-file'
options:
max-file: '5'
max-size: '10m'
x-env-storage: &x-env-storage |-
_APP_STORAGE_DEVICE
_APP_STORAGE_S3_ACCESS_KEY
_APP_STORAGE_S3_SECRET
_APP_STORAGE_S3_REGION
_APP_STORAGE_S3_BUCKET
_APP_STORAGE_DO_SPACES_ACCESS_KEY
_APP_STORAGE_DO_SPACES_SECRET
_APP_STORAGE_DO_SPACES_REGION
_APP_STORAGE_DO_SPACES_BUCKET
_APP_STORAGE_BACKBLAZE_ACCESS_KEY
_APP_STORAGE_BACKBLAZE_SECRET
_APP_STORAGE_BACKBLAZE_REGION
_APP_STORAGE_BACKBLAZE_BUCKET
_APP_STORAGE_DO_SPACES_BUCKET
_APP_STORAGE_LINODE_ACCESS_KEY
_APP_STORAGE_LINODE_SECRET
_APP_STORAGE_LINODE_REGION
_APP_STORAGE_LINODE_BUCKET
_APP_STORAGE_WASABI_ACCESS_KEY
_APP_STORAGE_WASABI_SECRET
_APP_STORAGE_WASABI_REGION
_APP_STORAGE_WASABI_BUCKET
max-file: "5"
max-size: 10m
version: '3'
services:
traefik:
image: traefik:2.7
image: traefik:2.5
<<: *x-logging
container_name: appwrite-traefik
command:
@@ -96,6 +72,7 @@ services:
- traefik.http.routers.appwrite_api_https.tls=true
volumes:
- appwrite-uploads:/storage/uploads:rw
- appwrite-video:/storage/video:rw
- appwrite-cache:/storage/cache:rw
- appwrite-config:/storage/config:rw
- appwrite-certificates:/storage/certificates:rw
@@ -103,7 +80,7 @@ services:
- ./phpunit.xml:/usr/src/code/phpunit.xml
- ./tests:/usr/src/code/tests
- ./app:/usr/src/code/app
# - ./vendor:/usr/src/code/vendor
- ./vendor:/usr/src/code/vendor #TODO remove when done!!
- ./docs:/usr/src/code/docs
- ./public:/usr/src/code/public
- ./src:/usr/src/code/src
@@ -152,11 +129,18 @@ services:
- _APP_INFLUXDB_HOST
- _APP_INFLUXDB_PORT
- _APP_STORAGE_LIMIT
- _APP_STORAGE_PREVIEW_LIMIT
- _APP_STORAGE_ANTIVIRUS
- _APP_STORAGE_ANTIVIRUS_HOST
- _APP_STORAGE_ANTIVIRUS_PORT
- *x-env-storage
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
- _APP_STORAGE_S3_SECRET
- _APP_STORAGE_S3_REGION
- _APP_STORAGE_S3_BUCKET
- _APP_STORAGE_DO_SPACES_ACCESS_KEY
- _APP_STORAGE_DO_SPACES_SECRET
- _APP_STORAGE_DO_SPACES_REGION
- _APP_STORAGE_DO_SPACES_BUCKET
- _APP_FUNCTIONS_SIZE_LIMIT
- _APP_FUNCTIONS_TIMEOUT
- _APP_FUNCTIONS_BUILD_TIMEOUT
@@ -166,7 +150,6 @@ services:
- _APP_FUNCTIONS_MEMORY_SWAP
- _APP_FUNCTIONS_RUNTIMES
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_STATSD_HOST
@@ -225,6 +208,40 @@ services:
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-transcoding:
entrypoint: worker-transcoding
<<: *x-logging
container_name: appwrite-worker-transcoding
build:
context: .
networks:
- appwrite
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
- appwrite-uploads:/storage/uploads:rw
- appwrite-video:/storage/video:rw
- ./tests:/usr/src/code/tests #TODO remove when done!!
- ./vendor:/usr/src/code/vendor #TODO remove when done!!
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-audits:
entrypoint: worker-audits
<<: *x-logging
@@ -293,6 +310,7 @@ services:
- mariadb
volumes:
- appwrite-uploads:/storage/uploads:rw
- appwrite-video:/storage/video:rw
- appwrite-cache:/storage/cache:rw
- appwrite-functions:/storage/functions:rw
- appwrite-builds:/storage/builds:rw
@@ -311,11 +329,18 @@ services:
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- *x-env-storage
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
- _APP_STORAGE_S3_SECRET
- _APP_STORAGE_S3_REGION
- _APP_STORAGE_S3_BUCKET
- _APP_STORAGE_DO_SPACES_ACCESS_KEY
- _APP_STORAGE_DO_SPACES_SECRET
- _APP_STORAGE_DO_SPACES_REGION
- _APP_STORAGE_DO_SPACES_BUCKET
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
appwrite-worker-database:
entrypoint: worker-database
@@ -365,7 +390,6 @@ services:
- _APP_ENV
- _APP_OPENSSL_KEY_V1
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@@ -397,7 +421,6 @@ services:
environment:
- _APP_ENV
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_DOMAIN_TARGET
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
- _APP_REDIS_HOST
@@ -441,7 +464,6 @@ services:
- _APP_DB_PASS
- _APP_FUNCTIONS_TIMEOUT
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
- _APP_USAGE_STATS
- DOCKERHUB_PULL_USERNAME
- DOCKERHUB_PULL_PASSWORD
@@ -488,7 +510,15 @@ services:
- OPEN_RUNTIMES_NETWORK
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- *x-env-storage
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
- _APP_STORAGE_S3_SECRET
- _APP_STORAGE_S3_REGION
- _APP_STORAGE_S3_BUCKET
- _APP_STORAGE_DO_SPACES_ACCESS_KEY
- _APP_STORAGE_DO_SPACES_SECRET
- _APP_STORAGE_DO_SPACES_REGION
- _APP_STORAGE_DO_SPACES_BUCKET
- DOCKERHUB_PULL_USERNAME
- DOCKERHUB_PULL_PASSWORD
@@ -539,18 +569,11 @@ services:
- redis
environment:
- _APP_ENV
- _APP_DOMAIN
- _APP_DOMAIN_TARGET
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_MAINTENANCE_INTERVAL
- _APP_MAINTENANCE_RETENTION_EXECUTION
- _APP_MAINTENANCE_RETENTION_ABUSE
@@ -779,6 +802,7 @@ volumes:
appwrite-redis:
appwrite-cache:
appwrite-uploads:
appwrite-video:
appwrite-certificates:
appwrite-functions:
appwrite-builds:
+81
View File
@@ -0,0 +1,81 @@
<?php
namespace Appwrite\Event;
use Resque;
use Utopia\Database\Document;
class Transcoding extends Event
{
protected string $bucketId = '';
protected string $fileId = '';
public function __construct()
{
parent::__construct(Event::TRANSCODING_QUEUE_NAME, Event::TRANSCODING_CLASS_NAME);
}
/**
* Sets bucketId event.
*
* @param $bucketId string
* @return self
*/
public function setBucketId(string $bucketId): self
{
$this->bucketId = $bucketId;
return $this;
}
/**
* Returns bucketId.
*
* @return null|Document
*/
public function getBucketId(): ?string
{
return $this->bucketId;
}
/**
* Sets fileId.
*
* @param $fileId string
* @return self
*/
public function setFileId(string $fileId): self
{
$this->fileId = $fileId;
return $this;
}
/**
* Returns fileId.
*
* @return null|Document
*/
public function getFileId(): ?string
{
return $this->fileId;
}
/**
* Executes the function event and sends it to the functions worker.
*
* @return string|bool
* @throws \InvalidArgumentException
*/
public function trigger(): string|bool
{
return Resque::enqueue($this->queue, $this->class, [
'project' => $this->project,
'user' => $this->user,
'bucketId' => $this->bucketId,
'fileId' => $this->fileId,
]);
}
}
+6
View File
@@ -69,6 +69,7 @@ use Appwrite\Utopia\Response\Model\UsageFunctions;
use Appwrite\Utopia\Response\Model\UsageProject;
use Appwrite\Utopia\Response\Model\UsageStorage;
use Appwrite\Utopia\Response\Model\UsageUsers;
use Appwrite\Utopia\Response\Model\FileRendition;
/**
* @method Response setStatusCode(int $code = 200)
@@ -127,6 +128,8 @@ class Response extends SwooleResponse
public const MODEL_FILE_LIST = 'fileList';
public const MODEL_BUCKET = 'bucket';
public const MODEL_BUCKET_LIST = 'bucketList';
public const MODEL_FILE_RENDITION = 'fileRendition';
public const MODEL_FILE_RENDITIONS_LIST = 'fileRenditionsList';
// Locale
public const MODEL_LOCALE = 'locale';
@@ -218,6 +221,7 @@ class Response extends SwooleResponse
->setModel(new BaseList('Sessions List', self::MODEL_SESSION_LIST, 'sessions', self::MODEL_SESSION))
->setModel(new BaseList('Logs List', self::MODEL_LOG_LIST, 'logs', self::MODEL_LOG))
->setModel(new BaseList('Files List', self::MODEL_FILE_LIST, 'files', self::MODEL_FILE))
->setModel(new BaseList('File Renditions List', self::MODEL_FILE_RENDITIONS_LIST, 'renditions', self::MODEL_FILE_RENDITION))
->setModel(new BaseList('Buckets List', self::MODEL_BUCKET_LIST, 'buckets', self::MODEL_BUCKET))
->setModel(new BaseList('Teams List', self::MODEL_TEAM_LIST, 'teams', self::MODEL_TEAM))
->setModel(new BaseList('Memberships List', self::MODEL_MEMBERSHIP_LIST, 'memberships', self::MODEL_MEMBERSHIP))
@@ -290,6 +294,8 @@ class Response extends SwooleResponse
->setModel(new UsageBuckets())
->setModel(new UsageFunctions())
->setModel(new UsageProject())
->setModel(new FileRendition())
// Verification
// Recovery
// Tests (keep last)
@@ -0,0 +1,89 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class FileRendition extends Model
{
public function __construct()
{
$this
->addRule('$id', [
'type' => self::TYPE_STRING,
'description' => 'ID.',
'default' => '',
'example' => '5e5ea5c16897e',
])
->addRule('bucketId', [
'type' => self::TYPE_STRING,
'description' => 'Bucket ID.',
'default' => '',
'example' => '5e5ea5c16897e',
])
->addRule('fileId', [
'type' => self::TYPE_STRING,
'description' => 'File ID.',
'default' => '',
'example' => 'd5fg5ehg1c168g7c',
])
->addRule('renditionId', [
'type' => self::TYPE_INTEGER,
'description' => 'Rendition ID.',
'default' => '',
'example' => 3,
])
->addRule('renditionName', [
'type' => self::TYPE_STRING,
'description' => 'Rendition name.',
'default' => '',
'example' => '720P',
])
->addRule('startedAt', [
'type' => self::TYPE_INTEGER,
'description' => 'Transcoding time started in Unix timestamp.',
'default' => 0,
'example' => 1592981220,
])
->addRule('endedAt', [
'type' => self::TYPE_INTEGER,
'description' => 'Transcoding time ended in Unix timestamp.',
'default' => 0,
'example' => 1592981290,
])
->addRule('status', [
'type' => self::TYPE_STRING,
'description' => 'Rendition transcoding status',
'default' => '',
'example' => 'ready',
])
->addRule('progress', [
'type' => self::TYPE_STRING,
'description' => 'Rendition trascoding progress',
'default' => 0,
'example' => 88,
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'File rendition';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_FILE_RENDITION;
}
}
@@ -0,0 +1,133 @@
<?php
namespace Tests\E2E\Services\Storage;
use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideServer;
class VideoCustomServerTest extends Scope
{
use StorageBase;
use ProjectCustom;
use SideServer;
public function testTranscoding(): array
{
// $bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', [
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// 'x-appwrite-key' => $this->getProject()['apiKey'],
// ], [
// 'bucketId' => 'unique()',
// 'name' => 'Test Bucket 2',
// 'permission' => 'file',
// 'read' => ['role:all'],
// 'write' => ['role:all']
// ]);
//
// $source = __DIR__ . "/../../../resources/disk-a/large-file.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, 'in1.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/' . $bucket['body']['$id'] . '/files', array_merge($headers, $this->getHeaders()), [
// 'fileId' => $fileId,
// 'file' => $curlFile,
// 'read' => ['role:all'],
// 'write' => ['role:all'],
// ]);
// $counter++;
// $id = $file['body']['$id'];
// }
// @fclose($handle);
//
// $pid = $this->getProject()['$id'];
// $key = $this->getProject()['apiKey'];
// $fid = $id;
// $bid = $bucket['body']['$id'];
//
// var_dump($pid);
// var_dump($key);
// var_dump($fid);
// var_dump($bid);
$pid = '62aaf3408decb0f5a0b3';
$key = '6e8bf2fd07e5206a9b90efbc3dfbf0794a8e35810838e6b906f0c651a510924b8cf0c535b3a4e84b0330344153a4d8d5e413a0d9314f6955a4b2693633ab120d4f674dd668820d4c195d3006bd814003de18dc2161d7ce639a03cd37fd6fa14151445eddb5c9a294ddf16276ec97d56ecb7275eddab3517254bc7201688d8f47';
$fid = '62aaf3423b91f5660b04';
$bid = '62aaf340e9c06064668d';
$transcoding = $this->client->call(Client::METHOD_POST, '/video/buckets/' . $bid . '/files/' . $fid, [
'content-type' => 'application/json',
'x-appwrite-project' => $pid,
'x-appwrite-key' => $key,
], [
'read' => ['role:all'],
'write' => ['role:all']
]);
return [
'projectId' => $pid,
'apiKey' => $key,
'bucketId' => $bid,
'fileId' => $fid,
];
}
public function testRenditions(): void
{
$pid = '62aaf3408decb0f5a0b3';
$key = '6e8bf2fd07e5206a9b90efbc3dfbf0794a8e35810838e6b906f0c651a510924b8cf0c535b3a4e84b0330344153a4d8d5e413a0d9314f6955a4b2693633ab120d4f674dd668820d4c195d3006bd814003de18dc2161d7ce639a03cd37fd6fa14151445eddb5c9a294ddf16276ec97d56ecb7275eddab3517254bc7201688d8f47';
$fid = '62aaf3423b91f5660b04';
$bid = '62aaf340e9c06064668d';
$renditions = $this->client->call(Client::METHOD_GET, '/video/buckets/' . $bid . '/files/' . $fid . '/renditions', [
'content-type' => 'application/json',
'x-appwrite-project' => $pid,
'x-appwrite-key' => $key,
]);
var_dump($renditions['body']);
}
public function testPlaylist(): void
{
$pid = '62aaf3408decb0f5a0b3';
$key = '6e8bf2fd07e5206a9b90efbc3dfbf0794a8e35810838e6b906f0c651a510924b8cf0c535b3a4e84b0330344153a4d8d5e413a0d9314f6955a4b2693633ab120d4f674dd668820d4c195d3006bd814003de18dc2161d7ce639a03cd37fd6fa14151445eddb5c9a294ddf16276ec97d56ecb7275eddab3517254bc7201688d8f47';
$fid = '62aaf3423b91f5660b04';
$bid = '62aaf340e9c06064668d';
$stream = 'dash';
$renditions = $this->client->call(Client::METHOD_GET, '/video/buckets/' . $bid . '/files/' . $stream . '/' . $fid, [
'content-type' => 'application/json',
'x-appwrite-project' => $pid,
'x-appwrite-key' => $key,
]);
var_dump($renditions['body']);
}
}