use std::{
borrow::Cow,
fmt, fs,
path::{Path, PathBuf},
process::Command,
};
use anyhow::{anyhow, Result};
pub(crate) mod metadata;
use crate::{
capture_group_as_str, get_stderr, get_stdout, MetaData, Rip, RipConfig,
};
pub(crate) struct Preset {
quality: Quality,
resolution: Resolution,
}
const GIGABYTE: u64 = 1_073_741_824;
impl Preset {
fn guess_from_source(s: &VideoMediaSource) -> Self {
use VideoMediaSource as VMS;
match s {
VMS::Dvd(_) => Preset {
quality: Quality::Default,
resolution: Resolution::Default,
},
VMS::Bluray(_) => Preset {
quality: Quality::High,
resolution: Resolution::High,
},
VMS::File(File { path, .. }) => {
if let Ok(md) = fs::metadata(path) {
const TWO_GIGS: u64 = 2 * GIGABYTE;
#[allow(clippy::match_overlapping_arm)]
match md.len() {
0..=GIGABYTE => Preset {
quality: Quality::Default,
resolution: Resolution::Default,
},
0..=TWO_GIGS => Preset {
quality: Quality::High,
resolution: Resolution::Default,
},
_ => Preset {
quality: Quality::High,
resolution: Resolution::High,
},
}
} else {
Preset {
quality: Quality::Default,
resolution: Resolution::Default,
}
}
}
}
}
}
impl fmt::Display for Preset {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {}", self.quality.as_ref(), self.resolution.as_ref())
}
}
enum Quality {
Default,
High,
}
impl AsRef<str> for Quality {
fn as_ref(&self) -> &str {
use Quality::{Default, High};
match self {
Default => "Devices/Apple",
High => "General/Super HQ",
}
}
}
enum Resolution {
Default,
High,
}
impl AsRef<str> for Resolution {
fn as_ref(&self) -> &str {
use Resolution::{Default, High};
match self {
Default => "720p30",
High => "1080p30",
}
}
}
mod dvd;
use dvd::Dvd;
pub(crate) mod file;
use file::File;
mod bluray;
use bluray::Bluray;
pub(crate) enum VideoMediaSource {
Dvd(Dvd),
Bluray(Bluray),
File(File),
}
impl VideoMediaSource {
pub(crate) fn new(path: impl Into<PathBuf>) -> Result<Self> {
match path.into() {
p if p.starts_with("/dev/dvd") => {
Ok(VideoMediaSource::Dvd(Dvd::new(p)?))
}
p if p.starts_with("/dev/bluray") => {
Ok(VideoMediaSource::Bluray(Bluray::new(p)?))
}
p => Ok(VideoMediaSource::File(File::new(p)?)),
}
}
pub(crate) fn duration(&self) -> Option<u32> {
match self {
VideoMediaSource::Dvd(dvd) => dvd.duration,
VideoMediaSource::Bluray(bluray) => bluray.duration,
VideoMediaSource::File(file) => file.duration,
}
}
pub(crate) fn title_hint(&self) -> &str {
match self {
VideoMediaSource::Dvd(dvd) => &dvd.title_hint,
VideoMediaSource::Bluray(bluray) => &bluray.title_hint,
VideoMediaSource::File(file) => &file.title_hint,
}
}
pub(crate) fn rip(&self, preset: Option<Preset>) -> Result<PathBuf> {
let preset = preset.unwrap_or_else(|| Preset::guess_from_source(self));
match self {
VideoMediaSource::Dvd(dvd) => dvd.rip(&preset),
VideoMediaSource::Bluray(bluray) => bluray.rip(&preset),
VideoMediaSource::File(file) => file.rip(&preset),
}
}
}
fn title_from_label(path: &Path) -> Result<String> {
let output = Command::new("blkid")
.args("-o value -s LABEL".split_whitespace())
.arg(path)
.output()?;
get_stdout(output).map(String::from)
}
impl Rip for VideoMediaSource {
fn rip(&self, config: &RipConfig) -> Result<PathBuf> {
let title_hint = self.title_hint();
let metadata = config
.tmdb_id
.and_then(|id| MetaData::from_id(id).ok())
.or_else(|| MetaData::guess_from_title(title_hint).ok());
let outfile: PathBuf = metadata
.as_ref()
.map(|md| Cow::Owned(md.title.trim().to_string()) + ".mp4")
.unwrap_or(Cow::Borrowed(title_hint))
.as_ref()
.into();
let ripped = self.rip(None)?;
if let Some(duration) = self.duration() {
let ripped_duration =
File::new(&ripped)?.duration.ok_or_else(|| {
anyhow!("Ripped file should have a duration")
})?;
let abs_diff =
duration.max(ripped_duration) - duration.min(ripped_duration);
if abs_diff > 1 {
log::warn!(
"Ripped file {} has duration {} seconds, expected duration \
{} seconds",
&ripped.display(),
&ripped_duration,
&duration
);
}
};
log::info!(
"File successfully converted and written to {}",
ripped.display(),
);
fs::rename(ripped, &*outfile)?;
log::info!(
"File successfully renamed to {}. Setting metadata:\n{:?}",
outfile.display(),
&metadata
);
if let Some(md) = metadata {
metadata::set_metadata(&*outfile, &md)?;
}
log::debug!("Done setting metadata");
Ok(outfile)
}
}