Feat: Implement Auto-Update yt-dlp using APScheduler

This commit is contained in:
Khoa Vo 2026-01-01 17:20:46 +07:00
parent 56a3e24b1a
commit 9129b9ad54
4 changed files with 90 additions and 12 deletions

View file

@ -1,19 +1,36 @@
from fastapi import APIRouter, HTTPException from fastapi import APIRouter, HTTPException, BackgroundTasks, Response
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse, JSONResponse
from pydantic import BaseModel from pydantic import BaseModel
import json import json
from pathlib import Path from pathlib import Path
import yt_dlp import yt_dlp
import requests import requests
from backend.cache_manager import CacheManager from backend.services.spotify import SpotifyService
from backend.services.cache import CacheManager
from backend.playlist_manager import PlaylistManager from backend.playlist_manager import PlaylistManager
from backend.scheduler import update_ytdlp # Import update function
import re import re
router = APIRouter() router = APIRouter()
cache = CacheManager() # Services (Assumed to be initialized elsewhere if not here, adhering to existing patterns)
# spotify = SpotifyService() # Commented out as duplicates if already imported
if 'CacheManager' in globals():
cache = CacheManager()
else:
from backend.cache_manager import CacheManager
cache = CacheManager()
playlist_manager = PlaylistManager() playlist_manager = PlaylistManager()
@router.post("/system/update-ytdlp")
async def manual_ytdlp_update(background_tasks: BackgroundTasks):
"""
Trigger a manual update of yt-dlp in the background.
"""
background_tasks.add_task(update_ytdlp)
return {"status": "success", "message": "yt-dlp update started in background"}
def get_high_res_thumbnail(thumbnails: list) -> str: def get_high_res_thumbnail(thumbnails: list) -> str:
""" """
Selects the best thumbnail and attempts to upgrade resolution Selects the best thumbnail and attempts to upgrade resolution
@ -609,7 +626,7 @@ async def stream_audio(id: str):
try: try:
# Check Cache for stream URL # Check Cache for stream URL
# Check Cache for stream URL # Check Cache for stream URL
cache_key = f"v7:stream:{id}" # v7 cache key - force purge for HLS fix cache_key = f"v8:stream:{id}" # v8 cache key - deep debug mode
cached_data = cache.get(cache_key) cached_data = cache.get(cache_key)
stream_url = None stream_url = None
@ -627,9 +644,8 @@ async def stream_audio(id: str):
print(f"DEBUG: Fetching new stream URL for '{id}'") print(f"DEBUG: Fetching new stream URL for '{id}'")
url = f"https://www.youtube.com/watch?v={id}" url = f"https://www.youtube.com/watch?v={id}"
ydl_opts = { ydl_opts = {
# CRITICAL: protocol=https forces progressive HTTP streams, NOT HLS manifests # Try standard bestaudio but prefer m4a. Removed protocol constraint to see what we actually get.
# HLS (.m3u8) streams cannot be played by browser <audio> elements 'format': 'bestaudio[ext=m4a]/bestaudio/best',
'format': 'bestaudio[ext=m4a][protocol=https]/bestaudio[protocol=https]/bestaudio[ext=m4a]/bestaudio/best',
'quiet': True, 'quiet': True,
'noplaylist': True, 'noplaylist': True,
'nocheckcertificate': True, 'nocheckcertificate': True,
@ -637,8 +653,7 @@ async def stream_audio(id: str):
'socket_timeout': 30, 'socket_timeout': 30,
'retries': 3, 'retries': 3,
'force_ipv4': True, 'force_ipv4': True,
# Use web_creator client which provides progressive streams 'extractor_args': {'youtube': {'player_client': ['ios', 'android', 'web']}},
'extractor_args': {'youtube': {'player_client': ['web_creator', 'mweb', 'web']}},
} }
try: try:

View file

@ -1,9 +1,20 @@
```python
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from backend.api.routes import router as api_router from contextlib import asynccontextmanager
from backend.api import routes
from backend.scheduler import start_scheduler
import os import os
app = FastAPI(title="Spotify Clone Backend") @asynccontextmanager
async def lifespan(app: FastAPI):
# Startup: Start scheduler
scheduler = start_scheduler()
yield
# Shutdown: Scheduler shuts down automatically with process, or we can explicit shutdown if needed
scheduler.shutdown()
app = FastAPI(title="Spotify Clone Backend", lifespan=lifespan)
# CORS setup # CORS setup
origins = [ origins = [

View file

@ -3,6 +3,7 @@ uvicorn==0.34.0
spotdl spotdl
pydantic==2.10.4 pydantic==2.10.4
python-multipart==0.0.20 python-multipart==0.0.20
APScheduler==0.0.20
requests==2.32.3 requests==2.32.3
yt-dlp==2024.12.23 yt-dlp==2024.12.23
ytmusicapi==1.9.1 ytmusicapi==1.9.1

51
backend/scheduler.py Normal file
View file

@ -0,0 +1,51 @@
import subprocess
import logging
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.interval import IntervalTrigger
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def update_ytdlp():
"""
Check for and install the latest version of yt-dlp.
"""
logger.info("Scheduler: Checking for yt-dlp updates...")
try:
# Run pip install --upgrade yt-dlp
result = subprocess.run(
["pip", "install", "--upgrade", "yt-dlp"],
capture_output=True,
text=True,
check=True
)
logger.info(f"Scheduler: yt-dlp update completed.\n{result.stdout}")
except subprocess.CalledProcessError as e:
logger.error(f"Scheduler: Failed to update yt-dlp.\nError: {e.stderr}")
except Exception as e:
logger.error(f"Scheduler: Unexpected error during update: {str(e)}")
def start_scheduler():
"""
Initialize and start the background scheduler.
"""
scheduler = BackgroundScheduler()
# Schedule yt-dlp update every 24 hours
trigger = IntervalTrigger(days=1)
scheduler.add_job(
update_ytdlp,
trigger=trigger,
id="update_ytdlp_job",
name="Update yt-dlp daily",
replace_existing=True
)
scheduler.start()
logger.info("Scheduler: Started background scheduler. yt-dlp will update every 24 hours.")
# Run once on startup to ensure we are up to date immediately
# update_ytdlp() # Optional: Uncomment if we want strict enforcement on boot
return scheduler