Implement our own quicktime parser

This commit is contained in:
2024-09-16 22:10:19 +10:00
parent a3013078e0
commit f9cdc8f9d2
42 changed files with 1367 additions and 62 deletions

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Media\QuickTime\Atoms\moov\trak;
use App\Media\QuickTime\Atoms\moov\trak\mdia\hdlr;
use App\Media\QuickTime\Atoms\SubAtom;
use App\Media\QuickTime\Atoms\Unknown;
class mdia extends SubAtom
{
private const subatom_classes = 'App\\Media\\QuickTime\\Atoms\\moov\\trak\\mdia\\';
public function __construct(int $offset,int $size,string $filename,?string $data)
{
parent::__construct($offset,$size,$filename);
// We'll find atoms
$this->atoms = $this->get_atoms(
self::subatom_classes,
Unknown::class,
$offset,
$size,
$data,
NULL,
fn($atom)=>($atom instanceof hdlr) ? $atom->cache['csubtype'] : NULL
);
// For debugging
if (FALSE)
$this->debug = hex_dump($data ?: $this->data());
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Media\QuickTime\Atoms\moov\trak\mdia;
use App\Media\QuickTime\Atoms\SubAtom;
class hdlr extends SubAtom
{
protected const unpack = [
'version'=>['c',1],
'flags'=>['a3',3],
'ctype'=>['a4',4],
'csubtype'=>['a4',4],
'cmanufact'=>['a4',4],
'cflags'=>['a4',4],
'cmask'=>['a4',4],
];
public function __construct(int $offset,int $size,string $filename,?string $data)
{
parent::__construct($offset,$size,$filename);
$this->cache = $this->cache();
$this->cache['name'] = pascal_string($this->unused_data);
$this->unused_data = (($x=strlen($this->cache['name'])+1) < strlen($this->unused_data)) ? substr($data,$x) : NULL;
// For debugging
if (FALSE)
$this->debug = hex_dump($data ?: $this->data());
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Media\QuickTime\Atoms\moov\trak\mdia;
use Leenooks\Traits\ObjectIssetFix;
use App\Media\QuickTime\Atoms\SubAtom;
use App\Media\QuickTime\Atoms\Unknown;
class minf extends SubAtom
{
use ObjectIssetFix;
private const subatom_classes = 'App\\Media\\QuickTime\\Atoms\\moov\\trak\\mdia\\minf\\';
protected ?string $type;
public function __construct(int $offset,int $size,string $filename,?string $data,string $arg=NULL)
{
parent::__construct($offset,$size,$filename);
$this->type = $arg;
$this->atoms = $this->get_atoms(self::subatom_classes,Unknown::class,$offset,$size,$data,$arg);
// For debugging
if (FALSE)
$this->debug = hex_dump($data ?: $this->data());
}
public function __get(string $key): mixed
{
switch ($key) {
case 'type':
return $this->{$key};
default:
return parent::__get($key);
}
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Media\QuickTime\Atoms\moov\trak\mdia\minf;
use App\Media\QuickTime\Atoms\moov\trak\mdia\hdlr as MdiaHdlr;
class hdlr extends MdiaHdlr
{
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Media\QuickTime\Atoms\moov\trak\mdia\minf;
use App\Media\QuickTime\Atoms\SubAtom;
use App\Media\QuickTime\Atoms\Unknown;
class stbl extends SubAtom
{
private const subatom_classes = 'App\\Media\\QuickTime\\Atoms\\moov\\trak\\mdia\\minf\\stbl\\';
public function __construct(int $offset,int $size,string $filename,?string $data,?string $arg=NULL)
{
parent::__construct($offset,$size,$filename);
$this->atoms = $this->get_atoms(self::subatom_classes,Unknown::class,$offset,$size,$data,$arg);
// For debugging
if (FALSE)
$this->debug = hex_dump($data ?: $this->data());
}
}

View File

@@ -0,0 +1,104 @@
<?php
namespace App\Media\QuickTime\Atoms\moov\trak\mdia\minf\stbl;
use Illuminate\Support\Arr;
use App\Media\QuickTime\Atoms\SubAtom;
class stsd extends SubAtom
{
protected const unpack = [
'size'=>['N',4],
'format'=>['a4',4],
'reserved'=>['a6',6],
'index'=>['n',2],
'encoder_version'=>['n',2],
'encoder_revision'=>['n',2],
'encoder_vendor'=>['a4',4],
];
protected const audio_record = [
'audio_channels'=>['n',2],
'audio_bit_depth'=>['n',2],
'audio_compression_id'=>['n',2],
'audio_packet_size'=>['n',2],
'audio_sample_rate'=>['a4',4],
];
protected const video_record = [
'temporal_quality'=>['N',4],
'spatial_quality'=>['N',4],
'width'=>['n',2],
'height'=>['n',2],
'resolution_x'=>['a4',4],
'resolution_y'=>['a4',4],
'data_size'=>['N',4],
'frame_count'=>['n',2],
//'codec_name'=>['a4',4], // pascal string
];
public function __construct(int $offset,int $size,string $filename,?string $data,string $arg=NULL)
{
parent::__construct($offset,$size,$filename);
$this->type = $arg;
$read = unpack($this->unpack(self::atom_record),substr($data,0,$ptr=$this->unpack_size(self::atom_record)));
for ($i=0; $i<$read['count']; $i++) {
$this->cache = collect(unpack($this->unpack(),substr($data,$ptr,$x=$this->unpack_size())));
$ptr += $x;
switch ($this->type) {
case 'soun':
// Audio Track
$this->audio = unpack($this->unpack(self::audio_record),substr($data,$ptr,$x=$this->unpack_size(self::audio_record)));
$ptr += $x;
break;
case 'vide':
// Video Track
$this->video = unpack($this->unpack(self::video_record),substr($data,$ptr,$x=$this->unpack_size(self::video_record)));
$ptr += $x;
// codec - pascal string
$this->video['codec'] = pascal_string(substr($data,$ptr));
$ptr += strlen($this->video['codec'])+1;
}
$this->extra = substr($data,$ptr);
}
}
public function __get(string $key):mixed
{
switch ($key) {
case 'audio_channels':
return Arr::get($this->audio,$key);
case 'audio_codec':
switch ($this->cache->get('format')) {
case 'mp4a': return 'ISO/IEC 14496-3 AAC';
default:
return $this->cache->get('format');
}
case 'audio_samplerate':
return fixed_point_int_to_float(Arr::get($this->audio,'audio_sample_rate',0));
case 'video_codec':
return Arr::get($this->video,'codec');
case 'video_framerate':
dd($this);
// return Arr::get($this->video,'codec');
default:
return parent::__get($key);
}
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Media\QuickTime\Atoms\moov\trak\mdia\minf\stbl;
use App\Media\QuickTime\Atoms\SubAtom;
class stts extends SubAtom
{
public function __construct(int $offset,int $size,string $filename,?string $data)
{
parent::__construct($offset,$size,$filename);
$read = unpack($this->unpack(self::atom_record),substr($data,0,$ptr=$this->unpack_size(self::atom_record)));
for ($i=0; $i<$read['count']; $i++) {
$this->cache->push(unpack('Ncount/Nduration',substr($data,$ptr,8)));
$ptr += 8;
}
// For debugging
if (FALSE)
$this->debug = hex_dump($data ?: $this->data());
}
public function frame_rate(float $time_scale): float
{
return $this->cache
->pluck('duration')
->map(fn($item)=>$time_scale/$item)
->max();
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Media\QuickTime\Atoms\moov\trak;
use App\Media\QuickTime\Atoms\moov\meta as MoovMeta;
class meta extends MoovMeta
{
}

View File

@@ -0,0 +1,55 @@
<?php
namespace App\Media\QuickTime\Atoms\moov\trak;
// An atom that specifies the characteristics of a single track within a movie
use Illuminate\Support\Arr;
use App\Media\QuickTime\Atoms\SubAtom;
class tkhd extends SubAtom
{
protected const unpack = [
'version'=>['c',1],
'flags'=>['a3',3],
'create'=>['N',4],
'modified'=>['N',4],
'trakid'=>['N',4], // The value 0 cannot be used
'reserved1'=>['a4',4],
'duration'=>['N',4],
'reserved2'=>['a8',8],
'layer'=>['n',2],
'altgroup'=>['n',2],
'volume'=>['a2',2], // 16 bit fixed point
'reserved3'=>['a2',2],
'matrix'=>['a36',36],
'twidth'=>['a4',4], // 32 bit fixed point
'theight'=>['a4',4], // 32 bit fixed point
];
public function __construct(int $offset,int $size,string $filename,?string $data)
{
parent::__construct($offset,$size,$filename,$data);
$this->cache = $this->cache();
// For debugging
if (FALSE)
$this->debug = hex_dump($data ?: $this->data());
}
public function __get($key): mixed
{
switch ($key) {
case 'height':
return fixed_point_int_to_float(Arr::get($this->cache(),'theight'));
case 'width':
return fixed_point_int_to_float(Arr::get($this->cache(),'twidth'));
default:
return parent::__get($key);
}
}
}