Fixed itunesdb bug. Added ability to download single song form playlist. Artwork addition development started, fs mode development started.

This commit is contained in:
Michael Wain 2025-02-18 00:08:57 +03:00
parent 3bd8f1c75d
commit 4d89b9e187
7 changed files with 172 additions and 38 deletions

12
Cargo.lock generated
View File

@ -405,7 +405,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -982,8 +982,8 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]] [[package]]
name = "itunesdb" name = "itunesdb"
version = "0.1.51" version = "0.1.57"
source = "git+https://gitea.awain.net/alterwain/ITunesDB.git#ed639f96d59b6777114551e8c3054f2a0ac9c7da" source = "git+https://gitea.awain.net/alterwain/ITunesDB.git#ea14aaf6c3284b2cd83f4628b21f652099a79815"
dependencies = [ dependencies = [
"bincode", "bincode",
"env_logger", "env_logger",
@ -1536,7 +1536,7 @@ dependencies = [
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -1879,7 +1879,7 @@ dependencies = [
"getrandom 0.3.1", "getrandom 0.3.1",
"once_cell", "once_cell",
"rustix", "rustix",
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -2367,7 +2367,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]

View File

@ -20,7 +20,7 @@ futures = "0.3"
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
tokio-util = { version = "0.7.12", features = ["codec"] } tokio-util = { version = "0.7.12", features = ["codec"] }
soundcloud = { version = "0.1.8", git = "https://gitea.awain.net/alterwain/soundcloud_api.git" } soundcloud = { version = "0.1.8", git = "https://gitea.awain.net/alterwain/soundcloud_api.git" }
itunesdb = { version = "0.1.51", git = "https://gitea.awain.net/alterwain/ITunesDB.git" } itunesdb = { version = "0.1.57", git = "https://gitea.awain.net/alterwain/ITunesDB.git" }
puremp3 = "0.1.0" puremp3 = "0.1.0"
mp3-duration = "0.1.10" mp3-duration = "0.1.10"
rand = "0.8.5" rand = "0.8.5"

View File

@ -18,6 +18,55 @@ pub struct DownloadProgress {
pub eta: String, pub eta: String,
} }
pub async fn download_track_from_soundcloud(
track_url: &str,
download_dir: &PathBuf,
sender: Sender<AppEvent>,
) -> std::result::Result<(), Box<dyn std::error::Error>> {
let _ = sender
.send(AppEvent::SwitchScreen(crate::AppState::LoadingScreen))
.await;
if download_dir.exists() {
let _ = std::fs::remove_dir_all(download_dir);
}
let _ = std::fs::create_dir_all(download_dir);
let args = &[
"-f",
"mp3",
"--ignore-errors",
"--newline",
"--progress-template",
"{\"progress_percentage\":\"%(progress._percent_str)s\",\"progress_total\":\"%(progress._total_bytes_str)s\",\"speed\":\"%(progress._speed_str)s\",\"eta\":\"%(progress._eta_str)s\"}",
"-o",
"%(id)i.%(ext)s",
"--write-thumbnail",
track_url
];
let mut command = Command::new("yt-dlp");
command.args(args);
command.stdout(Stdio::piped());
command.stderr(Stdio::null());
command.current_dir(download_dir);
let mut child = command.spawn()?;
let stdout = child.stdout.take().unwrap();
let mut reader = BufReader::new(stdout).lines();
while let Some(line) = reader.next_line().await? {
if line.starts_with("{") {
let progress: DownloadProgress = serde_json::from_str(&line).unwrap();
let _ = sender.send(AppEvent::OverallProgress((0, 1))).await;
let _ = sender.send(AppEvent::CurrentProgress(progress)).await;
}
}
let _ = sender.send(AppEvent::OverallProgress((1, 1))).await;
Ok(())
}
pub async fn download_from_soundcloud( pub async fn download_from_soundcloud(
playlist_url: &str, playlist_url: &str,
download_dir: &PathBuf, download_dir: &PathBuf,

View File

@ -1,5 +1,6 @@
use crate::component::table::SmartTable; use crate::component::table::SmartTable;
use crate::{screen::AppScreen, theme::Theme}; use crate::sync::AppEvent;
use crate::{screen::AppScreen, theme::Theme, AppState};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use crossterm::event::KeyCode; use crossterm::event::KeyCode;
use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::layout::{Constraint, Direction, Layout, Rect};
@ -10,30 +11,11 @@ use std::cmp::Ordering;
use std::fs::DirEntry; use std::fs::DirEntry;
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use std::path::PathBuf; use std::path::PathBuf;
use tokio::sync::mpsc::UnboundedSender;
pub struct FileSystem { pub struct FileSystem {
table: SmartTable, table: SmartTable,
} sender: UnboundedSender<AppEvent>,
impl Default for FileSystem {
fn default() -> Self {
let table = SmartTable::new(
["Name", "Type", "Size", "Modified"]
.iter_mut()
.map(|s| s.to_string())
.collect(),
vec![
Constraint::Percentage(50),
Constraint::Length(5),
Constraint::Percentage(20),
Constraint::Percentage(30),
],
);
let mut a = Self { table };
a.get_path(dirs::document_dir().unwrap());
a
}
} }
impl AppScreen for FileSystem { impl AppScreen for FileSystem {
@ -41,6 +23,11 @@ impl AppScreen for FileSystem {
match key_event.code { match key_event.code {
KeyCode::Up => self.table.previous_row(), KeyCode::Up => self.table.previous_row(),
KeyCode::Down => self.table.next_row(), KeyCode::Down => self.table.next_row(),
KeyCode::F(4) => {
let _ = self
.sender
.send(AppEvent::SwitchScreen(AppState::MainScreen));
}
_ => {} _ => {}
} }
} }
@ -58,6 +45,8 @@ impl AppScreen for FileSystem {
// Render Status Bar // Render Status Bar
let status_bar = Paragraph::new(Line::from(vec![ let status_bar = Paragraph::new(Line::from(vec![
"<F4> SWITCH TO NORMAL".bold(),
" | ".dark_gray(),
"<F5> SAVE AS PLAYLIST".bold(), "<F5> SAVE AS PLAYLIST".bold(),
" | ".dark_gray(), " | ".dark_gray(),
"<F6> SAVE AS IS".bold(), "<F6> SAVE AS IS".bold(),
@ -78,6 +67,25 @@ impl AppScreen for FileSystem {
} }
impl FileSystem { impl FileSystem {
pub fn new(sender: UnboundedSender<AppEvent>) -> Self {
let table = SmartTable::new(
["Name", "Type", "Size", "Modified"]
.iter_mut()
.map(|s| s.to_string())
.collect(),
vec![
Constraint::Percentage(50),
Constraint::Length(5),
Constraint::Percentage(20),
Constraint::Percentage(30),
],
);
let mut a = Self { table, sender };
a.get_path(dirs::document_dir().unwrap());
a
}
fn get_path(&mut self, p: PathBuf) { fn get_path(&mut self, p: PathBuf) {
let paths = std::fs::read_dir(&p).unwrap(); let paths = std::fs::read_dir(&p).unwrap();
let mut dir = paths let mut dir = paths

View File

@ -68,7 +68,7 @@ impl Default for App {
screens.insert(AppState::IPodWait, Box::new(WaitScreen::default())); screens.insert(AppState::IPodWait, Box::new(WaitScreen::default()));
screens.insert(AppState::MainScreen, Box::new(MainScreen::new(jx.clone()))); screens.insert(AppState::MainScreen, Box::new(MainScreen::new(jx.clone())));
screens.insert(AppState::LoadingScreen, Box::new(LoadingScreen::default())); screens.insert(AppState::LoadingScreen, Box::new(LoadingScreen::default()));
screens.insert(AppState::FileSystem, Box::new(FileSystem::default())); screens.insert(AppState::FileSystem, Box::new(FileSystem::new(jx.clone())));
Self { Self {
receiver: rx, receiver: rx,

View File

@ -160,14 +160,31 @@ impl MainScreen {
fn download_row(&mut self) { fn download_row(&mut self) {
if self.selected_tab == 1 { if self.selected_tab == 1 {
// SC // SC
let playlist = self match self.mode {
.soundcloud false => {
.as_ref() let playlist = self
.unwrap() .soundcloud
.get(self.pl_table.selected_row()) .as_ref()
.unwrap() .unwrap()
.clone(); .get(self.pl_table.selected_row())
let _ = self.sender.send(AppEvent::DownloadPlaylist(playlist)); .unwrap()
.clone();
let _ = self.sender.send(AppEvent::DownloadPlaylist(playlist));
}
true => {
let track = self
.soundcloud
.as_ref()
.unwrap()
.get(self.pl_table.selected_row())
.unwrap()
.tracks
.get(self.song_table.selected_row())
.unwrap()
.clone();
let _ = self.sender.send(AppEvent::DownloadTrack(track));
}
}
} }
} }

View File

@ -25,6 +25,7 @@ pub enum AppEvent {
ITunesParsed(Vec<DBPlaylist>), ITunesParsed(Vec<DBPlaylist>),
SoundcloudGot(CloudPlaylists), SoundcloudGot(CloudPlaylists),
DownloadPlaylist(CloudPlaylist), DownloadPlaylist(CloudPlaylist),
DownloadTrack(CloudTrack),
CurrentProgress(DownloadProgress), CurrentProgress(DownloadProgress),
OverallProgress((u32, u32)), OverallProgress((u32, u32)),
SwitchScreen(AppState), SwitchScreen(AppState),
@ -114,6 +115,7 @@ pub fn initialize_async_service(
} }
}, },
AppEvent::DownloadPlaylist(playlist) => download_playlist(playlist, database.as_mut().unwrap(), &sender, ipod_db.clone().unwrap()).await, AppEvent::DownloadPlaylist(playlist) => download_playlist(playlist, database.as_mut().unwrap(), &sender, ipod_db.clone().unwrap()).await,
AppEvent::DownloadTrack(track) => download_track(track, database.as_mut().unwrap(), &sender, ipod_db.clone().unwrap()).await,
AppEvent::SwitchScreen(state) => { let _ = sender.send(AppEvent::SwitchScreen(state)).await;}, AppEvent::SwitchScreen(state) => { let _ = sender.send(AppEvent::SwitchScreen(state)).await;},
_ => {} _ => {}
} }
@ -124,6 +126,64 @@ pub fn initialize_async_service(
}); });
} }
async fn download_track(
track: CloudTrack,
database: &mut XDatabase,
sender: &Sender<AppEvent>,
ipod_path: String,
) {
if let Ok(()) = dlp::download_track_from_soundcloud(
&track.permalink_url.clone().unwrap(),
&get_temp_dl_dir(),
sender.clone(),
)
.await
{
let p: PathBuf = Path::new(&ipod_path).into();
let mut t: XTrackItem = track_from_soundcloud(&track);
t.data.unique_id = database.get_unique_id();
let mut tp = PathBuf::new();
tp.push(":iPod_Control");
tp.push("Music");
tp.push(["F", &format!("{:02}", &(t.data.unique_id % 100))].concat());
tp.push(format!("{:X}", t.data.unique_id));
tp.set_extension("mp3");
t.set_location(
tp.to_str()
.unwrap()
.to_string()
.replace("/", ":")
.to_string(),
);
let mut dest = p.clone();
dest.push("iPod_Control");
dest.push("Music");
dest.push(["F", &format!("{:02}", &(t.data.unique_id % 100))].concat());
let _ = std::fs::create_dir_all(dest.to_str().unwrap());
dest.push(format!("{:X}", t.data.unique_id));
dest.set_extension("mp3");
let mut track_path = get_temp_dl_dir();
track_path.push(track.id.to_string());
track_path.set_extension("mp3");
let _ = std::fs::copy(track_path.to_str().unwrap(), dest.to_str().unwrap());
database.add_track(t);
}
let _ = sender
.send(AppEvent::SwitchScreen(AppState::MainScreen))
.await;
let _ = sender
.send(AppEvent::ITunesParsed(get_playlists(database)))
.await;
overwrite_database(database, &ipod_path);
}
async fn download_playlist( async fn download_playlist(
playlist: CloudPlaylist, playlist: CloudPlaylist,
database: &mut XDatabase, database: &mut XDatabase,