sheathe_package/
scte35.rs1#[derive(Debug, Clone)]
11pub struct Scte35Marker {
12 pub time_seconds: f64,
14 pub out_of_network: bool,
16 pub event_id: Option<u32>,
18 pub break_duration_seconds: Option<f64>,
20}
21
22impl Scte35Marker {
23 pub fn cue_out(time_seconds: f64) -> Self {
25 Self { time_seconds, out_of_network: true, event_id: None, break_duration_seconds: None }
26 }
27
28 pub fn cue_in(time_seconds: f64) -> Self {
30 Self { time_seconds, out_of_network: false, event_id: None, break_duration_seconds: None }
31 }
32}
33
34pub fn build_splice_insert(marker: &Scte35Marker) -> Vec<u8> {
40 let event_id = marker.event_id.unwrap_or_else(|| {
41 (marker.time_seconds * 1000.0).round() as u32
43 });
44 let pts = (marker.time_seconds * 90_000.0).round() as u64 & 0x1_ffff_ffff; let mut body = Vec::new();
48 body.extend_from_slice(&event_id.to_be_bytes());
50 body.push(0x7f);
52 let duration_flag = marker.break_duration_seconds.is_some() && marker.out_of_network;
55 let mut flags: u8 = 0b0100_1111; if marker.out_of_network {
57 flags |= 0b1000_0000;
58 }
59 if duration_flag {
60 flags |= 0b0010_0000;
61 }
62 body.push(flags);
63
64 let time_word: u64 = (1u64 << 39) | (0x3f << 33) | pts;
67 body.push(((time_word >> 32) & 0xff) as u8);
68 body.push(((time_word >> 24) & 0xff) as u8);
69 body.push(((time_word >> 16) & 0xff) as u8);
70 body.push(((time_word >> 8) & 0xff) as u8);
71 body.push((time_word & 0xff) as u8);
72
73 if duration_flag {
74 let dur_pts = (marker.break_duration_seconds.unwrap_or(0.0) * 90_000.0).round() as u64
76 & 0x1_ffff_ffff;
77 let dur_word: u64 = (1u64 << 39) | (0x3f << 33) | dur_pts;
78 body.push(((dur_word >> 32) & 0xff) as u8);
79 body.push(((dur_word >> 24) & 0xff) as u8);
80 body.push(((dur_word >> 16) & 0xff) as u8);
81 body.push(((dur_word >> 8) & 0xff) as u8);
82 body.push((dur_word & 0xff) as u8);
83 }
84
85 body.extend_from_slice(&0u16.to_be_bytes());
87 body.push(0);
88 body.push(0);
89
90 let command_type: u8 = 5; let command_len = body.len() as u16;
99 let section_payload_len = 1 + 5 + 1 + 3 + 1 + body.len() + 2;
103 let section_length = (section_payload_len + 4) as u16; let mut section = Vec::with_capacity(3 + section_payload_len + 4);
106 section.push(0xFC); let sl = (0b0011_0000_0000_0000) | (section_length & 0x0fff);
109 section.extend_from_slice(&sl.to_be_bytes());
110 section.push(0); section.extend_from_slice(&[0x00, 0x00, 0x00, 0x00, 0x00]);
114 section.push(0); let tier_cmd = (0x0fffu32 << 12) | (u32::from(command_len) & 0x0fff);
117 section.push(((tier_cmd >> 16) & 0xff) as u8);
118 section.push(((tier_cmd >> 8) & 0xff) as u8);
119 section.push((tier_cmd & 0xff) as u8);
120 section.push(command_type);
121 section.extend_from_slice(&body);
122 section.extend_from_slice(&0u16.to_be_bytes()); let crc = mpeg_crc32(§ion);
125 section.extend_from_slice(&crc.to_be_bytes());
126 section
127}
128
129pub fn to_hex_0x(bytes: &[u8]) -> String {
131 let mut s = String::from("0x");
132 for b in bytes {
133 s.push_str(&format!("{b:02X}"));
134 }
135 s
136}
137
138pub fn to_base64(bytes: &[u8]) -> String {
140 const T: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
142 let mut out = String::new();
143 let mut i = 0;
144 while i + 3 <= bytes.len() {
145 let n =
146 (u32::from(bytes[i]) << 16) | (u32::from(bytes[i + 1]) << 8) | u32::from(bytes[i + 2]);
147 out.push(T[((n >> 18) & 63) as usize] as char);
148 out.push(T[((n >> 12) & 63) as usize] as char);
149 out.push(T[((n >> 6) & 63) as usize] as char);
150 out.push(T[(n & 63) as usize] as char);
151 i += 3;
152 }
153 let rest = bytes.len() - i;
154 if rest == 1 {
155 let n = u32::from(bytes[i]) << 16;
156 out.push(T[((n >> 18) & 63) as usize] as char);
157 out.push(T[((n >> 12) & 63) as usize] as char);
158 out.push('=');
159 out.push('=');
160 } else if rest == 2 {
161 let n = (u32::from(bytes[i]) << 16) | (u32::from(bytes[i + 1]) << 8);
162 out.push(T[((n >> 18) & 63) as usize] as char);
163 out.push(T[((n >> 12) & 63) as usize] as char);
164 out.push(T[((n >> 6) & 63) as usize] as char);
165 out.push('=');
166 }
167 out
168}
169
170fn mpeg_crc32(data: &[u8]) -> u32 {
172 let mut crc: u32 = 0xffff_ffff;
173 for &byte in data {
174 crc ^= u32::from(byte) << 24;
175 for _ in 0..8 {
176 if crc & 0x8000_0000 != 0 {
177 crc = (crc << 1) ^ 0x04C1_1DB7;
178 } else {
179 crc <<= 1;
180 }
181 }
182 }
183 crc
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn splice_insert_has_table_id_and_crc_length() {
192 let bytes = build_splice_insert(&Scte35Marker::cue_out(12.0));
193 assert_eq!(bytes[0], 0xFC);
194 let section_length = u16::from_be_bytes([bytes[1] & 0x0f, bytes[2]]) as usize;
196 assert_eq!(bytes.len(), 3 + section_length);
197 assert!(bytes.contains(&5));
199 }
200
201 #[test]
202 fn hex_and_base64_round_shapes() {
203 let bytes = build_splice_insert(&Scte35Marker::cue_in(30.0));
204 let hex = to_hex_0x(&bytes);
205 assert!(hex.starts_with("0xFC"));
206 let b64 = to_base64(&bytes);
207 assert!(!b64.is_empty());
208 assert!(b64.is_ascii());
209 }
210}