1use anyhow::{Context, Result};
4use std::path::PathBuf;
5
6use crate::descriptor::StreamDescriptor;
7
8#[derive(Debug, Clone)]
10pub struct PackageKnobs {
11 pub hls_base_url: Option<String>,
13 pub hls_media_sequence_number: u64,
15 pub hls_start_time_offset: Option<f64>,
17 pub create_session_keys: bool,
19 pub add_program_date_time: bool,
21 pub closed_captions: Vec<ClosedCaption>,
23 pub use_legacy_vp9_codec_string: bool,
25 pub dash_add_last_segment_number: bool,
27 pub segment_template_constant_duration: bool,
29 pub use_dovi_supplemental_codecs: bool,
31 pub mvex_before_trak: bool,
33 pub strip_parameter_set_nalus: bool,
35 pub crypt_byte_block: u8,
37 pub skip_byte_block: u8,
39 pub playready_extra_header_data: Option<String>,
41 pub hls_aes128: bool,
43 pub labeled_keys: Vec<LabeledKey>,
45 pub decrypt_input: bool,
47 pub widevine: Option<WidevineConfig>,
49 pub cpix: Option<CpixConfig>,
51 pub ignore_http_output_failures: bool,
53 pub user_agent: Option<String>,
55 pub ca_file: Option<PathBuf>,
57 pub client_cert_file: Option<PathBuf>,
59 pub client_cert_key_file: Option<PathBuf>,
61 pub client_cert_key_password: Option<String>,
63 pub disable_peer_verification: bool,
65 pub live_rewrite: bool,
67 pub descriptors: Vec<StreamDescriptor>,
69 pub max_sd_pixels: u32,
71 pub max_hd_pixels: u32,
72 pub max_uhd1_pixels: u32,
73}
74
75impl Default for PackageKnobs {
76 fn default() -> Self {
77 Self {
78 hls_base_url: None,
79 hls_media_sequence_number: 0,
80 hls_start_time_offset: None,
81 create_session_keys: false,
82 add_program_date_time: false,
83 closed_captions: Vec::new(),
84 use_legacy_vp9_codec_string: false,
85 dash_add_last_segment_number: false,
86 segment_template_constant_duration: false,
87 use_dovi_supplemental_codecs: false,
88 mvex_before_trak: false,
89 strip_parameter_set_nalus: true,
90 crypt_byte_block: 1,
91 skip_byte_block: 9,
92 playready_extra_header_data: None,
93 hls_aes128: false,
94 labeled_keys: Vec::new(),
95 decrypt_input: false,
96 widevine: None,
97 cpix: None,
98 ignore_http_output_failures: false,
99 user_agent: None,
100 ca_file: None,
101 client_cert_file: None,
102 client_cert_key_file: None,
103 client_cert_key_password: None,
104 disable_peer_verification: false,
105 live_rewrite: false,
106 descriptors: Vec::new(),
107 max_sd_pixels: 442_368,
108 max_hd_pixels: 2_073_600,
109 max_uhd1_pixels: 8_847_360,
110 }
111 }
112}
113
114#[derive(Debug, Clone)]
116pub struct ClosedCaption {
117 pub instream_id: String,
118 pub name: String,
119 pub language: Option<String>,
120 pub default: bool,
121 pub autoselect: bool,
122}
123
124impl ClosedCaption {
125 pub fn parse_list(spec: &str) -> Result<Vec<Self>> {
127 let mut out = Vec::new();
128 for channel in spec.split(';').filter(|s| !s.is_empty()) {
129 let mut cc = ClosedCaption {
130 instream_id: "CC1".into(),
131 name: "CC".into(),
132 language: None,
133 default: false,
134 autoselect: true,
135 };
136 for kv in channel.split(',') {
137 let Some((k, v)) = kv.split_once('=') else { continue };
138 match k.trim() {
139 "channel" => cc.instream_id = v.trim().to_string(),
140 "name" => cc.name = v.trim().to_string(),
141 "lang" | "language" => cc.language = Some(v.trim().to_string()),
142 "default" => cc.default = v.trim() == "yes" || v.trim() == "1",
143 "autoselect" => cc.autoselect = v.trim() != "no" && v.trim() != "0",
144 _ => {}
145 }
146 }
147 out.push(cc);
148 }
149 Ok(out)
150 }
151}
152
153#[derive(Debug, Clone)]
155pub struct LabeledKey {
156 pub label: String,
157 pub kid: [u8; 16],
158 pub key: [u8; 16],
159 pub iv: Option<[u8; 16]>,
160}
161
162impl LabeledKey {
163 pub fn parse_list(spec: &str) -> Result<Vec<Self>> {
165 let mut out = Vec::new();
166 for entry in spec.split(',') {
167 let entry = entry.trim();
168 if entry.is_empty() {
169 continue;
170 }
171 let mut label = String::new();
172 let mut kid = None;
173 let mut key = None;
174 let mut iv = None;
175 for kv in entry.split(':') {
176 let Some((k, v)) = kv.split_once('=') else { continue };
177 match k.trim() {
178 "label" => label = v.trim().to_string(),
179 "key_id" | "kid" => kid = Some(parse_hex16(v.trim())?),
180 "key" => key = Some(parse_hex16(v.trim())?),
181 "iv" => iv = Some(parse_hex16(v.trim())?),
182 _ => {}
183 }
184 }
185 anyhow::ensure!(
186 !label.is_empty() && kid.is_some() && key.is_some(),
187 "keys entry needs label, key_id, key"
188 );
189 out.push(LabeledKey { label, kid: kid.unwrap(), key: key.unwrap(), iv });
190 }
191 Ok(out)
192 }
193}
194
195#[derive(Debug, Clone)]
197pub struct WidevineConfig {
198 pub key_server_url: String,
199 pub content_id: Vec<u8>,
200 pub signer: String,
201 pub aes_signing_key: Option<Vec<u8>>,
202 pub aes_signing_iv: Option<Vec<u8>>,
203 pub rsa_signing_key_pem: Option<String>,
204 pub policy: String,
205 pub group_id: Option<Vec<u8>>,
206 pub enable_entitlement_license: bool,
207 pub decrypt: bool,
208}
209
210#[derive(Debug, Clone)]
212pub struct CpixConfig {
213 pub path_or_url: String,
214 pub headers: Vec<(String, String)>,
215 pub private_key_pem: Option<String>,
216 pub request_file: Option<PathBuf>,
217 pub encrypt: bool,
218 pub decrypt: bool,
219}
220
221pub(crate) fn parse_hex16(s: &str) -> Result<[u8; 16]> {
222 let s = s.trim().trim_start_matches("0x");
223 anyhow::ensure!(s.len() == 32, "expected 32 hex chars, got {}", s.len());
224 let mut out = [0u8; 16];
225 for (i, b) in out.iter_mut().enumerate() {
226 *b = u8::from_str_radix(&s[i * 2..i * 2 + 2], 16).context("non-hex digit")?;
227 }
228 Ok(out)
229}
230
231pub fn drm_label_for(
233 kind: sheathe_core::MediaKind,
234 pixels: u32,
235 knobs: &PackageKnobs,
236) -> &'static str {
237 use sheathe_core::MediaKind;
238 match kind {
239 MediaKind::Audio | MediaKind::Text => "AUDIO",
240 MediaKind::Video => {
241 if pixels <= knobs.max_sd_pixels {
242 "SD"
243 } else if pixels <= knobs.max_hd_pixels {
244 "HD"
245 } else if pixels <= knobs.max_uhd1_pixels {
246 "UHD1"
247 } else {
248 "UHD2"
249 }
250 }
251 }
252}