/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
TODO:
These will be added when I see a real file that uses them.
Header objects:
Marker (3.7)
Bitrate Mutual Exclusion (3.8)
Content Branding (3.13)
Header Extension objects:
Group Mutual Exclusion (4.3)
Stream Prioritization (4.4)
Bandwidth Sharing (4.5)
Media Object Index Parameters (4.10)
Timecode Index Parameters (4.11)
Advanced Content Encryption (4.13)
Index objects:
Media Object Index (6.3)
Timecode Index (6.4)
*/
#include "asf.h"
static void
print_guid(GUID guid)
{
PerlIO_printf(PerlIO_stderr(),
"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x ",
guid.Data1, guid.Data2, guid.Data3,
guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3],
guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]
);
}
static int
get_asf_metadata(PerlIO *infile, char *file, HV *info, HV *tags)
{
Buffer asf_buf;
ASF_Object hdr;
ASF_Object data;
ASF_Object tmp;
off_t file_size;
int err = 0;
file_size = _file_size(infile);
buffer_init(&asf_buf, ASF_BLOCK_SIZE);
if ( !_check_buf(infile, &asf_buf, 30, ASF_BLOCK_SIZE) ) {
err = -1;
goto out;
}
buffer_get_guid(&asf_buf, &hdr.ID);
if ( !IsEqualGUID(&hdr.ID, &ASF_Header_Object) ) {
PerlIO_printf(PerlIO_stderr(), "Invalid ASF header: %s\n", file);
PerlIO_printf(PerlIO_stderr(), " Expecting: ");
print_guid(ASF_Header_Object);
PerlIO_printf(PerlIO_stderr(), "\n Got: ");
print_guid(hdr.ID);
PerlIO_printf(PerlIO_stderr(), "\n");
err = -1;
goto out;
}
hdr.size = buffer_get_int64_le(&asf_buf);
hdr.num_objects = buffer_get_int_le(&asf_buf);
hdr.reserved1 = buffer_get_char(&asf_buf);
hdr.reserved2 = buffer_get_char(&asf_buf);
if ( hdr.reserved2 != 0x02 ) {
PerlIO_printf(PerlIO_stderr(), "Invalid ASF header: %s\n", file);
err = -1;
goto out;
}
while ( hdr.num_objects-- ) {
if ( !_check_buf(infile, &asf_buf, 24, ASF_BLOCK_SIZE) ) {
err = -1;
goto out;
}
buffer_get_guid(&asf_buf, &tmp.ID);
tmp.size = buffer_get_int64_le(&asf_buf);
if ( !_check_buf(infile, &asf_buf, tmp.size - 24, ASF_BLOCK_SIZE) ) {
err = -1;
goto out;
}
if ( IsEqualGUID(&tmp.ID, &ASF_Content_Description) ) {
DEBUG_TRACE("Content_Description\n");
_parse_content_description(&asf_buf, info, tags);
}
else if ( IsEqualGUID(&tmp.ID, &ASF_File_Properties) ) {
DEBUG_TRACE("File_Properties\n");
_parse_file_properties(&asf_buf, info, tags);
}
else if ( IsEqualGUID(&tmp.ID, &ASF_Stream_Properties) ) {
DEBUG_TRACE("Stream_Properties\n");
_parse_stream_properties(&asf_buf, info, tags);
}
else if ( IsEqualGUID(&tmp.ID, &ASF_Extended_Content_Description) ) {
DEBUG_TRACE("Extended_Content_Description\n");
_parse_extended_content_description(&asf_buf, info, tags);
}
else if ( IsEqualGUID(&tmp.ID, &ASF_Codec_List) ) {
DEBUG_TRACE("Codec_List\n");
_parse_codec_list(&asf_buf, info, tags);
}
else if ( IsEqualGUID(&tmp.ID, &ASF_Stream_Bitrate_Properties) ) {
DEBUG_TRACE("Stream_Bitrate_Properties\n");
_parse_stream_bitrate_properties(&asf_buf, info, tags);
}
else if ( IsEqualGUID(&tmp.ID, &ASF_Content_Encryption) ) {
DEBUG_TRACE("Content_Encryption\n");
_parse_content_encryption(&asf_buf, info, tags);
}
else if ( IsEqualGUID(&tmp.ID, &ASF_Extended_Content_Encryption) ) {
DEBUG_TRACE("Extended_Content_Encryption\n");
_parse_extended_content_encryption(&asf_buf, info, tags);
}
else if ( IsEqualGUID(&tmp.ID, &ASF_Script_Command) ) {
DEBUG_TRACE("Script_Command\n");
_parse_script_command(&asf_buf, info, tags);
}
else if ( IsEqualGUID(&tmp.ID, &ASF_Digital_Signature) ) {
DEBUG_TRACE("Skipping Digital_Signature\n");
buffer_consume(&asf_buf, tmp.size - 24);
}
else if ( IsEqualGUID(&tmp.ID, &ASF_Header_Extension) ) {
DEBUG_TRACE("Header_Extension\n");
if ( !_parse_header_extension(&asf_buf, tmp.size, info, tags) ) {
PerlIO_printf(PerlIO_stderr(), "Invalid ASF file: %s (invalid header extension object)\n", file);
err = -1;
goto out;
}
}
else if ( IsEqualGUID(&tmp.ID, &ASF_Error_Correction) ) {
DEBUG_TRACE("Skipping Error_Correction\n");
buffer_consume(&asf_buf, tmp.size - 24);
}
else {
// Unhandled GUID
PerlIO_printf(PerlIO_stderr(), "** Unhandled GUID: ");
print_guid(tmp.ID);
PerlIO_printf(PerlIO_stderr(), "size: %llu\n", tmp.size);
buffer_consume(&asf_buf, tmp.size - 24);
}
}
// We should be at the start of the Data object.
// Seek past it to find more objects
if ( !_check_buf(infile, &asf_buf, 24, ASF_BLOCK_SIZE) ) {
err = -1;
goto out;
}
buffer_get_guid(&asf_buf, &data.ID);
if ( !IsEqualGUID(&data.ID, &ASF_Data) ) {
PerlIO_printf(PerlIO_stderr(), "Invalid ASF file: %s (no Data object after Header)\n", file);
err = -1;
goto out;
}
// Store offset to beginning of data (50 goes past the top-level data packet)
my_hv_store( info, "audio_offset", newSVuv(hdr.size + 50) );
my_hv_store( info, "file_size", newSVuv(file_size) );
/*
XXX: Not worth the overhead of skipping the data and parsing
the index objects, and can't compile it on Windows, so leave it out for now...
data.size = buffer_get_int64_le(&asf_buf);
if ( hdr.size + data.size < file_size ) {
DEBUG_TRACE("Seeking past data: %lu\n", hdr.size + data.size);
if ( PerlIO_seek(infile, hdr.size + data.size, SEEK_SET) != 0 ) {
PerlIO_printf(PerlIO_stderr(), "Invalid ASF file: %s (Invalid Data object size)\n", file);
err = -1;
goto out;
}
buffer_clear(&asf_buf);
if ( !_parse_index_objects(infile, file_size - hdr.size - data.size, hdr.size + 50, &asf_buf, info, tags) ) {
PerlIO_printf(PerlIO_stderr(), "Invalid ASF file: %s (Invalid Index object)\n", file);
err = -1;
goto out;
}
}
*/
out:
buffer_free(&asf_buf);
if (err) return err;
return 0;
}
void
_parse_content_description(Buffer *buf, HV *info, HV *tags)
{
int i;
uint16_t len[5];
Buffer utf8_buf;
char fields[5][12] = {
{ "Title" },
{ "Author" },
{ "Copyright" },
{ "Description" },
{ "Rating" }
};
for (i = 0; i < 5; i++) {
len[i] = buffer_get_short_le(buf);
}
buffer_init(&utf8_buf, len[0]);
for (i = 0; i < 5; i++) {
SV *value;
if ( len[i] ) {
buffer_clear(&utf8_buf);
buffer_get_utf16_as_utf8(buf, &utf8_buf, len[i], UTF16_BYTEORDER_LE);
value = newSVpv( buffer_ptr(&utf8_buf), 0 );
sv_utf8_decode(value);
_store_tag( tags, newSVpv(fields[i], 0), value );
}
}
buffer_free(&utf8_buf);
}
void
_parse_extended_content_description(Buffer *buf, HV *info, HV *tags)
{
Buffer utf8_buf;
uint16_t count = buffer_get_short_le(buf);
buffer_init(&utf8_buf, 32);
while ( count-- ) {
uint16_t name_len;
uint16_t data_type;
uint16_t value_len;
SV *key = NULL;
SV *value = NULL;
name_len = buffer_get_short_le(buf);
buffer_clear(&utf8_buf);
buffer_get_utf16_as_utf8(buf, &utf8_buf, name_len, UTF16_BYTEORDER_LE);
key = newSVpv( buffer_ptr(&utf8_buf), 0 );
sv_utf8_decode(key);
data_type = buffer_get_short_le(buf);
value_len = buffer_get_short_le(buf);
if (data_type == TYPE_UNICODE) {
buffer_clear(&utf8_buf);
buffer_get_utf16_as_utf8(buf, &utf8_buf, value_len, UTF16_BYTEORDER_LE);
value = newSVpv( buffer_ptr(&utf8_buf), 0 );
sv_utf8_decode(value);
}
else if (data_type == TYPE_BYTE) {
// handle picture data, interestingly it is compatible with the ID3v2 APIC frame
if ( !strcmp( SvPVX(key), "WM/Picture" ) ) {
value = _parse_picture(buf);
}
else {
value = newSVpvn( buffer_ptr(buf), value_len );
buffer_consume(buf, value_len);
}
}
else if (data_type == TYPE_BOOL) {
value = newSViv( buffer_get_int_le(buf) );
}
else if (data_type == TYPE_DWORD) {
value = newSViv( buffer_get_int_le(buf) );
}
else if (data_type == TYPE_QWORD) {
value = newSViv( buffer_get_int64_le(buf) );
}
else if (data_type == TYPE_WORD) {
value = newSViv( buffer_get_short_le(buf) );
}
else {
PerlIO_printf(PerlIO_stderr(), "Unknown extended content description data type %d\n", data_type);
buffer_consume(buf, value_len);
}
if (value != NULL) {
#ifdef AUDIO_SCAN_DEBUG
if ( data_type == 0 ) {
DEBUG_TRACE(" %s / type %d / %s\n", SvPVX(key), data_type, SvPVX(value));
}
else if ( data_type > 1 ) {
DEBUG_TRACE(" %s / type %d / %d\n", SvPVX(key), data_type, (int)SvIV(value));
}
else {
DEBUG_TRACE(" %s / type %d / <binary>\n", SvPVX(key), data_type);
}
#endif
_store_tag( tags, key, value );
}
}
buffer_free(&utf8_buf);
}
void
_parse_file_properties(Buffer *buf, HV *info, HV *tags)
{
GUID file_id;
uint64_t file_size;
uint64_t creation_date;
uint64_t data_packets;
uint64_t play_duration;
uint64_t send_duration;
uint64_t preroll;
uint32_t flags;
uint32_t min_packet_size;
uint32_t max_packet_size;
uint32_t max_bitrate;
uint8_t broadcast;
uint8_t seekable;
buffer_get_guid(buf, &file_id);
my_hv_store(
info, "file_id", newSVpvf( "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
file_id.Data1, file_id.Data2, file_id.Data3,
file_id.Data4[0], file_id.Data4[1], file_id.Data4[2], file_id.Data4[3],
file_id.Data4[4], file_id.Data4[5], file_id.Data4[6], file_id.Data4[7]
)
);
file_size = buffer_get_int64_le(buf);
creation_date = buffer_get_int64_le(buf);
data_packets = buffer_get_int64_le(buf);
play_duration = buffer_get_int64_le(buf);
send_duration = buffer_get_int64_le(buf);
preroll = buffer_get_int64_le(buf);
flags = buffer_get_int_le(buf);
min_packet_size = buffer_get_int_le(buf);
max_packet_size = buffer_get_int_le(buf);
max_bitrate = buffer_get_int_le(buf);
broadcast = flags & 0x01 ? 1 : 0;
seekable = flags & 0x02 ? 1 : 0;
if ( !broadcast ) {
creation_date = (creation_date - 116444736000000000ULL) / 10000000;
play_duration /= 10000;
send_duration /= 10000;
// Don't overwrite the actual file size we found from stat
//my_hv_store( info, "file_size", newSViv(file_size) );
my_hv_store( info, "creation_date", newSViv(creation_date) );
my_hv_store( info, "data_packets", newSViv(data_packets) );
my_hv_store( info, "play_duration_ms", newSViv(play_duration) );
my_hv_store( info, "send_duration_ms", newSViv(send_duration) );
// Calculate actual song duration
my_hv_store( info, "song_length_ms", newSViv( play_duration - preroll ) );
}
my_hv_store( info, "preroll", newSViv(preroll) );
my_hv_store( info, "broadcast", newSViv(broadcast) );
my_hv_store( info, "seekable", newSViv(seekable) );
my_hv_store( info, "min_packet_size", newSViv(min_packet_size) );
my_hv_store( info, "max_packet_size", newSViv(max_packet_size) );
my_hv_store( info, "max_bitrate", newSViv(max_bitrate) );
}
void
_parse_stream_properties(Buffer *buf, HV *info, HV *tags)
{
GUID stream_type;
GUID ec_type;
uint64_t time_offset;
uint32_t type_data_len;
uint32_t ec_data_len;
uint16_t flags;
uint16_t stream_number;
Buffer type_data_buf;
buffer_get_guid(buf, &stream_type);
buffer_get_guid(buf, &ec_type);
time_offset = buffer_get_int64_le(buf);
type_data_len = buffer_get_int_le(buf);
ec_data_len = buffer_get_int_le(buf);
flags = buffer_get_short_le(buf);
stream_number = flags & 0x007f;
// skip reserved bytes
buffer_consume(buf, 4);
// type-specific data
buffer_init(&type_data_buf, type_data_len);
buffer_append(&type_data_buf, buffer_ptr(buf), type_data_len);
buffer_consume(buf, type_data_len);
// skip error-correction data
buffer_consume(buf, ec_data_len);
if ( IsEqualGUID(&stream_type, &ASF_Audio_Media) ) {
uint8_t is_wma = 0;
uint16_t codec_id;
_store_stream_info( stream_number, info, newSVpv("stream_type", 0), newSVpv("ASF_Audio_Media", 0) );
// Parse WAVEFORMATEX data
codec_id = buffer_get_short_le(&type_data_buf);
switch (codec_id) {
case 0x000a:
case 0x0161:
case 0x0162:
case 0x0163:
is_wma = 1;
break;
}
_store_stream_info( stream_number, info, newSVpv("codec_id", 0), newSViv(codec_id) );
_store_stream_info( stream_number, info, newSVpv("channels", 0), newSViv( buffer_get_short_le(&type_data_buf) ) );
_store_stream_info( stream_number, info, newSVpv("samplerate", 0), newSViv( buffer_get_int_le(&type_data_buf) ) );
_store_stream_info( stream_number, info, newSVpv("avg_bytes_per_sec", 0), newSViv( buffer_get_int_le(&type_data_buf) ) );
_store_stream_info( stream_number, info, newSVpv("block_alignment", 0), newSViv( buffer_get_short_le(&type_data_buf) ) );
_store_stream_info( stream_number, info, newSVpv("bits_per_sample", 0), newSViv( buffer_get_short_le(&type_data_buf) ) );
// Read WMA-specific data
if (is_wma) {
buffer_consume(&type_data_buf, 2);
_store_stream_info( stream_number, info, newSVpv("samples_per_block", 0), newSViv( buffer_get_int_le(&type_data_buf) ) );
_store_stream_info( stream_number, info, newSVpv("encode_options", 0), newSViv( buffer_get_short_le(&type_data_buf) ) );
_store_stream_info( stream_number, info, newSVpv("super_block_align", 0), newSViv( buffer_get_int_le(&type_data_buf) ) );
}
}
else if ( IsEqualGUID(&stream_type, &ASF_Video_Media) ) {
_store_stream_info( stream_number, info, newSVpv("stream_type", 0), newSVpv("ASF_Video_Media", 0) );
DEBUG_TRACE("type_data_len: %d\n", type_data_len);
// Read video-specific data
_store_stream_info( stream_number, info, newSVpv("width", 0), newSVuv( buffer_get_int_le(&type_data_buf) ) );
_store_stream_info( stream_number, info, newSVpv("height", 0), newSVuv( buffer_get_int_le(&type_data_buf) ) );
// Skip format size, width, height, reserved
buffer_consume(&type_data_buf, 17);
_store_stream_info( stream_number, info, newSVpv("bpp", 0), newSVuv( buffer_get_short_le(&type_data_buf) ) );
_store_stream_info( stream_number, info, newSVpv("compression_id", 0), newSVpv( buffer_ptr(&type_data_buf), 4 ) );
// Rest of the data does not seem to apply to video
}
else if ( IsEqualGUID(&stream_type, &ASF_Command_Media) ) {
_store_stream_info( stream_number, info, newSVpv("stream_type", 0), newSVpv("ASF_Command_Media", 0) );
}
else if ( IsEqualGUID(&stream_type, &ASF_JFIF_Media) ) {
_store_stream_info( stream_number, info, newSVpv("stream_type", 0), newSVpv("ASF_JFIF_Media", 0) );
// type-specific data
_store_stream_info( stream_number, info, newSVpv("width", 0), newSVuv( buffer_get_int_le(&type_data_buf) ) );
_store_stream_info( stream_number, info, newSVpv("height", 0), newSVuv( buffer_get_int_le(&type_data_buf) ) );
}
else if ( IsEqualGUID(&stream_type, &ASF_Degradable_JPEG_Media) ) {
_store_stream_info( stream_number, info, newSVpv("stream_type", 0), newSVpv("ASF_Degradable_JPEG_Media", 0) );
// XXX: type-specific data (section 9.4.2)
}
else if ( IsEqualGUID(&stream_type, &ASF_File_Transfer_Media) ) {
_store_stream_info( stream_number, info, newSVpv("stream_type", 0), newSVpv("ASF_File_Transfer_Media", 0) );
// XXX: type-specific data (section 9.5)
}
else if ( IsEqualGUID(&stream_type, &ASF_Binary_Media) ) {
_store_stream_info( stream_number, info, newSVpv("stream_type", 0), newSVpv("ASF_Binary_Media", 0) );
// XXX: type-specific data (section 9.5)
}
if ( IsEqualGUID(&ec_type, &ASF_No_Error_Correction) ) {
_store_stream_info( stream_number, info, newSVpv("error_correction_type", 0), newSVpv("ASF_No_Error_Correction", 0) );
}
else if ( IsEqualGUID(&ec_type, &ASF_Audio_Spread) ) {
_store_stream_info( stream_number, info, newSVpv("error_correction_type", 0), newSVpv("ASF_Audio_Spread", 0) );
}
_store_stream_info( stream_number, info, newSVpv("time_offset", 0), newSViv(time_offset) );
_store_stream_info( stream_number, info, newSVpv("encrypted", 0), newSVuv( flags & 0x8000 ? 1 : 0 ) );
buffer_free(&type_data_buf);
}
int
_parse_header_extension(Buffer *buf, uint64_t len, HV *info, HV *tags)
{
int ext_size;
GUID hdr;
uint64_t hdr_size;
// Skip reserved fields
buffer_consume(buf, 18);
ext_size = buffer_get_int_le(buf);
// Sanity check ext size
// Must be 0 or 24+, and 46 less than header extension object size
if (ext_size > 0) {
if (ext_size < 24) {
return 0;
}
if (ext_size != len - 46) {
return 0;
}
}
DEBUG_TRACE(" size: %d\n", ext_size);
while (ext_size > 0) {
buffer_get_guid(buf, &hdr);
hdr_size = buffer_get_int64_le(buf);
ext_size -= hdr_size;
if ( IsEqualGUID(&hdr, &ASF_Metadata) ) {
DEBUG_TRACE(" Metadata\n");
_parse_metadata(buf, info, tags);
}
else if ( IsEqualGUID(&hdr, &ASF_Extended_Stream_Properties) ) {
DEBUG_TRACE(" Extended_Stream_Properties size %llu\n", hdr_size);
_parse_extended_stream_properties(buf, hdr_size, info, tags);
}
else if ( IsEqualGUID(&hdr, &ASF_Language_List) ) {
DEBUG_TRACE(" Language_List\n");
_parse_language_list(buf, info, tags);
}
else if ( IsEqualGUID(&hdr, &ASF_Advanced_Mutual_Exclusion) ) {
DEBUG_TRACE(" Advanced_Mutual_Exclusion\n");
_parse_advanced_mutual_exclusion(buf, info, tags);
}
else if ( IsEqualGUID(&hdr, &ASF_Metadata_Library) ) {
DEBUG_TRACE(" Metadata_Library\n");
_parse_metadata_library(buf, info, tags);
}
else if ( IsEqualGUID(&hdr, &ASF_Index_Parameters) ) {
DEBUG_TRACE(" Index_Parameters\n");
_parse_index_parameters(buf, info, tags);
}
else if ( IsEqualGUID(&hdr, &ASF_Compatibility) ) {
// reserved for future use, just ignore
DEBUG_TRACE(" Skipping Compatibility\n");
buffer_consume(buf, 2);
}
else if ( IsEqualGUID(&hdr, &ASF_Padding) ) {
// skip padding
DEBUG_TRACE(" Skipping Padding\n");
buffer_consume(buf, hdr_size - 24);
}
else if ( IsEqualGUID(&hdr, &ASF_Index_Placeholder) ) {
// skip undocumented placeholder
DEBUG_TRACE(" Skipping Index_Placeholder\n");
buffer_consume(buf, hdr_size - 24);
}
else {
// Unhandled
PerlIO_printf(PerlIO_stderr(), " ** Unhandled extended header: ");
print_guid(hdr);
PerlIO_printf(PerlIO_stderr(), "size: %llu\n", hdr_size);
buffer_consume(buf, hdr_size - 24);
}
}
return 1;
}
void
_parse_metadata(Buffer *buf, HV *info, HV *tags)
{
Buffer utf8_buf;
uint16_t count = buffer_get_short_le(buf);
buffer_init(&utf8_buf, 32);
while ( count-- ) {
uint16_t stream_number;
uint16_t name_len;
uint16_t data_type;
uint32_t data_len;
SV *key = NULL;
SV *value = NULL;
// Skip reserved
buffer_consume(buf, 2);
stream_number = buffer_get_short_le(buf);
name_len = buffer_get_short_le(buf);
data_type = buffer_get_short_le(buf);
data_len = buffer_get_int_le(buf);
buffer_clear(&utf8_buf);
buffer_get_utf16_as_utf8(buf, &utf8_buf, name_len, UTF16_BYTEORDER_LE);
key = newSVpv( buffer_ptr(&utf8_buf), 0 );
sv_utf8_decode(key);
if (data_type == TYPE_UNICODE) {
buffer_clear(&utf8_buf);
buffer_get_utf16_as_utf8(buf, &utf8_buf, data_len, UTF16_BYTEORDER_LE);
value = newSVpv( buffer_ptr(&utf8_buf), 0 );
sv_utf8_decode(value);
}
else if (data_type == TYPE_BYTE) {
value = newSVpvn( buffer_ptr(buf), data_len );
buffer_consume(buf, data_len);
}
else if (data_type == TYPE_BOOL || data_type == TYPE_WORD) {
value = newSViv( buffer_get_short_le(buf) );
}
else if (data_type == TYPE_DWORD) {
value = newSViv( buffer_get_int_le(buf) );
}
else if (data_type == TYPE_QWORD) {
value = newSViv( buffer_get_int64_le(buf) );
}
else {
DEBUG_TRACE("Unknown metadata data type %d\n", data_type);
buffer_consume(buf, data_len);
}
if (value != NULL) {
#ifdef AUDIO_SCAN_DEBUG
if ( data_type == 0 ) {
DEBUG_TRACE(" %s / type %d / stream_number %d / %s\n", SvPVX(key), data_type, stream_number, SvPVX(value));
}
else if ( data_type > 1 ) {
DEBUG_TRACE(" %s / type %d / stream_number %d / %d\n", SvPVX(key), data_type, stream_number, (int)SvIV(value));
}
else {
DEBUG_TRACE(" %s / type %d / stream_number %d / <binary>\n", SvPVX(key), stream_number, data_type);
}
#endif
// If stream_number is available, store the data with the stream info
if (stream_number > 0) {
_store_stream_info( stream_number, info, key, value );
}
else {
my_hv_store_ent( info, key, value );
SvREFCNT_dec(key);
}
}
}
buffer_free(&utf8_buf);
}
void
_parse_extended_stream_properties(Buffer *buf, uint64_t len, HV *info, HV *tags)
{
uint64_t start_time = buffer_get_int64_le(buf);
uint64_t end_time = buffer_get_int64_le(buf);
uint32_t bitrate = buffer_get_int_le(buf);
uint32_t buffer_size = buffer_get_int_le(buf);
uint32_t buffer_fullness = buffer_get_int_le(buf);
uint32_t alt_bitrate = buffer_get_int_le(buf);
uint32_t alt_buffer_size = buffer_get_int_le(buf);
uint32_t alt_buffer_fullness = buffer_get_int_le(buf);
uint32_t max_object_size = buffer_get_int_le(buf);
uint32_t flags = buffer_get_int_le(buf);
uint16_t stream_number = buffer_get_short_le(buf);
uint16_t lang_id = buffer_get_short_le(buf);
uint64_t avg_time_per_frame = buffer_get_int64_le(buf);
uint16_t stream_name_count = buffer_get_short_le(buf);
uint16_t payload_ext_count = buffer_get_short_le(buf);
len -= 88;
if (start_time > 0) {
_store_stream_info( stream_number, info, newSVpv("start_time", 0), newSViv(start_time) );
}
if (end_time > 0) {
_store_stream_info( stream_number, info, newSVpv("end_time", 0), newSViv(end_time) );
}
_store_stream_info( stream_number, info, newSVpv("bitrate", 0), newSViv(bitrate) );
_store_stream_info( stream_number, info, newSVpv("buffer_size", 0), newSViv(buffer_size) );
_store_stream_info( stream_number, info, newSVpv("buffer_fullness", 0), newSViv(buffer_fullness) );
_store_stream_info( stream_number, info, newSVpv("alt_bitrate", 0), newSViv(alt_bitrate) );
_store_stream_info( stream_number, info, newSVpv("alt_buffer_size", 0), newSViv(alt_buffer_size) );
_store_stream_info( stream_number, info, newSVpv("alt_buffer_fullness", 0), newSViv(alt_buffer_fullness) );
_store_stream_info( stream_number, info, newSVpv("alt_buffer_size", 0), newSViv(alt_buffer_size) );
_store_stream_info( stream_number, info, newSVpv("max_object_size", 0), newSViv(max_object_size) );
if ( flags & 0x01 )
_store_stream_info( stream_number, info, newSVpv("flag_reliable", 0), newSViv(1) );
if ( flags & 0x02 )
_store_stream_info( stream_number, info, newSVpv("flag_seekable", 0), newSViv(1) );
if ( flags & 0x04 )
_store_stream_info( stream_number, info, newSVpv("flag_no_cleanpoint", 0), newSViv(1) );
if ( flags & 0x08 )
_store_stream_info( stream_number, info, newSVpv("flag_resend_cleanpoints", 0), newSViv(1) );
_store_stream_info( stream_number, info, newSVpv("language_index", 0), newSViv(lang_id) );
if (avg_time_per_frame > 0) {
// XXX: can't get this to divide properly (?!)
//_store_stream_info( stream_number, info, newSVpv("avg_time_per_frame", 0), newSVuv(avg_time_per_frame / 10000) );
}
while ( stream_name_count-- ) {
uint16_t stream_name_len;
// stream_name_lang_id
buffer_consume(buf, 2);
stream_name_len = buffer_get_short_le(buf);
DEBUG_TRACE("stream_name_len: %d\n", stream_name_len);
// XXX, store this?
buffer_consume(buf, stream_name_len);
len -= 4 + stream_name_len;
}
while ( payload_ext_count-- ) {
// Skip
uint32_t payload_len;
buffer_consume(buf, 18);
payload_len = buffer_get_int_le(buf);
buffer_consume(buf, payload_len);
len -= 22 + payload_len;
}
if (len) {
// Anything left over means we have an embedded Stream Properties Object
DEBUG_TRACE(" embedded Stream_Properties, size %llu\n", len);
buffer_consume(buf, 24);
_parse_stream_properties(buf, info, tags);
}
}
void
_parse_language_list(Buffer *buf, HV *info, HV *tags)
{
Buffer utf8_buf;
AV *list = newAV();
uint16_t count = buffer_get_short_le(buf);
buffer_init(&utf8_buf, 32);
while ( count-- ) {
SV *value;
uint8_t len = buffer_get_char(buf);
buffer_clear(&utf8_buf);
buffer_get_utf16_as_utf8(buf, &utf8_buf, len, UTF16_BYTEORDER_LE);
value = newSVpv( buffer_ptr(&utf8_buf), 0 );
sv_utf8_decode(value);
av_push( list, value );
}
buffer_free(&utf8_buf);
my_hv_store( info, "language_list", newRV_noinc( (SV*)list ) );
}
void
_parse_advanced_mutual_exclusion(Buffer *buf, HV *info, HV *tags)
{
GUID mutex_type;
uint16_t count;
AV *mutex_list;
HV *mutex_hv = newHV();
SV *mutex_type_sv;
AV *mutex_streams = newAV();
buffer_get_guid(buf, &mutex_type);
count = buffer_get_short_le(buf);
if ( IsEqualGUID(&mutex_type, &ASF_Mutex_Language) ) {
mutex_type_sv = newSVpv( "ASF_Mutex_Language", 0 );
}
else if ( IsEqualGUID(&mutex_type, &ASF_Mutex_Bitrate) ) {
mutex_type_sv = newSVpv( "ASF_Mutex_Bitrate", 0 );
}
else {
mutex_type_sv = newSVpv( "ASF_Mutex_Unknown", 0 );
}
while ( count-- ) {
av_push( mutex_streams, newSViv( buffer_get_short_le(buf) ) );
}
my_hv_store_ent( mutex_hv, mutex_type_sv, newRV_noinc( (SV *)mutex_streams ) );
SvREFCNT_dec(mutex_type_sv);
if ( !my_hv_exists( info, "mutex_list" ) ) {
mutex_list = newAV();
av_push( mutex_list, newRV_noinc( (SV *)mutex_hv ) );
my_hv_store( info, "mutex_list", newRV_noinc( (SV *)mutex_list ) );
}
else {
SV **entry = my_hv_fetch( info, "mutex_list" );
if (entry != NULL) {
mutex_list = (AV *)SvRV(*entry);
}
else {
return;
}
av_push( mutex_list, newRV_noinc( (SV *)mutex_hv ) );
}
}
void
_parse_codec_list(Buffer *buf, HV *info, HV *tags)
{
Buffer utf8_buf;
uint32_t count;
AV *list = newAV();
buffer_init(&utf8_buf, 32);
// Skip reserved
buffer_consume(buf, 16);
count = buffer_get_int_le(buf);
while ( count-- ) {
HV *codec_info = newHV();
uint16_t name_len;
uint16_t desc_len;
SV *name = NULL;
SV *desc = NULL;
uint16_t codec_type = buffer_get_short_le(buf);
switch (codec_type) {
case 0x0001:
my_hv_store( codec_info, "type", newSVpv("Video", 0) );
break;
case 0x0002:
my_hv_store( codec_info, "type", newSVpv("Audio", 0) );
break;
default:
my_hv_store( codec_info, "type", newSVpv("Unknown", 0) );
}
// Unlike other objects, these lengths are the
// "number of Unicode chars", not bytes, so we need to double it
name_len = buffer_get_short_le(buf) * 2;
buffer_clear(&utf8_buf);
buffer_get_utf16_as_utf8(buf, &utf8_buf, name_len, UTF16_BYTEORDER_LE);
name = newSVpv( buffer_ptr(&utf8_buf), 0 );
sv_utf8_decode(name);
my_hv_store( codec_info, "name", name );
// Set a 'lossless' flag in info if Lossless codec is used
if ( strstr( buffer_ptr(&utf8_buf), "Lossless" ) ) {
my_hv_store( info, "lossless", newSVuv(1) );
}
desc_len = buffer_get_short_le(buf) * 2;
buffer_clear(&utf8_buf);
buffer_get_utf16_as_utf8(buf, &utf8_buf, desc_len, UTF16_BYTEORDER_LE);
desc = newSVpv( buffer_ptr(&utf8_buf), 0 );
sv_utf8_decode(desc);
my_hv_store( codec_info, "description", desc );
// Skip info
buffer_consume(buf, buffer_get_short_le(buf));
av_push( list, newRV_noinc( (SV *)codec_info ) );
}
buffer_free(&utf8_buf);
my_hv_store( info, "codec_list", newRV_noinc( (SV *)list ) );
}
void
_parse_stream_bitrate_properties(Buffer *buf, HV *info, HV *tags)
{
uint16_t count = buffer_get_short_le(buf);
while ( count-- ) {
uint16_t stream_number = buffer_get_short_le(buf) & 0x007f;
_store_stream_info( stream_number, info, newSVpv("avg_bitrate", 0), newSViv( buffer_get_int_le(buf) ) );
}
}
void
_parse_metadata_library(Buffer *buf, HV *info, HV *tags)
{
Buffer utf8_buf;
uint16_t count = buffer_get_short_le(buf);
buffer_init(&utf8_buf, 32);
while ( count-- ) {
SV *key = NULL;
SV *value = NULL;
uint16_t stream_number, name_len, data_type;
uint32_t data_len;
#ifdef AUDIO_SCAN_DEBUG
uint16_t lang_index = buffer_get_short_le(buf);
#else
buffer_consume(buf, 2);
#endif
stream_number = buffer_get_short_le(buf);
name_len = buffer_get_short_le(buf);
data_type = buffer_get_short_le(buf);
data_len = buffer_get_int_le(buf);
buffer_clear(&utf8_buf);
buffer_get_utf16_as_utf8(buf, &utf8_buf, name_len, UTF16_BYTEORDER_LE);
key = newSVpv( buffer_ptr(&utf8_buf), 0 );
sv_utf8_decode(key);
if (data_type == TYPE_UNICODE) {
buffer_clear(&utf8_buf);
buffer_get_utf16_as_utf8(buf, &utf8_buf, data_len, UTF16_BYTEORDER_LE);
value = newSVpv( buffer_ptr(&utf8_buf), 0 );
sv_utf8_decode(value);
}
else if (data_type == TYPE_BYTE) {
// handle picture data
if ( !strcmp( SvPVX(key), "WM/Picture" ) ) {
value = _parse_picture(buf);
}
else {
value = newSVpvn( buffer_ptr(buf), data_len );
buffer_consume(buf, data_len);
}
}
else if (data_type == TYPE_BOOL || data_type == TYPE_WORD) {
value = newSViv( buffer_get_short_le(buf) );
}
else if (data_type == TYPE_DWORD) {
value = newSViv( buffer_get_int_le(buf) );
}
else if (data_type == TYPE_QWORD) {
value = newSViv( buffer_get_int64_le(buf) );
}
else if (data_type == TYPE_GUID) {
GUID g;
buffer_get_guid(buf, &g);
value = newSVpvf(
"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
g.Data1, g.Data2, g.Data3,
g.Data4[0], g.Data4[1], g.Data4[2], g.Data4[3],
g.Data4[4], g.Data4[5], g.Data4[6], g.Data4[7]
);
}
else {
PerlIO_printf(PerlIO_stderr(), "Unknown metadata library data type %d\n", data_type);
buffer_consume(buf, data_len);
}
if (value != NULL) {
#ifdef AUDIO_SCAN_DEBUG
if ( data_type == 0 || data_type == 6 ) {
DEBUG_TRACE(" %s / type %d / lang_index %d / stream_number %d / %s\n", SvPVX(key), data_type, lang_index, stream_number, SvPVX(value));
}
else if ( data_type > 1 ) {
DEBUG_TRACE(" %s / type %d / lang_index %d / stream_number %d / %d\n", SvPVX(key), data_type, lang_index, stream_number, (int)SvIV(value));
}
else {
DEBUG_TRACE(" %s / type %d / lang_index %d / stream_number %d / <binary>\n", SvPVX(key), lang_index, stream_number, data_type);
}
#endif
// If stream_number is available, store the data with the stream info
// XXX: should store lang_index?
if (stream_number > 0) {
_store_stream_info( stream_number, info, key, value );
}
else {
_store_tag( tags, key, value );
}
}
}
buffer_free(&utf8_buf);
}
void
_parse_index_parameters(Buffer *buf, HV *info, HV *tags)
{
uint16_t count;
my_hv_store( info, "index_entry_interval", newSViv( buffer_get_int_le(buf) ) );
count = buffer_get_short_le(buf);
while ( count-- ) {
uint16_t stream_number = buffer_get_short_le(buf);
uint16_t index_type = buffer_get_short_le(buf);
switch (index_type) {
case 0x0001:
_store_stream_info( stream_number, info, newSVpv("index_type", 0), newSVpv("Nearest Past Data Packet", 0) );
break;
case 0x0002:
_store_stream_info( stream_number, info, newSVpv("index_type", 0), newSVpv("Nearest Past Media Object", 0) );
break;
case 0x0003:
_store_stream_info( stream_number, info, newSVpv("index_type", 0), newSVpv("Nearest Past Cleanpoint", 0) );
break;
default:
_store_stream_info( stream_number, info, newSVpv("index_type", 0), newSViv(index_type) );
}
}
}
void
_store_stream_info(int stream_number, HV *info, SV *key, SV *value )
{
AV *streams;
HV *streaminfo;
uint8_t found = 0;
int i = 0;
if ( !my_hv_exists( info, "streams" ) ) {
// Create
streams = newAV();
my_hv_store( info, "streams", newRV_noinc( (SV*)streams ) );
}
else {
SV **entry = my_hv_fetch( info, "streams" );
if (entry != NULL) {
streams = (AV *)SvRV(*entry);
}
else {
return;
}
}
if (streams != NULL) {
// Find entry for this stream number
for (i = 0; av_len(streams) >= 0 && i <= av_len(streams); i++) {
SV **stream = av_fetch(streams, i, 0);
if (stream != NULL) {
SV **sn;
streaminfo = (HV *)SvRV(*stream);
sn = my_hv_fetch( streaminfo, "stream_number" );
if (sn != NULL) {
if ( SvIV(*sn) == stream_number ) {
// XXX: if item exists, create array
my_hv_store_ent( streaminfo, key, value );
SvREFCNT_dec(key);
found = 1;
break;
}
}
}
}
if ( !found ) {
// New stream number
streaminfo = newHV();
my_hv_store( streaminfo, "stream_number", newSViv(stream_number) );
my_hv_store_ent( streaminfo, key, value );
SvREFCNT_dec(key);
av_push( streams, newRV_noinc( (SV *)streaminfo ) );
}
}
}
void
_store_tag(HV *tags, SV *key, SV *value)
{
// if key exists, create array
if ( my_hv_exists_ent( tags, key ) ) {
SV **entry = my_hv_fetch( tags, SvPVX(key) );
if (entry != NULL) {
if ( SvROK(*entry) && SvTYPE(SvRV(*entry)) == SVt_PVAV ) {
av_push( (AV *)SvRV(*entry), value );
}
else {
// A non-array entry, convert to array.
AV *ref = newAV();
av_push( ref, newSVsv(*entry) );
av_push( ref, value );
my_hv_store_ent( tags, key, newRV_noinc( (SV*)ref ) );
}
}
}
else {
my_hv_store_ent( tags, key, value );
}
SvREFCNT_dec(key);
}
/*
int
_parse_index_objects(PerlIO *infile, int index_size, uint64_t audio_offset, Buffer *buf, HV *info, HV *tags)
{
GUID tmp;
uint64_t size;
while (index_size > 0) {
// Make sure we have enough data
if ( !_check_buf(infile, buf, 24, ASF_BLOCK_SIZE) ) {
return 0;
}
buffer_get_guid(buf, &tmp);
size = buffer_get_int64_le(buf);
if ( !_check_buf(infile, buf, size - 24, ASF_BLOCK_SIZE) ) {
return 0;
}
if ( IsEqualGUID(&tmp, &ASF_Index) ) {
DEBUG_TRACE("Index size %d\n", size);
_parse_index(buf, audio_offset, info, tags);
}
else if ( IsEqualGUID(&tmp, &ASF_Simple_Index) ) {
DEBUG_TRACE("Skipping Simple_Index size %d\n", size);
// Simple Index is used for video files only
buffer_consume(buf, size - 24);
}
else {
// Unhandled GUID
PerlIO_printf(PerlIO_stderr(), "** Unhandled Index GUID: ");
print_guid(tmp);
PerlIO_printf(PerlIO_stderr(), "size: %lu\n", size);
buffer_consume(buf, size - 24);
}
index_size -= size;
}
return 1;
}
// XXX: These don't really seem that useful for seeking after all...
void
_parse_index(Buffer *buf, uint64_t audio_offset, HV *info, HV *tags)
{
AV *specs = newAV();
AV *blocks = newAV();
uint16_t spec_count;
uint32_t blocks_count;
int i;
// Skip index entry time interval, it is read from Index Parameters
buffer_consume(buf, 4);
spec_count = buffer_get_short_le(buf);
blocks_count = buffer_get_int_le(buf);
for (i = 0; i < spec_count; i++) {
// Add stream number
av_push( specs, newSViv( buffer_get_short_le(buf) ) );
// Skip index type, this is already read from Index Parameters
buffer_consume(buf, 2);
}
my_hv_store( info, "index_specifiers", newRV_noinc( (SV *)specs ) );
// XXX: if blocks_count > 1 the file is larger than 2^32 bytes and
// our stored index data is not valid. This seems unlikely to occur in real life...
while ( blocks_count-- ) {
AV *offsets[spec_count];
uint32_t entry_count = buffer_get_int_le(buf);
for (i = 0; i < spec_count; i++) {
uint64_t block_pos;
// Init offsets array for each spec_count
offsets[i] = newAV();
block_pos = buffer_get_int64_le(buf);
av_push( blocks, newSViv(block_pos) );
}
my_hv_store( info, "index_blocks", newRV_noinc( (SV *)blocks ) );
while ( entry_count-- ) {
for (i = 0; i < spec_count; i++) {
// These are byte offsets relative to start of the first data packet,
// so we add audio_offset here. An additional 50 bytes are added
// to skip past the top-level Data Object
av_push( offsets[i], newSViv( audio_offset + buffer_get_int_le(buf) ) );
}
}
if (spec_count == 1) {
my_hv_store( info, "index_offsets", newRV_noinc( (SV *)offsets[0] ) );
}
else {
// Nested arrays, one per spec_count (stream)
AV *offset_list = newAV();
for (i = 0; i < spec_count; i++) {
av_push( offset_list, newRV_noinc( (SV *)offsets[i] ) );
}
my_hv_store( info, "index_offsets", newRV_noinc( (SV *)offset_list ) );
}
}
}
*/
void
_parse_content_encryption(Buffer *buf, HV *info, HV *tags)
{
uint32_t protection_type_len;
uint32_t key_len;
uint32_t license_url_len;
// Skip secret data
buffer_consume(buf, buffer_get_int_le(buf));
protection_type_len = buffer_get_int_le(buf);
my_hv_store( info, "drm_protection_type", newSVpvn( buffer_ptr(buf), protection_type_len - 1 ) );
buffer_consume(buf, protection_type_len);
key_len = buffer_get_int_le(buf);
my_hv_store( info, "drm_key", newSVpvn( buffer_ptr(buf), key_len - 1 ) );
buffer_consume(buf, key_len);
license_url_len = buffer_get_int_le(buf);
my_hv_store( info, "drm_license_url", newSVpvn( buffer_ptr(buf), license_url_len - 1 ) );
buffer_consume(buf, license_url_len);
}
void
_parse_extended_content_encryption(Buffer *buf, HV *info, HV *tags)
{
uint32_t len = buffer_get_int_le(buf);
Buffer utf8_buf;
SV *value;
unsigned char *tmp_ptr = buffer_ptr(buf);
if ( tmp_ptr[0] == 0xFF && tmp_ptr[1] == 0xFE ) {
buffer_consume(buf, 2);
buffer_init(&utf8_buf, len - 2);
buffer_get_utf16_as_utf8(buf, &utf8_buf, len - 2, UTF16_BYTEORDER_LE);
value = newSVpv( buffer_ptr(&utf8_buf), 0 );
sv_utf8_decode(value);
buffer_free(&utf8_buf);
my_hv_store( info, "drm_data", value );
}
else {
buffer_consume(buf, len);
}
}
void
_parse_script_command(Buffer *buf, HV *info, HV *tags)
{
uint16_t command_count;
uint16_t type_count;
Buffer utf8_buf;
AV *types = newAV();
AV *commands = newAV();
buffer_init(&utf8_buf, 32);
// Skip reserved
buffer_consume(buf, 16);
command_count = buffer_get_short_le(buf);
type_count = buffer_get_short_le(buf);
while ( type_count-- ) {
SV *value;
uint16_t len = buffer_get_short_le(buf);
buffer_clear(&utf8_buf);
buffer_get_utf16_as_utf8(buf, &utf8_buf, len * 2, UTF16_BYTEORDER_LE);
value = newSVpv( buffer_ptr(&utf8_buf), 0 );
sv_utf8_decode(value);
av_push( types, value );
}
while ( command_count-- ) {
HV *command = newHV();
SV *value;
uint32_t pres_time = buffer_get_int_le(buf);
uint16_t type_index = buffer_get_short_le(buf);
uint16_t name_len = buffer_get_short_le(buf);
if (name_len) {
buffer_clear(&utf8_buf);
buffer_get_utf16_as_utf8(buf, &utf8_buf, name_len * 2, UTF16_BYTEORDER_LE);
value = newSVpv( buffer_ptr(&utf8_buf), 0 );
sv_utf8_decode(value);
my_hv_store( command, "command", value );
}
my_hv_store( command, "time", newSVuv(pres_time) );
my_hv_store( command, "type", newSVuv(type_index) );
av_push( commands, newRV_noinc( (SV *)command ) );
}
buffer_free(&utf8_buf);
my_hv_store( info, "script_types", newRV_noinc( (SV *)types ) );
my_hv_store( info, "script_commands", newRV_noinc( (SV *)commands ) );
}
SV *
_parse_picture(Buffer *buf)
{
char *tmp_ptr;
uint16_t mime_len = 2; // to handle double-null
uint16_t desc_len = 2;
uint32_t image_len;
SV *mime;
SV *desc;
HV *picture = newHV();
Buffer utf8_buf;
buffer_init(&utf8_buf, 32);
my_hv_store( picture, "image_type", newSVuv( buffer_get_char(buf) ) );
image_len = buffer_get_int_le(buf);
// MIME type is a double-null-terminated UTF-16 string
tmp_ptr = buffer_ptr(buf);
while ( tmp_ptr[0] != '\0' || tmp_ptr[1] != '\0' ) {
mime_len += 2;
tmp_ptr += 2;
}
buffer_get_utf16_as_utf8(buf, &utf8_buf, mime_len, UTF16_BYTEORDER_LE);
mime = newSVpv( buffer_ptr(&utf8_buf), 0 );
sv_utf8_decode(mime);
my_hv_store( picture, "mime_type", mime );
// Description is a double-null-terminated UTF-16 string
tmp_ptr = buffer_ptr(buf);
while ( tmp_ptr[0] != '\0' || tmp_ptr[1] != '\0' ) {
desc_len += 2;
tmp_ptr += 2;
}
buffer_clear(&utf8_buf);
buffer_get_utf16_as_utf8(buf, &utf8_buf, desc_len, UTF16_BYTEORDER_LE);
desc = newSVpv( buffer_ptr(&utf8_buf), 0 );
sv_utf8_decode(desc);
my_hv_store( picture, "description", desc );
if ( _env_true("AUDIO_SCAN_NO_ARTWORK") ) {
my_hv_store( picture, "image", newSVuv(image_len) );
}
else {
my_hv_store( picture, "image", newSVpvn( buffer_ptr(buf), image_len ) );
}
buffer_consume(buf, image_len);
buffer_free(&utf8_buf);
return newRV_noinc( (SV *)picture );
}
// offset is in ms
// Based on some code from Rockbox
int
asf_find_frame(PerlIO *infile, char *file, int offset)
{
int frame_offset = -1;
uint32_t audio_offset, max_packet_size, max_bitrate, file_size;
uint64_t data_packets;
uint32_t packet_num;
uint8_t count = 0;
// We need to read all tags first to get some data we need to calculate
HV *info = newHV();
HV *tags = newHV();
get_asf_metadata(infile, file, info, tags);
if ( !my_hv_exists(info, "data_packets") ) {
// Can't seek in file without data_packets value
goto out;
}
file_size = SvIV( *(my_hv_fetch( info, "file_size" )) );
audio_offset = SvIV( *(my_hv_fetch( info, "audio_offset" )) );
data_packets = SvIV( *(my_hv_fetch( info, "data_packets" )) );
max_packet_size = SvIV( *(my_hv_fetch( info, "max_packet_size" )) );
max_bitrate = SvIV( *(my_hv_fetch( info, "max_bitrate" )) );
packet_num = ( ( ((int64_t)offset) * (max_bitrate>>3) ) / max_packet_size / 1000 ) + 1;
if (packet_num > data_packets) {
packet_num = data_packets;
}
frame_offset = audio_offset + ( (packet_num - 1) * max_packet_size);
// Double-check above packet, make sure we have the right one
// with a timestamp within our desired range
DEBUG_TRACE("Looking for packet with timestamp %d (total packets %llu)\n", offset, data_packets);
while ( count < 10 ) {
int time, duration;
if ( frame_offset > file_size - 64 ) {
DEBUG_TRACE(" Offset too large: %d\n", frame_offset);
break;
}
time = _timestamp(infile, frame_offset, &duration);
DEBUG_TRACE(" Timestamp for packet %d at %d: %d, duration: %d\n", packet_num, frame_offset, time, duration);
if (time < 0) {
DEBUG_TRACE(" Invalid timestamp, giving up\n");
break;
}
if ( time + duration >= offset && time <= offset ) {
DEBUG_TRACE(" Found packet at offset %d\n", frame_offset);
break;
}
else {
int delta = offset - time;
DEBUG_TRACE(" Wrong packet, delta: %d\n", delta);
// XXX: too simplistic, but the re-calc code from Rockbox is not correct either
if (delta > 0) {
packet_num++;
}
else {
packet_num--;
}
if (packet_num < 1 || packet_num > data_packets) {
// Probably we were passed an offset out of bounds for this file
frame_offset = -1;
break;
}
frame_offset = audio_offset + ( (packet_num - 1) * max_packet_size);
count++;
DEBUG_TRACE(" Try #%d packet %d at frame offset %d\n", count, packet_num, frame_offset);
}
}
out:
// Don't leak
SvREFCNT_dec(info);
SvREFCNT_dec(tags);
return frame_offset;
}
// Return the timestamp of the data packet at offset
int
_timestamp(PerlIO *infile, int offset, int *duration)
{
Buffer asf_buf;
int timestamp = -1;
uint8_t tmp;
DEBUG_TRACE("Seeking to %d to read timestamp\n", offset);
if ((PerlIO_seek(infile, offset, SEEK_SET)) != 0) {
return -1;
}
buffer_init(&asf_buf, 64);
if ( !_check_buf(infile, &asf_buf, 64, 64) ) {
goto out;
}
// Read Error Correction Flags
tmp = buffer_get_char(&asf_buf);
if (tmp & 0x80) {
// Skip error correction data
buffer_consume(&asf_buf, tmp & 0x0f);
// Read Length Type Flags
tmp = buffer_get_char(&asf_buf);
}
else {
// The byte we already read is Length Type Flags
}
// Skip Property Flags, Packet Length, Sequence, Padding Length
buffer_consume( &asf_buf,
1 + GETLEN2b((tmp >> 1) & 0x03) +
GETLEN2b((tmp >> 3) & 0x03) +
GETLEN2b((tmp >> 5) & 0x03)
);
timestamp = buffer_get_int_le(&asf_buf);
*duration = buffer_get_short_le(&asf_buf);
out:
buffer_free(&asf_buf);
return timestamp;
}