/* Time-stamp: <2007-06-26 00:39:11 jcs> | | Copyright (C) 2002-2005 Jorg Schuler | Part of the gtkpod project. | | URL: http://www.gtkpod.org/ | URL: http://gtkpod.sourceforge.net/ | | 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 | | iTunes and iPod are trademarks of Apple | | This product is not supported/written/published by Apple! | | $Id: mp3file.c 1591 2007-06-26 03:33:25Z tmzullinger $ */ #define LOCALDEBUG 0 /* The code in the first section of this file is taken from the * mp3info (http://www.ibiblio.org/mp3info/) project. Only the code * needed for the playlength calculation has been extracted. */ /* The code in the second section of this file is taken from the * mpg123 code used in xmms-1.2.7 (Input/mpg123). Only the code needed * for the playlength calculation has been extracted. */ /* The code in the last section of this file is original gtkpod * code. */ /**************** * Declarations * ****************/ #include #include /* * Description of each item of the TagList list */ typedef struct _File_Tag File_Tag; typedef struct _GainData GainData; typedef struct _GaplessData GaplessData; struct _File_Tag { gchar *title; /* Title of track */ gchar *artist; /* Artist name */ gchar *album; /* Album name */ gchar *year; /* Year of track */ gchar *trackstring; /* Position of track in the album */ gchar *track_total; /* The number of tracks for the album (ex: 12/20) */ gchar *genre; /* Genre of song */ gchar *comment; /* Comment */ gchar *composer; /* Composer */ guint32 songlen; /* Length of file in ms */ gchar *cdnostring; /* Position of disc in the album */ gchar *cdno_total; /* The number of discs in the album (ex: 1/2) */ gchar *compilation; /* The track is a member of a compilation */ gchar *podcasturl; /* The following are mainly used for podcasts */ gchar *sort_artist; gchar *sort_title; gchar *sort_album; gchar *sort_albumartist; gchar *sort_composer; gchar *description; gchar *podcastrss; gchar *time_released; gchar *subtitle; gchar *BPM; /* beats per minute */ gchar *lyrics; /* does not appear to be the full lyrics -- only used to set the flag 'lyrics_flag' of the Track structure */ }; struct _GainData { guint32 peak_signal; /* LAME Peak Signal * 0x800000 */ gdouble radio_gain; /* RadioGain in dB (as defined by www.replaygain.org) */ gdouble audiophile_gain;/* AudiophileGain in dB (as defined by www.replaygain.org) */ gboolean peak_signal_set; /* has the peak signal been set? */ gboolean radio_gain_set; /* has the radio gain been set? */ gboolean audiophile_gain_set;/* has the audiophile gain been set? */ }; struct _GaplessData { guint32 pregap; /* number of pregap samples */ guint64 samplecount; /* number of actual music samples */ guint32 postgap; /* number of postgap samples */ guint32 gapless_data; /* number of bytes from the first sync frame to the 8th to last frame */ }; /* This code is taken from the mp3info code. Only the code needed for * the playlength calculation has been extracted */ /* mp3tech.c - Functions for handling MP3 files and most MP3 data structure manipulation. Copyright (C) 2000-2001 Cedric Tefft 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., 675 Mass Ave, Cambridge, MA 02139, USA. *************************************************************************** This file is based in part on: * MP3Info 0.5 by Ricardo Cerqueira * MP3Stat 0.9 by Ed Sweetman and Johannes Overmann */ #include #include #include #include #include #include #include #include #include "mp3file.h" #include "charset.h" #include "itdb.h" #include "file.h" #include "misc.h" /* MIN_CONSEC_GOOD_FRAMES defines how many consecutive valid MP3 frames we need to see before we decide we are looking at a real MP3 file */ #define MIN_CONSEC_GOOD_FRAMES 4 #define FRAME_HEADER_SIZE 4 #define MIN_FRAME_SIZE 21 enum VBR_REPORT { VBR_VARIABLE, VBR_AVERAGE, VBR_MEDIAN }; typedef struct { gulong sync; guint version; guint layer; guint crc; guint bitrate; guint freq; guint padding; guint extension; guint mode; guint mode_extension; guint copyright; guint original; guint emphasis; } MP3Header; typedef struct { gchar *filename; FILE *file; off_t datasize; gint header_isvalid; MP3Header header; gint id3_isvalid; gint vbr; float vbr_average; gint milliseconds; gint frames; gint badframes; } MP3Info; /* This is for xmms code */ static guint get_track_time(gchar *path); /* ------------------------------------------------------------ start of first section ------------------------------------------------------------ */ void get_mp3_info(MP3Info *mp3); gint frequencies[3][4] = { {22050,24000,16000,50000}, /* MPEG 2.0 */ {44100,48000,32000,50000}, /* MPEG 1.0 */ {11025,12000,8000,50000} /* MPEG 2.5 */ }; /* "0" added by JCS */ gint bitrate[2][3][16] = { { /* MPEG 2.0 */ {0,32,48,56,64,80,96,112,128,144,160,176,192,224,256,0},/* layer 1 */ {0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,0}, /* layer 2 */ {0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,0} /* layer 3 */ }, { /* MPEG 1.0 */ {0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0},/* layer 1 */ {0,32,48,56,64,80,96,112,128,160,192,224,256,320,384,0}, /* layer 2 */ {0,32,40,48,56,64,80,96,112,128,160,192,224,256,320,0} /* layer 3 */ } }; gint frame_size_index[] = {24000, 72000, 72000}; gchar *mode_text[] = { "stereo", "joint stereo", "dual channel", "mono" }; gchar *emphasis_text[] = { "none", "50/15 microsecs", "reserved", "CCITT J 17" }; static gint mp3file_header_bitrate(MP3Header *h) { return bitrate[h->version & 1][3-h->layer][h->bitrate]; } static gint mp3file_header_frequency(MP3Header *h) { return frequencies[h->version][h->freq]; } gint frame_length(MP3Header *header) { return header->sync == 0xFFE ? (frame_size_index[3-header->layer]*((header->version&1)+1)* mp3file_header_bitrate(header)/(float)mp3file_header_frequency(header))+ header->padding : 1; } /* Get next MP3 frame header. Return codes: positive value = Frame Length of this header 0 = No, we did not retrieve a valid frame header */ gint get_header(FILE *file,MP3Header *header) { guchar buffer[FRAME_HEADER_SIZE]; gint fl; if(fread(&buffer,FRAME_HEADER_SIZE,1,file)<1) { header->sync=0; return 0; } header->sync=(((gint)buffer[0]<<4) | ((gint)(buffer[1]&0xE0)>>4)); if(buffer[1] & 0x10) header->version=(buffer[1] >> 3) & 1; else header->version=2; header->layer=(buffer[1] >> 1) & 3; if (header->layer == 0) { header->layer = 1; /* sanity added by JCS */ } if((header->sync != 0xFFE) || (header->layer != 1)) { header->sync=0; return 0; } header->crc=buffer[1] & 1; header->bitrate=(buffer[2] >> 4) & 0x0F; header->freq=(buffer[2] >> 2) & 0x3; header->padding=(buffer[2] >>1) & 0x1; header->extension=(buffer[2]) & 0x1; header->mode=(buffer[3] >> 6) & 0x3; header->mode_extension=(buffer[3] >> 4) & 0x3; header->copyright=(buffer[3] >> 3) & 0x1; header->original=(buffer[3] >> 2) & 0x1; header->emphasis=(buffer[3]) & 0x3; return ((fl=frame_length(header)) >= MIN_FRAME_SIZE ? fl : 0); } gint sameConstant(MP3Header *h1, MP3Header *h2) { if((*(guint*)h1) == (*(guint*)h2)) return 1; if((h1->version == h2->version ) && (h1->layer == h2->layer ) && (h1->crc == h2->crc ) && (h1->freq == h2->freq ) && (h1->mode == h2->mode ) && (h1->copyright == h2->copyright ) && (h1->original == h2->original ) && (h1->emphasis == h2->emphasis )) return 1; else return 0; } gint get_first_header(MP3Info *mp3, long startpos) { gint k, l=0,c; MP3Header h, h2; long valid_start=0; fseek(mp3->file,startpos,SEEK_SET); while (1) { while((c=fgetc(mp3->file)) != 255 && (c != EOF)); if(c == 255) { ungetc(c,mp3->file); valid_start=ftell(mp3->file); if((l=get_header(mp3->file,&h))) { fseek(mp3->file,l-FRAME_HEADER_SIZE,SEEK_CUR); for(k=1; (k < MIN_CONSEC_GOOD_FRAMES) && (mp3->datasize-ftell(mp3->file) >= FRAME_HEADER_SIZE); k++) { if(!(l=get_header(mp3->file,&h2))) break; if(!sameConstant(&h,&h2)) break; fseek(mp3->file,l-FRAME_HEADER_SIZE,SEEK_CUR); } if(k == MIN_CONSEC_GOOD_FRAMES) { fseek(mp3->file,valid_start,SEEK_SET); memcpy(&(mp3->header),&h2,sizeof(MP3Header)); mp3->header_isvalid=1; return 1; } } } else { return 0; } } return 0; } /* get_next_header() - read header at current position or look for the next valid header if there isn't one at the current position */ gint get_next_header(MP3Info *mp3) { gint l=0,c,skip_bytes=0; MP3Header h; while(1) { while((c=fgetc(mp3->file)) != 255 && (ftell(mp3->file) < mp3->datasize)) skip_bytes++; if(c == 255) { ungetc(c,mp3->file); if((l=get_header(mp3->file,&h))) { if(skip_bytes) mp3->badframes++; fseek(mp3->file,l-FRAME_HEADER_SIZE,SEEK_CUR); return 15-h.bitrate; } else { skip_bytes += FRAME_HEADER_SIZE; } } else { if(skip_bytes) mp3->badframes++; return 0; } } } void get_mp3_info(MP3Info *mp3) { gint frame_type[15]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; float milliseconds=0,total_rate=0; gint frames=0,frame_types=0,frames_so_far=0; gint vbr_median=-1; guint bitrate; gint counter=0; MP3Header header; struct stat filestat; off_t data_start=0; stat(mp3->filename,&filestat); mp3->datasize=filestat.st_size; if(get_first_header(mp3,0L)) { data_start=ftell(mp3->file); while((bitrate=get_next_header(mp3))) { if (bitrate < 15) /* sanity added by JCS */ frame_type[15-bitrate]++; frames++; } memcpy(&header,&(mp3->header),sizeof(MP3Header)); for(counter=0;counter<15;counter++) { if(frame_type[counter]) { float header_bitrate; /* introduced by JCS to speed up */ frame_types++; header.bitrate=counter; frames_so_far += frame_type[counter]; header_bitrate = mp3file_header_bitrate(&header); if (header_bitrate != 0) milliseconds += (float)(8*frame_length(&header)*frame_type[counter])/header_bitrate; total_rate += header_bitrate*frame_type[counter]; if((vbr_median == -1) && (frames_so_far >= frames/2)) vbr_median=counter; } } mp3->milliseconds=(gint)(milliseconds+0.5); mp3->header.bitrate=vbr_median; mp3->vbr_average=total_rate/(float)frames; mp3->frames=frames; if(frame_types > 1) { mp3->vbr=1; } } } /* ------------------------------------------------------------ xmms code ------------------------------------------------------------ */ /* | Changed by Jorg Schuler to | compile with the gtkpod project. 2003/04/01 */ /* This code is taken from the mpg123 code used in xmms-1.2.7 * (Input/mpg123). Only the code needed for the playlength calculation * has been extracted */ #include "mp3file.h" #include #include #define FRAMES_FLAG 0x0001 #define BYTES_FLAG 0x0002 #define TOC_FLAG 0x0004 #define VBR_SCALE_FLAG 0x0008 #define SBLIMIT 32 #define SCALE_BLOCK 12 #define SSLIMIT 18 #define MPG_MD_STEREO 0 #define MPG_MD_JOINT_STEREO 1 #define MPG_MD_DUAL_CHANNEL 2 #define MPG_MD_MONO 3 #define MAXFRAMESIZE 1792 #define real float struct bitstream_info { int bitindex; unsigned char *wordpointer; }; struct bitstream_info bsi; real mpg123_muls[27][64]; /* also used by layer 1 */ int tabsel_123[2][3][16] = { { {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,}}, { {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,}} }; long mpg123_freqs[9] = {44100, 48000, 32000, 22050, 24000, 16000, 11025, 12000, 8000}; /* * structure to receive extracted header */ typedef struct { int frames; /* total bit stream frames from Xing header data */ int bytes; /* total bit stream bytes from Xing header data */ unsigned char toc[100]; /* "table of contents" */ } xing_header_t; struct al_table { short bits; short d; }; struct frame { struct al_table *alloc; int (*synth) (real *, int, unsigned char *, int *); int (*synth_mono) (real *, unsigned char *, int *); #ifdef USE_3DNOW void (*dct36)(real *,real *,real *,real *,real *); #endif int stereo; int jsbound; int single; int II_sblimit; int down_sample_sblimit; int lsf; int mpeg25; int down_sample; int header_change; int lay; int (*do_layer) (struct frame * fr); int error_protection; int bitrate_index; int sampling_frequency; int padding; int extension; int mode; int mode_ext; int copyright; int original; int emphasis; int framesize; /* computed framesize */ }; static guint32 convert_to_header(guint8 * buf) { return (buf[0] << 24) + (buf[1] << 16) + (buf[2] << 8) + buf[3]; } static int mpg123_head_check(unsigned long head) { if ((head & 0xffe00000) != 0xffe00000) return FALSE; if (!((head >> 17) & 3)) return FALSE; if (((head >> 12) & 0xf) == 0xf) return FALSE; if (!((head >> 12) & 0xf)) return FALSE; if (((head >> 10) & 0x3) == 0x3) return FALSE; if (((head >> 19) & 1) == 1 && ((head >> 17) & 3) == 3 && ((head >> 16) & 1) == 1) return FALSE; if ((head & 0xffff0000) == 0xfffe0000) return FALSE; return TRUE; } /* * the code a header and write the information * into the frame structure */ static int mpg123_decode_header(struct frame *fr, unsigned long newhead) { int ssize; if (newhead & (1 << 20)) { fr->lsf = (newhead & (1 << 19)) ? 0x0 : 0x1; fr->mpeg25 = 0; } else { fr->lsf = 1; fr->mpeg25 = 1; } fr->lay = 4 - ((newhead >> 17) & 3); if (fr->mpeg25) { fr->sampling_frequency = 6 + ((newhead >> 10) & 0x3); } else fr->sampling_frequency = ((newhead >> 10) & 0x3) + (fr->lsf * 3); fr->error_protection = ((newhead >> 16) & 0x1) ^ 0x1; fr->bitrate_index = ((newhead >> 12) & 0xf); fr->padding = ((newhead >> 9) & 0x1); fr->extension = ((newhead >> 8) & 0x1); fr->mode = ((newhead >> 6) & 0x3); fr->mode_ext = ((newhead >> 4) & 0x3); fr->copyright = ((newhead >> 3) & 0x1); fr->original = ((newhead >> 2) & 0x1); fr->emphasis = newhead & 0x3; fr->stereo = (fr->mode == MPG_MD_MONO) ? 1 : 2; ssize = 0; if (!fr->bitrate_index) return (0); switch (fr->lay) { case 1: /* fr->do_layer = mpg123_do_layer1; */ /* mpg123_init_layer2(); /\* inits also shared tables with layer1 *\/ */ fr->framesize = (long) tabsel_123[fr->lsf][0][fr->bitrate_index] * 12000; fr->framesize /= mpg123_freqs[fr->sampling_frequency]; fr->framesize = ((fr->framesize + fr->padding) << 2) - 4; break; case 2: /* fr->do_layer = mpg123_do_layer2; */ /* mpg123_init_layer2(); /\* inits also shared tables with layer1 *\/ */ fr->framesize = (long) tabsel_123[fr->lsf][1][fr->bitrate_index] * 144000; fr->framesize /= mpg123_freqs[fr->sampling_frequency]; fr->framesize += fr->padding - 4; break; case 3: /* fr->do_layer = mpg123_do_layer3; */ if (fr->lsf) ssize = (fr->stereo == 1) ? 9 : 17; else ssize = (fr->stereo == 1) ? 17 : 32; if (fr->error_protection) ssize += 2; fr->framesize = (long) tabsel_123[fr->lsf][2][fr->bitrate_index] * 144000; fr->framesize /= mpg123_freqs[fr->sampling_frequency] << (fr->lsf); fr->framesize = fr->framesize + fr->padding - 4; break; default: return (0); } if(fr->framesize > MAXFRAMESIZE) return 0; return 1; } #define GET_INT32BE(b) \ (i = (b[0] << 24) | (b[1] << 16) | b[2] << 8 | b[3], b += 4, i) static int mpg123_get_xing_header(xing_header_t * xing, unsigned char *buf) { int i, head_flags; int id, mode; memset(xing, 0, sizeof(xing_header_t)); /* get selected MPEG header data */ id = (buf[1] >> 3) & 1; mode = (buf[3] >> 6) & 3; buf += 4; /* Skip the sub band data */ if (id) { /* mpeg1 */ if (mode != 3) buf += 32; else buf += 17; } else { /* mpeg2 */ if (mode != 3) buf += 17; else buf += 9; } if (strncmp(buf, "Xing", 4)) return 0; buf += 4; head_flags = GET_INT32BE(buf); if (head_flags & FRAMES_FLAG) xing->frames = GET_INT32BE(buf); if (xing->frames < 1) xing->frames = 1; if (head_flags & BYTES_FLAG) xing->bytes = GET_INT32BE(buf); if (head_flags & TOC_FLAG) { for (i = 0; i < 100; i++) xing->toc[i] = buf[i]; buf += 100; } #ifdef XING_DEBUG for (i = 0; i < 100; i++) { if ((i % 10) == 0) fprintf(stderr, "\n"); fprintf(stderr, " %3d", xing->toc[i]); } #endif return 1; } static double mpg123_compute_tpf(struct frame *fr) { const int bs[4] = {0, 384, 1152, 1152}; double tpf; tpf = bs[fr->lay]; tpf /= mpg123_freqs[fr->sampling_frequency] << (fr->lsf); return tpf; } static double mpg123_compute_bpf(struct frame *fr) { double bpf; switch (fr->lay) { case 1: bpf = tabsel_123[fr->lsf][0][fr->bitrate_index]; bpf *= 12000.0 * 4.0; bpf /= mpg123_freqs[fr->sampling_frequency] << (fr->lsf); break; case 2: case 3: bpf = tabsel_123[fr->lsf][fr->lay - 1][fr->bitrate_index]; bpf *= 144000; bpf /= mpg123_freqs[fr->sampling_frequency] << (fr->lsf); break; default: bpf = 1.0; } return bpf; } unsigned int mpg123_getbits(int number_of_bits) { unsigned long rval; #ifdef DEBUG_GETBITS fprintf(stderr, "g%d", number_of_bits); #endif if(!number_of_bits) return 0; #if 0 check_buffer_range(number_of_bits + bsi.bitindex); #endif { rval = bsi.wordpointer[0]; rval <<= 8; rval |= bsi.wordpointer[1]; rval <<= 8; rval |= bsi.wordpointer[2]; rval <<= bsi.bitindex; rval &= 0xffffff; bsi.bitindex += number_of_bits; rval >>= (24-number_of_bits); bsi.wordpointer += (bsi.bitindex >> 3); bsi.bitindex &= 7; } #ifdef DEBUG_GETBITS fprintf(stderr,":%x ",rval); #endif return rval; } void I_step_one(unsigned int balloc[], unsigned int scale_index[2][SBLIMIT], struct frame *fr) { unsigned int *ba = balloc; unsigned int *sca = (unsigned int *) scale_index; if (fr->stereo) { int i; int jsbound = fr->jsbound; for (i = 0; i < jsbound; i++) { *ba++ = mpg123_getbits(4); *ba++ = mpg123_getbits(4); } for (i = jsbound; i < SBLIMIT; i++) *ba++ = mpg123_getbits(4); ba = balloc; for (i = 0; i < jsbound; i++) { if ((*ba++)) *sca++ = mpg123_getbits(6); if ((*ba++)) *sca++ = mpg123_getbits(6); } for (i = jsbound; i < SBLIMIT; i++) if ((*ba++)) { *sca++ = mpg123_getbits(6); *sca++ = mpg123_getbits(6); } } else { int i; for (i = 0; i < SBLIMIT; i++) *ba++ = mpg123_getbits(4); ba = balloc; for (i = 0; i < SBLIMIT; i++) if ((*ba++)) *sca++ = mpg123_getbits(6); } } void I_step_two(real fraction[2][SBLIMIT], unsigned int balloc[2 * SBLIMIT], unsigned int scale_index[2][SBLIMIT], struct frame *fr) { int i, n; int smpb[2 * SBLIMIT]; /* values: 0-65535 */ int *sample; register unsigned int *ba; register unsigned int *sca = (unsigned int *) scale_index; if (fr->stereo) { int jsbound = fr->jsbound; register real *f0 = fraction[0]; register real *f1 = fraction[1]; ba = balloc; for (sample = smpb, i = 0; i < jsbound; i++) { if ((n = *ba++)) *sample++ = mpg123_getbits(n + 1); if ((n = *ba++)) *sample++ = mpg123_getbits(n + 1); } for (i = jsbound; i < SBLIMIT; i++) if ((n = *ba++)) *sample++ = mpg123_getbits(n + 1); ba = balloc; for (sample = smpb, i = 0; i < jsbound; i++) { if ((n = *ba++)) *f0++ = (real) (((-1) << n) + (*sample++) + 1) * mpg123_muls[n + 1][*sca++]; else *f0++ = 0.0; if ((n = *ba++)) *f1++ = (real) (((-1) << n) + (*sample++) + 1) * mpg123_muls[n + 1][*sca++]; else *f1++ = 0.0; } for (i = jsbound; i < SBLIMIT; i++) { if ((n = *ba++)) { real samp = (((-1) << n) + (*sample++) + 1); *f0++ = samp * mpg123_muls[n + 1][*sca++]; *f1++ = samp * mpg123_muls[n + 1][*sca++]; } else *f0++ = *f1++ = 0.0; } for (i = fr->down_sample_sblimit; i < 32; i++) fraction[0][i] = fraction[1][i] = 0.0; } else { register real *f0 = fraction[0]; ba = balloc; for (sample = smpb, i = 0; i < SBLIMIT; i++) if ((n = *ba++)) *sample++ = mpg123_getbits(n + 1); ba = balloc; for (sample = smpb, i = 0; i < SBLIMIT; i++) { if ((n = *ba++)) *f0++ = (real) (((-1) << n) + (*sample++) + 1) * mpg123_muls[n + 1][*sca++]; else *f0++ = 0.0; } for (i = fr->down_sample_sblimit; i < 32; i++) fraction[0][i] = 0.0; } } static guint get_track_time_file(FILE * file) { guint32 head; guchar tmp[4], *buf; struct frame frm; xing_header_t xing_header; double tpf, bpf; guint32 len; if (!file) return -1; fseek(file, 0, SEEK_SET); if (fread(tmp, 1, 4, file) != 4) return 0; head = convert_to_header(tmp); while (!mpg123_head_check(head)) { head <<= 8; if (fread(tmp, 1, 1, file) != 1) return 0; head |= tmp[0]; } if (mpg123_decode_header(&frm, head)) { buf = g_malloc(frm.framesize + 4); fseek(file, -4, SEEK_CUR); fread(buf, 1, frm.framesize + 4, file); tpf = mpg123_compute_tpf(&frm); if (mpg123_get_xing_header(&xing_header, buf)) { g_free(buf); return ((guint) (tpf * xing_header.frames * 1000)); } g_free(buf); bpf = mpg123_compute_bpf(&frm); fseek(file, 0, SEEK_END); len = ftell(file); fseek(file, -128, SEEK_END); fread(tmp, 1, 3, file); if (!strncmp(tmp, "TAG", 3)) len -= 128; return ((guint) ((guint)(len / bpf) * tpf * 1000)); } return 0; } static guint get_track_time (gchar *path) { guint result = 0; if (path) { FILE *file = fopen (path, "r"); result = get_track_time_file (file); if (file) fclose (file); } return result; } /* libid3tag stuff */ #include #include "prefs.h" #ifndef ID3_FRAME_GROUP #define ID3_FRAME_GROUP "TPE2" #endif static const gchar* id3_get_binary (struct id3_tag *tag, char *frame_name, id3_length_t *len, int index) { const id3_byte_t *binary = NULL; struct id3_frame *frame; union id3_field *field; g_return_val_if_fail (len, NULL); *len = 0; frame = id3_tag_findframe (tag, frame_name, index); #if LOCALDEBUG printf ("frame: %p\n", frame); #endif if (!frame) return NULL; #if LOCALDEBUG printf (" nfields: %d\n", frame->nfields); if (strncmp (frame_name, "APIC", 4) == 0) { field = id3_frame_field (frame, 2); printf (" picture type: %ld\n", field->number.value); } #endif #if 0 /*-----------------*/ /* just to show that this field (before last) contains the d8 ff e0 ff part of the start of a jpeg file when the coverart war embedded by iTunes */ const id3_ucs4_t *string = NULL; gchar *raw = NULL; /* The last field contains the data */ field = id3_frame_field (frame, frame->nfields-2); #if LOCALDEBUG printf (" field: %p\n", field); #endif if (!field) return NULL; #if LOCALDEBUG printf (" type: %d\n", field->type); #endif switch (field->type) { case ID3_FIELD_TYPE_STRING: string = id3_field_getstring (field); break; default: break; } /* ISO_8859_1 is just a "marker" -- most people just drop whatever coding system they are using into it, so we use charset_to_utf8() to convert to utf8 */ if (string) { raw = id3_ucs4_latin1duplicate (string); } #if LOCALDEBUG { FILE *file; printf (" string len: %d\n", raw?strlen(raw):0); file = fopen ("/tmp/folder1.jpg", "w"); fwrite (raw, 1, raw?strlen(raw):0, file); fclose (file); } #endif g_free (raw); /*-----------------*/ #endif /* The last field contains the data */ field = id3_frame_field (frame, frame->nfields-1); #if LOCALDEBUG printf (" field: %p\n", field); #endif if (!field) return NULL; #if LOCALDEBUG printf (" type: %d\n", field->type); #endif switch (field->type) { case ID3_FIELD_TYPE_BINARYDATA: binary = id3_field_getbinarydata(field, len); break; default: break; } #if LOCALDEBUG { FILE *file; printf (" binary len: %ld\n", *len); file = fopen ("/tmp/folder2.jpg", "w"); fwrite (binary, 1, *len, file); fclose (file); } #endif return binary; } static gchar* id3_get_string (struct id3_tag *tag, char *frame_name) { const id3_ucs4_t *string = NULL; const id3_byte_t *binary = NULL; id3_length_t len = 0; struct id3_frame *frame; union id3_field *field; gchar *utf8 = NULL; enum id3_field_textencoding encoding = ID3_FIELD_TEXTENCODING_ISO_8859_1; frame = id3_tag_findframe (tag, frame_name, 0); #if LOCALDEGUB printf ("frame: %p\n", frame); #endif if (!frame) return NULL; /* Find the encoding used for the field */ field = id3_frame_field (frame, 0); #if LOCALDEBUG printf ("field: %p\n", field); printf ("type: %d\n", id3_field_type (field)); #endif if (field && (id3_field_type (field) == ID3_FIELD_TYPE_TEXTENCODING)) { encoding = field->number.value; #if LOCALDEBUG printf ("encoding: %d\n", encoding); #endif } /* The last field contains the data */ field = id3_frame_field (frame, frame->nfields-1); #if LOCALDEBUG printf ("field: %p\n", field); #endif if (!field) return NULL; #if LOCALDEBUG printf ("type: %d\n", field->type); #endif switch (field->type) { case ID3_FIELD_TYPE_STRINGLIST: string = id3_field_getstrings (field, 0); break; case ID3_FIELD_TYPE_STRINGFULL: string = id3_field_getfullstring (field); break; case ID3_FIELD_TYPE_BINARYDATA: binary = id3_field_getbinarydata(field, &len); #if LOCALDEBUG printf ("len: %ld\nbinary: %s\n", len, binary+1); #endif if (len > 0) return charset_to_utf8 (binary+1); break; default: break; } /* printf ("string: %p\n", string); */ if (!string) return NULL; if (strcmp (frame_name, ID3_FRAME_GENRE) == 0) string = id3_genre_name (string); if (encoding == ID3_FIELD_TEXTENCODING_ISO_8859_1) { /* ISO_8859_1 is just a "marker" -- most people just drop whatever coding system they are using into it, so we use charset_to_utf8() to convert to utf8 */ id3_latin1_t *raw = id3_ucs4_latin1duplicate (string); utf8 = charset_to_utf8 (raw); g_free (raw); } else { /* Standard unicode is being used -- we won't have to worry about charsets then. */ utf8 = id3_ucs4_utf8duplicate (string); } return utf8; } static void id3_set_string (struct id3_tag *tag, const char *frame_name, const char *data, enum id3_field_textencoding encoding) { int res; struct id3_frame *frame; union id3_field *field; id3_ucs4_t *ucs4; /* clear the frame, because of bug in libid3tag see http://www.mars.org/mailman/public/mad-dev/2004-October/001113.html */ while ((frame = id3_tag_findframe (tag, frame_name, 0))) { id3_tag_detachframe (tag, frame); id3_frame_delete (frame); } if ((data == NULL) || (strlen(data) == 0)) return; frame = id3_frame_new (frame_name); id3_tag_attachframe (tag, frame); /* Use the specified text encoding */ field = id3_frame_field (frame, 0); id3_field_settextencoding(field, encoding); if (strcmp (frame_name, ID3_FRAME_COMMENT) == 0) { field = id3_frame_field (frame, 3); field->type = ID3_FIELD_TYPE_STRINGFULL; } else { field = id3_frame_field (frame, 1); field->type = ID3_FIELD_TYPE_STRINGLIST; } /* maybe could be optimized see http://www.mars.org/mailman/public/mad-dev/2002-October/000739.html */ if (strcmp (frame_name, ID3_FRAME_GENRE) == 0) { id3_ucs4_t *tmp_ucs4 = id3_utf8_ucs4duplicate ((id3_utf8_t *)data); int index = id3_genre_number (tmp_ucs4); if (index != -1) { /* valid genre -- simply store the genre number */ gchar *tmp = g_strdup_printf("%d", index); ucs4 = id3_latin1_ucs4duplicate (tmp); g_free (tmp); } else { /* oups -- not a valid genre -- save the entire genre string */ if (encoding == ID3_FIELD_TEXTENCODING_ISO_8859_1) { /* we read 'ISO_8859_1' to stand for 'any locale charset' -- most programs seem to work that way */ id3_latin1_t *raw = charset_from_utf8 (data); ucs4 = id3_latin1_ucs4duplicate (raw); g_free (raw); } else { /* Yeah! We use unicode encoding and won't have to worry about charsets */ ucs4 = tmp_ucs4; tmp_ucs4 = NULL; } } g_free (tmp_ucs4); } else { if (encoding == ID3_FIELD_TEXTENCODING_ISO_8859_1) { /* we read 'ISO_8859_1' to stand for 'any locale charset' -- most programs seem to work that way */ id3_latin1_t *raw = charset_from_utf8 (data); ucs4 = id3_latin1_ucs4duplicate (raw); g_free (raw); } else { /* Yeah! We use unicode encoding and won't have to worry about charsets */ ucs4 = id3_utf8_ucs4duplicate ((id3_utf8_t *)data); } } if (strcmp (frame_name, ID3_FRAME_COMMENT) == 0) res = id3_field_setfullstring (field, ucs4); else res = id3_field_setstrings (field, 1, &ucs4); g_free (ucs4); if (res != 0) g_print(_("Error setting ID3 field: %s\n"), frame_name); } /*** * Reads id3v1.x / id3v2 apic data * @returns: TRUE on success, else FALSE. */ static gboolean id3_apic_read (gchar *filename, guchar **image_data, gsize *image_data_len) { struct id3_file *id3file; struct id3_tag *id3tag; g_return_val_if_fail (filename, FALSE); g_return_val_if_fail (image_data, FALSE); g_return_val_if_fail (image_data_len, FALSE); *image_data = NULL; *image_data_len = 0; if (!(id3file = id3_file_open (filename, ID3_FILE_MODE_READONLY))) { gchar *fbuf = charset_to_utf8 (filename); g_print(_("ERROR while opening file: '%s' (%s).\n"), fbuf, g_strerror(errno)); g_free (fbuf); return FALSE; } if ((id3tag = id3_file_tag(id3file))) { id3_length_t len; const guchar *coverart = NULL; int i; struct id3_frame *frame; /* Loop through APIC tags and set coverart. The picture type should be * 3 -- Cover (front), but iTunes has been known to use 0 -- Other. */ for (i = 0; (frame = id3_tag_findframe(id3tag, "APIC", i)) != NULL; i++) { union id3_field *field = id3_frame_field (frame, 2); int pictype = field->number.value; /* printf ("%s: found apic type %d\n", filename, pictype);*/ /* We'll prefer type 3 (cover) over type 0 (other) */ if (pictype == 3) { coverart = id3_get_binary (id3tag, "APIC", &len, i); break; } if ((pictype == 0) && !coverart) { coverart = id3_get_binary (id3tag, "APIC", &len, i); } } if (coverart) { /* I guess iTunes is doing something wrong -- the * beginning of the coverart data ends up in a different field... We'll just add the missing data manually. */ const guchar itunes_broken_jfif_marker[] = { 0x10, 'J', 'F', 'I', 'F'}; if (len >= 5) { if (strncmp (itunes_broken_jfif_marker, coverart, 5) == 0) { const guchar itunes_missing_header[] = { 0xff, 0xd8, 0xff, 0xe0, 0x00 }; *image_data = g_malloc (len+5); memcpy (*image_data, itunes_missing_header, 5); memcpy ((*image_data)+5, coverart, len); *image_data_len = len+5; } } if (!*image_data) { *image_data = g_malloc (len); memcpy (*image_data, coverart, len); *image_data_len = len; } #if LOCALDEBUG if (*image_data) { FILE *file; file = fopen ("/tmp/folder.jpg", "w"); fwrite (*image_data, 1, *image_data_len, file); fclose (file); } #endif } } id3_file_close (id3file); return TRUE; } /*** * Reads id3v1.x / id3v2 tag and load data into the Id3tag structure. * If a tag entry exists (ex: title), we allocate memory, else value * stays to NULL * @returns: TRUE on success, else FALSE. */ gboolean id3_tag_read (gchar *filename, File_Tag *tag) { struct id3_file *id3file; struct id3_tag *id3tag; gchar* string; gchar* string2; g_return_val_if_fail (filename, FALSE); g_return_val_if_fail (tag, FALSE); memset (tag, 0, sizeof (File_Tag)); if (!(id3file = id3_file_open (filename, ID3_FILE_MODE_READONLY))) { gchar *fbuf = charset_to_utf8 (filename); g_print(_("ERROR while opening file: '%s' (%s).\n"), fbuf, g_strerror(errno)); g_free (fbuf); return FALSE; } if ((id3tag = id3_file_tag(id3file))) { tag->title = id3_get_string (id3tag, ID3_FRAME_TITLE); tag->artist = id3_get_string (id3tag, ID3_FRAME_GROUP); if (!tag->artist || !*tag->artist) { g_free (tag->artist); tag->artist = id3_get_string (id3tag, ID3_FRAME_ARTIST); } tag->album = id3_get_string (id3tag, ID3_FRAME_ALBUM); tag->year = id3_get_string (id3tag, ID3_FRAME_YEAR); tag->composer = id3_get_string (id3tag, "TCOM"); tag->comment = id3_get_string (id3tag, ID3_FRAME_COMMENT); tag->genre = id3_get_string (id3tag, ID3_FRAME_GENRE); tag->compilation = id3_get_string (id3tag, "TCMP"); tag->subtitle = id3_get_string (id3tag, "TIT3"); tag->lyrics = id3_get_string (id3tag, "USLT"); tag->podcasturl = id3_get_string (id3tag, "YTID"); tag->podcastrss = id3_get_string (id3tag, "YWFD"); tag->description = id3_get_string (id3tag, "YTDS"); tag->time_released = id3_get_string (id3tag, "YTDR"); tag->BPM = id3_get_string (id3tag, "TBPM"); tag->sort_artist = id3_get_string (id3tag, "TSOP"); tag->sort_album = id3_get_string (id3tag, "TSOA"); tag->sort_title = id3_get_string (id3tag, "TSOT"); tag->sort_albumartist = id3_get_string (id3tag, "TSO2"); tag->sort_composer = id3_get_string (id3tag, "TSOC"); string = id3_get_string (id3tag, "TLEN"); if (string) { tag->songlen = (guint32) strtoul (string, 0, 10); g_free (string); } string = id3_get_string (id3tag, ID3_FRAME_TRACK); if (string) { string2 = strchr(string,'/'); if (string2) { tag->track_total = g_strdup_printf ("%.2d", atoi (string2+1)); *string2 = '\0'; } tag->trackstring = g_strdup_printf ("%.2d", atoi (string)); g_free(string); } /* CD/disc number tag handling */ string = id3_get_string (id3tag, "TPOS"); if (string) { string2 = strchr(string,'/'); if (string2) { tag->cdno_total = g_strdup_printf ("%.2d", atoi (string2+1)); *string2 = '\0'; } tag->cdnostring = g_strdup_printf ("%.2d", atoi (string)); g_free(string); } } id3_file_close (id3file); return TRUE; } static enum id3_field_textencoding get_encoding_of (struct id3_tag *tag, const char *frame_name) { struct id3_frame *frame; enum id3_field_textencoding encoding = -1; frame = id3_tag_findframe (tag, frame_name, 0); if (frame) { union id3_field *field = id3_frame_field (frame, 0); if (field && (id3_field_type (field) == ID3_FIELD_TYPE_TEXTENCODING)) encoding = field->number.value; } return encoding; } /* Find out which encoding is being used. If in doubt, return * latin1. This code assumes that the same encoding is used in all * fields. */ static enum id3_field_textencoding get_encoding (struct id3_tag *tag) { enum id3_field_textencoding enc; enc = get_encoding_of (tag, ID3_FRAME_TITLE); if (enc != -1) return enc; enc = get_encoding_of (tag, ID3_FRAME_ARTIST); if (enc != -1) return enc; enc = get_encoding_of (tag, ID3_FRAME_ALBUM); if (enc != -1) return enc; enc = get_encoding_of (tag, "TCOM"); if (enc != -1) return enc; enc = get_encoding_of (tag, ID3_FRAME_COMMENT); if (enc != -1) return enc; enc = get_encoding_of (tag, ID3_FRAME_YEAR); if (enc != -1) return enc; return ID3_FIELD_TEXTENCODING_ISO_8859_1; } /* I'm not really sure about this: The original TAG identifier was "TID", but no matter what I do I end up writing "YTID" */ void set_uncommon_tag (struct id3_tag *id3tag, const gchar *id, const gchar *text, enum id3_field_textencoding encoding) { #if 0 struct id3_frame *frame; frame = id3_tag_findframe (id3tag, id, 0); union id3_field *field; frame->flags = 0; field = id3_frame_field (frame, 0); if (field) { string1 = g_strdup_printf ("%c%s", '\0', track->podcasturl); id3_field_setbinarydata (field, string1, strlen(track->podcasturl)+1); g_free (string1); } #endif } /** * Write the ID3 tags to the file. * @returns: TRUE on success, else FALSE. */ gboolean mp3_write_file_info (gchar *filename, Track *track) { struct id3_tag* id3tag; struct id3_file* id3file; gint error = 0; id3file = id3_file_open (filename, ID3_FILE_MODE_READWRITE); if (!id3file) { gchar *fbuf = charset_to_utf8 (filename); g_print(_("ERROR while opening file: '%s' (%s).\n"), fbuf, g_strerror(errno)); g_free (fbuf); return FALSE; } if ((id3tag = id3_file_tag(id3file))) { char *string1; enum id3_field_textencoding encoding; /* use the same coding as before... */ encoding = get_encoding (id3tag); /* ...unless it's ISO_8859_1 and prefs say we should use unicode (i.e. ID3v2.4) */ if (prefs_get_int("id3_write_id3v24") && (encoding == ID3_FIELD_TEXTENCODING_ISO_8859_1)) encoding = ID3_FIELD_TEXTENCODING_UTF_8; /* always render id3v1 to prevent dj studio from crashing */ id3_tag_options(id3tag, ID3_TAG_OPTION_ID3V1, ~0); /* turn off frame compression and crc information to let itunes read tags see http://www.mars.org/mailman/public/mad-dev/2002-October/000742.html */ id3_tag_options(id3tag, ID3_TAG_OPTION_COMPRESSION, 0); id3_tag_options(id3tag, ID3_TAG_OPTION_CRC, 0); id3_set_string (id3tag, ID3_FRAME_TITLE, track->title, encoding); id3_set_string (id3tag, ID3_FRAME_ARTIST, track->artist, encoding); id3_set_string (id3tag, ID3_FRAME_ALBUM, track->album, encoding); id3_set_string (id3tag, ID3_FRAME_GENRE, track->genre, encoding); id3_set_string (id3tag, ID3_FRAME_COMMENT, track->comment, encoding); id3_set_string (id3tag, "TIT3", track->subtitle, encoding); id3_set_string (id3tag, "TSOP", track->sort_artist, encoding); id3_set_string (id3tag, "TSOA", track->sort_album, encoding); id3_set_string (id3tag, "TSOT", track->sort_title, encoding); id3_set_string (id3tag, "TSO2", track->sort_albumartist, encoding); id3_set_string (id3tag, "TSOC", track->sort_composer, encoding); set_uncommon_tag (id3tag, "YTID", track->podcasturl, encoding); set_uncommon_tag (id3tag, "YTDS", track->description, encoding); set_uncommon_tag (id3tag, "YWFD", track->podcastrss, encoding); id3_set_string (id3tag, "TCOM", track->composer, encoding); string1 = g_strdup_printf("%d", track->year); id3_set_string(id3tag, ID3_FRAME_YEAR, string1, encoding); g_free(string1); string1 = g_strdup_printf("%d", track->BPM); id3_set_string(id3tag, "TBPM", string1, encoding); g_free(string1); if (track->tracks) string1 = g_strdup_printf ("%d/%d", track->track_nr, track->tracks); else string1 = g_strdup_printf ("%d", track->track_nr); id3_set_string (id3tag, ID3_FRAME_TRACK, string1, encoding); g_free(string1); if (track->cds) string1 = g_strdup_printf ("%d/%d", track->cd_nr, track->cds); else string1 = g_strdup_printf ("%d", track->cd_nr); id3_set_string (id3tag, "TPOS", string1, encoding); g_free(string1); string1 = g_strdup_printf ("%d", track->compilation); id3_set_string (id3tag, "TCMP", string1, encoding); g_free(string1); } if (id3_file_update(id3file) != 0) { gchar *fbuf = charset_to_utf8 (filename); g_print(_("ERROR while writing tag to file: '%s' (%s).\n"), fbuf, g_strerror(errno)); g_free (fbuf); return FALSE; } id3_file_close (id3file); if (error) return FALSE; else return TRUE; } /* * Code to read the ReplayGain Values stored by LAME in its own tag. * * Most of the relevant information has been extracted from them LAME sources * (http://lame.sourceforge.net/). * The "Mp3 info Tag rev 1 specifications - draft 0" * (http://gabriel.mp3-tech.org/mp3infotag.html) by Gabriel Bouvigne describes * the actual Tag (except for small changes). * Details on the actual ReplayGain fields have been obtained from * http://www.replaygain.org . * * Apart from that, some information has been derived from phwip's LameTag * (http://www.silisoftware.com/applets/?scriptname=LameTag) * * * Code to read the ReplayGain Values stored in an Ape tag. * * Info on Lyrics3 V2.00 can be found at: * http://www.id3.org/lyrics3200.html * On the actual Ape Tag V2.0: * http://www.personal.uni-jena.de/~pfk/mpp/sv8/apetag.html * * Copyright (C) 2004 Jens Taprogge * * Provided under GPL according to Jens Taprogge. (JCS -- 12 March 2004) */ #define TAG_FOOTER 0x10 #define LAME_OFFSET 0x74 #define SIDEINFO_MPEG1_MONO 17 #define SIDEINFO_MPEG1_MULTI 32 #define SIDEINFO_MPEG2_MONO 9 #define SIDEINFO_MPEG2_MULTI 17 #define ID3V1_SIZE 0x80 #define APE_FOOTER_SIZE 0x20 #define LYRICS_FOOTER_SIZE 0x0f static gint lame_vcmp(gchar a[5], gchar b[5]) { int r; r = strncmp(a, b, 4); if (r) return r; if (a[4] == b[4]) return 0; /* check for '.': indicates subminor version */ if (a[4] == '.') return 1; if (b[4] == '.') return -1; /* check for alpha or beta versions */ if (a[4] == ' ') return 1; if (b[4] == ' ') return -1; /* check for alpha, beta etc. indicated by a, b... */ return strncmp(&a[4], &b[4], 1); } /* buf[] must be declared unsigned -- otherwise the casts, shifts and additions below produce funny results */ static void read_lame_replaygain(unsigned char buf[], GainData *gd, int gain_adjust) { char oc, nc; gint gain; g_return_if_fail (gd); /* buf[0] and buf[1] are a bit field: 3 bits: name (mask: 0xe0 = 11100000) 3 bits: originator (mask: 0x1c = 00011100) 1 bit: negative if set (mask: 0x02 = 00000010) 9 bits: value */ /* check originator */ oc = (buf[0] & 0x1c) >> 2; if ((oc <= 0) || (oc > 3)) return; /* check name code */ nc = buf[0] & 0xe0; if (!((nc == 0x20) || (nc == 0x40))) return; gain = ((((guint)buf[0]) & 0x1) << 8) + buf[1]; /* This would be a value of -0. * That value however is illegal by current standards and reserved for * future use. */ if ((!gain) && (buf[0] & 0x02)) return; if (buf[0] & 2) gain = -gain; gain += gain_adjust; switch (nc) { case 0x20: if (gd->radio_gain_set) return; gd->radio_gain = (gdouble)gain / 10; gd->radio_gain_set = TRUE; /* printf("radio_gain (lame): %i\n", gd->radio_gain); */ break; case 0x40: if (gd->audiophile_gain_set) return; gd->audiophile_gain = (gdouble)gain / 10; gd->audiophile_gain_set = TRUE; /* printf("audiophile_gain (lame): %i\n", gd->audiophile_gain);*/ break; } } static inline guint32 parse_ape_uint32(char *buf) { return (buf[0] & 0xff) | (buf[1] & 0xff) << 8 | (buf[2] & 0xff) << 16 | (buf[3] & 0xff) << 24; } static inline guint32 parse_lame_uint32(char *buf) { return (buf[0] & 0xff) << 24 | (buf[1] & 0xff) << 16 | (buf[2] & 0xff) << 8 | (buf[3] & 0xff); } /* * mp3_get_track_lame_replaygain - read the specified file and scan for LAME Tag * ReplayGain information. * * @path: localtion of the file * @track: structure holding track information * * FIXME: Are there other encoders writing a LAME Tag using a different magic * string? * TODO: Check CRC. */ gboolean mp3_get_track_lame_replaygain (gchar *path, GainData *gd) { struct { /* All members are defined in terms of chars so padding does not * occur. Is there a cleaner way to keep the compiler from * padding? */ char id[3]; char version[2]; char flags; char size[4]; } id3head; FILE *file = NULL; char buf[4], version[5]; int gain_adjust = 0; int sideinfo; guint32 ps; g_return_val_if_fail (gd, FALSE); gd->radio_gain = 0; gd->audiophile_gain = 0; gd->peak_signal = 0; gd->radio_gain_set = FALSE; gd->audiophile_gain_set = FALSE; gd->peak_signal_set = FALSE; if (!path) goto rg_fail; file = fopen (path, "r"); if (!file) goto rg_fail; /* Skip ID3 header if appropriate */ if (fread(&id3head, 1, sizeof(id3head), file) != sizeof(id3head)) goto rg_fail; if (!strncmp(id3head.id, "ID3", 3)) { int realsize = 0; realsize = (id3head.size[0] & 0x7f) << 21 | (id3head.size[1] & 0x7f) << 14 | (id3head.size[2] & 0x7f) << 7 | (id3head.size[3] & 0x7f); if (id3head.flags & TAG_FOOTER) { /* footer is copy of header */ realsize += sizeof(id3head); } if (fseek(file, realsize-1, SEEK_CUR) || (!fread(&buf[0], 1, 1, file))) goto rg_fail; } else { /* no ID3 Tag - go back */ fseek(file, -sizeof(id3head), SEEK_CUR); } /* Search Xing header. The location is dependant on the MPEG Layer and * whether the stream is mono or not. */ if (fread(buf, 1, 4, file) != 4) goto rg_fail; /* should start with 0xff 0xf? (synch) */ if (((buf[0] & 0xff) != 0xff) || ((buf[1] & 0xf0) != 0xf0)) goto rg_fail; /* determine the length of the sideinfo */ if (buf[1] & 0x08) { sideinfo = ((buf[3] & 0xc0) == 0xc0) ? SIDEINFO_MPEG1_MONO : SIDEINFO_MPEG1_MULTI; } else { sideinfo = ((buf[3] & 0xc0) == 0xc0) ? SIDEINFO_MPEG2_MONO : SIDEINFO_MPEG2_MULTI; } if (fseek(file, sideinfo, SEEK_CUR) || (fread(&buf[0], 1, 4, file) != 4)) goto rg_fail; /* Is this really a Xing or Info Header? * FIXME: Apparently (according to madplay sources) there is a different * possible location for the Xing header ("due to an unfortunate * historical event"). I do not thing we need to care though since * RaplayGain information is only conatined in recent files. */ if (strncmp(buf, "Xing", 4) && strncmp(buf, "Info", 4)) goto rg_fail; /* Check for LAME Tag */ if (fseek(file, LAME_OFFSET, SEEK_CUR) || (fread(&buf[0], 1, 4, file) != 4)) goto rg_fail; if (strncmp(buf, "LAME", 4)) goto rg_fail; /* Check LAME Version */ if (fread(version, 1, 5, file) != 5) goto rg_fail; /* Skip really old versions altogether. I am not sure when radio_gain * information was introduced. 3.89 does not seem to supprt it though. * */ if (lame_vcmp(version, "3.90") < 0) { /* fprintf(stderr, "Old lame version (%c%c%c%c%c). Not used.\n", version[0], version[1], version[2], version[3], version[4]); */ goto rg_fail; } if (fseek(file, 0x2, SEEK_CUR) || (fread(buf, 1, 4, file) != 4)) goto rg_fail; /* get the peak signal. */ ps = parse_lame_uint32(buf); /* Don't know when fixed-point PeakSingleAmplitude * was introduced exactly. 3.94b will be used for now.) */ if ((lame_vcmp(version, "3.94b") >= 0)) { if ((!gd->peak_signal_set) && ps) { gd->peak_signal = ps; gd->peak_signal_set = TRUE; /* printf("peak_signal (lame): %f\n", (double) gd->peak_signal / 0x800000);*/ } } else { float f = *((float *) (void *) (&ps)) * 0x800000; gd->peak_signal = (guint32) f; /* I would like to see an example of that. */ /* printf("peak_signal (lame floating point): %f. PLEASE report.\n", (double) gd->peak_signal / 0x800000);*/ } /* * Versions prior to 3.95.1 used a reference volume of 83dB. * (As compared to the currently used 89dB.) */ if ((lame_vcmp(version, "3.95.") < 0)) { gain_adjust = 60; /* fprintf(stderr, "Old lame version (%c%c%c%c%c). Adjusting gain.\n", version[0], version[1], version[2], version[3], version[4]); */ } if (fread(&buf[0], 1, 2, file) != 2) goto rg_fail; /* radio gain */ read_lame_replaygain (buf, gd, gain_adjust); if (fread(&buf[0], 1, 2, file) != 2) goto rg_fail; /* audiophile gain */ read_lame_replaygain (buf, gd, gain_adjust); fclose(file); return TRUE; rg_fail: if (file) fclose(file); return FALSE; } /* * mp3_get_track_ape_replaygain - read the specified file and scan for Ape Tag * ReplayGain information. * * @path: localtion of the file * @track: structure holding track information * * The function only modifies the gains if they have not previously been set. */ gboolean mp3_get_track_ape_replaygain(gchar *path, GainData *gd) { /* The Ape Tag is located a t the end of the file. Or at least that * seems where it can most likely be found. Either it is at the very end * or before a trailing ID3v1 Tag. Sometimes a Lyrics3 Tag is placed * between the ID3v1 and the Ape Tag. * If you find files that have the Tags located in different * positions please let me know. */ FILE *file = NULL; char buf[16]; char *dbuf = NULL, *ep; int offset = 0; int i; int pos = 0, pos2 = 0; guint32 version; guint32 data_length; guint32 entry_length = 0; guint32 entries; double d; g_return_val_if_fail (gd, FALSE); g_return_val_if_fail (path, FALSE); file = fopen (path, "r"); if (!file) goto rg_fail; /* check for ID3v1 Tag */ if (fseek(file, -ID3V1_SIZE, SEEK_END) || fread(&buf, 1, 3, file) != 3) goto rg_fail; if (!strncmp(buf, "TAG", 3)) offset -= ID3V1_SIZE; /* check for Lyrics3 Tag */ if (fseek(file, -9 + offset, SEEK_END) || fread(&buf, 1, 9, file) != 9) goto rg_fail; if (!strncmp(buf, "LYRICS200", 9)) { if (fseek(file, -LYRICS_FOOTER_SIZE + offset, SEEK_END) || fread(&buf, 1, 9, file) != 9) goto rg_fail; data_length = buf[0] - '0'; for (i = 1; i < 6; i++) { data_length *= 10; data_length += buf[i] - '0'; } if (fseek(file, -LYRICS_FOOTER_SIZE - data_length + offset, SEEK_END) || fread(&buf, 1, 11, file) != 11) goto rg_fail; if (!strncmp(buf, "LYRICSBEGIN", 11)) offset -= LYRICS_FOOTER_SIZE + data_length; } /* check for APE Tag */ if (fseek(file, -APE_FOOTER_SIZE + offset, SEEK_END) || fread(&buf, 1, 8, file) != 8) goto rg_fail; if (strncmp(buf, "APETAGEX", 8)) goto rg_fail; /* Check the version of the tag. 1000 and 2000 (v1.0 and 2.0) are the * only ones I know about. Make suer things do not break in the future. * */ if (fread(&buf, 1, 4, file) != 4) goto rg_fail; version = parse_ape_uint32(buf); if (version != 1000 && version != 2000) goto rg_fail; /* determine data length */ if (fread(&buf, 1, 4, file) != 4) goto rg_fail; data_length = parse_ape_uint32(buf) - APE_FOOTER_SIZE; /* determine number of entries */ if (fread(&buf, 1, 4, file) != 4) goto rg_fail; entries = parse_ape_uint32(buf); /* seek to first entry and read the whole buffer*/ if (fseek(file, -APE_FOOTER_SIZE + offset - data_length, SEEK_END)) goto rg_fail; if (!(dbuf = malloc(data_length))) goto rg_fail; if (fread(dbuf, 1, data_length, file) != data_length) goto rg_fail; for (i = 0; i < entries; i++) { if (gd->radio_gain_set && gd->peak_signal_set) break; pos = pos2 + entry_length; if (pos > data_length - 10) break; entry_length = parse_ape_uint32(&dbuf[pos]); pos += 4; pos += 4; pos2 = pos; while (dbuf[pos2] && pos2 < data_length) pos2++; if (pos2 == data_length) break; pos2++; if (entry_length + 1 > sizeof(buf)) continue; /* printf ("%s:%d:%d\n",&dbuf[pos], pos2, pos); */ if (!gd->radio_gain_set && !strcasecmp(&dbuf[pos], "REPLAYGAIN_TRACK_GAIN")) { memcpy(buf, &dbuf[pos2], entry_length); buf[entry_length] = '\0'; d = g_ascii_strtod(buf, &ep); /* printf("%f\n", d); */ if ((ep == buf + entry_length - 3) && (!strncasecmp(ep, " dB", 3))) { gd->radio_gain = d; gd->radio_gain_set = TRUE; /* printf("radio_gain (ape): %i\n", gd->radio_gain);*/ } continue; } if (!gd->peak_signal_set && !strcasecmp(&dbuf[pos], "REPLAYGAIN_TRACK_PEAK")) { memcpy(buf, &dbuf[pos2], entry_length); buf[entry_length] = '\0'; d = g_ascii_strtod(buf, &ep); if (ep == buf + entry_length) { d *= 0x800000; gd->peak_signal = (guint32) floor(d + 0.5); gd->peak_signal_set = TRUE; /* printf("peak_signal (ape): %f\n", (double) gd->peak_signal / 0x800000);*/ } continue; } } free(dbuf); fclose(file); return TRUE; rg_fail: if (dbuf) free(dbuf); if (file) fclose(file); return FALSE; } /* ---------------------------------------------------------------------- mp3gain code ---------------------------------------------------------------------- */ #include #include /** * mp3_read_soundcheck: * * try to read the ReplayGain values from the LAME or Ape Tags and set * the track's soundcheck field accordingly. * * @path: localtion of the file * @track: structure holding track information * * The function always rereads the gain from the file. * * Returns TRUE if the soundcheck field could be set. */ gboolean mp3_read_soundcheck (gchar *path, Track *track) { GainData gd; g_return_val_if_fail (track, FALSE); memset (&gd, 0, sizeof (GainData)); gd.radio_gain_set = FALSE; gd.audiophile_gain_set = FALSE; gd.peak_signal_set = FALSE; mp3_get_track_lame_replaygain (path, &gd); if (gd.radio_gain_set) { track->soundcheck = replaygain_to_soundcheck (gd.radio_gain); return TRUE; } mp3_get_track_ape_replaygain (path, &gd); if (gd.radio_gain_set) { track->soundcheck = replaygain_to_soundcheck (gd.radio_gain); return TRUE; } return FALSE; } /* mp3 slot size in bytes */ int slotsize[3] = {4,1,1}; /* layer 1, layer 2, layer 3 */ int samplesperframe[2][3] = { { /* MPEG 2.0 */ 384,1152,576 /* layer 1, layer 2, layer 3 */ }, { /* MPEG 1.0 */ 384,1152,1152 /* layer 1, layer 2, layer 3 */ } }; /* * mp3_get_track_lame_gapless - read the specified file and scan for LAME Tag * gapless information. * * @path: localtion of the file * @track: structure holding track information * * TODO: Split off non-LAME stuff (samplecount, gapless_data) to a separate function since it's generic */ gboolean mp3_get_track_lame_gapless (gchar *path, GaplessData *gd) { FILE *file = NULL; char buf[4], version[5]; unsigned char ubuf[4]; int sideinfo; int i; g_return_val_if_fail (gd, FALSE); if (!path) goto gp_fail; file = fopen (path, "rb"); if (!file) goto gp_fail; /* use get_first_header() to seek to the first mp3 header */ MP3Info *mp3i = NULL; mp3i = g_malloc0 (sizeof (MP3Info)); mp3i->filename = path; mp3i->file = file; get_mp3_info (mp3i); get_first_header (mp3i, 0); int xing_header_offset = ftell (file); MP3Header h; if (!get_header (file, &h)) goto gp_fail; int mysamplesperframe = samplesperframe[h.version & 1][3 - h.layer]; /* Determine offset of Xing header based on sideinfo size */ if (h.version & 0x1) { sideinfo = (h.mode & 0x2) ? SIDEINFO_MPEG1_MONO : SIDEINFO_MPEG1_MULTI; } else { sideinfo = (h.mode & 0x2) ? SIDEINFO_MPEG2_MONO : SIDEINFO_MPEG2_MULTI; } if (fseek (file, sideinfo, SEEK_CUR) || (fread (&buf[0], 1, 4, file) != 4)) goto gp_fail; /* Is this really a Xing or Info Header? * FIXME: Apparently (according to madplay sources) there is a different * possible location for the Xing header ("due to an unfortunate * historical event"). I do not thing we need to care though since * ReplayGain information is only contained in recent files. */ if (strncmp (buf, "Xing", 4) && strncmp (buf, "Info", 4)) goto gp_fail; /* Determine the offset of the LAME tag based on contents of the Xing header */ int flags; fread (&flags, 4, 1, file); int toskip = 0; if (flags | 0x1) { /* frames field is set */ toskip += 4; } if (flags | 0x2) { /* bytes field is set */ toskip += 4; } if (flags | 0x4) { /* TOC field is set */ toskip += 100; } if (flags | 0x8) { /* quality field is set */ toskip += 4; } /* Check for LAME Tag */ if (fseek (file, toskip, SEEK_CUR) || (fread (&buf[0], 1, 4, file) != 4)) goto gp_fail; if (strncmp (buf, "LAME", 4)) goto gp_fail; /* Check LAME Version */ if (fread (version, 1, 5, file) != 5) goto gp_fail; /* XXX skip old LAME versions, or just assume that pre/postgap * turn out zeros anyway, or check the CRC to vaidate the tag? */ gboolean cbr = FALSE; if (fread (ubuf, 1, 1, file) != 1) goto gp_fail; if ((ubuf[0] & 0xf) == 0x1) cbr = TRUE; if (fseek (file, 0xB, SEEK_CUR) || (fread (ubuf, 1, 4, file) != 4)) goto gp_fail; /* set pregap and postgap directly from LAME header */ gd->pregap = (ubuf[0] << 4) + (ubuf[1] >> 4); gd->postgap = ((ubuf[1] & 0xf) << 8) + ubuf[2]; /* jump the end of the frame with the xing header */ if (fseek (file, xing_header_offset + frame_length (&h), SEEK_SET)) goto gp_fail; /* counts bytes from the start of the 1st sync frame */ int totaldatasize = frame_length (&h); /* keeps track of the last 8 frame sizes */ int lastframes[8]; /* counts number of music frames */ int totalframes = 0; /* quickly parse the file, reading only frame headers */ int l = 0; while ((l = get_header (file, &h)) != 0) { for (i = 7; i > 0; i--) { lastframes[i] = lastframes[i - 1]; } lastframes[0] = l; totaldatasize += l; totalframes++; if (fseek (file, l - FRAME_HEADER_SIZE, SEEK_CUR)) goto gp_fail; } int finaleight = 0; for (i = 0; i < 8; i++) { finaleight += lastframes[i]; } if (cbr) totalframes++; /* all but last eight frames */ gd->gapless_data = totaldatasize - finaleight; /* total samples minus pre/postgap */ gd->samplecount = totalframes * mysamplesperframe - gd->pregap - gd->postgap; return TRUE; gp_fail: if (file) fclose (file); return FALSE; } /** * mp3_read_gapless: * * try to read the gapless values from the LAME Tag and set * the track's pregap, postgap, samplecount, and gapless_data fields * accordingly. * * @path: location of the file * @track: structure holding track information * * The function always rereads the data from the file. * * Returns TRUE if all four gapless fields could be * set. etrack->tchanged is set to TRUE if data has been changed, * FALSE otherwise. */ gboolean mp3_read_gapless (char *path, Track *track) { GaplessData gd; ExtraTrackData *etr; g_return_val_if_fail (track, FALSE); etr = track->userdata; memset (&gd, 0, sizeof (GaplessData)); gd.pregap = 0; gd.samplecount = 0; gd.postgap = 0; gd.gapless_data = 0; mp3_get_track_lame_gapless (path, &gd); etr->tchanged = FALSE; if ((gd.pregap) && (gd.samplecount) && (gd.postgap) && (gd.gapless_data)) { if ((track->pregap != gd.pregap) || (track->samplecount != gd.samplecount) || (track->postgap != gd.postgap) || (track->gapless_data != gd.gapless_data) || (track->gapless_track_flag == FALSE)) { etr->tchanged = TRUE; track->pregap = gd.pregap; track->samplecount = gd.samplecount; track->postgap = gd.postgap; track->gapless_data = gd.gapless_data; track->gapless_track_flag = TRUE; } } return FALSE; } /* ---------------------------------------------------------------------- From here starts original gtkpod code ---------------------------------------------------------------------- */ /* Return a Track structure with all information read from the mp3 file filled in */ Track *mp3_get_file_info (gchar *name) { Track *track = NULL; File_Tag filetag; MP3Info *mp3i=NULL; FILE *file; guchar *image_data = NULL; gsize image_data_len = 0; g_return_val_if_fail (name, NULL); /* Attempt to open the file */ file = fopen (name, "r"); if (file) { mp3i = g_malloc0 (sizeof (MP3Info)); mp3i->filename = name; mp3i->file = file; get_mp3_info (mp3i); mp3i->file = NULL; fclose (file); } else { gchar *fbuf = charset_to_utf8 (name); gtkpod_warning(_("ERROR while opening file: '%s' (%s).\n"), fbuf, g_strerror(errno)); g_free (fbuf); return NULL; } track = gp_track_new (); track->filetype = g_strdup ("MPEG audio file"); if (prefs_get_int("readtags") && (id3_tag_read (name, &filetag) == TRUE)) { if (filetag.album) { track->album = filetag.album; } if (filetag.artist) { track->artist = filetag.artist; } if (filetag.title) { track->title = filetag.title; } if (filetag.genre) { track->genre = filetag.genre; } if (filetag.composer) { track->composer = filetag.composer; } if (filetag.comment) { track->comment = filetag.comment; } if (filetag.podcasturl) { track->podcasturl = filetag.podcasturl; } if (filetag.podcastrss) { track->podcastrss = filetag.podcastrss; } if (filetag.subtitle) { track->subtitle = filetag.subtitle; } if (filetag.description) { track->description = filetag.description; } if (filetag.sort_artist) { track->sort_artist = filetag.sort_artist; } if (filetag.sort_title) { track->sort_title = filetag.sort_title; } if (filetag.sort_album) { track->sort_album = filetag.sort_album; } if (filetag.sort_albumartist) { track->sort_albumartist = filetag.sort_albumartist; } if (filetag.sort_composer) { track->sort_composer = filetag.sort_composer; } if (filetag.year == NULL) { track->year = 0; } else { track->year = atoi(filetag.year); g_free (filetag.year); } if (filetag.trackstring == NULL) { track->track_nr = 0; } else { track->track_nr = atoi(filetag.trackstring); g_free (filetag.trackstring); } if (filetag.track_total == NULL) { track->tracks = 0; } else { track->tracks = atoi(filetag.track_total); g_free (filetag.track_total); } /* CD/disc number handling */ if (filetag.cdnostring == NULL) { track->cd_nr = 0; } else { track->cd_nr = atoi(filetag.cdnostring); g_free (filetag.cdnostring); } if (filetag.cdno_total == NULL) { track->cds = 0; } else { track->cds = atoi(filetag.cdno_total); g_free (filetag.cdno_total); } if (filetag.compilation == NULL) { track->compilation = 0; } else { track->compilation = atoi(filetag.compilation); g_free (filetag.compilation); } if (filetag.BPM == NULL) { track->BPM = 0; } else { track->BPM = atoi(filetag.BPM); g_free (filetag.BPM); } if (filetag.lyrics) { track->lyrics_flag = 0x01; g_free (filetag.lyrics); } else { track->lyrics_flag = 0x00; } } if (prefs_get_int("coverart_apic") && (id3_apic_read (name, &image_data, &image_data_len) == TRUE)) { if (image_data) { gp_track_set_thumbnails_from_data (track, image_data, image_data_len); g_free (image_data); } } mp3_read_soundcheck (name, track); mp3_read_gapless (name, track); #if LOCALDEBUG printf("%s\n", name); printf("\tpregap: %i\n", track->pregap); printf("\tpostgap: %i\n", track->postgap); printf("\tsamplecount: %li\n", track->samplecount); printf("\tgaplessdata: %i\n", track->gapless_data); #endif /* Get additional info (play time and bitrate */ if (mp3i) { track->tracklen = mp3i->milliseconds; track->bitrate = (gint)(mp3i->vbr_average); track->samplerate = mp3file_header_frequency (&mp3i->header); g_free (mp3i); } /* Fall back to xmms code if tracklen is 0 */ if (track->tracklen == 0) { track->tracklen = get_track_time (name); if (track->tracklen) track->bitrate = (float)track->size*8/track->tracklen; } if (track->tracklen == 0) { /* Tracks with zero play length are ignored by iPod... */ gtkpod_warning (_("File \"%s\" has zero play length. Ignoring.\n"), name); gp_track_free (track); track = NULL; } return track; }