ddex-json-codec 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -85,6 +85,7 @@ interface DdexMessage {
85
85
  messageHeader: MessageHeader;
86
86
  updateIndicator?: string;
87
87
  resourceList: SoundRecording[];
88
+ imageList?: Image[];
88
89
  releaseList: Release[];
89
90
  dealList: ReleaseDeal[];
90
91
  partyList?: Party[]; // ERN 4.x only
@@ -95,9 +96,11 @@ interface DdexMessage {
95
96
  Exported types:
96
97
 
97
98
  `DdexMessage`, `ErnVersion`, `ErnMajorVersion`, `MessageHeader`, `MessageParty`,
98
- `SoundRecording`, `Release`, `TrackRelease`, `ResourceGroup`, `ReleaseResourceReference`,
99
+ `SoundRecording`, `TechnicalSoundRecordingDetails`,
100
+ `Image`, `ImageId`, `ImageDetailsByTerritory`, `TechnicalImageDetails`, `FileDetails`, `HashSum`,
101
+ `Release`, `TrackRelease`, `ResourceGroup`, `ReleaseResourceReference`,
99
102
  `ReleaseDeal`, `Deal`, `DealTerms`, `Usage`,
100
- `Party`, `Artist`, `DisplayArtist`, `ResourceContributor`, `IndirectResourceContributor`, `Contributor`,
103
+ `Party`, `Artist`, `ArtistRole`, `DisplayArtist`, `ResourceContributor`, `IndirectResourceContributor`, `Contributor`,
101
104
  `TextWithAttribute`, `Genre`, `PLine`, `CLine`, `Title`, `DisplayTitle`
102
105
 
103
106
  ## License
package/dist/index.d.mts CHANGED
@@ -45,13 +45,21 @@ interface PartyName {
45
45
  fullNameIndexed?: string;
46
46
  languageAndScriptCode?: string;
47
47
  }
48
+ interface ArtistRole {
49
+ role: string;
50
+ /** UserDefined時の属性 */
51
+ namespace?: string;
52
+ userDefinedValue?: string;
53
+ }
48
54
  interface Artist {
49
- /** 解決済みの名前(3.8系: FullNameから直接取得、4系: PartyListから解決) */
55
+ /** 解決済みのデフォルト名(3.8系: FullNameから直接取得、4系: PartyListの最初のFullName) */
50
56
  name: string;
57
+ /** 4系のみ: 多言語名(PartyListのPartyName[]をそのまま保持) */
58
+ names?: PartyName[];
51
59
  /** 4系のみ: 元の参照ID(ラウンドトリップ用) */
52
60
  partyReference?: string;
53
61
  partyId?: string[];
54
- roles?: string[];
62
+ roles?: ArtistRole[];
55
63
  }
56
64
  interface DisplayArtist {
57
65
  artist: Artist;
@@ -61,6 +69,7 @@ interface ResourceContributor {
61
69
  name: string;
62
70
  role: string;
63
71
  sequenceNumber?: number;
72
+ instrumentType?: string;
64
73
  /** UserDefined時の属性 */
65
74
  roleNamespace?: string;
66
75
  roleUserDefinedValue?: string;
@@ -79,26 +88,67 @@ interface Contributor {
79
88
  sequenceNumber?: number;
80
89
  }
81
90
  //#endregion
91
+ //#region src/types/image.d.ts
92
+ interface ImageBase {
93
+ resourceReference: string;
94
+ type?: string;
95
+ imageId?: ImageId;
96
+ }
97
+ interface Image38 extends ImageBase {
98
+ detailsByTerritory?: ImageDetailsByTerritory[];
99
+ }
100
+ interface Image4 extends ImageBase {
101
+ parentalWarningType?: string;
102
+ technicalDetails?: TechnicalImageDetails;
103
+ }
104
+ type Image = Image38 | Image4;
105
+ interface ImageId {
106
+ proprietaryId?: string;
107
+ proprietaryIdNamespace?: string;
108
+ }
109
+ interface ImageDetailsByTerritory {
110
+ territoryCode: string[];
111
+ parentalWarningType?: string;
112
+ technicalDetails?: TechnicalImageDetails;
113
+ }
114
+ interface TechnicalImageDetails {
115
+ technicalResourceDetailsReference?: string;
116
+ imageCodecType?: string;
117
+ imageHeight?: number;
118
+ imageWidth?: number;
119
+ file?: FileDetails;
120
+ }
121
+ interface FileDetails {
122
+ fileName?: string;
123
+ uri?: string;
124
+ hashSum?: HashSum;
125
+ }
126
+ interface HashSum {
127
+ algorithm?: string;
128
+ hashSumValue?: string;
129
+ }
130
+ //#endregion
82
131
  //#region src/types/sound-recording.d.ts
83
- interface SoundRecording {
132
+ interface SoundRecordingBase {
84
133
  resourceReference: string;
85
134
  type?: string;
86
135
  soundRecordingId?: SoundRecordingId;
87
- referenceTitle?: ReferenceTitle$1;
88
136
  displayArtists: DisplayArtist[];
89
137
  duration?: string;
90
138
  creationDate?: string;
91
139
  languageOfPerformance?: string;
92
140
  pLine?: PLine;
93
- /** 3.8系: territory別の詳細 */
141
+ }
142
+ interface SoundRecording38 extends SoundRecordingBase {
143
+ referenceTitle?: ReferenceTitle$1;
94
144
  detailsByTerritory?: SoundRecordingDetailsByTerritory[];
95
- /** 4系: フラットなタイトルテキスト */
145
+ }
146
+ interface SoundRecording4 extends SoundRecordingBase {
96
147
  displayTitleText?: string;
97
- /** 4系: 複数のDisplayTitle(territory+lang属性) */
98
148
  displayTitles?: DisplayTitle[];
99
- /** 4系: PartyReference参照のContributor */
100
149
  contributors?: Contributor[];
101
150
  }
151
+ type SoundRecording = SoundRecording38 | SoundRecording4;
102
152
  interface SoundRecordingId {
103
153
  isrc?: string;
104
154
  catalogNumber?: string;
@@ -107,9 +157,22 @@ interface ReferenceTitle$1 {
107
157
  titleText: string;
108
158
  subTitle?: string;
109
159
  }
160
+ interface TechnicalSoundRecordingDetails {
161
+ technicalResourceDetailsReference?: string;
162
+ audioCodecType?: string;
163
+ bitRate?: number;
164
+ bitRateUnit?: string;
165
+ bitsPerSample?: number;
166
+ numberOfChannels?: number;
167
+ samplingRate?: number;
168
+ samplingRateUnit?: string;
169
+ isPreview?: boolean;
170
+ file?: FileDetails;
171
+ }
110
172
  interface SoundRecordingDetailsByTerritory {
111
173
  territoryCode: string[];
112
174
  displayArtists?: DisplayArtist[];
175
+ displayArtistName?: string;
113
176
  titles?: Title[];
114
177
  labelName?: string;
115
178
  pLine?: PLine;
@@ -118,31 +181,34 @@ interface SoundRecordingDetailsByTerritory {
118
181
  sequenceNumber?: number;
119
182
  resourceContributors?: ResourceContributor[];
120
183
  indirectResourceContributors?: IndirectResourceContributor[];
184
+ technicalDetails?: TechnicalSoundRecordingDetails[];
121
185
  }
122
186
  //#endregion
123
187
  //#region src/types/release.d.ts
124
- interface Release {
188
+ interface ReleaseBase {
125
189
  releaseReference: string;
126
190
  releaseType?: string;
127
191
  releaseId?: ReleaseId;
128
- referenceTitle?: ReferenceTitle;
129
192
  displayArtists: DisplayArtist[];
130
193
  releaseResourceReferences?: ReleaseResourceReference[];
131
- resourceGroup?: ResourceGroup;
132
194
  duration?: string;
133
195
  pLine?: PLine;
134
196
  cLine?: CLine;
135
- /** 3.8系: territory別の詳細 */
197
+ }
198
+ interface Release38 extends ReleaseBase {
199
+ referenceTitle?: ReferenceTitle;
136
200
  detailsByTerritory?: ReleaseDetailsByTerritory[];
137
- /** 4系: フラットなタイトルテキスト */
201
+ resourceGroup?: ResourceGroup;
202
+ }
203
+ interface Release4 extends ReleaseBase {
138
204
  displayTitleText?: string;
139
- /** 4系: 複数のDisplayTitle(territory+lang属性) */
140
205
  displayTitles?: DisplayTitle[];
141
- /** 4系: ReleaseLabelReference(PartyRef値) */
142
206
  releaseLabelReferences?: string[];
143
207
  genre?: Genre;
144
208
  parentalWarningType?: string;
209
+ resourceGroup?: ResourceGroup;
145
210
  }
211
+ type Release = Release38 | Release4;
146
212
  interface ReferenceTitle {
147
213
  titleText: string;
148
214
  subTitle?: string;
@@ -153,6 +219,7 @@ interface ReleaseId {
153
219
  isrc?: string;
154
220
  gridOrIcpn?: string;
155
221
  catalogNumber?: string;
222
+ catalogNumberNamespace?: string;
156
223
  proprietaryId?: string;
157
224
  }
158
225
  interface ReleaseResourceReference {
@@ -176,9 +243,7 @@ interface TrackRelease {
176
243
  referenceTitle?: ReferenceTitle;
177
244
  displayArtists?: DisplayArtist[];
178
245
  releaseResourceReference: string;
179
- /** 4系: 複数のDisplayTitle */
180
246
  displayTitles?: DisplayTitle[];
181
- /** 4系: ReleaseLabelReference */
182
247
  releaseLabelReferences?: string[];
183
248
  genre?: Genre;
184
249
  }
@@ -231,7 +296,9 @@ interface PriceInformation {
231
296
  /**
232
297
  * パッチレベルまでのバージョン(namespace URIから検出)
233
298
  */
234
- type ErnVersion = '3.8' | '3.8.1' | '3.8.2' | '3.8.3' | '4.1' | '4.1.1' | '4.2' | '4.3' | '4.3.1' | '4.3.2';
299
+ type ErnVersion38 = '3.8' | '3.8.1' | '3.8.2' | '3.8.3';
300
+ type ErnVersion4 = '4.1' | '4.1.1' | '4.2' | '4.3' | '4.3.1' | '4.3.2';
301
+ type ErnVersion = ErnVersion38 | ErnVersion4;
235
302
  /**
236
303
  * 変換ロジックの分岐用(メジャー系統のみ)
237
304
  */
@@ -250,18 +317,27 @@ interface MessageHeader {
250
317
  messageRecipient: MessageParty;
251
318
  messageCreatedDateTime: string;
252
319
  }
253
- interface DdexMessage {
254
- ernVersion: ErnVersion;
320
+ interface DdexMessageBase {
255
321
  messageHeader: MessageHeader;
256
322
  updateIndicator?: string;
257
- resourceList: SoundRecording[];
258
- releaseList: Release[];
323
+ }
324
+ interface DdexMessage38 extends DdexMessageBase {
325
+ ernVersion: ErnVersion38;
326
+ resourceList: SoundRecording38[];
327
+ imageList?: Image38[];
328
+ releaseList: Release38[];
329
+ dealList: ReleaseDeal[];
330
+ }
331
+ interface DdexMessage4 extends DdexMessageBase {
332
+ ernVersion: ErnVersion4;
333
+ resourceList: SoundRecording4[];
334
+ imageList?: Image4[];
335
+ releaseList: Release4[];
259
336
  dealList: ReleaseDeal[];
260
- /** 4系のみ: PartyList(ラウンドトリップ用に保持) */
261
337
  partyList?: Party[];
262
- /** 4系のみ: TrackRelease */
263
338
  trackReleaseList?: TrackRelease[];
264
339
  }
340
+ type DdexMessage = DdexMessage38 | DdexMessage4;
265
341
  //#endregion
266
342
  //#region src/converter/xml-to-json/index.d.ts
267
343
  /**
@@ -287,4 +363,4 @@ declare function jsonToXml(message: DdexMessage, version?: ErnVersion): string;
287
363
  */
288
364
  declare function detectVersion(xml: string): ErnVersion;
289
365
  //#endregion
290
- export { type Artist, type CLine, type Contributor, type DdexMessage, type Deal, type DealTerms, type DisplayArtist, type DisplayTitle, type ErnMajorVersion, type ErnVersion, type Genre, type IndirectResourceContributor, type MessageHeader, type MessageParty, type PLine, type Party, type Release, type ReleaseDeal, type ReleaseResourceReference, type ResourceContributor, type ResourceGroup, type SoundRecording, type TextWithAttribute, type Title, type TrackRelease, type Usage, detectVersion, jsonToXml, xmlToJson };
366
+ export { type Artist, type ArtistRole, type CLine, type Contributor, type DdexMessage, type DdexMessage38, type DdexMessage4, type DdexMessageBase, type Deal, type DealTerms, type DisplayArtist, type DisplayTitle, type ErnMajorVersion, type ErnVersion, type ErnVersion38, type ErnVersion4, type FileDetails, type Genre, type HashSum, type Image, type Image38, type Image4, type ImageBase, type ImageDetailsByTerritory, type ImageId, type IndirectResourceContributor, type MessageHeader, type MessageParty, type PLine, type Party, type PartyName, type Release, type Release38, type Release4, type ReleaseBase, type ReleaseDeal, type ReleaseDetailsByTerritory, type ReleaseId, type ReleaseResourceReference, type ResourceContributor, type ResourceGroup, type SoundRecording, type SoundRecording38, type SoundRecording4, type SoundRecordingBase, type SoundRecordingDetailsByTerritory, type TechnicalImageDetails, type TechnicalSoundRecordingDetails, type TextWithAttribute, type Title, type TrackRelease, type Usage, detectVersion, jsonToXml, xmlToJson };
package/dist/index.mjs CHANGED
@@ -146,6 +146,7 @@ var Ern38Converter = class {
146
146
  messageHeader: this.parseMessageHeader(root.MessageHeader),
147
147
  updateIndicator: root.UpdateIndicator ?? void 0,
148
148
  resourceList: this.parseSoundRecordings(root.ResourceList),
149
+ imageList: this.parseImages(root.ResourceList),
149
150
  releaseList: this.parseReleases(root.ReleaseList),
150
151
  dealList: this.parseDealList(root.DealList)
151
152
  };
@@ -209,6 +210,7 @@ var Ern38Converter = class {
209
210
  return {
210
211
  territoryCode: ensureArray(raw.TerritoryCode),
211
212
  displayArtists: this.parseDisplayArtists(raw.DisplayArtist),
213
+ displayArtistName: Array.isArray(raw.DisplayArtistName) ? raw.DisplayArtistName[0] : raw.DisplayArtistName ?? void 0,
212
214
  titles: this.parseTitles(raw.Title),
213
215
  labelName: raw.LabelName ?? void 0,
214
216
  pLine: raw.PLine ? this.parsePLine(ensureArray(raw.PLine)[0]) : void 0,
@@ -216,7 +218,75 @@ var Ern38Converter = class {
216
218
  parentalWarningType: raw.ParentalWarningType ?? void 0,
217
219
  sequenceNumber: raw.SequenceNumber ? Number(raw.SequenceNumber) : void 0,
218
220
  resourceContributors: this.parseResourceContributors(raw.ResourceContributor),
219
- indirectResourceContributors: this.parseIndirectResourceContributors(raw.IndirectResourceContributor)
221
+ indirectResourceContributors: this.parseIndirectResourceContributors(raw.IndirectResourceContributor),
222
+ technicalDetails: raw.TechnicalSoundRecordingDetails ? ensureArray(raw.TechnicalSoundRecordingDetails).map((td) => this.parseTechnicalSoundRecordingDetails(td)) : void 0
223
+ };
224
+ }
225
+ parseTechnicalSoundRecordingDetails(raw) {
226
+ const bitRate = raw.BitRate;
227
+ const samplingRate = raw.SamplingRate;
228
+ return {
229
+ technicalResourceDetailsReference: raw.TechnicalResourceDetailsReference ?? void 0,
230
+ audioCodecType: raw.AudioCodecType ?? void 0,
231
+ bitRate: bitRate ? Number(typeof bitRate === "string" ? bitRate : bitRate["#text"]) : void 0,
232
+ bitRateUnit: typeof bitRate === "object" ? bitRate["@_UnitOfMeasure"] ?? void 0 : void 0,
233
+ bitsPerSample: raw.BitsPerSample ? Number(raw.BitsPerSample) : void 0,
234
+ numberOfChannels: raw.NumberOfChannels ? Number(raw.NumberOfChannels) : void 0,
235
+ samplingRate: samplingRate ? Number(typeof samplingRate === "string" ? samplingRate : samplingRate["#text"]) : void 0,
236
+ samplingRateUnit: typeof samplingRate === "object" ? samplingRate["@_UnitOfMeasure"] ?? void 0 : void 0,
237
+ isPreview: raw.IsPreview === "true" ? true : raw.IsPreview === "false" ? false : void 0,
238
+ file: raw.File ? this.parseFileDetails(raw.File) : void 0
239
+ };
240
+ }
241
+ parseImages(resourceList) {
242
+ if (!resourceList) return void 0;
243
+ const images = ensureArray(resourceList.Image);
244
+ if (images.length === 0) return void 0;
245
+ return images.map((img) => this.parseImage(img));
246
+ }
247
+ parseImage(raw) {
248
+ return {
249
+ resourceReference: raw.ResourceReference,
250
+ type: raw.ImageType ?? void 0,
251
+ imageId: raw.ImageId ? this.parseImageId(raw.ImageId) : void 0,
252
+ detailsByTerritory: raw.ImageDetailsByTerritory ? ensureArray(raw.ImageDetailsByTerritory).map((d) => this.parseImageDetailsByTerritory(d)) : void 0
253
+ };
254
+ }
255
+ parseImageId(raw) {
256
+ const propId = raw.ProprietaryId;
257
+ if (!propId) return void 0;
258
+ return {
259
+ proprietaryId: typeof propId === "string" ? propId : propId["#text"] ?? "",
260
+ proprietaryIdNamespace: typeof propId === "object" ? propId["@_Namespace"] ?? void 0 : void 0
261
+ };
262
+ }
263
+ parseImageDetailsByTerritory(raw) {
264
+ return {
265
+ territoryCode: ensureArray(raw.TerritoryCode),
266
+ parentalWarningType: raw.ParentalWarningType ?? void 0,
267
+ technicalDetails: raw.TechnicalImageDetails ? this.parseTechnicalImageDetails(Array.isArray(raw.TechnicalImageDetails) ? raw.TechnicalImageDetails[0] : raw.TechnicalImageDetails) : void 0
268
+ };
269
+ }
270
+ parseTechnicalImageDetails(raw) {
271
+ return {
272
+ technicalResourceDetailsReference: raw.TechnicalResourceDetailsReference ?? void 0,
273
+ imageCodecType: raw.ImageCodecType ?? void 0,
274
+ imageHeight: raw.ImageHeight ? Number(raw.ImageHeight) : void 0,
275
+ imageWidth: raw.ImageWidth ? Number(raw.ImageWidth) : void 0,
276
+ file: raw.File ? this.parseFileDetails(raw.File) : void 0
277
+ };
278
+ }
279
+ parseFileDetails(raw) {
280
+ return {
281
+ fileName: raw.FileName ?? void 0,
282
+ uri: raw.URI ?? void 0,
283
+ hashSum: raw.HashSum ? this.parseHashSum(raw.HashSum) : void 0
284
+ };
285
+ }
286
+ parseHashSum(raw) {
287
+ return {
288
+ algorithm: raw.HashSumAlgorithmType ?? raw.Algorithm ?? void 0,
289
+ hashSumValue: raw.HashSum ?? raw.HashSumValue ?? void 0
220
290
  };
221
291
  }
222
292
  parseReleases(releaseList) {
@@ -247,10 +317,19 @@ var Ern38Converter = class {
247
317
  icpn: typeof icpnRaw === "string" ? icpnRaw : icpnRaw["#text"],
248
318
  isEan: typeof icpnRaw === "object" && icpnRaw["@_IsEan"] === "true" ? true : void 0
249
319
  };
320
+ const catalogNumberRaw = raw.CatalogNumber;
321
+ let catalogNumber;
322
+ let catalogNumberNamespace;
323
+ if (catalogNumberRaw) if (typeof catalogNumberRaw === "string") catalogNumber = catalogNumberRaw;
324
+ else {
325
+ catalogNumber = catalogNumberRaw["#text"] ?? void 0;
326
+ catalogNumberNamespace = catalogNumberRaw["@_Namespace"] ?? void 0;
327
+ }
250
328
  return {
251
329
  isrc: raw.ISRC ?? void 0,
252
330
  gridOrIcpn: raw.GRid ?? void 0,
253
- catalogNumber: raw.CatalogNumber ?? void 0
331
+ catalogNumber,
332
+ catalogNumberNamespace
254
333
  };
255
334
  }
256
335
  parseReleaseResourceReferences(raw) {
@@ -351,11 +430,21 @@ var Ern38Converter = class {
351
430
  return artists.map((a) => ({
352
431
  artist: {
353
432
  name: a.PartyName?.FullName ?? "",
354
- roles: a.ArtistRole ? ensureArray(a.ArtistRole) : void 0
433
+ roles: a.ArtistRole ? this.parseArtistRoles(a.ArtistRole) : void 0
355
434
  },
356
435
  sequenceNumber: a["@_SequenceNumber"] ? Number(a["@_SequenceNumber"]) : void 0
357
436
  }));
358
437
  }
438
+ parseArtistRoles(raw) {
439
+ return ensureArray(raw).map((r) => {
440
+ if (typeof r === "string") return { role: r };
441
+ return {
442
+ role: r["#text"] ?? "",
443
+ namespace: r["@_Namespace"] ?? void 0,
444
+ userDefinedValue: r["@_UserDefinedValue"] ?? void 0
445
+ };
446
+ });
447
+ }
359
448
  parseResourceContributors(raw) {
360
449
  if (!raw) return void 0;
361
450
  const contributors = ensureArray(raw);
@@ -376,6 +465,7 @@ var Ern38Converter = class {
376
465
  name: c.PartyName?.FullName ?? "",
377
466
  role,
378
467
  sequenceNumber: c["@_SequenceNumber"] ? Number(c["@_SequenceNumber"]) : void 0,
468
+ instrumentType: c.InstrumentType ?? void 0,
379
469
  roleNamespace,
380
470
  roleUserDefinedValue
381
471
  };
@@ -431,7 +521,9 @@ var Ern4Converter = class {
431
521
  return {
432
522
  ernVersion: version,
433
523
  messageHeader: this.parseMessageHeader(root.MessageHeader),
524
+ updateIndicator: root["@_UpdateIndicator"] ?? void 0,
434
525
  resourceList: this.parseSoundRecordings(root.ResourceList),
526
+ imageList: this.parseImages(root.ResourceList),
435
527
  releaseList: this.parseReleases(root.ReleaseList),
436
528
  dealList: this.parseDealList(root.DealList),
437
529
  partyList,
@@ -472,10 +564,13 @@ var Ern4Converter = class {
472
564
  partyId: partyIds.length > 0 ? partyIds : void 0
473
565
  };
474
566
  }
475
- resolveArtistName(partyReference) {
567
+ resolveParty(partyReference) {
476
568
  const party = this.partyIndex.get(partyReference);
477
- if (!party?.partyName?.length) return partyReference;
478
- return party.partyName[0].fullName;
569
+ if (!party?.partyName?.length) return { name: partyReference };
570
+ return {
571
+ name: party.partyName[0].fullName,
572
+ names: party.partyName.length > 1 ? party.partyName : void 0
573
+ };
479
574
  }
480
575
  parseMessageHeader(raw) {
481
576
  return {
@@ -538,6 +633,43 @@ var Ern4Converter = class {
538
633
  catalogNumber: raw.CatalogNumber ?? void 0
539
634
  };
540
635
  }
636
+ parseImages(resourceList) {
637
+ if (!resourceList) return void 0;
638
+ const images = ensureArray(resourceList.Image);
639
+ if (images.length === 0) return void 0;
640
+ return images.map((img) => {
641
+ const result = {
642
+ resourceReference: img.ResourceReference,
643
+ type: img.Type ?? void 0,
644
+ parentalWarningType: img.ParentalWarningType ?? void 0
645
+ };
646
+ if (img.ResourceId) {
647
+ const propId = img.ResourceId.ProprietaryId;
648
+ if (propId) result.imageId = {
649
+ proprietaryId: typeof propId === "string" ? propId : propId["#text"] ?? "",
650
+ proprietaryIdNamespace: typeof propId === "object" ? propId["@_Namespace"] ?? void 0 : void 0
651
+ };
652
+ }
653
+ if (img.TechnicalDetails) {
654
+ const td = Array.isArray(img.TechnicalDetails) ? img.TechnicalDetails[0] : img.TechnicalDetails;
655
+ result.technicalDetails = {
656
+ technicalResourceDetailsReference: td.TechnicalResourceDetailsReference ?? void 0,
657
+ imageCodecType: td.ImageCodecType ?? void 0,
658
+ imageHeight: td.ImageHeight ? Number(td.ImageHeight) : void 0,
659
+ imageWidth: td.ImageWidth ? Number(td.ImageWidth) : void 0,
660
+ file: td.File ? {
661
+ uri: td.File.URI ?? void 0,
662
+ fileName: td.File.FileName ?? void 0,
663
+ hashSum: td.File.HashSum ? {
664
+ algorithm: td.File.HashSum.Algorithm ?? void 0,
665
+ hashSumValue: td.File.HashSum.HashSumValue ?? void 0
666
+ } : void 0
667
+ } : void 0
668
+ };
669
+ }
670
+ return result;
671
+ });
672
+ }
541
673
  parseReleases(releaseList) {
542
674
  if (!releaseList) return [];
543
675
  return ensureArray(releaseList.Release).map((r) => this.parseRelease(r));
@@ -574,10 +706,19 @@ var Ern4Converter = class {
574
706
  const propId = raw.ProprietaryId;
575
707
  let proprietaryId;
576
708
  if (propId) proprietaryId = typeof propId === "string" ? propId : propId["#text"];
709
+ const catalogNumberRaw = raw.CatalogNumber;
710
+ let catalogNumber;
711
+ let catalogNumberNamespace;
712
+ if (catalogNumberRaw) if (typeof catalogNumberRaw === "string") catalogNumber = catalogNumberRaw;
713
+ else {
714
+ catalogNumber = catalogNumberRaw["#text"] ?? void 0;
715
+ catalogNumberNamespace = catalogNumberRaw["@_Namespace"] ?? void 0;
716
+ }
577
717
  return {
578
718
  isrc: raw.ISRC ?? void 0,
579
719
  gridOrIcpn: raw.GRid ?? void 0,
580
- catalogNumber: raw.CatalogNumber ?? void 0,
720
+ catalogNumber,
721
+ catalogNumberNamespace,
581
722
  proprietaryId
582
723
  };
583
724
  }
@@ -675,23 +816,38 @@ var Ern4Converter = class {
675
816
  if (artists.length === 0) return void 0;
676
817
  return artists.map((a) => {
677
818
  const partyRef = a.ArtistPartyReference;
819
+ const resolved = partyRef ? this.resolveParty(partyRef) : {
820
+ name: a.PartyName?.FullName ?? "",
821
+ names: void 0
822
+ };
678
823
  return {
679
824
  artist: {
680
- name: partyRef ? this.resolveArtistName(partyRef) : a.PartyName?.FullName ?? "",
825
+ name: resolved.name,
826
+ names: resolved.names,
681
827
  partyReference: partyRef ?? void 0,
682
- roles: a.DisplayArtistRole ? ensureArray(a.DisplayArtistRole) : void 0
828
+ roles: a.DisplayArtistRole ? this.parseArtistRoles(a.DisplayArtistRole) : void 0
683
829
  },
684
830
  sequenceNumber: a["@_SequenceNumber"] ? Number(a["@_SequenceNumber"]) : void 0
685
831
  };
686
832
  });
687
833
  }
834
+ parseArtistRoles(raw) {
835
+ return ensureArray(raw).map((r) => {
836
+ if (typeof r === "string") return { role: r };
837
+ return {
838
+ role: r["#text"] ?? "",
839
+ namespace: r["@_Namespace"] ?? void 0,
840
+ userDefinedValue: r["@_UserDefinedValue"] ?? void 0
841
+ };
842
+ });
843
+ }
688
844
  parseContributors(raw) {
689
845
  if (!raw) return void 0;
690
846
  const contributors = ensureArray(raw);
691
847
  if (contributors.length === 0) return void 0;
692
848
  return contributors.map((c) => {
693
849
  const partyRef = c.ContributorPartyReference;
694
- const name = partyRef ? this.resolveArtistName(partyRef) : void 0;
850
+ const name = partyRef ? this.resolveParty(partyRef).name : void 0;
695
851
  return {
696
852
  contributorPartyReference: partyRef ?? "",
697
853
  name,
@@ -739,6 +895,7 @@ var Ern4Converter = class {
739
895
  //#region src/converter/json-to-xml/ern38-builder.ts
740
896
  var Ern38Builder = class {
741
897
  build(message, version) {
898
+ const msg = message;
742
899
  const nsUri = VERSION_NAMESPACE_MAP.get(version);
743
900
  if (!nsUri) throw new Error(`Unknown version: ${version}`);
744
901
  const nsPath = new URL(nsUri).pathname.replace(/^\/xml\//, "");
@@ -747,11 +904,11 @@ var Ern38Builder = class {
747
904
  "@_xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
748
905
  "@_MessageSchemaVersionId": nsPath,
749
906
  "@_xsi:schemaLocation": `${nsUri} ${nsUri}/release-notification.xsd`,
750
- MessageHeader: this.buildMessageHeader(message.messageHeader),
751
- ...message.updateIndicator ? { UpdateIndicator: message.updateIndicator } : {},
752
- ResourceList: this.buildResourceList(message.resourceList),
753
- ReleaseList: this.buildReleaseList(message.releaseList),
754
- DealList: this.buildDealList(message.dealList)
907
+ MessageHeader: this.buildMessageHeader(msg.messageHeader),
908
+ ...msg.updateIndicator ? { UpdateIndicator: msg.updateIndicator } : {},
909
+ ResourceList: this.buildResourceList(msg.resourceList, msg.imageList),
910
+ ReleaseList: this.buildReleaseList(msg.releaseList),
911
+ DealList: this.buildDealList(msg.dealList)
755
912
  } };
756
913
  return `<?xml version="1.0" encoding="UTF-8"?>\n${new XMLBuilder(BUILDER_OPTIONS).build(root)}`.trimEnd();
757
914
  }
@@ -775,8 +932,10 @@ var Ern38Builder = class {
775
932
  if (party.tradingName) result.TradingName = party.tradingName;
776
933
  return result;
777
934
  }
778
- buildResourceList(soundRecordings) {
779
- return { SoundRecording: soundRecordings.map((sr) => this.buildSoundRecording(sr)) };
935
+ buildResourceList(soundRecordings, images) {
936
+ const result = { SoundRecording: soundRecordings.map((sr) => this.buildSoundRecording(sr)) };
937
+ if (images?.length) result.Image = images.map((img) => this.buildImage(img));
938
+ return result;
780
939
  }
781
940
  buildSoundRecording(sr) {
782
941
  const result = {};
@@ -804,6 +963,7 @@ var Ern38Builder = class {
804
963
  result.TerritoryCode = d.territoryCode;
805
964
  if (d.titles) result.Title = d.titles.map((t) => this.buildTitle(t));
806
965
  if (d.displayArtists) result.DisplayArtist = d.displayArtists.map((a) => this.buildDisplayArtist(a));
966
+ if (d.displayArtistName) result.DisplayArtistName = d.displayArtistName;
807
967
  if (d.resourceContributors) result.ResourceContributor = d.resourceContributors.map((c) => this.buildResourceContributor(c));
808
968
  if (d.indirectResourceContributors) result.IndirectResourceContributor = d.indirectResourceContributors.map((c) => this.buildIndirectResourceContributor(c));
809
969
  if (d.labelName) result.LabelName = d.labelName;
@@ -811,6 +971,75 @@ var Ern38Builder = class {
811
971
  if (d.sequenceNumber != null) result.SequenceNumber = String(d.sequenceNumber);
812
972
  if (d.genre) result.Genre = this.buildGenre(d.genre);
813
973
  if (d.parentalWarningType) result.ParentalWarningType = d.parentalWarningType;
974
+ if (d.technicalDetails) result.TechnicalSoundRecordingDetails = d.technicalDetails.map((td) => this.buildTechnicalSoundRecordingDetails(td));
975
+ return result;
976
+ }
977
+ buildTechnicalSoundRecordingDetails(td) {
978
+ const result = {};
979
+ if (td.technicalResourceDetailsReference) result.TechnicalResourceDetailsReference = td.technicalResourceDetailsReference;
980
+ if (td.audioCodecType) result.AudioCodecType = td.audioCodecType;
981
+ if (td.bitRate != null) result.BitRate = td.bitRateUnit ? {
982
+ "#text": String(td.bitRate),
983
+ "@_UnitOfMeasure": td.bitRateUnit
984
+ } : String(td.bitRate);
985
+ if (td.bitsPerSample != null) result.BitsPerSample = String(td.bitsPerSample);
986
+ if (td.numberOfChannels != null) result.NumberOfChannels = String(td.numberOfChannels);
987
+ if (td.samplingRate != null) result.SamplingRate = td.samplingRateUnit ? {
988
+ "#text": String(td.samplingRate),
989
+ "@_UnitOfMeasure": td.samplingRateUnit
990
+ } : String(td.samplingRate);
991
+ if (td.isPreview != null) result.IsPreview = String(td.isPreview);
992
+ if (td.file) {
993
+ const file = {};
994
+ if (td.file.fileName) file.FileName = td.file.fileName;
995
+ if (td.file.uri) file.URI = td.file.uri;
996
+ if (td.file.hashSum) {
997
+ file.HashSum = {};
998
+ if (td.file.hashSum.algorithm) file.HashSum.HashSumAlgorithmType = td.file.hashSum.algorithm;
999
+ if (td.file.hashSum.hashSumValue) file.HashSum.HashSum = td.file.hashSum.hashSumValue;
1000
+ }
1001
+ result.File = file;
1002
+ }
1003
+ return result;
1004
+ }
1005
+ buildImage(img) {
1006
+ const result = {};
1007
+ if (img.type) result.ImageType = img.type;
1008
+ if (img.imageId) {
1009
+ const id = {};
1010
+ if (img.imageId.proprietaryId) id.ProprietaryId = img.imageId.proprietaryIdNamespace ? {
1011
+ "#text": img.imageId.proprietaryId,
1012
+ "@_Namespace": img.imageId.proprietaryIdNamespace
1013
+ } : img.imageId.proprietaryId;
1014
+ result.ImageId = id;
1015
+ }
1016
+ result.ResourceReference = img.resourceReference;
1017
+ if (img.detailsByTerritory) result.ImageDetailsByTerritory = img.detailsByTerritory.map((d) => {
1018
+ const dbt = {};
1019
+ dbt.TerritoryCode = d.territoryCode;
1020
+ if (d.parentalWarningType) dbt.ParentalWarningType = d.parentalWarningType;
1021
+ if (d.technicalDetails) dbt.TechnicalImageDetails = this.buildTechnicalImageDetails(d.technicalDetails);
1022
+ return dbt;
1023
+ });
1024
+ return result;
1025
+ }
1026
+ buildTechnicalImageDetails(td) {
1027
+ const result = {};
1028
+ if (td.technicalResourceDetailsReference) result.TechnicalResourceDetailsReference = td.technicalResourceDetailsReference;
1029
+ if (td.imageCodecType) result.ImageCodecType = td.imageCodecType;
1030
+ if (td.imageHeight != null) result.ImageHeight = String(td.imageHeight);
1031
+ if (td.imageWidth != null) result.ImageWidth = String(td.imageWidth);
1032
+ if (td.file) {
1033
+ const file = {};
1034
+ if (td.file.fileName) file.FileName = td.file.fileName;
1035
+ if (td.file.uri) file.URI = td.file.uri;
1036
+ if (td.file.hashSum) {
1037
+ file.HashSum = {};
1038
+ if (td.file.hashSum.algorithm) file.HashSum.HashSumAlgorithmType = td.file.hashSum.algorithm;
1039
+ if (td.file.hashSum.hashSumValue) file.HashSum.HashSum = td.file.hashSum.hashSumValue;
1040
+ }
1041
+ result.File = file;
1042
+ }
814
1043
  return result;
815
1044
  }
816
1045
  buildReleaseList(releases) {
@@ -838,7 +1067,10 @@ var Ern38Builder = class {
838
1067
  const result = {};
839
1068
  if (id.isrc) result.ISRC = id.isrc;
840
1069
  if (id.gridOrIcpn) result.GRid = id.gridOrIcpn;
841
- if (id.catalogNumber) result.CatalogNumber = id.catalogNumber;
1070
+ if (id.catalogNumber) result.CatalogNumber = id.catalogNumberNamespace ? {
1071
+ "#text": id.catalogNumber,
1072
+ "@_Namespace": id.catalogNumberNamespace
1073
+ } : id.catalogNumber;
842
1074
  return result;
843
1075
  }
844
1076
  buildReleaseResourceReference(ref) {
@@ -909,9 +1141,20 @@ var Ern38Builder = class {
909
1141
  const result = {};
910
1142
  if (da.sequenceNumber != null) result["@_SequenceNumber"] = String(da.sequenceNumber);
911
1143
  result.PartyName = { FullName: da.artist.name };
912
- if (da.artist.roles?.length) result.ArtistRole = da.artist.roles.length === 1 ? da.artist.roles[0] : da.artist.roles;
1144
+ if (da.artist.roles?.length) {
1145
+ const builtRoles = da.artist.roles.map((r) => this.buildArtistRole(r));
1146
+ result.ArtistRole = builtRoles.length === 1 ? builtRoles[0] : builtRoles;
1147
+ }
913
1148
  return result;
914
1149
  }
1150
+ buildArtistRole(r) {
1151
+ if (r.userDefinedValue) return {
1152
+ "#text": r.role,
1153
+ ...r.namespace ? { "@_Namespace": r.namespace } : {},
1154
+ "@_UserDefinedValue": r.userDefinedValue
1155
+ };
1156
+ return r.role;
1157
+ }
915
1158
  buildResourceContributor(c) {
916
1159
  const result = {};
917
1160
  if (c.sequenceNumber != null) result["@_SequenceNumber"] = String(c.sequenceNumber);
@@ -922,6 +1165,7 @@ var Ern38Builder = class {
922
1165
  "@_UserDefinedValue": c.roleUserDefinedValue
923
1166
  };
924
1167
  else result.ResourceContributorRole = c.role;
1168
+ if (c.instrumentType) result.InstrumentType = c.instrumentType;
925
1169
  return result;
926
1170
  }
927
1171
  buildIndirectResourceContributor(c) {
@@ -961,6 +1205,7 @@ var Ern38Builder = class {
961
1205
  //#region src/converter/json-to-xml/ern4-builder.ts
962
1206
  var Ern4Builder = class {
963
1207
  build(message, version) {
1208
+ const msg = message;
964
1209
  const nsUri = VERSION_NAMESPACE_MAP.get(version);
965
1210
  if (!nsUri) throw new Error(`Unknown version: ${version}`);
966
1211
  const root = { "ern:NewReleaseMessage": {
@@ -968,11 +1213,12 @@ var Ern4Builder = class {
968
1213
  "@_xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
969
1214
  "@_xsi:schemaLocation": `${nsUri} ${nsUri}/release-notification.xsd`,
970
1215
  "@_LanguageAndScriptCode": "en",
971
- MessageHeader: this.buildMessageHeader(message.messageHeader),
972
- PartyList: this.buildPartyList(message.partyList ?? []),
973
- ResourceList: this.buildResourceList(message.resourceList, version),
974
- ReleaseList: this.buildReleaseList(message.releaseList, message.trackReleaseList),
975
- DealList: this.buildDealList(message.dealList)
1216
+ ...msg.updateIndicator ? { "@_UpdateIndicator": msg.updateIndicator } : {},
1217
+ MessageHeader: this.buildMessageHeader(msg.messageHeader),
1218
+ PartyList: this.buildPartyList(msg.partyList ?? []),
1219
+ ResourceList: this.buildResourceList(msg.resourceList, msg.imageList, version),
1220
+ ReleaseList: this.buildReleaseList(msg.releaseList, msg.trackReleaseList),
1221
+ DealList: this.buildDealList(msg.dealList)
976
1222
  } };
977
1223
  return `<?xml version="1.0" encoding="UTF-8"?>\n${new XMLBuilder(BUILDER_OPTIONS).build(root)}`.trimEnd();
978
1224
  }
@@ -1009,8 +1255,40 @@ var Ern4Builder = class {
1009
1255
  if (party.partyId?.length) result.PartyId = party.partyId.map((id) => id);
1010
1256
  return result;
1011
1257
  }
1012
- buildResourceList(soundRecordings, _version) {
1013
- return { SoundRecording: soundRecordings.map((sr) => this.buildSoundRecording(sr)) };
1258
+ buildResourceList(soundRecordings, images, _version) {
1259
+ const result = { SoundRecording: soundRecordings.map((sr) => this.buildSoundRecording(sr)) };
1260
+ if (images?.length) result.Image = images.map((img) => this.buildImage(img));
1261
+ return result;
1262
+ }
1263
+ buildImage(img) {
1264
+ const result = {};
1265
+ result.ResourceReference = img.resourceReference;
1266
+ if (img.type) result.Type = img.type;
1267
+ if (img.imageId?.proprietaryId) result.ResourceId = { ProprietaryId: img.imageId.proprietaryIdNamespace ? {
1268
+ "#text": img.imageId.proprietaryId,
1269
+ "@_Namespace": img.imageId.proprietaryIdNamespace
1270
+ } : img.imageId.proprietaryId };
1271
+ if (img.parentalWarningType) result.ParentalWarningType = img.parentalWarningType;
1272
+ if (img.technicalDetails) {
1273
+ const td = {};
1274
+ if (img.technicalDetails.technicalResourceDetailsReference) td.TechnicalResourceDetailsReference = img.technicalDetails.technicalResourceDetailsReference;
1275
+ if (img.technicalDetails.imageCodecType) td.ImageCodecType = img.technicalDetails.imageCodecType;
1276
+ if (img.technicalDetails.imageHeight != null) td.ImageHeight = String(img.technicalDetails.imageHeight);
1277
+ if (img.technicalDetails.imageWidth != null) td.ImageWidth = String(img.technicalDetails.imageWidth);
1278
+ if (img.technicalDetails.file) {
1279
+ const file = {};
1280
+ if (img.technicalDetails.file.uri) file.URI = img.technicalDetails.file.uri;
1281
+ if (img.technicalDetails.file.fileName) file.FileName = img.technicalDetails.file.fileName;
1282
+ if (img.technicalDetails.file.hashSum) {
1283
+ file.HashSum = {};
1284
+ if (img.technicalDetails.file.hashSum.algorithm) file.HashSum.Algorithm = img.technicalDetails.file.hashSum.algorithm;
1285
+ if (img.technicalDetails.file.hashSum.hashSumValue) file.HashSum.HashSumValue = img.technicalDetails.file.hashSum.hashSumValue;
1286
+ }
1287
+ td.File = file;
1288
+ }
1289
+ result.TechnicalDetails = td;
1290
+ }
1291
+ return result;
1014
1292
  }
1015
1293
  buildSoundRecording(sr) {
1016
1294
  const result = {};
@@ -1063,7 +1341,10 @@ var Ern4Builder = class {
1063
1341
  if (id.isrc) result.ISRC = id.isrc;
1064
1342
  if (id.gridOrIcpn) result.GRid = id.gridOrIcpn;
1065
1343
  if (id.proprietaryId) result.ProprietaryId = id.proprietaryId;
1066
- if (id.catalogNumber) result.CatalogNumber = id.catalogNumber;
1344
+ if (id.catalogNumber) result.CatalogNumber = id.catalogNumberNamespace ? {
1345
+ "#text": id.catalogNumber,
1346
+ "@_Namespace": id.catalogNumberNamespace
1347
+ } : id.catalogNumber;
1067
1348
  return result;
1068
1349
  }
1069
1350
  buildTrackRelease(tr) {
@@ -1131,9 +1412,20 @@ var Ern4Builder = class {
1131
1412
  const result = {};
1132
1413
  if (da.sequenceNumber != null) result["@_SequenceNumber"] = String(da.sequenceNumber);
1133
1414
  if (da.artist.partyReference) result.ArtistPartyReference = da.artist.partyReference;
1134
- if (da.artist.roles?.length) result.DisplayArtistRole = da.artist.roles.length === 1 ? da.artist.roles[0] : da.artist.roles;
1415
+ if (da.artist.roles?.length) {
1416
+ const builtRoles = da.artist.roles.map((r) => this.buildArtistRole(r));
1417
+ result.DisplayArtistRole = builtRoles.length === 1 ? builtRoles[0] : builtRoles;
1418
+ }
1135
1419
  return result;
1136
1420
  }
1421
+ buildArtistRole(r) {
1422
+ if (r.userDefinedValue) return {
1423
+ "#text": r.role,
1424
+ ...r.namespace ? { "@_Namespace": r.namespace } : {},
1425
+ "@_UserDefinedValue": r.userDefinedValue
1426
+ };
1427
+ return r.role;
1428
+ }
1137
1429
  buildContributor(c) {
1138
1430
  const result = {};
1139
1431
  if (c.sequenceNumber != null) result["@_SequenceNumber"] = String(c.sequenceNumber);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ddex-json-codec",
3
3
  "type": "module",
4
- "version": "0.1.1",
4
+ "version": "0.3.0",
5
5
  "description": "DDEX ERN XML ↔ JSON converter for TypeScript",
6
6
  "license": "MIT",
7
7
  "author": "kazhs",