From a64f77c2a4a561f1990234ad0d16d4e2dee4e7d4 Mon Sep 17 00:00:00 2001 From: "alterwain@protonmail.com" Date: Thu, 20 Feb 2025 00:56:08 +0300 Subject: [PATCH] FS start --- Cargo.lock | 142 +++++++++++++++++++++++----- Cargo.toml | 4 +- README.md | 8 +- src/dlp.rs | 10 +- src/file_system.rs | 44 +++++++-- src/sync.rs | 230 ++++++++++++++++++++++++++++++++------------- 6 files changed, 332 insertions(+), 106 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a05ceb5..f22ea80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "aho-corasick" version = "1.1.3" @@ -64,6 +70,26 @@ dependencies = [ "winapi", ] +[[package]] +name = "audiotags" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44e797ce0164cf599c71f2c3849b56301d96a3dc033544588e875686b050ed39" +dependencies = [ + "audiotags-macro", + "id3", + "metaflac", + "mp4ameta", + "readme-rustdocifier", + "thiserror 1.0.69", +] + +[[package]] +name = "audiotags-macro" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaa9b2312fc01f7291f3b7b0f52ed08b1c0177c96a2e696ab55695cc4d06889" + [[package]] name = "autocfg" version = "1.4.0" @@ -80,7 +106,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.7.4", "object", "rustc-demangle", ] @@ -106,12 +132,6 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" -[[package]] -name = "bitstream-io" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614aa3f2bac03707e62a84d18a48dd3d9ea6171313fd5e6a53b5054d8ae74601" - [[package]] name = "bumpalo" version = "3.17.0" @@ -231,6 +251,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossterm" version = "0.28.1" @@ -424,6 +453,16 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "flate2" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +dependencies = [ + "crc32fast", + "miniz_oxide 0.8.4", +] + [[package]] name = "fnv" version = "1.0.7" @@ -629,6 +668,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "http" version = "1.2.0" @@ -888,6 +933,17 @@ dependencies = [ "syn", ] +[[package]] +name = "id3" +version = "1.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "472295f55960dd48e38c89442fa5d5423f5cf0ed2c665485be78e129231a39e9" +dependencies = [ + "bitflags", + "byteorder", + "flate2", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -1078,19 +1134,19 @@ dependencies = [ name = "lyrica" version = "0.1.0" dependencies = [ + "audiotags", "chrono", "color-eyre", "crossterm", "dirs", "futures", "itunesdb", - "mp3-duration", - "puremp3", "rand", "ratatui", "regex", "rusb", "serde", + "serde-xml-rs", "serde_json", "soundcloud", "throbber-widgets-tui", @@ -1106,6 +1162,16 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "metaflac" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf25a3451319c52a4a56d956475fbbb763bfb8420e2187d802485cb0fd8d965" +dependencies = [ + "byteorder", + "hex", +] + [[package]] name = "mime" version = "0.3.17" @@ -1121,6 +1187,15 @@ dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" +dependencies = [ + "adler2", +] + [[package]] name = "mio" version = "1.0.3" @@ -1134,14 +1209,21 @@ dependencies = [ ] [[package]] -name = "mp3-duration" -version = "0.1.10" +name = "mp4ameta" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "348bdc7300502f0801e5b57c448815713cd843b744ef9bda252a2698fdf90a0f" +checksum = "eb23d62e8eb5299a3f79657c70ea9269eac8f6239a76952689bcd06a74057e81" dependencies = [ - "thiserror 1.0.69", + "lazy_static", + "mp4ameta_proc", ] +[[package]] +name = "mp4ameta_proc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07dcca13d1740c0a665f77104803360da0bdb3323ecce2e93fa2c959a6d52806" + [[package]] name = "native-tls" version = "0.2.13" @@ -1331,16 +1413,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "puremp3" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b7efbb39e373af70c139e0611375fa6cad751fb93d528a610b55302710d883" -dependencies = [ - "bitstream-io", - "byteorder", -] - [[package]] name = "quote" version = "1.0.38" @@ -1402,6 +1474,12 @@ dependencies = [ "unicode-width 0.2.0", ] +[[package]] +name = "readme-rustdocifier" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ad765b21a08b1a8e5cdce052719188a23772bcbefb3c439f0baaf62c56ceac" + [[package]] name = "redox_syscall" version = "0.5.8" @@ -1637,6 +1715,18 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-xml-rs" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb3aa78ecda1ebc9ec9847d5d3aba7d618823446a049ba2491940506da6e2782" +dependencies = [ + "log", + "serde", + "thiserror 1.0.69", + "xml-rs", +] + [[package]] name = "serde_derive" version = "1.0.217" @@ -2527,6 +2617,12 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "xml-rs" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" + [[package]] name = "yoke" version = "0.7.5" diff --git a/Cargo.toml b/Cargo.toml index 4bc8082..32df9fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ dirs = "6.0.0" toml = "0.8.20" serde = "1.0.217" serde_json = "1.0" +serde-xml-rs = "0.6.0" regex = "1.11.1" ratatui = { version = "0.29.0", features = ["all-widgets"] } color-eyre = "0.6.3" @@ -21,8 +22,7 @@ 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.57", git = "https://gitea.awain.net/alterwain/ITunesDB.git" } -puremp3 = "0.1.0" -mp3-duration = "0.1.10" rand = "0.8.5" tui-big-text = "0.7.1" throbber-widgets-tui = "0.8.0" +audiotags = "0.5.0" \ No newline at end of file diff --git a/README.md b/README.md index 61712d6..d7033b6 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Lightweight iPod manager, batteries included. -# +#
@@ -39,8 +39,8 @@ Lightweight iPod manager, batteries included. ## Todos -- Implement scrollbox -- Implement single song download - Implement import from file system - Implement artwork integration -- Implement addition to fresh ipod (without any data) \ No newline at end of file +- Implement addition to fresh ipod (without any data) +- Implement youtube api +- Implement albums generation \ No newline at end of file diff --git a/src/dlp.rs b/src/dlp.rs index 6716c63..1243148 100644 --- a/src/dlp.rs +++ b/src/dlp.rs @@ -1,4 +1,4 @@ -use std::{path::PathBuf, process::Stdio}; +use std::{io, path::PathBuf, process::Stdio}; use regex::Regex; use serde::Deserialize; @@ -22,7 +22,7 @@ pub async fn download_track_from_soundcloud( track_url: &str, download_dir: &PathBuf, sender: Sender, -) -> std::result::Result<(), Box> { +) -> io::Result<()> { let _ = sender .send(AppEvent::SwitchScreen(crate::AppState::LoadingScreen)) .await; @@ -56,7 +56,7 @@ pub async fn download_track_from_soundcloud( let stdout = child.stdout.take().unwrap(); let mut reader = BufReader::new(stdout).lines(); - while let Some(line) = reader.next_line().await? { + while let Ok(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; @@ -71,7 +71,7 @@ pub async fn download_from_soundcloud( playlist_url: &str, download_dir: &PathBuf, sender: Sender, -) -> std::result::Result<(), Box> { +) -> io::Result<()> { let _ = sender .send(AppEvent::SwitchScreen(crate::AppState::LoadingScreen)) .await; @@ -106,7 +106,7 @@ pub async fn download_from_soundcloud( let stdout = child.stdout.take().unwrap(); let mut reader = BufReader::new(stdout).lines(); - while let Some(line) = reader.next_line().await? { + while let Ok(Some(line)) = reader.next_line().await { match dl_rx.find(&line) { Some(m) => { let mut s = m.as_str(); diff --git a/src/file_system.rs b/src/file_system.rs index 79c8d55..7a63791 100644 --- a/src/file_system.rs +++ b/src/file_system.rs @@ -8,16 +8,25 @@ use ratatui::prelude::{Line, Stylize}; use ratatui::widgets::Paragraph; use ratatui::Frame; use std::cmp::Ordering; +use std::ffi::OsStr; use std::fs::DirEntry; use std::os::unix::fs::MetadataExt; use std::path::PathBuf; use tokio::sync::mpsc::UnboundedSender; pub struct FileSystem { + files: Vec, table: SmartTable, sender: UnboundedSender, } +fn check_extension_comptability(ext: &OsStr) -> bool { + matches!( + ext.to_str().unwrap().to_lowercase().as_str(), + "mp3" | "m4a" | "wav" | "aiff" | "aif" | "aac" + ) +} + impl AppScreen for FileSystem { fn handle_key_event(&mut self, key_event: crossterm::event::KeyEvent) { match key_event.code { @@ -28,6 +37,9 @@ impl AppScreen for FileSystem { .sender .send(AppEvent::SwitchScreen(AppState::MainScreen)); } + KeyCode::F(5) => { + self.download_as_is(); + } _ => {} } } @@ -47,9 +59,9 @@ impl AppScreen for FileSystem { let status_bar = Paragraph::new(Line::from(vec![ " SWITCH TO NORMAL".bold(), " | ".dark_gray(), - " SAVE AS PLAYLIST".bold(), + " SAVE AS IS".bold(), " | ".dark_gray(), - " SAVE AS IS".bold(), + " SAVE AS PLAYLIST".bold(), " | ".dark_gray(), " SELECT".bold(), " | ".dark_gray(), @@ -81,7 +93,11 @@ impl FileSystem { ], ); - let mut a = Self { table, sender }; + let mut a = Self { + table, + sender, + files: Vec::new(), + }; a.get_path(dirs::document_dir().unwrap()); a } @@ -90,7 +106,12 @@ impl FileSystem { let paths = std::fs::read_dir(&p).unwrap(); let mut dir = paths .filter_map(|res| res.ok()) - .filter(|p| p.path().extension().map_or(false, |ext| ext == "mp3") || p.path().is_dir()) + .filter(|p| { + p.path() + .extension() + .map_or(false, check_extension_comptability) + || p.path().is_dir() + }) .collect::>(); dir.sort_by(|a, _b| { if a.file_type().unwrap().is_dir() { @@ -100,7 +121,7 @@ impl FileSystem { } }); - let dir = dir + let data = dir .iter() .map(|entry| { let datetime: DateTime = entry.metadata().unwrap().modified().unwrap().into(); @@ -116,11 +137,22 @@ impl FileSystem { }) .collect::>>(); - self.table.set_data(dir); + self.files = dir; + + self.table.set_data(data); self.table .set_title(p.iter().last().unwrap().to_str().unwrap().to_string()); } + fn download_as_is(&self) { + let entry = self.files.get(self.table.selected_row()).unwrap(); + if entry.path().is_dir() { + todo!("Implement that later"); + } else { + let _ = self.sender.send(AppEvent::LoadFromFS(entry.path())); + } + } + fn render_main(&self, frame: &mut Frame, area: Rect) { self.table.render(frame, area); } diff --git a/src/sync.rs b/src/sync.rs index a806c19..afa0a6a 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -1,7 +1,9 @@ +use audiotags::Tag; use itunesdb::objects::{ListSortOrder, PlaylistItem}; use itunesdb::serializer; use itunesdb::xobjects::{XDatabase, XPlArgument, XPlaylist, XTrackItem}; use soundcloud::sobjects::{CloudPlaylist, CloudPlaylists, CloudTrack}; +use std::error::Error; use std::io::Write; use std::path::{Path, PathBuf}; use tokio::{ @@ -29,6 +31,7 @@ pub enum AppEvent { CurrentProgress(DownloadProgress), OverallProgress((u32, u32)), SwitchScreen(AppState), + LoadFromFS(PathBuf), } pub struct DBPlaylist { @@ -38,23 +41,24 @@ pub struct DBPlaylist { pub tracks: Vec, } -fn track_from_soundcloud(value: &CloudTrack) -> XTrackItem { +async fn track_from_soundcloud(value: &CloudTrack) -> Option { let mut track_path = get_temp_dl_dir(); track_path.push(value.id.to_string()); track_path.set_extension("mp3"); let f = std::fs::File::open(&track_path).unwrap(); let mut data = &std::fs::read(&track_path).unwrap()[..]; - let (header, _samples) = puremp3::read_mp3(data).unwrap(); - - let duration = mp3_duration::from_read(&mut data).unwrap(); + let audio_info = audio_file_info::from_path(track_path.to_str().unwrap()) + .await + .unwrap(); + let audio_info = audio_info.audio_file.tracks.get(0).unwrap(); let mut track = XTrackItem::new( value.id as u32, f.metadata().unwrap().len() as u32, - duration.as_millis() as u32, + (audio_info.duration * 1000.0) as u32, 0, - header.bitrate.bps() / 1000, - header.sample_rate.hz(), + audio_info.bit_rate as u32, + audio_info.sample_rate as u32, hash(), 0, ); @@ -67,7 +71,7 @@ fn track_from_soundcloud(value: &CloudTrack) -> XTrackItem { ); track.set_genre(value.genre.clone().unwrap()); track.update_arg(6, String::from("MPEG audio file")); - track + Some(track) } // note: this hash function is used to make unique ids for each track. It doesn't aim to generate secure ones. @@ -108,15 +112,16 @@ pub fn initialize_async_service( AppEvent::SearchIPod => { if let Some(p) = util::search_ipod() { ipod_db = Some(p.clone()); - database = Some(parse_itunes(&sender, p).await); let _ = sender.send(AppEvent::SwitchScreen(AppState::MainScreen)).await; + database = Some(parse_itunes(&sender, p).await); } else { let _ = sender.send(AppEvent::IPodNotFound).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::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::LoadFromFS(path) => load_from_fs(path, database.as_mut().unwrap(), &sender, ipod_db.clone().unwrap()), _ => {} } } @@ -126,6 +131,51 @@ pub fn initialize_async_service( }); } +fn load_from_fs( + path: PathBuf, + database: &mut XDatabase, + sender: &Sender, + ipod_path: String, +) { + let mut tag = Tag::new().read_from_path(path).unwrap(); + + let id = database.get_unique_id(); + + let mut track = XTrackItem::new( + id, + 0, + (tag.duration().unwrap() / 1000.0) as u32, + tag.year().unwrap() as u32, + 0, + 0, + hash(), + 0, + ); + // TODO: implement check for every property + track.set_title(tag.title().unwrap().to_string()); + + let mut tp = PathBuf::new(); + tp.push(":iPod_Control"); + tp.push("Music"); + tp.push(["F", &format!("{:02}", &(track.data.unique_id % 100))].concat()); + tp.push(format!("{:X}", track.data.unique_id)); + tp.set_extension("mp3"); + track.set_location( + tp.to_str() + .unwrap() + .to_string() + .replace("/", ":") + .to_string(), + ); + //track.update_arg(6); + + track.set_genre(tag.genre().unwrap().to_string()); + track.set_artist(tag.artist().unwrap().to_string()); + track.set_album(tag.album().unwrap().title.to_string()); + + let cover = tag.album().unwrap().cover.unwrap(); +} + async fn download_track( track: CloudTrack, database: &mut XDatabase, @@ -141,36 +191,37 @@ async fn download_track( { 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"); + if let Some(mut t) = track_from_soundcloud(&track).await { + 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 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()); + let _ = std::fs::copy(track_path.to_str().unwrap(), dest.to_str().unwrap()); - database.add_track(t); + database.add_track(t); + } } let _ = sender @@ -206,37 +257,38 @@ async fn download_playlist( if track.title.is_none() { continue; } - let mut t: XTrackItem = track_from_soundcloud(&track); - t.data.unique_id = database.get_unique_id(); - new_playlist.add_elem(t.data.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"); + if let Some(mut t) = track_from_soundcloud(&track).await { + t.data.unique_id = database.get_unique_id(); + new_playlist.add_elem(t.data.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 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()); + let _ = std::fs::copy(track_path.to_str().unwrap(), dest.to_str().unwrap()); - database.add_track(t); + database.add_track(t); + } } database.add_playlist(new_playlist); @@ -335,3 +387,49 @@ async fn parse_itunes(sender: &Sender, path: String) -> XDatabase { database } + +mod audio_file_info { + use serde::{Deserialize, Serialize}; + use std::process::Stdio; + use tokio::io::{AsyncReadExt, BufReader}; + use tokio::process::Command; + + #[derive(Debug, Serialize, Deserialize, PartialEq)] + pub struct AudioInfo { + pub audio_file: AudioFileInfo, + } + + #[derive(Debug, Serialize, Deserialize, PartialEq)] + pub struct AudioFileInfo { + pub file_name: String, + pub file_type: String, + pub tracks: Vec, + } + + #[derive(Debug, Serialize, Deserialize, PartialEq)] + pub struct AudioFileTrack { + pub num_channels: u32, + pub sample_rate: u64, + pub format_type: String, + pub audio_bytes: u64, + pub duration: f64, + pub bit_rate: u64, + } + + pub async fn from_path(p: &str) -> Option { + let mut command = Command::new("afinfo"); + command.args(vec!["-x", p]); + command.stdout(Stdio::piped()); + command.stderr(Stdio::null()); + + let mut child = command.spawn().unwrap(); + + let mut str = String::new(); + let stdout = child.stdout.take().unwrap(); + let size = BufReader::new(stdout) + .read_to_string(&mut str) + .await + .unwrap(); + Some(serde_xml_rs::from_str(&str).unwrap()) + } +}