Update: Fix empty album and iOS playback issues, add diverse home content
This commit is contained in:
parent
e1cb73f817
commit
e23714bbd6
5 changed files with 17909 additions and 13684 deletions
|
|
@ -130,9 +130,31 @@ async def get_playlist(id: str):
|
|||
try:
|
||||
from ytmusicapi import YTMusic
|
||||
yt = YTMusic()
|
||||
# ytmusicapi returns a dict with 'tracks' list
|
||||
playlist_data = yt.get_playlist(id, limit=100)
|
||||
|
||||
playlist_data = None
|
||||
is_album = False
|
||||
|
||||
# Try as Album first if ID looks like an album (MPREb...) or just try block
|
||||
if id.startswith("MPREb"):
|
||||
try:
|
||||
playlist_data = yt.get_album(id)
|
||||
is_album = True
|
||||
except:
|
||||
pass
|
||||
|
||||
if not playlist_data:
|
||||
try:
|
||||
# ytmusicapi returns a dict with 'tracks' list
|
||||
playlist_data = yt.get_playlist(id, limit=100)
|
||||
except Exception as e:
|
||||
# Fallback: Try as album if not tried yet
|
||||
if not is_album:
|
||||
try:
|
||||
playlist_data = yt.get_album(id)
|
||||
is_album = True
|
||||
except:
|
||||
raise e # Re-raise if both fail
|
||||
|
||||
# Format to match our app's Protocol
|
||||
formatted_tracks = []
|
||||
if 'tracks' in playlist_data:
|
||||
|
|
@ -146,17 +168,22 @@ async def get_playlist(id: str):
|
|||
|
||||
# Safely extract thumbnails
|
||||
thumbnails = track.get('thumbnails', [])
|
||||
if not thumbnails and is_album:
|
||||
# Albums sometimes have thumbnails at root level, not per track
|
||||
thumbnails = playlist_data.get('thumbnails', [])
|
||||
|
||||
cover_url = thumbnails[-1]['url'] if thumbnails else "https://placehold.co/300x300"
|
||||
|
||||
# Safely extract album
|
||||
album_info = track.get('album')
|
||||
album_name = album_info.get('name', 'Single') if album_info else "Single"
|
||||
# If it's an album fetch, the album name is the playlist title
|
||||
album_name = album_info.get('name', playlist_data.get('title')) if album_info else playlist_data.get('title', 'Single')
|
||||
|
||||
formatted_tracks.append({
|
||||
"title": track.get('title', 'Unknown Title'),
|
||||
"artist": artist_names,
|
||||
"album": album_name,
|
||||
"duration": track.get('duration_seconds', 0),
|
||||
"duration": track.get('duration_seconds', track.get('length_seconds', 0)),
|
||||
"cover_url": cover_url,
|
||||
"id": track.get('videoId'),
|
||||
"url": f"https://music.youtube.com/watch?v={track.get('videoId')}"
|
||||
|
|
@ -167,10 +194,10 @@ async def get_playlist(id: str):
|
|||
p_cover = thumbnails[-1]['url'] if thumbnails else "https://placehold.co/300x300"
|
||||
|
||||
formatted_playlist = {
|
||||
"id": playlist_data.get('id'),
|
||||
"id": playlist_data.get('browseId', playlist_data.get('id')),
|
||||
"title": clean_title(playlist_data.get('title', 'Unknown')),
|
||||
"description": clean_description(playlist_data.get('description', '')),
|
||||
"author": playlist_data.get('author', {}).get('name', 'YouTube Music'),
|
||||
"author": playlist_data.get('author', {}).get('name', 'YouTube Music') if not is_album else ", ".join([a.get('name','') for a in playlist_data.get('artists', [])]),
|
||||
"cover_url": p_cover,
|
||||
"tracks": formatted_tracks
|
||||
}
|
||||
|
|
@ -403,7 +430,7 @@ async def stream_audio(id: str):
|
|||
print(f"DEBUG: Fetching new stream URL for '{id}'")
|
||||
url = f"https://www.youtube.com/watch?v={id}"
|
||||
ydl_opts = {
|
||||
'format': 'bestaudio/best',
|
||||
'format': 'bestaudio[ext=m4a]/best[ext=mp4]/best', # Prefer m4a/aac for iOS
|
||||
'quiet': True,
|
||||
'noplaylist': True,
|
||||
}
|
||||
|
|
|
|||
1095
backend/data.json
1095
backend/data.json
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
36
deploy_commands.sh
Executable file
36
deploy_commands.sh
Executable file
|
|
@ -0,0 +1,36 @@
|
|||
#!/bin/bash
|
||||
|
||||
# 1. Deployment to GitHub
|
||||
echo "--- 🚀 Deploying to GitHub ---"
|
||||
# Enter your correct repo URL here if different
|
||||
REPO_URL="https://github.com/vndangkhoa/spotify-clone.git"
|
||||
|
||||
if git remote | grep -q "origin"; then
|
||||
echo "Remote 'origin' already exists. Setting URL..."
|
||||
git remote set-url origin $REPO_URL
|
||||
else
|
||||
git remote add origin $REPO_URL
|
||||
fi
|
||||
|
||||
echo "Staging and Committing changes..."
|
||||
git add .
|
||||
git commit -m "Update: Fix empty album and iOS playback issues, add diverse home content"
|
||||
|
||||
echo "Pushing code..."
|
||||
# This might fail if the repo doesn't exist on GitHub yet.
|
||||
# Go to https://github.com/new and create 'spotify-clone' first!
|
||||
git push -u origin main
|
||||
|
||||
# 2. Deployment to Docker Hub
|
||||
echo ""
|
||||
echo "--- 🐳 Deploying to Docker Hub ---"
|
||||
echo "Building Image..."
|
||||
# Ensure Docker Desktop is running!
|
||||
# Use --platform to build for Synology NAS (x86_64) from Apple Silicon Mac
|
||||
docker build --platform linux/amd64 -t vndangkhoa/spotify-clone:latest .
|
||||
|
||||
echo "Pushing Image..."
|
||||
docker push vndangkhoa/spotify-clone:latest
|
||||
|
||||
echo ""
|
||||
echo "--- ✅ Deployment Script Finished ---"
|
||||
105
fetch_data.py
105
fetch_data.py
|
|
@ -1,40 +1,64 @@
|
|||
from ytmusicapi import YTMusic
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
from pathlib import Path
|
||||
|
||||
yt = YTMusic()
|
||||
|
||||
# Define diverse categories to fetch
|
||||
CATEGORIES = {
|
||||
"Trending Vietnam": "Top 50 Vietnam",
|
||||
"Vietnamese Artists": "Vietnamese Pop Hits",
|
||||
"Ballad Singers": "Vietnamese Ballad",
|
||||
"DJ & Remix": "Vinahouse Remix Vietnam",
|
||||
"YouTube Stars": "Vietnamese Cover Songs"
|
||||
"Trending Vietnam": {"query": "Top 50 Vietnam", "type": "playlists"},
|
||||
"Global Hits": {"query": "Global Top 50", "type": "playlists"},
|
||||
"New Albums 2024": {"query": "New Albums 2024 Vietnam", "type": "albums"},
|
||||
"Chill Vibes": {"query": "Chill Lofi", "type": "playlists"},
|
||||
"Party Time": {"query": "Party EDM Hits", "type": "playlists"},
|
||||
"Best of Ballad": {"query": "Vietnamese Ballad", "type": "playlists"},
|
||||
"Hip Hop & Rap": {"query": "Vietnamese Rap", "type": "playlists"},
|
||||
}
|
||||
|
||||
browse_data = {}
|
||||
|
||||
print("Starting data fetch...")
|
||||
print("Starting diverse data fetch...")
|
||||
|
||||
for category, query in CATEGORIES.items():
|
||||
print(f"\n--- Fetching Category: {category} (Query: '{query}') ---")
|
||||
def get_thumbnail(thumbnails):
|
||||
if not thumbnails:
|
||||
return "https://placehold.co/300x300"
|
||||
return thumbnails[-1]['url']
|
||||
|
||||
for category_name, info in CATEGORIES.items():
|
||||
query = info["query"]
|
||||
search_type = info["type"]
|
||||
print(f"\n--- Fetching Category: {category_name} (Query: '{query}', Type: {search_type}) ---")
|
||||
|
||||
try:
|
||||
results = yt.search(query, filter="playlists", limit=5)
|
||||
results = yt.search(query, filter=search_type, limit=5)
|
||||
|
||||
category_playlists = []
|
||||
category_items = []
|
||||
|
||||
for p_result in results[:4]: # Limit to 4 playlists per category
|
||||
playlist_id = p_result['browseId']
|
||||
print(f" > Processing: {p_result['title']}")
|
||||
for result in results[:4]: # Limit to 4 items per category
|
||||
item_id = result['browseId']
|
||||
title = result['title']
|
||||
print(f" > Processing: {title}")
|
||||
|
||||
try:
|
||||
# Fetch full playlist details
|
||||
playlist_data = yt.get_playlist(playlist_id, limit=50)
|
||||
|
||||
# Fetch details based on type
|
||||
if search_type == "albums":
|
||||
# Use get_album
|
||||
details = yt.get_album(item_id)
|
||||
tracks_source = details.get('tracks', [])
|
||||
is_album = True
|
||||
description = f"Album by {', '.join([a.get('name') for a in details.get('artists', [])])} • {details.get('year')}"
|
||||
else:
|
||||
# Use get_playlist
|
||||
details = yt.get_playlist(item_id, limit=50)
|
||||
tracks_source = details.get('tracks', [])
|
||||
is_album = False
|
||||
description = details.get('description', '')
|
||||
|
||||
# Process Tracks
|
||||
output_tracks = []
|
||||
for track in playlist_data.get('tracks', []):
|
||||
for track in tracks_source:
|
||||
artists_list = track.get('artists') or []
|
||||
if isinstance(artists_list, list):
|
||||
artists = ", ".join([a.get('name', 'Unknown') for a in artists_list])
|
||||
|
|
@ -42,42 +66,53 @@ for category, query in CATEGORIES.items():
|
|||
artists = "Unknown Artist"
|
||||
|
||||
thumbnails = track.get('thumbnails', [])
|
||||
cover_url = thumbnails[-1]['url'] if thumbnails else "https://placehold.co/300x300"
|
||||
# Fallback for album tracks which might not have thumbnails
|
||||
if not thumbnails and is_album:
|
||||
thumbnails = details.get('thumbnails', [])
|
||||
|
||||
cover_url = get_thumbnail(thumbnails)
|
||||
|
||||
album_info = track.get('album')
|
||||
album_name = album_info.get('name', 'Single') if album_info else "Single"
|
||||
# Use playlist/album title as album name if missing
|
||||
album_name = album_info.get('name', title) if album_info else title
|
||||
|
||||
# Track ID can be missing in some album views (very rare)
|
||||
track_id = track.get('videoId')
|
||||
if not track_id: continue
|
||||
|
||||
output_tracks.append({
|
||||
"title": track.get('title', 'Unknown Title'),
|
||||
"artist": artists,
|
||||
"album": album_name,
|
||||
"duration": track.get('duration_seconds', 0),
|
||||
"duration": track.get('duration_seconds', track.get('length_seconds', 0)),
|
||||
"cover_url": cover_url,
|
||||
"id": track.get('videoId', 'unknown'),
|
||||
"url": f"https://music.youtube.com/watch?v={track.get('videoId', '')}"
|
||||
"id": track_id,
|
||||
"url": f"https://music.youtube.com/watch?v={track_id}"
|
||||
})
|
||||
|
||||
# Process Playlist Info
|
||||
p_thumbnails = playlist_data.get('thumbnails', [])
|
||||
p_cover = p_thumbnails[-1]['url'] if p_thumbnails else "https://placehold.co/300x300"
|
||||
if not output_tracks:
|
||||
print(f" Skipping empty item: {title}")
|
||||
continue
|
||||
|
||||
category_playlists.append({
|
||||
"id": playlist_data.get('id'),
|
||||
"title": playlist_data.get('title'),
|
||||
"description": playlist_data.get('description', '') or f"Best of {category}",
|
||||
"cover_url": p_cover,
|
||||
"tracks": output_tracks
|
||||
# Final Item Object
|
||||
category_items.append({
|
||||
"id": item_id,
|
||||
"title": title,
|
||||
"description": description or f"Best of {category_name}",
|
||||
"cover_url": get_thumbnail(details.get('thumbnails', result.get('thumbnails'))),
|
||||
"tracks": output_tracks,
|
||||
"type": "album" if is_album else "playlist"
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f" Error processing playlist {playlist_id}: {e}")
|
||||
print(f" Error processing {item_id}: {e}")
|
||||
continue
|
||||
|
||||
if category_playlists:
|
||||
browse_data[category] = category_playlists
|
||||
if category_items:
|
||||
browse_data[category_name] = category_items
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error searching category {category}: {e}")
|
||||
print(f"Error searching category {category_name}: {e}")
|
||||
|
||||
# Save to backend/data/browse_playlists.json
|
||||
output_path = Path("backend/data/browse_playlists.json")
|
||||
|
|
|
|||
Loading…
Reference in a new issue