hdr10plus/
metadata_json.rs

1use std::{convert::TryFrom, fs::File, io::Read, path::Path};
2
3use anyhow::{Result, bail, ensure};
4use serde::{Deserialize, Serialize};
5use serde_json::{Value, json};
6
7use super::metadata::{
8    BezierCurve, DistributionMaxRgb, Hdr10PlusMetadata, PeakBrightnessSource,
9    VariablePeakBrightness,
10};
11
12#[derive(Debug, Default, Deserialize, Serialize)]
13pub struct MetadataJsonRoot {
14    #[serde(rename = "JSONInfo")]
15    pub info: JsonInfo,
16
17    #[serde(rename = "SceneInfo")]
18    pub scene_info: Vec<Hdr10PlusJsonMetadata>,
19
20    #[serde(rename = "SceneInfoSummary")]
21    pub scene_info_summary: SceneInfoSummary,
22
23    #[serde(rename = "ToolInfo")]
24    pub tool_info: ToolInfo,
25}
26
27#[derive(Debug, Default, Deserialize, Serialize)]
28pub struct JsonInfo {
29    #[serde(rename = "HDR10plusProfile")]
30    pub profile: String,
31
32    #[serde(rename = "Version")]
33    pub version: String,
34}
35
36#[derive(Debug, Default, Deserialize, Serialize)]
37#[serde(rename_all = "PascalCase")]
38pub struct Hdr10PlusJsonMetadata {
39    pub bezier_curve_data: Option<BezierCurveData>,
40    pub luminance_parameters: LuminanceParameters,
41    pub number_of_windows: u8,
42    pub targeted_system_display_maximum_luminance: u32,
43    pub scene_frame_index: usize,
44    pub scene_id: usize,
45    pub sequence_frame_index: usize,
46}
47
48#[derive(Debug, Default, Deserialize, Serialize)]
49#[serde(rename_all = "PascalCase")]
50pub struct SceneInfoSummary {
51    pub scene_first_frame_index: Vec<usize>,
52    pub scene_frame_numbers: Vec<usize>,
53}
54
55#[derive(Debug, Default, Deserialize, Serialize)]
56#[serde(rename_all = "PascalCase")]
57pub struct ToolInfo {
58    pub tool: String,
59    pub version: String,
60}
61
62#[derive(Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
63#[serde(rename_all = "PascalCase")]
64pub struct BezierCurveData {
65    pub anchors: Vec<u16>,
66    pub knee_point_x: u16,
67    pub knee_point_y: u16,
68}
69
70#[derive(Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
71#[serde(rename_all = "PascalCase")]
72pub struct LuminanceParameters {
73    #[serde(rename = "AverageRGB")]
74    pub average_rgb: u32,
75
76    pub luminance_distributions: LuminanceDistributions,
77    pub max_scl: Vec<u32>,
78}
79
80#[derive(Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
81#[serde(rename_all = "PascalCase")]
82pub struct LuminanceDistributions {
83    pub distribution_index: Vec<u8>,
84    pub distribution_values: Vec<u32>,
85}
86
87impl MetadataJsonRoot {
88    pub fn parse(str: &str) -> Result<MetadataJsonRoot> {
89        let res = serde_json::from_str::<MetadataJsonRoot>(str);
90
91        if let Ok(metadata_root) = res {
92            Ok(metadata_root)
93        } else {
94            bail!("Failed parsing JSON metadata\n{:?}", res);
95        }
96    }
97
98    pub fn from_file<P: AsRef<Path>>(input: P) -> Result<MetadataJsonRoot> {
99        let mut s = String::new();
100        File::open(input)?.read_to_string(&mut s)?;
101
102        Self::parse(&s)
103    }
104}
105
106pub fn generate_json(
107    metadata: &[&Hdr10PlusMetadata],
108    tool_name: &str,
109    tool_version: &str,
110) -> Value {
111    let (profile, frame_json_list): (String, Vec<Value>) = json_list(metadata);
112
113    let json_info = json!({
114        "HDR10plusProfile": profile,
115        "Version": format!("{}.0", &metadata[0].application_version),
116    });
117
118    let first_frames: Vec<u64> = frame_json_list
119        .iter()
120        .filter_map(|meta| {
121            if meta.get("SceneFrameIndex").unwrap().as_u64().unwrap() == 0 {
122                meta.get("SequenceFrameIndex").unwrap().as_u64()
123            } else {
124                None
125            }
126        })
127        .collect();
128
129    let mut scene_lengths: Vec<u64> = Vec::with_capacity(first_frames.len());
130
131    for i in 0..first_frames.len() {
132        if i < first_frames.len() - 1 {
133            scene_lengths.push(first_frames[i + 1] - first_frames[i]);
134        } else {
135            scene_lengths.push(frame_json_list.len() as u64 - first_frames[i]);
136        }
137    }
138
139    let scene_info_json = json!({
140        "SceneFirstFrameIndex": first_frames,
141        "SceneFrameNumbers": scene_lengths,
142    });
143
144    let final_json = json!({
145        "JSONInfo": json_info,
146        "SceneInfo": frame_json_list,
147        "SceneInfoSummary": scene_info_json,
148        "ToolInfo": json!({
149            "Tool": tool_name,
150            "Version": tool_version,
151        })
152    });
153
154    final_json
155}
156
157pub fn json_list(list: &[&Hdr10PlusMetadata]) -> (String, Vec<Value>) {
158    let profile = if list.iter().all(|m| m.profile == "B") {
159        "B"
160    } else if list.iter().all(|m| m.profile == "A") {
161        "A"
162    } else {
163        "N/A"
164    };
165
166    let mut metadata_json_array = list
167        .iter()
168        .map(|m| {
169            // Profile A, no bezier curve data
170            if profile == "A" {
171                json!({
172                    "LuminanceParameters": {
173                        "AverageRGB": m.average_maxrgb,
174                        "LuminanceDistributions": DistributionMaxRgb::separate_json(&m.distribution_maxrgb),
175                        "MaxScl": m.maxscl
176                    },
177                    "NumberOfWindows": m.num_windows,
178                    "TargetedSystemDisplayMaximumLuminance": m.targeted_system_display_maximum_luminance
179                })
180            } else { // Profile B
181                let bc = m.bezier_curve.as_ref().expect("Invalid profile B: no Bezier curve data");
182
183                json!({
184                    "BezierCurveData": bc.to_json(),
185                    "LuminanceParameters": {
186                        "AverageRGB": m.average_maxrgb,
187                        "LuminanceDistributions": DistributionMaxRgb::separate_json(&m.distribution_maxrgb),
188                        "MaxScl": m.maxscl
189                    },
190                    "NumberOfWindows": m.num_windows,
191                    "TargetedSystemDisplayMaximumLuminance": m.targeted_system_display_maximum_luminance
192                })
193            }
194        })
195        .collect::<Vec<Value>>();
196
197    compute_scene_information(profile, &mut metadata_json_array);
198
199    (profile.to_string(), metadata_json_array)
200}
201
202pub fn compute_scene_information(profile: &str, metadata_json_array: &mut [Value]) {
203    let mut scene_frame_index: u64 = 0;
204    let mut scene_id: u64 = 0;
205
206    for (sequence_frame_index, index) in (0..metadata_json_array.len()).enumerate() {
207        if index > 0 {
208            if let Some(metadata) = metadata_json_array[index].as_object() {
209                if let Some(prev_metadata) = metadata_json_array[index - 1].as_object() {
210                    // Can only be different if profile B
211                    let different_bezier = if profile == "B" {
212                        metadata.get("BezierCurveData") != prev_metadata.get("BezierCurveData")
213                    } else {
214                        false
215                    };
216
217                    let different_luminance = metadata.get("LuminanceParameters")
218                        != prev_metadata.get("LuminanceParameters");
219                    let different_windows =
220                        metadata.get("NumberOfWindows") != prev_metadata.get("NumberOfWindows");
221                    let different_target = metadata.get("TargetedSystemDisplayMaximumLuminance")
222                        != prev_metadata.get("TargetedSystemDisplayMaximumLuminance");
223
224                    if different_bezier
225                        || different_luminance
226                        || different_windows
227                        || different_target
228                    {
229                        scene_id += 1;
230                        scene_frame_index = 0;
231                    }
232                }
233            }
234        }
235
236        if let Some(map) = metadata_json_array[index].as_object_mut() {
237            map.insert("SceneFrameIndex".to_string(), json!(scene_frame_index));
238            map.insert("SceneId".to_string(), json!(scene_id));
239            map.insert(
240                "SequenceFrameIndex".to_string(),
241                json!(sequence_frame_index),
242            );
243        }
244
245        scene_frame_index += 1;
246    }
247}
248
249impl DistributionMaxRgb {
250    pub fn separate_json(list: &[Self]) -> Value {
251        json!({
252            "DistributionIndex": Self::distribution_index(list),
253            "DistributionValues": Self::distribution_values(list),
254        })
255    }
256}
257
258impl BezierCurve {
259    pub fn to_json(&self) -> Value {
260        json!({
261            "Anchors": self.bezier_curve_anchors,
262            "KneePointX": self.knee_point_x,
263            "KneePointY": self.knee_point_y
264        })
265    }
266}
267
268impl TryFrom<&Hdr10PlusJsonMetadata> for Hdr10PlusMetadata {
269    type Error = anyhow::Error;
270
271    fn try_from(jm: &Hdr10PlusJsonMetadata) -> Result<Self> {
272        let lp = &jm.luminance_parameters;
273        let dists = &lp.luminance_distributions;
274
275        ensure!(
276            lp.max_scl.len() == 3,
277            "MaxScl must contain exactly 3 elements"
278        );
279
280        let maxscl = [lp.max_scl[0], lp.max_scl[1], lp.max_scl[2]];
281
282        ensure!(
283            dists.distribution_index.len() == dists.distribution_values.len(),
284            "DistributionIndex and DistributionValue sizes don't match"
285        );
286        ensure!(
287            dists.distribution_index.len() <= 10,
288            "DistributionIndex size should be at most 10"
289        );
290        ensure!(
291            dists.distribution_values.len() <= 10,
292            "DistributionValues size should be at most 10"
293        );
294
295        let distribution_parsed = dists
296            .distribution_index
297            .iter()
298            .zip(dists.distribution_values.iter())
299            .map(|(percentage, percentile)| DistributionMaxRgb {
300                percentage: *percentage,
301                percentile: *percentile,
302            })
303            .collect();
304
305        let tone_mapping_flag = jm.bezier_curve_data.is_some();
306
307        let bezier_curve = if let Some(bcd) = &jm.bezier_curve_data {
308            let bc = BezierCurve {
309                knee_point_x: bcd.knee_point_x,
310                knee_point_y: bcd.knee_point_y,
311                num_bezier_curve_anchors: bcd.anchors.len() as u8,
312                bezier_curve_anchors: bcd.anchors.clone(),
313            };
314
315            Some(bc)
316        } else {
317            None
318        };
319
320        let mut meta = Self {
321            itu_t_t35_country_code: 0xB5,
322            itu_t_t35_terminal_provider_code: 0x3C,
323            itu_t_t35_terminal_provider_oriented_code: 1,
324            application_identifier: 4,
325            application_version: 1,
326            num_windows: jm.number_of_windows,
327            processing_windows: None,
328            targeted_system_display_maximum_luminance: jm.targeted_system_display_maximum_luminance,
329            targeted_system_display_actual_peak_luminance_flag: false,
330            actual_targeted_system_display: None,
331            maxscl,
332            average_maxrgb: lp.average_rgb,
333            num_distribution_maxrgb_percentiles: dists.distribution_index.len() as u8,
334            distribution_maxrgb: distribution_parsed,
335            fraction_bright_pixels: 0,
336            mastering_display_actual_peak_luminance_flag: false,
337            actual_mastering_display: None,
338            tone_mapping_flag,
339            bezier_curve,
340            color_saturation_mapping_flag: false,
341            color_saturation_weight: 0,
342            ..Default::default()
343        };
344
345        meta.set_profile();
346
347        Ok(meta)
348    }
349}
350
351impl VariablePeakBrightness for Hdr10PlusJsonMetadata {
352    fn peak_brightness_nits(&self, source: PeakBrightnessSource) -> Option<f64> {
353        match source {
354            PeakBrightnessSource::Histogram => self
355                .luminance_parameters
356                .luminance_distributions
357                .distribution_values
358                .iter()
359                .max()
360                .map(|e| *e as f64 / 10.0),
361            PeakBrightnessSource::Histogram99 => self
362                .luminance_parameters
363                .luminance_distributions
364                .distribution_values
365                .last()
366                .map(|e| *e as f64 / 10.0),
367            PeakBrightnessSource::MaxScl => self
368                .luminance_parameters
369                .max_scl
370                .iter()
371                .max()
372                .map(|max| *max as f64 / 10.0),
373            PeakBrightnessSource::MaxSclLuminance => {
374                if let [r, g, b] = self.luminance_parameters.max_scl.as_slice() {
375                    let r = *r as f64;
376                    let g = *g as f64;
377                    let b = *b as f64;
378
379                    let luminance = (0.2627 * r) + (0.678 * g) + (0.0593 * b);
380                    Some(luminance / 10.0)
381                } else {
382                    None
383                }
384            }
385        }
386    }
387}