feat(musicseerr): implement Last.fm recommendation auto-downloader
Build and Push Docker Images / build (api) (push) Failing after 2m37s
Details
Build and Push Docker Images / build (musicseerr) (push) Successful in 8m32s
Details
Build and Push Docker Images / build (nextgen) (push) Successful in 8m23s
Details
Build and Push Docker Images / build (web) (push) Successful in 5m19s
Details
Build and Push Docker Images / build (worker) (push) Successful in 12m58s
Details
Build and Push Docker Images / deploy (push) Has been skipped
Details
Build and Push Docker Images / build (api) (push) Failing after 2m37s
Details
Build and Push Docker Images / build (musicseerr) (push) Successful in 8m32s
Details
Build and Push Docker Images / build (nextgen) (push) Successful in 8m23s
Details
Build and Push Docker Images / build (web) (push) Successful in 5m19s
Details
Build and Push Docker Images / build (worker) (push) Successful in 12m58s
Details
Build and Push Docker Images / deploy (push) Has been skipped
Details
This commit is contained in:
parent
dff1fd4d04
commit
07b89e4018
|
|
@ -456,3 +456,121 @@ def index_page():
|
|||
</html>
|
||||
"""
|
||||
return HTMLResponse(content=html_content)
|
||||
|
||||
|
||||
def run_lastfm_auto_download(username: str):
|
||||
url = f"https://lfm.xiffy.nl/{username}/recommended"
|
||||
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"Fetching Last.fm recommendations feed from {url}")
|
||||
try:
|
||||
response = requests.get(url, headers=headers, proxies=proxies, timeout=20)
|
||||
response.raise_for_status()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to fetch Last.fm recommendations feed: {e}")
|
||||
return
|
||||
|
||||
try:
|
||||
import xml.etree.ElementTree as ET
|
||||
root = ET.fromstring(response.content)
|
||||
items = root.findall(".//item")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to parse Last.fm recommendations XML: {e}")
|
||||
return
|
||||
|
||||
logger.info(f"Found {len(items)} items in Last.fm feed.")
|
||||
|
||||
# Import tasks dynamically to avoid circular import
|
||||
from tasks import run_download_task
|
||||
import re
|
||||
|
||||
def is_track_already_downloaded(track_query: str) -> bool:
|
||||
remote_base = "/remote-music/music"
|
||||
if not os.path.isdir(remote_base):
|
||||
return False
|
||||
clean_query = re.sub(r'[^\w\s-]', '', track_query).lower()
|
||||
parts = track_query.split(" - ", 1)
|
||||
if len(parts) == 2:
|
||||
artist, title = parts[0].strip().lower(), parts[1].strip().lower()
|
||||
artist_clean = re.sub(r'[^\w\s-]', '', artist)
|
||||
title_clean = re.sub(r'[^\w\s-]', '', title)
|
||||
else:
|
||||
artist_clean = clean_query
|
||||
title_clean = clean_query
|
||||
|
||||
for r, d, files in os.walk(remote_base):
|
||||
for file in files:
|
||||
if file.lower().endswith((".mp3", ".flac", ".m4a", ".ogg", ".wav", ".opus")):
|
||||
file_lower = file.lower()
|
||||
if len(parts) == 2:
|
||||
if artist_clean in file_lower and title_clean in file_lower:
|
||||
return True
|
||||
else:
|
||||
if clean_query in file_lower:
|
||||
return True
|
||||
return False
|
||||
|
||||
for item in items:
|
||||
title_el = item.find("title")
|
||||
if title_el is not None and title_el.text:
|
||||
track_query = title_el.text.strip()
|
||||
|
||||
# Check if already queued
|
||||
already_queued = any(job.get("query") == f"[Last.fm Auto] {track_query}" for job in JOBS.values())
|
||||
if already_queued:
|
||||
continue
|
||||
|
||||
if is_track_already_downloaded(track_query):
|
||||
logger.info(f"Skipping Last.fm auto-download for '{track_query}' (already exists).")
|
||||
continue
|
||||
|
||||
logger.info(f"Queueing Last.fm recommendation: '{track_query}'")
|
||||
task_id = str(uuid.uuid4())
|
||||
JOBS[task_id] = {
|
||||
"id": task_id,
|
||||
"query": f"[Last.fm Auto] {track_query}",
|
||||
"status": "pending",
|
||||
"priority_used": None,
|
||||
"files": [],
|
||||
"error": None
|
||||
}
|
||||
try:
|
||||
run_download_task(task_id, track_query, PROXY_URL, JOBS)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to process auto-download job {task_id}: {e}")
|
||||
|
||||
|
||||
@app.post("/lastfm/trigger")
|
||||
def trigger_lastfm_sync(background_tasks: BackgroundTasks):
|
||||
username = os.getenv("LASTFM_AUTO_DOWNLOAD_USER")
|
||||
if not username:
|
||||
return JSONResponse(status_code=400, content={"error": "Last.fm auto-download is not enabled (LASTFM_AUTO_DOWNLOAD_USER not configured)"})
|
||||
background_tasks.add_task(run_lastfm_auto_download, username)
|
||||
return {"status": "triggered", "user": username}
|
||||
|
||||
|
||||
import asyncio
|
||||
|
||||
async def lastfm_scheduler():
|
||||
# Wait a short bit after startup before the first check
|
||||
await asyncio.sleep(30)
|
||||
while True:
|
||||
username = os.getenv("LASTFM_AUTO_DOWNLOAD_USER")
|
||||
if username:
|
||||
try:
|
||||
logger.info(f"Triggering scheduled Last.fm recommendations fetch for '{username}'...")
|
||||
loop = asyncio.get_event_loop()
|
||||
await loop.run_in_executor(None, run_lastfm_auto_download, username)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in Last.fm scheduler task: {e}")
|
||||
|
||||
interval_hours = float(os.getenv("LASTFM_AUTO_DOWNLOAD_INTERVAL_HOURS", "12"))
|
||||
await asyncio.sleep(interval_hours * 3600)
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup_event():
|
||||
asyncio.create_task(lastfm_scheduler())
|
||||
|
|
|
|||
|
|
@ -64,6 +64,10 @@ spec:
|
|||
name: musicseerr-secrets
|
||||
key: telegram-bot-token
|
||||
optional: true
|
||||
- name: LASTFM_AUTO_DOWNLOAD_USER
|
||||
value: "fchinex"
|
||||
- name: LASTFM_AUTO_DOWNLOAD_INTERVAL_HOURS
|
||||
value: "12"
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8000
|
||||
|
|
|
|||
Loading…
Reference in New Issue