mpeg-dash-stream

This commit is contained in:
shimon
2022-07-28 17:32:14 +03:00
parent b403a829de
commit fe6a47252e
9 changed files with 99 additions and 6411 deletions
+1 -1
View File
@@ -3312,7 +3312,7 @@ $collections = [
'required' => false,
'default' => null,
'array' => false,
'filters' => ['json'],
//'filters' => ['json'],
],
[
'$id' => 'status',
+6
View File
@@ -76,4 +76,10 @@ return [ // List of publicly visible scopes
'health.read' => [
'description' => 'Access to read your project\'s health status',
],
'videos.read' => [
'description' => 'Access to read your project\'s video content',
],
'videos.write' => [
'description' => 'Access to create, update, and delete your project\'s video content',
]
];
+56 -44
View File
@@ -767,38 +767,44 @@ App::get('/v1/videos/:videoId/streams/:streamId')
$response->setContentType('application/x-mpegurl')->send($template->render(false));
} else {
$adaptations = [];
foreach ($renditions as $rendition) {
$adaptationId = 0;
foreach ($renditions as $rendition) {
$metadata = $rendition->getAttribute('metadata');
if (!empty($metadata['Period']['AdaptationSet'])) {
foreach ($metadata['Period']['AdaptationSet'] as $adaptationId => $set) {
$adaption = $set['@attributes'];
$adaption['id'] = $adaptationId;
//$adaption['baseUrl'] = $baseUrl . '/renditions/' . $rendition->getId() . '/' . 'segments/';
$adaption['representation'] = $set['Representation']['@attributes'];
$adaption['representation']['SegmentList'] = $set['Representation']['SegmentList']['@attributes'];
$representationId = 0;
$segments = Authorization::skip(fn() => $dbForProject->find('videos_renditions_segments', [
new Query('renditionId', Query::TYPE_EQUAL, [$rendition->getId()]),
new Query('representationId', Query::TYPE_EQUAL, [$representationId]),
$xml = simplexml_load_string($metadata['xml']);
$representationId = 0;
foreach ($xml->Period->AdaptationSet as $adaptation) {
$representation = [];
$representation['id'] = $representationId;
$attributes = (array)$adaptation->Representation->attributes();
$representation['attributes'] = $attributes['@attributes'] ?? [];
$attributes = (array)$adaptation->Representation->SegmentList->attributes();
$representation['SegmentList']['attributes'] = $attributes['@attributes'] ?? [];
$segments = Authorization::skip(fn() => $dbForProject->find('videos_renditions_segments', [
new Query('renditionId', Query::TYPE_EQUAL, [$rendition->getId()]),
new Query('representationId', Query::TYPE_EQUAL, [$representationId]),
], 1000, 0, ['representationId'], ['ASC']));
foreach ($segments as $segment) {
if ($segment->getAttribute('isInit')) {
$adaption['representation']['SegmentList']['Initialization'] = $baseUrl . '/renditions/' . $rendition->getId() . '/' . 'segments/' . $segment->getId();
continue;
}
$adaption['representation']['SegmentList']['media'][] = $baseUrl . '/renditions/' . $rendition->getId() . '/' . 'segments/' . $segment->getId();
foreach ($segments ?? [] as $segment) {
if ($segment->getAttribute('isInit')) {
$representation['SegmentList']['Initialization'] = $segment->getId();
continue;
}
$adaptations[] = $adaption;
$representationId++;
$representation['SegmentList']['media'][] = $segment->getId();
}
$attributes = (array)$adaptation->attributes();
$adaptations[] = ['attributes' => $attributes['@attributes'] ?? [],
'id' => $adaptationId,
'baseUrl' => $baseUrl . '/renditions/' . $rendition->getId() . '/' . 'segments/',
'Representation' => $representation,
];
$adaptationId++;
$representationId++;
}
$template = new View(__DIR__ . '/../../views/videos/dash.phtml');
$template->setParam('params', $adaptations);
$response->setContentType('application/dash+xml')->send($template->render(false));
}
$template = new View(__DIR__ . '/../../views/videos/dash.phtml');
$template->setParam('params', $adaptations);
$response->setContentType('application/dash+xml')->send($template->render(false));
}
});
@@ -945,9 +951,10 @@ App::get('/v1/videos/:videoId/streams/:streamId/subtitles/:subtitleId')
->param('subtitleId', '', new UID(), 'Subtitle unique ID.')
->inject('response')
->inject('dbForProject')
->inject('deviceVideos')
->inject('mode')
->inject('user')
->action(function (string $videoId, string $streamId, string $subtitleId, Response $response, Database $dbForProject, string $mode, Document $user) {
->action(function (string $videoId, string $streamId, string $subtitleId, Response $response, Database $dbForProject, Device $deviceVideos, string $mode, Document $user) {
$video = Authorization::skip(fn() => $dbForProject->findOne('videos', [
new Query('_uid', Query::TYPE_EQUAL, [$videoId])
@@ -968,26 +975,31 @@ App::get('/v1/videos/:videoId/streams/:streamId/subtitles/:subtitleId')
throw new Exception('subtitle not found', 404, Exception::VIDEO_SUBTITLE_NOT_FOUND);
}
$segments = Authorization::skip(fn () => $dbForProject->find('videos_subtitles_segments', [
new Query('subtitleId', Query::TYPE_EQUAL, [$subtitleId]),
], 4000));
if ($streamId == 'hls') {
$segments = Authorization::skip(fn() => $dbForProject->find('videos_subtitles_segments', [
new Query('subtitleId', Query::TYPE_EQUAL, [$subtitleId]),
], 4000));
if (empty($segments)) {
throw new Exception('Subtitle segments not found', 404, Exception::VIDEO_SUBTITLE_SEGMENT_NOT_FOUND);
if (empty($segments)) {
throw new Exception('Subtitle segments not found', 404, Exception::VIDEO_SUBTITLE_SEGMENT_NOT_FOUND);
}
$paramsSegments = [];
foreach ($segments as $segment) {
$paramsSegments[] = [
'duration' => $segment->getAttribute('duration'),
'url' => 'http://127.0.0.1/v1/videos/' . $videoId . '/streams/' . $streamId . '/subtitles/' . $subtitleId . '/segments/' . $segment->getId(),
];
}
$template = new View(__DIR__ . '/../../views/videos/hls-subtitles.phtml');
$template->setParam('targetDuration', $subtitle->getAttribute('targetDuration'));
$template->setParam('paramsSegments', $paramsSegments);
$response->setContentType('application/x-mpegurl')->send($template->render(false));
} else {
$output = $deviceVideos->read($deviceVideos->getPath($subtitle->getAttribute('videoId') . $subtitle->getAttribute('fileId') . '.vtt'));
$response->setContentType('text/vtt')->send($output);
}
$paramsSegments = [];
foreach ($segments as $segment) {
$paramsSegments[] = [
'duration' => $segment->getAttribute('duration'),
'url' => 'http://127.0.0.1/v1/videos/' . $videoId . '/streams/' . $streamId . '/subtitles/' . $subtitleId . '/segments/' . $segment->getId() ,
];
}
$template = new View(__DIR__ . '/../../views/videos/hls-subtitles.phtml');
$template->setParam('targetDuration', $subtitle->getAttribute('targetDuration'));
$template->setParam('paramsSegments', $paramsSegments);
$response->setContentType('application/x-mpegurl')->send($template->render(false));
});
+1
View File
@@ -843,6 +843,7 @@ App::setResource('project', function ($dbForConsole, $request, $console) {
/** @var Utopia\Database\Database $dbForConsole */
/** @var Utopia\Database\Document $console */
$projectId = 'dev';
$projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', 'console'));
if ($projectId === 'console') {
+15 -11
View File
@@ -1,25 +1,29 @@
<?php echo '<?xml version="1.0" encoding="utf-8"?>'; ?>
<?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): ?>
<BaseURL><?php if (isset($param['baseUrl'])): echo $param['baseUrl']; endif;?></BaseURL>
<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;?>>
<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'):?>
<?php foreach ($this->getParam('params', []) as $param):
$representation = $param['Representation'];
?>
<AdaptationSet <?php if (isset($param['id'])):?> id="<?php echo $param['id']; ?>"<?php endif;?><?php if (isset($param['attributes']['contentType'])): ?> contentType="<?php echo $param['attributes']['contentType']; ?>"<?php endif;?><?php if (isset($param['attributes']['startWithSAP'])): ?> startWithSAP="<?php echo $param['attributes']['startWithSAP']; ?>"<?php endif;?><?php if (isset($param['attributes']['segmentAlignment'])): ?> segmentAlignment="<?php echo $param['attributes']['segmentAlignment']; ?>"<?php endif;?><?php if (isset($param['attributes']['bitstreamSwitching'])): ?> bitstreamSwitching="<?php echo $param['attributes']['bitstreamSwitching']; ?>"<?php endif;?><?php if (isset($param['attributes']['frameRate'])): ?> frameRate="<?php echo $param['attributes']['frameRate']; ?>"<?php endif;?><?php if (isset($param['attributes']['maxWidth'])): ?> maxWidth="<?php echo $param['attributes']['maxWidth']; ?>"<?php endif;?><?php if (isset($param['attributes']['par'])): ?> par="<?php echo $param['attributes']['par']; ?>"<?php endif;?><?php if (isset($param['attributes']['lang'])): ?> lang="<?php echo $param['attributes']['lang']; ?>"<?php endif;?>>
<Representation <?php if (isset($representation['attributes']['id'])):?> id="<?php echo $representation['attributes']['id']; ?>"<?php endif;?><?php if (isset($representation['attributes']['mimeType'])):?> mimeType="<?php echo $representation['attributes']['mimeType']; ?>"<?php endif;?><?php if (isset($representation['attributes']['codecs'])):?> codecs="<?php echo $representation['attributes']['codecs']; ?>"<?php endif;?><?php if (isset($representation['attributes']['width'])):?> width="<?php echo $representation['attributes']['width']; ?>"<?php endif;?><?php if (isset($representation['attributes']['height'])):?> height="<?php echo $representation['attributes']['height']; ?>"<?php endif;?><?php if (isset($representation['attributes']['sar'])):?> sar="<?php echo $representation['attributes']['sar']; ?>"<?php endif;?><?php if (isset($representation['attributes']['audioSamplingRate'])):?> audioSamplingRate="<?php echo $representation['attributes']['audioSamplingRate']; ?>"<?php endif;?>>
<BaseURL><?php if (isset($param['baseUrl'])): echo $param['baseUrl']; endif;?></BaseURL>
<?php if (isset($param['attributes']['contentType']) && $param['attributes']['contentType'] === 'audio' ):?>
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2" />
<?php endif;?>
<Initialization sourceURL="<?php if (isset($param['representation']['SegmentList']['Initialization'])):?><?php echo $param['representation']['SegmentList']['Initialization']; ?><?php endif;?>"/>
<?php if (isset($param['representation']['SegmentList']['media'])):?>
<?php foreach ($param['representation']['SegmentList']['media'] as $segment): ?>
<SegmentList<?php if (isset($representation['SegmentList']['attributes']['timescale'])): ?> timescale="<?php echo $representation['SegmentList']['attributes']['timescale']; ?>"<?php endif;?><?php if (isset($representation['SegmentList']['attributes']['duration'])): ?> duration="<?php echo $representation['SegmentList']['attributes']['duration']; ?>"<?php endif;?><?php if (isset($representation['SegmentList']['attributes']['startNumber'])): ?> startNumber="<?php echo $representation['SegmentList']['attributes']['startNumber']; ?>"<?php endif;?>>
<Initialization sourceURL="<?php if (isset($representation['SegmentList']['Initialization'])):?><?php echo $representation['SegmentList']['Initialization']; ?><?php endif;?>"/>
<?php if (isset($representation['SegmentList']['media'])):?>
<?php foreach ($representation['SegmentList']['media'] as $segment): ?>
<SegmentURL media="<?php if (isset($segment)):?><?php echo $segment; ?><?php endif;?>"/>
<?php endforeach; ?>
<?php endif;?>
</SegmentList>
</Representation>
</AdaptationSet>
<?php endforeach; ?>
+18 -19
View File
@@ -132,7 +132,7 @@ class TranscodingV1 extends Worker
}
}
$general = $this->getVideoSourceInfo($ffprobe->streams($inPath));
$general = $this->getVideoSourceInfo($ffprobe->streams($inPath));
if (!empty($general)) {
foreach ($general as $key => $value) {
$sourceVideo->setAttribute($key, $value);
@@ -255,7 +255,6 @@ class TranscodingV1 extends Worker
}
}
$query->setAttribute('targetDuration', $m3u8['targetDuration']);
} else {
$mpd = $this->parseMpd($this->outPath . '.mpd');
@@ -272,8 +271,9 @@ class TranscodingV1 extends Worker
});
}
}
if (!empty($mpd['metadata'])) {
$query->setAttribute('metadata', json_encode($mpd['metadata']));
$query->setAttribute('metadata', json_encode(['xml' => $mpd['metadata']]));
}
}
@@ -411,12 +411,12 @@ class TranscodingV1 extends Worker
{
$segments = [];
$metadata = null;
$representationId = 0;
$handle = fopen($path, "r");
if ($handle) {
$representationId = -1;
while (($line = fgets($handle)) !== false) {
$line = str_replace([",","\r","\n"], "", $line);
if (str_contains($line, "contentType=\"audio\"")) {
if (str_contains($line, "<AdaptationSet")) {
$representationId++;
}
@@ -426,17 +426,15 @@ class TranscodingV1 extends Worker
$segments[] = [
'isInit' => str_contains($line, "Initialization") ? 1 : 0,
'representationId' => $representationId,
'fileName' => str_replace(["<SegmentURL media=\"","<Initialization sourceURL=\"", "\"/>", " "], "", $line),
'fileName' => trim(str_replace(["<SegmentURL media=\"", "<Initialization sourceURL=\"", "\"/>", "\" />"], "", $line)),
];
}
}
fclose($handle);
}
$xml = simplexml_load_string($metadata);
return [
'metadata' => json_decode(json_encode((array)$xml), true),
'metadata' => $metadata,
'segments' => $segments
];
}
@@ -483,17 +481,18 @@ class TranscodingV1 extends Worker
*/
private function getVideoSourceInfo(StreamCollection $streams): array
{
return [
'duration' => !empty($streams->videos()) ? $streams->videos()->first()->get('duration') : '0',
'height' => !empty($streams->videos()) ? $streams->videos()->first()->get('height') : 0,
'width' => !empty($streams->videos()) ? $streams->videos()->first()->get('width') : 0,
'videoCodec' => !empty($streams->videos()) ? $streams->videos()->first()->get('codec_name') . ',' . $streams->videos()->first()->get('codec_tag_string') : '',
'videoFramerate' => !empty($streams->videos()) ? $streams->videos()->first()->get('avg_frame_rate') : '',
'videoBitrate' => !empty($streams->videos()) ? (int)$streams->videos()->first()->get('bit_rate') : 0,
'audioCodec' => !empty($streams->audios()) ? $streams->audios()->first()->get('codec_name') . ',' . $streams->audios()->first()->get('codec_tag_string') : '',
'audioSamplerate' => !empty($streams->audios()) ? (int)$streams->audios()->first()->get('sample_rate') : 0,
'audioBitrate' => !empty($streams->audios()) ? (int)$streams->audios()->first()->get('bit_rate') : 0,
];
'duration' => $streams->videos()->count() ? $streams->videos()->first()->get('duration') : '0',
'height' => $streams->videos()->count() ? $streams->videos()->first()->get('height') : 0,
'width' => $streams->videos()->count() ? $streams->videos()->first()->get('width') : 0,
'videoCodec' => $streams->videos()->count() ? $streams->videos()->first()->get('codec_name') . ',' . $streams->videos()->first()->get('codec_tag_string') : '',
'videoFramerate' => $streams->videos()->count() ? $streams->videos()->first()->get('avg_frame_rate') : '',
'videoBitrate' => $streams->videos()->count() > 0 ? (int)$streams->videos()->first()->get('bit_rate') : 0,
'audioCodec' => $streams->audios()->count() > 0 ? $streams->audios()->first()->get('codec_name') . ',' . $streams->audios()->first()->get('codec_tag_string') : '',
'audioSamplerate' => $streams->audios()->count() > 0 ? (int)$streams->audios()->first()->get('sample_rate') : 0,
'audioBitrate' => $streams->audios()->count() > 0 ? (int)$streams->audios()->first()->get('bit_rate') : 0,
];
}
/**
+1 -1
View File
@@ -56,7 +56,7 @@
"utopia-php/preloader": "0.2.*",
"utopia-php/domains": "1.1.*",
"utopia-php/swoole": "0.3.*",
"utopia-php/storage": "dev-main",
"utopia-php/storage": "dev-feat-transfer",
"utopia-php/websocket": "0.1.0",
"utopia-php/image": "0.5.*",
"utopia-php/orchestration": "0.6.*",
Generated
-6322
View File
File diff suppressed because it is too large Load Diff
@@ -409,7 +409,7 @@ class VideoCustomServerTest extends Scope
sleep(20);
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $data['videoId'] . '/streams/mpeg-dash', [
$response = $this->client->call(Client::METHOD_GET, '/videos/' . $data['videoId'] . '/streams/dash', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
@@ -418,17 +418,5 @@ class VideoCustomServerTest extends Scope
var_dump($response['body']);
}
public function testBla(){
$response = $this->client->call(Client::METHOD_GET, '/videos/62dfaeb69c97ea81c58c/streams/dash/renditions/62dfaeb83bc30e827719/segments/62dfaed714324026902c', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
var_dump($response['body']);
}
}