diff --git a/Cargo.lock b/Cargo.lock index 2b89647..e858be0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -814,9 +814,9 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "openssl" -version = "0.10.54" +version = "0.10.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" +checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" dependencies = [ "bitflags", "cfg-if", @@ -846,9 +846,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.88" +version = "0.9.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" +checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" dependencies = [ "cc", "libc", diff --git a/flake.nix b/flake.nix index fbc05ad..6e27a5a 100644 --- a/flake.nix +++ b/flake.nix @@ -31,6 +31,13 @@ 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 + ]); + }; }; } diff --git a/src/config.rs b/src/config.rs index 54ab8da..2db7917 100644 --- a/src/config.rs +++ b/src/config.rs @@ -15,7 +15,7 @@ pub struct Config { pub cohost_page: String, } -const VAR_PREFIX: &'static str = "SCREENCAP_BOT_"; +const VAR_PREFIX: &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() - .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 { - 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 { 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 83c4ba8..bb62355 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,7 +27,14 @@ async fn main() { loop { 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!( "{}{}", diff --git a/src/shows/mod.rs b/src/shows/mod.rs index 1dd894b..9abed11 100644 --- a/src/shows/mod.rs +++ b/src/shows/mod.rs @@ -6,7 +6,7 @@ use serde::Deserialize; mod enumeration; #[derive(Deserialize)] -pub struct ShowSpec { +pub struct Show { pub path: PathBuf, pub tags: Vec, } @@ -20,71 +20,50 @@ pub enum EpisodeNumber { type Episodes = HashMap; -pub struct Show { - pub episodes: Episodes, - pub tags: Vec, -} - 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); - }) - .ok() - .map(|eps| { - ( - name, - Show { - episodes: eps, - tags: show.tags, - }, - ) - }) - }) - .collect() + serde_yaml::from_reader(fs::File::open(shows_file).expect("Failed to open shows file")) + .expect("Failed to parse YAML from shows file") } -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()), - )) +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())) + }) + .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 5b320dd..3c56125 100644 --- a/src/video.rs +++ b/src/video.rs @@ -27,33 +27,31 @@ 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 - .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(); + 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) + }); Ok(VideoInfo { duration_secs, @@ -66,7 +64,7 @@ pub async fn take_screencap>( timestamp_secs: f64, subtitle_stream_index: Option, ) -> std::io::Result> { - 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") { return Err(std::io::Error::new( ErrorKind::Other,