Files
ytplailist/create_playlist_summer_breeze.py
T
Frank Schwenk b3a9f79dc3 harharhar
2026-06-01 09:07:02 +02:00

309 lines
10 KiB
Python

#!/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()