static void ExportIPTC_SubjectCode ( const SXMPMeta & xmp, IPTC_Manager * iptc ) { std::string xmpValue, iimValue; XMP_OptionBits xmpFlags; bool found = xmp.GetProperty ( kXMP_NS_IPTCCore, "SubjectCode", 0, &xmpFlags ); if ( ! found ) { iptc->DeleteDataSet ( kIPTC_SubjectCode ); return; } if ( ! XMP_PropIsArray ( xmpFlags ) ) return; // ? Complain? Delete the DataSet? XMP_Index xmpCount = xmp.CountArrayItems ( kXMP_NS_IPTCCore, "SubjectCode" ); XMP_Index iptcCount = (XMP_Index) iptc->GetDataSet ( kIPTC_SubjectCode, 0 ); if ( xmpCount != iptcCount ) iptc->DeleteDataSet ( kIPTC_SubjectCode ); for ( XMP_Index ds = 0; ds < xmpCount; ++ds ) { // ! XMP arrays are indexed from 1, IPTC from 0. (void) xmp.GetArrayItem ( kXMP_NS_IPTCCore, "SubjectCode", ds+1, &xmpValue, &xmpFlags ); if ( ! XMP_PropIsSimple ( xmpFlags ) ) continue; // ? Complain? if ( xmpValue.size() != 8 ) continue; // ? Complain? iimValue = "IPTC:"; iimValue += xmpValue; iimValue += ":::"; // Add the separating colons for the empty name portions. iptc->SetDataSet_UTF8 ( kIPTC_SubjectCode, iimValue.c_str(), (XMP_Uns32)iimValue.size(), ds ); // ! Appends if necessary. } } // ExportIPTC_SubjectCode
static void ExportIPTC_Array ( const SXMPMeta & xmp, IPTC_Manager * iptc, const char * xmpNS, const char * xmpProp, XMP_Uns8 id ) { std::string value; XMP_OptionBits xmpFlags; bool found = xmp.GetProperty ( xmpNS, xmpProp, 0, &xmpFlags ); if ( ! found ) { iptc->DeleteDataSet ( id ); return; } if ( ! XMP_PropIsArray ( xmpFlags ) ) return; // ? Complain? Delete the DataSet? XMP_Index xmpCount = xmp.CountArrayItems ( xmpNS, xmpProp ); XMP_Index iptcCount = (XMP_Index) iptc->GetDataSet ( id, 0 ); if ( xmpCount != iptcCount ) iptc->DeleteDataSet ( id ); for ( XMP_Index ds = 0; ds < xmpCount; ++ds ) { // ! XMP arrays are indexed from 1, IPTC from 0. (void) xmp.GetArrayItem ( xmpNS, xmpProp, ds+1, &value, &xmpFlags ); if ( ! XMP_PropIsSimple ( xmpFlags ) ) continue; // ? Complain? NormalizeToCR ( &value ); iptc->SetDataSet_UTF8 ( id, value.c_str(), (XMP_Uns32)value.size(), ds ); // ! Appends if necessary. } } // ExportIPTC_Array
static void ExportIPTC_IntellectualGenre ( const SXMPMeta & xmp, IPTC_Manager * iptc ) { std::string xmpValue; XMP_OptionBits xmpFlags; bool found = xmp.GetProperty ( kXMP_NS_IPTCCore, "IntellectualGenre", &xmpValue, &xmpFlags ); if ( ! found ) { iptc->DeleteDataSet ( kIPTC_IntellectualGenre ); return; } if ( ! XMP_PropIsSimple ( xmpFlags ) ) return; // ? Complain? Delete the DataSet? NormalizeToCR ( &xmpValue ); int i; XMP_StringPtr namePtr = xmpValue.c_str(); for ( i = 0; kIntellectualGenreMappings[i].name != 0; ++i ) { if ( strcmp ( namePtr, kIntellectualGenreMappings[i].name ) == 0 ) break; } if ( kIntellectualGenreMappings[i].name == 0 ) return; // Not a known genre, don't export it. std::string iimValue = kIntellectualGenreMappings[i].refNum; iimValue += ':'; iimValue += xmpValue; size_t iptcCount = iptc->GetDataSet ( kIPTC_IntellectualGenre, 0 ); if ( iptcCount > 1 ) iptc->DeleteDataSet ( kIPTC_IntellectualGenre ); iptc->SetDataSet_UTF8 ( kIPTC_IntellectualGenre, iimValue.c_str(), (XMP_Uns32)iimValue.size(), 0 ); // ! Don't append a 2nd DataSet! } // ExportIPTC_IntellectualGenre
static void FullUnicodeParse ( FILE * log, const char * encoding, size_t bufferSize, const std::string & packet, const std::string & fullUnicode ) { if ( bufferSize > sizeof(sU32) ) { fprintf ( log, "#ERROR: FullUnicodeParse buffer overrun for %s, %d byte buffers\n", encoding, bufferSize ); return; } SXMPMeta meta; try { memset ( sU32, -1, sizeof(sU32) ); for ( size_t i = 0; i < packet.size(); i += bufferSize ) { size_t count = bufferSize; if ( count > (packet.size() - i) ) count = packet.size() - i; memcpy ( sU32, &packet[i], count ); meta.ParseFromBuffer ( XMP_StringPtr(sU32), count, kXMP_ParseMoreBuffers ); } meta.ParseFromBuffer ( XMP_StringPtr(sU32), 0 ); } catch ( XMP_Error& excep ) { char message [200]; sprintf ( message, "#ERROR: Full Unicode parsing error for %s, %d byte buffers", encoding, bufferSize ); PrintXMPErrorInfo ( excep, message ); return; } std::string value; bool found = meta.GetProperty ( kNS1, "FullUnicode", &value, 0 ); if ( (! found) || (value != fullUnicode) ) fprintf ( log, "#ERROR: Failed to get full Unicode value for %s, %d byte buffers\n", encoding, bufferSize ); } // FullUnicodeParse
static void ExportIPTC_LangAlt ( const SXMPMeta & xmp, IPTC_Manager * iptc, const char * xmpNS, const char * xmpProp, XMP_Uns8 id ) { std::string value; XMP_OptionBits xmpFlags; bool found = xmp.GetProperty ( xmpNS, xmpProp, 0, &xmpFlags ); if ( ! found ) { iptc->DeleteDataSet ( id ); return; } if ( ! XMP_ArrayIsAltText ( xmpFlags ) ) return; // ? Complain? Delete the DataSet? found = xmp.GetLocalizedText ( xmpNS, xmpProp, "", "x-default", 0, &value, 0 ); if ( ! found ) { iptc->DeleteDataSet ( id ); return; } NormalizeToCR ( &value ); size_t iptcCount = iptc->GetDataSet ( id, 0 ); if ( iptcCount > 1 ) iptc->DeleteDataSet ( id ); iptc->SetDataSet_UTF8 ( id, value.c_str(), (XMP_Uns32)value.size(), 0 ); // ! Don't append a 2nd DataSet! } // ExportIPTC_LangAlt
void PhotoDataUtils::ExportPSIR ( const SXMPMeta & xmp, PSIR_Manager * psir ) { bool found; std::string utf8Value; try { // Don't let errors with one stop the others. found = xmp.GetProperty ( kXMP_NS_XMP_Rights, "Marked", &utf8Value, 0 ); if ( ! found ) { psir->DeleteImgRsrc ( kPSIR_CopyrightFlag ); } else { bool copyrighted = SXMPUtils::ConvertToBool ( utf8Value ); psir->SetImgRsrc ( kPSIR_CopyrightFlag, ©righted, 1 ); } } catch ( ... ) { // Do nothing, let other exports proceed. // ? Notify client? } try { // Don't let errors with one stop the others. found = xmp.GetProperty ( kXMP_NS_XMP_Rights, "WebStatement", &utf8Value, 0 ); if ( ! found ) { psir->DeleteImgRsrc ( kPSIR_CopyrightURL ); } else if ( ! ignoreLocalText ) { std::string localValue; ReconcileUtils::UTF8ToLocal ( utf8Value.c_str(), utf8Value.size(), &localValue ); psir->SetImgRsrc ( kPSIR_CopyrightURL, localValue.c_str(), (XMP_Uns32)localValue.size() ); } else if ( ReconcileUtils::IsASCII ( utf8Value.c_str(), utf8Value.size() ) ) { psir->SetImgRsrc ( kPSIR_CopyrightURL, utf8Value.c_str(), (XMP_Uns32)utf8Value.size() ); } else { psir->DeleteImgRsrc ( kPSIR_CopyrightURL ); } } catch ( ... ) { // Do nothing, let other exports proceed. // ? Notify client? } } // PhotoDataUtils::ExportPSIR;
bool ASF_LegacyManager::CheckDigest ( const SXMPMeta& xmp ) { bool ret = false; if ( ! digestComputed ) this->ComputeDigest(); std::string oldDigest; if ( xmp.GetProperty ( kXMP_NS_ASF, "NativeDigest", &oldDigest, 0 ) ) { ret = (digestStr == oldDigest); } return ret; }
bool xmp_get_property(XmpPtr xmp, const char *schema, const char *name, XmpStringPtr property, uint32_t *propsBits) { CHECK_PTR(xmp, false); RESET_ERROR; bool ret = false; try { SXMPMeta *txmp = (SXMPMeta *)xmp; XMP_OptionBits optionBits; ret = txmp->GetProperty(schema, name, STRING(property), &optionBits); if(propsBits) { *propsBits = optionBits; } } catch(const XMP_Error & e) { set_error(e); } return ret; }
/** * Initializes the toolkit and attempts to open a file for reading metadata. Initially * an attempt to open the file is done with a handler, if this fails then the file is opened with * packet scanning. Once the file is open several properties are read and displayed in the console. * The XMP object is then dumped to a text file and the resource file is closed. */ int main ( int argc, const char * argv[] ) { if ( argc != 2 ) // 2 := command and 1 parameter { cout << "usage: ReadingXMP (filename)" << endl; return 0; } string filename = string( argv[1] ); if(!SXMPMeta::Initialize()) { cout << "Could not initialize toolkit!"; return -1; } // Must initialize SXMPFiles before we use it if ( ! SXMPFiles::Initialize() ) { cout << "Could not initialize SXMPFiles."; return -1; } try { // Options to open the file with - read only and use a file handler XMP_OptionBits opts = kXMPFiles_OpenForRead | kXMPFiles_OpenUseSmartHandler; bool ok; SXMPFiles myFile; std::string status = ""; // First we try and open the file ok = myFile.OpenFile(filename, kXMP_UnknownFile, opts); if( ! ok ) { status += "No smart handler available for " + filename + "\n"; status += "Trying packet scanning.\n"; // Now try using packet scanning opts = kXMPFiles_OpenForUpdate | kXMPFiles_OpenUsePacketScanning; ok = myFile.OpenFile(filename, kXMP_UnknownFile, opts); } // If the file is open then read the metadata if(ok) { cout << status << endl; cout << filename << " is opened successfully" << endl; // Create the xmp object and get the xmp data SXMPMeta meta; myFile.GetXMP(&meta); bool exists; // Read a simple property string simpleValue; //Stores the value for the property exists = meta.GetProperty(kXMP_NS_XMP, "CreatorTool", &simpleValue, NULL); if(exists) cout << "CreatorTool = " << simpleValue << endl; else simpleValue.clear(); // Get the first element in the dc:creator array string elementValue; exists = meta.GetArrayItem(kXMP_NS_DC, "creator", 1, &elementValue, NULL); if(exists) cout << "dc:creator = " << elementValue << endl; else elementValue.clear(); // Get the the entire dc:subject array string propValue; int arrSize = meta.CountArrayItems(kXMP_NS_DC, "subject"); for(int i = 1; i <= arrSize;i++) { meta.GetArrayItem(kXMP_NS_DC, "subject", i, &propValue, 0); cout << "dc:subject[" << i << "] = " << propValue << endl; } // Get the dc:title for English and French string itemValue; string actualLang; meta.GetLocalizedText(kXMP_NS_DC, "title", "en", "en-US", NULL, &itemValue, NULL); cout << "dc:title in English = " << itemValue << endl; meta.GetLocalizedText(kXMP_NS_DC, "title", "fr", "fr-FR", NULL, &itemValue, NULL); cout << "dc:title in French = " << itemValue << endl; // Get dc:MetadataDate XMP_DateTime myDate; if(meta.GetProperty_Date(kXMP_NS_XMP, "MetadataDate", &myDate, NULL)) { // Convert the date struct into a convenient string and display it string myDateStr; SXMPUtils::ConvertFromDate(myDate, &myDateStr); cout << "meta:MetadataDate = " << myDateStr << endl; } // See if the flash struct exists and see if it was used string path, value; exists = meta.DoesStructFieldExist(kXMP_NS_EXIF, "Flash", kXMP_NS_EXIF,"Fired"); if(exists) { bool flashFired; SXMPUtils::ComposeStructFieldPath(kXMP_NS_EXIF, "Flash", kXMP_NS_EXIF, "Fired", &path); meta.GetProperty_Bool(kXMP_NS_EXIF, path.c_str(), &flashFired, NULL); string flash = (flashFired) ? "True" : "False"; cout << "Flash Used = " << flash << endl; } // Dump the current xmp object to a file ofstream dumpFile; dumpFile.open("XMPDump.txt", ios::out); meta.DumpObject(DumpXMPToFile, &dumpFile); dumpFile.close(); cout << endl << "XMP dumped to XMPDump.txt" << endl; // Close the SXMPFile. The resource file is already closed if it was // opened as read only but this call must still be made. myFile.CloseFile(); } else { cout << "Unable to open " << filename << endl; } } catch(XMP_Error & e) { cout << "ERROR: " << e.GetErrMsg() << endl; } // Terminate the toolkit SXMPFiles::Terminate(); SXMPMeta::Terminate(); return 0; }
int ASF_LegacyManager::ExportLegacy ( const SXMPMeta& xmp ) { int changed = 0; objectsToExport = 0; legacyDiff = 0; std::string utf8; std::string utf16; XMP_OptionBits flags; if ( ! broadcastSet ) { if ( xmp.GetProperty ( kXMP_NS_XMP, "CreateDate", &utf8, &flags ) ) { std::string date; ConvertISODateToMSDate ( utf8, &date ); if ( fields[fieldCreationDate] != date ) { legacyDiff += date.size(); legacyDiff -= fields[fieldCreationDate].size(); this->SetField ( fieldCreationDate, date ); objectsToExport |= objectFileProperties; changed ++; } } } if ( xmp.GetLocalizedText ( kXMP_NS_DC, "title", "", "x-default", 0, &utf8, &flags ) ) { NormalizeStringTrailingNull ( utf8 ); ToUTF16 ( (const UTF8Unit*)utf8.data(), utf8.size(), &utf16, false ); if ( fields[fieldTitle] != utf16 ) { legacyDiff += utf16.size(); legacyDiff -= fields[fieldTitle].size(); this->SetField ( fieldTitle, utf16 ); objectsToExport |= objectContentDescription; changed ++; } } utf8.clear(); SXMPUtils::CatenateArrayItems ( xmp, kXMP_NS_DC, "creator", 0, 0, kXMPUtil_AllowCommas, &utf8 ); if ( ! utf8.empty() ) { NormalizeStringTrailingNull ( utf8 ); ToUTF16 ( (const UTF8Unit*)utf8.data(), utf8.size(), &utf16, false ); if ( fields[fieldAuthor] != utf16 ) { legacyDiff += utf16.size(); legacyDiff -= fields[fieldAuthor].size(); this->SetField ( fieldAuthor, utf16 ); objectsToExport |= objectContentDescription; changed ++; } } if ( xmp.GetLocalizedText ( kXMP_NS_DC, "rights", "", "x-default", 0, &utf8, &flags ) ) { NormalizeStringTrailingNull ( utf8 ); ToUTF16 ( (const UTF8Unit*)utf8.data(), utf8.size(), &utf16, false ); if ( fields[fieldCopyright] != utf16 ) { legacyDiff += utf16.size(); legacyDiff -= fields[fieldCopyright].size(); this->SetField ( fieldCopyright, utf16 ); objectsToExport |= objectContentDescription; changed ++; } } if ( xmp.GetLocalizedText ( kXMP_NS_DC, "description", "", "x-default", 0, &utf8, &flags ) ) { NormalizeStringTrailingNull ( utf8 ); ToUTF16 ( (const UTF8Unit*)utf8.data(), utf8.size(), &utf16, false ); if ( fields[fieldDescription] != utf16 ) { legacyDiff += utf16.size(); legacyDiff -= fields[fieldDescription].size(); this->SetField ( fieldDescription, utf16 ); objectsToExport |= objectContentDescription; changed ++; } } if ( xmp.GetProperty ( kXMP_NS_XMP_Rights, "WebStatement", &utf8, &flags ) ) { NormalizeStringTrailingNull ( utf8 ); if ( fields[fieldCopyrightURL] != utf8 ) { legacyDiff += utf8.size(); legacyDiff -= fields[fieldCopyrightURL].size(); this->SetField ( fieldCopyrightURL, utf8 ); objectsToExport |= objectContentBranding; changed ++; } } #if ! Exclude_LicenseURL_Recon if ( xmp.GetProperty ( kXMP_NS_XMP_Rights, "Certificate", &utf8, &flags ) ) { NormalizeStringTrailingNull ( utf8 ); if ( fields[fieldLicenseURL] != utf8 ) { legacyDiff += utf8.size(); legacyDiff -= fields[fieldLicenseURL].size(); this->SetField ( fieldLicenseURL, utf8 ); objectsToExport |= objectContentEncryption; changed ++; } } #endif // find objects, that would need to be created on legacy export int newObjects = (objectsToExport & !objectsExisting); // calculate minimum storage for new objects, that might be created on export if ( newObjects & objectContentDescription ) legacyDiff += sizeContentDescription; if ( newObjects & objectContentBranding ) legacyDiff += sizeContentBranding; if ( newObjects & objectContentEncryption ) legacyDiff += sizeContentEncryption; ComputeDigest(); return changed; }
static void DoTest ( FILE * log ) { SXMPMeta meta; size_t u8Count, u32Count; SXMPMeta meta8, meta16b, meta16l, meta32b, meta32l; std::string u8Packet, u16bPacket, u16lPacket, u32bPacket, u32lPacket; InitializeUnicodeConversions(); // --------------------------------------------------------------------------------------------- fprintf ( log, "// ------------------------------------------------\n" ); fprintf ( log, "// Test basic serialization and parsing using ASCII\n\n" ); // ---------------------------------------------------- // Create basic ASCII packets in each of the encodings. meta.ParseFromBuffer ( kSimpleRDF, kXMP_UseNullTermination ); meta.SerializeToBuffer ( &u8Packet, (kXMP_OmitPacketWrapper | kXMP_EncodeUTF8) ); meta.SerializeToBuffer ( &u16bPacket, (kXMP_OmitPacketWrapper | kXMP_EncodeUTF16Big) ); meta.SerializeToBuffer ( &u16lPacket, (kXMP_OmitPacketWrapper | kXMP_EncodeUTF16Little) ); meta.SerializeToBuffer ( &u32bPacket, (kXMP_OmitPacketWrapper | kXMP_EncodeUTF32Big) ); meta.SerializeToBuffer ( &u32lPacket, (kXMP_OmitPacketWrapper | kXMP_EncodeUTF32Little) ); #if 0 FILE* dump; dump = fopen ( "u8Packet.txt", "w" ); fwrite ( u8Packet.c_str(), 1, u8Packet.size(), dump ); fclose ( dump ); dump = fopen ( "u16bPacket.txt", "w" ); fwrite ( u16bPacket.c_str(), 1, u16bPacket.size(), dump ); fclose ( dump ); dump = fopen ( "u16lPacket.txt", "w" ); fwrite ( u16lPacket.c_str(), 1, u16lPacket.size(), dump ); fclose ( dump ); dump = fopen ( "u32bPacket.txt", "w" ); fwrite ( u32bPacket.c_str(), 1, u32bPacket.size(), dump ); fclose ( dump ); dump = fopen ( "u32lPacket.txt", "w" ); fwrite ( u32lPacket.c_str(), 1, u32lPacket.size(), dump ); fclose ( dump ); #endif // Verify the character form. The conversion functions are tested separately. const char * ptr; ptr = u8Packet.c_str(); fprintf ( log, "UTF-8 : %d : %.2X %.2X \"%.10s...\"\n", u8Packet.size(), *ptr, *(ptr+1), ptr ); ptr = u16bPacket.c_str(); fprintf ( log, "UTF-16BE : %d : %.2X %.2X %.2X\n", u16bPacket.size(), *ptr, *(ptr+1), *(ptr+2) ); ptr = u16lPacket.c_str(); fprintf ( log, "UTF-16LE : %d : %.2X %.2X %.2X\n", u16lPacket.size(), *ptr, *(ptr+1), *(ptr+2) ); ptr = u32bPacket.c_str(); fprintf ( log, "UTF-32BE : %d : %.2X %.2X %.2X %.2X %.2X\n", u32bPacket.size(), *ptr, *(ptr+1), *(ptr+2), *(ptr+3), *(ptr+4) ); ptr = u32lPacket.c_str(); fprintf ( log, "UTF-32LE : %d : %.2X %.2X %.2X %.2X %.2X\n", u32lPacket.size(), *ptr, *(ptr+1), *(ptr+2), *(ptr+3), *(ptr+4) ); fprintf ( log, "\nBasic serialization tests done\n" ); // ------------------------------------------------- // Verify round trip reparsing of the basic packets. std::string origDump, rtDump; meta.DumpObject ( DumpToString, &origDump ); fprintf ( log, "Original dump\n%s\n", origDump.c_str() ); try { meta8.ParseFromBuffer ( u8Packet.c_str(), u8Packet.size() ); meta16b.ParseFromBuffer ( u16bPacket.c_str(), u16bPacket.size() ); meta16l.ParseFromBuffer ( u16lPacket.c_str(), u16lPacket.size() ); meta32b.ParseFromBuffer ( u32bPacket.c_str(), u32bPacket.size() ); meta32l.ParseFromBuffer ( u32lPacket.c_str(), u32lPacket.size() ); } catch ( XMP_Error& excep ) { PrintXMPErrorInfo ( excep, "## Caught reparsing exception" ); fprintf ( log, "\n" ); } #if 0 fprintf ( log, "After UTF-8 roundtrip\n" ); meta8.DumpObject ( DumpToFile, log ); fprintf ( log, "\nAfter UTF-16 BE roundtrip\n" ); meta16b.DumpObject ( DumpToFile, log ); fprintf ( log, "\nAfter UTF-16 LE roundtrip\n" ); meta16l.DumpObject ( DumpToFile, log ); fprintf ( log, "\nAfter UTF-32 BE roundtrip\n" ); meta32b.DumpObject ( DumpToFile, log ); fprintf ( log, "\nAfter UTF-32 LE roundtrip\n" ); meta32l.DumpObject ( DumpToFile, log ); #endif rtDump.clear(); meta8.DumpObject ( DumpToString, &rtDump ); if ( rtDump != origDump ) fprintf ( log, "#ERROR: Roundtrip failure for UTF-8\n%s\n", rtDump.c_str() ); rtDump.clear(); meta16b.DumpObject ( DumpToString, &rtDump ); if ( rtDump != origDump ) fprintf ( log, "#ERROR: Roundtrip failure for UTF-16BE\n%s\n", rtDump.c_str() ); rtDump.clear(); meta16l.DumpObject ( DumpToString, &rtDump ); if ( rtDump != origDump ) fprintf ( log, "#ERROR: Roundtrip failure for UTF-16LE\n%s\n", rtDump.c_str() ); #if IncludeUTF32 rtDump.clear(); meta32b.DumpObject ( DumpToString, &rtDump ); if ( rtDump != origDump ) fprintf ( log, "#ERROR: Roundtrip failure for UTF-32BE\n%s\n", rtDump.c_str() ); rtDump.clear(); meta32l.DumpObject ( DumpToString, &rtDump ); if ( rtDump != origDump ) fprintf ( log, "#ERROR: Roundtrip failure for UTF-32LE\n%s\n", rtDump.c_str() ); #endif fprintf ( log, "Basic round-trip parsing tests done\n\n" ); // --------------------------------------------------------------------------------------------- fprintf ( log, "// --------------------------------------------------\n" ); fprintf ( log, "// Test parse buffering logic using full Unicode data\n\n" ); // -------------------------------------------------------------------------------------------- // Construct the packets to parse in all encodings. There is just one property with a value // containing all of the Unicode representations. This isn't all of the Unicode characters, but // is more than enough to establish correctness of the buffering logic. It is almost everything // in the BMP, plus the range U+100000..U+10FFFF beyond the BMP. Doing all Unicode characters // takes far to long to execute and does not provide additional confidence. Skip ASCII controls, // they are not allowed in XML and get changed to spaces by SetProperty. Skip U+FFFE and U+FFFF, // the expat parser rejects them. #define kTab 0x09 #define kLF 0x0A #define kCR 0x0D size_t i; UTF32Unit cp; sU32[0] = kTab; sU32[1] = kLF; sU32[2] = kCR; for ( i = 3, cp = 0x20; cp < 0x7F; ++i, ++cp ) sU32[i] = cp; for ( cp = 0x80; cp < 0xD800; ++i, ++cp ) sU32[i] = cp; for ( cp = 0xE000; cp < 0xFFFE; ++i, ++cp ) sU32[i] = cp; for ( cp = 0x100000; cp < 0x110000; ++i, ++cp ) sU32[i] = cp; u32Count = i; assert ( u32Count == (3 + (0x7F-0x20) + (0xD800-0x80) + (0xFFFE - 0xE000) + (0x110000-0x100000)) ); if ( kBigEndianHost ) { UTF32BE_to_UTF8 ( sU32, u32Count, sU8, sizeof(sU8), &i, &u8Count ); } else { UTF32LE_to_UTF8 ( sU32, u32Count, sU8, sizeof(sU8), &i, &u8Count ); } if ( i != u32Count ) fprintf ( log, "#ERROR: Failed to convert full UTF-32 buffer\n" ); assert ( u8Count == (3 + (0x7F-0x20) + 2*(0x800-0x80) + 3*(0xD800-0x800) + 3*(0xFFFE - 0xE000) + 4*(0x110000-0x100000)) ); sU8[u8Count] = 0; std::string fullUnicode; SXMPUtils::RemoveProperties ( &meta, "", "", kXMPUI_DoAllProperties ); meta.SetProperty ( kNS1, "FullUnicode", XMP_StringPtr(sU8) ); meta.GetProperty ( kNS1, "FullUnicode", &fullUnicode, 0 ); if ( (fullUnicode.size() != u8Count) || (fullUnicode != XMP_StringPtr(sU8)) ) { fprintf ( log, "#ERROR: Failed to set full UTF-8 value\n" ); if ( (fullUnicode.size() != u8Count) ) { fprintf ( log, " Size mismatch, want %d, got %d\n", u8Count, fullUnicode.size() ); } else { for ( size_t b = 0; b < u8Count; ++b ) { if ( fullUnicode[b] != sU8[b] ) fprintf ( log, " Byte mismatch at %d\n", b ); } } } u8Packet.clear(); u16bPacket.clear(); u16lPacket.clear(); u32bPacket.clear(); u32lPacket.clear(); meta.SerializeToBuffer ( &u8Packet, (kXMP_OmitPacketWrapper | kXMP_EncodeUTF8) ); meta.SerializeToBuffer ( &u16bPacket, (kXMP_OmitPacketWrapper | kXMP_EncodeUTF16Big) ); meta.SerializeToBuffer ( &u16lPacket, (kXMP_OmitPacketWrapper | kXMP_EncodeUTF16Little) ); #if IncludeUTF32 meta.SerializeToBuffer ( &u32bPacket, (kXMP_OmitPacketWrapper | kXMP_EncodeUTF32Big) ); meta.SerializeToBuffer ( &u32lPacket, (kXMP_OmitPacketWrapper | kXMP_EncodeUTF32Little) ); #endif // --------------------------------------------------------------------- // Parse the whole packet as a sanity check, then at a variety of sizes. FullUnicodeParse ( log, "UTF-8", u8Packet.size(), u8Packet, fullUnicode ); FullUnicodeParse ( log, "UTF-16BE", u16bPacket.size(), u16bPacket, fullUnicode ); FullUnicodeParse ( log, "UTF-16LE", u16lPacket.size(), u16lPacket, fullUnicode ); #if IncludeUTF32 FullUnicodeParse ( log, "UTF-32BE", u32bPacket.size(), u32bPacket, fullUnicode ); FullUnicodeParse ( log, "UTF-32LE", u32lPacket.size(), u32lPacket, fullUnicode ); #endif fprintf ( log, "Full packet, no BOM, buffered parsing tests done\n" ); #if 0 // Skip the partial buffer tests, there seem to be problems, but no client uses partial buffers. for ( i = 1; i <= 3; ++i ) { FullUnicodeParse ( log, "UTF-8", i, u8Packet, fullUnicode ); FullUnicodeParse ( log, "UTF-16BE", i, u16bPacket, fullUnicode ); FullUnicodeParse ( log, "UTF-16LE", i, u16lPacket, fullUnicode ); #if IncludeUTF32 FullUnicodeParse ( log, "UTF-32BE", i, u32bPacket, fullUnicode ); FullUnicodeParse ( log, "UTF-32LE", i, u32lPacket, fullUnicode ); #endif fprintf ( log, "%d byte buffers, no BOM, buffered parsing tests done\n", i ); } for ( i = 4; i <= 16; i *= 2 ) { FullUnicodeParse ( log, "UTF-8", i, u8Packet, fullUnicode ); FullUnicodeParse ( log, "UTF-16BE", i, u16bPacket, fullUnicode ); FullUnicodeParse ( log, "UTF-16LE", i, u16lPacket, fullUnicode ); #if IncludeUTF32 FullUnicodeParse ( log, "UTF-32BE", i, u32bPacket, fullUnicode ); FullUnicodeParse ( log, "UTF-32LE", i, u32lPacket, fullUnicode ); #endif fprintf ( log, "%d byte buffers, no BOM, buffered parsing tests done\n", i ); } #endif fprintf ( log, "\n" ); // ----------------------------------------------------------------------- // Redo the buffered parsing tests, now with a leading BOM in the packets. u8Packet.insert ( 0, "\xEF\xBB\xBF", 3 ); UTF32Unit NatBOM = 0x0000FEFF; UTF32Unit SwapBOM = 0xFFFE0000; if ( kBigEndianHost ) { u16bPacket.insert ( 0, XMP_StringPtr(&NatBOM)+2, 2 ); u16lPacket.insert ( 0, XMP_StringPtr(&SwapBOM), 2 ); u32bPacket.insert ( 0, XMP_StringPtr(&NatBOM), 4 ); u32lPacket.insert ( 0, XMP_StringPtr(&SwapBOM), 4 ); } else { u16lPacket.insert ( 0, XMP_StringPtr(&NatBOM), 2 ); u16bPacket.insert ( 0, XMP_StringPtr(&SwapBOM)+2, 2 ); u32lPacket.insert ( 0, XMP_StringPtr(&NatBOM), 4 ); u32bPacket.insert ( 0, XMP_StringPtr(&SwapBOM), 4 ); } FullUnicodeParse ( log, "UTF-8", u8Packet.size(), u8Packet, fullUnicode ); FullUnicodeParse ( log, "UTF-16BE", u16bPacket.size(), u16bPacket, fullUnicode ); FullUnicodeParse ( log, "UTF-16LE", u16lPacket.size(), u16lPacket, fullUnicode ); #if IncludeUTF32 FullUnicodeParse ( log, "UTF-32BE", u32bPacket.size(), u32bPacket, fullUnicode ); FullUnicodeParse ( log, "UTF-32LE", u32lPacket.size(), u32lPacket, fullUnicode ); #endif fprintf ( log, "Full packet, leading BOM, buffered parsing tests done\n" ); #if 0 // Skip the partial buffer tests, there seem to be problems, but no client uses partial buffers. for ( i = 1; i <= 3; ++i ) { FullUnicodeParse ( log, "UTF-8", i, u8Packet, fullUnicode ); FullUnicodeParse ( log, "UTF-16BE", i, u16bPacket, fullUnicode ); FullUnicodeParse ( log, "UTF-16LE", i, u16lPacket, fullUnicode ); #if IncludeUTF32 FullUnicodeParse ( log, "UTF-32BE", i, u32bPacket, fullUnicode ); FullUnicodeParse ( log, "UTF-32LE", i, u32lPacket, fullUnicode ); #endif fprintf ( log, "%d byte buffers, leading BOM, buffered parsing tests done\n", i ); } for ( i = 4; i <= 16; i *= 2 ) { FullUnicodeParse ( log, "UTF-8", i, u8Packet, fullUnicode ); FullUnicodeParse ( log, "UTF-16BE", i, u16bPacket, fullUnicode ); FullUnicodeParse ( log, "UTF-16LE", i, u16lPacket, fullUnicode ); #if IncludeUTF32 FullUnicodeParse ( log, "UTF-32BE", i, u32bPacket, fullUnicode ); FullUnicodeParse ( log, "UTF-32LE", i, u32lPacket, fullUnicode ); #endif fprintf ( log, "%d byte buffers, leading BOM, buffered parsing tests done\n", i ); } #endif fprintf ( log, "\n" ); } // DoTest
static bool CreatorAtom_Update ( SXMPMeta& xmpObj, UserData& movieUserData ) { // Get Creator Atom XMP values. bool found = false; std::string posixPathString, uncPathString; if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "posixProjectPath", &posixPathString, 0 ) ) found = true; if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "uncProjectPath", &uncPathString, 0 ) ) found = true; std::string applicationCodeString, invocationAppleEventString, extensionString, invocationFlagsString, creatorToolString; if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", &applicationCodeString, 0 ) ) found = true; if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", &invocationAppleEventString, 0 ) ) found = true; if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", &extensionString, 0 ) ) found = true; if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", &invocationFlagsString, 0 ) ) found = true; if ( xmpObj.GetProperty ( kXMP_NS_XMP, "CreatorTool", &creatorToolString, 0 ) ) found = true; // If no Creator Atom information found, don't write anything. if ( ! found ) return false; // Read Legacy Creator Atom. unsigned long creatorAtomSize = 0; CR8R_CreatorAtom creatorAtomLegacy; CreatorAtom_Initialize ( creatorAtomLegacy ); bool ok = Mov_ReadCreatorAtom ( movieUserData, &creatorAtomLegacy ); // Generate new Creator Atom from XMP. CR8R_CreatorAtom creatorAtomViaXMP; CreatorAtom_Initialize ( creatorAtomViaXMP ); if ( ! applicationCodeString.empty() ) { creatorAtomViaXMP.creator_codeLu = strtoul ( applicationCodeString.c_str(), 0, 0 ); } if ( ! invocationAppleEventString.empty() ) { creatorAtomViaXMP.creator_eventLu = strtoul ( invocationAppleEventString.c_str(), 0, 0 ); } if ( ! extensionString.empty() ) { strncpy ( creatorAtomViaXMP.creator_extAC, extensionString.c_str(), sizeof(creatorAtomViaXMP.creator_extAC) ); EnsureFinalNul ( creatorAtomViaXMP.creator_extAC ); } if ( ! invocationFlagsString.empty() ) { strncpy ( creatorAtomViaXMP.creator_flagAC, invocationFlagsString.c_str(), sizeof(creatorAtomViaXMP.creator_flagAC) ); EnsureFinalNul ( creatorAtomViaXMP.creator_flagAC ); } if ( ! creatorToolString.empty() ) { strncpy ( creatorAtomViaXMP.creator_nameAC, creatorToolString.c_str(), sizeof(creatorAtomViaXMP.creator_nameAC) ); EnsureFinalNul ( creatorAtomViaXMP.creator_nameAC ); } // Write Creator Atom. if ( ok ) { // If there's legacy, update if atom generated from XMP doesn't match legacy. if ( memcmp ( &creatorAtomViaXMP, &creatorAtomLegacy, sizeof(CR8R_CreatorAtom) ) != 0 ) { ok = Mov_WriteCreatorAtom ( movieUserData, creatorAtomViaXMP, true ); } } else { // Write completely new atom from XMP. ok = Mov_WriteCreatorAtom ( movieUserData, creatorAtomViaXMP, false ); } return ok; }
bool CreatorAtom::Update ( SXMPMeta& xmpObj, LFA_FileRef fileRef, long riffType, RIFF_Support::RiffState& riffState ) { // Creator Atom related values. bool found = false; std::string posixPathString, uncPathString; if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "posixProjectPath", &posixPathString, 0 ) ) found = true; if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "uncProjectPath", &uncPathString, 0 ) ) found = true; std::string applicationCodeString, invocationAppleEventString, extensionString, invocationFlagsString, creatorToolString; if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", &applicationCodeString, 0 ) ) found = true; if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", &invocationAppleEventString, 0 ) ) found = true; if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", &extensionString, 0 ) ) found = true; if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", &invocationFlagsString, 0 ) ) found = true; if ( xmpObj.GetProperty ( kXMP_NS_XMP, "CreatorTool", &creatorToolString, 0 ) ) found = true; // No Creator Atom information present. if ( ! found ) return true; // Read Legacy Creator Atom. unsigned long creatorAtomSize = 0; CR8R_CreatorAtom creatorAtomLegacy; CreatorAtom_Initialize ( creatorAtomLegacy ); bool ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, myCreatorAtom, 0, 0, 0, &creatorAtomSize ); if ( ok ) { XMP_Assert ( creatorAtomSize == sizeof(CR8R_CreatorAtom) ); ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, myCreatorAtom, 0, 0, (char*) &creatorAtomLegacy, &creatorAtomSize ); CreatorAtom_MakeValid ( &creatorAtomLegacy ); } // Generate new Creator Atom from XMP. CR8R_CreatorAtom creatorAtomViaXMP; CreatorAtom_Initialize ( creatorAtomViaXMP ); if ( ! applicationCodeString.empty() ) { creatorAtomViaXMP.creator_codeLu = strtoul ( applicationCodeString.c_str(), 0, 0 ); } if ( ! invocationAppleEventString.empty() ) { creatorAtomViaXMP.creator_eventLu = strtoul ( invocationAppleEventString.c_str(), 0, 0 ); } if ( ! extensionString.empty() ) { strncpy ( creatorAtomViaXMP.creator_extAC, extensionString.c_str(), sizeof(creatorAtomViaXMP.creator_extAC) ); EnsureFinalNul ( creatorAtomViaXMP.creator_extAC ); } if ( ! invocationFlagsString.empty() ) { strncpy ( creatorAtomViaXMP.creator_flagAC, invocationFlagsString.c_str(), sizeof(creatorAtomViaXMP.creator_flagAC) ); EnsureFinalNul ( creatorAtomViaXMP.creator_flagAC ); } if ( ! creatorToolString.empty() ) { strncpy ( creatorAtomViaXMP.creator_nameAC, creatorToolString.c_str(), sizeof(creatorAtomViaXMP.creator_nameAC) ); EnsureFinalNul ( creatorAtomViaXMP.creator_nameAC ); } // Write new Creator Atom, if necessary. if ( memcmp ( &creatorAtomViaXMP, &creatorAtomLegacy, sizeof(CR8R_CreatorAtom) ) != 0 ) { CreatorAtom_ToBE ( &creatorAtomViaXMP ); ok = RIFF_Support::PutChunk ( fileRef, riffState, riffType, myCreatorAtom, (char*)&creatorAtomViaXMP, sizeof(CR8R_CreatorAtom) ); } return ok; }
static void exportXMPtoListChunk( XMP_Uns32 id, XMP_Uns32 containerType, RIFF_MetaHandler* handler, ContainerChunk** listChunk, Mapping mapping[]) { // note: ContainerChunk**: adress of pointer to allow changing the pointer itself (i.e. chunk creation) SXMPMeta* xmp = &handler->xmpObj; bool listChunkIsNeeded = false; // assume for now // ! The NUL is optional in WAV to avoid a parsing bug in Audition 3 - can't handle implicit pad byte. bool optionalNUL = (handler->parent->format == kXMP_WAVFile); for ( int p=0; mapping[p].chunkID != 0; ++p ) { // go through all potential property mappings bool propExists = false; std::string value, actualLang; switch ( mapping[p].propType ) { // get property. if existing, remove from XMP (to avoid redundant storage) case prop_TIMEVALUE: propExists = xmp->GetStructField ( mapping[p].ns, mapping[p].prop, kXMP_NS_DM, "timeValue", &value, 0 ); break; case prop_LOCALIZED_TEXT: propExists = xmp->GetLocalizedText ( mapping[p].ns, mapping[p].prop, "", "x-default", &actualLang, &value, 0); if ( actualLang != "x-default" ) propExists = false; // no "x-default" => nothing to reconcile ! break; case prop_ARRAYITEM: propExists = xmp->GetArrayItem ( mapping[p].ns, mapping[p].prop, 1, &value, 0 ); break; case prop_SIMPLE: propExists = xmp->GetProperty ( mapping[p].ns, mapping[p].prop, &value, 0 ); break; default: XMP_Throw ( "internal error", kXMPErr_InternalFailure ); } if ( ! propExists ) { if ( *listChunk != 0 ) (*listChunk)->removeValue ( mapping[p].chunkID ); } else { listChunkIsNeeded = true; if ( *listChunk == 0 ) *listChunk = new ContainerChunk ( handler->riffChunks[0], id, containerType ); valueMap* cm = &(*listChunk)->childmap; valueMapIter result = cm->find( mapping[p].chunkID ); ValueChunk* propChunk = 0; if ( result != cm->end() ) { propChunk = result->second; } else { propChunk = new ValueChunk ( *listChunk, std::string(), mapping[p].chunkID ); } propChunk->SetValue ( value.c_str(), optionalNUL ); } } // for each property if ( (! listChunkIsNeeded) && (*listChunk != 0) && ((*listChunk)->children.size() == 0) ) { (*listChunk)->parent->replaceChildWithJunk ( *listChunk ); (*listChunk) = 0; // reset direct Chunk pointer } }
// add bwf-bext related data to bext chunk, create if not existing yet. // * in fact, since bext is fully fixed and known, there can be no unknown subchunks worth keeping: // * prepare bext chunk in buffer // * value changed/created if needed only, otherways remove chunk // * remove bext-mapped properties from xmp (non-redundant storage) // note: ValueChunk**: adress of pointer to allow changing the pointer itself (i.e. chunk creation) static void exportXMPtoBextChunk( RIFF_MetaHandler* handler, ValueChunk** bextChunk ) { // register bext namespace ( if there was no import, this is news, otherwise harmless moot) SXMPMeta::RegisterNamespace( kXMP_NS_BWF, "bext:", 0 ); bool chunkUsed = false; // assume for now SXMPMeta* xmp = &handler->xmpObj; // prepare buffer, need to know CodingHistory size as the only variable XMP_Int32 bextBufferSize = MIN_BEXT_SIZE - 8; // -8 because of header std::string value; if ( xmp->GetProperty( bextCodingHistory.ns, bextCodingHistory.prop, &value, kXMP_NoOptions )) { bextBufferSize += ((XMP_StringLen)value.size()) + 1 ; // add to size (and a trailing zero) } // create and clear buffer XMP_Uns8* buffer = new XMP_Uns8[bextBufferSize]; for (XMP_Int32 i = 0; i < bextBufferSize; i++ ) buffer[i] = 0; // grab props, write into buffer, remove from XMP /////////////////////////// // bextDescription ------------------------------------------------ if ( xmp->GetProperty( bextDescription.ns, bextDescription.prop, &value, kXMP_NoOptions ) ) { setBextField( &value, (XMP_Uns8*) buffer, 0, 256 ); xmp->DeleteProperty( bextDescription.ns, bextDescription.prop) ; chunkUsed = true; } // bextOriginator ------------------------------------------------- if ( xmp->GetProperty( bextOriginator.ns , bextOriginator.prop, &value, kXMP_NoOptions ) ) { setBextField( &value, (XMP_Uns8*) buffer, 256, 32 ); xmp->DeleteProperty( bextOriginator.ns , bextOriginator.prop ); chunkUsed = true; } // bextOriginatorRef ---------------------------------------------- if ( xmp->GetProperty( bextOriginatorRef.ns , bextOriginatorRef.prop, &value, kXMP_NoOptions ) ) { setBextField( &value, (XMP_Uns8*) buffer, 256+32, 32 ); xmp->DeleteProperty( bextOriginatorRef.ns , bextOriginatorRef.prop ); chunkUsed = true; } // bextOriginationDate -------------------------------------------- if ( xmp->GetProperty( bextOriginationDate.ns , bextOriginationDate.prop, &value, kXMP_NoOptions ) ) { setBextField( &value, (XMP_Uns8*) buffer, 256+32+32, 10 ); xmp->DeleteProperty( bextOriginationDate.ns , bextOriginationDate.prop ); chunkUsed = true; } // bextOriginationTime -------------------------------------------- if ( xmp->GetProperty( bextOriginationTime.ns , bextOriginationTime.prop, &value, kXMP_NoOptions ) ) { setBextField( &value, (XMP_Uns8*) buffer, 256+32+32+10, 8 ); xmp->DeleteProperty( bextOriginationTime.ns , bextOriginationTime.prop ); chunkUsed = true; } // bextTimeReference ---------------------------------------------- // thanx to friendly byte order, all 8 bytes can be written in one go: if ( xmp->GetProperty( bextTimeReference.ns, bextTimeReference.prop, &value, kXMP_NoOptions ) ) { try { XMP_Int64 v = SXMPUtils::ConvertToInt64( value.c_str() ); PutUns64LE( v, &(buffer[256+32+32+10+8] )); chunkUsed = true; } catch (XMP_Error& e) { if ( e.GetID() != kXMPErr_BadParam ) throw e; // re-throw on any other error } // 'else' tolerate ( time reference remains 0x00000000 ) // valid or not, do not store redundantly: xmp->DeleteProperty( bextTimeReference.ns, bextTimeReference.prop ); } // bextVersion ---------------------------------------------------- // set version=1, no matter what. PutUns16LE( 1, &(buffer[256+32+32+10+8+8]) ); xmp->DeleteProperty( bextVersion.ns, bextVersion.prop ); // bextUMID ------------------------------------------------------- if ( xmp->GetProperty( bextUMID.ns, bextUMID.prop, &value, kXMP_NoOptions ) ) { std::string rawStr; if ( !DecodeFromHexString( value.data(), (XMP_StringLen) value.size(), &rawStr ) ) { delete [] buffer; // important. XMP_Throw ( "EncodeFromHexString: illegal umid string. Must contain an even number of 0-9 and uppercase A-F chars.", kXMPErr_BadParam ); } // if UMID is smaller/longer than 64 byte for any reason, // truncate/do a partial write (just like for any other bext property) memcpy( (char*) &(buffer[256+32+32+10+8+8+2]), rawStr.data(), MIN( 64, rawStr.size() ) ); xmp->DeleteProperty( bextUMID.ns, bextUMID.prop ); chunkUsed = true; } // bextCodingHistory ---------------------------------------------- if ( xmp->GetProperty( bextCodingHistory.ns, bextCodingHistory.prop, &value, kXMP_NoOptions ) ) { std::string ascii; convertToASCII( value.data(), (XMP_StringLen) value.size() , &ascii, (XMP_StringLen) value.size() ); strncpy( (char*) &(buffer[MIN_BEXT_SIZE-8]), ascii.data(), ascii.size() ); xmp->DeleteProperty( bextCodingHistory.ns, bextCodingHistory.prop ); chunkUsed = true; } // always delete old, recreate if needed if ( *bextChunk != 0 ) { (*bextChunk)->parent->replaceChildWithJunk( *bextChunk ); (*bextChunk) = 0; // clear direct Chunk pointer } if ( chunkUsed) *bextChunk = new ValueChunk( handler->riffChunks.at(0), std::string( (char*)buffer, bextBufferSize ), kChunk_bext ); delete [] buffer; // important. }