upd
This commit is contained in:
parent
a64f77c2a4
commit
41ef6bcbb1
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -434,7 +434,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1038,8 +1038,8 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itunesdb"
|
name = "itunesdb"
|
||||||
version = "0.1.57"
|
version = "0.1.69"
|
||||||
source = "git+https://gitea.awain.net/alterwain/ITunesDB.git#ea14aaf6c3284b2cd83f4628b21f652099a79815"
|
source = "git+https://gitea.awain.net/alterwain/ITunesDB.git#03263e0d02f45062238f020e2c58faa91a4a59ab"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode",
|
"bincode",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
@ -1614,7 +1614,7 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1969,7 +1969,7 @@ dependencies = [
|
|||||||
"getrandom 0.3.1",
|
"getrandom 0.3.1",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2457,7 +2457,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.59.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -21,7 +21,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.57", git = "https://gitea.awain.net/alterwain/ITunesDB.git" }
|
itunesdb = { version = "0.1.69", git = "https://gitea.awain.net/alterwain/ITunesDB.git" }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
tui-big-text = "0.7.1"
|
tui-big-text = "0.7.1"
|
||||||
throbber-widgets-tui = "0.8.0"
|
throbber-widgets-tui = "0.8.0"
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
Lightweight iPod manager, batteries included.
|
Lightweight iPod manager, batteries included.
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img src=""/>
|
<img src=""/>
|
||||||
@ -37,10 +37,10 @@ Lightweight iPod manager, batteries included.
|
|||||||
|
|
||||||
### Manually
|
### Manually
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
## Todos
|
## Todos
|
||||||
|
|
||||||
- Implement import from file system
|
|
||||||
- Implement artwork integration
|
- Implement artwork integration
|
||||||
- Implement addition to fresh ipod (without any data)
|
|
||||||
- Implement youtube api
|
- Implement youtube api
|
||||||
- Implement albums generation
|
- Implement albums generation
|
@ -16,17 +16,39 @@ use tokio::sync::mpsc::UnboundedSender;
|
|||||||
|
|
||||||
pub struct FileSystem {
|
pub struct FileSystem {
|
||||||
files: Vec<DirEntry>,
|
files: Vec<DirEntry>,
|
||||||
|
current_path: PathBuf,
|
||||||
table: SmartTable,
|
table: SmartTable,
|
||||||
sender: UnboundedSender<AppEvent>,
|
sender: UnboundedSender<AppEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_extension_comptability(ext: &OsStr) -> bool {
|
fn check_extension_compatibility(ext: &OsStr) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
ext.to_str().unwrap().to_lowercase().as_str(),
|
ext.to_str().unwrap().to_lowercase().as_str(),
|
||||||
"mp3" | "m4a" | "wav" | "aiff" | "aif" | "aac"
|
"mp3" | "m4a" | "wav" | "aiff" | "aif"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn list_files_recursively(p: PathBuf) -> Vec<PathBuf> {
|
||||||
|
let mut files = Vec::new();
|
||||||
|
|
||||||
|
let paths = std::fs::read_dir(p).unwrap();
|
||||||
|
|
||||||
|
for path in paths {
|
||||||
|
if path.is_err() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let a = path.unwrap().path();
|
||||||
|
if a.is_file() && check_extension_compatibility(a.extension().unwrap()) {
|
||||||
|
files.push(a.clone());
|
||||||
|
}
|
||||||
|
if a.is_dir() {
|
||||||
|
files.append(&mut list_files_recursively(a));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
files
|
||||||
|
}
|
||||||
|
|
||||||
impl AppScreen for FileSystem {
|
impl AppScreen for FileSystem {
|
||||||
fn handle_key_event(&mut self, key_event: crossterm::event::KeyEvent) {
|
fn handle_key_event(&mut self, key_event: crossterm::event::KeyEvent) {
|
||||||
match key_event.code {
|
match key_event.code {
|
||||||
@ -37,9 +59,10 @@ impl AppScreen for FileSystem {
|
|||||||
.sender
|
.sender
|
||||||
.send(AppEvent::SwitchScreen(AppState::MainScreen));
|
.send(AppEvent::SwitchScreen(AppState::MainScreen));
|
||||||
}
|
}
|
||||||
KeyCode::F(5) => {
|
KeyCode::F(5) => self.download_as_is(),
|
||||||
self.download_as_is();
|
KeyCode::F(6) => self.download_as_playlist(),
|
||||||
}
|
KeyCode::Tab => self.move_up(),
|
||||||
|
KeyCode::Enter => self.enter_directory(),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,16 +80,14 @@ 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![
|
||||||
|
"<TAB> MOVE UP".bold(),
|
||||||
|
" | ".dark_gray(),
|
||||||
"<F4> SWITCH TO NORMAL".bold(),
|
"<F4> SWITCH TO NORMAL".bold(),
|
||||||
" | ".dark_gray(),
|
" | ".dark_gray(),
|
||||||
"<F5> SAVE AS IS".bold(),
|
"<F5> SAVE AS IS".bold(),
|
||||||
" | ".dark_gray(),
|
" | ".dark_gray(),
|
||||||
"<F6> SAVE AS PLAYLIST".bold(),
|
"<F6> SAVE AS PLAYLIST".bold(),
|
||||||
" | ".dark_gray(),
|
" | ".dark_gray(),
|
||||||
"<F8> SELECT".bold(),
|
|
||||||
" | ".dark_gray(),
|
|
||||||
"<F9> DESELECT".bold(),
|
|
||||||
" | ".dark_gray(),
|
|
||||||
"<Q> QUIT".bold(),
|
"<Q> QUIT".bold(),
|
||||||
]))
|
]))
|
||||||
.centered();
|
.centered();
|
||||||
@ -97,23 +118,32 @@ impl FileSystem {
|
|||||||
table,
|
table,
|
||||||
sender,
|
sender,
|
||||||
files: Vec::new(),
|
files: Vec::new(),
|
||||||
|
current_path: dirs::document_dir().unwrap(),
|
||||||
};
|
};
|
||||||
a.get_path(dirs::document_dir().unwrap());
|
a.get_path(dirs::document_dir().unwrap());
|
||||||
a
|
a
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_path(&mut self, p: PathBuf) {
|
fn get_path(&mut self, p: PathBuf) {
|
||||||
|
self.current_path = p.clone();
|
||||||
let paths = std::fs::read_dir(&p).unwrap();
|
let paths = std::fs::read_dir(&p).unwrap();
|
||||||
let mut dir = paths
|
let mut dir = paths
|
||||||
.filter_map(|res| res.ok())
|
.filter_map(|res| res.ok())
|
||||||
.filter(|p| {
|
.filter(|p| {
|
||||||
p.path()
|
p.path()
|
||||||
.extension()
|
.extension()
|
||||||
.map_or(false, check_extension_comptability)
|
.map_or(false, check_extension_compatibility)
|
||||||
|| p.path().is_dir()
|
|| p.path().is_dir()
|
||||||
})
|
})
|
||||||
.collect::<Vec<DirEntry>>();
|
.collect::<Vec<DirEntry>>();
|
||||||
dir.sort_by(|a, _b| {
|
dir.sort_by(|a, b| {
|
||||||
|
if a.file_type().unwrap().is_dir() == b.file_type().unwrap().is_dir() {
|
||||||
|
let af = a.file_name();
|
||||||
|
let bf = b.file_name();
|
||||||
|
let ac = af.to_str().unwrap_or("a");
|
||||||
|
let bc = bf.to_str().unwrap_or("a");
|
||||||
|
return ac.cmp(bc);
|
||||||
|
}
|
||||||
if a.file_type().unwrap().is_dir() {
|
if a.file_type().unwrap().is_dir() {
|
||||||
Ordering::Less
|
Ordering::Less
|
||||||
} else {
|
} else {
|
||||||
@ -127,7 +157,12 @@ impl FileSystem {
|
|||||||
let datetime: DateTime<Utc> = entry.metadata().unwrap().modified().unwrap().into();
|
let datetime: DateTime<Utc> = entry.metadata().unwrap().modified().unwrap().into();
|
||||||
let datetime = datetime.format("%d/%m/%Y %T").to_string();
|
let datetime = datetime.format("%d/%m/%Y %T").to_string();
|
||||||
let size = entry.metadata().unwrap().size().to_string();
|
let size = entry.metadata().unwrap().size().to_string();
|
||||||
let file_type = entry.file_type().unwrap().is_file().to_string();
|
let file_type = if entry.file_type().unwrap().is_file() {
|
||||||
|
"FILE"
|
||||||
|
} else {
|
||||||
|
"DIR"
|
||||||
|
}
|
||||||
|
.to_string();
|
||||||
vec![
|
vec![
|
||||||
entry.file_name().to_str().unwrap().to_string(),
|
entry.file_name().to_str().unwrap().to_string(),
|
||||||
file_type,
|
file_type,
|
||||||
@ -147,12 +182,47 @@ impl FileSystem {
|
|||||||
fn download_as_is(&self) {
|
fn download_as_is(&self) {
|
||||||
let entry = self.files.get(self.table.selected_row()).unwrap();
|
let entry = self.files.get(self.table.selected_row()).unwrap();
|
||||||
if entry.path().is_dir() {
|
if entry.path().is_dir() {
|
||||||
todo!("Implement that later");
|
let files = list_files_recursively(entry.path());
|
||||||
|
let _ = self.sender.send(AppEvent::LoadFromFSVec(files));
|
||||||
} else {
|
} else {
|
||||||
let _ = self.sender.send(AppEvent::LoadFromFS(entry.path()));
|
let _ = self.sender.send(AppEvent::LoadFromFS(entry.path()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn download_as_playlist(&self) {
|
||||||
|
let entry = self.files.get(self.table.selected_row()).unwrap();
|
||||||
|
if entry.path().is_dir() {
|
||||||
|
let files = list_files_recursively(entry.path());
|
||||||
|
let _ = self.sender.send(AppEvent::LoadFromFSPL((
|
||||||
|
files,
|
||||||
|
entry
|
||||||
|
.path()
|
||||||
|
.file_name()
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
.to_string(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_up(&mut self) {
|
||||||
|
let p = self.current_path.parent();
|
||||||
|
if p.is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let p: PathBuf = p.unwrap().to_path_buf();
|
||||||
|
self.get_path(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_directory(&mut self) {
|
||||||
|
let entry = self.files.get(self.table.selected_row()).unwrap();
|
||||||
|
if !entry.path().is_dir() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.get_path(entry.path());
|
||||||
|
}
|
||||||
|
|
||||||
fn render_main(&self, frame: &mut Frame, area: Rect) {
|
fn render_main(&self, frame: &mut Frame, area: Rect) {
|
||||||
self.table.render(frame, area);
|
self.table.render(frame, area);
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,8 @@ impl AppScreen for MainScreen {
|
|||||||
KeyCode::Up => self.previous_row(),
|
KeyCode::Up => self.previous_row(),
|
||||||
KeyCode::Down => self.next_row(),
|
KeyCode::Down => self.next_row(),
|
||||||
KeyCode::F(5) => self.download_row(),
|
KeyCode::F(5) => self.download_row(),
|
||||||
|
KeyCode::F(8) => self.remove_row(),
|
||||||
|
KeyCode::F(9) => self.remove_completely(),
|
||||||
KeyCode::Tab => self.switch_mode(),
|
KeyCode::Tab => self.switch_mode(),
|
||||||
KeyCode::F(4) => {
|
KeyCode::F(4) => {
|
||||||
let _ = self
|
let _ = self
|
||||||
@ -74,8 +76,6 @@ impl AppScreen for MainScreen {
|
|||||||
|
|
||||||
// Render Status Bar
|
// Render Status Bar
|
||||||
let status_bar = Paragraph::new(Line::from(vec![
|
let status_bar = Paragraph::new(Line::from(vec![
|
||||||
"◄ ► to change tab".bold(),
|
|
||||||
" | ".dark_gray(),
|
|
||||||
"<TAB> SWITCH PANEL".bold(),
|
"<TAB> SWITCH PANEL".bold(),
|
||||||
" | ".dark_gray(),
|
" | ".dark_gray(),
|
||||||
"<F4> FS MODE".bold(),
|
"<F4> FS MODE".bold(),
|
||||||
@ -84,6 +84,8 @@ impl AppScreen for MainScreen {
|
|||||||
" | ".dark_gray(),
|
" | ".dark_gray(),
|
||||||
"<F8> DEL".bold(),
|
"<F8> DEL".bold(),
|
||||||
" | ".dark_gray(),
|
" | ".dark_gray(),
|
||||||
|
"<F9> DEL REC".bold(),
|
||||||
|
" | ".dark_gray(),
|
||||||
"<Q> QUIT".bold(),
|
"<Q> QUIT".bold(),
|
||||||
]))
|
]))
|
||||||
.centered();
|
.centered();
|
||||||
@ -157,6 +159,75 @@ impl MainScreen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn remove_row(&mut self) {
|
||||||
|
if self.selected_tab != 2 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let pl_id = self
|
||||||
|
.playlists
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.get(self.pl_table.selected_row())
|
||||||
|
.unwrap()
|
||||||
|
.id;
|
||||||
|
match self.mode {
|
||||||
|
false => {
|
||||||
|
let _ = self.sender.send(AppEvent::RemovePlaylist((pl_id, false)));
|
||||||
|
}
|
||||||
|
true => {
|
||||||
|
let track_id = self
|
||||||
|
.playlists
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.get(self.pl_table.selected_row())
|
||||||
|
.unwrap()
|
||||||
|
.tracks
|
||||||
|
.get(self.song_table.selected_row())
|
||||||
|
.unwrap()
|
||||||
|
.data
|
||||||
|
.unique_id;
|
||||||
|
|
||||||
|
let _ = self
|
||||||
|
.sender
|
||||||
|
.send(AppEvent::RemoveTrackFromPlaylist((track_id, pl_id)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_completely(&mut self) {
|
||||||
|
if self.selected_tab != 2 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
match self.mode {
|
||||||
|
false => {
|
||||||
|
let pl_id = self
|
||||||
|
.playlists
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.get(self.pl_table.selected_row())
|
||||||
|
.unwrap()
|
||||||
|
.id;
|
||||||
|
|
||||||
|
let _ = self.sender.send(AppEvent::RemovePlaylist((pl_id, true)));
|
||||||
|
}
|
||||||
|
true => {
|
||||||
|
let track = self
|
||||||
|
.playlists
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.get(self.pl_table.selected_row())
|
||||||
|
.unwrap()
|
||||||
|
.tracks
|
||||||
|
.get(self.song_table.selected_row())
|
||||||
|
.unwrap()
|
||||||
|
.clone();
|
||||||
|
let _ = self
|
||||||
|
.sender
|
||||||
|
.send(AppEvent::RemoveTrack(track.data.unique_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn download_row(&mut self) {
|
fn download_row(&mut self) {
|
||||||
if self.selected_tab == 1 {
|
if self.selected_tab == 1 {
|
||||||
// SC
|
// SC
|
||||||
@ -246,7 +317,7 @@ impl MainScreen {
|
|||||||
let date = Utc.timestamp_millis_opt(playlist.timestamp as i64).unwrap();
|
let date = Utc.timestamp_millis_opt(playlist.timestamp as i64).unwrap();
|
||||||
vec![
|
vec![
|
||||||
playlist.id.to_string(),
|
playlist.id.to_string(),
|
||||||
"".to_string(),
|
playlist.title.clone(),
|
||||||
playlist.tracks.len().to_string(),
|
playlist.tracks.len().to_string(),
|
||||||
format!("{}", date.format("%Y-%m-%d %H:%M")),
|
format!("{}", date.format("%Y-%m-%d %H:%M")),
|
||||||
"YES".to_string(),
|
"YES".to_string(),
|
||||||
@ -330,7 +401,7 @@ impl MainScreen {
|
|||||||
vec![
|
vec![
|
||||||
track.data.unique_id.to_string(),
|
track.data.unique_id.to_string(),
|
||||||
track.get_title(),
|
track.get_title(),
|
||||||
track.get_location(),
|
track.get_artist(),
|
||||||
track.data.bitrate.to_string(),
|
track.data.bitrate.to_string(),
|
||||||
track.get_genre(),
|
track.get_genre(),
|
||||||
]
|
]
|
||||||
|
426
src/sync.rs
426
src/sync.rs
@ -1,9 +1,9 @@
|
|||||||
use audiotags::Tag;
|
use audiotags::Tag;
|
||||||
|
use color_eyre::owo_colors::OwoColorize;
|
||||||
use itunesdb::objects::{ListSortOrder, PlaylistItem};
|
use itunesdb::objects::{ListSortOrder, PlaylistItem};
|
||||||
use itunesdb::serializer;
|
use itunesdb::serializer;
|
||||||
use itunesdb::xobjects::{XDatabase, XPlArgument, XPlaylist, XTrackItem};
|
use itunesdb::xobjects::{XDatabase, XPlArgument, XPlaylist, XTrackItem};
|
||||||
use soundcloud::sobjects::{CloudPlaylist, CloudPlaylists, CloudTrack};
|
use soundcloud::sobjects::{CloudPlaylist, CloudPlaylists, CloudTrack};
|
||||||
use std::error::Error;
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
@ -32,6 +32,11 @@ pub enum AppEvent {
|
|||||||
OverallProgress((u32, u32)),
|
OverallProgress((u32, u32)),
|
||||||
SwitchScreen(AppState),
|
SwitchScreen(AppState),
|
||||||
LoadFromFS(PathBuf),
|
LoadFromFS(PathBuf),
|
||||||
|
LoadFromFSVec(Vec<PathBuf>),
|
||||||
|
LoadFromFSPL((Vec<PathBuf>, String)),
|
||||||
|
RemoveTrack(u32),
|
||||||
|
RemovePlaylist((u64, bool)),
|
||||||
|
RemoveTrackFromPlaylist((u32, u64)),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DBPlaylist {
|
pub struct DBPlaylist {
|
||||||
@ -45,23 +50,24 @@ async fn track_from_soundcloud(value: &CloudTrack) -> Option<XTrackItem> {
|
|||||||
let mut track_path = get_temp_dl_dir();
|
let mut track_path = get_temp_dl_dir();
|
||||||
track_path.push(value.id.to_string());
|
track_path.push(value.id.to_string());
|
||||||
track_path.set_extension("mp3");
|
track_path.set_extension("mp3");
|
||||||
let f = std::fs::File::open(&track_path).unwrap();
|
let audio_file = audio_file_info::from_path(track_path.to_str().unwrap())
|
||||||
let mut data = &std::fs::read(&track_path).unwrap()[..];
|
|
||||||
let audio_info = audio_file_info::from_path(track_path.to_str().unwrap())
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let audio_info = audio_info.audio_file.tracks.get(0).unwrap();
|
let audio_info = &audio_file.audio_file.tracks.track;
|
||||||
|
|
||||||
let mut track = XTrackItem::new(
|
let mut track = XTrackItem::new(
|
||||||
value.id as u32,
|
value.id as u32,
|
||||||
f.metadata().unwrap().len() as u32,
|
audio_info.audio_bytes as u32,
|
||||||
(audio_info.duration * 1000.0) as u32,
|
(audio_info.duration * 1000.0) as u32,
|
||||||
0,
|
0,
|
||||||
audio_info.bit_rate as u32,
|
(audio_info.bit_rate / 1000) as u32,
|
||||||
audio_info.sample_rate as u32,
|
audio_info.sample_rate as u32,
|
||||||
hash(),
|
hash(),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
audio_file.modify_xtrack(&mut track);
|
||||||
|
|
||||||
track.set_title(value.title.clone().unwrap());
|
track.set_title(value.title.clone().unwrap());
|
||||||
track.set_artist(
|
track.set_artist(
|
||||||
value
|
value
|
||||||
@ -70,7 +76,6 @@ async fn track_from_soundcloud(value: &CloudTrack) -> Option<XTrackItem> {
|
|||||||
.map_or(String::new(), |a| a.username.unwrap_or(a.permalink)),
|
.map_or(String::new(), |a| a.username.unwrap_or(a.permalink)),
|
||||||
);
|
);
|
||||||
track.set_genre(value.genre.clone().unwrap());
|
track.set_genre(value.genre.clone().unwrap());
|
||||||
track.update_arg(6, String::from("MPEG audio file"));
|
|
||||||
Some(track)
|
Some(track)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,12 +84,42 @@ fn hash() -> u64 {
|
|||||||
rand::random::<u64>()
|
rand::random::<u64>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn overwrite_database(database: &mut XDatabase, ipod_path: &String) {
|
fn get_track_location(unique_id: u32, extension: &str) -> String {
|
||||||
let data = serializer::to_bytes(database);
|
let mut tp = PathBuf::new();
|
||||||
let mut p: PathBuf = Path::new(ipod_path).into();
|
tp.push(":iPod_Control");
|
||||||
|
tp.push("Music");
|
||||||
|
tp.push(["F", &format!("{:02}", &(unique_id % 100))].concat());
|
||||||
|
tp.push(format!("{:X}", unique_id));
|
||||||
|
tp.set_extension(extension);
|
||||||
|
tp.to_str()
|
||||||
|
.unwrap()
|
||||||
|
.to_string()
|
||||||
|
.replace("/", ":")
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_full_track_location(p: PathBuf, unique_id: u32, extension: &str) -> PathBuf {
|
||||||
|
let mut dest = p.clone();
|
||||||
|
dest.push("iPod_Control");
|
||||||
|
dest.push("Music");
|
||||||
|
dest.push(["F", &format!("{:02}", &(unique_id % 100))].concat());
|
||||||
|
let _ = std::fs::create_dir_all(dest.to_str().unwrap());
|
||||||
|
dest.push(format!("{:X}", unique_id));
|
||||||
|
dest.set_extension(extension);
|
||||||
|
dest
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_itunesdb_location(path: &str) -> PathBuf {
|
||||||
|
let mut p: PathBuf = Path::new(path).into();
|
||||||
p.push("iPod_Control");
|
p.push("iPod_Control");
|
||||||
p.push("iTunes");
|
p.push("iTunes");
|
||||||
p.push("iTunesDB");
|
p.push("iTunesDB");
|
||||||
|
p
|
||||||
|
}
|
||||||
|
|
||||||
|
fn overwrite_database(database: &mut XDatabase, ipod_path: &String) {
|
||||||
|
let data = serializer::to_bytes(database);
|
||||||
|
let p: PathBuf = get_itunesdb_location(ipod_path);
|
||||||
let mut file = std::fs::File::create(p).unwrap();
|
let mut file = std::fs::File::create(p).unwrap();
|
||||||
let _ = file.write(&data);
|
let _ = file.write(&data);
|
||||||
}
|
}
|
||||||
@ -121,7 +156,12 @@ 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::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;},
|
||||||
AppEvent::LoadFromFS(path) => load_from_fs(path, database.as_mut().unwrap(), &sender, ipod_db.clone().unwrap()),
|
AppEvent::LoadFromFS(path) => { load_from_fs(path, database.as_mut().unwrap(), &sender, ipod_db.clone().unwrap()).await; },
|
||||||
|
AppEvent::LoadFromFSVec(files) => load_files_from_fs(files, database.as_mut().unwrap(), &sender, ipod_db.clone().unwrap()).await,
|
||||||
|
AppEvent::LoadFromFSPL((files, title)) => load_files_from_fs_as_playlist(files, title, database.as_mut().unwrap(), &sender, ipod_db.clone().unwrap()).await,
|
||||||
|
AppEvent::RemoveTrack(id) => remove_track(id, database.as_mut().unwrap(), &sender, ipod_db.clone().unwrap()).await,
|
||||||
|
AppEvent::RemovePlaylist((pl_id, is_hard)) => remove_playlist(pl_id, is_hard, database.as_mut().unwrap(), &sender, ipod_db.clone().unwrap()).await,
|
||||||
|
AppEvent::RemoveTrackFromPlaylist((track_id, pl_id)) => remove_track_from_playlist(track_id, pl_id, database.as_mut().unwrap(), &sender, ipod_db.clone().unwrap()).await,
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,49 +171,220 @@ pub fn initialize_async_service(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_from_fs(
|
async fn remove_track_from_playlist(
|
||||||
path: PathBuf,
|
track_id: u32,
|
||||||
|
pl_id: u64,
|
||||||
database: &mut XDatabase,
|
database: &mut XDatabase,
|
||||||
sender: &Sender<AppEvent>,
|
sender: &Sender<AppEvent>,
|
||||||
ipod_path: String,
|
ipod_path: String,
|
||||||
) {
|
) {
|
||||||
let mut tag = Tag::new().read_from_path(path).unwrap();
|
let _ = sender.send(AppEvent::OverallProgress((0, 1))).await;
|
||||||
|
|
||||||
|
database.remove_track_from_playlist(track_id, pl_id);
|
||||||
|
|
||||||
|
let _ = sender.send(AppEvent::OverallProgress((1, 1))).await;
|
||||||
|
|
||||||
|
let _ = sender
|
||||||
|
.send(AppEvent::SwitchScreen(AppState::MainScreen))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let _ = sender
|
||||||
|
.send(AppEvent::ITunesParsed(get_playlists(database)))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
overwrite_database(database, &ipod_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn remove_playlist(
|
||||||
|
pl_id: u64,
|
||||||
|
is_hard: bool,
|
||||||
|
database: &mut XDatabase,
|
||||||
|
sender: &Sender<AppEvent>,
|
||||||
|
ipod_path: String,
|
||||||
|
) {
|
||||||
|
if is_hard {
|
||||||
|
let pls = database.get_playlists();
|
||||||
|
let pl = pls.iter().find(|p| p.data.persistent_playlist_id == pl_id);
|
||||||
|
if pl.is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let pl = pl.unwrap();
|
||||||
|
let max = pl.elems.len();
|
||||||
|
let mut i = 1;
|
||||||
|
for (item, args) in pl.elems.iter() {
|
||||||
|
let _ = sender
|
||||||
|
.send(AppEvent::OverallProgress((i, max as u32)))
|
||||||
|
.await;
|
||||||
|
remove_track(item.track_id, database, sender, ipod_path.clone()).await;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = sender.send(AppEvent::OverallProgress((0, 1))).await;
|
||||||
|
|
||||||
|
database.remove_playlist(pl_id);
|
||||||
|
|
||||||
|
let _ = sender.send(AppEvent::OverallProgress((1, 1))).await;
|
||||||
|
|
||||||
|
let _ = sender
|
||||||
|
.send(AppEvent::SwitchScreen(AppState::MainScreen))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let _ = sender
|
||||||
|
.send(AppEvent::ITunesParsed(get_playlists(database)))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
overwrite_database(database, &ipod_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn remove_track(
|
||||||
|
id: u32,
|
||||||
|
database: &mut XDatabase,
|
||||||
|
sender: &Sender<AppEvent>,
|
||||||
|
ipod_path: String,
|
||||||
|
) {
|
||||||
|
let _ = sender.send(AppEvent::OverallProgress((0, 1))).await;
|
||||||
|
database.remove_track_completely(id);
|
||||||
|
for ext in ["mp3", "m4a", "wav", "aif"].iter() {
|
||||||
|
let dest = get_full_track_location(PathBuf::from(ipod_path.clone()), id, ext);
|
||||||
|
let _ = std::fs::remove_file(dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = sender.send(AppEvent::OverallProgress((1, 1))).await;
|
||||||
|
|
||||||
|
let _ = sender
|
||||||
|
.send(AppEvent::SwitchScreen(AppState::MainScreen))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let _ = sender
|
||||||
|
.send(AppEvent::ITunesParsed(get_playlists(database)))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
overwrite_database(database, &ipod_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn load_files_from_fs_as_playlist(
|
||||||
|
files: Vec<PathBuf>,
|
||||||
|
title: String,
|
||||||
|
database: &mut XDatabase,
|
||||||
|
sender: &Sender<AppEvent>,
|
||||||
|
ipod_path: String,
|
||||||
|
) {
|
||||||
|
let mut new_playlist = XPlaylist::new(rand::random(), ListSortOrder::SongTitle);
|
||||||
|
|
||||||
|
new_playlist.set_title(title);
|
||||||
|
|
||||||
|
for (i, file) in files.iter().enumerate() {
|
||||||
|
let _ = sender
|
||||||
|
.send(AppEvent::OverallProgress((i as u32, files.len() as u32)))
|
||||||
|
.await;
|
||||||
|
let id = load_from_fs(file.clone(), database, sender, ipod_path.clone()).await;
|
||||||
|
|
||||||
|
new_playlist.add_elem(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
database.add_playlist(new_playlist);
|
||||||
|
|
||||||
|
let _ = sender
|
||||||
|
.send(AppEvent::SwitchScreen(AppState::MainScreen))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let _ = sender
|
||||||
|
.send(AppEvent::ITunesParsed(get_playlists(database)))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
overwrite_database(database, &ipod_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn load_files_from_fs(
|
||||||
|
files: Vec<PathBuf>,
|
||||||
|
database: &mut XDatabase,
|
||||||
|
sender: &Sender<AppEvent>,
|
||||||
|
ipod_path: String,
|
||||||
|
) {
|
||||||
|
for (i, file) in files.iter().enumerate() {
|
||||||
|
let _ = sender
|
||||||
|
.send(AppEvent::OverallProgress((i as u32, files.len() as u32)))
|
||||||
|
.await;
|
||||||
|
load_from_fs(file.clone(), database, sender, ipod_path.clone()).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn load_from_fs(
|
||||||
|
path: PathBuf,
|
||||||
|
database: &mut XDatabase,
|
||||||
|
sender: &Sender<AppEvent>,
|
||||||
|
ipod_path: String,
|
||||||
|
) -> u32 {
|
||||||
|
let tag = Tag::new().read_from_path(&path).unwrap();
|
||||||
|
|
||||||
let id = database.get_unique_id();
|
let id = database.get_unique_id();
|
||||||
|
|
||||||
|
let audio_file = audio_file_info::from_path(path.to_str().unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let audio_info = &audio_file.audio_file.tracks.track;
|
||||||
|
|
||||||
let mut track = XTrackItem::new(
|
let mut track = XTrackItem::new(
|
||||||
id,
|
id,
|
||||||
0,
|
audio_info.audio_bytes as u32,
|
||||||
(tag.duration().unwrap() / 1000.0) as u32,
|
(audio_info.duration * 1000.0) as u32,
|
||||||
tag.year().unwrap() as u32,
|
tag.year().unwrap_or(0) as u32,
|
||||||
0,
|
(audio_info.bit_rate / 1000) as u32,
|
||||||
0,
|
audio_info.sample_rate as u32,
|
||||||
hash(),
|
hash(),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
// TODO: implement check for every property
|
|
||||||
track.set_title(tag.title().unwrap().to_string());
|
|
||||||
|
|
||||||
let mut tp = PathBuf::new();
|
audio_file.modify_xtrack(&mut track);
|
||||||
tp.push(":iPod_Control");
|
|
||||||
tp.push("Music");
|
if let Some(title) = tag.title() {
|
||||||
tp.push(["F", &format!("{:02}", &(track.data.unique_id % 100))].concat());
|
track.set_title(title.to_string());
|
||||||
tp.push(format!("{:X}", track.data.unique_id));
|
} else {
|
||||||
tp.set_extension("mp3");
|
track.set_title(path.file_name().unwrap().to_str().unwrap().to_string());
|
||||||
track.set_location(
|
}
|
||||||
tp.to_str()
|
|
||||||
.unwrap()
|
if let Some(genre) = tag.genre() {
|
||||||
.to_string()
|
track.set_genre(genre.to_string());
|
||||||
.replace("/", ":")
|
}
|
||||||
.to_string(),
|
|
||||||
|
if let Some(artist) = tag.artist() {
|
||||||
|
track.set_artist(artist.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(album) = tag.album() {
|
||||||
|
track.set_album(album.title.to_string());
|
||||||
|
// TODO: Add new album into iTunesDB
|
||||||
|
}
|
||||||
|
|
||||||
|
track.set_location(get_track_location(
|
||||||
|
track.data.unique_id,
|
||||||
|
audio_file.get_audio_extension(),
|
||||||
|
));
|
||||||
|
|
||||||
|
//let cover = tag.album().unwrap().cover.unwrap();
|
||||||
|
|
||||||
|
let dest = get_full_track_location(
|
||||||
|
PathBuf::from(ipod_path.clone()),
|
||||||
|
track.data.unique_id,
|
||||||
|
audio_file.get_audio_extension(),
|
||||||
);
|
);
|
||||||
//track.update_arg(6);
|
|
||||||
|
|
||||||
track.set_genre(tag.genre().unwrap().to_string());
|
let _ = std::fs::copy(path.to_str().unwrap(), dest.to_str().unwrap());
|
||||||
track.set_artist(tag.artist().unwrap().to_string());
|
|
||||||
track.set_album(tag.album().unwrap().title.to_string());
|
|
||||||
|
|
||||||
let cover = tag.album().unwrap().cover.unwrap();
|
database.add_track(track);
|
||||||
|
|
||||||
|
let _ = sender
|
||||||
|
.send(AppEvent::SwitchScreen(AppState::MainScreen))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let _ = sender
|
||||||
|
.send(AppEvent::ITunesParsed(get_playlists(database)))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
overwrite_database(database, &ipod_path);
|
||||||
|
|
||||||
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn download_track(
|
async fn download_track(
|
||||||
@ -193,26 +404,8 @@ async fn download_track(
|
|||||||
|
|
||||||
if let Some(mut t) = track_from_soundcloud(&track).await {
|
if let Some(mut t) = track_from_soundcloud(&track).await {
|
||||||
t.data.unique_id = database.get_unique_id();
|
t.data.unique_id = database.get_unique_id();
|
||||||
let mut tp = PathBuf::new();
|
t.set_location(get_track_location(t.data.unique_id, "mp3"));
|
||||||
tp.push(":iPod_Control");
|
let dest = get_full_track_location(p.clone(), t.data.unique_id, "mp3");
|
||||||
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();
|
let mut track_path = get_temp_dl_dir();
|
||||||
track_path.push(track.id.to_string());
|
track_path.push(track.id.to_string());
|
||||||
@ -260,27 +453,8 @@ async fn download_playlist(
|
|||||||
if let Some(mut t) = track_from_soundcloud(&track).await {
|
if let Some(mut t) = track_from_soundcloud(&track).await {
|
||||||
t.data.unique_id = database.get_unique_id();
|
t.data.unique_id = database.get_unique_id();
|
||||||
new_playlist.add_elem(t.data.unique_id);
|
new_playlist.add_elem(t.data.unique_id);
|
||||||
let mut tp = PathBuf::new();
|
t.set_location(get_track_location(t.data.unique_id, "mp3"));
|
||||||
tp.push(":iPod_Control");
|
let dest = get_full_track_location(p.clone(), t.data.unique_id, "mp3");
|
||||||
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();
|
let mut track_path = get_temp_dl_dir();
|
||||||
track_path.push(track.id.to_string());
|
track_path.push(track.id.to_string());
|
||||||
track_path.set_extension("mp3");
|
track_path.set_extension("mp3");
|
||||||
@ -329,10 +503,7 @@ fn to_tracks(db: &mut XDatabase, elems: Vec<(PlaylistItem, Vec<XPlArgument>)>) -
|
|||||||
|
|
||||||
async fn parse_itunes(sender: &Sender<AppEvent>, path: String) -> XDatabase {
|
async fn parse_itunes(sender: &Sender<AppEvent>, path: String) -> XDatabase {
|
||||||
let cd = get_temp_itunesdb();
|
let cd = get_temp_itunesdb();
|
||||||
let mut p: PathBuf = Path::new(&path).into();
|
let p = get_itunesdb_location(&path);
|
||||||
p.push("iPod_Control");
|
|
||||||
p.push("iTunes");
|
|
||||||
p.push("iTunesDB");
|
|
||||||
let _ = std::fs::copy(p, &cd);
|
let _ = std::fs::copy(p, &cd);
|
||||||
let mut file = File::open(cd).await.unwrap();
|
let mut file = File::open(cd).await.unwrap();
|
||||||
let mut contents = vec![];
|
let mut contents = vec![];
|
||||||
@ -389,24 +560,84 @@ async fn parse_itunes(sender: &Sender<AppEvent>, path: String) -> XDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mod audio_file_info {
|
mod audio_file_info {
|
||||||
use serde::{Deserialize, Serialize};
|
use itunesdb::xobjects::XTrackItem;
|
||||||
|
use serde::Deserialize;
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
use tokio::io::{AsyncReadExt, BufReader};
|
use tokio::io::{AsyncReadExt, BufReader};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Deserialize, PartialEq)]
|
||||||
pub struct AudioInfo {
|
pub struct AudioInfo {
|
||||||
pub audio_file: AudioFileInfo,
|
pub audio_file: AudioFileInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Deserialize, PartialEq)]
|
||||||
pub struct AudioFileInfo {
|
pub struct AudioFileInfo {
|
||||||
pub file_name: String,
|
pub file_name: String,
|
||||||
pub file_type: String,
|
pub file_type: String,
|
||||||
pub tracks: Vec<AudioFileTrack>,
|
pub tracks: AudioFileTracks,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
impl AudioInfo {
|
||||||
|
pub fn get_audio_extension(&self) -> &str {
|
||||||
|
match self.audio_file.file_type.as_str() {
|
||||||
|
"'WAVE'" => "wav",
|
||||||
|
"'AIFF'" => "aif",
|
||||||
|
"'m4af'" => "m4a",
|
||||||
|
_ => "mp3",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_audio_codec(&self) -> String {
|
||||||
|
match self.audio_file.file_type.as_str() {
|
||||||
|
"'WAVE'" => "WAV audio file",
|
||||||
|
"'AIFF'" => "AIFF audio file",
|
||||||
|
"'m4af'" => match self.audio_file.tracks.track.format_type.as_str() {
|
||||||
|
"alac" => "Apple Lossless audio file",
|
||||||
|
_ => "AAC audio file",
|
||||||
|
},
|
||||||
|
_ => "MPEG audio file",
|
||||||
|
}
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn modify_xtrack(&self, track: &mut XTrackItem) {
|
||||||
|
track.data.type1 = 0;
|
||||||
|
track.data.type2 = if self.audio_file.file_type == "'MPG3'" {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
let bytes = match self.audio_file.file_type.as_str() {
|
||||||
|
"'WAVE'" => "WAV",
|
||||||
|
"'AIFF'" => "AIF",
|
||||||
|
"'m4af'" => match self.audio_file.tracks.track.format_type.as_str() {
|
||||||
|
"alac" => "M4A ",
|
||||||
|
_ => "M4A",
|
||||||
|
},
|
||||||
|
_ => "MP3",
|
||||||
|
}
|
||||||
|
.as_bytes();
|
||||||
|
|
||||||
|
let file_type = u32::from_le_bytes(if bytes.len() == 4 {
|
||||||
|
[bytes[0], bytes[1], bytes[2], bytes[3]]
|
||||||
|
} else {
|
||||||
|
[bytes[0], bytes[1], bytes[2], 0u8]
|
||||||
|
});
|
||||||
|
|
||||||
|
track.data.filetype = file_type;
|
||||||
|
|
||||||
|
track.update_arg(6, self.get_audio_codec());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, PartialEq)]
|
||||||
|
pub struct AudioFileTracks {
|
||||||
|
pub track: AudioFileTrack,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, PartialEq)]
|
||||||
pub struct AudioFileTrack {
|
pub struct AudioFileTrack {
|
||||||
pub num_channels: u32,
|
pub num_channels: u32,
|
||||||
pub sample_rate: u64,
|
pub sample_rate: u64,
|
||||||
@ -418,18 +649,25 @@ mod audio_file_info {
|
|||||||
|
|
||||||
pub async fn from_path(p: &str) -> Option<AudioInfo> {
|
pub async fn from_path(p: &str) -> Option<AudioInfo> {
|
||||||
let mut command = Command::new("afinfo");
|
let mut command = Command::new("afinfo");
|
||||||
command.args(vec!["-x", p]);
|
command.arg("-x");
|
||||||
|
command.arg(p);
|
||||||
command.stdout(Stdio::piped());
|
command.stdout(Stdio::piped());
|
||||||
command.stderr(Stdio::null());
|
command.stderr(Stdio::null());
|
||||||
|
|
||||||
let mut child = command.spawn().unwrap();
|
let mut child = command.spawn().unwrap();
|
||||||
|
|
||||||
let mut str = String::new();
|
let mut vec = Vec::new();
|
||||||
let stdout = child.stdout.take().unwrap();
|
let stdout = child.stdout.take().unwrap();
|
||||||
let size = BufReader::new(stdout)
|
let size = BufReader::new(stdout)
|
||||||
.read_to_string(&mut str)
|
.read_to_end(&mut vec)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap_or(0);
|
||||||
Some(serde_xml_rs::from_str(&str).unwrap())
|
if size == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*let mut f = File::create("afinfo_out.xml").unwrap();
|
||||||
|
let _ = f.write(str.as_bytes());*/
|
||||||
|
Some(serde_xml_rs::from_str(String::from_utf8_lossy(vec.as_slice()).as_ref()).unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user