Exemple #1
0
			// reads entire *FileHeader* structure from file (starting at current position)
			void read(XMP_IO* file)
			{
				this->release();

				file ->ReadAll ( fields , FIXED_SIZE  );

				XMP_Uns32 tmp32 = GetUns32LE( &this->fields[FileHeader::o_sig] );
				XMP_Validate( SIG == tmp32, "invalid header", kXMPErr_BadFileFormat );
				filenameLen   = GetUns16LE( &this->fields[FileHeader::o_fileNameLength] );
				extraFieldLen = GetUns16LE( &this->fields[FileHeader::o_extraFieldLength] );

				// nb unlike the CDFileHeader the FileHeader will in practice never have
				// extra fields. Reasoning: File headers never carry (their own) offsets,
				// (un)compressed size of XMP will hardly ever reach 4 GB

				if (filenameLen) {
					filename = new char[filenameLen];
					file->ReadAll ( filename, filenameLen );
				}
				if (extraFieldLen) {
					extraField = new char[extraFieldLen];
					file->ReadAll ( extraField, extraFieldLen );
					// *** NB: this WOULD need parsing for content files that are
					//   compressed or uncompressed >4GB (VERY unlikely for XMP)
				}
			}
Exemple #2
0
/* remove value chunk if existing.
   return true if it was existing. */
bool ContainerChunk::removeValue( XMP_Uns32	id_ )
{
	valueMap* cm = &this->childmap;
	valueMapIter iter = cm->find( id_ );

	if( iter == cm->end() )
		return false;  //not found

	ValueChunk* propChunk = iter->second;

	// remove from vector (difficult)
	chunkVect* cv = &this->children;
	chunkVectIter cvIter;
	for (cvIter = cv->begin(); cvIter != cv->end(); ++cvIter )
	{
		if ( (*cvIter)->id == id_ )
			break; // found!
	}
	XMP_Validate( cvIter != cv->end(), "property not found in children vector", kXMPErr_InternalFailure );
	cv->erase( cvIter );

	// remove from map (easy)
	cm->erase( iter );

	delete propChunk;
	return true; // found and removed
}
Exemple #3
0
void ID3v2Frame::setFrameValue ( const std::string& rawvalue, bool needDescriptor,
											  bool utf16, bool isXMPPRIVFrame, bool needEncodingByte )
{

	std::string value;

	if ( isXMPPRIVFrame ) {

		XMP_Assert ( (! needDescriptor) && (! utf16) );

		value.append ( "XMP\0", 4 );
		value.append ( rawvalue );
		value.append ( "\0", 1  ); // final zero byte

	} else {

		if ( needEncodingByte ) {
			if ( utf16 ) {
				value.append ( "\x1", 1  );
			} else {
				value.append ( "\x0", 1  );
			}
		}

		if ( needDescriptor ) value.append ( "eng", 3 );

		if ( utf16 ) {

			if ( needDescriptor ) value.append ( "\xFF\xFE\0\0", 4 );

			value.append ( "\xFF\xFE", 2 );
			std::string utf16str;
			ToUTF16 ( (XMP_Uns8*) rawvalue.c_str(), rawvalue.size(), &utf16str, false );
			value.append ( utf16str );
			value.append ( "\0\0", 2 );

		} else {

			std::string convertedValue;
			ReconcileUtils::UTF8ToLatin1 ( rawvalue.c_str(), rawvalue.size(), &convertedValue );

			if ( needDescriptor ) value.append ( "\0", 1 );
			value.append ( convertedValue );
			value.append ( "\0", 1  );

		}

	}

	this->changed = true;
	this->release();

	this->contentSize = (XMP_Int32) value.size();
	XMP_Validate ( (this->contentSize < 20*1024*1024), "XMP Property exceeds 20MB in size", kXMPErr_InternalFailure );
	this->content = new char [ this->contentSize ];
	memcpy ( this->content, value.c_str(), this->contentSize );

}	// ID3v2Frame::setFrameValue
Exemple #4
0
			// writes structure to file (starting at current position)
			void write(XMP_IO* file)
			{
				XMP_Validate( SIG == GetUns32LE( &this->fields[FileHeader::o_sig] ), "invalid header on write", kXMPErr_BadFileFormat );

				filenameLen   = GetUns16LE( &this->fields[FileHeader::o_fileNameLength] );
				extraFieldLen = GetUns16LE( &this->fields[FileHeader::o_extraFieldLength] );

				file ->Write ( fields , FIXED_SIZE  );
				if (filenameLen)	file->Write ( filename, filenameLen    );
				if (extraFieldLen)	file->Write ( extraField, extraFieldLen  );
			}
Exemple #5
0
void XMPChunk::changesAndSize( RIFF_MetaHandler* handler )
{
	XMP_Enforce( &handler->xmpPacket != 0 );
	XMP_Enforce( handler->xmpPacket.size() > 0 );
	this->newSize = 8 + handler->xmpPacket.size();

	XMP_Validate( this->newSize <= 0xFFFFFFFFLL, "no single chunk may be above 4 GB", kXMPErr_InternalFailure );

	// a complete no-change would have been caught in XMPFiles common code anyway
	this->hasChange = true;
}
Exemple #6
0
XMP_Int64 ID3v2Frame::read ( XMP_IO* file, XMP_Uns8 majorVersion )
{
	XMP_Assert ( (2 <= majorVersion) && (majorVersion <= 4) );

	this->release(); // ensures/allows reuse of 'curFrame'
	XMP_Int64 start = file->Offset();
	
	if ( majorVersion > 2 ) {
		file->ReadAll ( this->fields, kV23_FrameHeaderSize );
	} else {
		// Read the 6 byte v2.2 header into the 10 byte form.
		memset ( this->fields, 0, kV23_FrameHeaderSize );	// Clear all of the bytes.
		file->ReadAll ( &this->fields[o_id], 3 );		// Leave the low order byte as zero.
		file->ReadAll ( &this->fields[o_size+1], 3 );	// Read big endian UInt24.
	}

	this->id = GetUns32BE ( &this->fields[o_id] );

	if ( this->id == 0 ) {
		file->Seek ( start, kXMP_SeekFromStart );	// Zero ID must mean nothing but padding.
		return 0;
	}

	this->flags = GetUns16BE ( &this->fields[o_flags] );
	XMP_Validate ( (0 == (this->flags & 0xEE)), "invalid lower bits in frame flags", kXMPErr_BadFileFormat );

	//*** flag handling, spec :429ff aka line 431ff  (i.e. Frame should be discarded)
	//  compression and all of that..., unsynchronisation
	this->contentSize = GetUns32BE ( &this->fields[o_size] );
	if ( majorVersion == 4 ) this->contentSize = synchToInt32 ( this->contentSize );

	XMP_Validate ( (this->contentSize >= 0), "negative frame size", kXMPErr_BadFileFormat );
	XMP_Validate ( (this->contentSize < 20*1024*1024), "single frame exceeds 20MB", kXMPErr_BadFileFormat );

	this->content = new char [ this->contentSize ];

	file->ReadAll ( this->content, this->contentSize );
	return file->Offset() - start;

}	// ID3v2Frame::read
Exemple #7
0
		void read (XMP_IO* file)
		{
			UCFECD_Free();

			file->ReadAll ( fields, FIXED_SIZE );
			XMP_Validate( this->SIG == GetUns32LE( &this->fields[o_Sig] ), "invalid header", kXMPErr_BadFileFormat );

			commentLen = GetUns16LE( &this->fields[o_CommentLen] );
			if(commentLen)
			{
				comment = new char[commentLen];
				file->ReadAll ( comment, commentLen );
			}
		};
Exemple #8
0
bool ID3Header::read ( XMP_IO* file )
{

	XMP_Assert ( sizeof(fields) == kID3_TagHeaderSize );
	file->ReadAll ( this->fields, kID3_TagHeaderSize );

	if ( ! CheckBytes ( &this->fields[ID3Header::o_id], "ID3", 3 ) ) {
		// chuck in default contents:
		const static char defaultHeader[kID3_TagHeaderSize] = { 'I', 'D', '3', 3, 0, 0, 0, 0, 0, 0 };
		memcpy ( this->fields, defaultHeader, kID3_TagHeaderSize );
		return false; // no header found (o.k.) thus stick with new, default header constructed above
	}

	XMP_Uns8 major = this->fields[o_vMajor];
	XMP_Uns8 minor = this->fields[o_vMinor];
	XMP_Validate ( ((2 <= major) && (major <= 4)), "Invalid ID3 major version", kXMPErr_BadFileFormat );

	return true;

}
Exemple #9
0
// =================================================================================================
// MP3_MetaHandler::ProcessXMP
// ===========================
//
// Process the raw XMP and legacy metadata that was previously cached.
void MP3_MetaHandler::ProcessXMP()
{
	// Process the XMP packet.
	if ( ! this->xmpPacket.empty() ) {	
		XMP_Assert ( this->containsXMP );
		XMP_StringPtr packetStr = this->xmpPacket.c_str();
		XMP_StringLen packetLen = (XMP_StringLen) this->xmpPacket.size();
		this->xmpObj.ParseFromBuffer ( packetStr, packetLen );
		this->processedXMP = true;
	}

	///////////////////////////////////////////////////////////////////
	// assumptions on presence-absence "flag tags"
	//   ( unless no xmp whatsoever present )
	if ( ! this->xmpPacket.empty() ) {	
		this->xmpObj.SetProperty( kXMP_NS_DM, "partOfCompilation", "false" );
	}

	////////////////////////////////////////////////////////////////////
	// import of legacy properties
	ID3v2Frame* curFrame;
	XMP_Bool hasTDRC = false;
	XMP_DateTime newDateTime;

	if (this->hasID3Tag) // otherwise pretty pointless...		
		for (int r=0; reconProps[r].frameID != 0; r++)
		{
			//get the frame ID to look for
			XMP_Uns32 frameID = GetUns32BE( reconProps[r].frameID );

			// deal with each such frame in the frameVector
			// (since there might be several, some of them not applicable, i.e. COMM)
			for ( vector<ID3_Support::ID3v2Frame*>::iterator it = framesVector.begin(); it!=framesVector.end(); ++it)
			{
				curFrame = *it;
				if (frameID != curFrame->id) // nothing applicable. Next!
					continue;

				// go deal with it!
				// get the property
				std::string utf8string;
				bool result = curFrame->getFrameValue(majorVersion, frameID, &utf8string);

				if (! result)
					continue; //ignore but preserve this frame (i.e. not applicable COMM frame)

				//////////////////////////////////////////////////////////////////////////////////
				// if we come as far as here, it's proven that there's a relevant XMP property
				this->containsXMP = true;

				ID3_Support::ID3v2Frame* t = framesMap[ frameID ];
				if ( t != 0 ) // an (earlier, relevant) frame?
					t->active = false;

				// add this to map (needed on reconciliation)
				// note: above code reaches, that COMM/USLT frames
				// only then reach this map, if they are 'eng'(lish)
				// multiple occurences indeed leads to last one survives
				// ( in this map, all survive in the file )
				framesMap[ frameID ] = curFrame;

				// now write away as needed;
				// merely based on existence, relevant even if empty:
				if ( frameID == 0x54434D50) // TCMP if exists: part of compilation
				{
					this->xmpObj.SetProperty( kXMP_NS_DM, "partOfCompilation", "true" );
				} else if ( ! utf8string.empty() ) 
					switch( frameID )
					{
					case 0x54495432: // TIT2 -> title["x-default"]
					case 0x54434F50: // TCOP -> rights["x-default"]
						this->xmpObj.SetLocalizedText( reconProps[r].ns , reconProps[r].prop,"" , "x-default" , utf8string );				
						break;
					case 0x54434F4E: // TCON -> genre ( might be numeric string. prior to 2.3 a one-byte numeric value? )
						{					
							XMP_Int32 pos = 0; // going through input string
							if ( utf8string[pos] == '(' ) { // number in brackets?
								pos++;
								XMP_Uns8 iGenre = (XMP_Uns8) atoi( &utf8string.c_str()[1] );
								if ( (iGenre > 0) && (iGenre < 127) ) {
									utf8string.assign( Genres[iGenre] );
								} else {
									utf8string.assign( Genres[12] ); // "Other"
								}
							} else {
								// Text, let's "try" to find it anyway (for best upper/lower casing)
								int i;
								const char* genreCString = utf8string.c_str();
								for ( i=0; i < 127; ++i ) {
									if ( 
										   (strlen( genreCString ) == strlen(Genres[i])) &&  //fixing buggy stricmp behaviour on PPC
											(stricmp( genreCString, Genres[i] ) == 0 )) {
										utf8string.assign( Genres[i] ); // found, let's use the one in the list
										break;
									}
								}
								// otherwise (if for-loop runs through): leave as is
							}
							// write out property (derived or direct, but certainly non-numeric)
							this->xmpObj.SetProperty( reconProps[r].ns, reconProps[r].prop, utf8string );
						}
						break;
					case 0x54594552: // TYER -> xmp:CreateDate.year
						{
							try {	// Don't let wrong dates in id3 stop import.
								if ( !hasTDRC )
								{
									newDateTime.year = SXMPUtils::ConvertToInt( utf8string );
									newDateTime.hasDate = true;
								}
							} catch ( ... ) {
									// Do nothing, let other imports proceed.								
							}
							break;
						}
					case 0x54444154: //TDAT	-> xmp:CreateDate.month and day
						{
							try {	// Don't let wrong dates in id3 stop import.								
								// only if no TDRC has been found before
								//&& must have the format DDMM								
								if ( !hasTDRC && utf8string.length() == 4 )
								{
									newDateTime.day = SXMPUtils::ConvertToInt(utf8string.substr(0,2));
									newDateTime.month = SXMPUtils::ConvertToInt( utf8string.substr(2,2));
									newDateTime.hasDate = true;
								}
							} catch ( ... ) {								
								// Do nothing, let other imports proceed.								
							}
							break;
						}
					case 0x54494D45: //TIME	-> xmp:CreateDate.hours and minutes
						{
							try {	// Don't let wrong dates in id3 stop import.
								// only if no TDRC has been found before
								// && must have the format HHMM
								if ( !hasTDRC && utf8string.length() == 4 )
								{
									newDateTime.hour = SXMPUtils::ConvertToInt(utf8string.substr(0,2));
									newDateTime.minute = SXMPUtils::ConvertToInt( utf8string.substr(2,2));
									newDateTime.hasTime = true;
								}
							} catch ( ... ) {
								// Do nothing, let other imports proceed.								
							}
							break;
						}					
					case 0x54445243: // TDRC -> xmp:CreateDate //id3 v2.4
						{
							try {	// Don't let wrong dates in id3 stop import.								
								hasTDRC = true;
								// This always wins over TYER, TDAT and TIME
								SXMPUtils::ConvertToDate( utf8string, &newDateTime );					
							} catch ( ... ) {
								// Do nothing, let other imports proceed.								
							}
							break;
						}						
					default:
						// NB: COMM/USLT need no special fork regarding language alternatives/multiple occurence.
						//		relevant code forks are in ID3_Support::getFrameValue()
						this->xmpObj.SetProperty( reconProps[r].ns, reconProps[r].prop, utf8string );
						break;
					}//switch
			} //for iterator
		}//for reconProps

	// import DateTime
	XMP_DateTime oldDateTime;		
	xmpObj.GetProperty_Date( kXMP_NS_XMP, "CreateDate", &oldDateTime, 0 );


	
	// NOTE: no further validation nessesary the function "SetProperty_Date" will care about validating date and time
	// any exception will be caught and block import
	try {
		// invalid year will be catched and blocks import
		XMP_Validate( (newDateTime.year > 0 && newDateTime.year < 9999), "", kXMPErr_BadParam );
	
		// 2. if year has changed --> everything (date/time) has changed --> overwrite old DateTime with new DateTime
		if ( ( newDateTime.year != oldDateTime.year ) || // year has changed?
			// or has same year but new day/month (checking existance month indicates both (day and month) in our case)
			(( newDateTime.month != 0 ) && ( newDateTime.day != oldDateTime.day ||  newDateTime.month != oldDateTime.month )) ||
			// or has same year and same date but different time
			( newDateTime.hasTime && ( newDateTime.hour != oldDateTime.minute ||  newDateTime.hour != oldDateTime.minute )) )
		{
			this->xmpObj.SetProperty_Date( kXMP_NS_XMP, "CreateDate", newDateTime );
		} // ..else: keep old dateTime to don't loose data

	} catch ( ... ) {
		// Dont import invalid dates from ID3								
	}
	

	// very important to avoid multiple runs! (in which case I'd need to clean certain
	// fields (i.e. regarding ->active setting)
	this->processedXMP = true;

}	// MP3_MetaHandler::ProcessXMP
	inline XMP_Uns32 int32ToSynch ( XMP_Int32 value ) {
		XMP_Validate ( (0 <= 0x0FFFFFFF), "value too big", kXMPErr_InternalFailure );
		XMP_Uns32 r = (value & 0x0000007F) + ((value & 0x00003F80) << 1) +
					  ((value & 0x001FC000) << 2) + ((value & 0x0FE00000) << 3);
		return r;
	}
Exemple #11
0
void JunkChunk::changesAndSize( RIFF_MetaHandler* /*handler*/ )
{
	this->newSize = this->oldSize; // optimization at a later stage
	XMP_Validate( this->newSize <= 0xFFFFFFFFLL, "no single chunk may be above 4 GB", kXMPErr_InternalFailure );
	if ( this->id == kChunk_JUNQ ) this->hasChange = true;	// Force ID change to JUNK.
}
Exemple #12
0
void ContainerChunk::changesAndSize( RIFF_MetaHandler* handler )
{

	// Walk the container subtree adjusting the children that have size changes. The only containers
	// are RIFF and LIST chunks, they are treated differently.
	//
	// LISTs get recomposed as a whole. Existing JUNK children of a LIST are removed, existing real
	// children are left in order with their new size, new children have already been appended. The
	// LIST as a whole gets a new size that is the sum of the final children.
	//
	// Special rules apply to various children of a RIFF container. FIrst, adjacent JUNK children
	// are combined, this simplifies maximal reuse. The children are recursively adjusted in order
	// to get their final size.
	//
	// Try to determine the final placement of each RIFF child using general rules:
	//	- if the size is unchanged: leave at current location
	//	- if the chunk is at the end of the last RIFF chunk and grows: leave at current location
	//	- if there is enough following JUNK: add part of the JUNK, adjust remaining JUNK size
	//	- if it shrinks by 9 bytes or more: carve off trailing JUNK
	//	- try to find adequate JUNK in the current parent
	//
	// Use child-specific rules as a last resort:
	//	- if it is LIST:INFO: delete it, must be in first RIFF chunk
	//	- for others: move to end of last RIFF chunk, make old space JUNK

	// ! Don't create any junk chunks of exactly 8 bytes, just a header and no content. That has a
	// ! size field of zero, which hits a crashing bug in some versions of Windows Media Player.

	bool isRIFFContainer = (this->id == kChunk_RIFF);
	bool isLISTContainer = (this->id == kChunk_LIST);
	XMP_Enforce ( isRIFFContainer | isLISTContainer );

	XMP_Index childIndex;	// Could be local to the loops, this simplifies debuging. Need a signed type!
	Chunk * currChild;

	if ( this->children.empty() ) {
		if ( isRIFFContainer) {
			this->newSize = 12;	// Keep a minimal size container.
		} else {
			this->newSize = 0;	// Will get removed from parent in outer call.
		}
		this->hasChange = true;
		return;	// Nothing more to do without children.
	}

	// Collapse adjacent RIFF junk children, remove all LIST junk children. Work back to front to
	// simplify the effect of .erase() on the loop. Purposely ignore the first chunk.

	for ( childIndex = (XMP_Index)this->children.size() - 1; childIndex > 0; --childIndex ) {

		currChild = this->children[childIndex];
		if ( currChild->chunkType != chunk_JUNK ) continue;

		if ( isRIFFContainer ) {
			Chunk * prevChild = this->children[childIndex-1];
			if ( prevChild->chunkType != chunk_JUNK ) continue;
			prevChild->oldSize += currChild->oldSize;
			prevChild->newSize += currChild->newSize;
			prevChild->hasChange = true;
		}

		this->children.erase ( this->children.begin() + childIndex );
		delete currChild;
		this->hasChange = true;

	}

	// Process the children of RIFF and LIST containers to get their final size. Remove empty
	// children. Work back to front to simplify the effect of .erase() on the loop. Do not ignore
	// the first chunk.

	for ( childIndex = (XMP_Index)this->children.size() - 1; childIndex >= 0; --childIndex ) {

		currChild = this->children[childIndex];

		++handler->level;
		currChild->changesAndSize ( handler );
		--handler->level;

		if ( (currChild->newSize == 8) || (currChild->newSize == 0) ) {	// ! The newSIze is supposed to include the header.
			this->children.erase ( this->children.begin() + childIndex );
			delete currChild;
			this->hasChange = true;
		} else {
			this->hasChange |= currChild->hasChange;
			currChild->needSizeFix = (currChild->newSize != currChild->oldSize);
			if ( currChild->needSizeFix && (currChild->newSize > currChild->oldSize) &&
				 (this == handler->lastChunk) && (childIndex+1 == (XMP_Index)this->children.size()) ) {
				// Let an existing last-in-file chunk grow in-place. Shrinking is conceptually OK,
				// but complicates later sanity check that the main AVI chunk is not OK to append
				// other chunks later. Ignore new chunks, they might reuse junk space.
				if ( currChild->oldSize != 0 ) currChild->needSizeFix = false;
			}
		}

	}

	// Go through the children of a RIFF container, adjusting the placement as necessary. In brief,
	// things can only grow at the end of the last RIFF chunk, and non-junk chunks can't be shifted.

	if ( isRIFFContainer ) {

		for ( childIndex = 0; childIndex < (XMP_Index)this->children.size(); ++childIndex ) {

			currChild = this->children[childIndex];
			if ( ! currChild->needSizeFix ) continue;
			currChild->needSizeFix = false;

			XMP_Int64 sizeDiff = currChild->newSize - currChild->oldSize;	// Positive for growth.
			XMP_Uns8  padSize = (currChild->newSize & 1);	// Need a pad for odd size.

			// See if the following chunk is junk that can be utilized.

			Chunk * nextChild = 0;
			if ( childIndex+1 < (XMP_Index)this->children.size() ) nextChild = this->children[childIndex+1];

			if ( (nextChild != 0) && (nextChild->chunkType == chunk_JUNK) ) {
				if ( nextChild->newSize >= (9 + sizeDiff + padSize) ) {

					// Incorporate part of the trailing junk, or make the trailing junk grow.
					nextChild->newSize -= sizeDiff;
					nextChild->newSize -= padSize;
					nextChild->hasChange = true;
					continue;

				} else if (  nextChild->newSize == (sizeDiff + padSize)  ) {

					// Incorporate all of the trailing junk.
					this->children.erase ( this->children.begin() + childIndex + 1 );
					delete nextChild;
					continue;

				}
			}

			// See if the chunk shrinks enough to turn the leftover space into junk.

			if ( (sizeDiff + padSize) <= -9 ) {
				this->children.insert ( (this->children.begin() + childIndex + 1), new JunkChunk ( NULL, ((-sizeDiff) - padSize) ) );
				continue;
			}

			// Look through the parent for a usable span of junk.

			XMP_Index junkIndex;
			Chunk * junkChunk = 0;
			for ( junkIndex = 0; junkIndex < (XMP_Index)this->children.size(); ++junkIndex ) {
				junkChunk = this->children[junkIndex];
				if ( junkChunk->chunkType != chunk_JUNK ) continue;
				if ( (junkChunk->newSize >= (9 + currChild->newSize + padSize)) ||
					 (junkChunk->newSize == (currChild->newSize + padSize)) ) break;
			}

			if ( junkIndex < (XMP_Index)this->children.size() ) {

				// Use part or all of the junk for the relocated chunk, replace the old space with junk.

				if ( junkChunk->newSize == (currChild->newSize + padSize) ) {

					// The found junk is an exact fit.
					this->children[junkIndex] = currChild;
					delete junkChunk;

				} else {

					// The found junk has excess space. Insert the moving chunk and shrink the junk.
					XMP_Assert ( junkChunk->newSize >= (9 + currChild->newSize + padSize) );
					junkChunk->newSize -= (currChild->newSize + padSize);
					junkChunk->hasChange = true;
					this->children.insert ( (this->children.begin() + junkIndex), currChild );
					if ( junkIndex < childIndex ) ++childIndex;	// The insertion moved the current child.

				}

				if ( currChild->oldSize != 0 ) {
					this->children[childIndex] = new JunkChunk ( 0, currChild->oldSize );	// Replace the old space with junk.
				} else {
					this->children.erase ( this->children.begin() + childIndex );	// Remove the newly created chunk's old location.
					--childIndex;	// Make the next loop iteration not skip a chunk.
				}

				continue;

			}

			// If this is a LIST:INFO chunk not in the last of multiple RIFF chunks, then give up
			// and replace it with oldSize junk. Preserve the first RIFF chunk's original size.

			bool isListInfo = (currChild->id == kChunk_LIST) && (currChild->chunkType == chunk_CONTAINER) &&
							  (((ContainerChunk*)currChild)->containerType == kType_INFO);

			if ( isListInfo && (handler->riffChunks.size() > 1) &&
				 (this->id == kChunk_RIFF) && (this != handler->lastChunk) ) {

				if ( currChild->oldSize != 0 ) {
					this->children[childIndex] = new JunkChunk ( 0, currChild->oldSize );
				} else {
					this->children.erase ( this->children.begin() + childIndex );
					--childIndex;	// Make the next loop iteration not skip a chunk.
				}

				delete currChild;
				continue;

			}

			// Move the chunk to the end of the last RIFF chunk and make the old space junk.

			if ( (this == handler->lastChunk) && (childIndex+1 == (XMP_Index)this->children.size()) ) continue;	// Already last.

			handler->lastChunk->children.push_back( currChild );
			if ( currChild->oldSize != 0 ) {
				this->children[childIndex] = new JunkChunk ( 0, currChild->oldSize );	// Replace the old space with junk.
			} else {
				this->children.erase ( this->children.begin() + childIndex );	// Remove the newly created chunk's old location.
				--childIndex;	// Make the next loop iteration not skip a chunk.
			}

		}

	}

	// Compute the finished container's new size (for both RIFF and LIST).

	this->newSize = 12;	// Start with standard container header.
	for ( childIndex = 0; childIndex < (XMP_Index)this->children.size(); ++childIndex ) {
		currChild = this->children[childIndex];
		this->newSize += currChild->newSize;
		this->newSize += (this->newSize & 1);	// Round up if odd.
	}

	XMP_Validate ( (this->newSize <= 0xFFFFFFFFLL), "No single chunk may be above 4 GB", kXMPErr_Unimplemented );

}
Exemple #13
0
		// writes structure to file (starting at current position)
		void write(XMP_IO* file)
		{
			XMP_Validate( ID == GetUns32LE( &this->fields[o_sig] ), "invalid header on write", kXMPErr_BadFileFormat );
			file ->Write ( fields , TOTAL_SIZE  );
		}
Exemple #14
0
Chunk* getChunk ( ContainerChunk* parent, RIFF_MetaHandler* handler )
{
	XMP_IO* file = handler->parent->ioRef;
	XMP_Uns8 level = handler->level;
	XMP_Uns32 peek = XIO::PeekUns32_LE ( file );

	if ( level == 0 )
	{
		XMP_Validate( peek == kChunk_RIFF, "expected RIFF chunk not found", kXMPErr_BadFileFormat );
		XMP_Enforce( parent == NULL );
	}
	else
	{
		XMP_Validate( peek != kChunk_RIFF, "unexpected RIFF chunk below top-level", kXMPErr_BadFileFormat );
		XMP_Enforce( parent != NULL );
	}

	switch( peek )
	{
	case kChunk_RIFF:
		return new ContainerChunk( parent, handler );
	case kChunk_LIST:
		{
			if ( level != 1 ) break; // only care on this level

			// look further (beyond 4+4 = beyond id+size) to check on relevance
			file->Seek ( 8, kXMP_SeekFromCurrent  );
			XMP_Uns32 containerType = XIO::PeekUns32_LE ( file );
			file->Seek ( -8, kXMP_SeekFromCurrent  );

			bool isRelevantList = ( containerType== kType_INFO || containerType == kType_Tdat || containerType == kType_hdrl );
			if ( !isRelevantList ) break;
			return new ContainerChunk( parent, handler );
		}
	case kChunk_XMP:
			if ( level != 1 ) break; // ignore on inappropriate levels (might be compound metadata?)
			return new XMPChunk( parent, handler );
	case kChunk_DISP:
		{
			if ( level != 1 ) break; // only care on this level
			// peek even further to see if type is 0x001 and size is reasonable
			file ->Seek ( 4, kXMP_SeekFromCurrent  ); // jump DISP
			XMP_Uns32 dispSize = XIO::ReadUns32_LE( file );
			XMP_Uns32 dispType = XIO::ReadUns32_LE( file );
			file ->Seek ( -12, kXMP_SeekFromCurrent ); // rewind, be in front of chunkID again

			// only take as a relevant disp if both criteria met,
			// otherwise treat as generic chunk!
			if ( (dispType == 0x0001) && ( dispSize < 256 * 1024 ) )
			{
				ValueChunk* r = new ValueChunk( parent, handler );
				handler->dispChunk = r;
				return r;
			}
			break; // treat as irrelevant (non-0x1) DISP chunks as generic chunk
		}
	case kChunk_bext:
		{
			if ( level != 1 ) break; // only care on this level
			// store for now in a value chunk
			ValueChunk* r = new ValueChunk( parent, handler );
			handler->bextChunk = r;
			return r;
		}
	case kChunk_PrmL:
		{
			if ( level != 1 ) break; // only care on this level
			ValueChunk* r = new ValueChunk( parent, handler );
			handler->prmlChunk = r;
			return r;
		}
	case kChunk_Cr8r:
		{
			if ( level != 1 ) break; // only care on this level
			ValueChunk* r = new ValueChunk( parent, handler );
			handler->cr8rChunk = r;
			return r;
		}
	case kChunk_JUNQ:
	case kChunk_JUNK:
		{
			JunkChunk* r = new JunkChunk( parent, handler );
			return r;
		}
	case kChunk_IDIT:
		{
			if ( level != 2 ) break; // only care on this level
			ValueChunk* r = new ValueChunk( parent, handler );
			handler->iditChunk = r;
			return r;
		}
	}
	// this "default:" section must be ouside switch bracket, to be
	// reachable by all those break statements above:


	// digest 'valuable' container chunks: LIST:INFO, LIST:Tdat
	bool insideRelevantList = ( level==2 && parent->id == kChunk_LIST
		&& ( parent->containerType== kType_INFO || parent->containerType == kType_Tdat ));

	if ( insideRelevantList )
	{
		ValueChunk* r = new ValueChunk( parent, handler );
		return r;
	}

	// general chunk of no interest, treat as unknown blob
	return new Chunk( parent, handler, true, chunk_GENERAL );
}
Exemple #15
0
// b) parsing
ContainerChunk::ContainerChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ) : Chunk( parent, handler, false, chunk_CONTAINER )
{
	bool repairMode = ( 0 != ( handler->parent->openFlags & kXMPFiles_OpenRepairFile ));

	try
	{
		XMP_IO* file = handler->parent->ioRef;
		XMP_Uns8 level = handler->level;

		// get type of container chunk
		this->containerType = XIO::ReadUns32_LE( file );

		// ensure legality of top-level chunks
		if ( level == 0 && handler->riffChunks.size() > 0 )
		{
			XMP_Validate( handler->parent->format == kXMP_AVIFile, "only AVI may have multiple top-level chunks", kXMPErr_BadFileFormat );
			XMP_Validate( this->containerType == kType_AVIX, "all chunks beyond main chunk must be type AVIX", kXMPErr_BadFileFormat );
		}

		// has *relevant* subChunks? (there might be e.g. non-INFO LIST chunks we don't care about)
		bool hasSubChunks = ( ( this->id == kChunk_RIFF ) ||
							  ( this->id == kChunk_LIST && this->containerType == kType_INFO ) ||
							  ( this->id == kChunk_LIST && this->containerType == kType_Tdat ) ||
							  ( this->id == kChunk_LIST && this->containerType == kType_hdrl )
						  );
		XMP_Int64 endOfChunk = this->oldPos + this->oldSize;

		// this statement catches beyond-EoF-offsets on any level
		// exception: level 0, tolerate if in repairMode
		if ( (level == 0) && repairMode && (endOfChunk > handler->oldFileSize) )
		{
			endOfChunk = handler->oldFileSize; // assign actual file size
			this->oldSize = endOfChunk - this->oldPos; //reversely calculate correct oldSize
		}

		XMP_Validate( endOfChunk <= handler->oldFileSize, "offset beyond EoF", kXMPErr_BadFileFormat );

		Chunk* curChild = 0;
		if ( hasSubChunks )
		{
			handler->level++;
			while ( file->Offset() < endOfChunk )
			{
				curChild = RIFF::getChunk( this, handler );

				// digest pad byte - no value validation (0), since some 3rd party files have non-0-padding.
				if ( file->Offset() % 2 == 1 )
				{
					// [1521093] tolerate missing pad byte at very end of file:
					XMP_Uns8 pad;
					file->Read ( &pad, 1 );  // Read the pad, tolerate being at EOF.

				}

				// within relevant LISTs, relentlesly delete junk chunks (create a single one
				// at end as part of updateAndChanges()
				if ( (containerType== kType_INFO || containerType == kType_Tdat)
						&& ( curChild->chunkType == chunk_JUNK ) )
				{
						this->children.pop_back();
						delete curChild;
				} // for other chunks: join neighouring Junk chunks into one
				else if ( (curChild->chunkType == chunk_JUNK) && ( this->children.size() >= 2 ) )
				{
					// nb: if there are e.g 2 chunks, then last one is at(1), prev one at(0) ==> '-2'
					Chunk* prevChunk = this->children.at( this->children.size() - 2 );
					if ( prevChunk->chunkType == chunk_JUNK )
					{
						// stack up size to prior chunk
						prevChunk->oldSize += curChild->oldSize;
						prevChunk->newSize += curChild->newSize;
						XMP_Enforce( prevChunk->oldSize == prevChunk->newSize );
						// destroy current chunk
						this->children.pop_back();
						delete curChild;
					}
				}
			}
			handler->level--;
			XMP_Validate( file->Offset() == endOfChunk, "subchunks exceed outer chunk size", kXMPErr_BadFileFormat );

			// pointers for later legacy processing
			if ( level==1 && this->id==kChunk_LIST && this->containerType == kType_INFO )
				handler->listInfoChunk = this;
			if ( level==1 && this->id==kChunk_LIST && this->containerType == kType_Tdat )
				handler->listTdatChunk = this;
			if ( level == 1 && this->id == kChunk_LIST && this->containerType == kType_hdrl )
				handler->listHdlrChunk = this;
		}
		else // skip non-interest container chunk
		{
			file->Seek ( (this->oldSize - 8 - 4), kXMP_SeekFromCurrent );
		} // if - else

	} // try
	catch (XMP_Error& e) {
		this->release(); // free resources
		if ( this->parent != 0)
			this->parent->children.pop_back(); // hereby taken care of, so removing myself...

		throw e;         // re-throw
	}
}
// =================================================================================================
// MP3_MetaHandler::UpdateFile
// ===========================
void MP3_MetaHandler::UpdateFile ( bool doSafeUpdate )
{
    if ( doSafeUpdate ) XMP_Throw ( "MP3_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable );

    XMP_IO* file = this->parent->ioRef;

    // leave 2.3 resp. 2.4 header, since we want to let alone
    // and don't know enough about the encoding of unrelated frames...
    XMP_Assert( this->containsXMP );

    tagIsDirty = false;
    mustShift = false;

    // write out native properties:
    // * update existing ones
    // * create new frames as needed
    // * delete frames if property is gone!
    // see what there is to do for us:

    // RECON LOOP START
    for (int r = 0; reconProps[r].mainID != 0; r++ ) {

        std::string value;
        bool needDescriptor = false;
        bool needEncodingByte = true;

        XMP_Uns32 logicalID = GetUns32BE ( reconProps[r].mainID );
        XMP_Uns32 storedID = logicalID;
        if ( this->majorVersion == 2 ) storedID = GetUns32BE ( reconProps[r].v22ID );

        ID3v2Frame* frame = framesMap[ storedID ];	// the actual frame (if already existing)

        // get XMP property
        //	* honour specific exceptions
        //  * leave value empty() if it doesn't exist ==> frame must be delete/not created
        switch ( logicalID ) {

        case 0x54434D50: // TCMP if exists: part of compilation
            if ( xmpObj.GetProperty( kXMP_NS_DM, "partOfCompilation", &value, 0 ) && ( 0 == stricmp( value.c_str(), "true" ) )) {
                value = "1"; // set a TCMP frame of value 1
            } else {
                value.erase(); // delete/prevent creation of frame
            }
            break;

        case 0x54495432: // TIT2 -> title["x-default"]
        case 0x54434F50: // TCOP -> rights["x-default"]
            if (! xmpObj.GetLocalizedText( reconProps[r].ns, reconProps[r].prop, "", "x-default", 0, &value, 0 )) value.erase(); // if not, erase string.
            break;

        case 0x54434F4E: // TCON -> genre
        {
            bool found = xmpObj.GetProperty ( reconProps[r].ns, reconProps[r].prop, &value, 0 );
            if ( found ) {
                std::string xmpValue = value;
                ID3_Support::GenreUtils::ConvertGenreToID3 ( xmpValue.c_str(), &value );
            }
        }
        break;

        case 0x434F4D4D: // COMM
        case 0x55534C54: // USLT, both need descriptor.
            needDescriptor = true;
            if (! xmpObj.GetProperty( reconProps[r].ns, reconProps[r].prop, &value, 0 )) value.erase();
            break;

        case 0x54594552: //TYER
        case 0x54444154: //TDAT
        case 0x54494D45: //TIME
        {
            if ( majorVersion <= 3 ) {	// TYER, TIME and TDAT depricated since v. 2.4 -> else use TDRC

                XMP_DateTime dateTime;
                if (! xmpObj.GetProperty_Date( reconProps[r].ns, reconProps[r].prop, &dateTime, 0 )) {	// nothing found? -> Erase string. (Leads to Unset below)
                    value.erase();
                    break;
                }

                // TYER
                if ( logicalID == 0x54594552 ) {
                    XMP_Validate( dateTime.year <= 9999 && dateTime.year > 0, "Year is out of range", kXMPErr_BadParam);
                    // get only Year!
                    SXMPUtils::ConvertFromInt( dateTime.year, "", &value );
                    break;
                } else if ( logicalID == 0x54444154 && dateTime.hasDate ) {
                    std::string day, month;
                    SXMPUtils::ConvertFromInt( dateTime.day, "", &day );
                    SXMPUtils::ConvertFromInt( dateTime.month, "", &month );
                    if ( dateTime.day < 10 )
                        value = "0";
                    value += day;
                    if ( dateTime.month < 10 )
                        value += "0";
                    value += month;
                    break;
                } else if ( logicalID == 0x54494D45 && dateTime.hasTime ) {
                    std::string hour, minute;
                    SXMPUtils::ConvertFromInt( dateTime.hour, "", &hour );
                    SXMPUtils::ConvertFromInt( dateTime.minute, "", &minute );
                    if ( dateTime.hour < 10 )
                        value = "0";
                    value += hour;
                    if ( dateTime.minute < 10 )
                        value += "0";
                    value += minute;
                    break;
                } else {
                    value.erase();
                    break;
                }
            } else {
                value.erase();
                break;
            }
        }
        break;

        case 0x54445243: //TDRC (only v2.4)
        {
            // only export for id3 > v2.4
            if ( majorVersion > 3 )  {
                if (! xmpObj.GetProperty( reconProps[r].ns, reconProps[r].prop, &value, 0 )) value.erase();
            }
            break;
        }
        break;

        case 0x57434F50: //WCOP
            needEncodingByte = false;
            if (! xmpObj.GetProperty( reconProps[r].ns, reconProps[r].prop, &value, 0 )) value.erase(); // if not, erase string
            break;

        case 0x5452434B: // TRCK
        case 0x54504F53: // TPOS
        // no break, go on:

        default:
            if (! xmpObj.GetProperty( reconProps[r].ns, reconProps[r].prop, &value, 0 )) value.erase(); // if not, erase string
            break;

        }

        // [XMP exist] x [frame exist] => four cases:
        // 1/4) nothing before, nothing now
        if ( value.empty() && (frame==0)) continue; // nothing to do

        // all else means there will be rewrite work to do:
        tagIsDirty = true;

        // 2/4) value before, now gone:
        if ( value.empty() && (frame!=0)) {
            frame->active = false; //mark for non-use
            continue;
        }

        // 3/4) no old value, create new value
        bool needUTF16 = false;
        if ( needEncodingByte ) needUTF16 = (! ReconcileUtils::IsASCII ( value.c_str(), value.size() ) );
        if ( frame != 0 ) {
            frame->setFrameValue( value, needDescriptor, needUTF16, false, needEncodingByte );
        } else {
            ID3v2Frame* newFrame=new ID3v2Frame( storedID );
            newFrame->setFrameValue( value, needDescriptor,  needUTF16, false, needEncodingByte ); //always write as utf16-le incl. BOM
            framesVector.push_back( newFrame );
            framesMap[ storedID ] = newFrame;
            continue;
        }

    } 	// RECON LOOP END

    /////////////////////////////////////////////////////////////////////////////////
    // (Re)Build XMP frame:

    XMP_Uns32 xmpID = XMP_V23_ID;
    if ( this->majorVersion == 2 ) xmpID = XMP_V22_ID;

    ID3v2Frame* frame = framesMap[ xmpID ];
    if ( frame != 0 ) {
        frame->setFrameValue( this->xmpPacket, false, false, true );
    } else {
        ID3v2Frame* newFrame=new ID3v2Frame( xmpID );
        newFrame->setFrameValue ( this->xmpPacket, false, false, true );
        framesVector.push_back ( newFrame );
        framesMap[ xmpID ] = newFrame;
    }

    ////////////////////////////////////////////////////////////////////////////////
    // Decision making

    XMP_Int32 frameHeaderSize = ID3v2Frame::kV23_FrameHeaderSize;
    if ( this->majorVersion == 2 ) frameHeaderSize = ID3v2Frame::kV22_FrameHeaderSize;

    newFramesSize = 0;
    for ( XMP_Uns32 i = 0; i < framesVector.size(); i++ ) {
        if ( framesVector[i]->active ) newFramesSize += (frameHeaderSize + framesVector[i]->contentSize);
    }

    mustShift = (newFramesSize > (XMP_Int64)(oldTagSize - ID3Header::kID3_TagHeaderSize)) ||
                //optimization: If more than 8K can be saved by rewriting the MP3, go do it:
                ((newFramesSize + 8*1024) < oldTagSize );

    if ( ! mustShift )	{	// fill what we got
        newTagSize = oldTagSize;
    } else { // if need to shift anyway, get some nice 2K padding
        newTagSize = newFramesSize + 2048 + ID3Header::kID3_TagHeaderSize;
    }
    newPadding = newTagSize - ID3Header::kID3_TagHeaderSize - newFramesSize;

    // shifting needed? -> shift
    if ( mustShift ) {
        XMP_Int64 filesize = file ->Length();
        if ( this->hasID3Tag ) {
            XIO::Move ( file, oldTagSize, file, newTagSize, filesize - oldTagSize ); //fix [2338569]
        } else {
            XIO::Move ( file, 0, file, newTagSize, filesize ); // move entire file up.
        }
    }

    // correct size stuff, write out header
    file ->Rewind();
    id3Header.write ( file, newTagSize );

    // write out tags
    for ( XMP_Uns32 i = 0; i < framesVector.size(); i++ ) {
        if ( framesVector[i]->active ) framesVector[i]->write ( file, majorVersion );
    }

    // write out padding:
    for ( XMP_Int64 i = newPadding; i > 0; ) {
        const XMP_Uns64 zero = 0;
        if ( i >= 8 ) {
            file->Write ( &zero, 8  );
            i -= 8;
            continue;
        }
        file->Write ( &zero, 1  );
        i--;
    }

    // check end of file for ID3v1 tag
    XMP_Int64 possibleTruncationPoint = file->Seek ( -128, kXMP_SeekFromEnd );
    bool alreadyHasID3v1 = (XIO::ReadInt32_BE( file ) & 0xFFFFFF00) == 0x54414700; // "TAG"
    if ( ! alreadyHasID3v1 ) file->Seek ( 128, kXMP_SeekFromEnd );	// Seek will extend the file.
    id3v1Tag.write( file, &this->xmpObj );

    this->needsUpdate = false; //do last for safety reasons

}	// MP3_MetaHandler::UpdateFile
Exemple #17
0
			// reads entire structure from file (starting at current position)
			void read(XMP_IO* file)
			{
				this->release();

				file->ReadAll ( fields, FIXED_SIZE );
				XMP_Validate( SIG == GetUns32LE( &this->fields[CDFileHeader::o_sig] ), "invalid header", kXMPErr_BadFileFormat );

				filenameLen   = GetUns16LE( &this->fields[CDFileHeader::o_fileNameLength] );
				extraFieldLen = GetUns16LE( &this->fields[CDFileHeader::o_extraFieldLength] );
				commentLen    = GetUns16LE( &this->fields[CDFileHeader::o_commentLength] );

				if (filenameLen) {
					filename = new char[filenameLen];
					file->ReadAll ( filename, filenameLen );
				}
				if (extraFieldLen) {
					extraField = new char[extraFieldLen];
					file->ReadAll ( extraField, extraFieldLen );
				}
				if (commentLen) {
					comment = new char[commentLen];
					file->ReadAll ( comment, commentLen );
				}

				////// GET ACTUAL 64 BIT VALUES //////////////////////////////////////////////
				// get 32bit goodies first, correct later
				sizeUncompressed		= GetUns32LE( &fields[o_sizeUncompressed] );
				sizeCompressed			= GetUns32LE( &fields[o_sizeCompressed] );
				offsetLocalHeader		= GetUns32LE( &fields[o_offsetLocalHeader] );

				XMP_Int32 offset = 0;
				while ( offset < extraFieldLen )
				{
					XMP_Validate( (extraFieldLen - offset) >= 4, "need 4 bytes for next header ID+len", kXMPErr_BadFileFormat);
					XMP_Uns16 headerID = GetUns16LE( &extraField[offset] );
					XMP_Uns16 dataSize = GetUns16LE( &extraField[offset+2] );
					offset += 4;

					XMP_Validate( (extraFieldLen - offset) <= dataSize,
									"actual field lenght not given", kXMPErr_BadFileFormat);
					if ( headerID == 0x1 ) //we only care about "Zip64 extended information extra field"
					{
						XMP_Validate( offset < extraFieldLen, "extra field too short", kXMPErr_BadFileFormat);
						if (sizeUncompressed == 0xffffffff)
						{
							sizeUncompressed = GetUns64LE( &extraField[offset] );
							offset += 8;
						}
						if (sizeCompressed == 0xffffffff)
						{
							sizeCompressed = GetUns64LE( &extraField[offset] );
							offset += 8;
						}
						if (offsetLocalHeader == 0xffffffff)
						{
							offsetLocalHeader = GetUns64LE( &extraField[offset] );
							offset += 8;
						}
					}
					else
					{
						offset += dataSize;
					} // if
				} // while
			} // read()
Exemple #18
0
// =================================================================================================
// MP3_MetaHandler::CacheFileData
// ==============================
void MP3_MetaHandler::CacheFileData()
{
	//*** abort procedures
	this->containsXMP = false;		//assume no XMP for now

	LFA_FileRef file = this->parent->fileRef;
	XMP_PacketInfo &packetInfo = this->packetInfo;

	LFA_Rewind(file);

	hasID3Tag = id3Header.read( file );
	majorVersion = id3Header.fields[ID3Header::o_version_major];
	minorVersion = id3Header.fields[ID3Header::o_version_minor];
	hasExtHeader = (0 != ( 0x40 & id3Header.fields[ID3Header::o_flags])); //'naturally' false if no ID3Tag
	hasFooter = ( 0 != ( 0x10 & id3Header.fields[ID3Header::o_flags])); //'naturally' false if no ID3Tag

	// stored size is w/o initial header (thus adding 10)
	// + but extended header (if existing)
	// + padding + frames after unsynchronisation (?)
	// (if no ID3 tag existing, constructed default correctly sets size to 10.)
	oldTagSize = 10 + synchToInt32(GetUns32BE( &id3Header.fields[ID3Header::o_size] ));

	if (hasExtHeader)
	{
		extHeaderSize = synchToInt32( LFA_ReadInt32_BE( file));
		XMP_Uns8 extHeaderNumFlagBytes = LFA_ReadUns8( file );

		// v2.3 doesn't include the size, while v2.4 does
		if ( majorVersion < 4 ) extHeaderSize += 4; 
		XMP_Validate( extHeaderSize >= 6, "extHeader size too small", kXMPErr_BadFileFormat );

		bool ok;
		LFA_Seek(file, extHeaderSize - 6, SEEK_CUR , &ok);
		XMP_Assert(ok);
	}
	else
	{
		extHeaderSize = 0; // := there is no such header.
	}

	this->framesVector.clear(); //mac precaution
	ID3v2Frame* curFrame = 0; // reusable

	////////////////////////////////////////////////////
	// read frames
	while ( LFA_Tell(file) < oldTagSize )
	{
		curFrame = new ID3v2Frame();

		try {
			XMP_Int64 frameSize = curFrame->read( file, majorVersion );
			if (frameSize == 0) // no more frames coming => proceed to padding
			{
				delete curFrame; // ..since not becoming part of vector for latter delete.
				break;			 // not a throw. There's nothing wrong with padding.
			}
			this->containsXMP = true;
		} catch( XMP_Error e)
		{
			delete curFrame;
			XMP_Throw( e.GetErrMsg(), e.GetID()); // rethrow
		}

		// these are both pointer assignments, no (copy) construction
		// (MemLeak Note: for all things pushed, memory cleanup is taken care of in destructor.)
		this->framesVector.push_back( curFrame );

		//remember XMP-Frame, if it occurs:
		if ( CheckBytes( &curFrame->fields[ID3v2Frame::o_id], "PRIV", 4 ))
			if( curFrame->contentSize > 8 ) // to avoid adress violation on very small non-XMP PRIV frames
				if( CheckBytes( &curFrame->content[0], "XMP\0", 4 ))
				{
					// be sure that this is the first packet (all else would be illegal format)
					XMP_Validate( framesMap[ XMP_FRAME_ID] == 0, "two XMP packets in one file", kXMPErr_BadFileFormat );
					//add this to map, needed on reconciliation
					framesMap[ XMP_FRAME_ID ] = curFrame;

					this->packetInfo.length = curFrame->contentSize - 4; // content minus "XMP\0"
					this->packetInfo.offset = ( LFA_Tell(file) - this->packetInfo.length );

					this->xmpPacket.erase(); //safety
					this->xmpPacket.assign( &curFrame->content[4], curFrame->contentSize - 4 );
					this->containsXMP = true; // do this last, after all possible failure
				}

				// No space for another frame? => assume into ID3v2.4 padding.
				if ( LFA_Tell(file) + 10 >= oldTagSize )
					break;
	}

	////////////////////////////////////////////////////
	// padding
	oldPadding = oldTagSize - LFA_Tell( file );
	oldFramesSize = oldTagSize - 10 - oldPadding;

	XMP_Validate( oldPadding >= 0, "illegal oldTagSize or padding value", kXMPErr_BadFileFormat );

	for ( XMP_Int64 i = oldPadding; i > 0;)
	{
		if ( i >= 8 ) // worthwhile optimization
		{
			if ( LFA_ReadInt64_BE(file) != 0 )
				XMP_Throw ( "padding not nulled out.", kXMPErr_BadFileFormat );
			i -= 8;
			continue;
		}
		if ( LFA_ReadUns8(file) != 0)
			XMP_Throw ( "padding(2) not nulled out.", kXMPErr_BadFileFormat );
		i--;
	}

	//// read ID3v1 tag
	if ( ! this->containsXMP ) // all else has priority
	{
		this->containsXMP = id3v1Tag.read( file, &this->xmpObj );
	}

}	// MP3_MetaHandler::CacheFileData
Exemple #19
0
// =================================================================================================
// MP3_MetaHandler::UpdateFile
// ===========================
void MP3_MetaHandler::UpdateFile ( bool doSafeUpdate )
{
	if ( doSafeUpdate )
		XMP_Throw ( "UCF_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable );

	LFA_FileRef file ( this->parent->fileRef );

	// leave 2.3 resp. 2.4 header, since we want to let alone 
	// and don't know enough about the encoding of unrelated frames...
	XMP_Assert( this->containsXMP );

	tagIsDirty = false;
	mustShift = false;

	// write out native properties:
	// * update existing ones
	// * create new frames as needed
	// * delete frames if property is gone!
	// see what there is to do for us:

	// RECON LOOP START
	for (int r=0; reconProps[r].frameID != 0; r++)
	{
		std::string value;
		bool needDescriptor = false;
		bool need16LE = true;
		bool needEncodingByte = true;

		XMP_Uns32 frameID = GetUns32BE( reconProps[r].frameID ); // the would-be frame
		ID3v2Frame* frame = framesMap[ frameID ];	// the actual frame (if already existing)

		// get XMP property
		//	* honour specific exceptions
		//  * leave value empty() if it doesn't exist ==> frame must be delete/not created
		switch( frameID )
		{
		case 0x54434D50: // TCMP if exists: part of compilation
			need16LE = false;
			if ( xmpObj.GetProperty( kXMP_NS_DM, "partOfCompilation", &value, 0 )
				&& ( 0 == stricmp( value.c_str(), "true" ) ))
				value = "1"; // set a TCMP frame of value 1
			else
				value.erase(); // delete/prevent creation of frame
			break;

		case 0x54495432: // TIT2 -> title["x-default"]
		case 0x54434F50: // TCOP -> rights["x-default"]
			if (! xmpObj.GetLocalizedText( reconProps[r].ns, reconProps[r].prop, "", "x-default", 0, &value, 0 )) //jaja, side effect.
				value.erase(); // if not, erase string.
			break;
		case 0x54434F4E: // TCON -> genre
			{
				if (! xmpObj.GetProperty( reconProps[r].ns, reconProps[r].prop, &value, 0 ))
				{	// nothing found? -> Erase string. (Leads to Unset below)
					value.erase();
					break;
				}
				// genre: we need to get the number back, if possible
				XMP_Int16 iFound = -1; // flag as "not found"
				for ( int i=0; i < 127; ++i ) {
					if ( (value.size() == strlen(Genres[i])) 
					  && (stricmp( value.c_str(), Genres[i] ) == 0) ) //fixing stricmp buggy on PPC
						{
							iFound = i; // Found
							break;
						}
				}
				if ( iFound == -1 ) // not found known numeric genre?
					break; // stick with the literal value (also for v2.3, since this is common practice!)

				need16LE = false; // no unicode need for (##)
				char strGenre[64];
				snprintf ( strGenre, sizeof(strGenre), "(%d)", iFound );	// AUDIT: Using sizeof(strGenre) is safe.
				value.assign(strGenre);
			}
			break;
		case 0x434F4D4D: // COMM
		case 0x55534C54: // USLT, both need descriptor.
			needDescriptor = true;
			if (! xmpObj.GetProperty( reconProps[r].ns, reconProps[r].prop, &value, 0 ))
				value.erase();
			break;
		case 0x54594552: //TYER
		case 0x54444154: //TDAT	
		case 0x54494D45: //TIME			
			{
				if ( majorVersion <= 3 ) // TYER, TIME and TDAT depricated since v. 2.4 -> else use TDRC
				{
					XMP_DateTime dateTime;	
					if (! xmpObj.GetProperty_Date( reconProps[r].ns, reconProps[r].prop, &dateTime, 0 ))
					{	// nothing found? -> Erase string. (Leads to Unset below)
						value.erase();
						break;
					}

					// TYER
					if ( frameID == 0x54594552 )
					{
						XMP_Validate( dateTime.year <= 9999 && dateTime.year > 0 , "Year is out of range", kXMPErr_BadParam);
						// get only Year!
						SXMPUtils::ConvertFromInt( dateTime.year, "", &value );
						break;
					} 
					// TDAT
					else if ( frameID == 0x54444154 && dateTime.hasDate ) // date validation made by "GetProperty_Date"
					{						
						std::string day, month;
						SXMPUtils::ConvertFromInt( dateTime.day, "", &day );
						SXMPUtils::ConvertFromInt( dateTime.month, "", &month );
						if ( dateTime.day < 10 )
							value = "0";
						value += day;
						if ( dateTime.month < 10 )
							value += "0";
						value += month;
						break;
					} 
					// TIME
					else if ( frameID == 0x54494D45 && dateTime.hasTime ) // time validation made by "GetProperty_Date" )
					{
						std::string hour, minute;
						SXMPUtils::ConvertFromInt( dateTime.hour, "", &hour );
						SXMPUtils::ConvertFromInt( dateTime.minute, "", &minute );
						if ( dateTime.hour < 10 )
							value = "0";
						value += hour;
						if ( dateTime.minute < 10 )
							value += "0";
						value += minute;
						break;
					} 
					else
					{
						value.erase();
						break;
					}
				}
				else // v.2.4 --> delete TYER,TIME or TDAT & write into TDRC
				{
					value.erase();
					break;
				}
			}
		case 0x54445243: //TDRC (only v2.4)
			{
				// only export for id3 > v2.4
				if ( majorVersion > 3 ) // (only v2.4)
				{
					if (! xmpObj.GetProperty( reconProps[r].ns, reconProps[r].prop, &value, 0 ))
						value.erase();				
				}
				break;
			}
		break;
		case 0x57434F50: //WCOP
			needEncodingByte = false;
			need16LE = false;
			if (! xmpObj.GetProperty( reconProps[r].ns, reconProps[r].prop, &value, 0 ))
				value.erase(); // if not, erase string
		break;
		case 0x5452434B: // TRCK
		case 0x54504F53: // TPOS
			need16LE = false;
			// no break, go on:
		default:
			if (! xmpObj.GetProperty( reconProps[r].ns, reconProps[r].prop, &value, 0 ))
				value.erase(); // if not, erase string
			break;
		}

		// [XMP exist] x [frame exist] => four cases:
		// 1/4) nothing before, nothing now
		if ( value.empty() && (frame==0))  
			continue; // nothing to do

		// all else means there will be rewrite work to do:
		tagIsDirty = true;

		// 2/4) value before, now gone:
		if ( value.empty() && (frame!=0))
		{
			frame->active = false; //mark for non-use
			continue;
		}
		// 3/4) no old value, create new value
		if ( frame==0)
		{	
			ID3v2Frame* newFrame=new ID3v2Frame( frameID );
			newFrame->setFrameValue( value, needDescriptor,  need16LE, false, needEncodingByte ); //always write as utf16-le incl. BOM
			framesVector.push_back( newFrame );
			framesMap[ frameID ] = newFrame;
			continue;
		}
		// 4/4) change existing value
		else // resp. change frame
		{
			frame->setFrameValue( value, needDescriptor, need16LE, false, needEncodingByte );
		}
	} 	// RECON LOOP END

	/////////////////////////////////////////////////////////////////////////////////
	// (Re)Build XMP frame:
	ID3v2Frame* frame = framesMap[ XMP_FRAME_ID ];
	if ( frame == 0)
	{	
		ID3v2Frame* newFrame=new ID3v2Frame( XMP_FRAME_ID );
		newFrame->setFrameValue( this->xmpPacket, false, false, true );
		framesVector.push_back( newFrame );
		framesMap[ XMP_FRAME_ID ] = newFrame;
	} else
		frame->setFrameValue( this->xmpPacket, false, false, true );

	////////////////////////////////////////////////////////////////////////////////
	// Decision making
	newFramesSize = 0;
	for ( XMP_Uns32 i=0; i < framesVector.size(); i++)
	{
		if (framesVector[i]->active)
			newFramesSize += (10 + framesVector[i]->contentSize );
	}

	mustShift = ( newFramesSize > (oldTagSize - 10)) ||
	//optimization: If more than 8K can be saved by rewriting the MP3, go do it:
				((newFramesSize + 8*1024) < oldTagSize );

	if (!mustShift)	// fill what we got
		newTagSize = oldTagSize;
	else // if need to shift anyway, get some nice 2K padding
		newTagSize = newFramesSize + 2048 + 10;
	newPadding = newTagSize -10 - newFramesSize;

	// shifting needed? -> shift
	if ( mustShift )
	{
		XMP_Int64 filesize = LFA_Measure( file );
		if ( this->hasID3Tag )
			LFA_Move( file, oldTagSize, file, newTagSize , filesize - oldTagSize ); //fix [2338569]
		else
			LFA_Move( file, 0, file, newTagSize, filesize ); // move entire file up.
	}

	// correct size stuff, write out header
	LFA_Rewind( file );
	id3Header.write( file, newTagSize);	

	// write out tags
	for ( XMP_Uns32 i=0; i < framesVector.size(); i++)
	{
		if ( framesVector[i]->active)
			framesVector[i]->write(file, majorVersion);
	}

	// write out padding:
	for ( XMP_Int64 i = newPadding; i > 0;)
	{
		const XMP_Uns64 zero = 0;
		if ( i >= 8 ) // worthwhile optimization
		{
			LFA_Write( file, &zero, 8 );
			i -= 8;
			continue;
		}
		LFA_Write( file, &zero, 1 );
		i--;
	}

	// check end of file for ID3v1 tag
	XMP_Int64 possibleTruncationPoint = LFA_Seek( file, -128, SEEK_END);
	bool alreadyHasID3v1 = (LFA_ReadInt32_BE( file ) & 0xFFFFFF00) == 0x54414700; // "TAG"
	if ( ! alreadyHasID3v1 ) // extend file
		LFA_Extend( file, LFA_Measure( file ) + 128 );
	id3v1Tag.write( file, &this->xmpObj );

	this->needsUpdate = false; //do last for safety reasons
}	// MP3_MetaHandler::UpdateFile
void MP3_MetaHandler::CacheFileData()
{

    //*** abort procedures
    this->containsXMP = false;		//assume no XMP for now

    XMP_IO* file = this->parent->ioRef;
    XMP_PacketInfo &packetInfo = this->packetInfo;

    file->Rewind();

    this->hasID3Tag = this->id3Header.read( file );
    this->majorVersion = this->id3Header.fields[ID3Header::o_vMajor];
    this->minorVersion = this->id3Header.fields[ID3Header::o_vMinor];
    this->hasExtHeader = (0 != ( 0x40 & this->id3Header.fields[ID3Header::o_flags])); //'naturally' false if no ID3Tag
    this->hasFooter = ( 0 != ( 0x10 & this->id3Header.fields[ID3Header::o_flags])); //'naturally' false if no ID3Tag

    // stored size is w/o initial header (thus adding 10)
    // + but extended header (if existing)
    // + padding + frames after unsynchronisation (?)
    // (if no ID3 tag existing, constructed default correctly sets size to 10.)
    this->oldTagSize = ID3Header::kID3_TagHeaderSize + synchToInt32(GetUns32BE( &id3Header.fields[ID3Header::o_size] ));

    if ( ! hasExtHeader ) {

        this->extHeaderSize = 0; // := there is no such header.

    } else {

        this->extHeaderSize = synchToInt32( XIO::ReadInt32_BE( file));
        XMP_Uns8 extHeaderNumFlagBytes = XIO::ReadUns8( file );

        // v2.3 doesn't include the size, while v2.4 does
        if ( this->majorVersion < 4 ) this->extHeaderSize += 4;
        XMP_Validate( this->extHeaderSize >= 6, "extHeader size too small", kXMPErr_BadFileFormat );

        file->Seek ( this->extHeaderSize - 6, kXMP_SeekFromCurrent );

    }

    this->framesVector.clear(); //mac precaution
    ID3v2Frame* curFrame = 0; // reusable

    ////////////////////////////////////////////////////
    // read frames

    XMP_Uns32 xmpID = XMP_V23_ID;
    if ( this->majorVersion == 2 ) xmpID = XMP_V22_ID;

    while ( file->Offset() < this->oldTagSize ) {

        curFrame = new ID3v2Frame();

        try {
            XMP_Int64 frameSize = curFrame->read ( file, this->majorVersion );
            if ( frameSize == 0 ) {
                delete curFrame; // ..since not becoming part of vector for latter delete.
                break;			 // not a throw. There's nothing wrong with padding.
            }
            this->containsXMP = true;
        } catch ( ... ) {
            delete curFrame;
            throw;
        }

        // these are both pointer assignments, no (copy) construction
        // (MemLeak Note: for all things pushed, memory cleanup is taken care of in destructor.)
        this->framesVector.push_back ( curFrame );

        //remember XMP-Frame, if it occurs:
        if ( (curFrame->id ==xmpID) &&
                (curFrame->contentSize > 8) && CheckBytes ( &curFrame->content[0], "XMP\0", 4 ) ) {

            // be sure that this is the first packet (all else would be illegal format)
            XMP_Validate ( this->framesMap[xmpID] == 0, "two XMP packets in one file", kXMPErr_BadFileFormat );
            //add this to map, needed on reconciliation
            this->framesMap[xmpID] = curFrame;

            this->packetInfo.length = curFrame->contentSize - 4; // content minus "XMP\0"
            this->packetInfo.offset = ( file->Offset() - this->packetInfo.length );

            this->xmpPacket.erase(); //safety
            this->xmpPacket.assign( &curFrame->content[4], curFrame->contentSize - 4 );
            this->containsXMP = true; // do this last, after all possible failure

        }

        // No space for another frame? => assume into ID3v2.4 padding.
        XMP_Int64 newPos = file->Offset();
        XMP_Int64 spaceLeft = this->oldTagSize - newPos;	// Depends on first check below!
        if ( (newPos > this->oldTagSize) || (spaceLeft < (XMP_Int64)ID3Header::kID3_TagHeaderSize) ) break;

    }

    ////////////////////////////////////////////////////
    // padding

    this->oldPadding = this->oldTagSize - file->Offset();
    this->oldFramesSize = this->oldTagSize - ID3Header::kID3_TagHeaderSize - this->oldPadding;

    XMP_Validate ( (this->oldPadding >= 0), "illegal oldTagSize or padding value", kXMPErr_BadFileFormat );

    for ( XMP_Int64 i = this->oldPadding; i > 0; ) {
        if ( i >= 8 ) {
            if ( XIO::ReadInt64_BE(file) != 0 ) XMP_Throw ( "padding not nulled out", kXMPErr_BadFileFormat );
            i -= 8;
            continue;
        }
        if ( XIO::ReadUns8(file) != 0) XMP_Throw ( "padding(2) not nulled out", kXMPErr_BadFileFormat );
        i--;
    }

    //// read ID3v1 tag
    if ( ! this->containsXMP ) this->containsXMP = id3v1Tag.read ( file, &this->xmpObj );

}	// MP3_MetaHandler::CacheFileData