Implement streaming and file upload functionality
This commit is contained in:
parent
3614aa6487
commit
51cedada4a
@ -1,7 +1,7 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
# Import individual routers here
|
# Import individual routers here
|
||||||
from app.api.v1.endpoints import users, auth, songs, albums, artists, playlists
|
from app.api.v1.endpoints import users, auth, songs, albums, artists, playlists, streaming, upload
|
||||||
|
|
||||||
api_router = APIRouter()
|
api_router = APIRouter()
|
||||||
|
|
||||||
@ -11,4 +11,6 @@ api_router.include_router(auth.router, prefix="/auth", tags=["auth"])
|
|||||||
api_router.include_router(songs.router, prefix="/songs", tags=["songs"])
|
api_router.include_router(songs.router, prefix="/songs", tags=["songs"])
|
||||||
api_router.include_router(albums.router, prefix="/albums", tags=["albums"])
|
api_router.include_router(albums.router, prefix="/albums", tags=["albums"])
|
||||||
api_router.include_router(artists.router, prefix="/artists", tags=["artists"])
|
api_router.include_router(artists.router, prefix="/artists", tags=["artists"])
|
||||||
api_router.include_router(playlists.router, prefix="/playlists", tags=["playlists"])
|
api_router.include_router(playlists.router, prefix="/playlists", tags=["playlists"])
|
||||||
|
api_router.include_router(streaming.router, prefix="/stream", tags=["streaming"])
|
||||||
|
api_router.include_router(upload.router, prefix="/upload", tags=["upload"])
|
134
app/api/v1/endpoints/streaming.py
Normal file
134
app/api/v1/endpoints/streaming.py
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
from typing import Any
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, status, Response
|
||||||
|
from fastapi.responses import FileResponse
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from app.api.deps import get_db, get_current_active_user
|
||||||
|
from app.models.user import User
|
||||||
|
from app.services.song import get_song
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/song/{song_id}", response_class=Response)
|
||||||
|
async def stream_song(
|
||||||
|
song_id: int,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_active_user),
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Stream a song by ID. Requires authentication.
|
||||||
|
"""
|
||||||
|
song = get_song(db, song_id=song_id)
|
||||||
|
if not song:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Song not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Construct file path
|
||||||
|
file_path = Path(song.file_path)
|
||||||
|
|
||||||
|
# Check if file exists
|
||||||
|
if not file_path.exists():
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Audio file not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Return file response for streaming
|
||||||
|
return FileResponse(
|
||||||
|
path=file_path,
|
||||||
|
filename=f"{song.title}.mp3",
|
||||||
|
media_type="audio/mpeg"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/album/cover/{album_id}")
|
||||||
|
async def get_album_cover(
|
||||||
|
album_id: int,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Get album cover image by album ID.
|
||||||
|
"""
|
||||||
|
from app.services.album import get_album
|
||||||
|
|
||||||
|
album = get_album(db, album_id=album_id)
|
||||||
|
if not album or not album.cover_image_path:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Album cover not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Construct file path
|
||||||
|
file_path = Path(album.cover_image_path)
|
||||||
|
|
||||||
|
# Check if file exists
|
||||||
|
if not file_path.exists():
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Cover image file not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Determine media type based on file extension
|
||||||
|
extension = file_path.suffix.lower()
|
||||||
|
media_type = "image/jpeg" # Default
|
||||||
|
if extension == ".png":
|
||||||
|
media_type = "image/png"
|
||||||
|
elif extension == ".gif":
|
||||||
|
media_type = "image/gif"
|
||||||
|
elif extension == ".webp":
|
||||||
|
media_type = "image/webp"
|
||||||
|
|
||||||
|
# Return file response
|
||||||
|
return FileResponse(
|
||||||
|
path=file_path,
|
||||||
|
media_type=media_type
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/artist/image/{artist_id}")
|
||||||
|
async def get_artist_image(
|
||||||
|
artist_id: int,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Get artist image by artist ID.
|
||||||
|
"""
|
||||||
|
from app.services.artist import get_artist
|
||||||
|
|
||||||
|
artist = get_artist(db, artist_id=artist_id)
|
||||||
|
if not artist or not artist.image_path:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Artist image not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Construct file path
|
||||||
|
file_path = Path(artist.image_path)
|
||||||
|
|
||||||
|
# Check if file exists
|
||||||
|
if not file_path.exists():
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Image file not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Determine media type based on file extension
|
||||||
|
extension = file_path.suffix.lower()
|
||||||
|
media_type = "image/jpeg" # Default
|
||||||
|
if extension == ".png":
|
||||||
|
media_type = "image/png"
|
||||||
|
elif extension == ".gif":
|
||||||
|
media_type = "image/gif"
|
||||||
|
elif extension == ".webp":
|
||||||
|
media_type = "image/webp"
|
||||||
|
|
||||||
|
# Return file response
|
||||||
|
return FileResponse(
|
||||||
|
path=file_path,
|
||||||
|
media_type=media_type
|
||||||
|
)
|
102
app/api/v1/endpoints/upload.py
Normal file
102
app/api/v1/endpoints/upload.py
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, Form
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from app.api.deps import get_db, get_current_active_superuser
|
||||||
|
from app.models.user import User
|
||||||
|
from app.core.config import settings
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/song", status_code=status.HTTP_201_CREATED)
|
||||||
|
async def upload_song(
|
||||||
|
*,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
file: UploadFile = File(...),
|
||||||
|
current_user: User = Depends(get_current_active_superuser),
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Upload a song file. Only superusers can upload songs.
|
||||||
|
"""
|
||||||
|
# Check file type
|
||||||
|
if not file.content_type.startswith("audio/"):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="File must be an audio file",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generate unique filename to avoid collisions
|
||||||
|
filename = f"{uuid.uuid4()}{os.path.splitext(file.filename)[1]}"
|
||||||
|
|
||||||
|
# Create directory if it doesn't exist
|
||||||
|
audio_dir = settings.AUDIO_DIR
|
||||||
|
audio_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Save file
|
||||||
|
file_path = audio_dir / filename
|
||||||
|
|
||||||
|
try:
|
||||||
|
contents = await file.read()
|
||||||
|
with open(file_path, "wb") as f:
|
||||||
|
f.write(contents)
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
detail=f"Error uploading file: {str(e)}",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Return the file path (to be saved in Song model)
|
||||||
|
return {"file_path": str(file_path)}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/image", status_code=status.HTTP_201_CREATED)
|
||||||
|
async def upload_image(
|
||||||
|
*,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
file: UploadFile = File(...),
|
||||||
|
image_type: str = Form(...), # "artist" or "album"
|
||||||
|
current_user: User = Depends(get_current_active_superuser),
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Upload an image file for artist or album. Only superusers can upload images.
|
||||||
|
"""
|
||||||
|
# Check file type
|
||||||
|
if not file.content_type.startswith("image/"):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="File must be an image",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check image type
|
||||||
|
if image_type not in ["artist", "album"]:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="Image type must be 'artist' or 'album'",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generate unique filename to avoid collisions
|
||||||
|
filename = f"{uuid.uuid4()}{os.path.splitext(file.filename)[1]}"
|
||||||
|
|
||||||
|
# Create directory if it doesn't exist
|
||||||
|
images_dir = settings.IMAGES_DIR
|
||||||
|
images_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Save file
|
||||||
|
file_path = images_dir / filename
|
||||||
|
|
||||||
|
try:
|
||||||
|
contents = await file.read()
|
||||||
|
with open(file_path, "wb") as f:
|
||||||
|
f.write(contents)
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
detail=f"Error uploading file: {str(e)}",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Return the file path (to be saved in Artist or Album model)
|
||||||
|
return {"file_path": str(file_path)}
|
Loading…
x
Reference in New Issue
Block a user