hls-test/timestamp-id3.php
2021-10-22 11:27:02 -07:00

141 lines
3.3 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, $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";
$data = file_get_contents( $filename );
if ( substr( $data, 0, 3 ) == 'ID3' ) {
echo "SKIPPING already has ID3\n";
} else {
echo "ADDING ID3\n";
file_put_contents( $filename, "$tag$data" );
}
}
$playlist = "caminandes-llamigos.webm.audio.mp3.m3u8";
$lines = file( $playlist, FILE_IGNORE_NEW_LINES + FILE_SKIP_EMPTY_LINES );
$pts = 0.0;
$duration = 0.0;
foreach ( $lines as $line ) {
/*
#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
...
*/
$matches = null;
if ( preg_match( '/^#EXTINF:\s*(\d+(?:\.\d+)?),/', $line, $matches ) ) {
$duration = floatval( $matches[1] );
continue;
} else if (preg_match( '/^#/', $line ) ) {
continue;
}
$filename = $line;
process_mp3( $filename, $pts );
$pts += $duration;
$duration = 0;
}