/*---------------------------------------------------------------------------- Copies part of one .mp3 file to a new .mp3 file Chris Giese http://SisAndHappy.com/ChrisGiese/ This code is public domain (no copyright). You can do whatever you want with it. This code is ANSI C (!) 25 Apr 2017: infile, outfile, start time, stop time all on command line 5 Sep 2016: initial version; compile-time arguments, no command line :( TO DO: - Figure out maximum frame size; which should be the size of buf[] in main() - Bit-reservoir stuff. The first frame of audio in the output file produced by this code is probably busted, but most MP3 players just skip over invalid frames (i.e. this code works but it's not correct). - Extend code to support MPEG-1 layers 1 and 2 (no bit reservoir; and number of samples per frame is different from 1152). ----------------------------------------------------------------------------*/ #include #include #include /***************************************************************************** 'ss' means 'syncsafe' (or 'synchsafe'): b7=0 always so that eleven consecutive '1' bits -- the sync bits at the start of an MPEG audio frame -- never appear in the data. What's really stupid is that only the total length of the ID3 data is escaped like this; not the frame lengths. Also, the tag data is not syncsafe unless unsynchronization is used, but few MP3 files actually use it. It should've been mandatory. Old naive MP3 players (e.g. maplay 1.2+ for Windows) will crash when they try to 'play' the tag data. Also stupid is the fact that something already existed that could've been used for this: base-64 encoding; as used by MIME. *****************************************************************************/ static unsigned long read_be28_ss(const void *buf0) { const unsigned char *buf = buf0; unsigned long rv; rv = (buf[0] & 0x7F); rv <<= 7; rv |= (buf[1] & 0x7F); rv <<= 7; rv |= (buf[2] & 0x7F); rv <<= 7; rv |= (buf[3] & 0x7F); return rv; } /****************************************************************************/ int main(int arg_c, char *arg_v[]) { static const unsigned freq_table[2][3] = { { 22050u, 24000u, 16000u /* MPEG-2 */ }, { 44100u, 48000u, 32000u /* MPEG-1 */ } }; static const unsigned bitrate_table[2][3][15] = { {/* MPEG-2 */ {0,32,48,56,64,80,96,112,128,144,160,176,192,224,256}, {0,8,16,24,32,40,48,56,64,80,96,112,128,144,160}, {0,8,16,24,32,40,48,56,64,80,96,112,128,144,160} },{/* MPEG-1 */ {0,32,64,96,128,160,192,224,256,288,320,352,384,416,448}, {0,32,48,56,64,80,96,112,128,160,192,224,256,320,384}, {0,32,40,48,56,64,80,96,112,128,160,192,224,256,320} } }; /* static const char *mode_table[4] = { "stereo", "joint stereo", "dual-channel", "mono" };*/ static unsigned char buf[4096]; /* xxx - how big? */ /**/ double start_time, stop_time, curr_time = 0.0; /* seconds */ FILE *in_file, *out_file = NULL; char *s; if(arg_c != 5) { printf("Copies part of one MP3 file to another. Usage:\n" "\tcutmp3 infile.mp3 outfile.mp3 start_seconds end_seconds\n"); return 1; } /* get the start and stop time from the command line */ start_time = strtod(arg_v[3], &s); if(start_time < 0 || arg_v[3] == s) { printf("Error: invalid start time\n"); return 4; } stop_time = strtod(arg_v[4], &s); if(stop_time < 0 || stop_time < start_time || arg_v[4] == s) { printf("Error: invalid end time\n"); return 4; } /* open input file */ if((in_file = fopen(arg_v[1], "rb")) == NULL) { printf("Error: can't open input file '%s'\n", arg_v[1]); return 2; } /* error if we can't read 10 bytes */ if(fread(buf, 1, 10, in_file) != 10) READ: { printf("Error: unexpected end of input file '%s'\n", arg_v[1]); fclose(in_file); /* can jump here from loop below, so we need this: */ if(out_file != NULL) fclose(out_file); return 3; } printf("Input file '%s':\n", arg_v[1]); /* if ID3 data detected, skip it */ if(!strncmp(buf, "ID3", 3)) { unsigned long id3_len; id3_len = read_be28_ss(&buf[6]); printf("ID3 version=%u.%u, flags=0x%02X, content length=" "%lu bytes\n", buf[3], buf[4], buf[5], id3_len); fseek(in_file, id3_len, SEEK_SET); } else fseek(in_file, 0, SEEK_SET); /*printf( "MPEG audio data:\n" "Pos MPEG Layer kbps kHz Mode Num. bytes in frame\n" "------- ---- ----- ---- ----- ------------ --------------\n"); */ while(1) { unsigned char mpeg_ver, layer, has_crc, bitrate_index; unsigned char freq_index, has_pad_byte;/*, mode; */ unsigned bitrate, freq, num_bytes; unsigned long pos; int i; /* Look for sync pattern at start of frame: eleven '1' bits aligned on byte boundary. */ do { do { if((i = fgetc(in_file)) == EOF) goto DONE; } while(i != 0xFF); /* eight '1' bits */ if((i = fgetc(in_file)) == EOF) goto DONE; } while((i & 0xE0) != 0xE0); /* three '1' bits */ buf[0] = i; pos = ftell(in_file) - 2; /* read remaining two bytes of header */ if(fread(&buf[1], 1, 2, in_file) != 2) goto READ; /* ID: 1=MPEG-1, 0=MPEG-2 */ mpeg_ver = (buf[0] >> 3) & 1; /* layer: 0=?, 1=layer III, 2=layer II, 3=layer I */ layer = (buf[0] >> 1) & 3; /* 0=16-bit CRC follows header */ has_crc = !(buf[0] & 1); bitrate_index = (buf[1] >> 4) & 15; freq_index = (buf[1] >> 2) & 3; has_pad_byte = (buf[1] >> 1) & 1; /* mode = (buf[2] >> 6) & 3; */ /* Validate. The test for freq_index==3 will also detect a stream of 0xFF bytes (i.e. garbage). */ if(layer == 0 || bitrate_index == 0 || freq_index == 3) { fseek(in_file, -1, SEEK_CUR); continue; } bitrate = bitrate_table[mpeg_ver][3 - layer][bitrate_index]; freq = freq_table[mpeg_ver][freq_index]; /* total number of bytes in this frame */ num_bytes = (unsigned)((mpeg_ver ? 144000L : 72000L) * bitrate / freq); if(has_pad_byte) num_bytes++; if(has_crc) num_bytes += 2; /*printf("%7lu %4u %5u %4u %5u %12s %u\n", pos, 2 - mpeg_ver, 4 - layer, bitrate, freq, mode_table[mode], num_bytes); */ if(curr_time >= stop_time) break; /* copy input to output */ if(curr_time >= start_time) { if(out_file == NULL) { printf("Opening output file '%s'...\n", arg_v[2]); if((out_file = fopen(arg_v[2], "wb")) == NULL) { printf("Error: can't open output " "file '%s'\n", arg_v[2]); fclose(in_file); return 2; } } fseek(in_file, pos, SEEK_SET); if(fread(buf, 1, num_bytes, in_file) != num_bytes) goto READ; if(fwrite(buf, 1, num_bytes, out_file) != num_bytes) { printf("Error writing output " "file '%s'\n", arg_v[2]); fclose(out_file); fclose(in_file); return 3; } } else fseek(in_file, pos + num_bytes, SEEK_SET); /* layer 3: sec/frame = 1152 samples per frame / freq (=samp/sec) */ curr_time += 1152.0 / freq; } DONE: if(out_file != NULL) { if(curr_time < stop_time) printf("Warning: input file '%s' is shorter\n" "\t(%-.1f seconds) than stop time %-.1f " "seconds\n", arg_v[1], curr_time, stop_time); else printf("Success\n"); fclose(out_file); } else printf("Error: specified start time %-.1f seconds exceeds " "length\n\t(%-.1f) of input file '%s'\n", start_time, curr_time, arg_v[1]); fclose(in_file); return 0; }