hero search: stop reading " x " as a co-bill, and reject explicit results
Two follow-ups to reading both Bing buckets. The multi-artist guard treated " x " as a co-bill between two acts. In practice it far more often joins a venue and a festival: "Nikki Nair | Boiler Room x Bass Coast 2022". Because every YouTube thumbnail is named maxresdefault.jpg, image_names_artist is structurally false for that whole class, so the rule was rejecting Boiler Room and festival set photography -- the richest source of hero-shaped images that exists for DJs. Genuine collaboration graphics are still caught by named_collaboration, which requires the image itself to name the other subject, so nothing is lost. Nikki Nair's multi= counter drops from 1 to 0 with his identity accepts intact. And explicit results are now rejected before the identity gate. Reading the unfiltered bucket is necessary for coverage but it puts pornography in the candidate pool: one artist's unfiltered wave returned 24 hardcore cards. An audit found zero of ~67 such cards could clear bing_identity_confidence, because porn pages do not name obscure musicians -- but that is an empirical accident rather than a guarantee, and reading only the strict bucket used to provide an independent second layer. This restores one. The vocabulary is deliberately narrow so "adult contemporary", XXX as a roman numeral, and band names like Nude Beach or Pornography stay eligible. Canary unchanged: Fishmans still accepted=12 with the same three banners.
This commit is contained in:
1 parent
8b2aadce2b
commit
9536ff23a9
1 file changed
+70
-1
@@ -1985,6 +1985,13 @@ impl MetadataClient {
|
||||
}
|
||||
let title = result.title;
|
||||
let purl = result.page_url;
|
||||
// Before identity, not after: reading the unfiltered bucket
|
||||
// puts explicit results in the pool, and the identity gate
|
||||
// is a relevance test with no notion of decency.
|
||||
if is_adult_result(&title, &purl, &murl) {
|
||||
rejected_unsuitable += 1;
|
||||
continue;
|
||||
}
|
||||
let Some(identity) = bing_identity_confidence(&title, &purl, &murl, artist)
|
||||
else {
|
||||
rejected_identity += 1;
|
||||
@@ -8007,7 +8014,16 @@ fn multi_artist_result_mismatches_image(title: &str, image_url: &str, artist: &s
|
||||
|| listing_text.contains(" featuring ")
|
||||
|| listing_text.contains(" vs. ")
|
||||
|| listing_text.contains(" versus ")
|
||||
|| listing_text.contains(" x ")
|
||||
// `" x "` is deliberately NOT here. It reads as a co-bill between two
|
||||
// acts, but in practice it far more often joins a venue and a festival
|
||||
// or a brand: "Nikki Nair | Boiler Room x Bass Coast 2022". Because
|
||||
// every YouTube thumbnail is named `maxresdefault.jpg`,
|
||||
// `image_names_artist` is structurally false for the whole class, so
|
||||
// this one separator was rejecting every Boiler Room and festival set
|
||||
// photo -- the richest source of hero-shaped photography that exists
|
||||
// for DJs, and the only 1280x720 candidate Nikki Nair had. A genuine
|
||||
// collaboration graphic is still caught below by `named_collaboration`,
|
||||
// which requires the image itself to name the other subject.
|
||||
|| two_act_conjunction
|
||||
|| ([" y ", " con ", " junto a ", " et ", " und "]
|
||||
.iter()
|
||||
@@ -8053,6 +8069,59 @@ fn multi_artist_result_mismatches_image(title: &str, image_url: &str, artist: &s
|
||||
separator_count >= 2 && !image_names_artist
|
||||
}
|
||||
|
||||
/// Explicit-content rejection, applied BEFORE the identity gate.
|
||||
///
|
||||
/// The hero search reads Bing's unfiltered bucket as well as the strict one,
|
||||
/// because either can be serving another query's results. That is necessary for
|
||||
/// coverage, but it means pornography now enters the candidate pool: a direct
|
||||
/// read of one artist's unfiltered wave returned 24 hardcore cards. An audit
|
||||
/// found zero of ~67 such cards could clear `bing_identity_confidence` -- porn
|
||||
/// pages do not name obscure musicians -- but that is an empirical accident,
|
||||
/// not a guarantee, and it is one gate deep. This is the independent second
|
||||
/// layer that reading only the strict bucket used to provide.
|
||||
fn is_adult_result(title: &str, page_url: &str, image_url: &str) -> bool {
|
||||
let urls = format!("{page_url} {image_url}").to_lowercase();
|
||||
const ADULT_HOSTS: [&str; 16] = [
|
||||
"pornhub.",
|
||||
"xhamster.",
|
||||
"xhcdn.com",
|
||||
"xnxx.",
|
||||
"xvideos.",
|
||||
"eporner.",
|
||||
"redtube.",
|
||||
"youporn.",
|
||||
"spankbang.",
|
||||
"pictoa.",
|
||||
"thefappening",
|
||||
"onlyfans.",
|
||||
"fapello.",
|
||||
"hentai-foundry.",
|
||||
"rule34.",
|
||||
"e-hentai.",
|
||||
];
|
||||
if ADULT_HOSTS.iter().any(|host| urls.contains(host)) {
|
||||
return true;
|
||||
}
|
||||
// Title vocabulary, kept to terms that do not appear in music writing.
|
||||
// "Adult contemporary", "XXX" as a roman numeral and band names such as
|
||||
// Nude Beach or Pornography (The Cure) must NOT trip this, so match only
|
||||
// unambiguous phrases.
|
||||
let haystack = format!(" {} ", title.to_lowercase());
|
||||
const ADULT_PHRASES: [&str; 10] = [
|
||||
" porn ",
|
||||
" porno ",
|
||||
"hardcore porn",
|
||||
" nsfw ",
|
||||
" blowjob",
|
||||
" creampie",
|
||||
" anal sex",
|
||||
" sex tape",
|
||||
"nude leaks",
|
||||
" milf ",
|
||||
];
|
||||
ADULT_PHRASES.iter().any(|term| haystack.contains(term))
|
||||
}
|
||||
|
||||
/// True for Bing results that are unambiguously merchandise, a physical music
|
||||
/// format, or a shop listing rather than a photograph of the performer. Keep
|
||||
/// this conservative: page/image URL structure is the strongest signal, while
|
||||
|
||||
Reference in new issue
Block a user