Audacity 3.2.0
ImportQT.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 ImportQT.cpp
6
7 Joshua Haberman
8
9 On OS X, handles import of MPEG-4 audio including AAC and Apple
10 Lossless, import of audio from MPEG-4 video files, and import of
11 AIF(F)/AIFC files (AIF(F) might contain content libsndfile can't
12 handle). Could be modified to support QuickTime import on Windows.
13
14**********************************************************************/
15
16#include "../Audacity.h" // for USE_* macros
17
18#include "Import.h"
19#include "ImportPlugin.h"
20#include "../widgets/AudacityMessageBox.h"
21#include "../widgets/ProgressDialog.h"
22
23#define DESC XO("QuickTime files")
24
25static const auto exts = {
26 wxT("aif"),
27 wxT("aifc"),
28 wxT("aiff"),
29 wxT("mov"),
30 wxT("aac"),
31 wxT("m4a"),
32 wxT("mp4")
33};
34
35#if defined(__WXMAC__)
36#undef USE_QUICKTIME
37#endif
38
39#ifndef USE_QUICKTIME
40
41// Bug 2068: misleading error message about QuickTime
42// In 64 bit versions we cannot compile in (obsolete) QuickTime
43// So don't register the QuickTime extensions, so ensuring we never report
44// "This version of Audacity was not compiled with QuickTime files support"
45// When attempting to import MP4 files.
46/*
47static Importer::RegisteredUnusableImportPlugin registered{
48 std::make_unique<UnusableImportPlugin>(DESC,
49 FileExtensions( exts.begin(), exts.end() ) )
50};
51*/
52
53#else /* USE_QUICKTIME */
54
55// There's a name collision between our Track and QuickTime's...workaround it
56#define Track XTrack
57
58#if defined(__WXMAC__)
59// These get used when building under OSX
60 #include <Carbon/Carbon.h>
61 #include <QuickTime/QuickTime.h>
62
63 #include <wx/osx/core/private.h>
64#else
65// These get used when building under Windows
66 #include <ConditionalMacros.h>
67 #include <Movies.h>
68 #include <QuickTimeComponents.h>
69 #include <Sound.h>
70 #include <Folders.h>
71 #include <ToolUtils.h>
72 #include <Gestalt.h>
73 #include <Navigation.h>
74#endif
75
76// There's a name collision between our Track and QuickTime's...workaround it
77#undef Track
78
79#include "../Tags.h"
80#include "../WaveTrack.h"
81
82#define kQTAudioPropertyID_MaxAudioSampleSize 'mssz'
83
84class QTImportPlugin final : public ImportPlugin
85{
86 public:
87 QTImportPlugin()
89 mInitialized(false)
90 {
91 OSErr err = noErr;
92
93#if defined(__WXMSW__)
94 err = ::InitializeQTML(0);
95#endif
96
97 if (err == noErr) {
98 err = ::EnterMovies();
99 if (err == noErr) {
100 mInitialized = true;
101 }
102 else {
103#if defined(__WXMSW__)
104 TerminateQTML();
105#endif
106 }
107 }
108 }
109
110 ~QTImportPlugin()
111 {
112 if (mInitialized) {
113 ExitMovies();
114#if defined(__WXMSW__)
115 TerminateQTML();
116#endif
117 }
118
119 mInitialized = false;
120 }
121
122 wxString GetPluginStringID() override { return wxT("quicktime"); }
123
125 std::unique_ptr<ImportFileHandle> Open(
126 const wxString & Filename, AudacityProject*) override;
127
128 private:
129 bool mInitialized;
130};
131
132class QTImportFileHandle final : public ImportFileHandle
133{
134 public:
135 QTImportFileHandle(const wxString & name, Movie movie)
137 {
138 mMovie = movie;
139 }
140
141 virtual ~QTImportFileHandle()
142 {
143 if (mMovie) {
144 DisposeMovie(mMovie);
145 mMovie = NULL;
146 }
147 }
148
150 ByteCount GetFileUncompressedBytes() override;
151
152 wxInt32 GetStreamCount() override
153 {
154 return 1;
155 }
156
157 const TranslatableStrings &GetStreamInfo() override
158 {
159 static TranslatableStrings empty;
160 return empty;
161 }
162
163 void SetStreamUsage(wxInt32 StreamID, bool Use) override
164 {
165 }
166
167 ProgressResult Import(TrackFactory *trackFactory,
168 TrackHolders &outTracks,
169 Tags *tags) override;
170
171 private:
172 void AddMetadata(Tags *tags);
173
174 Movie mMovie;
175};
176
177TranslatableString QTImportPlugin::GetPluginFormatDescription()
178{
179 return DESC;
180}
181
182std::unique_ptr<ImportFileHandle> QTImportPlugin::Open(
183 const wxString & Filename, AudacityProject*)
184{
185 OSErr err;
186 FSRef inRef;
187 Movie theMovie = NULL;
188 Handle dataRef = NULL;
189 OSType dataRefType = 0;
190 short resID = 0;
191
192#if defined(__WXMAC__)
193 err = wxMacPathToFSRef(Filename, &inRef);
194#else
195 // LLL: This will not work for pathnames with Unicode characters...find
196 // another method.
197 err = FSPathMakeRef((UInt8 *)OSFILENAME(Filename), &inRef, NULL);
198#endif
199
200 if (err != noErr) {
201 return nullptr;
202 }
203
204 err = QTNewDataReferenceFromFSRef(&inRef, 0, &dataRef, &dataRefType);
205 if (err != noErr) {
206 return nullptr;
207 }
208
209 // instantiate the movie
210 err = NewMovieFromDataRef(&theMovie,
211 newMovieActive | newMovieDontAskUnresolvedDataRefs,
212 &resID,
213 dataRef,
214 dataRefType);
215 DisposeHandle(dataRef);
216 if (err != noErr) {
217 return nullptr;
218 }
219
220 return std::make_unique<QTImportFileHandle>(Filename, theMovie);
221}
222
224 std::make_unique< QTImportPlugin >()
225};
226
227
228TranslatableString QTImportFileHandle::GetFileDescription()
229{
230 return DESC;
231}
232
233auto QTImportFileHandle::GetFileUncompressedBytes() -> ByteCount
234{
235 return 0;
236}
237
238ProgressResult QTImportFileHandle::Import(TrackFactory *trackFactory,
239 TrackHolders &outTracks,
240 Tags *tags)
241{
242 outTracks.clear();
243
244 OSErr err = noErr;
245 MovieAudioExtractionRef maer = NULL;
246 auto updateResult = ProgressResult::Success;
247 auto totSamples =
248 (sampleCount) GetMovieDuration(mMovie); // convert from TimeValue
249 decltype(totSamples) numSamples = 0;
250 Boolean discrete = true;
251 UInt32 quality = kQTAudioRenderQuality_Max;
252 AudioStreamBasicDescription desc;
253 UInt32 maxSampleSize;
254 bool res = false;
255
256 auto cleanup = finally( [&] {
257 if (maer) {
258 MovieAudioExtractionEnd(maer);
259 }
260 } );
261
262 CreateProgress();
263
264 do
265 {
266 err = MovieAudioExtractionBegin(mMovie, 0, &maer);
267 if (err != noErr) {
268 AudacityMessageBox( XO("Unable to start QuickTime extraction") );
269 break;
270 }
271
272 err = MovieAudioExtractionSetProperty(maer,
273 kQTPropertyClass_MovieAudioExtraction_Audio,
274 kQTMovieAudioExtractionAudioPropertyID_RenderQuality,
275 sizeof(quality),
276 &quality);
277 if (err != noErr) {
278 AudacityMessageBox( XO("Unable to set QuickTime render quality") );
279 break;
280 }
281
282 err = MovieAudioExtractionSetProperty(maer,
283 kQTPropertyClass_MovieAudioExtraction_Movie,
284 kQTMovieAudioExtractionMoviePropertyID_AllChannelsDiscrete,
285 sizeof(discrete),
286 &discrete);
287 if (err != noErr) {
289"Unable to set QuickTime discrete channels property") );
290 break;
291 }
292
293 err = MovieAudioExtractionGetProperty(maer,
294 kQTPropertyClass_MovieAudioExtraction_Audio,
295 kQTAudioPropertyID_MaxAudioSampleSize,
296 sizeof(maxSampleSize),
297 &maxSampleSize,
298 NULL);
299 if (err != noErr) {
301"Unable to get QuickTime sample size property") );
302 break;
303 }
304
305 err = MovieAudioExtractionGetProperty(maer,
306 kQTPropertyClass_MovieAudioExtraction_Audio,
307 kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription,
308 sizeof(desc),
309 &desc,
310 NULL);
311 if (err != noErr) {
312 AudacityMessageBox( XO("Unable to retrieve stream description") );
313 break;
314 }
315
316 auto numchan = desc.mChannelsPerFrame;
317 const size_t bufsize = 5 * desc.mSampleRate;
318
319 // determine sample format
321 switch (maxSampleSize)
322 {
323 case 16:
325 break;
326
327 case 24:
329 break;
330
331 default:
333 break;
334 }
335
336 // Allocate an array of pointers, whose size is not known statically,
337 // and prefixed with the AudioBufferList structure.
339 static_cast< AudioBufferList * >(
340 calloc( 1, offsetof( AudioBufferList, mBuffers ) +
341 (sizeof(AudioBuffer) * numchan))) };
342 abl->mNumberBuffers = numchan;
343
344 NewChannelGroup channels{ numchan };
345
346 const auto size = sizeof(float) * bufsize;
347 ArraysOf<unsigned char> holders{ numchan, size };
348 for (size_t c = 0; c < numchan; c++) {
349 auto &buffer = abl->mBuffers[c];
350 auto &holder = holders[c];
351 auto &channel = channels[c];
352
353 buffer.mNumberChannels = 1;
354 buffer.mDataByteSize = size;
355
356 buffer.mData = holder.get();
357
358 channel = trackFactory->NewWaveTrack( format );
359 channel->SetRate( desc.mSampleRate );
360 }
361
362 do {
363 UInt32 flags = 0;
364 UInt32 numFrames = bufsize;
365
366 err = MovieAudioExtractionFillBuffer(maer,
367 &numFrames,
368 abl.get(),
369 &flags);
370 if (err != noErr) {
371 AudacityMessageBox( XO("Unable to get fill buffer") );
372 break;
373 }
374
375 for (size_t c = 0; c < numchan; c++) {
376 channels[c]->Append((char *) abl->mBuffers[c].mData, floatSample, numFrames);
377 }
378
379 numSamples += numFrames;
380
381 updateResult = mProgress->Update(
382 numSamples.as_long_long(),
383 totSamples.as_long_long() );
384
385 if (numFrames == 0 || flags & kQTMovieAudioExtractionComplete) {
386 break;
387 }
388 } while (updateResult == ProgressResult::Success);
389
390 res = (updateResult == ProgressResult::Success && err == noErr);
391
392 if (res) {
393 for (auto &channel: channels)
394 channel->Flush();
395 if (!channels.empty())
396 outTracks.push_back(std::move(channels));
397 }
398
399 //
400 // Extract any metadata
401 //
402 if (res) {
403 AddMetadata(tags);
404 }
405 } while (false);
406
407// done:
408
409 return (res ? ProgressResult::Success : ProgressResult::Failed);
410}
411
412static const struct
413{
414 const OSType key;
415 const wxChar *name;
416}
417names[] =
418{
419 { kQTMetaDataCommonKeyAuthor, wxT("Author") },
420 { kQTMetaDataCommonKeyComment, TAG_COMMENTS },
421 { kQTMetaDataCommonKeyCopyright, TAG_COPYRIGHT },
422 { kQTMetaDataCommonKeyDirector, wxT("Director") },
423 { kQTMetaDataCommonKeyDisplayName, wxT("Full Name") },
424 { kQTMetaDataCommonKeyInformation, wxT("Information") },
425 { kQTMetaDataCommonKeyKeywords, wxT("Keywords") },
426 { kQTMetaDataCommonKeyProducer, wxT("Producer") },
427 { kQTMetaDataCommonKeyAlbum, TAG_ALBUM },
428 { kQTMetaDataCommonKeyArtist, TAG_ARTIST },
429 { kQTMetaDataCommonKeyChapterName, wxT("Chapter") },
430 { kQTMetaDataCommonKeyComposer, wxT("Composer") },
431 { kQTMetaDataCommonKeyDescription, wxT("Description") },
432 { kQTMetaDataCommonKeyGenre, TAG_GENRE },
433 { kQTMetaDataCommonKeyOriginalFormat, wxT("Original Format") },
434 { kQTMetaDataCommonKeyOriginalSource, wxT("Original Source") },
435 { kQTMetaDataCommonKeyPerformers, wxT("Performers") },
436 { kQTMetaDataCommonKeySoftware, TAG_SOFTWARE },
437 { kQTMetaDataCommonKeyWriter, wxT("Writer") },
438};
439
440void QTImportFileHandle::AddMetadata(Tags *tags)
441{
442 QTMetaDataRef metaDataRef = NULL;
443 auto cleanup = finally( [&] {
444 // we are done so release our metadata object
445 if ( metaDataRef )
446 QTMetaDataRelease(metaDataRef);
447 } );
448
449 OSErr err;
450
451 err = QTCopyMovieMetaData(mMovie, &metaDataRef);
452 if (err != noErr) {
453 return;
454 }
455
456 for (int i = 0; i < WXSIZEOF(names); i++) {
457 QTMetaDataItem item = kQTMetaDataItemUninitialized;
458 // OSType key = names[i].key;
459
460 err = QTMetaDataGetNextItem(metaDataRef,
461 kQTMetaDataStorageFormatWildcard,
462 kQTMetaDataItemUninitialized,
463 kQTMetaDataKeyFormatCommon,
464 (const UInt8 *) &names[i].key,
465 sizeof(names[i].key),
466 &item);
467 if (err != noErr) {
468 continue;
469 }
470
471 if (item == kQTMetaDataItemUninitialized) {
472 continue;
473 }
474
475 QTPropertyValueType outPropType;
476 ::ByteCount outPropValueSize;
477 ::ByteCount outPropValueSizeUsed = 0;
478 UInt32 outPropFlags;
479 UInt32 dataType;
480
481 // Get data type
482 err = QTMetaDataGetItemProperty(metaDataRef,
483 item,
484 kPropertyClass_MetaDataItem,
485 kQTMetaDataItemPropertyID_DataType,
486 sizeof(dataType),
487 &dataType,
488 &outPropValueSizeUsed);
489 if (err != noErr) {
490 continue;
491 }
492
493 // Get the data length
494 err = QTMetaDataGetItemPropertyInfo(metaDataRef,
495 item,
496 kPropertyClass_MetaDataItem,
497 kQTMetaDataItemPropertyID_Value,
498 &outPropType,
499 &outPropValueSize,
500 &outPropFlags );
501 if (err != noErr) {
502 continue;
503 }
504
505 // Alloc memory for it
506 ArrayOf<char> outVals{ outPropValueSize };
507
508 // Retrieve the data
509 err = QTMetaDataGetItemProperty(metaDataRef,
510 item,
511 kPropertyClass_MetaDataItem,
512 kQTMetaDataItemPropertyID_Value,
513 outPropValueSize,
514 outVals.get(),
515 &outPropValueSizeUsed);
516 if (err != noErr)
517 continue;
518
519 wxString v;
520
521 switch (dataType)
522 {
523 case kQTMetaDataTypeUTF8:
524 v = wxString(outVals.get(), wxConvUTF8);
525 break;
526 case kQTMetaDataTypeUTF16BE:
527 {
528 wxMBConvUTF16BE conv;
529 v = wxString(outVals.get(), conv);
530 }
531 break;
532 }
533
534 if (!v.empty()) {
535 tags->SetTag(names[i].name, v);
536 }
537 }
538
539 return;
540}
541
542#endif
wxT("CloseDown"))
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
static const AudacityProject::AttachedObjects::RegisteredFactory key
const TranslatableString name
Definition: Distortion.cpp:76
const TranslatableString desc
Definition: ExportPCM.cpp:55
int format
Definition: ExportPCM.cpp:53
#define OSFILENAME(X)
XO("Cut/Copy/Paste")
std::vector< std::shared_ptr< WaveTrack > > NewChannelGroup
Definition: Import.cpp:59
std::vector< std::vector< std::shared_ptr< WaveTrack > > > TrackHolders
Definition: Import.h:39
static Importer::RegisteredImportPlugin registered
Definition: ImportAUP.cpp:280
The interface that all file import "plugins" (if you want to call them that) must implement....
static const auto exts
Definition: ImportQT.cpp:25
#define DESC
Definition: ImportQT.cpp:23
std::unique_ptr< T, freer > MallocPtr
Definition: MemoryX.h:140
constexpr sampleFormat int16Sample
Definition: SampleFormat.h:41
constexpr sampleFormat floatSample
Definition: SampleFormat.h:43
constexpr sampleFormat int24Sample
Definition: SampleFormat.h:42
sampleFormat
The ordering of these values with operator < agrees with the order of increasing bit width.
Definition: SampleFormat.h:30
#define TAG_COMMENTS
Definition: Tags.h:64
#define TAG_GENRE
Definition: Tags.h:63
#define TAG_SOFTWARE
Definition: Tags.h:65
#define TAG_COPYRIGHT
Definition: Tags.h:66
#define TAG_ALBUM
Definition: Tags.h:60
#define TAG_ARTIST
Definition: Tags.h:59
static TranslatableStrings names
Definition: TagsEditor.cpp:152
std::vector< TranslatableString > TranslatableStrings
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
An ImportFileHandle for data.
Definition: ImportPlugin.h:112
virtual ByteCount GetFileUncompressedBytes()=0
virtual TranslatableString GetFileDescription()=0
virtual wxInt32 GetStreamCount()=0
virtual ProgressResult Import(WaveTrackFactory *trackFactory, TrackHolders &outTracks, Tags *tags)=0
virtual const TranslatableStrings & GetStreamInfo()=0
virtual void SetStreamUsage(wxInt32 StreamID, bool Use)=0
Base class for FlacImportPlugin, LOFImportPlugin, MP3ImportPlugin, OggImportPlugin and PCMImportPlugi...
Definition: ImportPlugin.h:68
virtual TranslatableString GetPluginFormatDescription()=0
virtual wxString GetPluginStringID()=0
virtual std::unique_ptr< ImportFileHandle > Open(const FilePath &Filename, AudacityProject *)=0
ID3 Tags (for MP3)
Definition: Tags.h:73
void SetTag(const wxString &name, const wxString &value, const bool bSpecialTag=false)
Definition: Tags.cpp:441
Holds a msgid for the translation catalog; may also bind format arguments.
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
Extend wxArrayString with move operations and construction and insertion fromstd::initializer_list.
ProgressResult
Definition: BasicUI.h:147
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
auto begin(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:150