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>
|
</html>
|
||||||
"""
|
"""
|
||||||
return HTMLResponse(content=html_content)
|
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
|
name: musicseerr-secrets
|
||||||
key: telegram-bot-token
|
key: telegram-bot-token
|
||||||
optional: true
|
optional: true
|
||||||
|
- name: LASTFM_AUTO_DOWNLOAD_USER
|
||||||
|
value: "fchinex"
|
||||||
|
- name: LASTFM_AUTO_DOWNLOAD_INTERVAL_HOURS
|
||||||
|
value: "12"
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
containerPort: 8000
|
containerPort: 8000
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue