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",
]
[[package]]
name = "data-encoding"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010"
[[package]]
name = "dbus"
version = "0.9.7"
@ -571,6 +577,32 @@ dependencies = [
"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]]
name = "log"
version = "0.4.26"
@ -640,6 +672,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "ogg_pager"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e034c10fb5c1c012c1b327b85df89fb0ef98ae66ec28af30f0d1eed804a40c19"
dependencies = [
"byteorder",
]
[[package]]
name = "once_cell"
version = "1.21.1"
@ -1060,6 +1101,7 @@ dependencies = [
"file_type",
"hex",
"id3",
"lofty",
"opener",
"opus_headers",
"pad",

View file

@ -5,12 +5,13 @@ edition = "2024"
[dependencies]
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" ] }
enolib = { git = "https://codeberg.org/simonrepp/enolib-rs", tag = "0.5.0" }
file_type = "0.7"
hex = "0.4"
id3 = "1.16"
lofty = "0.22.2"
opener = "0.7"
opus_headers = "0.1.2"
pad = "0.1"

View file

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

View file

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