File size: 5,465 Bytes
964950d b6093b0 0cb486a 964950d c564fad 0cb486a b6093b0 2b0ce36 23e5406 c564fad 0cb486a 228a3b1 0cb486a c564fad 7f3ee84 0cb486a 228a3b1 0cb486a 7f3ee84 228a3b1 7f3ee84 0cb486a 78c135b 3fc2a44 228a3b1 0cb486a 7f3ee84 3fc2a44 ab25f71 2a6a104 ab25f71 d0c0836 ab25f71 b6093b0 1aceee0 0cb486a b6093b0 23e5406 0cb486a b6093b0 c564fad 964950d c564fad 964950d 0cb486a 964950d 0cb486a 964950d 0cb486a 7f3ee84 0cb486a 7f3ee84 0cb486a 7f3ee84 0cb486a c564fad b6093b0 0cb486a 7f3ee84 0cb486a d63a8c1 0cb486a b6093b0 3fc2a44 d0c0836 964950d 8cdcb92 b6093b0 0cb486a 964950d 0cb486a 228a3b1 964950d 0cb486a 964950d b6093b0 7f3ee84 3fc2a44 25a7124 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
import shutil, os, logging, uvicorn
from typing import Optional
from uuid import uuid4
from tempfile import TemporaryDirectory
from utils.transcriber import transcriber
from utils.process_video import process_video
from utils.zip_response import zip_response
from utils.read_html import read_html
from fastapi import FastAPI, UploadFile, HTTPException, Request, Form, Depends
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse, Response, RedirectResponse
from fastapi.security import HTTPBasic
from pydantic import BaseModel, field_validator
from cachetools import TTLCache
## THIS IS A BREAKING CHANGE. SRT FILE INPUT DEPRECATED. WIP.
## DONE: separate transcriber from subtitler logic. WIP.
## DONE: improve loading spinner. WIP (with redirect)
## DONE: fix tempdir cleanup
## DONE: add transcription preview component + allow for interactive validation of transcription in-browser.
## TODO: add word level highlighting option
## TODO: improve UI
app = FastAPI()
security = HTTPBasic()
static_dir = os.path.join(os.path.dirname(__file__), 'static')
app.mount("/static", StaticFiles(directory=static_dir), name="static")
templates = Jinja2Templates(directory=static_dir)
cache = TTLCache(maxsize=1024, ttl=600)
class MP4Video(BaseModel):
video_file: UploadFile
@property
def filename(self):
return self.video_file.filename
@property
def file(self):
return self.video_file.file
@field_validator('video_file')
def validate_video_file(cls, v):
if not v.filename.endswith('.mp4'):
raise HTTPException(status_code=500, detail='Invalid video file type. Please upload an MP4 file.')
return v
@app.get("/")
async def root():
html_content = f"""
{read_html(os.path.join(os.getcwd(),"static/landing_page.html"))}
"""
return HTMLResponse(content=html_content)
@app.get("/transcribe_video/")
async def get_form():
html_content = f"""
{read_html(os.path.join(os.getcwd(),"static/transcribe_video.html"))}
"""
return HTMLResponse(content=html_content)
async def get_temp_dir():
dir = TemporaryDirectory(delete=False)
try:
yield dir
except Exception as e:
HTTPException(status_code=500, detail=str(e))
@app.post("/transcribe/")
async def transcribe_api(video_file: MP4Video = Depends(),
task: str = Form("transcribe"),
model_version: str = Form("deepdml/faster-whisper-large-v3-turbo-ct2"),
max_words_per_line: int = Form(6),
temp_dir: TemporaryDirectory = Depends(get_temp_dir)):
try:
video_path = os.path.join(temp_dir.name, video_file.filename)
with open(video_path, 'wb') as f:
shutil.copyfileobj(video_file.file, f)
transcription = transcriber(video_path, max_words_per_line, task, model_version)
uid = str(uuid4())
cache[uid] = {"video_path": video_path, "transcription": transcription, "temp_dir_path": temp_dir.name}
return RedirectResponse(url=f"/process_settings/?uid={uid}", status_code=303)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/process_settings/")
async def process_settings(request: Request, uid: str):
data = cache.get(uid)
if not data:
raise HTTPException(404, "Data not found")
return templates.TemplateResponse("process_settings.html", {
"request": request,
"transcription": data["transcription"],
"video_path": data["video_path"],
"temp_dir_path": data["temp_dir_path"]
})
@app.post("/process_video/")
async def process_video_api(video_path: str = Form(...),
temp_dir_path: str = Form(...),
srt_string: str = Form(...),
fontsize: Optional[int] = Form(42),
font: Optional[str] = Form("Helvetica"),
bg_color: Optional[str] = Form("#070a13b3"),
text_color: Optional[str] = Form("white"),
caption_mode: Optional[str] = Form("desktop"),
temp_dir: TemporaryDirectory = Depends(get_temp_dir)
):
try:
output_path = process_video(video_path, srt_string, fontsize, font, bg_color, text_color, caption_mode)
with open(os.path.join(temp_dir.name, f"{video_path.split('.')[0]}.srt"), 'w+') as temp_srt_file:
logging.info("Processing the video...")
temp_srt_file.write(srt_string)
logging.info("Zipping response...")
with open(os.path.join(temp_dir.name, f"{video_path.split('.')[0]}.zip"), 'w+b') as temp_zip_file:
zip_file = zip_response(temp_zip_file.name, [output_path, temp_srt_file.name])
return Response(
content = zip_file,
media_type="application/zip",
headers={"Content-Disposition": f"attachment; filename={os.path.basename(video_path).split('.')[0]}.zip"}
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
finally:
if temp_dir_path and os.path.exists(temp_dir_path):
shutil.rmtree(temp_dir_path, ignore_errors=True)
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000) |