modified: Cargo.lock
modified: Cargo.toml modified: src/config.rs new file: src/dlp.rs modified: src/main.rs modified: src/main_screen.rs modified: src/sync.rs
This commit is contained in:
parent
34cf8c0a15
commit
f016bd754b
75
Cargo.lock
generated
75
Cargo.lock
generated
@ -32,6 +32,21 @@ version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
@ -139,6 +154,20 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color-eyre"
|
||||
version = "0.6.3"
|
||||
@ -624,6 +653,29 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "1.5.0"
|
||||
@ -923,6 +975,7 @@ dependencies = [
|
||||
name = "lyrica"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"color-eyre",
|
||||
"crossterm",
|
||||
"dirs",
|
||||
@ -994,6 +1047,15 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.7"
|
||||
@ -1563,8 +1625,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "soundcloud"
|
||||
version = "0.1.1"
|
||||
source = "git+https://gitea.awain.net/alterwain/soundcloud_api.git#87614c2a10c30f7d3e4b3cb0f8973d62ffec7916"
|
||||
version = "0.1.4"
|
||||
source = "git+https://gitea.awain.net/alterwain/soundcloud_api.git#22f02cfa43bb91370211b64c9c6240496bd44515"
|
||||
dependencies = [
|
||||
"hyper-util",
|
||||
"regex",
|
||||
@ -2166,6 +2228,15 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.2.0"
|
||||
|
@ -6,6 +6,7 @@ license = "AGPLv3"
|
||||
authors = ["Michael Wain <alterwain@protonmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4.39"
|
||||
rusb = "0.9.4"
|
||||
dirs = "6.0.0"
|
||||
toml = "0.8.20"
|
||||
@ -17,5 +18,5 @@ crossterm = "0.28.1"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-util = { version = "0.7.12", features = ["codec"] }
|
||||
strum = { version = "0.27", features = ["derive"] }
|
||||
soundcloud = { version = "0.1.1", git = "https://gitea.awain.net/alterwain/soundcloud_api.git" }
|
||||
soundcloud = { version = "0.1.4", git = "https://gitea.awain.net/alterwain/soundcloud_api.git" }
|
||||
itunesdb = { version = "0.1.1", git = "https://gitea.awain.net/alterwain/ITunesDB.git" }
|
@ -1,6 +1,6 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn get_configs_dir() -> PathBuf {
|
||||
let mut p = dirs::home_dir().unwrap();
|
||||
@ -8,22 +8,47 @@ pub fn get_configs_dir() -> PathBuf {
|
||||
p
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub fn get_temp_dl_dir() -> PathBuf {
|
||||
let mut p = get_configs_dir();
|
||||
p.push("tmp");
|
||||
p
|
||||
}
|
||||
|
||||
pub fn get_config_path() -> PathBuf {
|
||||
let mut p = get_configs_dir();
|
||||
p.push("config");
|
||||
p.set_extension(".toml");
|
||||
p
|
||||
}
|
||||
|
||||
pub fn get_temp_itunesdb() -> PathBuf {
|
||||
let mut p = get_configs_dir();
|
||||
p.push("idb");
|
||||
p
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct YouTubeConfiguration {
|
||||
pub user_id: u64
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct SoundCloudConfiguration {
|
||||
pub user_id: u64
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct LyricaConfiguration {
|
||||
soundcloud: SoundCloudConfiguration,
|
||||
youtube: YouTubeConfiguration
|
||||
}
|
||||
|
||||
impl Default for LyricaConfiguration {
|
||||
fn default() -> Self {
|
||||
Self { soundcloud: SoundCloudConfiguration { user_id: 0 }, youtube: YouTubeConfiguration { user_id: 0 } }
|
||||
}
|
||||
}
|
||||
|
||||
impl LyricaConfiguration {
|
||||
pub fn get_soundcloud(&self) -> &SoundCloudConfiguration {
|
||||
&self.soundcloud
|
||||
|
28
src/dlp.rs
Normal file
28
src/dlp.rs
Normal file
@ -0,0 +1,28 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use tokio::process::Command;
|
||||
|
||||
pub async fn download_from_soundcloud(playlist_url: &str, download_dir: &PathBuf) -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||
let args = &[
|
||||
"--ignore-errors",
|
||||
"--newline",
|
||||
"--progress-template",
|
||||
"{\"progressPercentage\":\"%(progress._percent_str)s\",\"progressTotal\":\"%(progress._total_bytes_str)s\",\"speed\":\"%(progress._speed_str)s\",\"ETA\":\"%(progress._eta_str)s\"}",
|
||||
"-o",
|
||||
"%(id)i.%(ext)s",
|
||||
"--write-thumbnail",
|
||||
playlist_url
|
||||
];
|
||||
|
||||
let mut command = Command::new("yt-dlp");
|
||||
command.args(args);
|
||||
command.current_dir(download_dir);
|
||||
|
||||
let mut child = command.spawn()?;
|
||||
|
||||
let mut stdout = Vec::new();
|
||||
let child_stdout = child.stdout.take();
|
||||
tokio::io::copy(&mut child_stdout.unwrap(), &mut stdout).await.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
@ -11,6 +11,7 @@ use tokio_util::sync::CancellationToken;
|
||||
use ratatui::prelude::Constraint::{Length, Min};
|
||||
use wait_screen::WaitScreen;
|
||||
|
||||
mod dlp;
|
||||
mod util;
|
||||
mod config;
|
||||
mod screen;
|
||||
|
@ -1,14 +1,17 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use color_eyre::owo_colors::OwoColorize;
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use ratatui::{buffer::Buffer, layout::{Constraint, Direction, Layout, Rect}, style::{Color, Modifier, Style, Stylize}, text::{Line, Span}, widgets::{Block, Borders, Paragraph, Tabs, Widget}, Frame};
|
||||
use ratatui::{buffer::Buffer, layout::{Constraint, Direction, Layout, Rect}, style::{Color, Modifier, Style, Stylize}, text::{Line, Span}, widgets::{Block, Borders, List, ListItem, Paragraph, Row, Table, Tabs, Widget}, Frame};
|
||||
use soundcloud::sobjects::CloudPlaylists;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::screen::AppScreen;
|
||||
use crate::{config::get_temp_dl_dir, dlp, screen::AppScreen};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MainScreen {
|
||||
selected_tab: i8,
|
||||
selected_row: i32,
|
||||
max_rows: i32,
|
||||
tab_titles: Vec<String>,
|
||||
pub soundcloud: Option<CloudPlaylists>
|
||||
}
|
||||
@ -16,8 +19,11 @@ pub struct MainScreen {
|
||||
impl AppScreen for MainScreen {
|
||||
fn handle_key_event(&mut self, key_event: KeyEvent) {
|
||||
match key_event.code {
|
||||
KeyCode::Char('l') | KeyCode::Right => self.next_tab(),
|
||||
KeyCode::Char('h') | KeyCode::Left => self.previous_tab(),
|
||||
KeyCode::Right => self.next_tab(),
|
||||
KeyCode::Left => self.previous_tab(),
|
||||
KeyCode::Up => self.previous_row(),
|
||||
KeyCode::Down => self.next_row(),
|
||||
KeyCode::F(6) => self.download_row(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@ -36,15 +42,15 @@ impl AppScreen for MainScreen {
|
||||
self.tab_titles.iter().map(|t| Span::raw(t.clone())).collect::<Vec<Span>>(),
|
||||
)
|
||||
.block(Block::default().borders(Borders::ALL))
|
||||
.highlight_style(Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD))
|
||||
.highlight_style(Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD))
|
||||
.select(self.selected_tab as usize)
|
||||
.style(Style::default().fg(Color::White));
|
||||
|
||||
frame.render_widget(tabs, chunks[0]);
|
||||
|
||||
let main_content = Paragraph::new("Main content goes here!")
|
||||
.block(Block::default().borders(Borders::ALL).title("Main"));
|
||||
frame.render_widget(main_content, chunks[1]); // Render into second chunk
|
||||
/*let main_content = Paragraph::new("Main content goes here!")
|
||||
.block(Block::default().borders(Borders::ALL).title("Main")); */
|
||||
frame.render_widget(self.render_tab(), chunks[1]); // Render into second chunk
|
||||
|
||||
// Render Status Bar
|
||||
let status_bar = Paragraph::new(
|
||||
@ -63,7 +69,7 @@ impl AppScreen for MainScreen {
|
||||
|
||||
impl MainScreen {
|
||||
pub fn new() -> Self {
|
||||
MainScreen { soundcloud: None, selected_tab: 0, tab_titles: vec!["YouTube".to_string(), "SoundCloud".to_string(), "Local Playlists".to_string(), "Settings".to_string()] }
|
||||
MainScreen { selected_row: -1, max_rows: 0, soundcloud: None, selected_tab: 0, tab_titles: vec!["YouTube".to_string(), "SoundCloud".to_string(), "Local Playlists".to_string(), "Settings".to_string()] }
|
||||
}
|
||||
|
||||
fn next_tab(&mut self) {
|
||||
@ -73,4 +79,62 @@ impl MainScreen {
|
||||
fn previous_tab(&mut self) {
|
||||
self.selected_tab = std::cmp::max(0, self.selected_tab-1);
|
||||
}
|
||||
|
||||
fn previous_row(&mut self) {
|
||||
self.selected_row = std::cmp::max(0, self.selected_row-1);
|
||||
}
|
||||
|
||||
fn next_row(&mut self) {
|
||||
self.selected_row = std::cmp::min(self.selected_row + 1, self.max_rows);
|
||||
}
|
||||
|
||||
fn download_row(&mut self) {
|
||||
match self.selected_tab {
|
||||
1 => {// SC
|
||||
let playlist_url = self.soundcloud.as_ref().unwrap().collection.get(self.selected_row as usize).unwrap().permalink_url.clone();
|
||||
dlp::download_from_soundcloud(&playlist_url, &get_temp_dl_dir());
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_tab(&self) -> Table<'_> {
|
||||
let rows = match self.selected_tab {
|
||||
1 => { // SC
|
||||
let mut v = Vec::new();
|
||||
v.push(Row::new(vec!["Id", "Title", "Songs Count", "Date", "IS"]).style(Style::default().fg(Color::Gray)));
|
||||
if let Some(s) = &self.soundcloud {
|
||||
for (i, playlist) in (&s.collection).iter().enumerate() {
|
||||
let date: DateTime<Utc> = playlist.created_at.parse().unwrap();
|
||||
let mut row = Row::new(
|
||||
vec![
|
||||
playlist.id.to_string(),
|
||||
playlist.title.clone(),
|
||||
[playlist.track_count.to_string(), " songs".to_string()].concat(),
|
||||
format!("{}", date.format("%Y-%m-%d %H:%M")),
|
||||
"NO".to_string()
|
||||
]
|
||||
);
|
||||
if self.selected_row == i as i32 {
|
||||
row = row.style(Style::default().bg(Color::Yellow));
|
||||
}
|
||||
v.push(row);
|
||||
}
|
||||
}
|
||||
v
|
||||
}
|
||||
_ => Vec::new()
|
||||
};
|
||||
|
||||
// Create the table
|
||||
Table::new(rows, &[
|
||||
Constraint::Length(3), // ID column
|
||||
Constraint::Percentage(50), // Playlist name column
|
||||
Constraint::Percentage(20), // Song count column
|
||||
Constraint::Percentage(30),
|
||||
Constraint::Length(2)
|
||||
])
|
||||
.block(Block::default().borders(Borders::ALL).title(" Playlists "))
|
||||
.style(Style::default().fg(Color::White))
|
||||
}
|
||||
}
|
18
src/sync.rs
18
src/sync.rs
@ -2,10 +2,10 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use itunesdb::xobjects::XDatabase;
|
||||
use soundcloud::sobjects::CloudPlaylists;
|
||||
use tokio::{fs::File, io::AsyncReadExt, sync::mpsc::{Sender, UnboundedReceiver}};
|
||||
use tokio::{fs::File, io::{AsyncReadExt, AsyncWriteExt}, sync::mpsc::{Sender, UnboundedReceiver}};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::config::{get_configs_dir, LyricaConfiguration};
|
||||
use crate::config::{get_config_path, get_configs_dir, get_temp_itunesdb, LyricaConfiguration};
|
||||
|
||||
pub enum AppEvent {
|
||||
SearchIPod,
|
||||
@ -36,8 +36,7 @@ pub fn initialize_async_service(sender: Sender<AppEvent>, receiver: UnboundedRec
|
||||
AppEvent::ParseItunes(path) => {
|
||||
// todo: parse itunes
|
||||
let _ = std::fs::create_dir_all(get_configs_dir());
|
||||
let mut cd = get_configs_dir();
|
||||
cd.push("idb");
|
||||
let cd = get_temp_itunesdb();
|
||||
let mut p: PathBuf = Path::new(&path).into();
|
||||
// p.push("iPod_Control");
|
||||
// p.push("iTunes");
|
||||
@ -49,10 +48,13 @@ pub fn initialize_async_service(sender: Sender<AppEvent>, receiver: UnboundedRec
|
||||
let xdb = itunesdb::deserializer::parse_bytes(&contents);
|
||||
let _ = sender.send(AppEvent::ITunesParsed(xdb)).await;
|
||||
|
||||
let mut p = get_configs_dir();
|
||||
p.push("config");
|
||||
p.set_extension(".toml");
|
||||
if !p.exists() { return; }
|
||||
let p = get_config_path();
|
||||
if !p.exists() {
|
||||
let config = LyricaConfiguration::default();
|
||||
let cfg_str = toml::to_string_pretty(&config).unwrap();
|
||||
let mut file = File::create(&p).await.unwrap();
|
||||
file.write(cfg_str.as_bytes()).await;
|
||||
}
|
||||
let mut file = File::open(p).await.unwrap();
|
||||
let mut content = String::new();
|
||||
file.read_to_string(&mut content).await.unwrap();
|
||||
|
Loading…
x
Reference in New Issue
Block a user