#!/usr/bin/env python3 """ Create YouTube Music playlist "Doom in Bloom - Chapel Göppingen 2026" with top 3 songs from each festival band. """ import os import sys import json import time from ytmusicapi import YTMusic from ytmusicapi.auth.browser import setup_browser # Lineup extracted from event poster attachment BANDS = [ "AHAB", "WELL OF SOULS", "PETRIFIED", "APTERA", "ASTRAL RISING", "DAWN OF WINTER", "TAEVUS", "MIRROR OF DECEPTION", ] PLAYLIST_NAME = "Doom in Bloom - Chapel Göppingen 2026" PLAYLIST_DESCRIPTION = ( "Festival playlist featuring top songs from Doom in Bloom (Göppingen, 10-11 April 2026)" ) SONGS_PER_BAND = 3 BROWSER_AUTH_FILE = "browser.json" BRAND_ACCOUNT_ID = "107494778873257953135" # cuidas brand account def main(): if not os.path.exists(BROWSER_AUTH_FILE): print(f"Error: {BROWSER_AUTH_FILE} not found!") print(f"Please follow the instructions in SETUP.md to create {BROWSER_AUTH_FILE}") sys.exit(1) account_info = f" (brand account: {BRAND_ACCOUNT_ID})" if BRAND_ACCOUNT_ID else "" print(f"Initializing YouTube Music API with {BROWSER_AUTH_FILE}{account_info}...") try: with open(BROWSER_AUTH_FILE, "r", encoding="utf-8") as f: browser_data = json.load(f) required_keys = ["Cookie", "User-Agent"] missing_keys = [k for k in required_keys if k not in browser_data] if missing_keys: print(f"Error: Missing required keys in {BROWSER_AUTH_FILE}: {missing_keys}") sys.exit(1) headers_string = "\n".join([f"{k}: {v}" for k, v in browser_data.items()]) auth_string = setup_browser(headers_raw=headers_string, filepath=None) from ytmusicapi.auth.auth_parse import determine_auth_type, parse_auth_str from ytmusicapi.auth.types import AuthType parsed_headers, _ = parse_auth_str(auth_string) detected_type = determine_auth_type(parsed_headers) # Workaround for auth type detection with browser cookie headers. if detected_type == AuthType.OAUTH_CUSTOM_CLIENT: parsed_headers["authorization"] = "SAPISIDHASH dummy_for_detection" auth_dict = dict(parsed_headers) auth_string = json.dumps(auth_dict) yt = YTMusic(auth=auth_string, user=BRAND_ACCOUNT_ID if BRAND_ACCOUNT_ID else None) except json.JSONDecodeError as e: print(f"Error parsing {BROWSER_AUTH_FILE}: {e}") print("Please check that your browser.json file is valid JSON.") sys.exit(1) except Exception as e: print(f"Error initializing YTMusic: {e}") import traceback traceback.print_exc() sys.exit(1) print(f"\nCreating playlist: {PLAYLIST_NAME}...") try: playlist_id = yt.create_playlist(PLAYLIST_NAME, PLAYLIST_DESCRIPTION) print(f"✓ Playlist created successfully! ID: {playlist_id}") except Exception as e: print(f"Error creating playlist: {e}") sys.exit(1) added_video_ids = set() total_added = 0 failed_bands = [] print(f"\nSearching for songs from {len(BANDS)} bands...") print("-" * 60) for i, band in enumerate(BANDS, 1): print(f"\n[{i}/{len(BANDS)}] Searching for: {band}") try: artist_results = yt.search(band, filter="artists", limit=1) songs_to_add = [] song_info_list = [] if not artist_results: print(f" ⚠ No artist found for {band}, trying song search...") search_results = yt.search(band, filter="songs", limit=SONGS_PER_BAND * 2) if not search_results: print(f" ⚠ No results found for {band}") failed_bands.append(band) continue for result in search_results: video_id = result.get("videoId") if video_id and video_id not in added_video_ids: songs_to_add.append(video_id) song_info_list.append(result) added_video_ids.add(video_id) if len(songs_to_add) >= SONGS_PER_BAND: break else: artist_browse_id = artist_results[0].get("browseId") if not artist_browse_id: print(f" ⚠ No browseId found for artist {band}") failed_bands.append(band) continue try: artist_info = yt.get_artist(artist_browse_id) artist_songs = artist_info.get("songs", {}).get("results", []) if not artist_songs: print(f" ⚠ No songs found for artist {band}") failed_bands.append(band) continue for song in artist_songs: video_id = song.get("videoId") if video_id and video_id not in added_video_ids: songs_to_add.append(video_id) song_info_list.append(song) added_video_ids.add(video_id) if len(songs_to_add) >= SONGS_PER_BAND: break except Exception as e: print(f" ⚠ Error getting artist info: {e}, falling back to song search...") search_results = yt.search(band, filter="songs", limit=SONGS_PER_BAND * 2) if not search_results: print(f" ⚠ No results found for {band}") failed_bands.append(band) continue for result in search_results: video_id = result.get("videoId") if video_id and video_id not in added_video_ids: songs_to_add.append(video_id) song_info_list.append(result) added_video_ids.add(video_id) if len(songs_to_add) >= SONGS_PER_BAND: break if not songs_to_add: print(f" ⚠ No new songs to add for {band} (may be duplicates)") continue max_retries = 3 retry_delay = 5 for attempt in range(1, max_retries + 1): try: yt.add_playlist_items(playlist_id, songs_to_add) print(f" ✓ Added {len(songs_to_add)} song(s):") for song_info in song_info_list[: len(songs_to_add)]: title = song_info.get("title", "Unknown") artists = song_info.get("artists", []) if isinstance(artists, list) and len(artists) > 0: artist = ( artists[0].get("name", "Unknown") if isinstance(artists[0], dict) else str(artists[0]) ) else: artist = song_info.get("artist", "Unknown") print(f" - {title} by {artist}") total_added += len(songs_to_add) break except Exception as e: error_msg = str(e) if "409" in error_msg or "Conflict" in error_msg: if attempt < max_retries: print( f" ⚠ HTTP 409 Conflict on attempt {attempt}/{max_retries}. " f"Retrying in {retry_delay} seconds..." ) time.sleep(retry_delay) else: print(f" ✗ Error adding songs after {max_retries} attempts: {e}") failed_bands.append(band) else: print(f" ✗ Error adding songs: {e}") failed_bands.append(band) break except Exception as e: print(f" ✗ Error searching for {band}: {e}") failed_bands.append(band) print("\n" + "=" * 60) print("SUMMARY") print("=" * 60) print(f"Playlist: {PLAYLIST_NAME}") print(f"Total songs added: {total_added}") print(f"Bands processed: {len(BANDS) - len(failed_bands)}/{len(BANDS)}") if failed_bands: print(f"\nBands that failed or had no results ({len(failed_bands)}):") for band in failed_bands: print(f" - {band}") print(f"\n✓ Done! Check your YouTube Music library for the playlist.") if __name__ == "__main__": main()