Add media browser with grid/compact/list views and single-click playback
- Add browser UI with three view modes (grid, compact, list) and pagination - Add file browsing, thumbnail loading, download, and play endpoints - Add duration extraction via mutagen for media files - Single-click plays media or navigates folders, with play overlay on hover - Add type badges, file size display, and duration metadata - Add localization keys for browser UI (en/ru) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,10 +7,10 @@ from typing import Optional
|
||||
from urllib.parse import unquote
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Response
|
||||
from fastapi.responses import StreamingResponse
|
||||
from fastapi.responses import FileResponse, StreamingResponse
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ..auth import verify_token
|
||||
from ..auth import verify_token, verify_token_or_query
|
||||
from ..config import MediaFolderConfig, settings
|
||||
from ..config_manager import config_manager
|
||||
from ..services.browser_service import BrowserService
|
||||
@@ -421,3 +421,49 @@ async def play_file(
|
||||
except Exception as e:
|
||||
logger.error(f"Error playing file: {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to play file")
|
||||
|
||||
|
||||
# Download Endpoint
|
||||
@router.get("/download")
|
||||
async def download_file(
|
||||
folder_id: str = Query(..., description="Media folder ID"),
|
||||
path: str = Query(..., description="File path relative to folder root (URL-encoded)"),
|
||||
_: str = Depends(verify_token_or_query),
|
||||
):
|
||||
"""Download a media file.
|
||||
|
||||
Args:
|
||||
folder_id: ID of the media folder.
|
||||
path: Path to the file (URL-encoded, relative to folder root).
|
||||
|
||||
Returns:
|
||||
File download response.
|
||||
|
||||
Raises:
|
||||
HTTPException: If file not found or not a media file.
|
||||
"""
|
||||
try:
|
||||
decoded_path = unquote(path)
|
||||
file_path = BrowserService.validate_path(folder_id, decoded_path)
|
||||
|
||||
if not file_path.is_file():
|
||||
raise HTTPException(status_code=400, detail="Path is not a file")
|
||||
|
||||
if not BrowserService.is_media_file(file_path):
|
||||
raise HTTPException(status_code=400, detail="File is not a media file")
|
||||
|
||||
return FileResponse(
|
||||
path=file_path,
|
||||
filename=file_path.name,
|
||||
media_type="application/octet-stream",
|
||||
)
|
||||
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except FileNotFoundError as e:
|
||||
raise HTTPException(status_code=404, detail=str(e))
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error downloading file: {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to download file")
|
||||
|
||||
Reference in New Issue
Block a user