This commit is contained in:
Michael Wain 2025-02-20 00:56:08 +03:00
parent 4d89b9e187
commit a64f77c2a4
6 changed files with 332 additions and 106 deletions

142
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -4,7 +4,7 @@
Lightweight iPod manager, batteries included.
#
#
<div align="center">
<img src=""/>
@ -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)
- Implement addition to fresh ipod (without any data)
- Implement youtube api
- Implement albums generation

View File

@ -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<AppEvent>,
) -> std::result::Result<(), Box<dyn std::error::Error>> {
) -> 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<AppEvent>,
) -> std::result::Result<(), Box<dyn std::error::Error>> {
) -> 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();

View File

@ -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<DirEntry>,
table: SmartTable,
sender: UnboundedSender<AppEvent>,
}
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![
"<F4> SWITCH TO NORMAL".bold(),
" | ".dark_gray(),
"<F5> SAVE AS PLAYLIST".bold(),
"<F5> SAVE AS IS".bold(),
" | ".dark_gray(),
"<F6> SAVE AS IS".bold(),
"<F6> SAVE AS PLAYLIST".bold(),
" | ".dark_gray(),
"<F8> 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::<Vec<DirEntry>>();
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<Utc> = entry.metadata().unwrap().modified().unwrap().into();
@ -116,11 +137,22 @@ impl FileSystem {
})
.collect::<Vec<Vec<String>>>();
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);
}

View File

@ -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<XTrackItem>,
}
fn track_from_soundcloud(value: &CloudTrack) -> XTrackItem {
async fn track_from_soundcloud(value: &CloudTrack) -> Option<XTrackItem> {
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<AppEvent>,
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<AppEvent>, 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<AudioFileTrack>,
}
#[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<AudioInfo> {
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())
}
}