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)