apetag.c0000644000175000017500000005352313303547222012672 0ustar snhardinsnhardin#include "apetag.h" #include "mp3gain.h" #include "rg_error.h" #include #include #include #include #include #ifdef WIN32 #include #else #include #endif #ifndef WIN32 #define _stricmp strcasecmp #endif /* WIN32 */ int ReadMP3ID3v1Tag(FILE *fi, unsigned char **tagbuff, long *tag_offset) { char tmp[128]; if ( *tag_offset < 128 ) return 0; if ( fseek(fi, *tag_offset - 128,SEEK_SET) ) return 0; if ( fread(tmp, 1, 128, fi) != 128 ) return 0; if ( memcmp (tmp, "TAG", 3) ) return 0; //we have tag, so store it in buffer if (*tagbuff) free(*tagbuff); *tagbuff = (unsigned char *)malloc(128); memcpy(*tagbuff,tmp,128); *tag_offset -= 128; return 1; } /* static int Lyrics3GetNumber5 ( const unsigned char* string ) { return ( string[0] - '0') * 10000 + ( string[1] - '0') * 1000 + ( string[2] - '0') * 100 + ( string[3] - '0') * 10 + ( string[4] - '0') * 1; } */ static int Lyrics3GetNumber6 ( const unsigned char* string ) { if (string[0] < '0' || string[0] > '9') return 0; if (string[1] < '0' || string[1] > '9') return 0; if (string[2] < '0' || string[2] > '9') return 0; if (string[3] < '0' || string[3] > '9') return 0; if (string[4] < '0' || string[4] > '9') return 0; if (string[5] < '0' || string[5] > '9') return 0; return ( string[0] - '0') * 100000 + ( string[1] - '0') * 10000 + ( string[2] - '0') * 1000 + ( string[3] - '0') * 100 + ( string[4] - '0') * 10 + ( string[5] - '0') * 1; } struct Lyrics3TagFooterStruct { unsigned char Length [6]; unsigned char ID [9]; }; struct Lyrics3TagField { unsigned char ID [3]; unsigned char Length [5]; }; // Reads Lyrics3 v2.0 tag static int ReadMP3Lyrics3v2Tag ( FILE *fp, unsigned char **tagbuff, unsigned long *tagSize, unsigned char **id3tagbuff, long *tag_offset ) { int len; struct Lyrics3TagFooterStruct T; char tmpid3[128]; char tmp[11]; long taglen; if ( *tag_offset < 128 ) return 0; if ( fseek (fp, *tag_offset - 128, SEEK_SET) ) return 0; if ( fread (tmpid3, 1, 128, fp) != 128 ) return 0; // check for id3-tag if ( memcmp (tmpid3, "TAG", 3) ) return 0; //if we have id3-tag, put it in the id3tagbuff if (*id3tagbuff) free(*id3tagbuff); *id3tagbuff = (unsigned char *)malloc(128); memcpy(*id3tagbuff,tmpid3,128); if ( *tag_offset < (128 + (long)(sizeof(T))) ) return 0; if ( fseek (fp, *tag_offset - 128 - sizeof (T), SEEK_SET) ) return 0; if ( fread (&T, 1, sizeof (T), fp) != sizeof (T) ) return 0; // check for lyrics3 v2.00 tag if ( memcmp (T.ID, "LYRICS200", sizeof (T.ID)) ) return 0; len = Lyrics3GetNumber6 (T.Length); if (*tag_offset < (128 + (long)(sizeof(T)) + len)) return 0; if ( fseek (fp, *tag_offset - 128 - (long)sizeof (T) - len, SEEK_SET) ) return 0; if ( fread (tmp, 1, 11, fp) != 11 ) return 0; if ( memcmp (tmp, "LYRICSBEGIN", 11) ) return 0; taglen = 128 + Lyrics3GetNumber6(T.Length) + sizeof(T); *tag_offset -= taglen; if (*tagbuff != NULL) { free(*tagbuff); } *tagbuff = (unsigned char *)malloc(taglen); fseek(fp,*tag_offset,SEEK_SET); fread(*tagbuff,1,taglen,fp); *tagSize = taglen; return 1; } static unsigned long Read_LE_Uint32_unsigned ( const unsigned char* p ) { return ((unsigned long)p[0] << 0) | ((unsigned long)p[1] << 8) | ((unsigned long)p[2] << 16) | ((unsigned long)p[3] << 24); } static unsigned long Read_LE_Uint32 ( const char* p ) {return Read_LE_Uint32_unsigned((const unsigned char*)p);} static void Write_LE_Uint32 ( char* p, const unsigned long value ) { p[0] = (unsigned char) (value >> 0); p[1] = (unsigned char) (value >> 8); p[2] = (unsigned char) (value >> 16); p[3] = (unsigned char) (value >> 24); } enum { MAX_FIELD_SIZE = 1024*1024 //treat bigger fields as errors }; unsigned long strlen_max(const char * ptr, unsigned long max) { unsigned long n = 0; while (ptr[n] && n < max) n++; return n; } // Reads APE v1.0/2.0 tag int ReadMP3APETag ( FILE *fp, struct MP3GainTagInfo *info, struct APETagStruct **apeTag, long *tag_offset ) { unsigned long vsize; unsigned long isize; unsigned long flags; unsigned long remaining; char* buff; char* p; char* value; char* vp; char* end; struct APETagFooterStruct T; unsigned long TagLen; unsigned long TagCount; unsigned long origTagCount, otherFieldsCount; unsigned long curFieldNum; unsigned long Ver; char* name; int is_info; char tmpString[10]; if ( *tag_offset < (long)(sizeof(T)) ) return 0; if ( fseek(fp,*tag_offset - sizeof(T),SEEK_SET) ) return 0; if ( fread (&T, 1, sizeof(T), fp) != sizeof(T) ) return 0; if ( memcmp (T.ID, "APETAGEX", sizeof(T.ID)) ) return 0; Ver = Read_LE_Uint32 (T.Version); if ( (Ver != 1000) && (Ver != 2000) ) return 0; if ( (TagLen = Read_LE_Uint32 (T.Length)) < sizeof (T) ) return 0; if (*tag_offset < TagLen) return 0; if ( fseek (fp, *tag_offset - TagLen, SEEK_SET) ) return 0; buff = (char *)malloc (TagLen); if ( fread (buff, 1, TagLen - sizeof (T), fp) != (TagLen - sizeof (T)) ) { free (buff); return 0; } if (*apeTag) { if ((*apeTag)->otherFields) free((*apeTag)->otherFields); free(*apeTag); } *apeTag = (struct APETagStruct *)malloc(sizeof(struct APETagStruct)); (*apeTag)->haveHeader = 0; (*apeTag)->otherFields = (unsigned char *)malloc(TagLen - sizeof(T)); (*apeTag)->otherFieldsSize = 0; memcpy(&((*apeTag)->footer),&T,sizeof(T)); origTagCount = TagCount = Read_LE_Uint32 (T.TagCount); otherFieldsCount = 0; end = buff + TagLen - sizeof (T); curFieldNum = 0; for ( p = buff; p < end && TagCount--; ) { if (end - p < 8) break; vsize = Read_LE_Uint32 (p); p += 4; flags = Read_LE_Uint32 (p); p += 4; remaining = (unsigned long) (end - p); isize = strlen_max (p, remaining); if (isize >= remaining || vsize > MAX_FIELD_SIZE || isize + 1 + vsize > remaining) break; name = (char*)malloc(isize+1); memcpy(name, p, isize); name[isize] = 0; value = (char*)malloc(vsize+1); memcpy(value, p+isize+1, vsize); value[vsize] = 0; is_info = 0; { if (!_stricmp (name, "REPLAYGAIN_TRACK_GAIN")) { info->haveTrackGain = !0; info->trackGain = atof(value); } else if (!_stricmp(name,"REPLAYGAIN_TRACK_PEAK")) { info->haveTrackPeak = !0; info->trackPeak = atof(value); } else if (!_stricmp(name,"REPLAYGAIN_ALBUM_GAIN")) { info->haveAlbumGain = !0; info->albumGain = atof(value); } else if (!_stricmp(name,"REPLAYGAIN_ALBUM_PEAK")) { info->haveAlbumPeak = !0; info->albumPeak = atof(value); } else if (!_stricmp(name,"MP3GAIN_UNDO")) { /* value should be something like "+003,+003,W" */ info->haveUndo = !0; vp = value; memcpy(tmpString,vp,4); tmpString[4] = '\0'; info->undoLeft = atoi(tmpString); vp = vp + 5; /* skip the comma, too */ memcpy(tmpString,vp,4); tmpString[4] = '\0'; info->undoRight = atoi(tmpString); vp = vp + 5; /* skip the comma, too */ if ((*vp == 'w')||(*vp == 'W')) { info->undoWrap = !0; } else { info->undoWrap = 0; } } else if (!_stricmp(name,"MP3GAIN_MINMAX")) { /* value should be something like "001,153" */ info->haveMinMaxGain = !0; vp = value; memcpy(tmpString,vp,3); tmpString[3] = '\0'; info->minGain = atoi(tmpString); vp = vp + 4; /* skip the comma, too */ memcpy(tmpString,vp,3); tmpString[3] = '\0'; info->maxGain = atoi(tmpString); } else if (!_stricmp(name,"MP3GAIN_ALBUM_MINMAX")) { /* value should be something like "001,153" */ info->haveAlbumMinMaxGain = !0; vp = value; memcpy(tmpString,vp,3); tmpString[3] = '\0'; info->albumMinGain = atoi(tmpString); vp = vp + 4; /* skip the comma, too */ memcpy(tmpString,vp,3); tmpString[3] = '\0'; info->albumMaxGain = atoi(tmpString); } else { memcpy((*apeTag)->otherFields + (*apeTag)->otherFieldsSize, p - 8, 8 + isize + 1 + vsize); (*apeTag)->otherFieldsSize += 8 + isize + 1 + vsize; otherFieldsCount++; } } if ( isize > 0 && vsize > 0 ) { if (is_info) { } else { } } free(value); free(name); p += isize + 1 + vsize; } free (buff); *tag_offset -= TagLen; (*apeTag)->originalTagSize = TagLen; if ( Read_LE_Uint32 (T.Flags) & (1<<31) ) { // Tag contains header if (*tag_offset < (long)(sizeof(T))) return 0; *tag_offset -= sizeof (T); fseek (fp, *tag_offset, SEEK_SET); fread (&((*apeTag)->header),1,sizeof(T),fp); (*apeTag)->haveHeader = !0; (*apeTag)->originalTagSize += sizeof(T); } if (otherFieldsCount != origTagCount) { Write_LE_Uint32((*apeTag)->footer.Length, sizeof(T) + (*apeTag)->otherFieldsSize); Write_LE_Uint32((*apeTag)->footer.TagCount, otherFieldsCount); if ((*apeTag)->haveHeader) { Write_LE_Uint32((*apeTag)->header.Length, sizeof(T) + (*apeTag)->otherFieldsSize); Write_LE_Uint32((*apeTag)->header.TagCount, otherFieldsCount); } } return 1; } int truncate_file (char *filename, long truncLength) { #ifdef WIN32 int fh, result; /* Open a file */ if( (fh = _open(filename, _O_RDWR)) != -1 ) { if( ( result = _chsize( fh, truncLength ) ) == 0 ) { _close(fh); return 1; } else { _close(fh); return 0; } } else { return 0; } #else int fd; fd = open(filename, O_RDWR); if (fd < 0) return 0; if (ftruncate(fd, truncLength)) { close(fd); passError( MP3GAIN_UNSPECIFED_ERROR, 3, "Could not truncate ", filename, "\n"); return 0; } close(fd); return 1; #endif } /** * Read gain information from an APE tag. * * Look for an APE tag at the end of the MP3 file, and extract * gain information from it. Any ID3v1 or Lyrics3v2 tags at the end * of the file are read and stored, but not processed. */ int ReadMP3GainAPETag (char *filename, struct MP3GainTagInfo *info, struct FileTagsStruct *fileTags) { FILE *fi; long tag_offset, offs_bk, file_size; fi = fopen(filename, "rb"); if (fi == NULL) return 0; fseek(fi, 0, SEEK_END); tag_offset = file_size = ftell(fi); fileTags->lyrics3TagSize = 0; do { offs_bk = tag_offset; ReadMP3APETag ( fi, info, &(fileTags->apeTag), &tag_offset ); ReadMP3Lyrics3v2Tag ( fi, &(fileTags->lyrics3tag), &(fileTags->lyrics3TagSize), &(fileTags->id31tag), &tag_offset ); ReadMP3ID3v1Tag ( fi, &(fileTags->id31tag), &tag_offset ); } while ( offs_bk != tag_offset ); if (tag_offset >= 0 && tag_offset <= file_size) { fileTags->tagOffset = tag_offset; } else { //Corrupt tag information, simply default to end-of-file fileTags->tagOffset = file_size; } fclose(fi); return 1; }; /** * (Re-)Write gain information to an APEv2 tag. * * You need to have already called ReadMP3GainTag and filled in the info * and fileTags structures. */ int WriteMP3GainAPETag (char *filename, struct MP3GainTagInfo *info, struct FileTagsStruct *fileTags, int saveTimeStamp) { FILE *outputFile; unsigned long newTagLength; unsigned long newTagCount; char *newFieldData; char *mp3gainTagData; unsigned long mp3gainTagLength; char valueString[100]; struct APETagFooterStruct newFooter; struct APETagFooterStruct newHeader; if (saveTimeStamp) fileTime(filename, storeTime); /* For the new tag, we'll have a footer _AND_ header (whether or not a header was in the original */ newTagLength = sizeof(struct APETagFooterStruct) * 2; newTagCount = 0; mp3gainTagLength = 0; if (fileTags->apeTag) { /* For the new tag, we'll have the non-MP3Gain fields from the original tag */ newTagLength += fileTags->apeTag->otherFieldsSize; newTagCount += Read_LE_Uint32(fileTags->apeTag->footer.TagCount); } if (info->haveAlbumGain) { /* 8 bytes + "REPLAYGAIN_ALBUM_GAIN" + '/0' + "+2.456789 dB" = 42 bytes */ mp3gainTagLength += 42; newTagCount++; } if (info->haveAlbumPeak) { /* 8 bytes + "REPLAYGAIN_ALBUM_PEAK" + '/0' + "1.345678" = 38 bytes */ mp3gainTagLength += 38; newTagCount++; } if (info->haveTrackGain) { /* 8 bytes + "REPLAYGAIN_TRACK_GAIN" + '/0' + "+2.456789 dB" = 42 bytes */ mp3gainTagLength += 42; newTagCount++; } if (info->haveTrackPeak) { /* 8 bytes + "REPLAYGAIN_TRACK_PEAK" + '/0' + "1.345678" = 38 bytes */ mp3gainTagLength += 38; newTagCount++; } if (info->haveMinMaxGain) { /* 8 bytes + "MP3GAIN_MINMAX" + '/0' + "123,123" = 30 bytes */ mp3gainTagLength += 30; newTagCount++; } if (info->haveAlbumMinMaxGain) { /* 8 bytes + "MP3GAIN_ALBUM_MINMAX" + '/0' + "123,123" = 36 bytes */ mp3gainTagLength += 36; newTagCount++; } if (info->haveUndo) { /* 8 bytes + "MP3GAIN_UNDO" + '/0' + "+123,+123,W" = 32 bytes */ mp3gainTagLength += 32; newTagCount++; } newTagLength += mp3gainTagLength; newFieldData = (char *)malloc(newTagLength - (sizeof(newFooter) + sizeof(newHeader))); mp3gainTagData = newFieldData; if (fileTags->apeTag) { /* Check if the new tag will be shorter than the old tag */ if (fileTags->apeTag->originalTagSize > newTagLength) { /* we'll need to truncate the file */ if (!truncate_file(filename, fileTags->tagOffset)) { return 0; } } memcpy(&newFooter,&(fileTags->apeTag->footer),sizeof(newFooter)); Write_LE_Uint32(newFooter.Length, newTagLength - sizeof(newHeader)); Write_LE_Uint32(newFooter.TagCount, newTagCount); if (fileTags->apeTag->haveHeader) { memcpy(&newHeader,&(fileTags->apeTag->header), sizeof(newHeader)); Write_LE_Uint32(newHeader.Length, newTagLength - sizeof(newFooter)); Write_LE_Uint32(newHeader.TagCount, newTagCount); } else { memcpy(newHeader.ID,"APETAGEX",sizeof(newHeader.ID)); Write_LE_Uint32(newHeader.Version,2000); Write_LE_Uint32(newHeader.Length, newTagLength - sizeof(newFooter)); Write_LE_Uint32(newHeader.TagCount,newTagCount); Write_LE_Uint32(newHeader.Flags,1<<31 | 1<<29); /* tag has header, this _is_ the header */ memset(newHeader.Reserved,0,sizeof(newHeader.Reserved)); /* and don't forget to fix the footer so that it now shows that the tag has a header! */ Write_LE_Uint32(newFooter.Flags, Read_LE_Uint32(newFooter.Flags) | 1<<31); } if (fileTags->apeTag->otherFieldsSize) { memcpy(newFieldData, fileTags->apeTag->otherFields, fileTags->apeTag->otherFieldsSize); mp3gainTagData += fileTags->apeTag->otherFieldsSize; } } else { /* we don't already have an APE tag, so create one from scratch */ memcpy(newHeader.ID,"APETAGEX",sizeof(newHeader.ID)); Write_LE_Uint32(newHeader.Version,2000); Write_LE_Uint32(newHeader.Length, newTagLength - sizeof(newFooter)); Write_LE_Uint32(newHeader.TagCount,newTagCount); Write_LE_Uint32(newHeader.Flags,1<<31 | 1<<29); /* tag has header, this _is_ the header */ memset(newHeader.Reserved,0,sizeof(newHeader.Reserved)); memcpy(newFooter.ID,"APETAGEX",sizeof(newFooter.ID)); Write_LE_Uint32(newFooter.Version,2000); Write_LE_Uint32(newFooter.Length, newTagLength - sizeof(newHeader)); Write_LE_Uint32(newFooter.TagCount,newTagCount); Write_LE_Uint32(newFooter.Flags,1<<31); /* tag has header */ memset(newFooter.Reserved,0,sizeof(newFooter.Reserved)); } if (info->haveMinMaxGain) { /* 8 bytes + "MP3GAIN_MINMAX" + '/0' + "123,123" = 30 bytes */ Write_LE_Uint32(mp3gainTagData,7); mp3gainTagData += 4; Write_LE_Uint32(mp3gainTagData,0); mp3gainTagData += 4; strcpy(mp3gainTagData, "MP3GAIN_MINMAX"); mp3gainTagData += 15; sprintf(mp3gainTagData,"%03d", info->minGain); /* write directly to tag buffer, because we'll replace the trailing null char anyhow */ mp3gainTagData[3] = ','; mp3gainTagData += 4; sprintf(valueString,"%03d", info->maxGain); memcpy(mp3gainTagData, valueString, 3); /* DON'T write trailing null char */ mp3gainTagData += 3; } if (info->haveAlbumMinMaxGain) { /* 8 bytes + "MP3GAIN_ALBUM_MINMAX" + '/0' + "123,123" = 36 bytes */ Write_LE_Uint32(mp3gainTagData,7); mp3gainTagData += 4; Write_LE_Uint32(mp3gainTagData,0); mp3gainTagData += 4; strcpy(mp3gainTagData, "MP3GAIN_ALBUM_MINMAX"); mp3gainTagData += 21; sprintf(mp3gainTagData,"%03d", info->albumMinGain); /* write directly to tag buffer, because we'll replace the trailing null char anyhow */ mp3gainTagData[3] = ','; mp3gainTagData += 4; sprintf(valueString,"%03d", info->albumMaxGain); memcpy(mp3gainTagData, valueString, 3); /* DON'T write trailing null char */ mp3gainTagData += 3; } if (info->haveUndo) { /* 8 bytes + "MP3GAIN_UNDO" + '/0' + "+234,+234,W" = 32 bytes */ Write_LE_Uint32(mp3gainTagData,11); mp3gainTagData += 4; Write_LE_Uint32(mp3gainTagData,0); mp3gainTagData += 4; strcpy(mp3gainTagData, "MP3GAIN_UNDO"); mp3gainTagData += 13; sprintf(mp3gainTagData,"%+04d", info->undoLeft); /* write directly to tag buffer, because we'll replace the trailing null char anyhow */ mp3gainTagData[4] = ','; mp3gainTagData += 5; sprintf(mp3gainTagData,"%+04d", info->undoRight); /* write directly to tag buffer, because we'll replace the trailing null char anyhow */ mp3gainTagData[4] = ','; mp3gainTagData += 5; if (info->undoWrap) { *mp3gainTagData = 'W'; } else { *mp3gainTagData = 'N'; } mp3gainTagData++; } if (info->haveTrackGain) { /* 8 bytes + "REPLAYGAIN_TRACK_GAIN" + '/0' + "+2.456789 dB" = 42 bytes */ Write_LE_Uint32(mp3gainTagData,12); mp3gainTagData += 4; Write_LE_Uint32(mp3gainTagData,0); mp3gainTagData += 4; strcpy(mp3gainTagData, "REPLAYGAIN_TRACK_GAIN"); mp3gainTagData += 22; sprintf(valueString,"%-+9.6f", info->trackGain); memcpy(mp3gainTagData, valueString, 9); mp3gainTagData += 9; memcpy(mp3gainTagData, " dB", 3); mp3gainTagData += 3; } if (info->haveTrackPeak) { /* 8 bytes + "REPLAYGAIN_TRACK_PEAK" + '/0' + "1.345678" = 38 bytes */ Write_LE_Uint32(mp3gainTagData,8); mp3gainTagData += 4; Write_LE_Uint32(mp3gainTagData,0); mp3gainTagData += 4; strcpy(mp3gainTagData, "REPLAYGAIN_TRACK_PEAK"); mp3gainTagData += 22; sprintf(valueString,"%-8.6f", info->trackPeak); memcpy(mp3gainTagData, valueString, 8); mp3gainTagData += 8; } if (info->haveAlbumGain) { /* 8 bytes + "REPLAYGAIN_ALBUM_GAIN" + '/0' + "+2.456789 dB" = 42 bytes */ Write_LE_Uint32(mp3gainTagData,12); mp3gainTagData += 4; Write_LE_Uint32(mp3gainTagData,0); mp3gainTagData += 4; strcpy(mp3gainTagData, "REPLAYGAIN_ALBUM_GAIN"); mp3gainTagData += 22; sprintf(valueString,"%-+9.6f", info->albumGain); memcpy(mp3gainTagData, valueString, 9); mp3gainTagData += 9; memcpy(mp3gainTagData, " dB", 3); mp3gainTagData += 3; } if (info->haveAlbumPeak) { /* 8 bytes + "REPLAYGAIN_ALBUM_PEAK" + '/0' + "1.345678" = 38 bytes */ Write_LE_Uint32(mp3gainTagData,8); mp3gainTagData += 4; Write_LE_Uint32(mp3gainTagData,0); mp3gainTagData += 4; strcpy(mp3gainTagData, "REPLAYGAIN_ALBUM_PEAK"); mp3gainTagData += 22; sprintf(valueString,"%-8.6f", info->albumPeak); memcpy(mp3gainTagData, valueString, 8); mp3gainTagData += 8; } outputFile = fopen(filename,"r+b"); if (outputFile == NULL) { passError( MP3GAIN_UNSPECIFED_ERROR, 3, "\nCan't open ", filename, " for modifying\n"); return 0; } fseek(outputFile,fileTags->tagOffset,SEEK_SET); if (newTagCount > 0) { /* write APE tag */ fwrite(&newHeader,1,sizeof(newHeader),outputFile); fwrite(newFieldData, 1, newTagLength - (sizeof(newFooter) + sizeof(newHeader)), outputFile); fwrite(&newFooter,1,sizeof(newFooter),outputFile); } if (fileTags->lyrics3TagSize > 0) { fwrite(fileTags->lyrics3tag, 1, fileTags->lyrics3TagSize, outputFile); } else if (fileTags->id31tag) { //by definition, Lyrics3 tag includes id3 tag, fwrite(fileTags->id31tag, 1, 128, outputFile); //so only write id3 tag alone if } //no Lyrics3 tag fclose(outputFile); if (saveTimeStamp) fileTime(filename,setStoredTime); free(newFieldData); return 1; }; /** * Remove gain information from the APE tag. */ int RemoveMP3GainAPETag (char *filename, int saveTimeStamp) { struct MP3GainTagInfo info; struct FileTagsStruct fileTags; info.dirty = 0; info.haveAlbumGain = 0; info.haveAlbumPeak = 0; info.haveTrackGain = 0; info.haveTrackPeak = 0; info.haveMinMaxGain = 0; info.haveAlbumMinMaxGain = 0; info.haveUndo = 0; fileTags.apeTag = NULL; fileTags.id31tag = NULL; fileTags.lyrics3tag = NULL; fileTags.lyrics3TagSize = 0; ReadMP3GainAPETag(filename, &info, &fileTags); /* if any MP3Gain tags exist, then we're going to change the tag */ if (info.haveAlbumGain || info.haveAlbumPeak || info.haveTrackGain || info.haveTrackPeak || info.haveMinMaxGain || info.haveAlbumMinMaxGain || info.haveUndo) info.dirty = !0; info.haveAlbumGain = 0; info.haveAlbumPeak = 0; info.haveTrackGain = 0; info.haveTrackPeak = 0; info.haveMinMaxGain = 0; info.haveAlbumMinMaxGain = 0; info.haveUndo = 0; if (info.dirty) WriteMP3GainAPETag(filename, &info, &fileTags, saveTimeStamp); return 1; }; apetag.h0000644000175000017500000000400713303547222012670 0ustar snhardinsnhardin#ifndef APETAG_H #define APETAG_H #include struct MP3GainTagInfo { int haveTrackGain; int haveTrackPeak; int haveAlbumGain; int haveAlbumPeak; int haveUndo; int haveMinMaxGain; int haveAlbumMinMaxGain; double trackGain; double trackPeak; double albumGain; double albumPeak; int undoLeft; int undoRight; int undoWrap; /* undoLeft and undoRight will be the same 95% of the time. mp3gain DOES have a command-line switch to adjust the gain on just one channel, though. The "undoWrap" field indicates whether or not "wrapping" was turned on when the mp3 was adjusted */ unsigned char minGain; unsigned char maxGain; unsigned char albumMinGain; unsigned char albumMaxGain; /* minGain and maxGain are the current minimum and maximum values of the "global gain" fields in the frames of the mp3 file */ int dirty; /* flag if data changes after loaded from file */ int recalc; /* Used to signal if recalculation is required */ }; struct APEFieldStruct { unsigned long vsize; unsigned long isize; unsigned long flags; char *name; char *value; }; struct APETagFooterStruct { char ID [8]; char Version [4]; char Length [4]; char TagCount [4]; char Flags [4]; char Reserved [8]; }; struct APETagStruct { unsigned long originalTagSize; int haveHeader; struct APETagFooterStruct header; struct APETagFooterStruct footer; unsigned char *otherFields; /* i.e. other than MP3Gain */ unsigned long otherFieldsSize; }; struct FileTagsStruct { long tagOffset; struct APETagStruct *apeTag; unsigned char *lyrics3tag; unsigned long lyrics3TagSize; unsigned char *id31tag; }; int ReadMP3GainAPETag (char *filename, struct MP3GainTagInfo *info, struct FileTagsStruct *fileTags); int WriteMP3GainAPETag (char *filename, struct MP3GainTagInfo *info, struct FileTagsStruct *fileTags, int saveTimeStamp); int RemoveMP3GainAPETag (char *filename, int saveTimeStamp); #endif gain_analysis.c0000644000175000017500000005344313303547222014253 0ustar snhardinsnhardin/* * ReplayGainAnalysis - analyzes input samples and give the recommended dB change * Copyright (C) 2001-2009 David Robinson and Glen Sawyer * Improvements and optimizations added by Frank Klemm, and by Marcel Müller * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * concept and filter values by David Robinson (David@Robinson.org) * -- blame him if you think the idea is flawed * original coding by Glen Sawyer (mp3gain@hotmail.com) * -- blame him if you think this runs too slowly, or the coding is otherwise flawed * * lots of code improvements by Frank Klemm ( http://www.uni-jena.de/~pfk/mpp/ ) * -- credit him for all the _good_ programming ;) * * * For an explanation of the concepts and the basic algorithms involved, go to: * http://www.replaygain.org/ */ /* * Here's the deal. Call * * InitGainAnalysis ( long samplefreq ); * * to initialize everything. Call * * AnalyzeSamples ( const Float_t* left_samples, * const Float_t* right_samples, * size_t num_samples, * int num_channels ); * * as many times as you want, with as many or as few samples as you want. * If mono, pass the sample buffer in through left_samples, leave * right_samples NULL, and make sure num_channels = 1. * * GetTitleGain() * * will return the recommended dB level change for all samples analyzed * SINCE THE LAST TIME you called GetTitleGain() OR InitGainAnalysis(). * * GetAlbumGain() * * will return the recommended dB level change for all samples analyzed * since InitGainAnalysis() was called and finalized with GetTitleGain(). * * Pseudo-code to process an album: * * Float_t l_samples [4096]; * Float_t r_samples [4096]; * size_t num_samples; * unsigned int num_songs; * unsigned int i; * * InitGainAnalysis ( 44100 ); * for ( i = 1; i <= num_songs; i++ ) { * while ( ( num_samples = getSongSamples ( song[i], left_samples, right_samples ) ) > 0 ) * AnalyzeSamples ( left_samples, right_samples, num_samples, 2 ); * fprintf ("Recommended dB change for song %2d: %+6.2f dB\n", i, GetTitleGain() ); * } * fprintf ("Recommended dB change for whole album: %+6.2f dB\n", GetAlbumGain() ); */ /* * So here's the main source of potential code confusion: * * The filters applied to the incoming samples are IIR filters, * meaning they rely on up to number of previous samples * AND up to number of previous filtered samples. * * I set up the AnalyzeSamples routine to minimize memory usage and interface * complexity. The speed isn't compromised too much (I don't think), but the * internal complexity is higher than it should be for such a relatively * simple routine. * * Optimization/clarity suggestions are welcome. */ #include #include #include #include #include "gain_analysis.h" typedef unsigned short Uint16_t; typedef signed short Int16_t; typedef unsigned int Uint32_t; typedef signed int Int32_t; #define YULE_ORDER 10 #define BUTTER_ORDER 2 #define YULE_FILTER filterYule #define BUTTER_FILTER filterButter #define RMS_PERCENTILE 0.95 // percentile which is louder than the proposed level #define MAX_SAMP_FREQ_KHZ 96 // maximum allowed sample frequency [kHz] #define RMS_WINDOW_TIME_MS 50 // Time slice size [ms] #define STEPS_per_dB 100 // Table entries per dB #define MAX_dB 120 // Table entries for 0...MAX_dB (normal max. values are 70...80 dB) #define MAX_ORDER (BUTTER_ORDER > YULE_ORDER ? BUTTER_ORDER : YULE_ORDER) #define MAX_SAMPLES_PER_WINDOW (size_t) (MAX_SAMP_FREQ_KHZ * RMS_WINDOW_TIME_MS + 1) // max. Samples per Time slice #define PINK_REF 64.82 //298640883795 // calibration value Float_t linprebuf [MAX_ORDER * 2]; Float_t* linpre; // left input samples, with pre-buffer Float_t lstepbuf [MAX_SAMPLES_PER_WINDOW + MAX_ORDER]; Float_t* lstep; // left "first step" (i.e. post first filter) samples Float_t loutbuf [MAX_SAMPLES_PER_WINDOW + MAX_ORDER]; Float_t* lout; // left "out" (i.e. post second filter) samples Float_t rinprebuf [MAX_ORDER * 2]; Float_t* rinpre; // right input samples ... Float_t rstepbuf [MAX_SAMPLES_PER_WINDOW + MAX_ORDER]; Float_t* rstep; Float_t routbuf [MAX_SAMPLES_PER_WINDOW + MAX_ORDER]; Float_t* rout; long sampleWindow; // number of samples required to reach number of milliseconds required for RMS window long totsamp; double lsum; double rsum; int freqindex; int first; static Uint32_t A [(size_t)(STEPS_per_dB * MAX_dB)]; static Uint32_t B [(size_t)(STEPS_per_dB * MAX_dB)]; // for each filter: // [0] 48 kHz, [1] 44.1 kHz, [2] 32 kHz, [3] 24 kHz, [4] 22050 Hz, [5] 16 kHz, [6] 12 kHz, [7] is 11025 Hz, [8] 8 kHz #ifdef WIN32 #ifndef __GNUC__ #pragma warning ( disable : 4305 ) #endif #endif static const Float_t ABYule[12][2*YULE_ORDER + 1] = { {0.006471345933032, -7.22103125152679, -0.02567678242161, 24.7034187975904, 0.049805860704367, -52.6825833623896, -0.05823001743528, 77.4825736677539, 0.040611847441914, -82.0074753444205, -0.010912036887501, 63.1566097101925, -0.00901635868667, -34.889569769245, 0.012448886238123, 13.2126852760198, -0.007206683749426, -3.09445623301669, 0.002167156433951, 0.340344741393305, -0.000261819276949}, {0.015415414474287, -7.19001570087017, -0.07691359399407, 24.4109412087159, 0.196677418516518, -51.6306373580801, -0.338855114128061, 75.3978476863163, 0.430094579594561, -79.4164552507386, -0.415015413747894, 61.0373661948115, 0.304942508151101, -33.7446462547014, -0.166191795926663, 12.8168791146274, 0.063198189938739, -3.01332198541437, -0.015003978694525, 0.223619893831468, 0.001748085184539}, {0.021776466467053, -5.74819833657784, -0.062376961003801, 16.246507961894, 0.107731165328514, -29.9691822642542, -0.150994515142316, 40.027597579378, 0.170334807313632, -40.3209196052655, -0.157984942890531, 30.8542077487718, 0.121639833268721, -17.5965138737281, -0.074094040816409, 7.10690214103873, 0.031282852041061, -1.82175564515191, -0.00755421235941, 0.223619893831468, 0.00117925454213 }, {0.03857599435200, -3.84664617118067, -0.02160367184185, 7.81501653005538, -0.00123395316851, -11.34170355132042, -0.00009291677959, 13.05504219327545, -0.01655260341619, -12.28759895145294, 0.02161526843274, 9.48293806319790, -0.02074045215285, -5.87257861775999, 0.00594298065125, 2.75465861874613, 0.00306428023191, -0.86984376593551, 0.00012025322027, 0.13919314567432, 0.00288463683916 }, {0.05418656406430, -3.47845948550071, -0.02911007808948, 6.36317777566148, -0.00848709379851, -8.54751527471874, -0.00851165645469, 9.47693607801280, -0.00834990904936, -8.81498681370155, 0.02245293253339, 6.85401540936998, -0.02596338512915, -4.39470996079559, 0.01624864962975, 2.19611684890774, -0.00240879051584, -0.75104302451432, 0.00674613682247, 0.13149317958808, -0.00187763777362 }, {0.15457299681924, -2.37898834973084, -0.09331049056315, 2.84868151156327, -0.06247880153653, -2.64577170229825, 0.02163541888798, 2.23697657451713, -0.05588393329856, -1.67148153367602, 0.04781476674921, 1.00595954808547, 0.00222312597743, -0.45953458054983, 0.03174092540049, 0.16378164858596, -0.01390589421898, -0.05032077717131, 0.00651420667831, 0.02347897407020, -0.00881362733839 }, {0.30296907319327, -1.61273165137247, -0.22613988682123, 1.07977492259970, -0.08587323730772, -0.25656257754070, 0.03282930172664, -0.16276719120440, -0.00915702933434, -0.22638893773906, -0.02364141202522, 0.39120800788284, -0.00584456039913, -0.22138138954925, 0.06276101321749, 0.04500235387352, -0.00000828086748, 0.02005851806501, 0.00205861885564, 0.00302439095741, -0.02950134983287 }, {0.33642304856132, -1.49858979367799, -0.25572241425570, 0.87350271418188, -0.11828570177555, 0.12205022308084, 0.11921148675203, -0.80774944671438, -0.07834489609479, 0.47854794562326, -0.00469977914380, -0.12453458140019, -0.00589500224440, -0.04067510197014, 0.05724228140351, 0.08333755284107, 0.00832043980773, -0.04237348025746, -0.01635381384540, 0.02977207319925, -0.01760176568150 }, {0.44915256608450, -0.62820619233671, -0.14351757464547, 0.29661783706366, -0.22784394429749, -0.37256372942400, -0.01419140100551, 0.00213767857124, 0.04078262797139, -0.42029820170918, -0.12398163381748, 0.22199650564824, 0.04097565135648, 0.00613424350682, 0.10478503600251, 0.06747620744683, -0.01863887810927, 0.05784820375801, -0.03193428438915, 0.03222754072173, 0.00541907748707 }, {0.56619470757641, -1.04800335126349, -0.75464456939302, 0.29156311971249, 0.16242137742230, -0.26806001042947, 0.16744243493672, 0.00819999645858, -0.18901604199609, 0.45054734505008, 0.30931782841830, -0.33032403314006, -0.27562961986224, 0.06739368333110, 0.00647310677246, -0.04784254229033, 0.08647503780351, 0.01639907836189, -0.03788984554840, 0.01807364323573, -0.00588215443421 }, {0.58100494960553, -0.51035327095184, -0.53174909058578, -0.31863563325245, -0.14289799034253, -0.20256413484477, 0.17520704835522, 0.14728154134330, 0.02377945217615, 0.38952639978999, 0.15558449135573, -0.23313271880868, -0.25344790059353, -0.05246019024463, 0.01628462406333, -0.02505961724053, 0.06920467763959, 0.02442357316099, -0.03721611395801, 0.01818801111503, -0.00749618797172 }, {0.53648789255105, -0.25049871956020, -0.42163034350696, -0.43193942311114, -0.00275953611929, -0.03424681017675, 0.04267842219415, -0.04678328784242, -0.10214864179676, 0.26408300200955, 0.14590772289388, 0.15113130533216, -0.02459864859345, -0.17556493366449, -0.11202315195388, -0.18823009262115, -0.04060034127000, 0.05477720428674, 0.04788665548180, 0.04704409688120, -0.02217936801134 } }; static const Float_t ABButter[12][2*BUTTER_ORDER + 1] = { {0.99308203517541, -1.98611621154089, -1.98616407035082, 0.986211929160751, 0.99308203517541 }, {0.992472550461293,-1.98488843762334, -1.98494510092258, 0.979389350028798, 0.992472550461293}, {0.989641019334721,-1.97917472731008, -1.97928203866944, 0.979389350028798, 0.989641019334721}, {0.98621192462708, -1.97223372919527, -1.97242384925416, 0.97261396931306, 0.98621192462708 }, {0.98500175787242, -1.96977855582618, -1.97000351574484, 0.97022847566350, 0.98500175787242 }, {0.97938932735214, -1.95835380975398, -1.95877865470428, 0.95920349965459, 0.97938932735214 }, {0.97531843204928, -1.95002759149878, -1.95063686409857, 0.95124613669835, 0.97531843204928 }, {0.97316523498161, -1.94561023566527, -1.94633046996323, 0.94705070426118, 0.97316523498161 }, {0.96454515552826, -1.92783286977036, -1.92909031105652, 0.93034775234268, 0.96454515552826 }, {0.96009142950541, -1.91858953033784, -1.92018285901082, 0.92177618768381, 0.96009142950541 }, {0.95856916599601, -1.91542108074780, -1.91713833199203, 0.91885558323625, 0.95856916599601 }, {0.94597685600279, -1.88903307939452, -1.89195371200558, 0.89487434461664, 0.94597685600279 } }; #ifdef WIN32 #ifndef __GNUC__ #pragma warning ( default : 4305 ) #endif #endif // When calling these filter procedures, make sure that ip[-order] and op[-order] point to real data! // If your compiler complains that "'operation on 'output' may be undefined", you can // either ignore the warnings or uncomment the three "y" lines (and comment out the indicated line) static void filterYule (const Float_t* input, Float_t* output, size_t nSamples, const Float_t* kernel) { while (nSamples--) { *output = 1e-10 /* 1e-10 is a hack to avoid slowdown because of denormals */ + input [0] * kernel[0] - output[-1] * kernel[1] + input [-1] * kernel[2] - output[-2] * kernel[3] + input [-2] * kernel[4] - output[-3] * kernel[5] + input [-3] * kernel[6] - output[-4] * kernel[7] + input [-4] * kernel[8] - output[-5] * kernel[9] + input [-5] * kernel[10] - output[-6] * kernel[11] + input [-6] * kernel[12] - output[-7] * kernel[13] + input [-7] * kernel[14] - output[-8] * kernel[15] + input [-8] * kernel[16] - output[-9] * kernel[17] + input [-9] * kernel[18] - output[-10]* kernel[19] + input [-10]* kernel[20]; ++output; ++input; } } static void filterButter (const Float_t* input, Float_t* output, size_t nSamples, const Float_t* kernel) { while (nSamples--) { *output = input [0] * kernel[0] - output[-1] * kernel[1] + input [-1] * kernel[2] - output[-2] * kernel[3] + input [-2] * kernel[4]; ++output; ++input; } } // returns a INIT_GAIN_ANALYSIS_OK if successful, INIT_GAIN_ANALYSIS_ERROR if not int ResetSampleFrequency ( long samplefreq ) { int i; // zero out initial values for ( i = 0; i < MAX_ORDER; i++ ) linprebuf[i] = lstepbuf[i] = loutbuf[i] = rinprebuf[i] = rstepbuf[i] = routbuf[i] = 0.; switch ( (int)(samplefreq) ) { case 96000: freqindex = 0; break; case 88200: freqindex = 1; break; case 64000: freqindex = 2; break; case 48000: freqindex = 3; break; case 44100: freqindex = 4; break; case 32000: freqindex = 5; break; case 24000: freqindex = 6; break; case 22050: freqindex = 7; break; case 16000: freqindex = 8; break; case 12000: freqindex = 9; break; case 11025: freqindex = 10; break; case 8000: freqindex = 11; break; default: return INIT_GAIN_ANALYSIS_ERROR; } sampleWindow = (int) ceil (samplefreq * RMS_WINDOW_TIME_MS / 1000.); lsum = 0.; rsum = 0.; totsamp = 0; memset ( A, 0, sizeof(A) ); return INIT_GAIN_ANALYSIS_OK; } int InitGainAnalysis ( long samplefreq ) { if (ResetSampleFrequency(samplefreq) != INIT_GAIN_ANALYSIS_OK) { return INIT_GAIN_ANALYSIS_ERROR; } linpre = linprebuf + MAX_ORDER; rinpre = rinprebuf + MAX_ORDER; lstep = lstepbuf + MAX_ORDER; rstep = rstepbuf + MAX_ORDER; lout = loutbuf + MAX_ORDER; rout = routbuf + MAX_ORDER; memset ( B, 0, sizeof(B) ); return INIT_GAIN_ANALYSIS_OK; } // returns GAIN_ANALYSIS_OK if successful, GAIN_ANALYSIS_ERROR if not static __inline double fsqr(const double d) { return d*d; } int AnalyzeSamples ( const Float_t* left_samples, const Float_t* right_samples, size_t num_samples, int num_channels ) { const Float_t* curleft; const Float_t* curright; long batchsamples; long cursamples; long cursamplepos; int i; if ( num_samples == 0 ) return GAIN_ANALYSIS_OK; cursamplepos = 0; batchsamples = (long)num_samples; switch ( num_channels) { case 1: right_samples = left_samples; case 2: break; default: return GAIN_ANALYSIS_ERROR; } if ( num_samples < MAX_ORDER ) { memcpy ( linprebuf + MAX_ORDER, left_samples , num_samples * sizeof(Float_t) ); memcpy ( rinprebuf + MAX_ORDER, right_samples, num_samples * sizeof(Float_t) ); } else { memcpy ( linprebuf + MAX_ORDER, left_samples, MAX_ORDER * sizeof(Float_t) ); memcpy ( rinprebuf + MAX_ORDER, right_samples, MAX_ORDER * sizeof(Float_t) ); } while ( batchsamples > 0 ) { cursamples = batchsamples > sampleWindow-totsamp ? sampleWindow - totsamp : batchsamples; if ( cursamplepos < MAX_ORDER ) { curleft = linpre+cursamplepos; curright = rinpre+cursamplepos; if (cursamples > MAX_ORDER - cursamplepos ) cursamples = MAX_ORDER - cursamplepos; } else { curleft = left_samples + cursamplepos; curright = right_samples + cursamplepos; } YULE_FILTER ( curleft , lstep + totsamp, cursamples, ABYule[freqindex]); YULE_FILTER ( curright, rstep + totsamp, cursamples, ABYule[freqindex]); BUTTER_FILTER ( lstep + totsamp, lout + totsamp, cursamples, ABButter[freqindex]); BUTTER_FILTER ( rstep + totsamp, rout + totsamp, cursamples, ABButter[freqindex]); curleft = lout + totsamp; // Get the squared values curright = rout + totsamp; i = cursamples % 16; while (i--) { lsum += fsqr(*curleft++); rsum += fsqr(*curright++); } i = cursamples / 16; while (i--) { lsum += fsqr(curleft[0]) + fsqr(curleft[1]) + fsqr(curleft[2]) + fsqr(curleft[3]) + fsqr(curleft[4]) + fsqr(curleft[5]) + fsqr(curleft[6]) + fsqr(curleft[7]) + fsqr(curleft[8]) + fsqr(curleft[9]) + fsqr(curleft[10]) + fsqr(curleft[11]) + fsqr(curleft[12]) + fsqr(curleft[13]) + fsqr(curleft[14]) + fsqr(curleft[15]); curleft += 16; rsum += fsqr(curright[0]) + fsqr(curright[1]) + fsqr(curright[2]) + fsqr(curright[3]) + fsqr(curright[4]) + fsqr(curright[5]) + fsqr(curright[6]) + fsqr(curright[7]) + fsqr(curright[8]) + fsqr(curright[9]) + fsqr(curright[10]) + fsqr(curright[11]) + fsqr(curright[12]) + fsqr(curright[13]) + fsqr(curright[14]) + fsqr(curright[15]); curright += 16; } batchsamples -= cursamples; cursamplepos += cursamples; totsamp += cursamples; if ( totsamp == sampleWindow ) { // Get the Root Mean Square (RMS) for this set of samples double val = STEPS_per_dB * 10. * log10 ( (lsum+rsum) / totsamp * 0.5 + 1.e-37 ); int ival = (int) val; if ( ival < 0 ) ival = 0; if ( ival >= (int)(sizeof(A)/sizeof(*A)) ) ival = sizeof(A)/sizeof(*A) - 1; A [ival]++; lsum = rsum = 0.; memmove ( loutbuf , loutbuf + totsamp, MAX_ORDER * sizeof(Float_t) ); memmove ( routbuf , routbuf + totsamp, MAX_ORDER * sizeof(Float_t) ); memmove ( lstepbuf, lstepbuf + totsamp, MAX_ORDER * sizeof(Float_t) ); memmove ( rstepbuf, rstepbuf + totsamp, MAX_ORDER * sizeof(Float_t) ); totsamp = 0; } if ( totsamp > sampleWindow ) // somehow I really screwed up: Error in programming! Contact author about totsamp > sampleWindow return GAIN_ANALYSIS_ERROR; } if ( num_samples < MAX_ORDER ) { memmove ( linprebuf, linprebuf + num_samples, (MAX_ORDER-num_samples) * sizeof(Float_t) ); memmove ( rinprebuf, rinprebuf + num_samples, (MAX_ORDER-num_samples) * sizeof(Float_t) ); memcpy ( linprebuf + MAX_ORDER - num_samples, left_samples, num_samples * sizeof(Float_t) ); memcpy ( rinprebuf + MAX_ORDER - num_samples, right_samples, num_samples * sizeof(Float_t) ); } else { memcpy ( linprebuf, left_samples + num_samples - MAX_ORDER, MAX_ORDER * sizeof(Float_t) ); memcpy ( rinprebuf, right_samples + num_samples - MAX_ORDER, MAX_ORDER * sizeof(Float_t) ); } return GAIN_ANALYSIS_OK; } static Float_t analyzeResult ( Uint32_t* Array, size_t len ) { Uint32_t elems; Int32_t upper; size_t i; elems = 0; for ( i = 0; i < len; i++ ) elems += Array[i]; if ( elems == 0 ) return GAIN_NOT_ENOUGH_SAMPLES; upper = (Int32_t) ceil (elems * (1. - RMS_PERCENTILE)); for ( i = len; i-- > 0; ) { if ( (upper -= Array[i]) <= 0 ) break; } return (Float_t) ((Float_t)PINK_REF - (Float_t)i / (Float_t)STEPS_per_dB); } Float_t GetTitleGain ( void ) { Float_t retval; int i; retval = analyzeResult ( A, sizeof(A)/sizeof(*A) ); for ( i = 0; i < (int)(sizeof(A)/sizeof(*A)); i++ ) { B[i] += A[i]; A[i] = 0; } for ( i = 0; i < MAX_ORDER; i++ ) linprebuf[i] = lstepbuf[i] = loutbuf[i] = rinprebuf[i] = rstepbuf[i] = routbuf[i] = 0.f; totsamp = 0; lsum = rsum = 0.; return retval; } Float_t GetAlbumGain ( void ) { return analyzeResult ( B, sizeof(B)/sizeof(*B) ); } /* end of gain_analysis.c */ gain_analysis.h0000644000175000017500000000376613303547222014263 0ustar snhardinsnhardin/* * ReplayGainAnalysis - analyzes input samples and give the recommended dB change * Copyright (C) 2001-2009 David Robinson and Glen Sawyer * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * concept and filter values by David Robinson (David@Robinson.org) * -- blame him if you think the idea is flawed * coding by Glen Sawyer (mp3gain@hotmail.com) 735 W 255 N, Orem, UT 84057-4505 USA * -- blame him if you think this runs too slowly, or the coding is otherwise flawed * * For an explanation of the concepts and the basic algorithms involved, go to: * http://www.replaygain.org/ */ #ifndef GAIN_ANALYSIS_H #define GAIN_ANALYSIS_H #include #define GAIN_NOT_ENOUGH_SAMPLES -24601 #define GAIN_ANALYSIS_ERROR 0 #define GAIN_ANALYSIS_OK 1 #define INIT_GAIN_ANALYSIS_ERROR 0 #define INIT_GAIN_ANALYSIS_OK 1 #ifdef __cplusplus extern "C" { #endif typedef double Float_t; // Type used for filtering int InitGainAnalysis ( long samplefreq ); int AnalyzeSamples ( const Float_t* left_samples, const Float_t* right_samples, size_t num_samples, int num_channels ); int ResetSampleFrequency ( long samplefreq ); Float_t GetTitleGain ( void ); Float_t GetAlbumGain ( void ); #ifdef __cplusplus } #endif #endif /* GAIN_ANALYSIS_H */ id3tag.c0000644000175000017500000011433713303547222012605 0ustar snhardinsnhardin/** * Handling of Replay Gain information in ID3v2 tags. * * We store Replay Gain data in RVA2 frames inside the ID3v2.4 tag. * Album and track gain are written as two separate frames. * We use the RVA2 frame as follows: * * Identification string: either "track" or "album"; * Channel number: always 1 (master volume); * Volume adjustment: recommended gain relative to 89 dB standard; * Bits representing peak: always 16; * Peak volume: max absolute sample value relative to full scale * * The meaning of the RVA2 peak volume field is not specified in ID3v2.4. * We follow the interpretation of QuodLibet/mutagen: Peak volume is the * maximum absolute sample value relative to full scale, before application * of the volume adjustment, stored as an unsigned fixed point value with * 1 integer bit and (peakbits-1) fractional bits. * * In addition to standard Replay Gain data, we also store mp3gain-specific * fields in TXXX frames. The description string of such frames starts with * "MP3GAIN_". These frames are only needed when mp3gain updates the encoded * audio volume in MP3 stream (-r and -a command line options). * * We read tag data in ID3v2.2, ID3v2.3 or ID3v2.4 format, from either * the beginning or the end of the file. Extended tag headers are ignored * and removed; compressed frames are ignored but preserved. No workarounds * are attempted for invalid ID3v2.4 tags. * * When writing/updating tag data, we always write a single ID3v2.4 tag * at the beginning of the file, fully unsynchronized. If the original * file had an ID3v2.2 or ID3v2.3 tag, it is upgraded to ID3v2.4. If the * original file had only an appended tag, it is moved to the beginning of * the file. If the original file contained multiple tags, we update only * the first tag and ignore the rest (this is bad, but it's probably a * rare case). If the original file had only an ID3v1 tag, it is copied * to a new ID3v2 tag and the ID3v1 tag is left as it was. * * See: http://www.id3.org/id3v2.4.0-structure * http://www.id3.org/id3v2.4.0-frames */ #include #include #include #include #include "apetag.h" #include "id3tag.h" #include "mp3gain.h" #ifdef WIN32 #define strcasecmp(x,y) _stricmp(x,y) #endif #define DBG(x) /*#define DBG(x) (printf x)*/ #define TAGFL_UNSYNC (0x80) #define TAGFL_EXTHDR (0x40) #define TAGFL_EXPR (0x20) #define TAGFL_FOOTER (0x10) #define FRAMEFL_BAD (0x00f0) #define FRAMEFL_TAGALTER (0x4000) #define FRAMEFL_GROUP (0x0040) #define FRAMEFL_COMPR (0x0008) #define FRAMEFL_CRYPT (0x0004) #define FRAMEFL_UNSYNC (0x0002) #define FRAMEFL_DLEN (0x0001) #define SYNCSAFE_INT_BAD (0xffffffff) struct ID3v2TagStruct { unsigned long offset; /* offset of tag in file */ unsigned long length; /* total length of ID3v2 tag, including header/footer */ unsigned int version; /* ID3v2 version */ unsigned int flags; /* tag flags */ struct ID3v2FrameStruct *frames; /* linked list of frames */ }; struct ID3v2FrameStruct { struct ID3v2FrameStruct *next; /* pointer to next frame */ char frameid[4]; /* frame ID */ unsigned int flags; /* frame flags */ unsigned long len; /* length of frame, excluding header */ unsigned long hskip; /* length of flag parameters */ unsigned char *data; /* pointer to data, excluding header */ }; struct upgrade_id3v22_struct { char id_v22[3]; char id_new[4]; }; static const struct upgrade_id3v22_struct upgrade_id3v22_table[] = { { "BUF", "RBUF" }, { "CNT", "PCNT" }, { "COM", "COMM" }, { "CRA", "AENC" }, { "ETC", "ETCO" }, { "EQU", "EQUA" }, { "GEO", "GEOB" }, { "IPL", "IPLS" }, { "LNK", "LINK" }, { "MCI", "MCDI" }, { "MLL", "MLLT" }, { "PIC", "APIC" /* NOTE: incompatible format */ }, { "POP", "POPM" }, { "REV", "RVRB" }, { "RVA", "RVAD" }, { "SLT", "SYLT" }, { "STC", "SYTC" }, { "TAL", "TALB" }, { "TBP", "TBPM" }, { "TCM", "TCOM" }, { "TCO", "TCON" }, { "TCR", "TCOP" }, { "TDA", "TDAT" }, { "TDY", "TDLY" }, { "TEN", "TENC" }, { "TFT", "TFLT" }, { "TIM", "TIME" }, { "TKE", "TKEY" }, { "TLA", "TLAN" }, { "TLE", "TLEN" }, { "TMT", "TMED" }, { "TOA", "TOPE" }, { "TOF", "TOFN" }, { "TOL", "TOLY" }, { "TOR", "TORY" }, { "TOT", "TOAL" }, { "TP1", "TPE1" }, { "TP2", "TPE2" }, { "TP3", "TPE3" }, { "TP4", "TPE4" }, { "TPA", "TPOS" }, { "TPB", "TPUB" }, { "TRC", "TSRC" }, { "TRD", "TRDA" }, { "TRK", "TRCK" }, { "TSI", "TSIZ" }, { "TSS", "TSSE" }, { "TT1", "TIT1" }, { "TT2", "TIT2" }, { "TT3", "TIT3" }, { "TXT", "TEXT" }, { "TXX", "TXXX" }, { "TYE", "TYER" }, { "UFI", "UFID" }, { "ULT", "USLT" }, { "WAF", "WOAF" }, { "WAR", "WOAR" }, { "WAS", "WOAS" }, { "WCM", "WCOM" }, { "WCP", "WCOP" }, { "WPB", "WPUB" }, { "WXX", "WXXX" }, { "\0\0\0", "\0\0\0\0" } }; /** * Decode a 4-byte unsigned integer. */ static __inline unsigned long id3_get_int32(const unsigned char *p) { return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; } /** * Decode a 4-byte unsigned synchsafe integer. */ static __inline unsigned long id3_get_syncsafe_int(const unsigned char *p) { if ((p[0] | p[1] | p[2] | p[3]) & 0x80) return SYNCSAFE_INT_BAD; return (p[0] << 21) | (p[1] << 14) | (p[2] << 7) | p[3]; } /** * Write a 4-byte unsigned synchsafe integer. */ static __inline void id3_put_syncsafe_int(unsigned char *p, unsigned long i) { p[0] = (i >> 21) & 0x7f; p[1] = (i >> 14) & 0x7f; p[2] = (i >> 7) & 0x7f; p[3] = i & 0x7f; } /** * Decode srclen bytes of unsynchronized data from src into dest. * Return the number of bytes written into dest. * * The buffer pointed to by dest must be large enough to hold the decoded data. * The decoded data will never be longer than the unsynchronized data. * Decoding may be performed in-place by specifying dest equal to src. * * If dest == NULL, decoded data are not stored but the decoded length is * still computed and returned. */ static unsigned long id3_get_unsync_data(unsigned char *dest, const unsigned char *src, unsigned long srclen) { unsigned long k = 0, i; for (i = 0; i < srclen; i++) { if (dest) dest[k] = src[i]; k++; if (src[i] == 0xff && i + 1 < srclen && src[i+1] == 0x00) i++; } return k; } /** * Encode srclen bytes of data from src, writing the unsynchronized version * into dest. Return the number of bytes written into dest. * * The buffer pointed to by dest must be large enough to hold the * unsynchronized data. * * If dest == NULL, unsynchronized data are not stored but the unsynchronized * length is still computed and returned. * * If the returned length is equal to srclen, unsynchronization is not * needed for these data. */ static unsigned long id3_put_unsync_data(unsigned char *dest, const unsigned char *src, unsigned long srclen) { unsigned long k = 0, i; for (i = 0; i < srclen; i++) { if (dest) dest[k] = src[i]; k++; if (src[i] == 0xff && (i + 1 == srclen || src[i+1] == 0x00 || (src[i+1] & 0xe0) == 0xe0)) { if (dest) dest[k] = 0x00; k++; } } return k; } /** * Release memory associated with a chain of frame structures. */ static void id3_release_frames(struct ID3v2FrameStruct *frame) { struct ID3v2FrameStruct *tframe; while (frame) { tframe = frame; frame = frame->next; free(tframe->data); free(tframe); } } /** * Construct an ID3v2 frame according to specified format and parameters. * * The format string contains one character per field in the frame: * 's' stores a string without null-termination (expects const char *) * 'b' stores a single byte (expects unsigned int) * 'h' stores a 16-bit big-endian integer (expects unsigned int) * * Return a newly allocated ID3v2FrameStruct, or NULL if the format * is invalid. */ static struct ID3v2FrameStruct * id3_make_frame(const char *frameid, const char *format, ...) { va_list ap; struct ID3v2FrameStruct *frame; unsigned long j, k; unsigned int i; const char *p, *s; va_start(ap, format); k = 0; for (p = format; *p; p++) { switch (*p) { case 's': /* string */ s = va_arg(ap, const char *); k += strlen(s); break; case 'b': /* byte */ i = va_arg(ap, unsigned int); k += 1; break; case 'h': /* 16-bit integer */ i = va_arg(ap, unsigned int); k += 2; break; default: va_end(ap); return NULL; } } va_end(ap); frame = malloc(sizeof(struct ID3v2FrameStruct)); frame->next = NULL; strncpy(frame->frameid, frameid, 4); frame->flags = 0; frame->len = k; frame->hskip = 0; frame->data = malloc(k); va_start(ap, format); k = 0; for (p = format; *p; p++) { switch (*p) { case 's': /* string */ s = va_arg(ap, const char *); j = strlen(s); memcpy(frame->data + k, s, j); k += j; break; case 'b': /* byte */ i = va_arg(ap, unsigned int); frame->data[k] = i; k += 1; break; case 'h': /* 16-bit integer */ i = va_arg(ap, unsigned int); frame->data[k] = (i >> 8) & 0xff; frame->data[k+1] = i & 0xff; k += 2; break; default: va_end(ap); free(frame->data); free(frame); return NULL; } } va_end(ap); return frame; } /** * Decode an RVA2 frame (only track/album gain, only master channel). * * Store gain information in the info structure, unless info == NULL. * Return 1 if the frame is an RVA2 frame with track/album gain, 0 otherwise. */ static int id3_decode_rva2_frame(const struct ID3v2FrameStruct *frame, struct MP3GainTagInfo *info) { unsigned long p; int is_album_gain, channel, peakbits; double gain, peak; /* Ignore non-RVA2 frames. */ if (memcmp(frame->frameid, "RVA2", 4) != 0) return 0; p = frame->hskip; /* Check identification string; we understand only "track" and "album". */ if (p + 6 <= frame->len && (memcmp(frame->data + p, "track\0", 6) == 0 || memcmp(frame->data + p, "TRACK\0", 6) == 0)) { is_album_gain = 0; p += 6; } else if (p + 6 <= frame->len && (memcmp(frame->data + p, "album\0", 6) == 0 || memcmp(frame->data + p, "ALBUM\0", 6) == 0)) { is_album_gain = 1; p += 6; } else { return 0; } /* Process per-channel data. */ while (p + 4 <= frame->len) { /* * p+0 : channel number * p+1, p+2 : 16-bit signed BE int = adjustment * 512 * p+3 : nr of bits representing peak * p+4 ... : unsigned multibyte BE int = peak sample * 2**(peakbits-1) */ channel = frame->data[p]; gain = (double)(((signed char)(frame->data[p+1]) << 8) | frame->data[p+2]) / 512.0; peakbits = frame->data[p+3]; if (p + 4 + (peakbits + 7) / 8 > frame->len) break; peak = 0; if (peakbits > 0) peak += (double)(frame->data[p+4]); if (peakbits > 8) peak += (double)(frame->data[p+5]) / 256.0; if (peakbits > 16) peak += (double)(frame->data[p+6]) / 65536.0; if (peakbits > 0) peak = peak / (double)(1 << ((peakbits - 1) & 7)); p += 4 + (peakbits + 7) / 8; if (channel == 1) { /* channel == master volume channel */ if (info) { if (is_album_gain) { info->haveAlbumGain = 1; info->albumGain = gain; info->haveAlbumPeak = (peakbits > 0); info->albumPeak = peak; } else { info->haveTrackGain = 1; info->trackGain = gain; info->haveTrackPeak = (peakbits > 0); info->trackPeak = peak; } } } } return 1; } /** * Create an RVA2 frame, track or album gain, master channel. * Return a newly allocated ID3v2FrameStruct. */ static struct ID3v2FrameStruct * id3_make_rva2_frame(int is_album_gain, double gain, int have_peak, double peak) { const char *ident; int g; unsigned int p; /* * identification: string = "track" or "album" * channel type: byte = 1 (master volume) * volume adjustment: int16 = gain * 512 * peak bits: byte = 16 (or 0 if no peak information) * peak: uint16 = peak * 32768 */ ident = (is_album_gain) ? "album" : "track"; g = (gain <= -64) ? -32768 : (gain >= 64) ? 32767 : (int)(gain * 512); if (g < -32768) g = -32768; if (g > 32767) g = 32767; if (have_peak) { p = (peak <= 0) ? 0 : (peak >= 2) ? 65535 : (unsigned int)(peak * 32768); if (p > 65535) p = 65535; return id3_make_frame("RVA2", "sbbhbh", ident, 0, 1, g, 16, p); } else { return id3_make_frame("RVA2", "sbbhb", ident, 0, 1, g, 0); } } /** * Decode an APEv2/Vorbis-style TXXX frame, matching * /REPLAYGAIN_(ALBUM|TRACK)_(GAIN|PEAK)/ case-insensitively. * * Store gain information in the info structure, unless info == NULL. * Return 1 if the frame is one of our TXXX frames, 0 otherwise. */ static int id3_decode_txxx_frame(const struct ID3v2FrameStruct *frame, struct MP3GainTagInfo *info) { unsigned long p, k; char buf[64]; const char *value; /* Ignore non-TXXX frames. */ if (memcmp(frame->frameid, "TXXX", 4) != 0) return 0; p = frame->hskip; /* Check text encoding; we understand only 0 (ISO-8859-1) and 3 (UTF-8). */ if (p >= frame->len || (frame->data[p] != 0 && frame->data[p] != 3)) return 0; p++; /* Copy character data to temporary buffer. */ k = (frame->len - p + 1 < sizeof(buf)) ? (frame->len - p) : (sizeof(buf) - 2); memcpy(buf, frame->data + p, k); buf[k] = '\0'; /* terminate the value string */ buf[k+1] = '\0'; /* ensure buf contains two terminated strings, even for invalid frame data */ value = buf + strlen(buf) + 1; /* Check identification string. */ if (strcasecmp(buf, "REPLAYGAIN_ALBUM_GAIN") == 0) { if (info) { info->haveAlbumGain = !0; info->albumGain = atof(value); } return 1; } else if (strcasecmp(buf, "REPLAYGAIN_TRACK_GAIN") == 0) { if (info) { info->haveTrackGain = !0; info->trackGain = atof(value); } return 1; } else if (strcasecmp(buf, "REPLAYGAIN_ALBUM_PEAK") == 0) { if (info) { info->haveAlbumPeak = !0; info->albumPeak = atof(value); } return 1; } else if (strcasecmp(buf, "REPLAYGAIN_TRACK_PEAK") == 0) { if (info) { info->haveTrackPeak = !0; info->trackPeak = atof(value); } return 1; } else if (strcasecmp(buf, "REPLAYGAIN_REFERENCE_LOUDNESS") == 0) { /* we derive no information from this at the moment, but * we do want to delete this frame if re-writing */ return 1; } return 0; } /** * Decode a mp3gain-specific TXXX frame, either "MP3GAIN_UNDO" or * "MP3GAIN_MINMAX" or "MP3GAIN_ALBUM_MINMAX". * * Store gain information in the info structure, unless info == NULL. * Return 1 if the frame is a mp3gain-specific TXXX frame, 0 otherwise. */ static int id3_decode_mp3gain_frame(const struct ID3v2FrameStruct *frame, struct MP3GainTagInfo *info) { unsigned long p, k; char buf[64]; int f1, f2; char f3; /* Ignore non-TXXX frames. */ if (memcmp(frame->frameid, "TXXX", 4) != 0) return 0; p = frame->hskip; /* Check text encoding; we understand only 0 (ISO-8859-1) and 3 (UTF-8). */ if (p >= frame->len || (frame->data[p] != 0 && frame->data[p] != 3)) return 0; p++; /* Copy character data to temporary buffer. */ k = (frame->len - p + 1 < sizeof(buf)) ? (frame->len - p) : (sizeof(buf) - 2); memcpy(buf, frame->data + p, k); buf[k] = '\0'; /* terminate the value string */ buf[k+1] = '\0'; /* ensure buf contains two terminated strings, even for invalid frame data */ /* Check identification string. */ if (strcasecmp(buf, "MP3GAIN_UNDO") == 0) { /* value should be something like "+003,+003,W" */ if (sscanf(buf + strlen(buf) + 1, "%d,%d,%c", &f1, &f2, &f3) == 3 && info != NULL) { info->haveUndo = 1; info->undoLeft = f1; info->undoRight = f2; info->undoWrap = (f3 == 'w' || f3 == 'W'); } return 1; } else if (strcasecmp(buf, "MP3GAIN_MINMAX") == 0) { /* value should be something like "001,153" */ if (sscanf(buf + strlen(buf) + 1, "%d,%d", &f1, &f2) == 2 && info != NULL) { info->haveMinMaxGain = 1; info->minGain = f1; info->maxGain = f2; } return 1; } else if (strcasecmp(buf, "MP3GAIN_ALBUM_MINMAX") == 0) { /* value should be something like "001,153" */ if (sscanf(buf + strlen(buf) + 1, "%d,%d", &f1, &f2) == 2 && info != NULL) { info->haveAlbumMinMaxGain = 1; info->albumMinGain = f1; info->albumMaxGain = f2; } return 1; } return 0; } /** * Read an ID3v2 tag from the current position in the MP3 file. * * Return 1 on success, 0 if no tag is found, or a negative error code * if the tag can not be processed. */ static int id3_parse_v2_tag(FILE *f, struct ID3v2TagStruct *tag) { unsigned char buf[12], frameid[4]; unsigned int fflags; unsigned long dlen, flen, fhskip, p, k; unsigned char *tagdata; struct ID3v2FrameStruct *frame, **pframe; /* Read header */ tag->offset = ftell(f); if (fread(buf, 1, 10, f) != 10) return 0; /* Check 'ID3' signature. */ if (memcmp(buf, "ID3", 3) != 0) return 0; DBG(("DEBUG: Found ID3v2 tag at offset %lu\n", tag->offset)); /* Check version and flags. */ switch (buf[3]) { case 2: /* ID3v2.2 */ if (buf[5] & (~(TAGFL_UNSYNC))) return M3G_ERR_TAGFORMAT; /* unknown flags */ break; case 3: /* ID3v2.3 */ if (buf[5] & (~(TAGFL_UNSYNC | TAGFL_EXTHDR | TAGFL_EXPR))) return M3G_ERR_TAGFORMAT; /* unknown flags */ break; case 4: /* ID3v2.4 */ if (buf[5] & (~(TAGFL_UNSYNC | TAGFL_EXTHDR | TAGFL_EXPR | TAGFL_FOOTER))) return M3G_ERR_TAGFORMAT; /* unknown flags */ break; default: return M3G_ERR_TAGFORMAT; /* unknown version */ } /* Check length. */ dlen = id3_get_syncsafe_int(buf + 6); if (dlen == SYNCSAFE_INT_BAD) return M3G_ERR_TAGFORMAT; /* Fill tag structure. */ tag->flags = buf[5]; tag->version = (buf[3] << 8) | buf[4]; tag->length = 10 + dlen + ((tag->flags & TAGFL_FOOTER) ? 10 : 0); tag->frames = NULL; DBG((" version=%04x length=%lu flags=%02x\n", tag->version, tag->length, tag->flags)); /* Read rest of the tag. */ tagdata = malloc(dlen); if (fread(tagdata, 1, dlen, f) != dlen) goto badtag; /* If this is an unsynced v2.2 or v2.3 tag, decode it now. */ if ((tag->version >> 8) != 4 && (tag->flags & TAGFL_UNSYNC) != 0) { dlen = id3_get_unsync_data(tagdata, tagdata, dlen); } /* Skip extended header. */ p = 0; if ((tag->flags & TAGFL_EXTHDR) != 0) { DBG((" skip extended header\n")); if (p + 6 > dlen) goto badtag; if ((tag->version >> 8) == 4) { /* Skip ID3v2.4 extended header */ k = id3_get_syncsafe_int(tagdata + p); if (k == SYNCSAFE_INT_BAD || k > dlen) goto badtag; p += k; } else if ((tag->version >> 8) == 3) { /* Skip ID3v2.3 extended header */ k = id3_get_int32(tagdata + p); if (k > dlen) goto badtag; p += 4 + k; } } /* Scan frames. */ pframe = &(tag->frames); while (p < dlen && tagdata[p] != '\0') { /* Decode frame header. */ switch (tag->version >> 8) { case 2: /* ID3v2.2 */ if (p + 5 > dlen) goto badtag; memset(frameid, 0, 4); for (k = 0; upgrade_id3v22_table[k].id_new[0]; k++) { if (memcmp(tagdata + p, upgrade_id3v22_table[k].id_v22, 3) == 0) { memcpy(frameid, upgrade_id3v22_table[k].id_new, 4); break; } } flen = (tagdata[p+3] << 16) | (tagdata[p+4] << 8) | tagdata[p+5]; fflags = 0; if (flen > dlen) goto badtag; p += 6; break; case 3: /* ID3v2.3 */ if (p + 10 > dlen) goto badtag; memcpy(frameid, tagdata + p, 4); flen = id3_get_int32(tagdata + p + 4); fflags = (tagdata[p+8] << 7) & 0xff00; if (tagdata[p+9] & 0x80) fflags |= FRAMEFL_COMPR | FRAMEFL_DLEN; if (tagdata[p+9] & 0x40) fflags |= FRAMEFL_CRYPT; if (tagdata[p+9] & 0x20) fflags |= FRAMEFL_GROUP; if (tagdata[p+9] & 0x1f) fflags |= FRAMEFL_BAD; if (flen > dlen) goto badtag; p += 10; break; case 4: /* ID3v2.4 */ if (p + 10 > dlen) goto badtag; memcpy(frameid, tagdata + p, 4); flen = id3_get_syncsafe_int(tagdata + p + 4); fflags = (tagdata[p+8] << 8) | tagdata[p+9]; if (flen == SYNCSAFE_INT_BAD || flen > dlen) goto badtag; p += 10; break; default: /* unreachable */ goto badtag; } if (p + flen > dlen) goto badtag; DBG((" got frameid=%.4s fflags=%04x\n", frameid, fflags)); /* Drop unsupported frame types. */ if (frameid[0] == '\0' || memcmp(frameid, "RVAD", 4) == 0 || memcmp(frameid, "RGAD", 4) == 0 || memcmp(frameid, "XRVA", 4) == 0) { DBG((" drop unsupported frame\n")); p += flen; continue; } /* Drop v2.3 frames which we can not upgrade. */ if ((tag->version >> 8) == 3 && (fflags & (FRAMEFL_CRYPT | FRAMEFL_BAD)) != 0) { DBG((" drop non-upgradable frame\n")); p += flen; continue; } /* Drop frames that are too short for their flags. */ fhskip = (fflags & FRAMEFL_GROUP ? 1 : 0) + (fflags & FRAMEFL_CRYPT ? 1 : 0) + (fflags & FRAMEFL_DLEN ? 4 : 0); if (fhskip > flen) { DBG((" drop short frame\n")); p += flen; continue; } /* Drop v2.2 PIC frames that are too short. */ if ((tag->version >> 8) == 2 && memcmp(frameid, "APIC", 4) == 0 && flen < 6) { DBG((" drop short PIC frame\n")); p += flen; continue; } /* Rename TYER (Year) to TDRC (Recording time) */ if (memcmp(frameid, "TYER", 4) == 0) memcpy(frameid, "TDRC", 4); /* Drop frames that want to be dropped on tag alteration. */ if (fflags & FRAMEFL_TAGALTER) { DBG((" drop FRAMEFL_TAGALTER frame\n")); p += flen; continue; } /* Allocate frame structure. */ frame = malloc(sizeof(struct ID3v2FrameStruct)); frame->next = NULL; memcpy(frame->frameid, frameid, 4); frame->flags = fflags; frame->hskip = fhskip; *pframe = frame; pframe = &(frame->next); /* Copy frame data. */ if ((tag->version >> 8) == 4 && (fflags & FRAMEFL_UNSYNC) != 0) { /* This frame is unsynchronized; decode it now. */ frame->data = malloc(flen); k = id3_get_unsync_data(frame->data, tagdata + p, flen); frame->len = k; p += flen; } else if ((tag->version >> 8) == 2 && memcmp(frameid, "APIC", 4) == 0) { /* APIC frame format differs from PIC frame format */ frame->data = malloc(flen + 12); frame->data[0] = tagdata[p]; k = 1; if (memcmp(tagdata + p + 1, "PNG", 3) == 0) { memcpy(frame->data + k, "image/png", strlen("image/png") + 1); k += strlen("image/png") + 1; } else if (memcmp(tagdata + p + 1, "JPG", 3) == 0) { memcpy(frame->data + k, "image/jpeg", strlen("image/jpeg") + 1); k += strlen("image/jpeg") + 1; } else if (tagdata[p+1] == '\0') { memcpy(frame->data + k, tagdata + p + 1, 3); frame->data[k+3] = '\0'; k += 4; } memcpy(frame->data + k, tagdata + p + 4, flen - 4); frame->len = k + flen - 4; p += flen; } else { /* Normal case, just copy the data. */ frame->data = malloc(flen); memcpy(frame->data, tagdata + p, flen); frame->len = flen; p += flen; } /* Reorder flag parameters of upgraded v2.3 frames. */ if ((tag->version >> 8) == 3 && (fflags & FRAMEFL_DLEN) != 0) { k = id3_get_int32(frame->data); if (fflags & FRAMEFL_GROUP) { frame->data[0] = frame->data[4]; id3_put_syncsafe_int(frame->data + 1, k); } else { id3_put_syncsafe_int(frame->data, k); } } } if (p > dlen) goto badtag; free(tagdata); return 1; badtag: free(tagdata); id3_release_frames(tag->frames); return M3G_ERR_TAGFORMAT; } /** * Write an ID3v2 tag at the current position in the MP3 file. * * Tags are always written in ID3v2.4 format. * Tags are always written completely unsynchronized, * without extended header, padded up to an integer multiple of 2 KB. * We don't write a tag footer (not supported by QuodLibet). * * Return 1 on success, 0 if the tag contains no frames, * or a negative error code. */ static int id3_write_tag(FILE *f, struct ID3v2TagStruct *tag) { unsigned long dlen, p, k; unsigned char *tagdata; struct ID3v2FrameStruct *frame; DBG(("DEBUG: Writing ID3v2 tag\n")); /* Do not write a tag with zero frames. */ if (tag->frames == NULL) return 0; /* Compute raw total length of tag. */ dlen = 10; /* tag header */ for (frame = tag->frames; frame; frame = frame->next) { dlen += 10; dlen += id3_put_unsync_data(NULL, frame->data, frame->len); } dlen = (dlen + 2047) & (~2047); /* padding */ DBG((" length=%lu\n", dlen)); /* Allocate buffer and fill with zeros. */ tagdata = calloc(dlen, sizeof(unsigned char)); /* Prepare tag header. */ tagdata[0] = 'I'; tagdata[1] = 'D'; tagdata[2] = '3'; tagdata[3] = 4; tagdata[4] = 0; tagdata[5] = TAGFL_UNSYNC | (tag->flags & TAGFL_EXPR); id3_put_syncsafe_int(tagdata + 6, dlen - 10); p = 10; /* Prepare frames. */ for (frame = tag->frames; frame; frame = frame->next) { unsigned long fflags = frame->flags & (~FRAMEFL_UNSYNC); memcpy(tagdata + p, frame->frameid, 4); k = id3_put_unsync_data(tagdata + p + 10, frame->data, frame->len); id3_put_syncsafe_int(tagdata + p + 4, k); if (k != frame->len) fflags |= FRAMEFL_UNSYNC; tagdata[p+8] = (fflags >> 8) & 0xff; tagdata[p+9] = fflags & 0xff; p += 10 + k; DBG((" write frameid=%.4s length=%lu\n", frame->frameid, 10 + k)); } /* Write the whole thing. */ if (fwrite(tagdata, 1, dlen, f) != dlen) { free(tagdata); return M3G_ERR_WRITE; } free(tagdata); return 1; } /** * Read an ID3v1 tag from the current position in the MP3 file. * * Return 1 on success, 0 if no tag is found. */ static int id3_parse_v1_tag(FILE *f, struct ID3v2TagStruct *tag) { unsigned char buf[128]; char sbuf[32],*p; struct ID3v2FrameStruct **pframe; /* Read tag */ tag->offset = ftell(f); if (fread(buf, 1, 128, f) != 128) return 0; /* Check 'TAG' signature. */ if (memcmp(buf, "TAG", 3) != 0) return 0; DBG(("DEBUG: Found ID3v1 tag at offset %lu\n", tag->offset)); /* Convert ID3v1 data to ID3v2.4 */ tag->length = 128; tag->version = 0; tag->flags = 0; tag->frames = NULL; pframe = &(tag->frames); /* Add title field (offset 3, len 30). */ if (buf[3] != '\0') { memcpy(sbuf, buf + 3, 30); sbuf[30] = '\0'; /* get rid of trailing spaces */ for(p=sbuf+29; *p==' ' && p>=sbuf; *p--='\0'); *pframe = id3_make_frame("TIT2", "bs", 0, sbuf); pframe = &((*pframe)->next); } /* Add artist field (offset 33, len 30). */ if (buf[33] != '\0') { memcpy(sbuf, buf + 33, 30); sbuf[30] = '\0'; /* get rid of trailing spaces */ for(p=sbuf+29; *p==' ' && p>=sbuf; *p--='\0'); DBG(("fixed v1 artist: \"%s\"\n",sbuf)); *pframe = id3_make_frame("TPE1", "bs", 0, sbuf); pframe = &((*pframe)->next); } /* Add album field (offset 63, len 30). */ if (buf[63] != '\0') { memcpy(sbuf, buf + 63, 30); sbuf[30] = '\0'; /* get rid of trailing spaces */ for(p=sbuf+29; *p==' ' && p>=sbuf; *p--='\0'); *pframe = id3_make_frame("TALB", "bs", 0, sbuf); pframe = &((*pframe)->next); } /* Add release year (offset 93, len 4). */ if (buf[93] >= '0' && buf[93] <= '9' && buf[94] >= '0' && buf[94] <= '9' && buf[95] >= '0' && buf[95] <= '9' && buf[96] >= '0' && buf[96] <= '9') { memcpy(sbuf, buf + 93, 4); sbuf[4] = '\0'; *pframe = id3_make_frame("TDRC", "bs", 0, sbuf); pframe = &((*pframe)->next); } /* Add comment (offset 97, len 30). */ if (buf[97] != '\0') { memcpy(sbuf, buf + 97, 30); sbuf[30] = '\0'; /* get rid of trailing spaces */ for(p=sbuf+29; *p==' ' && p>=sbuf; *p--='\0'); /* assume ISO-8859-1, unknown language, no description */ *pframe = id3_make_frame("COMM", "bssbs", 0, "XXX", "", 0, sbuf); pframe = &((*pframe)->next); } /* Add track number (offset 126, len 1). */ if (buf[125] == '\0' && buf[126] != 0) { sprintf(sbuf, "%u", buf[126]); *pframe = id3_make_frame("TRCK", "bs", 0, sbuf); pframe = &((*pframe)->next); } return 1; } /** * Search for an ID3v2 tag at the beginning and end of the file. * If no ID3v2 tag is found, search for an ID3v1 tag at the end * of the file. * * Return 1 on success, 0 if no tag is found, or a negative error code * if the tag can not be processed. */ static int id3_search_tag(FILE *f, struct ID3v2TagStruct *tag) { unsigned char buf[32]; unsigned long pos, k, id3v1_pos = 0; int j, ret; /* Look for ID3v2 at the beginning of the file. */ fseek(f, 0, SEEK_SET); ret = id3_parse_v2_tag(f, tag); if (ret == 0) { /* Look for ID3v2 at the end of the file. */ fseek(f, 0, SEEK_END); pos = ftell(f); while (pos > 128) { /* Test for ID3v2 footer */ fseek(f, pos - 10, SEEK_SET); if (fread(buf, 1, 10, f) != 10) return M3G_ERR_READ; if (memcmp(buf, "3DI", 3) == 0 && buf[3] == 4 && ((buf[6] | buf[7] | buf[8] | buf[9]) & 0x80) == 0) { /* Parse ID3v2.4 tag */ k = id3_get_syncsafe_int(buf + 6); if (20 + k < pos) { pos -= 20 + k; fseek(f, pos, SEEK_SET); ret = id3_parse_v2_tag(f, tag); break; } } /* Test for ID3v1/Lyrics3v2 tag */ fseek(f, pos - 128, SEEK_SET); if (fread(buf, 1, 3, f) != 3) return M3G_ERR_READ; if (memcmp(buf, "TAG", 3) == 0) { /* Skip over ID3v1 tag and continue */ pos -= 128; DBG(("DEBUG: Skipping ID3v1 tag at offset %lu\n", pos)); id3v1_pos = pos; /* Test for Lyrics3v2 tag */ if (pos > 26) { fseek(f, pos - 15, SEEK_SET); if (fread(buf, 1, 15, f) != 15) return M3G_ERR_READ; if (memcmp(buf + 6, "LYRICS200", 9) == 0) { /* Skip over Lyrics tag */ k = 0; for (j = 0; j < 6; j++) { if (buf[j] < '0' || buf[j] > '9') { k = 0; break; } k = 10 * k + (buf[j] - '0'); } if (k >= 11 && k + 15 < pos) { pos -= k + 15; DBG(("DEBUG: Skipping Lyrics3v2 tag at offset %lu\n", pos)); } } } continue; } /* Test for APE tag */ fseek(f, pos - 32, SEEK_SET); if (fread(buf, 1, 32, f) != 32) return M3G_ERR_READ; if (memcmp(buf, "APETAGEX", 8) == 0) { /* Skip over APE tag and continue */ k = buf[12] | (buf[13] << 8) | (buf[14] << 16) | (buf[15] << 24); /* tag length */ if (buf[23] & 0x80) k += 32; /* tag header present */ if (k >= 32 && k < pos) { pos -= k; DBG(("DEBUG: Skipping APE tag at offset %lu\n", pos)); continue; } } /* No more tags to skip. */ break; } } if (ret == 0 && id3v1_pos != 0) { /* Read ID3v1 at the end of the file. */ fseek(f, id3v1_pos, SEEK_SET); ret = id3_parse_v1_tag(f, tag); } return ret; } /** * Seek to offset in inf and copy count bytes to the current * position in outf. If count == -1, copy until the end of the file. * * Return 1 on success, otherwise a negative error code. */ static int id3_copy_data(FILE *inf, FILE *outf, long offset, long count) { const long bufsize = 65536; char *buf; size_t k; int ret; ret = 1; buf = malloc(bufsize); if (fseek(inf, offset, SEEK_SET)) ret = M3G_ERR_READ; while (ret == 1 && count != 0) { k = (count > 0 && count < bufsize) ? count : bufsize; k = fread(buf, 1, k, inf); if (k == 0) { if (!feof(inf) || count > 0) ret = M3G_ERR_READ; break; } if (fwrite(buf, 1, k, outf) != k) { ret = M3G_ERR_WRITE; break; } if (count > 0) count -= k; } free(buf); return ret; } /** * Read gain information from an ID3v2 tag. */ int ReadMP3GainID3Tag(char *filename, struct MP3GainTagInfo *info) { FILE *f; struct ID3v2TagStruct tag; struct ID3v2FrameStruct *frame; int ret; f = fopen(filename, "rb"); if (f == NULL) { passError(MP3GAIN_UNSPECIFED_ERROR, 3, "Could not open ", filename, "\n"); return M3G_ERR_FILEOPEN; } ret = id3_search_tag(f, &tag); fclose(f); if (ret == M3G_ERR_READ) { passError(MP3GAIN_UNSPECIFED_ERROR, 3, "Error reading ", filename, "\n"); } if (ret == 1) { /* Got the tag; extract gain information from the RVA2/TXXX frames. */ frame = tag.frames; while (frame) { id3_decode_rva2_frame(frame, info); id3_decode_txxx_frame(frame, info); id3_decode_mp3gain_frame(frame, info); frame = frame->next; } id3_release_frames(tag.frames); } return ret; } /** * (Re-)Write gain information to an ID3v2 tag. * * Writes or updates an ID3v2.4 tag at the beginning of the file. * If the file does not yet contain an ID3v2 tag, a new tag is created, * copying basic fields from an ID3v1 tag if present. * If the file contains an ID3v2.2 or ID3v2.3 tag, it is rewritten as ID3v2.4. * * Since modifications are made at the beginning of the file, the entire * file is rewritten to a temporary file and then moved in place of the * old file. */ int WriteMP3GainID3Tag(char *filename, struct MP3GainTagInfo *info, int saveTimeStamp) { char sbuf[32]; char *tmpfilename; FILE *f, *outf; struct ID3v2TagStruct tag; struct ID3v2FrameStruct *frame, **pframe; int ret, need_update; if (saveTimeStamp) fileTime(filename, storeTime); f = fopen(filename, "rb"); if (f == NULL) { passError(MP3GAIN_UNSPECIFED_ERROR, 3, "Could not open ", filename, "\n"); return M3G_ERR_FILEOPEN; } ret = id3_search_tag(f, &tag); switch (ret) { case M3G_ERR_READ: passError(MP3GAIN_UNSPECIFED_ERROR, 3, "Error reading ", filename, "\n"); break; case M3G_ERR_TAGFORMAT: passError(MP3GAIN_UNSPECIFED_ERROR, 3, "Unsupported ID3v2 tag in ", filename, "\n"); break; } if (ret < 0) { /* Error. */ fclose(f); return ret; } if (ret == 0) { /* No ID3v2 or ID3v1 tag found in the file; create a new tag. */ tag.offset = 0; tag.length = 0; tag.version = 0; tag.flags = 0; tag.frames = NULL; } /* Kill existing replaygain frames. */ need_update = 0; pframe = &(tag.frames); while ((frame = *pframe)) { if (id3_decode_rva2_frame(frame, NULL) == 1 || id3_decode_txxx_frame(frame, NULL) == 1 || id3_decode_mp3gain_frame(frame, NULL) == 1) { /* This is a ReplayGain frame; kill it. */ need_update = 1; *pframe = frame->next; free(frame->data); free(frame); } else { pframe = &((*pframe)->next); } } /* Append new replaygain frames. The TXXX versions are lower-case, * because that's what Winamp wants... */ if (info->haveTrackGain || info->haveTrackPeak || info->haveAlbumGain || info->haveAlbumPeak) { need_update = 1; frame = id3_make_frame("TXXX", "bsbs", 0, "replaygain_reference_loudness", 0, "89.0 dB"); *pframe = frame; pframe = &(frame->next); } if (info->haveTrackGain) { need_update = 1; frame = id3_make_rva2_frame(0, info->trackGain, info->haveTrackPeak, info->trackPeak); *pframe = frame; pframe = &(frame->next); sprintf(sbuf, "%-+9.6f dB", info->trackGain); frame = id3_make_frame("TXXX", "bsbs", 0, "replaygain_track_gain", 0, sbuf); *pframe = frame; pframe = &(frame->next); } if (info->haveTrackPeak) { need_update = 1; sprintf(sbuf, "%-8.6f", info->trackPeak); frame = id3_make_frame("TXXX", "bsbs", 0, "replaygain_track_peak", 0, sbuf); *pframe = frame; pframe = &(frame->next); } if (info->haveAlbumGain) { need_update = 1; frame = id3_make_rva2_frame(1, info->albumGain, info->haveAlbumPeak, info->albumPeak); *pframe = frame; pframe = &(frame->next); sprintf(sbuf, "%-+9.6f dB", info->albumGain); frame = id3_make_frame("TXXX", "bsbs", 0, "replaygain_album_gain", 0, sbuf); *pframe = frame; pframe = &(frame->next); } if (info->haveAlbumPeak) { need_update = 1; sprintf(sbuf, "%-8.6f", info->albumPeak); frame = id3_make_frame("TXXX", "bsbs", 0, "replaygain_album_peak", 0, sbuf); *pframe = frame; pframe = &(frame->next); } /* Append mp3gain-specific frames. */ if (info->haveMinMaxGain) { need_update = 1; sprintf(sbuf, "%03d,%03d", info->minGain, info->maxGain); frame = id3_make_frame("TXXX", "bsbs", 0, "MP3GAIN_MINMAX", 0, sbuf); *pframe = frame; pframe = &(frame->next); } if (info->haveAlbumMinMaxGain) { need_update = 1; sprintf(sbuf, "%03d,%03d", info->albumMinGain, info->albumMaxGain); frame = id3_make_frame("TXXX", "bsbs", 0, "MP3GAIN_ALBUM_MINMAX", 0, sbuf); *pframe = frame; pframe = &(frame->next); } if (info->haveUndo) { need_update = 1; sprintf(sbuf, "%+04d,%+04d,%c", info->undoLeft, info->undoRight, info->undoWrap ? 'W' : 'N'); frame = id3_make_frame("TXXX", "bsbs", 0, "MP3GAIN_UNDO", 0, sbuf); *pframe = frame; pframe = &(frame->next); } if (!need_update) { /* No need to change MP3 file. */ fclose(f); id3_release_frames(tag.frames); return 0; } /* Create temporary file. */ tmpfilename = malloc(strlen(filename) + 5); strcpy(tmpfilename, filename); strcat(tmpfilename, ".TMP"); outf = fopen(tmpfilename, "wb"); if (outf == NULL) { passError(MP3GAIN_UNSPECIFED_ERROR, 3, "Can not create temporary file ", tmpfilename, "\n"); fclose(f); free(tmpfilename); id3_release_frames(tag.frames); return M3G_ERR_CANT_MAKE_TMP; } /* Write new ID3v2 tag. */ ret = id3_write_tag(outf, &tag); /* Write rest of MP3 file. */ if (ret >= 0) { if (tag.version == 0) { /* The original file has no ID3v2 tag; copy everything. */ ret = id3_copy_data(f, outf, 0, -1); } else { /* The original file has an ID3v2 tag; copy everything except the original tag. */ ret = id3_copy_data(f, outf, 0, tag.offset); if (ret >= 0) ret = id3_copy_data(f, outf, tag.offset + tag.length, -1); } } fclose(outf); fclose(f); id3_release_frames(tag.frames); switch (ret) { case M3G_ERR_READ: passError(MP3GAIN_UNSPECIFED_ERROR, 3, "Error reading ", filename, "\n"); break; case M3G_ERR_WRITE: passError(MP3GAIN_UNSPECIFED_ERROR, 3, "Error writing ", tmpfilename, "\n"); break; } if (ret < 0) { /* Delete temp file after error. */ remove(tmpfilename); free(tmpfilename); return ret; } /* Replace original file. */ #ifdef WIN32 /* "rename" function in WIN32 does _not_ replace existing destination file, so we have to manually remove it first */ if (remove(filename) != 0) { passError(MP3GAIN_UNSPECIFED_ERROR, 5, "Can not rename ", tmpfilename, " to ", filename, "\n"); ret = M3G_ERR_RENAME_TMP; /* Do NOT remove the temp file itself, just in case the "remove(filename)" function only _temporarily_ failed, and the original file will disappear soon, such as when all handles on the file are closed. If it does disappear and we also delete the tmp file, then the file is completely gone... */ free(tmpfilename); } #endif ret = 1; if (rename(tmpfilename, filename)) { remove(tmpfilename); passError(MP3GAIN_UNSPECIFED_ERROR, 5, "Can not rename ", tmpfilename, " to ", filename, "\n"); ret = M3G_ERR_RENAME_TMP; } else { if (saveTimeStamp) fileTime(filename, setStoredTime); } free(tmpfilename); return ret; } /** * Remove gain information from the ID3v2 tag. * Return 1 on success, 0 if no changes are needed, or a negative error code. */ int RemoveMP3GainID3Tag(char *filename, int saveTimeStamp) { struct MP3GainTagInfo info; info.haveAlbumGain = 0; info.haveAlbumPeak = 0; info.haveTrackGain = 0; info.haveTrackPeak = 0; info.haveMinMaxGain = 0; info.haveAlbumMinMaxGain = 0; info.haveUndo = 0; return WriteMP3GainID3Tag(filename, &info, saveTimeStamp); } id3tag.h0000644000175000017500000000040513303547222012600 0ustar snhardinsnhardin#ifndef ID3TAG_H #define ID3TAG_H int ReadMP3GainID3Tag(char *filename, struct MP3GainTagInfo *info); int WriteMP3GainID3Tag(char *filename, struct MP3GainTagInfo *info, int saveTimeStamp); int RemoveMP3GainID3Tag(char *filename, int saveTimeStamp); #endif lgpl.txt0000644000175000017500000006364213303547222012767 0ustar snhardinsnhardin GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! Makefile0000644000175000017500000000307013303547222012715 0ustar snhardinsnhardin# # Quick 'n dirty unix Makefile # # Mike Oliphant (oliphant@gtk.org) # CC?= gcc CFLAGS+= -Wall -DHAVE_MEMCPY # all known MS Windows OS define the ComSpec environment variable ifdef ComSpec ifndef OSTYPE OSTYPE = win endif EXE_EXT = .exe else EXE_EXT = endif ifneq ($(OSTYPE),beos) INSTALL_PATH= /usr/local/bin else INSTALL_PATH= $(HOME)/config/bin endif # BeOS doesn't have libm (it's all in libroot) ifneq ($(OSTYPE),beos) LIBS= -lm else # BeOS: without this it wants to use bcopy() :^) CFLAGS+= -DHAVE_MEMCPY endif # Could ask pkg-config for libmpg123 flags/libs. LIBS+= -lmpg123 ifeq ($(OSTYPE),win) # gnu windows resource compiler WINDRES = windres endif OBJS= mp3gain.o apetag.o id3tag.o gain_analysis.o rg_error.o ifeq ($(OSTYPE),win) RC_OBJ = VerInfo.o endif all: mp3gain $(RC_OBJ): $(WINDRES) $(RC_OBJ:.o=.rc) $(RC_OBJ) mp3gain: $(RC_OBJ) $(OBJS) $(CC) $(LDFLAGS) -o mp3gain $(OBJS) $(RC_OBJ) $(LIBS) ifeq ($(OSTYPE),beos) mimeset -f mp3gain$(EXE_EXT) endif install: mp3gain ifneq ($(OSTYPE),win) cp -p mp3gain$(EXE_EXT) "$(INSTALL_PATH)" ifeq ($(OSTYPE),beos) mimeset -f "$(INSTALL_PATH)/mp3gain$(EXE_EXT)" endif else @echo install target is not implemented on windows endif uninstall: ifneq ($(OSTYPE),win) -rm -f "$(INSTALL_PATH)/mp3gain$(EXE_EXT)" else @echo uninstall target is not implemented on windows endif clean: -rm -rf mp3gain$(EXE_EXT) mp3gain.zip $(OBJS) $(RC_OBJ) dist: clean ifneq ($(OSTYPE),win) ifneq ($(OSTYPE),beos) zip -r mp3gain.zip * -x "CVS/*" "*/CVS/*" endif else @echo dist target is not implemented on windows and beos endif mp3gain.c0000644000175000017500000024611713303547222012772 0ustar snhardinsnhardin/* * mp3gain.c - analyzes mp3 files, determines the perceived volume, * and adjusts the volume of the mp3 accordingly * * Copyright (C) 2001-2009 Glen Sawyer * AAC support (C) 2004-2009 David Lasker, Altos Design, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * coding by Glen Sawyer (mp3gain@hotmail.com) 735 W 255 N, Orem, UT 84057-4505 USA * -- go ahead and laugh at me for my lousy coding skillz, I can take it :) * Just do me a favor and let me know how you improve the code. * Thanks. * * Unix-ification by Stefan Partheymüller * (other people have made Unix-compatible alterations-- I just ended up using * Stefan's because it involved the least re-work) * * DLL-ification by John Zitterkopf (zitt@hotmail.com) * * Additional tweaks by Artur Polaczynski, Mark Armbrust, and others */ /* * General warning: I coded this in several stages over the course of several * months. During that time, I changed my mind about my coding style and * naming conventions many, many times. So there's not a lot of consistency * in the code. Sorry about that. I may clean it up some day, but by the time * I would be getting around to it, I'm sure that the more clever programmers * out there will have come up with superior versions already... * * So have fun dissecting. */ #include #include #include #include "apetag.h" #include "id3tag.h" #ifdef AACGAIN #include "aacgain.h" #endif #ifndef WIN32 #undef asWIN32DLL #ifdef __FreeBSD__ #include #endif /* __FreeBSD__ */ #include #endif /* WIN32 */ #ifdef WIN32 #include #define SWITCH_CHAR '/' #else /* time stamp preservation when using temp file */ # include # include # include # if defined(__BEOS__) # include # endif #define SWITCH_CHAR '-' #endif /* WIN32 */ #ifdef __BEOS__ #include #endif /* __BEOS__ */ #include #include #include #ifndef asWIN32DLL /* Was in lame.h. */ #include /* Used to be part of decode_i386. */ unsigned char maxAmpOnly; /* layer3.c */ unsigned char *minGain; unsigned char *maxGain; #include #include "gain_analysis.h" #endif #include "mp3gain.h" /*jzitt*/ #include "rg_error.h" /*jzitt*/ #define HEADERSIZE 4 #define CRC16_POLYNOMIAL 0x8005 #define BUFFERSIZE 3000000 #define WRITEBUFFERSIZE 100000 #define FULL_RECALC 1 #define AMP_RECALC 2 #define MIN_MAX_GAIN_RECALC 4 #ifdef AACGAIN #define AACGAIN_ARG(x) , x #else #define AACGAIN_ARG(x) #endif typedef struct { unsigned long fileposition; unsigned char val[2]; } wbuffer; /* Yes, yes, I know I should do something about these globals */ wbuffer writebuffer[WRITEBUFFERSIZE]; unsigned long writebuffercnt; unsigned char buffer[BUFFERSIZE]; int writeself = 0; int QuietMode = 0; int UsingTemp = 1; int NowWriting = 0; double lastfreq = -1.0; int whichChannel = 0; int BadLayer = 0; int LayerSet = 0; int Reckless = 0; int wrapGain = 0; int undoChanges = 0; int skipTag = 0; int deleteTag = 0; int forceRecalculateTag = 0; int forceUpdateTag = 0; int checkTagOnly = 0; static int useId3 = 0; int gSuccess; long inbuffer; unsigned long bitidx; unsigned char *wrdpntr; unsigned char *curframe; char *curfilename; FILE *inf = NULL; FILE *outf; short int saveTime; unsigned long filepos; static const double bitrate[4][16] = { { 1, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 1 }, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, { 1, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 1 }, { 1, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 1 } }; static const double frequency[4][4] = { { 11.025, 12, 8, 1 }, { 1, 1, 1, 1 }, { 22.05, 24, 16, 1 }, { 44.1, 48, 32, 1 } }; long arrbytesinframe[16]; /* instead of writing each byte change, I buffer them up */ static void flushWriteBuff() { unsigned long i; for (i = 0; i < writebuffercnt; i++) { fseek(inf,writebuffer[i].fileposition,SEEK_SET); fwrite(writebuffer[i].val,1,2,inf); } writebuffercnt = 0; }; static void addWriteBuff(unsigned long pos, unsigned char *vals) { if (writebuffercnt >= WRITEBUFFERSIZE) { flushWriteBuff(); fseek(inf,filepos,SEEK_SET); } writebuffer[writebuffercnt].fileposition = pos; writebuffer[writebuffercnt].val[0] = *vals; writebuffer[writebuffercnt].val[1] = vals[1]; writebuffercnt++; }; /* fill the mp3 buffer */ static unsigned long fillBuffer(long savelastbytes) { unsigned long i; unsigned long skip; unsigned long skipbuf; skip = 0; if (savelastbytes < 0) { skip = -savelastbytes; savelastbytes = 0; } if (UsingTemp && NowWriting) { if (fwrite(buffer,1,inbuffer-savelastbytes,outf) != (size_t)(inbuffer-savelastbytes)) return 0; } if (savelastbytes != 0) /* save some of the bytes at the end of the buffer */ memmove((void*)buffer,(const void*)(buffer+inbuffer-savelastbytes),savelastbytes); while (skip > 0) { /* skip some bytes from the input file */ skipbuf = skip > BUFFERSIZE ? BUFFERSIZE : skip; i = (unsigned long)fread(buffer,1,skipbuf,inf); if (i != skipbuf) return 0; if (UsingTemp && NowWriting) { if (fwrite(buffer,1,skipbuf,outf) != skipbuf) return 0; } filepos += i; skip -= skipbuf; } i = (unsigned long)fread(buffer+savelastbytes,1,BUFFERSIZE-savelastbytes,inf); filepos = filepos + i; inbuffer = i + savelastbytes; return i; } static const unsigned char maskLeft8bits[8] = { 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE }; static const unsigned char maskRight8bits[8] = { 0xFF, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01 }; static void set8Bits(unsigned short val) { val <<= (8 - bitidx); wrdpntr[0] &= maskLeft8bits[bitidx]; wrdpntr[0] |= (val >> 8); wrdpntr[1] &= maskRight8bits[bitidx]; wrdpntr[1] |= (val & 0xFF); if (!UsingTemp) addWriteBuff(filepos-(inbuffer-(wrdpntr-buffer)),wrdpntr); } static void skipBits(int nbits) { bitidx += nbits; wrdpntr += (bitidx >> 3); bitidx &= 7; return; } static unsigned char peek8Bits() { unsigned short rval; rval = wrdpntr[0]; rval <<= 8; rval |= wrdpntr[1]; rval >>= (8 - bitidx); return (rval & 0xFF); } static unsigned long skipID3v2() { /* * An ID3v2 tag 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. */ unsigned long ok; unsigned long ID3Size; ok = 1; if (wrdpntr[0] == 'I' && wrdpntr[1] == 'D' && wrdpntr[2] == '3' && wrdpntr[3] < 0xFF && wrdpntr[4] < 0xFF) { ID3Size = (long)(wrdpntr[9]) | ((long)(wrdpntr[8]) << 7) | ((long)(wrdpntr[7]) << 14) | ((long)(wrdpntr[6]) << 21); ID3Size += 10; wrdpntr = wrdpntr + ID3Size; if ((wrdpntr+HEADERSIZE-buffer) > inbuffer) { ok = fillBuffer(inbuffer-(wrdpntr-buffer)); wrdpntr = buffer; } } return ok; } void passError(MMRESULT lerrnum, int numStrings, ...) { char * errstr; size_t totalStrLen = 0; int i; va_list marker; va_start(marker, numStrings); for (i = 0; i < numStrings; i++) { totalStrLen += strlen(va_arg(marker, const char *)); } va_end(marker); errstr = (char *)malloc(totalStrLen + 3); errstr[0] = '\0'; va_start(marker, numStrings); for (i = 0; i < numStrings; i++) { strcat(errstr,va_arg(marker, const char *)); } va_end(marker); DoError(errstr,lerrnum); free(errstr); errstr = NULL; } static unsigned long frameSearch(int startup) { unsigned long ok; int done; static int startfreq; static int startmpegver; long tempmpegver; double bitbase; int i; done = 0; ok = 1; if ((wrdpntr+HEADERSIZE-buffer) > inbuffer) { ok = fillBuffer(inbuffer-(wrdpntr-buffer)); wrdpntr = buffer; if (!ok) done = 1; } while (!done) { done = 1; if ((wrdpntr[0] & 0xFF) != 0xFF) done = 0; /* first 8 bits must be '1' */ else if ((wrdpntr[1] & 0xE0) != 0xE0) done = 0; /* next 3 bits are also '1' */ else if ((wrdpntr[1] & 0x18) == 0x08) done = 0; /* invalid MPEG version */ else if ((wrdpntr[2] & 0xF0) == 0xF0) done = 0; /* bad bitrate */ else if ((wrdpntr[2] & 0xF0) == 0x00) done = 0; /* we'll just completely ignore "free format" bitrates */ else if ((wrdpntr[2] & 0x0C) == 0x0C) done = 0; /* bad sample frequency */ else if ((wrdpntr[1] & 0x06) != 0x02) { /* not Layer III */ if (!LayerSet) { switch (wrdpntr[1] & 0x06) { case 0x06: BadLayer = !0; passError(MP3GAIN_FILEFORMAT_NOTSUPPORTED, 2, curfilename, " is an MPEG Layer I file, not a layer III file\n"); return 0; case 0x04: BadLayer = !0; passError(MP3GAIN_FILEFORMAT_NOTSUPPORTED, 2, curfilename, " is an MPEG Layer II file, not a layer III file\n"); return 0; } } done = 0; /* probably just corrupt data, keep trying */ } else if (startup) { startmpegver = wrdpntr[1] & 0x18; startfreq = wrdpntr[2] & 0x0C; tempmpegver = startmpegver >> 3; if (tempmpegver == 3) bitbase = 1152.0; else bitbase = 576.0; for (i = 0; i < 16; i++) arrbytesinframe[i] = (long)(floor(floor((bitbase*bitrate[tempmpegver][i])/frequency[tempmpegver][startfreq >> 2]) / 8.0)); } else { /* !startup -- if MPEG version or frequency is different, then probably not correctly synched yet */ if ((wrdpntr[1] & 0x18) != startmpegver) done = 0; else if ((wrdpntr[2] & 0x0C) != startfreq) done = 0; else if ((wrdpntr[2] & 0xF0) == 0) /* bitrate is "free format" probably just corrupt data if we've already found valid frames */ done = 0; } if (!done) wrdpntr++; if ((wrdpntr+HEADERSIZE-buffer) > inbuffer) { ok = fillBuffer(inbuffer-(wrdpntr-buffer)); wrdpntr = buffer; if (!ok) done = 1; } } if (ok) { if (inbuffer - (wrdpntr-buffer) < (arrbytesinframe[(wrdpntr[2] >> 4) & 0x0F] + ((wrdpntr[2] >> 1) & 0x01))) { ok = fillBuffer(inbuffer-(wrdpntr-buffer)); wrdpntr = buffer; } bitidx = 0; curframe = wrdpntr; } return ok; } static int crcUpdate(int value, int crc) { int i; value <<= 8; for (i = 0; i < 8; i++) { value <<= 1; crc <<= 1; if (((crc ^ value) & 0x10000)) crc ^= CRC16_POLYNOMIAL; } return crc; } static void crcWriteHeader(int headerlength, char *header) { int crc = 0xffff; /* (jo) init crc16 for error_protection */ int i; crc = crcUpdate(((unsigned char*)header)[2], crc); crc = crcUpdate(((unsigned char*)header)[3], crc); for (i = 6; i < headerlength; i++) { crc = crcUpdate(((unsigned char*)header)[i], crc); } header[4] = crc >> 8; header[5] = crc & 255; } static long getSizeOfFile(char *filename) { long size = 0; FILE *file; file = fopen(filename, "rb"); if (file) { fseek(file, 0, SEEK_END); size = ftell(file); fclose(file); } return size; } int deleteFile(char *filename) { return remove(filename); } int moveFile(char *currentfilename, char *newfilename) { return rename(currentfilename, newfilename); } /* Get File size and datetime stamp */ void fileTime(char *filename, timeAction action) { static int timeSaved=0; #ifdef WIN32 HANDLE outfh; static FILETIME create_time, access_time, write_time; #else static struct stat savedAttributes; #endif if (action == storeTime) { #ifdef WIN32 outfh = CreateFile((LPCTSTR)filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (outfh != INVALID_HANDLE_VALUE) { if (GetFileTime(outfh,&create_time,&access_time,&write_time)) timeSaved = !0; CloseHandle(outfh); } #else timeSaved = (stat(filename, &savedAttributes) == 0); #endif } else { if (timeSaved) { #ifdef WIN32 outfh = CreateFile((LPCTSTR)filename, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (outfh != INVALID_HANDLE_VALUE) { SetFileTime(outfh,&create_time,&access_time,&write_time); CloseHandle(outfh); } #else struct utimbuf setTime; setTime.actime = savedAttributes.st_atime; setTime.modtime = savedAttributes.st_mtime; timeSaved = 0; utime(filename, &setTime); #endif } } } unsigned long reportPercentWritten(unsigned long percent, unsigned long bytes) { int ok = 1; #ifndef asWIN32DLL fprintf(stderr," \r %2lu%% of %lu bytes written\r" ,percent,bytes); fflush(stderr); #else /* report % back to calling app */ ok = sendpercentdone( (int)percent, bytes ); //non-zero return means error bail out if ( ok != 0) return 0; ok = 1; /* allow us to continue processing file */ #endif return ok; } int numFiles, totFiles; unsigned long reportPercentAnalyzed(unsigned long percent, unsigned long bytes) { char fileDivFiles[21]; fileDivFiles[0]='\0'; if (totFiles-1) /* if 1 file then don't show [x/n] */ sprintf(fileDivFiles,"[%d/%d]",numFiles,totFiles); fprintf(stderr," \r%s %2lu%% of %lu bytes analyzed\r" ,fileDivFiles,percent,bytes); fflush(stderr); return 1; } void scanFrameGain() { int crcflag; int mpegver; int mode; int nchan; int gr, ch; int gain; mpegver = (curframe[1] >> 3) & 0x03; crcflag = curframe[1] & 0x01; mode = (curframe[3] >> 6) & 0x03; nchan = (mode == 3) ? 1 : 2; if (!crcflag) wrdpntr = curframe + 6; else wrdpntr = curframe + 4; bitidx = 0; if (mpegver == 3) { /* 9 bit main_data_begin */ wrdpntr++; bitidx = 1; if (mode == 3) skipBits(5); /* private bits */ else skipBits(3); /* private bits */ skipBits(nchan * 4); /* scfsi[ch][band] */ for (gr = 0; gr < 2; gr++) { for (ch = 0; ch < nchan; ch++) { skipBits(21); gain = peek8Bits(); if (*minGain > gain) { *minGain = gain; } if (*maxGain < gain) { *maxGain = gain; } skipBits(38); } } } else { /* mpegver != 3 */ wrdpntr++; /* 8 bit main_data_begin */ if (mode == 3) skipBits(1); else skipBits(2); /* only one granule, so no loop */ for (ch = 0; ch < nchan; ch++) { skipBits(21); gain = peek8Bits(); if (*minGain > gain) { *minGain = gain; } if (*maxGain < gain) { *maxGain = gain; } skipBits(42); } } } #ifndef asWIN32DLL static #endif int changeGain(char *filename AACGAIN_ARG(AACGainHandle aacH), int leftgainchange, int rightgainchange) { unsigned long ok; int mode; int crcflag; unsigned char *Xingcheck; unsigned long frame; int nchan; int ch; int gr; unsigned char gain; int bitridx; int freqidx; long bytesinframe; int sideinfo_len; int mpegver; long gFilesize = 0; char *outfilename; int gainchange[2]; int singlechannel; long outlength, inlength; /* size checker when using Temp files */ outfilename = NULL; frame = 0; BadLayer = 0; LayerSet = Reckless; NowWriting = !0; if ((leftgainchange == 0) && (rightgainchange == 0)) return 0; #ifdef AACGAIN if (aacH) { int rc = aac_modify_gain(aacH, leftgainchange, rightgainchange, QuietMode ? NULL : reportPercentWritten); NowWriting = 0; if (rc) passError(MP3GAIN_FILEFORMAT_NOTSUPPORTED, 1, "failed to modify gain\n"); return rc; } #endif gainchange[0] = leftgainchange; gainchange[1] = rightgainchange; singlechannel = !(leftgainchange == rightgainchange); if (saveTime) fileTime(filename, storeTime); gFilesize = getSizeOfFile(filename); if (UsingTemp) { fflush(stderr); fflush(stdout); outlength = (long)strlen(filename); outfilename = (char *)malloc(outlength+5); strcpy(outfilename,filename); if ((filename[outlength-3] == 'T' || filename[outlength-3] == 't') && (filename[outlength-2] == 'M' || filename[outlength-2] == 'm') && (filename[outlength-1] == 'P' || filename[outlength-1] == 'p')) { strcat(outfilename,".TMP"); } else { outfilename[outlength-3] = 'T'; outfilename[outlength-2] = 'M'; outfilename[outlength-1] = 'P'; } inf = fopen(filename,"r+b"); if (inf != NULL) { outf = fopen(outfilename, "wb"); if (outf == NULL) { fclose(inf); inf = NULL; passError(MP3GAIN_UNSPECIFED_ERROR, 3, "\nCan't open ", outfilename, " for temp writing\n"); NowWriting = 0; free(outfilename); return M3G_ERR_CANT_MAKE_TMP; } } } else { inf = fopen(filename,"r+b"); } if (inf == NULL) { if (UsingTemp && (outf != NULL)) { fclose(outf); outf = NULL; } passError( MP3GAIN_UNSPECIFED_ERROR, 3, "\nCan't open ", filename, " for modifying\n"); NowWriting = 0; free(outfilename); return M3G_ERR_CANT_MODIFY_FILE; } else { writebuffercnt = 0; inbuffer = 0; filepos = 0; bitidx = 0; ok = fillBuffer(0); if (ok) { wrdpntr = buffer; ok = skipID3v2(); ok = frameSearch(!0); if (!ok) { if (!BadLayer) passError( MP3GAIN_UNSPECIFED_ERROR, 3, "Can't find any valid MP3 frames in file ", filename, "\n"); } else { LayerSet = 1; /* We've found at least one valid layer 3 frame. * Assume any later layer 1 or 2 frames are just * bitstream corruption */ mode = (curframe[3] >> 6) & 3; if ((curframe[1] & 0x08) == 0x08) /* MPEG 1 */ sideinfo_len = (mode == 3) ? 4 + 17 : 4 + 32; else /* MPEG 2 */ sideinfo_len = (mode == 3) ? 4 + 9 : 4 + 17; if (!(curframe[1] & 0x01)) sideinfo_len += 2; Xingcheck = curframe + sideinfo_len; //LAME CBR files have "Info" tags, not "Xing" tags if ((Xingcheck[0] == 'X' && Xingcheck[1] == 'i' && Xingcheck[2] == 'n' && Xingcheck[3] == 'g') || (Xingcheck[0] == 'I' && Xingcheck[1] == 'n' && Xingcheck[2] == 'f' && Xingcheck[3] == 'o')) { bitridx = (curframe[2] >> 4) & 0x0F; if (bitridx == 0) { passError( MP3GAIN_FILEFORMAT_NOTSUPPORTED, 2, filename, " is free format (not currently supported)\n"); ok = 0; } else { mpegver = (curframe[1] >> 3) & 0x03; freqidx = (curframe[2] >> 2) & 0x03; bytesinframe = arrbytesinframe[bitridx] + ((curframe[2] >> 1) & 0x01); wrdpntr = curframe + bytesinframe; ok = frameSearch(0); } } frame = 1; } /* if (!ok) else */ #ifdef asWIN32DLL while (ok && (!blnCancel)) { #else while (ok) { #endif bitridx = (curframe[2] >> 4) & 0x0F; if (singlechannel) { if ((curframe[3] >> 6) & 0x01) { /* if mode is NOT stereo or dual channel */ passError( MP3GAIN_FILEFORMAT_NOTSUPPORTED, 2, filename, ": Can't adjust single channel for mono or joint stereo\n"); ok = 0; } } if (bitridx == 0) { passError( MP3GAIN_FILEFORMAT_NOTSUPPORTED, 2, filename, " is free format (not currently supported)\n"); ok = 0; } if (ok) { mpegver = (curframe[1] >> 3) & 0x03; crcflag = curframe[1] & 0x01; freqidx = (curframe[2] >> 2) & 0x03; bytesinframe = arrbytesinframe[bitridx] + ((curframe[2] >> 1) & 0x01); mode = (curframe[3] >> 6) & 0x03; nchan = (mode == 3) ? 1 : 2; if (!crcflag) /* we DO have a crc field */ wrdpntr = curframe + 6; /* 4-byte header, 2-byte CRC */ else wrdpntr = curframe + 4; /* 4-byte header */ bitidx = 0; if (mpegver == 3) { /* 9 bit main_data_begin */ wrdpntr++; bitidx = 1; if (mode == 3) skipBits(5); /* private bits */ else skipBits(3); /* private bits */ skipBits(nchan*4); /* scfsi[ch][band] */ for (gr = 0; gr < 2; gr++) for (ch = 0; ch < nchan; ch++) { skipBits(21); gain = peek8Bits(); if (wrapGain) gain += (unsigned char)(gainchange[ch]); else { if (gain != 0) { if ((int)(gain) + gainchange[ch] > 255) gain = 255; else if ((int)gain + gainchange[ch] < 0) gain = 0; else gain += (unsigned char)(gainchange[ch]); } } set8Bits(gain); skipBits(38); } if (!crcflag) { if (nchan == 1) crcWriteHeader(23,(char*)curframe); else crcWriteHeader(38,(char*)curframe); /* WRITETOFILE */ if (!UsingTemp) addWriteBuff(filepos-(inbuffer-(curframe+4-buffer)),curframe+4); } } else { /* mpegver != 3 */ wrdpntr++; /* 8 bit main_data_begin */ if (mode == 3) skipBits(1); else skipBits(2); /* only one granule, so no loop */ for (ch = 0; ch < nchan; ch++) { skipBits(21); gain = peek8Bits(); if (wrapGain) gain += (unsigned char)(gainchange[ch]); else { if (gain != 0) { if ((int)(gain) + gainchange[ch] > 255) gain = 255; else if ((int)gain + gainchange[ch] < 0) gain = 0; else gain += (unsigned char)(gainchange[ch]); } } set8Bits(gain); skipBits(42); } if (!crcflag) { if (nchan == 1) crcWriteHeader(15,(char*)curframe); else crcWriteHeader(23,(char*)curframe); /* WRITETOFILE */ if (!UsingTemp) addWriteBuff(filepos-(inbuffer-(curframe+4-buffer)),curframe+4); } } if (!QuietMode) { frame++; if (frame%200 == 0) { ok = reportPercentWritten((unsigned long)(((double)(filepos-(inbuffer-(curframe+bytesinframe-buffer))) * 100.0) / gFilesize),gFilesize); if (!ok) return ok; } } wrdpntr = curframe+bytesinframe; ok = frameSearch(0); } } } #ifdef asWIN32DLL if (blnCancel) { //need to clean up as best as possible fclose(inf); if (UsingTemp) { fclose(outf); outf = NULL; deleteFile(outfilename); free(outfilename); passError(MP3GAIN_CANCELLED,2,"Cancelled processing of ",filename); } else { passError(MP3GAIN_CANCELLED,3,"Cancelled processing.\n", filename, " is probably corrupted now."); } if (saveTime) fileTime(filename, setStoredTime); NowWriting = 0; return; } #endif if (!QuietMode) { #ifndef asWIN32DLL fprintf(stderr," \r"); #else /* report DONE (100%) message back to calling app */ sendpercentdone( 100, gFilesize ); #endif } fflush(stderr); fflush(stdout); if (UsingTemp) { while (fillBuffer(0)); fflush(outf); #ifdef WIN32 outlength = _filelength(_fileno(outf)); inlength = _filelength(_fileno(inf)); #else fseek(outf, 0, SEEK_END); fseek(inf, 0, SEEK_END); outlength=ftell(outf); inlength =ftell(inf); #endif #ifdef __BEOS__ /* some stuff to preserve attributes */ do { DIR *attrs = NULL; struct dirent *de; struct attr_info ai; int infd, outfd; void *attrdata; infd = fileno(inf); if (infd < 0) goto attrerror; outfd = fileno(outf); if (outfd < 0) goto attrerror; attrs = fs_fopen_attr_dir(infd); while ((de = fs_read_attr_dir(attrs)) != NULL) { if (fs_stat_attr(infd, de->d_name, &ai) < B_OK) goto attrerror; if ((attrdata = malloc(ai.size)) == NULL) goto attrerror; fs_read_attr(infd, de->d_name, ai.type, 0, attrdata, ai.size); fs_write_attr(outfd, de->d_name, ai.type, 0, attrdata, ai.size); free(attrdata); } fs_close_attr_dir(attrs); break; attrerror: if (attrdata) free(attrdata); if (attrs) fs_close_attr_dir(attrs); fprintf(stderr, "can't preserve attributes for '%s': %s\n", filename, strerror(errno)); } while (0); #endif fclose(outf); fclose(inf); inf = NULL; outf = NULL; if (outlength != inlength) { deleteFile(outfilename); passError( MP3GAIN_UNSPECIFED_ERROR, 3, "Not enough temp space on disk to modify ", filename, "\nEither free some space, or switch off \"temp file\" option with -T\n"); NowWriting = 0; return M3G_ERR_NOT_ENOUGH_TMP_SPACE; } else { if (deleteFile(filename)) { deleteFile(outfilename); //try to delete tmp file passError( MP3GAIN_UNSPECIFED_ERROR, 3, "Can't open ", filename, " for modifying\n"); NowWriting = 0; return M3G_ERR_CANT_MODIFY_FILE; } if (moveFile(outfilename, filename)) { passError( MP3GAIN_UNSPECIFED_ERROR, 9, "Problem re-naming ", outfilename, " to ", filename, "\nThe mp3 was correctly modified, but you will need to re-name ", outfilename, " to ", filename, " yourself.\n"); NowWriting = 0; return M3G_ERR_RENAME_TMP; }; if (saveTime) fileTime(filename, setStoredTime); } free(outfilename); } else { flushWriteBuff(); fclose(inf); inf = NULL; if (saveTime) fileTime(filename, setStoredTime); } } NowWriting = 0; return 0; } #ifndef asWIN32DLL #ifdef AACGAIN void WriteAacGainTags (AACGainHandle aacH, struct MP3GainTagInfo *info) { if (info->haveAlbumGain) aac_set_tag_float(aacH, replaygain_album_gain, info->albumGain); if (info->haveAlbumPeak) aac_set_tag_float(aacH, replaygain_album_peak, info->albumPeak); if (info->haveAlbumMinMaxGain) aac_set_tag_int_2(aacH, replaygain_album_minmax, info->albumMinGain, info->albumMaxGain); if (info->haveTrackGain) aac_set_tag_float(aacH, replaygain_track_gain, info->trackGain); if (info->haveTrackPeak) aac_set_tag_float(aacH, replaygain_track_peak, info->trackPeak); if (info->haveMinMaxGain) aac_set_tag_int_2(aacH, replaygain_track_minmax, info->minGain, info->maxGain); if (info->haveUndo) aac_set_tag_int_2(aacH, replaygain_undo, info->undoLeft, info->undoRight); } #endif static void WriteMP3GainTag(char *filename AACGAIN_ARG(AACGainHandle aacH), struct MP3GainTagInfo *info, struct FileTagsStruct *fileTags, int saveTimeStamp) { #ifdef AACGAIN if (aacH) { WriteAacGainTags(aacH, info); } else #endif if (useId3) { /* Write ID3 tag; remove stale APE tag if it exists. */ if (WriteMP3GainID3Tag(filename, info, saveTimeStamp) >= 0) RemoveMP3GainAPETag(filename, saveTimeStamp); } else { /* Write APE tag */ WriteMP3GainAPETag(filename, info, fileTags, saveTimeStamp); } } void changeGainAndTag(char *filename AACGAIN_ARG(AACGainHandle aacH), int leftgainchange, int rightgainchange, struct MP3GainTagInfo *tag, struct FileTagsStruct *fileTag) { double dblGainChange; int curMin; int curMax; if (leftgainchange != 0 || rightgainchange != 0) { if (!changeGain(filename AACGAIN_ARG(aacH), leftgainchange, rightgainchange)) { if (!tag->haveUndo) { tag->undoLeft = 0; tag->undoRight = 0; } tag->dirty = !0; tag->undoRight -= rightgainchange; tag->undoLeft -= leftgainchange; tag->undoWrap = wrapGain; /* if undo == 0, then remove Undo tag */ tag->haveUndo = !0; /* on second thought, don't remove it. Shortening the tag causes full file copy, which is slow so we avoid it if we can tag->haveUndo = ((tag->undoRight != 0) || (tag->undoLeft != 0)); */ if (leftgainchange == rightgainchange) { /* don't screw around with other fields if mis-matched left/right */ dblGainChange = leftgainchange * 1.505; /* approx. 5 * log10(2) */ if (tag->haveTrackGain) { tag->trackGain -= dblGainChange; } if (tag->haveTrackPeak) { tag->trackPeak *= pow(2.0,(double)(leftgainchange)/4.0); } if (tag->haveAlbumGain) { tag->albumGain -= dblGainChange; } if (tag->haveAlbumPeak) { tag->albumPeak *= pow(2.0,(double)(leftgainchange)/4.0); } if (tag->haveMinMaxGain) { curMin = tag->minGain; curMax = tag->maxGain; curMin += leftgainchange; curMax += leftgainchange; if (wrapGain) { if (curMin < 0 || curMin > 255 || curMax < 0 || curMax > 255) { /* we've lost the "real" min or max because of wrapping */ tag->haveMinMaxGain = 0; } } else { tag->minGain = tag->minGain == 0 ? 0 : curMin < 0 ? 0 : curMin > 255 ? 255 : curMin; tag->maxGain = curMax < 0 ? 0 : curMax > 255 ? 255 : curMax; } } if (tag->haveAlbumMinMaxGain) { curMin = tag->albumMinGain; curMax = tag->albumMaxGain; curMin += leftgainchange; curMax += leftgainchange; if (wrapGain) { if (curMin < 0 || curMin > 255 || curMax < 0 || curMax > 255) { /* we've lost the "real" min or max because of wrapping */ tag->haveAlbumMinMaxGain = 0; } } else { tag->albumMinGain = tag->albumMinGain == 0 ? 0 : curMin < 0 ? 0 : curMin > 255 ? 255 : curMin; tag->albumMaxGain = curMax < 0 ? 0 : curMax > 255 ? 255 : curMax; } } } // if (leftgainchange == rightgainchange ... WriteMP3GainTag(filename AACGAIN_ARG(aacH), tag, fileTag, saveTime); } // if (!changeGain(filename ... }// if (leftgainchange !=0 ... } static int queryUserForClipping(char * argv_mainloop,int intGainChange) { int ch; fprintf(stderr,"\nWARNING: %s may clip with mp3 gain change %d\n",argv_mainloop,intGainChange); ch = 0; fflush(stdout); fflush(stderr); while ((ch != 'Y') && (ch != 'N')) { fprintf(stderr,"Make change? [y/n]:"); fflush(stderr); ch = getchar(); if (ch == EOF) { ch='N'; } ch = toupper(ch); } if (ch == 'N') return 0; return 1; } static void showVersion(char *progname) { #ifdef AACGAIN fprintf(stderr,"aacgain version %s, derived from mp3gain version %s\n",AACGAIN_VERSION,MP3GAIN_VERSION); #else fprintf(stderr,"%s version %s\n",progname,MP3GAIN_VERSION); #endif } static void wrapExplanation() { fprintf(stderr,"Here's the problem:\n"); fprintf(stderr,"The \"global gain\" field that mp3gain adjusts is an 8-bit unsigned integer, so\n"); fprintf(stderr,"the possible values are 0 to 255.\n"); fprintf(stderr,"\n"); fprintf(stderr,"MOST mp3 files (in fact, ALL the mp3 files I've examined so far) don't go\n"); fprintf(stderr,"over 230. So there's plenty of headroom on top-- you can increase the gain\n"); fprintf(stderr,"by 37dB (multiplying the amplitude by 76) without a problem.\n"); fprintf(stderr,"\n"); fprintf(stderr,"The problem is at the bottom of the range. Some encoders create frames with\n"); fprintf(stderr,"0 as the global gain for silent frames.\n"); fprintf(stderr,"What happens when you _lower_ the global gain by 1?\n"); fprintf(stderr,"Well, in the past, mp3gain always simply wrapped the result up to 255.\n"); fprintf(stderr,"That way, if you lowered the gain by any amount and then raised it by the\n"); fprintf(stderr,"same amount, the mp3 would always be _exactly_ the same.\n"); fprintf(stderr,"\n"); fprintf(stderr,"There are a few encoders out there, unfortunately, that create 0-gain frames\n"); fprintf(stderr,"with other audio data in the frame.\n"); fprintf(stderr,"As long as the global gain is 0, you'll never hear the data.\n"); fprintf(stderr,"But if you lower the gain on such a file, the global gain is suddenly _huge_.\n"); fprintf(stderr,"If you play this modified file, there might be a brief, very loud blip.\n"); fprintf(stderr,"\n"); fprintf(stderr,"So now the default behavior of mp3gain is to _not_ wrap gain changes.\n"); fprintf(stderr,"In other words,\n"); fprintf(stderr,"1) If the gain change would make a frame's global gain drop below 0,\n"); fprintf(stderr," then the global gain is set to 0.\n"); fprintf(stderr,"2) If the gain change would make a frame's global gain grow above 255,\n"); fprintf(stderr," then the global gain is set to 255.\n"); fprintf(stderr,"3) If a frame's global gain field is already 0, it is not changed, even if\n"); fprintf(stderr," the gain change is a positive number\n"); fprintf(stderr,"\n"); fprintf(stderr,"To use the original \"wrapping\" behavior, use the \"%cw\" switch.\n",SWITCH_CHAR); #ifdef AACGAIN fprintf(stderr,"\n"); fprintf(stderr,"The \"%cw\" switch is not supported for AAC files. An attempt to wrap\n",SWITCH_CHAR); fprintf(stderr,"an AAC file is treated as an error, and the file will not be modified.\n"); #endif exit(0); } static void errUsage(char *progname) { showVersion(progname); fprintf(stderr,"copyright(c) 2001-2009 by Glen Sawyer\n"); #ifdef AACGAIN fprintf(stderr,"AAC support copyright(c) 2004-2009 David Lasker, Altos Design, Inc.\n"); #endif fprintf(stderr,"uses mpglib, which can be found at http://www.mpg123.de\n"); #ifdef AACGAIN fprintf(stderr,"AAC support uses faad2 (http://www.audiocoding.com), and\n"); fprintf(stderr,"mpeg4ip's mp4v2 (http://www.mpeg4ip.net)\n"); #endif fprintf(stderr,"Usage: %s [options] [ ...]\n",progname); fprintf(stderr," --use %c? or %ch for a full list of options\n",SWITCH_CHAR,SWITCH_CHAR); fclose(stdout); fclose(stderr); exit(1); } static void fullUsage(char *progname) { showVersion(progname); fprintf(stderr,"copyright(c) 2001-2009 by Glen Sawyer\n"); #ifdef AACGAIN fprintf(stderr,"AAC support copyright(c) 2004-2009 David Lasker, Altos Design, Inc.\n"); #endif fprintf(stderr,"uses mpglib, which can be found at http://www.mpg123.de\n"); #ifdef AACGAIN fprintf(stderr,"AAC support uses faad2 (http://www.audiocoding.com), and\n"); fprintf(stderr,"mpeg4ip's mp4v2 (http://www.mpeg4ip.net)\n"); #endif fprintf(stderr,"Usage: %s [options] [ ...]\n",progname); fprintf(stderr,"options:\n"); fprintf(stderr,"\t%cv - show version number\n",SWITCH_CHAR); fprintf(stderr,"\t%cg - apply gain i without doing any analysis\n",SWITCH_CHAR); fprintf(stderr,"\t%cl 0 - apply gain i to channel 0 (left channel)\n",SWITCH_CHAR); fprintf(stderr,"\t without doing any analysis (ONLY works for STEREO files,\n"); fprintf(stderr,"\t not Joint Stereo)\n"); fprintf(stderr,"\t%cl 1 - apply gain i to channel 1 (right channel)\n",SWITCH_CHAR); fprintf(stderr,"\t%ce - skip Album analysis, even if multiple files listed\n",SWITCH_CHAR); fprintf(stderr,"\t%cr - apply Track gain automatically (all files set to equal loudness)\n",SWITCH_CHAR); fprintf(stderr,"\t%ck - automatically lower Track/Album gain to not clip audio\n",SWITCH_CHAR); fprintf(stderr,"\t%ca - apply Album gain automatically (files are all from the same\n",SWITCH_CHAR); fprintf(stderr,"\t album: a single gain change is applied to all files, so\n"); fprintf(stderr,"\t their loudness relative to each other remains unchanged,\n"); fprintf(stderr,"\t but the average album loudness is normalized)\n"); fprintf(stderr,"\t%cm - modify suggested MP3 gain by integer i\n",SWITCH_CHAR); fprintf(stderr,"\t%cd - modify suggested dB gain by floating-point n\n",SWITCH_CHAR); fprintf(stderr,"\t%cc - ignore clipping warning when applying gain\n",SWITCH_CHAR); fprintf(stderr,"\t%co - output is a database-friendly tab-delimited list\n",SWITCH_CHAR); fprintf(stderr,"\t%ct - mp3gain writes modified mp3 to temp file, then deletes original \n",SWITCH_CHAR); fprintf(stderr,"\t instead of modifying bytes in original file (default)\n"); fprintf(stderr,"\t%cT - mp3gain directly modifies mp3 file (opposite of %ct)\n",SWITCH_CHAR,SWITCH_CHAR); #ifdef AACGAIN fprintf(stderr,"\t Ignored for AAC files.\n"); #endif fprintf(stderr,"\t%cq - Quiet mode: no status messages\n",SWITCH_CHAR); fprintf(stderr,"\t%cp - Preserve original file timestamp\n",SWITCH_CHAR); fprintf(stderr,"\t%cx - Only find max. amplitude of file\n",SWITCH_CHAR); fprintf(stderr,"\t%cf - Assume input file is an MPEG 2 Layer III file\n",SWITCH_CHAR); fprintf(stderr,"\t (i.e. don't check for mis-named Layer I or Layer II files)\n"); #ifdef AACGAIN fprintf(stderr,"\t This option is ignored for AAC files.\n"); #endif fprintf(stderr,"\t%c? or %ch - show this message\n",SWITCH_CHAR,SWITCH_CHAR); fprintf(stderr,"\t%cs c - only check stored tag info (no other processing)\n",SWITCH_CHAR); fprintf(stderr,"\t%cs d - delete stored tag info (no other processing)\n",SWITCH_CHAR); fprintf(stderr,"\t%cs s - skip (ignore) stored tag info (do not read or write tags)\n",SWITCH_CHAR); fprintf(stderr,"\t%cs r - force re-calculation (do not read tag info)\n",SWITCH_CHAR); fprintf(stderr,"\t%cs i - use ID3v2 tag for MP3 gain info\n",SWITCH_CHAR); fprintf(stderr,"\t%cs a - use APE tag for MP3 gain info (default)\n",SWITCH_CHAR); fprintf(stderr,"\t%cu - undo changes made (based on stored tag info)\n",SWITCH_CHAR); fprintf(stderr,"\t%cw - \"wrap\" gain change if gain+change > 255 or gain+change < 0\n",SWITCH_CHAR); #ifdef AACGAIN fprintf(stderr,"\t MP3 only. (use \"%c? wrap\" switch for a complete explanation)\n",SWITCH_CHAR); #else fprintf(stderr,"\t (use \"%c? wrap\" switch for a complete explanation)\n",SWITCH_CHAR); #endif fprintf(stderr,"If you specify %cr and %ca, only the second one will work\n",SWITCH_CHAR,SWITCH_CHAR); fprintf(stderr,"If you do not specify %cc, the program will stop and ask before\n applying gain change to a file that might clip\n",SWITCH_CHAR); fclose(stdout); fclose(stderr); exit(0); } #ifdef AACGAIN void ReadAacTags(AACGainHandle gh, struct MP3GainTagInfo *info) { int p1, p2; if (aac_get_tag_float(gh, replaygain_album_gain, &info->albumGain) == 0) info->haveAlbumGain = !0; if (aac_get_tag_float(gh, replaygain_album_peak, &info->albumPeak) == 0) info->haveAlbumPeak = !0; if (aac_get_tag_int_2(gh, replaygain_album_minmax, &p1, &p2) == 0) { info->albumMinGain = p1; info->albumMaxGain = p2; info->haveAlbumMinMaxGain = !0; } if (aac_get_tag_float(gh, replaygain_track_gain, &info->trackGain) == 0) info->haveTrackGain = !0; if (aac_get_tag_float(gh, replaygain_track_peak, &info->trackPeak) == 0) info->haveTrackPeak = !0; if (aac_get_tag_int_2(gh, replaygain_track_minmax, &p1, &p2) == 0) { info->minGain = p1; info->maxGain = p2; info->haveMinMaxGain = !0; } if (aac_get_tag_int_2(gh, replaygain_undo, &p1, &p2) == 0) { info->undoLeft = p1; info->undoRight = p2; info->haveUndo = !0; } } #endif void dumpTaginfo(struct MP3GainTagInfo *info) { fprintf(stderr, "haveAlbumGain %d albumGain %f\n",info->haveAlbumGain, info->albumGain); fprintf(stderr, "haveAlbumPeak %d albumPeak %f\n",info->haveAlbumPeak, info->albumPeak); fprintf(stderr, "haveAlbumMinMaxGain %d min %d max %d\n",info->haveAlbumMinMaxGain, info->albumMinGain, info->albumMaxGain); fprintf(stderr, "haveTrackGain %d trackGain %f\n",info->haveTrackGain, info->trackGain); fprintf(stderr, "haveTrackPeak %d trackPeak %f\n",info->haveTrackPeak, info->trackPeak); fprintf(stderr, "haveMinMaxGain %d min %d max %d\n",info->haveMinMaxGain, info->minGain, info->maxGain); } void convert_decout(float *decout, int nprocsamp, int nchan, Float_t *lsamples, Float_t *rsamples) { int n; if(nchan == 1) for(n=0; n maxsample) maxsample = val; } return maxsample; } #ifdef WIN32 int __cdecl main(int argc, char **argv) { /*make sure this one is standard C declaration*/ #else int main(int argc, char **argv) { #endif mpg123_handle *mh = NULL; unsigned long ok; int mode; int crcflag; unsigned char *Xingcheck; unsigned long frame; int nchan; int bitridx; int freqidx; long bytesinframe; double dBchange; double dblGainChange; int intGainChange = 0; int intAlbumGainChange = 0; int nprocsamp = 0; int first = 1; int mainloop; Float_t maxsample; Float_t lsamples[1152]; Float_t rsamples[1152]; unsigned char maxgain; unsigned char mingain; int ignoreClipWarning = 0; int autoClip = 0; int applyTrack = 0; int applyAlbum = 0; int analysisTrack = 0; char analysisError = 0; int fileStart; int databaseFormat = 0; int i; int *fileok; int goAhead; int directGain = 0; int directSingleChannelGain = 0; int directGainVal = 0; int mp3GainMod = 0; double dBGainMod = 0; int mpegver; int sideinfo_len; long gFilesize = 0; int decodeSuccess; struct MP3GainTagInfo *tagInfo; struct MP3GainTagInfo *curTag; struct FileTagsStruct *fileTags; int albumRecalc; double curAlbumGain = 0; double curAlbumPeak = 0; unsigned char curAlbumMinGain = 0; unsigned char curAlbumMaxGain = 0; char chtmp; #ifdef AACGAIN AACGainHandle *aacInfo; #endif mpg123_init(); gSuccess = 1; if (argc < 2) { errUsage(argv[0]); } maxAmpOnly = 0; saveTime = 0; fileStart = 1; numFiles = 0; for (i = 1; i < argc; i++) { #ifdef WIN32 if ((argv[i][0] == '/')||((argv[i][0] == '-') && (strlen(argv[i])==2))) { /* don't need to force single-character command parameters */ #else if (((argv[i][0] == '/')||(argv[i][0] == '-'))&& (strlen(argv[i])==2)) { #endif fileStart++; switch(argv[i][1]) { case 'a': case 'A': applyTrack = 0; applyAlbum = !0; break; case 'c': case 'C': ignoreClipWarning = !0; break; case 'd': case 'D': if (argv[i][2] != '\0') { dBGainMod = atof(argv[i]+2); } else { if (i+1 < argc) { dBGainMod = atof(argv[i+1]); i++; fileStart++; } else { errUsage(argv[0]); } } break; case 'f': case 'F': Reckless = 1; break; case 'g': case 'G': directGain = !0; directSingleChannelGain = 0; if (argv[i][2] != '\0') { directGainVal = atoi(argv[i]+2); } else { if (i+1 < argc) { directGainVal = atoi(argv[i+1]); i++; fileStart++; } else { errUsage(argv[0]); } } break; case 'h': case 'H': case '?': if ((argv[i][2] == 'w')||(argv[i][2] == 'W')) { wrapExplanation(); } else { if (i+1 < argc) { if ((argv[i+1][0] == 'w')||(argv[i+1][0] =='W')) wrapExplanation(); } else { fullUsage(argv[0]); } } fullUsage(argv[0]); break; case 'k': case 'K': autoClip = !0; break; case 'l': case 'L': directSingleChannelGain = !0; directGain = 0; if (argv[i][2] != '\0') { whichChannel = atoi(argv[i]+2); if (i+1 < argc) { directGainVal = atoi(argv[i+1]); i++; fileStart++; } else { errUsage(argv[0]); } } else { if (i+2 < argc) { whichChannel = atoi(argv[i+1]); i++; fileStart++; directGainVal = atoi(argv[i+1]); i++; fileStart++; } else { errUsage(argv[0]); } } break; case 'm': case 'M': if (argv[i][2] != '\0') { mp3GainMod = atoi(argv[i]+2); } else { if (i+1 < argc) { mp3GainMod = atoi(argv[i+1]); i++; fileStart++; } else { errUsage(argv[0]); } } break; case 'o': case 'O': databaseFormat = !0; break; case 'p': case 'P': saveTime = !0; break; case 'q': case 'Q': QuietMode = !0; break; case 'r': case 'R': applyTrack = !0; applyAlbum = 0; break; case 's': case 'S': chtmp = 0; if (argv[i][2] == '\0') { if (i+1 < argc) { i++; fileStart++; chtmp = argv[i][0]; } else { errUsage(argv[0]); } } else { chtmp = argv[i][2]; } switch (chtmp) { case 'c': case 'C': checkTagOnly = !0; break; case 'd': case 'D': deleteTag = !0; break; case 's': case 'S': skipTag = !0; break; case 'u': case 'U': forceUpdateTag = !0; break; case 'r': case 'R': forceRecalculateTag = !0; break; case 'i': case 'I': useId3 = 1; break; case 'a': case 'A': useId3 = 0; break; default: errUsage(argv[0]); } break; case 't': UsingTemp = !0; break; case 'T': UsingTemp = 0; break; case 'u': case 'U': undoChanges = !0; break; case 'v': case 'V': showVersion(argv[0]); fclose(stdout); fclose(stderr); exit(0); case 'w': case 'W': wrapGain = !0; break; case 'x': case 'X': maxAmpOnly = !0; break; case 'e': case 'E': analysisTrack = !0; break; default: fprintf(stderr,"I don't recognize option %s\n",argv[i]); } } } /* now stored in tagInfo--- maxsample = malloc(sizeof(Float_t) * argc); */ fileok = (int *)malloc(sizeof(int) * argc); /* now stored in tagInfo--- maxgain = malloc(sizeof(unsigned char) * argc); */ /* now stored in tagInfo--- mingain = malloc(sizeof(unsigned char) * argc); */ tagInfo = (struct MP3GainTagInfo *)calloc(argc, sizeof(struct MP3GainTagInfo)); fileTags = (struct FileTagsStruct *)malloc(sizeof(struct FileTagsStruct) * argc); #ifdef AACGAIN aacInfo = (AACGainHandle)malloc(sizeof(AACGainHandle) * argc); #endif if (databaseFormat) { if (checkTagOnly) { fprintf(stdout,"File\tMP3 gain\tdB gain\tMax Amplitude\tMax global_gain\tMin global_gain\tAlbum gain\tAlbum dB gain\tAlbum Max Amplitude\tAlbum Max global_gain\tAlbum Min global_gain\n"); } else if (undoChanges) { fprintf(stdout,"File\tleft global_gain change\tright global_gain change\n"); } else { fprintf(stdout,"File\tMP3 gain\tdB gain\tMax Amplitude\tMax global_gain\tMin global_gain\n"); } fflush(stdout); } /* read all the tags first */ totFiles = argc - fileStart; for (mainloop = fileStart; mainloop < argc; mainloop++) { fileok[mainloop] = 0; curfilename = argv[mainloop]; fileTags[mainloop].apeTag = NULL; fileTags[mainloop].lyrics3tag = NULL; fileTags[mainloop].id31tag = NULL; tagInfo[mainloop].dirty = forceUpdateTag; tagInfo[mainloop].haveAlbumGain = 0; tagInfo[mainloop].haveAlbumPeak = 0; tagInfo[mainloop].haveTrackGain = 0; tagInfo[mainloop].haveTrackPeak = 0; tagInfo[mainloop].haveUndo = 0; tagInfo[mainloop].haveMinMaxGain = 0; tagInfo[mainloop].haveAlbumMinMaxGain = 0; tagInfo[mainloop].recalc = 0; #ifdef AACGAIN //check for aac file; open it if found //note: we try to open aac even if /f (reckless) if (aac_open(curfilename, UsingTemp, saveTime, &aacInfo[mainloop]) != 0) { //in case of any errors, don't continue processing so there is no risk of corrupting //a bad file passError(MP3GAIN_FILEFORMAT_NOTSUPPORTED, 2, curfilename, " is not a valid mp4/m4a file.\n"); exit(1); } #endif if ((!skipTag)&&(!deleteTag)) { #ifdef AACGAIN if (aacInfo[mainloop]) { if (!skipTag) ReadAacTags(aacInfo[mainloop], &(tagInfo[mainloop])); } else #endif { ReadMP3GainAPETag(curfilename,&(tagInfo[mainloop]),&(fileTags[mainloop])); if (useId3) { if (tagInfo[mainloop].haveTrackGain || tagInfo[mainloop].haveAlbumGain || tagInfo[mainloop].haveMinMaxGain || tagInfo[mainloop].haveAlbumMinMaxGain || tagInfo[mainloop].haveUndo) { /* Mark the file dirty to force upgrade to ID3v2 */ tagInfo[mainloop].dirty = 1; } ReadMP3GainID3Tag(curfilename,&(tagInfo[mainloop])); } } /*fprintf(stdout,"Read previous tags from %s\n",curfilename); dumpTaginfo(&(tagInfo[mainloop]));*/ if (forceRecalculateTag) { if (tagInfo[mainloop].haveAlbumGain) { tagInfo[mainloop].dirty = !0; tagInfo[mainloop].haveAlbumGain = 0; } if (tagInfo[mainloop].haveAlbumPeak) { tagInfo[mainloop].dirty = !0; tagInfo[mainloop].haveAlbumPeak = 0; } if (tagInfo[mainloop].haveTrackGain) { tagInfo[mainloop].dirty = !0; tagInfo[mainloop].haveTrackGain = 0; } if (tagInfo[mainloop].haveTrackPeak) { tagInfo[mainloop].dirty = !0; tagInfo[mainloop].haveTrackPeak = 0; } /* NOT Undo information! if (tagInfo[mainloop].haveUndo) { tagInfo[mainloop].dirty = !0; tagInfo[mainloop].haveUndo = 0; } */ if (tagInfo[mainloop].haveMinMaxGain) { tagInfo[mainloop].dirty = !0; tagInfo[mainloop].haveMinMaxGain = 0; } if (tagInfo[mainloop].haveAlbumMinMaxGain) { tagInfo[mainloop].dirty = !0; tagInfo[mainloop].haveAlbumMinMaxGain = 0; } } } } /* check if we need to actually process the file(s) */ albumRecalc = forceRecalculateTag || skipTag ? FULL_RECALC : 0; if ((!skipTag)&&(!deleteTag)&&(!forceRecalculateTag)) { /* we're not automatically recalculating, so check if we already have all the information */ if (argc - fileStart > 1) { curAlbumGain = tagInfo[fileStart].albumGain; curAlbumPeak = tagInfo[fileStart].albumPeak; curAlbumMinGain = tagInfo[fileStart].albumMinGain; curAlbumMaxGain = tagInfo[fileStart].albumMaxGain; } for (mainloop = fileStart; mainloop < argc; mainloop++) { if (!maxAmpOnly) { /* we don't care about these things if we're only looking for max amp */ if (argc - fileStart > 1 && !applyTrack && !analysisTrack) { /* only check album stuff if more than one file in the list */ if (!tagInfo[mainloop].haveAlbumGain) { albumRecalc |= FULL_RECALC; } else if (tagInfo[mainloop].albumGain != curAlbumGain) { albumRecalc |= FULL_RECALC; } } if (!tagInfo[mainloop].haveTrackGain) { tagInfo[mainloop].recalc |= FULL_RECALC; } } if (argc - fileStart > 1 && !applyTrack && !analysisTrack) { /* only check album stuff if more than one file in the list */ if (!tagInfo[mainloop].haveAlbumPeak) { albumRecalc |= AMP_RECALC; } else if (tagInfo[mainloop].albumPeak != curAlbumPeak) { albumRecalc |= AMP_RECALC; } if (!tagInfo[mainloop].haveAlbumMinMaxGain) { albumRecalc |= MIN_MAX_GAIN_RECALC; } else if (tagInfo[mainloop].albumMaxGain != curAlbumMaxGain) { albumRecalc |= MIN_MAX_GAIN_RECALC; } else if (tagInfo[mainloop].albumMinGain != curAlbumMinGain) { albumRecalc |= MIN_MAX_GAIN_RECALC; } } if (!tagInfo[mainloop].haveTrackPeak) { tagInfo[mainloop].recalc |= AMP_RECALC; } if (!tagInfo[mainloop].haveMinMaxGain) { tagInfo[mainloop].recalc |= MIN_MAX_GAIN_RECALC; } } } for (mainloop = fileStart; mainloop < argc; mainloop++) { #ifdef AACGAIN AACGainHandle aacH = aacInfo[mainloop]; #endif // if the entire Album requires some kind of recalculation, then each track needs it tagInfo[mainloop].recalc |= albumRecalc; curfilename = argv[mainloop]; if (checkTagOnly) { curTag = tagInfo + mainloop; if (curTag->haveTrackGain) { dblGainChange = curTag->trackGain / (5.0 * log10(2.0)); if (fabs(dblGainChange) - (double)((int)(fabs(dblGainChange))) < 0.5) intGainChange = (int)(dblGainChange); else intGainChange = (int)(dblGainChange) + (dblGainChange < 0 ? -1 : 1); } if (curTag->haveAlbumGain) { dblGainChange = curTag->albumGain / (5.0 * log10(2.0)); if (fabs(dblGainChange) - (double)((int)(fabs(dblGainChange))) < 0.5) intAlbumGainChange = (int)(dblGainChange); else intAlbumGainChange = (int)(dblGainChange) + (dblGainChange < 0 ? -1 : 1); } if (!databaseFormat) { fprintf(stdout,"%s\n",argv[mainloop]); if (curTag->haveTrackGain) { fprintf(stdout,"Recommended \"Track\" dB change: %f\n",curTag->trackGain); fprintf(stdout,"Recommended \"Track\" mp3 gain change: %d\n",intGainChange); if (curTag->haveTrackPeak) { if (curTag->trackPeak * (Float_t)(pow(2.0,(double)(intGainChange)/4.0)) > 1.0) { fprintf(stdout,"WARNING: some clipping may occur with this gain change!\n"); } } } if (curTag->haveTrackPeak) fprintf(stdout,"Max PCM sample at current gain: %f\n",curTag->trackPeak * 32768.0); if (curTag->haveMinMaxGain) { fprintf(stdout,"Max mp3 global gain field: %d\n",curTag->maxGain); fprintf(stdout,"Min mp3 global gain field: %d\n",curTag->minGain); } if (curTag->haveAlbumGain) { fprintf(stdout,"Recommended \"Album\" dB change: %f\n",curTag->albumGain); fprintf(stdout,"Recommended \"Album\" mp3 gain change: %d\n",intAlbumGainChange); if (curTag->haveTrackPeak) { if (curTag->trackPeak * (Float_t)(pow(2.0,(double)(intAlbumGainChange)/4.0)) > 1.0) { fprintf(stdout,"WARNING: some clipping may occur with this gain change!\n"); } } } if (curTag->haveAlbumPeak) { fprintf(stdout,"Max Album PCM sample at current gain: %f\n",curTag->albumPeak * 32768.0); } if (curTag->haveAlbumMinMaxGain) { fprintf(stdout,"Max Album mp3 global gain field: %d\n",curTag->albumMaxGain); fprintf(stdout,"Min Album mp3 global gain field: %d\n",curTag->albumMinGain); } fprintf(stdout,"\n"); } else { fprintf(stdout,"%s\t",argv[mainloop]); if (curTag->haveTrackGain) { fprintf(stdout,"%d\t",intGainChange); fprintf(stdout,"%f\t",curTag->trackGain); } else { fprintf(stdout,"NA\tNA\t"); } if (curTag->haveTrackPeak) { fprintf(stdout,"%f\t",curTag->trackPeak * 32768.0); } else { fprintf(stdout,"NA\t"); } if (curTag->haveMinMaxGain) { fprintf(stdout,"%d\t",curTag->maxGain); fprintf(stdout,"%d\t",curTag->minGain); } else { fprintf(stdout,"NA\tNA\t"); } if (curTag->haveAlbumGain) { fprintf(stdout,"%d\t",intAlbumGainChange); fprintf(stdout,"%f\t",curTag->albumGain); } else { fprintf(stdout,"NA\tNA\t"); } if (curTag->haveAlbumPeak) { fprintf(stdout,"%f\t",curTag->albumPeak * 32768.0); } else { fprintf(stdout,"NA\t"); } if (curTag->haveAlbumMinMaxGain) { fprintf(stdout,"%d\t",curTag->albumMaxGain); fprintf(stdout,"%d\n",curTag->albumMinGain); } else { fprintf(stdout,"NA\tNA\n"); } fflush(stdout); } } else if (undoChanges) { directGain = !0; /* so we don't write the tag a second time */ if ((tagInfo[mainloop].haveUndo)&&(tagInfo[mainloop].undoLeft || tagInfo[mainloop].undoRight)) { if ((!QuietMode)&&(!databaseFormat)) fprintf(stderr,"Undoing mp3gain changes (%d,%d) to %s...\n", tagInfo[mainloop].undoLeft, tagInfo[mainloop].undoRight, argv[mainloop]); if (databaseFormat) fprintf(stdout,"%s\t%d\t%d\n", argv[mainloop], tagInfo[mainloop].undoLeft, tagInfo[mainloop].undoRight); changeGainAndTag(argv[mainloop] AACGAIN_ARG(aacH), tagInfo[mainloop].undoLeft, tagInfo[mainloop].undoRight, tagInfo + mainloop, fileTags + mainloop); } else { if (databaseFormat) { fprintf(stdout,"%s\t0\t0\n",argv[mainloop]); } else if (!QuietMode) { if (tagInfo[mainloop].haveUndo) { fprintf(stderr,"No changes to undo in %s\n",argv[mainloop]); } else { fprintf(stderr,"No undo information in %s\n",argv[mainloop]); } gSuccess = 0; } } } else if (directSingleChannelGain) { if (!QuietMode) fprintf(stderr,"Applying gain change of %d to CHANNEL %d of %s...\n",directGainVal,whichChannel,argv[mainloop]); if (whichChannel) { /* do right channel */ if (skipTag) { changeGain(argv[mainloop] AACGAIN_ARG(aacH), 0, directGainVal); } else { changeGainAndTag(argv[mainloop] AACGAIN_ARG(aacH), 0, directGainVal, tagInfo + mainloop, fileTags + mainloop); } } else { /* do left channel */ if (skipTag) { changeGain(argv[mainloop] AACGAIN_ARG(aacH), directGainVal, 0); } else { changeGainAndTag(argv[mainloop] AACGAIN_ARG(aacH), directGainVal, 0, tagInfo + mainloop, fileTags + mainloop); } } if ((!QuietMode) && (gSuccess == 1)) fprintf(stderr,"\ndone\n"); } else if (directGain) { if (!QuietMode) fprintf(stderr,"Applying gain change of %d to %s...\n",directGainVal,argv[mainloop]); if (skipTag) { changeGain(argv[mainloop] AACGAIN_ARG(aacH), directGainVal, directGainVal); } else { changeGainAndTag(argv[mainloop] AACGAIN_ARG(aacH), directGainVal,directGainVal, tagInfo + mainloop, fileTags + mainloop); } if ((!QuietMode) && (gSuccess == 1)) fprintf(stderr,"\ndone\n"); } else if (deleteTag) { #ifdef AACGAIN if (aacH) aac_clear_rg_tags(aacH); else #endif { RemoveMP3GainAPETag(argv[mainloop], saveTime); if (useId3) { RemoveMP3GainID3Tag(argv[mainloop], saveTime); } } if ((!QuietMode)&&(!databaseFormat)) fprintf(stderr,"Deleting tag info of %s...\n", argv[mainloop]); if (databaseFormat) fprintf(stdout,"%s\tNA\tNA\tNA\tNA\tNA\n", argv[mainloop]); } else { if (!databaseFormat) fprintf(stdout,"%s\n",argv[mainloop]); if (tagInfo[mainloop].recalc > 0) { gFilesize = getSizeOfFile(argv[mainloop]); #ifdef AACGAIN if (!aacH) #endif inf = fopen(argv[mainloop],"rb"); } #ifdef AACGAIN if (!aacH&&(inf == NULL)&&(tagInfo[mainloop].recalc > 0)) { #else if ((inf == NULL)&&(tagInfo[mainloop].recalc > 0)) { #endif fprintf(stderr, "Can't open %s for reading\n",argv[mainloop]); fflush(stderr); gSuccess = 0; } else { #ifdef AACGAIN if (!aacH) #endif /* We want mpg123 decoding to float only, and without fancy sample cutting, */ /* so that mp3gain and mpg123 agree on sample offsets. The padding should */ /* have no big effect on gain anyway. Seek buffer needs to be disabled to */ /* avoid MPG123_NEED_MORE after feeding the first frame. */ if( !(mh = mpg123_new(NULL, &decodeSuccess)) || MPG123_OK != mpg123_param(mh, MPG123_ADD_FLAGS, MPG123_FORCE_FLOAT, 0.) || MPG123_OK != mpg123_param(mh, MPG123_REMOVE_FLAGS, MPG123_GAPLESS|MPG123_SEEKBUFFER, 0.) || MPG123_OK != mpg123_param(mh, MPG123_VERBOSE, 10, 0.) || #if (MPG123_API_VERSION >= 45) MPG123_OK != mpg123_param(mh, MPG123_ADD_FLAGS, MPG123_NO_READAHEAD, 0.) || #endif MPG123_OK != mpg123_open_feed(mh) ) { fprintf( stderr, "Failed to create/setup mpg123 handle: %s\n", mh ? mpg123_strerror(mh) : mpg123_plain_strerror(decodeSuccess) ); exit(1); } if (tagInfo[mainloop].recalc == 0) { maxsample = tagInfo[mainloop].trackPeak * 32767.0; maxgain = tagInfo[mainloop].maxGain; mingain = tagInfo[mainloop].minGain; ok = !0; } else { if (!((tagInfo[mainloop].recalc & FULL_RECALC)||(tagInfo[mainloop].recalc & AMP_RECALC))) { /* only min/max rescan */ maxsample = tagInfo[mainloop].trackPeak * 32768.0; } else { maxsample = 0; } #ifdef AACGAIN if (aacH) { int rc; if (first) { lastfreq = aac_get_sample_rate(aacH); InitGainAnalysis((long)lastfreq); analysisError = 0; first = 0; } else { if (aac_get_sample_rate(aacH) != lastfreq) { lastfreq = aac_get_sample_rate(aacH); ResetSampleFrequency ((long)lastfreq); } } numFiles++; if (maxAmpOnly) rc = aac_compute_peak(aacH, &maxsample, &mingain, &maxgain, QuietMode ? NULL : reportPercentAnalyzed); else rc = aac_compute_gain(aacH, &maxsample, &mingain, &maxgain, QuietMode ? NULL : reportPercentAnalyzed); //in case of any error, bail to avoid corrupting file if (rc != 0) { passError(MP3GAIN_FILEFORMAT_NOTSUPPORTED, 2, curfilename, " is not a valid mp4/m4a file.\n"); exit(1); } ok = !0; } else #endif { BadLayer = 0; LayerSet = Reckless; maxgain = 0; mingain = 255; inbuffer = 0; filepos = 0; bitidx = 0; ok = fillBuffer(0); } } if (ok) { #ifdef AACGAIN if (!aacH && (tagInfo[mainloop].recalc > 0)) { #else if (tagInfo[mainloop].recalc > 0) { #endif wrdpntr = buffer; ok = skipID3v2(); ok = frameSearch(!0); } #ifdef AACGAIN if (!ok && !aacH) { #else if (!ok) { #endif if (!BadLayer) { fprintf(stderr,"Can't find any valid MP3 frames in file %s\n",argv[mainloop]); fflush(stderr); gSuccess = 0; } } else { LayerSet = 1; /* We've found at least one valid layer 3 frame. * Assume any later layer 1 or 2 frames are just * bitstream corruption */ fileok[mainloop] = !0; #ifdef AACGAIN if (!aacH || (tagInfo[mainloop].recalc == 0)) #endif numFiles++; #ifdef AACGAIN if (!aacH && (tagInfo[mainloop].recalc > 0)) { #else if (tagInfo[mainloop].recalc > 0) { #endif mode = (curframe[3] >> 6) & 3; if ((curframe[1] & 0x08) == 0x08) /* MPEG 1 */ sideinfo_len = ((curframe[3] & 0xC0) == 0xC0) ? 4 + 17 : 4 + 32; else /* MPEG 2 */ sideinfo_len = ((curframe[3] & 0xC0) == 0xC0) ? 4 + 9 : 4 + 17; if (!(curframe[1] & 0x01)) sideinfo_len += 2; Xingcheck = curframe + sideinfo_len; //LAME CBR files have "Info" tags, not "Xing" tags if ((Xingcheck[0] == 'X' && Xingcheck[1] == 'i' && Xingcheck[2] == 'n' && Xingcheck[3] == 'g') || (Xingcheck[0] == 'I' && Xingcheck[1] == 'n' && Xingcheck[2] == 'f' && Xingcheck[3] == 'o')) { bitridx = (curframe[2] >> 4) & 0x0F; if (bitridx == 0) { fprintf(stderr, "%s is free format (not currently supported)\n",curfilename); fflush(stderr); ok = 0; } else { mpegver = (curframe[1] >> 3) & 0x03; freqidx = (curframe[2] >> 2) & 0x03; bytesinframe = arrbytesinframe[bitridx] + ((curframe[2] >> 1) & 0x01); wrdpntr = curframe + bytesinframe; ok = frameSearch(0); } } frame = 1; if (!maxAmpOnly) { if (ok) { mpegver = (curframe[1] >> 3) & 0x03; freqidx = (curframe[2] >> 2) & 0x03; if (first) { lastfreq = frequency[mpegver][freqidx]; InitGainAnalysis((long)(lastfreq * 1000.0)); analysisError = 0; first = 0; } else { if (frequency[mpegver][freqidx] != lastfreq) { lastfreq = frequency[mpegver][freqidx]; ResetSampleFrequency ((long)(lastfreq * 1000.0)); } } } } else { analysisError = 0; } while (ok) { bitridx = (curframe[2] >> 4) & 0x0F; if (bitridx == 0) { fprintf(stderr,"%s is free format (not currently supported)\n",curfilename); fflush(stderr); ok = 0; } else { mpegver = (curframe[1] >> 3) & 0x03; crcflag = curframe[1] & 0x01; freqidx = (curframe[2] >> 2) & 0x03; bytesinframe = arrbytesinframe[bitridx] + ((curframe[2] >> 1) & 0x01); mode = (curframe[3] >> 6) & 0x03; nchan = (mode == 3) ? 1 : 2; if (inbuffer >= bytesinframe) { maxGain = &maxgain; minGain = &mingain; if ((tagInfo[mainloop].recalc & AMP_RECALC) || (tagInfo[mainloop].recalc & FULL_RECALC)) { #ifdef WIN32 #ifndef __GNUC__ __try { /* this is the Windows try/catch equivalent for C. If you want this in some other system, you should be able to use the C++ try/catch mechanism. I've tried to keep all of the code plain C, though. This error only occurs with _very_ corrupt mp3s, so I don't know if you'll think it's worth the trouble */ #endif #endif size_t decbytes = 0; unsigned char *decout; /* Get gain values directly, not from decoder. */ scanFrameGain(); /* Feed the frames to libmpg123. As they are pre-parsed already, */ /* we tolerate no deviations. Calls for more data mean that the */ /* mpg123 parser disagrees with us here, which is fatal. */ /* It could also mean free format streams where you need to seek */ /* ahead. But: mp3gain will not work on those anyway. A rework */ /* might just rely on libmpg123's parser and pull the raw data */ /* from there. */ if(MPG123_OK != mpg123_feed(mh, curframe, bytesinframe)) { fprintf(stderr, "Feeding mpg123 failed: %s\n", mpg123_strerror(mh)); exit(1); } /* We will check output format for each frame, as there is the */ /* case where mp3gain may think the channel count changed, but */ /* mpg123 does not (CVE-2017-14407). */ decodeSuccess = mpg123_decode_frame(mh, NULL, &decout, &decbytes); if(decodeSuccess == MPG123_NEW_FORMAT) decodeSuccess = mpg123_decode_frame(mh, NULL, &decout, &decbytes); if(decodeSuccess == MPG123_OK) { int enc = 0, channels = 0; mpg123_getformat(mh, NULL, &channels, &enc); /* Could also check that sampling freq matches ... */ if(enc != MPG123_ENC_FLOAT_32 || channels != nchan) { fprintf(stderr, "Unexpected format returned by libmpg123.\n"); exit(1); } } else { #if (MPG123_API_VERSION < 45) if(decodeSuccess == MPG123_NEED_MORE) { fprintf(stderr, "Delaying a frame in decoding with old libmpg123.\n"); decbytes = 0; } else { #endif fprintf(stderr, "Failed to decode MPEG frame: %s\n", mpg123_strerror(mh)); exit(1); #if (MPG123_API_VERSION < 45) } #endif } nprocsamp = decbytes / sizeof(float) / nchan; /* Paranoia triggered by CVE-2017-14407. */ if(nprocsamp > sizeof(lsamples)/sizeof(Float_t)) { fprintf(stderr, "Too many samples in libmpg123 output.\n"); exit(1); } convert_decout((float*)decout, nprocsamp, nchan, lsamples, rsamples); maxsample = find_maxsample((float*)decout, nprocsamp*nchan, maxsample); #ifdef WIN32 #ifndef __GNUC__ } __except(1) { fprintf(stderr,"Error analyzing %s. This mp3 has some very corrupt data.\n",curfilename); fclose(stdout); fclose(stderr); exit(1); } #endif #endif } else { /* don't need to actually decode frame, just scan for min/max gain values */ decodeSuccess = !MPG123_OK; scanFrameGain();//curframe); } if (decodeSuccess == MPG123_OK) { if ((!maxAmpOnly)&&(tagInfo[mainloop].recalc & FULL_RECALC)) { if (AnalyzeSamples(lsamples,rsamples,nprocsamp,nchan) == GAIN_ANALYSIS_ERROR) { fprintf(stderr,"Error analyzing further samples (max time reached) \n"); analysisError = !0; ok = 0; } } } } if (!analysisError) { wrdpntr = curframe+bytesinframe; ok = frameSearch(0); } if (!QuietMode) { if ( !(++frame % 200)) { reportPercentAnalyzed((int)(((double)(filepos-(inbuffer-(curframe+bytesinframe-buffer))) * 100.0) / gFilesize),gFilesize); } } } } } if (!QuietMode) fprintf(stderr," \r"); if (tagInfo[mainloop].recalc & FULL_RECALC) { if (maxAmpOnly) dBchange = 0; else dBchange = GetTitleGain(); } else { dBchange = tagInfo[mainloop].trackGain; } if (dBchange == GAIN_NOT_ENOUGH_SAMPLES) { fprintf(stderr,"Not enough samples in %s to do analysis\n",argv[mainloop]); fflush(stderr); gSuccess = 0; numFiles--; } else { /* even if skipTag is on, we'll leave this part running just to store the minpeak and maxpeak */ curTag = tagInfo + mainloop; if (!maxAmpOnly) { if ( /* if we don't already have a tagged track gain OR we have it, but it doesn't match */ !curTag->haveTrackGain || (curTag->haveTrackGain && (fabs(dBchange - curTag->trackGain) >= 0.01)) ) { curTag->dirty = !0; curTag->haveTrackGain = 1; curTag->trackGain = dBchange; } } if (!curTag->haveMinMaxGain || /* if minGain or maxGain doesn't match tag */ (curTag->haveMinMaxGain && (curTag->minGain != mingain || curTag->maxGain != maxgain))) { curTag->dirty = !0; curTag->haveMinMaxGain = !0; curTag->minGain = mingain; curTag->maxGain = maxgain; } if (!curTag->haveTrackPeak || (curTag->haveTrackPeak && (fabs(maxsample - (curTag->trackPeak) * 32768.0) >= 3.3))) { curTag->dirty = !0; curTag->haveTrackPeak = !0; curTag->trackPeak = maxsample / 32768.0; } /* the TAG version of the suggested Track Gain should ALWAYS be based on the 89dB standard. So we don't modify the suggested gain change until this point */ dBchange += dBGainMod; dblGainChange = dBchange / (5.0 * log10(2.0)); if (fabs(dblGainChange) - (double)((int)(fabs(dblGainChange))) < 0.5) intGainChange = (int)(dblGainChange); else intGainChange = (int)(dblGainChange) + (dblGainChange < 0 ? -1 : 1); intGainChange += mp3GainMod; if (databaseFormat) { fprintf(stdout,"%s\t%d\t%f\t%f\t%d\t%d\n",argv[mainloop],intGainChange,dBchange,maxsample,maxgain,mingain); fflush(stdout); } if ((!applyTrack)&&(!applyAlbum)) { if (!databaseFormat) { fprintf(stdout,"Recommended \"Track\" dB change: %f\n",dBchange); fprintf(stdout,"Recommended \"Track\" mp3 gain change: %d\n",intGainChange); if (maxsample * (Float_t)(pow(2.0,(double)(intGainChange)/4.0)) > 32767.0) { fprintf(stdout,"WARNING: some clipping may occur with this gain change!\n"); } fprintf(stdout,"Max PCM sample at current gain: %f\n",maxsample); fprintf(stdout,"Max mp3 global gain field: %d\n",maxgain); fprintf(stdout,"Min mp3 global gain field: %d\n",mingain); fprintf(stdout,"\n"); } } else if (applyTrack) { first = !0; /* don't keep track of Album gain */ if (inf) fclose(inf); inf = NULL; goAhead = !0; if (intGainChange == 0) { fprintf(stdout,"No changes to %s are necessary\n",argv[mainloop]); if (!skipTag && tagInfo[mainloop].dirty) { fprintf(stdout,"...but tag needs update: Writing tag information for %s\n",argv[mainloop]); WriteMP3GainTag(argv[mainloop] AACGAIN_ARG(aacInfo[mainloop]), tagInfo + mainloop, fileTags + mainloop, saveTime); } } else { if (autoClip) { int intMaxNoClipGain = (int)(floor(4.0 * log10(32767.0 / maxsample) / log10(2.0))); if (intGainChange > intMaxNoClipGain) { fprintf(stdout,"Applying auto-clipped mp3 gain change of %d to %s\n(Original suggested gain was %d)\n",intMaxNoClipGain,argv[mainloop],intGainChange); intGainChange = intMaxNoClipGain; } } else if (!ignoreClipWarning) { if (maxsample * (Float_t)(pow(2.0,(double)(intGainChange)/4.0)) > 32767.0) { if (queryUserForClipping(argv[mainloop],intGainChange)) { fprintf(stdout,"Applying mp3 gain change of %d to %s...\n",intGainChange,argv[mainloop]); } else { goAhead = 0; } } } if (goAhead) { fprintf(stdout,"Applying mp3 gain change of %d to %s...\n",intGainChange,argv[mainloop]); if (skipTag) { changeGain(argv[mainloop] AACGAIN_ARG(aacH), intGainChange, intGainChange); } else { changeGainAndTag(argv[mainloop] AACGAIN_ARG(aacH), intGainChange,intGainChange, tagInfo + mainloop, fileTags + mainloop); } } else if (!skipTag && tagInfo[mainloop].dirty) { fprintf(stdout,"Writing tag information for %s\n",argv[mainloop]); WriteMP3GainTag(argv[mainloop] AACGAIN_ARG(aacH), tagInfo + mainloop, fileTags + mainloop, saveTime); } } } } } } #ifdef AACGAIN if (!aacH) #endif mpg123_delete(mh); mh = NULL; fflush(stderr); fflush(stdout); if (inf) fclose(inf); inf = NULL; } } } if ((numFiles > 0)&&(!applyTrack)&&(!analysisTrack)) { if (albumRecalc & FULL_RECALC) { if (maxAmpOnly) dBchange = 0; else dBchange = GetAlbumGain(); } else { /* the following if-else is for the weird case where someone applies "Album" gain to a single file, but the file doesn't actually have an Album field */ if (tagInfo[fileStart].haveAlbumGain) dBchange = tagInfo[fileStart].albumGain; else dBchange = tagInfo[fileStart].trackGain; } if (dBchange == GAIN_NOT_ENOUGH_SAMPLES) { fprintf(stderr,"Not enough samples in mp3 files to do analysis\n"); fflush(stderr); } else { Float_t maxmaxsample; unsigned char maxmaxgain; unsigned char minmingain; maxmaxsample = 0; maxmaxgain = 0; minmingain = 255; for (mainloop = fileStart; mainloop < argc; mainloop++) { if (fileok[mainloop]) { if (tagInfo[mainloop].trackPeak > maxmaxsample) maxmaxsample = tagInfo[mainloop].trackPeak; if (tagInfo[mainloop].maxGain > maxmaxgain) maxmaxgain = tagInfo[mainloop].maxGain; if (tagInfo[mainloop].minGain < minmingain) minmingain = tagInfo[mainloop].minGain; } } if ((!skipTag)&&(numFiles > 1 || applyAlbum)) { for (mainloop = fileStart; mainloop < argc; mainloop++) { curTag = tagInfo + mainloop; if (!maxAmpOnly) { if ( /* if we don't already have a tagged track gain OR we have it, but it doesn't match */ !curTag->haveAlbumGain || (curTag->haveAlbumGain && (fabs(dBchange - curTag->albumGain) >= 0.01)) ){ curTag->dirty = !0; curTag->haveAlbumGain = 1; curTag->albumGain = dBchange; } } if (!curTag->haveAlbumMinMaxGain || /* if albumMinGain or albumMaxGain doesn't match tag */ (curTag->haveAlbumMinMaxGain && (curTag->albumMinGain != minmingain || curTag->albumMaxGain != maxmaxgain))) { curTag->dirty = !0; curTag->haveAlbumMinMaxGain = !0; curTag->albumMinGain = minmingain; curTag->albumMaxGain = maxmaxgain; } if (!curTag->haveAlbumPeak || (curTag->haveAlbumPeak && (fabs(maxmaxsample - curTag->albumPeak) >= 0.0001))) { curTag->dirty = !0; curTag->haveAlbumPeak = !0; curTag->albumPeak = maxmaxsample; } } } /* the TAG version of the suggested Album Gain should ALWAYS be based on the 89dB standard. So we don't modify the suggested gain change until this point */ dBchange += dBGainMod; dblGainChange = dBchange / (5.0 * log10(2.0)); if (fabs(dblGainChange) - (double)((int)(fabs(dblGainChange))) < 0.5) intGainChange = (int)(dblGainChange); else intGainChange = (int)(dblGainChange) + (dblGainChange < 0 ? -1 : 1); intGainChange += mp3GainMod; if (databaseFormat) { fprintf(stdout,"\"Album\"\t%d\t%f\t%f\t%d\t%d\n",intGainChange,dBchange,maxmaxsample * 32768.0 ,maxmaxgain,minmingain); fflush(stdout); } if (!applyAlbum) { if (!databaseFormat) { fprintf(stdout,"\nRecommended \"Album\" dB change for all files: %f\n",dBchange); fprintf(stdout,"Recommended \"Album\" mp3 gain change for all files: %d\n",intGainChange); for (mainloop = fileStart; mainloop < argc; mainloop++) { if (fileok[mainloop]) if (tagInfo[mainloop].trackPeak * (Float_t)(pow(2.0,(double)(intGainChange)/4.0)) > 1.0) { fprintf(stdout,"WARNING: with this global gain change, some clipping may occur in file %s\n",argv[mainloop]); } } } } else { /*MAA*/ if (autoClip) { /*MAA*/ int intMaxNoClipGain = (int)(floor(-4.0 * log10(maxmaxsample) / log10(2.0))); /*MAA*/ if (intGainChange > intMaxNoClipGain) { /*MAA*/ fprintf(stdout,"Applying auto-clipped mp3 gain change of %d to album\n(Original suggested gain was %d)\n",intMaxNoClipGain,intGainChange); /*MAA*/ intGainChange = intMaxNoClipGain; /*MAA*/ } /*MAA*/ } for (mainloop = fileStart; mainloop < argc; mainloop++) { if (fileok[mainloop]) { goAhead = !0; if (intGainChange == 0) { fprintf(stdout,"\nNo changes to %s are necessary\n",argv[mainloop]); if (!skipTag && tagInfo[mainloop].dirty) { fprintf(stdout,"...but tag needs update: Writing tag information for %s\n",argv[mainloop]); WriteMP3GainTag(argv[mainloop] AACGAIN_ARG(aacInfo[mainloop]), tagInfo + mainloop, fileTags + mainloop, saveTime); } } else { if (!ignoreClipWarning) { if (tagInfo[mainloop].trackPeak * (Float_t)(pow(2.0,(double)(intGainChange)/4.0)) > 1.0) goAhead = queryUserForClipping(argv[mainloop],intGainChange); } if (goAhead) { fprintf(stdout,"Applying mp3 gain change of %d to %s...\n",intGainChange,argv[mainloop]); if (skipTag) { changeGain(argv[mainloop] AACGAIN_ARG(aacInfo[mainloop]), intGainChange, intGainChange); } else { changeGainAndTag(argv[mainloop] AACGAIN_ARG(aacInfo[mainloop]), intGainChange, intGainChange, tagInfo + mainloop, fileTags + mainloop); } } else if (!skipTag && tagInfo[mainloop].dirty) { fprintf(stdout,"Writing tag information for %s\n",argv[mainloop]); WriteMP3GainTag(argv[mainloop] AACGAIN_ARG(aacInfo[mainloop]), tagInfo + mainloop, fileTags + mainloop, saveTime); } } } } } } } /* update file tags */ if ((!applyTrack)&& (!applyAlbum)&& (!directGain)&& (!directSingleChannelGain)&& (!deleteTag)&& (!skipTag)&& (!checkTagOnly)) { /* if we made changes, we already updated the tags */ for (mainloop = fileStart; mainloop < argc; mainloop++) { if (fileok[mainloop]) { if (tagInfo[mainloop].dirty) { WriteMP3GainTag(argv[mainloop] AACGAIN_ARG(aacInfo[mainloop]), tagInfo + mainloop, fileTags + mainloop, saveTime); } } } } free(tagInfo); /* now stored in tagInfo--- free(maxsample); */ free(fileok); /* now stored in tagInfo--- free(maxgain); */ /* now stored in tagInfo--- free(mingain); */ for (mainloop = fileStart; mainloop < argc; mainloop++) { if (fileTags[mainloop].apeTag) { if (fileTags[mainloop].apeTag->otherFields) { free(fileTags[mainloop].apeTag->otherFields); } free(fileTags[mainloop].apeTag); } if (fileTags[mainloop].lyrics3tag) { free(fileTags[mainloop].lyrics3tag); } if (fileTags[mainloop].id31tag) { free(fileTags[mainloop].id31tag); } #ifdef AACGAIN //close any open aac files if (aacInfo[mainloop]) aac_close(aacInfo[mainloop]); #endif } free(fileTags); #ifdef AACGAIN free(aacInfo); #endif fclose(stdout); fclose(stderr); if (gSuccess) return 0; else return 1; } #endif /* asWIN32DLL */ mp3gain.dsp0000644000175000017500000003624213303547222013332 0ustar snhardinsnhardin# Microsoft Developer Studio Project File - Name="mp3gain" - Package Owner=<4> # Microsoft Developer Studio Generated Build File, Format Version 6.00 # ** DO NOT EDIT ** # TARGTYPE "Win32 (x86) Console Application" 0x0103 CFG=mp3gain - Win32 DebugDLL !MESSAGE This is not a valid makefile. To build this project using NMAKE, !MESSAGE use the Export Makefile command and run !MESSAGE !MESSAGE NMAKE /f "mp3gain.mak". !MESSAGE !MESSAGE You can specify a configuration when running NMAKE !MESSAGE by defining the macro CFG on the command line. For example: !MESSAGE !MESSAGE NMAKE /f "mp3gain.mak" CFG="mp3gain - Win32 DebugDLL" !MESSAGE !MESSAGE Possible choices for configuration are: !MESSAGE !MESSAGE "mp3gain - Win32 Release" (based on "Win32 (x86) Console Application") !MESSAGE "mp3gain - Win32 Debug" (based on "Win32 (x86) Console Application") !MESSAGE "mp3gain - Win32 ReleaseDLL" (based on "Win32 (x86) Console Application") !MESSAGE "mp3gain - Win32 DebugDLL" (based on "Win32 (x86) Console Application") !MESSAGE # Begin Project # PROP AllowPerConfigDependencies 0 # PROP Scc_ProjName ""$/analysis", SCAAAAAA" # PROP Scc_LocalPath "." CPP=cl.exe RSC=rc.exe !IF "$(CFG)" == "mp3gain - Win32 Release" # PROP BASE Use_MFC 0 # PROP BASE Use_Debug_Libraries 0 # PROP BASE Output_Dir "Release" # PROP BASE Intermediate_Dir "Release" # PROP BASE Target_Dir "" # PROP Use_MFC 0 # PROP Use_Debug_Libraries 0 # PROP Output_Dir "Release" # PROP Intermediate_Dir "Release" # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" MTL=midl.exe # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c # ADD CPP /nologo /W4 /GX /Zi /O2 /Ob2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /D "HAVE_MEMCPY" /D "NOANALYSIS" /D "HAVE_STRCHR" /U "asWIN32DLL" /YX /FD /c # SUBTRACT CPP /WX # ADD BASE RSC /l 0x409 /d "NDEBUG" # ADD RSC /l 0x409 /d "NDEBUG" BSC32=bscmake.exe # ADD BASE BSC32 /nologo # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 # ADD LINK32 setargv.obj /nologo /subsystem:console /map /debug /machine:I386 /OPT:REF # SUBTRACT LINK32 /pdb:none !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" # PROP BASE Use_MFC 0 # PROP BASE Use_Debug_Libraries 1 # PROP BASE Output_Dir "Debug" # PROP BASE Intermediate_Dir "Debug" # PROP BASE Target_Dir "" # PROP Use_MFC 0 # PROP Use_Debug_Libraries 1 # PROP Output_Dir "Debug" # PROP Intermediate_Dir "Debug" # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" MTL=midl.exe # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c # ADD CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /D "HAVE_MEMCPY" /D "NOANALYSIS" /YX /FD /GZ /c # ADD BASE RSC /l 0x409 /d "_DEBUG" # ADD RSC /l 0x409 /d "_DEBUG" BSC32=bscmake.exe # ADD BASE BSC32 /nologo # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept # ADD LINK32 setargv.obj /nologo /subsystem:console /incremental:no /map /debug /machine:I386 /pdbtype:sept !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" # PROP BASE Use_MFC 0 # PROP BASE Use_Debug_Libraries 0 # PROP BASE Output_Dir "ReleaseDLL" # PROP BASE Intermediate_Dir "ReleaseDLL" # PROP BASE Target_Dir "" # PROP Use_MFC 0 # PROP Use_Debug_Libraries 0 # PROP Output_Dir "ReleaseDLL" # PROP Intermediate_Dir "ReleaseDLL" # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" MTL=midl.exe # ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o "NUL" /win32 # ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o "NUL" /win32 # ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c # ADD CPP /nologo /MD /W4 /GX /Zi /O2 /Ob2 /D "NDEBUG" /D "asWIN32DLL" /D "WIN32" /D "_WINDOWS" /D "HAVE_MEMCPY" /D "NOANALYSIS" /FR /FD /c # ADD BASE RSC /l 0x409 /d "NDEBUG" # ADD RSC /l 0x409 /d "NDEBUG" BSC32=bscmake.exe # ADD BASE BSC32 /nologo # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386 # ADD LINK32 user32.lib /nologo /base:"0x60000000" /subsystem:windows /dll /map /debug /machine:I386 /nodefaultlib:"LIBC" /def:"replaygaindll.def" /out:"ReleaseDLL/replaygain.dll" /OPT:REF # SUBTRACT LINK32 /pdb:none !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" # PROP BASE Use_MFC 0 # PROP BASE Use_Debug_Libraries 0 # PROP BASE Output_Dir "DebugDLL" # PROP BASE Intermediate_Dir "DebugDLL" # PROP BASE Ignore_Export_Lib 0 # PROP BASE Target_Dir "" # PROP Use_MFC 0 # PROP Use_Debug_Libraries 0 # PROP Output_Dir "DebugDLL" # PROP Intermediate_Dir "DebugDLL" # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" MTL=midl.exe # ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o "NUL" /win32 # ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o "NUL" /win32 # ADD BASE CPP /nologo /Zp1 /MD /W3 /GX /Zi /Od /D "WIN32" /D "_WINDOWS" /FR /FD /c # ADD CPP /nologo /MD /W4 /GX /Zi /Od /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "HAVE_MEMCPY" /D "NOANALYSIS" /D "asWIN32DLL" /FR /FD /c # ADD BASE RSC /l 0x409 /d "NDEBUG" # ADD RSC /l 0x409 /d "NDEBUG" BSC32=bscmake.exe # ADD BASE BSC32 /nologo # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /map /debug /machine:I386 /def:"replaygaindll.def" # SUBTRACT BASE LINK32 /verbose /pdb:none # ADD LINK32 user32.lib /nologo /base:"0x60000000" /subsystem:windows /dll /incremental:yes /map /debug /machine:I386 /nodefaultlib:"LIBC" /def:"replaygaindll.def" /out:"DebugDLL/replaygain.dll" # SUBTRACT LINK32 /verbose /pdb:none !ENDIF # Begin Target # Name "mp3gain - Win32 Release" # Name "mp3gain - Win32 Debug" # Name "mp3gain - Win32 ReleaseDLL" # Name "mp3gain - Win32 DebugDLL" # Begin Group "Source Files" # PROP Default_Filter ".c" # Begin Source File SOURCE=.\apetag.c # End Source File # Begin Source File SOURCE=.\id3tag.c # End Source File # Begin Source File SOURCE=.\mpglibDBL\common.c !IF "$(CFG)" == "mp3gain - Win32 Release" !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" # PROP Exclude_From_Build 1 !ENDIF # End Source File # Begin Source File SOURCE=.\mpglibDBL\dct64_i386.c !IF "$(CFG)" == "mp3gain - Win32 Release" !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" # PROP Exclude_From_Build 1 !ENDIF # End Source File # Begin Source File SOURCE=.\mpglibDBL\decode_i386.c !IF "$(CFG)" == "mp3gain - Win32 Release" !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" # PROP Exclude_From_Build 1 !ENDIF # End Source File # Begin Source File SOURCE=.\gain_analysis.c # End Source File # Begin Source File SOURCE=.\mpglibDBL\interface.c !IF "$(CFG)" == "mp3gain - Win32 Release" !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" # PROP Exclude_From_Build 1 !ENDIF # End Source File # Begin Source File SOURCE=.\mpglibDBL\layer3.c !IF "$(CFG)" == "mp3gain - Win32 Release" !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" # PROP Exclude_From_Build 1 !ENDIF # End Source File # Begin Source File SOURCE=.\mp3gain.c # End Source File # Begin Source File SOURCE=.\replaygaindll.c !IF "$(CFG)" == "mp3gain - Win32 Release" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" !ENDIF # End Source File # Begin Source File SOURCE=.\rg_error.c # End Source File # Begin Source File SOURCE=.\mpglibDBL\tabinit.c !IF "$(CFG)" == "mp3gain - Win32 Release" !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" # PROP Exclude_From_Build 1 !ENDIF # End Source File # End Group # Begin Group "Header Files" # PROP Default_Filter ".h" # Begin Source File SOURCE=.\apetag.h # End Source File # Begin Source File SOURCE=.\id3tag.h # End Source File # Begin Source File SOURCE=.\mpglibDBL\bitstream.h !IF "$(CFG)" == "mp3gain - Win32 Release" !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" # PROP Exclude_From_Build 1 !ENDIF # End Source File # Begin Source File SOURCE=.\mpglibDBL\common.h !IF "$(CFG)" == "mp3gain - Win32 Release" !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" # PROP Exclude_From_Build 1 !ENDIF # End Source File # Begin Source File SOURCE=.\mpglibDBL\config.h !IF "$(CFG)" == "mp3gain - Win32 Release" !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" # PROP Exclude_From_Build 1 !ENDIF # End Source File # Begin Source File SOURCE=.\mpglibDBL\dct64_i386.h !IF "$(CFG)" == "mp3gain - Win32 Release" !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" # PROP Exclude_From_Build 1 !ENDIF # End Source File # Begin Source File SOURCE=.\mpglibDBL\decode_i386.h !IF "$(CFG)" == "mp3gain - Win32 Release" !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" # PROP Exclude_From_Build 1 !ENDIF # End Source File # Begin Source File SOURCE=.\mpglibDBL\encoder.h !IF "$(CFG)" == "mp3gain - Win32 Release" !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" # PROP Exclude_From_Build 1 !ENDIF # End Source File # Begin Source File SOURCE=.\gain_analysis.h # End Source File # Begin Source File SOURCE=.\mpglibDBL\huffman.h !IF "$(CFG)" == "mp3gain - Win32 Release" !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" # PROP Exclude_From_Build 1 !ENDIF # End Source File # Begin Source File SOURCE=.\mpglibDBL\interface.h !IF "$(CFG)" == "mp3gain - Win32 Release" !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" # PROP Exclude_From_Build 1 !ENDIF # End Source File # Begin Source File SOURCE=.\mpglibDBL\lame.h !IF "$(CFG)" == "mp3gain - Win32 Release" !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" # PROP Exclude_From_Build 1 !ENDIF # End Source File # Begin Source File SOURCE=.\mpglibDBL\layer3.h !IF "$(CFG)" == "mp3gain - Win32 Release" !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" # PROP Exclude_From_Build 1 !ENDIF # End Source File # Begin Source File SOURCE=.\mpglibDBL\machine.h !IF "$(CFG)" == "mp3gain - Win32 Release" !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" # PROP Exclude_From_Build 1 !ENDIF # End Source File # Begin Source File SOURCE=.\mp3gain.h # End Source File # Begin Source File SOURCE=.\mpglibDBL\mpg123.h !IF "$(CFG)" == "mp3gain - Win32 Release" !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" # PROP Exclude_From_Build 1 !ENDIF # End Source File # Begin Source File SOURCE=.\mpglibDBL\mpglib.h !IF "$(CFG)" == "mp3gain - Win32 Release" !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" # PROP Exclude_From_Build 1 !ENDIF # End Source File # Begin Source File SOURCE=.\resource.h !IF "$(CFG)" == "mp3gain - Win32 Release" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" !ENDIF # End Source File # Begin Source File SOURCE=.\rg_error.h # End Source File # Begin Source File SOURCE=.\mpglibDBL\tabinit.h !IF "$(CFG)" == "mp3gain - Win32 Release" !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" # PROP Exclude_From_Build 1 !ENDIF # End Source File # Begin Source File SOURCE=.\mpglibDBL\VbrTag.h !IF "$(CFG)" == "mp3gain - Win32 Release" !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" # PROP Exclude_From_Build 1 !ENDIF # End Source File # End Group # Begin Group "Resource Files" # PROP Default_Filter ".rc" # Begin Source File SOURCE=.\replaygaindll.def !IF "$(CFG)" == "mp3gain - Win32 Release" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" !ENDIF # End Source File # Begin Source File SOURCE=.\replaygainversion.rc !IF "$(CFG)" == "mp3gain - Win32 Release" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" !ENDIF # End Source File # Begin Source File SOURCE=.\VerInfo.rc !IF "$(CFG)" == "mp3gain - Win32 Release" !ELSEIF "$(CFG)" == "mp3gain - Win32 Debug" !ELSEIF "$(CFG)" == "mp3gain - Win32 ReleaseDLL" # PROP Exclude_From_Build 1 !ELSEIF "$(CFG)" == "mp3gain - Win32 DebugDLL" # PROP Exclude_From_Build 1 !ENDIF # End Source File # End Group # End Target # End Project mp3gain.dsw0000644000175000017500000000124513303547222013334 0ustar snhardinsnhardinMicrosoft Developer Studio Workspace File, Format Version 6.00 # WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! ############################################################################### Project: "mp3gain"=.\mp3gain.dsp - Package Owner=<4> Package=<5> {{{ begin source code control "$/CLI", ECAAAAAA . end source code control }}} Package=<4> {{{ }}} ############################################################################### Global: Package=<5> {{{ begin source code control "$/", aaaaaaaa . end source code control }}} Package=<3> {{{ }}} ############################################################################### mp3gain.h0000644000175000017500000000376113303547222012773 0ustar snhardinsnhardin/* * ReplayGainAnalysis Heder - DLL for Glen Sawyer's MP3GAIN.C source * Copyright (C) 2002 John Zitterkopf (zitt@bigfoot.com) * (http://www.zittware.com) * * These comments must remain intact in all copies of the source code. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * This header for VC++5.0 by John Zitterkopf (zitt@bigfoot.com) * -- blame him for nothing. This work evolves as needed. * * V0.0 - jzitt9/4/2002 * * header needed to eliminate 'changeGain' undefined compiler warning */ #ifndef MP3GAIN_H #define MP3GAIN_H #define MP3GAIN_VERSION "1.6.2" /* jzitt: moved from mp3gain.c */ #define M3G_ERR_CANT_MODIFY_FILE -1 #define M3G_ERR_CANT_MAKE_TMP -2 #define M3G_ERR_NOT_ENOUGH_TMP_SPACE -3 #define M3G_ERR_RENAME_TMP -4 #define M3G_ERR_FILEOPEN -5 #define M3G_ERR_READ -6 #define M3G_ERR_WRITE -7 #define M3G_ERR_TAGFORMAT -8 #include "rg_error.h" #ifdef asWIN32DLL void changeGain(char *filename, int leftgainchange, int rightgainchange); #endif int deleteFile(char *filename); int moveFile(char *currentfilename, char *newfilename); typedef enum { storeTime, setStoredTime } timeAction; void passError(MMRESULT lerrnum, int numStrings, ...); /* Get/Set file datetime stamp */ void fileTime(char *filename, timeAction action); #endif mp3gain.sln0000644000175000017500000000155713303547222013341 0ustar snhardinsnhardin Microsoft Visual Studio Solution File, Format Version 10.00 # Visual Studio 2008 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mp3gain", "mp3gain.vcproj", "{B16A1BFF-6ADE-4705-AE72-079C0312DC23}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 Release|Win32 = Release|Win32 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {B16A1BFF-6ADE-4705-AE72-079C0312DC23}.Debug|Win32.ActiveCfg = Debug|Win32 {B16A1BFF-6ADE-4705-AE72-079C0312DC23}.Debug|Win32.Build.0 = Debug|Win32 {B16A1BFF-6ADE-4705-AE72-079C0312DC23}.Release|Win32.ActiveCfg = Release|Win32 {B16A1BFF-6ADE-4705-AE72-079C0312DC23}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal mp3gain.vcproj0000644000175000017500000001446013303547222014045 0ustar snhardinsnhardin replaygaindll.c0000644000175000017500000001416613303547222014260 0ustar snhardinsnhardin/* * ReplayGainAnalysis DLL Wrapper - DLL Wrapper for Glen Sawyer's headers * Copyright (C) 2002 John Zitterkopf (zitt@bigfoot.com) * (http://www.zittware.com) * * These comments must remain intact in all copies of the source code. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * concept and filter values by David Robinson (David@Robinson.org) * -- blame him if you think the idea is flawed * coding by Glen Sawyer (mp3gain@hotmail.com) 735 W 255 N, Orem, UT 84057-4505 USA * -- blame him if you think this runs too slowly, or the coding is otherwise flawed * DLL Wrapper code for VC++5.0 by John Zitterkopf (zitt@bigfoot.com) * -- blame him for nothing. This work evolves as needed. * * For an explanation of the concepts and the basic algorithms involved, go to: * http://www.replaygain.org/ * * V1.0 - jzitt * * Based on V1.0 header source provided by Glen Sawyer * * Attempts to maintain some backward capability with V0.9 of the same source. * * V1.2.1 - jzitt 9/4/2002 * * Incorporates V1.2.1 MP3Gain sources. * * Adds MP3GAIN capabilities to DLL. */ /*define below tells sourcecode that we are compiling as a Win32 DLL*/ #ifndef asWIN32DLL #define asWIN32DLL #endif #include #include "gain_analysis.h" #include "mp3gain.h" #include "rg_error.h" /*jzitt*/ #define MAXSAMPLES 2400 BOOL APIENTRY DllMain(HANDLE hModule, unsigned long dwReason, LPVOID lpReserved) { switch(dwReason) { case DLL_PROCESS_ATTACH: mp3gainerrstr = NULL; break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; case DLL_PROCESS_DETACH: if (mp3gainerrstr != NULL) { free(mp3gainerrstr); mp3gainerrstr = NULL; } break; } return TRUE; UNREFERENCED_PARAMETER(lpReserved); UNREFERENCED_PARAMETER(hModule); } int AnalyzeSamplesInterleaved(char *samples, long num_samples, int num_channels) { double leftbuf[MAXSAMPLES], rightbuf[MAXSAMPLES]; long i; long totSamples = num_samples; long nSamples = num_samples; signed short int *samp = (signed short int *)samples; int result = GAIN_ANALYSIS_ERROR; /* NOTES: * leftbuf and rightbuf are arrays of doubles * samp is a short (16-bit) integer * inf is the input file * totSamples is the total number of samples remaining in the input file * MAXSAMPLES is the maximum number of samples to send to AnalyzeSamples at once */ while (totSamples > 0) { if (totSamples > MAXSAMPLES) nSamples = MAXSAMPLES; else nSamples = totSamples; if (num_channels == 2) { for (i = 0; i < nSamples; i++) { leftbuf[i] = *samp++; /* default conversion from short to double */ rightbuf[i] = *samp++; } result = AnalyzeSamples(leftbuf,rightbuf,nSamples,2); if (result != GAIN_ANALYSIS_OK) return result; } //end stereo else { /* Just one channel (mono) */ for (i = 0; i < nSamples; i++) { leftbuf[i] = *samp++; } result = AnalyzeSamples(leftbuf,NULL,nSamples,1); if (result != GAIN_ANALYSIS_OK) return result; } //end just mono totSamples -= nSamples; } //end while return result; } double GetRadioGain() { return GetTitleGain(); } double GetAudiophileGain() { return GetAlbumGain(); } int InitGainAnalysisAsInt( int samplingFreq ) { return InitGainAnalysis( samplingFreq ); } char *thFilename; int thGainchange; DWORD WINAPI changeGainThread( LPVOID lpParam ) { changeGain(thFilename, thGainchange, thGainchange); return 0; UNREFERENCED_PARAMETER(lpParam); } unsigned int __stdcall ChangeGainOfMP3File(char *filename, int gainchange) { DWORD dwThreadID; HANDLE hThread; MSG msg; mp3gainerr = MP3GAIN_NOERROR; blnCancel = 0; if (mp3gainerrstr != NULL) { free(mp3gainerrstr); mp3gainerrstr = NULL; } thFilename = filename; thGainchange = gainchange; hThread = CreateThread(NULL,0,changeGainThread,NULL,0,&dwThreadID); if (hThread == NULL) return MP3GAIN_UNSPECIFED_ERROR; while ((MsgWaitForMultipleObjects(1, &hThread, FALSE, INFINITE, QS_ALLINPUT)) == (WAIT_OBJECT_0 + 1)) { while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) DispatchMessage(&msg); } CloseHandle(hThread); return mp3gainerr; } unsigned int __stdcall GetMP3GainError() { return mp3gainerr; } long __stdcall GetMP3GainErrorStrLen() { if ((mp3gainerr == MP3GAIN_NOERROR) || (mp3gainerrstr == NULL)) return 0; return strlen(mp3gainerrstr); } char * __stdcall GetMP3GainErrorStr( char * buffer, int buflen ) { if (buflen < 1) { //strcpy(buffer,'\0'); return NULL;//buffer; } if ((mp3gainerr == MP3GAIN_NOERROR) || (mp3gainerrstr == NULL)) { buffer[0] = '\0'; return buffer; } buffer[buflen-1] = '\0'; //don't assume buffer has extra byte at the end //for null terminator return strncpy( buffer, mp3gainerrstr, buflen-1 ); } void __stdcall StopMP3GainProcessing() { blnCancel = !0; } int __stdcall attachmsgpump(HANDLE ahwnd, UINT percentdonemsg, UINT errmsg) { apphandle = 0; apppercentdonemsg = 0; apperrmsg = 0; if (ahwnd != 0) { apphandle = ahwnd; apppercentdonemsg = percentdonemsg; apperrmsg = errmsg; } /*printf("Hi, John!\n"); MessageBox( 0, "Hi, John!\n", "ReplayGainDLL\n", MB_OK ); */ return(MP3GAIN_NOERROR); //return success } char * GetDLLVersion( char * buffer, int buflen ) { if (buflen < 1) { //strcpy(buffer,'\0'); return NULL;//buffer; } buffer[buflen] = '\0'; return strncpy( buffer, MP3GAIN_VERSION, buflen-1 ); }replaygaindll.def0000644000175000017500000000116213303547222014564 0ustar snhardinsnhardinLIBRARY replaygain.DLL DESCRIPTION 'ReplayGain.DLL Windows Dynamic Link Library' EXPORTS ; ReplayGain Exposures InitGainAnalysis @1 InitGainAnalysisAsInt @8 ResetSampleFrequency @9 AnalyzeSamples @2 AnalyzeSamplesInterleaved @5 GetRadioGain @3 GetTitleGain @6 GetAudiophileGain @4 GetAlbumGain @7 GetDLLVersion @10 ; MP3 Gain Exposures (leave room, start at ordinal 100) ChangeGainOfMP3File @100 StopMP3GainProcessing @101 ; Error Reporting (leave room, start at ordinal 300) GetMP3GainError @300 GetMP3GainErrorStr @301 attachmsgpump @302 GetMP3GainErrorStrLen @303replaygainversion.rc0000644000175000017500000000510113303547222015341 0ustar snhardinsnhardin//Microsoft Developer Studio generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 #ifndef _MAC ///////////////////////////////////////////////////////////////////////////// // // Version // VS_VERSION_INFO VERSIONINFO FILEVERSION 1,3,1,0 PRODUCTVERSION 1,3,1,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x40004L FILETYPE 0x2L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904b0" BEGIN VALUE "Comments", "Replay Gain DLL \r\nBased on David Robinson's www.replaygain.org idea \r\nand C++ headers by Glen Sawyer.\0" VALUE "CompanyName", "Zittware\0" VALUE "FileDescription", "Replay Gain DLL\0" VALUE "FileVersion", "1, 3, 1, 0\0" VALUE "InternalName", "replaygain\0" VALUE "LegalCopyright", "Copyright © 2002 by John Zitterkopf\0" VALUE "LegalTrademarks", "ALL RIGHTS RESERVED\0" VALUE "OriginalFilename", "replaygain.dll\0" VALUE "PrivateBuild", "\0" VALUE "ProductName", "Zittware Replay Gain DLL\0" VALUE "ProductVersion", "1, 3, 1, 0\0" VALUE "SpecialBuild", "\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1200 END END #endif // !_MAC #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED #endif // English (U.S.) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED resource.h0000644000175000017500000000402313303547222013254 0ustar snhardinsnhardin//{{NO_DEPENDENCIES}} // Microsoft Developer Studio generated include file. // Used by replaygainversion.rc /* * ReplayGainAnalysis DLL Wrapper - DLL Wrapper for Glen Sawyer's headers * Copyright (C) 2002 John Zitterkopf (zitt@bigfoot.com) * (http://www.zittware.com) * * These comments must remain intact in all copies of the source code. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * concept and filter values by David Robinson (David@Robinson.org) * -- blame him if you think the idea is flawed * coding by Glen Sawyer (mp3gain@hotmail.com) 735 W 255 N, Orem, UT 84057-4505 USA * -- blame him if you think this runs too slowly, or the coding is otherwise flawed * DLL Wrapper code for VC++5.0 by John Zitterkopf (zitt@bigfoot.com) * * For an explanation of the concepts and the basic algorithms involved, go to: * http://www.replaygain.org/ * * V1.0 - jzitt * * Based on V1.0 header source provided by Glen Sawyer * * Attempts to maintain some backward capability with V0.9 of the same source. */ // // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 101 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif rg_error.c0000644000175000017500000000511213303547222013241 0ustar snhardinsnhardin/* * ReplayGainAnalysis Error Reporting * Handles error reporting for mp3gain in either standalone or DLL form. * * Copyright (C) 2002 John Zitterkopf (zitt@bigfoot.com) * (http://www.zittware.com) * * These comments must remain intact in all copies of the source code. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * based on code by Glen Sawyer (mp3gain@hotmail.com) 735 W 255 N, Orem, UT 84057-4505 USA * * Error code for VC++5.0 by John Zitterkopf (zitt@bigfoot.com) * -- blame him for nothing. This work evolves as needed. * * V1.0 - jzitt * * initial release based on V1.2.3 sources */ #include "rg_error.h" #ifndef asWIN32DLL extern int gSuccess; #endif void DoError( char * localerrstr, MMRESULT localerrnum ) { #ifndef asWIN32DLL gSuccess = 0; fprintf(stdout, "%s", localerrstr); #else //send message to DLL's parent mp3gainerr = localerrnum; if (mp3gainerrstr != NULL) { free(mp3gainerrstr); mp3gainerrstr = NULL; } mp3gainerrstr = malloc(strlen(localerrstr) + 1); strcpy(mp3gainerrstr,localerrstr); if ( (apphandle != 0) && ( apperrmsg != 0 ) ) { SendMessage(apphandle, apperrmsg, localerrnum, (LPARAM) localerrstr); } #endif } void DoUnkError( char * localerrstr) { DoError( localerrstr, MP3GAIN_UNSPECIFED_ERROR ); } #ifdef asWIN32DLL /*the sendpercentdone sends a windows message to the calling app with the progress into the file (pdone). The calling app acknowledges the message by returning an LRESULT. LRESULT = 0 means continue processing the file. LRESULT != 0 means abort processing this file. */ LRESULT sendpercentdone( int pdone, long filesize ) { //send message to DLL's parent if ( (apphandle != 0) && ( apppercentdonemsg != 0 ) ) { return !(SendNotifyMessage(apphandle, apppercentdonemsg, pdone, filesize)); } else return(0); //no calling app defined, send by 0 to continue } #endif rg_error.h0000644000175000017500000000437613303547222013261 0ustar snhardinsnhardin/* * ReplayGainAnalysis Error Reporting Header * Handles error reporting for mp3gain in either standalone or DLL form. * * Copyright (C) 2002 John Zitterkopf (zitt@bigfoot.com) * (http://www.zittware.com) * * These comments must remain intact in all copies of the source code. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * based on code by Glen Sawyer (mp3gain@hotmail.com) 735 W 255 N, Orem, UT 84057-4505 USA * * Error code for VC++5.0 by John Zitterkopf (zitt@bigfoot.com) * -- blame him for nothing. This work evolves as needed. * * V1.0 - jzitt * * initial release based on V1.2.3 */ #ifndef RG_ERROR_H #define RG_ERROR_H #include #ifdef asWIN32DLL #include #include MMRESULT mp3gainerr; char * mp3gainerrstr; BOOL blnCancel; HANDLE apphandle; //holds calling app's window handle int apppercentdonemsg; //holds calling apps message number for percent done int apperrmsg; //holds calling apps message number for errors LRESULT sendpercentdone( int pdone, long filesize ); #else /*asWIN32DLL*/ #include #define MMSYSERR_NOERROR 0 #define MMSYSERR_ERROR 01 #define WAVERR_BADFORMAT 32 typedef unsigned int MMRESULT; #endif /*asWIN32DLL*/ #define MP3GAIN_NOERROR MMSYSERR_NOERROR #define MP3GAIN_UNSPECIFED_ERROR MMSYSERR_ERROR #define MP3GAIN_FILEFORMAT_NOTSUPPORTED WAVERR_BADFORMAT #define MP3GAIN_CANCELLED 2006 void DoUnkError( char * localerrstr); void DoError( char * localerrstr, MMRESULT localerrnum ); #endif VerInfo.rc0000644000175000017500000000471413303547222013161 0ustar snhardinsnhardin//Microsoft Developer Studio generated resource script. // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 101 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 #ifndef _MAC ///////////////////////////////////////////////////////////////////////////// // // Version // VS_VERSION_INFO VERSIONINFO FILEVERSION 1,5,0,2 PRODUCTVERSION 1,5,0,2 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x40004L FILETYPE 0x1L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904b0" BEGIN VALUE "ProductName", "mp3gain\0" VALUE "Author", "Glen Sawyer\0" VALUE "FileDescription", "mp3 volume normalizer\0" VALUE "LegalCopyright", "Copyright © 2001-2009 Glen Sawyer\0" VALUE "OriginalFilename", "mp3gain.exe\0" VALUE "FileVersion", "1, 5, 0, 2\0" VALUE "ProductVersion", "1, 5, 0, 2\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1200 END END #endif // !_MAC #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED #endif // English (U.S.) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED