semdiff_differ_audio/
report_json.rs1use crate::{AudioDiff, AudioDiffReporter, audio_extension};
2use semdiff_core::fs::FileLeaf;
3use semdiff_core::{DetailReporter, MayUnsupported};
4use semdiff_output::json::JsonReport;
5use serde::Serialize;
6use thiserror::Error;
7
8const COMPARES_NAME: &str = "audio";
9
10#[derive(Debug, Error)]
11pub enum AudioJsonReportError {
12 #[error("audio decode error: {0}")]
13 AudioDecode(#[from] crate::AudioDecodeError),
14}
15
16impl<W> DetailReporter<AudioDiff, FileLeaf, JsonReport<W>> for AudioDiffReporter {
17 type Error = AudioJsonReportError;
18
19 fn report_unchanged(
20 &self,
21 name: &str,
22 _diff: &AudioDiff,
23 reporter: &JsonReport<W>,
24 ) -> Result<MayUnsupported<()>, Self::Error> {
25 reporter.record_unchanged(name, COMPARES_NAME, ());
26 Ok(MayUnsupported::Ok(()))
27 }
28
29 fn report_modified(
30 &self,
31 name: &str,
32 diff: &AudioDiff,
33 reporter: &JsonReport<W>,
34 ) -> Result<MayUnsupported<()>, Self::Error> {
35 let (spectrogram_diff_rate, shift_samples, lufs_diff_db) = if let Some(detail) = diff.diff_detail() {
36 let stat = detail.stat();
37 (
38 Some(stat.spectrogram_diff_rate),
39 Some(stat.shift_samples),
40 Some(stat.lufs_diff_db),
41 )
42 } else {
43 (None, None, None)
44 };
45 let report = ModifiedReport {
46 status: diff.status().as_str().to_string(),
47 expected_sample_rate: diff.expected().sample_rate(),
48 expected_channels: diff.expected().channels(),
49 expected_duration_seconds: diff.expected().duration_seconds(),
50 actual_sample_rate: diff.actual().sample_rate(),
51 actual_channels: diff.actual().channels(),
52 actual_duration_seconds: diff.actual().duration_seconds(),
53 spectrogram_diff_rate,
54 shift_samples,
55 lufs_diff_db,
56 };
57 reporter.record_modified(name, COMPARES_NAME, report);
58 Ok(MayUnsupported::Ok(()))
59 }
60
61 fn report_added(
62 &self,
63 name: &str,
64 data: &FileLeaf,
65 reporter: &JsonReport<W>,
66 ) -> Result<MayUnsupported<()>, Self::Error> {
67 if audio_extension(&data.kind).is_none() {
68 return Ok(MayUnsupported::Unsupported);
69 }
70 let Ok(decoded) = self.spectrogram_analyzer.decode_audio(&data.kind, &data.content) else {
71 return Ok(MayUnsupported::Unsupported);
72 };
73 reporter.record_added(
74 name,
75 COMPARES_NAME,
76 SingleReport {
77 sample_rate: decoded.sample_rate,
78 channels: decoded.channels,
79 duration_seconds: decoded.duration_seconds,
80 },
81 );
82 Ok(MayUnsupported::Ok(()))
83 }
84
85 fn report_deleted(
86 &self,
87 name: &str,
88 data: &FileLeaf,
89 reporter: &JsonReport<W>,
90 ) -> Result<MayUnsupported<()>, Self::Error> {
91 if audio_extension(&data.kind).is_none() {
92 return Ok(MayUnsupported::Unsupported);
93 }
94 let Ok(decoded) = self.spectrogram_analyzer.decode_audio(&data.kind, &data.content) else {
95 return Ok(MayUnsupported::Unsupported);
96 };
97 reporter.record_deleted(
98 name,
99 COMPARES_NAME,
100 SingleReport {
101 sample_rate: decoded.sample_rate,
102 channels: decoded.channels,
103 duration_seconds: decoded.duration_seconds,
104 },
105 );
106 Ok(MayUnsupported::Ok(()))
107 }
108}
109
110#[derive(Serialize)]
111struct ModifiedReport {
112 status: String,
113 expected_sample_rate: u32,
114 expected_channels: u16,
115 expected_duration_seconds: f32,
116 actual_sample_rate: u32,
117 actual_channels: u16,
118 actual_duration_seconds: f32,
119 #[serde(skip_serializing_if = "Option::is_none")]
120 spectrogram_diff_rate: Option<f64>,
121 #[serde(skip_serializing_if = "Option::is_none")]
122 shift_samples: Option<i32>,
123 #[serde(skip_serializing_if = "Option::is_none")]
124 lufs_diff_db: Option<f32>,
125}
126
127#[derive(Serialize)]
128struct SingleReport {
129 sample_rate: u32,
130 channels: u16,
131 duration_seconds: f32,
132}