pub mod yt_playlist;
pub mod yt_video;
pub mod config;
use crate::error::{BlobdlError, BlobResult};
use dialoguer::console::Term;
use dialoguer::{theme::ColorfulTheme, Select, Input};
use serde::{Deserialize, Serialize};
use serde_json;
use std::{env, fmt};
fn get_media_selection(term: &Term) -> Result<MediaSelection, std::io::Error> {
let download_formats = &[
"Normal Video",
"Audio-only",
"Video-only"
];
let media_selection = Select::with_theme(&ColorfulTheme::default())
.with_prompt("What kind of file(s) do you want to download?")
.default(0)
.items(download_formats)
.interact_on(term)?;
match media_selection {
0 => Ok(MediaSelection::FullVideo),
1 => Ok(MediaSelection::AudioOnly),
_ => Ok(MediaSelection::VideoOnly),
}
}
fn get_output_path(term: &Term) -> BlobResult<String> {
let output_path_options = &[
"Current directory",
"Other [specify]",
];
let output_path = Select::with_theme(&ColorfulTheme::default())
.with_prompt("Where do you want the downloaded file(s) to be saved?")
.default(0)
.items(output_path_options)
.interact_on(term)?;
match output_path {
0 => Ok(env::current_dir()?
.as_path()
.display()
.to_string()),
_ => Ok(Input::with_theme(&ColorfulTheme::default())
.with_prompt("Output path:")
.interact_text()?),
}
}
use spinoff;
use std::process;
use execute::Execute;
fn get_ytdlp_formats(url: &str) -> Result<process::Output, std::io::Error> {
let sp = spinoff::Spinner::new(spinoff::Spinners::Dots10, "Fetching available formats...", spinoff::Color::Cyan);
let mut command = process::Command::new("yt-dlp");
command.arg("-j");
command.arg("-i");
command.arg(url);
command.stdout(process::Stdio::piped());
command.stderr(process::Stdio::piped());
let output = command.execute_output();
sp.stop();
output
}
fn convert_to_format(term: &Term, media_selected: &MediaSelection)
-> BlobResult<VideoQualityAndFormatPreferences>
{
let format_options = match *media_selected {
MediaSelection::AudioOnly => vec!["mp3", "m4a", "wav", "aac", "alac", "flac", "opus", "vorbis"],
MediaSelection::VideoOnly => vec!["mp4", "mkv", "mov", "avi", "flv", "gif", "webm", "aiff", "mka", "ogg"],
MediaSelection::FullVideo => vec!["mp4", "mkv", "mov", "avi", "flv", "gif", "webm", "aac", "aiff",
"alac", "flac", "m4a", "mka", "mp3", "ogg", "opus", "vorbis", "wav"],
};
let user_selection = Select::with_theme(&ColorfulTheme::default())
.with_prompt("Which container do you want the final file to be in?")
.default(0)
.items(&format_options)
.interact_on(term)?;
Ok(VideoQualityAndFormatPreferences::ConvertTo(format_options[user_selection].to_string()))
}
fn serialize_formats(json_dump: &str) -> BlobResult<VideoSpecs> {
let result = serde_json::from_str(json_dump);
match result {
Ok(cool) => Ok(cool),
Err(err) => Err(BlobdlError::SerdeError(err))
}
}
fn check_format(format: &VideoFormat, media_selected: &MediaSelection) -> bool {
if format.filesize.is_none() {
return false;
}
if *media_selected == MediaSelection::FullVideo && format.resolution == "audio only" {
return false;
}
if *media_selected == MediaSelection::AudioOnly && format.resolution != "audio only" {
return false;
}
if let Some(acodec) = &format.acodec {
if *media_selected == MediaSelection::FullVideo && acodec == "none" {
return false;
}
if *media_selected == MediaSelection::VideoOnly && acodec != "none" {
return false;
}
}
true
}
#[derive(Debug)]
struct GenericConfig {
chosen_format: VideoQualityAndFormatPreferences,
output_path: String,
media_selected: MediaSelection,
include_indexes: bool,
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub(crate) enum MediaSelection {
FullVideo,
VideoOnly,
AudioOnly,
}
#[derive(Deserialize, Serialize, Debug, PartialOrd, PartialEq)]
struct VideoFormat {
format_id: String,
ext: String,
fps: Option<f64>,
audio_channels: Option<u64>,
resolution: String,
filesize: Option<u64>,
vcodec: String,
acodec: Option<String>,
container: Option<String>,
tbr: Option<f64>,
filesize_approx: Option<u64>,
}
#[derive(Deserialize, Serialize, Debug)]
struct VideoSpecs {
formats: Vec<VideoFormat>,
}
#[derive(Debug, Clone)]
pub(crate) enum VideoQualityAndFormatPreferences {
UniqueFormat(String),
ConvertTo(String),
BestQuality,
SmallestSize,
}
impl fmt::Display for VideoFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut result;
if let Some(tbr) = self.tbr {
result = format!("{:<6} ", self.ext);
if self.resolution != "audio only" {
result = format!("{}| {:<13} ", result, self.resolution);
}
let filesize = self.filesize.unwrap_or(0);
let filesize_section = format!("| filesize: {:<.2}MB", filesize as f32 * 0.000001);
result = format!("{}{:<24}", result, filesize_section);
if let Some(ch) = self.audio_channels {
result = format!("{}| {} audio ch ", result, ch);
}
result = format!("{}| tbr: {:<8.2} ", result, tbr);
if self.vcodec != "none" {
result = format!("{}| vcodec: {:<13} ", result, self.vcodec);
}
if let Some(acodec) = &self.acodec {
if acodec != "none" {
result = format!("{}| acodec: {:<13} ", result, acodec);
}
}
#[cfg(debug_assertions)]
return {
result = format!("[[DEBUG code: {:<3}]] {} ", self.format_id, result);
write!(f, "{}", result)
};
#[cfg(not(debug_assertions))]
write!(f, "{}", result)
} else {
write!(f, "I shouldn't show up because I am a picture format")
}
}
}
impl VideoSpecs {
fn formats(&self) -> &Vec<VideoFormat> {
&self.formats
}
}