/**************************************************************************
 * Copyright (C) 2008 Cocha                                               *
 * http://sourceforge.jp/projects/ecodecotool/                            *
 *                                                                        *
 *  This Program is free software; you can redistribute it and/or modify  *
 *  it under the terms of the GNU General Public License as published by  *
 *  the Free Software Foundation; either version 2, or (at your option)   *
 *  any later version.                                                    *
 *                                                                        *
 *  This Program is distributed in the hope that it will be useful,       *
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the          *
 *  GNU General Public License for more details.                          *
 *                                                                        *
 *  You should have received a copy of the GNU General Public License     *
 *  along with GNU Make; see the file COPYING.  If not, write to          *
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. *
 *                                                                        *
 **************************************************************************/

#include "EcoDecoTooL.h"


CConvertToMp3Lame::CConvertToMp3Lame()
{
   m_pEncodeLameInterface = NULL;
   ::ZeroMemory(&m_awOutputFileName, sizeof(m_awOutputFileName));
}

CConvertToMp3Lame::~CConvertToMp3Lame()
{
   SAFE_RELEASE(m_pEncodeLameInterface);
}

void CConvertToMp3Lame::Release(void)
{
   
   SAFE_RELEASE(m_pEncodeLameInterface);
}

void CConvertToMp3Lame::GetFormat(WCHAR *pwInputFormat, WCHAR *pwOutputFormat)
{
   HRESULT hr;
   WAVEFORMATEX inputFormat, outputFormat;

   hr = GetDirectShowFormat(&inputFormat, &outputFormat);

   if(FAILED(hr))
   {
      ::lstrcpy(pwInputFormat, L"-");
      ::lstrcpy(pwOutputFormat, L"-");
   }
   else
   {
      ::wsprintf(pwInputFormat, L"%dHz %2dbit %dch", inputFormat.nSamplesPerSec, inputFormat.wBitsPerSample, inputFormat.nChannels);
      ::wsprintf(pwOutputFormat, L"%dHz %2dbit %dch", outputFormat.nSamplesPerSec, outputFormat.wBitsPerSample, outputFormat.nChannels);
   }
}

HRESULT CConvertToMp3Lame::GetReplayGain(double *pdPeak, double *pdGain)
{
   if(m_pEncodeLameInterface == NULL)
      return E_FAIL;

   m_pEncodeLameInterface->GetReplayGain(pdPeak, pdGain);

   return S_OK;
}

HRESULT CConvertToMp3Lame::WriteReplayGain(WCHAR *pwFileName, int nGainMode, double dAlbumPeak, double dAlbumGain, double dTrackPeak, double dTrackGain)
{  

   HANDLE hFile = NULL;

   BYTE buffer[2226];
   int nReadSize, nRestSize;
   DWORD dwNumberOfBytesRead, dwNumberOfBytesWrite;

   
   hFile = ::CreateFile(pwFileName, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, (DWORD)0, NULL);
   if(hFile == NULL || hFile == (HANDLE)0xffffffff) return E_FAIL;

   
   nRestSize = (int)::GetFileSize(hFile, NULL);

   
   
   
   while(0 < nRestSize)
   {
      if(2226 < nRestSize)
         nReadSize = 2226;
      else
         nReadSize = nRestSize;

      ::SetFilePointer(hFile, nRestSize - nReadSize, NULL, FILE_BEGIN);
      ::ReadFile(hFile, buffer, nReadSize, &dwNumberOfBytesRead, NULL);
      if(dwNumberOfBytesRead != nReadSize)
         goto error;

      ::SetFilePointer(hFile, nRestSize - nReadSize + 2226, NULL, FILE_BEGIN);
      ::WriteFile(hFile, buffer, nReadSize, &dwNumberOfBytesWrite, NULL);
      if(dwNumberOfBytesWrite != nReadSize)
         goto error;

      nRestSize -= nReadSize;
   }


   
   char szText[32];
   DWORD dwWritten, dwBufferSize;
   BYTE id3Header[10], gainHeader[11], peakHeader[11];

   
   ::SetFilePointer(hFile, 0, NULL, FILE_BEGIN);

   
   
   
   id3Header[0]=0x49; id3Header[1]=0x44; id3Header[2]=0x33; 
   id3Header[3]=0x03; id3Header[4]=0x00;                    
   id3Header[5]=0x80;                                       

   
   id3Header[6]=0x00; id3Header[7]=0x00; id3Header[8]=0x11; id3Header[9]=0x28; 
   ::WriteFile(hFile, id3Header, 10, &dwWritten, NULL);

   
   gainHeader[0]=0x54; gainHeader[1]=0x58; gainHeader[2]=0x58; gainHeader[3]=0x58; 
   gainHeader[4]=0x00; gainHeader[5]=0x00; gainHeader[6]=0x00; gainHeader[7]=0x26; 
   gainHeader[8]=0x00; gainHeader[9]=0x00;                                         
   gainHeader[10]=0x03;                                                            

   peakHeader[0]=0x54; peakHeader[1]=0x58; peakHeader[2]=0x58; peakHeader[3]=0x58; 
   peakHeader[4]=0x00; peakHeader[5]=0x00; peakHeader[6]=0x00; peakHeader[7]=0x22; 
   peakHeader[8]=0x00; peakHeader[9]=0x00;                                         
   peakHeader[10]=0x03;                                                            

   if(nGainMode == GAIN_ALBUM_GAIN)
   {
      
      ::WriteFile(hFile, gainHeader, 11, &dwWritten, NULL);
       
      ::lstrcpyA(szText, "replaygain_album_gain");
      ::WriteFile(hFile, szText, ::lstrlenA(szText), &dwWritten, NULL);

      ::WriteFile(hFile, "\0", 1, &dwWritten, NULL);

      sprintf_s(szText, 32, "%+.8f", dAlbumGain);
      szText[11] = ' '; szText[12] = 'd'; szText[13] = 'B'; szText[14] = '\0';
      ::WriteFile(hFile, szText, ::lstrlenA(szText), &dwWritten, NULL);

      ::WriteFile(hFile, "\0", 1, &dwWritten, NULL);
       
      
      ::WriteFile(hFile, peakHeader, 11, &dwWritten, NULL);
       
      ::lstrcpyA(szText, "replaygain_album_peak");
      ::WriteFile(hFile, szText, ::lstrlenA(szText), &dwWritten, NULL);
       
      ::WriteFile(hFile, "\0", 1, &dwWritten, NULL);

      sprintf_s(szText, 32, "%.8f", dAlbumPeak);
      szText[10] = '\0';
      ::WriteFile(hFile, szText, ::lstrlenA(szText), &dwWritten, NULL);
       
      ::WriteFile(hFile, "\0", 1, &dwWritten, NULL);
   }

   
   ::WriteFile(hFile, gainHeader, 11, &dwWritten, NULL);

   ::lstrcpyA(szText, "replaygain_track_gain");
   ::WriteFile(hFile, szText, ::lstrlenA(szText), &dwWritten, NULL);

   ::WriteFile(hFile, "\0", 1, &dwWritten, NULL);

   sprintf_s(szText, 32, "%+.8f", dTrackGain);
   szText[11] = ' '; szText[12] = 'd'; szText[13] = 'B'; szText[14] = '\0';
   ::WriteFile(hFile, szText, ::lstrlenA(szText), &dwWritten, NULL);

   ::WriteFile(hFile, "\0", 1, &dwWritten, NULL);

   
   ::WriteFile(hFile, peakHeader, 11, &dwWritten, NULL);

   ::lstrcpyA(szText, "replaygain_track_peak");
   ::WriteFile(hFile, szText, ::lstrlenA(szText), &dwWritten, NULL);

   ::WriteFile(hFile, "\0", 1, &dwWritten, NULL);

   sprintf_s(szText, 32, "%.8f", dTrackPeak);
   szText[10] = '\0';
   ::WriteFile(hFile, szText, ::lstrlenA(szText), &dwWritten, NULL);

   ::WriteFile(hFile, "\0", 1, &dwWritten, NULL);

   
   

   
   if(nGainMode == GAIN_ALBUM_GAIN)
      dwBufferSize = 2032;
   else
      dwBufferSize = 2124;

   ::ZeroMemory(buffer, dwBufferSize);
   ::WriteFile(hFile, buffer, dwBufferSize, &dwWritten, NULL);

   ::CloseHandle(hFile);
   return S_OK;

error:

   if(hFile != NULL)
      ::CloseHandle(hFile);

   return E_FAIL;
}

HRESULT CConvertToMp3Lame::WriteAlbumGain(WCHAR *pwInputFileName, WCHAR *pwOutputFolder, double dAlbumPeak, double dAlbumGain)
{
   HRESULT hr;
   HANDLE hFile = NULL;
   char szText[32];
   DWORD dwWritten;
   WCHAR awOutputFileName[MAX_PATH];

   
   hr = GetOutputFileName(awOutputFileName, pwInputFileName, pwOutputFolder, L".mp3", false);
   if(FAILED(hr)) return E_FAIL;

   if(::PathFileExists(awOutputFileName) == FALSE)
      return E_FAIL;

   
   hFile = ::CreateFile(awOutputFileName, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, (DWORD)0, NULL);
   if(hFile == NULL) return E_FAIL;
   if(hFile == (HANDLE)0xffffffff) return E_FAIL;

   
   ::SetFilePointer(hFile, 43, NULL, FILE_BEGIN);
   sprintf_s(szText, 32, "%+.8f", dAlbumGain);
   szText[11] = ' '; szText[12] = 'd'; szText[13] = 'B'; szText[14] = '\0';
   ::WriteFile(hFile, szText, ::lstrlenA(szText), &dwWritten, NULL);

   
   ::SetFilePointer(hFile, 91, NULL, FILE_BEGIN);
   sprintf_s(szText, 32, "%.8f", dAlbumPeak);
   szText[10] = '\0';
   ::WriteFile(hFile, szText, ::lstrlenA(szText), &dwWritten, NULL);
       
   ::CloseHandle(hFile);
   return S_OK;
}

int CConvertToMp3Lame::ConvertStart(HWND hWnd, WCHAR *pwInputFileName, WCHAR *pwOutputFolder, CONVERTDATA2 *pConvertData, int nWait)
{  

   HRESULT hr;

   if(pConvertData->bNormalize == false)
   {
      WCHAR awOutputFileName[MAX_PATH];

      
      hr = GetOutputFileName(awOutputFileName, pwInputFileName, pwOutputFolder, L".mp3", true);
      if(FAILED(hr)) goto error;

      
      hr = Render(hWnd, pwInputFileName, awOutputFileName, pConvertData, nWait);
      if(FAILED(hr)) goto error;

      
      ::lstrcpy(m_awOutputFileName, awOutputFileName);
   }
   else
   {  
      
      hr = Render(hWnd, pwInputFileName, NULL, pConvertData, nWait);
      if(FAILED(hr))
         goto error;
   }

   return STATE_CONVERTING;

error:

   return STATE_END_ERROR;
}

int CConvertToMp3Lame::Converting(int *pnPercent)
{  
   *pnPercent = GetPercent();
   return STATE_CONVERTING;
}

int CConvertToMp3Lame::ConvertEnd(CONVERTDATA2 *pConvertData)
{  

   if(pConvertData->bNormalize == true || pConvertData->nGainMode == GAIN_TRACK_GAIN || pConvertData->nGainMode == GAIN_ALBUM_GAIN)
   {
      HRESULT hr;
      double dPeak, dGain;

      
      hr = GetReplayGain(&dPeak, &dGain);

      
      dGain = dGain + (pConvertData->dTargetGain - 89.0);

      
      ReleaseDirectShow();

      if(SUCCEEDED(hr))
      {
         if( (pConvertData->nGainMode == GAIN_ALBUM_NORMALIZE && pConvertData->bNormalize == true) ||
             (pConvertData->nGainMode == GAIN_ALBUM_GAIN && pConvertData->nPass == 1) )
         {
            if(pConvertData->dAlbumPeak < dPeak)
               pConvertData->dAlbumPeak = dPeak;

            if(dGain < pConvertData->dAlbumGain) 
               pConvertData->dAlbumGain = dGain;

            WriteReplayGain(m_awOutputFileName, pConvertData->nGainMode, 0.0, 0.0, dPeak, dGain);
         }
         else if(pConvertData->bNormalize == true) 
            pConvertData->dNormalize = ::pow(10.0, dGain / 20.0);
         else if(pConvertData->nGainMode == GAIN_TRACK_GAIN) 
            WriteReplayGain(m_awOutputFileName, pConvertData->nGainMode, 0.0, 0.0, dPeak, dGain);
      }
   }
   else
   {
      
      ReleaseDirectShow();
   }

   
   ::lstrcpy(m_awOutputFileName, L"");

   if(pConvertData->nGainMode == GAIN_TRACK_NORMALIZE && pConvertData->bNormalize == true)
      return STATE_START_SAMEFILE;

   return STATE_START_NEXTFILE;
}

void CConvertToMp3Lame::ConvertExit(void)
{  

   
   ReleaseDirectShow();

   
   if(::PathFileExists(m_awOutputFileName) != FALSE)
      ::DeleteFile(m_awOutputFileName);
}

HRESULT CConvertToMp3Lame::RenderGraph(IGraphBuilder *pGraphBuilder, WCHAR *pwInputFileName, WCHAR *pwOutputFileName, IBaseFilter *pConvertWav, IConvertWavInterface *pConvertWavInterface , CONVERTDATA2 *pConvertData)
{
   int i, j;
   HRESULT hr;
   WAVEFORMATEX inFormat, outFormat;

   IBaseFilter *pWriter = NULL;

   LAME_CONFIG lameConfig;

   
   ::ZeroMemory(&lameConfig, sizeof(lameConfig));
   if(pConvertData->nEncodeMode == 0)
   {  
      lameConfig.nMode = 90;
      lameConfig.nQuality = 0;
      lameConfig.abr.dwBitrate = pConvertData->nABRCBR;
      lameConfig.vbr.dwMaxBitrate = 320;
      lameConfig.vbr.dwMinBitrate = 32;
      lameConfig.vbr.nVBRQuality = 0;
   }
   else if(pConvertData->nEncodeMode == 1)
   {  
      lameConfig.nMode = 91;
      lameConfig.nQuality = 0;
      lameConfig.cbr.dwBitrate = pConvertData->nABRCBR / 1000;
   }
   else
   {  
      lameConfig.nMode = 92;
      lameConfig.nQuality = 0;
      lameConfig.vbr.nVBRQuality = pConvertData->nVBR;
      lameConfig.vbr.dwMaxBitrate = 320;
      lameConfig.vbr.dwMinBitrate = 32;
   }

   
   hr = RenderByFileExt(pGraphBuilder, pwInputFileName, pConvertWav, false); 

   if(FAILED(hr))
   {  
      hr = RenderByAllFilter(pGraphBuilder, pwInputFileName, pConvertWav, false); 
   }

   if(FAILED(hr)) goto error;

   
   if(pConvertData->bNormalize == false)
      pWriter = AddWriteFilter(pGraphBuilder, CLSID_EncodeLame, pwOutputFileName);
   else
      pWriter = AddFilter(pGraphBuilder, CLSID_EncodeLame);

   
   pWriter->QueryInterface(IID_IEncodeLameInterface, (void **)&m_pEncodeLameInterface);
   if(m_pEncodeLameInterface == NULL) goto error;

   
   pConvertWavInterface->GetInFormat(&inFormat);

   
   hr = m_pEncodeLameInterface->SetOutFormat(&inFormat, &lameConfig);

   if(hr == S_OK)
   {
      ::CopyMemory(&outFormat, &inFormat, sizeof(outFormat));
   }
   else
   {
     
      if(inFormat.wBitsPerSample != 16)
      {
         ::CopyMemory(&outFormat, &inFormat, sizeof(outFormat));
         outFormat.wFormatTag = WAVE_FORMAT_PCM;
         outFormat.wBitsPerSample = 16;
         outFormat.nBlockAlign = outFormat.nChannels * (outFormat.wBitsPerSample / 8);
         outFormat.nAvgBytesPerSec = outFormat.nBlockAlign * outFormat.nSamplesPerSec;
         hr = m_pEncodeLameInterface->SetOutFormat(&outFormat, &lameConfig);
      }

      
      if(hr != S_OK)
      {
         ::CopyMemory(&outFormat, &inFormat, sizeof(outFormat));

         if(inFormat.nChannels == 2)
            outFormat.nChannels = 1;
         else
            outFormat.nChannels = 2;

         outFormat.wFormatTag = WAVE_FORMAT_PCM;
         outFormat.wBitsPerSample = 16;
         outFormat.nBlockAlign = outFormat.nChannels * (outFormat.wBitsPerSample / 8);
         outFormat.nAvgBytesPerSec = outFormat.nBlockAlign * outFormat.nSamplesPerSec;
         hr = m_pEncodeLameInterface->SetOutFormat(&outFormat, &lameConfig);
      }
   }

   if(hr != S_OK)
   {  

      outFormat.wFormatTag = WAVE_FORMAT_PCM;
      outFormat.cbSize = 0;
      outFormat.wBitsPerSample = 16;
      outFormat.nChannels = inFormat.nChannels;
      outFormat.nSamplesPerSec = inFormat.nSamplesPerSec;
      outFormat.nBlockAlign = outFormat.nChannels * (outFormat.wBitsPerSample / 8);
      outFormat.nAvgBytesPerSec = outFormat.nBlockAlign * outFormat.nSamplesPerSec;

      DWORD anSamplingRate[] = {8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000};

      for(i=0;i<2;i++)
      {
         if(i==0)
            outFormat.nChannels = inFormat.nChannels;
         else
         {
            if(inFormat.nChannels == 2)
               outFormat.nChannels = 1;
            else
               outFormat.nChannels = 2;
         }

         for(j=0;j<=8;j++)
         {  

            if(anSamplingRate[j] <= inFormat.nSamplesPerSec)
               continue;

            outFormat.nSamplesPerSec = anSamplingRate[j];
            outFormat.nBlockAlign = outFormat.nChannels * (outFormat.wBitsPerSample / 8);
            outFormat.nAvgBytesPerSec = outFormat.nBlockAlign * outFormat.nSamplesPerSec;

            hr = m_pEncodeLameInterface->SetOutFormat(&outFormat, &lameConfig);
            if(hr == S_OK)
               break;
         }

         if(hr == S_OK)
            break;

         for(j=8;j>=0;j--)
         {  

            if(anSamplingRate[j] >= inFormat.nSamplesPerSec)
               continue;

            outFormat.nSamplesPerSec = anSamplingRate[j];
            outFormat.nBlockAlign = outFormat.nChannels * (outFormat.wBitsPerSample / 8);
            outFormat.nAvgBytesPerSec = outFormat.nBlockAlign * outFormat.nSamplesPerSec;

            hr = m_pEncodeLameInterface->SetOutFormat(&outFormat, &lameConfig);
            if(hr == S_OK)
               break;
         }

         if(hr == S_OK)
            break;
      }
   }

   if(hr != S_OK)
      goto error;

   
   pConvertWavInterface->SetOutFormat(&outFormat);

   
   if(pConvertData->bNormalize == true)
      m_pEncodeLameInterface->CheckVolumeMode(true);

   if(pConvertData->nGainMode == GAIN_TRACK_GAIN)
   {  
      m_pEncodeLameInterface->SetReplaygain(true, false);
   }
   else if(pConvertData->nGainMode == GAIN_ALBUM_GAIN)
   {  
      m_pEncodeLameInterface->SetReplaygain(true, true);
   }

   
   hr = ConnectDirect(pGraphBuilder, pConvertWav, pWriter, NULL);
   if(FAILED(hr)) goto error;

   SAFE_RELEASE(pWriter);
   return S_OK;

error:
   SAFE_RELEASE(pWriter);
   return E_FAIL;
}

