Files
youtube-upload-rs/src/main.rs
LinlyBoi 691e2ad85f refactor: split it into files
Claude might be the goat but his code ain't that clean yo
2025-07-07 18:09:12 +03:00

185 lines
5.6 KiB
Rust

use clap::{Arg, Command};
use oauth2::basic::BasicClient;
use oauth2::reqwest::async_http_client;
use oauth2::{
AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken, PkceCodeChallenge, RedirectUrl,
Scope, TokenResponse, TokenUrl,
};
use reqwest::Client;
use serde_json::json;
use youtube::{create_default_metadata, load_oauth_config, load_video_metadata, YouTubeUploader};
use youtube_scheduler::*;
use std::path::Path;
use tokio;
use chrono::{DateTime, Duration, Utc};
use std::fs::{self, File};
use serde::{Deserialize, Serialize};
use std::io::{self};
mod youtube;
#[cfg(test)]
mod test;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let matches = Command::new("youtube-scheduler")
.version("1.0")
.author("LinlyBoi")
.about("Upload and schedule YouTube videos")
.arg(
Arg::new("videos")
.short('v')
.long("videos")
.value_name("VIDEO_FILES")
.help("Comma-separated list of video file paths")
.required(true),
)
.arg(
Arg::new("interval")
.short('i')
.long("interval")
.value_name("DURATION")
.help("Time interval between uploads (e.g., 2h, 30m, 1d)")
.required(true),
)
.arg(
Arg::new("oauth-config")
.short('c')
.long("oauth-config")
.value_name("CONFIG_FILE")
.help("OAuth configuration file (JSON)")
.default_value("~/.client_secrets.json"),
)
.arg(
Arg::new("metadata")
.short('m')
.long("metadata")
.value_name("METADATA_FILE")
.help("JSON file containing video metadata"),
)
.arg(
Arg::new("start-time")
.short('s')
.long("start-time")
.value_name("START_TIME")
.help("Start time for first upload (ISO 8601 format)"),
)
.arg(
Arg::new("timestamp-file")
.long("timestamp-file")
.value_name("FILE")
.help("File containing unix timestamp for start time"),
)
.arg(
Arg::new("dry-run")
.long("dry-run")
.help("Show schedule without uploading")
.action(clap::ArgAction::SetTrue),
)
.get_matches();
let video_files: Vec<String> = matches
.get_one::<String>("videos")
.unwrap()
.split(',')
.map(|s| s.trim().to_string())
.collect();
let interval_str = matches.get_one::<String>("interval").unwrap();
let interval = parse_duration(interval_str)?;
let oauth_config_path = matches.get_one::<String>("oauth-config").unwrap();
let oauth_config = load_oauth_config(oauth_config_path)?;
let start_time = if let Some(start_str) = matches.get_one::<String>("start-time") {
Some(DateTime::parse_from_rfc3339(start_str)?.with_timezone(&Utc))
} else {
None
};
let timestamp_file = matches.get_one::<String>("timestamp-file");
let dry_run = matches.get_flag("dry-run");
// Load or create metadata
let mut metadata = if let Some(metadata_path) = matches.get_one::<String>("metadata") {
load_video_metadata(metadata_path)?
} else {
create_default_metadata(&video_files)
};
// Generate schedule
let schedule = generate_schedule(
video_files.len(),
interval,
start_time,
timestamp_file.map(|s| s.as_str()),
)?;
// Apply schedule to metadata
for (i, scheduled_time) in schedule.iter().enumerate() {
if i < metadata.len() {
metadata[i].scheduled_start_time = Some(scheduled_time.to_rfc3339());
metadata[i].privacy_status = "private".to_string(); // Set to private for scheduling
}
}
// Display schedule
println!("Upload Schedule:");
println!("================");
for (i, (video_file, scheduled_time)) in video_files.iter().zip(schedule.iter()).enumerate() {
println!(
"{}. {} -> {}",
i + 1,
video_file,
scheduled_time.format("%Y-%m-%d %H:%M:%S UTC")
);
}
if dry_run {
println!("\nDry run complete. No videos were uploaded.");
return Ok(());
}
// Confirm before proceeding
println!("\nProceed with upload? (y/N): ");
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
if !input.trim().to_lowercase().starts_with('y') {
println!("Upload cancelled.");
return Ok(());
}
// Create uploader and authenticate
let mut uploader = YouTubeUploader::new(&oauth_config)?;
if !dry_run {
println!("Authenticating with YouTube...");
uploader.authenticate().await?;
}
// Upload videos
println!("\nUploading videos...");
for (i, (video_file, video_metadata)) in video_files.iter().zip(metadata.iter()).enumerate() {
println!("Uploading {} ({}/{})", video_file, i + 1, video_files.len());
match uploader.upload_video(video_file, video_metadata).await {
Ok(response) => {
println!(
"✓ Successfully uploaded: {} (ID: {})",
video_file, response.id
);
}
Err(e) => {
eprintln!("✗ Failed to upload {}: {}", video_file, e);
}
}
}
println!("\nUpload process completed!");
Ok(())
}
//For the random descriptions LOL