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

243 lines
10 KiB
Python

#!/usr/bin/env python3
"""
Create YouTube Music playlist "Rock in Weiler 2025" with top 3 songs from each festival band using OAuth authentication.
"""
import os
import sys
import json
import time
from ytmusicapi import YTMusic
from ytmusicapi.auth.browser import setup_browser
# List of all festival bands
BANDS = [
"Unjust All",
"And Phobos Falls",
"Let Me Fall",
"Out Of Vision",
"Callejon",
]
PLAYLIST_NAME = "Rock in Weiler 2025"
PLAYLIST_DESCRIPTION = "Festival playlist featuring top songs from Rock in Weiler 2025 bands"
SONGS_PER_BAND = 3
BROWSER_AUTH_FILE = "browser.json"
# Brand account ID (set to None to use default account)
BRAND_ACCOUNT_ID = "107494778873257953135" # cuidas brand account
def main():
# Check if browser.json exists
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)
# Initialize YTMusic with browser authentication
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:
# Load browser.json as dict
with open(BROWSER_AUTH_FILE, 'r') as f:
browser_data = json.load(f)
# Ensure we have required keys for browser auth
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)
# Convert dict to headers string format (as ytmusicapi expects)
headers_string = "\n".join([f"{k}: {v}" for k, v in browser_data.items()])
# Use setup_browser to properly format and validate browser headers
auth_string = setup_browser(headers_raw=headers_string, filepath=None)
# Parse the result to check auth type detection
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: determine_auth_type() only detects browser auth if there's an
# "authorization" header with "SAPISIDHASH". Since browser.json doesn't have it,
# we need to add a dummy one to pass detection. ytmusicapi will regenerate it properly.
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)
# Initialize with the properly formatted auth string
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}")
print("Please check that your browser.json file is correctly formatted.")
import traceback
traceback.print_exc()
sys.exit(1)
# Create playlist
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)
# Track added songs to avoid duplicates
added_video_ids = set()
total_added = 0
failed_bands = []
# Process each band
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:
# First, search for the artist to find the correct band
artist_results = yt.search(band, filter="artists", limit=1)
songs_to_add = []
song_info_list = [] # Store song info for display
if not artist_results:
# Fallback: try searching for songs if artist search fails
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
# Filter to get top songs and avoid duplicates
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) # Store for display
added_video_ids.add(video_id)
if len(songs_to_add) >= SONGS_PER_BAND:
break
else:
# Found artist, get their songs
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
# Get artist information which includes songs
try:
artist_info = yt.get_artist(artist_browse_id)
# Get songs from the artist
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
# Filter to get top songs and avoid duplicates
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) # Store for display
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...")
# Fallback 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) # Store for display
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
# Add songs to playlist with retry logic for HTTP 409 conflicts
max_retries = 3
retry_delay = 5 # seconds
added_successfully = False
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")
# Handle both artist list format and single artist format
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)
added_successfully = True
break
except Exception as e:
error_msg = str(e)
# Check if it's an HTTP 409 Conflict error
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:
# For other errors, don't retry
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)
# Summary
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()