semdiff-differ-audio 0.1.0

Audio diff calculator and reporters for semdiff.
Documentation
use crate::{AudioDiff, AudioDiffReporter, audio_extension};
use semdiff_core::fs::FileLeaf;
use semdiff_core::{DetailReporter, MayUnsupported};
use semdiff_output::json::JsonReport;
use serde::Serialize;
use thiserror::Error;

const COMPARES_NAME: &str = "audio";

#[derive(Debug, Error)]
pub enum AudioJsonReportError {
    #[error("audio decode error: {0}")]
    AudioDecode(#[from] crate::AudioDecodeError),
}

impl<W> DetailReporter<AudioDiff, FileLeaf, JsonReport<W>> for AudioDiffReporter {
    type Error = AudioJsonReportError;

    fn report_unchanged(
        &self,
        name: &str,
        _diff: AudioDiff,
        reporter: &JsonReport<W>,
    ) -> Result<MayUnsupported<()>, Self::Error> {
        reporter.record_unchanged(name, COMPARES_NAME, ());
        Ok(MayUnsupported::Ok(()))
    }

    fn report_modified(
        &self,
        name: &str,
        diff: AudioDiff,
        reporter: &JsonReport<W>,
    ) -> Result<MayUnsupported<()>, Self::Error> {
        let (spectrogram_diff_rate, shift_samples, lufs_diff_db) = if let Some(detail) = diff.diff_detail() {
            let stat = detail.stat();
            (
                Some(stat.spectrogram_diff_rate),
                Some(stat.shift_samples),
                Some(stat.lufs_diff_db),
            )
        } else {
            (None, None, None)
        };
        let report = ModifiedReport {
            status: diff.status().as_str().to_string(),
            expected_sample_rate: diff.expected().sample_rate(),
            expected_channels: diff.expected().channels(),
            expected_duration_seconds: diff.expected().duration_seconds(),
            actual_sample_rate: diff.actual().sample_rate(),
            actual_channels: diff.actual().channels(),
            actual_duration_seconds: diff.actual().duration_seconds(),
            spectrogram_diff_rate,
            shift_samples,
            lufs_diff_db,
        };
        reporter.record_modified(name, COMPARES_NAME, report);
        Ok(MayUnsupported::Ok(()))
    }

    fn report_added(
        &self,
        name: &str,
        data: FileLeaf,
        reporter: &JsonReport<W>,
    ) -> Result<MayUnsupported<()>, Self::Error> {
        if audio_extension(&data.kind).is_none() {
            return Ok(MayUnsupported::Unsupported);
        }
        let Ok(decoded) = self.spectrogram_analyzer.decode_audio(&data.kind, &data.content) else {
            return Ok(MayUnsupported::Unsupported);
        };
        reporter.record_added(
            name,
            COMPARES_NAME,
            SingleReport {
                sample_rate: decoded.sample_rate,
                channels: decoded.channels,
                duration_seconds: decoded.duration_seconds,
            },
        );
        Ok(MayUnsupported::Ok(()))
    }

    fn report_deleted(
        &self,
        name: &str,
        data: FileLeaf,
        reporter: &JsonReport<W>,
    ) -> Result<MayUnsupported<()>, Self::Error> {
        if audio_extension(&data.kind).is_none() {
            return Ok(MayUnsupported::Unsupported);
        }
        let Ok(decoded) = self.spectrogram_analyzer.decode_audio(&data.kind, &data.content) else {
            return Ok(MayUnsupported::Unsupported);
        };
        reporter.record_deleted(
            name,
            COMPARES_NAME,
            SingleReport {
                sample_rate: decoded.sample_rate,
                channels: decoded.channels,
                duration_seconds: decoded.duration_seconds,
            },
        );
        Ok(MayUnsupported::Ok(()))
    }
}

#[derive(Serialize)]
struct ModifiedReport {
    status: String,
    expected_sample_rate: u32,
    expected_channels: u16,
    expected_duration_seconds: f32,
    actual_sample_rate: u32,
    actual_channels: u16,
    actual_duration_seconds: f32,
    #[serde(skip_serializing_if = "Option::is_none")]
    spectrogram_diff_rate: Option<f64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    shift_samples: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    lufs_diff_db: Option<f32>,
}

#[derive(Serialize)]
struct SingleReport {
    sample_rate: u32,
    channels: u16,
    duration_seconds: f32,
}