/** * $Id$ * * quicktime_export.c * * Code to create QuickTime Movies with Blender * * ***** BEGIN GPL LICENSE BLOCK ***** * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * * The Original Code is written by Rob Haarsma (phase) * * Contributor(s): Stefan Gartner (sgefant) * * ***** END GPL LICENSE BLOCK ***** */ #ifdef WITH_QUICKTIME #if defined(_WIN32) || defined(__APPLE__) #include "DNA_scene_types.h" #include "BKE_global.h" #include "BKE_scene.h" #include "BLI_blenlib.h" #include "BLO_sys_types.h" #include "IMB_imbuf.h" #include "IMB_imbuf_types.h" #include "MEM_guardedalloc.h" #include "quicktime_import.h" #include "quicktime_export.h" #ifdef _WIN32 #include #include #include #include #endif /* _WIN32 */ #ifdef __APPLE__ /* evil */ #ifndef __AIFF__ #define __AIFF__ #endif #include #include #include /* open() */ #include /* close() */ #include /* file permissions */ #endif /* __APPLE__ */ #define kMyCreatorType FOUR_CHAR_CODE('TVOD') #define kTrackStart 0 #define kMediaStart 0 static void QT_StartAddVideoSamplesToMedia (const Rect *trackFrame, int rectx, int recty); static void QT_DoAddVideoSamplesToMedia (int frame, int *pixels, int rectx, int recty); static void QT_EndAddVideoSamplesToMedia (void); static void QT_CreateMyVideoTrack (int rectx, int recty); static void QT_EndCreateMyVideoTrack (void); static void check_renderbutton_framerate(struct RenderData *rd); typedef struct QuicktimeExport { FSSpec theSpec; short resRefNum; Str255 qtfilename; Media theMedia; Movie theMovie; Track theTrack; GWorldPtr theGWorld; PixMapHandle thePixMap; ImageDescription **anImageDescription; ImBuf *ibuf; //imagedata for Quicktime's Gworld ImBuf *ibuf2; //copy of renderdata, to be Y-flipped } QuicktimeExport; typedef struct QuicktimeComponentData { ComponentInstance theComponent; SCTemporalSettings gTemporalSettings; SCSpatialSettings gSpatialSettings; SCDataRateSettings aDataRateSetting; TimeValue duration; long kVideoTimeScale; } QuicktimeComponentData; static struct QuicktimeExport *qtexport; static struct QuicktimeComponentData *qtdata; static int sframe; static void CheckError(OSErr err, char *msg) { if(err != noErr) printf("%s: %d\n", msg, err); } static OSErr QT_SaveCodecSettingsToScene(RenderData *rd) { QTAtomContainer myContainer = NULL; ComponentResult myErr = noErr; Ptr myPtr; long mySize = 0; CodecInfo ci; char str[255]; QuicktimeCodecData *qcd = rd->qtcodecdata; // check if current scene already has qtcodec settings, and clear them if (qcd) { free_qtcodecdata(qcd); } else { qcd = rd->qtcodecdata = MEM_callocN(sizeof(QuicktimeCodecData), "QuicktimeCodecData"); } // obtain all current codec settings SCSetInfo(qtdata->theComponent, scTemporalSettingsType, &qtdata->gTemporalSettings); SCSetInfo(qtdata->theComponent, scSpatialSettingsType, &qtdata->gSpatialSettings); SCSetInfo(qtdata->theComponent, scDataRateSettingsType, &qtdata->aDataRateSetting); // retreive codecdata from quicktime in a atomcontainer myErr = SCGetSettingsAsAtomContainer(qtdata->theComponent, &myContainer); if (myErr != noErr) { printf("Quicktime: SCGetSettingsAsAtomContainer failed\n"); goto bail; } // get the size of the atomcontainer mySize = GetHandleSize((Handle)myContainer); // lock and convert the atomcontainer to a *valid* pointer QTLockContainer(myContainer); myPtr = *(Handle)myContainer; // copy the Quicktime data into the blender qtcodecdata struct if (myPtr) { qcd->cdParms = MEM_mallocN(mySize, "qt.cdParms"); memcpy(qcd->cdParms, myPtr, mySize); qcd->cdSize = mySize; GetCodecInfo (&ci, qtdata->gSpatialSettings.codecType, 0); CopyPascalStringToC(ci.typeName, str); sprintf(qcd->qtcodecname, "Codec: %s", str); } else { printf("Quicktime: QT_SaveCodecSettingsToScene failed\n"); } QTUnlockContainer(myContainer); bail: if (myContainer != NULL) QTDisposeAtomContainer(myContainer); return((OSErr)myErr); } static OSErr QT_GetCodecSettingsFromScene(RenderData *rd) { Handle myHandle = NULL; ComponentResult myErr = noErr; // CodecInfo ci; // char str[255]; QuicktimeCodecData *qcd = rd->qtcodecdata; // if there is codecdata in the blendfile, convert it to a Quicktime handle if (qcd) { myHandle = NewHandle(qcd->cdSize); PtrToHand( qcd->cdParms, &myHandle, qcd->cdSize); } // restore codecsettings to the quicktime component if(qcd->cdParms && qcd->cdSize) { myErr = SCSetSettingsFromAtomContainer((GraphicsExportComponent)qtdata->theComponent, (QTAtomContainer)myHandle); if (myErr != noErr) { printf("Quicktime: SCSetSettingsFromAtomContainer failed\n"); goto bail; } // update runtime codecsettings for use with the codec dialog SCGetInfo(qtdata->theComponent, scDataRateSettingsType, &qtdata->aDataRateSetting); SCGetInfo(qtdata->theComponent, scSpatialSettingsType, &qtdata->gSpatialSettings); SCGetInfo(qtdata->theComponent, scTemporalSettingsType, &qtdata->gTemporalSettings); // GetCodecInfo (&ci, qtdata->gSpatialSettings.codecType, 0); // CopyPascalStringToC(ci.typeName, str); // printf("restored Codec: %s\n", str); } else { printf("Quicktime: QT_GetCodecSettingsFromScene failed\n"); } bail: if (myHandle != NULL) DisposeHandle(myHandle); return((OSErr)myErr); } static OSErr QT_AddUserDataTextToMovie (Movie theMovie, char *theText, OSType theType) { UserData myUserData = NULL; Handle myHandle = NULL; long myLength = strlen(theText); OSErr myErr = noErr; // get the movie's user data list myUserData = GetMovieUserData(theMovie); if (myUserData == NULL) return(paramErr); // copy the specified text into a new handle myHandle = NewHandleClear(myLength); if (myHandle == NULL) return(MemError()); BlockMoveData(theText, *myHandle, myLength); // add the data to the movie's user data myErr = AddUserDataText(myUserData, myHandle, theType, 1, (short)GetScriptManagerVariable(smRegionCode)); // clean up DisposeHandle(myHandle); return(myErr); } static void QT_CreateMyVideoTrack(int rectx, int recty) { OSErr err = noErr; Rect trackFrame; // MatrixRecord myMatrix; trackFrame.top = 0; trackFrame.left = 0; trackFrame.bottom = recty; trackFrame.right = rectx; qtexport->theTrack = NewMovieTrack (qtexport->theMovie, FixRatio(trackFrame.right,1), FixRatio(trackFrame.bottom,1), 0); CheckError( GetMoviesError(), "NewMovieTrack error" ); // SetIdentityMatrix(&myMatrix); // ScaleMatrix(&myMatrix, fixed1, Long2Fix(-1), 0, 0); // TranslateMatrix(&myMatrix, 0, Long2Fix(trackFrame.bottom)); // SetMovieMatrix(qtexport->theMovie, &myMatrix); qtexport->theMedia = NewTrackMedia (qtexport->theTrack, VideoMediaType, qtdata->kVideoTimeScale, nil, 0); CheckError( GetMoviesError(), "NewTrackMedia error" ); err = BeginMediaEdits (qtexport->theMedia); CheckError( err, "BeginMediaEdits error" ); QT_StartAddVideoSamplesToMedia (&trackFrame, rectx, recty); } static void QT_EndCreateMyVideoTrack(void) { OSErr err = noErr; QT_EndAddVideoSamplesToMedia (); err = EndMediaEdits (qtexport->theMedia); CheckError( err, "EndMediaEdits error" ); err = InsertMediaIntoTrack (qtexport->theTrack, kTrackStart,/* track start time */ kMediaStart,/* media start time */ GetMediaDuration (qtexport->theMedia), fixed1); CheckError( err, "InsertMediaIntoTrack error" ); } static void QT_StartAddVideoSamplesToMedia (const Rect *trackFrame, int rectx, int recty) { SCTemporalSettings gTemporalSettings; OSErr err = noErr; qtexport->ibuf = IMB_allocImBuf (rectx, recty, 32, IB_rect, 0); qtexport->ibuf2 = IMB_allocImBuf (rectx, recty, 32, IB_rect, 0); err = NewGWorldFromPtr( &qtexport->theGWorld, k32ARGBPixelFormat, trackFrame, NULL, NULL, 0, (unsigned char *)qtexport->ibuf->rect, rectx * 4 ); CheckError (err, "NewGWorldFromPtr error"); qtexport->thePixMap = GetGWorldPixMap(qtexport->theGWorld); LockPixels(qtexport->thePixMap); SCDefaultPixMapSettings (qtdata->theComponent, qtexport->thePixMap, true); // workaround for crash with H.264, which requires an upgrade to // the new callback based api for proper encoding, but that's not // really compatible with rendering out frames sequentially gTemporalSettings = qtdata->gTemporalSettings; if(qtdata->gSpatialSettings.codecType == kH264CodecType) { if(gTemporalSettings.temporalQuality != codecMinQuality) { fprintf(stderr, "Only minimum quality compression supported for QuickTime H.264.\n"); gTemporalSettings.temporalQuality = codecMinQuality; } } SCSetInfo(qtdata->theComponent, scTemporalSettingsType, &gTemporalSettings); SCSetInfo(qtdata->theComponent, scSpatialSettingsType, &qtdata->gSpatialSettings); SCSetInfo(qtdata->theComponent, scDataRateSettingsType, &qtdata->aDataRateSetting); err = SCCompressSequenceBegin(qtdata->theComponent, qtexport->thePixMap, NULL, &qtexport->anImageDescription); CheckError (err, "SCCompressSequenceBegin error" ); } static void QT_DoAddVideoSamplesToMedia (int frame, int *pixels, int rectx, int recty) { OSErr err = noErr; Rect imageRect; int index; int boxsize; unsigned char *from, *to; short syncFlag; long dataSize; Handle compressedData; Ptr myPtr; //copy and flip renderdata memcpy(qtexport->ibuf2->rect, pixels, 4*rectx*recty); IMB_flipy(qtexport->ibuf2); //get pointers to parse bitmapdata myPtr = GetPixBaseAddr(qtexport->thePixMap); imageRect = (**qtexport->thePixMap).bounds; from = (unsigned char *) qtexport->ibuf2->rect; to = (unsigned char *) myPtr; //parse RGBA bitmap into Quicktime's ARGB GWorld boxsize = rectx * recty; for( index = 0; index < boxsize; index++) { to[0] = from[3]; to[1] = from[0]; to[2] = from[1]; to[3] = from[2]; to +=4, from += 4; } err = SCCompressSequenceFrame(qtdata->theComponent, qtexport->thePixMap, &imageRect, &compressedData, &dataSize, &syncFlag); CheckError(err, "SCCompressSequenceFrame error"); err = AddMediaSample(qtexport->theMedia, compressedData, 0, dataSize, qtdata->duration, (SampleDescriptionHandle)qtexport->anImageDescription, 1, syncFlag, NULL); CheckError(err, "AddMediaSample error"); printf ("added frame %3d (frame %3d in movie): ", frame, frame-sframe); } static void QT_EndAddVideoSamplesToMedia (void) { SCCompressSequenceEnd(qtdata->theComponent); UnlockPixels(qtexport->thePixMap); if (qtexport->theGWorld) DisposeGWorld (qtexport->theGWorld); if (qtexport->ibuf) IMB_freeImBuf(qtexport->ibuf); if (qtexport->ibuf2) IMB_freeImBuf(qtexport->ibuf2); } void makeqtstring (RenderData *rd, char *string) { char txt[64]; if (string==0) return; strcpy(string, rd->pic); BLI_convertstringcode(string, G.sce); BLI_make_existing_file(string); if (BLI_strcasecmp(string + strlen(string) - 4, ".mov")) { sprintf(txt, "%04d_%04d.mov", (rd->sfra) , (rd->efra) ); strcat(string, txt); } } void start_qt(struct RenderData *rd, int rectx, int recty) { OSErr err = noErr; char name[2048]; char theFullPath[255]; #ifdef __APPLE__ int myFile; FSRef myRef; #else char *qtname; #endif if(qtexport == NULL) qtexport = MEM_callocN(sizeof(QuicktimeExport), "QuicktimeExport"); if(qtdata) { if(qtdata->theComponent) CloseComponent(qtdata->theComponent); free_qtcomponentdata(); } qtdata = MEM_callocN(sizeof(QuicktimeComponentData), "QuicktimeCodecDataExt"); if(rd->qtcodecdata == NULL && rd->qtcodecdata->cdParms == NULL) { get_qtcodec_settings(rd); } else { qtdata->theComponent = OpenDefaultComponent(StandardCompressionType, StandardCompressionSubType); QT_GetCodecSettingsFromScene(rd); check_renderbutton_framerate(rd); } if (G.afbreek != 1) { sframe = (rd->sfra); makeqtstring(rd, name); #ifdef __APPLE__ sprintf(theFullPath, "%s", name); /* hack: create an empty file to make FSPathMakeRef() happy */ myFile = open(theFullPath, O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRUSR|S_IWUSR); if (myFile < 0) { printf("error while creating file!\n"); /* do something? */ } close(myFile); err = FSPathMakeRef(theFullPath, &myRef, 0); CheckError(err, "FsPathMakeRef error"); err = FSGetCatalogInfo(&myRef, kFSCatInfoNone, NULL, NULL, &qtexport->theSpec, NULL); CheckError(err, "FsGetCatalogInfoRef error"); #endif #ifdef _WIN32 qtname = get_valid_qtname(name); sprintf(theFullPath, "%s", qtname); strcpy(name, qtname); MEM_freeN(qtname); CopyCStringToPascal(theFullPath, qtexport->qtfilename); err = FSMakeFSSpec(0, 0L, qtexport->qtfilename, &qtexport->theSpec); #endif err = CreateMovieFile (&qtexport->theSpec, kMyCreatorType, smCurrentScript, createMovieFileDeleteCurFile | createMovieFileDontCreateResFile, &qtexport->resRefNum, &qtexport->theMovie ); CheckError(err, "CreateMovieFile error"); if(err != noErr) { G.afbreek = 1; // XXX error("Unable to create Quicktime movie: %s", name); } else { printf("Created QuickTime movie: %s\n", name); QT_CreateMyVideoTrack(rectx, recty); } } } void append_qt(int frame, int *pixels, int rectx, int recty) { QT_DoAddVideoSamplesToMedia(frame, pixels, rectx, recty); } void end_qt(void) { OSErr err = noErr; short resId = movieInDataForkResID; if(qtexport->theMovie) { QT_EndCreateMyVideoTrack(); err = AddMovieResource (qtexport->theMovie, qtexport->resRefNum, &resId, qtexport->qtfilename); CheckError(err, "AddMovieResource error"); err = QT_AddUserDataTextToMovie(qtexport->theMovie, "Made with Blender", kUserDataTextInformation); CheckError(err, "AddUserDataTextToMovie error"); err = UpdateMovieResource(qtexport->theMovie, qtexport->resRefNum, resId, qtexport->qtfilename); CheckError(err, "UpdateMovieResource error"); if(qtexport->resRefNum) CloseMovieFile(qtexport->resRefNum); DisposeMovie(qtexport->theMovie); printf("Finished QuickTime movie.\n"); } if(qtexport) { MEM_freeN(qtexport); qtexport = NULL; } } void free_qtcomponentdata(void) { if(qtdata) { if(qtdata->theComponent) CloseComponent(qtdata->theComponent); MEM_freeN(qtdata); qtdata = NULL; } } static void check_renderbutton_framerate(RenderData *rd) { // to keep float framerates consistent between the codec dialog and frs/sec button. OSErr err; err = SCGetInfo(qtdata->theComponent, scTemporalSettingsType, &qtdata->gTemporalSettings); CheckError(err, "SCGetInfo fr error"); if( (rd->frs_sec == 24 || rd->frs_sec == 30 || rd->frs_sec == 60) && (qtdata->gTemporalSettings.frameRate == 1571553 || qtdata->gTemporalSettings.frameRate == 1964113 || qtdata->gTemporalSettings.frameRate == 3928227)) {;} else qtdata->gTemporalSettings.frameRate = (rd->frs_sec << 16) / rd->frs_sec_base ; err = SCSetInfo(qtdata->theComponent, scTemporalSettingsType, &qtdata->gTemporalSettings); CheckError( err, "SCSetInfo error" ); if(qtdata->gTemporalSettings.frameRate == 1571553) { // 23.98 fps qtdata->kVideoTimeScale = 24000; qtdata->duration = 1001; } else if (qtdata->gTemporalSettings.frameRate == 1964113) { // 29.97 fps qtdata->kVideoTimeScale = 30000; qtdata->duration = 1001; } else if (qtdata->gTemporalSettings.frameRate == 3928227) { // 59.94 fps qtdata->kVideoTimeScale = 60000; qtdata->duration = 1001; } else { qtdata->kVideoTimeScale = (qtdata->gTemporalSettings.frameRate >> 16) * 100; qtdata->duration = 100; } } int get_qtcodec_settings(RenderData *rd) { OSErr err = noErr; // erase any existing codecsetting if(qtdata) { if(qtdata->theComponent) CloseComponent(qtdata->theComponent); free_qtcomponentdata(); } // allocate new qtdata = MEM_callocN(sizeof(QuicktimeComponentData), "QuicktimeComponentData"); qtdata->theComponent = OpenDefaultComponent(StandardCompressionType, StandardCompressionSubType); // get previous selected codecsetting, if any if(rd->qtcodecdata && rd->qtcodecdata->cdParms) { QT_GetCodecSettingsFromScene(rd); check_renderbutton_framerate(rd); } else { // configure the standard image compression dialog box // set some default settings qtdata->gSpatialSettings.codec = anyCodec; qtdata->gSpatialSettings.spatialQuality = codecMaxQuality; qtdata->gTemporalSettings.temporalQuality = codecMaxQuality; qtdata->gTemporalSettings.keyFrameRate = 25; qtdata->aDataRateSetting.dataRate = 90 * 1024; err = SCSetInfo(qtdata->theComponent, scTemporalSettingsType, &qtdata->gTemporalSettings); CheckError(err, "SCSetInfo1 error"); err = SCSetInfo(qtdata->theComponent, scSpatialSettingsType, &qtdata->gSpatialSettings); CheckError(err, "SCSetInfo2 error"); err = SCSetInfo(qtdata->theComponent, scDataRateSettingsType, &qtdata->aDataRateSetting); CheckError(err, "SCSetInfo3 error"); } check_renderbutton_framerate(rd); // put up the dialog box err = SCRequestSequenceSettings(qtdata->theComponent); if (err == scUserCancelled) { G.afbreek = 1; return 0; } // get user selected data SCGetInfo(qtdata->theComponent, scTemporalSettingsType, &qtdata->gTemporalSettings); SCGetInfo(qtdata->theComponent, scSpatialSettingsType, &qtdata->gSpatialSettings); SCGetInfo(qtdata->theComponent, scDataRateSettingsType, &qtdata->aDataRateSetting); QT_SaveCodecSettingsToScene(rd); // framerate jugglin' if(qtdata->gTemporalSettings.frameRate == 1571553) { // 23.98 fps qtdata->kVideoTimeScale = 24000; qtdata->duration = 1001; rd->frs_sec = 24; rd->frs_sec_base = 1.001; } else if (qtdata->gTemporalSettings.frameRate == 1964113) { // 29.97 fps qtdata->kVideoTimeScale = 30000; qtdata->duration = 1001; rd->frs_sec = 30; rd->frs_sec_base = 1.001; } else if (qtdata->gTemporalSettings.frameRate == 3928227) { // 59.94 fps qtdata->kVideoTimeScale = 60000; qtdata->duration = 1001; rd->frs_sec = 60; rd->frs_sec_base = 1.001; } else { double fps = qtdata->gTemporalSettings.frameRate; qtdata->kVideoTimeScale = 60000; qtdata->duration = qtdata->kVideoTimeScale / (qtdata->gTemporalSettings.frameRate / 65536); if ((qtdata->gTemporalSettings.frameRate & 0xffff) == 0) { rd->frs_sec = fps / 65536; rd->frs_sec_base = 1; } else { /* we do our very best... */ rd->frs_sec = (fps * 10000 / 65536); rd->frs_sec_base = 10000; } } return 1; } #endif /* _WIN32 || __APPLE__ */ #endif /* WITH_QUICKTIME */