/* * MatroskaImport.cpp * * MatroskaImport.cpp - QuickTime importer interface for opening a Matroska file. * * * Copyright (c) 2006 David Conrad * * This program 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; * version 2.1 of the License. * * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include #include #include #include "PerianResourceIDs.h" #include "MatroskaImport.h" #include "SubImport.h" #include "Codecprintf.h" extern "C" ComponentResult create_placeholder_track(Movie movie, Track *placeholderTrack, TimeValue duration, Handle dataRef, OSType dataRefType); using namespace libmatroska; #pragma mark- Component Dispatch // Setup required for ComponentDispatchHelper.c since I don't feel like writing a dispatcher // for a C++ class akin to Apple's ACCodec.cpp classes for Core Audio codecs. #define MOVIEIMPORT_BASENAME() MatroskaImport #define MOVIEIMPORT_GLOBALS() MatroskaImport *storage #define CALLCOMPONENT_BASENAME() MOVIEIMPORT_BASENAME() #define CALLCOMPONENT_GLOBALS() MOVIEIMPORT_GLOBALS() #define COMPONENT_DISPATCH_FILE "MatroskaImportDispatch.h" #define COMPONENT_UPP_SELECT_ROOT() MovieImport extern "C" { #if __MACH__ #include #include #include #else #include #include #include #endif } #pragma mark- // Component Open Request - Required ComponentResult MatroskaImportOpen(MatroskaImport *store, ComponentInstance self) { // Allocate memory for our globals, and inform the component manager that we've done so store = new MatroskaImport(self); if (store == NULL) return memFullErr; SetComponentInstanceStorage(self, (Handle)store); return noErr; } // Component Close Request - Required ComponentResult MatroskaImportClose(MatroskaImport *store, ComponentInstance self) { // Make sure to dealocate our storage delete store; return noErr; } // Component Version Request - Required ComponentResult MatroskaImportVersion(MatroskaImport *store) { return kMatroskaImportVersion; } #pragma mark- // MovieImportFile ComponentResult MatroskaImportFile(MatroskaImport *store, const FSSpec *theFile, Movie theMovie, Track targetTrack, Track *usedTrack, TimeValue atTime, TimeValue *durationAdded, long inFlags, long *outFlags) { OSErr err = noErr; AliasHandle alias = NULL; FSRef theFileFSRef; *outFlags = 0; err = FSpMakeFSRef(theFile, &theFileFSRef); if (err) goto bail; err = FSNewAliasMinimal(&theFileFSRef, &alias); if (err) goto bail; err = store->ImportDataRef((Handle)alias, rAliasType, theMovie, targetTrack, usedTrack, atTime, durationAdded, inFlags, outFlags); bail: if (alias) DisposeHandle((Handle)alias); return err; } // MovieImportDataRef ComponentResult MatroskaImportDataRef(MatroskaImport *store, Handle dataRef, OSType dataRefType, Movie theMovie, Track targetTrack, Track *usedTrack, TimeValue atTime, TimeValue *durationAdded, long inFlags, long *outFlags) { ComponentResult res = store->ImportDataRef(dataRef, dataRefType, theMovie, targetTrack, usedTrack, atTime, durationAdded, inFlags, outFlags); LoadExternalSubtitlesFromFileDataRef(dataRef, dataRefType, theMovie); return res; } // MovieImportValidate ComponentResult MatroskaImportValidate(MatroskaImport *store, const FSSpec *theFile, Handle theData, Boolean *valid) { OSErr err = noErr; AliasHandle alias = NULL; FSRef theFileFSRef; *valid = false; err = FSpMakeFSRef(theFile, &theFileFSRef); if (err) goto bail; err = FSNewAliasMinimal(&theFileFSRef, &alias); if (err) goto bail; err = store->ValidateDataRef((Handle)alias, rAliasType, (UInt8 *)valid); bail: if (alias) DisposeHandle((Handle)alias); return err; } // MovieImportGetMIMETypeList ComponentResult MatroskaImportGetMIMETypeList(MatroskaImport *store, QTAtomContainer *retMimeInfo) { // Note that GetComponentResource is only available in QuickTime 3.0 or later. // However, it's safe to call it here because GetMIMETypeList is only defined in QuickTime 3.0 or later. return GetComponentResource((Component)store->Component(), 'mime', 510, (Handle *)retMimeInfo); } // MovieImportValidateDataRef ComponentResult MatroskaImportValidateDataRef(MatroskaImport *store, Handle dataRef, OSType dataRefType, UInt8 *valid) { return store->ValidateDataRef(dataRef, dataRefType, valid); } ComponentResult MatroskaImportIdle(MatroskaImport *store, long inFlags, long *outFlags) { return store->Idle(inFlags, outFlags); } ComponentResult MatroskaImportSetIdleManager(MatroskaImport *store, IdleManager im) { return store->SetIdleManager(im); } ComponentResult MatroskaImportGetMaxLoadedTime(MatroskaImport *store, TimeValue *time) { return store->GetMaxLoadedTime(time); } ComponentResult MatroskaImportGetLoadState(MatroskaImport *store, long *importerLoadState) { return store->GetLoadState(importerLoadState); } MatroskaImport::MatroskaImport(ComponentInstance self) { this->self = self; dataRef = NULL; dataRefType = 0; theMovie = NULL; chapterTrack = NULL; baseTrack = NULL; timecodeScale = 1000000; movieDuration = 0; idleManager = NULL; loadState = kMovieLoadStateLoading; lastIdleTime = 0; idlesSinceLastAdd = 0; ioHandler = NULL; aStream = NULL; el_l0 = NULL; el_l1 = NULL; segmentOffset = 0; seenInfo = false; seenTracks = false; seenChapters = false; } MatroskaImport::~MatroskaImport() { if (el_l1) delete el_l1; if (el_l0) delete el_l0; if (aStream) delete aStream; if (ioHandler) delete ioHandler; } ComponentResult MatroskaImport::ImportDataRef(Handle dataRef, OSType dataRefType, Movie theMovie, Track targetTrack, Track *usedTrack, TimeValue atTime, TimeValue *durationAdded, long inFlags, long *outFlags) { ComponentResult err = noErr; this->dataRef = dataRef; this->dataRefType = dataRefType; this->theMovie = theMovie; *outFlags = 0; if (inFlags & movieImportMustUseTrack) return paramErr; loadState = kMovieLoadStateLoading; try { if (!OpenFile()) // invalid file, validate should catch this return invalidMovie; err = SetupMovie(); if (err) return err; // SetupMovie() couldn't find any level one elements, so nothing to import if (el_l1 == NULL) return noErr; if (inFlags & movieImportWithIdle) { create_placeholder_track(theMovie, &baseTrack, movieDuration, dataRef, dataRefType); // try to import one cluster so we have at least something if (EbmlId(*el_l1) == KaxCluster::ClassInfos.GlobalId) { int upperLevel = 0; EbmlElement *dummyElt = NULL; el_l1->Read(*aStream, KaxCluster::ClassInfos.Context, upperLevel, dummyElt, true); KaxCluster & cluster = *static_cast(el_l1); ImportCluster(cluster, true); } if (!NextLevel1Element()) { *outFlags |= movieImportResultComplete; for (int i = 0; i < tracks.size(); i++) tracks[i].FinishTrack(); } else { *outFlags |= movieImportResultNeedIdles; } return noErr; } do { if (EbmlId(*el_l1) == KaxCluster::ClassInfos.GlobalId) { int upperLevel = 0; EbmlElement *dummyElt = NULL; el_l1->Read(*aStream, KaxCluster::ClassInfos.Context, upperLevel, dummyElt, true); KaxCluster & cluster = *static_cast(el_l1); ImportCluster(cluster, false); } } while (NextLevel1Element()); // insert the a/v tracks' samples for (int i = 0; i < tracks.size(); i++) tracks[i].FinishTrack(); } catch (CRTError &err) { return err.getError(); } loadState = kMovieLoadStateComplete; return noErr; } ComponentResult MatroskaImport::ValidateDataRef(Handle dataRef, OSType dataRefType, UInt8 *valid) { this->dataRef = dataRef; this->dataRefType = dataRefType; *valid = 0; try { if (OpenFile()) *valid = 128; } catch (CRTError &err) { return err.getError(); } return noErr; } ComponentResult MatroskaImport::Idle(long inFlags, long *outFlags) { TimeValue currentIdleTime = GetMovieTime(theMovie, NULL); TimeValue maxLoadedTime; GetMaxLoadedTime(&maxLoadedTime); TimeScale movieTimeScale = GetMovieTimeScale(theMovie); idlesSinceLastAdd++; if (EbmlId(*el_l1) == KaxCluster::ClassInfos.GlobalId) { int upperLevel = 0; EbmlElement *dummyElt = NULL; el_l1->Read(*aStream, KaxCluster::ClassInfos.Context, upperLevel, dummyElt, true); KaxCluster & cluster = *static_cast(el_l1); // Only add samples every 20 idles when paused // or if we're playing and only have 5 seconds away from the end of // what's already been added if (currentIdleTime == lastIdleTime && idlesSinceLastAdd > 20 || maxLoadedTime < currentIdleTime + 5*movieTimeScale) { idlesSinceLastAdd = 0; ImportCluster(cluster, true); } else ImportCluster(cluster, false); } if (!NextLevel1Element()) { if (baseTrack) DisposeMovieTrack(baseTrack); *outFlags |= movieImportResultComplete; loadState = kMovieLoadStateComplete; for (int i = 0; i < tracks.size(); i++) tracks[i].FinishTrack(); } lastIdleTime = currentIdleTime; return noErr; } ComponentResult MatroskaImport::SetIdleManager(IdleManager im) { idleManager = im; return noErr; } ComponentResult MatroskaImport::GetMaxLoadedTime(TimeValue *time) { *time = 0; for (int i = 0; i < tracks.size(); i++) { if (tracks[i].maxLoadedTime > *time) *time = tracks[i].maxLoadedTime; } return noErr; } ComponentResult MatroskaImport::GetLoadState(long *importerLoadState) { *importerLoadState = loadState; return noErr; }