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:
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,
}

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