btparser/cparser/MP3Template.bt

499 lines
15 KiB
Plaintext
Raw Normal View History

//--------------------------------------
//--- 010 Editor v1.3.2 Binary Template
//
// File: MP3Template.bt
// Author: Ivan Getta (ivanitto@ukr.net)
// Purpose: MP3 files analysis and smart editing
//
// Template version: 1.0
// Release date: 2005 Feb 18
// Licence: free
//
//--- Template features ----------------
//
// 1) MPEG frames support:
// * MPEG-1, MPEG-2
// * Layer 1,2,3
// 2) ID3v1 tags support
// 3) ID3v1.1 tags support
// 4) ID3v2.3 tags support
//
//--- Notes ----------------------------
//
// TODO:
// 1) MPEG 2.5 support
// 2) Resolve known bugs (see below)
//
// KNOWN BUGS (STILL PRESENT):
// 1) Incorrect frame size detection for MPEG 1.0 layer 3
// mono files with low bitrate (for example 56k, 64k).
// Frame size must be detected twice long.
// 2) Mp3pro files have some problems
//
//--- References -----------------------
//
// 1. "010 Editor templates"
// http://www.sweetscape.com/010editor/templates.html
//
// 2. "The private life of MP3 frames"
// http://www.id3.org/mp3frame.html
//
// 3. "ID3 made easy (ID3v1 & ID3v1.1)"
// http://www.id3.org/id3v1.html
//
// 4. "ID3 tag version 2.3.0 (Informal standard)"
// http://www.id3.org/id3v2.3.0.html
//
//--------------------------------------
local uint32 bitrate, frame_size, sampling_freq, frames_count = 0;
local quad frame_header_offset, seek_pos, sum_bitrate = 0;
local short data;
local byte was_bad_sync, id3v1_tag_found = 0;
local ubyte buf[3];
enum <ubyte> ID3_GENRES
{
Blues, Classic_Rock, Country, Dance, Disco, Funk, Grunge, Hip_Hop, // 7
Jazz, Metal, New_Age, Oldies, Other, Pop, R_and_B, Rap, // 15
Reggae, Rock, Techno, Industrial, Alternative, Ska, Death_Metal, Pranks, // 23
Soundtrack, Euro_Techno, Ambient, Trip_Hop, Vocal, Jazz_Funk, Fusion, Trance, // 31
Classical, Instrumental, Acid, House, Game, Sound_Clip, Gospel, Noise, // 39
AlternRock, Bass, Soul, Punk, Space, Meditative, Instrumental_Pop, Instrumental_Rock, // 47
Ethnic, Gothic, Darkwave, Techno_Industrial, Electronic, Pop_Folk, Eurodance, Dream, // 55
Southern_Rock, Comedy, Cult, Gangsta, Top_40, Christian_Rap, Pop_Funk, Jungle, // 63
Native_American, Cabaret, New_Wave, Psychadelic, Rave, Showtunes, Trailer, Lo_Fi, // 71
Tribal, Acid_Punk, Acid_Jazz, Polka, Retro, Musical, Rock_n_Roll, Hard_Rock, // 79
Folk, Folk_Rock, National_Folk, Swing, Fast_Fusion, Bebob, Latin, Revival, // 87
Celtic, Bluegrass, Avantgarde, Gothic_Rock,
Progressive_Rock, Psychedelic_Rock, Symphonic_Rock, Slow_Rock, // 95
Big_Band, Chorus, Easy_Listening, Acoustic, Humour, Speech, Chanson, Opera, // 103
Chamber_Music, Sonata, Symphony, Booty_Bass, Primus, Porn_Groove, Satire, Slow_Jam, // 111
Club, Tango, Samba, Folklore, Ballad, Power_Ballad, Rhythmic_Soul, Freestyle, // 119
Duet, Punk_Rock, Drum_Solo, A_capella, Euro_House, Dance_Hall, Goa, Drum_and_Bass, // 127
Club_House, Hardcore, Terror, Indie, BritPop, Negerpunk, Polsk_Punk, Beat, // 135
Christian, Heavy_Metal, Black_Metal, Crossover,
Contemporary, Christian_Rock, Merengue, Salsa, Thrash_Metal, Anime, JPop, Synthpop // 147
};
struct ID3v1_TAG
{
DisplayFormatDecimal();
SetBackColor(0x33BC55);
char id[3]; // always must be "TAG"
SetBackColor(0x48E048);
char title[30];
SetBackColor(0x5DE45D);
char artist[30];
SetBackColor(0x72E872);
char album[30];
SetBackColor(0x87EC87);
char year[4];
if ( ReadByte(FTell()+28) == 0 && ReadByte(FTell()+29) != 0 )
{
// We have ID3v1.1 tag
SetBackColor(0x9CF09C);
char comment[28];
SetBackColor(0xB1F4B1);
byte zero;
SetBackColor(0xC6F8C6);
ubyte track;
}
else
{
// We have ID3v1.0 tag
SetBackColor(0x9CF09C);
char comment[30];
}
SetBackColor(0xDBFCDB);
ID3_GENRES genre;
};
struct ID3v2_HEADER
{
SetBackColor(0x91C4FF);
char head[3]; // always must be "ID3" ($49 44 33)
DisplayFormatDecimal();
ubyte ver_major; // this byte will never be $FF
ubyte ver_revision; // this byte will never be $FF
struct FLAGS {
ubyte UNSYNCHRONISATION_USED : 1;
ubyte EXTENDED_HEADER_PRESENT : 1;
ubyte EXPERIMENTAL_TAG : 1;
ubyte : 5;
} flags;
DisplayFormatHex();
ubyte size[4]; // Is the size of the complete tag after unsynchronisation,
// including padding, excluding the header but not excluding
// the extended header (total tag size - 10). Most
// significant bit (bit 7) of each byte is set to zero
};
struct ID3v2_EXTENDED_HEADER
{
SetBackColor(0xA1D4FF);
DisplayFormatDecimal();
uint32 size; // extended header size, excluding this 'size' field
uint16 FLAG_CRC_PRESENT : 1; // extended header flags
uint16 : 15; //
uint32 padding_sz;
if (FLAG_CRC_PRESENT)
{
DisplayFormatHex();
uint32 crc;
}
};
struct ID3v2_FRAME
{
char id[4]; // four alpha chars
DisplayFormatDecimal();
uint32 size; // frame size without frame header
struct FRAME_FLAGS {
uint16 TAG_ALTER_PRESERV : 1;
uint16 FILE_ALTER_PRESERV : 1;
uint16 READ_ONLY_FRAME : 1;
uint16 : 5;
uint16 COMPRESSED_FRAME : 1;
uint16 ENCRYPTED_FRAME : 1;
uint16 GROUP_MEMBER_FRAME : 1;
uint16 : 5;
} flags;
if (id[0] == 'T')
{
// frame contains text related data
if ( ReadByte(FTell()) == 0 && size > 1)
{
byte id_asciiz_str;
char frame_data [size - 1];
}
else
char frame_data [size];
}
else
{
DisplayFormatHex();
ubyte frame_data [size];
}
};
struct ID3v2_TAG
{
ID3v2_HEADER hdr;
// calculating real size of the ID3v2 tag
local uint32 tag_sz = hdr.size[0];
tag_sz <<= 7;
tag_sz |= hdr.size[1];
tag_sz <<= 7;
tag_sz |= hdr.size[2];
tag_sz <<= 7;
tag_sz |= hdr.size[3];
//
// An ID3v2 tag header can be detected with the following pattern:
// $49 44 33 yy yy xx zz zz zz zz
// Where yy is less than $FF, xx is the 'flags' byte and zz is less than $80.
//
if (hdr.ver_major == 0xFF || hdr.ver_revision == 0xFF ||
hdr.size[0] >= 0x80 || hdr.size[1] >= 0x80 ||
hdr.size[2] >= 0x80 || hdr.size[3] >= 0x80)
{
Printf("MP3: warning: invalid ID3v2 tag header\n");
}
else
{
if (hdr.ver_major != 3 || hdr.flags.UNSYNCHRONISATION_USED || hdr.flags.EXPERIMENTAL_TAG)
{
Printf("MP3: warning: skipping unsupported ID3v2.%d tag\n", hdr.ver_major);
SetBackColor(0xA9DCFF);
DisplayFormatHex();
ubyte id3v2_data[tag_sz];
}
else
{
if ( hdr.flags.EXTENDED_HEADER_PRESENT )
ID3v2_EXTENDED_HEADER ext_hdr;
// Now reading ID3v2 frames
// A tag must contain at least one frame. A frame must be
// at least 1 byte big, excluding the header.
//
local uint32 frame_color = 0xC9FCFF;
do
{
SetBackColor(frame_color);
ID3v2_FRAME tf;
frame_color -= 0x020200;
}
while ( FTell() < tag_sz + sizeof(hdr) && ReadByte(FTell()) != 0 );
SetBackColor(0x99CCFF);
ubyte id3v2_padding [ tag_sz + sizeof(hdr) - FTell() ];
}
}
};
// 32-bit MPEG frame header octets:
// AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM
//
struct MPEG_HEADER
{
SetBackColor(0xCC99FF);
DisplayFormatHex();
uint32 frame_sync : 12; // A
DisplayFormatDecimal();
uint32 mpeg_id : 1; // B
uint32 layer_id : 2; // C
uint32 protection_bit : 1; // D
uint32 bitrate_index : 4; // E
uint32 frequency_index : 2; // F
uint32 padding_bit : 1; // G
uint32 private_bit : 1; // H
uint32 channel_mode : 2; // I
uint32 mode_extension : 2; // J
uint32 copyright : 1; // K
uint32 original : 1; // L
uint32 emphasis : 2; // M
if (protection_bit == 0)
{
DisplayFormatHex();
uint16 checksum;
}
};
struct MPEG_FRAME
{
MPEG_HEADER mpeg_hdr;
// define frame bitrate
bitrate = 0;
// header sanity check
if (mpeg_hdr.frame_sync < 0xFFE || mpeg_hdr.layer_id == 0 ||
mpeg_hdr.bitrate_index == 0 || mpeg_hdr.bitrate_index == 15 ||
mpeg_hdr.frequency_index == 3)
{
Printf("MP3: warning: invalid MPEG header in frame at offset 0x%X\n",
FTell() - 4 - (mpeg_hdr.protection_bit==0 ? 2:0) );
// Try to find MPEG header starting from offset (current - 2)
FSeek( FTell() - 2 );
}
else
{
if (mpeg_hdr.layer_id == 3) // MPEG-1,2 Layer 1
{
bitrate = (uint32)mpeg_hdr.bitrate_index<<5;
}
else
{
if (mpeg_hdr.layer_id == 2) // MPEG-1,2 Layer 2
{
bitrate = (uint32)mpeg_hdr.bitrate_index==1 ? 32 :
(1 << 5+(uint32)mpeg_hdr.bitrate_index/4) +
( ((uint32)mpeg_hdr.bitrate_index&3) <<
3+(uint32)mpeg_hdr.bitrate_index/4 );
}
else
{
if (mpeg_hdr.mpeg_id == 1) // MPEG-1 (Layer 3)
{
bitrate = (1 << 5+((uint32)mpeg_hdr.bitrate_index-1)/4) +
( ((uint32)mpeg_hdr.bitrate_index-1&3) <<
3+((uint32)mpeg_hdr.bitrate_index-1)/4);
}
else // (MPEG-2) (Layer 3)
{
bitrate = (uint32)mpeg_hdr.bitrate_index<4 ?
8*(uint32)mpeg_hdr.bitrate_index :
(1<<4+(uint32)mpeg_hdr.bitrate_index/4) +
(
((uint32)mpeg_hdr.bitrate_index&3)==0 ? 0 :
((uint32)mpeg_hdr.bitrate_index&3)==1 ?
(1<<4+(uint32)mpeg_hdr.bitrate_index/4) :
((uint32)mpeg_hdr.bitrate_index&3)==2 ?
(1<<4+(uint32)mpeg_hdr.bitrate_index/4) +
((1<<4+(uint32)mpeg_hdr.bitrate_index/4)>>1) :
(1<<4+(uint32)mpeg_hdr.bitrate_index/4) -
((1<<4+(uint32)mpeg_hdr.bitrate_index/4)>>2)
);
}
}
}
}
if (bitrate != 0)
{
local uint16 freq[3];
freq[0] = 2205;
freq[1] = 2400;
freq[2] = 1600;
sampling_freq = freq[mpeg_hdr.frequency_index];
if (mpeg_hdr.mpeg_id == 1) // if MPEG-1
sampling_freq <<= 1;
frame_size = (bitrate * 14400) / sampling_freq;
if (mpeg_hdr.channel_mode == 3)
frame_size >>= 1;
frame_size -= 4 + (mpeg_hdr.protection_bit==0 ? 2:0) - mpeg_hdr.padding_bit;
frame_header_offset = FTell() - 4 - (mpeg_hdr.protection_bit==0 ? 2:0);
// read frame data
if ( FTell() + frame_size > FileSize() )
{
Printf("MP3: warning: cut MPEG frame at end of file (frame header offset = 0x%LX, data length = %u)\n",
frame_header_offset, frame_size);
Printf("MP3: file parsing completed!\nMP3: valid MPEG frames found: %d\n", frames_count);
if (frames_count != 0)
Printf("MP3: average frame bitrate: %d kbit\n", sum_bitrate / frames_count);
return;
}
else
{
DisplayFormatHex();
SetBackColor(0xCCCCFF);
ubyte mpeg_frame_data [ frame_size ];
}
sum_bitrate += bitrate;
frames_count++;
}
};
//--------------------------------------------------------------
BigEndian();
ReadBytes(buf, 0, 3);
if ( ! Strcmp(buf, "ID3") )
{
Printf("MP3: ID3v2 tag found\n");
ID3v2_TAG id3v2_tag;
}
while ( !FEof() && !id3v1_tag_found )
{
// Reading file, until find frame synchronization
seek_pos = FTell();
was_bad_sync = 0;
do
{
data = ReadShort( seek_pos );
if ( (uint16)data == 0x5441 && (uchar)ReadByte(seek_pos+2) == 0x47 )
id3v1_tag_found = 1; // we found "TAG" identifier
if ( !was_bad_sync && (uint16)data < 0xFFE0 && !id3v1_tag_found )
{
Printf("MP3: warning: invalid MPEG frame synchronization at offset 0x%LX\n", seek_pos);
was_bad_sync = 1;
}
seek_pos++;
}
while ( (uint16)data < 0xFFE0 && seek_pos < (FileSize()-1) && !id3v1_tag_found );
if ( (uint16)data >= 0xFFE0 || id3v1_tag_found )
{
FSeek(seek_pos - 1);
}
else
{
Printf("MP3: file parsing completed!\nMP3: valid MPEG frames found: %d\n", frames_count);
if (frames_count != 0)
Printf("MP3: average frame bitrate: %d kbit\n", sum_bitrate / frames_count);
return;
}
if ( !id3v1_tag_found )
{
MPEG_FRAME mf;
if (frames_count == 1 && bitrate)
Printf("MP3: first found MPEG frame parameters:\nMP3:\t- header ofsset: 0x%LX\nMP3:\t- bitrate: %d kbit\nMP3:\t- MPEG-%d layer %d\nMP3:\t- sampling frequency: %d Hz\nMP3:\t- channel mode: %s\nMP3:\t- CRC protected: %s\n",
frame_header_offset,
bitrate,
mf.mpeg_hdr.mpeg_id==1 ? 1:2,
mf.mpeg_hdr.layer_id==1 ? 3 : mf.mpeg_hdr.layer_id==2 ? 2:1,
sampling_freq*10,
mf.mpeg_hdr.channel_mode==3 ? "mono" :
mf.mpeg_hdr.channel_mode==0 ? "stereo" :
mf.mpeg_hdr.channel_mode==1 ? "joint stereo" : "dual channel",
mf.mpeg_hdr.protection_bit==0 ? "Yes" : "No");
}
}
if (id3v1_tag_found)
{
Printf("MP3: ID3v1 tag found\n");
ID3v1_TAG id3v1_tag;
}
if ( !FEof() )
Printf("MP3: warning: there is some unknown extra-data after ID3v1 tag at end of file\n");
Printf("MP3: file parsing completed!\nMP3: valid MPEG frames found: %d\n", frames_count);
if (frames_count != 0)
Printf("MP3: average frame bitrate: %d kbit\n", sum_bitrate / frames_count);