--- mp3gain-1.4.6/mp3gain.c.bak 2004-12-23 15:47:04.000000000 +0200 +++ mp3gain-1.4.6/mp3gain.c 2007-08-14 00:20:36.632870417 +0200 @@ -1,2343 +1,2604 @@ -/* - * mp3gain.c - analyzes mp3 files, determines the perceived volume, - * and adjusts the volume of the mp3 accordingly - * - * Copyright (C) 2001-2004 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 - * - * 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" - -#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 - -/* I tweaked the mpglib library just a bit to spit out the raw - * decoded double values, instead of rounding them to 16-bit integers. - * Hence the "mpglibDBL" directory - */ - -#ifndef asWIN32DLL -#include "mpglibDBL/interface.h" - -#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 - -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 = 0; -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 checkTagOnly = 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 = 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 = 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 - } - } -} - -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, 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; - - gainchange[0] = leftgainchange; - gainchange[1] = rightgainchange; - singlechannel = !(leftgainchange == rightgainchange); - - if (saveTime) - fileTime(filename, storeTime); - - gFilesize = getSizeOfFile(filename); - - if (UsingTemp) { - fflush(stderr); - fflush(stdout); - outlength = 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"); - return M3G_ERR_CANT_MAKE_TMP; - } - - } - } - else { - inf = fopen(filename,"r+b"); - } - - if (inf == NULL) { - if (UsingTemp && (outf != NULL)) - fclose(outf); - passError( MP3GAIN_UNSPECIFED_ERROR, 3, - "\nCan't open ", filename, " for modifying\n"); - 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"); - 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) { -#ifndef asWIN32DLL - fprintf(stderr," \r %2d%% of %ld bytes written\r" - ,(int)(((double)(filepos-(inbuffer-(curframe+bytesinframe-buffer))) * 100.0) / gFilesize),gFilesize); - fflush(stderr); -#else - /* report % back to calling app */ - ok = (int)(((double)(filepos-(inbuffer-(curframe+bytesinframe-buffer))) * 100.0 ) / gFilesize) ; - ok = sendpercentdone( (int)ok, gFilesize ); - - if ( ok != 0) - return /* ok */; - ok = 1; /* allow us to continue processing file */ -#endif - } - } - wrdpntr = curframe+bytesinframe; - ok = frameSearch(0); - } - } - } - -#ifdef asWIN32DLL - if (blnCancel) { //need to clean up as best as possible - fclose(inf); - if (UsingTemp) { - fclose(outf); - 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); - 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; - - if (outlength != inlength) { - deleteFile(outfilename); - passError( MP3GAIN_UNSPECIFED_ERROR, 3, - "Not enough temp space on disk to modify ", filename, - "\nEither free some space, or do not use \"temp file\" option\n"); - 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"); - 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"); - 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 - -void changeGainAndTag(char *filename, int leftgainchange, int rightgainchange, struct MP3GainTagInfo *tag, struct FileTagsStruct *fileTag) { - double dblGainChange; - int curMin; - int curMax; - - if (leftgainchange != 0 || rightgainchange != 0) { - if (!changeGain(filename,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 ... - WriteMP3GainAPETag(filename, tag, fileTag, saveTime); - } // if (!changeGain(filename ... - }// if (leftgainchange !=0 ... - -} - -static -int queryUserForClipping(char * argv_mainloop,int intGainChange) -{ - char 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(); - ch = toupper(ch); - } - if (ch == 'N') - return 0; - - return 1; -} - -static -void showVersion(char *progname) { - fprintf(stderr,"%s version %s\n",progname,MP3GAIN_VERSION); -} - - -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); - exit(0); - -} - - - -static -void errUsage(char *progname) { - showVersion(progname); - fprintf(stderr,"copyright(c) 2001-2004 by Glen Sawyer\n"); - fprintf(stderr,"uses mpglib, which can be found at http://www.mpg123.de\n"); - 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-2004 by Glen Sawyer\n"); - fprintf(stderr,"uses mpglib, which can be found at http://www.mpg123.de\n"); - 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 to mp3 without doing any analysis\n",SWITCH_CHAR); - fprintf(stderr,"\t%cl 0 - apply gain i to channel 0 (left channel) of mp3\n",SWITCH_CHAR); - fprintf(stderr,"\t without doing any analysis (ONLY works for STEREO mp3s,\n"); - fprintf(stderr,"\t not Joint Stereo mp3s)\n"); - fprintf(stderr,"\t%cl 1 - apply gain i to channel 1 (right channel) of mp3\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\n"); - 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 mp3\n",SWITCH_CHAR); - fprintf(stderr,"\t%cf - Force mp3gain to 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"); - 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%cu - undo changes made by mp3gain (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); - fprintf(stderr,"\t (use \"%c? wrap\" switch for a complete explanation)\n",SWITCH_CHAR); - 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); -} - -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); -} - - -#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 - MPSTR mp; - 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; - 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; - char analysisError = 0; - int fileStart; - int numFiles; - int databaseFormat = 0; - int i; - int *fileok; - int goAhead; - int directGain = 0; - int directSingleChannelGain = 0; - int directGainVal = 0; - int mp3GainMod = 0; - double dBGainMod = 0; - char fileDivFiles[21]; - 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; - - 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 'r': - case 'R': - forceRecalculateTag = !0; - break; - default: - errUsage(argv[0]); - } - - break; - - case 't': - 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; - - 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); - - 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 */ - 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 = 0; - 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; - - - if ((!skipTag)&&(!deleteTag)) { - ReadMP3GainAPETag(curfilename,&(tagInfo[mainloop]),&(fileTags[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) { /* 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) { /* 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++) { - memset(&mp, 0, sizeof(mp)); - - // 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], 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]); - } - } - } - } - 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],0,directGainVal); - } else { - changeGainAndTag(argv[mainloop],0,directGainVal, tagInfo + mainloop, fileTags + mainloop); - } - } - else { /* do left channel */ - if (skipTag) { - changeGain(argv[mainloop],directGainVal,0); - } else { - changeGainAndTag(argv[mainloop],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],directGainVal,directGainVal); - } else { - changeGainAndTag(argv[mainloop],directGainVal,directGainVal, tagInfo + mainloop, fileTags + mainloop); - } - if ((!QuietMode) && (gSuccess == 1)) - fprintf(stderr,"\ndone\n"); - } - else if (deleteTag) { - RemoveMP3GainAPETag(argv[mainloop], saveTime); - } - else { - if (!databaseFormat) - fprintf(stdout,"%s\n",argv[mainloop]); - - if (tagInfo[mainloop].recalc > 0) { - gFilesize = getSizeOfFile(argv[mainloop]); - - inf = fopen(argv[mainloop],"rb"); - } - - if ((inf == NULL)&&(tagInfo[mainloop].recalc > 0)) { - fprintf(stdout, "Can't open %s for reading\n",argv[mainloop]); - fflush(stdout); - } - else { - InitMP3(&mp); - if (tagInfo[mainloop].recalc == 0) { - maxsample = tagInfo[mainloop].trackPeak * 32768.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; - } - BadLayer = 0; - LayerSet = Reckless; - maxgain = 0; - mingain = 255; - inbuffer = 0; - filepos = 0; - bitidx = 0; - ok = fillBuffer(0); - } - if (ok) { - - if (tagInfo[mainloop].recalc > 0) { - wrdpntr = buffer; - - ok = skipID3v2(); - - ok = frameSearch(!0); - } - - if (!ok) { - if (!BadLayer) { - fprintf(stdout,"Can't find any valid MP3 frames in file %s\n",argv[mainloop]); - fflush(stdout); - } - } - 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; - numFiles++; - - if (tagInfo[mainloop].recalc > 0) { - 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(stdout, "%s is free format (not currently supported)\n",curfilename); - fflush(stdout); - 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(stdout,"%s is free format (not currently supported)\n",curfilename); - fflush(stdout); - 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) { - lSamp = lsamples; - rSamp = rsamples; - maxSamp = &maxsample; - maxGain = &maxgain; - minGain = &mingain; - procSamp = 0; - 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 - decodeSuccess = decodeMP3(&mp,curframe,bytesinframe,&nprocsamp); -#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 = !MP3_OK; - scanFrameGain();//curframe); - } - if (decodeSuccess == MP3_OK) { - if ((!maxAmpOnly)&&(tagInfo[mainloop].recalc & FULL_RECALC)) { - if (AnalyzeSamples(lsamples,rsamples,procSamp/nchan,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)) { - fileDivFiles[0]='\0'; - - if (argc-fileStart-1) /* if 1 file then don't show [x/n] */ - sprintf(fileDivFiles,"[%d/%d]",numFiles,argc-fileStart); - - fprintf(stderr," \r%s %2d%% of %ld bytes analyzed\r" - ,fileDivFiles,(int)(((double)(filepos-(inbuffer-(curframe+bytesinframe-buffer))) * 100.0) / gFilesize),gFilesize); - fflush(stderr); - } - } - } - } - } - - 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(stdout,"Not enough samples in %s to do analysis\n",argv[mainloop]); - fflush(stdout); - 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]); - WriteMP3GainAPETag(argv[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],intGainChange,intGainChange); - } else { - changeGainAndTag(argv[mainloop],intGainChange,intGainChange, tagInfo + mainloop, fileTags + mainloop); - } - } else if (!skipTag && tagInfo[mainloop].dirty) { - fprintf(stdout,"Writing tag information for %s\n",argv[mainloop]); - WriteMP3GainAPETag(argv[mainloop],tagInfo + mainloop, fileTags + mainloop, saveTime); - } - } - } - } - } - } - - ExitMP3(&mp); - fflush(stderr); - fflush(stdout); - if (inf) - fclose(inf); - inf = NULL; - } - } - } - - if ((numFiles > 0)&&(!applyTrack)) { - 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(stdout,"Not enough samples in mp3 files to do analysis\n"); - fflush(stdout); - } - 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]); - WriteMP3GainAPETag(argv[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],intGainChange,intGainChange); - } else { - changeGainAndTag(argv[mainloop],intGainChange,intGainChange, tagInfo + mainloop, fileTags + mainloop); - } - } else if (!skipTag && tagInfo[mainloop].dirty) { - fprintf(stdout,"Writing tag information for %s\n",argv[mainloop]); - WriteMP3GainAPETag(argv[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) { - WriteMP3GainAPETag(argv[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); - } - } - free(fileTags); - - fclose(stdout); - fclose(stderr); - if (gSuccess) - return 0; - else - return 1; -} - -#endif /* asWIN32DLL */ +/* + * vim:tw=76:ts=4:sts=4:sw=4:cindent:expandtab + */ + +/* + * mp3gain.c - analyzes mp3 files, determines the perceived volume, + * and adjusts the volume of the mp3 accordingly + * + * Copyright (C) 2001-2004 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 + * + * 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 +#include "apetag.h" +#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 +/* I tweaked the mpglib library just a bit to spit out the raw + * decoded double values, instead of rounding them to 16-bit integers. + * Hence the "mpglibDBL" directory + */ +#ifndef asWIN32DLL +#include "mpglibDBL/interface.h" +#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 +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 = 0; +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 checkTagOnly = 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 = 0; + unsigned long skipbuf; + + 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 = 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 = 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 = 1; + int done = 0; + static int startfreq; + static int startmpegver; + long tempmpegver; + double bitbase; + int i; + + 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 /* */ + } +}} 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, 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; + gainchange[0] = leftgainchange; + gainchange[1] = rightgainchange; + singlechannel = !(leftgainchange == rightgainchange); + if (saveTime) + fileTime(filename, storeTime); + gFilesize = getSizeOfFile(filename); + if (UsingTemp) { + fflush(stderr); + fflush(stdout); + outfilename = malloc(strlen(filename) + 12); + if (outfilename == NULL) { + fclose(inf); + return M3G_ERR_CANT_MAKE_TMP; + } + sprintf(outfilename, "%s.tmp.XXXXXX", filename); + inf = fopen(filename, "r+b"); + if (inf != NULL) { + int fd; + + fd = mkstemp(outfilename); + if ((fd == -1) || ((outf = fdopen(fd, "wb")) == NULL)) { + fclose(inf); + inf = NULL; + passError(MP3GAIN_UNSPECIFED_ERROR, 3, "\nCan't open ", + outfilename, " for temp writing\n"); + return M3G_ERR_CANT_MAKE_TMP; + } + } + } else { + inf = fopen(filename, "r+b"); + } + + if (inf == NULL) { + if (UsingTemp && (outf != NULL)) + fclose(outf); + passError(MP3GAIN_UNSPECIFED_ERROR, 3, "\nCan't open ", filename, + " for modifying\n"); + 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"); + 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) { + +#ifndef asWIN32DLL + fprintf(stderr, + " \r %2d%% of %ld bytes written\r", + (int) (((double) + (filepos - + (inbuffer - + (curframe + bytesinframe - + buffer))) * 100.0) / + gFilesize), gFilesize); + fflush(stderr); + +#else /* */ + /* report % back to calling app */ + ok = (int) (((double) + (filepos - + (inbuffer - + (curframe + bytesinframe - + buffer))) * 100.0) / + gFilesize); + ok = sendpercentdone((int) ok, gFilesize); + if (ok != 0) + return /* ok */ ; + ok = 1; /* allow us to continue processing file */ + +#endif /* */ + } + } + 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); + 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(inf); + inf = NULL; + if ((fflush(outf) == EOF) || (fsync(fileno(outf)) == -1) + || (fclose(outf) == EOF) || (outlength != inlength)) { + deleteFile(outfilename); + passError(MP3GAIN_UNSPECIFED_ERROR, 3, + "Not enough temp space on disk to modify ", + filename, + "\nEither free some space, or do not use \"temp file\" option\n"); + return M3G_ERR_NOT_ENOUGH_TMP_SPACE; + } else { + 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"); + 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 +void changeGainAndTag(char *filename, int leftgainchange, + int rightgainchange, struct MP3GainTagInfo *tag, + struct FileTagsStruct *fileTag) +{ + double dblGainChange; + int curMin; + int curMax; + if (leftgainchange != 0 || rightgainchange != 0) { + if (!changeGain(filename, 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 ... + WriteMP3GainAPETag(filename, tag, fileTag, saveTime); + } // if (!changeGain(filename ... + } // if (leftgainchange !=0 ... +} +static +int queryUserForClipping(char *argv_mainloop, int intGainChange) +{ + char 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(); + ch = toupper(ch); + } + if (ch == 'N') + return 0; + return 1; +} +static void showVersion(char *progname) +{ + fprintf(stderr, "%s version %s\n", progname, MP3GAIN_VERSION); +} 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); + exit(0); +} static void errUsage(char *progname) +{ + showVersion(progname); + fprintf(stderr, "copyright(c) 2001-2004 by Glen Sawyer\n"); + fprintf(stderr, + "uses mpglib, which can be found at http://www.mpg123.de\n"); + 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-2004 by Glen Sawyer\n"); + fprintf(stderr, + "uses mpglib, which can be found at http://www.mpg123.de\n"); + 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 to mp3 without doing any analysis\n", + SWITCH_CHAR); + fprintf(stderr, + "\t%cl 0 - apply gain i to channel 0 (left channel) of mp3\n", + SWITCH_CHAR); + fprintf(stderr, + "\t without doing any analysis (ONLY works for STEREO mp3s,\n"); + fprintf(stderr, "\t not Joint Stereo mp3s)\n"); + fprintf(stderr, + "\t%cl 1 - apply gain i to channel 1 (right channel) of mp3\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\n"); + 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 mp3\n", + SWITCH_CHAR); + fprintf(stderr, + "\t%cf - Force mp3gain to 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"); + 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%cu - undo changes made by mp3gain (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); + fprintf(stderr, + "\t (use \"%c? wrap\" switch for a complete explanation)\n", + SWITCH_CHAR); + 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); +} 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); +} + +#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 /* */ + MPSTR mp; + 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; + 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; + char analysisError = 0; + int fileStart; + int numFiles; + int databaseFormat = 0; + int i; + int *fileok; + int goAhead; + int directGain = 0; + int directSingleChannelGain = 0; + int directGainVal = 0; + int mp3GainMod = 0; + double dBGainMod = 0; + char fileDivFiles[21]; + 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; + 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 'r': + case 'R': + forceRecalculateTag = !0; + break; + default: + errUsage(argv[0]); + } + break; + case 't': + 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; + 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); + 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 */ + 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 = 0; + 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; + if ((!skipTag) && (!deleteTag)) { + ReadMP3GainAPETag(curfilename, &(tagInfo[mainloop]), + &(fileTags[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) { /* 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) { /* 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++) { + memset(&mp, 0, sizeof(mp)); + + // 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], + 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]); + } + } + } + } + + 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], 0, directGainVal); + } else { + changeGainAndTag(argv[mainloop], 0, directGainVal, + tagInfo + mainloop, + fileTags + mainloop); + } + } + + else { /* do left channel */ + if (skipTag) { + changeGain(argv[mainloop], directGainVal, 0); + } else { + changeGainAndTag(argv[mainloop], 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], directGainVal, directGainVal); + } else { + changeGainAndTag(argv[mainloop], directGainVal, + directGainVal, tagInfo + mainloop, + fileTags + mainloop); + } + if ((!QuietMode) && (gSuccess == 1)) + fprintf(stderr, "\ndone\n"); + } + + else if (deleteTag) { + RemoveMP3GainAPETag(argv[mainloop], saveTime); + } + + else { + if (!databaseFormat) + fprintf(stdout, "%s\n", argv[mainloop]); + if (tagInfo[mainloop].recalc > 0) { + gFilesize = getSizeOfFile(argv[mainloop]); + inf = fopen(argv[mainloop], "rb"); + } + if ((inf == NULL) && (tagInfo[mainloop].recalc > 0)) { + fprintf(stdout, "Can't open %s for reading\n", + argv[mainloop]); + fflush(stdout); + } + + else { + InitMP3(&mp); + if (tagInfo[mainloop].recalc == 0) { + maxsample = tagInfo[mainloop].trackPeak * 32768.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; + } + BadLayer = 0; + LayerSet = Reckless; + maxgain = 0; + mingain = 255; + inbuffer = 0; + filepos = 0; + bitidx = 0; + ok = fillBuffer(0); + } + if (ok) { + if (tagInfo[mainloop].recalc > 0) { + wrdpntr = buffer; + ok = skipID3v2(); + ok = frameSearch(!0); + } + if (!ok) { + if (!BadLayer) { + fprintf(stdout, + "Can't find any valid MP3 frames in file %s\n", + argv[mainloop]); + fflush(stdout); + } + } + + 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; + numFiles++; + if (tagInfo[mainloop].recalc > 0) { + 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(stdout, + "%s is free format (not currently supported)\n", + curfilename); + fflush(stdout); + 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(stdout, + "%s is free format (not currently supported)\n", + curfilename); + fflush(stdout); + 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) { + lSamp = lsamples; + rSamp = rsamples; + maxSamp = &maxsample; + maxGain = &maxgain; + minGain = &mingain; + procSamp = 0; + 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 /* */ + decodeSuccess = + decodeMP3(&mp, + curframe, + bytesinframe, + &nprocsamp); + +#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 = !MP3_OK; + scanFrameGain(); //curframe); + } + if (decodeSuccess == MP3_OK) { + if ((!maxAmpOnly) + && (tagInfo[mainloop]. + recalc & FULL_RECALC)) + { + if (AnalyzeSamples + (lsamples, rsamples, + procSamp / nchan, + 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)) { + fileDivFiles[0] = '\0'; + if (argc - fileStart - 1) /* if 1 file then don't show [x/n] */ + sprintf(fileDivFiles, + "[%d/%d]", + numFiles, + argc - fileStart); + fprintf(stderr, + " \r%s %2d%% of %ld bytes analyzed\r", + fileDivFiles, + (int) (((double) + (filepos - + (inbuffer - + (curframe + + bytesinframe + - buffer))) + * 100.0) / + gFilesize), + gFilesize); + fflush(stderr); + } + } + }}} + 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(stdout, + "Not enough samples in %s to do analysis\n", + argv[mainloop]); + fflush(stdout); + 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]); + WriteMP3GainAPETag(argv[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], + intGainChange, + intGainChange); + } else { + changeGainAndTag(argv + [mainloop], + intGainChange, + intGainChange, + tagInfo + + mainloop, + fileTags + + mainloop); + } + } else if (!skipTag + && tagInfo[mainloop]. + dirty) { + fprintf(stdout, + "Writing tag information for %s\n", + argv[mainloop]); + WriteMP3GainAPETag(argv[mainloop], + tagInfo + + mainloop, + fileTags + + mainloop, + saveTime); + } + } + } + } + } + } + ExitMP3(&mp); + fflush(stderr); + fflush(stdout); + if (inf) + fclose(inf); + inf = NULL; + } + } + } + if ((numFiles > 0) && (!applyTrack)) { + 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(stdout, + "Not enough samples in mp3 files to do analysis\n"); + fflush(stdout); + } + + 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]); + WriteMP3GainAPETag(argv[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], + intGainChange, + intGainChange); + } else { + changeGainAndTag(argv[mainloop], + intGainChange, + intGainChange, + tagInfo + mainloop, + fileTags + mainloop); + } + } else if (!skipTag && tagInfo[mainloop].dirty) { + fprintf(stdout, + "Writing tag information for %s\n", + argv[mainloop]); + WriteMP3GainAPETag(argv[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) { + WriteMP3GainAPETag(argv[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); + } + } + free(fileTags); + fclose(stdout); + fclose(stderr); + if (gSuccess) + return 0; + + else + return 1; +} + + +#endif /* asWIN32DLL */