Feat: Implement Auto-Update yt-dlp using APScheduler
This commit is contained in:
parent
56a3e24b1a
commit
9129b9ad54
4 changed files with 90 additions and 12 deletions
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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 = [
|
||||||
|
|
|
||||||
|
|
@ -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
51
backend/scheduler.py
Normal 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
|
||||||
Loading…
Reference in a new issue