Skip to main content

st377_1/
source_clip.rs

1//! Source Clip — SMPTE ST 377-1:2019 Annex B §B.10 (`docs/st377-1.md`):
2//! a Structural Component that references a contiguous range of essence
3//! from a Source Package's Track (byte 14/15 = `0x01`/`0x11`).
4//!
5//! Inherits the Structural Component base properties (B.8): Data
6//! Definition and Duration.  Adds Start Position, Source Package ID
7//! (UMID), and Source Track ID.
8
9extern crate alloc;
10
11use alloc::vec::Vec;
12
13use broadcast_common::{Parse, Serialize};
14
15use crate::error::{Error, Result};
16use crate::local_set::{LocalSet, StructuralSetKind};
17use crate::sets::{
18    InterchangeObjectFields, LocalSetOwnedItem, collect_dark, finish_owned_set, get_optional_fixed,
19    get_required_fixed, owned_set_serialized_len, serialize_owned_set,
20};
21use crate::types::{PackageId, UlBytes};
22
23// ── Structural Component base tags (B.8) — shared with sequence.rs ──────
24
25/// Local tag: Data Definition (B.8).
26pub const TAG_DATA_DEFINITION: u16 = 0x0201;
27/// Local tag: Duration (B.8).
28pub const TAG_DURATION: u16 = 0x0202;
29
30// ── Source Clip own tags (B.10) ─────────────────────────────────────────
31
32/// Local tag: Source Package ID (B.10) — 32-byte UMID (PackageRef).
33pub const TAG_SOURCE_PACKAGE_ID: u16 = 0x1101;
34/// Local tag: Source Track ID (B.10) — UInt32.
35pub const TAG_SOURCE_TRACK_ID: u16 = 0x1102;
36/// Local tag: Start Position (B.10) — Position / Int64.
37pub const TAG_START_POSITION: u16 = 0x1201;
38
39const KNOWN_TAGS: [u16; 8] = [
40    crate::sets::TAG_INSTANCE_UID,
41    crate::sets::TAG_GENERATION_UID,
42    crate::sets::TAG_OBJECT_CLASS,
43    TAG_DATA_DEFINITION,
44    TAG_DURATION,
45    TAG_SOURCE_PACKAGE_ID,
46    TAG_SOURCE_TRACK_ID,
47    TAG_START_POSITION,
48];
49
50/// The Source Clip Set — SMPTE ST 377-1:2019 Annex B §B.10 (byte 14/15
51/// = `0x01`/`0x11`): references a contiguous span of essence from a
52/// Source Package Track, identified by its UMID, Track ID, and a start
53/// position within that track.
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct SourceClip {
56    /// Interchange Object (A.1) base properties.
57    pub interchange: InterchangeObjectFields,
58    /// Data Definition (`0x0201`, Req) — UL identifying the essence kind.
59    pub data_definition: UlBytes,
60    /// Duration (`0x0202`, Opt) — edit-unit count for this clip.
61    pub duration: Option<i64>,
62    /// Start Position (`0x1201`, Req) — the position in the referenced
63    /// Track where this clip starts.
64    pub start_position: i64,
65    /// Source Package ID (`0x1101`, Req) — the UMID of the referenced
66    /// Source Package (all-zero terminates the chain).
67    pub source_package_id: PackageId,
68    /// Source Track ID (`0x1102`, Req) — the Track ID within the
69    /// referenced Source Package.
70    pub source_track_id: u32,
71    /// Unrecognized properties preserved for round-trip fidelity.
72    pub dark: Vec<(u16, Vec<u8>)>,
73}
74
75impl<'a> Parse<'a> for SourceClip {
76    type Error = Error;
77
78    fn parse(bytes: &'a [u8]) -> Result<Self> {
79        let set = LocalSet::parse(bytes)?;
80        if set.kind() != StructuralSetKind::SourceClip {
81            return Err(Error::KeyPrefixMismatch {
82                what: "Source Clip (Table 17)",
83            });
84        }
85        let items = &set.items;
86        let interchange = InterchangeObjectFields::decode(items, "Source Clip")?;
87        let data_definition =
88            get_required_fixed::<16>(items, TAG_DATA_DEFINITION, "Data Definition", "Source Clip")?;
89        let duration =
90            get_optional_fixed::<8>(items, TAG_DURATION, "Duration")?.map(i64::from_be_bytes);
91        let start_position = i64::from_be_bytes(get_required_fixed::<8>(
92            items,
93            TAG_START_POSITION,
94            "Start Position",
95            "Source Clip",
96        )?);
97        let source_package_id = PackageId(get_required_fixed::<32>(
98            items,
99            TAG_SOURCE_PACKAGE_ID,
100            "Source Package ID",
101            "Source Clip",
102        )?);
103        let source_track_id = u32::from_be_bytes(get_required_fixed::<4>(
104            items,
105            TAG_SOURCE_TRACK_ID,
106            "Source Track ID",
107            "Source Clip",
108        )?);
109        let dark = collect_dark(items, &KNOWN_TAGS);
110
111        Ok(SourceClip {
112            interchange,
113            data_definition,
114            duration,
115            start_position,
116            source_package_id,
117            source_track_id,
118            dark,
119        })
120    }
121}
122
123impl SourceClip {
124    fn owned_items(&self) -> Vec<LocalSetOwnedItem> {
125        let mut out = Vec::new();
126        self.interchange.encode_into(&mut out);
127        out.push(LocalSetOwnedItem::fixed(
128            TAG_DATA_DEFINITION,
129            self.data_definition,
130        ));
131        if let Some(d) = self.duration {
132            out.push(LocalSetOwnedItem::fixed(TAG_DURATION, d.to_be_bytes()));
133        }
134        out.push(LocalSetOwnedItem::fixed(
135            TAG_START_POSITION,
136            self.start_position.to_be_bytes(),
137        ));
138        out.push(LocalSetOwnedItem::fixed(
139            TAG_SOURCE_PACKAGE_ID,
140            self.source_package_id.0,
141        ));
142        out.push(LocalSetOwnedItem::fixed(
143            TAG_SOURCE_TRACK_ID,
144            self.source_track_id.to_be_bytes(),
145        ));
146        out
147    }
148}
149
150impl Serialize for SourceClip {
151    type Error = Error;
152
153    fn serialized_len(&self) -> usize {
154        let (key, items) = finish_owned_set(
155            StructuralSetKind::SourceClip,
156            self.owned_items(),
157            &self.dark,
158        );
159        owned_set_serialized_len(key, &items)
160    }
161
162    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
163        let (key, items) = finish_owned_set(
164            StructuralSetKind::SourceClip,
165            self.owned_items(),
166            &self.dark,
167        );
168        serialize_owned_set(key, &items, buf)
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175
176    /// SMPTE-RP 224 Picture essence data definition UL (test placeholder).
177    const PICTURE_DD: UlBytes = [
178        0x06, 0x0E, 0x2B, 0x34, 0x04, 0x01, 0x01, 0x01, 0x01, 0x03, 0x02, 0x02, 0x01, 0x00, 0x00,
179        0x00,
180    ];
181
182    fn sample() -> SourceClip {
183        SourceClip {
184            interchange: InterchangeObjectFields {
185                instance_uid: [0x01; 16],
186                generation_uid: None,
187                object_class: None,
188            },
189            data_definition: PICTURE_DD,
190            duration: Some(250),
191            start_position: 0,
192            source_package_id: PackageId([0x02; 32]),
193            source_track_id: 1,
194            dark: Vec::new(),
195        }
196    }
197
198    #[test]
199    fn round_trip() {
200        let sc = sample();
201        let bytes = sc.to_bytes();
202        let parsed = SourceClip::parse(&bytes).unwrap();
203        assert_eq!(parsed, sc);
204        assert_eq!(parsed.to_bytes(), bytes);
205    }
206
207    #[test]
208    fn no_duration_round_trip() {
209        let mut sc = sample();
210        sc.duration = None;
211        let bytes = sc.to_bytes();
212        let parsed = SourceClip::parse(&bytes).unwrap();
213        assert_eq!(parsed.duration, None);
214        assert_eq!(parsed.to_bytes(), bytes);
215    }
216
217    #[test]
218    fn null_source_terminates_chain() {
219        let mut sc = sample();
220        sc.source_package_id = PackageId::NULL;
221        sc.source_track_id = 0;
222        let bytes = sc.to_bytes();
223        let parsed = SourceClip::parse(&bytes).unwrap();
224        assert!(parsed.source_package_id.is_null());
225    }
226
227    #[test]
228    fn dark_preserved() {
229        let mut sc = sample();
230        sc.dark = alloc::vec![(0x9003, alloc::vec![0xDE, 0xAD])];
231        let bytes = sc.to_bytes();
232        let parsed = SourceClip::parse(&bytes).unwrap();
233        assert_eq!(parsed.dark, sc.dark);
234    }
235
236    #[test]
237    fn wrong_kind_rejected() {
238        let key = LocalSet::build_key(
239            StructuralSetKind::Sequence,
240            crate::local_set::ItemLengthMode::TwoByte,
241        );
242        let set = LocalSet {
243            key,
244            items: Vec::new(),
245        };
246        let bytes = set.to_bytes();
247        assert!(matches!(
248            SourceClip::parse(&bytes),
249            Err(Error::KeyPrefixMismatch { .. })
250        ));
251    }
252
253    #[test]
254    fn mutation_changes_serialized_bytes() {
255        let mut sc = sample();
256        let before = sc.to_bytes();
257        sc.start_position = 100;
258        let after = sc.to_bytes();
259        assert_ne!(before, after);
260        assert_eq!(SourceClip::parse(&after).unwrap().start_position, 100);
261    }
262}