Files
you-music/backend/app/api/download.py
T
2025-10-30 22:35:02 +11:00

225 lines
7.5 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.db.session import get_db
from app.models.models import Music
from app.schemas.schemas import DownloadRequest
from app.services.downloader import music_downloader
from app.services.download_queue import download_queue
from app.core.config import settings
import os
import uuid
router = APIRouter()
async def process_download(
task_id: str,
url: str,
title: str,
db: AsyncSession,
add_to_playlist: str = None
):
"""Background task to download music"""
await download_queue.update_status(task_id, "downloading", progress=0.0)
success, file_path, error = await music_downloader.download_music(url, title)
if success and file_path:
await download_queue.update_status(task_id, "downloading", progress=80.0)
# Extract metadata
metadata = await music_downloader.get_music_metadata(file_path)
# Determine source type
source_type = "youtube" if music_downloader.is_youtube_url(url) else \
"bilibili" if music_downloader.is_bilibili_url(url) else "other"
# Get relative path and file format
relative_path = os.path.relpath(file_path, settings.MUSIC_DIR)
file_extension = os.path.splitext(file_path)[1][1:] # Get extension without dot
# Create database record
db_music = Music(
title=metadata.get("title", title or "Unknown"),
artist=metadata.get("artist", "Unknown"),
album=metadata.get("album", ""),
duration=metadata.get("duration", 0),
file_path=relative_path,
file_size=os.path.getsize(file_path),
file_format=file_extension,
file_location=file_path,
file_exists=True,
source_url=url,
source_type=source_type,
thumbnail=metadata.get("thumbnail") # Add thumbnail
)
db.add(db_music)
await db.commit()
await db.refresh(db_music)
await download_queue.update_status(task_id, "completed", progress=100.0, music_id=db_music.id)
# Add to playlist if specified
if add_to_playlist:
from app.models.models import Playlist
result = await db.execute(
select(Playlist).where(Playlist.name == add_to_playlist)
)
playlist = result.scalar_one_or_none()
if playlist:
playlist.music_items.append(db_music)
await db.commit()
else:
await download_queue.update_status(task_id, "failed", error=error or "Download failed")
@router.post("/music")
async def download_music(
request: DownloadRequest,
background_tasks: BackgroundTasks,
db: AsyncSession = Depends(get_db)
):
"""Download music from URL"""
# Validate URL
if not request.url:
raise HTTPException(status_code=400, detail="URL is required")
# Check if already downloaded
result = await db.execute(
select(Music).where(Music.source_url == request.url)
)
existing = result.scalar_one_or_none()
if existing:
return {
"message": "Music already downloaded",
"music_id": existing.id,
"status": "existing",
"task_id": None
}
# Create task ID and add to queue
task_id = str(uuid.uuid4())
await download_queue.add_task(
task_id,
request.url,
request.title or "Unknown",
thumbnail=request.thumbnail,
artist=request.artist
)
# Start download in background
background_tasks.add_task(
process_download,
task_id,
request.url,
request.title,
db,
request.add_to_playlist
)
return {
"message": "Download started",
"status": "downloading",
"task_id": task_id
}
@router.post("/playlist")
async def download_playlist(
request: DownloadRequest,
background_tasks: BackgroundTasks,
db: AsyncSession = Depends(get_db)
):
"""Download entire playlist"""
if not request.url:
raise HTTPException(status_code=400, detail="URL is required")
playlist_name = request.title or "Downloaded Playlist"
async def process_playlist_download():
success, files, error = await music_downloader.download_playlist(
request.url,
playlist_name
)
if success:
# Create playlist in database
from app.models.models import Playlist
result = await db.execute(
select(Playlist).where(Playlist.name == playlist_name)
)
playlist = result.scalar_one_or_none()
if not playlist:
playlist = Playlist(name=playlist_name)
db.add(playlist)
await db.commit()
await db.refresh(playlist)
# Add all downloaded files to database and playlist
for file_path in files:
metadata = await music_downloader.get_music_metadata(file_path)
relative_path = os.path.relpath(file_path, settings.MUSIC_DIR)
# Check if already in database
result = await db.execute(
select(Music).where(Music.file_path == relative_path)
)
existing_music = result.scalar_one_or_none()
if not existing_music:
db_music = Music(
title=metadata.get("title", os.path.basename(file_path)),
artist=metadata.get("artist", "Unknown"),
album=metadata.get("album", ""),
duration=metadata.get("duration", 0),
file_path=relative_path,
file_size=os.path.getsize(file_path),
source_url=request.url,
source_type="youtube" if music_downloader.is_youtube_url(request.url) else "bilibili"
)
db.add(db_music)
await db.commit()
await db.refresh(db_music)
playlist.music_items.append(db_music)
await db.commit()
background_tasks.add_task(process_playlist_download)
return {
"message": "Playlist download started",
"status": "downloading"
}
@router.get("/status")
async def get_download_status():
"""Get current download status"""
all_tasks = await download_queue.get_all_tasks()
return {
"tasks": [task.model_dump() for task in all_tasks],
"active_downloads": len([t for t in all_tasks if t.status == "downloading"]),
"pending": len([t for t in all_tasks if t.status == "pending"]),
"completed": len([t for t in all_tasks if t.status == "completed"]),
"failed": len([t for t in all_tasks if t.status == "failed"])
}
@router.delete("/task/{task_id}")
async def remove_download_task(task_id: str):
"""Remove a download task from the queue"""
await download_queue.remove_task(task_id)
return {"message": "Task removed"}
@router.post("/clear-completed")
async def clear_completed_tasks():
"""Clear all completed and failed tasks"""
await download_queue.clear_completed()
return {"message": "Completed tasks cleared"}