transcription/backend/main.py
2026-05-29 12:52:51 +03:00

217 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""FastAPI backend для сервиса транскрибации."""
import json
from contextlib import asynccontextmanager
from pathlib import Path
from typing import List, Optional
from fastapi import FastAPI, File, UploadFile, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, PlainTextResponse, HTMLResponse
from fastapi.staticfiles import StaticFiles
from backend.queue import (
UPLOAD_DIR,
PROCESSED_DIR,
enqueue,
get_all_tasks,
get_task_status,
get_processed_tree,
read_file_content,
set_progress_callback,
start_workers,
stop_workers,
)
# WebSocket менеджер
class ConnectionManager:
def __init__(self):
self.active_connections: List[WebSocket] = []
async def connect(self, websocket: WebSocket):
await websocket.accept()
self.active_connections.append(websocket)
def disconnect(self, websocket: WebSocket):
if websocket in self.active_connections:
self.active_connections.remove(websocket)
async def broadcast(self, message: dict):
for conn in self.active_connections:
try:
await conn.send_json(message)
except Exception:
pass
manager = ConnectionManager()
# Устанавливаем callback для отправки прогресса через WebSocket
set_progress_callback(manager.broadcast)
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Управление жизненным циклом приложения."""
print("🚀 Запуск рабочих процессов...")
start_workers(num_workers=1)
yield
print("🛑 Остановка рабочих процессов...")
stop_workers()
app = FastAPI(
title="Transcription Service",
version="1.0.0",
lifespan=lifespan,
)
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# === API Endpoints ===
@app.get("/", response_class=HTMLResponse)
async def root():
"""Главная страница."""
index_path = Path(__file__).parent / "static" / "index.html"
if index_path.exists():
return index_path.read_text(encoding="utf-8")
return "<h1>Transcription Service</h1><p>Frontend not built</p>"
@app.post("/upload")
async def upload_file(file: UploadFile = File(...)):
"""Загружает файл и добавляет в очередь обработки."""
# Сохраняем файл
file_path = UPLOAD_DIR / file.filename
with open(file_path, "wb") as f:
content = await file.read()
f.write(content)
# Добавляем в очередь
task_id = await enqueue(file_path)
return {
"task_id": task_id,
"filename": file.filename,
"status": "queued",
"message": "Файл добавлен в очередь обработки",
}
@app.post("/upload-batch")
async def upload_batch(files: List[UploadFile] = File(...)):
"""Загружает несколько файлов пакетно."""
results = []
for file in files:
file_path = UPLOAD_DIR / file.filename
with open(file_path, "wb") as f:
content = await file.read()
f.write(content)
task_id = await enqueue(file_path)
results.append({
"task_id": task_id,
"filename": file.filename,
"status": "queued",
})
return {
"uploaded": len(results),
"tasks": results,
}
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
"""WebSocket для получения прогресса обработки."""
await manager.connect(websocket)
try:
while True:
# Ждём сообщения от клиента (ping/keepalive)
data = await websocket.receive_text()
msg = json.loads(data)
if msg.get("action") == "get_tasks":
tasks = get_all_tasks()
await websocket.send_json({
"type": "tasks_list",
"tasks": tasks,
})
elif msg.get("action") == "get_tree":
tree = get_processed_tree()
await websocket.send_json({
"type": "file_tree",
"tree": tree,
})
except WebSocketDisconnect:
manager.disconnect(websocket)
except Exception:
manager.disconnect(websocket)
@app.get("/api/tasks")
async def api_tasks():
"""Возвращает список всех задач."""
return {"tasks": get_all_tasks()}
@app.get("/api/tasks/{task_id}")
async def api_task(task_id: str):
"""Возвращает статус конкретной задачи."""
status = get_task_status(task_id)
if not status:
return {"error": "Task not found"}
return status
@app.get("/api/files")
async def api_files():
"""Возвращает дерево обработанных файлов."""
return {"tree": get_processed_tree()}
@app.get("/api/files/content")
async def api_file_content(path: str):
"""Возвращает содержимое файла."""
try:
content = read_file_content(path)
return {"content": content, "path": path}
except Exception as e:
return {"error": str(e)}
@app.get("/api/files/download")
async def api_download(path: str):
"""Скачивает файл."""
file_path = PROCESSED_DIR / path
if not file_path.exists():
return {"error": "File not found"}
return FileResponse(file_path, filename=file_path.name)
import shutil
@app.delete("/api/folders/{folder_name}")
async def api_delete_folder(folder_name: str):
"""Удаляет папку с обработанными файлами."""
folder_path = PROCESSED_DIR / folder_name
if not folder_path.exists():
return {"error": "Folder not found"}
try:
shutil.rmtree(folder_path)
return {"deleted": folder_name}
except Exception as e:
return {"error": str(e)}
# Статические файлы
app.mount("/static", StaticFiles(directory="backend/static"), name="static")