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