1use crate::{
13 bail, data,
14 error::Result,
15 parser,
16 parser::{Mp4Parser, ParsedBox},
17 pssh::{playready, widevine},
18};
19use base64::Engine;
20
21const COMMON_SYSTEM_ID: &str = "1077efecc0b24d02ace33c1e52e2fb4b";
22const PLAYREADY_SYSTEM_ID: &str = "9a04f07998404286ab92e65be0885f95";
23const WIDEVINE_SYSTEM_ID: &str = "edef8ba979d64acea3c827dcd51d21ed";
24
25#[derive(Debug)]
27pub struct PsshBox {
28 pub boxes: Vec<PsshData>,
30}
31
32impl PsshBox {
33 pub fn from_init(data: &[u8]) -> Result<Self> {
40 let boxes = data!(Vec::new());
41
42 Mp4Parser::new()
43 .base_box("moov", parser::children)
44 .base_box("moof", parser::children)
45 .full_box("pssh", {
46 let boxes = boxes.clone();
47 move |mut box_| {
48 Self::parse(&mut box_, &mut boxes.borrow_mut())?;
49 Ok(())
50 }
51 })
52 .parse(data, false, false)?;
53
54 Ok(Self {
55 boxes: boxes.take(),
56 })
57 }
58
59 fn parse(box_: &mut ParsedBox, boxes: &mut Vec<PsshData>) -> Result<()> {
60 let Some(box_version) = box_.version else {
61 bail!("PSSH boxes are full boxes and must have a valid version.");
62 };
63
64 if box_.flags.is_none() {
65 bail!("PSSH boxes are full boxes and must have a valid flag.");
66 }
67
68 if box_version > 1 {
69 bail!("Unrecognized PSSH version found!");
70 }
71
72 let system_id = hex::encode(box_.reader.read_bytes_u8(16)?);
73
74 if box_version > 0 {
75 let mut data = PsshData {
76 data: box_.full_data(),
77 key_ids: Vec::new(),
78 system_id: if system_id == COMMON_SYSTEM_ID {
79 SystemId::Common
80 } else {
81 SystemId::Other(system_id.to_owned())
82 },
83 };
84 let num_key_ids = box_.reader.read_u32()?;
85
86 for _ in 0..num_key_ids {
87 let key_id = hex::encode(box_.reader.read_bytes_u8(16)?);
88 data.key_ids.push(key_id);
89 }
90
91 boxes.push(data);
92 }
93
94 let pssh_data_size = box_.reader.read_u32()?;
95 let pssh_data = box_.reader.read_bytes_u8(pssh_data_size as usize)?;
96 let mut key_ids = Vec::new();
97
98 match system_id.as_str() {
99 PLAYREADY_SYSTEM_ID => key_ids = playready::parse_key_ids(&pssh_data)?,
100 WIDEVINE_SYSTEM_ID => key_ids = widevine::parse_key_ids(&pssh_data)?,
101 _ => (),
102 }
103
104 boxes.push(PsshData {
105 data: box_.full_data(),
106 key_ids,
107 system_id: match system_id.as_str() {
108 PLAYREADY_SYSTEM_ID => SystemId::PlayReady,
109 WIDEVINE_SYSTEM_ID => SystemId::WideVine,
110 _ => SystemId::Other(system_id.to_owned()),
111 },
112 });
113 Ok(())
114 }
115}
116
117#[derive(Debug, Clone, PartialEq)]
119pub enum SystemId {
120 Common,
122 Other(String),
124 PlayReady,
126 WideVine,
128}
129
130impl std::fmt::Display for SystemId {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 write!(
133 f,
134 "{}",
135 match self {
136 SystemId::Common => "cen",
137 SystemId::Other(x) => x,
138 SystemId::PlayReady => "prd",
139 SystemId::WideVine => "wvd",
140 }
141 )
142 }
143}
144
145#[derive(Debug, Clone)]
147pub struct PsshData {
148 pub data: Vec<u8>,
150 pub key_ids: Vec<String>,
152 pub system_id: SystemId,
154}
155
156impl PartialEq for PsshData {
157 fn eq(&self, other: &Self) -> bool {
158 self.data == other.data
159 }
160}
161
162impl PsshData {
163 pub fn as_base64(&self) -> String {
165 base64::engine::general_purpose::STANDARD.encode(&self.data)
166 }
167}