summaryrefslogtreecommitdiff
path: root/source/XMPFiles/FileHandlers/FLV_Handler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/XMPFiles/FileHandlers/FLV_Handler.cpp')
-rw-r--r--source/XMPFiles/FileHandlers/FLV_Handler.cpp750
1 files changed, 750 insertions, 0 deletions
diff --git a/source/XMPFiles/FileHandlers/FLV_Handler.cpp b/source/XMPFiles/FileHandlers/FLV_Handler.cpp
new file mode 100644
index 0000000..2472870
--- /dev/null
+++ b/source/XMPFiles/FileHandlers/FLV_Handler.cpp
@@ -0,0 +1,750 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2002-2008 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "FLV_Handler.hpp"
+
+#include "MD5.h"
+
+using namespace std;
+
+// =================================================================================================
+/// \file FLV_Handler.cpp
+/// \brief File format handler for FLV.
+///
+/// FLV is a fairly simple format, with a strong orientation to streaming use. It consists of a
+/// small file header then a sequence of tags that can contain audio data, video data, or
+/// ActionScript data. All integers in FLV are big endian.
+///
+/// For FLV version 1, the file header contains:
+///
+/// UI24 signature - the characters "FLV"
+/// UI8 version - 1
+/// UI8 flags - 0x01 = has video tags, 0x04 = has audio tags
+/// UI32 length in bytes of file header
+///
+/// For FLV version 1, each tag begins with an 11 byte header:
+///
+/// UI8 tag type - 8 = audio tag, 9 = video tag, 18 = script data tag
+/// UI24 content length in bytes
+/// UI24 time - low order 3 bytes
+/// UI8 time - high order byte
+/// UI24 stream ID
+///
+/// This is followed by the tag's content, then a UI32 "back pointer" which is the header size plus
+/// the content size. A UI32 zero is placed between the file header and the first tag as a
+/// terminator for backward scans. The time in a tag header is the start of playback for that tag.
+/// The tags must be in ascending time order. For a given time it is preferred that script data tags
+/// precede audio and video tags.
+///
+/// For metadata purposes only the script data tags are of interest. Script data information becomes
+/// accessible to ActionScript at the playback moment of the script data tag through a call to a
+/// registered data handler. The content of a script data tag contains a string and an ActionScript
+/// data value. The string is the name of the handler to be invoked, the data value is passed as an
+/// ActionScript Object parameter to the handler.
+///
+/// The XMP is placed in a script data tag with the name "onXMPData". A variety of legacy metadata
+/// is contained in a script data tag with the name "onMetaData". This contains only "internal"
+/// information (like duration or width/height), nothing that is user or author editiable (like
+/// title or description). Some of these legacy items are imported into the XMP, none are updated
+/// from the XMP.
+///
+/// A script data tag's content is:
+///
+/// UI8 0x02
+/// UI16 name length - includes nul terminator if present
+/// UI8n object name - UTF-8, possibly with nul terminator
+/// ... object value - serialized ActionScript value (SCRIPTDATAVALUE in FLV spec)
+///
+/// The onXMPData and onMetaData values are both ECMA arrays. These have more in common with XMP
+/// structs than arrays, the items have arbitrary string names. The serialized form is:
+///
+/// UI8 0x08
+/// UI32 array length - need not be exact, an optimization hint
+/// array items
+/// UI16 name length - includes nul terminator if present
+/// UI8n item name - UTF-8, possibly with nul terminator
+/// ... object value - serialized ActionScript value (SCRIPTDATAVALUE in FLV spec)
+/// UI24 0x000009 - array terminator
+///
+/// The object names and array item names in sample files do not have a nul terminator. The policy
+/// here is to treat them as optional when reading, and to omit them when writing.
+///
+/// The onXMPData array typically has one item named "liveXML". The value of this is a short or long
+/// string as necessary:
+///
+/// UI8 type - 2 for a short string, 12 for a long string
+/// UIx value length - UI16 for a short string, UI32 for a long string, includes nul terminator
+/// UI8n value - UTF-8 with nul terminator
+///
+// =================================================================================================
+
+static inline XMP_Uns32 GetUns24BE ( const void * addr )
+{
+ return (GetUns32BE(addr) >> 8);
+}
+
+static inline void PutUns24BE ( XMP_Uns32 value, void * addr )
+{
+ XMP_Uns8 * bytes = (XMP_Uns8*)addr;
+ bytes[0] = (XMP_Uns8)(value >> 16);
+ bytes[1] = (XMP_Uns8)(value >> 8);
+ bytes[2] = (XMP_Uns8)(value);
+}
+
+// =================================================================================================
+// FLV_CheckFormat
+// ===============
+//
+// Check for "FLV" and 1 in the first 4 bytes, that the header length is at least 9, that the file
+// size is at least as big as the header, and that the leading 0 back pointer is present if the file
+// is bigger than the header.
+
+#define kFLV1 0x464C5601UL
+
+bool FLV_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ LFA_FileRef fileRef,
+ XMPFiles * parent )
+{
+ XMP_Uns8 buffer [9];
+
+ LFA_Seek ( fileRef, 0, SEEK_SET );
+ XMP_Uns32 ioCount = LFA_Read ( fileRef, buffer, 9 );
+ if ( ioCount != 9 ) return false;
+
+ XMP_Uns32 fileSignature = GetUns32BE ( &buffer[0] );
+ if ( fileSignature != kFLV1 ) return false;
+
+ XMP_Uns32 headerSize = GetUns32BE ( &buffer[5] );
+ XMP_Uns64 fileSize = LFA_Measure ( fileRef );
+ if ( (fileSize < (headerSize + 4)) && (fileSize != headerSize) ) return false;
+
+ if ( fileSize >= (headerSize + 4) ) {
+ XMP_Uns32 bpZero;
+ LFA_Seek ( fileRef, headerSize, SEEK_SET );
+ ioCount = LFA_Read ( fileRef, &bpZero, 4 );
+ if ( (ioCount != 4) || (bpZero != 0) ) return false;
+ }
+
+ return true;
+
+} // FLV_CheckFormat
+
+// =================================================================================================
+// FLV_MetaHandlerCTor
+// ===================
+
+XMPFileHandler * FLV_MetaHandlerCTor ( XMPFiles * parent )
+{
+
+ return new FLV_MetaHandler ( parent );
+
+} // FLV_MetaHandlerCTor
+
+// =================================================================================================
+// FLV_MetaHandler::FLV_MetaHandler
+// ================================
+
+FLV_MetaHandler::FLV_MetaHandler ( XMPFiles * _parent )
+ : flvHeaderLen(0), longXMP(false), xmpTagPos(0), omdTagPos(0), xmpTagLen(0), omdTagLen(0)
+{
+
+ this->parent = _parent; // Inherited, can't set in the prefix.
+ this->handlerFlags = kFLV_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+} // FLV_MetaHandler::FLV_MetaHandler
+
+// =================================================================================================
+// FLV_MetaHandler::~FLV_MetaHandler
+// =================================
+
+FLV_MetaHandler::~FLV_MetaHandler()
+{
+
+ // Nothing to do yet.
+
+} // FLV_MetaHandler::~FLV_MetaHandler
+
+// =================================================================================================
+// GetTagInfo
+// ==========
+//
+// Seek to the start of a tag and extract the type, data size, and timestamp. Leave the file
+// positioned at the first byte of data.
+
+struct TagInfo {
+ XMP_Uns8 type;
+ XMP_Uns32 time;
+ XMP_Uns32 dataSize;
+};
+
+static void GetTagInfo ( LFA_FileRef fileRef, XMP_Uns64 tagPos, TagInfo * info )
+{
+ XMP_Uns8 buffer [11];
+
+ LFA_Seek ( fileRef, tagPos, SEEK_SET );
+ LFA_Read ( fileRef, buffer, 11, kLFA_RequireAll );
+
+ info->type = buffer[0];
+ info->time = GetUns24BE ( &buffer[4] ) || (buffer[7] << 24);
+ info->dataSize = GetUns24BE ( &buffer[1] );
+
+} // GetTagInfo
+
+// =================================================================================================
+// GetASValueLen
+// =============
+//
+// Return the full length of a serialized ActionScript value, including the type byte, zero if unknown.
+
+static XMP_Uns32 GetASValueLen ( const XMP_Uns8 * asValue, const XMP_Uns8 * asLimit )
+{
+ XMP_Uns32 valueLen = 0;
+ const XMP_Uns8 * itemPtr;
+ XMP_Uns32 arrayCount;
+
+ switch ( asValue[0] ) {
+
+ case 0 : // IEEE double
+ valueLen = 1 + 8;
+ break;
+
+ case 1 : // UI8 Boolean
+ valueLen = 1 + 1;
+ break;
+
+ case 2 : // Short string
+ valueLen = 1 + 2 + GetUns16BE ( &asValue[1] );
+ break;
+
+ case 3 : // ActionScript object, a name and value.
+ itemPtr = &asValue[1];
+ itemPtr += 2 + GetUns16BE ( itemPtr ); // Move past the name portion.
+ itemPtr += GetASValueLen ( itemPtr, asLimit ); // And past the data portion.
+ valueLen = (XMP_Uns32) (itemPtr - asValue);
+ break;
+
+ case 4 : // Short string (movie clip path)
+ valueLen = 1 + 2 + GetUns16BE ( &asValue[1] );
+ break;
+
+ case 5 : // Null
+ valueLen = 1;
+ break;
+
+ case 6 : // Undefined
+ valueLen = 1;
+ break;
+
+ case 7 : // UI16 reference ID
+ valueLen = 1 + 2;
+ break;
+
+ case 8 : // ECMA array, ignore the count, look for the 0x000009 terminator.
+ itemPtr = &asValue[5];
+ while ( itemPtr < asLimit ) {
+ XMP_Uns16 nameLen = GetUns16BE ( itemPtr );
+ itemPtr += 2 + nameLen; // Move past the name portion.
+ if ( (nameLen == 0) && (*itemPtr == 9) ) {
+ itemPtr += 1;
+ break; // Done, found the 0x000009 terminator.
+ }
+ itemPtr += GetASValueLen ( itemPtr, asLimit ); // And past the data portion.
+ }
+ valueLen = (XMP_Uns32) (itemPtr - asValue);
+ break;
+
+ case 10 : // Strict array, has an exact count.
+ arrayCount = GetUns32BE ( &asValue[1] );
+ itemPtr = &asValue[5];
+ for ( ; (arrayCount > 0) && (itemPtr < asLimit); --arrayCount ) {
+ itemPtr += 2 + GetUns16BE ( itemPtr ); // Move past the name portion.
+ itemPtr += GetASValueLen ( itemPtr, asLimit ); // And past the data portion.
+ }
+ valueLen = (XMP_Uns32) (itemPtr - asValue);
+ break;
+
+ case 11 : // Date
+ valueLen = 1 + 8 + 2;
+ break;
+
+ case 12: // Long string
+ valueLen = 1 + 4 + GetUns32BE ( &asValue[1] );
+ break;
+
+ }
+
+ return valueLen;
+
+} // GetASValueLen
+
+// =================================================================================================
+// CheckName
+// =========
+//
+// Check for the name portion of a script data tag or array item, with optional nul terminator. The
+// wantedLen must not count the terminator.
+
+static inline bool CheckName ( XMP_StringPtr inputName, XMP_Uns16 inputLen,
+ XMP_StringPtr wantedName, XMP_Uns16 wantedLen )
+{
+
+ if ( inputLen == wantedLen+1 ) {
+ if ( inputName[wantedLen] != 0 ) return false; // Extra byte must be terminating nul.
+ --inputLen;
+ }
+
+ if ( (inputLen == wantedLen) && XMP_LitNMatch ( inputName, wantedName, wantedLen ) ) return true;
+ return false;
+
+} // CheckName
+
+// =================================================================================================
+// FLV_MetaHandler::CacheFileData
+// ==============================
+//
+// Look for the onXMPData and onMetaData script data tags at time 0. Cache all of onMetaData, it
+// shouldn't be that big and this removes a need to know what is reconciled. It can't be more than
+// 16MB anyway, the size field is only 24 bits.
+
+void FLV_MetaHandler::CacheFileData()
+{
+ XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) );
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ LFA_FileRef fileRef = this->parent->fileRef;
+ XMP_Uns64 fileSize = LFA_Measure ( fileRef );
+
+ XMP_Uns8 buffer [16]; // Enough for 1+2+"onMetaData"+nul.
+ XMP_Uns32 ioCount;
+ TagInfo info;
+
+ LFA_Seek ( fileRef, 5, SEEK_SET );
+ LFA_Read ( fileRef, buffer, 4, kLFA_RequireAll );
+
+ 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 = LFA_Read ( fileRef, 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, ' ' );
+ LFA_Seek ( fileRef, this->packetInfo.offset, SEEK_SET );
+ LFA_Read ( fileRef, (void*)this->onXMP.data(), ioCount, kLFA_RequireAll );
+
+ 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, ' ' );
+ LFA_Seek ( fileRef, (tagPos + 11 + 1+2+nameLen), SEEK_SET );
+ LFA_Read ( fileRef, (void*)this->onMetaData.data(), ioCount, kLFA_RequireAll );
+
+ if ( ! this->onXMP.empty() ) break; // Done if we've found both.
+
+ }
+
+ }
+
+} // FLV_MetaHandler::CacheFileData
+
+// =================================================================================================
+// FLV_MetaHandler::MakeLegacyDigest
+// =================================
+
+#define kHexDigits "0123456789ABCDEF"
+
+void FLV_MetaHandler::MakeLegacyDigest ( std::string * digestStr )
+{
+ MD5_CTX context;
+ unsigned char digestBin [16];
+
+ MD5Init ( &context );
+ MD5Update ( &context, (XMP_Uns8*)this->onMetaData.data(), (unsigned int)this->onMetaData.size() );
+ MD5Final ( digestBin, &context );
+
+ char buffer [40];
+ for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) {
+ XMP_Uns8 byte = digestBin[in];
+ buffer[out] = kHexDigits [ byte >> 4 ];
+ buffer[out+1] = kHexDigits [ byte & 0xF ];
+ }
+ buffer[32] = 0;
+ digestStr->erase();
+ digestStr->append ( buffer, 32 );
+
+} // FLV_MetaHandler::MakeLegacyDigest
+
+// =================================================================================================
+// FLV_MetaHandler::ExtractLiveXML
+// ===============================
+//
+// Extract the XMP packet from the cached onXMPData ECMA array's "liveXMP" item.
+
+void FLV_MetaHandler::ExtractLiveXML()
+{
+ if ( this->onXMP[0] != 0x08 ) return; // Make sure onXMPData is an ECMA array.
+ const XMP_Uns8 * ecmaArray = (const XMP_Uns8 *) this->onXMP.c_str();
+ const XMP_Uns8 * ecmaLimit = ecmaArray + this->onXMP.size();
+
+ if ( this->onXMP.size() >= 3 ) { // Omit the 0x000009 terminator, simplifies the loop.
+ if ( GetUns24BE ( ecmaLimit-3 ) == 9 ) ecmaLimit -= 3;
+ }
+
+ for ( const XMP_Uns8 * itemPtr = ecmaArray + 5; itemPtr < ecmaLimit; /* internal increment */ ) {
+
+ // Find the "liveXML" array item, make sure it is a short or long string.
+
+ XMP_Uns16 nameLen = GetUns16BE ( itemPtr );
+ const XMP_Uns8 * namePtr = itemPtr + 2;
+
+ itemPtr += (2 + nameLen); // Move to the value portion.
+ XMP_Uns32 valueLen = GetASValueLen ( itemPtr, ecmaLimit );
+ if ( valueLen == 0 ) return; // ! Unknown value type, can't look further.
+
+ if ( CheckName ( (char*)namePtr, nameLen, "liveXML", 7 ) ) {
+
+ XMP_Uns32 lenLen = 2; // Assume a short string.
+ if ( *itemPtr == 12 ) {
+ lenLen = 4;
+ this->longXMP = true;
+ } else if ( *itemPtr != 2 ) {
+ return; // Not a short or long string.
+ }
+
+ valueLen -= (1 + lenLen);
+ itemPtr += (1 + lenLen);
+
+ this->packetInfo.offset += (itemPtr - ecmaArray);
+ this->packetInfo.length += valueLen;
+
+ this->xmpPacket.reserve ( valueLen );
+ this->xmpPacket.assign ( (char*)itemPtr, valueLen );
+
+ return;
+
+ }
+
+ itemPtr += valueLen; // Move past the value portion.
+
+ }
+
+} // FLV_MetaHandler::ExtractLiveXML
+
+// =================================================================================================
+// FLV_MetaHandler::ProcessXMP
+// ===========================
+
+void FLV_MetaHandler::ProcessXMP()
+{
+ if ( this->processedXMP ) return;
+ this->processedXMP = true; // Make sure only called once.
+
+ if ( ! this->onXMP.empty() ) { // Look for the XMP packet.
+
+ this->ExtractLiveXML();
+ if ( ! this->xmpPacket.empty() ) {
+ FillPacketInfo ( this->xmpPacket, &this->packetInfo );
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+ this->containsXMP = true;
+ }
+
+ }
+
+ // Now process the legacy, if necessary.
+
+ if ( this->onMetaData.empty() ) return; // No legacy, we're done.
+
+ std::string oldDigest;
+ bool oldDigestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "FLV", &oldDigest, 0 );
+
+ if ( oldDigestFound ) {
+ std::string newDigest;
+ this->MakeLegacyDigest ( &newDigest );
+ if ( oldDigest == newDigest ) return; // No legacy changes.
+ }
+
+ // *** No spec yet for what legacy to reconcile.
+
+} // FLV_MetaHandler::ProcessXMP
+
+// =================================================================================================
+// FLV_MetaHandler::UpdateFile
+// ===========================
+
+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);
+
+ LFA_FileRef fileRef = this->parent->fileRef;
+ XMP_Uns64 fileSize = LFA_Measure ( fileRef );
+
+ // 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 ) {
+
+ LFA_Seek ( fileRef, this->packetInfo.offset, SEEK_SET );
+ LFA_Write ( fileRef, this->xmpPacket.data(), (XMP_Int32)this->xmpPacket.size() );
+
+ } else {
+
+ std::string origPath = this->parent->filePath;
+ LFA_FileRef origRef = this->parent->fileRef;
+
+ std::string updatePath;
+ LFA_FileRef updateRef = 0;
+
+ CreateTempFile ( origPath, &updatePath );
+ updateRef = LFA_Open ( updatePath.c_str(), 'w' );
+
+ this->parent->filePath = updatePath;
+ this->parent->fileRef = updateRef;
+
+ try {
+ this->WriteFile ( origRef, origPath );
+ } catch ( ... ) {
+ LFA_Close ( updateRef );
+ this->parent->filePath = origPath;
+ this->parent->fileRef = origRef;
+ throw;
+ }
+
+ LFA_Close ( origRef );
+ LFA_Delete ( origPath.c_str() );
+
+ LFA_Close ( updateRef );
+ LFA_Rename ( updatePath.c_str(), origPath.c_str() );
+ this->parent->filePath = origPath;
+ this->parent->fileRef = 0;
+
+ }
+
+ this->needsUpdate = false;
+
+} // FLV_MetaHandler::UpdateFile
+
+// =================================================================================================
+// WriteOnXMP
+// ==========
+//
+// Write the XMP packet wrapped up in an ECMA array script data tag:
+//
+// 0 UI8 tag type : 18
+// 1 UI24 content length : 1+2+9+1+4+2+7+1 + <2 or 4> + XMP packet size + 1 + 3
+// 4 UI24 time low : 0
+// 7 UI8 time high : 0
+// 8 UI24 stream ID : 0
+//
+// 11 UI8 0x02
+// 12 UI16 name length : 9
+// 14 str9 tag name : "onXMPData", no nul terminator
+// 23 UI8 value type : 8
+// 24 UI32 array count : 1
+// 28 UI16 name length : 7
+// 30 str7 item name : "liveXML", no nul terminator
+//
+// 37 UI8 value type : 2 for a short string, 12 for a long string
+// 38 UIn XMP packet size + 1, UI16 or UI32 as needed
+// -- str XMP packet, with nul terminator
+//
+// -- UI24 array terminator : 0x000009
+// -- UI32 back pointer : content length + 11
+
+static void WriteOnXMP ( LFA_FileRef fileRef, const std::string & xmpPacket )
+{
+ char buffer [64];
+ bool longXMP = false;
+ XMP_Uns32 tagLen = 1+2+9+1+4+2+7+1 + 2 + (XMP_Uns32)xmpPacket.size() + 1 + 3;
+
+ if ( xmpPacket.size() > 0xFFFE ) {
+ longXMP = true;
+ tagLen += 2;
+ }
+
+ if ( tagLen > 16*1024*1024 ) XMP_Throw ( "FLV tags can't be larger than 16MB", kXMPErr_TBD );
+
+ // Fill in the script data tag header.
+
+ buffer[0] = 18;
+ PutUns24BE ( tagLen, &buffer[1] );
+ PutUns24BE ( 0, &buffer[4] );
+ buffer[7] = 0;
+ PutUns24BE ( 0, &buffer[8] );
+
+ // Fill in the "onXMPData" name, ECMA array start, and "liveXML" name.
+
+ buffer[11] = 2;
+ PutUns16BE ( 9, &buffer[12] );
+ memcpy ( &buffer[14], "onXMPData", 9 ); // AUDIT: Safe, buffer has 64 chars.
+ buffer[23] = 8;
+ PutUns32BE ( 1, &buffer[24] );
+ PutUns16BE ( 7, &buffer[28] );
+ memcpy ( &buffer[30], "liveXML", 7 ); // AUDIT: Safe, buffer has 64 chars.
+
+ // Fill in the XMP packet string type and length, write what we have so far.
+
+ LFA_Seek ( fileRef, 0, SEEK_END );
+ if ( ! longXMP ) {
+ buffer[37] = 2;
+ PutUns16BE ( (XMP_Uns16)xmpPacket.size()+1, &buffer[38] );
+ LFA_Write ( fileRef, buffer, 40 );
+ } else {
+ buffer[37] = 12;
+ PutUns32BE ( (XMP_Uns32)xmpPacket.size()+1, &buffer[38] );
+ LFA_Write ( fileRef, buffer, 42 );
+ }
+
+ // Write the XMP packet, nul terminator, array terminator, and back pointer.
+
+ LFA_Write ( fileRef, xmpPacket.c_str(), (XMP_Int32)xmpPacket.size()+1 );
+ PutUns24BE ( 9, &buffer[0] );
+ PutUns32BE ( tagLen+11, &buffer[3] );
+ LFA_Write ( fileRef, buffer, 7 );
+
+} // WriteOnXMP
+
+// =================================================================================================
+// FLV_MetaHandler::WriteFile
+// ==========================
+//
+// Use a source (old) file and the current XMP to build a destination (new) file. All of the source
+// file is copied except for previous XMP. The current XMP is inserted after onMetaData, or at least
+// before the first time 0 audio or video tag.
+
+// ! We do not currently update anything in onMetaData.
+
+void FLV_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath )
+{
+ if ( ! this->needsUpdate ) return;
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ LFA_FileRef destRef = this->parent->fileRef;
+
+ XMP_Uns64 sourceLen = LFA_Measure ( sourceRef );
+ XMP_Uns64 sourcePos = 0;
+
+ LFA_Seek ( sourceRef, 0, SEEK_SET );
+ LFA_Seek ( destRef, 0, SEEK_SET );
+ LFA_Truncate ( destRef, 0 );
+
+ // 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.
+
+ LFA_Copy ( sourceRef, destRef, this->flvHeaderLen, abortProc, abortArg );
+
+ XMP_Uns32 zero = 0; // Don't require the initial 0 back pointer to be in the source file.
+ LFA_Write ( destRef, &zero, 4 );
+
+ sourcePos = this->flvHeaderLen + 4;
+
+ WriteOnXMP ( destRef, 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) ) {
+ LFA_Copy ( sourceRef, destRef, this->xmpTagPos, abortProc, abortArg );
+ sourcePos = this->xmpTagPos + this->xmpTagLen;
+ LFA_Seek ( sourceRef, sourcePos, SEEK_SET );
+ }
+
+ LFA_Copy ( sourceRef, destRef, (omdEnd - sourcePos), abortProc, abortArg );
+ sourcePos = omdEnd;
+
+ WriteOnXMP ( destRef, this->xmpPacket );
+
+ }
+
+ // Copy the rest of the file, skipping any XMP that is in the way.
+
+ if ( (this->xmpTagPos != 0) && (this->xmpTagPos >= sourcePos) ) {
+ LFA_Copy ( sourceRef, destRef, (this->xmpTagPos - sourcePos), abortProc, abortArg );
+ sourcePos = this->xmpTagPos + this->xmpTagLen;
+ LFA_Seek ( sourceRef, sourcePos, SEEK_SET );
+ }
+
+ LFA_Copy ( sourceRef, destRef, (sourceLen - sourcePos), abortProc, abortArg );
+
+ this->needsUpdate = false;
+
+} // FLV_MetaHandler::WriteFile
+
+// =================================================================================================