selene-core 0.3.1

selene-core is the backend for Selene, a local-first music player
Documentation
use std::{path::Path, process::Command};

use crate::{
    errors::FfmpegError,
    ffmpeg::{command_mutators::FfmpegPresets, output_ffmpeg_err},
};
use lunar_lib::paths::sys::null_path;
use serde::{Deserialize, Serialize};

/// [`LoudnormAnalysis`] represents the measured values from the first-pass of EBU R 128 loudnorm
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd, Copy)]
pub struct LoudnormAnalysis {
    pub(crate) input_i: f32,
    pub(crate) input_tp: f32,
    pub(crate) input_lra: f32,
    pub(crate) input_thresh: f32,
}

impl LoudnormAnalysis {
    /// Uses ffmpeg to measure the input file and return a [`LoudnormAnalysis`]
    pub fn from_file(path: impl AsRef<Path>) -> Result<Self, FfmpegError> {
        #[derive(Deserialize, Debug, Clone, PartialEq, PartialOrd)]
        struct Root {
            pub input_i: String,
            pub input_tp: String,
            pub input_lra: String,
            pub input_thresh: String,
            pub output_i: String,
            pub output_tp: String,
            pub output_lra: String,
            pub output_thresh: String,
            pub normalization_type: String,
            pub target_offset: String,
        }

        let mut command = Command::new("ffmpeg");
        command.arg("-hide_banner");
        command.input_file(path);
        command.args(["-filter:a", "loudnorm=print_format=json", "-f", "null"]);
        command.arg(null_path());

        let result = output_ffmpeg_err(command)?;

        let json_start = result.find('{').ok_or(FfmpegError::FfmpegWrapperError(
            "Failed to find json start".to_string(),
        ))?;

        let json_end = result.rfind('}').ok_or(FfmpegError::FfmpegWrapperError(
            "Failed to find json end".to_string(),
        ))?;

        let json_slice = &result[json_start..=json_end];

        let root: Root = serde_json::from_str(json_slice)?;

        let result = Self {
            input_i: root
                .input_i
                .parse::<f32>()
                .map_err(|err| FfmpegError::Other(err.to_string()))?,
            input_tp: root
                .input_tp
                .parse::<f32>()
                .map_err(|err| FfmpegError::Other(err.to_string()))?,
            input_lra: root
                .input_lra
                .parse::<f32>()
                .map_err(|err| FfmpegError::Other(err.to_string()))?,
            input_thresh: root
                .input_thresh
                .parse::<f32>()
                .map_err(|err| FfmpegError::Other(err.to_string()))?,
        };

        Ok(result)
    }
}