58 lines
1.6 KiB
Python
58 lines
1.6 KiB
Python
|
|
import asyncio
|
||
|
|
import logging
|
||
|
|
|
||
|
|
import httpx
|
||
|
|
|
||
|
|
logger = logging.getLogger(__name__)
|
||
|
|
|
||
|
|
ITUNES_SEARCH_URL = "https://itunes.apple.com/search"
|
||
|
|
|
||
|
|
# Rate limit: ~20 req/min for iTunes
|
||
|
|
_last_request_time = 0.0
|
||
|
|
_min_interval = 3.0 # 3s between requests
|
||
|
|
|
||
|
|
|
||
|
|
async def _rate_limit():
|
||
|
|
global _last_request_time
|
||
|
|
now = asyncio.get_event_loop().time()
|
||
|
|
elapsed = now - _last_request_time
|
||
|
|
if elapsed < _min_interval:
|
||
|
|
await asyncio.sleep(_min_interval - elapsed)
|
||
|
|
_last_request_time = asyncio.get_event_loop().time()
|
||
|
|
|
||
|
|
|
||
|
|
async def search_track(artist: str, title: str) -> dict | None:
|
||
|
|
"""Search iTunes for a track and return preview info, or None if not found."""
|
||
|
|
await _rate_limit()
|
||
|
|
|
||
|
|
query = f"{artist} {title}"
|
||
|
|
async with httpx.AsyncClient(timeout=10) as client:
|
||
|
|
resp = await client.get(
|
||
|
|
ITUNES_SEARCH_URL,
|
||
|
|
params={
|
||
|
|
"term": query,
|
||
|
|
"media": "music",
|
||
|
|
"entity": "song",
|
||
|
|
"limit": 5,
|
||
|
|
},
|
||
|
|
)
|
||
|
|
resp.raise_for_status()
|
||
|
|
data = resp.json()
|
||
|
|
|
||
|
|
results = data.get("results", [])
|
||
|
|
if not results:
|
||
|
|
return None
|
||
|
|
|
||
|
|
# Find best match (simple: first result with a preview URL)
|
||
|
|
for r in results:
|
||
|
|
if r.get("previewUrl"):
|
||
|
|
return {
|
||
|
|
"track_id": r["trackId"],
|
||
|
|
"preview_url": r["previewUrl"],
|
||
|
|
"apple_music_id": str(r.get("trackId", "")),
|
||
|
|
"duration_ms": r.get("trackTimeMillis"),
|
||
|
|
"genre": r.get("primaryGenreName"),
|
||
|
|
}
|
||
|
|
|
||
|
|
return None
|