// DIE's signature file
// Author: Jason Hood <jadoxa@yahoo.com.au>
/*
   Ref: http://mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm
        http://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header
*/

init("audio","MP3 Audio File");

includeScript("duration");

var MP3={
    aVersion:["2.5","2","1"],
    aLayer:  ["III","II","I"],
    aRatev1: [32,40,48, 56, 64, 80, 96,112,128,160,192,224,256,320,     // Layer III
              32,48,56, 64, 80, 96,112,128,160,192,224,256,320,384,     // Layer II
              32,64,96,128,160,192,224,256,288,320,352,384,416,448],    // Layer I
    aRatev2: [ 8,16,24,32,40,48, 56, 64, 80, 96,112,128,144,160,        // Layers III & II
              32,48,56,64,80,96,112,128,144,160,176,192,224,256],       // Layer I
    aFreq:   [11025,12000, 8000,                                        // v2.5
              22050,24000,16000,                                        // v2
              44100,48000,32000],                                       // v1
    aCh:     ["Stereo","Joint stereo","Dual mono","Mono"],

    header: function(nOffset)
    {
        // AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM
        var nHeader=Binary.readBEDword(nOffset);
        if((nHeader&0xFFE00000)!=~~0xFFE00000)  // AAAAAAAAAAA
        {
            return null;
        }
        var nVer=(nHeader>>>19)&3;              // BB
        if(nVer==1)                             // reserved
        {
            return null;
        }
        if(nVer>1)
        {
            --nVer;
        }
        var nLayer=(nHeader>>>17)&3;            // CC
        if(nLayer==0)                           // reserved
        {
            return null;
        }
        --nLayer;
        var nBitrateIndex=(nHeader>>>12)&15;    // EEEE
        if(nBitrateIndex==0||nBitrateIndex==15) // free/bad
        {
            return null;
        }
        --nBitrateIndex;
        var nSamplingIndex=(nHeader>>>10)&3;    // FF
        if(nSamplingIndex==3)                   // reserved
        {
            return null;
        }
        var nPadding=(nHeader>>>9)&1;           // G
        var nChannelMode=(nHeader>>>6)&3;       // II

        var nBitrate=nVer==2?this.aRatev1[nLayer*14+nBitrateIndex]
                            :this.aRatev2[(nLayer==2)*14+nBitrateIndex];
        var nFreq=this.aFreq[nVer*3+nSamplingIndex];
        var nTime;
        if(nLayer==2) // Layer I
        {
            nTime=384/nFreq;
        }
        else if(nLayer==1||nVer==2) // Layer II or v1,Layer III
        {
            nTime=1152/nFreq;
        }
        else // v2/2.5,Layer III
        {
            nTime=576/nFreq;
        }
        var bVBR=1;
        var nVBR=Binary.findString(nOffset,40,"Xing");
        if(nVBR==-1)
        {
            nVBR=Binary.findString(nOffset,40,"Info");
            bVBR=0;
        }
        if(nVBR!=-1&&(Binary.readByte(nVBR+7)&1))
        {
            nTime*=Binary.readBEDword(nVBR+8);
            nOffset=bVBR?-1:-2;
        }
        else if(Binary.compare("'VBRI'",nOffset+32))
        {
            nTime*=Binary.readBEDword(nVBR+46);
            nOffset=-1;
        }
        else
        {
            if(nLayer==2) // Layer I
            {
                nOffset+=Math.floor(12000*nBitrate/nFreq+nPadding)*4;
            }
            else
            {
                // 125 = 1000/8 = kilobits to bytes
                nOffset+=Math.floor(125*nBitrate*nTime+nPadding);
            }
        }
        return [this.aVersion[nVer],
                this.aLayer[nLayer],
                nBitrate,
                nFreq,
                this.aCh[nChannelMode],
                nTime,
                nOffset];
    }
}

function detect(bShowType,bShowVersion,bShowOptions)
{
    if(Binary.getSize()>=128)
    {
        if(Binary.compare("'TAG'",Binary.getSize()-128))
        {
            sOptions="ID3v1";
        }
        var nOffset=0,bID3v2=0;
        if(Binary.compare("'ID3'"))
        {
            nOffset=(Binary.readByte(6)<<21)
                   |(Binary.readByte(7)<<14)
                   |(Binary.readByte(8)<<7)
                   |(Binary.readByte(9));
            nOffset+=10;
            if(sOptions=="")
            {
                sOptions="ID3v2";
            }
            else
            {
                sOptions+="&2";
            }
            bID3v2=1;
        }
        else if(Binary.compare("'WAVEfmt '........55",8))
        {
            nOffset=Binary.findString(50,50,"data")+8;
        }
        var aResult1,aResult=null;
        aResult1=MP3.header(nOffset);
        if(aResult1==null)
        {
            if(bID3v2)
            {
                // There may be some junk between the ID3 tag & the data (Win7
                // sample music files - C:\Users\Public\Music\Sample Music).
                // Since the ID3 tag is a fair indicator of an MP3, do a search
                // for v1,Layer III header.
                nOffset=Binary.findSignature(nOffset,768,"FFFB");
                aResult1=MP3.header(nOffset);
            }
            if(aResult1==null)
            {
                return "";
            }
        }
        if(aResult1[6]>0)
        {
            aResult=MP3.header(aResult1[6]);
            if(aResult==null)
            {
                return "";
            }
        }
        bDetected=1;
        // Assume version, layer & channels don't change.
        sVersion="v"+aResult1[0]+",Layer "+aResult1[1];

        if(bShowOptions)
        {
            var nFrames=1;
            var nRateSum=aResult1[2];
            var nFreqSum=aResult1[3];
            var nSeconds=aResult1[5];
            var bVBR=0,bVF=0;
            while(aResult!=null)
            {
                nFrames++;
                nRateSum+=aResult[2];
                nFreqSum+=aResult[3];
                nSeconds+=aResult[5];
                if(aResult[2]!=aResult1[2])
                {
                    bVBR=1;
                }
                if(aResult[3]!=aResult1[3])
                {
                    bVF=1;
                }
                aResult=MP3.header(aResult[6]);
            }
            sOptions=sOptions.append(Math.round(nRateSum/nFrames)+"kbps");
            if(bVBR)
            {
                sOptions+="(avg)";
            }
            sOptions=sOptions.append(Math.round(nFreqSum/nFrames)+"Hz");
            if(bVF)
            {
                sOptions+="(avg)";
            }
            sOptions=sOptions.append(aResult1[4],duration(nSeconds));
        }
    }

    return result(bShowType,bShowVersion,bShowOptions);
}
