1 // =================================================================================================
2 // ADOBE SYSTEMS INCORPORATED
3 // Copyright 2010 Adobe Systems Incorporated
4 // All Rights Reserved
5 //
6 // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
7 // of the Adobe license agreement accompanying it.
8 // =================================================================================================
9
10 #include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
11 #include "public/include/XMP_Const.h"
12
13 #include <string.h>
14
15 #include "XMPFiles/source/FormatSupport/WAVE/WAVEReconcile.h"
16 #include "XMPFiles/source/FormatSupport/WAVE/DISPMetadata.h"
17 #include "XMPFiles/source/FormatSupport/WAVE/INFOMetadata.h"
18 #include "XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.h"
19 #include "XMPFiles/source/FormatSupport/WAVE/CartMetadata.h"
20 #include "XMPFiles/source/FormatSupport/WAVE/iXMLMetadata.h"
21
22 #include "XMPFiles/source/FormatSupport/TimeConversionUtils.hpp"
23
24 // cr8r is not yet required for WAVE
25 //#include "Cr8rMetadata.h"
26
27 #include "XMPFiles/source/NativeMetadataSupport/MetadataSet.h"
28
29 #include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp"
30
31
32 using namespace IFF_RIFF;
33
34 // ************** legacy Mappings ***************** //
35
36 static const char * kBWF_description = "description";
37 static const char * kBWF_originator = "originator";
38 static const char * kBWF_originatorReference = "originatorReference";
39 static const char * kBWF_originationDate = "originationDate";
40 static const char * kBWF_originationTime = "originationTime";
41 static const char * kBWF_timeReference = "timeReference";
42 static const char * kBWF_version = "version";
43 static const char * kBWF_umid = "umid";
44 static const char * kBWF_codingHistory = "codingHistory";
45 static const char * kBWF_timeStampSampleRate = "timeSampleRate"; // its transient should be deleted at the end.
46
47 static const MetadataPropertyInfo kBextProperties[] =
48 {
49 // XMP NS XMP Property Name Native Metadata Identifier Native Datatype XMP Datatype Delete Priority ExportPolicy
50 { kXMP_NS_BWF, kBWF_description, BEXTMetadata::kDescription, kNativeType_StrLocal, kXMPType_Simple, false, false, kExport_Always }, // bext:description <-> BEXT:Description
51 { kXMP_NS_BWF, kBWF_originator, BEXTMetadata::kOriginator, kNativeType_StrLocal, kXMPType_Simple, false, false, kExport_Always }, // bext:originator <-> BEXT:originator
52 { kXMP_NS_BWF, kBWF_originatorReference, BEXTMetadata::kOriginatorReference, kNativeType_StrLocal, kXMPType_Simple, false, false, kExport_Always }, // bext:OriginatorReference <-> BEXT:OriginatorReference
53 { kXMP_NS_BWF, kBWF_originationDate, BEXTMetadata::kOriginationDate, kNativeType_StrLocal, kXMPType_Simple, false, false, kExport_Always }, // bext:originationDate <-> BEXT:originationDate
54 { kXMP_NS_BWF, kBWF_originationTime, BEXTMetadata::kOriginationTime, kNativeType_StrLocal, kXMPType_Simple, false, false, kExport_Always }, // bext:originationTime <-> BEXT:originationTime
55 { kXMP_NS_BWF, kBWF_timeReference, BEXTMetadata::kTimeReference, kNativeType_Uns64, kXMPType_Simple, false, false, kExport_Always }, // bext:timeReference <-> BEXT:TimeReferenceLow + BEXT:TimeReferenceHigh
56 // Special case: On export BEXT:version is always written as 1
57 { kXMP_NS_BWF, kBWF_version, BEXTMetadata::kVersion, kNativeType_Uns16, kXMPType_Simple, false, false, kExport_Never }, // bext:version <-> BEXT:version
58 // special case: bext:umid <-> BEXT:UMID
59 { kXMP_NS_BWF, kBWF_codingHistory, BEXTMetadata::kCodingHistory, kNativeType_StrLocal, kXMPType_Simple, false, false, kExport_Always }, // bext:codingHistory <-> BEXT:codingHistory
60 { NULL }
61 };
62
63 static const char * kDM_shotNumber = "shotNumber";
64 static const char * kDM_audioSampleType = "audioSampleType";
65 static const char * kDM_scene = "scene";
66 static const char * kDM_tapeName = "tapeName";
67 static const char * kDM_logComment = "logComment";
68 static const char * kDM_projectName = "projectName";
69 static const char * kDM_audioSampleRate = "audioSampleRate";
70 static const char * kDM_startTimecode = "startTimecode";
71 static const char * kDM_timeFormat = "timeFormat";
72 static const char * kDM_timeValue = "timeValue";
73 static const char * kDM_good = "good";
74 static const char * kIXML_trackList = "trackList";
75 static const char * kIXML_channelIndex = "channelIndex";
76 /*static const char * kIXML_interleaveIndex = "interleaveIndex";*/
77 static const char * kIXML_Name = "name";
78 static const char * kIXML_Function = "function";
79
80 static const MetadataPropertyInfo kiXMLProperties[] =
81 {
82 // XMP NS XMP Property Name Native Metadata Identifier Native Datatype XMP Datatype Delete Priority ExportPolicy
83 { kXMP_NS_DM, kDM_tapeName, iXMLMetadata::kTape, kNativeType_StrUTF8, kXMPType_Simple, false, false, kExport_Always }, //xmpDM:tapeName <-> iXML:TAPE
84 { kXMP_NS_DM, kDM_shotNumber, iXMLMetadata::kTake, kNativeType_StrUTF8, kXMPType_Simple, false, false, kExport_Always }, //xmpDM:shotNumber <-> iXML:TAKE
85 { kXMP_NS_DM, kDM_scene, iXMLMetadata::kScene, kNativeType_StrUTF8, kXMPType_Simple, false, false, kExport_Always }, //xmpDM:scene <-> iXML:SCENE
86 { kXMP_NS_DM, kDM_logComment, iXMLMetadata::kNote, kNativeType_StrUTF8, kXMPType_Simple, false, false, kExport_Always }, //xmpDM:logComment <-> iXML:NOTE
87 { kXMP_NS_DM, kDM_projectName, iXMLMetadata::kProject, kNativeType_StrUTF8, kXMPType_Simple, false, false, kExport_Always }, //xmpDM:project <-> iXML:PROJECT
88 { kXMP_NS_DM, kDM_good, iXMLMetadata::kCircled, kNativeType_Bool, kXMPType_Simple, false, false, kExport_Always }, //xmpDM:good <-> iXML:CIRCLED
89 { kXMP_NS_DM, kDM_audioSampleRate, iXMLMetadata::kFileSampleRate, kNativeType_Uns64, kXMPType_Simple, false, false, kExport_Always }, // xmpDM:audioSampleRate <-> iXML:FILE_SAMPLE_RATE
90 // special case for AudioBitDepth // xmpDM:audioSampleType <-> iXML:AUDIO_BIT_DEPTH
91 { kXMP_NS_BWF, kBWF_description, iXMLMetadata::kBWFDescription, kNativeType_StrUTF8, kXMPType_Simple, true, false, kExport_Always }, // bext:description <-> iXML:BWF_DESCRIPTION
92 { kXMP_NS_BWF, kBWF_originator, iXMLMetadata::kBWFOriginator, kNativeType_StrUTF8, kXMPType_Simple, true, false, kExport_Always }, // bext:originator <-> iXML:BWF_ORIGINATOR
93 { kXMP_NS_BWF, kBWF_originatorReference, iXMLMetadata::kBWFOriginatorReference, kNativeType_StrUTF8, kXMPType_Simple, true, false, kExport_Always }, // bext:OriginatorReference <-> iXML:BWF_ORIGINATOR_REFERENCE
94 { kXMP_NS_BWF, kBWF_originationDate, iXMLMetadata::kBWFOriginationDate, kNativeType_StrUTF8, kXMPType_Simple, true, false, kExport_Always }, // bext:originationDate <-> iXML:BWF_ORIGINATION_DATE
95 { kXMP_NS_BWF, kBWF_originationTime, iXMLMetadata::kBWFOriginationTime, kNativeType_StrUTF8, kXMPType_Simple, true, false, kExport_Always }, // bext:originationTime <-> iXML:BWF_ORIGINATION_TIME
96 // special case for timeReference // bext:timeReference <-> iXML:BWF_TIME_REFERENCE_LOW and iXML:BWF_TIME_REFERENCE_HIGH
97 // Special case: On export BEXT:version is always written as 1
98 { kXMP_NS_BWF, kBWF_version, iXMLMetadata::kBWFVersion, kNativeType_Uns64, kXMPType_Simple, true, false, kExport_Never }, // bext:version <-> iXML:BWF_VERSION
99 { kXMP_NS_BWF, kBWF_codingHistory, iXMLMetadata::kBWFHistory, kNativeType_StrUTF8, kXMPType_Simple, true, false, kExport_Always }, // bext:codingHistory <-> iXML:BWF_CODING_HISTORY
100 { kXMP_NS_BWF, kBWF_umid, iXMLMetadata::kBWFUMID, kNativeType_StrASCII, kXMPType_Simple, true, false, kExport_Always }, // bext:codingHistory <-> iXML:BWF_CODING_HISTORY
101 // special case for timeReference // bext:timeReference <-> iXML:TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_HO and iXML:TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_HI
102 // special case for startTimeCode // xmpDM:startTimecode <-> iXML:TIMECODE_RATE, iXML:TIMECODE_FLAG and bext:timeReference.
103 { kXMP_NS_BWF, kBWF_timeStampSampleRate, iXMLMetadata::kTimeStampSampleRate, kNativeType_Uns64, kXMPType_Simple, false, false, kExport_NoDelete }, // bext::timeStampSampleRate <-> iXML
104 // special case for TRACK_LIST // ixml:Track_List <-> ixml:trackList
105 { NULL }
106 };
107
108 static const MetadataPropertyInfo kINFOProperties[] =
109 {
110 // XMP NS XMP Property Name Native Metadata Identifier Native Datatype XMP Datatype Delete Priority ExportPolicy
111 { kXMP_NS_DM, "artist", INFOMetadata::kArtist, kNativeType_StrUTF8, kXMPType_Simple, false, true, kExport_Always }, // xmpDM:artist <-> IART
112 { kXMP_NS_DM, "logComment", INFOMetadata::kComments, kNativeType_StrUTF8, kXMPType_Simple, false, true, kExport_Always }, // xmpDM:logComment <-> ICMT
113 { kXMP_NS_DC, "rights", INFOMetadata::kCopyright, kNativeType_StrUTF8, kXMPType_Localized, false, true, kExport_Always }, // dc:rights <-> ICOP
114 { kXMP_NS_XMP, "CreateDate", INFOMetadata::kCreationDate, kNativeType_StrUTF8, kXMPType_Simple, false, true, kExport_Always }, // xmp:CreateDate <-> ICRD
115 { kXMP_NS_DM, "engineer", INFOMetadata::kEngineer, kNativeType_StrUTF8, kXMPType_Simple, false, true, kExport_Always }, // xmpDM:engineer <-> IENG
116 { kXMP_NS_DM, "genre", INFOMetadata::kGenre, kNativeType_StrUTF8, kXMPType_Simple, false, true, kExport_Always }, // xmpDM:genre <-> IGNR
117 { kXMP_NS_XMP, "CreatorTool", INFOMetadata::kSoftware, kNativeType_StrUTF8, kXMPType_Simple, false, true, kExport_Always }, // xmp:CreatorTool <-> ISFT
118 { kXMP_NS_DC, "source", INFOMetadata::kMedium, kNativeType_StrUTF8, kXMPType_Simple, false, false, kExport_Always }, // dc:source <-> IMED, not in old digest
119 { kXMP_NS_DC, "type", INFOMetadata::kSourceForm, kNativeType_StrUTF8, kXMPType_Array, false, false, kExport_Always }, // dc:type <-> ISRF, not in old digest
120
121 // new mappings
122 { kXMP_NS_RIFFINFO, "name", INFOMetadata::kName, kNativeType_StrUTF8, kXMPType_Localized, true, false, kExport_Always }, // riffinfo:name <-> INAM
123 { kXMP_NS_RIFFINFO, "archivalLocation", INFOMetadata::kArchivalLocation,kNativeType_StrUTF8, kXMPType_Simple, true, false, kExport_Always }, // riffinfo:archivalLocation <-> IARL
124 { kXMP_NS_RIFFINFO, "commissioned", INFOMetadata::kCommissioned, kNativeType_StrUTF8, kXMPType_Simple, true, false, kExport_Always }, // riffinfo:commissioned <-> ICMS
125 // special case, the native value is a semicolon-separated array
126 // { kXMP_NS_DC, "subject", INFOMetadata::kKeywords, kNativeType_StrUTF8, kXMPType_Array, false, false, kExport_Always }, // dc:subject <-> IKEY
127 { kXMP_NS_RIFFINFO, "product", INFOMetadata::kProduct, kNativeType_StrUTF8, kXMPType_Simple, true, false, kExport_Always }, // riffinfo:product <-> IPRD
128 { kXMP_NS_DC, "description", INFOMetadata::kSubject, kNativeType_StrUTF8, kXMPType_Localized, false, false, kExport_Always }, // dc:description <-> ISBJ
129 { kXMP_NS_RIFFINFO, "source", INFOMetadata::kSource, kNativeType_StrUTF8, kXMPType_Simple, true, false, kExport_Always }, // riffinfo:source <-> ISRC
130 { kXMP_NS_RIFFINFO, "technician", INFOMetadata::kTechnican, kNativeType_StrUTF8, kXMPType_Simple, true, false, kExport_Always }, // riffinfo:technician <-> ITCH
131
132 { NULL }
133 };
134
135 static const MetadataPropertyInfo kDISPProperties[] =
136 {
137 // XMP NS XMP Property Name Native Metadata Identifier Datatype Datatype Delete Priority ExportPolicy
138 { kXMP_NS_DC, "title", DISPMetadata::kTitle, kNativeType_StrUTF8, kXMPType_Localized, false, true, kExport_Always }, // dc:title <-> DISP
139 // Special case: DISP will overwrite LIST/INFO:INAM in dc:title if existing
140 { NULL }
141 };
142
143 static const MetadataPropertyInfo kCartProperties[] =
144 {
145 // XMP NS XMP Property Name Native Metadata Identifier Datatype Datatype Delete Priority ExportPolicy
146 { kXMP_NS_AEScart, "Version", CartMetadata::kVersion, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
147 { kXMP_NS_AEScart, "Title", CartMetadata::kTitle, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
148 { kXMP_NS_AEScart, "Artist", CartMetadata::kArtist, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
149 { kXMP_NS_AEScart, "CutID", CartMetadata::kCutID, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
150 { kXMP_NS_AEScart, "ClientID", CartMetadata::kClientID, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
151 { kXMP_NS_AEScart, "Category", CartMetadata::kCategory, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
152 { kXMP_NS_AEScart, "Classification", CartMetadata::kClassification, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
153 { kXMP_NS_AEScart, "OutCue", CartMetadata::kOutCue, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
154 { kXMP_NS_AEScart, "StartDate", CartMetadata::kStartDate, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
155 { kXMP_NS_AEScart, "StartTime", CartMetadata::kStartTime, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
156 { kXMP_NS_AEScart, "EndDate", CartMetadata::kEndDate, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
157 { kXMP_NS_AEScart, "EndTime", CartMetadata::kEndTime, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
158 { kXMP_NS_AEScart, "ProducerAppID", CartMetadata::kProducerAppID, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
159 { kXMP_NS_AEScart, "ProducerAppVersion", CartMetadata::kProducerAppVersion, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
160 { kXMP_NS_AEScart, "UserDef", CartMetadata::kUserDef, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
161 { kXMP_NS_AEScart, "URL", CartMetadata::kURL, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
162 { kXMP_NS_AEScart, "TagText", CartMetadata::kTagText, kNativeType_StrLocal, kXMPType_Simple, true, false, kExport_Always },
163 { kXMP_NS_AEScart, "LevelReference", CartMetadata::kLevelReference, kNativeType_Int32, kXMPType_Simple, true, false, kExport_Always },
164 // Special case Post Timer
165 { NULL }
166 };
167
168 // cr8r is not yet required for WAVE
169 //
170 //static const MetadataPropertyInfo kCr8rProperties[] =
171 //{
172 //// XMP NS XMP Property Name Native Metadata Identifier Native Datatype XMP Datatype Delete Priority ExportPolicy
173 // { kXMP_NS_CreatorAtom, "macAtom/creatorAtom:applicationCode", Cr8rMetadata::kCreatorCode, kNativeType_Uns32, kXMPType_Simple, false, false, kExport_Always }, // creatorAtom:macAtom/creatorAtom:applicationCode <-> Cr8r.creatorCode
174 // { kXMP_NS_CreatorAtom, "macAtom/creatorAtom:invocationAppleEvent", Cr8rMetadata::kAppleEvent, kNativeType_Uns32, kXMPType_Simple, false, false, kExport_Always }, // creatorAtom:macAtom/creatorAtom:invocationAppleEvent <-> Cr8r.appleEvent
175 // { kXMP_NS_CreatorAtom, "windowsAtom/creatorAtom:extension", Cr8rMetadata::kFileExt, kNativeType_Str, kXMPType_Simple, false, false, kExport_Always }, // creatorAtom:windowsAtom/creatorAtom:extension <-> Cr8r.fileExt
176 // { kXMP_NS_CreatorAtom, "windowsAtom/creatorAtom:invocationFlags", Cr8rMetadata::kAppOptions, kNativeType_Str, kXMPType_Simple, false, false, kExport_Always }, // creatorAtom:windowsAtom/creatorAtom:invocationFlags <-> Cr8r.appOptions
177 // { kXMP_NS_XMP, "CreatorTool", Cr8rMetadata::kAppName, kNativeType_Str, kXMPType_Simple, false, false, kExport_Always }, // xmp:CreatorTool <-> Cr8r.appName
178 // { NULL }
179 //};
180
181 // ! PrmL atom has all special mappings
182
183 // ************** legacy Mappings end ***************** //
184
importToXMP(SXMPMeta & outXMP,const MetadataSet & inMetaData)185 XMP_Bool WAVEReconcile::importToXMP( SXMPMeta& outXMP, const MetadataSet& inMetaData )
186 {
187 bool changed = false;
188
189 // the reconciliation is based on the existing outXMP packet
190
191 //
192 // ! The existence of a digest leads to prefering pre-existing XMP over legacy properties.
193 //
194 bool hasDigest = outXMP.GetProperty( kXMP_NS_WAV, "NativeDigest", NULL , NULL );
195
196 if ( hasDigest )
197 {
198 // remove, as digests are no longer used.
199 outXMP.DeleteProperty( kXMP_NS_WAV, "NativeDigest" );
200 }
201
202
203 if ( ! ignoreLocalText )
204 {
205 //
206 // Import iXML
207 //
208 iXMLMetadata * iXMLMeta = inMetaData.get<iXMLMetadata>();
209 if ( iXMLMeta != NULL ) {
210 changed |= IReconcile::importNativeToXMP( outXMP, *iXMLMeta, kiXMLProperties, false );
211 changed |= exportSpecialiXMLToXMP( *iXMLMeta, outXMP );
212 }
213
214 //
215 // Import BEXT
216 //
217 BEXTMetadata *bextMeta = inMetaData.get<BEXTMetadata>();
218
219 if (bextMeta != NULL)
220 {
221 changed |= IReconcile::importNativeToXMP( outXMP, *bextMeta, kBextProperties, false );
222
223 // bext:umid <-> BEXT:UMID
224 if ( bextMeta->valueExists( BEXTMetadata::kUMID ) )
225 {
226 XMP_Uns32 umidSize = 0;
227 const XMP_Uns8* const umid = bextMeta->getArray<XMP_Uns8>( BEXTMetadata::kUMID, umidSize );
228
229 std::string xmpValue;
230 bool allZero = encodeToHexString( umid, xmpValue );
231
232 if( ! allZero )
233 {
234 outXMP.SetProperty(kXMP_NS_BWF, "umid", xmpValue.c_str());
235 changed = true;
236 }
237 }
238
239 }
240
241
242 //
243 // Import cart
244 //
245 CartMetadata* cartData = inMetaData.get<CartMetadata>();
246 if ( cartData != NULL )
247 {
248
249 if (cartData->valueExists( CartMetadata::kPostTimer ) )
250 {
251 // first get Array
252 XMP_Uns32 size = 0;
253 const CartMetadata::StoredCartTimer* timerArray = cartData->getArray<CartMetadata::StoredCartTimer> ( CartMetadata::kPostTimer, size );
254 XMP_Assert (size == CartMetadata::kPostTimerLength );
255
256 char usage [5];
257 XMP_Uns32 usageBE = 0;
258 char value [25]; // Unsigned has 10 dezimal digets (buffer has 25 just to be save)
259 std::string path = "";
260 memset ( usage, 0, 5 ); // Fill with zeros
261 memset ( value, 0, 25 ); // Fill with zeros
262
263 outXMP.DeleteProperty( kXMP_NS_AEScart, "PostTimer");
264 outXMP.AppendArrayItem( kXMP_NS_AEScart, "PostTimer", kXMP_PropArrayIsOrdered, NULL, kXMP_PropValueIsStruct );
265
266 for ( XMP_Uns32 i = 0; i<CartMetadata::kPostTimerLength; i++ )
267 {
268 // Ensure to write as Big Endian
269 usageBE = MakeUns32BE(timerArray[i].usage);
270 memcpy( usage, &usageBE, 4 );
271
272 snprintf ( value, 24, "%u", timerArray[i].value);
273
274 SXMPUtils::ComposeArrayItemPath( kXMP_NS_AEScart, "PostTimer", i+1, &path);
275 outXMP.SetStructField( kXMP_NS_AEScart, path.c_str(), kXMP_NS_AEScart, "Usage", usage );
276 outXMP.SetStructField( kXMP_NS_AEScart, path.c_str(), kXMP_NS_AEScart, "Value", value );
277 }
278
279 changed = true;
280 }
281
282 // import rest if cart properties
283 changed |= IReconcile::importNativeToXMP ( outXMP, *cartData, kCartProperties, false );
284 }
285
286 }
287
288 // cr8r is not yet required for WAVE
289
290 ////
291 //// import cr8r
292 //// Special case: If both Cr8r.AppName and LIST/INFO:ISFT are present, ISFT shall win.
293 //// therefor import Cr8r first.
294 ////
295 //Cr8rMetadata* cr8rMeta = inMetaData.get<Cr8rMetadata>();
296
297 //if( cr8rMeta != NULL )
298 //{
299 // changed |= IReconcile::importNativeToXMP( outXMP, *cr8rMeta, kCr8rProperties, false );
300 //}
301
302 //
303 // Import LIST/INFO
304 //
305 INFOMetadata *infoMeta = inMetaData.get<INFOMetadata>();
306 bool hasINAM = false;
307 std::string actualLang;
308 bool hasDCTitle = outXMP.GetLocalizedText( kXMP_NS_DC, "title", "" , "x-default" , &actualLang, NULL, NULL );
309
310 if (infoMeta != NULL)
311 {
312 //
313 // Remember if List/INFO:INAM has been imported
314 //
315 hasINAM = infoMeta->valueExists( INFOMetadata::kName );
316
317 // Keywords are a ;-separated list and is therefore handled manually,
318 // leveraging the XMPUtils functions
319 if (infoMeta->valueExists( INFOMetadata::kKeywords ) )
320 {
321 std::string keywordsUTF8;
322 outXMP.DeleteProperty( kXMP_NS_DC, "subject" );
323 ReconcileUtils::NativeToUTF8( infoMeta->getValue<std::string>( INFOMetadata::kKeywords ), keywordsUTF8 );
324 SXMPUtils::SeparateArrayItems( &outXMP, kXMP_NS_DC, "subject", kXMP_PropArrayIsUnordered, keywordsUTF8 );
325 changed = true;
326 }
327
328 //
329 // import properties
330 //
331 changed |= IReconcile::importNativeToXMP( outXMP, *infoMeta, kINFOProperties, hasDigest );
332 }
333
334 //
335 // Import DISP
336 // ! DISP will overwrite dc:title
337 //
338 bool hasDISP = false;
339
340 DISPMetadata *dispMeta = inMetaData.get<DISPMetadata>();
341
342 if( dispMeta != NULL && dispMeta->valueExists( DISPMetadata::kTitle) )
343 {
344 changed |= IReconcile::importNativeToXMP( outXMP, *dispMeta, kDISPProperties, hasDigest );
345 hasDISP = true;
346 }
347
348 if( !hasDISP )
349 {
350 //
351 // map INAM to dc:title ONLY in the case if:
352 // * DISP does NOT exists
353 // * dc:title does NOT exists
354 // * INAM exists
355 //
356 if( !hasDCTitle && hasINAM )
357 {
358 std::string xmpValue;
359 ReconcileUtils::NativeToUTF8( infoMeta->getValue<std::string>( INFOMetadata::kName ), xmpValue );
360 outXMP.SetLocalizedText( kXMP_NS_DC, "title", NULL, "x-default", xmpValue.c_str() );
361 }
362 }
363
364 // do timecode calculations
365 if ( outXMP.DoesPropertyExist( kXMP_NS_BWF, kBWF_timeReference ) &&
366 outXMP.DoesPropertyExist( kXMP_NS_BWF, kDM_timeFormat ) &&
367 outXMP.DoesPropertyExist( kXMP_NS_BWF, kBWF_timeStampSampleRate ) )
368 {
369 std::string xmpValue;
370 XMP_Int64 sampleRate = 0;
371 XMP_Uns64 nSamples = 0;
372 std::string timeFormat;
373 bool ok = outXMP.GetProperty( kXMP_NS_BWF, kBWF_timeReference, &xmpValue, 0 );
374
375 if ( ok )
376 {
377 int count;
378 char nextCh;
379 const char * strValue = xmpValue.c_str();
380
381 // make sure the format use the right type
382 long long unsigned numSamples;
383 count = sscanf ( strValue, "%llu%c", &numSamples, &nextCh );
384 nSamples = numSamples;
385
386 if ( count != 1 ) ok = false;
387 }
388 if ( ok )
389 ok = outXMP.GetProperty_Int64( kXMP_NS_BWF, kBWF_timeStampSampleRate, &sampleRate, 0 );
390 if ( ok )
391 ok = outXMP.GetProperty( kXMP_NS_BWF, kDM_timeFormat, &timeFormat, 0 );
392
393 if ( ok && sampleRate != 0 && timeFormat.size() > 0 ) {
394 // compute time code from all the information we have
395 std::string timecode;
396 if ( TimeConversionUtils::ConvertSamplesToSMPTETimecode( timecode, nSamples, sampleRate, timeFormat ) ) {
397 outXMP.SetStructField( kXMP_NS_DM, kDM_startTimecode, kXMP_NS_DM, kDM_timeValue, timecode, 0 );
398 outXMP.SetStructField( kXMP_NS_DM, kDM_startTimecode, kXMP_NS_DM, kDM_timeFormat, timeFormat, 0 );
399 }
400 }
401 }
402
403 // delete transient properties
404 outXMP.DeleteProperty( kXMP_NS_BWF, kBWF_timeStampSampleRate );
405 outXMP.DeleteProperty( kXMP_NS_BWF, kDM_timeFormat );
406
407 return changed;
408 } // importToXMP
409
410
exportFromXMP(MetadataSet & outMetaData,SXMPMeta & inXMP)411 XMP_Bool WAVEReconcile::exportFromXMP( MetadataSet& outMetaData, SXMPMeta& inXMP )
412 {
413 // tracks if anything has been exported from the XMP
414 bool changed = false;
415
416 //
417 // Export DISP
418 //
419 DISPMetadata *dispMeta = outMetaData.get<DISPMetadata>();
420 if (dispMeta != NULL)
421 {
422 // dc:title <-> DISP
423 changed |= IReconcile::exportXMPToNative( *dispMeta, inXMP, kDISPProperties );
424 }
425
426 PropertyList propertiesToBeRemovedFromXMPMeta;
427 if ( ! ignoreLocalText )
428 {
429 bool removeAllPropertiesinBextNameSpace = false;
430 //
431 // Export iXML
432 //
433 iXMLMetadata *iXMLMeta = outMetaData.get<iXMLMetadata>();
434 if (iXMLMeta != NULL)
435 {
436 IReconcile::exportXMPToNative( *iXMLMeta, inXMP, kiXMLProperties, &propertiesToBeRemovedFromXMPMeta );
437 exportSpecialXMPToiXML( inXMP, *iXMLMeta, propertiesToBeRemovedFromXMPMeta );
438 changed |= iXMLMeta->hasChanged();
439 removeAllPropertiesinBextNameSpace = true;
440 // for maintaing backward compatibility we don't want to remove properties from xmp packet.
441 propertiesToBeRemovedFromXMPMeta.clear();
442
443 // update time code value
444 if ( iXMLMeta->valueExists( iXMLMetadata::kTimeStampSampleRate ) &&
445 iXMLMeta->valueExists( iXMLMetadata::kTimeStampSampleSinceMidnightHigh ) &&
446 iXMLMeta->valueExists( iXMLMetadata::kTimeStampSampleSinceMidnightLow ) &&
447 iXMLMeta->valueExists( iXMLMetadata::kTimeCodeRate ) )
448 {
449 if ( iXMLMeta->valueChanged( iXMLMetadata::kTimeStampSampleRate ) ||
450 iXMLMeta->valueChanged( iXMLMetadata::kTimeStampSampleSinceMidnightHigh ) ||
451 iXMLMeta->valueChanged( iXMLMetadata::kTimeStampSampleSinceMidnightLow ) ||
452 iXMLMeta->valueChanged( iXMLMetadata::kTimeCodeRate ) ||
453 ( iXMLMeta->valueExists( iXMLMetadata::kTimeCodeFlag ) && iXMLMeta->valueChanged( iXMLMetadata::kTimeCodeFlag ) ) )
454 {
455 // update the time code value
456 XMP_Int64 sampleRate = iXMLMeta->getValue<XMP_Uns64>( iXMLMetadata::kTimeStampSampleRate );
457 XMP_Int64 nSamples = 0;
458 std::string timeFormat;
459 bool ok = inXMP.GetProperty_Int64( kXMP_NS_BWF, kBWF_timeReference, &nSamples, 0 );
460 if ( ok )
461 ok = inXMP.GetStructField( kXMP_NS_DM, kDM_startTimecode, kXMP_NS_DM, kDM_timeFormat, &timeFormat, 0 );
462
463 if ( ok && sampleRate != 0 && timeFormat.size() > 0 ) {
464 // compute time code from all the information we have
465 std::string timecode;
466 if ( TimeConversionUtils::ConvertSamplesToSMPTETimecode( timecode, nSamples, sampleRate, timeFormat ) ) {
467 inXMP.SetStructField( kXMP_NS_DM, kDM_startTimecode, kXMP_NS_DM, kDM_timeValue, timecode, 0 );
468 }
469 }
470
471 }
472 }
473 }
474
475 //
476 // Export BEXT
477 //
478
479 BEXTMetadata *bextMeta = outMetaData.get<BEXTMetadata>();
480
481 if (bextMeta != NULL)
482 {
483 IReconcile::exportXMPToNative( *bextMeta, inXMP, kBextProperties );
484
485 std::string xmpValue;
486
487 // bext:umid <-> RIFF:WAVE/bext.UMID
488 if (inXMP.GetProperty(kXMP_NS_BWF, "umid", &xmpValue, 0))
489 {
490 std::string umid;
491
492 if( this->decodeFromHexString( xmpValue, umid ) )
493 {
494 //
495 // if the XMP property doesn't contain a valid hex string then
496 // keep the existing value in the umid BEXT field
497 //
498 bextMeta->setArray<XMP_Uns8>(BEXTMetadata::kUMID, reinterpret_cast<const XMP_Uns8*>(umid.data()), umid.length());
499 }
500 }
501 else
502 {
503 bextMeta->deleteValue(BEXTMetadata::kUMID);
504 }
505
506 // bext:version <-> RIFF:WAVE/bext.version
507 // Special case: bext.version is always written as 1
508 if (inXMP.GetProperty(kXMP_NS_BWF, "version", NULL, 0))
509 {
510 bextMeta->setValue<XMP_Uns16>(BEXTMetadata::kVersion, 1);
511 }
512 else
513 {
514 bextMeta->deleteValue(BEXTMetadata::kVersion);
515 }
516
517 removeAllPropertiesinBextNameSpace = true;
518
519 changed |= bextMeta->hasChanged();
520 }
521
522 if ( removeAllPropertiesinBextNameSpace )
523 SXMPUtils::RemoveProperties(&inXMP, kXMP_NS_BWF, NULL, kXMPUtil_DoAllProperties );
524
525 //
526 // Export cart
527 //
528 CartMetadata* cartData = outMetaData.get<CartMetadata>();
529
530 if ( cartData != NULL )
531 {
532 IReconcile::exportXMPToNative ( *cartData, inXMP, kCartProperties );
533
534 // Export PostTimer
535 if ( inXMP.DoesPropertyExist( kXMP_NS_AEScart, "PostTimer" ) )
536 {
537 if( inXMP.CountArrayItems( kXMP_NS_AEScart, "PostTimer" ) == CartMetadata::kPostTimerLength )
538 {
539
540 CartMetadata::StoredCartTimer timer[CartMetadata::kPostTimerLength];
541 memset ( timer, 0, sizeof(CartMetadata::StoredCartTimer)*CartMetadata::kPostTimerLength ); // Fill with zeros
542 std::string path = "";
543 std::string usageSt = "";
544 std::string valueSt = "";
545 XMP_Bool invalidArray = false;
546 XMP_Uns32 usage = 0;
547 XMP_Uns32 value = 0;
548 XMP_Int64 tmp = 0;
549 XMP_OptionBits opts;
550
551
552 for ( XMP_Uns32 i = 0; i<CartMetadata::kPostTimerLength && !invalidArray; i++ )
553 {
554 // get options of array item
555 inXMP.GetArrayItem( kXMP_NS_AEScart, "PostTimer", i+1, NULL, &opts );
556 // copose path to array item
557 SXMPUtils::ComposeArrayItemPath( kXMP_NS_AEScart, "PostTimer", i+1, &path);
558
559 if ( opts == kXMP_PropValueIsStruct &&
560 inXMP.DoesStructFieldExist ( kXMP_NS_AEScart, path.c_str(), kXMP_NS_AEScart, "Usage" ) &&
561 inXMP.DoesStructFieldExist ( kXMP_NS_AEScart, path.c_str(), kXMP_NS_AEScart, "Value" ) )
562 {
563 inXMP.GetStructField( kXMP_NS_AEScart, path.c_str(), kXMP_NS_AEScart, "Usage", &usageSt, NULL);
564 inXMP.GetStructField( kXMP_NS_AEScart, path.c_str(), kXMP_NS_AEScart, "Value", &valueSt, NULL);
565
566 if ( stringToFOURCC( usageSt,usage ) )
567 {
568 // don't add if the String is not set or not exactly 4 characters
569 timer[i].usage = usage;
570
571 // now the value
572 if ( valueSt.length() > 0 )
573 {
574 tmp = SXMPUtils::ConvertToInt64(valueSt);
575 if ( tmp > 0 && tmp <= 0xffffffff)
576 {
577 value = static_cast<XMP_Uns32>(tmp); // save because I checked that the number is positiv.
578 timer[i].value = value;
579 }
580 // else value stays 0
581 }
582 // else value stays 0
583 }
584 }
585 else
586 {
587 invalidArray = true;
588 }
589 }
590
591 if (!invalidArray) // if the structure of the array is wrong don't add anything
592 {
593 cartData->setArray<CartMetadata::StoredCartTimer> (CartMetadata::kPostTimer, timer, CartMetadata::kPostTimerLength);
594 }
595 } // Array length is wrong: don't add anything
596 }
597 else
598 {
599 cartData->deleteValue( CartMetadata::kPostTimer );
600 }
601 SXMPUtils::RemoveProperties ( &inXMP, kXMP_NS_AEScart, 0, kXMPUtil_DoAllProperties );
602 changed |= cartData->hasChanged();
603 }
604 }
605
606 //
607 // export LIST:INFO
608 //
609 INFOMetadata *infoMeta = outMetaData.get<INFOMetadata>();
610
611 if (infoMeta != NULL)
612 {
613 IReconcile::exportXMPToNative( *infoMeta, inXMP, kINFOProperties );
614
615 if ( inXMP.DoesPropertyExist( kXMP_NS_DC, "subject" ) )
616 {
617 std::string catedStr;
618 SXMPUtils::CatenateArrayItems( inXMP, kXMP_NS_DC, "subject", NULL, NULL, kXMP_NoOptions, &catedStr );
619 infoMeta->setValue<std::string>(INFOMetadata::kKeywords, catedStr);
620 }
621 else
622 {
623 infoMeta->deleteValue( INFOMetadata::kKeywords );
624 }
625
626 // Remove RIFFINFO properties from the XMP
627 SXMPUtils::RemoveProperties(&inXMP, kXMP_NS_RIFFINFO, NULL, kXMPUtil_DoAllProperties );
628
629 changed |= infoMeta->hasChanged();
630 }
631
632 // cr8r is not yet required for WAVE
633
634 ////
635 //// export cr8r
636 ////
637 //Cr8rMetadata* cr8rMeta = outMetaData.get<Cr8rMetadata>();
638
639 //if( cr8rMeta != NULL )
640 //{
641 // changed |= IReconcile::exportXMPToNative( *cr8rMeta, inXMP, kCr8rProperties );
642 //}
643
644 //
645 // remove WAV digest
646 //
647 inXMP.DeleteProperty( kXMP_NS_WAV, "NativeDigest" );
648
649 for ( PropertyList::iterator it = propertiesToBeRemovedFromXMPMeta.begin(), last = propertiesToBeRemovedFromXMPMeta.end(); it != last; it++ )
650 {
651 inXMP.DeleteProperty( it->first, it->second );
652 }
653
654 return changed;
655 } // exportFromXMP
656
657
658 // ************** Helper Functions ***************** //
659
encodeToHexString(const XMP_Uns8 * input,std::string & output)660 bool WAVEReconcile::encodeToHexString ( const XMP_Uns8* input, std::string& output )
661 {
662 bool allZero = true; // assume for now
663 XMP_Uns32 kFixedSize = 64; // Only used for UMID Bext field, which is fixed
664 output.erase();
665
666 if ( input != 0 )
667 {
668 output.reserve ( kFixedSize * 2 );
669
670 for( XMP_Uns32 i = 0; i < kFixedSize; i++ )
671 {
672 // first, second nibble
673 XMP_Uns8 first = input[i] >> 4;
674 XMP_Uns8 second = input[i] & 0xF;
675
676 if ( allZero && (( first != 0 ) || (second != 0)))
677 allZero = false;
678
679 output.append( 1, ReconcileUtils::kHexDigits[first] );
680 output.append( 1, ReconcileUtils::kHexDigits[second] );
681 }
682 }
683 return allZero;
684 } // encodeToHexString
685
686
decodeFromHexString(std::string input,std::string & output)687 bool WAVEReconcile::decodeFromHexString ( std::string input, std::string &output)
688 {
689 if ( (input.length() % 2) != 0 )
690 return false;
691 output.erase();
692 output.reserve ( input.length() / 2 );
693
694 for( XMP_Uns32 i = 0; i < input.length(); )
695 {
696 XMP_Uns8 upperNibble = input[i];
697 if ( (upperNibble < 48) || ( (upperNibble > 57 ) && ( upperNibble < 65 ) ) || (upperNibble > 70) )
698 {
699 return false;
700 }
701 if ( upperNibble >= 65 )
702 {
703 upperNibble -= 7; // shift A-F area adjacent to 0-9
704 }
705 upperNibble -= 48; // 'shift' to a value [0..15]
706 upperNibble = ( upperNibble << 4 );
707 i++;
708
709 XMP_Uns8 lowerNibble = input[i];
710 if ( (lowerNibble < 48) || ( (lowerNibble > 57 ) && ( lowerNibble < 65 ) ) || (lowerNibble > 70) )
711 {
712 return false;
713 }
714 if ( lowerNibble >= 65 )
715 {
716 lowerNibble -= 7; // shift A-F area adjacent to 0-9
717 }
718 lowerNibble -= 48; // 'shift' to a value [0..15]
719 i++;
720
721 output.append ( 1, (upperNibble + lowerNibble) );
722 }
723 return true;
724 } // decodeFromHexString
725
stringToFOURCC(std::string input,XMP_Uns32 & output)726 bool WAVEReconcile::stringToFOURCC ( std::string input, XMP_Uns32 &output )
727 {
728 bool result = false;
729 std::string asciiST = "";
730
731 // convert to ACSII
732 convertToASCII(input, asciiST);
733 if ( asciiST.length() == 4 )
734 {
735
736 output = GetUns32BE( asciiST.c_str() );
737 result = true;
738 }
739
740 return result;
741 }
742
743 struct iXMLAudioSampleTypeMapping
744 {
745 const char * xmpStringValue;
746 XMP_Uns64 ixmlIntValue;
747 };
748
749 iXMLAudioSampleTypeMapping ixmlAudioSampleTypeMappings[] = {
750 { "8Int", 8 },
751 { "16Int", 16 },
752 { "24Int", 24 },
753 { "32Float", 32 },
754 };
755
756 struct iXMLTimeCodeRateAndFlagMapping
757 {
758 const char * xmpStringValue;
759 const char * ixmlTimeCodeRateValue;
760 const char * ixmlTimeCodeFlagValue;
761 };
762
763 iXMLTimeCodeRateAndFlagMapping ixmlTimeCodeRateAndFlagMappings[] = {
764 { "24Timecode", "24/1", "NDF" },
765 { "25Timecode", "25/1", "NDF" },
766 { "2997DropTimecode", "30000/1001", "DF" },
767 { "2997NonDropTimecode", "30000/1001", "NDF" },
768 { "30Timecode", "30/1", "NDF" },
769 { "50Timecode", "50/1", "NDF" },
770 { "5994DropTimecode", "60000/1001", "DF" },
771 { "5994NonDropTimecode", "60000/1001", "NDF" },
772 { "60Timecode", "60/1", "NDF" },
773 { "23976Timecode", "24000/1001", "NDF" },
774 };
775
exportSpecialXMPToiXML(SXMPMeta & inXMP,IMetadata & outNativeMeta,PropertyList & propertiesToBeDeleted)776 void IFF_RIFF::WAVEReconcile::exportSpecialXMPToiXML( SXMPMeta & inXMP, IMetadata & outNativeMeta, PropertyList & propertiesToBeDeleted )
777 {
778 std::string sXmpValue;
779 XMP_Int64 iXMPValue;
780
781 // special case for NoGood and Circled // xmpDM:good ,-> iXML:NO_GOOD and iXML:CIRCLED
782
783 // special case for AudioBitDepth // xmpDM:audioSampleType <-> iXML:AUDIO_BIT_DEPTH
784 bool deleteNativeEntry = false;
785 try
786 {
787 if ( inXMP.GetProperty( kXMP_NS_DM, kDM_audioSampleType, &sXmpValue, 0 ) )
788 {
789 bool matchingValueFound = false;
790 XMP_Uns64 ixmlValue = 0;
791 for ( size_t i = 0, total = sizeof(ixmlAudioSampleTypeMappings )/sizeof( iXMLAudioSampleTypeMapping); i < total; i++ )
792 {
793 if ( sXmpValue.compare( ixmlAudioSampleTypeMappings[i].xmpStringValue ) == 0 )
794 {
795 ixmlValue = ixmlAudioSampleTypeMappings[i].ixmlIntValue;
796 matchingValueFound = true;
797 break;
798 }
799 }
800
801 if ( matchingValueFound )
802 {
803 outNativeMeta.setValue< XMP_Uns64 >( iXMLMetadata::kAudioBitDepth, ixmlValue );
804 propertiesToBeDeleted.push_back( std::make_pair( kXMP_NS_DM, kDM_audioSampleType ) );
805 deleteNativeEntry = false;
806 }
807 else
808 {
809 deleteNativeEntry = true;
810 }
811 }
812 else
813 {
814 deleteNativeEntry = true;
815 }
816
817 if ( deleteNativeEntry )
818 {
819 if ( outNativeMeta.valueExists( iXMLMetadata::kAudioBitDepth ) )
820 {
821 XMP_Uns64 ixmlValue = outNativeMeta.getValue< XMP_Uns64 >( iXMLMetadata::kAudioBitDepth );
822 bool validValue = false;
823 for ( size_t i = 0, total = sizeof(ixmlAudioSampleTypeMappings )/sizeof( iXMLAudioSampleTypeMapping); i < total; i++ )
824 {
825 if ( ixmlValue == ixmlAudioSampleTypeMappings[i].ixmlIntValue )
826 {
827 validValue = true;
828 break;
829 }
830 }
831 if ( validValue )
832 outNativeMeta.deleteValue( iXMLMetadata::kAudioBitDepth );
833 }
834 }
835 }
836 catch ( ... )
837 {
838 // do nothing
839 }
840
841 // Special case: On export BEXT:version is always written as 1
842 try
843 {
844 if ( inXMP.GetProperty( kXMP_NS_BWF, "version", NULL, 0 ) )
845 {
846 outNativeMeta.setValue< XMP_Uns64 >( iXMLMetadata::kBWFVersion, 1 );
847 }
848 else
849 {
850 outNativeMeta.deleteValue( iXMLMetadata::kBWFVersion );
851 }
852 }
853 catch( ... )
854 {
855 // do nothing
856 }
857
858 // special case for xmpDM:startTimecode\xmpDM:timeFormat
859 try
860 {
861 if ( inXMP.GetStructField( kXMP_NS_DM, kDM_startTimecode, kXMP_NS_DM, kDM_timeFormat, &sXmpValue, 0 ) )
862 {
863 bool matchingValueFound = false;
864 const char * ixmlValueForTimeCodeRate = NULL;
865 const char * ixmlValueForTimeCodeFlag = NULL;
866 for ( size_t i = 0, total = sizeof(ixmlTimeCodeRateAndFlagMappings)/sizeof(iXMLTimeCodeRateAndFlagMapping); i < total; i++ )
867 {
868 if ( sXmpValue.compare( ixmlTimeCodeRateAndFlagMappings[i].xmpStringValue ) == 0 )
869 {
870 ixmlValueForTimeCodeRate = ixmlTimeCodeRateAndFlagMappings[i].ixmlTimeCodeRateValue;
871 ixmlValueForTimeCodeFlag = ixmlTimeCodeRateAndFlagMappings[i].ixmlTimeCodeFlagValue;
872 matchingValueFound = true;
873 deleteNativeEntry = false;
874 break;
875 }
876 }
877
878 if ( matchingValueFound )
879 {
880 outNativeMeta.setValue< std::string >( iXMLMetadata::kTimeCodeRate, ixmlValueForTimeCodeRate );
881 outNativeMeta.setValue< std::string >( iXMLMetadata::kTimeCodeFlag, ixmlValueForTimeCodeFlag );
882 }
883 else
884 {
885 deleteNativeEntry = true;
886 }
887 }
888 else
889 {
890 deleteNativeEntry = true;
891 }
892
893 if ( deleteNativeEntry )
894 {
895 if ( outNativeMeta.valueExists( iXMLMetadata::kTimeCodeRate ) )
896 {
897 std::string ixmlValueForTimecodeRate = outNativeMeta.getValue< std::string >( iXMLMetadata::kTimeCodeRate );
898 bool validValue = false;
899 for ( size_t i = 0, total = sizeof(ixmlTimeCodeRateAndFlagMappings)/sizeof(iXMLTimeCodeRateAndFlagMapping); i < total; i++ )
900 {
901 if ( ixmlValueForTimecodeRate.compare( ixmlTimeCodeRateAndFlagMappings[i].ixmlTimeCodeRateValue ) == 0 )
902 {
903 validValue = true;
904 break;
905 }
906 }
907 if ( validValue )
908 {
909 outNativeMeta.deleteValue( iXMLMetadata::kTimeCodeRate );
910 outNativeMeta.deleteValue( iXMLMetadata::kTimeCodeFlag );
911 }
912 }
913 }
914 }
915 catch( ... )
916 {
917 // do nothing
918 }
919
920 // special case for timeReference // bext:timeReference <-> iXML:BWF_TIME_REFERENCE_LOW and iXML:BWF_TIME_REFERENCE_HIGH
921 bool bextTimeReferenceDataAvailable = false;
922 XMP_Uns32 bextLowValue = 0;
923 XMP_Uns32 bextHighValue = 0;
924
925 try
926 {
927 if ( inXMP.GetProperty_Int64( kXMP_NS_BWF, "timeReference", &iXMPValue, 0 ) )
928 {
929 bextTimeReferenceDataAvailable = true;
930 XMP_Uns64 uXmpValue = ( XMP_Uns64 )( iXMPValue );
931 bextLowValue = ( XMP_Uns32 ) uXmpValue;
932 bextHighValue = ( XMP_Uns32 ) ( uXmpValue >> 32 );
933 }
934 }
935 catch ( ... )
936 {
937 // do nothing
938 }
939
940 #if 0 // reverse calculation from timecode to samples can result in different number of samples. So ignoring for time being
941 bool timeCodeDataAvailable = false;
942 XMP_Uns32 tcLowValue = 0;
943 XMP_Uns32 tcHighValue = 0;
944
945 if ( outNativeMeta.valueExists( iXMLMetadata::kTimeStampSampleRate ) )
946 {
947 try
948 {
949 std::string timeFormat, timeValue;
950 if ( inXMP.GetStructField( kXMP_NS_DM, kDM_startTimecode, kXMP_NS_DM, kDM_timeValue, &timeValue, 0 ) &&
951 inXMP.GetStructField( kXMP_NS_DM, kDM_startTimecode, kXMP_NS_DM, kDM_timeFormat, &timeFormat, 0) )
952 {
953 XMP_Int64 nSamples;
954 if ( TimeConversionUtils::ConvertSMPTETimecodeToSamples( nSamples, timeValue,
955 outNativeMeta.getValue< XMP_Uns64 >( iXMLMetadata::kTimeStampSampleRate ), timeFormat ) )
956 {
957 timeCodeDataAvailable = true;
958 XMP_Uns64 uXmpValue = ( XMP_Uns64 )( nSamples );
959 tcLowValue = ( XMP_Uns32 ) uXmpValue;
960 tcHighValue = ( XMP_Uns32 ) ( uXmpValue >> 32 );
961 }
962
963 }
964 }
965 catch ( ... )
966 {
967 // do nothing
968 }
969 }
970
971 if ( bextTimeReferenceDataAvailable && timeCodeDataAvailable )
972 {
973 // pick the one which is different, if both different pick the bext one
974 XMP_Uns64
975 }
976 else if ( bextTimeReferenceDataAvailable )
977 {
978 }
979 else if ( timeCodeDataAvailable )
980 {
981 }
982 else // none of the bextTimeReference and timeCode data is available
983 {
984 outNativeMeta.deleteValue( iXMLMetadata::kTimeStampSampleSinceMidnightHigh );
985 outNativeMeta.deleteValue( iXMLMetadata::kTimeStampSampleSinceMidnightLow );
986 outNativeMeta.deleteValue( iXMLMetadata::kBWFTimeReferenceHigh );
987 outNativeMeta.deleteValue( iXMLMetadata::kBWFTimeReferenceLow );
988 }
989 #endif
990
991 if ( bextTimeReferenceDataAvailable ) {
992 outNativeMeta.setValue< XMP_Uns64 >( iXMLMetadata::kBWFTimeReferenceHigh, bextHighValue );
993 outNativeMeta.setValue< XMP_Uns64 >( iXMLMetadata::kBWFTimeReferenceLow, bextLowValue );
994 outNativeMeta.setValue< XMP_Uns64 >( iXMLMetadata::kTimeStampSampleSinceMidnightHigh, bextHighValue );
995 outNativeMeta.setValue< XMP_Uns64 >( iXMLMetadata::kTimeStampSampleSinceMidnightLow, bextLowValue );
996 } else {
997 outNativeMeta.deleteValue( iXMLMetadata::kTimeStampSampleSinceMidnightHigh );
998 outNativeMeta.deleteValue( iXMLMetadata::kTimeStampSampleSinceMidnightLow );
999 outNativeMeta.deleteValue( iXMLMetadata::kBWFTimeReferenceHigh );
1000 outNativeMeta.deleteValue( iXMLMetadata::kBWFTimeReferenceLow );
1001 }
1002
1003 // track list
1004 try {
1005 if ( inXMP.DoesPropertyExist( kXMP_NS_iXML, kIXML_trackList ) ) {
1006 XMP_OptionBits options( 0 );
1007 if ( inXMP.GetProperty( kXMP_NS_iXML, kIXML_trackList, NULL, &options ) &&
1008 XMP_OptionIsSet( options, kXMP_PropArrayIsOrdered ) )
1009 {
1010 XMP_Index count = inXMP.CountArrayItems( kXMP_NS_iXML, kIXML_trackList );
1011 std::vector< iXMLMetadata::TrackListInfo > trackListInfo( count );
1012 for ( XMP_Index i = 0; i < count; i++ ) {
1013 iXMLMetadata::TrackListInfo & trackRef = trackListInfo[i];
1014 std::string trackPath;
1015 SXMPUtils::ComposeArrayItemPath( kXMP_NS_iXML, kIXML_trackList, i + 1, &trackPath );
1016 std::string fieldPath;
1017 SXMPUtils::ComposeStructFieldPath( kXMP_NS_iXML, trackPath.c_str(), kXMP_NS_iXML, kIXML_channelIndex, &fieldPath );
1018 XMP_Int64 int64Value;
1019 inXMP.GetProperty_Int64( kXMP_NS_iXML, fieldPath.c_str(), &int64Value, &options );
1020 trackRef.mChannelIndex = int64Value;
1021 inXMP.GetStructField( kXMP_NS_iXML, trackPath.c_str(), kXMP_NS_iXML, kIXML_Name, &trackRef.mName, &options );
1022 inXMP.GetStructField( kXMP_NS_iXML, trackPath.c_str(), kXMP_NS_iXML, kIXML_Function, &trackRef.mFunction, &options );
1023 }
1024 outNativeMeta.setArray< iXMLMetadata::TrackListInfo >( iXMLMetadata::kTrackList, trackListInfo.data(), count );
1025 inXMP.DeleteProperty( kXMP_NS_iXML, kIXML_trackList );
1026 }
1027 } else {
1028 outNativeMeta.deleteValue( iXMLMetadata::kTrackList );
1029 }
1030 } catch( ... ) {
1031 // do nothing
1032 }
1033 }
1034
exportSpecialiXMLToXMP(IMetadata & inNativeMeta,SXMPMeta & outXMP)1035 bool IFF_RIFF::WAVEReconcile::exportSpecialiXMLToXMP( IMetadata & inNativeMeta, SXMPMeta & outXMP )
1036 {
1037 bool changed = false;
1038 // special case for AudioBitDepth // xmpDM:audioSampleType <-> iXML:AUDIO_BIT_DEPTH
1039 if ( inNativeMeta.valueExists( iXMLMetadata::kAudioBitDepth ) ) {
1040 XMP_Uns64 ixmlValue = inNativeMeta.getValue< XMP_Uns64 >( iXMLMetadata::kAudioBitDepth);
1041 const char * xmpValue = NULL;
1042 bool matchingValueFound = false;
1043
1044 for ( size_t i = 0, total = sizeof(ixmlAudioSampleTypeMappings)/sizeof(iXMLAudioSampleTypeMapping); i < total; i++ )
1045 {
1046 if ( ixmlAudioSampleTypeMappings[i].ixmlIntValue == ixmlValue )
1047 {
1048 xmpValue = ixmlAudioSampleTypeMappings[i].xmpStringValue;
1049 matchingValueFound = true;
1050 break;
1051 }
1052 }
1053 if ( matchingValueFound )
1054 {
1055 outXMP.SetProperty( kXMP_NS_DM, kDM_audioSampleType, xmpValue );
1056 changed = true;
1057 }
1058 }
1059
1060 // special case for timeReference // bext:timeReference <-> iXML:TIME_SAMPLES_SINCE_MIDNIGHT_LO and iXML:TIME_SAMPLES_SINCE_MIDNIGHT_HI
1061 if ( inNativeMeta.valueExists( iXMLMetadata::kTimeStampSampleSinceMidnightHigh ) && inNativeMeta.valueExists( iXMLMetadata::kTimeStampSampleSinceMidnightLow ) ) {
1062 XMP_Uns64 combinedValue = inNativeMeta.getValue< XMP_Uns64 >( iXMLMetadata::kTimeStampSampleSinceMidnightHigh );
1063 combinedValue = combinedValue << 32;
1064 combinedValue += inNativeMeta.getValue< XMP_Uns64 >( iXMLMetadata::kTimeStampSampleSinceMidnightLow );
1065 std::string strValue;
1066 SXMPUtils::ConvertFromInt64( combinedValue, "%llu", &strValue );
1067 outXMP.SetProperty( kXMP_NS_BWF, "timeReference", strValue );
1068 changed = true;
1069 }
1070
1071 // special case for timeReference // bext:timeReference <-> iXML:BWF_TIME_REFERENCE_LOW and iXML:BWF_TIME_REFERENCE_HIGH
1072 if ( inNativeMeta.valueExists( iXMLMetadata::kBWFTimeReferenceHigh ) && inNativeMeta.valueExists( iXMLMetadata::kBWFTimeReferenceLow ) ) {
1073 XMP_Uns64 combinedValue = inNativeMeta.getValue< XMP_Uns64 >( iXMLMetadata::kBWFTimeReferenceHigh );
1074 combinedValue = combinedValue << 32;
1075 combinedValue += inNativeMeta.getValue< XMP_Uns64 >( iXMLMetadata::kBWFTimeReferenceLow );
1076 std::string strValue;
1077 SXMPUtils::ConvertFromInt64( combinedValue, "%llu", &strValue );
1078 outXMP.SetProperty( kXMP_NS_BWF, "timeReference", strValue );
1079 changed = true;
1080 }
1081
1082 // special case for xmpDM:startTimecode\xmpDM:timeFormat
1083 if ( inNativeMeta.valueExists( iXMLMetadata::kTimeCodeRate ) )
1084 {
1085 std::string ixmlTimecodeRateValue = inNativeMeta.getValue<std::string>( iXMLMetadata::kTimeCodeRate );
1086 std::string ixmlTimecodeFlagValue = "NDF";
1087 const char * xmpValue = NULL;
1088 bool matchingValueFound = false;
1089 if ( inNativeMeta.valueExists( iXMLMetadata::kTimeCodeFlag ) )
1090 {
1091 ixmlTimecodeFlagValue = inNativeMeta.getValue<std::string>( iXMLMetadata::kTimeCodeFlag );
1092 }
1093
1094 for ( size_t i = 0, total = sizeof(ixmlTimeCodeRateAndFlagMappings)/sizeof(iXMLTimeCodeRateAndFlagMapping); i < total; i++ )
1095 {
1096 if ( ( ixmlTimecodeRateValue.compare(ixmlTimeCodeRateAndFlagMappings[i].ixmlTimeCodeRateValue) == 0 ) &&
1097 ( ixmlTimecodeFlagValue.compare(ixmlTimeCodeRateAndFlagMappings[i].ixmlTimeCodeFlagValue) == 0 ) )
1098 {
1099 xmpValue = ixmlTimeCodeRateAndFlagMappings[i].xmpStringValue;
1100 matchingValueFound = true;
1101 break;
1102 }
1103 }
1104 if ( matchingValueFound )
1105 {
1106 outXMP.SetProperty( kXMP_NS_BWF, kDM_timeFormat, xmpValue );
1107 changed = true;
1108 }
1109 }
1110
1111 // special case for iXML:trackList
1112 if ( inNativeMeta.valueExists( iXMLMetadata::kTrackList ) )
1113 {
1114 XMP_Uns32 countOfTracks( 0 );
1115 const iXMLMetadata::TrackListInfo * trackInfoArray =
1116 inNativeMeta.getArray< iXMLMetadata::TrackListInfo >( iXMLMetadata::kTrackList, countOfTracks );
1117 if ( countOfTracks > 0 && trackInfoArray != NULL ) {
1118 outXMP.DeleteProperty( kXMP_NS_iXML, kIXML_trackList );
1119 outXMP.SetProperty( kXMP_NS_iXML, kIXML_trackList, 0, kXMP_PropArrayIsOrdered );
1120 for ( XMP_Uns32 i = 0; i < countOfTracks; i++ ) {
1121 std::string trackPath;
1122 SXMPUtils::ComposeArrayItemPath( kXMP_NS_iXML, kIXML_trackList, i + 1, &trackPath );
1123 const iXMLMetadata::TrackListInfo & ref = trackInfoArray[i];
1124 std::string value;
1125 SXMPUtils::ConvertFromInt64( ref.mChannelIndex, "%llu", &value );
1126 outXMP.SetStructField( kXMP_NS_iXML, trackPath.c_str(), kXMP_NS_iXML, kIXML_channelIndex, value );
1127 if ( ref.mName.size() > 0 )
1128 outXMP.SetStructField( kXMP_NS_iXML, trackPath.c_str(), kXMP_NS_iXML, kIXML_Name, ref.mName );
1129 if ( ref.mFunction.size() > 0 )
1130 outXMP.SetStructField( kXMP_NS_iXML, trackPath.c_str(), kXMP_NS_iXML, kIXML_Function, ref.mFunction );
1131 }
1132 changed = true;
1133 }
1134 }
1135
1136 return changed;
1137 }
1138