load metadata using lofty (shout out @Frieder_Hannenheim - you are #1)

This commit is contained in:
🪞👃🪞 2025-03-16 08:50:29 +02:00
parent 28f9220e6c
commit bc067e2739
4 changed files with 92 additions and 53 deletions

42
Cargo.lock generated
View file

@ -324,6 +324,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "data-encoding"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010"
[[package]] [[package]]
name = "dbus" name = "dbus"
version = "0.9.7" version = "0.9.7"
@ -571,6 +577,32 @@ dependencies = [
"scopeguard", "scopeguard",
] ]
[[package]]
name = "lofty"
version = "0.22.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "781de624f162b1a8cbfbd577103ee9b8e5f62854b053ff48f4e31e68a0a7df6f"
dependencies = [
"byteorder",
"data-encoding",
"flate2",
"lofty_attr",
"log",
"ogg_pager",
"paste",
]
[[package]]
name = "lofty_attr"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed9983e64b2358522f745c1251924e3ab7252d55637e80f6a0a3de642d6a9efc"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.26" version = "0.4.26"
@ -640,6 +672,15 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "ogg_pager"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e034c10fb5c1c012c1b327b85df89fb0ef98ae66ec28af30f0d1eed804a40c19"
dependencies = [
"byteorder",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.21.1" version = "1.21.1"
@ -1060,6 +1101,7 @@ dependencies = [
"file_type", "file_type",
"hex", "hex",
"id3", "id3",
"lofty",
"opener", "opener",
"opus_headers", "opus_headers",
"pad", "pad",

View file

@ -5,18 +5,19 @@ edition = "2024"
[dependencies] [dependencies]
tengri = { git = "https://codeberg.org/unspeaker/tengri", rev = "877b344765" } tengri = { git = "https://codeberg.org/unspeaker/tengri", rev = "877b344765" }
enolib = { git = "https://codeberg.org/simonrepp/enolib-rs", tag = "0.5.0" }
clap = { version = "4.5.4", features = [ "cargo" ] } clap = { version = "4.5.4", features = [ "cargo" ] }
file_type = "0.7" enolib = { git = "https://codeberg.org/simonrepp/enolib-rs", tag = "0.5.0" }
hex = "0.4" file_type = "0.7"
id3 = "1.16" hex = "0.4"
opener = "0.7" id3 = "1.16"
opus_headers = "0.1.2" lofty = "0.22.2"
pad = "0.1" opener = "0.7"
opus_headers = "0.1.2"
pad = "0.1"
unicode-width = "0.2" unicode-width = "0.2"
walkdir = "2" walkdir = "2"
xxhash-rust = { version = "0.8.5", features = ["xxh3"] } xxhash-rust = { version = "0.8.5", features = ["xxh3"] }
#base64 = "0.22" #base64 = "0.22"
#moku = "0.2" #moku = "0.2"

View file

@ -4,7 +4,6 @@
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::env::{current_dir, set_current_dir}; use std::env::{current_dir, set_current_dir};
use std::fs::read;
use std::thread::{sleep, spawn, JoinHandle}; use std::thread::{sleep, spawn, JoinHandle};
use std::time::Duration; use std::time::Duration;
@ -12,7 +11,7 @@ use tengri::{input::*, output::*, tui::*};
use crate::crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventState, KeyEventKind}; use crate::crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventState, KeyEventKind};
use clap::{arg, command, value_parser}; use clap::{arg, command, value_parser};
use walkdir::{WalkDir, DirEntry}; use walkdir::WalkDir;
use xxhash_rust::xxh3::xxh3_64; use xxhash_rust::xxh3::xxh3_64;
use file_type::FileType; use file_type::FileType;
@ -55,11 +54,10 @@ fn main () -> Usually<()> {
if let Some(true) = args.get_one::<bool>("check") { if let Some(true) = args.get_one::<bool>("check") {
return Ok(()) return Ok(())
} }
return Tui::new()?.run(&state) Tui::new()?.run(&state)
} else { } else {
panic!("read did not finish") panic!("read did not finish")
} }
Ok(())
} }
fn collect (root: &impl AsRef<Path>, thread_count: usize) -> Usually<Arc<RwLock<Vec<Entry>>>> { fn collect (root: &impl AsRef<Path>, thread_count: usize) -> Usually<Arc<RwLock<Vec<Entry>>>> {
@ -96,7 +94,7 @@ fn collect (root: &impl AsRef<Path>, thread_count: usize) -> Usually<Arc<RwLock<
}); });
} }
let timer = Duration::from_millis(100); let timer = Duration::from_millis(100);
spawn(move || loop { let _ = spawn(move || loop {
if threads.iter().all(|x|x.is_finished()) { if threads.iter().all(|x|x.is_finished()) {
break break
} else { } else {

View file

@ -1,6 +1,11 @@
use crate::*; use crate::*;
use walkdir::DirEntry; use std::fs::File;
use id3::{Tag, TagLike}; use std::io::{BufReader, Read};
use lofty::{
file::TaggedFileExt,
probe::Probe,
tag::Accessor,
};
pub struct Taggart { pub struct Taggart {
pub _root: PathBuf, pub _root: PathBuf,
@ -36,7 +41,7 @@ pub enum EntryInfo {
track: Option<u32>, track: Option<u32>,
title: Option<Arc<str>>, title: Option<Arc<str>>,
date: Option<Arc<str>>, date: Option<Arc<str>>,
year: Option<i32>, year: Option<u32>,
people: Option<Vec<Arc<str>>>, people: Option<Vec<Arc<str>>>,
publisher: Option<Arc<str>>, publisher: Option<Arc<str>>,
key: Option<Arc<str>>, key: Option<Arc<str>>,
@ -104,7 +109,7 @@ impl Entry {
Ok(None) Ok(None)
} }
} }
fn new_dir (root: &impl AsRef<Path>, path: &Path, depth: usize) -> Perhaps<Self> { fn new_dir (_: &impl AsRef<Path>, path: &Path, depth: usize) -> Perhaps<Self> {
Ok(Some(Self { Ok(Some(Self {
depth, depth,
path: path.into(), path: path.into(),
@ -116,11 +121,11 @@ impl Entry {
}, },
})) }))
} }
fn new_file (root: &impl AsRef<Path>, path: &Path, depth: usize) -> Perhaps<Self> { fn new_file (_: &impl AsRef<Path>, path: &Path, depth: usize) -> Perhaps<Self> {
Ok(Some(Self { Ok(Some(Self {
depth, depth,
info: EntryInfo::new(path)?,
path: path.into(), path: path.into(),
info: EntryInfo::new(&read(path)?)?
})) }))
} }
pub fn is_dir (&self) -> bool { pub fn is_dir (&self) -> bool {
@ -165,19 +170,23 @@ impl Entry {
} }
} }
impl EntryInfo { impl EntryInfo {
pub fn new (bytes: &[u8]) -> Usually<Self> { pub fn new (path: &Path) -> Usually<Self> {
// MP3 with ID3v2 let reader = BufReader::new(File::open(path)?);
if bytes.starts_with(&[b'I', b'D', b'3']) { let probe = Probe::new(reader)
#[allow(deprecated)] //.options(ParseOptions::new().parsing_mode(ParsingMode::Strict))
let id3 = Tag::read_from(bytes)?; .guess_file_type()?;
return Ok(Self::Music { if probe.file_type().is_some() {
hash: hex::encode(xxh3_64(&bytes).to_be_bytes()).into(), let file = lofty::read_from_path(path)?;
let tag = file.primary_tag();
let hash = hex::encode(xxh3_64(std::fs::read(path)?.as_slice()).to_be_bytes()).into();
Ok(Self::Music {
hash,
file_type: None, file_type: None,
artist: id3.artist().map(|x|x.into()), artist: tag.map(|t|t.artist().map(|t|t.into())).flatten(),
album: id3.album().map(|x|x.into()), album: tag.map(|t|t.album().map(|t|t.into())).flatten(),
track: id3.track().map(|x|x.into()), track: tag.map(|t|t.track().map(|t|t.into())).flatten(),
title: id3.title().map(|x|x.into()), title: tag.map(|t|t.title().map(|t|t.into())).flatten(),
year: id3.year().map(|x|x.into()), year: tag.map(|t|t.year().map(|t|t.into())).flatten(),
date: None, date: None,
people: None, people: None,
publisher: None, publisher: None,
@ -185,29 +194,18 @@ impl EntryInfo {
bpm: None, bpm: None,
invalid: false, invalid: false,
}) })
} else {
Self::new_fallback(path)
} }
// Ogg (Opus) }
if bytes.starts_with(&[b'O', b'g', b'g', b'S']) { pub fn new_fallback (path: &Path) -> Usually<Self> {
let headers = opus_headers::parse_from_read(bytes)?; let mut reader = BufReader::new(File::open(path)?);
println!("{headers:?}"); let mut bytes = vec![0;16];
return Ok(Self::Music { reader.read(&mut bytes)?;
hash: hex::encode(xxh3_64(&bytes).to_be_bytes()).into(),
file_type: None,
artist: None,
album: None,
track: None,
title: None,
date: None,
year: None,
people: None,
publisher: None,
key: None,
bpm: None,
invalid: false,
})
}
// PNG // PNG
if bytes.starts_with(&[0x89, b'P', b'N', b'G', 0x0D, 0x0A, 0x1A, 0x0A]) { if bytes.starts_with(&[0x89, b'P', b'N', b'G', 0x0D, 0x0A, 0x1A, 0x0A]) {
let mut bytes = vec![];
BufReader::new(File::open(path)?).read(&mut bytes)?;
return Ok(Self::Image { return Ok(Self::Image {
file_type: None, file_type: None,
hash: hex::encode(xxh3_64(&bytes).to_be_bytes()).into(), hash: hex::encode(xxh3_64(&bytes).to_be_bytes()).into(),