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 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 TIFF_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) { XMP_IO* origRef = this->parent->ioRef; XMP_AbortProc abortProc = this->parent->abortProc; void * abortArg = this->parent->abortArg; XMP_Int64 fileLen = origRef->Length(); if ( fileLen > 0xFFFFFFFFLL ) { // Check before making a copy of the file. XMP_Throw ( "TIFF fles can't exceed 4GB", kXMPErr_BadTIFF ); } XMP_ProgressTracker* progressTracker = this->parent->progressTracker; if ( progressTracker != 0 ) progressTracker->BeginWork ( (float)fileLen ); origRef->Rewind ( ); tempRef->Truncate ( 0 ); XIO::Copy ( origRef, tempRef, fileLen, abortProc, abortArg ); try { this->parent->ioRef = tempRef; // ! Make UpdateFile update the temp. this->UpdateFile ( false ); this->parent->ioRef = origRef; } catch ( ... ) { this->parent->ioRef = origRef; throw; } if ( progressTracker != 0 ) progressTracker->WorkComplete(); } // TIFF_MetaHandler::WriteTempFile
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 Matroska_MetaHandler::WriteTempFile(XMP_IO* tempRef) { XMP_Assert(needsUpdate); XMP_IO* originalRef = parent->ioRef; bool localProgressTracking(false); XMP_ProgressTracker* progressTracker = parent->progressTracker; if (progressTracker) { float xmpSize = static_cast<float>(xmpPacket.size()); if (progressTracker->WorkInProgress()) { progressTracker->AddTotalWork(xmpSize); } else { localProgressTracking = true; progressTracker->BeginWork(xmpSize); } } XMP_Assert(tempRef); XMP_Assert(originalRef); tempRef->Rewind(); originalRef->Rewind(); XIO::Copy(originalRef, tempRef, originalRef->Length(), parent->abortProc, parent->abortArg); try { parent->ioRef = tempRef; // ! Fool UpdateFile into using the temp file. UpdateFile(false); parent->ioRef = originalRef; } catch (...) { parent->ioRef = originalRef; throw; } if (localProgressTracking) progressTracker->WorkComplete(); }
void WEBP_MetaHandler::CacheFileData() { this->containsXMP = false; // assume for now XMP_IO* file = this->parent->ioRef; this->initialFileSize = file->Length(); file->Rewind(); XMP_Int64 filePos = 0; while (filePos < this->initialFileSize) { this->mainChunk = new WEBP::Container(this); filePos = file->Offset(); } // covered before => internal error if it occurs XMP_Validate(file->Offset() == this->initialFileSize, "WEBP_MetaHandler::CacheFileData: unknown data at end of file", kXMPErr_InternalFailure); }
// ================================================================================================= // 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 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 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 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 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