Initial commit: YouTube Music playlist creator for Core Fest 2025
- Add playlist creation script with browser authentication - Support for 17 festival bands with top 3 songs each - Includes setup documentation and requirements - Fixes OAuth detection issue by adding dummy authorization header
This commit is contained in:
+24
@@ -0,0 +1,24 @@
|
||||
# Browser authentication file (contains sensitive cookies)
|
||||
browser.json
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
.venv
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# Core Fest 2025 YouTube Music Playlist Creator
|
||||
|
||||
Automatically creates a YouTube Music playlist "Core Fest 2025" with the top 3 songs from each festival band.
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. **Set up browser authentication** (see [SETUP.md](SETUP.md))
|
||||
2. **Install dependencies:**
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
3. **Run the script:**
|
||||
```bash
|
||||
python create_playlist.py
|
||||
```
|
||||
|
||||
## What It Does
|
||||
|
||||
- Creates a playlist named "Core Fest 2025"
|
||||
- Searches for each of the 17 festival bands
|
||||
- Adds the top 3 songs from each band to the playlist
|
||||
- Handles errors gracefully and logs progress
|
||||
|
||||
## Bands Included
|
||||
|
||||
**CLUB STAGE:** DETARTRATED, BAD ASSUMPTION, SEVEN BLOOD, VICIOUS RAIN, DAGGER THREAT, KANINE, GLOOM IN THE CORNER, MENTAL CRUELTY, WITHIN DESTRUCTION
|
||||
|
||||
**MAIN STAGE:** WATCH ME RISE, ATENA, DIAMOND CONSTRUCT, STAIN THE CANVAS, AS EVERYTHING UNFOLDS, AVIANA, FUTURE PALACE, DEAD BY APRIL
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
# Browser Authentication Setup
|
||||
|
||||
This guide will help you extract the necessary authentication cookies from your browser to use with `ytmusicapi`.
|
||||
|
||||
## Step 1: Open YouTube Music
|
||||
|
||||
1. Open your browser (Chrome or Firefox)
|
||||
2. Navigate to [https://music.youtube.com](https://music.youtube.com)
|
||||
3. **Make sure you are logged in** to your Google account
|
||||
|
||||
## Step 2: Open Developer Tools
|
||||
|
||||
- **Chrome/Edge:** Press `F12` or `Ctrl+Shift+I` (Windows/Linux) / `Cmd+Option+I` (Mac)
|
||||
- **Firefox:** Press `F12` or `Ctrl+Shift+I` (Windows/Linux) / `Cmd+Option+I` (Mac)
|
||||
|
||||
## Step 3: Go to Network Tab
|
||||
|
||||
1. Click on the **"Network"** tab in Developer Tools
|
||||
2. If you don't see it, look for it in the tabs at the top of the developer tools panel
|
||||
|
||||
## Step 4: Capture Network Requests
|
||||
|
||||
1. **Refresh the page** (press `F5` or `Ctrl+R` / `Cmd+R`)
|
||||
2. Wait for the page to load completely
|
||||
3. Look for requests to `music.youtube.com` in the network list
|
||||
|
||||
## Step 5: Find the Right Request
|
||||
|
||||
1. Click on any request that goes to `music.youtube.com`
|
||||
2. Look for requests like:
|
||||
- `browse`
|
||||
- `getBrowse`
|
||||
- `search`
|
||||
- Or any request that shows `music.youtube.com` in the URL
|
||||
|
||||
## Step 6: Copy Headers
|
||||
|
||||
1. With a request selected, click on the **"Headers"** tab
|
||||
2. Scroll down to **"Request Headers"** section
|
||||
3. You need to copy the following headers:
|
||||
|
||||
### Required Headers:
|
||||
|
||||
- **Cookie** - A long string starting with something like `VISITOR_INFO1_LIVE=...`
|
||||
- **User-Agent** - Something like `Mozilla/5.0 (Windows NT 10.0; Win64; x64)...`
|
||||
|
||||
### Optional but Recommended:
|
||||
|
||||
- **X-Goog-AuthUser** - Usually `0` or `1`
|
||||
- **Accept** - Usually `*/*` or `application/json`
|
||||
- **Accept-Language** - Your language preference
|
||||
- **Content-Type** - Usually `application/json`
|
||||
- **x-origin** - Usually `https://music.youtube.com`
|
||||
|
||||
## Step 7: Create browser.json
|
||||
|
||||
Create a file named `browser.json` in this directory with the following structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"User-Agent": "PASTE_YOUR_USER_AGENT_HERE",
|
||||
"Accept": "*/*",
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
"Content-Type": "application/json",
|
||||
"X-Goog-AuthUser": "0",
|
||||
"x-origin": "https://music.youtube.com",
|
||||
"Cookie": "PASTE_YOUR_COOKIE_HERE"
|
||||
}
|
||||
```
|
||||
|
||||
Replace:
|
||||
- `PASTE_YOUR_USER_AGENT_HERE` with the User-Agent header value you copied
|
||||
- `PASTE_YOUR_COOKIE_HERE` with the Cookie header value you copied
|
||||
|
||||
**Important:** The Cookie value is very long and contains sensitive information. Keep this file private and never commit it to version control.
|
||||
|
||||
## Step 8: Verify
|
||||
|
||||
Your `browser.json` should look something like this (with your actual values):
|
||||
|
||||
```json
|
||||
{
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||
"Accept": "*/*",
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
"Content-Type": "application/json",
|
||||
"X-Goog-AuthUser": "0",
|
||||
"x-origin": "https://music.youtube.com",
|
||||
"Cookie": "VISITOR_INFO1_LIVE=abc123...; YSC=xyz789...; [many more cookie values]"
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **Can't find the Cookie header?** Make sure you're looking at a request to `music.youtube.com`, not `youtube.com`
|
||||
- **Authentication fails?** Your cookies may have expired. Repeat the process to get fresh cookies
|
||||
- **Still having issues?** Try using a different request from the network tab, or refresh the page and try again
|
||||
|
||||
## Security Note
|
||||
|
||||
The `browser.json` file contains your authentication cookies. Anyone with access to this file can access your YouTube Music account. Keep it secure and never share it publicly.
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Create YouTube Music playlist "Core Fest 2025" with top 3 songs from each festival band.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
from ytmusicapi import YTMusic
|
||||
from ytmusicapi.auth.browser import setup_browser
|
||||
|
||||
# List of all festival bands
|
||||
BANDS = [
|
||||
# CLUB STAGE
|
||||
"DETARTRATED",
|
||||
"BAD ASSUMPTION",
|
||||
"SEVEN BLOOD",
|
||||
"VICIOUS RAIN",
|
||||
"DAGGER THREAT",
|
||||
"KANINE",
|
||||
"GLOOM IN THE CORNER",
|
||||
"MENTAL CRUELTY",
|
||||
"WITHIN DESTRUCTION",
|
||||
# MAIN STAGE
|
||||
"WATCH ME RISE",
|
||||
"ATENA",
|
||||
"DIAMOND CONSTRUCT",
|
||||
"STAIN THE CANVAS",
|
||||
"AS EVERYTHING UNFOLDS",
|
||||
"AVIANA",
|
||||
"FUTURE PALACE",
|
||||
"DEAD BY APRIL",
|
||||
]
|
||||
|
||||
PLAYLIST_NAME = "Core Fest 2025"
|
||||
PLAYLIST_DESCRIPTION = "Festival playlist featuring top songs from Core Fest 2025 bands"
|
||||
SONGS_PER_BAND = 3
|
||||
BROWSER_AUTH_FILE = "browser.json"
|
||||
|
||||
|
||||
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
|
||||
print(f"Initializing YouTube Music API with {BROWSER_AUTH_FILE}...")
|
||||
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)
|
||||
# Format: "Header-Name: value\nHeader-Name2: value2"
|
||||
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:
|
||||
# Add a dummy authorization header with SAPISIDHASH to trigger browser detection
|
||||
# ytmusicapi will regenerate this properly when needed
|
||||
parsed_headers["authorization"] = "SAPISIDHASH dummy_for_detection"
|
||||
# Re-check auth type
|
||||
detected_type = determine_auth_type(parsed_headers)
|
||||
# Reconstruct auth_string with the authorization header
|
||||
auth_dict = dict(parsed_headers)
|
||||
auth_string = json.dumps(auth_dict)
|
||||
|
||||
# Initialize with the properly formatted auth string
|
||||
yt = YTMusic(auth=auth_string)
|
||||
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:
|
||||
error_msg = str(e)
|
||||
print(f"Error initializing YTMusic: {error_msg}")
|
||||
|
||||
# If it's the OAuth detection error, provide specific guidance
|
||||
if "oauth" in error_msg.lower() and "oauth_credentials" in error_msg.lower():
|
||||
print("\n⚠ OAuth detection error detected.")
|
||||
print("This may be a ytmusicapi version issue or file format issue.")
|
||||
print("\nTry one of these solutions:")
|
||||
print("1. Regenerate browser.json using: ytmusicapi browser")
|
||||
print("2. Update ytmusicapi: pip install --upgrade ytmusicapi")
|
||||
print("3. Ensure browser.json has exactly these keys:")
|
||||
print(" - User-Agent")
|
||||
print(" - Cookie")
|
||||
print(" - X-Goog-AuthUser")
|
||||
print(" - x-origin")
|
||||
print(" - Accept (optional)")
|
||||
print(" - Accept-Language (optional)")
|
||||
print(" - Content-Type (optional)")
|
||||
else:
|
||||
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:
|
||||
# Search for songs by this band
|
||||
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
|
||||
songs_to_add = []
|
||||
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)
|
||||
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
|
||||
try:
|
||||
yt.add_playlist_items(playlist_id, songs_to_add)
|
||||
print(f" ✓ Added {len(songs_to_add)} song(s):")
|
||||
for result in search_results[:len(songs_to_add)]:
|
||||
title = result.get("title", "Unknown")
|
||||
artist = result.get("artists", [{}])[0].get("name", "Unknown")
|
||||
print(f" - {title} by {artist}")
|
||||
total_added += len(songs_to_add)
|
||||
except Exception as e:
|
||||
print(f" ✗ Error adding songs: {e}")
|
||||
failed_bands.append(band)
|
||||
|
||||
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()
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ytmusicapi>=1.0.0
|
||||
|
||||
Reference in New Issue
Block a user