use log::{debug, error, info}; use rand::{distributions::Standard, seq::IteratorRandom, Rng}; mod config; mod shows; mod video; use crate::shows::EpisodeNumber; #[tokio::main] async fn main() { dotenvy::dotenv().ok(); env_logger::init(); ffmpeg_next::init().unwrap(); let mut rng = rand::thread_rng(); let conf = config::load(); info!("Loading shows from {}", conf.shows_file.display()); let shows = shows::load(conf.shows_file); info!("Logging into cohost as {}", conf.cohost_email); let session = eggbug::Session::login(&conf.cohost_email, &conf.cohost_password) .await .expect("Failed to login to cohost"); 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 descriptor = format!( "{}{}", title, match num { EpisodeNumber::Standalone => String::new(), EpisodeNumber::SingleSeason(n) => format!(" episode {}", n), EpisodeNumber::MultiSeason(season, ep) => format!(" season {} episode {}", season, ep), } ); info!("Selected: {} - {}", descriptor, file.display()); let video_info = video::get_video_info(file, Some("eng")).unwrap(); debug!( "Video duration: {}", format_timestamp(video_info.duration_secs, None) ); debug!( "Subtitle stream index: {:?}", video_info.subtitle_stream_index ); let timestamp = video_info.duration_secs * rng.sample::(Standard); let formatted_timestamp = format_timestamp(timestamp, Some(video_info.duration_secs)); info!("Taking screencap at {}", formatted_timestamp); match video::take_screencap(file, timestamp, video_info.subtitle_stream_index).await { Err(e) => { error!("Failed to take screencap: {}", e); } Ok(img_data) => { let attachment = eggbug::Attachment::new( img_data, format!("{} @{}.png", descriptor, formatted_timestamp), String::from("image/png"), ) .with_alt_text(format!( "Screencap of {} at {}", descriptor, formatted_timestamp )); let mut tags = show.tags.clone(); tags.extend_from_slice(&conf.global_tags); match session .create_post( &conf.cohost_page, &mut eggbug::Post { content_warnings: vec![descriptor], attachments: vec![attachment], tags, draft: false, adult_content: false, headline: String::new(), markdown: String::new(), }, ) .await { Ok(id) => info!("Created post {}", id), Err(e) => error!("Failed to create post: {}", e), } } } tokio::time::sleep(conf.post_interval).await; } } fn format_timestamp(timestamp: f64, total_duration: Option) -> String { let total_duration = total_duration.unwrap_or(timestamp); format!( "{}{:02}:{:05.2}", if total_duration >= 3600.0 { format!("{}:", (timestamp / 3600.0) as u32) } else { String::new() }, ((timestamp % 3600.0) / 60.0) as u32, timestamp % 60.0 ) }