summaryrefslogtreecommitdiff
path: root/XMPFiles/source/FileHandlers/RIFF_Handler.cpp
blob: 7d6fdb3bc0ad0453a833a130f389d286119b29d0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
// Copyright 2009 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
// of the Adobe license agreement accompanying it.
// =================================================================================================

#include "public/include/XMP_Environment.h"	// ! XMP_Environment.h must be the first included header.
#include "public/include/XMP_Const.h"

#include "XMPFiles/source/FormatSupport/RIFF.hpp"
#include "XMPFiles/source/FileHandlers/RIFF_Handler.hpp"
#include "source/XIO.hpp"

using namespace std;

// =================================================================================================
/// \file RIFF_Handler.cpp
/// \brief File format handler for RIFF.
// =================================================================================================

// =================================================================================================
// RIFF_MetaHandlerCTor
// ====================

XMPFileHandler * RIFF_MetaHandlerCTor ( XMPFiles * parent )
{
	return new RIFF_MetaHandler ( parent );
}

// =================================================================================================
// RIFF_CheckFormat
// ===============
//
// An RIFF file must begin with "RIFF", a 4 byte length, then the chunkType (AVI or WAV)
// The specified length MUST (in practice: SHOULD) match fileSize-8, but we don't bother checking this here.

bool RIFF_CheckFormat ( XMP_FileFormat  format,
					   XMP_StringPtr    filePath,
			           XMP_IO*      	file,
			           XMPFiles*        parent )
{
	IgnoreParam(format); IgnoreParam(parent);
	XMP_Assert ( (format == kXMP_AVIFile) || (format == kXMP_WAVFile) );

	if ( file->Length() < 12 ) return false;
	file ->Rewind();

	XMP_Uns8 chunkID[12];
	file->ReadAll ( chunkID, 12 );
	if ( ! CheckBytes( &chunkID[0], "RIFF", 4 )) return false;

	if ( CheckBytes(&chunkID[8],"AVI ",4) && format == kXMP_AVIFile ) return true;
	if ( CheckBytes(&chunkID[8],"WAVE",4) && format == kXMP_WAVFile ) return true;

	return false;

}	// RIFF_CheckFormat

// =================================================================================================
// RIFF_MetaHandler::RIFF_MetaHandler
// ================================

RIFF_MetaHandler::RIFF_MetaHandler ( XMPFiles * _parent )
{
	this->parent = _parent;
	this->handlerFlags = kRIFF_HandlerFlags;
	this->stdCharForm  = kXMP_Char8Bit;

	this->oldFileSize = this->newFileSize = this->trailingGarbageSize = 0;
	this->level = 0;
	this->listInfoChunk = this->listTdatChunk = 0;
	this->dispChunk = this->bextChunk = this->cr8rChunk = this->prmlChunk = 0;
	this->xmpChunk = 0;
	this->lastChunk = 0;
	this->hasListInfoINAM = false;
}

// =================================================================================================
// RIFF_MetaHandler::~RIFF_MetaHandler
// =================================

RIFF_MetaHandler::~RIFF_MetaHandler()
{
	while ( ! this->riffChunks.empty() )
	{
		delete this->riffChunks.back();
		this->riffChunks.pop_back();
	}
}

// =================================================================================================
// RIFF_MetaHandler::CacheFileData
// ==============================

void RIFF_MetaHandler::CacheFileData()
{
	this->containsXMP = false; //assume for now

	XMP_IO* file = this->parent->ioRef;
	this->oldFileSize = file ->Length();
	if ( (this->parent->format == kXMP_WAVFile) && (this->oldFileSize > 0xFFFFFFFF) )
		XMP_Throw ( "RIFF_MetaHandler::CacheFileData: WAV Files larger 4GB not supported", kXMPErr_Unimplemented );

	file ->Rewind();
	this->level = 0;

	// parse top-level chunks (most likely just one, except large avi files)
	XMP_Int64 filePos = 0;
	while ( filePos < this->oldFileSize )
	{

		this->riffChunks.push_back( (RIFF::ContainerChunk*) RIFF::getChunk( NULL, this ) );

		// Tolerate limited forms of trailing garbage in a file. Some apps append private data.

		filePos = file->Offset();
		XMP_Int64 fileTail = this->oldFileSize - filePos;

		if ( fileTail != 0 ) {

			if ( fileTail < 12 ) {

				this->oldFileSize = filePos;	// Pretend the file is smaller.
				this->trailingGarbageSize = fileTail;

			} else if ( this->parent->format == kXMP_WAVFile ) {

				if ( fileTail < 1024*1024 ) {
					this->oldFileSize = filePos;	// Pretend the file is smaller.
					this->trailingGarbageSize = fileTail;
				} else {
					XMP_Throw ( "Excessive garbage at end of file", kXMPErr_BadFileFormat )
				}

			} else {

				XMP_Int32 chunkInfo [3];
				file->ReadAll ( &chunkInfo, 12 );
				file->Seek ( -12, kXMP_SeekFromCurrent );
				if ( (GetUns32LE ( &chunkInfo[0] ) != RIFF::kChunk_RIFF) || (GetUns32LE ( &chunkInfo[2] ) != RIFF::kType_AVIX) ) {
					if ( fileTail < 1024*1024 ) {
						this->oldFileSize = filePos;	// Pretend the file is smaller.
						this->trailingGarbageSize = fileTail;
					} else {
						XMP_Throw ( "Excessive garbage at end of file", kXMPErr_BadFileFormat )
					}
				}

			}

		}

	}

	// covered before => internal error if it occurs
	XMP_Validate( file->Offset() == this->oldFileSize,
		          "RIFF_MetaHandler::CacheFileData: unknown data at end of file",
				  kXMPErr_InternalFailure );

}	// RIFF_MetaHandler::CacheFileData

// =================================================================================================
// RIFF_MetaHandler::ProcessXMP
// ============================

void RIFF_MetaHandler::ProcessXMP()
{
	SXMPUtils::RemoveProperties ( &this->xmpObj, 0, 0, kXMPUtil_DoAllProperties );
	// process physical packet first
	if ( this->containsXMP ) this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
	// then import native properties:
	RIFF::importProperties( this );
	this->processedXMP = true;
}

// =================================================================================================
// RIFF_MetaHandler::UpdateFile
// ===========================

void RIFF_MetaHandler::UpdateFile ( bool doSafeUpdate )
{
	XMP_Validate( this->needsUpdate, "nothing to update", kXMPErr_InternalFailure );

	////////////////////////////////////////////////////////////////////////////////////////
	//////////// PASS 1: basics, exports, packet reserialze
	XMP_IO* file = this->parent->ioRef;
	RIFF::containerVect *rc = &this->riffChunks;

	//temptemp
	//printf( "BEFORE:\n%s\n", rc->at(0)->toString().c_str() );

	XMP_Enforce( rc->size() >= 1);
	RIFF::ContainerChunk* mainChunk = rc->at(0);
	this->lastChunk = rc->at( rc->size() - 1 );  // (may or may not coincide with mainChunk: )
	XMP_Enforce( mainChunk != NULL );

	RIFF::relocateWronglyPlacedXMPChunk( this );
	// [2435625] lifted disablement for AVI
	RIFF::exportAndRemoveProperties( this );

	// always rewrite both LISTs (implicit size changes, e.g. through 0-term corrections may
	// have very well led to size changes...)
	// set XMP packet info, re-serialize
	this->packetInfo.charForm = stdCharForm;
	this->packetInfo.writeable = true;
	this->packetInfo.offset = kXMPFiles_UnknownOffset;
	this->packetInfo.length = kXMPFiles_UnknownLength;

	// re-serialization ( needed because of above exportAndRemoveProperties() )
	try {
		if ( this->xmpChunk == 0 ) // new chunk? pad with 2K
			this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_NoOptions , 2048 );
		else // otherwise try to match former size
			this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_ExactPacketLength , (XMP_Uns32) this->xmpChunk->oldSize-8 );
	}   catch ( ... ) { // if that fails, be happy with whatever.
			this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_NoOptions );
	}

	if ( (this->xmpPacket.size() & 1) == 1 ) this->xmpPacket += ' ';	// Force the XMP to have an even size.

	// if missing, add xmp packet at end:
	if( this->xmpChunk == 0 )
		this->xmpChunk = new RIFF::XMPChunk( this->lastChunk );
	// * parenting happens within this call.
	// * size computation will happen in XMPChunk::changesAndSize()
	// * actual data will be set in XMPChunk::write()

	////////////////////////////////////////////////////////////////////////////////////////
	// PASS 2: compute sizes, optimize container structure (no writing yet)
	{
		this->newFileSize = 0;

		// note: going through forward (not vice versa) is an important condition,
		// so that parking LIST:Tdat,Cr8r, PrmL to last chunk is doable
		// when encountered en route
		for ( XMP_Uns32 chunkNo = 0; chunkNo < rc->size(); chunkNo++ )
		{
			RIFF::Chunk* cur = rc->at(chunkNo);
			cur->changesAndSize( this );
			this->newFileSize += cur->newSize;
			if ( this->newFileSize % 2 == 1 ) this->newFileSize++; // pad byte
		}
		this->newFileSize += this->trailingGarbageSize;
	} // PASS2

	////////////////////////////////////////////////////////////////////////////////////////
	// PASS 2a: verify no chunk violates 2GB boundaries
	switch( this->parent->format )
	{
		// NB: <4GB for ALL chunks asserted before in ContainerChunk::changesAndSize()

		case kXMP_AVIFile:
			// ensure no chunk (single or multi, no matter) crosses 2 GB ...
			for ( XMP_Int32 chunkNo = 0; chunkNo < (XMP_Int32)rc->size(); chunkNo++ )
			{
				if ( rc->at(chunkNo)->oldSize <= 0x80000000LL ) // ... if <2GB before
					XMP_Validate( rc->at(chunkNo)->newSize <= 0x80000000LL,
							"Chunk grew beyond 2 GB", kXMPErr_Unimplemented );
			}

			// compatibility: if single-chunk AND below <1GB, ensure <1GB
			if ( ( rc->size() > 1 ) && ( rc->at(0)->oldSize < 0x40000000 ) )
			{
				 XMP_Validate( rc->at(0)->newSize < 0x40000000LL, "compatibility: mainChunk must remain < 1GB" , kXMPErr_Unimplemented );
			}

			// [2473381] compatibility: if single-chunk AND >2GB,<4GB, ensure <4GB
			if ( ( rc->size() > 1 ) &&
				 ( rc->at(0)->oldSize >  0x80000000LL ) && // 2GB
				 ( rc->at(0)->oldSize < 0x100000000LL ) )  // 4GB
			{
				 XMP_Validate( rc->at(0)->newSize < 0x100000000LL, "compatibility: mainChunk must remain < 4GB" , kXMPErr_Unimplemented );
			}

			break;

		case kXMP_WAVFile:
			XMP_Validate( 1 == rc->size(), "WAV must be single-chunk", kXMPErr_InternalFailure );
			XMP_Validate( rc->at(0)->newSize <= 0xFFFFFFFFLL, "WAV above 4 GB not supported", kXMPErr_Unimplemented );
			break;

		default:
			XMP_Throw( "unknown format", kXMPErr_InternalFailure );
	}

	////////////////////////////////////////////////////////////////////////////////////////
	// PASS 3: write avix chunk(s) if applicable (shrinks or stays)
	//         and main chunk. -- operation order depends on mainHasShrunk.
	{
		// if needed, extend file beforehand
		if ( this->newFileSize > this->oldFileSize ) {
			file->Seek ( newFileSize, kXMP_SeekFromStart );
			file->Rewind();
		}

		RIFF::Chunk* mainChunk = rc->at(0);

		XMP_Int64 mainGrowth = mainChunk->newSize - mainChunk->oldSize;
		XMP_Enforce( mainGrowth >= 0 ); // main always stays or grows

		//temptemp
		//printf( "AFTER:\n%s\n", rc->at(0)->toString().c_str() );

		if ( rc->size() > 1 ) // [2457482]
			XMP_Validate( mainGrowth == 0, "mainChunk must not grow, if multiple RIFF chunks", kXMPErr_InternalFailure );

		// back to front:

		XMP_Int64 avixStart = newFileSize; // count from the back

		if ( this->trailingGarbageSize != 0 ) {
			XMP_Int64 goodDataEnd = this->newFileSize - this->trailingGarbageSize;
			XIO::Move ( file, this->oldFileSize, file, goodDataEnd, this->trailingGarbageSize );
			avixStart = goodDataEnd;
		}

		for ( XMP_Int32 chunkNo = ((XMP_Int32)rc->size()) -1; chunkNo >= 0; chunkNo-- )
		{
			RIFF::Chunk* cur = rc->at(chunkNo);

			avixStart -= cur->newSize;
			if ( avixStart % 2 == 1 ) // rewind one more
				avixStart -= 1;

			file->Seek ( avixStart , kXMP_SeekFromStart  );

			if ( cur->hasChange ) // need explicit write-out ?
				cur->write( this, file, chunkNo == 0 );
			else // or will a simple move do?
			{
				XMP_Enforce( cur->oldSize == cur->newSize );
				if ( cur->oldPos != avixStart ) // important optimization: only move if there's a need to
					XIO::Move( file, cur->oldPos, file, avixStart, cur->newSize );
			}
		}

		// if needed, shrink file afterwards
		if ( this->newFileSize < this->oldFileSize ) file->Truncate ( this->newFileSize  );
	} // PASS 3

	this->needsUpdate = false; //do last for safety
}	// RIFF_MetaHandler::UpdateFile

// =================================================================================================
// RIFF_MetaHandler::WriteTempFile
// ===============================

void RIFF_MetaHandler::WriteTempFile ( XMP_IO* tempRef  )
{
	IgnoreParam( tempRef );
	XMP_Throw ( "RIFF_MetaHandler::WriteTempFile: Not supported (must go through UpdateFile", kXMPErr_Unavailable );
}