Compare commits
4 commits
3113a50169
...
9c2b726faa
Author | SHA1 | Date | |
---|---|---|---|
xenofem | 9c2b726faa | ||
xenofem | d1def859fd | ||
xenofem | 6ba3d749a1 | ||
xenofem | 4081056224 |
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -814,9 +814,9 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
version = "0.10.54"
|
version = "0.10.55"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019"
|
checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
@ -846,9 +846,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-sys"
|
name = "openssl-sys"
|
||||||
version = "0.9.88"
|
version = "0.9.90"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617"
|
checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
|
|
|
@ -31,6 +31,13 @@
|
||||||
|
|
||||||
packages.x86_64-linux.default = packages.x86_64-linux.${pname};
|
packages.x86_64-linux.default = packages.x86_64-linux.${pname};
|
||||||
|
|
||||||
devShells.x86_64-linux.default = pkgs.mkShell buildDeps;
|
devShells.x86_64-linux.default = pkgs.mkShell {
|
||||||
|
inherit (buildDeps) buildInputs LIBCLANG_PATH;
|
||||||
|
nativeBuildInputs = buildDeps.nativeBuildInputs ++ (with pkgs; [
|
||||||
|
cargo-audit
|
||||||
|
clippy
|
||||||
|
rustfmt
|
||||||
|
]);
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ pub struct Config {
|
||||||
pub cohost_page: String,
|
pub cohost_page: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
const VAR_PREFIX: &'static str = "SCREENCAP_BOT_";
|
const VAR_PREFIX: &str = "SCREENCAP_BOT_";
|
||||||
|
|
||||||
fn get_var(name: &str) -> Result<String, VarError> {
|
fn get_var(name: &str) -> Result<String, VarError> {
|
||||||
env::var(VAR_PREFIX.to_string() + name)
|
env::var(VAR_PREFIX.to_string() + name)
|
||||||
|
@ -24,19 +24,19 @@ fn get_var(name: &str) -> Result<String, VarError> {
|
||||||
fn parse_var<E: Debug, T: FromStr<Err = E>>(name: &str) -> Result<T, VarError> {
|
fn parse_var<E: Debug, T: FromStr<Err = E>>(name: &str) -> Result<T, VarError> {
|
||||||
get_var(name).map(|s| {
|
get_var(name).map(|s| {
|
||||||
s.parse()
|
s.parse()
|
||||||
.expect(&format!("Failed to parse {}{}", VAR_PREFIX, name))
|
.unwrap_or_else(|e| panic!("Failed to parse {}{}: {:?}", VAR_PREFIX, name, e))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expect_var(name: &str) -> String {
|
fn expect_var(name: &str) -> String {
|
||||||
get_var(name).expect(&format!("{}{} must be set", VAR_PREFIX, name))
|
get_var(name).unwrap_or_else(|_| panic!("{}{} must be set", VAR_PREFIX, name))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load() -> Config {
|
pub fn load() -> Config {
|
||||||
Config {
|
Config {
|
||||||
shows_file: parse_var("SHOWS_FILE").unwrap_or(PathBuf::from("./shows.yaml")),
|
shows_file: parse_var("SHOWS_FILE").unwrap_or(PathBuf::from("./shows.yaml")),
|
||||||
global_tags: get_var("GLOBAL_TAGS")
|
global_tags: get_var("GLOBAL_TAGS")
|
||||||
.map(|s| s.split(",").map(String::from).collect())
|
.map(|s| s.split(',').map(String::from).collect())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
post_interval: Duration::from_secs(parse_var("POST_INTERVAL").unwrap_or(6 * 3600)),
|
post_interval: Duration::from_secs(parse_var("POST_INTERVAL").unwrap_or(6 * 3600)),
|
||||||
cohost_email: expect_var("COHOST_EMAIL"),
|
cohost_email: expect_var("COHOST_EMAIL"),
|
||||||
|
|
|
@ -27,7 +27,14 @@ async fn main() {
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let (title, show) = shows.iter().choose(&mut rng).expect("No shows found!");
|
let (title, show) = shows.iter().choose(&mut rng).expect("No shows found!");
|
||||||
let (num, file) = show.episodes.iter().choose(&mut rng).unwrap();
|
let episodes = match show.episodes() {
|
||||||
|
Ok(eps) => eps,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to get episodes for {}: {}", title, e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let (num, file) = episodes.iter().choose(&mut rng).unwrap();
|
||||||
|
|
||||||
let descriptor = format!(
|
let descriptor = format!(
|
||||||
"{}{}",
|
"{}{}",
|
||||||
|
|
107
src/shows/mod.rs
107
src/shows/mod.rs
|
@ -6,7 +6,7 @@ use serde::Deserialize;
|
||||||
mod enumeration;
|
mod enumeration;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct ShowSpec {
|
pub struct Show {
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub tags: Vec<String>,
|
pub tags: Vec<String>,
|
||||||
}
|
}
|
||||||
|
@ -20,71 +20,50 @@ pub enum EpisodeNumber {
|
||||||
|
|
||||||
type Episodes = HashMap<EpisodeNumber, PathBuf>;
|
type Episodes = HashMap<EpisodeNumber, PathBuf>;
|
||||||
|
|
||||||
pub struct Show {
|
|
||||||
pub episodes: Episodes,
|
|
||||||
pub tags: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load(shows_file: PathBuf) -> HashMap<String, Show> {
|
pub fn load(shows_file: PathBuf) -> HashMap<String, Show> {
|
||||||
let show_specs: HashMap<String, ShowSpec> =
|
serde_yaml::from_reader(fs::File::open(shows_file).expect("Failed to open shows file"))
|
||||||
serde_yaml::from_reader(fs::File::open(shows_file).expect("Failed to open shows file"))
|
.expect("Failed to parse YAML from shows file")
|
||||||
.expect("Failed to parse YAML from shows file");
|
|
||||||
|
|
||||||
show_specs
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|(name, show)| {
|
|
||||||
debug!("Enumerating show {}: {}", name, show.path.display());
|
|
||||||
load_path(show.path)
|
|
||||||
.map_err(|e| {
|
|
||||||
error!("Error processing {}: {}", name, e);
|
|
||||||
})
|
|
||||||
.ok()
|
|
||||||
.map(|eps| {
|
|
||||||
(
|
|
||||||
name,
|
|
||||||
Show {
|
|
||||||
episodes: eps,
|
|
||||||
tags: show.tags,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_path(path: PathBuf) -> std::io::Result<Episodes> {
|
impl Show {
|
||||||
let metadata = fs::metadata(&path)?;
|
pub fn episodes(&self) -> std::io::Result<Episodes> {
|
||||||
if metadata.is_file() {
|
let path = &self.path;
|
||||||
debug!("{} is a file, standalone", path.display());
|
let metadata = fs::metadata(path)?;
|
||||||
Ok(HashMap::from([(EpisodeNumber::Standalone, path)]))
|
if metadata.is_file() {
|
||||||
} else if metadata.is_dir() {
|
debug!("{} is a file, standalone", path.display());
|
||||||
debug!("{} is a directory, enumerating episodes", path.display());
|
Ok(HashMap::from([(
|
||||||
let files: Vec<PathBuf> = fs::read_dir(&path)?
|
EpisodeNumber::Standalone,
|
||||||
.map(|entry| {
|
path.to_path_buf(),
|
||||||
let entry = entry?;
|
)]))
|
||||||
if !entry.file_type()?.is_file() {
|
} else if metadata.is_dir() {
|
||||||
debug!("Skipping {}, not a file", entry.path().display());
|
debug!("{} is a directory, enumerating episodes", path.display());
|
||||||
return Ok(None);
|
let files: Vec<PathBuf> = fs::read_dir(path)?
|
||||||
}
|
.map(|entry| {
|
||||||
if entry.file_name().into_string().is_err() {
|
let entry = entry?;
|
||||||
debug!(
|
if !entry.file_type()?.is_file() {
|
||||||
"Skipping {}, contains invalid unicode",
|
debug!("Skipping {}, not a file", entry.path().display());
|
||||||
entry.path().display()
|
return Ok(None);
|
||||||
);
|
}
|
||||||
return Ok(None);
|
if entry.file_name().into_string().is_err() {
|
||||||
}
|
error!(
|
||||||
Ok(Some(entry.path()))
|
"Path {} contains invalid unicode, skipping",
|
||||||
})
|
entry.path().display()
|
||||||
.filter_map(|r| r.transpose())
|
);
|
||||||
.collect::<std::io::Result<Vec<PathBuf>>>()?;
|
return Ok(None);
|
||||||
enumeration::enumerate_episodes(files).ok_or(std::io::Error::new(
|
}
|
||||||
ErrorKind::InvalidData,
|
Ok(Some(entry.path()))
|
||||||
"No valid prefixes found",
|
})
|
||||||
))
|
.filter_map(|r| r.transpose())
|
||||||
} else {
|
.collect::<std::io::Result<Vec<PathBuf>>>()?;
|
||||||
Err(std::io::Error::new(
|
enumeration::enumerate_episodes(files).ok_or(std::io::Error::new(
|
||||||
ErrorKind::InvalidInput,
|
ErrorKind::InvalidData,
|
||||||
format!("Invalid file type for {}", path.display()),
|
"No valid prefixes found",
|
||||||
))
|
))
|
||||||
|
} else {
|
||||||
|
Err(std::io::Error::new(
|
||||||
|
ErrorKind::InvalidInput,
|
||||||
|
format!("Invalid file type for {}", path.display()),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
54
src/video.rs
54
src/video.rs
|
@ -27,33 +27,31 @@ pub fn get_video_info<P: AsRef<Path>>(
|
||||||
|
|
||||||
let duration_secs = ctx.duration() as f64 / f64::from(ffmpeg_next::ffi::AV_TIME_BASE);
|
let duration_secs = ctx.duration() as f64 / f64::from(ffmpeg_next::ffi::AV_TIME_BASE);
|
||||||
|
|
||||||
let subtitle_stream_index = subtitle_lang
|
let subtitle_stream_index = subtitle_lang.and_then(|lang| {
|
||||||
.map(|lang| {
|
ctx.streams()
|
||||||
ctx.streams()
|
.filter(|stream| {
|
||||||
.filter(|stream| {
|
ffmpeg_next::codec::context::Context::from_parameters(stream.parameters())
|
||||||
ffmpeg_next::codec::context::Context::from_parameters(stream.parameters())
|
.map(|c| c.medium())
|
||||||
.map(|c| c.medium())
|
== Ok(Type::Subtitle)
|
||||||
== Ok(Type::Subtitle)
|
})
|
||||||
})
|
.enumerate()
|
||||||
.enumerate()
|
.filter(|(_, stream)| {
|
||||||
.filter(|(_, stream)| {
|
let metadata = stream.metadata();
|
||||||
let metadata = stream.metadata();
|
if metadata.get("language") != Some(lang) {
|
||||||
if metadata.get("language") != Some(lang) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
if metadata
|
||||||
if metadata
|
.get("title")
|
||||||
.get("title")
|
.map(|t| SUBTITLE_FORBID_REGEX.is_match(t))
|
||||||
.map(|t| SUBTITLE_FORBID_REGEX.is_match(t))
|
== Some(true)
|
||||||
== Some(true)
|
{
|
||||||
{
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
true
|
||||||
true
|
})
|
||||||
})
|
.min_by_key(|(_, stream)| stream.disposition().contains(Disposition::FORCED))
|
||||||
.min_by_key(|(_, stream)| stream.disposition().contains(Disposition::FORCED))
|
.map(|(idx, _)| idx)
|
||||||
.map(|(idx, _)| idx)
|
});
|
||||||
})
|
|
||||||
.flatten();
|
|
||||||
|
|
||||||
Ok(VideoInfo {
|
Ok(VideoInfo {
|
||||||
duration_secs,
|
duration_secs,
|
||||||
|
@ -66,7 +64,7 @@ pub async fn take_screencap<P: AsRef<Path>>(
|
||||||
timestamp_secs: f64,
|
timestamp_secs: f64,
|
||||||
subtitle_stream_index: Option<usize>,
|
subtitle_stream_index: Option<usize>,
|
||||||
) -> std::io::Result<Vec<u8>> {
|
) -> std::io::Result<Vec<u8>> {
|
||||||
let ext = source.as_ref().extension().map(|s| s.to_str()).flatten();
|
let ext = source.as_ref().extension().and_then(|s| s.to_str());
|
||||||
if ext != Some("mkv") && ext != Some("mp4") {
|
if ext != Some("mkv") && ext != Some("mp4") {
|
||||||
return Err(std::io::Error::new(
|
return Err(std::io::Error::new(
|
||||||
ErrorKind::Other,
|
ErrorKind::Other,
|
||||||
|
|
Loading…
Reference in a new issue