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 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 { 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 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}