use std::convert::{Into, TryFrom};
use crate::header::{CommentList, FixedPointGain};
use crate::header_rewriter::{CodecHeaders, HeaderRewrite, HeaderSummarize};
use crate::opus::{TAG_ALBUM_GAIN, TAG_TRACK_GAIN};
use crate::{Decibels, Error, R128_LUFS};
#[derive(Clone, Copy, Debug)]
pub enum VolumeTarget {
ZeroGain,
LUFS(Decibels),
NoChange,
}
#[derive(Clone, Copy, Debug)]
pub enum OutputGainMode {
Album,
Track,
}
#[derive(Clone, Copy, Debug)]
pub struct VolumeRewriterConfig {
pub output_gain: VolumeTarget,
pub output_gain_mode: OutputGainMode,
pub track_volume: Option<Decibels>,
pub album_volume: Option<Decibels>,
}
impl VolumeRewriterConfig {
pub fn volume_for_output_gain_calculation(&self) -> Option<Decibels> {
match self.output_gain_mode {
OutputGainMode::Album => self.album_volume,
OutputGainMode::Track => self.track_volume,
}
}
}
impl VolumeTarget {
pub fn to_friendly_string(&self) -> String {
match *self {
VolumeTarget::ZeroGain => String::from("original input"),
VolumeTarget::LUFS(lufs) => format!("{:.2} LUFS", lufs.as_f64()),
VolumeTarget::NoChange => String::from("existing gain value"),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct OpusGains {
pub output: Decibels,
pub track_r128: Option<Decibels>,
pub album_r128: Option<Decibels>,
}
#[derive(Debug, Default)]
pub struct GainsSummary {}
impl HeaderSummarize for GainsSummary {
type Error = Error;
type Summary = OpusGains;
fn summarize(&self, headers: &CodecHeaders) -> Result<OpusGains, Error> {
match headers {
CodecHeaders::Opus(opus_header, comment_header) => {
let gains = OpusGains {
output: opus_header.get_output_gain().into(),
track_r128: comment_header.get_gain_from_tag(TAG_TRACK_GAIN).unwrap_or(None).map(Into::into),
album_r128: comment_header.get_gain_from_tag(TAG_ALBUM_GAIN).unwrap_or(None).map(Into::into),
};
Ok(gains)
}
CodecHeaders::Vorbis(_, _) => Err(Error::UnsupportedCodec(headers.codec())),
}
}
}
#[derive(Debug)]
pub struct VolumeHeaderRewrite {
config: VolumeRewriterConfig,
}
impl VolumeHeaderRewrite {
pub fn new(config: VolumeRewriterConfig) -> VolumeHeaderRewrite { VolumeHeaderRewrite { config } }
}
impl HeaderRewrite for VolumeHeaderRewrite {
type Error = Error;
fn rewrite(&self, headers: &mut CodecHeaders) -> Result<(), Error> {
match headers {
CodecHeaders::Opus(opus_header, comment_header) => {
let new_header_gain = match self.config.output_gain {
VolumeTarget::ZeroGain => FixedPointGain::default(),
VolumeTarget::LUFS(target_lufs) => {
let volume_for_output_gain = self
.config
.volume_for_output_gain_calculation()
.expect("Precomputed volume unexpectedly missing");
FixedPointGain::try_from(target_lufs - volume_for_output_gain)?
}
VolumeTarget::NoChange => opus_header.get_output_gain(),
};
opus_header.set_output_gain(new_header_gain);
let compute_gain = |volume| -> Result<Option<FixedPointGain>, Error> {
if let Some(volume) = volume {
FixedPointGain::try_from(R128_LUFS - volume - new_header_gain.into()).map(Some)
} else {
Ok(None)
}
};
let track_gain_r128 = compute_gain(self.config.track_volume)?;
let album_gain_r128 = compute_gain(self.config.album_volume)?;
for (tag, gain) in [(TAG_TRACK_GAIN, track_gain_r128), (TAG_ALBUM_GAIN, album_gain_r128)] {
if let Some(gain) = gain {
comment_header.set_tag_to_gain(tag, gain)?;
} else {
comment_header.remove_all(tag);
}
}
Ok(())
}
CodecHeaders::Vorbis(_, _) => Err(Error::UnsupportedCodec(headers.codec())),
}
}
}