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

View File

@ -20,7 +20,7 @@ futures = "0.3"
tokio = { version = "1", features = ["full"] }
tokio-util = { version = "0.7.12", features = ["codec"] }
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"
mp3-duration = "0.1.10"
rand = "0.8.5"

View File

@ -18,6 +18,55 @@ pub struct DownloadProgress {
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(
playlist_url: &str,
download_dir: &PathBuf,

View File

@ -1,5 +1,6 @@
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 crossterm::event::KeyCode;
use ratatui::layout::{Constraint, Direction, Layout, Rect};
@ -10,30 +11,11 @@ use std::cmp::Ordering;
use std::fs::DirEntry;
use std::os::unix::fs::MetadataExt;
use std::path::PathBuf;
use tokio::sync::mpsc::UnboundedSender;
pub struct FileSystem {
table: SmartTable,
}
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
}
sender: UnboundedSender<AppEvent>,
}
impl AppScreen for FileSystem {
@ -41,6 +23,11 @@ impl AppScreen for FileSystem {
match key_event.code {
KeyCode::Up => self.table.previous_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
let status_bar = Paragraph::new(Line::from(vec![
"<F4> SWITCH TO NORMAL".bold(),
" | ".dark_gray(),
"<F5> SAVE AS PLAYLIST".bold(),
" | ".dark_gray(),
"<F6> SAVE AS IS".bold(),
@ -78,6 +67,25 @@ impl AppScreen for 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) {
let paths = std::fs::read_dir(&p).unwrap();
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::MainScreen, Box::new(MainScreen::new(jx.clone())));
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 {
receiver: rx,

View File

@ -160,14 +160,31 @@ impl MainScreen {
fn download_row(&mut self) {
if self.selected_tab == 1 {
// SC
let playlist = self
.soundcloud
.as_ref()
.unwrap()
.get(self.pl_table.selected_row())
.unwrap()
.clone();
let _ = self.sender.send(AppEvent::DownloadPlaylist(playlist));
match self.mode {
false => {
let playlist = self
.soundcloud
.as_ref()
.unwrap()
.get(self.pl_table.selected_row())
.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>),
SoundcloudGot(CloudPlaylists),
DownloadPlaylist(CloudPlaylist),
DownloadTrack(CloudTrack),
CurrentProgress(DownloadProgress),
OverallProgress((u32, u32)),
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::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;},
_ => {}
}
@ -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(
playlist: CloudPlaylist,
database: &mut XDatabase,