Skip to content

Commit

Permalink
TRACK mild metadata loading refactor
Browse files Browse the repository at this point in the history
Shares code between art/tags

I also fixed the weird vorbis/flac mishandling that's been plauging the
app since forever. Turns out when I was trying other solutions and used
the static typed readers as a fix it was actually because the
FormatReader metadata is *different* than the ProbeResult metadata.

So effecitvely the same effect can be achieved file-agnostically by
using the `ProbeResult.format.metadata` as a fallback to
`ProbeResult.metadata`...

Aaaaaaa
  • Loading branch information
Beinsezii committed Aug 6, 2024
1 parent c3e10ed commit fe3798e
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 129 deletions.
6 changes: 1 addition & 5 deletions src/library/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,11 +313,7 @@ impl Library {
}
}
if let Ok(mut art) = self.art.write() {
let visual = track.map(|t| t.read_art()).flatten();
if let Some(visual) = &visual {
debug!("ART SIZE: {}x{}", visual[0].len(), visual.len());
}
*art = visual.map(|v| Arc::new(v));
*art = track.map(|t| t.read_art()).flatten().map(|v| Arc::new(v));
}
if let Ok(mut thumbnail) = self.thumbnail.write() {
*thumbnail = None;
Expand Down
185 changes: 66 additions & 119 deletions src/library/track/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::path::PathBuf;
use std::time::Instant;

use symphonia::core::io::MediaSourceStream;
use symphonia::core::meta::MetadataLog;
use symphonia::core::meta::MetadataRevision;
use symphonia::core::probe::Hint;

use lexical_sort::natural_lexical_cmp;
Expand Down Expand Up @@ -386,14 +386,72 @@ impl Track {
})
}

/// Reads the current metadata revision
fn read_metadata(&self) -> Option<MetadataRevision> {
// {{{
let Ok(file) = File::open(&self.path) else { return None };
let Ok(mut probed) = symphonia::default::get_probe().format(
Hint::new().with_extension(
self.path()
.extension()
.map(|s| s.to_str())
.flatten()
.expect("HINT EXTENSION FAIL - should be unreachable"),
),
MediaSourceStream::new(Box::new(file), Default::default()),
&Default::default(),
&Default::default(),
) else {
return None;
};
probed
.metadata
.get()
.map(|m| m.current().cloned())
.flatten()
// Vorbis comments aren't found until the FormatReader is initialized
.or_else(|| probed.format.metadata().current().cloned())
// }}}
}

/// Reads metadata into the struct. This doesn't happen on ::new() for performance reasons.
pub fn load_meta(&mut self) {
// {{{
match self.path.extension().map(|e| e.to_str()).flatten() {
Some("mp3") | Some("wav") => self.probe_meta(true),
Some("flac") => self.load_meta_vorbis::<symphonia::default::formats::FlacReader>(),
Some("ogg") => self.load_meta_vorbis::<symphonia::default::formats::OggReader>(),
_ => (),
let Some(meta) = self.read_metadata() else {
return;
};

for tag in meta.tags() {
let mut val = tag.value.to_string();
let mut key = tag.key.to_ascii_lowercase();

// convert id3v1 genres
if key == "tcon" {
val = val
.trim()
.trim_start_matches('(')
.trim_end_matches(')')
.parse::<usize>()
.ok()
.map(|i| ID3_GENRES.get(i).map(|s| s.to_string()))
.flatten()
.unwrap_or(val)
}

// convert id3v2 keys to human readables
for (fromkey, tokey) in ID3_TAGS {
if fromkey == &key {
self.tags.insert(tokey.to_string(), val.clone());
break;
}
}

// removing id3's "txxx:" leader for custom values
if key.starts_with("txxx:") {
key.replace_range(0..5, "");
}
// Additionally push all tags as they are
self.tags.insert(key, val);
}

if let Some(text) = self.tags.get("replaygain_track_gain") {
Expand All @@ -418,9 +476,8 @@ impl Track {

pub fn read_art(&self) -> Option<Box<[Box<[[u8; 4]]>]>> {
// {{{
let Some(mut log) = self.read_metadata() else { return None };
let meta = log.metadata();
let Some(visual) = meta.current().map(|m| m.visuals().get(0)).flatten() else {
let meta = self.read_metadata();
let Some(visual) = meta.as_ref().map(|m| m.visuals().get(0)).flatten() else {
return None;
};
let buff: &[u8] = &visual.data;
Expand All @@ -442,116 +499,6 @@ impl Track {
}
} //}}}

fn read_metadata(&self) -> Option<MetadataLog> {
let Ok(Ok(probed)) = File::open(&self.path).map(|file| {
symphonia::default::get_probe().format(
Hint::new().with_extension(
self.path()
.extension()
.map(|s| s.to_str())
.flatten()
.expect("HINT EXTENSION FAIL - should be unreachable"),
),
MediaSourceStream::new(Box::new(file), Default::default()),
&Default::default(),
&Default::default(),
)
}) else {
return None;
};
probed.metadata.into_inner()
}

// # probe_meta # {{{
/// id3 mode handles standard frames, like 'TCON' & 'TXXX'
/// non-id3 mode just dumps everything straight into the list as-is
///
/// Or at least that's the plan, but it doesn't seem to work for vorbis comments...
/// No idea why not. Nothing in here on symphonia's side is id3/mp3 specific.
fn probe_meta(&mut self, id3: bool) {
let Ok(Ok(mut probed)) = File::open(&self.path).map(|file| {
symphonia::default::get_probe().format(
Hint::new().with_extension(
self.path()
.extension()
.map(|s| s.to_str())
.flatten()
.expect("HINT EXTENSION FAIL - should be unreachable"),
),
MediaSourceStream::new(Box::new(file), Default::default()),
&Default::default(),
&Default::default(),
)
}) else {
return;
};
let Some(metadata) = probed.metadata.get() else {
return;
};
if let Some(meta) = metadata.current() {
for tag in meta.tags() {
let mut val = match &tag.value {
symphonia::core::meta::Value::String(s) => s.clone(),
_ => continue,
};
let mut key = tag.key.to_ascii_lowercase();

if id3 {
// convert id3v1 genres
if key == "tcon" {
val = val
.trim()
.trim_start_matches('(')
.trim_end_matches(')')
.parse::<usize>()
.ok()
.map(|i| ID3_GENRES.get(i).map(|s| s.to_string()))
.flatten()
.unwrap_or(val)
}
// convert id3v2 keys to human readables
for (fromkey, tokey) in ID3_TAGS {
if fromkey == &key {
self.tags.insert(tokey.to_string(), val.clone());
break;
}
}
}

// also just push everything for good measure,
// removing id3's "txxx" cause its dumb
if id3 && key.starts_with("txxx:") {
key.replace_range(0..5, "");
self.tags.insert(key, val);
} else {
self.tags.insert(key, val);
}
}
}
}
// # probe_meta # }}}

// # vorbis comment # {{{
/// Coincidentally, this also doesnt work for id3 when you plug Mp3Reader into it.
/// The source code literally just Default::default()'s the metadata,
/// What the fuck, Symphonia?
fn load_meta_vorbis<R: symphonia::core::formats::FormatReader>(&mut self) {
let Ok(Ok(mut reader)): Result<Result<R, _>, _> = File::open(&self.path).map(|file| {
symphonia::core::formats::FormatReader::try_new(
symphonia::core::io::MediaSourceStream::new(Box::new(file), symphonia::core::io::MediaSourceStreamOptions::default()),
&symphonia::core::formats::FormatOptions::default(),
)
}) else {
return;
};
if let Some(meta) = reader.metadata().current() {
for tag in meta.tags() {
self.tags.insert(tag.key.to_ascii_lowercase(), tag.value.to_string());
}
}
}
// # vorbis comment # }}}

// ## GET / SET ## {{{

pub fn tags(&self) -> &Tags {
Expand Down
5 changes: 0 additions & 5 deletions src/tui/widgets/art.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use super::ContainedWidget;

use crate::library::Library;
use crate::logging::*;

use std::sync::Weak;

Expand All @@ -21,8 +20,6 @@ impl ContainedWidget for Art {
assert_eq!(self.area.width, self.area.height * 2);
let (w, h) = (self.area.width as usize, self.area.height as usize * 2);
if let Some(thumbnail) = library.thumbnail(w, h) {
debug!("THUMB SIZE {}x{}", thumbnail[0].len(), thumbnail.len());

let lines: Vec<Line> = thumbnail
.chunks(2)
.map(|rows| {
Expand All @@ -46,8 +43,6 @@ impl ContainedWidget for Art {
})
.collect();

debug!("ART SPANS {}x{}", lines[0].spans.len(), lines.len() * 2);

frame.render_widget(Paragraph::new(lines), self.area)
} else {
frame.render_widget(Block::new().style(stylesheet.base).borders(Borders::ALL), self.area)
Expand Down

0 comments on commit fe3798e

Please sign in to comment.