Compare commits
	
		
			No commits in common. "9c2b726faa936ad7402958f5fcf243515572d39e" and "3113a501699e6e193a5a677af42f0d16c9449218" have entirely different histories.
		
	
	
		
			9c2b726faa
			...
			3113a50169
		
	
		
					 6 changed files with 102 additions and 93 deletions
				
			
		
							
								
								
									
										8
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							| 
						 | 
					@ -814,9 +814,9 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "openssl"
 | 
					name = "openssl"
 | 
				
			||||||
version = "0.10.55"
 | 
					version = "0.10.54"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d"
 | 
					checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019"
 | 
				
			||||||
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.90"
 | 
					version = "0.9.88"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6"
 | 
					checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "cc",
 | 
					 "cc",
 | 
				
			||||||
 "libc",
 | 
					 "libc",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,13 +31,6 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    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 {
 | 
					    devShells.x86_64-linux.default = pkgs.mkShell buildDeps;
 | 
				
			||||||
      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: &str = "SCREENCAP_BOT_";
 | 
					const VAR_PREFIX: &'static 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()
 | 
				
			||||||
            .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 {
 | 
					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 {
 | 
					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,14 +27,7 @@ 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 episodes = match show.episodes() {
 | 
					        let (num, file) = show.episodes.iter().choose(&mut rng).unwrap();
 | 
				
			||||||
            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 Show {
 | 
					pub struct ShowSpec {
 | 
				
			||||||
    pub path: PathBuf,
 | 
					    pub path: PathBuf,
 | 
				
			||||||
    pub tags: Vec<String>,
 | 
					    pub tags: Vec<String>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -20,50 +20,71 @@ pub enum EpisodeNumber {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Episodes = HashMap<EpisodeNumber, PathBuf>;
 | 
					type Episodes = HashMap<EpisodeNumber, PathBuf>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn load(shows_file: PathBuf) -> HashMap<String, Show> {
 | 
					pub struct Show {
 | 
				
			||||||
    serde_yaml::from_reader(fs::File::open(shows_file).expect("Failed to open shows file"))
 | 
					    pub episodes: Episodes,
 | 
				
			||||||
        .expect("Failed to parse YAML from shows file")
 | 
					    pub tags: Vec<String>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Show {
 | 
					pub fn load(shows_file: PathBuf) -> HashMap<String, Show> {
 | 
				
			||||||
    pub fn episodes(&self) -> std::io::Result<Episodes> {
 | 
					    let show_specs: HashMap<String, ShowSpec> =
 | 
				
			||||||
        let path = &self.path;
 | 
					        serde_yaml::from_reader(fs::File::open(shows_file).expect("Failed to open shows file"))
 | 
				
			||||||
        let metadata = fs::metadata(path)?;
 | 
					            .expect("Failed to parse YAML from shows file");
 | 
				
			||||||
        if metadata.is_file() {
 | 
					
 | 
				
			||||||
            debug!("{} is a file, standalone", path.display());
 | 
					    show_specs
 | 
				
			||||||
            Ok(HashMap::from([(
 | 
					        .into_iter()
 | 
				
			||||||
                EpisodeNumber::Standalone,
 | 
					        .filter_map(|(name, show)| {
 | 
				
			||||||
                path.to_path_buf(),
 | 
					            debug!("Enumerating show {}: {}", name, show.path.display());
 | 
				
			||||||
            )]))
 | 
					            load_path(show.path)
 | 
				
			||||||
        } else if metadata.is_dir() {
 | 
					                .map_err(|e| {
 | 
				
			||||||
            debug!("{} is a directory, enumerating episodes", path.display());
 | 
					                    error!("Error processing {}: {}", name, e);
 | 
				
			||||||
            let files: Vec<PathBuf> = 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())
 | 
					                .ok()
 | 
				
			||||||
                .collect::<std::io::Result<Vec<PathBuf>>>()?;
 | 
					                .map(|eps| {
 | 
				
			||||||
            enumeration::enumerate_episodes(files).ok_or(std::io::Error::new(
 | 
					                    (
 | 
				
			||||||
                ErrorKind::InvalidData,
 | 
					                        name,
 | 
				
			||||||
                "No valid prefixes found",
 | 
					                        Show {
 | 
				
			||||||
            ))
 | 
					                            episodes: eps,
 | 
				
			||||||
        } else {
 | 
					                            tags: show.tags,
 | 
				
			||||||
            Err(std::io::Error::new(
 | 
					                        },
 | 
				
			||||||
                ErrorKind::InvalidInput,
 | 
					                    )
 | 
				
			||||||
                format!("Invalid file type for {}", path.display()),
 | 
					                })
 | 
				
			||||||
            ))
 | 
					        })
 | 
				
			||||||
        }
 | 
					        .collect()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn load_path(path: PathBuf) -> std::io::Result<Episodes> {
 | 
				
			||||||
 | 
					    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<PathBuf> = 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::<std::io::Result<Vec<PathBuf>>>()?;
 | 
				
			||||||
 | 
					        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()),
 | 
				
			||||||
 | 
					        ))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										54
									
								
								src/video.rs
									
										
									
									
									
								
							
							
						
						
									
										54
									
								
								src/video.rs
									
										
									
									
									
								
							| 
						 | 
					@ -27,31 +27,33 @@ 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.and_then(|lang| {
 | 
					    let subtitle_stream_index = subtitle_lang
 | 
				
			||||||
        ctx.streams()
 | 
					        .map(|lang| {
 | 
				
			||||||
            .filter(|stream| {
 | 
					            ctx.streams()
 | 
				
			||||||
                ffmpeg_next::codec::context::Context::from_parameters(stream.parameters())
 | 
					                .filter(|stream| {
 | 
				
			||||||
                    .map(|c| c.medium())
 | 
					                    ffmpeg_next::codec::context::Context::from_parameters(stream.parameters())
 | 
				
			||||||
                    == Ok(Type::Subtitle)
 | 
					                        .map(|c| c.medium())
 | 
				
			||||||
            })
 | 
					                        == Ok(Type::Subtitle)
 | 
				
			||||||
            .enumerate()
 | 
					                })
 | 
				
			||||||
            .filter(|(_, stream)| {
 | 
					                .enumerate()
 | 
				
			||||||
                let metadata = stream.metadata();
 | 
					                .filter(|(_, stream)| {
 | 
				
			||||||
                if metadata.get("language") != Some(lang) {
 | 
					                    let metadata = stream.metadata();
 | 
				
			||||||
                    return false;
 | 
					                    if metadata.get("language") != Some(lang) {
 | 
				
			||||||
                }
 | 
					                        return false;
 | 
				
			||||||
                if metadata
 | 
					                    }
 | 
				
			||||||
                    .get("title")
 | 
					                    if metadata
 | 
				
			||||||
                    .map(|t| SUBTITLE_FORBID_REGEX.is_match(t))
 | 
					                        .get("title")
 | 
				
			||||||
                    == Some(true)
 | 
					                        .map(|t| SUBTITLE_FORBID_REGEX.is_match(t))
 | 
				
			||||||
                {
 | 
					                        == Some(true)
 | 
				
			||||||
                    return false;
 | 
					                    {
 | 
				
			||||||
                }
 | 
					                        return false;
 | 
				
			||||||
                true
 | 
					                    }
 | 
				
			||||||
            })
 | 
					                    true
 | 
				
			||||||
            .min_by_key(|(_, stream)| stream.disposition().contains(Disposition::FORCED))
 | 
					                })
 | 
				
			||||||
            .map(|(idx, _)| idx)
 | 
					                .min_by_key(|(_, stream)| stream.disposition().contains(Disposition::FORCED))
 | 
				
			||||||
    });
 | 
					                .map(|(idx, _)| idx)
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .flatten();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(VideoInfo {
 | 
					    Ok(VideoInfo {
 | 
				
			||||||
        duration_secs,
 | 
					        duration_secs,
 | 
				
			||||||
| 
						 | 
					@ -64,7 +66,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().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") {
 | 
					    if ext != Some("mkv") && ext != Some("mp4") {
 | 
				
			||||||
        return Err(std::io::Error::new(
 | 
					        return Err(std::io::Error::new(
 | 
				
			||||||
            ErrorKind::Other,
 | 
					            ErrorKind::Other,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue