artworks implemented for soundcloud
This commit is contained in:
parent
7a75c45fa0
commit
a724560d58
14
Cargo.lock
generated
14
Cargo.lock
generated
@ -1284,8 +1284,8 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itunesdb"
|
name = "itunesdb"
|
||||||
version = "0.1.89"
|
version = "0.1.90"
|
||||||
source = "git+https://gitea.awain.net/alterwain/ITunesDB.git#2a867778b7bbd82956247f2f17b98fdb6865a910"
|
source = "git+https://gitea.awain.net/alterwain/ITunesDB.git#d37957a9a0b66577dadf3b305b02c72f1ca50a62"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode",
|
"bincode",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
@ -1441,6 +1441,7 @@ dependencies = [
|
|||||||
"tokio-util",
|
"tokio-util",
|
||||||
"toml",
|
"toml",
|
||||||
"tui-big-text",
|
"tui-big-text",
|
||||||
|
"twox-hash",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2789,6 +2790,15 @@ dependencies = [
|
|||||||
"ratatui",
|
"ratatui",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "twox-hash"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7b17f197b3050ba473acf9181f7b1d3b66d1cf7356c6cc57886662276e65908"
|
||||||
|
dependencies = [
|
||||||
|
"rand",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.16"
|
version = "1.0.16"
|
||||||
|
@ -21,9 +21,10 @@ 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.89", git = "https://gitea.awain.net/alterwain/ITunesDB.git" }
|
itunesdb = { version = "0.1.90", 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"
|
||||||
audiotags = "0.5.0"
|
audiotags = "0.5.0"
|
||||||
image = "0.25.5"
|
image = "0.25.5"
|
||||||
|
twox-hash = "2.1.0"
|
@ -14,6 +14,11 @@ pub fn get_temp_dl_dir() -> PathBuf {
|
|||||||
p
|
p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clear_temp_dl_dir() {
|
||||||
|
let path = get_temp_dl_dir();
|
||||||
|
let _ = std::fs::remove_dir_all(path);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_config_path() -> PathBuf {
|
pub fn get_config_path() -> PathBuf {
|
||||||
let mut p = get_configs_dir();
|
let mut p = get_configs_dir();
|
||||||
p.push("config");
|
p.push("config");
|
||||||
|
106
src/sync.rs
106
src/sync.rs
@ -1,7 +1,7 @@
|
|||||||
use audiotags::{Picture, Tag};
|
use audiotags::Tag;
|
||||||
use color_eyre::owo_colors::OwoColorize;
|
use color_eyre::owo_colors::OwoColorize;
|
||||||
use image::imageops::FilterType;
|
use image::imageops::FilterType;
|
||||||
use image::ImageReader;
|
use image::{GenericImageView, ImageReader};
|
||||||
use itunesdb::artworkdb::aobjects::ADatabase;
|
use itunesdb::artworkdb::aobjects::ADatabase;
|
||||||
use itunesdb::objects::{ListSortOrder, PlaylistItem};
|
use itunesdb::objects::{ListSortOrder, PlaylistItem};
|
||||||
use itunesdb::serializer;
|
use itunesdb::serializer;
|
||||||
@ -51,15 +51,20 @@ pub struct DBPlaylist {
|
|||||||
pub tracks: Vec<XTrackItem>,
|
pub tracks: Vec<XTrackItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn track_from_soundcloud(value: &CloudTrack) -> Option<XTrackItem> {
|
async fn track_from_soundcloud(value: &CloudTrack, ipod_path: String) -> 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 mut image_path = get_temp_dl_dir();
|
||||||
|
image_path.push(value.id.to_string());
|
||||||
|
image_path.set_extension("jpg");
|
||||||
let audio_file = audio_file_info::from_path(track_path.to_str().unwrap())
|
let audio_file = audio_file_info::from_path(track_path.to_str().unwrap())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let audio_info = &audio_file.audio_file.tracks.track;
|
let audio_info = &audio_file.audio_file.tracks.track;
|
||||||
|
|
||||||
|
let song_dbid = util::hash_from_path(track_path);
|
||||||
|
|
||||||
let mut track = XTrackItem::new(
|
let mut track = XTrackItem::new(
|
||||||
value.id as u32,
|
value.id as u32,
|
||||||
audio_info.audio_bytes as u32,
|
audio_info.audio_bytes as u32,
|
||||||
@ -67,10 +72,34 @@ async fn track_from_soundcloud(value: &CloudTrack) -> Option<XTrackItem> {
|
|||||||
0,
|
0,
|
||||||
(audio_info.bit_rate / 1000) as u32,
|
(audio_info.bit_rate / 1000) as u32,
|
||||||
audio_info.sample_rate as u32,
|
audio_info.sample_rate as u32,
|
||||||
hash(),
|
song_dbid,
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if image_path.exists() {
|
||||||
|
let mut adb = get_artwork_db(&ipod_path);
|
||||||
|
|
||||||
|
let image_data = std::fs::read(image_path).unwrap();
|
||||||
|
|
||||||
|
let (small_img_name, large_img_name) = adb.add_images(song_dbid, util::hash(&image_data));
|
||||||
|
|
||||||
|
let mut dst = PathBuf::from(&ipod_path);
|
||||||
|
dst.push("iPod_Control");
|
||||||
|
dst.push("Artwork");
|
||||||
|
|
||||||
|
let size = image_data.len();
|
||||||
|
|
||||||
|
make_cover_image(&image_data, &ipod_path, &small_img_name, (100, 100));
|
||||||
|
make_cover_image(&image_data, &ipod_path, &large_img_name, (200, 200));
|
||||||
|
|
||||||
|
write_artwork_db(adb, &ipod_path);
|
||||||
|
|
||||||
|
track.data.artwork_size = size as u32;
|
||||||
|
track.data.mhii_link = 0;
|
||||||
|
track.data.has_artwork = 1;
|
||||||
|
track.data.artwork_count = 1;
|
||||||
|
}
|
||||||
|
|
||||||
audio_file.modify_xtrack(&mut track);
|
audio_file.modify_xtrack(&mut track);
|
||||||
|
|
||||||
track.set_title(value.title.clone().unwrap());
|
track.set_title(value.title.clone().unwrap());
|
||||||
@ -84,11 +113,6 @@ async fn track_from_soundcloud(value: &CloudTrack) -> Option<XTrackItem> {
|
|||||||
Some(track)
|
Some(track)
|
||||||
}
|
}
|
||||||
|
|
||||||
// note: this hash function is used to make unique ids for each track. It doesn't aim to generate secure ones.
|
|
||||||
fn hash() -> u64 {
|
|
||||||
rand::random::<u64>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_track_location(unique_id: u32, extension: &str) -> String {
|
fn get_track_location(unique_id: u32, extension: &str) -> String {
|
||||||
let mut tp = PathBuf::new();
|
let mut tp = PathBuf::new();
|
||||||
tp.push(":iPod_Control");
|
tp.push(":iPod_Control");
|
||||||
@ -330,7 +354,7 @@ async fn load_from_fs(
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let audio_info = &audio_file.audio_file.tracks.track;
|
let audio_info = &audio_file.audio_file.tracks.track;
|
||||||
|
|
||||||
let song_dbid = hash();
|
let song_dbid = util::hash_from_path(path.clone());
|
||||||
|
|
||||||
let mut track = XTrackItem::new(
|
let mut track = XTrackItem::new(
|
||||||
id,
|
id,
|
||||||
@ -362,18 +386,20 @@ async fn load_from_fs(
|
|||||||
if let Some(cover) = tag.album_cover() {
|
if let Some(cover) = tag.album_cover() {
|
||||||
let mut adb = get_artwork_db(&ipod_path);
|
let mut adb = get_artwork_db(&ipod_path);
|
||||||
|
|
||||||
let (small_img_name, large_img_name) = adb.add_images(song_dbid, id);
|
let (small_img_name, large_img_name) = adb.add_images(song_dbid, util::hash(cover.data));
|
||||||
|
|
||||||
let mut dst = PathBuf::from(&ipod_path);
|
let mut dst = PathBuf::from(&ipod_path);
|
||||||
dst.push("iPod_Control");
|
dst.push("iPod_Control");
|
||||||
dst.push("Artwork");
|
dst.push("Artwork");
|
||||||
|
|
||||||
make_small_image(cover.clone(), &ipod_path, &small_img_name);
|
let size = cover.data.len();
|
||||||
make_large_image(cover, &ipod_path, &large_img_name);
|
|
||||||
|
make_cover_image(cover.data, &ipod_path, &small_img_name, (100, 100));
|
||||||
|
make_cover_image(cover.data, &ipod_path, &large_img_name, (200, 200));
|
||||||
|
|
||||||
write_artwork_db(adb, &ipod_path);
|
write_artwork_db(adb, &ipod_path);
|
||||||
|
|
||||||
track.data.artwork_size = 1134428;
|
track.data.artwork_size = size as u32;
|
||||||
track.data.mhii_link = 0;
|
track.data.mhii_link = 0;
|
||||||
track.data.has_artwork = 1;
|
track.data.has_artwork = 1;
|
||||||
track.data.artwork_count = 1;
|
track.data.artwork_count = 1;
|
||||||
@ -441,32 +467,34 @@ fn get_artwork_db(ipod_path: &str) -> ADatabase {
|
|||||||
itunesdb::artworkdb::deserializer::new_db()
|
itunesdb::artworkdb::deserializer::new_db()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_small_image(cover: Picture, ipod_path: &str, file_name: &str) {
|
fn make_cover_image(cover: &[u8], ipod_path: &str, file_name: &str, dim: (u32, u32)) {
|
||||||
let img: IPodImage = ImageReader::new(Cursor::new(cover.data))
|
let mut dynamic_im = ImageReader::new(Cursor::new(cover))
|
||||||
.with_guessed_format()
|
.with_guessed_format()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.decode()
|
.decode()
|
||||||
.unwrap()
|
.unwrap();
|
||||||
.resize_exact(100, 100, FilterType::Lanczos3)
|
|
||||||
.into();
|
|
||||||
|
|
||||||
let mut dst = PathBuf::from(ipod_path);
|
if dynamic_im.height() != dynamic_im.width() {
|
||||||
dst.push("iPod_Control");
|
let side = if dynamic_im.height() < dynamic_im.width() {
|
||||||
dst.push("Artwork");
|
dynamic_im.height()
|
||||||
|
} else {
|
||||||
|
dynamic_im.width()
|
||||||
|
};
|
||||||
|
let x = if dynamic_im.height() < dynamic_im.width() {
|
||||||
|
(dynamic_im.width() - side) / 2
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
let y = if dynamic_im.height() < dynamic_im.width() {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
(dynamic_im.height() - side) / 2
|
||||||
|
};
|
||||||
|
dynamic_im = dynamic_im.crop(x, y, side, side);
|
||||||
|
}
|
||||||
|
|
||||||
let _ = std::fs::create_dir_all(dst.clone());
|
let img: IPodImage = dynamic_im
|
||||||
|
.resize_exact(dim.0, dim.1, FilterType::Lanczos3)
|
||||||
dst.push(file_name);
|
|
||||||
img.write(dst);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_large_image(cover: Picture, ipod_path: &str, file_name: &str) {
|
|
||||||
let img: IPodImage = ImageReader::new(Cursor::new(cover.data))
|
|
||||||
.with_guessed_format()
|
|
||||||
.unwrap()
|
|
||||||
.decode()
|
|
||||||
.unwrap()
|
|
||||||
.resize_exact(200, 200, FilterType::Lanczos3)
|
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
let mut dst = PathBuf::from(ipod_path);
|
let mut dst = PathBuf::from(ipod_path);
|
||||||
@ -494,7 +522,7 @@ async fn download_track(
|
|||||||
{
|
{
|
||||||
let p: PathBuf = Path::new(&ipod_path).into();
|
let p: PathBuf = Path::new(&ipod_path).into();
|
||||||
|
|
||||||
if let Some(mut t) = track_from_soundcloud(&track).await {
|
if let Some(mut t) = track_from_soundcloud(&track, ipod_path.clone()).await {
|
||||||
t.data.unique_id = database.get_unique_id();
|
t.data.unique_id = database.get_unique_id();
|
||||||
t.set_location(get_track_location(t.data.unique_id, "mp3"));
|
t.set_location(get_track_location(t.data.unique_id, "mp3"));
|
||||||
let dest = get_full_track_location(p.clone(), t.data.unique_id, "mp3");
|
let dest = get_full_track_location(p.clone(), t.data.unique_id, "mp3");
|
||||||
@ -518,6 +546,8 @@ async fn download_track(
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
overwrite_database(database, &ipod_path);
|
overwrite_database(database, &ipod_path);
|
||||||
|
|
||||||
|
crate::config::clear_temp_dl_dir();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn download_playlist(
|
async fn download_playlist(
|
||||||
@ -542,7 +572,7 @@ async fn download_playlist(
|
|||||||
if track.title.is_none() {
|
if track.title.is_none() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(mut t) = track_from_soundcloud(&track).await {
|
if let Some(mut t) = track_from_soundcloud(&track, ipod_path.clone()).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);
|
||||||
t.set_location(get_track_location(t.data.unique_id, "mp3"));
|
t.set_location(get_track_location(t.data.unique_id, "mp3"));
|
||||||
@ -569,6 +599,8 @@ async fn download_playlist(
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
overwrite_database(database, &ipod_path);
|
overwrite_database(database, &ipod_path);
|
||||||
|
|
||||||
|
crate::config::clear_temp_dl_dir();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_playlists(db: &mut XDatabase) -> Vec<DBPlaylist> {
|
fn get_playlists(db: &mut XDatabase) -> Vec<DBPlaylist> {
|
||||||
|
10
src/util.rs
10
src/util.rs
@ -3,6 +3,7 @@ use regex::Regex;
|
|||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::{error::Error, process::Command, str, str::FromStr};
|
use std::{error::Error, process::Command, str, str::FromStr};
|
||||||
|
use twox_hash::XxHash3_64;
|
||||||
|
|
||||||
const VENDOR_ID: u16 = 1452;
|
const VENDOR_ID: u16 = 1452;
|
||||||
const PRODUCT_ID: u16 = 4617;
|
const PRODUCT_ID: u16 = 4617;
|
||||||
@ -110,6 +111,15 @@ fn get_ipod_path() -> Option<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// note: this hash function is used to make unique ids for each track. It doesn't aim to generate secure ones.
|
||||||
|
pub fn hash(data: &[u8]) -> u64 {
|
||||||
|
XxHash3_64::oneshot(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hash_from_path(path: PathBuf) -> u64 {
|
||||||
|
hash(&std::fs::read(path).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
pub struct IPodImage {
|
pub struct IPodImage {
|
||||||
pixels: Vec<u16>,
|
pixels: Vec<u16>,
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user