1.5.0: make preferred audio and subtitle languages configurable
This commit is contained in:
parent
0f75889a3e
commit
f584b17dd1
6 changed files with 121 additions and 33 deletions
|
@ -12,6 +12,8 @@ use anyhow::{anyhow, Context};
|
|||
pub struct Config {
|
||||
pub capture_images: bool,
|
||||
pub capture_audio_duration: Option<f64>,
|
||||
pub subtitle_language: Option<String>,
|
||||
pub audio_language: Option<String>,
|
||||
pub shows_file: PathBuf,
|
||||
pub global_tags: Vec<String>,
|
||||
pub post_interval: Duration,
|
||||
|
@ -49,18 +51,55 @@ fn require_var(name: &str) -> anyhow::Result<String> {
|
|||
get_var(name)?.ok_or_else(|| anyhow!("{}{} must be set", VAR_PREFIX, name))
|
||||
}
|
||||
|
||||
fn get_language_code_var<F: FnOnce() -> Option<String>>(
|
||||
name: &str,
|
||||
default: F,
|
||||
) -> anyhow::Result<Option<String>> {
|
||||
match get_var(name)? {
|
||||
Some(s) => {
|
||||
if s.is_ascii() && s.len() == 3 {
|
||||
Ok(Some(s))
|
||||
} else if s.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"{}{} must be an ISO-639-2 three-letter language code",
|
||||
VAR_PREFIX,
|
||||
name
|
||||
))
|
||||
}
|
||||
}
|
||||
None => Ok(default()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load() -> anyhow::Result<Config> {
|
||||
let capture_images = parse_var("CAPTURE_IMAGES")?.unwrap_or(true);
|
||||
|
||||
let capture_audio_duration = parse_var("CAPTURE_AUDIO_DURATION")?;
|
||||
if let Some(d) = capture_audio_duration {
|
||||
if d <= 0.0 {
|
||||
return Err(anyhow!(
|
||||
"{}CAPTURE_AUDIO_DURATION cannot be <= 0",
|
||||
VAR_PREFIX
|
||||
));
|
||||
let capture_audio_duration = match parse_var::<_, f64>("CAPTURE_AUDIO_DURATION")? {
|
||||
Some(d) => {
|
||||
if !d.is_finite() {
|
||||
return Err(anyhow!(
|
||||
"non-finite float value for {}CAPTURE_AUDIO_DURATION",
|
||||
VAR_PREFIX
|
||||
));
|
||||
} else if d >= 1.0 {
|
||||
Some(d)
|
||||
} else if d > 0.0 {
|
||||
return Err(anyhow!(
|
||||
"cannot capture audio clips less than 1 second long"
|
||||
));
|
||||
} else if d == 0.0 {
|
||||
None
|
||||
} else {
|
||||
return Err(anyhow!(
|
||||
"{}CAPTURE_AUDIO_DURATION cannot be negative",
|
||||
VAR_PREFIX
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
if let (false, None) = (capture_images, capture_audio_duration) {
|
||||
return Err(anyhow!(
|
||||
|
@ -71,6 +110,10 @@ pub fn load() -> anyhow::Result<Config> {
|
|||
Ok(Config {
|
||||
capture_images,
|
||||
capture_audio_duration,
|
||||
subtitle_language: get_language_code_var("SUBTITLE_LANGUAGE", || {
|
||||
Some(String::from("eng"))
|
||||
})?,
|
||||
audio_language: get_language_code_var("AUDIO_LANGUAGE", || None)?,
|
||||
shows_file: parse_var("SHOWS_FILE")?.unwrap_or_else(|| PathBuf::from("./shows.yaml")),
|
||||
global_tags: get_var("GLOBAL_TAGS")?
|
||||
.map(|s| s.split(',').map(String::from).collect())
|
||||
|
|
15
src/main.rs
15
src/main.rs
|
@ -89,8 +89,12 @@ async fn post_random_capture<R: Rng>(
|
|||
|
||||
info!("Selected: {} - {}", descriptor, file.display());
|
||||
|
||||
let media_info = media::get_media_info(file, Some("eng"))
|
||||
.with_context(|| format!("Failed to get info for media file {}", file.display()))?;
|
||||
let media_info = media::get_media_info(
|
||||
file,
|
||||
conf.subtitle_language.as_deref(),
|
||||
conf.audio_language.as_deref(),
|
||||
)
|
||||
.with_context(|| format!("Failed to get info for media file {}", file.display()))?;
|
||||
debug!(
|
||||
"Media duration: {}",
|
||||
format_timestamp(media_info.duration_secs, None)
|
||||
|
@ -142,9 +146,10 @@ async fn post_random_capture<R: Rng>(
|
|||
}
|
||||
|
||||
if let Some(duration) = conf.capture_audio_duration {
|
||||
let audio_data = media::take_audio_clip(file, timestamp, duration)
|
||||
.await
|
||||
.context("Failed to take audio clip")?;
|
||||
let audio_data =
|
||||
media::take_audio_clip(file, timestamp, duration, media_info.audio_stream_index)
|
||||
.await
|
||||
.context("Failed to take audio clip")?;
|
||||
|
||||
let audio_attachment = eggbug::Attachment::new(
|
||||
audio_data,
|
||||
|
|
44
src/media.rs
44
src/media.rs
|
@ -2,6 +2,7 @@ use anyhow::{anyhow, Context};
|
|||
use ffmpeg_next::{
|
||||
format::{input, stream::Disposition},
|
||||
media::Type,
|
||||
Stream,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use log::debug;
|
||||
|
@ -19,11 +20,27 @@ pub struct MediaInfo {
|
|||
pub duration_secs: f64,
|
||||
// The index among the subtitle streams, not among the streams in general
|
||||
pub subtitle_stream_index: Option<usize>,
|
||||
// The index among the audio streams, not among the streams in general
|
||||
pub audio_stream_index: Option<usize>,
|
||||
}
|
||||
|
||||
fn indexed_streams(
|
||||
ctx: &ffmpeg_next::format::context::common::Context,
|
||||
stream_type: Type,
|
||||
) -> impl Iterator<Item = (usize, Stream<'_>)> {
|
||||
ctx.streams()
|
||||
.filter(move |stream| {
|
||||
ffmpeg_next::codec::context::Context::from_parameters(stream.parameters())
|
||||
.map(|c| c.medium())
|
||||
== Ok(stream_type)
|
||||
})
|
||||
.enumerate()
|
||||
}
|
||||
|
||||
pub fn get_media_info<P: AsRef<Path>>(
|
||||
source: &P,
|
||||
subtitle_lang: Option<&str>,
|
||||
audio_lang: Option<&str>,
|
||||
) -> anyhow::Result<MediaInfo> {
|
||||
let ctx = input(source).context("Failed to load media file")?;
|
||||
|
||||
|
@ -31,13 +48,7 @@ pub fn get_media_info<P: AsRef<Path>>(
|
|||
debug!("{:?}", ctx.metadata());
|
||||
|
||||
let subtitle_stream_index = subtitle_lang.and_then(|lang| {
|
||||
ctx.streams()
|
||||
.filter(|stream| {
|
||||
ffmpeg_next::codec::context::Context::from_parameters(stream.parameters())
|
||||
.map(|c| c.medium())
|
||||
== Ok(Type::Subtitle)
|
||||
})
|
||||
.enumerate()
|
||||
indexed_streams(&ctx, Type::Subtitle)
|
||||
.filter(|(_, stream)| {
|
||||
let metadata = stream.metadata();
|
||||
if metadata.get("language") != Some(lang) {
|
||||
|
@ -56,9 +67,16 @@ pub fn get_media_info<P: AsRef<Path>>(
|
|||
.map(|(idx, _)| idx)
|
||||
});
|
||||
|
||||
let audio_stream_index = audio_lang.and_then(|lang| {
|
||||
indexed_streams(&ctx, Type::Audio)
|
||||
.find(|(_, stream)| stream.metadata().get("language") == Some(lang))
|
||||
.map(|(idx, _)| idx)
|
||||
});
|
||||
|
||||
Ok(MediaInfo {
|
||||
duration_secs,
|
||||
subtitle_stream_index,
|
||||
audio_stream_index,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -139,6 +157,7 @@ pub async fn take_audio_clip<P: AsRef<Path>>(
|
|||
source: &P,
|
||||
timestamp_secs: f64,
|
||||
duration_secs: f64,
|
||||
audio_stream_index: Option<usize>,
|
||||
) -> anyhow::Result<Vec<u8>> {
|
||||
take_ffmpeg_capture(source, "mp3", |cmd, in_path, out_path| {
|
||||
cmd.arg("-ss")
|
||||
|
@ -146,10 +165,13 @@ pub async fn take_audio_clip<P: AsRef<Path>>(
|
|||
.arg("-t")
|
||||
.arg(format!("{:.2}", duration_secs))
|
||||
.arg("-i")
|
||||
.arg(in_path)
|
||||
.args(["-loglevel", "quiet"])
|
||||
.arg("-y")
|
||||
.arg(out_path);
|
||||
.arg(in_path);
|
||||
|
||||
if let Some(idx) = audio_stream_index {
|
||||
cmd.arg("-map").arg(format!("0:a:{}", idx));
|
||||
}
|
||||
|
||||
cmd.args(["-loglevel", "quiet"]).arg("-y").arg(out_path);
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue