fix: resolve Spotify URL to text title at start of task to fix fallback for both yt-dlp and Tubidy search
Build and Push Docker Images / build (api) (push) Successful in 1m16s
Details
Build and Push Docker Images / build (musicseerr) (push) Successful in 5m29s
Details
Build and Push Docker Images / build (web) (push) Successful in 1m36s
Details
Build and Push Docker Images / build (nextgen) (push) Successful in 7m17s
Details
Build and Push Docker Images / build (worker) (push) Successful in 11m4s
Details
Build and Push Docker Images / deploy (push) Successful in 25s
Details
Build and Push Docker Images / build (api) (push) Successful in 1m16s
Details
Build and Push Docker Images / build (musicseerr) (push) Successful in 5m29s
Details
Build and Push Docker Images / build (web) (push) Successful in 1m36s
Details
Build and Push Docker Images / build (nextgen) (push) Successful in 7m17s
Details
Build and Push Docker Images / build (worker) (push) Successful in 11m4s
Details
Build and Push Docker Images / deploy (push) Successful in 25s
Details
This commit is contained in:
parent
d7415de1e7
commit
7da517dffb
|
|
@ -227,6 +227,40 @@ def send_telegram_notification(chat_id: int, text: str):
|
||||||
logger.error(f"Failed to send Telegram notification: {e}")
|
logger.error(f"Failed to send Telegram notification: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def extract_spotify_track_title(url: str, proxy_url: str) -> str:
|
||||||
|
"""
|
||||||
|
Fetches the Spotify track page and extracts the track name and artists from the title tag.
|
||||||
|
"""
|
||||||
|
proxies = {"http": proxy_url, "https": proxy_url} if proxy_url else None
|
||||||
|
headers = {
|
||||||
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||||
|
}
|
||||||
|
logger.info(f"Extracting track title from Spotify page: {url}")
|
||||||
|
try:
|
||||||
|
response = requests.get(url, headers=headers, proxies=proxies, timeout=15)
|
||||||
|
if response.status_code == 200:
|
||||||
|
soup = BeautifulSoup(response.text, "html.parser")
|
||||||
|
title_tag = soup.find("title")
|
||||||
|
if title_tag:
|
||||||
|
title_text = title_tag.text
|
||||||
|
# Format is typically "Song Name - song and lyrics by Artist1, Artist2 | Spotify"
|
||||||
|
# Or "Song Name - song by Artist | Spotify"
|
||||||
|
if " - song " in title_text:
|
||||||
|
parts = title_text.split(" - song ", 1)
|
||||||
|
song_name = parts[0].strip()
|
||||||
|
artist_part = parts[1].split(" by ", 1)
|
||||||
|
if len(artist_part) > 1:
|
||||||
|
artists = artist_part[1].split(" | Spotify", 1)[0].strip()
|
||||||
|
return f"{artists} - {song_name}"
|
||||||
|
return song_name
|
||||||
|
elif " | Spotify" in title_text:
|
||||||
|
return title_text.split(" | Spotify")[0].strip()
|
||||||
|
return title_text.strip()
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to extract Spotify track title: {e}")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def symlink_existing_tracks(temp_dir: str):
|
def symlink_existing_tracks(temp_dir: str):
|
||||||
"""
|
"""
|
||||||
Finds all audio files recursively in /remote-music/music, extracts artist and title,
|
Finds all audio files recursively in /remote-music/music, extracts artist and title,
|
||||||
|
|
@ -311,6 +345,15 @@ def run_download_task(task_id: str, query: str, proxy_url: str, jobs_dict: dict,
|
||||||
logger.error(f"Failed to resolve proxy hostname: {e}")
|
logger.error(f"Failed to resolve proxy hostname: {e}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
download_successful = False
|
||||||
|
|
||||||
|
# If query is a Spotify track URL, resolve it to a text title first for fallback options
|
||||||
|
resolved_title = ""
|
||||||
|
if "spotify.com/track/" in query.lower():
|
||||||
|
resolved_title = extract_spotify_track_title(query, resolved_proxy_url)
|
||||||
|
if resolved_title:
|
||||||
|
logger.info(f"[{task_id}] Pre-resolved Spotify URL to text title: '{resolved_title}'")
|
||||||
|
|
||||||
is_tubidy = "tubidy" in query.lower()
|
is_tubidy = "tubidy" in query.lower()
|
||||||
is_spotify = "spotify.com" in query.lower()
|
is_spotify = "spotify.com" in query.lower()
|
||||||
is_youtube = "youtube.com" in query.lower() or "youtu.be" in query.lower()
|
is_youtube = "youtube.com" in query.lower() or "youtu.be" in query.lower()
|
||||||
|
|
@ -333,6 +376,7 @@ def run_download_task(task_id: str, query: str, proxy_url: str, jobs_dict: dict,
|
||||||
result = subprocess.run(cmd, cwd=temp_dir, env=env, capture_output=True, text=True)
|
result = subprocess.run(cmd, cwd=temp_dir, env=env, capture_output=True, text=True)
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
logger.info(f"[{task_id}] spotDL download succeeded.")
|
logger.info(f"[{task_id}] spotDL download succeeded.")
|
||||||
|
download_successful = True
|
||||||
else:
|
else:
|
||||||
logger.warning(f"[{task_id}] spotDL failed: {result.stderr or result.stdout}. Falling back to yt-dlp.")
|
logger.warning(f"[{task_id}] spotDL failed: {result.stderr or result.stdout}. Falling back to yt-dlp.")
|
||||||
# Clear state for P2 fallback
|
# Clear state for P2 fallback
|
||||||
|
|
@ -348,9 +392,12 @@ def run_download_task(task_id: str, query: str, proxy_url: str, jobs_dict: dict,
|
||||||
env["http_proxy"] = resolved_proxy_url
|
env["http_proxy"] = resolved_proxy_url
|
||||||
env["https_proxy"] = resolved_proxy_url
|
env["https_proxy"] = resolved_proxy_url
|
||||||
|
|
||||||
# If not a link, treat as search query
|
# If not a link or if resolved from Spotify track URL, treat as search query
|
||||||
search_query = query
|
search_query = query
|
||||||
if not query.startswith("http"):
|
if resolved_title:
|
||||||
|
search_query = f"ytsearch1:{resolved_title}"
|
||||||
|
logger.info(f"[{task_id}] Using resolved Spotify track title for yt-dlp search: '{search_query}'")
|
||||||
|
elif not query.startswith("http"):
|
||||||
search_query = f"ytsearch1:{query}"
|
search_query = f"ytsearch1:{query}"
|
||||||
|
|
||||||
cmd = [
|
cmd = [
|
||||||
|
|
@ -368,6 +415,7 @@ def run_download_task(task_id: str, query: str, proxy_url: str, jobs_dict: dict,
|
||||||
result = subprocess.run(cmd, cwd=temp_dir, env=env, capture_output=True, text=True)
|
result = subprocess.run(cmd, cwd=temp_dir, env=env, capture_output=True, text=True)
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
logger.info(f"[{task_id}] yt-dlp download succeeded.")
|
logger.info(f"[{task_id}] yt-dlp download succeeded.")
|
||||||
|
download_successful = True
|
||||||
else:
|
else:
|
||||||
logger.warning(f"[{task_id}] yt-dlp failed: {result.stderr or result.stdout}. Falling back to Tubidy Search.")
|
logger.warning(f"[{task_id}] yt-dlp failed: {result.stderr or result.stdout}. Falling back to Tubidy Search.")
|
||||||
|
|
||||||
|
|
@ -393,18 +441,23 @@ def run_download_task(task_id: str, query: str, proxy_url: str, jobs_dict: dict,
|
||||||
# Download file stream
|
# Download file stream
|
||||||
download_file_stream(mp3_url, dest_path, resolved_proxy_url)
|
download_file_stream(mp3_url, dest_path, resolved_proxy_url)
|
||||||
logger.info(f"[{task_id}] Tubidy download completed.")
|
logger.info(f"[{task_id}] Tubidy download completed.")
|
||||||
|
download_successful = True
|
||||||
|
|
||||||
# Tag metadata
|
# Tag metadata
|
||||||
tag_mp3(dest_path, filename)
|
tag_mp3(dest_path, filename)
|
||||||
logger.info(f"[{task_id}] Tubidy ID3 tagging complete.")
|
logger.info(f"[{task_id}] Tubidy ID3 tagging complete.")
|
||||||
|
|
||||||
# Priority 4: Tubidy Search Fallback (if spotDL and yt-dlp failed and no files are present)
|
# Priority 4: Tubidy Search Fallback (if spotDL and yt-dlp failed and no files are present)
|
||||||
downloaded_files = os.listdir(temp_dir)
|
if not download_successful and not is_tubidy:
|
||||||
if not downloaded_files and not is_tubidy:
|
|
||||||
jobs_dict[task_id]["priority_used"] = "Tubidy-Search"
|
jobs_dict[task_id]["priority_used"] = "Tubidy-Search"
|
||||||
logger.info(f"[{task_id}] Attempting Fallback Priority 4: Tubidy Search Scraper for '{query}'")
|
logger.info(f"[{task_id}] Attempting Fallback Priority 4: Tubidy Search Scraper for '{query}'")
|
||||||
|
|
||||||
search_results = search_tubidy(query, resolved_proxy_url)
|
search_query = query
|
||||||
|
if resolved_title:
|
||||||
|
search_query = resolved_title
|
||||||
|
logger.info(f"[{task_id}] Using pre-resolved Spotify track title for Tubidy search: '{search_query}'")
|
||||||
|
|
||||||
|
search_results = search_tubidy(search_query, resolved_proxy_url)
|
||||||
if search_results:
|
if search_results:
|
||||||
first_result = search_results[0]
|
first_result = search_results[0]
|
||||||
logger.info(f"[{task_id}] Found Tubidy match: '{first_result['title']}' (ID: {first_result['id']})")
|
logger.info(f"[{task_id}] Found Tubidy match: '{first_result['title']}' (ID: {first_result['id']})")
|
||||||
|
|
@ -430,14 +483,18 @@ def run_download_task(task_id: str, query: str, proxy_url: str, jobs_dict: dict,
|
||||||
# Tag metadata
|
# Tag metadata
|
||||||
tag_mp3(dest_path, filename)
|
tag_mp3(dest_path, filename)
|
||||||
logger.info(f"[{task_id}] Tubidy fallback ID3 tagging complete.")
|
logger.info(f"[{task_id}] Tubidy fallback ID3 tagging complete.")
|
||||||
|
download_successful = True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[{task_id}] Tubidy fallback download failed: {e}")
|
logger.error(f"[{task_id}] Tubidy fallback download failed: {e}")
|
||||||
raise Exception(f"All download options (spotDL, yt-dlp, Tubidy) failed. Last error: {e}")
|
raise Exception(f"All download options (spotDL, yt-dlp, Tubidy) failed. Last error: {e}")
|
||||||
else:
|
else:
|
||||||
logger.error(f"[{task_id}] No Tubidy search results found for query '{query}'")
|
logger.error(f"[{task_id}] No Tubidy search results found for query '{search_query}'")
|
||||||
raise Exception("All download options (spotDL, yt-dlp, Tubidy) failed. No Tubidy search results found.")
|
raise Exception("All download options (spotDL, yt-dlp, Tubidy) failed. No Tubidy search results found.")
|
||||||
|
|
||||||
# Verify files and move to /remote-music/music
|
# Verify files and move to /remote-music/music
|
||||||
|
if not download_successful:
|
||||||
|
raise Exception("Ingestion finished but no download was successful.")
|
||||||
|
|
||||||
downloaded_files = os.listdir(temp_dir)
|
downloaded_files = os.listdir(temp_dir)
|
||||||
actual_files = [f for f in downloaded_files if not os.path.islink(os.path.join(temp_dir, f))]
|
actual_files = [f for f in downloaded_files if not os.path.islink(os.path.join(temp_dir, f))]
|
||||||
|
|
||||||
|
|
@ -450,7 +507,7 @@ def run_download_task(task_id: str, query: str, proxy_url: str, jobs_dict: dict,
|
||||||
send_telegram_notification(chat_id, f"✅ Successfully ingested request: '{query}'\n\n(All tracks already existed - skipped duplicates)")
|
send_telegram_notification(chat_id, f"✅ Successfully ingested request: '{query}'\n\n(All tracks already existed - skipped duplicates)")
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
raise Exception("Ingestion finished but no files were found in local temp folder.")
|
raise Exception("Ingestion finished successfully but no files were found in local temp folder.")
|
||||||
|
|
||||||
os.makedirs("/remote-music/music", exist_ok=True)
|
os.makedirs("/remote-music/music", exist_ok=True)
|
||||||
moved_files = []
|
moved_files = []
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue