File size: 5,758 Bytes
4338241
 
 
 
 
ab91fef
 
4af161c
e0266c8
4338241
4ada954
4338241
ab91fef
e0266c8
 
 
 
 
 
 
 
f6f36b6
 
4338241
e0266c8
4338241
e0266c8
4338241
ab91fef
e0266c8
 
4ada954
 
 
7f7b879
e0266c8
4ada954
e0266c8
 
 
ab91fef
e0266c8
 
ab91fef
e0266c8
 
cff1609
e0266c8
 
7f7b879
ab91fef
e0266c8
cff1609
d5b01e8
cff1609
 
f6f36b6
e0266c8
4338241
 
 
 
 
 
 
 
e0266c8
 
 
4af161c
 
 
 
 
 
ab91fef
cff1609
e0266c8
 
 
 
cff1609
 
e0266c8
082d255
 
e0266c8
 
082d255
ab91fef
e0266c8
ab91fef
4ada954
4338241
ab91fef
 
e0266c8
45284c6
4338241
 
 
e0266c8
4338241
 
 
 
cff1609
e0266c8
 
 
 
 
cff1609
 
4af161c
 
 
 
 
4338241
e0266c8
 
4338241
e0266c8
 
 
 
 
 
 
 
cff1609
e0266c8
 
 
 
 
 
 
 
cff1609
ab91fef
e0266c8
cff1609
 
 
e0266c8
 
cff1609
e0266c8
 
 
 
 
 
 
cff1609
e0266c8
 
cff1609
e0266c8
 
 
 
cff1609
 
e0266c8
 
 
ab91fef
e0266c8
 
cff1609
e0266c8
 
 
 
4338241
cff1609
4338241
cff1609
e0266c8
cff1609
e0266c8
 
4af161c
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
import os
import shutil
import subprocess
import uuid
from datetime import timedelta
from typing import List
from fastapi import FastAPI, UploadFile, File
from fastapi.responses import FileResponse, JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from faster_whisper import WhisperModel
from pydantic import BaseModel

app = FastAPI(title="AI Subtitle Studio Pro")

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)

TEMP_DIR = "temp"
os.makedirs(TEMP_DIR, exist_ok=True)

print("⚡ [SYSTEM] Initializing AI Neural Network...")
model = WhisperModel("small", device="cpu", compute_type="int8")
print("✅ [SYSTEM] AI Core Ready.")

# --- MODELS ---
class SubtitleSegment(BaseModel):
    id: int
    start: float
    end: float
    text: str

class StyleConfig(BaseModel):
    font: str
    fontSize: int
    primaryColor: str
    outlineColor: str
    backType: str  
    outlineWidth: float
    marginV: int
    alignment: int 

class ProcessRequest(BaseModel):
    file_id: str
    segments: List[SubtitleSegment]
    style: StyleConfig

# --- HELPERS ---
def hex_to_ass(hex_color, alpha="00"):
    hex_color = hex_color.lstrip('#')
    if len(hex_color) != 6: return "&H00FFFFFF"
    r, g, b = hex_color[0:2], hex_color[2:4], hex_color[4:6]
    return f"&H{alpha}{b}{g}{r}"

def format_time_ass(seconds: float):
    td = timedelta(seconds=seconds)
    total_seconds = int(td.total_seconds())
    hours = total_seconds // 3600
    minutes = (total_seconds % 3600) // 60
    secs = total_seconds % 60
    centisecs = int(td.microseconds / 10000)
    return f"{hours:01d}:{minutes:02d}:{secs:02d}.{centisecs:02d}"

def generate_ass_file(data: ProcessRequest, output_path: str):
    s = data.style
    
    font_map = {
        "vazir": "Vazirmatn",
        "lalezar": "Lalezar",
        "roboto": "Roboto",
        "bangers": "Bangers"
    }
    font_name = font_map.get(s.font, "Arial")
    
    primary = hex_to_ass(s.primaryColor)
    outline = hex_to_ass(s.outlineColor)
    
    border_style = 1
    back_color = "&H00000000"
    
    if s.backType == 'solid':
        border_style = 3 
        outline = hex_to_ass(s.outlineColor, "00") 
    elif s.backType == 'transparent':
        border_style = 3
        outline = "&H80000000" 
    else:
        border_style = 1
        
    header = f"""[Script Info]
ScriptType: v4.00+
PlayResX: 1080
PlayResY: 1920
WrapStyle: 1
ScaledBorderAndShadow: yes

[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,{font_name},{s.fontSize},{primary},&H000000FF,{outline},{back_color},1,0,0,0,100,100,0,0,{border_style},{s.outlineWidth},0,{s.alignment},10,10,{s.marginV},1

[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
"""
    with open(output_path, "w", encoding="utf-8") as f:
        f.write(header)
        for seg in data.segments:
            start = format_time_ass(seg.start)
            end = format_time_ass(seg.end)
            clean_text = seg.text.strip().replace("\n", "\\N")
            f.write(f"Dialogue: 0,{start},{end},Default,,0,0,0,,{clean_text}\n")

# --- ROUTES ---

@app.get("/")
async def home():
    return FileResponse("index.html")

@app.post("/api/analyze")
async def analyze_media(file: UploadFile = File(...)):
    try:
        file_id = str(uuid.uuid4())[:12]
        file_ext = file.filename.split('.')[-1]
        input_path = f"{TEMP_DIR}/{file_id}.{file_ext}"
        
        with open(input_path, "wb") as f:
            shutil.copyfileobj(file.file, f)
            
        segments_gen, _ = model.transcribe(input_path, language="fa", beam_size=5)
        
        results = []
        for idx, seg in enumerate(segments_gen):
            results.append({
                "id": idx,
                "start": seg.start,
                "end": seg.end,
                "text": seg.text.strip()
            })
            
        return {"status": "success", "file_id": file_id, "segments": results}
        
    except Exception as e:
        return JSONResponse(status_code=500, content={"error": str(e)})

@app.post("/api/render")
async def render_video(data: ProcessRequest):
    try:
        exts = ["mp4", "mov", "avi", "mkv", "mp3"]
        input_path = None
        for ext in exts:
            test_path = f"{TEMP_DIR}/{data.file_id}.{ext}"
            if os.path.exists(test_path):
                input_path = test_path
                break
        
        if not input_path:
            return JSONResponse(status_code=404, content={"error": "Source file lost"})
            
        ass_path = f"{TEMP_DIR}/{data.file_id}.ass"
        output_path = f"{TEMP_DIR}/{data.file_id}_final.mp4"
        
        generate_ass_file(data, ass_path)
        
        cmd = [
            "ffmpeg", "-y",
            "-i", input_path,
            "-vf", f"ass={ass_path}",
            "-c:v", "libx264", "-preset", "ultrafast", "-crf", "26",
            "-c:a", "aac",
            output_path
        ]
        subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        
        return {"status": "done", "url": f"/download/{data.file_id}_final.mp4"}
        
    except Exception as e:
        return JSONResponse(status_code=500, content={"error": str(e)})

@app.get("/download/{filename}")
async def get_file(filename: str):
    path = f"{TEMP_DIR}/{filename}"
    if os.path.exists(path):
        return FileResponse(path)
    return JSONResponse(status_code=404, content={"error": "File missing"})