Update: Fix empty album and iOS playback issues, add diverse home content

This commit is contained in:
Khoa.vo 2025-12-17 13:58:34 +07:00
parent e1cb73f817
commit e23714bbd6
5 changed files with 17909 additions and 13684 deletions

View file

@ -130,9 +130,31 @@ async def get_playlist(id: str):
try: try:
from ytmusicapi import YTMusic from ytmusicapi import YTMusic
yt = 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 # Format to match our app's Protocol
formatted_tracks = [] formatted_tracks = []
if 'tracks' in playlist_data: if 'tracks' in playlist_data:
@ -146,17 +168,22 @@ async def get_playlist(id: str):
# Safely extract thumbnails # Safely extract thumbnails
thumbnails = track.get('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" cover_url = thumbnails[-1]['url'] if thumbnails else "https://placehold.co/300x300"
# Safely extract album # Safely extract album
album_info = track.get('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({ formatted_tracks.append({
"title": track.get('title', 'Unknown Title'), "title": track.get('title', 'Unknown Title'),
"artist": artist_names, "artist": artist_names,
"album": album_name, "album": album_name,
"duration": track.get('duration_seconds', 0), "duration": track.get('duration_seconds', track.get('length_seconds', 0)),
"cover_url": cover_url, "cover_url": cover_url,
"id": track.get('videoId'), "id": track.get('videoId'),
"url": f"https://music.youtube.com/watch?v={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" p_cover = thumbnails[-1]['url'] if thumbnails else "https://placehold.co/300x300"
formatted_playlist = { formatted_playlist = {
"id": playlist_data.get('id'), "id": playlist_data.get('browseId', playlist_data.get('id')),
"title": clean_title(playlist_data.get('title', 'Unknown')), "title": clean_title(playlist_data.get('title', 'Unknown')),
"description": clean_description(playlist_data.get('description', '')), "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, "cover_url": p_cover,
"tracks": formatted_tracks "tracks": formatted_tracks
} }
@ -403,7 +430,7 @@ async def stream_audio(id: str):
print(f"DEBUG: Fetching new stream URL for '{id}'") print(f"DEBUG: Fetching new stream URL for '{id}'")
url = f"https://www.youtube.com/watch?v={id}" url = f"https://www.youtube.com/watch?v={id}"
ydl_opts = { ydl_opts = {
'format': 'bestaudio/best', 'format': 'bestaudio[ext=m4a]/best[ext=mp4]/best', # Prefer m4a/aac for iOS
'quiet': True, 'quiet': True,
'noplaylist': True, 'noplaylist': True,
} }

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
View 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 ---"

View file

@ -1,40 +1,64 @@
from ytmusicapi import YTMusic from ytmusicapi import YTMusic
import json import json
import os import os
import random
from pathlib import Path from pathlib import Path
yt = YTMusic() yt = YTMusic()
# Define diverse categories to fetch
CATEGORIES = { CATEGORIES = {
"Trending Vietnam": "Top 50 Vietnam", "Trending Vietnam": {"query": "Top 50 Vietnam", "type": "playlists"},
"Vietnamese Artists": "Vietnamese Pop Hits", "Global Hits": {"query": "Global Top 50", "type": "playlists"},
"Ballad Singers": "Vietnamese Ballad", "New Albums 2024": {"query": "New Albums 2024 Vietnam", "type": "albums"},
"DJ & Remix": "Vinahouse Remix Vietnam", "Chill Vibes": {"query": "Chill Lofi", "type": "playlists"},
"YouTube Stars": "Vietnamese Cover Songs" "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 = {} browse_data = {}
print("Starting data fetch...") print("Starting diverse data fetch...")
for category, query in CATEGORIES.items(): def get_thumbnail(thumbnails):
print(f"\n--- Fetching Category: {category} (Query: '{query}') ---") 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: 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 for result in results[:4]: # Limit to 4 items per category
playlist_id = p_result['browseId'] item_id = result['browseId']
print(f" > Processing: {p_result['title']}") title = result['title']
print(f" > Processing: {title}")
try: try:
# Fetch full playlist details # Fetch details based on type
playlist_data = yt.get_playlist(playlist_id, limit=50) 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 # Process Tracks
output_tracks = [] output_tracks = []
for track in playlist_data.get('tracks', []): for track in tracks_source:
artists_list = track.get('artists') or [] artists_list = track.get('artists') or []
if isinstance(artists_list, list): if isinstance(artists_list, list):
artists = ", ".join([a.get('name', 'Unknown') for a in artists_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" artists = "Unknown Artist"
thumbnails = track.get('thumbnails', []) 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_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({ output_tracks.append({
"title": track.get('title', 'Unknown Title'), "title": track.get('title', 'Unknown Title'),
"artist": artists, "artist": artists,
"album": album_name, "album": album_name,
"duration": track.get('duration_seconds', 0), "duration": track.get('duration_seconds', track.get('length_seconds', 0)),
"cover_url": cover_url, "cover_url": cover_url,
"id": track.get('videoId', 'unknown'), "id": track_id,
"url": f"https://music.youtube.com/watch?v={track.get('videoId', '')}" "url": f"https://music.youtube.com/watch?v={track_id}"
}) })
# Process Playlist Info if not output_tracks:
p_thumbnails = playlist_data.get('thumbnails', []) print(f" Skipping empty item: {title}")
p_cover = p_thumbnails[-1]['url'] if p_thumbnails else "https://placehold.co/300x300" continue
category_playlists.append({ # Final Item Object
"id": playlist_data.get('id'), category_items.append({
"title": playlist_data.get('title'), "id": item_id,
"description": playlist_data.get('description', '') or f"Best of {category}", "title": title,
"cover_url": p_cover, "description": description or f"Best of {category_name}",
"tracks": output_tracks "cover_url": get_thumbnail(details.get('thumbnails', result.get('thumbnails'))),
"tracks": output_tracks,
"type": "album" if is_album else "playlist"
}) })
except Exception as e: except Exception as e:
print(f" Error processing playlist {playlist_id}: {e}") print(f" Error processing {item_id}: {e}")
continue continue
if category_playlists: if category_items:
browse_data[category] = category_playlists browse_data[category_name] = category_items
except Exception as e: 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 # Save to backend/data/browse_playlists.json
output_path = Path("backend/data/browse_playlists.json") output_path = Path("backend/data/browse_playlists.json")