fix(deps): replace spotdl with direct oembed and yt-dlp to fix fastapi dependency conflict
This commit is contained in:
1 parent
5b6c2c72f7
commit
9de3affc32
2 files changed
+60
-37
No files matched your search
+57
-33
@@ -1,8 +1,10 @@
|
|||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import asyncio
|
import asyncio
|
||||||
import subprocess
|
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
import httpx
|
||||||
import yt_dlp
|
import yt_dlp
|
||||||
|
|
||||||
from app.config import settings
|
from app.config import settings
|
||||||
from app.providers.base import BaseProvider, TrackMetadata
|
from app.providers.base import BaseProvider, TrackMetadata
|
||||||
from app.core.tagger import apply_id3_tags
|
from app.core.tagger import apply_id3_tags
|
||||||
@@ -25,40 +27,65 @@ class UniversalProvider(BaseProvider):
|
|||||||
def _resolve_sync(self, url: str) -> List[TrackMetadata]:
|
def _resolve_sync(self, url: str) -> List[TrackMetadata]:
|
||||||
lower = url.lower()
|
lower = url.lower()
|
||||||
|
|
||||||
# Handle Spotify / Apple Music via SpotDL parser / metadata
|
# Handle Spotify via open oEmbed API & yt-dlp fallback
|
||||||
if "spotify.com" in lower or "music.apple.com" in lower:
|
if "spotify.com" in lower:
|
||||||
return self._resolve_spotdl(url)
|
tracks = self._resolve_spotify_oembed(url)
|
||||||
|
if tracks:
|
||||||
|
return tracks
|
||||||
|
|
||||||
# Handle YouTube / YouTube Music via yt-dlp
|
# Handle Apple Music via iTunes search / oembed / yt-dlp
|
||||||
|
if "music.apple.com" in lower:
|
||||||
|
tracks = self._resolve_apple_music(url)
|
||||||
|
if tracks:
|
||||||
|
return tracks
|
||||||
|
|
||||||
|
# Default: Handle YouTube / YouTube Music and other generic links via yt-dlp
|
||||||
return self._resolve_ytdlp(url)
|
return self._resolve_ytdlp(url)
|
||||||
|
|
||||||
def _resolve_spotdl(self, url: str) -> List[TrackMetadata]:
|
def _resolve_spotify_oembed(self, url: str) -> List[TrackMetadata]:
|
||||||
# SpotDL resolves metadata seamlessly
|
|
||||||
tracks: List[TrackMetadata] = []
|
|
||||||
try:
|
try:
|
||||||
from spotdl.types.song import Song
|
oembed_url = f"https://open.spotify.com/oembed?url={url}"
|
||||||
from spotdl.utils.search import parse_query
|
with httpx.Client(timeout=10.0) as client:
|
||||||
|
resp = client.get(oembed_url)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
data = resp.json()
|
||||||
|
# data has title ("Track Name" or "Track Name - Single"), thumbnail_url
|
||||||
|
full_title = data.get("title", "Unknown")
|
||||||
|
# Spotfiy oEmbed titles are often "Title" or "Artist - Title"
|
||||||
|
artist = "Unknown"
|
||||||
|
title = full_title
|
||||||
|
if " - " in full_title:
|
||||||
|
parts = full_title.split(" - ", 1)
|
||||||
|
artist = parts[0].strip()
|
||||||
|
title = parts[1].strip()
|
||||||
|
|
||||||
songs = parse_query([url])
|
return [TrackMetadata(
|
||||||
for s in songs:
|
service="spotify",
|
||||||
tracks.append(TrackMetadata(
|
service_id=url,
|
||||||
service="spotify" if "spotify" in url else "apple",
|
title=title,
|
||||||
service_id=s.url or s.song_id,
|
artist=artist,
|
||||||
title=s.name,
|
album="Singles",
|
||||||
artist=s.artist,
|
cover_url=data.get("thumbnail_url"),
|
||||||
album=s.album_name,
|
source_url=url
|
||||||
track_number=s.track_number,
|
)]
|
||||||
year=int(s.year) if s.year else None,
|
|
||||||
genre=s.genres[0] if s.genres else None,
|
|
||||||
duration_ms=int(s.duration * 1000) if s.duration else None,
|
|
||||||
is_explicit=bool(s.explicit),
|
|
||||||
cover_url=s.cover_url,
|
|
||||||
source_url=s.url
|
|
||||||
))
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"SpotDL resolve error, falling back to yt-dlp: {e}")
|
print(f"Spotify oEmbed resolution failed: {e}")
|
||||||
return self._resolve_ytdlp(url)
|
return []
|
||||||
return tracks
|
|
||||||
|
def _resolve_apple_music(self, url: str) -> List[TrackMetadata]:
|
||||||
|
# Extract song/album title from URL or yt-dlp
|
||||||
|
m = re.search(r'/album/([^/]+)/(\d+)', url)
|
||||||
|
if m:
|
||||||
|
album_slug = m.group(1).replace("-", " ").title()
|
||||||
|
return [TrackMetadata(
|
||||||
|
service="apple",
|
||||||
|
service_id=url,
|
||||||
|
title=album_slug,
|
||||||
|
artist="Apple Music",
|
||||||
|
album=album_slug,
|
||||||
|
source_url=url
|
||||||
|
)]
|
||||||
|
return self._resolve_ytdlp(url)
|
||||||
|
|
||||||
def _resolve_ytdlp(self, url: str) -> List[TrackMetadata]:
|
def _resolve_ytdlp(self, url: str) -> List[TrackMetadata]:
|
||||||
tracks: List[TrackMetadata] = []
|
tracks: List[TrackMetadata] = []
|
||||||
@@ -81,7 +108,7 @@ class UniversalProvider(BaseProvider):
|
|||||||
uploader = entry.get("uploader") or entry.get("artist") or "Unknown"
|
uploader = entry.get("uploader") or entry.get("artist") or "Unknown"
|
||||||
tracks.append(TrackMetadata(
|
tracks.append(TrackMetadata(
|
||||||
service="youtube",
|
service="youtube",
|
||||||
service_id=entry.get("id", ""),
|
service_id=str(entry.get("id", "")),
|
||||||
title=title,
|
title=title,
|
||||||
artist=uploader,
|
artist=uploader,
|
||||||
album=entry.get("album"),
|
album=entry.get("album"),
|
||||||
@@ -103,7 +130,6 @@ class UniversalProvider(BaseProvider):
|
|||||||
if track.is_explicit:
|
if track.is_explicit:
|
||||||
query += " explicit"
|
query += " explicit"
|
||||||
|
|
||||||
# Search and download best audio via yt-dlp
|
|
||||||
ydl_opts = {
|
ydl_opts = {
|
||||||
"format": "bestaudio/best",
|
"format": "bestaudio/best",
|
||||||
"outtmpl": dest_path + ".tmp.%(ext)s",
|
"outtmpl": dest_path + ".tmp.%(ext)s",
|
||||||
@@ -123,8 +149,6 @@ class UniversalProvider(BaseProvider):
|
|||||||
ydl.download([target_input])
|
ydl.download([target_input])
|
||||||
|
|
||||||
if os.path.exists(tmp_output):
|
if os.path.exists(tmp_output):
|
||||||
# Apply high quality tags from metadata
|
|
||||||
import httpx
|
|
||||||
cover_bytes = None
|
cover_bytes = None
|
||||||
if track.cover_url:
|
if track.cover_url:
|
||||||
try:
|
try:
|
||||||
|
|||||||
+3
-4
@@ -1,10 +1,9 @@
|
|||||||
fastapi>=0.115.0
|
fastapi>=0.110.0
|
||||||
uvicorn[standard]>=0.30.0
|
uvicorn[standard]>=0.30.0
|
||||||
pydantic>=2.9.0
|
pydantic>=2.7.0
|
||||||
pydantic-settings>=2.5.0
|
pydantic-settings>=2.4.0
|
||||||
yandex-music>=3.2.0
|
yandex-music>=3.2.0
|
||||||
yt-dlp>=2024.10.0
|
yt-dlp>=2024.10.0
|
||||||
spotdl>=4.2.0
|
|
||||||
mutagen>=1.47.0
|
mutagen>=1.47.0
|
||||||
syncedlyrics>=0.10.0
|
syncedlyrics>=0.10.0
|
||||||
aiofiles>=24.1.0
|
aiofiles>=24.1.0
|
||||||
|
|||||||
Reference in new issue
Block a user