/**************************************************************************
 * 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"

#define CONTAINER_RAW     100
#define CONTAINER_RAW_WAV 101
#define CONTAINER_RAW_AAC 102
#define CONTAINER_RAW_OGG 103

#define CONTAINER_RAW_MP2 1010
#define CONTAINER_RAW_MP3 1011
#define CONTAINER_RAW_MP4 1012
#define CONTAINER_RAW_AC3 1013



static const GUID CLSID_AC3Parser         = { 0x280a3020, 0x86cf, 0x11d1, { 0xab, 0xe6, 0x00, 0xa0, 0xc9, 0x05, 0xf3, 0x75 } };
static const GUID CLSID_WAVEParser        = { 0xD51BD5A1, 0x7548, 0x11CF, { 0xA5, 0x20, 0x00, 0x80, 0xC7, 0x7E, 0xF5, 0x8A } };
static const GUID CLSID_FlvSplitter       = { 0x47E792CF, 0x0BBE, 0x4F7A, { 0x85, 0x9C, 0x19, 0x4B, 0x07, 0x68, 0x65, 0x0A } };
static const GUID CLSID_MatroskaSplitter  = { 0x149D2E01, 0xC32E, 0x4939, { 0x80, 0xF6, 0xC0, 0x7B, 0x81, 0x01, 0x5A, 0x7A } };
static const GUID CLSID_MP4Splitter       = { 0x61F47056, 0xE400, 0x43D3, { 0xAF, 0x1E, 0xAB, 0x7D, 0xFF, 0xD4, 0xC4, 0xAD } };
static const GUID CLSID_MpaSplitter       = { 0x0E9D4BF7, 0xCBCB, 0x46C7, { 0xBD, 0x80, 0x4E, 0xF2, 0x23, 0xA3, 0xDC, 0x2B } };
static const GUID CLSID_OggSplitter       = { 0x9FF48807, 0xE133, 0x40AA, { 0x82, 0x6F, 0x9B, 0x29, 0x59, 0xE5, 0x23, 0x2D } };
static const GUID CLSID_RealMediaSplitter = { 0xE21BE468, 0x5C18, 0x43EB, { 0xB0, 0xCC, 0xDB, 0x93, 0xA8, 0x47, 0xD7, 0x69 } };



static const GUID MEDIASUBTYPE_AAC    =      { 0x000000ff, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
static const GUID MEDIASUBTYPE_MP3    =      { 0x00000055, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
static const GUID MEDIASUBTYPE_Vorbis =      { 0xcddca2d5, 0x6d75, 0x4f98, { 0x84, 0x0e, 0x73, 0x7b, 0xed, 0xd5, 0xc6, 0x3b } };
static const GUID MEDIASUBTYPE_AVI_AC3 =     { 0x00002000, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };

static const int g_splitterArraySize = 11;
static const CLSID g_splitterCLSID[] = 
{
   CLSID_MpaSplitter,
   CLSID_AviSplitter,
   CLSID_FlvSplitter,
   CLSID_MatroskaSplitter,
   CLSID_MP4Splitter,
   CLSID_OggSplitter,
   CLSID_WAVEParser,
   CLSID_MPEG1Splitter,
   CLSID_MPEG2Demultiplexer,
   CLSID_AC3Parser,
   CLSID_RealMediaSplitter,
};

CConvertRaw::CConvertRaw()
{
   m_nRawFileType = 0;
   m_pGraphBuilder = NULL;
   m_pMediaEventEx = NULL;
   m_pMediaControl = NULL;

   ::ZeroMemory(&m_awOutputFileName, sizeof(m_awOutputFileName));
}

CConvertRaw::~CConvertRaw()
{

}

void CConvertRaw::Release(void)
{
   if(m_pMediaControl != NULL)
      m_pMediaControl->Stop();

   if(m_pMediaEventEx != NULL)
      m_pMediaEventEx->SetNotifyWindow(NULL, 0, 0);

   
   SAFE_RELEASE(m_pMediaEventEx);
   SAFE_RELEASE(m_pMediaControl);
   SAFE_RELEASE(m_pGraphBuilder);
}

void CConvertRaw::SetWait(int nWait)
{

}

int CConvertRaw::GraphNotify(void)
{
   if(m_pMediaEventEx == NULL)
      return E_FAIL;

   
   long lEventCode;
   m_pMediaEventEx->WaitForCompletion(100, &lEventCode);

   if(lEventCode == EC_COMPLETE)
      return EC_COMPLETE;

   if(lEventCode == EC_ERRORABORT)
      return EC_ERRORABORT;

   if(lEventCode == EC_USERABORT)
      return EC_USERABORT;

   return S_OK;
}

void CConvertRaw::GetFormat(WCHAR *pwInputFormat, WCHAR *pwOutputFormat)
{
   ::lstrcpy(pwInputFormat, L"-");

   if(::lstrlen(::PathFindExtension(m_awOutputFileName)) == 0)
      ::lstrcpy(pwInputFormat, L"-");
   else
      ::lstrcpy(pwOutputFormat, ::PathFindExtension(m_awOutputFileName)+1);
}

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

   HRESULT hr;
   WCHAR awOutputFileName[MAX_PATH];

   
   ::lstrcpy(awOutputFileName, pwOutputFolder);
   ::lstrcat(awOutputFileName, L"\\");
   ::lstrcat(awOutputFileName, ::PathFindFileName(pwInputFileName));
   ::PathRemoveExtension(awOutputFileName);

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

   
   ::lstrcpy(m_awOutputFileName, awOutputFileName);

   return STATE_CONVERTING;

error:
   Release();
   return STATE_END_ERROR;
}

int CConvertRaw::Converting(int *pnPercent)
{  
   *pnPercent = -1;
   return STATE_CONVERTING;
}

int CConvertRaw::ConvertEnd(CONVERTDATA2 *pConvertData)
{  

   
   Release();

   
   ::lstrcpy(m_awOutputFileName, L"");

   return STATE_START_NEXTFILE;
}

void CConvertRaw::ConvertExit(void)
{  

   
   Release();

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

HRESULT CConvertRaw::Render(HWND hWnd, WCHAR *pwInputFileName, WCHAR *pwOutputFileName)
{
   int i;
   HRESULT hr;

   IBaseFilter *pSource   = NULL;
   IBaseFilter *pSplitter = NULL;
   IBaseFilter *pWriter   = NULL;
   IPin *pInPin = NULL;
   IPin *pOutPin = NULL;
   AM_MEDIA_TYPE *pAm = NULL;
   IEnumMediaTypes *pEnumMediaTypes = NULL;
   IFileSinkFilter *pFileSinkFilter = NULL;
   IEcoDecoWriterInterface *pEcoDecoWriterInterface = NULL;

   
   ::CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&m_pGraphBuilder);
   if(m_pGraphBuilder == NULL) goto error;

   
   pSource = AddSourceFilter(m_pGraphBuilder, CLSID_AsyncReader, pwInputFileName);
   if(pSource == NULL) goto error;

   
   pWriter = AddFilter(m_pGraphBuilder, CLSID_EcoDecoWriter);
   if(pWriter == NULL) goto error;

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

   
   for(i=0;i<g_splitterArraySize;i++)
   {
      pSplitter = AddFilter(m_pGraphBuilder, g_splitterCLSID[i]);
      if(pSplitter == NULL)
         continue;

      
      hr = ConnectDirect(m_pGraphBuilder, pSource, pSplitter, NULL);
      if(FAILED(hr))
      {
         m_pGraphBuilder->RemoveFilter(pSplitter);
         SAFE_RELEASE(pSplitter);
         continue;
      }

      break;
   }

   if(i == g_splitterArraySize)
   {  
      goto error;
   }

   
   pOutPin = GetPin(pSplitter, PINDIR_OUTPUT, MEDIATYPE_Audio);
   if(pOutPin == NULL)
      goto error;

   pOutPin->EnumMediaTypes(&pEnumMediaTypes);
   if(pEnumMediaTypes == NULL)
      goto error;

   
   pEnumMediaTypes->Reset();

   while(true)
   {
      hr = pEnumMediaTypes->Next(1, &pAm, NULL);
      if(hr != S_OK)
         goto error;

      if(pAm->subtype == MEDIASUBTYPE_AAC)
      {
         pEcoDecoWriterInterface->SetOutputMode(CONTAINER_RAW_AAC);
         m_nRawFileType = CONTAINER_RAW_AAC;
         ::lstrcat(pwOutputFileName, L".aac");
         break;
      }
      else if(pAm->subtype == MEDIASUBTYPE_MP3)
      {
         pEcoDecoWriterInterface->SetOutputMode(CONTAINER_RAW);
         m_nRawFileType = CONTAINER_RAW_MP3;
         ::lstrcat(pwOutputFileName, L".mp3");
         break;
      }
      else if(pAm->subtype == MEDIASUBTYPE_MPEG1AudioPayload)
      {
         pEcoDecoWriterInterface->SetOutputMode(CONTAINER_RAW);
         m_nRawFileType = CONTAINER_RAW_MP2;
         ::lstrcat(pwOutputFileName, L".mp2");
         break;
      }
      else if(pAm->subtype == MEDIASUBTYPE_MPEG2_AUDIO)
      {
         pEcoDecoWriterInterface->SetOutputMode(CONTAINER_RAW);
         m_nRawFileType = CONTAINER_RAW_MP2;
         ::lstrcat(pwOutputFileName, L".mp2");
         break;
      }
      else if(pAm->subtype == MEDIASUBTYPE_Vorbis)
      {
         pEcoDecoWriterInterface->SetOutputMode(CONTAINER_RAW_OGG);
         m_nRawFileType = CONTAINER_RAW_OGG;
         ::lstrcat(pwOutputFileName, L".ogg");
         break;
      }
      else if(pAm->subtype == MEDIASUBTYPE_AVI_AC3)
      {
         pEcoDecoWriterInterface->SetOutputMode(CONTAINER_RAW);
         m_nRawFileType = CONTAINER_RAW_AC3;
         ::lstrcat(pwOutputFileName, L".ac3");
         break;
      }
      else if(pAm->subtype == MEDIASUBTYPE_DOLBY_AC3)
      {
         pEcoDecoWriterInterface->SetOutputMode(CONTAINER_RAW);
         m_nRawFileType = CONTAINER_RAW_AC3;
         ::lstrcat(pwOutputFileName, L".ac3");
         break;
      }

      SAFE_DELETEMEDIATYPE(pAm);
   }

   if(::lstrlen(::PathFindExtension(pwOutputFileName)) == 0)
      goto error;

   SAFE_DELETEMEDIATYPE(pAm);
   SAFE_RELEASE(pEnumMediaTypes);

   
   pInPin = GetPin(pWriter, PINDIR_INPUT, 0);
   if(pInPin == NULL) goto error;

   
   hr = m_pGraphBuilder->ConnectDirect(pOutPin, pInPin, NULL);
   if(FAILED(hr)) goto error;

   SAFE_RELEASE(pInPin);
   SAFE_RELEASE(pOutPin);

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

   hr = pFileSinkFilter->SetFileName(pwOutputFileName, NULL);
   if(FAILED(hr)) goto error;

   
   m_pGraphBuilder->QueryInterface(IID_IMediaEventEx, (void **)&m_pMediaEventEx);
   if(m_pMediaEventEx == NULL) goto error;

   if(hWnd != NULL)
   {
      hr = m_pMediaEventEx->SetNotifyWindow((OAHWND)hWnd, WM_GRAPHNOTIFY, 0);
      if(hr != NO_ERROR) goto error;
   }

   
   m_pGraphBuilder->QueryInterface(IID_IMediaControl, (void **)&m_pMediaControl);
   if(m_pMediaControl == NULL) goto error;

   
   hr = m_pMediaControl->Run();
   if(FAILED(hr)) goto error;

   
   SAFE_RELEASE(pInPin);
   SAFE_RELEASE(pOutPin);
   SAFE_DELETEMEDIATYPE(pAm);
   SAFE_RELEASE(pEnumMediaTypes);
   SAFE_RELEASE(pFileSinkFilter);
   SAFE_RELEASE(pEcoDecoWriterInterface);
   SAFE_RELEASE(pWriter);
   SAFE_RELEASE(pSplitter);
   SAFE_RELEASE(pSource);

   return S_OK;

error:

   
   SAFE_RELEASE(pInPin);
   SAFE_RELEASE(pOutPin);
   SAFE_DELETEMEDIATYPE(pAm);
   SAFE_RELEASE(pEnumMediaTypes);
   SAFE_RELEASE(pFileSinkFilter);
   SAFE_RELEASE(pEcoDecoWriterInterface);
   SAFE_RELEASE(pWriter);
   SAFE_RELEASE(pSplitter);
   SAFE_RELEASE(pSource);

   return E_FAIL;
}













HRESULT CConvertRaw::ConnectDirect(IGraphBuilder *pGraphBuilder, IBaseFilter *pFilter1, IBaseFilter *pFilter2, AM_MEDIA_TYPE *pAmMediaType)
{
   CheckPointer(pGraphBuilder, E_POINTER);
   CheckPointer(pFilter1, E_POINTER);
   CheckPointer(pFilter2, E_POINTER);


   HRESULT hr;
   int nInputPinCount, nOutputPinCount;
   IPin *pInPin = NULL;
   IPin *pOutPin = NULL;

   
   nOutputPinCount = this->GetPinCount(pFilter1, PINDIR_OUTPUT);
   if(nOutputPinCount == 0) return E_FAIL;

   
   nInputPinCount  = this->GetPinCount(pFilter2, PINDIR_INPUT);
   if(nInputPinCount == 0) return E_FAIL;

   for(int i=0;i<nOutputPinCount;i++)
   {
      for(int j=0;j<nInputPinCount;j++)
      {
         pOutPin = this->GetPin(pFilter1, PINDIR_OUTPUT, i);
         if(pOutPin == NULL) return E_FAIL;

         pInPin = this->GetPin(pFilter2, PINDIR_INPUT, j);
         if(pInPin == NULL)
         {
            SAFE_RELEASE(pOutPin);
            return E_FAIL;
         }

         hr = pGraphBuilder->ConnectDirect(pOutPin, pInPin, pAmMediaType);

         SAFE_RELEASE(pInPin);
         SAFE_RELEASE(pOutPin);

         if (hr == S_OK)
           return S_OK;
      }
   }

   SAFE_RELEASE(pInPin);
   SAFE_RELEASE(pOutPin);

   return VFW_E_CANNOT_CONNECT;
}





IPin* CConvertRaw::GetPin(IBaseFilter *pFilter, PIN_DIRECTION pinDirection, int nPinNumber)
{
   CheckPointer(pFilter, NULL);

   int hr, count;
   IEnumPins *pEnumPins = NULL;
   IPin *pPin = NULL;

   count = 0;

   hr = pFilter->EnumPins(&pEnumPins);
   if (hr != NO_ERROR) return NULL;

   pEnumPins->Reset();

   while(pEnumPins->Next(1, &pPin, 0) == S_OK)
   {
      PIN_DIRECTION dir;
      pPin->QueryDirection(&dir);

      if (dir == pinDirection)
      {
         if(count == nPinNumber)
         {
            SAFE_RELEASE(pEnumPins);
            return pPin;
         }

         count++;
      }

      SAFE_RELEASE(pPin);
   }

   SAFE_RELEASE(pPin);
   SAFE_RELEASE(pEnumPins);

   return NULL;
}





int CConvertRaw::GetPinCount(IBaseFilter *pFilter, PIN_DIRECTION pinDirection)
{
   CheckPointer(pFilter, 0);

   HRESULT hr;
   int nPinCount;
   IEnumPins *pEnumPins = NULL;
   IPin *pPin = NULL;

   nPinCount = 0;

   hr = pFilter->EnumPins(&pEnumPins);
   if (hr != NO_ERROR) return 0;

   hr = pEnumPins->Reset();

   while(pEnumPins->Next(1, &pPin, 0) == S_OK)
   {
      PIN_DIRECTION dir;
      pPin->QueryDirection(&dir);

      if (dir == pinDirection)
         nPinCount++;

      SAFE_RELEASE(pPin);
   }

   SAFE_RELEASE(pPin);
   SAFE_RELEASE(pEnumPins);

   return nPinCount;
}





IPin* CConvertRaw::GetPin(IBaseFilter *pFilter, PIN_DIRECTION pinDirection, GUID majortype)
{
   CheckPointer(pFilter, NULL);

   int hr;
   ULONG cFetched;

   IPin *pPin = NULL;
   AM_MEDIA_TYPE *pAm = NULL;
   IEnumPins *pEnumPins = NULL;
   IEnumMediaTypes *pEnumMediaTypes = NULL;

   hr = pFilter->EnumPins(&pEnumPins);
   if (hr != NO_ERROR) return NULL;

   pEnumPins->Reset();

   while(pEnumPins->Next(1, &pPin, 0) == S_OK)
   {
      PIN_DIRECTION dir;
      pPin->QueryDirection(&dir);

      if (dir == pinDirection)
      {
         pPin->EnumMediaTypes(&pEnumMediaTypes);
         if(pEnumMediaTypes == NULL)
            goto error;

         pEnumMediaTypes->Reset();

         while(pEnumMediaTypes->Next(1, &pAm, &cFetched) == S_OK)
         {
            if(pAm->majortype == majortype)
            {
               SAFE_DELETEMEDIATYPE(pAm);
               SAFE_RELEASE(pEnumMediaTypes);
               SAFE_RELEASE(pEnumPins);

               return pPin;
            }

            SAFE_DELETEMEDIATYPE(pAm);
         }

         SAFE_RELEASE(pEnumMediaTypes);
      }
   }

error:

   SAFE_DELETEMEDIATYPE(pAm);
   SAFE_RELEASE(pEnumMediaTypes);
   SAFE_RELEASE(pPin);
   SAFE_RELEASE(pEnumPins);

   return NULL;
}





IBaseFilter* CConvertRaw::AddFilter(IGraphBuilder *pGraphBuilder, CLSID clsid)
{
   CheckPointer(pGraphBuilder, NULL);

   HRESULT hr;
   IBaseFilter *pBaseFilter = NULL;

   hr = ::CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&pBaseFilter);
   if(pBaseFilter == NULL) return NULL;

   hr = pGraphBuilder->AddFilter(pBaseFilter, NULL);
   if(hr != NO_ERROR)
   {
      SAFE_RELEASE(pBaseFilter);
      return NULL;
   }

   return pBaseFilter;
}





IBaseFilter* CConvertRaw::AddSourceFilter(IGraphBuilder *pGraphBuilder, CLSID clsid, WCHAR *wpFileName)
{
   CheckPointer(pGraphBuilder, NULL);

   HRESULT hr;
   IBaseFilter *pBaseFilter = NULL;
   IFileSourceFilter *pFileSourceFilter = NULL;

   hr = ::CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&pBaseFilter);
   if(pBaseFilter == NULL) return NULL;

   pBaseFilter->QueryInterface(IID_IFileSourceFilter, (void**)&pFileSourceFilter);
   if(pFileSourceFilter == NULL)
   {
      SAFE_RELEASE(pBaseFilter);
      return NULL;
   }

   hr = pGraphBuilder->AddFilter(pBaseFilter, NULL);
   if(hr != NO_ERROR)
   {
      SAFE_RELEASE(pBaseFilter);
      return NULL;
   }

   hr = pFileSourceFilter->Load(wpFileName, NULL);
   SAFE_RELEASE(pFileSourceFilter);
   if(FAILED(hr))
   {
      pGraphBuilder->RemoveFilter(pBaseFilter);
      SAFE_RELEASE(pBaseFilter);
      return NULL;
   }

   return pBaseFilter;
}

