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