#!/usr/bin/env python3 """ Create YouTube Music playlist "Summer Breeze 2026" with top 3 songs from each festival band. Bands from https://www.summer-breeze.de/de/bands/ """ import os import sys import json import time from ytmusicapi import YTMusic from ytmusicapi.auth.browser import setup_browser # List of all festival bands from https://www.summer-breeze.de/de/bands/ BANDS = [ "Helloween", "In Flames", "Arch Enemy", "Eisbrecher", "Saxon", "Lamb Of God", "Airbourne", "Alestorm", "Versengold", "The Ghost Inside", "Thy Art Is Murder", "Testament", "Amorphis", "Imminence", "Paleface Swiss", "Alcest", "The Butcher Sisters", "Orbit Culture", "Hatebreed", "Skindred", "Kim Dracula", "Soulfly", "Paradise Lost", "Kadavar", "Deicide", "Brothers Of Metal", "Fit For An Autopsy", "Northlane", "dARTAGNAN", "Betontod", "Terror", "Deafheaven", "Decapitated", "Future Palace", "BLACKBRAID", "Der Weg Einer Freiheit", "Miracle Of Sound", "Soen", "Alien Ant Farm", "Mushroomhead", "Municipal Waste", "EIVØR", "Das Lumpenpack", "Wolves In The Throne Room", "Trollfest", "Thundermother", "Saor", "Heavysaurus", "Nanowar of Steel", "From Fall To Spring", "Unprocessed", "THE SONS OF HUENS", "SANGUISUGABOGG", "SPEED", "Deserted Fear", "Misery Index", "Bloodred Hourglass", "Cryptopsy", "Brainstorm", "Parasite Inc.", "Excrementory Grindfuckers", "Grand Magus", "MASSIVE WAGONS", "Our Promise", "Green Lung", "The Narrator", "FULCI", "Illdisposed", "Setyøursails", "Ten56.", "Møl", "200 Stab Wounds", "Groza", "Mittel Alta", "Slomosa", "Nytt Land", "Soulbound", "Urne", "Manntra", "Haggefugg", "NECKBREAKKER", "KING NUGGET GANG", "Skeleton Pit", "CASTLE RAT", "Wucan", "Blood Command", "Cabal", "Filth", "Erdling", "Brymir", "Rectal Smegma", "Stam1na", "Zerre", "CÂN BARDD", "BROKEN BY THE SCREAM", "BIZARREKULT", "INNER SPACE", "FIREBORN", "Luna Kills", "PRIDIAN", "INHUMAN NATURE", "802", "PERSECUTOR", "Blasmusik Illenschwang", ] PLAYLIST_NAME = "Summer Breeze 2026" PLAYLIST_DESCRIPTION = "Festival playlist featuring top songs from Summer Breeze 2026 bands" 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') 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) if detected_type == AuthType.OAUTH_CUSTOM_CLIENT: parsed_headers["authorization"] = "SAPISIDHASH dummy_for_detection" detected_type = determine_auth_type(parsed_headers) 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}") 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}. 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()