heystreamer/app/routers/media.py
Bashy 6ad661c220 Initial commit — BashyOverlay v1
Twitch stream event platform with OBS browser source overlay.

Features:
- Twitch OAuth login (streamer + mod access)
- EventSub WebSocket for subs, gift subs, bits, channel points, follows, raids
- Chat commands via tmi.js in the overlay
- Gap audio — tone plays on new chat message after silence, volume/pitch scale with gap length
- Self-hosted media library (upload sounds and videos)
- Action configuration — map any event to sound, video, or alert
- Per-action cooldowns, test button, enable/disable toggle
- Multiple actions per event (all matching actions fire)
- Activity log dashboard with EventSub connection status
- Layout editor — iframe-based WYSIWYG with drag handles, live style preview
- Custom CSS and custom JS injection into overlay
- Custom DOM events (bashyoverlay:sub, bashyoverlay:raid, etc.) for custom JS hooks
- deploy.sh — one-shot setup and launch script

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 23:48:35 +03:00

79 lines
2.2 KiB
Python

import os
import uuid
from fastapi import APIRouter, Depends, HTTPException, Request, UploadFile
from fastapi.responses import HTMLResponse
from sqlmodel import Session, select
from app.auth import require_user
from app.database import get_session
from app.models import MediaFile
from app.templates import templates
router = APIRouter(prefix="/media")
MEDIA_DIR = "static/media"
AUDIO_EXTENSIONS = {".mp3", ".ogg", ".wav", ".m4a"}
VIDEO_EXTENSIONS = {".mp4", ".webm", ".mov"}
@router.get("", response_class=HTMLResponse)
async def media_page(request: Request, session: Session = Depends(get_session), user=Depends(require_user)):
files = session.exec(select(MediaFile)).all()
return templates.TemplateResponse("media.html", {
"request": request,
"user": user,
"files": files,
})
@router.post("", response_class=HTMLResponse)
async def upload_file(
request: Request,
file: UploadFile,
session: Session = Depends(get_session),
user=Depends(require_user),
):
ext = os.path.splitext(file.filename)[1].lower()
if ext in AUDIO_EXTENSIONS:
file_type = "audio"
elif ext in VIDEO_EXTENSIONS:
file_type = "video"
else:
raise HTTPException(status_code=400, detail="Unsupported file type")
filename = f"{uuid.uuid4().hex}{ext}"
path = os.path.join(MEDIA_DIR, filename)
content = await file.read()
with open(path, "wb") as f:
f.write(content)
media = MediaFile(filename=filename, original_name=file.filename, file_type=file_type)
session.add(media)
session.commit()
session.refresh(media)
return templates.TemplateResponse("partials/media_item.html", {
"request": request,
"file": media,
})
@router.delete("/{file_id}", response_class=HTMLResponse)
async def delete_file(
file_id: int,
session: Session = Depends(get_session),
user=Depends(require_user),
):
media = session.get(MediaFile, file_id)
if not media:
raise HTTPException(status_code=404)
path = os.path.join(MEDIA_DIR, media.filename)
if os.path.exists(path):
os.remove(path)
session.delete(media)
session.commit()
return HTMLResponse("")