harharhar
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user