Blob Blame Raw


#ifndef __LP64__

#include <iostream>

#include "texception.h"
#include "tsound.h"
#include "tconvert.h"
#include "tpropertytype.h"
#include "timageinfo.h"
#include "trasterimage.h"
#include "tmachine.h"
#include "tsystem.h"

#include "movsettings.h"

#include "tiio_movM.h"

/* QuickDraw は 10.7 以降なくなった */
//#define HAS_QUICKDRAW

namespace
{

const string firstFrameKey = "frst";
const int firstFrameKeySize = 4 * sizeof(char);

//------------------------------------------------------------------------------

class QuickTimeCleanUp;
class QuickTimeStuff
{
public:
	static QuickTimeStuff *instance()
	{
		if (!m_singleton)
			m_singleton = new QuickTimeStuff();
		return m_singleton;
	}
	OSErr getStatus() { return m_status; }
	~QuickTimeStuff()
	{
	}

private:
	QuickTimeStuff() : m_status(noErr)
	{
		m_status = noErr;
		EnterMovies();
	}

	static QuickTimeStuff *m_singleton;

	OSErr m_status;
	friend class QuickTimeCleanUp; //questa DEVE essere friend, cosi' posso controllare direttamente
								   //lo stato del singleton.
};

class QuickTimeCleanUp
{
public:
	QuickTimeCleanUp() {}
	~QuickTimeCleanUp()
	{ /*
                        Nel caso si arrivasse qui senza il singleton instanziato, e si facesse direttamente
                        'delete QuickTimeStuff::instance();' Quicktime non farebbe in tempo a terminare le
                        sue routine di inizializzazione (che sono ANCHE su altri thread) e la chiamata a
                        TerminateQTML() causerebbe un crash
                        */
		//std::cout << "destroing variable... Thread = " << GetCurrentThreadId() << std::endl;

		//              std::cout <<"~QTCleanup"<< std::endl;
		if (QuickTimeStuff::m_singleton)
			delete QuickTimeStuff::m_singleton;
	}
};

QuickTimeCleanUp cleanUp;
QuickTimeStuff *QuickTimeStuff::m_singleton = 0;

//------------------------------------------------------------------------------

enum QTLibError {
	QTNoError = 0x0000,
	QTNotInstalled,
	QTUnknownError,
	QTUnableToOpenFile,
	QTCantCreateFile,
	QTUnableToGetCompressorNames,
	QTUnableToCreateResource,
	QTUnableToUpdateMovie,
	QTBadTimeValue,
	QTUnableToDoMovieTask,
	QTUnableToSetTimeValue,
	QTUnableToSetMovieGWorld,
	QTUnableToSetMovieBox,
};

} //namespace

////-----------------------------------------------------------
//
//CodecType Tiio::MovWriterProperties::getCurrentCodec()const
//{
//std::map<wstring, CodecType>::const_iterator it;
//
//it = m_codecMap.find(m_codec.getValue());
//assert(it!=m_codecMap.end());
//return it->second;
//}
//
////-----------------------------------------------------------
//
//wstring Tiio::MovWriterProperties::getCurrentNameFromCodec(CodecType &cCodec)const
//{
//std::map<wstring, CodecType>::const_iterator it;
//
//for(it=m_codecMap.begin();it!=m_codecMap.end();++it)
//{
////  CodecType tmp=it->second;
//  if(it->second==cCodec) break;
//}
//assert(it!=m_codecMap.end());
//return it->first;
//}
//
////-----------------------------------------------------------
//
//wstring Tiio::MovWriterProperties::getCurrentQualityFromCodeQ(CodecQ &cCodecQ)const
//{
//std::map<wstring, CodecQ>::const_iterator it;
//
//for(it=m_qualityMap.begin();it!=m_qualityMap.end();++it)
//{
////  CodecQ tmp=it->second;
//  if(it->second==cCodecQ) break;
//}
//assert(it!=m_qualityMap.end());
//return it->first;
//}
//
////-----------------------------------------------------------
//
//CodecQ Tiio::MovWriterProperties::getCurrentQuality() const
//{
//std::map<wstring, CodecQ>::const_iterator it;
//
//it = m_qualityMap.find(m_quality.getValue());
//assert(it!=m_qualityMap.end());
//return it->second;
//}
//
////-----------------------------------------------------------
//
//
//TPropertyGroup* Tiio::MovWriterProperties::clone() const
//{
//  MovWriterProperties*g = new MovWriterProperties();
//
//	g->m_codec.setValue(m_codec.getValue());
//	g->m_quality.setValue(m_quality.getValue());
//
//return (TPropertyGroup*)g;
//}

Tiio::MovWriterProperties::MovWriterProperties()
//: m_codec("Codec")
//, m_quality("Quality")
{
	if (QuickTimeStuff::instance()->getStatus() != noErr) {
		return;
	}

	//if (InitializeQTML(0)!=noErr)
	//    return;

	ComponentInstance ci = OpenDefaultComponent(StandardCompressionType, StandardCompressionSubType);
	QTAtomContainer settings;

	if (SCGetSettingsAsAtomContainer(ci, &settings) != noErr)
		assert(false);

	fromAtomsToProperties(settings, *this);

	/*
CodecNameSpecListPtr codecList;
if (GetCodecNameList(&codecList, 0) !=noErr) return;

TIntEnumeration codecNames;
for (short i = 0; i < codecList->count; ++i)
  {
  CodecNameSpec *codecNameSpec = &(codecList->list[i]);
  // codecNameSpec->typeName contains the name of the codec in Pascal String Format
  // byte 0 = Len
  // byte [1..Len] data
  UCHAR len = codecNameSpec->typeName[0];
  string name((char*)&codecNameSpec->typeName[1], len);
	m_codec.addValue(toWideString(name));
	if (i==0) m_codec.setValue(toWideString(name));
	m_codecMap[toWideString(name)] = codecNameSpec->cType;

  }

DisposeCodecNameList(codecList);

TIntEnumeration  codecQuality;

m_quality.addValue(L"min quality"); 
      m_qualityMap[L"min quality"] = codecMinQuality;
m_quality.addValue(L"low quality");
      m_qualityMap[L"low quality"] = codecLowQuality;
m_quality.addValue(L"normal quality");
      m_qualityMap[L"normal quality"] = codecNormalQuality;
m_quality.addValue(L"high quality");
      m_qualityMap[L"high quality"] = codecHighQuality;
m_quality.addValue(L"max quality");
      m_qualityMap[L"max quality"] = codecMaxQuality;
m_quality.addValue(L"lossless quality");
      m_qualityMap[L"lossless quality"] = codecLosslessQuality;

m_quality.setValue(L"normal quality");

bind(m_codec);
bind(m_quality);
*/
	/*
m_qualityTable.insert(QualityTable::value_type(q++, codecMinQuality));
m_qualityTable.insert(QualityTable::value_type(q++, codecLowQuality));
m_qualityTable.insert(QualityTable::value_type(q++, codecMaxQuality));
m_qualityTable.insert(QualityTable::value_type(q++, codecNormalQuality));
m_qualityTable.insert(QualityTable::value_type(q++, codecHighQuality));
m_qualityTable.insert(QualityTable::value_type(q++, codecLosslessQuality));

addProperty(CodecQualityId, codecQuality,0);*/
}

namespace
{

string buildQTErrorString(int ec)
{
	switch (ec) {
	case QTNotInstalled:
		return "Can't create; ensure that quicktime is correctly installed on your machine";
	case QTUnknownError:
		return "Unknown error";
	case QTUnableToOpenFile:
		return "can't open file";
	case QTCantCreateFile:
		return "can't create movie";
	case QTUnableToGetCompressorNames:
		return "unable to get compressor name";
	case QTUnableToCreateResource:
		return "can't create resource";
	case QTUnableToUpdateMovie:
		return "unable to update movie";
	case QTBadTimeValue:
		return "bad frame number";
	case QTUnableToDoMovieTask:
		return "unable to do movie task";
	case QTUnableToSetTimeValue:
		return "unable to set time value";
	case QTUnableToSetMovieGWorld:
		return "unable to set movie graphic world";
	case QTUnableToSetMovieBox:
		return "unable to set movie box";

	default: {
		return "unknown error ('" + toString(ec) + "')";
	}
	}
}

//-----------------------------------------------------------
#ifdef WIN2
inline LPSTR AtlW2AHelper(LPSTR lpa, LPCWSTR lpw, int nChars, UINT acp)
{
	assert(lpw != NULL);
	assert(lpa != NULL);
	// verify that no illegal character present
	// since lpa was allocated based on the size of lpw
	// don't worry about the number of chars
	lpa[0] = '\0';
	WideCharToMultiByte(acp, 0, lpw, -1, lpa, nChars, NULL, NULL);
	return lpa;
}
#endif
//-----------------------------------------------------------

string long2fourchar(TINT32 fcc)
{
	string s;
	s += (char((fcc & 0xff000000) >> 24));
	s += (char((fcc & 0x00ff0000) >> 16));
	s += (char((fcc & 0x0000ff00) >> 8));
	s += (char((fcc & 0x000000ff) >> 0));

	return s;
}

//-------------------------------------------------------------------------------

TimeScale frameRateToTimeScale(double frameRate)
{
	return tround(frameRate * 100.0);
}

//-------------------------------------------------------------------------------

const string CodecNamesId = "PU_CodecName";
const string CodecQualityId = "PU_CodecQuality";

} //namespace

//------------------------------------------------------------------------------

bool IsQuickTimeInstalled()
{
	return QuickTimeStuff::instance()->getStatus() == noErr;
}

//------------------------------------------------------------------------------
//  TImageWriterMov
//------------------------------------------------------------------------------

class TImageWriterMov : public TImageWriter
{

public:
	TImageWriterMov(const TFilePath &, int frameIndex, TLevelWriterMov *);
	~TImageWriterMov() { m_lwm->release(); }
	bool is64bitOutputSupported() { return false; }

private:
	//not implemented
	TImageWriterMov(const TImageWriterMov &);
	TImageWriterMov &operator=(const TImageWriterMov &src);

public:
	void save(const TImageP &);
	int m_frameIndex;

private:
	TLevelWriterMov *m_lwm;
};

//-----------------------------------------------------------
//  TImageReaderMov
//-----------------------------------------------------------
class TImageReaderMov : public TImageReader
{

public:
	TImageReaderMov(const TFilePath &, int frameIndex, TLevelReaderMov *);
	~TImageReaderMov() { m_lrm->release(); }

private:
	//not implemented
	TImageReaderMov(const TImageReaderMov &);
	TImageReaderMov &operator=(const TImageReaderMov &src);

public:
	TImageP load();
	void load(const TRasterP &rasP, const TPoint &pos = TPoint(0, 0), int shrinkX = 1, int shrinkY = 1);
	int m_frameIndex;

	TDimension getSize() const { return m_lrm->getSize(); }
	TRect getBBox() const { return m_lrm->getBBox(); }

private:
	TLevelReaderMov *m_lrm;
};

//-----------------------------------------------------------
//-----------------------------------------------------------
//        TImageWriterMov
//-----------------------------------------------------------

TImageWriterMov::TImageWriterMov(const TFilePath &path, int frameIndex, TLevelWriterMov *lwm)
	: TImageWriter(path), m_lwm(lwm), m_frameIndex(frameIndex)
{
	m_lwm->addRef();
}

//----------
namespace
{

void copy(TRasterP rin, PixelXRGB *bufout, int lx, int ly, unsigned short rowbytes = 0)
{
	rin->lock();
	TRaster32P rin32 = rin;
	assert(rin32);
	int lineOffset = (rowbytes == 0) ? lx : rowbytes / sizeof(PixelXRGB); //in pixelsize, cioe 4 bytes

	PixelXRGB *rowout = &(bufout[(ly - 1) * lineOffset]) /*-startOffset*/;
	for (int y = 0; y < rin32->getLy(); y++) {
		PixelXRGB *pixout = rowout;
		TPixelRGBM32 *pixin = rin32->pixels(y);
		TPixelRGBM32 *pixinEnd = pixin + rin32->getLx();
		while (pixin < pixinEnd) {
			pixout->x = pixin->m;
			pixout->r = pixin->r;
			pixout->g = pixin->g;
			pixout->b = pixin->b;
			pixout++;
			pixin++;
		}
		rowout -= lineOffset;
	}
	rin->unlock();
} //end function
};
//-----------------------------------------------------------
/*
TWriterInfo *TWriterInfoMov::create(const string &)
{
return new TWriterInfoMov();
}

//-----------------------------------------------------------

TWriterInfoMov::TWriterInfoMov(const TWriterInfoMov&src)
               :TWriterInfo(src)
               ,m_codecTable(src.m_codecTable)
               ,m_qualityTable(src.m_qualityTable)
{
}

//-----------------------------------------------------------

TWriterInfo *TWriterInfoMov::clone() const
{
return new TWriterInfoMov(*this);
}

//-----------------------------------------------------------

TWriterInfoMov::TWriterInfoMov() 
               :TWriterInfo()
{
if (QuickTimeStuff::instance()->getStatus()!=noErr)
  {
  return;
  }

CodecNameSpecListPtr codecList;

if (GetCodecNameList(&codecList, 0) !=noErr)
  {
  return;
  }

TIntEnumeration codecNames;
for (short i = 0; i < codecList->count; ++i)
  {
  CodecNameSpec *codecNameSpec = &(codecList->List[i]);
  // codecNameSpec->typeName contains the name of the codec in Pascal String Format
  // byte 0 = Len
  // byte [1..Len] data
  UCHAR len = codecNameSpec->typeName[0];
  string name((char*)&codecNameSpec->typeName[1], len);
  
  codecNames.addItem(name, i);

  m_codecTable.insert(CodecTable::value_type(i, codecNameSpec->cType));
  }

DisposeCodecNameList(codecList);

addProperty(CodecNamesId, codecNames, 0);
int q = 0;

TIntEnumeration  codecQuality;

codecQuality.addItem("min quality",q); 
m_qualityTable.insert(QualityTable::value_type(q++, codecMinQuality));

codecQuality.addItem("low quality",q);
m_qualityTable.insert(QualityTable::value_type(q++, codecLowQuality));

codecQuality.addItem("max quality",q);
m_qualityTable.insert(QualityTable::value_type(q++, codecMaxQuality));

codecQuality.addItem("normal quality",q);
m_qualityTable.insert(QualityTable::value_type(q++, codecNormalQuality));

codecQuality.addItem("high quality",q);
m_qualityTable.insert(QualityTable::value_type(q++, codecHighQuality));

codecQuality.addItem("lossless quality",q);
m_qualityTable.insert(QualityTable::value_type(q++, codecLosslessQuality));

addProperty(CodecQualityId, codecQuality,0);

}

TWriterInfoMov::~TWriterInfoMov()
{}

*/
//-----------------------------------------------------------

void TImageWriterMov::save(const TImageP &img)
{
	m_lwm->save(img, m_frameIndex);
}

//-----------------------------------------------------------

namespace
{
bool getFSRefFromPosixPath(const char *filepath, FSRef *fileRef, bool writing)
{
	const UInt8 *filename = (const UInt8 *)filepath;
	if (writing) {
		FILE *fp = fopen(filepath, "wb");
		if (!fp)
			return false;
		fclose(fp);
	}
	OSErr err = FSPathMakeRef(filename, fileRef, 0);
	return err == noErr;
}

bool getFSSpecFromPosixPath(const char *filepath, FSSpec *fileSpec, bool writing)
{
	FSRef fileRef;

	if (!getFSRefFromPosixPath(filepath, &fileRef, writing))
		return false;
	OSErr err = FSGetCatalogInfo(&fileRef, kFSCatInfoNone, 0, 0, fileSpec, 0);
	return err == noErr;
}
}

void TLevelWriterMov::save(const TImageP &img, int frameIndex)
{
	m_firstFrame = tmin(frameIndex, m_firstFrame);

	TRasterImageP image(img);
	if (!image)
		throw TImageException(getFilePath(), "Unsupported image type");

	int lx = image->getRaster()->getLx();
	int ly = image->getRaster()->getLy();
	//void *buffer = image->getRaster()->getRawData();

	int pixSize = image->getRaster()->getPixelSize();
	if (pixSize != 4)
		throw TImageException(getFilePath(), "Unsupported pixel type");

	QMutexLocker sl(&m_mutex);
	if (!m_properties)
		m_properties = new Tiio::MovWriterProperties();

	Tiio::MovWriterProperties *prop = (Tiio::MovWriterProperties *)(m_properties);

	/*
const char* codec = toString(prop->m_codec.getValue()).c_str();
const char* qual = toString(prop->m_quality.getValue()).c_str();
*/

	//CodecType codecType;
	//try {
	//codecType = prop->getCurrentCodec();
	//  } catch (...)//boost::bad_any_cast&)
	//    {
	//    throw TImageException(m_path, "bad codec name");
	//    }
	//
	//CodecQ quality = prop->getCurrentQuality();
	QDErr err;

	if (m_videoTrack == 0) {
		QTAtomContainer atoms;
		QTNewAtomContainer(&atoms);
		fromPropertiesToAtoms(*prop, atoms);
		m_ci = OpenDefaultComponent(StandardCompressionType, StandardCompressionSubType);
		if (SCSetSettingsFromAtomContainer(m_ci, atoms) != noErr) {
			CloseComponent(m_ci);
			assert(!"cannot use that quickTime codec, use default");
			m_ci = OpenDefaultComponent(StandardCompressionType, StandardCompressionSubType);
		}

		QTDisposeAtomContainer(atoms);

		//FSSpec fspec;
		Rect frame;
		long max_compressed_size;

		if (QuickTimeStuff::instance()->getStatus() != noErr) {
			m_IOError = QTNotInstalled;
			throw TImageException(m_path, buildQTErrorString(m_IOError));
		}

		// First, set the movie's time scale to match a suitable multiple of m_frameRate.
		// The timeScale will be used for both the movie and media.
		TimeScale timeScale = ::frameRateToTimeScale(m_frameRate);
		SetMovieTimeScale(m_movie, timeScale);

		m_videoTrack = NewMovieTrack(m_movie, FixRatio((short)lx, 1), FixRatio((short)ly, 1), kNoVolume);

		if ((err = GetMoviesError() != noErr))
			throw TImageException(getFilePath(), "can't create video track");

		m_videoMedia = NewTrackMedia(m_videoTrack, VideoMediaType, timeScale, 0, 0);

		if ((err = GetMoviesError() != noErr))
			throw TImageException(getFilePath(), "can't create video media");

		frame.left = 0;
		frame.top = 0;
		frame.right = lx;
		frame.bottom = ly;

#if QT_VERSION >= 0x050000
		if ((err = QTNewGWorld(&(m_gworld), pixSize * 8, &frame, 0, 0, 0)) != noErr)
#else
		if ((err = NewGWorld(&(m_gworld), pixSize * 8, &frame, 0, 0, 0)) != noErr)
#endif
			throw TImageException(getFilePath(), "can't create movie buffer");

		//#ifdef WIN32
		//  LockPixels(m_gworld->portPixMap);
		//  if ((err = GetMaxCompressionSize(m_gworld->portPixMap, &frame, 0,
		//                                quality,  compression,anyCodec,
		//                         	 &max_compressed_size))!=noErr)
		//    throw TImageException(getFilePath(), "can't get max compression size");
		//
		//#else
		//  PixMapHandle pixmapH = GetPortPixMap (m_gworld);
		//
		//  LockPixels(pixmapH);
		//  if ((err = GetMaxCompressionSize(pixmapH, &frame, 0,
		//                                quality,  codecType,anyCodec,
		//				 &max_compressed_size))!=noErr)
		//    throw TImageException(getFilePath(), "can't get max compression size");
		//
		//#endif

		//m_compressedData = NewHandle(max_compressed_size);

		if ((err = MemError()) != noErr)
			throw TImageException(getFilePath(), "can't allocate compressed data for movie");

		//MoveHHi(m_compressedData);
		//HLock(m_compressedData);

		if ((err = MemError()) != noErr)
			throw TImageException(getFilePath(), "can't allocate img handle");

/* FIXME: QuickDraw とともに無くなったようだ */
#if defined(HAS_QUICKDRAW)
		m_pixmap = GetGWorldPixMap(m_gworld);

		if (!LockPixels(m_pixmap))
			throw TImageException(getFilePath(), "can't lock pixels");

		buf = (PixelXRGB *)GetPixBaseAddr(m_pixmap);
#endif
		buf_lx = lx;
		buf_ly = ly;
	}

	if ((err = BeginMediaEdits(m_videoMedia)) != noErr)
		throw TImageException(getFilePath(), "can't begin edit video media");

	unsigned short rowBytes = (unsigned short)(((short)(*(m_pixmap))->rowBytes & ~(3 << 14)));

	Rect frame;
	ImageDescriptionHandle img_descr = 0;
	Handle compressedData = 0;

	frame.left = 0;
	frame.top = 0;
	frame.right = lx;
	frame.bottom = ly;

	TRasterP ras = image->getRaster();

#ifdef WIN32
	compressed_data_ptr = StripAddress(*(m_compressedData));
	copy(ras, buf, buf_lx, buf_ly);
#else
	//compressed_data_ptr  = *m_compressedData;
	copy(ras, buf, buf_lx, buf_ly, rowBytes);
#endif

//img_descr = (ImageDescriptionHandle)NewHandle(4);

#ifdef WIN32
	if ((err = CompressImage(m_gworld->portPixMap,
							 &frame,
							 quality, compression,
							 img_descr, compressed_data_ptr)) != noErr)
		throw TImageException(getFilePath(), "can't compress image");
#else

#if defined(HAS_QUICKDRAW)
	PixMapHandle pixmapH = GetPortPixMap(m_gworld);

	if ((err = SCCompressImage(
			 m_ci,
			 pixmapH,
			 &frame,
			 &img_descr,
			 &compressedData)) != noErr)
		throw TImageException(getFilePath(), "can't compress image");
#endif

// if ((err = CompressImage(pixmapH,
//	                 &frame,
//  			 quality, codecType,
//			 img_descr, compressed_data_ptr))!=noErr)
//  throw TImageException(getFilePath(), "can't compress image");

#endif

	TimeValue sampleTime;
	if ((err = AddMediaSample(m_videoMedia, compressedData, 0,
							  (*img_descr)->dataSize, 100,
							  (SampleDescriptionHandle)img_descr,
							  1, 0, &sampleTime)) != noErr)
		throw TImageException(getFilePath(), "can't add image to movie media");

	if ((err = EndMediaEdits(m_videoMedia)) != noErr)
		throw TImageException(getFilePath(), "can't end edit media");

	DisposeHandle(compressedData);

	DisposeHandle((Handle)img_descr);

	m_savedFrames.push_back(std::pair<int, TimeValue>(frameIndex, sampleTime));
}

//-----------------------------------------------------------
//        TLevelWriterMov
//-----------------------------------------------------------

TLevelWriterMov::TLevelWriterMov(const TFilePath &path, TPropertyGroup *winfo)
	: TLevelWriter(path, winfo), m_IOError(QTNoError), m_pixmap(0)
	  //,m_compressedData(0)
	  ,
	  m_gworld(0), m_videoMedia(0), m_videoTrack(0), m_soundMedia(0), m_soundTrack(0), m_movie(0), m_firstFrame((std::numeric_limits<int>::max)())
{

	m_frameRate = 12.;
	QDErr err;
	FSSpec fspec;
	if (QuickTimeStuff::instance()->getStatus() != noErr) {
		m_IOError = QTNotInstalled;
		throw TImageException(m_path, buildQTErrorString(m_IOError));
	}

	//  const char *pStr = toString(m_path.getWideString()).c_str();
	QString qStr = QString::fromStdWString(m_path.getWideString());
	const char *pStr = qStr.toUtf8().data();

	//   std::cout << "TLevelWriterMov costruttore FP: " << toString(m_path.getWideString()) << std::endl;
	//   std::cout << "TLevelWriterMov costruttore char: " << pStr << std::endl;

	if (!getFSSpecFromPosixPath(pStr, &fspec, true)) {
		m_IOError = QTUnableToOpenFile;
		throw TImageException(m_path, buildQTErrorString(m_IOError));
	}

	// Fix: in alcuni rari casi non veniva il mov preesistente
	if (TSystem::doesExistFileOrLevel(m_path))
		TSystem::deleteFile(m_path);

	if ((err = CreateMovieFile(&fspec, 'TVOD', smCurrentScript,
							   createMovieFileDeleteCurFile | createMovieFileDontCreateResFile,
							   &(m_refNum), &(m_movie)) != noErr)) {
		m_IOError = QTCantCreateFile;
		throw TImageException(m_path, buildQTErrorString(m_IOError));
	}
}

//-----------------------------------------------------------

#ifdef _DEBUG
#define FailIf(cond, handler)                                      \
	if (cond) {                                                    \
		/*		DebugStr((ConstStr255Param)#cond " goto " #handler);*/ \
		goto handler;                                              \
	}
#else
#define FailIf(cond, handler) \
	if (cond) {               \
		goto handler;         \
	}
#endif

#ifdef _DEBUG
#define C(cond, action, handler)                                   \
	if (cond) {                                                    \
		/*		DebugStr((ConstStr255Param)#cond " goto " #handler);*/ \
		{                                                          \
			action;                                                \
		}                                                          \
		goto handler;                                              \
	}
#else
#define FailWithAction(cond, action, handler) \
	if (cond) {                               \
		{                                     \
			action;                           \
		}                                     \
		goto handler;                         \
	}
#endif

void TLevelWriterMov::saveSoundTrack(TSoundTrack *st)
{

	OSErr myErr = noErr;

	//int err;

	if (!st)
		throw TException("null reference to soundtrack");

	if (st->getBitPerSample() != 16) {
		throw TImageException(m_path, "Only 16 bits per sample is supported");
	}

	if (m_soundTrack == 0) {
		m_soundTrack = NewMovieTrack(m_movie, 0, 0, kFullVolume);
		myErr = GetMoviesError();

		FailIf(myErr != noErr, CompressErr);

		m_soundMedia = NewTrackMedia(m_soundTrack, SoundMediaType, st->getSampleRate(), NULL, 0); //track->rate >> 16
		myErr = GetMoviesError();
		FailIf(myErr != noErr, Exit);
	}

	SoundDescriptionV1Handle mySampleDesc;
	Handle myDestHandle;
	SoundComponentData sourceInfo;
	SoundComponentData destInfo;
	SoundConverter converter;
	CompressionInfo compressionInfo;

	myDestHandle = NewHandle(0);
	FailWithAction(myDestHandle == NULL, myErr = MemError(), NoDest);

	*myDestHandle = (char *)st->getRawData();

	// start a media editing session
	myErr = BeginMediaEdits(m_soundMedia);
	FailIf(myErr != noErr, Exit);

	sourceInfo.flags = 0x0;
	sourceInfo.format = kSoundNotCompressed;
	sourceInfo.numChannels = st->getChannelCount();
	sourceInfo.sampleSize = st->getBitPerSample();
	sourceInfo.sampleRate = st->getSampleRate();
	sourceInfo.sampleCount = st->getSampleCount();
	sourceInfo.buffer = (unsigned char *)st->getRawData();
	sourceInfo.reserved = 0x0;

	destInfo.flags = kNoSampleRateConversion | kNoSampleSizeConversion |
					 kNoSampleFormatConversion | kNoChannelConversion |
					 kNoDecompression | kNoVolumeConversion |
					 kNoRealtimeProcessing;

	destInfo.format = k16BitNativeEndianFormat;

	destInfo.numChannels = st->getChannelCount();
	destInfo.sampleSize = st->getBitPerSample();
	destInfo.sampleRate = st->getSampleRate();
	destInfo.sampleCount = st->getSampleCount();
	destInfo.buffer = (unsigned char *)st->getRawData();
	destInfo.reserved = 0x0;

	SoundConverterOpen(&sourceInfo, &destInfo, &converter);

	myErr = SoundConverterGetInfo(converter, siCompressionFactor, &compressionInfo);

	myErr = SoundConverterGetInfo(converter, siCompressionFactor, &compressionInfo);
	myErr = GetCompressionInfo(fixedCompression, sourceInfo.format, sourceInfo.numChannels, sourceInfo.sampleSize, &compressionInfo);
	FailIf(myErr != noErr, ConverterErr);

	compressionInfo.bytesPerFrame = compressionInfo.bytesPerPacket * destInfo.numChannels;

	//////////
	//
	// create a sound sample description
	//
	//////////

	// use the SoundDescription format 1 because it adds fields for data size information
	// and is required by AddSoundDescriptionExtension if an extension is required for the compression format

	mySampleDesc = (SoundDescriptionV1Handle)NewHandleClear(sizeof(SoundDescriptionV1));
	FailWithAction(myErr != noErr, myErr = MemError(), Exit);

	(**mySampleDesc).desc.descSize = sizeof(SoundDescriptionV1);
	(**mySampleDesc).desc.dataFormat = destInfo.format;
	(**mySampleDesc).desc.resvd1 = 0;
	(**mySampleDesc).desc.resvd2 = 0;
	(**mySampleDesc).desc.dataRefIndex = 1;
	(**mySampleDesc).desc.version = 1;
	(**mySampleDesc).desc.revlevel = 0;
	(**mySampleDesc).desc.vendor = 0;
	(**mySampleDesc).desc.numChannels = destInfo.numChannels;
	(**mySampleDesc).desc.sampleSize = destInfo.sampleSize;
	(**mySampleDesc).desc.compressionID = 0;
	(**mySampleDesc).desc.packetSize = 0;
	(**mySampleDesc).desc.sampleRate = st->getSampleRate() << 16;
	(**mySampleDesc).samplesPerPacket = compressionInfo.samplesPerPacket;
	(**mySampleDesc).bytesPerPacket = compressionInfo.bytesPerPacket;
	(**mySampleDesc).bytesPerFrame = compressionInfo.bytesPerFrame;
	(**mySampleDesc).bytesPerSample = compressionInfo.bytesPerSample;

	//////////
	//
	// add samples to the media
	//
	//////////

	/*if ((err = AddMediaSample(m_videoMedia, compressedData,  0, 
	                        (*img_descr)->dataSize, 1, 
												  (SampleDescriptionHandle)img_descr, 
													1, 0, 0))!=noErr)
  throw TImageException(getFilePath(), "can't add image to movie media");*/

	myErr = AddMediaSample(m_soundMedia, myDestHandle,
						   0,
						   destInfo.sampleCount * compressionInfo.bytesPerFrame,
						   1,
						   (SampleDescriptionHandle)mySampleDesc,
						   destInfo.sampleCount * compressionInfo.samplesPerPacket,
						   0,
						   NULL);
	FailIf(myErr != noErr, MediaErr);

	myErr = EndMediaEdits(m_soundMedia);
	FailIf(myErr != noErr, MediaErr);

	//////////
	//
	// insert the media into the track
	//
	//////////

	goto Done;

ConverterErr:
NoDest:
CompressErr:
Exit:

Done:

MediaErr:
	if (mySampleDesc != NULL)
		DisposeHandle((Handle)mySampleDesc);

	if (converter)
		SoundConverterClose(converter);

	if (myErr != noErr)
		throw TImageException(m_path, "error saving audio track");
}

//-----------------------------------------------------------

TLevelWriterMov::~TLevelWriterMov()
{
/* FIXME: */
#if defined(HAS_QUICKDRAW)
	if (m_pixmap)
		UnlockPixels(m_pixmap);
	if (m_gworld)
		DisposeGWorld(m_gworld);
#endif
	if (m_ci)
		CloseComponent(m_ci);

	QDErr err;
	OSStatus mderr;

	if (m_videoTrack) {
		//Insert the saved Frames into the track. Holes in the frames sequence will be intended as still
		//frames.
		TimeValue movieTimeScale = GetMovieTimeScale(m_movie);
		int savedFramesSize = (int)m_savedFrames.size() - 1;

		int i;
		for (i = 0; i < savedFramesSize; ++i) {
			TimeValue mediaPosition = m_savedFrames[i].second;
			TimeValue trackPosition = ((m_savedFrames[i].first - m_firstFrame) * movieTimeScale) / m_frameRate;

			TimeValue duration = m_savedFrames[i + 1].first - m_savedFrames[i].first;
			Fixed ratio = tround(fixed1 / (double)duration);

			if ((err =
					 InsertMediaIntoTrack(m_videoTrack, trackPosition, mediaPosition, 100, ratio)) != noErr)
				throw TImageException(getFilePath(), "can't insert media into track");
		}

		if (savedFramesSize >= 0) {
			TimeValue mediaPosition = m_savedFrames[i].second;
			TimeValue trackPosition = ((m_savedFrames[i].first - m_firstFrame) * movieTimeScale) / m_frameRate;

			// The last frame has duration 1 (frame):
			//   duration = 1  =>  ratio = fixed1

			if ((err =
					 InsertMediaIntoTrack(m_videoTrack, trackPosition, mediaPosition, 100, fixed1)) != noErr)
				throw TImageException(getFilePath(), "can't insert media into track");
		}

		QTMetaDataRef metaDataRef;
		if ((mderr = QTCopyMovieMetaData(m_movie, &metaDataRef)) != noErr)
			throw TImageException(getFilePath(), "can't access metadata informations");

		if ((mderr =
				 QTMetaDataAddItem(
					 metaDataRef,
					 kQTMetaDataStorageFormatUserData,
					 kQTMetaDataKeyFormatUserData,
					 (const UInt8 *)firstFrameKey.c_str(),
					 firstFrameKeySize,
					 (const UInt8 *)(&m_firstFrame),
					 sizeof(int),
					 kQTMetaDataTypeUnsignedIntegerBE,
					 0)) != noErr)
			throw TImageException(getFilePath(), "can't insert metadata informations");

		QTMetaDataRelease(metaDataRef);
	}

	if (m_soundTrack) {
		if (0 != (err = InsertMediaIntoTrack(m_soundTrack, 0, 0, GetMediaDuration(m_soundMedia), fixed1)))
			throw TImageException(getFilePath(), "can't insert sound into track");
		//FailIf(err != noErr, MediaErr);
	}

	short resId = movieInDataForkResID;
	if (m_movie)
		if ((err = AddMovieResource(m_movie, m_refNum, &resId, 0)) != noErr) {
		}

/**************************************************************************************/
#ifdef PROVA3GP
	//  char *inputFileName = toString(m_path.getWideString()).c_str();
	QString qStr = QString::fromStdWString(m_path.getWideString());
	char *inputFileName = qStr.toUtf8().data();

	OSType movieType = FOUR_CHAR_CODE('3gpp');
	OSType creator = 'TVOD';
	Movie movie;

	// get the file
	FSSpec inFsSpec;
	//NativePathNameToFSSpec(inputFileName, &inFsSpec, 0);
	FSMakeFSSpec(0, 0, inputFileName, &inFsSpec);
	getFSSpecFromPosixPath(pStr, &inFsSpec, false);

	// Initialize Quicktime Movie Toolbox
	//EnterMovies ();

	// open the movie
	short sResRefNum;
	OpenMovieFile(&inFsSpec, &sResRefNum, fsRdPerm);
	NewMovieFromFile(&movie, sResRefNum, nil, nil, 0, nil);
	CloseMovieFile(sResRefNum);

	UCHAR myCancelled = FALSE;
	MovieExportComponent m_myExporter;
	OpenADefaultComponent(MovieExportType, '3gpp', &m_myExporter);
	err = (short)MovieExportDoUserDialog(m_myExporter, movie, 0, 0, 0, &myCancelled);

	if ((err != noErr))
		throw TImageException(getFilePath(), "can't export 3gp");

	FSSpec fspec;
	FSMakeFSSpec(0, 0, "C:\\temp\\out.3gp", &fspec);
	getFSSpecFromPosixPath(pStr, &fspec, false);

	//NativePathNameToFSSpec	("C:\\temp\\out.3gp", &fspec, kFullNativePath);

	long myFlags = 0L;
	myFlags = createMovieFileDeleteCurFile; // |
											//movieFileSpecValid | movieToFileOnlyExport;

	err = ConvertMovieToFile(
		movie,					// the movie to convert
		0,						// all tracks in the movie
		&fspec,					// the output file
		FOUR_CHAR_CODE('3gpp'), // the output file type
		FOUR_CHAR_CODE('TVOD'), // the output file creator
		smSystemScript,			// the script
		0,						// no resource ID to be returned
		myFlags,				// export flags
		m_myExporter);			// no specific exp
}
if (sResRefNum)
	CloseMovieFile(sResRefNum);
DisposeMovie(movie);
}
#endif

if (m_refNum)
	CloseMovieFile(m_refNum);
DisposeMovie(m_movie);
}

//-----------------------------------------------------------

TImageWriterP TLevelWriterMov::getFrameWriter(TFrameId fid)
{
	if (m_IOError)
		throw TImageException(m_path, buildQTErrorString(m_IOError));
	if (fid.getLetter() != 0)
		return TImageWriterP(0);
	int index = fid.getNumber() - 1;

	TImageWriterMov *iwm = new TImageWriterMov(m_path, index, this);
	return TImageWriterP(iwm);
}

//-----------------------------------------------------------

TLevelReaderMov::TLevelReaderMov(const TFilePath &path)
	: TLevelReader(path), m_IOError(QTNoError), m_track(0), m_movie(0), m_depth(0), m_readAsToonzOutput(false)
{

	//std::cout << "MOVIE PATH : " << toString(path) << std::endl;
	FSSpec fspec;
	QDErr err;
	Boolean dataRefWasChanged;
	if (QuickTimeStuff::instance()->getStatus() != noErr) {
		m_IOError = QTNotInstalled;
		return;
	}

	//	const char *pStr = toString(m_path.getWideString()).c_str();
	QString qStr = QString::fromStdWString(m_path.getWideString());
	char *pStr = qStr.toUtf8().data();

	//FSMakeFSSpec(0, 0,(const unsigned char*)pStr , &fspec);
	getFSSpecFromPosixPath(pStr, &fspec, false);
	pStr = 0;

	if ((err = OpenMovieFile(&fspec, &m_refNum, fsRdPerm))) {
		m_IOError = QTUnableToOpenFile;
		return;
	}

	m_resId = 0;
	Str255 name;
	err = NewMovieFromFile(&m_movie, m_refNum, &m_resId,
						   name, fsRdPerm, &dataRefWasChanged);

	int numTracks = GetMovieTrackCount(m_movie);
	//assert(numTracks==1 || numTracks ==2);
	m_track = GetMovieIndTrackType(m_movie, 1, VideoMediaType, movieTrackMediaType);

	//m_track=GetMovieTrack(m_movie,numTracks);

	ImageDescriptionHandle imageH;
	imageH = (ImageDescriptionHandle)NewHandleClear(sizeof(ImageDescription));
	TINT32 index = 1;
	Media theMedia = GetTrackMedia(m_track);

	GetMediaSampleDescription(theMedia, index, (SampleDescriptionHandle)imageH);
	ImageDescriptionPtr imagePtr = *imageH;
	m_lx = imagePtr->width;
	m_ly = imagePtr->height;
	m_depth = imagePtr->depth;
	m_info = new TImageInfo();
	m_info->m_lx = m_lx;
	m_info->m_ly = m_ly;
	Tiio::MovWriterProperties *prop = new Tiio::MovWriterProperties();
	m_info->m_properties = prop;

	DisposeHandle((Handle)imageH);

	m_info->m_frameRate = GetMediaTimeScale(theMedia) / 100.0;
}

//------------------------------------------------

//------------------------------------------------

//------------------------------------------------
// TImageReaderMov
//------------------------------------------------

TImageReaderMov::TImageReaderMov(const TFilePath &path, int frameIndex, TLevelReaderMov *lrm)
	: TImageReader(path), m_lrm(lrm), m_frameIndex(frameIndex)
{
	m_lrm->addRef();
}

//------------------------------------------------

TLevelReaderMov::~TLevelReaderMov()
{
	StopMovie(m_movie);

	if (m_refNum)
		CloseMovieFile(m_refNum);
	if (m_movie)
		DisposeMovie(m_movie);
}

//------------------------------------------------

TLevelP TLevelReaderMov::loadInfo()
{
	if (m_readAsToonzOutput)
		return loadToonzOutputFormatInfo();

	TLevelP level;
	if (m_IOError != QTNoError)
		throw TImageException(m_path, buildQTErrorString(m_IOError));

	OSType mediaType = VisualMediaCharacteristic;
	TimeValue nextTime, currentTime;
	currentTime = 0;
	nextTime = 0;
	//per il primo
	int f = 0;
	// io vorrei fare '|', ma sul manuale c'e' scritto '+'
	GetMovieNextInterestingTime(m_movie, nextTimeMediaSample + nextTimeEdgeOK, 1, &mediaType, currentTime, 0, &nextTime, 0);
	if (nextTime != -1) {
		TFrameId frame(f + 1);
		level->setFrame(frame, TImageP());
		currentTimes[f] = nextTime;
		f++;
	}
	currentTime = nextTime;
	while (nextTime != -1) {
		GetMovieNextInterestingTime(m_movie, nextTimeMediaSample, 1, &mediaType, currentTime, 0, &nextTime, 0);
		if (nextTime != -1) {
			TFrameId frame(f + 1);
			level->setFrame(frame, TImageP());
			currentTimes[f] = nextTime;
			f++;
		}
		currentTime = nextTime;
	}
	return level;
}

//------------------------------------------------

TLevelP TLevelReaderMov::loadToonzOutputFormatInfo()
{
	TLevelP level;
	if (m_IOError != QTNoError)
		throw TImageException(m_path, buildQTErrorString(m_IOError));

	OSStatus mderr;

	//Retrieve the first frame of movie. This info has been put in a metadata atom.
	QTMetaDataRef metaDataRef;
	if ((mderr = QTCopyMovieMetaData(m_movie, &metaDataRef)) != noErr)
		throw TImageException(m_path, "can't access metadata informations");

	QTMetaDataItem firstFrameItem;

	//Find the metadata atom
	mderr = QTMetaDataGetNextItem(
		metaDataRef,
		kQTMetaDataStorageFormatUserData,
		kQTMetaDataItemUninitialized,
		kQTMetaDataKeyFormatUserData,
		(const UInt8 *)firstFrameKey.c_str(),
		firstFrameKeySize,
		&firstFrameItem);

	//Try to read the value. If the atom was not found, just assume firstFrame = 0.
	int firstFrame = 0;
	if (mderr == noErr) {
		//The atom was found. Then, retrieve the value
		if ((mderr =
				 QTMetaDataGetItemValue(
					 metaDataRef,
					 firstFrameItem,
					 (UInt8 *)(&firstFrame),
					 sizeof(int),
					 0) != noErr))
			throw TImageException(m_path, "can't read metadata informations");
	}

	mderr = 0;

	QTMetaDataRelease(metaDataRef);

	OSType mediaType = VisualMediaCharacteristic;
	TimeValue nextTime, currentTime;
	currentTime = 0;
	nextTime = 0;

	TimeValue movieDuration = GetMovieDuration(m_movie);

	std::vector<TimeValue> interestingTimeValues;

	// io vorrei fare '|', ma sul manuale c'e' scritto '+'
	GetMovieNextInterestingTime(m_movie, nextTimeMediaSample + nextTimeEdgeOK, 1, &mediaType, currentTime, 0, &nextTime, 0);
	if (nextTime != -1) {
		interestingTimeValues.push_back(nextTime);
		currentTime = nextTime;
	}
	while (nextTime != -1) {
		GetMovieNextInterestingTime(m_movie, nextTimeMediaSample, 1, &mediaType, currentTime, 0, &nextTime, 0);
		if (nextTime != -1) {
			interestingTimeValues.push_back(nextTime);
			currentTime = nextTime;
		}
	}

	if (currentTime != -1) {
		double frameRate = m_info->m_frameRate;
		TimeScale movieTimeScale = GetMovieTimeScale(m_movie);
		int firstFrameTimeValue = movieTimeScale * firstFrame;

		std::vector<TimeValue>::iterator it;
		for (it = interestingTimeValues.begin(); it != interestingTimeValues.end(); ++it) {
			int frame = firstFrame + tround((*it * frameRate) / (double)movieTimeScale);
			TFrameId frameId(frame + 1);
			level->setFrame(frameId, TImageP());
			currentTimes[frame] = *it;
		}
	}

	return level;
}

//------------------------------------------------

TImageP TImageReaderMov::load()
{
	TRasterPT<TPixelRGBM32> ret(m_lrm->getSize());

	m_lrm->load(ret, m_frameIndex, TPointI(), 1, 1);

	return TRasterImageP(ret);
}

//------------------------------------------------

void TImageReaderMov::load(const TRasterP &rasP, const TPoint &pos, int shrinkX, int shrinkY)
{
	if ((shrinkX != 1) || (shrinkY != 1) || (pos != TPoint(0, 0))) {
		TImageReader::load(rasP, pos, shrinkX, shrinkY);
	} else
		m_lrm->load(rasP, m_frameIndex, pos, shrinkX, shrinkY);
}
//------------------------------------------------
inline void setMatteAndYMirror(const TRaster32P &ras)
{
	ras->lock();
	TPixel32 *upRow = ras->pixels();
	TPixel32 *dwRow = ras->pixels(ras->getLy() - 1);
	int hLy = (int)(ras->getLy() / 2. + 0.5); //piccola pessimizzazione...
	int wrap = ras->getWrap();
	int lx = ras->getLx();
	TPixel32 *upPix = 0;
	TPixel32 *lastPix = ras->pixels(hLy);
	while (upPix < lastPix) {
		upPix = upRow;
		TPixel32 *dwPix = dwRow;
		TPixel32 *endPix = upPix + lx;
		while (upPix < endPix) {
			TPixel32 tmpPix(upPix->r, upPix->g, upPix->b, 0xff);
			*upPix = *dwPix;
			upPix->m = 0xff;
			*dwPix = tmpPix;
			++upPix;
			++dwPix;
		}
		upRow += wrap;
		dwRow -= wrap;
	}
	ras->lock();
}

//------------------------------------------------

void TLevelReaderMov::load(const TRasterP &rasP, int frameIndex, const TPoint &pos, int shrinkX, int shrinkY)
{
	GWorldPtr offscreenGWorld = 0;
	{
		QMutexLocker sl(&m_mutex);
		if (m_IOError != QTNoError)
			throw TImageException(m_path, buildQTErrorString(m_IOError));

		TRaster32P ras = rasP;

		Rect rect;
		rect.right = pos.x + ras->getLx();
		rect.left = pos.x;
		rect.bottom = pos.y + ras->getLy();
		rect.top = pos.y;

		OSErr err;

#if defined TNZ_MACHINE_CHANNEL_ORDER_BGRM
		OSType pixelFormat = k32BGRAPixelFormat;
#elif defined TNZ_MACHINE_CHANNEL_ORDER_MRGB
		OSType pixelFormat = k32ARGBPixelFormat;
#endif
		//ras->lock();
		err = QTNewGWorldFromPtr(
			&offscreenGWorld, pixelFormat,
			&rect, 0, 0, 0, ras->getRawData(), ras->getWrap() * 4);

		if (err != noErr) {
			m_IOError = QTUnableToCreateResource;
			goto error;
		}

		SetMovieBox(m_movie, &rect);
		err = GetMoviesError();
		if (err != noErr) {
			m_IOError = QTUnableToSetMovieBox;

			goto error;
		}

#if defined(HAS_QUICKDRAW)
		SetMovieGWorld(m_movie, offscreenGWorld, GetGWorldDevice(offscreenGWorld));
#endif
		err = GetMoviesError();
		if (err != noErr) {
			m_IOError = QTUnableToSetMovieGWorld;
			goto error;
		}

		if (currentTimes.empty())
			loadInfo();

		std::map<int, TimeValue>::iterator it = currentTimes.find(frameIndex);
		if (it == currentTimes.end())
			goto error;

		TimeValue currentTime = it->second;
		SetMovieTimeValue(m_movie, currentTime);

		err = GetMoviesError();
		if (err != noErr) {
			m_IOError = QTUnableToSetTimeValue;
			goto error;
		}

		err = UpdateMovie(m_movie);
		if (err != noErr) {
			m_IOError = QTUnableToUpdateMovie;
			goto error;
		}

		MoviesTask(m_movie, 0);
		err = GetMoviesError();
		if (err != noErr) {
			m_IOError = QTUnableToDoMovieTask;
			goto error;
		}

		SetMovieGWorld(m_movie, 0, 0);
#if defined(HAS_QUICKDRAW)
		DisposeGWorld(offscreenGWorld);
#endif
		// ras->unlock();
	}

	if (m_depth != 32) {
		setMatteAndYMirror(rasP);
	} else {
		rasP->yMirror();
	}

	return;

error:
//rasP->unlock();
#if defined(HAS_QUICKDRAW)
	if (offscreenGWorld)
		DisposeGWorld(offscreenGWorld);
#endif
	throw TImageException(m_path, buildQTErrorString(m_IOError));
}

//------------------------------------------------

TImageReaderP TLevelReaderMov::getFrameReader(TFrameId fid)
{
	if (m_IOError != QTNoError)
		throw TImageException(m_path, buildQTErrorString(m_IOError));

	if (fid.getLetter() != 0)
		return TImageReaderP(0);
	int index = fid.getNumber() - 1;

	TImageReaderMov *irm = new TImageReaderMov(m_path, index, this);
	return TImageReaderP(irm);
}

#endif //!__LP64__