Files
SoulStream/crates/metadata/examples/hero_source_probe.rs
T
KeelrLeek a667b4b28e 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.
2026-07-22 17:57:00 -05:00

163 lines
5.3 KiB
Rust

//! Live diagnostic for the production artist-art resolver.
//!
//! Usage:
//! cargo run -p metadata --example hero_source_probe -- [output-dir] [artist ...]
//! cargo run -p metadata --example hero_source_probe -- [output-dir] --extended
//! cargo run -p metadata --example hero_source_probe -- [output-dir] --bing-only [artist ...]
//!
//! With no artists, the five core sourcing fixtures are used. `--extended`
//! adds ten lesser-known stress fixtures. Passing `-` as the output directory
//! prints URLs without saving the image bytes.
use metadata::{CatalogArtist, MetadataClient};
use std::path::PathBuf;
const DEFAULT_ARTISTS: [&str; 5] = [
"Camping in Alaska",
"Partiboi69",
"Hermeth",
"Billy Cobb",
"Fishmans",
];
const EXTENDED_ARTISTS: [&str; 10] = [
"Wednesday",
"Equipment",
"Hana Vu",
"Jane Remover",
"DJ Sabrina The Teenage DJ",
"Nikki Nair",
"MSPAINT",
"Horse Jumper of Love",
"They Are Gutting a Body of Water",
"Sword II",
];
#[tokio::main]
async fn main() {
let mut args = std::env::args().skip(1);
let output = args.next().filter(|value| value != "-").map(PathBuf::from);
let mut requested: Vec<String> = args.collect();
let bing_only = requested.iter().any(|value| value == "--bing-only");
requested.retain(|value| value != "--bing-only");
let artists: Vec<String> = if requested.is_empty() {
DEFAULT_ARTISTS
.iter()
.map(|name| name.to_string())
.collect()
} else if requested.len() == 1 && requested[0] == "--extended" {
DEFAULT_ARTISTS
.iter()
.chain(EXTENDED_ARTISTS.iter())
.map(|name| name.to_string())
.collect()
} else {
requested
};
if let Some(output) = output.as_ref() {
if let Err(error) = std::fs::create_dir_all(output) {
eprintln!("could not create {}: {error}", output.display());
std::process::exit(2);
}
}
let client = MetadataClient::new().with_hero_diagnostics();
for requested in artists {
// Bing-only diagnostics do not need a Deezer id. Keeping that path
// independent means catalog rate limits cannot turn a scraper probe
// into an immediate, misleading NOT_FOUND for every requested name.
let (artist_id, artist_name, fans) = if bing_only {
(None, requested, 0)
} else {
let search = client.catalog_search(&requested).await;
let Some(artist) = best_exact_artist(&search.artists, &requested) else {
println!("ARTIST\t{requested}\tNOT_FOUND");
continue;
};
(
Some(artist.id),
artist.name.clone(),
artist.fans.unwrap_or(0),
)
};
println!(
"ARTIST\t{}\t{}\t{}",
artist_id
.map(|id| id.to_string())
.unwrap_or_else(|| "BING_ONLY".to_string()),
artist_name,
fans,
);
let banners = if let Some(artist_id) = artist_id {
let Some(art) = client.catalog_artist_art(artist_id).await else {
println!("ERROR\t{}\tartist art unavailable", artist_name);
continue;
};
art.banners
} else {
client.debug_bing_hero_banners(&artist_name).await
};
if banners.is_empty() {
println!("BANNER\t{}\tNONE", artist_name);
continue;
}
for (index, url) in banners.iter().enumerate() {
println!("BANNER\t{}\t{}\t{}", artist_name, index + 1, url);
let Some(output) = output.as_ref() else {
continue;
};
let Some(bytes) = client.fetch_image_bytes(url).await else {
println!(
"SAVE_ERROR\t{}\t{}\tdownload failed",
artist_name,
index + 1
);
continue;
};
let extension = match image::guess_format(&bytes) {
Ok(image::ImageFormat::Png) => "png",
Ok(image::ImageFormat::WebP) => "webp",
_ => "jpg",
};
let path = output.join(format!(
"{}-{}.{}",
file_stem(&artist_name),
index + 1,
extension
));
match std::fs::write(&path, bytes) {
Ok(()) => println!("SAVED\t{}", path.display()),
Err(error) => println!("SAVE_ERROR\t{}\t{}", path.display(), error),
}
}
}
}
fn best_exact_artist<'a>(
artists: &'a [CatalogArtist],
requested: &str,
) -> Option<&'a CatalogArtist> {
artists
.iter()
.filter(|artist| artist.name.trim().eq_ignore_ascii_case(requested.trim()))
.max_by_key(|artist| artist.fans.unwrap_or(0))
}
fn file_stem(name: &str) -> String {
name.to_lowercase()
.chars()
.map(|character| {
if character.is_ascii_alphanumeric() {
character
} else {
'-'
}
})
.collect::<String>()
.split('-')
.filter(|part| !part.is_empty())
.collect::<Vec<_>>()
.join("-")
}