reddit-video-bot/web.py

138 lines
3.5 KiB
Python
Raw Normal View History

import asyncio
import json
import os
from pathlib import Path
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import FileResponse, HTMLResponse
app = FastAPI()
OUTPUT_DIR = Path("output")
SETTINGS_FILE = Path("bot_settings.json")
DEFAULT_SETTINGS = {
"subreddits": ["AITAH", "relationship_advice", "confessions", "tifu"],
"whisper_model": "base",
"voice": "en-US-ChristopherNeural",
"min_score": 1000,
"min_words": 200,
"max_words": 1500,
}
log_clients: list[WebSocket] = []
bot_running = False
def load_settings() -> dict:
if SETTINGS_FILE.exists():
return json.loads(SETTINGS_FILE.read_text())
return DEFAULT_SETTINGS.copy()
def save_settings(data: dict):
SETTINGS_FILE.write_text(json.dumps(data, indent=2))
@app.get("/", response_class=HTMLResponse)
async def index():
return open("templates/index.html").read()
@app.get("/api/settings")
async def get_settings():
return load_settings()
@app.post("/api/settings")
async def post_settings(data: dict):
save_settings(data)
return {"ok": True}
@app.get("/api/videos")
async def list_videos():
OUTPUT_DIR.mkdir(exist_ok=True)
videos = []
for f in sorted(OUTPUT_DIR.glob("*.mp4"), key=lambda x: x.stat().st_mtime, reverse=True):
videos.append({
"name": f.name,
"size": f.stat().st_size,
"url": f"/videos/{f.name}",
})
return videos
@app.get("/videos/{filename}")
async def serve_video(filename: str):
path = OUTPUT_DIR / filename
if not path.exists():
return {"error": "not found"}
return FileResponse(path, media_type="video/mp4")
async def broadcast(message: str):
dead = []
for ws in log_clients:
try:
await ws.send_text(message)
except Exception:
dead.append(ws)
for ws in dead:
log_clients.remove(ws)
@app.post("/api/run")
async def run_bot():
global bot_running
if bot_running:
return {"error": "Bot is already running"}
bot_running = True
asyncio.create_task(_run_bot_task())
return {"ok": True}
async def _run_bot_task():
global bot_running
settings = load_settings()
env = os.environ.copy()
env["WHISPER_MODEL"] = settings.get("whisper_model", "base")
env["BOT_VOICE"] = settings.get("voice", "en-US-ChristopherNeural")
env["BOT_SUBREDDITS"] = ",".join(settings.get("subreddits", []))
env["BOT_MIN_SCORE"] = str(settings.get("min_score", 1000))
env["BOT_MIN_WORDS"] = str(settings.get("min_words", 200))
env["BOT_MAX_WORDS"] = str(settings.get("max_words", 1500))
try:
proc = await asyncio.create_subprocess_exec(
"python", "main.py",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.STDOUT,
env=env,
)
async for line in proc.stdout:
await broadcast(line.decode().rstrip())
await proc.wait()
await broadcast(f"__DONE__{proc.returncode}")
except Exception as e:
await broadcast(f"[ERROR] {e}")
await broadcast("__DONE__1")
finally:
bot_running = False
@app.websocket("/ws/logs")
async def ws_logs(websocket: WebSocket):
await websocket.accept()
log_clients.append(websocket)
if bot_running:
await websocket.send_text("__RUNNING__")
try:
while True:
await asyncio.sleep(30)
except WebSocketDisconnect:
pass
finally:
if websocket in log_clients:
log_clients.remove(websocket)