symphony / collector_python_backup /fill_zero_songs.py
Vaibhav Pharmacy
Optimize frontend speed and add Go backend
5a30a94
Raw History Blame Contribute Delete
7.1 kB
"""Fill artists with 0 songs using YouTube Data API v3 search + ytmusicapi fallback."""
import sys
import os
import time
import hashlib
sys.path.insert(0, os.path.dirname(__file__))
from dotenv import load_dotenv
load_dotenv(os.path.join(os.path.dirname(__file__), "..", ".env"))
from db import get_connection, init_db
# YouTube Data API
API_KEY = os.getenv("YOUTUBE_API_KEY")
if not API_KEY:
print("ERROR: YOUTUBE_API_KEY not set in .env")
sys.exit(1)
try:
from googleapiclient.discovery import build
youtube = build("youtube", "v3", developerKey=API_KEY)
print(f"✅ YouTube Data API v3 initialized")
except ImportError:
print("ERROR: google-api-python-client not installed. Run: pip install google-api-python-client")
sys.exit(1)
# ytmusicapi as second attempt
try:
from ytmusicapi import YTMusic
ytmusic = YTMusic()
print("✅ ytmusicapi initialized")
except:
ytmusic = None
print("⚠️ ytmusicapi not available, using YouTube API only")
def generate_song_id(platform, platform_id):
return hashlib.md5(f"{platform}:{platform_id}".encode()).hexdigest()
def search_youtube_api(artist_name, max_results=30):
"""Search YouTube Data API for an artist's music videos."""
songs = []
try:
# Search for music videos by artist
request = youtube.search().list(
q=f"{artist_name} official music video",
part="snippet",
type="video",
videoCategoryId="10", # Music category
maxResults=min(max_results, 50),
order="viewCount",
)
response = request.execute()
video_ids = []
video_map = {}
for item in response.get("items", []):
vid = item["id"]["videoId"]
snippet = item["snippet"]
title = snippet.get("title", "Unknown")
# Filter: title should contain something related to the artist
if artist_name.lower().split()[0] not in title.lower() and artist_name.lower() not in snippet.get("channelTitle", "").lower():
continue
video_ids.append(vid)
video_map[vid] = {
"title": title,
"video_id": vid,
"thumbnail": snippet.get("thumbnails", {}).get("high", {}).get("url", ""),
"release_date": snippet.get("publishedAt", "")[:10],
"album": None,
}
# Get stats for matching videos
if video_ids:
stats_request = youtube.videos().list(
part="statistics",
id=",".join(video_ids[:50]),
)
stats_response = stats_request.execute()
for item in stats_response.get("items", []):
vid = item["id"]
if vid in video_map:
stats = item.get("statistics", {})
video_map[vid]["views"] = int(stats.get("viewCount", 0))
video_map[vid]["likes"] = int(stats.get("likeCount", 0))
video_map[vid]["comments"] = int(stats.get("commentCount", 0))
songs = list(video_map.values())
except Exception as e:
print(f" [yt-api] Error: {e}")
return songs
def search_ytmusic(artist_name):
"""Fallback: search ytmusicapi for songs."""
if not ytmusic:
return []
songs = []
try:
results = ytmusic.search(artist_name, filter="songs", limit=30)
for item in results:
vid = item.get("videoId")
if not vid:
continue
artists = [a.get("name", "") for a in item.get("artists", [])]
if artist_name.lower() not in " ".join(artists).lower():
continue
album = item.get("album", {})
songs.append({
"title": item.get("title", "Unknown"),
"video_id": vid,
"album": album.get("name") if album else None,
"views": 0,
"likes": 0,
"comments": 0,
"thumbnail": (item.get("thumbnails", [{}])[-1].get("url", "") if item.get("thumbnails") else ""),
"release_date": "",
})
except Exception as e:
print(f" [ytmusic-search] Error: {e}")
return songs
def main():
init_db()
conn = get_connection()
# Get all artists with 0 songs
zero_artists = conn.execute("""
SELECT a.id, a.name FROM artists a
WHERE (SELECT COUNT(*) FROM songs WHERE artist_id = a.id) = 0
ORDER BY a.name
""").fetchall()
print(f"\n{'='*60}")
print(f"Populating {len(zero_artists)} artists with 0 songs")
print(f"Using YouTube Data API v3 + ytmusicapi search fallback")
print(f"{'='*60}\n")
total_songs = 0
filled = 0
for idx, artist in enumerate(zero_artists):
artist_id = artist["id"]
name = artist["name"]
print(f"[{idx+1}/{len(zero_artists)}] {name}")
# Try YouTube Data API first
songs = search_youtube_api(name)
source = "yt-api"
# Fallback to ytmusic search if nothing found
if not songs:
songs = search_ytmusic(name)
source = "ytmusic-search"
if songs:
for song in songs:
vid = song.get("video_id")
if not vid:
continue
song_id = generate_song_id("youtube", vid)
views = song.get("views", 0) or 0
likes = song.get("likes", 0) or 0
comments = song.get("comments", 0) or 0
conn.execute("""
INSERT OR IGNORE INTO songs (id, artist_id, title, platform, platform_id, album_name, release_date, thumbnail_url, created_at)
VALUES (?, ?, ?, 'youtube', ?, ?, ?, ?, datetime('now'))
""", (song_id, artist_id, song.get("title", "Unknown"), vid, song.get("album") or "", song.get("release_date") or "", song.get("thumbnail") or ""))
# Only add snapshot if the song actually belongs to this artist
exists = conn.execute("SELECT 1 FROM songs WHERE id = ? AND artist_id = ?", (song_id, artist_id)).fetchone()
if exists:
conn.execute("""
INSERT INTO play_snapshots (song_id, play_count, like_count, comment_count, platform, collected_at)
VALUES (?, ?, ?, ?, 'youtube', datetime('now'))
""", (song_id, views, likes, comments))
conn.commit()
total_songs += len(songs)
filled += 1
print(f" ✅ [{source}] {len(songs)} songs (views: {sum(s.get('views', 0) or 0 for s in songs):,})")
else:
print(f" ❌ No songs found")
# Rate limit: YouTube API has 10K daily quota
time.sleep(0.5)
conn.close()
print(f"\n{'='*60}")
print(f"Done! Filled {filled}/{len(zero_artists)} artists with {total_songs} total songs")
print(f"{'='*60}")
if __name__ == "__main__":
main()