1#[derive(Debug, Clone, Default)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8pub struct Level1Metadata {
9 pub min_pq: u16,
11
12 pub max_pq: u16,
14
15 pub avg_pq: u16,
17}
18
19#[derive(Debug, Clone, Default)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22pub struct Level2Metadata {
23 pub target_display_index: u8,
25
26 pub trim_slope: i16,
28
29 pub trim_offset: i16,
31
32 pub trim_power: i16,
34
35 pub trim_chroma_weight: i16,
37
38 pub trim_saturation_gain: i16,
40
41 pub ms_weight: i16,
43
44 pub target_mid_contrast: u16,
46
47 pub clip_trim: u16,
49
50 pub saturation_vector_field: Vec<SaturationVector>,
52
53 pub hue_vector_field: Vec<HueVector>,
55}
56
57#[derive(Debug, Clone, Copy, Default)]
59#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
60pub struct SaturationVector {
61 pub saturation_gain: i16,
63}
64
65#[derive(Debug, Clone, Copy, Default)]
67#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
68pub struct HueVector {
69 pub hue_angle: i16,
71}
72
73#[derive(Debug, Clone, Default)]
75#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
76pub struct Level3Metadata {
77 pub reserved: Vec<u8>,
79}
80
81#[derive(Debug, Clone, Default)]
83#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
84pub struct Level5Metadata {
85 pub active_area_left_offset: u16,
87
88 pub active_area_right_offset: u16,
90
91 pub active_area_top_offset: u16,
93
94 pub active_area_bottom_offset: u16,
96}
97
98#[derive(Debug, Clone, Default)]
100#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
101pub struct Level6Metadata {
102 pub max_cll: u16,
104
105 pub max_fall: u16,
107
108 pub min_display_mastering_luminance: u32,
110
111 pub max_display_mastering_luminance: u32,
113
114 pub master_display_primaries: [[u16; 2]; 3],
116
117 pub master_display_white_point: [u16; 2],
119}
120
121impl Level6Metadata {
122 #[must_use]
124 pub fn bt2020() -> Self {
125 Self {
126 max_cll: 1000,
127 max_fall: 400,
128 min_display_mastering_luminance: 50, max_display_mastering_luminance: 1000,
130 master_display_primaries: [
131 [34000, 16000], [13250, 34500], [7500, 3000], ],
135 master_display_white_point: [15635, 16450], }
137 }
138
139 #[must_use]
141 pub fn dci_p3() -> Self {
142 Self {
143 max_cll: 1000,
144 max_fall: 400,
145 min_display_mastering_luminance: 50,
146 max_display_mastering_luminance: 1000,
147 master_display_primaries: [
148 [34000, 15500], [16500, 35000], [7500, 3000], ],
152 master_display_white_point: [15700, 17850], }
154 }
155}
156
157#[derive(Debug, Clone, Default)]
159#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
160pub struct Level8Metadata {
161 pub target_display_index: u8,
163
164 pub target_max_pq: u16,
166
167 pub target_min_pq: u16,
169
170 pub target_primary_index: u8,
172
173 pub target_eotf: u8,
175
176 pub diagonal_size: u16,
178
179 pub peak_luminance: u16,
181
182 pub diffuse_white_luminance: u16,
184
185 pub ambient_luminance: u16,
187
188 pub surround_reflection: u16,
190}
191
192impl Level8Metadata {
193 #[must_use]
195 pub fn hdr_1000() -> Self {
196 Self {
197 target_display_index: 0,
198 target_max_pq: 3696, target_min_pq: 62, target_primary_index: 0,
201 target_eotf: 1, diagonal_size: 65,
203 peak_luminance: 1000,
204 diffuse_white_luminance: 200,
205 ambient_luminance: 5,
206 surround_reflection: 10,
207 }
208 }
209
210 #[must_use]
212 pub fn hdr_4000() -> Self {
213 Self {
214 target_display_index: 1,
215 target_max_pq: 4079, target_min_pq: 62,
217 target_primary_index: 0,
218 target_eotf: 1, diagonal_size: 65,
220 peak_luminance: 4000,
221 diffuse_white_luminance: 200,
222 ambient_luminance: 5,
223 surround_reflection: 10,
224 }
225 }
226
227 #[must_use]
229 pub fn hlg() -> Self {
230 Self {
231 target_display_index: 2,
232 target_max_pq: 2081, target_min_pq: 0,
234 target_primary_index: 0,
235 target_eotf: 2, diagonal_size: 55,
237 peak_luminance: 1000,
238 diffuse_white_luminance: 100,
239 ambient_luminance: 5,
240 surround_reflection: 10,
241 }
242 }
243}
244
245#[derive(Debug, Clone, Default)]
247#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
248pub struct Level9Metadata {
249 pub source_primary_index: u8,
251
252 pub source_max_pq: u16,
254
255 pub source_min_pq: u16,
257
258 pub source_diagonal: u16,
260}
261
262impl Level9Metadata {
263 #[must_use]
265 pub fn bt2020_mastering() -> Self {
266 Self {
267 source_primary_index: 0,
268 source_max_pq: 3696, source_min_pq: 62, source_diagonal: 65,
271 }
272 }
273
274 #[must_use]
276 pub fn dci_p3_mastering() -> Self {
277 Self {
278 source_primary_index: 1,
279 source_max_pq: 3696,
280 source_min_pq: 62,
281 source_diagonal: 65,
282 }
283 }
284}
285
286#[derive(Debug, Clone, Default, PartialEq)]
293#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
294pub struct Level4Metadata {
295 pub anchor_pq: u16,
297 pub anchor_power: u16,
299 pub active_area_flag: bool,
301}
302
303impl Level4Metadata {
304 #[must_use]
306 pub fn sdr_anchor() -> Self {
307 Self {
308 anchor_pq: nits_to_pq(100),
309 anchor_power: 1 << 12, active_area_flag: false,
311 }
312 }
313
314 #[must_use]
316 pub fn hdr_anchor() -> Self {
317 Self {
318 anchor_pq: nits_to_pq(1000),
319 anchor_power: 1 << 12,
320 active_area_flag: false,
321 }
322 }
323
324 #[must_use]
326 pub fn custom_anchor(nits: u16, power_gain: f32) -> Self {
327 let anchor_power = (power_gain * (1 << 12) as f32).clamp(0.0, 65535.0) as u16;
328 Self {
329 anchor_pq: nits_to_pq(nits),
330 anchor_power,
331 active_area_flag: false,
332 }
333 }
334
335 #[must_use]
337 pub fn anchor_power_f32(&self) -> f32 {
338 f32::from(self.anchor_power) / (1 << 12) as f32
339 }
340
341 #[must_use]
343 pub fn anchor_nits(&self) -> u16 {
344 pq_to_nits(self.anchor_pq)
345 }
346
347 #[must_use]
351 pub fn validate(&self) -> Vec<String> {
352 let mut errors = Vec::new();
353 if self.anchor_pq > 4095 {
354 errors.push(format!("anchor_pq {} exceeds maximum 4095", self.anchor_pq));
355 }
356 if self.anchor_power == 0 {
357 errors.push("anchor_power must be > 0".to_string());
358 }
359 errors
360 }
361}
362
363#[derive(Debug, Clone, Default, PartialEq)]
369#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
370pub struct Level7Metadata {
371 pub source_min_pq: u16,
373 pub source_max_pq: u16,
375 pub source_primary_index: u8,
377 pub red_primary: [u16; 2],
379 pub green_primary: [u16; 2],
381 pub blue_primary: [u16; 2],
383 pub white_point: [u16; 2],
385}
386
387impl Level7Metadata {
388 #[must_use]
390 pub fn bt2020_1000nits() -> Self {
391 Self {
392 source_min_pq: 62, source_max_pq: nits_to_pq(1000),
394 source_primary_index: 0,
395 red_primary: [34000, 16000], green_primary: [13250, 34500], blue_primary: [7500, 3000], white_point: [15635, 16450], }
400 }
401
402 #[must_use]
404 pub fn bt2020_4000nits() -> Self {
405 Self {
406 source_min_pq: 62,
407 source_max_pq: nits_to_pq(4000),
408 source_primary_index: 0,
409 red_primary: [34000, 16000],
410 green_primary: [13250, 34500],
411 blue_primary: [7500, 3000],
412 white_point: [15635, 16450],
413 }
414 }
415
416 #[must_use]
418 pub fn dci_p3_1000nits() -> Self {
419 Self {
420 source_min_pq: 62,
421 source_max_pq: nits_to_pq(1000),
422 source_primary_index: 1,
423 red_primary: [34000, 15500], green_primary: [16500, 35000], blue_primary: [7500, 3000], white_point: [15635, 16450], }
428 }
429
430 #[must_use]
432 pub fn bt709_100nits() -> Self {
433 Self {
434 source_min_pq: 62,
435 source_max_pq: nits_to_pq(100),
436 source_primary_index: 2,
437 red_primary: [32000, 16500], green_primary: [15000, 30000], blue_primary: [7500, 3000], white_point: [15635, 16450], }
442 }
443
444 #[must_use]
446 pub fn source_min_nits(&self) -> u16 {
447 pq_to_nits(self.source_min_pq)
448 }
449
450 #[must_use]
452 pub fn source_max_nits(&self) -> u16 {
453 pq_to_nits(self.source_max_pq)
454 }
455
456 #[must_use]
458 pub fn primary_name(&self) -> &str {
459 match self.source_primary_index {
460 0 => "BT.2020",
461 1 => "DCI-P3",
462 2 => "BT.709",
463 _ => "Unknown",
464 }
465 }
466
467 #[must_use]
469 pub fn validate(&self) -> Vec<String> {
470 let mut errors = Vec::new();
471 if self.source_min_pq > 4095 {
472 errors.push(format!(
473 "source_min_pq {} exceeds maximum 4095",
474 self.source_min_pq
475 ));
476 }
477 if self.source_max_pq > 4095 {
478 errors.push(format!(
479 "source_max_pq {} exceeds maximum 4095",
480 self.source_max_pq
481 ));
482 }
483 if self.source_min_pq >= self.source_max_pq {
484 errors.push(format!(
485 "source_min_pq ({}) >= source_max_pq ({})",
486 self.source_min_pq, self.source_max_pq
487 ));
488 }
489 if self.source_primary_index > 2 {
490 errors.push(format!(
491 "source_primary_index {} is out of range (0-2)",
492 self.source_primary_index
493 ));
494 }
495 errors
496 }
497}
498
499#[derive(Debug, Clone, Default)]
501#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
502pub struct Level10Metadata {
503 pub reserved: Vec<u8>,
505}
506
507#[derive(Debug, Clone, Default)]
509#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
510pub struct Level11Metadata {
511 pub content_type: ContentType,
513
514 pub whitepoint: u8,
516
517 pub reference_mode_flag: bool,
519
520 pub sharpness: u8,
522
523 pub noise_reduction: u8,
525
526 pub mpeg_noise_reduction: u8,
528
529 pub frame_rate: u8,
531
532 pub temporal_filter_strength: u8,
534}
535
536#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
538#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
539pub enum ContentType {
540 #[default]
542 Unknown = 0,
543
544 Movie = 1,
546
547 Tv = 2,
549
550 Sports = 3,
552
553 Gaming = 4,
555
556 Animation = 5,
558}
559
560impl ContentType {
561 #[must_use]
563 pub const fn from_u8(value: u8) -> Self {
564 match value {
565 1 => Self::Movie,
566 2 => Self::Tv,
567 3 => Self::Sports,
568 4 => Self::Gaming,
569 5 => Self::Animation,
570 _ => Self::Unknown,
571 }
572 }
573}
574
575#[derive(Debug, Clone, Default)]
577#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
578pub struct ContentMetadataDescriptor {
579 pub title: Option<String>,
581
582 pub description: Option<String>,
584
585 pub language: Option<String>,
587
588 pub creation_date: Option<String>,
590
591 pub creator: Option<String>,
593
594 pub copyright: Option<String>,
596
597 pub additional_metadata: Vec<(String, String)>,
599}
600
601impl ContentMetadataDescriptor {
602 #[must_use]
604 pub const fn new() -> Self {
605 Self {
606 title: None,
607 description: None,
608 language: None,
609 creation_date: None,
610 creator: None,
611 copyright: None,
612 additional_metadata: Vec::new(),
613 }
614 }
615
616 pub fn add_metadata(&mut self, key: String, value: String) {
618 self.additional_metadata.push((key, value));
619 }
620}
621
622#[derive(Debug, Clone, Default)]
624#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
625pub struct TrimPass {
626 pub target_max_pq: u16,
628
629 pub target_min_pq: u16,
631
632 pub trim_slope: i16,
634
635 pub trim_offset: i16,
637
638 pub trim_power: i16,
640
641 pub trim_chroma_weight: i16,
643
644 pub trim_saturation_gain: i16,
646
647 pub ms_weight: i16,
649}
650
651impl TrimPass {
652 #[must_use]
654 pub fn identity() -> Self {
655 Self {
656 target_max_pq: 4095,
657 target_min_pq: 0,
658 trim_slope: 1 << 12,
659 trim_offset: 0,
660 trim_power: 1 << 12,
661 trim_chroma_weight: 1 << 12,
662 trim_saturation_gain: 1 << 12,
663 ms_weight: 1 << 12,
664 }
665 }
666
667 #[must_use]
669 pub fn for_peak_brightness(target_nits: u16) -> Self {
670 let target_max_pq = nits_to_pq(target_nits);
672
673 Self {
674 target_max_pq,
675 target_min_pq: 62, trim_slope: 1 << 12,
677 trim_offset: 0,
678 trim_power: 1 << 12,
679 trim_chroma_weight: 1 << 12,
680 trim_saturation_gain: 1 << 12,
681 ms_weight: 1 << 12,
682 }
683 }
684}
685
686#[must_use]
688pub fn nits_to_pq(nits: u16) -> u16 {
689 const M1: f64 = 0.159_301_758_113_479_8;
690 const M2: f64 = 78.843_750;
691 const C1: f64 = 0.835_937_5;
692 const C2: f64 = 18.851_562_5;
693 const C3: f64 = 18.6875;
694
695 let y = f64::from(nits) / 10_000.0;
696 let y_m1 = y.powf(M1);
697 let pq = ((C1 + C2 * y_m1) / (1.0 + C3 * y_m1)).powf(M2);
698
699 (pq * 4095.0).min(4095.0) as u16
700}
701
702#[must_use]
704#[allow(dead_code)]
705pub fn pq_to_nits(pq: u16) -> u16 {
706 const M1_INV: f64 = 1.0 / 0.159_301_758_113_479_8;
707 const M2_INV: f64 = 1.0 / 78.843_750;
708 const C1: f64 = 0.835_937_5;
709 const C2: f64 = 18.851_562_5;
710 const C3: f64 = 18.6875;
711
712 let pq_norm = f64::from(pq) / 4095.0;
713 let v = pq_norm.powf(M2_INV);
714 let y = ((v - C1).max(0.0) / (C2 - C3 * v)).powf(M1_INV);
715
716 (y * 10_000.0).min(10_000.0) as u16
717}
718
719#[derive(Debug, Clone, Default)]
721#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
722pub struct MetadataBlock {
723 pub block_id: u8,
725
726 pub block_length: u16,
728
729 pub block_data: Vec<u8>,
731}
732
733#[derive(Debug, Clone, Default)]
735#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
736pub struct ColorVolumeTransform {
737 pub lut_size: u8,
739
740 pub lut_data: Vec<[u16; 3]>,
742}
743
744impl ColorVolumeTransform {
745 #[must_use]
747 pub fn identity(size: u8) -> Self {
748 let total_points = usize::from(size) * usize::from(size) * usize::from(size);
749 let mut lut_data = Vec::with_capacity(total_points);
750
751 for r in 0..size {
752 for g in 0..size {
753 for b in 0..size {
754 let scale = 4095_u16 / u16::from(size - 1);
755 lut_data.push([
756 u16::from(r) * scale,
757 u16::from(g) * scale,
758 u16::from(b) * scale,
759 ]);
760 }
761 }
762 }
763
764 Self {
765 lut_size: size,
766 lut_data,
767 }
768 }
769}
770
771#[cfg(test)]
772mod tests {
773 use super::*;
774
775 #[test]
776 fn test_content_type() {
777 assert_eq!(ContentType::from_u8(0), ContentType::Unknown);
778 assert_eq!(ContentType::from_u8(1), ContentType::Movie);
779 assert_eq!(ContentType::from_u8(2), ContentType::Tv);
780 assert_eq!(ContentType::from_u8(3), ContentType::Sports);
781 assert_eq!(ContentType::from_u8(99), ContentType::Unknown);
782 }
783
784 #[test]
785 fn test_level6_presets() {
786 let bt2020 = Level6Metadata::bt2020();
787 assert_eq!(bt2020.max_cll, 1000);
788 assert_eq!(bt2020.master_display_primaries[0][0], 34000);
789
790 let dci_p3 = Level6Metadata::dci_p3();
791 assert_eq!(dci_p3.max_cll, 1000);
792 }
793
794 #[test]
795 fn test_level8_presets() {
796 let hdr1000 = Level8Metadata::hdr_1000();
797 assert_eq!(hdr1000.peak_luminance, 1000);
798 assert_eq!(hdr1000.target_eotf, 1);
799
800 let hdr4000 = Level8Metadata::hdr_4000();
801 assert_eq!(hdr4000.peak_luminance, 4000);
802
803 let hlg = Level8Metadata::hlg();
804 assert_eq!(hlg.target_eotf, 2);
805 }
806
807 #[test]
808 fn test_level9_presets() {
809 let bt2020 = Level9Metadata::bt2020_mastering();
810 assert_eq!(bt2020.source_primary_index, 0);
811
812 let p3 = Level9Metadata::dci_p3_mastering();
813 assert_eq!(p3.source_primary_index, 1);
814 }
815
816 #[test]
817 fn test_nits_pq_conversion() {
818 let pq_100 = nits_to_pq(100);
819 let nits_100 = pq_to_nits(pq_100);
820 assert!((nits_100 as i32 - 100).abs() <= 5);
821
822 let pq_1000 = nits_to_pq(1000);
823 let nits_1000 = pq_to_nits(pq_1000);
824 assert!((nits_1000 as i32 - 1000).abs() <= 50);
825
826 let pq_10000 = nits_to_pq(10000);
827 assert_eq!(pq_10000, 4095);
828 }
829
830 #[test]
831 fn test_trim_pass() {
832 let identity = TrimPass::identity();
833 assert_eq!(identity.target_max_pq, 4095);
834 assert_eq!(identity.trim_slope, 1 << 12);
835
836 let trim_1000 = TrimPass::for_peak_brightness(1000);
837 assert!(trim_1000.target_max_pq > 0);
838 assert!(trim_1000.target_max_pq < 4095);
839 }
840
841 #[test]
842 fn test_cmd() {
843 let mut cmd = ContentMetadataDescriptor::new();
844 cmd.add_metadata("key1".to_string(), "value1".to_string());
845 assert_eq!(cmd.additional_metadata.len(), 1);
846 assert_eq!(cmd.additional_metadata[0].0, "key1");
847 }
848
849 #[test]
850 fn test_color_volume_transform() {
851 let cvt = ColorVolumeTransform::identity(5);
852 assert_eq!(cvt.lut_size, 5);
853 assert_eq!(cvt.lut_data.len(), 125);
854 }
855}