1#![forbid(unsafe_code)]
25#![warn(missing_docs)]
26
27pub mod ambient_metadata;
28pub mod cm_analysis;
29pub mod compat;
30pub mod delivery_spec;
31pub mod display_config;
32pub mod dm_metadata;
33pub mod dv_xml_metadata;
34pub mod enhancement;
35pub mod frame_analysis;
36pub mod ipt_pq;
37pub mod level_analysis;
38pub mod level_mapping;
39pub mod mapping_curve;
40pub mod mastering;
41mod metadata;
42pub mod metadata_block;
43pub mod metadata_validator;
44#[allow(dead_code)]
45mod parser;
46pub mod profile8;
47pub mod profile_convert;
48pub mod profiles;
49mod rpu;
50pub mod scene_trim;
51pub mod shot_boundary;
52pub mod shot_metadata;
53pub mod shot_metadata_ext;
54pub mod target_display;
55pub mod tone_mapping;
56mod tonemap;
57pub mod trim_passes;
58pub mod validation;
59mod writer;
60pub mod xml_metadata;
61
62pub use metadata::{
64 ColorVolumeTransform, ContentMetadataDescriptor, ContentType, HueVector, Level10Metadata,
65 Level11Metadata, Level1Metadata, Level2Metadata, Level3Metadata, Level4Metadata,
66 Level5Metadata, Level6Metadata, Level7Metadata, Level8Metadata, Level9Metadata, MetadataBlock,
67 SaturationVector, TrimPass,
68};
69pub use rpu::*;
70pub use tonemap::{
71 apply_dolbyvision_tonemap, apply_eotf, apply_inverse_eotf, bt1886_to_linear, hlg_constants,
72 hlg_to_linear, linear_to_bt1886, linear_to_hlg, linear_to_pq, pq_constants, pq_to_linear,
73 BilateralGrid, ColorVolumeLut, ReshapingLut, TonemapParams,
74};
75
76use oximedia_core::error::OxiError;
77use thiserror::Error;
78
79#[derive(Debug, Error)]
81pub enum DolbyVisionError {
82 #[error("Invalid RPU header: {0}")]
84 InvalidHeader(String),
85
86 #[error("Invalid RPU payload: {0}")]
88 InvalidPayload(String),
89
90 #[error("Unsupported profile: {0}")]
92 UnsupportedProfile(u8),
93
94 #[error("Invalid NAL unit: {0}")]
96 InvalidNalUnit(String),
97
98 #[error("CRC mismatch: expected {expected:#x}, got {actual:#x}")]
100 CrcMismatch {
101 expected: u32,
103 actual: u32,
105 },
106
107 #[error("I/O error: {0}")]
109 Io(#[from] std::io::Error),
110
111 #[error("{0}")]
113 Generic(String),
114}
115
116impl From<DolbyVisionError> for OxiError {
117 fn from(err: DolbyVisionError) -> Self {
118 OxiError::Codec(err.to_string())
119 }
120}
121
122pub type Result<T> = std::result::Result<T, DolbyVisionError>;
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
127#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
128pub enum Profile {
129 Profile5 = 5,
131 Profile7 = 7,
133 Profile8 = 8,
135 Profile8_1 = 81,
137 Profile8_4 = 84,
139}
140
141impl Profile {
142 #[must_use]
144 pub fn from_u8(value: u8) -> Option<Self> {
145 match value {
146 5 => Some(Self::Profile5),
147 7 => Some(Self::Profile7),
148 8 => Some(Self::Profile8),
149 81 => Some(Self::Profile8_1),
150 84 => Some(Self::Profile8_4),
151 _ => None,
152 }
153 }
154
155 #[must_use]
157 pub const fn is_backward_compatible(self) -> bool {
158 matches!(self, Self::Profile5 | Self::Profile8 | Self::Profile8_4)
159 }
160
161 #[must_use]
163 pub const fn has_mel(self) -> bool {
164 matches!(self, Self::Profile7)
165 }
166
167 #[must_use]
169 pub const fn is_hlg(self) -> bool {
170 matches!(self, Self::Profile8_4)
171 }
172
173 #[must_use]
175 pub const fn is_low_latency(self) -> bool {
176 matches!(self, Self::Profile8_1)
177 }
178}
179
180#[derive(Debug, Clone)]
184#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
185pub struct DolbyVisionRpu {
186 pub profile: Profile,
188
189 pub header: RpuHeader,
191
192 pub vdr_dm_data: Option<VdrDmData>,
194
195 pub level1: Option<Level1Metadata>,
197
198 pub level2: Option<Level2Metadata>,
200
201 pub level4: Option<Level4Metadata>,
203
204 pub level5: Option<Level5Metadata>,
206
207 pub level6: Option<Level6Metadata>,
209
210 pub level7: Option<Level7Metadata>,
212
213 pub level8: Option<Level8Metadata>,
215
216 pub level9: Option<Level9Metadata>,
218
219 pub level11: Option<Level11Metadata>,
221}
222
223impl DolbyVisionRpu {
224 #[must_use]
226 pub fn new(profile: Profile) -> Self {
227 Self {
228 profile,
229 header: RpuHeader::default_for_profile(profile),
230 vdr_dm_data: None,
231 level1: None,
232 level2: None,
233 level4: None,
234 level5: None,
235 level6: None,
236 level7: None,
237 level8: None,
238 level9: None,
239 level11: None,
240 }
241 }
242
243 pub fn parse_from_nal(data: &[u8]) -> Result<Self> {
249 parser::parse_nal_unit(data)
250 }
251
252 pub fn parse_from_bitstream(data: &[u8]) -> Result<Self> {
258 parser::parse_rpu_bitstream(data)
259 }
260
261 pub fn write_to_nal(&self) -> Result<Vec<u8>> {
267 writer::write_nal_unit(self)
268 }
269
270 pub fn write_to_bitstream(&self) -> Result<Vec<u8>> {
276 writer::write_rpu_bitstream(self)
277 }
278
279 pub fn apply_tonemap(&self, pixel_data: &mut [f32]) -> Result<()> {
285 tonemap::apply_dolbyvision_tonemap(self, pixel_data)
286 }
287
288 pub fn validate(&self) -> Result<()> {
294 validation::validate_rpu(self)
295 }
296}
297
298impl Default for DolbyVisionRpu {
299 fn default() -> Self {
300 Self::new(Profile::Profile8)
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307
308 #[test]
309 fn test_profile_creation() {
310 assert_eq!(Profile::from_u8(5), Some(Profile::Profile5));
311 assert_eq!(Profile::from_u8(7), Some(Profile::Profile7));
312 assert_eq!(Profile::from_u8(8), Some(Profile::Profile8));
313 assert_eq!(Profile::from_u8(81), Some(Profile::Profile8_1));
314 assert_eq!(Profile::from_u8(84), Some(Profile::Profile8_4));
315 assert_eq!(Profile::from_u8(99), None);
316 }
317
318 #[test]
319 fn test_profile_properties() {
320 assert!(Profile::Profile5.is_backward_compatible());
321 assert!(Profile::Profile8.is_backward_compatible());
322 assert!(Profile::Profile8_4.is_backward_compatible());
323 assert!(!Profile::Profile7.is_backward_compatible());
324
325 assert!(Profile::Profile7.has_mel());
326 assert!(!Profile::Profile8.has_mel());
327
328 assert!(Profile::Profile8_4.is_hlg());
329 assert!(!Profile::Profile8.is_hlg());
330
331 assert!(Profile::Profile8_1.is_low_latency());
332 assert!(!Profile::Profile8.is_low_latency());
333 }
334
335 #[test]
336 fn test_rpu_creation() {
337 let rpu = DolbyVisionRpu::new(Profile::Profile8);
338 assert_eq!(rpu.profile, Profile::Profile8);
339 assert!(rpu.validate().is_ok());
340 }
341
342 #[test]
343 fn test_default_rpu() {
344 let rpu = DolbyVisionRpu::default();
345 assert_eq!(rpu.profile, Profile::Profile8);
346 }
347}