mirror of
https://codeberg.org/unspeaker/perch.git
synced 2025-12-06 09:36:42 +01:00
load metadata using lofty (shout out @Frieder_Hannenheim - you are #1)
This commit is contained in:
parent
28f9220e6c
commit
bc067e2739
4 changed files with 92 additions and 53 deletions
42
Cargo.lock
generated
42
Cargo.lock
generated
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,13 @@ 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" ] }
|
||||||
|
enolib = { git = "https://codeberg.org/simonrepp/enolib-rs", tag = "0.5.0" }
|
||||||
file_type = "0.7"
|
file_type = "0.7"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
id3 = "1.16"
|
id3 = "1.16"
|
||||||
|
lofty = "0.22.2"
|
||||||
opener = "0.7"
|
opener = "0.7"
|
||||||
opus_headers = "0.1.2"
|
opus_headers = "0.1.2"
|
||||||
pad = "0.1"
|
pad = "0.1"
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
72
src/model.rs
72
src/model.rs
|
|
@ -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']) {
|
|
||||||
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
|
// 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(),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue