Create downloader layer + manage library installation
This commit is contained in:
+21
-33
@@ -1,13 +1,16 @@
|
|||||||
|
use crate::args::ArgsConfig;
|
||||||
|
use crate::types::{Playlist, Video};
|
||||||
|
use crate::video_quality::VideoDownloadQuality;
|
||||||
use eframe::egui::{Context, Ui};
|
use eframe::egui::{Context, Ui};
|
||||||
use eframe::{CreationContext, Frame, Storage};
|
use eframe::{CreationContext, Frame, Storage};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use yt_dlp::model::VideoQuality;
|
|
||||||
|
|
||||||
pub(crate) struct Application {
|
pub(crate) struct Application {
|
||||||
pub settings: AppSettings,
|
pub(crate) settings: AppSettings,
|
||||||
layers: Vec<Box<dyn crate::layer::Layer>>,
|
layers: Vec<Box<dyn crate::layer::Layer>>,
|
||||||
data: Data,
|
pub(crate) data: Data,
|
||||||
|
pub(crate) data_dir: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default)]
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
@@ -15,42 +18,22 @@ pub struct AppSettings {
|
|||||||
pub(crate) use_firefox_cookies: bool,
|
pub(crate) use_firefox_cookies: bool,
|
||||||
pub(crate) use_chrome_cookies: bool,
|
pub(crate) use_chrome_cookies: bool,
|
||||||
pub(crate) save_thumbnails: bool,
|
pub(crate) save_thumbnails: bool,
|
||||||
pub(crate) video_download_quality: crate::video_quality::VideoDownloadQuality,
|
pub(crate) video_download_quality: VideoDownloadQuality,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default)]
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
struct Data {
|
pub(crate) struct Data {
|
||||||
playlists: Vec<Playlist>,
|
pub(crate) playlists: Vec<Playlist>,
|
||||||
videos: Vec<Video>,
|
pub(crate) videos: Vec<Video>,
|
||||||
root_path: String,
|
pub(crate) root_path: String,
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default)]
|
|
||||||
struct Video {
|
|
||||||
name: String,
|
|
||||||
uuid: String,
|
|
||||||
original_url: String,
|
|
||||||
video_path: String,
|
|
||||||
has_thumbnail: bool,
|
|
||||||
thumbnail_path: String,
|
|
||||||
thumbnail_path_low_quality: String,
|
|
||||||
video_quality: VideoQuality,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default)]
|
|
||||||
struct Playlist {
|
|
||||||
name: String,
|
|
||||||
uuid: String,
|
|
||||||
original_url: String,
|
|
||||||
videos: Vec<Video>,
|
|
||||||
creator: String,
|
|
||||||
has_thumbnail: bool,
|
|
||||||
thumbnail_path: String,
|
|
||||||
thumbnail_path_low_quality: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Application {
|
impl Application {
|
||||||
pub fn new(cc: &CreationContext, layers: Vec<Box<dyn crate::layer::Layer>>) -> Self {
|
pub fn new(
|
||||||
|
cc: &CreationContext,
|
||||||
|
layers: Vec<Box<dyn crate::layer::Layer>>,
|
||||||
|
args_config: &ArgsConfig,
|
||||||
|
) -> Self {
|
||||||
let mut settings = AppSettings::default();
|
let mut settings = AppSettings::default();
|
||||||
if let Some(storage) = cc.storage {
|
if let Some(storage) = cc.storage {
|
||||||
if let Some(stored_str) = storage.get_string("app_settings") {
|
if let Some(stored_str) = storage.get_string("app_settings") {
|
||||||
@@ -69,10 +52,15 @@ impl Application {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if data.root_path.is_empty() {
|
||||||
|
data.root_path = args_config.data_dir.clone() + "/data";
|
||||||
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
settings,
|
settings,
|
||||||
layers,
|
layers,
|
||||||
data,
|
data,
|
||||||
|
data_dir: args_config.data_dir.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
use crate::app::Application;
|
||||||
|
use crate::layer::Layer;
|
||||||
|
use crate::types::{Playlist, Video};
|
||||||
|
use eframe::egui::{Context, Ui};
|
||||||
|
use eframe::{Frame, egui};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use tokio::runtime::Handle;
|
||||||
|
use tracing::warn;
|
||||||
|
use yt_dlp::client::LibraryInstaller;
|
||||||
|
|
||||||
|
pub(crate) struct Downloader {
|
||||||
|
video_queue: Vec<Video>,
|
||||||
|
playlists_queue: Vec<Playlist>,
|
||||||
|
yt_downloader: Option<yt_dlp::Downloader>,
|
||||||
|
|
||||||
|
tokio_handle: Handle,
|
||||||
|
|
||||||
|
yt_dlp_installed: bool,
|
||||||
|
rejected_yt_dlp_install: bool,
|
||||||
|
|
||||||
|
ffmpeg_installed: bool,
|
||||||
|
rejected_ffmpeg_install: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Downloader {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let yt_downloader = None;
|
||||||
|
Self {
|
||||||
|
video_queue: Vec::new(),
|
||||||
|
playlists_queue: Vec::new(),
|
||||||
|
yt_downloader,
|
||||||
|
tokio_handle: Handle::current(),
|
||||||
|
yt_dlp_installed: false,
|
||||||
|
rejected_yt_dlp_install: false,
|
||||||
|
ffmpeg_installed: false,
|
||||||
|
rejected_ffmpeg_install: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn install_yt_dlp(data_dir: String) -> Result<(), String> {
|
||||||
|
let libraries_dir = PathBuf::from(format!("{}/libs", &data_dir));
|
||||||
|
let installer = LibraryInstaller::new(libraries_dir);
|
||||||
|
if let Err(e) = installer.install_youtube(None).await {
|
||||||
|
warn!("Failed to install yt-dlp: {}", e);
|
||||||
|
return Err(e.to_string());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn install_ffmpeg(data_dir: String) -> Result<(), String> {
|
||||||
|
let libraries_dir = PathBuf::from(format!("{}/libs", &data_dir));
|
||||||
|
let installer = LibraryInstaller::new(libraries_dir);
|
||||||
|
if let Err(e) = installer.install_ffmpeg(None).await {
|
||||||
|
warn!("Failed to install yt-dlp: {}", e);
|
||||||
|
return Err(e.to_string());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_new_yt_downloader(application: &Application) -> Option<yt_dlp::Downloader> {
|
||||||
|
Err("Not implemented".to_string()).expect("Soon")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_yt_dlp(data_dir: String) -> bool {
|
||||||
|
let path = PathBuf::from(format!("{}/libs/yt-dlp", &data_dir));
|
||||||
|
path.exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_ffmpeg(data_dir: String) -> bool {
|
||||||
|
let path = PathBuf::from(format!("{}/libs/ffmpeg", &data_dir));
|
||||||
|
path.exists()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Layer for Downloader {
|
||||||
|
fn logic(&mut self, application: &mut Application, ctx: &Context, frame: &mut Frame) {
|
||||||
|
if !self.yt_dlp_installed {
|
||||||
|
if Downloader::check_yt_dlp(application.data_dir.clone()) {
|
||||||
|
self.yt_dlp_installed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !self.ffmpeg_installed {
|
||||||
|
if Downloader::check_ffmpeg(application.data_dir.clone()) {
|
||||||
|
self.ffmpeg_installed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ui(&mut self, application: &mut Application, ui: &mut Ui, frame: &mut Frame) {
|
||||||
|
if !self.yt_dlp_installed && !self.rejected_yt_dlp_install {
|
||||||
|
egui::Window::new("Missing yt-dlp").show(ui, |ui| {
|
||||||
|
ui.label("yt-dlp is not installed. Would you like to install it?");
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.button("Yes").clicked() {
|
||||||
|
self.tokio_handle
|
||||||
|
.spawn(Self::install_yt_dlp(application.data_dir.clone()));
|
||||||
|
}
|
||||||
|
if ui.button("No").clicked() {
|
||||||
|
self.rejected_yt_dlp_install = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.ffmpeg_installed && !self.rejected_ffmpeg_install {
|
||||||
|
egui::Window::new("Missing ffmpeg").show(ui, |ui| {
|
||||||
|
ui.label("ffmpeg is not installed. Would you like to install it?");
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.button("Yes").clicked() {
|
||||||
|
self.tokio_handle
|
||||||
|
.spawn(Self::install_ffmpeg(application.data_dir.clone()));
|
||||||
|
}
|
||||||
|
if ui.button("No").clicked() {
|
||||||
|
self.rejected_ffmpeg_install = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-11
@@ -1,19 +1,13 @@
|
|||||||
use eframe::{egui, Frame};
|
use crate::app::Application;
|
||||||
|
use eframe::{Frame, egui};
|
||||||
use egui::Context;
|
use egui::Context;
|
||||||
use egui::Ui;
|
use egui::Ui;
|
||||||
use crate::app::Application;
|
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
pub trait Layer {
|
pub trait Layer {
|
||||||
fn logic(&mut self, application: &mut Application, ctx: &Context, frame: &mut Frame) {
|
fn logic(&mut self, application: &mut Application, ctx: &Context, frame: &mut Frame) {}
|
||||||
|
|
||||||
}
|
fn ui(&mut self, application: &mut Application, ui: &mut Ui, frame: &mut Frame) {}
|
||||||
|
|
||||||
fn ui(&mut self, application: &mut Application, ui: &mut Ui, frame: &mut Frame) {
|
fn on_exit(&mut self, application: &mut Application) {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_exit(&mut self, application: &mut Application) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+6
-2
@@ -6,12 +6,15 @@ use tracing::{debug, error};
|
|||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
mod args;
|
mod args;
|
||||||
|
mod downloader;
|
||||||
mod layer;
|
mod layer;
|
||||||
mod logging;
|
mod logging;
|
||||||
mod top_bar_layer;
|
mod top_bar_layer;
|
||||||
|
mod types;
|
||||||
mod video_quality;
|
mod video_quality;
|
||||||
|
|
||||||
fn main() {
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
let args = args::parse_args();
|
let args = args::parse_args();
|
||||||
logging::init(args.verbose);
|
logging::init(args.verbose);
|
||||||
debug!("Using args: \n{:#?}\n", args);
|
debug!("Using args: \n{:#?}\n", args);
|
||||||
@@ -32,6 +35,7 @@ fn main() {
|
|||||||
|
|
||||||
let mut layers: Vec<Box<dyn layer::Layer>> = Vec::new();
|
let mut layers: Vec<Box<dyn layer::Layer>> = Vec::new();
|
||||||
layers.push(Box::new(TopBarLayer::default()));
|
layers.push(Box::new(TopBarLayer::default()));
|
||||||
|
layers.push(Box::new(downloader::Downloader::new()));
|
||||||
|
|
||||||
let mut options = eframe::NativeOptions::default();
|
let mut options = eframe::NativeOptions::default();
|
||||||
options.persistence_path = Some(
|
options.persistence_path = Some(
|
||||||
@@ -42,7 +46,7 @@ fn main() {
|
|||||||
eframe::run_native(
|
eframe::run_native(
|
||||||
"yt-offline",
|
"yt-offline",
|
||||||
options,
|
options,
|
||||||
Box::new(|cc| Ok(Box::new(app::Application::new(cc, layers)))),
|
Box::new(|cc| Ok(Box::new(app::Application::new(cc, layers, &args)))),
|
||||||
)
|
)
|
||||||
.expect("TODO: panic message");
|
.expect("TODO: panic message");
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-6
@@ -67,27 +67,27 @@ impl Layer for TopBarLayer {
|
|||||||
ui.radio_value(
|
ui.radio_value(
|
||||||
&mut application.settings.video_download_quality,
|
&mut application.settings.video_download_quality,
|
||||||
VideoDownloadQuality::Best,
|
VideoDownloadQuality::Best,
|
||||||
"Best",
|
"Best (Highest available)",
|
||||||
);
|
);
|
||||||
ui.radio_value(
|
ui.radio_value(
|
||||||
&mut application.settings.video_download_quality,
|
&mut application.settings.video_download_quality,
|
||||||
VideoDownloadQuality::High,
|
VideoDownloadQuality::High,
|
||||||
"High",
|
"High (Target 1080p)",
|
||||||
);
|
);
|
||||||
ui.radio_value(
|
ui.radio_value(
|
||||||
&mut application.settings.video_download_quality,
|
&mut application.settings.video_download_quality,
|
||||||
VideoDownloadQuality::Medium,
|
VideoDownloadQuality::Medium,
|
||||||
"Medium",
|
"Medium (Target 720p)",
|
||||||
);
|
);
|
||||||
ui.radio_value(
|
ui.radio_value(
|
||||||
&mut application.settings.video_download_quality,
|
&mut application.settings.video_download_quality,
|
||||||
VideoDownloadQuality::Low,
|
VideoDownloadQuality::Low,
|
||||||
"Low",
|
"Low (Target 480p)",
|
||||||
);
|
);
|
||||||
ui.radio_value(
|
ui.radio_value(
|
||||||
&mut application.settings.video_download_quality,
|
&mut application.settings.video_download_quality,
|
||||||
VideoDownloadQuality::Worst,
|
VideoDownloadQuality::Worst,
|
||||||
"Worst",
|
"Worst (Lowest available)",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -99,7 +99,7 @@ impl Layer for TopBarLayer {
|
|||||||
.show(ui, |ui: &mut Ui| {
|
.show(ui, |ui: &mut Ui| {
|
||||||
ui.heading("yt-offline");
|
ui.heading("yt-offline");
|
||||||
ui.label("A simple unified offline YouTube downloader + viewer");
|
ui.label("A simple unified offline YouTube downloader + viewer");
|
||||||
ui.label("Created by Lyra in Rust <3");
|
ui.label("Fully human generated by Lyra in Rust <3");
|
||||||
ui.separator();
|
ui.separator();
|
||||||
ui.label("Honorable mentions:");
|
ui.label("Honorable mentions:");
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
@@ -110,6 +110,14 @@ impl Layer for TopBarLayer {
|
|||||||
ui.label("egui");
|
ui.label("egui");
|
||||||
ui.hyperlink("https://github.com/emilk/egui");
|
ui.hyperlink("https://github.com/emilk/egui");
|
||||||
});
|
});
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("egui-sharkplayer");
|
||||||
|
ui.hyperlink("https://codeberg.org/sharkyshark/egui-sharkplayer");
|
||||||
|
});
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("The yt-dlp crate");
|
||||||
|
ui.hyperlink("https://github.com/boul2gom/yt-dlp")
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
use crate::video_quality::VideoDownloadQuality;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
|
pub(crate) struct Video {
|
||||||
|
pub(crate) meta_data: MetaData,
|
||||||
|
pub(crate) video_path: String,
|
||||||
|
pub(crate) video_quality: VideoDownloadQuality,
|
||||||
|
pub(crate) downloaded: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
|
pub(crate) struct MetaData {
|
||||||
|
pub(crate) title: String,
|
||||||
|
pub(crate) author: String,
|
||||||
|
pub(crate) description: String,
|
||||||
|
pub(crate) original_url: String,
|
||||||
|
pub(crate) has_thumbnail: bool,
|
||||||
|
pub(crate) thumbnail_path: String,
|
||||||
|
pub(crate) thumbnail_path_low_quality: String,
|
||||||
|
pub(crate) downloaded: bool,
|
||||||
|
pub(crate) date_downloaded: String,
|
||||||
|
pub(crate) date_published: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
|
pub(crate) struct Playlist {
|
||||||
|
pub(crate) meta_data: MetaData,
|
||||||
|
pub(crate) videos: Vec<Video>,
|
||||||
|
pub(crate) num_videos: usize,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user