diff --git a/main.py b/main.py index 056e7da..65a2796 100755 --- a/main.py +++ b/main.py @@ -12,7 +12,6 @@ import pickle import re import sqlite3 import time -import traceback from google.auth.transport.requests import Request from google_auth_oauthlib.flow import InstalledAppFlow @@ -356,9 +355,8 @@ async def message_callback(conn, cursor, youtube, client, room, event): ) print(f"Added track to this week's playlist: {link}") if recent: - task = asyncio.create_task(process_radio_track(link, video_id, title)) - background_tasks.add(task) - task.add_done_callback(background_tasks.discard) + asyncio.create_task(process_radio_track(link, video_id, title)) + def in_playlist(cursor, video_id, playlist_id): """Checks if video is in playlist.""" @@ -446,99 +444,92 @@ async def backwards_sync(conn, cursor, youtube, client, room, start_token): async def process_radio_track(video_link, video_id, title): """Downloads native audio and pushes it to AzuraCast API in the background.""" + if not AZURACAST_API_KEY: + return + + # Sanitize title for the local filesystem + safe_title = "".join(c for c in title if c.isalnum() or c in " -_").strip() + base_filename = f"{safe_title}_{video_id}" + base_filepath = os.path.join(DATA_DIR, base_filename) + + print(f"📻 Downloading audio for radio: {title}") + + # 1. Download with yt-dlp + dl_cmd = [ + "yt-dlp", + "--extract-audio", + "--output", f"{base_filepath}.%(ext)s", + video_link + ] + process = await asyncio.create_subprocess_exec( + *dl_cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + if process.returncode != 0: + print(f"❌ yt-dlp crashed! Error:\n{stderr.decode().strip()}") + return + + # Check for the file using glob since the extension (.m4a, .webm, .opus) is dynamic + downloaded_files = glob.glob(f"{base_filepath}.*") + + if not downloaded_files: + print(f"❌ Failed to download audio for {title}") + return + + filepath = downloaded_files[0] + + print(f"📻 Uploading to AzuraCast: {title}") + + # 2. Upload to AzuraCast using curl + upload_cmd = [ + "curl", + "-s", + "-X", + "POST", + f"{AZURACAST_URL}/api/station/{AZURACAST_STATION_ID}/files", + "-H", + f"X-API-Key: {AZURACAST_API_KEY}", + "-F", + f"file=@{filepath}", + ] + + curl_proc = await asyncio.create_subprocess_exec( + *upload_cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, _ = await curl_proc.communicate() + + # 3. Extract unique_id and push to the live queue try: - print(f"🚀 DEBUG: Background task started for {title}") + response = json.loads(stdout.decode()) + unique_id = response.get("unique_id") - if not AZURACAST_API_KEY: - print("❌ Error: AZURACAST_API_KEY is empty or missing.") - return + if unique_id: + req_cmd = [ + "curl", + "-s", + "-X", + "POST", + f"{AZURACAST_URL}/api/station/{AZURACAST_STATION_ID}/request/{unique_id}", + "-H", + f"X-API-Key: {AZURACAST_API_KEY}", + ] + req_proc = await asyncio.create_subprocess_exec( + *req_cmd, + stdout=asyncio.subprocess.DEVNULL, + stderr=asyncio.subprocess.DEVNULL, + ) + await req_proc.communicate() + print(f"✅ Queued on radio: {title}") + else: + print(f"⚠️ Uploaded, but no unique_id returned. Response: {stdout.decode()}") + except json.JSONDecodeError: + print(f"❌ Failed to parse AzuraCast response: {stdout.decode()}") - # Sanitize title for the local filesystem - safe_title = "".join(c for c in title if c.isalnum() or c in " -_").strip() - base_filename = f"{safe_title}_{video_id}" - base_filepath = os.path.join(DATA_DIR, base_filename) - - print(f"📻 Downloading audio for radio: {title}") - - # 1. Download with yt-dlp - dl_cmd = [ - "yt-dlp", - "--extract-audio", - "--output", f"{base_filepath}.%(ext)s", - video_link - ] - process = await asyncio.create_subprocess_exec( - *dl_cmd, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE - ) - stdout, stderr = await process.communicate() - if process.returncode != 0: - print(f"❌ yt-dlp crashed! Error:\n{stderr.decode().strip()}") - return - - # Check for the file using glob - downloaded_files = glob.glob(f"{base_filepath}.*") - - if not downloaded_files: - print(f"❌ Failed to download audio for {title}") - return - - filepath = downloaded_files[0] - - print(f"📻 Uploading to AzuraCast: {title}") - - # 2. Upload to AzuraCast using curl - upload_cmd = [ - "curl", - "-s", - "-X", - "POST", - f"{AZURACAST_URL}/api/station/{AZURACAST_STATION_ID}/files", - "-H", - f"X-API-Key: {AZURACAST_API_KEY}", - "-F", - f"file=@{filepath}", - ] - - curl_proc = await asyncio.create_subprocess_exec( - *upload_cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE - ) - stdout, _ = await curl_proc.communicate() - - # 3. Extract unique_id and queue - try: - response = json.loads(stdout.decode()) - unique_id = response.get("unique_id") - - if unique_id: - req_cmd = [ - "curl", - "-s", - "-X", - "POST", - f"{AZURACAST_URL}/api/station/{AZURACAST_STATION_ID}/request/{unique_id}", - "-H", - f"X-API-Key: {AZURACAST_API_KEY}", - ] - req_proc = await asyncio.create_subprocess_exec( - *req_cmd, - stdout=asyncio.subprocess.DEVNULL, - stderr=asyncio.subprocess.DEVNULL, - ) - await req_proc.communicate() - print(f"✅ Queued on radio: {title}") - else: - print(f"⚠️ Uploaded, but no unique_id returned: {stdout.decode()}") - except json.JSONDecodeError: - print(f"❌ Failed to parse AzuraCast response: {stdout.decode()}") - - if os.path.exists(filepath): - os.remove(filepath) - - except Exception as e: - print(f"💥 CRITICAL ERROR in background task: {e}") - traceback.print_exc() + # Cleanup the local file so your container doesn't bloat + if os.path.exists(filepath): + os.remove(filepath) async def main(): @@ -549,7 +540,6 @@ async def main(): youtube = get_authenticated_service() client = await get_client(conn, cursor, youtube) sync_token = load_sync_token() - background_tasks = set() # This is incredibly dumb and most probably will exceed your YouTube API quota. if args.backwards_sync: