text: /**********************************************************************
  
    Audacity: A Digital Audio Editor
  
    ImportQT.cpp
  
    Joshua Haberman
  
    Handles importing MPEG-4 audio files, including AAC and Apple Lossless,
    on Mac OS X.  Could be modified to support QuickTime importing on
    Windows, too.
  
  **********************************************************************/
  
  #include "../Audacity.h"
  #include "ImportPlugin.h"
  
  #define DESC _("QuickTime files")
  
  static const wxChar *exts[] =
  {
     wxT("mov"),
     wxT("aac"),
     wxT("m4a")
  };
  
  #ifndef USE_QUICKTIME
  
  void GetQTImportPlugin(ImportPluginList *importPluginList,
                         UnusableImportPluginList *unusableImportPluginList)
  {
     UnusableImportPlugin* qtIsUnsupported =
        new UnusableImportPlugin(DESC, wxArrayString(WXSIZEOF(exts), exts));
  
     unusableImportPluginList->Append(qtIsUnsupported);
  }
  
  #else /* USE_QUICKTIME */
  
  #include <wx/msgdlg.h>
  
  // There's a name collision between our Track and QuickTime's...workaround it
  #define Track XTrack
  
  #if defined(__WXMAC__)
  // These get used when building under OSX
     #include <Carbon/Carbon.h>
     #include <QuickTime/QuickTime.h>
  
     #include <wx/mac/private.h>
  #else
  // These get used when building under Windows
     #include <ConditionalMacros.h>
     #include <Movies.h>
     #include <QuickTimeComponents.h>
     #include <Sound.h>
     #include <Folders.h>
     #include <ToolUtils.h>
     #include <Gestalt.h>
     #include <Navigation.h>
  #endif
  
  // There's a name collision between our Track and QuickTime's...workaround it
  #undef Track
  
  #include "../Internat.h"
  #include "../Tags.h"
  #include "../WaveTrack.h"
  #include "ImportQT.h"
  
  #define kQTAudioPropertyID_MaxAudioSampleSize   'mssz'
  
  class QTImportPlugin : public ImportPlugin
  {
   public:
     QTImportPlugin()
     :  ImportPlugin(wxArrayString(WXSIZEOF(exts), exts)),
        mInitialized(false)
     {
        OSErr err = noErr;
  
  #if defined(__WXMSW__)
        err = ::InitializeQTML(0);
  #endif
  
        if (err == noErr) {
           err = ::EnterMovies();
           if (err == noErr) {
              mInitialized = true;
           }
           else {
  #if defined(__WXMSW__)
              TerminateQTML();
  #endif
           }
        }
     }
  
     ~QTImportPlugin()
     {
        if (mInitialized) {
           ExitMovies();
  #if defined(__WXMSW__)
           TerminateQTML();
  #endif
        }
  
        mInitialized = false;
     }
  
     wxString GetPluginStringID() { return wxT("quicktime"); }
  
     wxString GetPluginFormatDescription();
     ImportFileHandle *Open(wxString Filename);
  
   private:
     bool mInitialized;
  };
  
  class QTImportFileHandle : public ImportFileHandle
  {
   public:
     QTImportFileHandle(const wxString & name, Movie movie)
     :  ImportFileHandle(name)
     {
        mMovie = movie;
     }
  
     virtual ~QTImportFileHandle()
     {
        if (mMovie) {
           DisposeMovie(mMovie);
           mMovie = NULL;
        }
     }
  
     wxString GetFileDescription();
     int GetFileUncompressedBytes();
  
     wxInt32 GetStreamCount()
     {
        return 1;
     }
  
     wxArrayString *GetStreamInfo()
     {
        return NULL;
     }
  
     void SetStreamUsage(wxInt32 StreamID, bool Use)
     {
     }
  
     int Import(TrackFactory *trackFactory,
                Track ***outTracks,
                int *outNumTracks,
                Tags *tags);
  
   private:
     void AddMetadata(Tags *tags);
  
     Movie mMovie;
  };
  
  void GetQTImportPlugin(ImportPluginList *importPluginList,
                         UnusableImportPluginList *unusableImportPluginList)
  {
     importPluginList->Append(new QTImportPlugin);
  }
  
  wxString QTImportPlugin::GetPluginFormatDescription()
  {
     return DESC;
  }
  
  ImportFileHandle *QTImportPlugin::Open(wxString Filename)
  {
     OSErr err;
     FSRef inRef;
     Movie theMovie = NULL;
     Handle dataRef = NULL;
     OSType dataRefType = 0;
     short resID = 0;
  
  #if defined(__WXMAC__)
     err = wxMacPathToFSRef(Filename, &inRef);
  #else
     // LLL:  This will not work for pathnames with Unicode characters...find
     //       another method.
     err = FSPathMakeRef((UInt8 *)OSFILENAME(Filename), &inRef, NULL);
  #endif
  
     if (err != noErr) {
        return NULL;
     }
  
     err = QTNewDataReferenceFromFSRef(&inRef, 0, &dataRef, &dataRefType);
     if (err != noErr) {
        return NULL;
     }
  
     // instantiate the movie
     err = NewMovieFromDataRef(&theMovie,
                               newMovieActive | newMovieDontAskUnresolvedDataRefs,
                               &resID,
                               dataRef,
                               dataRefType);
     DisposeHandle(dataRef);
     if (err != noErr) {
        return NULL;
     }
  
     return new QTImportFileHandle(Filename, theMovie);
  }
  
  
  wxString QTImportFileHandle::GetFileDescription()
  {
     return DESC;
  }
  
  int QTImportFileHandle::GetFileUncompressedBytes()
  {
     return 0;
  }
  
  int QTImportFileHandle::Import(TrackFactory *trackFactory,
                                 Track ***outTracks,
                                 int *outNumTracks,
                                 Tags *tags)
  {
     OSErr err = noErr;
     MovieAudioExtractionRef maer = NULL;
     int updateResult = eProgressSuccess;
     sampleCount totSamples = (sampleCount) GetMovieDuration(mMovie);
     sampleCount numSamples = 0;
     Boolean discrete = true;
     UInt32 quality = kQTAudioRenderQuality_Max;
     AudioStreamBasicDescription desc;
     UInt32 maxSampleSize;
     UInt32 numchan;
     UInt32 bufsize;
  
     CreateProgress();
  
     err = MovieAudioExtractionBegin(mMovie, 0, &maer);
     if (err != noErr) {
        wxMessageBox(_("Unable to start QuickTime extraction"));
        goto done;
     }
  
     err = MovieAudioExtractionSetProperty(maer,
                                           kQTPropertyClass_MovieAudioExtraction_Audio,
                                           kQTMovieAudioExtractionAudioPropertyID_RenderQuality,
                                           sizeof(quality),
                                           &quality);
     if (err != noErr) {
        wxMessageBox(_("Unable to set QuickTime render quality"));
        goto done;
     }
  
     err = MovieAudioExtractionSetProperty(maer,
                                           kQTPropertyClass_MovieAudioExtraction_Movie,
                                           kQTMovieAudioExtractionMoviePropertyID_AllChannelsDiscrete,
                                           sizeof(discrete),
                                           &discrete);
     if (err != noErr) {
        wxMessageBox(_("Unable to set QuickTime discrete channels property"));
        goto done;
     }
  
     err = MovieAudioExtractionGetProperty(maer,
                                           kQTPropertyClass_MovieAudioExtraction_Audio,
                                           kQTAudioPropertyID_MaxAudioSampleSize,
                                           sizeof(maxSampleSize),
                                           &maxSampleSize,
                                           NULL);
     if (err != noErr) {
        wxMessageBox(_("Unable to get QuickTime sample size property"));
        goto done;
     }
  
     err = MovieAudioExtractionGetProperty(maer,
                                           kQTPropertyClass_MovieAudioExtraction_Audio,
                                           kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription,
                                           sizeof(desc),
                                           &desc,
                                           NULL);
     if (err != noErr) {
        wxMessageBox(_("Unable to retrieve stream description"));
        goto done;
     }
  
     numchan = desc.mChannelsPerFrame;
     bufsize = 5 * desc.mSampleRate;
  
     // determine sample format
     sampleFormat format;
     switch (maxSampleSize)
     {
        case 16:
           format = int16Sample;
           break;
  
        case 24:
           format = int24Sample;
           break;
  
        default:
           format = floatSample;
           break;
     }
  
     AudioBufferList *abl = (AudioBufferList *)
        calloc(1, offsetof(AudioBufferList, mBuffers) + (sizeof(AudioBuffer) * numchan));
     abl->mNumberBuffers = numchan;
  
     WaveTrack **channels = new WaveTrack *[numchan];
  
     int c;
     for (c = 0; c < numchan; c++) {
        abl->mBuffers[c].mNumberChannels = 1;
        abl->mBuffers[c].mDataByteSize = sizeof(float) * bufsize;
        abl->mBuffers[c].mData = malloc(abl->mBuffers[c].mDataByteSize);
  
        channels[c] = trackFactory->NewWaveTrack(format);
        channels[c]->SetRate(desc.mSampleRate);
  
        if (numchan == 2) {
           if (c == 0) {
              channels[c]->SetChannel(Track::LeftChannel);
              channels[c]->SetLinked(true);
           }
           else if (c == 1) {
              channels[c]->SetChannel(Track::RightChannel);
           }
        }
     }
  
     do {
        UInt32 flags = 0;
        UInt32 numFrames = bufsize;
  
        err = MovieAudioExtractionFillBuffer(maer,
                                             &numFrames,
                                             abl,
                                             &flags);
        if (err != noErr) {
           wxMessageBox(_("Unable to get fill buffer"));
           break;
        }
  
        for (c = 0; c < numchan; c++) {
           channels[c]->Append((char *) abl->mBuffers[c].mData, floatSample, numFrames);
        }
  
        numSamples += numFrames;
  
        updateResult = mProgress->Update((wxULongLong_t)numSamples,
                                         (wxULongLong_t)totSamples);
  
        if (numFrames == 0 || flags & kQTMovieAudioExtractionComplete) {
           break;
        }
     } while (updateResult == eProgressSuccess);
  
     bool res = (updateResult == eProgressSuccess && err == noErr);
  
     if (res) {
        for (c = 0; c < numchan; c++) {
           channels[c]->Flush();
        }
  
        *outTracks = (Track **) channels;
        *outNumTracks = numchan;
     }
     else {
        for (c = 0; c < numchan; c++) {
           delete channels[c];
        }
  
        delete [] channels;
     }
  
     for (c = 0; c < numchan; c++) {
        free(abl->mBuffers[c].mData);
     }
     free(abl);
  
     //
     // Extract any metadata
     //
     if (res) {
        AddMetadata(tags);
     }
  
  done:
  
     if (maer) {
        MovieAudioExtractionEnd(maer);
     }
  
     return (res ? eProgressSuccess : eProgressFailed);
  }
  
  static const struct
  {
     OSType key;
     wxChar *name;
  }
  names[] =
  {
     {  kQTMetaDataCommonKeyAuthor,         wxT("Author")           },
     {  kQTMetaDataCommonKeyComment,        TAG_COMMENTS            },
     {  kQTMetaDataCommonKeyCopyright,      TAG_COPYRIGHT           },
     {  kQTMetaDataCommonKeyDirector,       wxT("Director")         },
     {  kQTMetaDataCommonKeyDisplayName,    wxT("Full Name")        },
     {  kQTMetaDataCommonKeyInformation,    wxT("Information")      },
     {  kQTMetaDataCommonKeyKeywords,       wxT("Keywords")         },
     {  kQTMetaDataCommonKeyProducer,       wxT("Producer")         },
     {  kQTMetaDataCommonKeyAlbum,          TAG_ALBUM               },
     {  kQTMetaDataCommonKeyArtist,         TAG_ARTIST              },
     {  kQTMetaDataCommonKeyChapterName,    wxT("Chapter")          },
     {  kQTMetaDataCommonKeyComposer,       wxT("Composer")         },
     {  kQTMetaDataCommonKeyDescription,    wxT("Description")      },
     {  kQTMetaDataCommonKeyGenre,          TAG_GENRE               },
     {  kQTMetaDataCommonKeyOriginalFormat, wxT("Original Format")  },
     {  kQTMetaDataCommonKeyOriginalSource, wxT("Original Source")  },
     {  kQTMetaDataCommonKeyPerformers,     wxT("Performers")       },
     {  kQTMetaDataCommonKeySoftware,       TAG_SOFTWARE            },
     {  kQTMetaDataCommonKeyWriter,         wxT("Writer")           },
  };
  
  void QTImportFileHandle::AddMetadata(Tags *tags)
  {
     QTMetaDataRef metaDataRef = NULL;
     OSErr err;
  
     err = QTCopyMovieMetaData(mMovie, &metaDataRef);
     if (err != noErr) {
        return;
     }
  
     for (int i = 0; i < WXSIZEOF(names); i++) {
        QTMetaDataItem item = kQTMetaDataItemUninitialized;
        OSType key = names[i].key;
  
        err = QTMetaDataGetNextItem(metaDataRef,
                                    kQTMetaDataStorageFormatWildcard,
                                    kQTMetaDataItemUninitialized,
                                    kQTMetaDataKeyFormatCommon,
                                    (const UInt8 *) &names[i].key,
                                    sizeof(names[i].key),
                                    &item);
        if (err != noErr) {
           continue;
        }
  
        if (item == kQTMetaDataItemUninitialized) {
           continue;
        }
  
        QTPropertyValuePtr outValPtr = nil;
        QTPropertyValueType outPropType;
        ByteCount outPropValueSize;
        ByteCount outPropValueSizeUsed = 0;
        UInt32 outPropFlags;
        UInt32 dataType;
  
        // Get data type
        err =  QTMetaDataGetItemProperty(metaDataRef,
                                         item,
                                         kPropertyClass_MetaDataItem,
                                         kQTMetaDataItemPropertyID_DataType,
                                         sizeof(dataType),
                                         &dataType,
                                         &outPropValueSizeUsed);
        if (err != noErr) {
           continue;
        }
  
        // Get the data length
        err = QTMetaDataGetItemPropertyInfo(metaDataRef,
                                            item,
                                            kPropertyClass_MetaDataItem,
                                            kQTMetaDataItemPropertyID_Value,
                                            &outPropType,
                                            &outPropValueSize,
                                            &outPropFlags );
        if (err != noErr) {
           continue;
        }
  
        // Alloc memory for it
        outValPtr = malloc(outPropValueSize);
  
        // Retrieve the data
        err =  QTMetaDataGetItemProperty(metaDataRef,
                                         item,
                                         kPropertyClass_MetaDataItem,
                                         kQTMetaDataItemPropertyID_Value,
                                         outPropValueSize,
                                         outValPtr,
                                         &outPropValueSizeUsed);
        if (err != noErr) {
           free(outValPtr);
           continue;
        }
  
        wxString v = wxT("");
  
        switch (dataType)
        {
           case kQTMetaDataTypeUTF8:
              v = wxString((char *)outValPtr, wxConvUTF8);
           break;
           case kQTMetaDataTypeUTF16BE:
           {
              wxMBConvUTF16BE conv;
              v = wxString((char *)outValPtr, conv);
           }
           break;
        }
  
        if (!v.IsEmpty()) {
           tags->SetTag(names[i].name, v);
        }
  
        free(outValPtr);
     }
  
     // we are done so release our metadata object
     QTMetaDataRelease(metaDataRef);
  
     return;
  }
  
  #endif
  