use anyhow::Result;
use clap::Parser;
use std::{
env, fs,
path::{Path, PathBuf},
str::FromStr,
};
mod agent;
mod definitions;
mod download;
mod ffmpeg;
mod handlers;
#[derive(Parser)]
#[clap(version, about = "Yet Another Youtube Down Loader", long_about = None)]
struct Args {
#[clap(long = "only-audio", short = 'x', help = "Only keeps the audio stream")]
onlyaudio: bool,
#[clap(
long = "keep-temp-file",
short = 'k',
help = "Keeps all downloaded data even with --only-audio"
)]
keeptempfile: bool,
#[clap(long, short = 'v', help = "Talks more while the URL is processed")]
verbose: bool,
#[clap(
long = "audio-format",
short = 'f',
help = "Sets the target audio format (only if --only-audio is used).\nSpecify the file extension here.",
default_value = "mp3"
)]
audioformat: String,
#[clap(long = "output", short = 'o', help = "Sets the output file name")]
outputfile: Option<String>,
#[clap(long, help = "The port of your web driver (required for some sites)")]
webdriver: Option<u16>,
#[clap(help = "Sets the input URL to use", index = 1)]
url: String,
}
pub struct VIDEO {
info: String,
title: String,
mime: String,
}
#[allow(non_local_definitions)]
fn main() -> Result<()> {
let args = Args::parse();
let in_url = &args.url;
inventory::collect!(&'static dyn definitions::SiteDefinition);
let mut site_def_found = false;
let mut video = VIDEO {
info: String::new(),
title: String::new(),
mime: String::new(),
};
let mut handled = false;
for handler in inventory::iter::<&dyn definitions::SiteDefinition> {
if args.verbose {
println!("Trying {}.", handler.display_name());
}
let mut webdriverport: u16 = 0;
let webdriver_env = env::var("YAYDL_WEBDRIVER_PORT");
if args.webdriver.is_some() {
webdriverport = args.webdriver.unwrap();
} else if webdriver_env.is_ok() {
webdriverport = u16::from_str(&webdriver_env.unwrap_or("0".to_string())).unwrap_or(0);
}
if !handler
.can_handle_url(&mut video, in_url, webdriverport)
.unwrap_or(false)
{
continue;
}
site_def_found = true;
println!("Fetching from {}.", handler.display_name());
if handler.web_driver_required() && webdriverport == 0 {
println!("{} requires a web driver installed and running as described in the README. Please tell yaydl which port to use (yaydl --webdriver <PORT>) and try again.", handler.display_name());
continue;
}
let video_exists = handler.does_video_exist(&mut video, in_url, webdriverport)?;
if !video_exists {
if args.verbose {
println!(
"{} failed to find the video. Checking for other supported handlers.",
handler.display_name()
);
}
continue;
} else {
if args.verbose {
println!("The requested video was found. Processing...");
}
handled = true;
let video_title = handler.find_video_title(&mut video, in_url, webdriverport);
let vt = match video_title {
Err(_e) => "".to_string(),
Ok(title) => title,
};
if vt.is_empty() {
println!("The video title could not be extracted. Invalid link?");
} else {
if args.verbose {
println!("Title: {}", vt);
}
let url = handler.find_video_direct_url(
&mut video,
in_url,
webdriverport,
args.onlyaudio,
)?;
let ext = handler.find_video_file_extension(
&mut video,
in_url,
webdriverport,
args.onlyaudio,
)?;
let mut targetfile = format!(
"{}.{}",
vt.trim().replace(
&['|', '\'', '\"', ':', '\'', '\\', '/', '?', '*'][..],
r#""#
),
ext
);
if let Some(in_targetfile) = args.outputfile {
targetfile = in_targetfile.to_string();
}
if args.verbose {
println!("Starting the download.");
}
let mut force_ffmpeg = false;
if handler.is_playlist(in_url, webdriverport).unwrap_or(false) {
download::download_from_playlist(&url, &targetfile, args.verbose)?;
force_ffmpeg = true;
} else {
download::download(&url, &targetfile)?;
}
let outputext = args.audioformat;
if args.onlyaudio && ext != outputext || force_ffmpeg {
if args.verbose {
println!("Post-processing.");
}
let inpath = Path::new(&targetfile);
let mut outpathbuf = PathBuf::from(&targetfile);
if args.onlyaudio {
outpathbuf.set_extension(outputext);
let outpath = &outpathbuf.as_path();
ffmpeg::to_audio(inpath, outpath);
} else {
outpathbuf.set_extension("mp4");
let outpath = &outpathbuf.as_path();
ffmpeg::ts_to_mp4(inpath, outpath);
}
if !args.keeptempfile {
fs::remove_file(&targetfile)?;
}
println!(
"\"{}\" successfully downloaded.",
outpathbuf
.into_os_string()
.into_string()
.unwrap_or_else(|_| targetfile.to_string())
);
} else {
println!("\"{}\" successfully downloaded.", &targetfile);
}
}
break;
}
}
if !site_def_found {
println!(
"yaydl could not find a site definition that would satisfy {}. Exiting.",
in_url
);
}
if !handled {
println!("The video could not be found. Invalid link?");
}
Ok(())
}