// 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) } }
/* 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 }
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
// 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 ); }
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; }
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
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 ); } };
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; }
// ================================================================================================= // 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; }
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. }
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 ); }
// 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 ); }
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 ); }
// 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
// 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()
// ================================================================================================= // 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
// ================================================================================================= // 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