Skip to main content

oximedia_dolbyvision/
lib.rs

1//! Dolby Vision RPU (Reference Processing Unit) metadata parser and writer.
2//!
3//! This crate provides **metadata-only** Dolby Vision support, respecting Dolby's intellectual property.
4//! It can parse and generate RPU metadata structures but does not implement proprietary encoding algorithms.
5//!
6//! # Supported Profiles
7//!
8//! - **Profile 5**: IPT-PQ, backward compatible with HDR10
9//! - **Profile 7**: MEL (Metadata Enhancement Layer) + BL (Base Layer), single track
10//! - **Profile 8**: BL only, backward compatible with HDR10
11//! - **Profile 8.1**: Low-latency variant of Profile 8
12//! - **Profile 8.4**: HLG-based, backward compatible with HLG
13//!
14//! # Examples
15//!
16//! ```rust
17//! use oximedia_dolbyvision::{DolbyVisionRpu, Profile};
18//!
19//! // Create new RPU for Profile 8.4
20//! let rpu = DolbyVisionRpu::new(Profile::Profile8_4);
21//! assert_eq!(rpu.profile, Profile::Profile8_4);
22//! ```
23
24#![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
62// Re-export types, but avoid ambiguous glob re-exports
63pub 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/// Errors that can occur during Dolby Vision RPU processing.
80#[derive(Debug, Error)]
81pub enum DolbyVisionError {
82    /// Invalid RPU header
83    #[error("Invalid RPU header: {0}")]
84    InvalidHeader(String),
85
86    /// Invalid RPU payload
87    #[error("Invalid RPU payload: {0}")]
88    InvalidPayload(String),
89
90    /// Unsupported Dolby Vision profile
91    #[error("Unsupported profile: {0}")]
92    UnsupportedProfile(u8),
93
94    /// Invalid NAL unit
95    #[error("Invalid NAL unit: {0}")]
96    InvalidNalUnit(String),
97
98    /// CRC mismatch
99    #[error("CRC mismatch: expected {expected:#x}, got {actual:#x}")]
100    CrcMismatch {
101        /// Expected CRC value
102        expected: u32,
103        /// Actual CRC value
104        actual: u32,
105    },
106
107    /// I/O error
108    #[error("I/O error: {0}")]
109    Io(#[from] std::io::Error),
110
111    /// Generic error
112    #[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
122/// Result type for Dolby Vision operations.
123pub type Result<T> = std::result::Result<T, DolbyVisionError>;
124
125/// Dolby Vision profile identifier.
126#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
127#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
128pub enum Profile {
129    /// Profile 5: IPT-PQ, backward compatible with HDR10
130    Profile5 = 5,
131    /// Profile 7: MEL + BL, single track, full enhancement
132    Profile7 = 7,
133    /// Profile 8: BL only, backward compatible with HDR10
134    Profile8 = 8,
135    /// Profile 8.1: Low-latency variant
136    Profile8_1 = 81,
137    /// Profile 8.4: HLG-based, backward compatible with HLG
138    Profile8_4 = 84,
139}
140
141impl Profile {
142    /// Create profile from numeric value.
143    #[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    /// Check if profile requires backward compatibility.
156    #[must_use]
157    pub const fn is_backward_compatible(self) -> bool {
158        matches!(self, Self::Profile5 | Self::Profile8 | Self::Profile8_4)
159    }
160
161    /// Check if profile supports MEL (Metadata Enhancement Layer).
162    #[must_use]
163    pub const fn has_mel(self) -> bool {
164        matches!(self, Self::Profile7)
165    }
166
167    /// Check if profile uses HLG transfer function.
168    #[must_use]
169    pub const fn is_hlg(self) -> bool {
170        matches!(self, Self::Profile8_4)
171    }
172
173    /// Check if profile is low-latency variant.
174    #[must_use]
175    pub const fn is_low_latency(self) -> bool {
176        matches!(self, Self::Profile8_1)
177    }
178}
179
180/// Main Dolby Vision RPU (Reference Processing Unit) structure.
181///
182/// Contains all metadata required for Dolby Vision HDR display mapping.
183#[derive(Debug, Clone)]
184#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
185pub struct DolbyVisionRpu {
186    /// Dolby Vision profile
187    pub profile: Profile,
188
189    /// RPU header
190    pub header: RpuHeader,
191
192    /// VDR (Vizio Display Management) metadata
193    pub vdr_dm_data: Option<VdrDmData>,
194
195    /// Level 1 metadata (frame-level)
196    pub level1: Option<Level1Metadata>,
197
198    /// Level 2 metadata (trim passes)
199    pub level2: Option<Level2Metadata>,
200
201    /// Level 4 metadata (global dimming)
202    pub level4: Option<Level4Metadata>,
203
204    /// Level 5 metadata (active area)
205    pub level5: Option<Level5Metadata>,
206
207    /// Level 6 metadata (fallback)
208    pub level6: Option<Level6Metadata>,
209
210    /// Level 7 metadata (source display color volume)
211    pub level7: Option<Level7Metadata>,
212
213    /// Level 8 metadata (target display)
214    pub level8: Option<Level8Metadata>,
215
216    /// Level 9 metadata (source display)
217    pub level9: Option<Level9Metadata>,
218
219    /// Level 11 metadata (content type)
220    pub level11: Option<Level11Metadata>,
221}
222
223impl DolbyVisionRpu {
224    /// Create a new RPU with default values for the given profile.
225    #[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    /// Parse RPU from NAL unit bytes (including NAL header).
244    ///
245    /// # Errors
246    ///
247    /// Returns error if NAL unit is invalid or RPU parsing fails.
248    pub fn parse_from_nal(data: &[u8]) -> Result<Self> {
249        parser::parse_nal_unit(data)
250    }
251
252    /// Parse RPU from raw bitstream (without NAL wrapper).
253    ///
254    /// # Errors
255    ///
256    /// Returns error if RPU parsing fails.
257    pub fn parse_from_bitstream(data: &[u8]) -> Result<Self> {
258        parser::parse_rpu_bitstream(data)
259    }
260
261    /// Write RPU to NAL unit bytes (including NAL header).
262    ///
263    /// # Errors
264    ///
265    /// Returns error if writing fails.
266    pub fn write_to_nal(&self) -> Result<Vec<u8>> {
267        writer::write_nal_unit(self)
268    }
269
270    /// Write RPU to raw bitstream (without NAL wrapper).
271    ///
272    /// # Errors
273    ///
274    /// Returns error if writing fails.
275    pub fn write_to_bitstream(&self) -> Result<Vec<u8>> {
276        writer::write_rpu_bitstream(self)
277    }
278
279    /// Apply tone mapping to convert from source to target display.
280    ///
281    /// # Errors
282    ///
283    /// Returns error if tone mapping fails or required metadata is missing.
284    pub fn apply_tonemap(&self, pixel_data: &mut [f32]) -> Result<()> {
285        tonemap::apply_dolbyvision_tonemap(self, pixel_data)
286    }
287
288    /// Validate RPU structure for consistency, including all metadata levels.
289    ///
290    /// # Errors
291    ///
292    /// Returns error if validation fails.
293    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}