Improved functionality for Quicktime movie export.
[blender.git] / source / blender / quicktime / apple / quicktime_export.c
1 /**
2  * $Id$
3  *
4  * quicktime_export.c
5  *
6  * Code to create QuickTime Movies with Blender
7  *
8  * ***** BEGIN GPL/BL DUAL LICENSE BLOCK *****
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License
11  * as published by the Free Software Foundation; either version 2
12  * of the License, or (at your option) any later version. The Blender
13  * Foundation also sells licenses for use in proprietary software under
14  * the Blender License.  See http://www.blender.org/BL/ for information
15  * about this.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software Foundation,
24  * Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
25  *
26  *
27  * The Original Code is written by Rob Haarsma (phase)
28  *
29  * Contributor(s): Stefan Gartner (sgefant)
30  *
31  * ***** END GPL/BL DUAL LICENSE BLOCK *****
32  */
33
34 #ifdef WITH_QUICKTIME
35 #if defined(_WIN32) || defined(__APPLE__)
36
37 #include "BKE_global.h"
38 #include "BKE_scene.h"
39 #include "BLI_blenlib.h"
40 #include "BIF_toolbox.h"        /* error() */
41 #include "BLO_sys_types.h"
42 #include "IMB_imbuf.h"
43 #include "IMB_imbuf_types.h"
44 #include "MEM_guardedalloc.h"
45 #include "render.h"
46 #include "quicktime_import.h"
47 #include "quicktime_export.h"
48
49 #ifdef _WIN32
50 #include <QTML.h>
51 #include <Movies.h>
52 #include <QuicktimeComponents.h>
53 #include <TextUtils.h> 
54 #endif /* _WIN32 */
55
56 #ifdef __APPLE__
57 /* evil */
58 #ifndef __AIFF__
59 #define __AIFF__
60 #endif
61 #include <QuickTime/Movies.h>
62 #include <QuickTime/QuicktimeComponents.h>
63 #include <fcntl.h> /* open() */
64 #include <unistd.h> /* close() */
65 #include <sys/stat.h> /* file permissions */
66 #endif /* __APPLE__ */
67
68 #define kMyCreatorType  FOUR_CHAR_CODE('TVOD')
69 #define kTrackStart             0
70 #define kMediaStart             0
71
72 static void QT_StartAddVideoSamplesToMedia (const Rect *trackFrame);
73 static void QT_DoAddVideoSamplesToMedia (int frame);
74 static void QT_EndAddVideoSamplesToMedia (void);
75 static void QT_CreateMyVideoTrack (void);
76 static void QT_EndCreateMyVideoTrack (void);
77 static void check_renderbutton_framerate(void);
78
79 typedef struct QuicktimeExport {
80
81         FSSpec          theSpec;
82         short           resRefNum;
83         short           resId;
84         short           movieResId;
85         Str255          qtfilename;
86
87         Media           theMedia;
88         Movie           theMovie;
89         Track           theTrack;
90
91         GWorldPtr       theGWorld;
92         PixMapHandle    thePixMap;
93
94         ImageDescription        **anImageDescription;
95         ImageSequence           anImageSequence;
96
97         ImBuf           *ibuf;  //imagedata for Quicktime's Gworld
98
99 } QuicktimeExport;
100
101 typedef struct QuicktimeComponentData {
102
103         ComponentInstance       theComponent;
104         SCTemporalSettings  gTemporalSettings;
105         SCSpatialSettings   gSpatialSettings;
106         SCDataRateSettings  aDataRateSetting;
107         TimeValue                       duration;
108         long                            kVideoTimeScale;
109
110 } QuicktimeComponentData;
111
112 static struct QuicktimeExport *qtexport;
113 static struct QuicktimeComponentData *qtdata;
114
115 static int      sframe;
116
117
118 void CheckError(OSErr err, char *msg)
119 {
120         if(err != noErr) printf("%s: %d\n", msg, err);
121 }
122
123
124 OSErr QT_SaveCodecSettingsToScene(void)
125 {       
126         QTAtomContainer         myContainer = NULL;
127         ComponentResult         myErr = noErr;
128         Ptr                                     myPtr;
129         long                            mySize = 0;
130
131         CodecInfo                       ci;
132         char str[255];
133
134         QuicktimeCodecData *qcd = G.scene->r.qtcodecdata;
135
136         // check if current scene already has qtcodec settings, and clear them
137         if (qcd) {
138                 free_qtcodecdata(qcd);
139         } else {
140                 qcd = G.scene->r.qtcodecdata = MEM_callocN(sizeof(QuicktimeCodecData), "QuicktimeCodecData");
141         }
142
143         // obtain all current codec settings
144         SCSetInfo(qtdata->theComponent, scTemporalSettingsType, &qtdata->gTemporalSettings);
145         SCSetInfo(qtdata->theComponent, scSpatialSettingsType,  &qtdata->gSpatialSettings);
146         SCSetInfo(qtdata->theComponent, scDataRateSettingsType, &qtdata->aDataRateSetting);
147
148         // retreive codecdata from quicktime in a atomcontainer
149         myErr = SCGetSettingsAsAtomContainer(qtdata->theComponent,  &myContainer);
150         if (myErr != noErr) {
151                 printf("Quicktime: SCGetSettingsAsAtomContainer failed\n"); 
152                 goto bail;
153         }
154
155         // get the size of the atomcontainer
156         mySize = GetHandleSize((Handle)myContainer);
157
158         // lock and convert the atomcontainer to a *valid* pointer
159         QTLockContainer(myContainer);
160         myPtr = *(Handle)myContainer;
161
162         // copy the Quicktime data into the blender qtcodecdata struct
163         if (myPtr) {
164                 qcd->cdParms = MEM_mallocN(mySize, "qt.cdParms");
165                 memcpy(qcd->cdParms, myPtr, mySize);
166                 qcd->cdSize = mySize;
167
168                 GetCodecInfo (&ci, qtdata->gSpatialSettings.codecType, 0);
169                 CopyPascalStringToC(ci.typeName, str);
170                 sprintf(qcd->qtcodecname, "Codec: %s", str);
171         } else {
172                 printf("Quicktime: SaveExporterSettingsToMem failed\n"); 
173         }
174
175         QTUnlockContainer(myContainer);
176
177 bail:
178         if (myContainer != NULL)
179                 QTDisposeAtomContainer(myContainer);
180                 
181         return((OSErr)myErr);
182 }
183
184
185 OSErr QT_GetCodecSettingsFromScene(void)
186 {       
187         Handle                          myHandle = NULL;
188         ComponentResult         myErr = noErr;
189 //      CodecInfo ci;
190 //      char str[255];
191
192         QuicktimeCodecData *qcd = G.scene->r.qtcodecdata;
193
194         // if there is codecdata in the blendfile, convert it to a Quicktime handle 
195         if (qcd) {
196                 myHandle = NewHandle(qcd->cdSize);
197                 PtrToHand( qcd->cdParms, &myHandle, qcd->cdSize);
198         }
199                 
200         // restore codecsettings to the quicktime component
201         if(qcd->cdParms && qcd->cdSize) {
202                 myErr = SCSetSettingsFromAtomContainer((GraphicsExportComponent)qtdata->theComponent, (QTAtomContainer)myHandle);
203                 if (myErr != noErr) {
204                         printf("Quicktime: SCSetSettingsFromAtomContainer failed\n"); 
205                         goto bail;
206                 }
207
208                 // update runtime codecsettings for use with the codec dialog
209                 SCGetInfo(qtdata->theComponent, scDataRateSettingsType, &qtdata->aDataRateSetting);
210                 SCGetInfo(qtdata->theComponent, scSpatialSettingsType,  &qtdata->gSpatialSettings);
211                 SCGetInfo(qtdata->theComponent, scTemporalSettingsType, &qtdata->gTemporalSettings);
212
213 //              GetCodecInfo (&ci, qtdata->gSpatialSettings.codecType, 0);
214 //              CopyPascalStringToC(ci.typeName, str);
215 //              printf("restored Codec: %s\n", str);
216         } else {
217                 printf("Quicktime: GetExporterSettingsFromMem failed\n"); 
218         }
219 bail:
220         if (myHandle != NULL)
221                 DisposeHandle(myHandle);
222                 
223         return((OSErr)myErr);
224 }
225
226
227 OSErr QT_AddUserDataTextToMovie (Movie theMovie, char *theText, OSType theType)
228 {
229         UserData                                        myUserData = NULL;
230         Handle                                          myHandle = NULL;
231         long                                            myLength = strlen(theText);
232         OSErr                                           myErr = noErr;
233
234         // get the movie's user data list
235         myUserData = GetMovieUserData(theMovie);
236         if (myUserData == NULL)
237                 return(paramErr);
238         
239         // copy the specified text into a new handle
240         myHandle = NewHandleClear(myLength);
241         if (myHandle == NULL)
242                 return(MemError());
243
244         BlockMoveData(theText, *myHandle, myLength);
245
246         // add the data to the movie's user data
247         myErr = AddUserDataText(myUserData, myHandle, theType, 1, (short)GetScriptManagerVariable(smRegionCode));
248
249         // clean up
250         DisposeHandle(myHandle);
251         return(myErr);
252 }
253
254
255 static void QT_CreateMyVideoTrack(void)
256 {
257         OSErr err = noErr;
258         Rect trackFrame;
259         MatrixRecord myMatrix;
260
261         trackFrame.top = 0;
262         trackFrame.left = 0;
263         trackFrame.bottom = R.recty;
264         trackFrame.right = R.rectx;
265         
266         qtexport->theTrack = NewMovieTrack (qtexport->theMovie, 
267                                                         FixRatio(trackFrame.right,1),
268                                                         FixRatio(trackFrame.bottom,1), 
269                                                         kNoVolume);
270         CheckError( GetMoviesError(), "NewMovieTrack error" );
271
272         SetIdentityMatrix(&myMatrix);
273         ScaleMatrix(&myMatrix, fixed1, Long2Fix(-1), 0, 0);
274         TranslateMatrix(&myMatrix, 0, Long2Fix(trackFrame.bottom));
275         SetMovieMatrix(qtexport->theMovie, &myMatrix);
276
277         qtexport->theMedia = NewTrackMedia (qtexport->theTrack,
278                                                         VideoMediaType,
279                                                         qtdata->kVideoTimeScale,
280                                                         nil,
281                                                         0);
282         CheckError( GetMoviesError(), "NewTrackMedia error" );
283
284         err = BeginMediaEdits (qtexport->theMedia);
285         CheckError( err, "BeginMediaEdits error" );
286
287         QT_StartAddVideoSamplesToMedia (&trackFrame);
288
289
290
291 static void QT_EndCreateMyVideoTrack(void)
292 {
293         OSErr err = noErr;
294
295         QT_EndAddVideoSamplesToMedia ();
296
297         err = EndMediaEdits (qtexport->theMedia);
298         CheckError( err, "EndMediaEdits error" );
299
300         err = InsertMediaIntoTrack (qtexport->theTrack,
301                                                                 kTrackStart,/* track start time */
302                                                                 kMediaStart,/* media start time */
303                                                                 GetMediaDuration (qtexport->theMedia),
304                                                                 fixed1);
305         CheckError( err, "InsertMediaIntoTrack error" );
306
307
308
309 static void QT_StartAddVideoSamplesToMedia (const Rect *trackFrame)
310 {
311         OSErr err = noErr;
312
313         qtexport->ibuf = IMB_allocImBuf (R.rectx, R.recty, 32, IB_rect, 0);
314
315         err = NewGWorldFromPtr( &qtexport->theGWorld,
316                                                         k32RGBAPixelFormat,
317                                                         trackFrame,
318                                                         NULL, NULL, 0,
319                                                         (unsigned char *)qtexport->ibuf->rect,
320                                                         R.rectx * 4 );
321         CheckError (err, "NewGWorldFromPtr error");
322
323         qtexport->thePixMap = GetGWorldPixMap(qtexport->theGWorld);
324         LockPixels(qtexport->thePixMap);
325
326         SCDefaultPixMapSettings (qtdata->theComponent, qtexport->thePixMap, true);
327
328         SCSetInfo(qtdata->theComponent, scTemporalSettingsType, &qtdata->gTemporalSettings);
329         SCSetInfo(qtdata->theComponent, scSpatialSettingsType,  &qtdata->gSpatialSettings);
330         SCSetInfo(qtdata->theComponent, scDataRateSettingsType, &qtdata->aDataRateSetting);
331
332         err = SCCompressSequenceBegin(qtdata->theComponent, qtexport->thePixMap, NULL, &qtexport->anImageDescription); 
333         CheckError (err, "SCCompressSequenceBegin error" );
334 }
335
336
337 static void QT_DoAddVideoSamplesToMedia (int frame)
338 {
339         OSErr   err = noErr;
340         Rect    imageRect;
341
342         register int            boxsize;
343         register uint32_t       *readPos;
344         register uint32_t       *changePos;
345         Ptr                                     myPtr;
346
347         short   syncFlag;
348         long    dataSize;
349         Handle  compressedData;
350
351         //get pointers to parse bitmapdata
352         myPtr = GetPixBaseAddr(qtexport->thePixMap);
353         imageRect = (**qtexport->thePixMap).bounds;
354
355         boxsize = R.rectx * R.recty;
356         readPos = (uint32_t *) R.rectot;
357         changePos = (uint32_t *) myPtr;
358
359         //parse render bitmap into Quicktime's GWorld
360         memcpy(changePos, readPos, boxsize*4);
361
362         err = SCCompressSequenceFrame(qtdata->theComponent,
363                 qtexport->thePixMap,
364                 &imageRect,
365                 &compressedData,
366                 &dataSize,
367                 &syncFlag);
368         CheckError(err, "SCCompressSequenceFrame error");
369
370         err = AddMediaSample(qtexport->theMedia,
371                 compressedData,
372                 0,
373                 dataSize,
374                 qtdata->duration,
375                 (SampleDescriptionHandle)qtexport->anImageDescription,
376                 1,
377                 syncFlag,
378                 NULL);
379         CheckError(err, "AddMediaSample error");
380
381         printf ("added frame %3d (frame %3d in movie): ", frame, frame-sframe);
382 }
383
384
385 static void QT_EndAddVideoSamplesToMedia (void)
386 {
387         SCCompressSequenceEnd(qtdata->theComponent);
388
389         UnlockPixels(qtexport->thePixMap);
390         if (qtexport->theGWorld) DisposeGWorld (qtexport->theGWorld);
391         if (qtexport->ibuf) IMB_freeImBuf(qtexport->ibuf);
392
393
394
395 void makeqtstring (char *string) {
396         char txt[64];
397
398         if (string==0) return;
399
400         strcpy(string, G.scene->r.pic);
401         BLI_convertstringcode(string, G.sce, G.scene->r.cfra);
402
403         RE_make_existing_file(string);
404
405         if (strcasecmp(string + strlen(string) - 4, ".mov")) {
406                 sprintf(txt, "%04d_%04d.mov", (G.scene->r.sfra) , (G.scene->r.efra) );
407                 strcat(string, txt);
408         }
409 }
410
411
412 void start_qt(void) {
413         OSErr err = noErr;
414
415         char name[2048];
416         char theFullPath[255];
417
418 #ifdef __APPLE__
419         int             myFile;
420         FSRef   myRef;
421 #else
422         char    *qtname;
423 #endif
424
425         if(qtexport == NULL) qtexport = MEM_callocN(sizeof(QuicktimeExport), "QuicktimeExport");
426
427         if(qtdata) {
428                 if(qtdata->theComponent) CloseComponent(qtdata->theComponent);
429                 free_qtcomponentdata();
430         }
431
432         qtdata = MEM_callocN(sizeof(QuicktimeComponentData), "QuicktimeCodecDataExt");
433
434         if(G.scene->r.qtcodecdata == NULL && G.scene->r.qtcodecdata->cdParms == NULL) {
435                 get_qtcodec_settings();
436         } else {
437                 qtdata->theComponent = OpenDefaultComponent(StandardCompressionType, StandardCompressionSubType);
438
439                 QT_GetCodecSettingsFromScene();
440                 check_renderbutton_framerate();
441         }
442         
443         if (G.afbreek != 1) {
444                 sframe = (G.scene->r.sfra);
445
446                 makeqtstring(name);
447
448 #ifdef __APPLE__
449                 sprintf(theFullPath, "%s", name);
450
451                 /* hack: create an empty file to make FSPathMakeRef() happy */
452                 myFile = open(theFullPath, O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRUSR|S_IWUSR);
453                 if (myFile < 0) {
454                         printf("error while creating file!\n");
455                         /* do something? */
456                 }
457                 close(myFile);
458                 err = FSPathMakeRef(theFullPath, &myRef, 0);
459                 CheckError(err, "FsPathMakeRef error");
460                 err = FSGetCatalogInfo(&myRef, kFSCatInfoNone, NULL, NULL, &qtexport->theSpec, NULL);
461                 CheckError(err, "FsGetCatalogInfoRef error");
462 #else
463                 qtname = get_valid_qtname(name);
464                 sprintf(theFullPath, "%s", qtname);
465                 
466                 CopyCStringToPascal(theFullPath, qtexport->qtfilename);
467                 err = FSMakeFSSpec(0, 0L, qtexport->qtfilename, &qtexport->theSpec);
468 #endif
469
470                 err = CreateMovieFile (&qtexport->theSpec, 
471                                                         kMyCreatorType,
472                                                         smCurrentScript, 
473                                                         createMovieFileDeleteCurFile | createMovieFileDontCreateResFile,
474                                                         &qtexport->resRefNum, 
475                                                         &qtexport->theMovie );
476                 CheckError(err, "CreateMovieFile error");
477
478                 if(err != noErr) {
479                         G.afbreek = 1;
480 #ifdef __APPLE__
481                         error("Unable to create Quicktime movie: %s\n", name);
482 #else
483                         error("Unable to create Quicktime movie: %s\n", qtname);
484                         MEM_freeN(qtname);
485 #endif
486                 } else {
487
488 #ifdef __APPLE__
489                         printf("Created QuickTime movie: %s\n", name);
490 #else
491                         printf("Created QuickTime movie: %s\n", qtname);
492                         MEM_freeN(qtname);
493 #endif
494
495                         QT_CreateMyVideoTrack();
496                 }
497         }
498 }
499
500
501 void append_qt(int frame) {
502         QT_DoAddVideoSamplesToMedia(frame);
503 }
504
505
506 void end_qt(void) {
507         OSErr err = noErr;
508
509         if(qtexport->theMovie) {
510                 QT_EndCreateMyVideoTrack ();
511
512                 qtexport->resId = movieInDataForkResID;
513                 err = AddMovieResource (qtexport->theMovie, qtexport->resRefNum, &qtexport->resId, qtexport->qtfilename);
514                 CheckError(err, "AddMovieResource error");
515
516                 err = QT_AddUserDataTextToMovie(qtexport->theMovie, "Made with Blender", kUserDataTextInformation);
517                 CheckError(err, "AddUserDataTextToMovie error");
518
519                 err = UpdateMovieResource(qtexport->theMovie, qtexport->resRefNum, qtexport->resId, qtexport->qtfilename);
520                 CheckError(err, "UpdateMovieResource error");
521
522                 if(qtexport->resRefNum) CloseMovieFile(qtexport->resRefNum);
523
524                 DisposeMovie(qtexport->theMovie);
525
526                 printf("Finished QuickTime movie.\n");
527         }
528
529         if(qtexport) {
530                 MEM_freeN(qtexport);
531                 qtexport = NULL;
532         }
533 }
534
535
536 void free_qtcomponentdata(void) {
537         if(qtdata) {
538                 if(qtdata->theComponent) CloseComponent(qtdata->theComponent);
539                 MEM_freeN(qtdata);
540                 qtdata = NULL;
541         }
542 }
543
544
545 static void check_renderbutton_framerate(void) {
546         //    To keep float framerates consistent between the codec dialog and frs/sec button.
547         OSErr   err;    
548
549         err = SCGetInfo(qtdata->theComponent, scTemporalSettingsType,   &qtdata->gTemporalSettings);
550         CheckError(err, "SCGetInfo fr error");
551
552         if( (G.scene->r.frs_sec == 24 || G.scene->r.frs_sec == 30 || G.scene->r.frs_sec == 60) &&
553                 (qtdata->gTemporalSettings.frameRate == 1571553 ||
554                  qtdata->gTemporalSettings.frameRate == 1964113 ||
555                  qtdata->gTemporalSettings.frameRate == 3928227)) {;} else
556         qtdata->gTemporalSettings.frameRate = G.scene->r.frs_sec << 16;
557
558         err = SCSetInfo(qtdata->theComponent, scTemporalSettingsType,   &qtdata->gTemporalSettings);
559         CheckError( err, "SCSetInfo error" );
560
561         if(qtdata->gTemporalSettings.frameRate == 1571553) {                    // 23.98 fps
562                 qtdata->kVideoTimeScale = 2398;
563                 qtdata->duration = 100;
564         } else if (qtdata->gTemporalSettings.frameRate == 1964113) {    // 29.97 fps
565                 qtdata->kVideoTimeScale = 2997;
566                 qtdata->duration = 100;
567         } else if (qtdata->gTemporalSettings.frameRate == 3928227) {    // 59.94 fps
568                 qtdata->kVideoTimeScale = 5994;
569                 qtdata->duration = 100;
570         } else {
571                 qtdata->kVideoTimeScale = (qtdata->gTemporalSettings.frameRate >> 16) * 100;
572                 qtdata->duration = 100;
573         }
574 }
575
576
577 int get_qtcodec_settings(void) 
578 {
579         OSErr   err = noErr;
580
581         // erase any existing codecsetting
582         if(qtdata) {
583                 if(qtdata->theComponent) CloseComponent(qtdata->theComponent);
584                 free_qtcomponentdata();
585         }
586
587         // allocate new
588         qtdata = MEM_callocN(sizeof(QuicktimeComponentData), "QuicktimeComponentData");
589         qtdata->theComponent = OpenDefaultComponent(StandardCompressionType, StandardCompressionSubType);
590
591         // get previous selected codecsetting, if any 
592         if(G.scene->r.qtcodecdata && G.scene->r.qtcodecdata->cdParms) {
593                 QT_GetCodecSettingsFromScene();
594                 check_renderbutton_framerate();
595         } else {
596
597         // configure the standard image compression dialog box
598         // set some default settings
599 //              qtdata->gSpatialSettings.codecType = nil;     
600                 qtdata->gSpatialSettings.codec = anyCodec;         
601 //              qtdata->gSpatialSettings.depth;         
602                 qtdata->gSpatialSettings.spatialQuality = codecMaxQuality;
603
604                 qtdata->gTemporalSettings.temporalQuality = codecMaxQuality;
605 //              qtdata->gTemporalSettings.frameRate;      
606                 qtdata->gTemporalSettings.keyFrameRate = 25;   
607
608                 qtdata->aDataRateSetting.dataRate = 90 * 1024;          
609 //              qtdata->aDataRateSetting.frameDuration;     
610 //              qtdata->aDataRateSetting.minSpatialQuality; 
611 //              qtdata->aDataRateSetting.minTemporalQuality;
612
613                 err = SCSetInfo(qtdata->theComponent, scTemporalSettingsType,   &qtdata->gTemporalSettings);
614                 CheckError(err, "SCSetInfo1 error");
615                 err = SCSetInfo(qtdata->theComponent, scSpatialSettingsType,    &qtdata->gSpatialSettings);
616                 CheckError(err, "SCSetInfo2 error");
617                 err = SCSetInfo(qtdata->theComponent, scDataRateSettingsType,   &qtdata->aDataRateSetting);
618                 CheckError(err, "SCSetInfo3 error");
619         }
620
621         check_renderbutton_framerate();
622
623         // put up the dialog box
624         err = SCRequestSequenceSettings(qtdata->theComponent);
625  
626         if (err == scUserCancelled) {
627                 G.afbreek = 1;
628                 return 0;
629         }
630
631         // get user selected data
632         SCGetInfo(qtdata->theComponent, scTemporalSettingsType, &qtdata->gTemporalSettings);
633         SCGetInfo(qtdata->theComponent, scSpatialSettingsType,  &qtdata->gSpatialSettings);
634         SCGetInfo(qtdata->theComponent, scDataRateSettingsType, &qtdata->aDataRateSetting);
635
636         QT_SaveCodecSettingsToScene();
637
638         // framerate jugglin'
639         if(qtdata->gTemporalSettings.frameRate == 1571553) {                    // 23.98 fps
640                 qtdata->kVideoTimeScale = 2398;
641                 qtdata->duration = 100;
642
643                 G.scene->r.frs_sec = 24;
644         } else if (qtdata->gTemporalSettings.frameRate == 1964113) {    // 29.97 fps
645                 qtdata->kVideoTimeScale = 2997;
646                 qtdata->duration = 100;
647
648                 G.scene->r.frs_sec = 30;
649         } else if (qtdata->gTemporalSettings.frameRate == 3928227) {    // 59.94 fps
650                 qtdata->kVideoTimeScale = 5994;
651                 qtdata->duration = 100;
652
653                 G.scene->r.frs_sec = 60;
654         } else {
655                 qtdata->kVideoTimeScale = 600;
656                 qtdata->duration = qtdata->kVideoTimeScale / (qtdata->gTemporalSettings.frameRate / 65536);
657
658                 G.scene->r.frs_sec = (qtdata->gTemporalSettings.frameRate / 65536);
659         }
660
661         return 1;
662 }
663
664 #endif /* _WIN32 || __APPLE__ */
665 #endif /* WITH_QUICKTIME */
666