harharhar

This commit is contained in:
Frank Schwenk
2026-06-01 09:07:02 +02:00
parent d69f88db59
commit b3a9f79dc3
5 changed files with 945 additions and 0 deletions
+3
View File
@@ -5,6 +5,9 @@ browser.json
client_secret_*.json
oauth.json
# Playlist exports
playlists/
# Python
__pycache__/
*.py[cod]
+220
View File
@@ -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()
+242
View File
@@ -0,0 +1,242 @@
#!/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()
+308
View File
@@ -0,0 +1,308 @@
#!/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()
+172
View File
@@ -0,0 +1,172 @@
#!/usr/bin/env python3
"""
Fetch all playlists from YouTube Music brand account and save each playlist's
information to a separate JSON file.
"""
import os
import sys
import json
import time
from ytmusicapi import YTMusic
from ytmusicapi.auth.browser import setup_browser
BROWSER_AUTH_FILE = "browser.json"
BRAND_ACCOUNT_ID = "107494778873257953135" # cuidas brand account
OUTPUT_DIR = "playlists"
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})"
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
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 output directory
if not os.path.exists(OUTPUT_DIR):
os.makedirs(OUTPUT_DIR)
print(f"Created output directory: {OUTPUT_DIR}")
# Fetch all playlists
print(f"\nFetching all playlists from brand account...")
print("-" * 60)
try:
# Get all playlists (limit=None should get all)
playlists = yt.get_library_playlists(limit=None)
print(f"Found {len(playlists)} playlist(s)")
if not playlists:
print("No playlists found!")
return
# Process each playlist
for i, playlist in enumerate(playlists, 1):
playlist_id = playlist.get('playlistId')
playlist_title = playlist.get('title', 'Unknown')
print(f"\n[{i}/{len(playlists)}] Processing: {playlist_title} (ID: {playlist_id})")
try:
# Get full playlist details
playlist_details = yt.get_playlist(playlist_id, limit=None)
if not playlist_details:
print(f" ⚠ Warning: get_playlist returned None for {playlist_id}")
# Save what we have from library_info
playlist_info = {
"library_info": playlist,
"full_details": None,
"error": "get_playlist returned None"
}
else:
# Combine library playlist info with full details
playlist_info = {
"library_info": playlist, # Info from get_library_playlists
"full_details": playlist_details # Full details from get_playlist
}
# Create safe filename from playlist title
safe_filename = "".join(c for c in playlist_title if c.isalnum() or c in (' ', '-', '_')).rstrip()
safe_filename = safe_filename.replace(' ', '_')
if not safe_filename:
safe_filename = f"playlist_{playlist_id}"
# Add playlist ID to filename to ensure uniqueness
filename = f"{safe_filename}_{playlist_id}.json"
filepath = os.path.join(OUTPUT_DIR, filename)
# Save to file
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(playlist_info, f, indent=2, ensure_ascii=False)
# Print summary
if playlist_details:
tracks_count = playlist_details.get('trackCount', 0)
print(f" ✓ Saved to: {filepath}")
print(f" Tracks: {tracks_count}")
print(f" Title: {playlist_details.get('title', 'Unknown')}")
description = playlist_details.get('description', 'N/A')
if description:
print(f" Description: {description[:50]}...")
else:
print(f" Description: N/A")
else:
print(f" ⚠ Saved (library info only) to: {filepath}")
print(f" Title: {playlist_title}")
except Exception as e:
print(f" ✗ Error processing playlist {playlist_title}: {e}")
import traceback
traceback.print_exc()
continue
# Summary
print("\n" + "=" * 60)
print("SUMMARY")
print("=" * 60)
print(f"Total playlists processed: {len(playlists)}")
print(f"Output directory: {OUTPUT_DIR}")
print(f"✓ Done! All playlist information saved to individual files.")
except Exception as e:
print(f"Error fetching playlists: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()