void Matroska_MetaHandler::UpdateXMP() { XMP_IO* fileRef = parent->ioRef; fileRef->Seek(fileRef->Length(), kXMP_SeekFromStart); for (auto segment : _dom->_root->getElementsById(kSegment)) { for (auto tags : segment->getElementsById(kTags)) { for (auto tag : tags->getElementsById(kTag)) { for (auto simple_tag : tag->getElementsById(kSimpleTag)) { auto tag_name = simple_tag->getElementById(kTagName); if (tag_name->_value.StringValue == "XMP") { auto tag_string = simple_tag->getElementById(kTagString); // we have found valid XMP, and if it's in the very end of file, we can truncate segment if (tag_string->_offset + tag_string->size() == fileRef->Length()) { segment->_size._value -= tags->size(); fileRef->Truncate(tags->_offset); fileRef->Seek(tags->_offset, kXMP_SeekFromStart); } // otherwise, make old XMP tag Void and create new one from the scratch else { tags->wipe(fileRef); } } } } } } auto segments = _dom->_root->getElementsById(kSegment); auto segment = segments.back(); auto tag_name = std::make_shared<ebml_element_t>(kTagName, ebml_variant_t("XMP")); auto tag_string = std::make_shared<ebml_element_t>(kTagString, ebml_variant_t(xmpPacket)); auto tag_language = std::make_shared<ebml_element_t>(kTagLanguage, ebml_variant_t("eng")); auto tag_default = std::make_shared<ebml_element_t>(kTagDefault, ebml_variant_t(1ULL)); auto simple_tag = std::make_shared<ebml_element_t>(kSimpleTag, ebml_element_t::vec{ tag_language, tag_default, tag_name, tag_string }); auto tag = std::make_shared<ebml_element_t>(kTag, simple_tag); auto tags = std::make_shared<ebml_element_t>(kTags, tag); tags->write(fileRef); segment->_size._value += tags->size(); segment->update_header(fileRef); }
void PSD_MetaHandler::CacheFileData() { XMP_IO* fileRef = this->parent->ioRef; XMP_PacketInfo & packetInfo = this->packetInfo; XMP_AbortProc abortProc = this->parent->abortProc; void * abortArg = this->parent->abortArg; const bool checkAbort = (abortProc != 0); XMP_Assert ( ! this->containsXMP ); // Set containsXMP to true here only if the XMP image resource is found. if ( checkAbort && abortProc(abortArg) ) { XMP_Throw ( "PSD_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort ); } XMP_Uns8 psdHeader[30]; XMP_Uns32 ioLen, cmLen; XMP_Int64 filePos = 0; fileRef->Rewind ( ); ioLen = fileRef->Read ( psdHeader, 30 ); if ( ioLen != 30 ) return; // Throw? this->imageHeight = GetUns32BE ( &psdHeader[14] ); this->imageWidth = GetUns32BE ( &psdHeader[18] ); cmLen = GetUns32BE ( &psdHeader[26] ); XMP_Int64 psirOrigin = 26 + 4 + cmLen; filePos = fileRef->Seek ( psirOrigin, kXMP_SeekFromStart ); if ( filePos != psirOrigin ) return; // Throw? if ( ! XIO::CheckFileSpace ( fileRef, 4 ) ) return; // Throw? XMP_Uns32 psirLen = XIO::ReadUns32_BE ( fileRef ); this->psirMgr.ParseFileResources ( fileRef, psirLen ); PSIR_Manager::ImgRsrcInfo xmpInfo; bool found = this->psirMgr.GetImgRsrc ( kPSIR_XMP, &xmpInfo ); if ( found ) { // printf ( "PSD_MetaHandler::CacheFileData - XMP packet offset %d (0x%X), size %d\n", // xmpInfo.origOffset, xmpInfo.origOffset, xmpInfo.dataLen ); this->packetInfo.offset = xmpInfo.origOffset; this->packetInfo.length = xmpInfo.dataLen; this->packetInfo.padSize = 0; // Assume for now, set these properly in ProcessXMP. this->packetInfo.charForm = kXMP_CharUnknown; this->packetInfo.writeable = true; this->xmpPacket.assign ( (XMP_StringPtr)xmpInfo.dataPtr, xmpInfo.dataLen ); this->containsXMP = true; } } // PSD_MetaHandler::CacheFileData
void GIF_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) { XMP_Assert( this->needsUpdate ); XMP_IO* originalRef = this->parent->ioRef; originalRef->Rewind(); tempRef->Truncate ( 0 ); if ( XMPPacketOffset != 0 ) { // Copying blocks before XMP Application Block XIO::Copy( originalRef, tempRef, XMPPacketOffset ); // Writing XMP Packet tempRef->Write( this->xmpPacket.c_str(), (XMP_Uns32)this->xmpPacket.size() ); // Copying Rest of the file originalRef->Seek( XMPPacketLength, kXMP_SeekFromCurrent ); XIO::Copy( originalRef, tempRef, originalRef->Length() - originalRef->Offset() ); } else { if ( trailerOffset == 0 ) XMP_Throw( "Not able to write XMP packet in GIF file", kXMPErr_BadFileFormat ); // Copying blocks before XMP Application Block XIO::Copy( originalRef, tempRef, trailerOffset ); // Writing Extension Introducer XIO::WriteUns8( tempRef, kXMP_block_Extension ); // Writing Application Extension label XIO::WriteUns8( tempRef, 0xFF ); // Writing Application Extension label XIO::WriteUns8( tempRef, APP_ID_LEN ); // Writing Application Extension label tempRef->Write( XMP_APP_ID_DATA, APP_ID_LEN ); // Writing XMP Packet tempRef->Write( this->xmpPacket.c_str(), (XMP_Uns32)this->xmpPacket.size() ); // Writing Magic trailer XMP_Uns8 magicByte = 0x01; tempRef->Write( &magicByte, 1 ); for ( magicByte = 0xFF; magicByte != 0x00; --magicByte ) tempRef->Write( &magicByte, 1 ); tempRef->Write( &magicByte, 1 ); tempRef->Write( &magicByte, 1 ); // Copying Rest of the file XIO::Copy( originalRef, tempRef, originalRef->Length() - originalRef->Offset() ); } } // GIF_MetaHandler::WriteTempFile
void RIFF_MetaHandler::CacheFileData() { this->containsXMP = false; //assume for now XMP_IO* file = this->parent->ioRef; this->oldFileSize = file ->Length(); if ( (this->parent->format == kXMP_WAVFile) && (this->oldFileSize > 0xFFFFFFFF) ) XMP_Throw ( "RIFF_MetaHandler::CacheFileData: WAV Files larger 4GB not supported", kXMPErr_Unimplemented ); file ->Rewind(); this->level = 0; // parse top-level chunks (most likely just one, except large avi files) XMP_Int64 filePos = 0; while ( filePos < this->oldFileSize ) { this->riffChunks.push_back( (RIFF::ContainerChunk*) RIFF::getChunk( NULL, this ) ); // Tolerate limited forms of trailing garbage in a file. Some apps append private data. filePos = file->Offset(); XMP_Int64 fileTail = this->oldFileSize - filePos; if ( fileTail != 0 ) { if ( fileTail < 12 ) { this->oldFileSize = filePos; // Pretend the file is smaller. this->trailingGarbageSize = fileTail; } else if ( this->parent->format == kXMP_WAVFile ) { if ( fileTail < 1024*1024 ) { this->oldFileSize = filePos; // Pretend the file is smaller. this->trailingGarbageSize = fileTail; } else { XMP_Throw ( "Excessive garbage at end of file", kXMPErr_BadFileFormat ) } } else { XMP_Int32 chunkInfo [3]; file->ReadAll ( &chunkInfo, 12 ); file->Seek ( -12, kXMP_SeekFromCurrent ); if ( (GetUns32LE ( &chunkInfo[0] ) != RIFF::kChunk_RIFF) || (GetUns32LE ( &chunkInfo[2] ) != RIFF::kType_AVIX) ) { if ( fileTail < 1024*1024 ) { this->oldFileSize = filePos; // Pretend the file is smaller. this->trailingGarbageSize = fileTail; } else { XMP_Throw ( "Excessive garbage at end of file", kXMPErr_BadFileFormat ) } } } } }
void FLV_MetaHandler::UpdateFile ( bool doSafeUpdate ) { if ( ! this->needsUpdate ) return; XMP_Assert ( ! doSafeUpdate ); // This should only be called for "unsafe" updates. XMP_AbortProc abortProc = this->parent->abortProc; void * abortArg = this->parent->abortArg; const bool checkAbort = (abortProc != 0); XMP_IO* fileRef = this->parent->ioRef; XMP_Uns64 fileSize = fileRef->Length(); // Make sure the XMP has a legacy digest if appropriate. if ( ! this->onMetaData.empty() ) { std::string newDigest; this->MakeLegacyDigest ( &newDigest ); this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "FLV", newDigest.c_str(), kXMP_DeleteExisting ); try { XMP_StringLen xmpLen = (XMP_StringLen)this->xmpPacket.size(); this->xmpObj.SerializeToBuffer ( &this->xmpPacket, (kXMP_UseCompactFormat | kXMP_ExactPacketLength), xmpLen ); } catch ( ... ) { this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); } } // Rewrite the packet in-place if it fits. Otherwise rewrite the whole file. if ( this->xmpPacket.size() == (size_t)this->packetInfo.length ) { XMP_ProgressTracker* progressTracker = this->parent->progressTracker; if ( progressTracker != 0 ) progressTracker->BeginWork ( (float)this->xmpPacket.size() ); fileRef->Seek ( this->packetInfo.offset, kXMP_SeekFromStart ); fileRef->Write ( this->xmpPacket.data(), (XMP_Int32)this->xmpPacket.size() ); if ( progressTracker != 0 ) progressTracker->WorkComplete(); } else { XMP_IO* tempRef = fileRef->DeriveTemp(); if ( tempRef == 0 ) XMP_Throw ( "Failure creating FLV temp file", kXMPErr_InternalFailure ); this->WriteTempFile ( tempRef ); fileRef->AbsorbTemp(); } this->needsUpdate = false; } // FLV_MetaHandler::UpdateFile
void Trivial_MetaHandler::UpdateFile ( bool doSafeUpdate ) { IgnoreParam ( doSafeUpdate ); XMP_Assert ( ! doSafeUpdate ); // Not supported at this level. if ( ! this->needsUpdate ) return; XMP_IO* fileRef = this->parent->ioRef; XMP_PacketInfo & packetInfo = this->packetInfo; std::string & xmpPacket = this->xmpPacket; fileRef->Seek ( packetInfo.offset, kXMP_SeekFromStart ); fileRef->Write ( xmpPacket.c_str(), packetInfo.length ); XMP_Assert ( xmpPacket.size() == (size_t)packetInfo.length ); this->needsUpdate = false; } // Trivial_MetaHandler::UpdateFile
// parsing creation Chunk::Chunk( ContainerChunk* parent_, RIFF_MetaHandler* handler, bool skip, ChunkType c ) { chunkType = c; // base class assumption this->parent = parent_; this->oldSize = 0; this->hasChange = false; // [2414649] valid assumption at creation time XMP_IO* file = handler->parent->ioRef; this->oldPos = file->Offset(); this->id = XIO::ReadUns32_LE( file ); this->oldSize = XIO::ReadUns32_LE( file ); this->oldSize += 8; // Make sure the size is within expected bounds. XMP_Int64 chunkEnd = this->oldPos + this->oldSize; XMP_Int64 chunkLimit = handler->oldFileSize; if ( parent_ != 0 ) chunkLimit = parent_->oldPos + parent_->oldSize; if ( chunkEnd > chunkLimit ) { bool isUpdate = XMP_OptionIsSet ( handler->parent->openFlags, kXMPFiles_OpenForUpdate ); bool repairFile = XMP_OptionIsSet ( handler->parent->openFlags, kXMPFiles_OpenRepairFile ); if ( (! isUpdate) || (repairFile && (parent_ == 0)) ) { this->oldSize = chunkLimit - this->oldPos; } else { XMP_Throw ( "Bad RIFF chunk size", kXMPErr_BadFileFormat ); } } this->newSize = this->oldSize; this->needSizeFix = false; if ( skip ) file->Seek ( (this->oldSize - 8), kXMP_SeekFromCurrent ); // "good parenting", essential for latter destruction. if ( this->parent != NULL ) { this->parent->children.push_back( this ); if( this->chunkType == chunk_VALUE ) this->parent->childmap.insert( std::make_pair( this->id, (ValueChunk*) this ) ); } }
void JPEG_MetaHandler::CacheFileData() { XMP_IO* fileRef = this->parent->ioRef; XMP_PacketInfo & packetInfo = this->packetInfo; static const size_t kBufferSize = 64*1024; // Enough for maximum segment contents. XMP_Uns8 buffer [kBufferSize]; psirContents.clear(); exifContents.clear(); XMP_AbortProc abortProc = this->parent->abortProc; void * abortArg = this->parent->abortArg; const bool checkAbort = (abortProc != 0); ExtendedXMPInfo extXMP; XMP_Assert ( ! this->containsXMP ); // Set containsXMP to true here only if the standard XMP packet is found. XMP_Assert ( kPSIRSignatureLength == (strlen(kPSIRSignatureString) + 1) ); XMP_Assert ( kMainXMPSignatureLength == (strlen(kMainXMPSignatureString) + 1) ); XMP_Assert ( kExtXMPSignatureLength == (strlen(kExtXMPSignatureString) + 1) ); // ------------------------------------------------------------------------------------------- // Look for any of the Exif, PSIR, main XMP, or extended XMP marker segments. Quit when we hit // an SOFn, EOI, or invalid/unexpected marker. fileRef->Seek ( 2, kXMP_SeekFromStart ); // Skip the SOI, CheckFormat made sure it is present. while ( true ) { if ( checkAbort && abortProc(abortArg) ) { XMP_Throw ( "JPEG_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort ); } if ( ! XIO::CheckFileSpace ( fileRef, 2 ) ) return; // Quit, don't throw, if the file ends unexpectedly. XMP_Uns16 marker = XIO::ReadUns16_BE ( fileRef ); // Read the next marker. if ( marker == 0xFFFF ) { // Have a pad byte, skip it. These are almost unheard of, so efficiency isn't critical. fileRef->Seek ( -1, kXMP_SeekFromCurrent ); // Skip the first 0xFF, read the second again. continue; } if ( (marker == 0xFFDA) || (marker == 0xFFD9) ) break; // Quit reading at the first SOS marker or at EOI. if ( (marker == 0xFF01) || // Ill-formed file if we encounter a TEM or RSTn marker. ((0xFFD0 <= marker) && (marker <= 0xFFD7)) ) return; XMP_Uns16 contentLen = XIO::ReadUns16_BE ( fileRef ); // Read this segment's length. if ( contentLen < 2 ) XMP_Throw ( "Invalid JPEG segment length", kXMPErr_BadJPEG ); contentLen -= 2; // Reduce to just the content length. XMP_Int64 contentOrigin = fileRef->Offset(); size_t signatureLen; if ( (marker == 0xFFED) && (contentLen >= kPSIRSignatureLength) ) { // This is an APP13 marker, is it the Photoshop image resources? signatureLen = fileRef->Read ( buffer, kPSIRSignatureLength ); if ( (signatureLen == kPSIRSignatureLength) && CheckBytes ( &buffer[0], kPSIRSignatureString, kPSIRSignatureLength ) ) { size_t psirLen = contentLen - kPSIRSignatureLength; fileRef->Seek ( (contentOrigin + kPSIRSignatureLength), kXMP_SeekFromStart ); fileRef->ReadAll ( buffer, psirLen ); this->psirContents.append( (char *) buffer, psirLen ); continue; // Move on to the next marker. } } else if ( (marker == 0xFFE1) && (contentLen >= kExifSignatureLength) ) { // Check for the shortest signature. // This is an APP1 marker, is it the Exif, main XMP, or extended XMP? // ! Check in that order, which is in increasing signature string length. XMP_Assert ( (kExifSignatureLength < kMainXMPSignatureLength) && (kMainXMPSignatureLength < kExtXMPSignatureLength) ); signatureLen = fileRef->Read ( buffer, kExtXMPSignatureLength ); // Read for the longest signature. if ( (signatureLen >= kExifSignatureLength) && (CheckBytes ( &buffer[0], kExifSignatureString, kExifSignatureLength ) || CheckBytes ( &buffer[0], kExifSignatureAltStr, kExifSignatureLength )) ) { size_t exifLen = contentLen - kExifSignatureLength; fileRef->Seek ( (contentOrigin + kExifSignatureLength), kXMP_SeekFromStart ); fileRef->ReadAll ( buffer, exifLen ); this->exifContents.append ( (char*)buffer, exifLen ); continue; // Move on to the next marker. } if ( (signatureLen >= kMainXMPSignatureLength) && CheckBytes ( &buffer[0], kMainXMPSignatureString, kMainXMPSignatureLength ) ) { this->containsXMP = true; // Found the standard XMP packet. size_t xmpLen = contentLen - kMainXMPSignatureLength; fileRef->Seek ( (contentOrigin + kMainXMPSignatureLength), kXMP_SeekFromStart ); fileRef->ReadAll ( buffer, xmpLen ); this->xmpPacket.assign ( (char*)buffer, xmpLen ); this->packetInfo.offset = contentOrigin + kMainXMPSignatureLength; this->packetInfo.length = (XMP_Int32)xmpLen; this->packetInfo.padSize = 0; // Assume the rest for now, set later in ProcessXMP. this->packetInfo.charForm = kXMP_CharUnknown; this->packetInfo.writeable = true; continue; // Move on to the next marker. } if ( (signatureLen >= kExtXMPSignatureLength) && CheckBytes ( &buffer[0], kExtXMPSignatureString, kExtXMPSignatureLength ) ) { fileRef->Seek ( contentOrigin, kXMP_SeekFromStart ); fileRef->ReadAll ( buffer, contentLen ); CacheExtendedXMP ( &extXMP, buffer, contentLen ); continue; // Move on to the next marker. } } // None of the above, seek to the next marker. fileRef->Seek ( (contentOrigin + contentLen) , kXMP_SeekFromStart ); } if ( ! extXMP.empty() ) { // We have extended XMP. Find out which ones are complete, collapse them into a single // string, and save them for ProcessXMP. ExtendedXMPInfo::iterator guidPos = extXMP.begin(); ExtendedXMPInfo::iterator guidEnd = extXMP.end(); for ( ; guidPos != guidEnd; ++guidPos ) { ExtXMPContent & thisContent = guidPos->second; ExtXMPPortions::iterator partZero = thisContent.portions.begin(); ExtXMPPortions::iterator partEnd = thisContent.portions.end(); ExtXMPPortions::iterator partPos = partZero; #if Trace_UnlimitedJPEG printf ( "Extended XMP portions for GUID %.32s, full length %d\n", guidPos->first.data, guidPos->second.length ); printf ( " Offset %d, length %d, next offset %d\n", partZero->first, partZero->second.size(), (partZero->first + partZero->second.size()) ); #endif for ( ++partPos; partPos != partEnd; ++partPos ) { #if Trace_UnlimitedJPEG printf ( " Offset %d, length %d, next offset %d\n", partPos->first, partPos->second.size(), (partPos->first + partPos->second.size()) ); #endif if ( partPos->first != partZero->second.size() ) break; // Quit if not contiguous. partZero->second.append ( partPos->second ); } if ( (partPos == partEnd) && (partZero->first == 0) && (partZero->second.size() == thisContent.length) ) { // This is a complete extended XMP stream. this->extendedXMP.insert ( ExtendedXMPMap::value_type ( guidPos->first, partZero->second ) ); #if Trace_UnlimitedJPEG printf ( "Full extended XMP for GUID %.32s, full length %d\n", guidPos->first.data, partZero->second.size() ); #endif } } } } // JPEG_MetaHandler::CacheFileData
void SVG_MetaHandler::UpdateFile( bool doSafeUpdate ) { XMP_Assert( !doSafeUpdate ); // This should only be called for "unsafe" updates. XMP_IO* sourceRef = this->parent->ioRef; if ( sourceRef == NULL || svgNode == NULL ) return; // Checking whether Title updation requires or not std::string title; XML_NodePtr titleNode = svgNode->GetNamedElement( svgNode->ns.c_str(), "title" ); (void) this->xmpObj.GetLocalizedText( kXMP_NS_DC, "title", "", "x-default", 0, &title, 0 ); if ( ( titleNode == NULL ) == ( title.empty() ) ) { if ( titleNode != NULL && titleNode->content.size() == 1 && titleNode->content[ 0 ]->kind == kCDataNode && !XMP_LitMatch( titleNode->content[ 0 ]->value.c_str(), title.c_str() ) ) isTitleUpdateReq = true; } else isTitleUpdateReq = true; // Checking whether Description updation requires or not std::string description; XML_NodePtr descNode = svgNode->GetNamedElement( svgNode->ns.c_str(), "desc" ); ( void ) this->xmpObj.GetLocalizedText( kXMP_NS_DC, "description", "", "x-default", 0, &description, 0 ); if ( ( descNode == NULL ) == ( description.empty() ) ) { if ( descNode != NULL && descNode->content.size() == 1 && descNode->content[ 0 ]->kind == kCDataNode && !XMP_LitMatch( descNode->content[ 0 ]->value.c_str(), description.c_str() ) ) isDescUpdateReq = true; } else isDescUpdateReq = true; // If any updation is required then don't do inplace replace bool isUpdateRequire = isTitleUpdateReq | isDescUpdateReq | (this->packetInfo.offset == kXMPFiles_UnknownOffset); // Inplace Updation of XMP if ( !isUpdateRequire && this->xmpPacket.size() == this->packetInfo.length ) { sourceRef->Seek( this->packetInfo.offset, kXMP_SeekFromStart ); sourceRef->Write( this->xmpPacket.c_str(), static_cast< int >( this->xmpPacket.size() ) ); } else { // Inplace is not possibe, So perform full updation try { XMP_IO* tempRef = sourceRef->DeriveTemp(); this->WriteTempFile( tempRef ); } catch ( ... ) { sourceRef->DeleteTemp(); throw; } sourceRef->AbsorbTemp(); } this->needsUpdate = false; } // SVG_MetaHandler::UpdateFile
// ================================================================================================= // SVG_MetaHandler::WriteTempFile // ============================== // void SVG_MetaHandler::WriteTempFile( XMP_IO* tempRef ) { XMP_Assert( this->needsUpdate ); XMP_IO* sourceRef = this->parent->ioRef; if ( sourceRef == NULL || svgNode == NULL ) return; tempRef->Rewind(); sourceRef->Rewind(); XMP_Int64 currentOffset = svgAdapter->firstSVGElementOffset; XIO::Copy( sourceRef, tempRef, currentOffset ); OffsetStruct titleOffset = svgAdapter->GetElementOffsets( "title" ); OffsetStruct descOffset = svgAdapter->GetElementOffsets( "desc" ); OffsetStruct metadataOffset = svgAdapter->GetElementOffsets( "metadata" ); std::string title; std::string description; XML_NodePtr titleNode = svgNode->GetNamedElement( svgNode->ns.c_str(), "title" ); ( void ) this->xmpObj.GetLocalizedText( kXMP_NS_DC, "title", "", "x-default", 0, &title, 0 ); XML_NodePtr descNode = svgNode->GetNamedElement( svgNode->ns.c_str(), "desc" ); ( void ) this->xmpObj.GetLocalizedText( kXMP_NS_DC, "description", "", "x-default", 0, &description, 0 ); // Need to cover the case of both workflows // This would have been called after inplace is not possible // This would have called for safe update if ( !isTitleUpdateReq ) { if ( ( titleNode == NULL ) == ( title.empty() ) ) { if ( titleNode != NULL && titleNode->content.size() == 1 && titleNode->content[ 0 ]->kind == kCDataNode && !XMP_LitMatch( titleNode->content[ 0 ]->value.c_str(), title.c_str() ) ) isTitleUpdateReq = true; } else isTitleUpdateReq = true; } if ( !isDescUpdateReq ) { if ( ( descNode == NULL ) == ( description.empty() ) ) { if ( descNode != NULL && descNode->content.size() == 1 && descNode->content[ 0 ]->kind == kCDataNode && !XMP_LitMatch( descNode->content[ 0 ]->value.c_str(), description.c_str() ) ) isDescUpdateReq = true; } else isDescUpdateReq = true; } // Initial Insertion/Updation // Insert/Update Title if requires // Don't insert/update it if Metadata or desc child comes before title child bool isTitleWritten = !isTitleUpdateReq; if ( isTitleUpdateReq ) { // Insertion Case if ( titleNode == NULL ) { InsertNewTitle( tempRef, title ); isTitleWritten = true; } else if ( ( descOffset.startOffset == -1 || titleOffset.startOffset < descOffset.startOffset ) // Updation/Deletion Case && ( metadataOffset.startOffset == -1 || titleOffset.startOffset < metadataOffset.startOffset ) ) { ProcessTitle( sourceRef, tempRef, title, currentOffset, titleOffset ); isTitleWritten = true; } } // Insert/Update Description if requires // Don't insert/update it if Metadata child comes before desc child bool isDescWritten = !isDescUpdateReq; if ( isDescUpdateReq ) { if ( descNode == NULL ) { if ( titleOffset.nextOffset != -1 ) { XIO::Copy( sourceRef, tempRef, titleOffset.nextOffset - currentOffset ); currentOffset = titleOffset.nextOffset; } InsertNewDescription( tempRef, description ); isDescWritten = true; } else if ( metadataOffset.startOffset == -1 || descOffset.startOffset < metadataOffset.startOffset ) { ProcessDescription( sourceRef, tempRef, description, currentOffset, descOffset ); isDescWritten = true; } } // Insert/Update Metadata if requires // Don't insert/update it if case is DTM bool isMetadataWritten = false; if ( metadataOffset.startOffset == -1 ) { if ( descOffset.nextOffset != -1 ) { XIO::Copy( sourceRef, tempRef, descOffset.nextOffset - currentOffset ); currentOffset = descOffset.nextOffset; } else if ( titleOffset.nextOffset != -1 ) { XIO::Copy( sourceRef, tempRef, titleOffset.nextOffset - currentOffset ); currentOffset = titleOffset.nextOffset; } InsertNewMetadata( tempRef, this->xmpPacket ); isMetadataWritten = true; } else if ( !( !isTitleWritten && isDescWritten && titleOffset.startOffset < metadataOffset.startOffset ) ) // Not DTM { // No XMP packet was present in the file if ( this->packetInfo.offset == kXMPFiles_UnknownOffset ) { std::string metadataElement = "<metadata>"; XIO::Copy( sourceRef, tempRef, metadataOffset.startOffset - currentOffset + metadataElement.length() ); currentOffset = sourceRef->Offset(); tempRef->Write( this->xmpPacket.c_str(), static_cast< int >( this->xmpPacket.length() ) ); } else // Replace XMP Packet { XIO::Copy( sourceRef, tempRef, this->packetInfo.offset - currentOffset ); tempRef->Write( this->xmpPacket.c_str(), static_cast< int >( this->xmpPacket.length() ) ); sourceRef->Seek( this->packetInfo.offset + this->packetInfo.length, kXMP_SeekFromStart ); currentOffset = sourceRef->Offset(); } isMetadataWritten = true; } // If simple cases was followed then copy rest file if ( isTitleWritten && isDescWritten && isMetadataWritten ) { XIO::Copy( sourceRef, tempRef, ( sourceRef->Length() - currentOffset ) ); return; } // If the case is not Simple (TDM) then perform these operations if ( isDescWritten ) // TDM, DTM, DMT { if ( !isTitleWritten ) // DTM, DMT { if ( titleOffset.startOffset < metadataOffset.startOffset ) // DTM { ProcessTitle( sourceRef, tempRef, title, currentOffset, titleOffset ); isTitleWritten = true; if ( this->packetInfo.offset == kXMPFiles_UnknownOffset ) { std::string metadataElement = "<metadata>"; XIO::Copy( sourceRef, tempRef, metadataOffset.startOffset - currentOffset + metadataElement.length() ); currentOffset = sourceRef->Offset(); tempRef->Write( this->xmpPacket.c_str(), static_cast< int >( this->xmpPacket.length() ) ); } else { XIO::Copy( sourceRef, tempRef, this->packetInfo.offset - currentOffset ); tempRef->Write( this->xmpPacket.c_str(), static_cast< int >( this->xmpPacket.length() ) ); sourceRef->Seek( this->packetInfo.offset + this->packetInfo.length, kXMP_SeekFromStart ); currentOffset = sourceRef->Offset(); } isMetadataWritten = true; } else // DMT { ProcessTitle( sourceRef, tempRef, title, currentOffset, titleOffset ); isTitleWritten = true; } } // Else // Would have already covered this case: TDM } else // TMD, MDT, MTD { if ( isTitleWritten ) // TMD { ProcessDescription( sourceRef, tempRef, description, currentOffset, descOffset ); isDescWritten = true; } else // MDT or MTD { if ( titleOffset.startOffset < descOffset.startOffset ) // MTD { ProcessTitle( sourceRef, tempRef, title, currentOffset, titleOffset ); isTitleWritten = true; ProcessDescription( sourceRef, tempRef, description, currentOffset, descOffset ); isDescWritten = true; } else // MDT { ProcessDescription( sourceRef, tempRef, description, currentOffset, descOffset ); isDescWritten = true; ProcessTitle( sourceRef, tempRef, title, currentOffset, titleOffset ); isTitleWritten = true; } } } // Finally Everything would have been written XMP_Enforce( isTitleWritten && isDescWritten && isMetadataWritten ); XIO::Copy( sourceRef, tempRef, ( sourceRef->Length() - currentOffset ) ); this->needsUpdate = false; } // SVG_MetaHandler::WriteTempFile
void PSD_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) { XMP_IO* origRef = this->parent->ioRef; XMP_AbortProc abortProc = this->parent->abortProc; void * abortArg = this->parent->abortArg; const bool checkAbort = (abortProc != 0); XMP_ProgressTracker* progressTracker = this->parent->progressTracker; XMP_Uns64 sourceLen = origRef->Length(); if ( sourceLen == 0 ) return; // Tolerate empty files. // Reconcile the legacy metadata, unless this is called from UpdateFile. Reserialize the XMP to // get standard padding, PutXMP has probably done an in-place serialize. Set the XMP image resource. if ( ! skipReconcile ) { // Update the IPTC-IIM and native TIFF/Exif metadata, and reserialize the now final XMP packet. ExportPhotoData ( kXMP_JPEGFile, &this->xmpObj, this->exifMgr, this->iptcMgr, &this->psirMgr ); this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); } this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); this->packetInfo.offset = kXMPFiles_UnknownOffset; this->packetInfo.length = (XMP_StringLen)this->xmpPacket.size(); FillPacketInfo ( this->xmpPacket, &this->packetInfo ); this->psirMgr.SetImgRsrc ( kPSIR_XMP, this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); // Calculate the total writes I/O to be done by this method. This includes header section, color // mode section and tail length after the image resources section. The write I/O for image // resources section is added to total work in PSIR_FileWriter::UpdateFileResources. origRef->Seek ( 26, kXMP_SeekFromStart ); //move to the point after Header 26 is the header length XMP_Uns32 cmLen,cmLen1; origRef->Read ( &cmLen, 4 ); // get the length of color mode section cmLen1 = GetUns32BE ( &cmLen ); origRef->Seek ( cmLen1, kXMP_SeekFromCurrent ); //move to the end of color mode section XMP_Uns32 irLen; origRef->Read ( &irLen, 4 ); // Get the source image resource section length. irLen = GetUns32BE ( &irLen ); XMP_Uns64 tailOffset = 26 + 4 + cmLen1 + 4 + irLen; XMP_Uns64 tailLength = sourceLen - tailOffset; // Add work for 26 bytes header, 4 bytes color mode section length, color mode section length // and tail length after the image resources section length. if ( progressTracker != 0 ) progressTracker->BeginWork ( (float)(26.0f + 4.0f + cmLen1 + tailLength) ); // Copy the file header and color mode section, then write the updated image resource section, // and copy the tail of the source file (layer and mask section to EOF). origRef->Rewind ( ); tempRef->Truncate ( 0 ); XIO::Copy ( origRef, tempRef, 26 ); // Copy the file header. origRef->Seek ( 4, kXMP_SeekFromCurrent ); tempRef->Write ( &cmLen, 4 ); // Copy the color mode section length. XIO::Copy ( origRef, tempRef, cmLen1 ); // Copy the color mode section contents. this->psirMgr.UpdateFileResources ( origRef, tempRef, abortProc, abortArg ,progressTracker ); origRef->Seek ( tailOffset, kXMP_SeekFromStart ); tempRef->Seek ( 0, kXMP_SeekFromEnd ); XIO::Copy ( origRef, tempRef, tailLength ); // Copy the tail of the file. this->needsUpdate = false; if ( progressTracker != 0 ) progressTracker->WorkComplete(); } // PSD_MetaHandler::WriteTempFile
void SVG_MetaHandler::CacheFileData() { XMP_Assert( !this->containsXMP ); XMP_IO * fileRef = this->parent->ioRef; XMP_Uns8 marker[ 4 ]; fileRef->Rewind(); fileRef->Read( marker, 4 ); // Checking for UTF-16 BOM and UTF-32 BOM if ( ( marker[ 0 ] == 0xFF && marker[ 1 ] == 0xFE ) || ( marker[ 0 ] == 0xFE && marker[ 1 ] == 0xFF ) || ( marker[ 0 ] == marker[ 1 ] == 0x00 && marker[ 2 ] == 0xFE && marker[ 3 ] == 0xFF ) ) { XMP_Error error( kXMPErr_BadXML, "Invalid SVG file" ); this->NotifyClient( &this->parent->errorCallback, kXMPErrSev_OperationFatal, error ); } // Creating a new SVG Parser svgAdapter = new SVG_Adapter(); if ( svgAdapter == 0 ) XMP_Throw( "SVG_MetaHandler: Can't create SVG adapter", kXMPErr_NoMemory ); svgAdapter->SetErrorCallback( &this->parent->errorCallback ); // Registering all the required tags to SVG Parser svgAdapter->RegisterPI( "xpacket" ); svgAdapter->RegisterElement( "metadata", "svg" ); svgAdapter->RegisterElement( "xmpmeta", "metadata" ); svgAdapter->RegisterElement( "RDF", "metadata" ); svgAdapter->RegisterElement( "title", "svg" ); svgAdapter->RegisterElement( "desc", "svg" ); // Parsing the whole buffer fileRef->Rewind(); XMP_Uns8 buffer[ 64 * 1024 ]; while ( true ) { XMP_Int32 ioCount = fileRef->Read( buffer, sizeof( buffer ) ); if ( ioCount == 0 || !svgAdapter->IsParsingRequire() ) break; svgAdapter->ParseBuffer( buffer, ioCount, false /* not the end */ ); } svgAdapter->ParseBuffer( 0, 0, true ); // End the parse. XML_Node & xmlTree = this->svgAdapter->tree; XML_NodePtr rootElem = 0; for ( size_t i = 0, limit = xmlTree.content.size(); i < limit; ++i ) { if ( xmlTree.content[ i ]->kind == kElemNode ) { rootElem = xmlTree.content[ i ]; } } if ( rootElem == 0 ) XMP_Throw( "Not a valid SVG File", kXMPErr_BadFileFormat ); XMP_StringPtr rootLocalName = rootElem->name.c_str() + rootElem->nsPrefixLen; if ( ! XMP_LitMatch( rootLocalName, "svg" ) ) XMP_Throw( "Not able to parse such SVG File", kXMPErr_BadFileFormat ); // Making SVG node as Root Node svgNode = rootElem; bool FoundPI = false; bool FoundWrapper = false; XML_NodePtr metadataNode = svgNode->GetNamedElement( rootElem->ns.c_str(), "metadata" ); // We are intersted only in the Metadata tag of outer SVG element // XMP should be present only in metadata Node of SVG if ( metadataNode != NULL ) { XMP_Int64 packetLength = -1; XMP_Int64 packetOffset = -1; XMP_Int64 PIOffset = svgAdapter->GetPIOffset( "xpacket", 1 ); OffsetStruct wrapperOffset = svgAdapter->GetElementOffsets( "xmpmeta" ); OffsetStruct rdfOffset = svgAdapter->GetElementOffsets( "RDF" ); // Checking XMP PI's position if ( PIOffset != -1 ) { if ( wrapperOffset.startOffset != -1 && wrapperOffset.startOffset < PIOffset ) packetOffset = wrapperOffset.startOffset; else { XMP_Int64 trailerOffset = svgAdapter->GetPIOffset( "xpacket", 2 ); XML_NodePtr trailerNode = metadataNode->GetNamedElement( "", "xpacket", 1 ); if ( trailerOffset != -1 || trailerNode != 0 ) { packetLength = 2; // "<?" = 2 packetLength += trailerNode->name.length(); // Node's name packetLength += 1; // Empty Space after Node's name packetLength += trailerNode->value.length(); // Value packetLength += 2; // "?>" = 2 packetLength += ( trailerOffset - PIOffset ); packetOffset = PIOffset; } } } else if ( wrapperOffset.startOffset != -1 ) // XMP Wrapper is present without PI { XML_NodePtr wrapperNode = metadataNode->GetNamedElement( "adobe:ns:meta/", "xmpmeta" ); if ( wrapperNode != 0 ) { std::string trailerWrapper = "</x:xmpmeta>"; packetLength = trailerWrapper.length(); packetLength += ( wrapperOffset.endOffset - wrapperOffset.startOffset ); packetOffset = wrapperOffset.startOffset; } } else // RDF packet is present without PI and wrapper { XML_NodePtr rdfNode = metadataNode->GetNamedElement( "http://www.w3.org/1999/02/22-rdf-syntax-ns#", "RDF" ); if ( rdfNode != 0 ) { std::string rdfTrailer = "</rdf:RDF>"; packetLength = rdfTrailer.length(); packetLength += ( rdfOffset.endOffset - rdfOffset.startOffset ); packetOffset = rdfOffset.startOffset; } } // Fill the necesarry information and packet with XMP data if ( packetOffset != -1 ) { this->packetInfo.offset = packetOffset; this->packetInfo.length = ( XMP_Int32 ) packetLength; this->xmpPacket.assign( this->packetInfo.length, ' ' ); fileRef->Seek( packetOffset, kXMP_SeekFromStart ); fileRef->ReadAll( ( void* )this->xmpPacket.data(), this->packetInfo.length ); FillPacketInfo( this->xmpPacket, &this->packetInfo ); this->containsXMP = true; return; } } this->containsXMP = false; } // SVG_MetaHandler::CacheFileData
void FLV_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) { if ( ! this->needsUpdate ) return; XMP_AbortProc abortProc = this->parent->abortProc; void * abortArg = this->parent->abortArg; const bool checkAbort = (abortProc != 0); XMP_IO* originalRef = this->parent->ioRef; XMP_Uns64 sourceLen = originalRef->Length(); XMP_Uns64 sourcePos = 0; originalRef->Rewind(); tempRef->Rewind(); tempRef->Truncate ( 0 ); XMP_ProgressTracker* progressTracker = this->parent->progressTracker; if ( progressTracker != 0 ) { float fileSize=(float)(this->xmpPacket.size()+48); if ( this->omdTagPos == 0 ) { sourcePos=(this->flvHeaderLen+4); fileSize+=sourcePos; } else { if ( this->xmpTagPos < this->omdTagPos ) { fileSize+=this->xmpTagPos; } fileSize+=(this->omdTagPos + this->omdTagLen- ((this->xmpTagPos!=0 && this->xmpTagPos < this->omdTagPos)? (this->xmpTagPos + this->xmpTagLen):0)); sourcePos =this->omdTagPos + this->omdTagLen; } if ( (this->xmpTagPos != 0) && (this->xmpTagPos >= sourcePos) ) { fileSize+=(this->xmpTagPos - sourcePos); sourcePos=this->xmpTagPos + this->xmpTagLen; } fileSize+=(sourceLen - sourcePos); sourcePos=0; progressTracker->BeginWork ( fileSize ); } // First do whatever is needed to put the new XMP after any existing onMetaData tag, or as the // first time 0 tag. if ( this->omdTagPos == 0 ) { // There is no onMetaData tag. Copy the file header, then write the new XMP as the first tag. // Allow the degenerate case of a file with just a header, no initial back pointer or tags. originalRef->Seek ( sourcePos, kXMP_SeekFromStart ); XIO::Copy ( originalRef, tempRef, this->flvHeaderLen, abortProc, abortArg ); XMP_Uns32 zero = 0; // Ensure that the initial back offset really is zero. tempRef->Write ( &zero, 4 ); sourcePos = this->flvHeaderLen + 4; WriteOnXMP ( tempRef, this->xmpPacket ); } else { // There is an onMetaData tag. Copy the front of the file through the onMetaData tag, // skipping any XMP that happens to be in the way. The XMP should not be before onMetaData, // but let's be robust. Write the new XMP immediately after onMetaData, at the same time. XMP_Uns64 omdEnd = this->omdTagPos + this->omdTagLen; if ( (this->xmpTagPos != 0) && (this->xmpTagPos < this->omdTagPos) ) { // The XMP tag was in front of the onMetaData tag. Copy up to it, then skip it. originalRef->Seek ( sourcePos, kXMP_SeekFromStart ); XIO::Copy ( originalRef, tempRef, this->xmpTagPos, abortProc, abortArg ); sourcePos = this->xmpTagPos + this->xmpTagLen; // The tag length includes the trailing size field. } // Copy through the onMetaData tag, then write the XMP. originalRef->Seek ( sourcePos, kXMP_SeekFromStart ); XIO::Copy ( originalRef, tempRef, (omdEnd - sourcePos), abortProc, abortArg ); sourcePos = omdEnd; WriteOnXMP ( tempRef, this->xmpPacket ); } // Copy the rest of the file, skipping any XMP that is in the way. if ( (this->xmpTagPos != 0) && (this->xmpTagPos >= sourcePos) ) { originalRef->Seek ( sourcePos, kXMP_SeekFromStart ); XIO::Copy ( originalRef, tempRef, (this->xmpTagPos - sourcePos), abortProc, abortArg ); sourcePos = this->xmpTagPos + this->xmpTagLen; } originalRef->Seek ( sourcePos, kXMP_SeekFromStart ); XIO::Copy ( originalRef, tempRef, (sourceLen - sourcePos), abortProc, abortArg ); this->needsUpdate = false; if ( progressTracker != 0 ) progressTracker->WorkComplete(); } // FLV_MetaHandler::WriteTempFile
void PSD_MetaHandler::UpdateFile ( bool doSafeUpdate ) { XMP_Assert ( ! doSafeUpdate ); // This should only be called for "unsafe" updates. XMP_Int64 oldPacketOffset = this->packetInfo.offset; XMP_Int32 oldPacketLength = this->packetInfo.length; if ( oldPacketOffset == kXMPFiles_UnknownOffset ) oldPacketOffset = 0; // ! Simplify checks. if ( oldPacketLength == kXMPFiles_UnknownLength ) oldPacketLength = 0; bool fileHadXMP = ((oldPacketOffset != 0) && (oldPacketLength != 0)); // Update the IPTC-IIM and native TIFF/Exif metadata. ExportPhotoData also trips the tiff: and // exif: copies from the XMP, so reserialize the now final XMP packet. ExportPhotoData ( kXMP_PhotoshopFile, &this->xmpObj, this->exifMgr, this->iptcMgr, &this->psirMgr ); try { XMP_OptionBits options = kXMP_UseCompactFormat; if ( fileHadXMP ) options |= kXMP_ExactPacketLength; this->xmpObj.SerializeToBuffer ( &this->xmpPacket, options, oldPacketLength ); } catch ( ... ) { this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); } // Decide whether to do an in-place update. This can only happen if all of the following are true: // - There is an XMP packet in the file. // - The are no changes to the legacy image resources. (The IPTC and EXIF are in the PSIR.) // - The new XMP can fit in the old space. bool doInPlace = (fileHadXMP && (this->xmpPacket.size() <= (size_t)oldPacketLength)); if ( this->psirMgr.IsLegacyChanged() ) doInPlace = false; XMP_ProgressTracker* progressTracker = this->parent->progressTracker; if ( doInPlace ) { #if GatherPerformanceData sAPIPerf->back().extraInfo += ", PSD in-place update"; #endif if ( this->xmpPacket.size() < (size_t)this->packetInfo.length ) { // They ought to match, cheap to be sure. size_t extraSpace = (size_t)this->packetInfo.length - this->xmpPacket.size(); this->xmpPacket.append ( extraSpace, ' ' ); } XMP_IO* liveFile = this->parent->ioRef; XMP_Assert ( this->xmpPacket.size() == (size_t)oldPacketLength ); // ! Done by common PutXMP logic. if ( progressTracker != 0 ) progressTracker->BeginWork ( this->xmpPacket.size() ); liveFile->Seek ( oldPacketOffset, kXMP_SeekFromStart ); liveFile->Write ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); if ( progressTracker != 0 ) progressTracker->WorkComplete(); } else { #if GatherPerformanceData sAPIPerf->back().extraInfo += ", PSD copy update"; #endif XMP_IO* origRef = this->parent->ioRef; XMP_IO* tempRef = origRef->DeriveTemp(); try { XMP_Assert ( ! this->skipReconcile ); this->skipReconcile = true; this->WriteTempFile ( tempRef ); this->skipReconcile = false; } catch ( ... ) { this->skipReconcile = false; origRef->DeleteTemp(); throw; } origRef->AbsorbTemp(); } this->needsUpdate = false; } // PSD_MetaHandler::UpdateFile
void InDesign_MetaHandler::CacheFileData() { XMP_IO* fileRef = this->parent->ioRef; XMP_PacketInfo & packetInfo = this->packetInfo; XMP_Assert ( kINDD_PageSize == sizeof(InDesignMasterPage) ); static const size_t kBufferSize = (2 * kINDD_PageSize); XMP_Uns8 buffer [kBufferSize]; size_t dbPages; XMP_Uns8 cobjEndian; XMP_AbortProc abortProc = this->parent->abortProc; void * abortArg = this->parent->abortArg; const bool checkAbort = (abortProc != 0); this->containsXMP = false; // --------------------------------------------------------------------------------- // Figure out which master page is active and seek to the contiguous object portion. { fileRef->Rewind(); fileRef->ReadAll ( buffer, (2 * kINDD_PageSize) ); InDesignMasterPage * masters = (InDesignMasterPage *) &buffer[0]; XMP_Uns64 seq0 = GetUns64LE ( (XMP_Uns8 *) &masters[0].fSequenceNumber ); XMP_Uns64 seq1 = GetUns64LE ( (XMP_Uns8 *) &masters[1].fSequenceNumber ); dbPages = GetUns32LE ( (XMP_Uns8 *) &masters[0].fFilePages ); cobjEndian = masters[0].fObjectStreamEndian; if ( seq1 > seq0 ) { dbPages = GetUns32LE ( (XMP_Uns8 *) &masters[1].fFilePages ); cobjEndian = masters[1].fObjectStreamEndian; } } XMP_Assert ( ! this->streamBigEndian ); if ( cobjEndian == kINDD_BigEndian ) this->streamBigEndian = true; // --------------------------------------------------------------------------------------------- // Look for the XMP contiguous object. Each contiguous object has a header and trailer, both of // the InDesignContigObjMarker structure. The stream size in the header/trailer is the number of // data bytes between the header and trailer. The XMP stream begins with a 4 byte size of the // XMP packet. Yes, this is the contiguous object data size minus 4 - silly but true. The XMP // must have a packet wrapper, the leading xpacket PI is used as the marker of XMP. XMP_Int64 cobjPos = (XMP_Int64)dbPages * kINDD_PageSize; // ! Use a 64 bit multiply! cobjPos -= (2 * sizeof(InDesignContigObjMarker)); // ! For the first pass in the loop. XMP_Uns32 streamLength = 0; // ! For the first pass in the loop. while ( true ) { if ( checkAbort && abortProc(abortArg) ) { XMP_Throw ( "InDesign_MetaHandler::LocateXMP - User abort", kXMPErr_UserAbort ); } // Fetch the start of the next stream and check the contiguous object header. // ! The writeable bit of fObjectClassID is ignored, we use the packet trailer flag. cobjPos += streamLength + (2 * sizeof(InDesignContigObjMarker)); fileRef->Seek ( cobjPos, kXMP_SeekFromStart ); fileRef->ReadAll ( buffer, sizeof(InDesignContigObjMarker) ); const InDesignContigObjMarker * cobjHeader = (const InDesignContigObjMarker *) &buffer[0]; if ( ! CheckBytes ( Uns8Ptr(&cobjHeader->fGUID), kINDDContigObjHeaderGUID, kInDesignGUIDSize ) ) break; // Not a contiguous object header. this->xmpObjID = cobjHeader->fObjectUID; // Save these now while the buffer is good. this->xmpClassID = cobjHeader->fObjectClassID; streamLength = GetUns32LE ( (XMP_Uns8 *) &cobjHeader->fStreamLength ); // See if this is the XMP stream. if ( streamLength < (4 + kUTF8_PacketHeaderLen + kUTF8_PacketTrailerLen) ) continue; // Too small, can't possibly be XMP. fileRef->ReadAll ( buffer, (4 + kUTF8_PacketHeaderLen) ); XMP_Uns32 innerLength = GetUns32LE ( &buffer[0] ); if ( this->streamBigEndian ) innerLength = GetUns32BE ( &buffer[0] ); if ( innerLength != (streamLength - 4) ) { // Be tolerant of a mistake with the endian flag. innerLength = Flip4 ( innerLength ); if ( innerLength != (streamLength - 4) ) continue; // Not legit XMP. } XMP_Uns8 * chPtr = &buffer[4]; size_t startLen = strlen((char*)kUTF8_PacketStart); size_t idLen = strlen((char*)kUTF8_PacketID); if ( ! CheckBytes ( chPtr, kUTF8_PacketStart, startLen ) ) continue; chPtr += startLen; XMP_Uns8 quote = *chPtr; if ( (quote != '\'') && (quote != '"') ) continue; chPtr += 1; if ( *chPtr != quote ) { if ( ! CheckBytes ( chPtr, Uns8Ptr("\xEF\xBB\xBF"), 3 ) ) continue; chPtr += 3; } if ( *chPtr != quote ) continue; chPtr += 1; if ( ! CheckBytes ( chPtr, Uns8Ptr(" id="), 4 ) ) continue; chPtr += 4; quote = *chPtr; if ( (quote != '\'') && (quote != '"') ) continue; chPtr += 1; if ( ! CheckBytes ( chPtr, kUTF8_PacketID, idLen ) ) continue; chPtr += idLen; if ( *chPtr != quote ) continue; chPtr += 1; // We've seen enough, it is the XMP. To fit the Basic_Handler model we need to compute the // total size of remaining contiguous objects, the trailingContentSize. We don't use the // size to EOF, that would wrongly include the final zero padding for 4KB alignment. this->xmpPrefixSize = sizeof(InDesignContigObjMarker) + 4; this->xmpSuffixSize = sizeof(InDesignContigObjMarker); packetInfo.offset = cobjPos + this->xmpPrefixSize; packetInfo.length = innerLength; XMP_Int64 tcStart = cobjPos + streamLength + (2 * sizeof(InDesignContigObjMarker)); while ( true ) { if ( checkAbort && abortProc(abortArg) ) { XMP_Throw ( "InDesign_MetaHandler::LocateXMP - User abort", kXMPErr_UserAbort ); } cobjPos += streamLength + (2 * sizeof(InDesignContigObjMarker)); XMP_Uns32 len = fileRef->Read ( buffer, sizeof(InDesignContigObjMarker) ); if ( len < sizeof(InDesignContigObjMarker) ) break; // Too small, must be end of file. cobjHeader = (const InDesignContigObjMarker *) &buffer[0]; if ( ! CheckBytes ( Uns8Ptr(&cobjHeader->fGUID), kINDDContigObjHeaderGUID, kInDesignGUIDSize ) ) break; // Not a contiguous object header. streamLength = GetUns32LE ( (XMP_Uns8 *) &cobjHeader->fStreamLength ); } this->trailingContentSize = cobjPos - tcStart; #if TraceInDesignHandler XMP_Uns32 pktOffset = (XMP_Uns32)this->packetInfo.offset; printf ( "Found XMP in InDesign file, offsets:\n" ); printf ( " CObj head %X, XMP %X, CObj tail %X, file tail %X, padding %X\n", (pktOffset - this->xmpPrefixSize), pktOffset, (pktOffset + this->packetInfo.length), (pktOffset + this->packetInfo.length + this->xmpSuffixSize), (pktOffset + this->packetInfo.length + this->xmpSuffixSize + (XMP_Uns32)this->trailingContentSize) ); #endif this->containsXMP = true; break; } if ( this->containsXMP ) { this->xmpFileOffset = packetInfo.offset; this->xmpFileSize = packetInfo.length; ReadXMPPacket ( this ); } } // InDesign_MetaHandler::CacheFileData
void TIFF_MetaHandler::UpdateFile ( bool doSafeUpdate ) { XMP_Assert ( ! doSafeUpdate ); // This should only be called for "unsafe" updates. XMP_IO* destRef = this->parent->ioRef; XMP_AbortProc abortProc = this->parent->abortProc; void * abortArg = this->parent->abortArg; XMP_Int64 oldPacketOffset = this->packetInfo.offset; XMP_Int32 oldPacketLength = this->packetInfo.length; if ( oldPacketOffset == kXMPFiles_UnknownOffset ) oldPacketOffset = 0; // ! Simplify checks. if ( oldPacketLength == kXMPFiles_UnknownLength ) oldPacketLength = 0; bool fileHadXMP = ((oldPacketOffset != 0) && (oldPacketLength != 0)); // Update the IPTC-IIM and native TIFF/Exif metadata. ExportPhotoData also trips the tiff: and // exif: copies from the XMP, so reserialize the now final XMP packet. ExportPhotoData ( kXMP_TIFFFile, &this->xmpObj, &this->tiffMgr, this->iptcMgr, this->psirMgr ); try { XMP_OptionBits options = kXMP_UseCompactFormat; if ( fileHadXMP ) options |= kXMP_ExactPacketLength; this->xmpObj.SerializeToBuffer ( &this->xmpPacket, options, oldPacketLength ); } catch ( ... ) { this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); } // Decide whether to do an in-place update. This can only happen if all of the following are true: // - There is an XMP packet in the file. // - The are no changes to the legacy tags. (The IPTC and PSIR are in the TIFF tags.) // - The new XMP can fit in the old space. bool doInPlace = (fileHadXMP && (this->xmpPacket.size() <= (size_t)oldPacketLength)); if ( this->tiffMgr.IsLegacyChanged() ) doInPlace = false; bool localProgressTracking = false; XMP_ProgressTracker* progressTracker = this->parent->progressTracker; if ( ! doInPlace ) { #if GatherPerformanceData sAPIPerf->back().extraInfo += ", TIFF append update"; #endif if ( (progressTracker != 0) && (! progressTracker->WorkInProgress()) ) { localProgressTracking = true; progressTracker->BeginWork(); } this->tiffMgr.SetTag ( kTIFF_PrimaryIFD, kTIFF_XMP, kTIFF_UndefinedType, (XMP_Uns32)this->xmpPacket.size(), this->xmpPacket.c_str() ); this->tiffMgr.UpdateFileStream ( destRef, progressTracker ); } else { #if GatherPerformanceData sAPIPerf->back().extraInfo += ", TIFF in-place update"; #endif if ( this->xmpPacket.size() < (size_t)this->packetInfo.length ) { // They ought to match, cheap to be sure. size_t extraSpace = (size_t)this->packetInfo.length - this->xmpPacket.size(); this->xmpPacket.append ( extraSpace, ' ' ); } XMP_IO* liveFile = this->parent->ioRef; XMP_Assert ( this->xmpPacket.size() == (size_t)oldPacketLength ); // ! Done by common PutXMP logic. if ( progressTracker != 0 ) { if ( progressTracker->WorkInProgress() ) { progressTracker->AddTotalWork ( this->xmpPacket.size() ); } else { localProgressTracking = true; progressTracker->BeginWork ( this->xmpPacket.size() ); } } liveFile->Seek ( oldPacketOffset, kXMP_SeekFromStart ); liveFile->Write ( this->xmpPacket.c_str(), (XMP_Int32)this->xmpPacket.size() ); } if ( localProgressTracking ) progressTracker->WorkComplete(); this->needsUpdate = false; } // TIFF_MetaHandler::UpdateFile
// 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
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
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 ); }
void JPEG_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) { XMP_IO* origRef = this->parent->ioRef; XMP_AbortProc abortProc = this->parent->abortProc; void * abortArg = this->parent->abortArg; const bool checkAbort = (abortProc != 0); XMP_Uns16 marker, contentLen; static const size_t kBufferSize = 64*1024; // Enough for a segment with maximum contents. XMP_Uns8 buffer [kBufferSize]; XMP_Int64 origLength = origRef->Length(); if ( origLength == 0 ) return; // Tolerate empty files. if ( origLength < 4 ) { XMP_Throw ( "JPEG must have at least SOI and EOI markers", kXMPErr_BadJPEG ); } if ( ! skipReconcile ) { // Update the IPTC-IIM and native TIFF/Exif metadata, and reserialize the now final XMP packet. ExportPhotoData ( kXMP_JPEGFile, &this->xmpObj, this->exifMgr, this->iptcMgr, this->psirMgr ); this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); } origRef->Rewind(); tempRef->Truncate ( 0 ); marker = XIO::ReadUns16_BE ( origRef ); // Just read the SOI marker. if ( marker != 0xFFD8 ) XMP_Throw ( "Missing SOI marker", kXMPErr_BadJPEG ); XIO::WriteUns16_BE ( tempRef, marker ); // Copy any leading APP0 marker segments. while ( true ) { if ( checkAbort && abortProc(abortArg) ) { XMP_Throw ( "JPEG_MetaHandler::WriteFile - User abort", kXMPErr_UserAbort ); } if ( ! XIO::CheckFileSpace ( origRef, 2 ) ) break; // Tolerate a file that ends abruptly. marker = XIO::ReadUns16_BE ( origRef ); // Read the next marker. if ( marker == 0xFFFF ) { // Have a pad byte, skip it. These are almost unheard of, so efficiency isn't critical. origRef->Seek ( -1, kXMP_SeekFromCurrent ); // Skip the first 0xFF, read the second again. continue; } if ( marker != 0xFFE0 ) break; // Have a non-APP0 marker. XIO::WriteUns16_BE ( tempRef, marker ); // Write the APP0 marker. contentLen = XIO::ReadUns16_BE ( origRef ); // Copy the APP0 segment's length. XIO::WriteUns16_BE ( tempRef, contentLen ); if ( contentLen < 2 ) XMP_Throw ( "Invalid JPEG segment length", kXMPErr_BadJPEG ); contentLen -= 2; // Reduce to just the content length. origRef->ReadAll ( buffer, contentLen ); // Copy the APP0 segment's content. tempRef->Write ( buffer, contentLen ); } // Write the new Exif APP1 marker segment. XMP_Uns32 first4; if ( this->exifMgr != 0 ) { void* exifPtr; XMP_Uns32 exifLen = this->exifMgr->UpdateMemoryStream ( &exifPtr ); if ( exifLen > kExifMaxDataLength ) exifLen = this->exifMgr->UpdateMemoryStream ( &exifPtr, true /* compact */ ); while ( exifLen > 0 ) { XMP_Uns32 count = std::min ( exifLen, (XMP_Uns32) kExifMaxDataLength ); first4 = MakeUns32BE ( 0xFFE10000 + 2 + kExifSignatureLength + count ); tempRef->Write ( &first4, 4 ); tempRef->Write ( kExifSignatureString, kExifSignatureLength ); tempRef->Write ( exifPtr, count ); exifPtr = (XMP_Uns8 *) exifPtr + count; exifLen -= count; } } // Write the new XMP APP1 marker segment, with possible extension marker segments. std::string mainXMP, extXMP, extDigest; SXMPUtils::PackageForJPEG ( this->xmpObj, &mainXMP, &extXMP, &extDigest ); XMP_Assert ( (extXMP.size() == 0) || (extDigest.size() == 32) ); first4 = MakeUns32BE ( 0xFFE10000 + 2 + kMainXMPSignatureLength + (XMP_Uns32)mainXMP.size() ); tempRef->Write ( &first4, 4 ); tempRef->Write ( kMainXMPSignatureString, kMainXMPSignatureLength ); tempRef->Write ( mainXMP.c_str(), (XMP_Int32)mainXMP.size() ); size_t extPos = 0; size_t extLen = extXMP.size(); while ( extLen > 0 ) { size_t partLen = extLen; if ( partLen > 65000 ) partLen = 65000; first4 = MakeUns32BE ( 0xFFE10000 + 2 + kExtXMPPrefixLength + (XMP_Uns32)partLen ); tempRef->Write ( &first4, 4 ); tempRef->Write ( kExtXMPSignatureString, kExtXMPSignatureLength ); tempRef->Write ( extDigest.c_str(), (XMP_Int32)extDigest.size() ); first4 = MakeUns32BE ( (XMP_Int32)extXMP.size() ); tempRef->Write ( &first4, 4 ); first4 = MakeUns32BE ( (XMP_Int32)extPos ); tempRef->Write ( &first4, 4 ); tempRef->Write ( &extXMP[extPos], (XMP_Int32)partLen ); extPos += partLen; extLen -= partLen; } // Write the new PSIR APP13 marker segments. if ( this->psirMgr != 0 ) { void* psirPtr; XMP_Uns32 psirLen = this->psirMgr->UpdateMemoryResources ( &psirPtr ); while ( psirLen > 0 ) { XMP_Uns32 count = std::min ( psirLen, (XMP_Uns32) kPSIRMaxDataLength ); first4 = MakeUns32BE ( 0xFFED0000 + 2 + kPSIRSignatureLength + count ); tempRef->Write ( &first4, 4 ); tempRef->Write ( kPSIRSignatureString, kPSIRSignatureLength ); tempRef->Write ( psirPtr, count ); psirPtr = (XMP_Uns8 *) psirPtr + count; psirLen -= count; } } // Copy remaining marker segments, skipping old metadata, to the first SOS marker or to EOI. origRef->Seek ( -2, kXMP_SeekFromCurrent ); // Back up to the marker from the end of the APP0 copy loop. while ( true ) { if ( checkAbort && abortProc(abortArg) ) { XMP_Throw ( "JPEG_MetaHandler::WriteFile - User abort", kXMPErr_UserAbort ); } if ( ! XIO::CheckFileSpace ( origRef, 2 ) ) break; // Tolerate a file that ends abruptly. marker = XIO::ReadUns16_BE ( origRef ); // Read the next marker. if ( marker == 0xFFFF ) { // Have a pad byte, skip it. These are almost unheard of, so efficiency isn't critical. origRef->Seek ( -1, kXMP_SeekFromCurrent ); // Skip the first 0xFF, read the second again. continue; } if ( (marker == 0xFFDA) || (marker == 0xFFD9) ) { // Quit at the first SOS marker or at EOI. origRef->Seek ( -2, kXMP_SeekFromCurrent ); // The tail copy must include this marker. break; } if ( (marker == 0xFF01) || // Ill-formed file if we encounter a TEM or RSTn marker. ((0xFFD0 <= marker) && (marker <= 0xFFD7)) ) { XMP_Throw ( "Unexpected TEM or RSTn marker", kXMPErr_BadJPEG ); } contentLen = XIO::ReadUns16_BE ( origRef ); // Read this segment's length. if ( contentLen < 2 ) XMP_Throw ( "Invalid JPEG segment length", kXMPErr_BadJPEG ); contentLen -= 2; // Reduce to just the content length. XMP_Int64 contentOrigin = origRef->Offset(); bool copySegment = true; size_t signatureLen; if ( (marker == 0xFFED) && (contentLen >= kPSIRSignatureLength) ) { // This is an APP13 segment, skip if it is the old PSIR. signatureLen = origRef->Read ( buffer, kPSIRSignatureLength ); if ( (signatureLen == kPSIRSignatureLength) && CheckBytes ( &buffer[0], kPSIRSignatureString, kPSIRSignatureLength ) ) { copySegment = false; } } else if ( (marker == 0xFFE1) && (contentLen >= kExifSignatureLength) ) { // Check for the shortest signature. // This is an APP1 segment, skip if it is the old Exif or XMP. XMP_Assert ( (kExifSignatureLength < kMainXMPSignatureLength) && (kMainXMPSignatureLength < kExtXMPSignatureLength) ); signatureLen = origRef->Read ( buffer, kExtXMPSignatureLength ); // Read for the longest signature. if ( (signatureLen >= kExifSignatureLength) && (CheckBytes ( &buffer[0], kExifSignatureString, kExifSignatureLength ) || CheckBytes ( &buffer[0], kExifSignatureAltStr, kExifSignatureLength )) ) { copySegment = false; } if ( copySegment && (signatureLen >= kMainXMPSignatureLength) && CheckBytes ( &buffer[0], kMainXMPSignatureString, kMainXMPSignatureLength ) ) { copySegment = false; } if ( copySegment && (signatureLen == kExtXMPSignatureLength) && CheckBytes ( &buffer[0], kExtXMPSignatureString, kExtXMPPrefixLength ) ) { copySegment = false; } } if ( ! copySegment ) { origRef->Seek ( (contentOrigin + contentLen), kXMP_SeekFromStart ); } else { XIO::WriteUns16_BE ( tempRef, marker ); XIO::WriteUns16_BE ( tempRef, (contentLen + 2) ); origRef->Seek ( contentOrigin, kXMP_SeekFromStart ); origRef->ReadAll ( buffer, contentLen ); tempRef->Write ( buffer, contentLen ); } } // Copy the remainder of the source file. XIO::Copy ( origRef, tempRef, (origLength - origRef->Offset()) ); this->needsUpdate = false; } // JPEG_MetaHandler::WriteTempFile
void Scanner_MetaHandler::CacheFileData() { XMP_IO* fileRef = this->parent->ioRef; bool beLenient = XMP_OptionIsClear ( this->parent->openFlags, kXMPFiles_OpenStrictly ); int pkt; XMP_Int64 bufPos; size_t bufLen; SXMPMeta * newMeta; XMP_AbortProc abortProc = this->parent->abortProc; void * abortArg = this->parent->abortArg; const bool checkAbort = (abortProc != 0); std::vector<CandidateInfo> candidates; // ! These have SXMPMeta* fields, don't leak on exceptions. this->containsXMP = false; try { // ------------------------------------------------------ // Scan the entire file to find all of the valid packets. XMP_Int64 fileLen = fileRef->Length(); XMPScanner scanner ( fileLen ); enum { kBufferSize = 64*1024 }; XMP_Uns8 buffer [kBufferSize]; fileRef->Rewind(); for ( bufPos = 0; bufPos < fileLen; bufPos += bufLen ) { if ( checkAbort && abortProc(abortArg) ) { XMP_Throw ( "Scanner_MetaHandler::LocateXMP - User abort", kXMPErr_UserAbort ); } bufLen = fileRef->Read ( buffer, kBufferSize ); if ( bufLen == 0 ) XMP_Throw ( "Scanner_MetaHandler::LocateXMP: Read failure", kXMPErr_ExternalFailure ); scanner.Scan ( buffer, bufPos, bufLen ); } // -------------------------------------------------------------- // Parse the valid packet snips, building a vector of candidates. long snipCount = scanner.GetSnipCount(); XMPScanner::SnipInfoVector snips ( snipCount ); scanner.Report ( snips ); for ( pkt = 0; pkt < snipCount; ++pkt ) { if ( checkAbort && abortProc(abortArg) ) { XMP_Throw ( "Scanner_MetaHandler::LocateXMP - User abort", kXMPErr_UserAbort ); } // Seek to the packet then try to parse it. if ( snips[pkt].fState != XMPScanner::eValidPacketSnip ) continue; fileRef->Seek ( snips[pkt].fOffset, kXMP_SeekFromStart ); newMeta = new SXMPMeta(); std::string xmpPacket; xmpPacket.reserve ( (size_t)snips[pkt].fLength ); try { for ( bufPos = 0; bufPos < snips[pkt].fLength; bufPos += bufLen ) { bufLen = kBufferSize; if ( (bufPos + bufLen) > (size_t)snips[pkt].fLength ) bufLen = size_t ( snips[pkt].fLength - bufPos ); (void) fileRef->ReadAll ( buffer, (XMP_Int32)bufLen ); xmpPacket.append ( (const char *)buffer, bufLen ); newMeta->ParseFromBuffer ( (char *)buffer, (XMP_StringLen)bufLen, kXMP_ParseMoreBuffers ); } newMeta->ParseFromBuffer ( 0, 0, kXMP_NoOptions ); } catch ( ... ) { delete newMeta; if ( beLenient ) continue; // Skip if we're being lenient, else rethrow. throw; } // It parsed OK, add it to the array of candidates. candidates.push_back ( CandidateInfo() ); CandidateInfo & newInfo = candidates.back(); newInfo.xmpObj = newMeta; newInfo.xmpPacket.swap ( xmpPacket ); newInfo.packetInfo.offset = snips[pkt].fOffset; newInfo.packetInfo.length = (XMP_Int32)snips[pkt].fLength; newInfo.packetInfo.charForm = snips[pkt].fCharForm; newInfo.packetInfo.writeable = (snips[pkt].fAccess == 'w'); } // ---------------------------------------- // Figure out which packet is the main one. int main = PickMainPacket ( candidates, beLenient ); if ( main != -1 ) { this->packetInfo = candidates[main].packetInfo; this->xmpPacket.swap ( candidates[main].xmpPacket ); this->xmpObj = *candidates[main].xmpObj; this->containsXMP = true; this->processedXMP = true; } for ( pkt = 0; pkt < (int)candidates.size(); ++pkt ) { if ( candidates[pkt].xmpObj != 0 ) delete candidates[pkt].xmpObj; } } catch ( ... ) { // Clean up the SXMPMeta* fields from the vector of candidates. for ( pkt = 0; pkt < (int)candidates.size(); ++pkt ) { if ( candidates[pkt].xmpObj != 0 ) delete candidates[pkt].xmpObj; } throw; } } // Scanner_MetaHandler::CacheFileData
void FLV_MetaHandler::CacheFileData() { XMP_Assert ( ! this->containsXMP ); XMP_AbortProc abortProc = this->parent->abortProc; void * abortArg = this->parent->abortArg; const bool checkAbort = (abortProc != 0); XMP_IO* fileRef = this->parent->ioRef; XMP_Uns64 fileSize = fileRef->Length(); XMP_Uns8 buffer [16]; // Enough for 1+2+"onMetaData"+nul. XMP_Uns32 ioCount; TagInfo info; fileRef->Seek ( 5, kXMP_SeekFromStart ); fileRef->ReadAll ( buffer, 4 ); this->flvHeaderLen = GetUns32BE ( &buffer[0] ); XMP_Uns32 firstTagPos = this->flvHeaderLen + 4; // Include the initial zero back pointer. if ( firstTagPos >= fileSize ) return; // Quit now if the file is just a header. for ( XMP_Uns64 tagPos = firstTagPos; tagPos < fileSize; tagPos += (11 + info.dataSize + 4) ) { if ( checkAbort && abortProc(abortArg) ) { XMP_Throw ( "FLV_MetaHandler::LookForMetadata - User abort", kXMPErr_UserAbort ); } GetTagInfo ( fileRef, tagPos, &info ); // ! GetTagInfo seeks to the tag offset. if ( info.time != 0 ) break; if ( info.type != 18 ) continue; XMP_Assert ( sizeof(buffer) >= (1+2+10+1) ); // 02 000B onMetaData 00 ioCount = fileRef->Read ( buffer, sizeof(buffer) ); if ( (ioCount < 4) || (buffer[0] != 0x02) ) continue; XMP_Uns16 nameLen = GetUns16BE ( &buffer[1] ); XMP_StringPtr namePtr = (XMP_StringPtr)(&buffer[3]); if ( this->onXMP.empty() && CheckName ( namePtr, nameLen, "onXMPData", 9 ) ) { // ! Put the raw data in onXMPData, analyze the value in ProcessXMP. this->xmpTagPos = tagPos; this->xmpTagLen = 11 + info.dataSize + 4; // ! Includes the trailing back pointer. this->packetInfo.offset = tagPos + 11 + 1+2+nameLen; // ! Not the real offset yet, the offset of the onXMPData value. ioCount = info.dataSize - (1+2+nameLen); // Just the onXMPData value portion. this->onXMP.reserve ( ioCount ); this->onXMP.assign ( ioCount, ' ' ); fileRef->Seek ( this->packetInfo.offset, kXMP_SeekFromStart ); fileRef->ReadAll ( (void*)this->onXMP.data(), ioCount ); if ( ! this->onMetaData.empty() ) break; // Done if we've found both. } else if ( this->onMetaData.empty() && CheckName ( namePtr, nameLen, "onMetaData", 10 ) ) { this->omdTagPos = tagPos; this->omdTagLen = 11 + info.dataSize + 4; // ! Includes the trailing back pointer. ioCount = info.dataSize - (1+2+nameLen); // Just the onMetaData value portion. this->onMetaData.reserve ( ioCount ); this->onMetaData.assign ( ioCount, ' ' ); fileRef->Seek ( (tagPos + 11 + 1+2+nameLen), kXMP_SeekFromStart ); fileRef->ReadAll ( (void*)this->onMetaData.data(), ioCount ); if ( ! this->onXMP.empty() ) break; // Done if we've found both. } } } // FLV_MetaHandler::CacheFileData