From 5b58834c9ffbe6aa17a6c365b55ede4e8db01ee6 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Wed, 12 May 2021 17:16:37 +0200 Subject: [PATCH] merged ytd.rs into lib.rs, no extra module needed --- Cargo.toml | 4 +- src/lib.rs | 237 ++++++++++++++++++++++++++++++++++++++++++++++++++++- src/ytd.rs | 186 ----------------------------------------- 3 files changed, 235 insertions(+), 192 deletions(-) delete mode 100644 src/ytd.rs diff --git a/Cargo.toml b/Cargo.toml index e4ada6c..d0e5b0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "ytd-rs" -version = "0.1.0" +version = "0.1.1" authors = ["Nils Pukropp "] edition = "2018" license-file ="LICENSE" description = "A simple wrapper for youtube-dl. Youtube-dl has to be installed on the system" -homepape = "https://github.com/Nirusu99/ytd-rs" +homepage = "https://github.com/Nirusu99/ytd-rs" repository = "https://github.com/Nirusu99/ytd-rs" readme = "README.md" diff --git a/src/lib.rs b/src/lib.rs index 01413cd..6b9969d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,20 +1,249 @@ -pub mod ytd; +//! Rust-Wrapper for youtube-dl +//! +//! # Example +//! +//! ```no_run +//! use ytd_rs::{YoutubeDL, ResultType, Arg}; +//! // youtube-dl arguments quietly run process and to format the output +//! // one doesn't take any input and is an option, the other takes the desired output format as input +//! let args = vec![Arg::new("--quiet"), Arg::new_with_arg("--output", "%(title).90s.%(ext)s")]; +//! let link = "https://www.youtube.com/watch?v=uTO0KnDsVH0"; +//! let ytd = YoutubeDL::new("./path/to/download/directory", args, link).unwrap(); +//! +//! // start download +//! let download = ytd.download(); +//! +//! // check what the result is and print out the path to the download or the error +//! match download.result_type() { +//! ResultType::SUCCESS => println!("Your download: {}", download.output_dir().to_string_lossy()), +//! ResultType::IOERROR | ResultType::FAILURE => +//! println!("Couldn't start download: {}", download.output()), +//! }; +//! ``` + +use std::{ + fmt, + process::{Output, Stdio}, +}; +use std::{ + fmt::{Display, Formatter}, + fs::{canonicalize, create_dir_all}, + io::Error, + path::PathBuf, +}; +use std::{path::Path, process::Command}; + +/// A structure that represents an argument of a youtube-dl command. +/// +/// There are two different kinds of Arg: +/// - Option with no other input +/// - Argument with input +/// +/// # Example +/// +/// ``` +/// use ytd_rs::Arg; +/// // youtube-dl option to embed metadata into the file +/// // doesn't take any input +/// let simple_arg = Arg::new("--add-metadata"); +/// +/// // youtube-dl cookies argument that takes a path to +/// // cookie file +/// let input_arg = Arg::new_with_arg("--cookie", "/path/to/cookie"); +/// ``` +#[derive(Clone, Debug)] +pub struct Arg { + arg: String, + input: Option, +} + +impl Arg { + pub fn new(argument: &str) -> Arg { + Arg { + arg: argument.to_string(), + input: None, + } + } + + pub fn new_with_arg(argument: &str, input: &str) -> Arg { + Arg { + arg: argument.to_string(), + input: Option::from(input.to_string()), + } + } +} + +impl Display for Arg { + fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + match &self.input { + Some(input) => write!(fmt, "{} {}", self.arg, input), + None => write!(fmt, "{}", self.arg), + } + } +} + +/// Structure that represents a youtube-dl task. +/// +/// Every task needs a download location [`path`], a list of ['Arg'] that can be empty +/// and a ['link'] to the desired source. +#[derive(Clone, Debug)] +pub struct YoutubeDL { + path: PathBuf, + link: String, + args: Vec, +} + +/// +/// This is the result of a [`YoutubeDL`]. +/// +/// It contains the information about the exit status, the output and the directory it was executed +/// in. +/// +#[derive(Debug, Clone)] +pub struct YoutubeDLResult { + path: PathBuf, + output: String, + kind: ResultType, +} + +impl YoutubeDLResult { + /// creates a new YoutubeDLResult + fn new(path: PathBuf) -> YoutubeDLResult { + YoutubeDLResult { + path, + output: String::new(), + kind: ResultType::FAILURE, + } + } + + /// get the exit status of the process + pub fn result_type(&self) -> &ResultType { + &self.kind + } + + /// get the output of the youtube-dl process + pub fn output(&self) -> &str { + &self.output + } + + /// get the directory where youtube-dl was executed + pub fn output_dir(&self) -> &PathBuf { + &self.path + } +} + +/// Represents the different exit types of the [`YoutubeDL`] process +#[derive(Debug, Clone)] +pub enum ResultType { + SUCCESS, + FAILURE, + IOERROR, +} + +impl YoutubeDL { + /// Creates a new YoutubeDL job to be executed. + /// It takes a path where youtube-dl should be executed, a vec! of [`Arg`] that can be empty + /// and finally a link that can be `""` if no video should be downloaded + /// + /// The path gets canonicalized and the directory gets created by the constructor + pub fn new(dl_path: &str, args: Vec, link: &str) -> Result { + // create path + let path = Path::new(dl_path); + + // check if it already exists + if !path.exists() { + // if not create + if let Err(why) = create_dir_all(&path) { + return Err(format!( + "Error while creating directories {}: {:?}", + dl_path, why + )); + } + } + + // return error if no directory + if !path.is_dir() { + return Err("Error: path is not a directory".to_string()); + } + + // absolute path + match canonicalize(dl_path) { + // return new youtube-dl job + Ok(path) => Ok(YoutubeDL { + path, + link: link.to_string(), + args, + }), + Err(why) => Err(format!("Error creating YouTubeDL: {:?}", why)), + } + } + + /// Starts the download and returns when finished the result as [`YoutubeDLResult`]. + pub fn download(&self) -> YoutubeDLResult { + let pr_result = self.spawn_youtube_dl(); + let mut result = YoutubeDLResult::new(self.path.clone()); + + let output = match pr_result { + Err(why) => { + result.output = why.to_string(); + result.kind = ResultType::IOERROR; + return result; + } + Ok(output) => output, + }; + + if output.status.success() { + result.kind = ResultType::SUCCESS; + result.output = String::from_utf8_lossy(&output.stdout).to_string(); + } else { + result.output = String::from_utf8_lossy(&output.stderr).to_string(); + } + + result + } + + fn spawn_youtube_dl(&self) -> Result { + let mut cmd = Command::new("youtube-dl"); + cmd.current_dir(&self.path) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + for arg in self.args.iter() { + match &arg.input { + Some(input) => cmd.arg(&arg.arg).arg(input), + None => cmd.arg(&arg.arg), + }; + } + + cmd.arg(&self.link); + + let pr = match cmd.spawn() { + Err(why) => { + return Err(why); + } + Ok(process) => process, + }; + pr.wait_with_output() + } +} #[cfg(test)] mod tests { + use crate::{Arg, YoutubeDL}; use regex::Regex; use std::env; #[test] fn version() { - let ytd = crate::ytd::YoutubeDL::new( + let ytd = YoutubeDL::new( env::current_dir() .expect("couldn't get working directory") .to_str() .unwrap(), // get youtube-dl version - vec![crate::ytd::Arg::new("--version")], - String::new(), + vec![Arg::new("--version")], + // we don't need a link to print version + "", ) .expect("Couldn't create youtube-dl"); diff --git a/src/ytd.rs b/src/ytd.rs deleted file mode 100644 index 87542e1..0000000 --- a/src/ytd.rs +++ /dev/null @@ -1,186 +0,0 @@ -//! Rust-Wrapper for youtube-dl - -use std::{ - fmt, - process::{Output, Stdio}, -}; -use std::{ - fmt::{Display, Formatter}, - fs::{canonicalize, create_dir_all}, - io::Error, - path::PathBuf, -}; -use std::{path::Path, process::Command}; - -/// A structure that represents an argument of a youtube-dl command. -/// -/// There are two different kinds of Arg: -/// - Option with no other input -/// - Argument with input -/// -/// # Example -/// -/// ``` -/// use ytd_rs::ytd::Arg; -/// // youtube-dl option to embed metadata into the file -/// // doesn't take any input -/// let simple_arg = Arg::new("--add-metadata"); -/// -/// // youtube-dl cookies argument that takes a path to -/// // cookie file -/// let input_arg = Arg::new_with_arg("--cookie", "/path/to/cookie"); -/// ``` -#[derive(Clone, Debug)] -pub struct Arg { - arg: String, - input: Option, -} - -impl Arg { - pub fn new(argument: &str) -> Arg { - Arg { - arg: argument.to_string(), - input: None, - } - } - - pub fn new_with_arg(argument: &str, input: &str) -> Arg { - Arg { - arg: argument.to_string(), - input: Option::from(input.to_string()), - } - } -} - -impl Display for Arg { - fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { - match &self.input { - Some(input) => write!(fmt, "{} {}", self.arg, input), - None => write!(fmt, "{}", self.arg), - } - } -} - -/// Structure that represents a youtube-dl task. -/// -/// Every task needs a download location [`path`], a list of ['Arg'] that can be empty -/// and a ['link'] to the desired source. -#[derive(Clone, Debug)] -pub struct YoutubeDL { - path: PathBuf, - link: String, - args: Vec, -} - -#[derive(Debug, Clone)] -pub struct YoutubeDLResult { - path: PathBuf, - output: String, - kind: ResultType, -} - -impl YoutubeDLResult { - pub fn new(path: PathBuf) -> YoutubeDLResult { - YoutubeDLResult { - path, - output: String::new(), - kind: ResultType::FAILURE, - } - } - - pub fn result_type(&self) -> &ResultType { - &self.kind - } - - pub fn output(&self) -> &str { - &self.output - } - - pub fn output_dir(&self) -> &PathBuf { - &self.path - } -} - -#[derive(Debug, Clone)] -pub enum ResultType { - SUCCESS, - FAILURE, - IOERROR, -} - -impl YoutubeDL { - pub fn new(dl_path: &str, args: Vec, link: String) -> Result { - // create path - let path = Path::new(dl_path); - - // check if it already exists - if !path.exists() { - // if not create - if let Err(why) = create_dir_all(&path) { - return Err(format!( - "Error while creating directories {}: {:?}", - dl_path, why - )); - } - } - - // return error if no directory - if !path.is_dir() { - return Err("Error: path is not a directory".to_string()); - } - - // absolute path - match canonicalize(dl_path) { - // return new youtube-dl job - Ok(path) => Ok(YoutubeDL { path, link, args }), - Err(why) => Err(format!("Error creating YouTubeDL: {:?}", why)), - } - } - - pub fn download(&self) -> YoutubeDLResult { - let pr_result = self.spawn_youtube_dl(); - let mut result = YoutubeDLResult::new(self.path.clone()); - - let output = match pr_result { - Err(why) => { - result.output = why.to_string(); - result.kind = ResultType::IOERROR; - return result; - } - Ok(output) => output, - }; - - if output.status.success() { - result.kind = ResultType::SUCCESS; - result.output = String::from_utf8_lossy(&output.stdout).to_string(); - } else { - result.output = String::from_utf8_lossy(&output.stderr).to_string(); - } - - result - } - - fn spawn_youtube_dl(&self) -> Result { - let mut cmd = Command::new("youtube-dl"); - cmd.current_dir(&self.path) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()); - - for arg in self.args.iter() { - match &arg.input { - Some(input) => cmd.arg(&arg.arg).arg(input), - None => cmd.arg(&arg.arg), - }; - } - - cmd.arg(&self.link); - - let pr = match cmd.spawn() { - Err(why) => { - return Err(why); - } - Ok(process) => process, - }; - pr.wait_with_output() - } -}