157 lines
No EOL
3.7 KiB
PHP
157 lines
No EOL
3.7 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 );
|
|
return "$tag$data";
|
|
}
|
|
|
|
$playlist = "caminandes-llamigos.webm.audio.mp3.m3u8";
|
|
$playlist_out = "caminandes-llamigos.webm.audio.mp3.combined.m3u8";
|
|
$outfile = "caminandes-llamigos.webm.audio.mp3";
|
|
$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 ) {
|
|
|
|
// todo: 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( '/^#/', $line ) ) {
|
|
$lines_out[] = $line;
|
|
continue;
|
|
}
|
|
$filename = $line;
|
|
$chunk = process_mp3( $filename, $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 ) ); |