hls-test/timestamp-id3.php
Brion Vibber b14806a74b wip trying with segment muxer
not having luck with the fmp4
just gonna stick with hls i think
2023-02-17 16:51:21 -08:00

171 lines
No EOL
4.1 KiB
PHP

<?php
/*
PRIV frame type
should contain:
The ID3 PRIV owner identifier MUST be
"com.apple.streaming.transportStreamTimestamp". The ID3 payload MUST
be a 33-bit MPEG-2 Program Elementary Stream timestamp expressed as a
big-endian eight-octet number, with the upper 31 bits set to zero.
Clients SHOULD NOT play Packed Audio Segments without this ID3 tag.
https://id3.org/id3v2.4.0-frames
https://id3.org/id3v2.4.0-structure
bit order is MSB first, big-endian
header 10 bytes
extended header (var, optional)
frames (variable)
pading (variable, optional)
footer (10 bytes, optional)
header:
"ID3"
version: 16 bits $04 00
flags: 32 bits
idv2 size: 32 bits (in chunks of 4 bytes, not counting header or footer)
flags:
bit 7 - unsyncrhonization (??)
bit 6 - extended header
bit 5 - experimental indicator
bit 4 - footer present
frame:
id - 32 bits (four chars)
size - 32 bits (in chunks of 4 bytes, excluding frame header)
flags - 16 bits
(frame data)
priv payload:
owner text string followed by \x00
(binary data)
The timestamps... I think... have 90 kHz integer resolution
so convert from the decimal seconds in the HLS
*/
function hexdump($str) {
$len = strlen( $str );
return unpack("H*", $str)[1];
}
const KHZ_90 = 90000;
const MHZ_27 = 27000000;
function process_mp3( $filename, $data, $pts ) {
$owner = "com.apple.streaming.transportStreamTimestamp\x00";
$timestamp = $pts * KHZ_90;
$timestamp_high = 0;
$timestamp_low = intval( $timestamp ); // assume they won't get too big for 31 bits
$frame_data = pack(
'a*NN',
$owner,
$timestamp_high,
$timestamp_low,
);
$frame_type = 'PRIV';
$frame_flags = 0;
$frame_length = strlen( $frame_data ); // if >127 bytes may need to adjust
$frame = pack(
'a4Nna*',
$frame_type,
$frame_length,
$frame_flags,
$frame_data
);
$tag_type = 'ID3';
$tag_version = 0x0400;
$tag_flags = 0;
$tag_length = strlen( $frame ); // if >127 bytes may need to adjust
$tag = pack(
'a3nCNa*',
$tag_type,
$tag_version,
$tag_flags,
$tag_length,
$frame
);
$hex = hexdump($tag);
print "$filename $pts $hex\n";
return "$tag$data";
}
$args = $_SERVER['argv'];
$self = array_shift( $args );
//$playlist = "caminandes-llamigos.webm.audio.mp3.m3u8";
//$playlist_out = "caminandes-llamigos.webm.audio.mp3.combined.m3u8";
//$outfile = "caminandes-llamigos.webm.audio.mp3";
$playlist = array_shift( $args );
$playlist_out = $playlist;
$outfile = array_shift( $args );
$process_mp3 = preg_match('/\.mp3$/i', $outfile);
$lines = file( $playlist, FILE_IGNORE_NEW_LINES + FILE_SKIP_EMPTY_LINES );
$pts = 0.0;
$duration = 0.0;
$lines_out = [];
$chunks = [];
$offset = 0;
foreach ( $lines as $line ) {
// create a single-file version
// and rewrite the manifest
/*
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-ALLOW-CACHE:YES
#EXT-X-TARGETDURATION:11
#EXTINF:10.005397,
caminandes-llamigos.webm.audio.mp3.0000.mp3
#EXTINF:10.004898,
caminandes-llamigos.webm.audio.mp3.0001.mp3
...
for output:
#EXT-X-BYTERANGE:132872@730
*/
$matches = null;
if ( preg_match( '/^#EXTINF:\s*(\d+(?:\.\d+)?),/', $line, $matches ) ) {
$duration = floatval( $matches[1] );
}
if ( preg_match( '/^#EXT-X-VERSION:(.*)/', $line, $matches ) ) {
if ( intval( $matches[1] ) < 4 ) {
$line = "#EXT-X-VERSION:7";
}
}
if (preg_match( '/^#/', $line ) ) {
$lines_out[] = $line;
continue;
}
$filename = $line;
$chunk = file_get_contents( $filename );
if ( $process_mp3 ) {
$chunk = process_mp3( $filename, $chunk, $pts );
}
$len = strlen( $chunk );
$lines_out[] = "#EXT-X-BYTERANGE:$len@$offset";
$lines_out[] = "$outfile";
$chunks[] = $chunk;
$offset += $len;
$pts += $duration;
$duration = 0;
}
file_put_contents( $outfile, implode( '', $chunks ) );
file_put_contents( $playlist_out, implode( "\n", $lines_out ) );