//-------------------------------------- //--- 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 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);