Compare commits
2
Commits
1b4b4d39c4
...
0abe9a8206
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0abe9a8206 | ||
|
|
5fc42bd0fb |
Generated
+1
@@ -6338,6 +6338,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"webbrowser",
|
||||
"yt-dlp",
|
||||
]
|
||||
|
||||
|
||||
+2
-1
@@ -13,6 +13,7 @@ serde_json = "1.0.151"
|
||||
tokio = {version = "1.53.1", features = ["macros", "rt-multi-thread"]}
|
||||
tracing = "0.1.44"
|
||||
tracing-subscriber = {version = "0.3.23", features = ["env-filter"]}
|
||||
webbrowser = "1.2.4"
|
||||
yt-dlp = "2.8.3"
|
||||
|
||||
[profile.release]
|
||||
@@ -24,4 +25,4 @@ debug-assertions = false
|
||||
incremental = false
|
||||
rpath = false
|
||||
codegen-units = 1
|
||||
strip = true
|
||||
strip = true
|
||||
|
||||
+66
-15
@@ -1,19 +1,52 @@
|
||||
use std::time::Duration;
|
||||
use eframe::{Frame, Storage, CreationContext};
|
||||
use eframe::egui::{Context, Ui};
|
||||
use eframe::{CreationContext, Frame, Storage};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use yt_dlp::model::VideoQuality;
|
||||
|
||||
pub(crate) struct Application {
|
||||
settings: AppSettings,
|
||||
layers: Vec<Box<dyn crate::layer::Layer>>,
|
||||
pub settings: AppSettings,
|
||||
layers: Vec<Box<dyn crate::layer::Layer>>,
|
||||
data: Data,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
struct AppSettings {
|
||||
use_cookies: bool,
|
||||
use_firefox_cookies: bool,
|
||||
use_chrome_cookies: bool,
|
||||
save_thumbnails: bool,
|
||||
pub struct AppSettings {
|
||||
pub(crate) use_firefox_cookies: bool,
|
||||
pub(crate) use_chrome_cookies: bool,
|
||||
pub(crate) save_thumbnails: bool,
|
||||
pub(crate) video_download_quality: crate::video_quality::VideoDownloadQuality,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
struct Data {
|
||||
playlists: Vec<Playlist>,
|
||||
videos: Vec<Video>,
|
||||
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 {
|
||||
@@ -27,12 +60,24 @@ impl Application {
|
||||
}
|
||||
}
|
||||
|
||||
Self { settings, layers }
|
||||
let mut data = Data::default();
|
||||
if let Some(storage) = cc.storage {
|
||||
if let Some(stored_str) = storage.get_string("app_data") {
|
||||
if let Ok(parsed) = serde_json::from_str(&stored_str) {
|
||||
data = parsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
settings,
|
||||
layers,
|
||||
data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for Application {
|
||||
|
||||
fn logic(&mut self, ctx: &Context, frame: &mut Frame) {
|
||||
let mut layers = std::mem::take(&mut self.layers);
|
||||
for layer in &mut layers {
|
||||
@@ -53,15 +98,21 @@ impl eframe::App for Application {
|
||||
if let Ok(serialized) = serde_json::to_string(&self.settings) {
|
||||
_storage.set_string("app_settings", serialized);
|
||||
}
|
||||
|
||||
if let Ok(serialized) = serde_json::to_string(&self.data) {
|
||||
_storage.set_string("app_data", serialized);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_exit(&mut self) {
|
||||
|
||||
let mut layers = std::mem::take(&mut self.layers);
|
||||
for layer in &mut layers {
|
||||
layer.on_exit(self);
|
||||
}
|
||||
self.layers = layers;
|
||||
}
|
||||
|
||||
fn auto_save_interval(&self) -> Duration {
|
||||
Duration::from_secs(5)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+18
-12
@@ -1,14 +1,15 @@
|
||||
use crate::top_bar_layer::TopBarLayer;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::process::exit;
|
||||
use tracing::{debug, error};
|
||||
use crate::test_layer::TestLayer;
|
||||
|
||||
mod logging;
|
||||
mod args;
|
||||
mod app;
|
||||
mod args;
|
||||
mod layer;
|
||||
mod test_layer;
|
||||
mod logging;
|
||||
mod top_bar_layer;
|
||||
mod video_quality;
|
||||
|
||||
fn main() {
|
||||
let args = args::parse_args();
|
||||
@@ -16,7 +17,9 @@ fn main() {
|
||||
debug!("Using args: \n{:#?}\n", args);
|
||||
let data_dir_path = PathBuf::from(&args.data_dir);
|
||||
if data_dir_path.is_file() {
|
||||
error!("data_dir is a file... If you have set the data_dir manually, please pass in a directory. If you didn't pass in a path at all, please report this.");
|
||||
error!(
|
||||
"data_dir is a file... If you have set the data_dir manually, please pass in a directory. If you didn't pass in a path at all, please report this."
|
||||
);
|
||||
error!("{:#?}", args);
|
||||
exit(1);
|
||||
}
|
||||
@@ -28,15 +31,18 @@ fn main() {
|
||||
}
|
||||
|
||||
let mut layers: Vec<Box<dyn layer::Layer>> = Vec::new();
|
||||
layers.push(Box::new(TestLayer::default()));
|
||||
|
||||
layers.push(Box::new(TopBarLayer::default()));
|
||||
|
||||
let mut options = eframe::NativeOptions::default();
|
||||
options.persistence_path = Some(args.state.parse().expect("Failed to parse state config file path into path"));
|
||||
options.persistence_path = Some(
|
||||
args.state
|
||||
.parse()
|
||||
.expect("Failed to parse state config file path into path"),
|
||||
);
|
||||
eframe::run_native(
|
||||
"yt-offline",
|
||||
options,
|
||||
Box::new(|cc| {
|
||||
Ok(Box::new(app::Application::new(cc, layers)))
|
||||
})).expect("TODO: panic message");
|
||||
}
|
||||
Box::new(|cc| Ok(Box::new(app::Application::new(cc, layers)))),
|
||||
)
|
||||
.expect("TODO: panic message");
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
use eframe::egui::{Context, Ui};
|
||||
use eframe::{egui, Frame};
|
||||
use crate::app;
|
||||
use crate::layer::Layer;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TestLayer {
|
||||
test_string: String,
|
||||
}
|
||||
|
||||
impl Layer for TestLayer {
|
||||
fn logic(&mut self, application: &mut app::Application, ctx: &Context, frame: &mut Frame) {
|
||||
|
||||
}
|
||||
|
||||
fn ui(&mut self, application: &mut app::Application, ui: &mut Ui, frame: &mut Frame) {
|
||||
egui::CentralPanel::default().show(ui, |ui| {
|
||||
ui.heading("Hello from the test_layer!");
|
||||
ui.text_edit_singleline(&mut self.test_string);
|
||||
if ui.button("Print test_string in layer").clicked() {
|
||||
println!("{}", self.test_string);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
use crate::app::Application;
|
||||
use crate::layer::Layer;
|
||||
use crate::video_quality::VideoDownloadQuality;
|
||||
use eframe::egui::{Context, Ui};
|
||||
use eframe::{Frame, egui};
|
||||
use tracing::warn;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TopBarLayer {
|
||||
show_settings_window: bool,
|
||||
show_about_window: bool,
|
||||
}
|
||||
|
||||
impl Layer for TopBarLayer {
|
||||
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) {
|
||||
egui::MenuBar::new().ui(ui, |ui: &mut Ui| {
|
||||
ui.menu_button("File", |ui: &mut Ui| {
|
||||
if ui.button("Settings").clicked() {
|
||||
self.show_settings_window = true;
|
||||
}
|
||||
if ui.button("Exit").clicked() {
|
||||
ui.send_viewport_cmd(eframe::egui::ViewportCommand::Close);
|
||||
}
|
||||
});
|
||||
ui.menu_button("Help", |ui: &mut Ui| {
|
||||
if ui.button("About").clicked() {
|
||||
self.show_about_window = true;
|
||||
}
|
||||
if ui.button("Git").clicked() {
|
||||
if let Err(e) =
|
||||
webbrowser::open("https://git.catgirls.fish/firewire/yt-offline")
|
||||
{
|
||||
warn!("Failed to open web browser: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if self.show_settings_window {
|
||||
egui::Window::new("Settings")
|
||||
.open(&mut self.show_settings_window)
|
||||
.show(ui, |ui: &mut Ui| {
|
||||
if ui
|
||||
.checkbox(
|
||||
&mut application.settings.use_firefox_cookies,
|
||||
"Use cookies from Firefox",
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
if application.settings.use_chrome_cookies {
|
||||
application.settings.use_chrome_cookies = false;
|
||||
}
|
||||
};
|
||||
if ui
|
||||
.checkbox(
|
||||
&mut application.settings.use_chrome_cookies,
|
||||
"Use cookies from Chrome",
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
if application.settings.use_firefox_cookies {
|
||||
application.settings.use_firefox_cookies = false;
|
||||
}
|
||||
};
|
||||
ui.checkbox(&mut application.settings.save_thumbnails, "Save thumbnails");
|
||||
ui.menu_button("Video download quality", |ui: &mut Ui| {
|
||||
ui.radio_value(
|
||||
&mut application.settings.video_download_quality,
|
||||
VideoDownloadQuality::Best,
|
||||
"Best",
|
||||
);
|
||||
ui.radio_value(
|
||||
&mut application.settings.video_download_quality,
|
||||
VideoDownloadQuality::High,
|
||||
"High",
|
||||
);
|
||||
ui.radio_value(
|
||||
&mut application.settings.video_download_quality,
|
||||
VideoDownloadQuality::Medium,
|
||||
"Medium",
|
||||
);
|
||||
ui.radio_value(
|
||||
&mut application.settings.video_download_quality,
|
||||
VideoDownloadQuality::Low,
|
||||
"Low",
|
||||
);
|
||||
ui.radio_value(
|
||||
&mut application.settings.video_download_quality,
|
||||
VideoDownloadQuality::Worst,
|
||||
"Worst",
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if self.show_about_window {
|
||||
egui::Window::new("About")
|
||||
.open(&mut self.show_about_window)
|
||||
.show(ui, |ui: &mut Ui| {
|
||||
ui.heading("yt-offline");
|
||||
ui.label("A simple unified offline YouTube downloader + viewer");
|
||||
ui.label("Created by Lyra in Rust <3");
|
||||
ui.separator();
|
||||
ui.label("Honorable mentions:");
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("yt-dlp");
|
||||
ui.hyperlink("https://github.com/yt-dlp/yt-dlp");
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("egui");
|
||||
ui.hyperlink("https://github.com/emilk/egui");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use yt_dlp::model::VideoQuality;
|
||||
|
||||
#[derive(Deserialize, Serialize, PartialEq, Default)]
|
||||
pub enum VideoDownloadQuality {
|
||||
#[default]
|
||||
Best,
|
||||
High,
|
||||
Medium,
|
||||
Low,
|
||||
Worst,
|
||||
}
|
||||
|
||||
impl VideoDownloadQuality {
|
||||
pub fn to_ytdlp(&self) -> VideoQuality {
|
||||
match self {
|
||||
VideoDownloadQuality::Best => VideoQuality::Best,
|
||||
VideoDownloadQuality::High => VideoQuality::High,
|
||||
VideoDownloadQuality::Medium => VideoQuality::Medium,
|
||||
VideoDownloadQuality::Low => VideoQuality::Low,
|
||||
VideoDownloadQuality::Worst => VideoQuality::Worst,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
match self {
|
||||
VideoDownloadQuality::Best => "best".to_string(),
|
||||
VideoDownloadQuality::High => "high".to_string(),
|
||||
VideoDownloadQuality::Medium => "medium".to_string(),
|
||||
VideoDownloadQuality::Low => "low".to_string(),
|
||||
VideoDownloadQuality::Worst => "worst".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user