928 lines
29 KiB
C
928 lines
29 KiB
C
/*
|
|
* ***** 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*
|
|
* The Original Code is written by Rob Haarsma (phase)
|
|
*
|
|
* Contributor(s): Stefan Gartner (sgefant)
|
|
*
|
|
* ***** END GPL LICENSE BLOCK *****
|
|
*/
|
|
|
|
/** \file blender/quicktime/apple/quicktime_export.c
|
|
* \ingroup quicktime
|
|
*
|
|
* Code to create QuickTime Movies with Blender
|
|
*/
|
|
|
|
|
|
#ifdef WITH_QUICKTIME
|
|
#if defined(_WIN32) || defined(__APPLE__)
|
|
#ifndef USE_QTKIT
|
|
|
|
#include "DNA_scene_types.h"
|
|
#include "DNA_windowmanager_types.h"
|
|
|
|
#include "WM_api.h"
|
|
#include "WM_types.h"
|
|
|
|
#include "BKE_context.h"
|
|
#include "BKE_global.h"
|
|
#include "BKE_main.h"
|
|
#include "BKE_report.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 <QTML.h>
|
|
#include <Movies.h>
|
|
#include <QuickTimeComponents.h>
|
|
#include <TextUtils.h>
|
|
#include <string.h>
|
|
#include <memory.h>
|
|
|
|
#endif /* _WIN32 */
|
|
|
|
#ifdef __APPLE__
|
|
/* evil */
|
|
#ifndef __AIFF__
|
|
#define __AIFF__
|
|
#endif
|
|
#include <QuickTime/Movies.h>
|
|
#include <QuickTime/QuickTimeComponents.h>
|
|
#include <fcntl.h> /* open() */
|
|
#include <unistd.h> /* close() */
|
|
#include <sys/stat.h> /* 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, struct ReportList *reports);
|
|
static void QT_DoAddVideoSamplesToMedia(int frame, int *pixels, int rectx, int recty, struct ReportList *reports);
|
|
static void QT_EndAddVideoSamplesToMedia(void);
|
|
static void QT_CreateMyVideoTrack(int rectx, int recty, struct ReportList *reports);
|
|
static void QT_EndCreateMyVideoTrack(struct ReportList *reports);
|
|
static void check_renderbutton_framerate(struct RenderData *rd, struct ReportList *reports);
|
|
static int get_qtcodec_settings(struct RenderData *rd, struct ReportList *reports);
|
|
|
|
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;
|
|
|
|
/* RNA functions */
|
|
|
|
static QuicktimeCodecTypeDesc qtVideoCodecList[] = {
|
|
{kRawCodecType, 1, "Uncompressed"},
|
|
{kJPEGCodecType, 2, "JPEG"},
|
|
{kMotionJPEGACodecType, 3, "M-JPEG A"},
|
|
{kMotionJPEGBCodecType, 4, "M-JPEG B"},
|
|
{kDVCPALCodecType, 5, "DV PAL"},
|
|
{kDVCNTSCCodecType, 6, "DV/DVCPRO NTSC"},
|
|
{kDVCPROHD720pCodecType, 7, "DVCPRO HD 720p"},
|
|
{kDVCPROHD1080i50CodecType, 8, "DVCPRO HD 1080i50"},
|
|
{kDVCPROHD1080i60CodecType, 9, "DVCPRO HD 1080i60"},
|
|
{kMPEG4VisualCodecType, 10, "MPEG4"},
|
|
{kH263CodecType, 11, "H.263"},
|
|
{kH264CodecType, 12, "H.264"},
|
|
{0, 0, NULL}
|
|
};
|
|
|
|
static int qtVideoCodecCount = 12;
|
|
|
|
int quicktime_get_num_videocodecs()
|
|
{
|
|
return qtVideoCodecCount;
|
|
}
|
|
|
|
QuicktimeCodecTypeDesc *quicktime_get_videocodecType_desc(int indexValue)
|
|
{
|
|
if ((indexValue >= 0) && (indexValue < qtVideoCodecCount))
|
|
return &qtVideoCodecList[indexValue];
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
int quicktime_rnatmpvalue_from_videocodectype(int codecType)
|
|
{
|
|
int i;
|
|
for (i = 0; i < qtVideoCodecCount; i++) {
|
|
if (qtVideoCodecList[i].codecType == codecType)
|
|
return qtVideoCodecList[i].rnatmpvalue;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int quicktime_videocodecType_from_rnatmpvalue(int rnatmpvalue)
|
|
{
|
|
int i;
|
|
for (i = 0; i < qtVideoCodecCount; i++) {
|
|
if (qtVideoCodecList[i].rnatmpvalue == rnatmpvalue)
|
|
return qtVideoCodecList[i].codecType;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
static void CheckError(OSErr err, char *msg, ReportList *reports)
|
|
{
|
|
if (err != noErr) {
|
|
BKE_reportf(reports, RPT_ERROR, "%s: %d", msg, err);
|
|
}
|
|
}
|
|
|
|
|
|
static OSErr QT_SaveCodecSettingsToScene(RenderData *rd, ReportList *reports)
|
|
{
|
|
QTAtomContainer myContainer = NULL;
|
|
ComponentResult myErr = noErr;
|
|
Ptr myPtr;
|
|
long mySize = 0;
|
|
|
|
CodecInfo ci;
|
|
|
|
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) {
|
|
BKE_report(reports, RPT_ERROR, "Quicktime: SCGetSettingsAsAtomContainer failed");
|
|
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);
|
|
}
|
|
else {
|
|
BKE_report(reports, RPT_ERROR, "Quicktime: QT_SaveCodecSettingsToScene failed");
|
|
}
|
|
|
|
QTUnlockContainer(myContainer);
|
|
|
|
bail:
|
|
if (myContainer != NULL)
|
|
QTDisposeAtomContainer(myContainer);
|
|
|
|
return((OSErr)myErr);
|
|
}
|
|
|
|
|
|
static OSErr QT_GetCodecSettingsFromScene(RenderData *rd, ReportList *reports)
|
|
{
|
|
Handle myHandle = NULL;
|
|
ComponentResult myErr = noErr;
|
|
|
|
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) {
|
|
BKE_report(reports, RPT_ERROR, "Quicktime: SCSetSettingsFromAtomContainer failed");
|
|
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);
|
|
|
|
|
|
/* Fill the render QuicktimeCodecSettigns struct */
|
|
rd->qtcodecsettings.codecTemporalQuality = (qtdata->gTemporalSettings.temporalQuality * 100) / codecLosslessQuality;
|
|
/* Do not override scene frame rate (qtdata->gTemporalSettings.framerate) */
|
|
rd->qtcodecsettings.keyFrameRate = qtdata->gTemporalSettings.keyFrameRate;
|
|
|
|
rd->qtcodecsettings.codecType = qtdata->gSpatialSettings.codecType;
|
|
rd->qtcodecsettings.codec = (int)qtdata->gSpatialSettings.codec;
|
|
rd->qtcodecsettings.colorDepth = qtdata->gSpatialSettings.depth;
|
|
rd->qtcodecsettings.codecSpatialQuality = (qtdata->gSpatialSettings.spatialQuality * 100) / codecLosslessQuality;
|
|
|
|
rd->qtcodecsettings.bitRate = qtdata->aDataRateSetting.dataRate;
|
|
rd->qtcodecsettings.minSpatialQuality = (qtdata->aDataRateSetting.minSpatialQuality * 100) / codecLosslessQuality;
|
|
rd->qtcodecsettings.minTemporalQuality = (qtdata->aDataRateSetting.minTemporalQuality * 100) / codecLosslessQuality;
|
|
/* Frame duration is already known (qtdata->aDataRateSetting.frameDuration) */
|
|
|
|
}
|
|
else {
|
|
BKE_report(reports, RPT_ERROR, "Quicktime: QT_GetCodecSettingsFromScene failed");
|
|
}
|
|
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, ReportList *reports)
|
|
{
|
|
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", reports);
|
|
|
|
// 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", reports);
|
|
|
|
err = BeginMediaEdits(qtexport->theMedia);
|
|
CheckError(err, "BeginMediaEdits error", reports);
|
|
|
|
QT_StartAddVideoSamplesToMedia(&trackFrame, rectx, recty, reports);
|
|
}
|
|
|
|
|
|
static void QT_EndCreateMyVideoTrack(ReportList *reports)
|
|
{
|
|
OSErr err = noErr;
|
|
|
|
QT_EndAddVideoSamplesToMedia();
|
|
|
|
err = EndMediaEdits(qtexport->theMedia);
|
|
CheckError(err, "EndMediaEdits error", reports);
|
|
|
|
err = InsertMediaIntoTrack(qtexport->theTrack,
|
|
kTrackStart, /* track start time */
|
|
kMediaStart, /* media start time */
|
|
GetMediaDuration(qtexport->theMedia),
|
|
fixed1);
|
|
CheckError(err, "InsertMediaIntoTrack error", reports);
|
|
}
|
|
|
|
|
|
static void QT_StartAddVideoSamplesToMedia(const Rect *trackFrame, int rectx, int recty, ReportList *reports)
|
|
{
|
|
SCTemporalSettings gTemporalSettings;
|
|
OSErr err = noErr;
|
|
|
|
qtexport->ibuf = IMB_allocImBuf(rectx, recty, 32, IB_rect);
|
|
qtexport->ibuf2 = IMB_allocImBuf(rectx, recty, 32, IB_rect);
|
|
|
|
err = NewGWorldFromPtr(&qtexport->theGWorld,
|
|
k32ARGBPixelFormat,
|
|
trackFrame,
|
|
NULL, NULL, 0,
|
|
(Ptr)qtexport->ibuf->rect,
|
|
rectx * 4);
|
|
CheckError(err, "NewGWorldFromPtr error", reports);
|
|
|
|
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) {
|
|
BKE_report(reports, RPT_WARNING, "Only minimum quality compression supported for Quicktime H.264");
|
|
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", reports);
|
|
}
|
|
|
|
|
|
static void QT_DoAddVideoSamplesToMedia(int frame, int *pixels, int rectx, int recty, ReportList *reports)
|
|
{
|
|
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", reports);
|
|
|
|
err = AddMediaSample(qtexport->theMedia,
|
|
compressedData,
|
|
0,
|
|
dataSize,
|
|
qtdata->duration,
|
|
(SampleDescriptionHandle)qtexport->anImageDescription,
|
|
1,
|
|
syncFlag,
|
|
NULL);
|
|
CheckError(err, "AddMediaSample error", reports);
|
|
}
|
|
|
|
|
|
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 filepath_qt(char *string, RenderData *rd)
|
|
{
|
|
char txt[64];
|
|
|
|
if (string == 0) return;
|
|
|
|
strcpy(string, rd->pic);
|
|
BLI_path_abs(string, G.main->name);
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
|
|
int start_qt(struct Scene *scene, struct RenderData *rd, int rectx, int recty, ReportList *reports)
|
|
{
|
|
OSErr err = noErr;
|
|
|
|
char name[2048];
|
|
char theFullPath[255];
|
|
|
|
#ifdef __APPLE__
|
|
int myFile;
|
|
FSRef myRef;
|
|
#else
|
|
char *qtname;
|
|
#endif
|
|
int success = 1;
|
|
|
|
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, reports);
|
|
}
|
|
else {
|
|
qtdata->theComponent = OpenDefaultComponent(StandardCompressionType, StandardCompressionSubType);
|
|
|
|
QT_GetCodecSettingsFromScene(rd, reports);
|
|
check_renderbutton_framerate(rd, reports);
|
|
}
|
|
|
|
sframe = (rd->sfra);
|
|
|
|
filepath_qt(name, rd);
|
|
|
|
#ifdef __APPLE__
|
|
EnterMoviesOnThread(0);
|
|
strcpy(theFullPath, 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) {
|
|
BKE_report(reports, RPT_ERROR, "Error while creating movie file!");
|
|
/* do something? */
|
|
}
|
|
close(myFile);
|
|
err = FSPathMakeRef((const UInt8 *)theFullPath, &myRef, 0);
|
|
CheckError(err, "FsPathMakeRef error", reports);
|
|
err = FSGetCatalogInfo(&myRef, kFSCatInfoNone, NULL, NULL, &qtexport->theSpec, NULL);
|
|
CheckError(err, "FsGetCatalogInfoRef error", reports);
|
|
#endif
|
|
#ifdef _WIN32
|
|
qtname = get_valid_qtname(name);
|
|
strcpy(theFullPath, 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", reports);
|
|
|
|
if (err != noErr) {
|
|
BKE_reportf(reports, RPT_ERROR, "Unable to create Quicktime movie: %s", name);
|
|
success = 0;
|
|
#ifdef __APPLE__
|
|
ExitMoviesOnThread();
|
|
#endif
|
|
}
|
|
else {
|
|
/* printf("Created QuickTime movie: %s\n", name); */
|
|
|
|
QT_CreateMyVideoTrack(rectx, recty, reports);
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
int append_qt(struct RenderData *rd, int start_frame, int frame, int *pixels, int rectx, int recty, ReportList *reports)
|
|
{
|
|
QT_DoAddVideoSamplesToMedia(frame, pixels, rectx, recty, reports);
|
|
return 1;
|
|
}
|
|
|
|
|
|
void end_qt(void)
|
|
{
|
|
OSErr err = noErr;
|
|
short resId = movieInDataForkResID;
|
|
|
|
if (qtexport->theMovie) {
|
|
QT_EndCreateMyVideoTrack(NULL);
|
|
|
|
err = AddMovieResource(qtexport->theMovie, qtexport->resRefNum, &resId, qtexport->qtfilename);
|
|
CheckError(err, "AddMovieResource error", NULL);
|
|
|
|
err = QT_AddUserDataTextToMovie(qtexport->theMovie, "Made with Blender", kUserDataTextInformation);
|
|
CheckError(err, "AddUserDataTextToMovie error", NULL);
|
|
|
|
err = UpdateMovieResource(qtexport->theMovie, qtexport->resRefNum, resId, qtexport->qtfilename);
|
|
CheckError(err, "UpdateMovieResource error", NULL);
|
|
|
|
if (qtexport->resRefNum) CloseMovieFile(qtexport->resRefNum);
|
|
|
|
DisposeMovie(qtexport->theMovie);
|
|
|
|
/* printf("Finished QuickTime movie.\n"); */
|
|
}
|
|
|
|
#ifdef __APPLE__
|
|
ExitMoviesOnThread();
|
|
#endif
|
|
|
|
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, ReportList *reports)
|
|
{
|
|
/* 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", reports);
|
|
|
|
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))
|
|
{
|
|
/* do nothing */
|
|
}
|
|
else {
|
|
if (rd->frs_sec_base > 0)
|
|
qtdata->gTemporalSettings.frameRate =
|
|
((float)(rd->frs_sec << 16) / rd->frs_sec_base);
|
|
}
|
|
|
|
err = SCSetInfo(qtdata->theComponent, scTemporalSettingsType, &qtdata->gTemporalSettings);
|
|
CheckError(err, "SCSetInfo error", reports);
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
void quicktime_verify_image_type(RenderData *rd, ImageFormatData *imf)
|
|
{
|
|
if (imf->imtype == R_IMF_IMTYPE_QUICKTIME) {
|
|
if ((rd->qtcodecsettings.codecType == 0) ||
|
|
(rd->qtcodecsettings.codecSpatialQuality < 0) ||
|
|
(rd->qtcodecsettings.codecSpatialQuality > 100))
|
|
{
|
|
rd->qtcodecsettings.codecType = kJPEGCodecType;
|
|
rd->qtcodecsettings.codec = (int)anyCodec;
|
|
rd->qtcodecsettings.codecSpatialQuality = (codecHighQuality * 100) / codecLosslessQuality;
|
|
rd->qtcodecsettings.codecTemporalQuality = (codecHighQuality * 100) / codecLosslessQuality;
|
|
rd->qtcodecsettings.keyFrameRate = 25;
|
|
rd->qtcodecsettings.bitRate = 5000000; /* 5 Mbps */
|
|
}
|
|
}
|
|
}
|
|
|
|
int get_qtcodec_settings(RenderData *rd, ReportList *reports)
|
|
{
|
|
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, from qtatom or detailed settings */
|
|
if (rd->qtcodecdata && rd->qtcodecdata->cdParms) {
|
|
QT_GetCodecSettingsFromScene(rd, reports);
|
|
}
|
|
else {
|
|
SCGetInfo(qtdata->theComponent, scDataRateSettingsType, &qtdata->aDataRateSetting);
|
|
SCGetInfo(qtdata->theComponent, scSpatialSettingsType, &qtdata->gSpatialSettings);
|
|
SCGetInfo(qtdata->theComponent, scTemporalSettingsType, &qtdata->gTemporalSettings);
|
|
|
|
qtdata->gSpatialSettings.codecType = rd->qtcodecsettings.codecType;
|
|
qtdata->gSpatialSettings.codec = (CodecComponent)rd->qtcodecsettings.codec;
|
|
qtdata->gSpatialSettings.spatialQuality = (rd->qtcodecsettings.codecSpatialQuality * codecLosslessQuality) / 100;
|
|
qtdata->gTemporalSettings.temporalQuality = (rd->qtcodecsettings.codecTemporalQuality * codecLosslessQuality) / 100;
|
|
qtdata->gTemporalSettings.keyFrameRate = rd->qtcodecsettings.keyFrameRate;
|
|
qtdata->aDataRateSetting.dataRate = rd->qtcodecsettings.bitRate;
|
|
qtdata->gSpatialSettings.depth = rd->qtcodecsettings.colorDepth;
|
|
qtdata->aDataRateSetting.minSpatialQuality = (rd->qtcodecsettings.minSpatialQuality * codecLosslessQuality) / 100;
|
|
qtdata->aDataRateSetting.minTemporalQuality = (rd->qtcodecsettings.minTemporalQuality * codecLosslessQuality) / 100;
|
|
|
|
qtdata->aDataRateSetting.frameDuration = rd->frs_sec;
|
|
SetMovieTimeScale(qtexport->theMovie, rd->frs_sec_base * 1000);
|
|
|
|
|
|
err = SCSetInfo(qtdata->theComponent, scTemporalSettingsType, &qtdata->gTemporalSettings);
|
|
CheckError(err, "SCSetInfo1 error", reports);
|
|
err = SCSetInfo(qtdata->theComponent, scSpatialSettingsType, &qtdata->gSpatialSettings);
|
|
CheckError(err, "SCSetInfo2 error", reports);
|
|
err = SCSetInfo(qtdata->theComponent, scDataRateSettingsType, &qtdata->aDataRateSetting);
|
|
CheckError(err, "SCSetInfo3 error", reports);
|
|
}
|
|
|
|
check_renderbutton_framerate(rd, reports);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int request_qtcodec_settings(bContext *C, wmOperator *op)
|
|
{
|
|
OSErr err = noErr;
|
|
Scene *scene = CTX_data_scene(C);
|
|
RenderData *rd = &scene->r;
|
|
|
|
/* 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, from qtatom or detailed settings */
|
|
if (rd->qtcodecdata && rd->qtcodecdata->cdParms) {
|
|
QT_GetCodecSettingsFromScene(rd, op->reports);
|
|
}
|
|
else {
|
|
SCGetInfo(qtdata->theComponent, scDataRateSettingsType, &qtdata->aDataRateSetting);
|
|
SCGetInfo(qtdata->theComponent, scSpatialSettingsType, &qtdata->gSpatialSettings);
|
|
SCGetInfo(qtdata->theComponent, scTemporalSettingsType, &qtdata->gTemporalSettings);
|
|
|
|
qtdata->gSpatialSettings.codecType = rd->qtcodecsettings.codecType;
|
|
qtdata->gSpatialSettings.codec = (CodecComponent)rd->qtcodecsettings.codec;
|
|
qtdata->gSpatialSettings.spatialQuality = (rd->qtcodecsettings.codecSpatialQuality * codecLosslessQuality) / 100;
|
|
qtdata->gTemporalSettings.temporalQuality = (rd->qtcodecsettings.codecTemporalQuality * codecLosslessQuality) / 100;
|
|
qtdata->gTemporalSettings.keyFrameRate = rd->qtcodecsettings.keyFrameRate;
|
|
qtdata->gTemporalSettings.frameRate = ((float)(rd->frs_sec << 16) / rd->frs_sec_base);
|
|
qtdata->aDataRateSetting.dataRate = rd->qtcodecsettings.bitRate;
|
|
qtdata->gSpatialSettings.depth = rd->qtcodecsettings.colorDepth;
|
|
qtdata->aDataRateSetting.minSpatialQuality = (rd->qtcodecsettings.minSpatialQuality * codecLosslessQuality) / 100;
|
|
qtdata->aDataRateSetting.minTemporalQuality = (rd->qtcodecsettings.minTemporalQuality * codecLosslessQuality) / 100;
|
|
|
|
qtdata->aDataRateSetting.frameDuration = rd->frs_sec;
|
|
|
|
err = SCSetInfo(qtdata->theComponent, scTemporalSettingsType, &qtdata->gTemporalSettings);
|
|
CheckError(err, "SCSetInfo1 error", op->reports);
|
|
err = SCSetInfo(qtdata->theComponent, scSpatialSettingsType, &qtdata->gSpatialSettings);
|
|
CheckError(err, "SCSetInfo2 error", op->reports);
|
|
err = SCSetInfo(qtdata->theComponent, scDataRateSettingsType, &qtdata->aDataRateSetting);
|
|
CheckError(err, "SCSetInfo3 error", op->reports);
|
|
}
|
|
/* put up the dialog box - it needs to be called from the main thread */
|
|
err = SCRequestSequenceSettings(qtdata->theComponent);
|
|
|
|
if (err == scUserCancelled) {
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
/* 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);
|
|
|
|
|
|
/* Fill the render QuicktimeCodecSettings struct */
|
|
rd->qtcodecsettings.codecTemporalQuality = (qtdata->gTemporalSettings.temporalQuality * 100) / codecLosslessQuality;
|
|
/* Do not override scene frame rate (qtdata->gTemporalSettings.framerate) */
|
|
rd->qtcodecsettings.keyFrameRate = qtdata->gTemporalSettings.keyFrameRate;
|
|
|
|
rd->qtcodecsettings.codecType = qtdata->gSpatialSettings.codecType;
|
|
rd->qtcodecsettings.codec = (int)qtdata->gSpatialSettings.codec;
|
|
rd->qtcodecsettings.colorDepth = qtdata->gSpatialSettings.depth;
|
|
rd->qtcodecsettings.codecSpatialQuality = (qtdata->gSpatialSettings.spatialQuality * 100) / codecLosslessQuality;
|
|
|
|
rd->qtcodecsettings.bitRate = qtdata->aDataRateSetting.dataRate;
|
|
rd->qtcodecsettings.minSpatialQuality = (qtdata->aDataRateSetting.minSpatialQuality * 100) / codecLosslessQuality;
|
|
rd->qtcodecsettings.minTemporalQuality = (qtdata->aDataRateSetting.minTemporalQuality * 100) / codecLosslessQuality;
|
|
/* Frame duration is already known (qtdata->aDataRateSetting.frameDuration) */
|
|
|
|
QT_SaveCodecSettingsToScene(rd, op->reports);
|
|
|
|
/* 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.0;
|
|
}
|
|
else {
|
|
/* we do our very best... */
|
|
rd->frs_sec = fps / 65536;
|
|
rd->frs_sec_base = 1.0;
|
|
}
|
|
}
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static int ED_operator_setqtcodec(bContext *C)
|
|
{
|
|
return G.have_quicktime != FALSE;
|
|
}
|
|
|
|
#if defined(__APPLE__) && defined(GHOST_COCOA)
|
|
/* Need to set up a Cocoa NSAutoReleasePool to avoid memory leak
|
|
* And it must be done in an objC file, so use a GHOST_SystemCocoa.mm function for that */
|
|
extern int cocoa_request_qtcodec_settings(bContext *C, wmOperator *op);
|
|
|
|
int fromcocoa_request_qtcodec_settings(bContext *C, wmOperator *op)
|
|
{
|
|
return request_qtcodec_settings(C, op);
|
|
}
|
|
#endif
|
|
|
|
|
|
void SCENE_OT_render_data_set_quicktime_codec(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Change Codec";
|
|
ot->description = "Change Quicktime codec Settings";
|
|
ot->idname = "SCENE_OT_render_data_set_quicktime_codec";
|
|
|
|
/* api callbacks */
|
|
#if defined(__APPLE__) && defined(GHOST_COCOA)
|
|
ot->exec = cocoa_request_qtcodec_settings;
|
|
#else
|
|
ot->exec = request_qtcodec_settings;
|
|
#endif
|
|
ot->poll = ED_operator_setqtcodec;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
#endif /* USE_QTKIT */
|
|
#endif /* _WIN32 || __APPLE__ */
|
|
#endif /* WITH_QUICKTIME */
|
|
|