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::{
    config::common::common_config, ffmpeg::loudnorm::LoudnormAnalysis,
    library::metadata::canonicalize_metadata_key,
};

pub trait FfmpegPresets {
    fn input_file(&mut self, input: impl AsRef<Path>);

    fn output_file(&mut self, output: &Path);

    fn cover_art_from(&mut self, input_index: usize);

    fn drop_metadata(&mut self);

    fn copy_all(&mut self);

    fn add_metadata<K, V>(&mut self, key: K, value: V)
    where
        K: AsRef<str>,
        V: AsRef<str>;

    fn add_metadata_group<'a, I, K, V>(&mut self, metadata: I)
    where
        I: IntoIterator<Item = (&'a K, &'a V)>,
        K: AsRef<str> + 'a,
        V: AsRef<str> + 'a;

    fn apply_loudnorm_filter(&mut self, measured: LoudnormAnalysis);

    fn set_codec(&mut self, codec: impl AsRef<str>);

    fn set_container(&mut self, container: impl AsRef<str>);

    fn map_audio_from(&mut self, input_index: usize);

    fn drop_subtitles(&mut self);
}

impl FfmpegPresets for Command {
    fn input_file(&mut self, file: impl AsRef<Path>) {
        self.arg("-i").arg(file.as_ref());
    }

    fn output_file(&mut self, output: &Path) {
        self.arg("-y").arg(output);
    }

    fn cover_art_from(&mut self, input_index: usize) {
        self.args([
            "-map",
            &format!("{input_index}:v:0"),
            "-disposition:v",
            "attached_pic",
            "-metadata:s:v",
            "title=cover",
            "-metadata:s:v",
            "comment=Cover (front)",
        ]);
    }

    fn drop_metadata(&mut self) {
        self.args(["-map_metadata", "-1"]);
    }

    fn add_metadata<K, V>(&mut self, key: K, value: V)
    where
        K: AsRef<str>,
        V: AsRef<str>,
    {
        let key = canonicalize_metadata_key(key);
        let value = value.as_ref();
        self.args(["-metadata", format!("{key}={value}").as_str()]);
    }

    fn add_metadata_group<'a, I, K, V>(&mut self, metadata: I)
    where
        I: IntoIterator<Item = (&'a K, &'a V)>,
        K: AsRef<str> + 'a,
        V: AsRef<str> + 'a,
    {
        for (key, value) in metadata {
            self.add_metadata(key.as_ref(), value.as_ref());
        }
    }

    fn apply_loudnorm_filter(&mut self, measured: LoudnormAnalysis) {
        let loudnorm_config = &common_config().loudnorm_config;

        let loudnorm_filter = format!(
            "loudnorm=linear={linear}:measured_i={measured_i}:measured_tp={measured_tp}:measured_lra={measured_lra}:measured_thresh={measured_thresh}:i={target_i}:tp={target_tp}:lra={target_lra}:offset={target_offset}",
            linear = loudnorm_config.linear,
            measured_i = measured.input_i,
            measured_tp = measured.input_tp,
            measured_lra = measured.input_lra,
            measured_thresh = measured.input_thresh,
            target_i = loudnorm_config.target_i,
            target_tp = loudnorm_config.target_tp,
            target_lra = loudnorm_config.target_lra,
            target_offset = loudnorm_config.target_offset,
        );

        self.args(["-filter:a".to_string(), loudnorm_filter]);
    }

    fn set_codec(&mut self, codec: impl AsRef<str>) {
        self.args(["-c:a", codec.as_ref()]);
    }

    fn set_container(&mut self, container: impl AsRef<str>) {
        self.args(["-f", container.as_ref()]);
    }

    fn map_audio_from(&mut self, input_index: usize) {
        self.args(["-map", &format!("{input_index}:a:0")]);
    }

    fn drop_subtitles(&mut self) {
        self.arg("-sn");
    }

    fn copy_all(&mut self) {
        self.args(["-c", "copy"]);
    }
}