#!/usr/bin/env python3 """ Famous Artist Detection Script for Voice Designer Searches through metas_v6 JSONL files to find records from famous artists for voice analysis testing. """ import json import argparse from pathlib import Path from typing import List, Dict, Set import logging from tqdm import tqdm from collections import defaultdict # Famous artists to search for - organized by genre/era FAMOUS_ARTISTS = { "taylor_swift": ["taylor swift", "taylor", "swift", "tswift", "t-swift"], "kendrick_lamar": ["kendrick lamar", "kendrick", "k dot", "kdot", "kung fu kenny"], "drake": ["drake", "drizzy", "champagne papi", "aubrey graham"], "beyonce": ["beyonce", "beyoncé", "queen b", "sasha fierce", "destiny's child"], "kanye_west": ["kanye west", "kanye", "ye", "yeezy", "pablo"], "adele": ["adele", "adele laurie blue adkins"], "ed_sheeran": ["ed sheeran", "ed", "sheeran"], "billie_eilish": ["billie eilish", "billie", "eilish"], "the_weeknd": ["the weeknd", "weeknd", "abel tesfaye"], "ariana_grande": ["ariana grande", "ariana", "grande", "ari"], "post_malone": ["post malone", "post", "malone", "posty"], "bruno_mars": ["bruno mars", "bruno", "mars", "peter gene hernandez"], "rihanna": ["rihanna", "riri", "robyn fenty"], "justin_bieber": ["justin bieber", "justin", "bieber", "jb"], "lady_gaga": ["lady gaga", "gaga", "stefani germanotta"], "eminem": ["eminem", "slim shady", "marshall mathers", "em"], "jay_z": ["jay-z", "jay z", "hov", "shawn carter"], "lana_del_rey": ["lana del rey", "lana", "del rey", "elizabeth grant"], "dua_lipa": ["dua lipa", "dua", "lipa"], "olivia_rodrigo": ["olivia rodrigo", "olivia", "rodrigo"], "the_beatles": [ "beatles", "the beatles", "john lennon", "paul mccartney", "george harrison", "ringo starr", ], "queen": ["queen", "freddie mercury", "brian may"], "michael_jackson": ["michael jackson", "mj", "king of pop"], "madonna": ["madonna", "madonna ciccone", "material girl"], "prince": ["prince", "the artist formerly known as prince", "purple one"], "david_bowie": ["david bowie", "bowie", "ziggy stardust"], "elvis_presley": ["elvis presley", "elvis", "the king"], "bob_dylan": ["bob dylan", "dylan", "robert zimmerman"], "led_zeppelin": ["led zeppelin", "zeppelin", "robert plant", "jimmy page"], "pink_floyd": ["pink floyd", "floyd", "roger waters", "david gilmour"], "radiohead": ["radiohead", "thom yorke", "jonny greenwood"], "nirvana": ["nirvana", "kurt cobain", "dave grohl"], "metallica": ["metallica", "james hetfield", "lars ulrich"], "ac_dc": ["ac/dc", "acdc", "ac dc", "angus young"], "guns_n_roses": ["guns n' roses", "guns and roses", "axl rose", "slash"], "coldplay": ["coldplay", "chris martin"], "u2": ["u2", "bono", "the edge"], "green_day": ["green day", "billie joe armstrong"], "red_hot_chili_peppers": ["red hot chili peppers", "rhcp", "anthony kiedis"], "foo_fighters": ["foo fighters", "dave grohl"], "linkin_park": ["linkin park", "chester bennington", "mike shinoda"], "maroon_5": ["maroon 5", "maroon five", "adam levine"], "imagine_dragons": ["imagine dragons", "dan reynolds"], "twenty_one_pilots": ["twenty one pilots", "21 pilots", "tyler joseph"], "panic_at_the_disco": ["panic! at the disco", "panic at the disco", "brendon urie"], "fall_out_boy": ["fall out boy", "patrick stump", "pete wentz"], "my_chemical_romance": ["my chemical romance", "mcr", "gerard way"], "paramore": ["paramore", "hayley williams"], "black_eyed_peas": ["black eyed peas", "will.i.am", "fergie"], "outkast": ["outkast", "andre 3000", "big boi"], "tupac": ["tupac", "2pac", "makaveli", "tupac shakur"], "biggie": ["biggie", "notorious b.i.g.", "biggie smalls", "christopher wallace"], "nas": ["nas", "nasir jones"], "snoop_dogg": ["snoop dogg", "snoop", "calvin cordozar broadus"], "dr_dre": ["dr. dre", "dr dre", "andre young"], "nicki_minaj": ["nicki minaj", "nicki", "minaj", "onika maraj"], "cardi_b": ["cardi b", "cardi", "belcalis almanzar"], "megan_thee_stallion": ["megan thee stallion", "megan", "thee stallion"], "doja_cat": ["doja cat", "doja", "amala ratna zandile dlamini"], "lizzo": ["lizzo", "melissa jefferson"], "sza": ["sza", "solána imani rowe"], "frank_ocean": ["frank ocean", "frank", "ocean", "christopher breaux"], "the_weeknd": ["the weeknd", "abel tesfaye"], "childish_gambino": ["childish gambino", "donald glover"], "tyler_the_creator": ["tyler the creator", "tyler", "odd future"], "asap_rocky": ["a$ap rocky", "asap rocky", "rakim mayers"], "travis_scott": ["travis scott", "travis", "la flame", "jacques webster"], "lil_wayne": ["lil wayne", "lil' wayne", "dwayne carter", "weezy"], "future": ["future", "nayvadius wilburn"], "j_cole": ["j. cole", "j cole", "jermaine cole"], "chance_the_rapper": ["chance the rapper", "chance", "chancelor bennett"], } def setup_logging(verbose: bool = False): """Setup logging configuration""" level = logging.DEBUG if verbose else logging.INFO logging.basicConfig(level=level, format="%(asctime)s - %(levelname)s - %(message)s") def contains_artist_keywords(text: str, artist_keywords: List[str]) -> bool: """Check if text contains any of the artist keywords (case insensitive)""" if not text: return False text_lower = text.lower() return any(keyword.lower() in text_lower for keyword in artist_keywords) def search_record_for_artists(record: Dict) -> Dict[str, List[str]]: """ Search a record for famous artist-related keywords in various fields Returns: Dictionary mapping artist names to list of matching fields/contexts """ found_artists = defaultdict(list) # Fields to search in order of priority search_fields = [ ("text", record.get("text", "")), ("tags", " ".join(record.get("tags", []))), ("artist_ids", str(record.get("artist_ids", []))), ("playlist_ids", str(record.get("playlist_ids", []))), ("s3_filepath", record.get("s3_filepath", "")), ("local_filepath", record.get("local_filepath", "")), ] # Also check for any additional metadata that might contain artist info if "metadata" in record: metadata_str = json.dumps(record["metadata"]) search_fields.append(("metadata", metadata_str)) for artist_name, keywords in FAMOUS_ARTISTS.items(): for field_name, field_content in search_fields: if contains_artist_keywords(field_content, keywords): # Find which specific keywords matched matched_keywords = [kw for kw in keywords if kw.lower() in field_content.lower()] found_artists[artist_name].append( { "field": field_name, "matched_keywords": matched_keywords, "context": field_content[:300] + ("..." if len(field_content) > 300 else ""), } ) return dict(found_artists) def has_vocal_stems(record: Dict) -> bool: """Check if record has vocal-related stems""" stems = record.get("stems", {}) if not stems: return False vocal_keywords = ["vocal", "vox"] return any( any(keyword in stem_name.lower() for keyword in vocal_keywords) for stem_name in stems.keys() ) def process_jsonl_file( input_file: str, output_file: str, target_artists: List[str] = None, vocal_stems_only: bool = True, max_per_artist: int = 5, limit: int = None, ) -> Dict[str, int]: """ Process JSONL file to find records from famous artists Args: input_file: Path to input JSONL file output_file: Path to output JSONL file target_artists: List of artist names to search for (None = all) vocal_stems_only: Only include records with vocal stems max_per_artist: Maximum records per artist limit: Maximum total records to process Returns: Dictionary with counts of found artists """ input_path = Path(input_file) output_path = Path(output_file) if not input_path.exists(): raise FileNotFoundError(f"Input file not found: {input_file}") # Create output directory output_path.parent.mkdir(parents=True, exist_ok=True) # Track findings artist_counts = defaultdict(int) found_records = [] processed_count = 0 # Filter target artists if specified search_artists = target_artists or list(FAMOUS_ARTISTS.keys()) logging.info( f"Searching for artists: {', '.join(search_artists[:10])}" + (f" and {len(search_artists)-10} more..." if len(search_artists) > 10 else "") ) logging.info(f"Input: {input_file}") logging.info(f"Output: {output_file}") logging.info(f"Vocal stems only: {vocal_stems_only}") logging.info(f"Max per artist: {max_per_artist}") with open(input_path, "r") as f: # Get total lines for progress bar total_lines = sum(1 for _ in f) f.seek(0) with tqdm(total=min(total_lines, limit) if limit else total_lines, desc="Processing") as pbar: for line_num, line in enumerate(f): if limit and processed_count >= limit: break try: record = json.loads(line.strip()) processed_count += 1 pbar.update(1) # Skip if no stems when vocal_stems_only is True if vocal_stems_only and not has_vocal_stems(record): continue # Search for artists found_artists = search_record_for_artists(record) if found_artists: # Check if we need any of these artists relevant_artists = { artist: matches for artist, matches in found_artists.items() if artist in search_artists and artist_counts[artist] < max_per_artist } if relevant_artists: # Add metadata about found artists record["_artist_analysis"] = { "found_artists": relevant_artists, "search_priority": min( artist_counts[artist] for artist in relevant_artists.keys() ), "line_number": line_num + 1, } found_records.append(record) # Update counts for artist in relevant_artists.keys(): artist_counts[artist] += 1 logging.debug( f"Found record {record.get('id', 'unknown')} with artists: {list(relevant_artists.keys())}" ) except json.JSONDecodeError as e: logging.warning(f"Skipping malformed JSON at line {line_num + 1}: {e}") continue except Exception as e: logging.error(f"Error processing line {line_num + 1}: {e}") continue # Sort records by artist rarity and diversity found_records.sort( key=lambda r: ( r["_artist_analysis"]["search_priority"], # Prefer rarer artists -len(r["_artist_analysis"]["found_artists"]), # Prefer records with multiple artists ) ) # Write results with open(output_path, "w") as f: for record in found_records: f.write(json.dumps(record) + "\n") logging.info(f"Found {len(found_records)} records from famous artists") logging.info("Artist distribution:") for artist, count in sorted(artist_counts.items()): artist_display = artist.replace("_", " ").title() logging.info(f" {artist_display}: {count} records") return dict(artist_counts) def main(): parser = argparse.ArgumentParser( description="Find records from famous artists for voice analysis testing", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: # Find all famous artists in validation set python find_famous_artist_records.py --input /app2/suno/data/auk_v0/metas_v6_val.jsonl --output famous_artist_records.jsonl # Find only specific artists python find_famous_artist_records.py --input metas_v6_val.jsonl --output taylor_kendrick.jsonl --artists taylor_swift kendrick_lamar drake # Find pop artists, allow non-vocal stems, get up to 10 per artist python find_famous_artist_records.py --input metas_v6_val.jsonl --output pop_stars.jsonl --artists taylor_swift ariana_grande billie_eilish --no-vocal-only --max-per-artist 10 Available artists: taylor_swift, kendrick_lamar, drake, beyonce, kanye_west, adele, ed_sheeran, billie_eilish, the_weeknd, ariana_grande, post_malone, bruno_mars, rihanna, justin_bieber, lady_gaga, eminem, jay_z, lana_del_rey, dua_lipa, olivia_rodrigo, the_beatles, queen, michael_jackson, madonna, prince, david_bowie, elvis_presley, bob_dylan, led_zeppelin, pink_floyd, radiohead, nirvana, metallica, ac_dc, guns_n_roses, coldplay, u2, green_day, red_hot_chili_peppers, foo_fighters, linkin_park, maroon_5, imagine_dragons, twenty_one_pilots, panic_at_the_disco, fall_out_boy, my_chemical_romance, paramore, black_eyed_peas, outkast, tupac, biggie, nas, snoop_dogg, dr_dre, nicki_minaj, cardi_b, megan_thee_stallion, doja_cat, lizzo, sza, frank_ocean, childish_gambino, tyler_the_creator, asap_rocky, travis_scott, lil_wayne, future, j_cole, chance_the_rapper """, ) parser.add_argument( "--input", "-i", type=str, required=True, help="Input JSONL file (e.g., metas_v6_val.jsonl)" ) parser.add_argument( "--output", "-o", type=str, required=True, help="Output JSONL file for found records" ) parser.add_argument( "--artists", nargs="+", choices=list(FAMOUS_ARTISTS.keys()), default=None, help="Specific artists to search for (default: all)", ) parser.add_argument( "--vocal-only", action="store_true", default=True, help="Only include records with vocal stems (default: True)", ) parser.add_argument( "--no-vocal-only", action="store_true", help="Include records without vocal stems" ) parser.add_argument( "--max-per-artist", type=int, default=5, help="Maximum records per artist (default: 5)" ) parser.add_argument( "--limit", type=int, default=None, help="Maximum records to process from input (for testing)" ) parser.add_argument("--verbose", "-v", action="store_true", help="Verbose logging") args = parser.parse_args() setup_logging(args.verbose) # Handle vocal stems logic vocal_stems_only = args.vocal_only and not args.no_vocal_only try: artist_counts = process_jsonl_file( input_file=args.input, output_file=args.output, target_artists=args.artists, vocal_stems_only=vocal_stems_only, max_per_artist=args.max_per_artist, limit=args.limit, ) print(f"\n✅ Famous artist detection complete!") print(f"📁 Results saved to: {args.output}") print(f"📊 Found {sum(artist_counts.values())} total records") if artist_counts: print("\n🎤 Artist distribution:") for artist, count in sorted(artist_counts.items()): artist_display = artist.replace("_", " ").title() print(f" {artist_display}: {count} records") else: print("⚠️ No records found from target artists") return 0 except Exception as e: logging.error(f"Script failed: {e}") return 1 if __name__ == "__main__": exit(main())