feat: add real yt-dlp client and opt-in live test; ffmpeg in worker image
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
FROM python:3.12-slim
|
FROM python:3.12-slim
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends ffmpeg && rm -rf /var/lib/apt/lists/*
|
||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
COPY lyra_worker ./lyra_worker
|
COPY lyra_worker ./lyra_worker
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import os
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
import yt_dlp
|
||||||
|
|
||||||
|
|
||||||
|
class YtDlpClient:
|
||||||
|
"""Real YtClient backed by yt_dlp. Not unit-tested offline; see test_youtube_live.py."""
|
||||||
|
|
||||||
|
def search_album(self, artist: str, album: str) -> list[dict]:
|
||||||
|
query = f"ytsearch5:{artist} {album} full album"
|
||||||
|
opts = {
|
||||||
|
"quiet": True,
|
||||||
|
"no_warnings": True,
|
||||||
|
"extract_flat": "in_playlist",
|
||||||
|
"skip_download": True,
|
||||||
|
}
|
||||||
|
with yt_dlp.YoutubeDL(opts) as ydl:
|
||||||
|
info = ydl.extract_info(query, download=False)
|
||||||
|
results: list[dict] = []
|
||||||
|
for e in (info.get("entries") or []):
|
||||||
|
if not e:
|
||||||
|
continue
|
||||||
|
results.append(
|
||||||
|
{
|
||||||
|
"source_ref": e.get("url") or e.get("webpage_url") or e.get("id"),
|
||||||
|
"title": e.get("title") or "",
|
||||||
|
"artist": e.get("uploader") or e.get("channel") or "",
|
||||||
|
"track_count": e.get("playlist_count") or 1,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return results
|
||||||
|
|
||||||
|
def download(self, source_ref: str, dest: str, on_progress: Callable[[float], None]) -> dict:
|
||||||
|
os.makedirs(dest, exist_ok=True)
|
||||||
|
|
||||||
|
def hook(d: dict) -> None:
|
||||||
|
if d.get("status") == "downloading":
|
||||||
|
total = d.get("total_bytes") or d.get("total_bytes_estimate")
|
||||||
|
if total:
|
||||||
|
on_progress(min(1.0, d.get("downloaded_bytes", 0) / total))
|
||||||
|
elif d.get("status") == "finished":
|
||||||
|
on_progress(1.0)
|
||||||
|
|
||||||
|
opts = {
|
||||||
|
"quiet": True,
|
||||||
|
"no_warnings": True,
|
||||||
|
"format": "bestaudio/best",
|
||||||
|
"outtmpl": os.path.join(dest, "%(playlist_index)s-%(title)s.%(ext)s"),
|
||||||
|
"progress_hooks": [hook],
|
||||||
|
"postprocessors": [{"key": "FFmpegExtractAudio", "preferredcodec": "opus"}],
|
||||||
|
}
|
||||||
|
with yt_dlp.YoutubeDL(opts) as ydl:
|
||||||
|
info = ydl.extract_info(source_ref, download=True)
|
||||||
|
entries = info.get("entries") if info.get("entries") is not None else [info]
|
||||||
|
return {"track_count": len([e for e in entries if e]), "path": dest}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
psycopg[binary]>=3.2,<4
|
psycopg[binary]>=3.2,<4
|
||||||
pytest>=8.3,<9
|
pytest>=8.3,<9
|
||||||
cryptography>=42,<46
|
cryptography>=42,<46
|
||||||
|
yt-dlp>=2024.1
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from lyra_worker.adapters._ytdlp import YtDlpClient
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.skipif(
|
||||||
|
not os.environ.get("LYRA_LIVE_TESTS"),
|
||||||
|
reason="live network test; set LYRA_LIVE_TESTS=1 to run",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_live_search_returns_results():
|
||||||
|
results = YtDlpClient().search_album("Radiohead", "In Rainbows")
|
||||||
|
assert len(results) >= 1
|
||||||
|
assert all(r["source_ref"] for r in results)
|
||||||
Reference in New Issue
Block a user