Baseline: full tree snapshot after git repository loss

The previous .git directory was found gutted (only info/ remained), so
history is unrecoverable locally. This commit restores version control
with the tree exactly as it stands: the working app, the preview-tracks
launcher on the artist hero, the scrollable-track-previews design bundle
(docs + approved mockups), and the Phase 0 validation artifacts.
This commit is contained in:
KeelrLeek committed 2026-07-22 17:57:00 -05:00
commit a667b4b28e
161 files changed
+53170

No files matched your search

+10
View File
@@ -0,0 +1,10 @@
[package]
name = "tagger"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
[dependencies]
lofty = "0.22"
thiserror.workspace = true
log.workspace = true
+24
View File
@@ -0,0 +1,24 @@
//! Read tags from every audio file in a folder and print the outcome.
//! cargo run -p tagger --example read -- "C:\folder"
fn main() {
let dir = std::env::args().nth(1).expect("usage: read <dir>");
for entry in std::fs::read_dir(&dir).expect("read_dir").flatten() {
let path = entry.path();
if !path.is_file() {
continue;
}
match tagger::read_summary(&path) {
Ok(s) => println!(
"OK {} | artist={:?} album={:?} title={:?} track={:?} dur={:?}",
path.file_name().unwrap().to_string_lossy(),
s.artist,
s.album,
s.title,
s.track_no,
s.duration_secs
),
Err(e) => println!("ERR {} | {e}", path.file_name().unwrap().to_string_lossy()),
}
}
}
+261
View File
@@ -0,0 +1,261 @@
//! Tag reading/writing and album-art embedding via lofty.
use std::path::Path;
use lofty::config::WriteOptions;
use lofty::file::{AudioFile, TaggedFileExt};
use lofty::picture::{MimeType, Picture, PictureType};
use lofty::probe::Probe;
use lofty::tag::{Accessor, Tag};
#[derive(Debug, thiserror::Error)]
pub enum TagError {
#[error("io: {0}")]
Io(#[from] std::io::Error),
#[error("lofty: {0}")]
Lofty(#[from] lofty::error::LoftyError),
}
pub type Result<T> = std::result::Result<T, TagError>;
/// What we know about a file from its embedded tags + audio properties.
#[derive(Debug, Clone, Default)]
pub struct TagSummary {
pub artist: Option<String>,
pub album_artist: Option<String>,
pub album: Option<String>,
pub title: Option<String>,
pub track_no: Option<u32>,
pub year: Option<u32>,
pub duration_secs: Option<u32>,
pub bitrate_kbps: Option<u32>,
pub sample_rate: Option<u32>,
pub bit_depth: Option<u32>,
pub has_art: bool,
}
pub fn read_summary(path: &Path) -> Result<TagSummary> {
let tagged = Probe::open(path)?.read()?;
let props = tagged.properties();
let mut summary = TagSummary {
duration_secs: Some(props.duration().as_secs() as u32),
bitrate_kbps: props.audio_bitrate(),
sample_rate: props.sample_rate(),
bit_depth: props.bit_depth().map(|d| d as u32),
..Default::default()
};
if let Some(tag) = tagged.primary_tag().or_else(|| tagged.first_tag()) {
summary.artist = non_empty(tag.artist().as_deref());
summary.album = non_empty(tag.album().as_deref());
summary.title = non_empty(tag.title().as_deref());
summary.track_no = tag.track();
summary.year = tag.year();
summary.album_artist = non_empty(
tag.get_string(&lofty::tag::ItemKey::AlbumArtist)
.map(|s| s.to_string())
.as_deref(),
);
summary.has_art = !tag.pictures().is_empty();
}
Ok(summary)
}
/// Canonical values to write. `None` fields are left untouched.
#[derive(Debug, Clone, Default)]
pub struct TagWrite {
pub artist: Option<String>,
pub album_artist: Option<String>,
pub album: Option<String>,
pub title: Option<String>,
pub track_no: Option<u32>,
pub year: Option<u32>,
}
/// Cover bytes come from several providers; PNG originals exist (Cover Art
/// Archive scans), so the embedded mime is sniffed rather than assumed.
fn sniff_mime(bytes: &[u8]) -> MimeType {
if bytes.starts_with(b"\x89PNG") {
MimeType::Png
} else {
MimeType::Jpeg
}
}
/// Write tags and (optionally) embed front-cover art. With `replace_art`
/// false the cover is only added when the file has none; true drops whatever
/// pictures the file arrived with first, so the canonical cover always wins.
pub fn write_tags(
path: &Path,
write: &TagWrite,
art_jpeg: Option<&[u8]>,
replace_art: bool,
) -> Result<()> {
let mut tagged = Probe::open(path)?.read()?;
if tagged.primary_tag().is_none() && tagged.first_tag().is_none() {
let tag_type = tagged.primary_tag_type();
tagged.insert_tag(Tag::new(tag_type));
}
let tag = tagged.primary_tag_mut().expect("tag inserted above");
if let Some(v) = &write.artist {
tag.set_artist(v.clone());
}
if let Some(v) = &write.album {
tag.set_album(v.clone());
}
if let Some(v) = &write.title {
tag.set_title(v.clone());
}
if let Some(v) = write.track_no {
tag.set_track(v);
}
if let Some(v) = write.year {
tag.set_year(v);
}
if let Some(v) = &write.album_artist {
tag.insert_text(lofty::tag::ItemKey::AlbumArtist, v.clone());
}
if let Some(bytes) = art_jpeg {
if replace_art {
while !tag.pictures().is_empty() {
tag.remove_picture(0);
}
}
if tag.pictures().is_empty() {
let picture = Picture::new_unchecked(
PictureType::CoverFront,
Some(sniff_mime(bytes)),
None,
bytes.to_vec(),
);
tag.push_picture(picture);
}
}
tagged.save_to_path(path, WriteOptions::default())?;
Ok(())
}
/// Replace any existing embedded pictures with the given front cover.
pub fn replace_art(path: &Path, art_jpeg: &[u8]) -> Result<()> {
let mut tagged = Probe::open(path)?.read()?;
if tagged.primary_tag().is_none() && tagged.first_tag().is_none() {
let tag_type = tagged.primary_tag_type();
tagged.insert_tag(Tag::new(tag_type));
}
let tag = tagged.primary_tag_mut().expect("tag inserted above");
while !tag.pictures().is_empty() {
tag.remove_picture(0);
}
tag.push_picture(Picture::new_unchecked(
PictureType::CoverFront,
Some(sniff_mime(art_jpeg)),
None,
art_jpeg.to_vec(),
));
tagged.save_to_path(path, WriteOptions::default())?;
Ok(())
}
fn non_empty(s: Option<&str>) -> Option<String> {
s.map(str::trim).filter(|s| !s.is_empty()).map(String::from)
}
/// Best-effort metadata parse from a Soulseek path when tags are missing.
/// Handles `Artist - Album (Year)` folders and `NN - Title` / `NN Title`
/// filenames.
pub fn parse_from_path(remote_or_local_path: &str) -> TagWrite {
let parts: Vec<&str> = remote_or_local_path.split(['\\', '/']).collect();
let file = parts.last().copied().unwrap_or_default();
let folder = parts
.len()
.checked_sub(2)
.and_then(|i| parts.get(i))
.copied()
.unwrap_or("");
let stem = file.rsplit_once('.').map(|(s, _)| s).unwrap_or(file);
let mut write = TagWrite::default();
// "01 - Title", "01. Title", "01 Title"
let trimmed = stem.trim();
let (track, title) = match trimmed.split_once(|c: char| !c.is_ascii_digit()) {
Some((digits, rest)) if !digits.is_empty() && digits.len() <= 3 => {
let title = rest.trim_start_matches(['-', '.', ' ', '_']).trim();
(
digits.parse::<u32>().ok(),
if title.is_empty() { trimmed } else { title },
)
}
_ => (None, trimmed),
};
write.track_no = track;
write.title = Some(title.to_string());
// Folder: "Artist - Album (1997)" | "Album (1997)" | "1997 - Album"
let mut folder = folder.trim().to_string();
if let Some(idx) = folder.rfind('(') {
let inner: String = folder[idx + 1..]
.chars()
.take_while(|c| c.is_ascii_digit())
.collect();
if inner.len() == 4 {
write.year = inner.parse().ok();
folder = folder[..idx].trim().to_string();
}
}
if write.year.is_none() {
let lead: String = folder.chars().take_while(|c| c.is_ascii_digit()).collect();
if lead.len() == 4 {
write.year = lead.parse().ok();
folder = folder[lead.len()..]
.trim_start_matches(['-', '.', ' '])
.to_string();
}
}
if let Some((artist, album)) = folder.split_once(" - ") {
write.artist = Some(artist.trim().to_string());
write.album = Some(album.trim().to_string());
} else if !folder.is_empty() {
write.album = Some(folder);
}
write
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_full_convention() {
let w = parse_from_path(
r"Music\Radiohead - OK Computer (1997)\03 - Subterranean Homesick Alien.flac",
);
assert_eq!(w.artist.as_deref(), Some("Radiohead"));
assert_eq!(w.album.as_deref(), Some("OK Computer"));
assert_eq!(w.year, Some(1997));
assert_eq!(w.track_no, Some(3));
assert_eq!(w.title.as_deref(), Some("Subterranean Homesick Alien"));
}
#[test]
fn parse_year_leading_folder() {
let w = parse_from_path(r"share\1997 - OK Computer\01 Airbag.flac");
assert_eq!(w.album.as_deref(), Some("OK Computer"));
assert_eq!(w.year, Some(1997));
assert_eq!(w.track_no, Some(1));
assert_eq!(w.title.as_deref(), Some("Airbag"));
}
#[test]
fn parse_no_track_number() {
let w = parse_from_path(r"stuff\Nevermind (1991)\Lithium.flac");
assert_eq!(w.title.as_deref(), Some("Lithium"));
assert_eq!(w.track_no, None);
assert_eq!(w.album.as_deref(), Some("Nevermind"));
assert_eq!(w.year, Some(1991));
}
}