1extern crate alloc;
13
14use alloc::string::String;
15use alloc::vec::Vec;
16
17use broadcast_common::{Parse, Serialize};
18
19use crate::error::{Error, Result};
20use crate::local_set::{LocalSet, StructuralSetKind};
21use crate::sets::{
22 InterchangeObjectFields, LocalSetOwnedItem, collect_dark, finish_owned_set, get_optional_raw,
23 get_required_fixed, get_required_raw, owned_set_serialized_len, serialize_owned_set,
24};
25use crate::types::{
26 MxfTimestamp, PackageId, StrongRef, TIMESTAMP_LEN, decode_utf16_be, encode_utf16_be,
27 parse_uid_batch, serialize_uid_batch,
28};
29
30pub const TAG_PACKAGE_UID: u16 = 0x4401;
34pub const TAG_NAME: u16 = 0x4402;
36pub const TAG_TRACKS: u16 = 0x4403;
38pub const TAG_MODIFIED_DATE: u16 = 0x4404;
40pub const TAG_CREATION_DATE: u16 = 0x4405;
42
43pub const TAG_DESCRIPTOR: u16 = 0x4701;
47
48const MATERIAL_KNOWN_TAGS: [u16; 8] = [
51 crate::sets::TAG_INSTANCE_UID,
52 crate::sets::TAG_GENERATION_UID,
53 crate::sets::TAG_OBJECT_CLASS,
54 TAG_PACKAGE_UID,
55 TAG_NAME,
56 TAG_TRACKS,
57 TAG_MODIFIED_DATE,
58 TAG_CREATION_DATE,
59];
60
61const SOURCE_KNOWN_TAGS: [u16; 9] = [
62 crate::sets::TAG_INSTANCE_UID,
63 crate::sets::TAG_GENERATION_UID,
64 crate::sets::TAG_OBJECT_CLASS,
65 TAG_PACKAGE_UID,
66 TAG_NAME,
67 TAG_TRACKS,
68 TAG_MODIFIED_DATE,
69 TAG_CREATION_DATE,
70 TAG_DESCRIPTOR,
71];
72
73#[derive(Debug, Clone, PartialEq, Eq)]
82pub struct MaterialPackage {
83 pub interchange: InterchangeObjectFields,
85 pub package_uid: PackageId,
87 pub name: Option<String>,
89 pub creation_date: MxfTimestamp,
91 pub modified_date: MxfTimestamp,
93 pub tracks: Vec<StrongRef>,
95 pub dark: Vec<(u16, Vec<u8>)>,
97}
98
99impl<'a> Parse<'a> for MaterialPackage {
100 type Error = Error;
101
102 fn parse(bytes: &'a [u8]) -> Result<Self> {
103 let set = LocalSet::parse(bytes)?;
104 if set.kind() != StructuralSetKind::MaterialPackage {
105 return Err(Error::KeyPrefixMismatch {
106 what: "Material Package (Table 17)",
107 });
108 }
109 let items = &set.items;
110 let interchange = InterchangeObjectFields::decode(items, "Material Package")?;
111 let package_uid = PackageId(get_required_fixed::<32>(
112 items,
113 TAG_PACKAGE_UID,
114 "Package UID",
115 "Material Package",
116 )?);
117 let name = get_optional_raw(items, TAG_NAME)
118 .map(decode_utf16_be)
119 .transpose()
120 .map_err(|_| Error::InvalidUtf16 {
121 tag: TAG_NAME,
122 name: "Name",
123 })?;
124 let creation_date = MxfTimestamp::parse(get_required_raw(
125 items,
126 TAG_CREATION_DATE,
127 "Package Creation Date",
128 "Material Package",
129 )?)?;
130 let modified_date = MxfTimestamp::parse(get_required_raw(
131 items,
132 TAG_MODIFIED_DATE,
133 "Package Modified Date",
134 "Material Package",
135 )?)?;
136 let tracks = parse_uid_batch(get_required_raw(
137 items,
138 TAG_TRACKS,
139 "Tracks",
140 "Material Package",
141 )?)?;
142 let dark = collect_dark(items, &MATERIAL_KNOWN_TAGS);
143
144 Ok(MaterialPackage {
145 interchange,
146 package_uid,
147 name,
148 creation_date,
149 modified_date,
150 tracks,
151 dark,
152 })
153 }
154}
155
156impl MaterialPackage {
157 fn owned_items(&self) -> Vec<LocalSetOwnedItem> {
158 let mut out = Vec::new();
159 self.interchange.encode_into(&mut out);
160 out.push(LocalSetOwnedItem::fixed(
161 TAG_PACKAGE_UID,
162 self.package_uid.0,
163 ));
164 if let Some(n) = &self.name {
165 out.push(LocalSetOwnedItem::owned(TAG_NAME, encode_utf16_be(n)));
166 }
167 encode_timestamp_item(&mut out, TAG_CREATION_DATE, &self.creation_date);
168 encode_timestamp_item(&mut out, TAG_MODIFIED_DATE, &self.modified_date);
169 out.push(LocalSetOwnedItem::owned(
170 TAG_TRACKS,
171 serialize_uid_batch(&self.tracks),
172 ));
173 out
174 }
175}
176
177impl Serialize for MaterialPackage {
178 type Error = Error;
179
180 fn serialized_len(&self) -> usize {
181 let (key, items) = finish_owned_set(
182 StructuralSetKind::MaterialPackage,
183 self.owned_items(),
184 &self.dark,
185 );
186 owned_set_serialized_len(key, &items)
187 }
188
189 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
190 let (key, items) = finish_owned_set(
191 StructuralSetKind::MaterialPackage,
192 self.owned_items(),
193 &self.dark,
194 );
195 serialize_owned_set(key, &items, buf)
196 }
197}
198
199#[derive(Debug, Clone, PartialEq, Eq)]
208pub struct SourcePackage {
209 pub interchange: InterchangeObjectFields,
211 pub package_uid: PackageId,
213 pub name: Option<String>,
215 pub creation_date: MxfTimestamp,
217 pub modified_date: MxfTimestamp,
219 pub tracks: Vec<StrongRef>,
221 pub descriptor: StrongRef,
229 pub dark: Vec<(u16, Vec<u8>)>,
231}
232
233impl<'a> Parse<'a> for SourcePackage {
234 type Error = Error;
235
236 fn parse(bytes: &'a [u8]) -> Result<Self> {
237 let set = LocalSet::parse(bytes)?;
238 if set.kind() != StructuralSetKind::SourcePackage {
239 return Err(Error::KeyPrefixMismatch {
240 what: "Source Package (Table 17)",
241 });
242 }
243 let items = &set.items;
244 let interchange = InterchangeObjectFields::decode(items, "Source Package")?;
245 let package_uid = PackageId(get_required_fixed::<32>(
246 items,
247 TAG_PACKAGE_UID,
248 "Package UID",
249 "Source Package",
250 )?);
251 let name = get_optional_raw(items, TAG_NAME)
252 .map(decode_utf16_be)
253 .transpose()
254 .map_err(|_| Error::InvalidUtf16 {
255 tag: TAG_NAME,
256 name: "Name",
257 })?;
258 let creation_date = MxfTimestamp::parse(get_required_raw(
259 items,
260 TAG_CREATION_DATE,
261 "Package Creation Date",
262 "Source Package",
263 )?)?;
264 let modified_date = MxfTimestamp::parse(get_required_raw(
265 items,
266 TAG_MODIFIED_DATE,
267 "Package Modified Date",
268 "Source Package",
269 )?)?;
270 let tracks = parse_uid_batch(get_required_raw(
271 items,
272 TAG_TRACKS,
273 "Tracks",
274 "Source Package",
275 )?)?;
276 let descriptor =
277 get_required_fixed::<16>(items, TAG_DESCRIPTOR, "Descriptor", "Source Package")?;
278 let dark = collect_dark(items, &SOURCE_KNOWN_TAGS);
279
280 Ok(SourcePackage {
281 interchange,
282 package_uid,
283 name,
284 creation_date,
285 modified_date,
286 tracks,
287 descriptor,
288 dark,
289 })
290 }
291}
292
293impl SourcePackage {
294 fn owned_items(&self) -> Vec<LocalSetOwnedItem> {
295 let mut out = Vec::new();
296 self.interchange.encode_into(&mut out);
297 out.push(LocalSetOwnedItem::fixed(
298 TAG_PACKAGE_UID,
299 self.package_uid.0,
300 ));
301 if let Some(n) = &self.name {
302 out.push(LocalSetOwnedItem::owned(TAG_NAME, encode_utf16_be(n)));
303 }
304 encode_timestamp_item(&mut out, TAG_CREATION_DATE, &self.creation_date);
305 encode_timestamp_item(&mut out, TAG_MODIFIED_DATE, &self.modified_date);
306 out.push(LocalSetOwnedItem::owned(
307 TAG_TRACKS,
308 serialize_uid_batch(&self.tracks),
309 ));
310 out.push(LocalSetOwnedItem::fixed(TAG_DESCRIPTOR, self.descriptor));
311 out
312 }
313}
314
315impl Serialize for SourcePackage {
316 type Error = Error;
317
318 fn serialized_len(&self) -> usize {
319 let (key, items) = finish_owned_set(
320 StructuralSetKind::SourcePackage,
321 self.owned_items(),
322 &self.dark,
323 );
324 owned_set_serialized_len(key, &items)
325 }
326
327 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
328 let (key, items) = finish_owned_set(
329 StructuralSetKind::SourcePackage,
330 self.owned_items(),
331 &self.dark,
332 );
333 serialize_owned_set(key, &items, buf)
334 }
335}
336
337fn encode_timestamp_item(out: &mut Vec<LocalSetOwnedItem>, tag: u16, ts: &MxfTimestamp) {
341 let mut buf = [0u8; TIMESTAMP_LEN];
342 ts.serialize_into(&mut buf).expect("fixed-size buffer");
343 out.push(LocalSetOwnedItem::owned(tag, buf.to_vec()));
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349
350 fn sample_timestamp() -> MxfTimestamp {
351 MxfTimestamp {
352 year: 2026,
353 month: 7,
354 day: 12,
355 hour: 10,
356 minute: 0,
357 second: 0,
358 msec_div4: 0,
359 }
360 }
361
362 fn sample_material_package() -> MaterialPackage {
363 MaterialPackage {
364 interchange: InterchangeObjectFields {
365 instance_uid: [0x11; 16],
366 generation_uid: Some([0x22; 16]),
367 object_class: None,
368 },
369 package_uid: PackageId([0x33; 32]),
370 name: Some(String::from("Main Timeline")),
371 creation_date: sample_timestamp(),
372 modified_date: sample_timestamp(),
373 tracks: alloc::vec![[0x44; 16], [0x55; 16]],
374 dark: Vec::new(),
375 }
376 }
377
378 fn sample_source_package() -> SourcePackage {
379 SourcePackage {
380 interchange: InterchangeObjectFields {
381 instance_uid: [0xAA; 16],
382 generation_uid: None,
383 object_class: None,
384 },
385 package_uid: PackageId([0xBB; 32]),
386 name: None,
387 creation_date: sample_timestamp(),
388 modified_date: sample_timestamp(),
389 tracks: alloc::vec![[0xCC; 16]],
390 descriptor: [0xDD; 16],
391 dark: Vec::new(),
392 }
393 }
394
395 #[test]
396 fn material_package_round_trip() {
397 let mp = sample_material_package();
398 let bytes = mp.to_bytes();
399 let parsed = MaterialPackage::parse(&bytes).unwrap();
400 assert_eq!(parsed, mp);
401 assert_eq!(parsed.to_bytes(), bytes);
402 }
403
404 #[test]
405 fn material_package_no_name_round_trip() {
406 let mut mp = sample_material_package();
407 mp.name = None;
408 let bytes = mp.to_bytes();
409 let parsed = MaterialPackage::parse(&bytes).unwrap();
410 assert_eq!(parsed.name, None);
411 assert_eq!(parsed.to_bytes(), bytes);
412 }
413
414 #[test]
415 fn material_package_dark_preserved() {
416 let mut mp = sample_material_package();
417 mp.dark = alloc::vec![(0x9001, alloc::vec![1, 2, 3])];
418 let bytes = mp.to_bytes();
419 let parsed = MaterialPackage::parse(&bytes).unwrap();
420 assert_eq!(parsed.dark, mp.dark);
421 }
422
423 #[test]
424 fn material_package_wrong_kind_rejected() {
425 let sp = sample_source_package();
426 let bytes = sp.to_bytes();
427 assert!(matches!(
428 MaterialPackage::parse(&bytes),
429 Err(Error::KeyPrefixMismatch { .. })
430 ));
431 }
432
433 #[test]
434 fn source_package_round_trip() {
435 let sp = sample_source_package();
436 let bytes = sp.to_bytes();
437 let parsed = SourcePackage::parse(&bytes).unwrap();
438 assert_eq!(parsed, sp);
439 assert_eq!(parsed.to_bytes(), bytes);
440 }
441
442 #[test]
443 fn source_package_with_name_round_trip() {
444 let mut sp = sample_source_package();
445 sp.name = Some(String::from("File Source"));
446 let bytes = sp.to_bytes();
447 let parsed = SourcePackage::parse(&bytes).unwrap();
448 assert_eq!(parsed.name.as_deref(), Some("File Source"));
449 assert_eq!(parsed.to_bytes(), bytes);
450 }
451
452 #[test]
453 fn source_package_wrong_kind_rejected() {
454 let mp = sample_material_package();
455 let bytes = mp.to_bytes();
456 assert!(matches!(
457 SourcePackage::parse(&bytes),
458 Err(Error::KeyPrefixMismatch { .. })
459 ));
460 }
461
462 #[test]
463 fn mutation_changes_serialized_bytes() {
464 let mut mp = sample_material_package();
465 let before = mp.to_bytes();
466 mp.tracks.push([0x66; 16]);
467 let after = mp.to_bytes();
468 assert_ne!(before, after);
469 assert_eq!(MaterialPackage::parse(&after).unwrap().tracks.len(), 3);
470 }
471}