Update 2025-04-24_11:44:19
This commit is contained in:
127
upload.py
Normal file
127
upload.py
Normal file
@ -0,0 +1,127 @@
|
||||
# upload.py — FastAPI route for upload + quota check + voice conversion
|
||||
|
||||
from fastapi import APIRouter, UploadFile, Form, HTTPException, Request, Depends
|
||||
from slowapi import Limiter
|
||||
from slowapi.util import get_remote_address
|
||||
from slowapi.errors import RateLimitExceeded
|
||||
from pathlib import Path
|
||||
from convert_to_opus import convert_to_opus
|
||||
from database import get_db
|
||||
from models import UploadLog, UserQuota, User
|
||||
from sqlalchemy import select
|
||||
|
||||
limiter = Limiter(key_func=get_remote_address)
|
||||
router = APIRouter()
|
||||
# # Not needed for SlowAPI ≥0.1.5
|
||||
DATA_ROOT = Path("./data")
|
||||
|
||||
@limiter.limit("5/minute")
|
||||
@router.post("/upload")
|
||||
async def upload(request: Request, db = Depends(get_db), uid: str = Form(...), file: UploadFile = Form(...)):
|
||||
from log import log_violation
|
||||
try:
|
||||
user_dir = DATA_ROOT / uid
|
||||
user_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
raw_path = user_dir / ("raw." + file.filename.split(".")[-1])
|
||||
import uuid
|
||||
|
||||
unique_name = str(uuid.uuid4()) + ".opus"
|
||||
|
||||
# Save temp upload FIRST
|
||||
with open(raw_path, "wb") as f:
|
||||
f.write(await file.read())
|
||||
|
||||
# Block music/singing via Ollama prompt
|
||||
import requests
|
||||
try:
|
||||
with open(raw_path, "rb") as f:
|
||||
audio = f.read()
|
||||
res = requests.post("http://localhost:11434/api/generate", json={
|
||||
"model": "whisper",
|
||||
"prompt": "Does this audio contain music or singing? Answer yes or no only.",
|
||||
"audio": audio
|
||||
}, timeout=10)
|
||||
resp = res.json().get("response", "").lower()
|
||||
if "yes" in resp:
|
||||
raw_path.unlink(missing_ok=True)
|
||||
raise HTTPException(status_code=403, detail="Upload rejected: music or singing detected")
|
||||
except Exception as ollama_err:
|
||||
# fallback: allow, log if needed
|
||||
pass
|
||||
processed_path = user_dir / unique_name
|
||||
|
||||
# Block unconfirmed users (use ORM)
|
||||
user = db.exec(select(User).where((User.username == uid) | (User.email == uid))).first()
|
||||
# If result is a Row or tuple, extract the User object
|
||||
if user is not None and not isinstance(user, User) and hasattr(user, "__getitem__"):
|
||||
user = user[0]
|
||||
from log import log_violation
|
||||
log_violation("UPLOAD", request.client.host, uid, f"DEBUG: Incoming uid={uid}, user found={user}, confirmed={getattr(user, 'confirmed', None)}")
|
||||
log_violation("UPLOAD", request.client.host, uid, f"DEBUG: After unpack, user={user}, type={type(user)}, confirmed={getattr(user, 'confirmed', None)}")
|
||||
if not user or not hasattr(user, "confirmed") or not user.confirmed:
|
||||
raw_path.unlink(missing_ok=True)
|
||||
raise HTTPException(status_code=403, detail="Account not confirmed")
|
||||
|
||||
# DB-based quota check
|
||||
quota = db.get(UserQuota, uid)
|
||||
if quota and quota.storage_bytes >= 100 * 1024 * 1024:
|
||||
raw_path.unlink(missing_ok=True)
|
||||
raise HTTPException(status_code=400, detail="Quota exceeded")
|
||||
|
||||
try:
|
||||
convert_to_opus(str(raw_path), str(processed_path))
|
||||
except Exception as e:
|
||||
raw_path.unlink(missing_ok=True)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
original_size = raw_path.stat().st_size
|
||||
raw_path.unlink(missing_ok=True) # cleanup
|
||||
|
||||
# Always copy latest upload as stream.opus for redirect compatibility
|
||||
import shutil
|
||||
stream_path = user_dir / "stream.opus"
|
||||
shutil.copy2(processed_path, stream_path)
|
||||
|
||||
# Also update ./data/{uid}/stream.opus for public stream listing
|
||||
streams_dir = Path("data") / uid
|
||||
streams_dir.mkdir(parents=True, exist_ok=True)
|
||||
streams_stream_path = streams_dir / "stream.opus"
|
||||
shutil.copy2(processed_path, streams_stream_path)
|
||||
|
||||
db.add(UploadLog(
|
||||
uid=uid,
|
||||
ip=request.client.host,
|
||||
filename=file.filename,
|
||||
size_bytes=original_size
|
||||
))
|
||||
|
||||
# Store updated quota
|
||||
size = processed_path.stat().st_size
|
||||
quota = db.get(UserQuota, uid)
|
||||
if not quota:
|
||||
quota = UserQuota(uid=uid)
|
||||
db.add(quota)
|
||||
quota.storage_bytes += size
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"stream_url": f"http://localhost:8000/streams/{uid}/stream.opus",
|
||||
"filename": file.filename,
|
||||
"original_size": round(original_size / 1024, 1),
|
||||
"quota": {
|
||||
"used_mb": round(quota.storage_bytes / (1024 * 1024), 2)
|
||||
}
|
||||
}
|
||||
except HTTPException as e:
|
||||
# Already a JSON response, just re-raise
|
||||
raise e
|
||||
except Exception as e:
|
||||
import traceback
|
||||
tb = traceback.format_exc()
|
||||
# Log and return a JSON error
|
||||
try:
|
||||
log_violation("UPLOAD", request.client.host, uid, f"Unexpected error: {type(e).__name__}: {str(e)}\n{tb}")
|
||||
except Exception:
|
||||
pass
|
||||
return {"detail": f"Server error: {type(e).__name__}: {str(e)}"}
|
Reference in New Issue
Block a user