6 * Code to create QuickTime Movies with Blender
8 * ***** BEGIN GPL 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.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software Foundation,
21 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 * The Original Code is written by Rob Haarsma (phase)
26 * Contributor(s): Stefan Gartner (sgefant)
28 * ***** END GPL LICENSE BLOCK *****
32 #if defined(_WIN32) || defined(__APPLE__)
34 #include "DNA_scene_types.h"
36 #include "BKE_global.h"
37 #include "BKE_scene.h"
39 #include "BLI_blenlib.h"
41 #include "BLO_sys_types.h"
43 #include "IMB_imbuf.h"
44 #include "IMB_imbuf_types.h"
46 #include "MEM_guardedalloc.h"
48 #include "quicktime_import.h"
49 #include "quicktime_export.h"
54 #include <QuickTimeComponents.h>
55 #include <TextUtils.h>
63 #include <QuickTime/Movies.h>
64 #include <QuickTime/QuickTimeComponents.h>
65 #include <fcntl.h> /* open() */
66 #include <unistd.h> /* close() */
67 #include <sys/stat.h> /* file permissions */
68 #endif /* __APPLE__ */
70 #define kMyCreatorType FOUR_CHAR_CODE('TVOD')
74 static void QT_StartAddVideoSamplesToMedia (const Rect *trackFrame, int rectx, int recty);
75 static void QT_DoAddVideoSamplesToMedia (int frame, int *pixels, int rectx, int recty);
76 static void QT_EndAddVideoSamplesToMedia (void);
77 static void QT_CreateMyVideoTrack (int rectx, int recty);
78 static void QT_EndCreateMyVideoTrack (void);
79 static void check_renderbutton_framerate(struct RenderData *rd);
81 typedef struct QuicktimeExport {
92 PixMapHandle thePixMap;
93 ImageDescription **anImageDescription;
95 ImBuf *ibuf; //imagedata for Quicktime's Gworld
96 ImBuf *ibuf2; //copy of renderdata, to be Y-flipped
100 typedef struct QuicktimeComponentData {
102 ComponentInstance theComponent;
103 SCTemporalSettings gTemporalSettings;
104 SCSpatialSettings gSpatialSettings;
105 SCDataRateSettings aDataRateSetting;
107 long kVideoTimeScale;
109 } QuicktimeComponentData;
111 static struct QuicktimeExport *qtexport;
112 static struct QuicktimeComponentData *qtdata;
117 static void CheckError(OSErr err, char *msg)
119 if(err != noErr) printf("%s: %d\n", msg, err);
123 static OSErr QT_SaveCodecSettingsToScene(RenderData *rd)
125 QTAtomContainer myContainer = NULL;
126 ComponentResult myErr = noErr;
133 QuicktimeCodecData *qcd = rd->qtcodecdata;
135 // check if current scene already has qtcodec settings, and clear them
137 free_qtcodecdata(qcd);
139 qcd = rd->qtcodecdata = MEM_callocN(sizeof(QuicktimeCodecData), "QuicktimeCodecData");
142 // obtain all current codec settings
143 SCSetInfo(qtdata->theComponent, scTemporalSettingsType, &qtdata->gTemporalSettings);
144 SCSetInfo(qtdata->theComponent, scSpatialSettingsType, &qtdata->gSpatialSettings);
145 SCSetInfo(qtdata->theComponent, scDataRateSettingsType, &qtdata->aDataRateSetting);
147 // retreive codecdata from quicktime in a atomcontainer
148 myErr = SCGetSettingsAsAtomContainer(qtdata->theComponent, &myContainer);
149 if (myErr != noErr) {
150 printf("Quicktime: SCGetSettingsAsAtomContainer failed\n");
154 // get the size of the atomcontainer
155 mySize = GetHandleSize((Handle)myContainer);
157 // lock and convert the atomcontainer to a *valid* pointer
158 QTLockContainer(myContainer);
159 myPtr = *(Handle)myContainer;
161 // copy the Quicktime data into the blender qtcodecdata struct
163 qcd->cdParms = MEM_mallocN(mySize, "qt.cdParms");
164 memcpy(qcd->cdParms, myPtr, mySize);
165 qcd->cdSize = mySize;
167 GetCodecInfo (&ci, qtdata->gSpatialSettings.codecType, 0);
168 CopyPascalStringToC(ci.typeName, str);
169 sprintf(qcd->qtcodecname, "Codec: %s", str);
171 printf("Quicktime: QT_SaveCodecSettingsToScene failed\n");
174 QTUnlockContainer(myContainer);
177 if (myContainer != NULL)
178 QTDisposeAtomContainer(myContainer);
180 return((OSErr)myErr);
184 static OSErr QT_GetCodecSettingsFromScene(RenderData *rd)
186 Handle myHandle = NULL;
187 ComponentResult myErr = noErr;
191 QuicktimeCodecData *qcd = rd->qtcodecdata;
193 // if there is codecdata in the blendfile, convert it to a Quicktime handle
195 myHandle = NewHandle(qcd->cdSize);
196 PtrToHand( qcd->cdParms, &myHandle, qcd->cdSize);
199 // restore codecsettings to the quicktime component
200 if(qcd->cdParms && qcd->cdSize) {
201 myErr = SCSetSettingsFromAtomContainer((GraphicsExportComponent)qtdata->theComponent, (QTAtomContainer)myHandle);
202 if (myErr != noErr) {
203 printf("Quicktime: SCSetSettingsFromAtomContainer failed\n");
207 // update runtime codecsettings for use with the codec dialog
208 SCGetInfo(qtdata->theComponent, scDataRateSettingsType, &qtdata->aDataRateSetting);
209 SCGetInfo(qtdata->theComponent, scSpatialSettingsType, &qtdata->gSpatialSettings);
210 SCGetInfo(qtdata->theComponent, scTemporalSettingsType, &qtdata->gTemporalSettings);
212 // GetCodecInfo (&ci, qtdata->gSpatialSettings.codecType, 0);
213 // CopyPascalStringToC(ci.typeName, str);
214 // printf("restored Codec: %s\n", str);
216 printf("Quicktime: QT_GetCodecSettingsFromScene failed\n");
219 if (myHandle != NULL)
220 DisposeHandle(myHandle);
222 return((OSErr)myErr);
226 static OSErr QT_AddUserDataTextToMovie (Movie theMovie, char *theText, OSType theType)
228 UserData myUserData = NULL;
229 Handle myHandle = NULL;
230 long myLength = strlen(theText);
233 // get the movie's user data list
234 myUserData = GetMovieUserData(theMovie);
235 if (myUserData == NULL)
238 // copy the specified text into a new handle
239 myHandle = NewHandleClear(myLength);
240 if (myHandle == NULL)
243 BlockMoveData(theText, *myHandle, myLength);
245 // add the data to the movie's user data
246 myErr = AddUserDataText(myUserData, myHandle, theType, 1, (short)GetScriptManagerVariable(smRegionCode));
249 DisposeHandle(myHandle);
254 static void QT_CreateMyVideoTrack(int rectx, int recty)
258 // MatrixRecord myMatrix;
262 trackFrame.bottom = recty;
263 trackFrame.right = rectx;
265 qtexport->theTrack = NewMovieTrack (qtexport->theMovie,
266 FixRatio(trackFrame.right,1),
267 FixRatio(trackFrame.bottom,1),
269 CheckError( GetMoviesError(), "NewMovieTrack error" );
271 // SetIdentityMatrix(&myMatrix);
272 // ScaleMatrix(&myMatrix, fixed1, Long2Fix(-1), 0, 0);
273 // TranslateMatrix(&myMatrix, 0, Long2Fix(trackFrame.bottom));
274 // SetMovieMatrix(qtexport->theMovie, &myMatrix);
276 qtexport->theMedia = NewTrackMedia (qtexport->theTrack,
278 qtdata->kVideoTimeScale,
281 CheckError( GetMoviesError(), "NewTrackMedia error" );
283 err = BeginMediaEdits (qtexport->theMedia);
284 CheckError( err, "BeginMediaEdits error" );
286 QT_StartAddVideoSamplesToMedia (&trackFrame, rectx, recty);
290 static void QT_EndCreateMyVideoTrack(void)
294 QT_EndAddVideoSamplesToMedia ();
296 err = EndMediaEdits (qtexport->theMedia);
297 CheckError( err, "EndMediaEdits error" );
299 err = InsertMediaIntoTrack (qtexport->theTrack,
300 kTrackStart,/* track start time */
301 kMediaStart,/* media start time */
302 GetMediaDuration (qtexport->theMedia),
304 CheckError( err, "InsertMediaIntoTrack error" );
308 static void QT_StartAddVideoSamplesToMedia (const Rect *trackFrame, int rectx, int recty)
310 SCTemporalSettings gTemporalSettings;
313 qtexport->ibuf = IMB_allocImBuf (rectx, recty, 32, IB_rect, 0);
314 qtexport->ibuf2 = IMB_allocImBuf (rectx, recty, 32, IB_rect, 0);
316 err = NewGWorldFromPtr( &qtexport->theGWorld,
320 (unsigned char *)qtexport->ibuf->rect,
322 CheckError (err, "NewGWorldFromPtr error");
324 qtexport->thePixMap = GetGWorldPixMap(qtexport->theGWorld);
325 LockPixels(qtexport->thePixMap);
327 SCDefaultPixMapSettings (qtdata->theComponent, qtexport->thePixMap, true);
329 // workaround for crash with H.264, which requires an upgrade to
330 // the new callback based api for proper encoding, but that's not
331 // really compatible with rendering out frames sequentially
332 gTemporalSettings = qtdata->gTemporalSettings;
333 if(qtdata->gSpatialSettings.codecType == kH264CodecType) {
334 if(gTemporalSettings.temporalQuality != codecMinQuality) {
335 fprintf(stderr, "Only minimum quality compression supported for QuickTime H.264.\n");
336 gTemporalSettings.temporalQuality = codecMinQuality;
340 SCSetInfo(qtdata->theComponent, scTemporalSettingsType, &gTemporalSettings);
341 SCSetInfo(qtdata->theComponent, scSpatialSettingsType, &qtdata->gSpatialSettings);
342 SCSetInfo(qtdata->theComponent, scDataRateSettingsType, &qtdata->aDataRateSetting);
344 err = SCCompressSequenceBegin(qtdata->theComponent, qtexport->thePixMap, NULL, &qtexport->anImageDescription);
345 CheckError (err, "SCCompressSequenceBegin error" );
349 static void QT_DoAddVideoSamplesToMedia (int frame, int *pixels, int rectx, int recty)
356 unsigned char *from, *to;
360 Handle compressedData;
364 //copy and flip renderdata
365 memcpy(qtexport->ibuf2->rect, pixels, 4*rectx*recty);
366 IMB_flipy(qtexport->ibuf2);
368 //get pointers to parse bitmapdata
369 myPtr = GetPixBaseAddr(qtexport->thePixMap);
370 imageRect = (**qtexport->thePixMap).bounds;
372 from = (unsigned char *) qtexport->ibuf2->rect;
373 to = (unsigned char *) myPtr;
375 //parse RGBA bitmap into Quicktime's ARGB GWorld
376 boxsize = rectx * recty;
377 for( index = 0; index < boxsize; index++) {
385 err = SCCompressSequenceFrame(qtdata->theComponent,
391 CheckError(err, "SCCompressSequenceFrame error");
393 err = AddMediaSample(qtexport->theMedia,
398 (SampleDescriptionHandle)qtexport->anImageDescription,
402 CheckError(err, "AddMediaSample error");
404 printf ("added frame %3d (frame %3d in movie): ", frame, frame-sframe);
408 static void QT_EndAddVideoSamplesToMedia (void)
410 SCCompressSequenceEnd(qtdata->theComponent);
412 UnlockPixels(qtexport->thePixMap);
413 if (qtexport->theGWorld)
414 DisposeGWorld (qtexport->theGWorld);
417 IMB_freeImBuf(qtexport->ibuf);
420 IMB_freeImBuf(qtexport->ibuf2);
424 void makeqtstring (RenderData *rd, char *string) {
427 if (string==0) return;
429 strcpy(string, rd->pic);
430 BLI_convertstringcode(string, G.sce);
432 BLI_make_existing_file(string);
434 if (BLI_strcasecmp(string + strlen(string) - 4, ".mov")) {
435 sprintf(txt, "%04d_%04d.mov", (rd->sfra) , (rd->efra) );
441 void start_qt(struct RenderData *rd, int rectx, int recty) {
445 char theFullPath[255];
454 if(qtexport == NULL) qtexport = MEM_callocN(sizeof(QuicktimeExport), "QuicktimeExport");
457 if(qtdata->theComponent) CloseComponent(qtdata->theComponent);
458 free_qtcomponentdata();
461 qtdata = MEM_callocN(sizeof(QuicktimeComponentData), "QuicktimeCodecDataExt");
463 if(rd->qtcodecdata == NULL && rd->qtcodecdata->cdParms == NULL) {
464 get_qtcodec_settings(rd);
466 qtdata->theComponent = OpenDefaultComponent(StandardCompressionType, StandardCompressionSubType);
468 QT_GetCodecSettingsFromScene(rd);
469 check_renderbutton_framerate(rd);
472 if (G.afbreek != 1) {
475 makeqtstring(rd, name);
478 sprintf(theFullPath, "%s", name);
480 /* hack: create an empty file to make FSPathMakeRef() happy */
481 myFile = open(theFullPath, O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRUSR|S_IWUSR);
483 printf("error while creating file!\n");
487 err = FSPathMakeRef(theFullPath, &myRef, 0);
488 CheckError(err, "FsPathMakeRef error");
489 err = FSGetCatalogInfo(&myRef, kFSCatInfoNone, NULL, NULL, &qtexport->theSpec, NULL);
490 CheckError(err, "FsGetCatalogInfoRef error");
493 qtname = get_valid_qtname(name);
494 sprintf(theFullPath, "%s", qtname);
495 strcpy(name, qtname);
498 CopyCStringToPascal(theFullPath, qtexport->qtfilename);
499 err = FSMakeFSSpec(0, 0L, qtexport->qtfilename, &qtexport->theSpec);
502 err = CreateMovieFile (&qtexport->theSpec,
505 createMovieFileDeleteCurFile | createMovieFileDontCreateResFile,
506 &qtexport->resRefNum,
507 &qtexport->theMovie );
508 CheckError(err, "CreateMovieFile error");
512 // XXX error("Unable to create Quicktime movie: %s", name);
514 printf("Created QuickTime movie: %s\n", name);
516 QT_CreateMyVideoTrack(rectx, recty);
522 void append_qt(struct RenderData *rd, int frame, int *pixels, int rectx, int recty) {
523 QT_DoAddVideoSamplesToMedia(frame, pixels, rectx, recty);
529 short resId = movieInDataForkResID;
531 if(qtexport->theMovie) {
532 QT_EndCreateMyVideoTrack();
534 err = AddMovieResource (qtexport->theMovie, qtexport->resRefNum, &resId, qtexport->qtfilename);
535 CheckError(err, "AddMovieResource error");
537 err = QT_AddUserDataTextToMovie(qtexport->theMovie, "Made with Blender", kUserDataTextInformation);
538 CheckError(err, "AddUserDataTextToMovie error");
540 err = UpdateMovieResource(qtexport->theMovie, qtexport->resRefNum, resId, qtexport->qtfilename);
541 CheckError(err, "UpdateMovieResource error");
543 if(qtexport->resRefNum) CloseMovieFile(qtexport->resRefNum);
545 DisposeMovie(qtexport->theMovie);
547 printf("Finished QuickTime movie.\n");
557 void free_qtcomponentdata(void) {
559 if(qtdata->theComponent) CloseComponent(qtdata->theComponent);
566 static void check_renderbutton_framerate(RenderData *rd)
568 // to keep float framerates consistent between the codec dialog and frs/sec button.
571 err = SCGetInfo(qtdata->theComponent, scTemporalSettingsType, &qtdata->gTemporalSettings);
572 CheckError(err, "SCGetInfo fr error");
574 if( (rd->frs_sec == 24 || rd->frs_sec == 30 || rd->frs_sec == 60) &&
575 (qtdata->gTemporalSettings.frameRate == 1571553 ||
576 qtdata->gTemporalSettings.frameRate == 1964113 ||
577 qtdata->gTemporalSettings.frameRate == 3928227)) {;} else
578 qtdata->gTemporalSettings.frameRate =
579 (rd->frs_sec << 16) / rd->frs_sec_base ;
581 err = SCSetInfo(qtdata->theComponent, scTemporalSettingsType, &qtdata->gTemporalSettings);
582 CheckError( err, "SCSetInfo error" );
584 if(qtdata->gTemporalSettings.frameRate == 1571553) { // 23.98 fps
585 qtdata->kVideoTimeScale = 24000;
586 qtdata->duration = 1001;
587 } else if (qtdata->gTemporalSettings.frameRate == 1964113) { // 29.97 fps
588 qtdata->kVideoTimeScale = 30000;
589 qtdata->duration = 1001;
590 } else if (qtdata->gTemporalSettings.frameRate == 3928227) { // 59.94 fps
591 qtdata->kVideoTimeScale = 60000;
592 qtdata->duration = 1001;
594 qtdata->kVideoTimeScale = (qtdata->gTemporalSettings.frameRate >> 16) * 100;
595 qtdata->duration = 100;
600 int get_qtcodec_settings(RenderData *rd)
604 // erase any existing codecsetting
606 if(qtdata->theComponent) CloseComponent(qtdata->theComponent);
607 free_qtcomponentdata();
611 qtdata = MEM_callocN(sizeof(QuicktimeComponentData), "QuicktimeComponentData");
612 qtdata->theComponent = OpenDefaultComponent(StandardCompressionType, StandardCompressionSubType);
614 // get previous selected codecsetting, if any
615 if(rd->qtcodecdata && rd->qtcodecdata->cdParms) {
616 QT_GetCodecSettingsFromScene(rd);
617 check_renderbutton_framerate(rd);
619 // configure the standard image compression dialog box
620 // set some default settings
621 qtdata->gSpatialSettings.codec = anyCodec;
622 qtdata->gSpatialSettings.spatialQuality = codecMaxQuality;
623 qtdata->gTemporalSettings.temporalQuality = codecMaxQuality;
624 qtdata->gTemporalSettings.keyFrameRate = 25;
625 qtdata->aDataRateSetting.dataRate = 90 * 1024;
627 err = SCSetInfo(qtdata->theComponent, scTemporalSettingsType, &qtdata->gTemporalSettings);
628 CheckError(err, "SCSetInfo1 error");
629 err = SCSetInfo(qtdata->theComponent, scSpatialSettingsType, &qtdata->gSpatialSettings);
630 CheckError(err, "SCSetInfo2 error");
631 err = SCSetInfo(qtdata->theComponent, scDataRateSettingsType, &qtdata->aDataRateSetting);
632 CheckError(err, "SCSetInfo3 error");
635 check_renderbutton_framerate(rd);
637 // put up the dialog box
638 err = SCRequestSequenceSettings(qtdata->theComponent);
640 if (err == scUserCancelled) {
645 // get user selected data
646 SCGetInfo(qtdata->theComponent, scTemporalSettingsType, &qtdata->gTemporalSettings);
647 SCGetInfo(qtdata->theComponent, scSpatialSettingsType, &qtdata->gSpatialSettings);
648 SCGetInfo(qtdata->theComponent, scDataRateSettingsType, &qtdata->aDataRateSetting);
650 QT_SaveCodecSettingsToScene(rd);
652 // framerate jugglin'
653 if(qtdata->gTemporalSettings.frameRate == 1571553) { // 23.98 fps
654 qtdata->kVideoTimeScale = 24000;
655 qtdata->duration = 1001;
658 rd->frs_sec_base = 1.001;
659 } else if (qtdata->gTemporalSettings.frameRate == 1964113) { // 29.97 fps
660 qtdata->kVideoTimeScale = 30000;
661 qtdata->duration = 1001;
664 rd->frs_sec_base = 1.001;
665 } else if (qtdata->gTemporalSettings.frameRate == 3928227) { // 59.94 fps
666 qtdata->kVideoTimeScale = 60000;
667 qtdata->duration = 1001;
670 rd->frs_sec_base = 1.001;
672 double fps = qtdata->gTemporalSettings.frameRate;
674 qtdata->kVideoTimeScale = 60000;
675 qtdata->duration = qtdata->kVideoTimeScale / (qtdata->gTemporalSettings.frameRate / 65536);
677 if ((qtdata->gTemporalSettings.frameRate & 0xffff) == 0) {
678 rd->frs_sec = fps / 65536;
679 rd->frs_sec_base = 1;
681 /* we do our very best... */
682 rd->frs_sec = (fps * 10000 / 65536);
683 rd->frs_sec_base = 10000;
690 #endif /* _WIN32 || __APPLE__ */
691 #endif /* WITH_QUICKTIME */