Audio-Scan
view release on metacpan or search on metacpan
genre = buffer_get_char(id3->buf);
if (genre < NGENRES) {
char const *genre_string = _id3_genre_index(genre);
my_hv_store( id3->tags, ID3_FRAME_GENRE, newSVpv(genre_string, 0) );
}
else if (genre < 255) {
my_hv_store( id3->tags, ID3_FRAME_GENRE, newSVpvf("Unknown/%d", genre) );
}
return 1;
}
int
_id3_parse_v2(id3info *id3)
{
int ret = 1;
unsigned char *bptr;
// Verify we have a valid tag
bptr = buffer_ptr(id3->buf);
if ( !(
bptr[3] < 0xff && bptr[4] < 0xff &&
bptr[6] < 0x80 && bptr[7] < 0x80 && bptr[8] < 0x80 && bptr[9] < 0x80
) ) {
PerlIO_printf(PerlIO_stderr(), "Invalid ID3v2 tag in %s\n", id3->file);
return 0;
}
buffer_consume(id3->buf, 3); // ID3
id3->version_major = buffer_get_char(id3->buf);
id3->version_minor = buffer_get_char(id3->buf);
id3->flags = buffer_get_char(id3->buf);
id3->size = 10 + buffer_get_syncsafe(id3->buf, 4);
id3->size_remain = id3->size - 10;
if (id3->flags & ID3_TAG_FLAG_FOOTERPRESENT) {
id3->size += 10;
}
DEBUG_TRACE("Parsing ID3v2.%d.%d tag, flags %x, size %d\n", id3->version_major, id3->version_minor, id3->flags, id3->size);
if (id3->flags & ID3_TAG_FLAG_UNSYNCHRONISATION) {
if (id3->version_major < 4) {
// It's unclear but the v2.4.0-changes document seems to say that v2.4 should
// ignore the tag-level unsync flag and only worry about frame-level unsync
// For v2.2/v2.3, unsync the entire tag. This is unfortunate due to
// increased memory usage but the only way to do it, as frame size values only
// indicate the post-unsync size, so it's not possible to unsync each frame individually
// tested with v2.3-unsync.mp3
if ( !_check_buf(id3->infile, id3->buf, id3->size, id3->size) ) {
ret = 0;
goto out;
}
id3->size_remain = _id3_deunsync( buffer_ptr(id3->buf), id3->size );
DEBUG_TRACE(" Un-synchronized tag, new_size %d\n", id3->size_remain);
my_hv_store( id3->info, "id3_was_unsynced", newSVuv(1) );
}
else {
DEBUG_TRACE(" Ignoring v2.4 tag un-synchronize flag\n");
}
}
if (id3->flags & ID3_TAG_FLAG_EXTENDEDHEADER) {
uint32_t ehsize;
// If the tag is v2.2, this bit is actually the compression bit and the tag should be ignored
if (id3->version_major == 2) {
ret = 0;
goto out;
}
// tested with v2.3-ext-header.mp3
// We don't care about the value of the extended flags or CRC, so just read the size and skip it
ehsize = buffer_get_int(id3->buf);
// ehsize may be invalid, tested with v2.3-ext-header-invalid.mp3
if (ehsize > id3->size_remain - 4) {
warn("Error: Invalid ID3 extended header size (%s)\n", id3->file);
ret = 0;
goto out;
}
DEBUG_TRACE(" Skipping extended header, size %d\n", ehsize);
if ( !_check_buf(id3->infile, id3->buf, ehsize, ID3_BLOCK_SIZE) ) {
ret = 0;
goto out;
}
buffer_consume(id3->buf, ehsize);
id3->size_remain -= ehsize + 4;
}
// Parse frames
while (id3->size_remain > 0) {
//DEBUG_TRACE(" remain: %d\n", id3->size_remain);
if ( !_id3_parse_v2_frame(id3) ) {
break;
}
}
if (id3->version_major < 4) {
// map old year/date/time (TYER/TDAT/TIME) frames to TDRC
// tested in v2.3-xsop.mp3
_id3_convert_tdrc(id3);
}
// Set id3_version info element, which contains all tag versions found
{
SV *version = newSVpvf( "ID3v2.%d.%d", id3->version_major, id3->version_minor );
if ( my_hv_exists(id3->info, "id3_version") ) {
SV **entry = my_hv_fetch(id3->info, "id3_version");
if (entry != NULL) {
sv_catpv( version, ", " );
sv_catsv( version, *entry );
}
}
if (size > id3->size_remain) {
DEBUG_TRACE(" frame size too big, aborting\n");
ret = 0;
goto out;
}
// iTunes writes bad frame IDs such as 'TSA ', these should be run through compat
// as 3-char frames
if (id[3] == ' ') {
id3_compat const *compat;
compat = _id3_compat_lookup((char *)&id, 3);
if (compat && compat->equiv) {
strncpy(id, compat->equiv, 4);
id[4] = 0;
DEBUG_TRACE(" bad iTunes v2.4 tag, compat -> %s\n", id);
}
}
if (flags & ID3_FRAME_FLAG_V24_GROUPINGIDENTITY) {
// tested with v2.4-group-id.mp3
#ifdef AUDIO_SCAN_DEBUG
DEBUG_TRACE(" group_id %d\n", buffer_get_char(id3->buf));
#else
buffer_consume(id3->buf, 1);
#endif
id3->size_remain--;
size--;
}
if (flags & ID3_FRAME_FLAG_V24_ENCRYPTION) {
// tested with v2.4-encrypted-frame.mp3
#ifdef AUDIO_SCAN_DEBUG
DEBUG_TRACE(" encrypted, method %d\n", buffer_get_char(id3->buf));
#else
buffer_consume(id3->buf, 1);
#endif
id3->size_remain--;
size--;
DEBUG_TRACE(" skipping encrypted frame\n");
_id3_skip(id3, size);
id3->size_remain -= size;
goto out;
}
if (flags & ID3_FRAME_FLAG_V24_DATALENGTHINDICATOR) {
decoded_size = buffer_get_syncsafe(id3->buf, 4);
id3->size_remain -= 4;
size -= 4;
DEBUG_TRACE(" data length indicator, size %d\n", decoded_size);
}
if (flags & ID3_FRAME_FLAG_V24_UNSYNCHRONISATION) {
// Special case, do not unsync an APIC frame if not reading artwork,
// FF's are not likely to appear in the part we care about anyway
if ( !strcmp(id, "APIC") && _env_true("AUDIO_SCAN_NO_ARTWORK") ) {
DEBUG_TRACE(" Would un-synchronize APIC frame, but ignoring because of AUDIO_SCAN_NO_ARTWORK\n");
// Reset decoded_size to 0 since we aren't actually decoding.
// XXX this would break if we have a compressed + unsync APIC frame but not very likely in the real world
decoded_size = 0;
id3->tag_data_safe = 0;
}
else {
// tested with v2.4-unsync.mp3
if ( !_check_buf(id3->infile, id3->buf, size, ID3_BLOCK_SIZE) ) {
ret = 0;
goto out;
}
decoded_size = _id3_deunsync( buffer_ptr(id3->buf), size );
unsync_extra = size - decoded_size;
DEBUG_TRACE(" Un-synchronized frame, new_size %d\n", decoded_size);
}
}
if (flags & ID3_FRAME_FLAG_V24_COMPRESSION) {
// tested with v2.4-compressed-frame.mp3
// XXX need test for compressed + unsync
unsigned long tmp_size;
if ( !_check_buf(id3->infile, id3->buf, size, ID3_BLOCK_SIZE) ) {
ret = 0;
goto out;
}
DEBUG_TRACE(" decompressing\n");
Newz(0, decompressed, sizeof(Buffer), Buffer);
buffer_init(decompressed, decoded_size);
tmp_size = decoded_size;
if (
uncompress(buffer_ptr(decompressed), &tmp_size, buffer_ptr(id3->buf), size) != Z_OK
||
tmp_size != decoded_size
) {
DEBUG_TRACE(" unable to decompress frame\n");
buffer_free(decompressed);
Safefree(decompressed);
decompressed = 0;
}
else {
// Hack buffer so it knows we've added data directly
decompressed->end = decoded_size;
}
}
}
}
// Special case, completely skip XHD3 frame (mp3HD) as it will be large
// Also skip NCON, a large tag written by MusicMatch
if ( !strcmp(id, "XHD3") || !strcmp(id, "NCON") ) {
DEBUG_TRACE(" skipping large binary %s frame\n", id);
_id3_skip(id3, size);
id3->size_remain -= size;
goto out;
}
frametype = _id3_frametype_lookup(id, 4);
if (frametype == 0) {
switch ( id[0] ) {
case 'T':
frametype = &id3_frametype_text;
break;
case 'W':
frametype = &id3_frametype_url;
break;
case 'X':
case 'Y':
case 'Z':
buffer_consume(id3->buf, 8);
read += 8;
DEBUG_TRACE(" date, read %d\n", read);
}
break;
case ID3_FIELD_TYPE_INT8: // ETCO, MLLT, SYTC, SYLT, EQU2, RVRB, APIC,
// POPM, RBUF, POSS, COMR, ENCR, GRID, SIGN, ASPI
if (size - read >= 1) {
av_push( framedata, newSViv( buffer_get_char(id3->buf) ) );
read += 1;
DEBUG_TRACE(" int8, read %d\n", read);
}
break;
case ID3_FIELD_TYPE_INT16: // MLLT, RVRB, AENC, ASPI
if (size - read >= 2) {
av_push( framedata, newSViv( buffer_get_short(id3->buf) ) );
read += 2;
DEBUG_TRACE(" int16, read %d\n", read);
}
break;
case ID3_FIELD_TYPE_INT24: // MLLT, RBUF
if (size - read >= 3) {
av_push( framedata, newSViv( buffer_get_int24(id3->buf) ) );
read += 3;
DEBUG_TRACE(" int24, read %d\n", read);
}
break;
case ID3_FIELD_TYPE_INT32: // RBUF, SEEK, ASPI
if (size - read >= 4) {
av_push( framedata, newSViv( buffer_get_int(id3->buf) ) );
read += 4;
DEBUG_TRACE(" int32, read %d\n", read);
}
break;
case ID3_FIELD_TYPE_INT32PLUS: // POPM
if (size - read >= 4) {
av_push( framedata, newSViv( _varint( buffer_ptr(id3->buf), size - read ) ) );
buffer_consume(id3->buf, size - read);
read = size;
DEBUG_TRACE(" int32plus, read %d\n", read);
}
break;
case ID3_FIELD_TYPE_BINARYDATA: // ETCO, MLLT, SYTC, SYLT, RVA2, EQU2, APIC,
// GEOB, AENC, POSS, COMR, ENCR, GRID, PRIV, SIGN, ASPI
// Special handling for APIC tags when in skip_art mode
if (skip_art) {
av_push( framedata, newSVuv(size - read) );
// I don't think it's possible to obtain an APIC offset when a tag has been unsync'ed,
// so we can't support skip_art mode in this case. See v2.3-unsync-apic-bad-offset.mp3
if (id3->flags & ID3_TAG_FLAG_UNSYNCHRONISATION && id3->version_major < 4) {
DEBUG_TRACE(" cannot obtain APIC offset due to v2.3 unsync tag\n");
}
else {
// Record offset of APIC image data too, unless the data needs to be unsynchronized or is empty
if (id3->tag_data_safe && (size - read) > 0)
av_push( framedata, newSVuv(id3->offset + (id3->size - id3->size_remain) + read) );
}
_id3_skip(id3, size - read);
read = size;
}
// Special buffering mode for APIC data, avoids a large buffer allocation
else if (buffer_art) {
uint32_t remain = size - read;
uint32_t chunk_size;
SV *artwork = newSVpv("", 0);
while (read < size) {
if ( !_check_buf(id3->infile, id3->buf, 1, ID3_BLOCK_SIZE) ) {
return 0;
}
chunk_size = remain < buffer_len(id3->buf) ? remain : buffer_len(id3->buf);
read += chunk_size;
remain -= chunk_size;
sv_catpvn( artwork, buffer_ptr(id3->buf), chunk_size );
buffer_consume(id3->buf, chunk_size);
DEBUG_TRACE(" buffered %d bytes of APIC data (remaining %d)\n", chunk_size, remain);
}
av_push( framedata, artwork );
}
// Special handling for RVA2 tags
else if ( !strcmp(id, "RVA2") ) {
read += _id3_parse_rva2(id3, size, framedata);
}
// Special handling for SYLT tags
else if ( !strcmp(id, "SYLT") ) {
read += _id3_parse_sylt(id3, encoding, size - read, framedata);
}
// Special handling for ETCO tags
else if ( !strcmp(id, "ETCO") ) {
read += _id3_parse_etco(id3, size - read, framedata);
}
// All other binary frames, copy as-is
else {
if (size - read > 1) {
av_push( framedata, newSVpvn( buffer_ptr(id3->buf), size - read ) );
buffer_consume(id3->buf, size - read);
read = size;
DEBUG_TRACE(" binarydata, read %d\n", read);
}
}
break;
default:
( run in 2.037 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )