Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -186,9 +186,7 @@ const downloadDiv = document.getElementById("downloadDiv");
|
|
| 186 |
const preview = document.getElementById("preview");
|
| 187 |
|
| 188 |
document.getElementById("image").addEventListener("change", function(e){
|
| 189 |
-
|
| 190 |
const file = e.target.files[0];
|
| 191 |
-
|
| 192 |
if(file){
|
| 193 |
preview.src = URL.createObjectURL(file);
|
| 194 |
preview.style.display = "block";
|
|
@@ -196,7 +194,6 @@ document.getElementById("image").addEventListener("change", function(e){
|
|
| 196 |
});
|
| 197 |
|
| 198 |
form.addEventListener("submit", async (e)=>{
|
| 199 |
-
|
| 200 |
e.preventDefault();
|
| 201 |
|
| 202 |
loading.style.display = "block";
|
|
@@ -206,7 +203,6 @@ form.addEventListener("submit", async (e)=>{
|
|
| 206 |
const formData = new FormData(form);
|
| 207 |
|
| 208 |
try{
|
| 209 |
-
|
| 210 |
const response = await fetch("/generate", {
|
| 211 |
method:"POST",
|
| 212 |
body:formData
|
|
@@ -217,24 +213,19 @@ form.addEventListener("submit", async (e)=>{
|
|
| 217 |
loading.style.display = "none";
|
| 218 |
|
| 219 |
if(data.video_url){
|
| 220 |
-
|
| 221 |
video.src = data.video_url + "?t=" + new Date().getTime();
|
| 222 |
video.style.display = "block";
|
| 223 |
|
| 224 |
downloadBtn.href = data.video_url;
|
| 225 |
downloadDiv.style.display = "block";
|
| 226 |
-
|
| 227 |
}else{
|
| 228 |
alert(data.error || "Failed");
|
| 229 |
}
|
| 230 |
|
| 231 |
}catch(err){
|
| 232 |
-
|
| 233 |
loading.style.display = "none";
|
| 234 |
alert("Server Error");
|
| 235 |
-
|
| 236 |
}
|
| 237 |
-
|
| 238 |
});
|
| 239 |
</script>
|
| 240 |
|
|
@@ -268,17 +259,17 @@ def escape_ffmpeg_path(path: str) -> str:
|
|
| 268 |
def wrap_caption_text(text: str) -> str:
|
| 269 |
text = text.strip()
|
| 270 |
|
| 271 |
-
#
|
| 272 |
if len(text) <= 20:
|
| 273 |
-
width =
|
| 274 |
elif len(text) <= 40:
|
| 275 |
-
width =
|
| 276 |
elif len(text) <= 70:
|
| 277 |
-
width =
|
| 278 |
elif len(text) <= 110:
|
| 279 |
-
width =
|
| 280 |
else:
|
| 281 |
-
width =
|
| 282 |
|
| 283 |
return textwrap.fill(
|
| 284 |
text,
|
|
@@ -298,7 +289,7 @@ WrapStyle: 2
|
|
| 298 |
[V4+ Styles]
|
| 299 |
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
| 300 |
|
| 301 |
-
Style: Default,Arial,
|
| 302 |
|
| 303 |
[Events]
|
| 304 |
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|
@@ -318,10 +309,10 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|
| 318 |
wrapped = ass_escape(wrapped)
|
| 319 |
wrapped = wrapped.replace("\n", r"\N")
|
| 320 |
|
| 321 |
-
# Solid black
|
| 322 |
dialogue = (
|
| 323 |
f"Dialogue: 0,{start},{end},Default,,0,0,0,,"
|
| 324 |
-
r"{\bord0\shad0\blur0\1c&HFFFFFF&\4c&H000000&}"
|
| 325 |
f"{wrapped}\n"
|
| 326 |
)
|
| 327 |
|
|
@@ -337,13 +328,13 @@ def home():
|
|
| 337 |
@app.route("/generate", methods=["POST"])
|
| 338 |
def generate():
|
| 339 |
if "image" not in request.files or "audio" not in request.files:
|
| 340 |
-
return jsonify({"error":"Missing files"})
|
| 341 |
|
| 342 |
image = request.files["image"]
|
| 343 |
audio = request.files["audio"]
|
| 344 |
|
| 345 |
if not image.filename or not audio.filename:
|
| 346 |
-
return jsonify({"error":"Please upload both image and audio"})
|
| 347 |
|
| 348 |
uid = str(uuid.uuid4())
|
| 349 |
|
|
@@ -385,7 +376,7 @@ def generate():
|
|
| 385 |
make_ass_subtitles(transcript, ass_path)
|
| 386 |
safe_ass_path = escape_ffmpeg_path(os.path.abspath(ass_path))
|
| 387 |
|
| 388 |
-
#
|
| 389 |
vf = (
|
| 390 |
"scale=1080:1920:force_original_aspect_ratio=increase,"
|
| 391 |
"crop=1080:1920,"
|
|
@@ -425,13 +416,13 @@ def generate():
|
|
| 425 |
|
| 426 |
except subprocess.CalledProcessError as e:
|
| 427 |
return jsonify({
|
| 428 |
-
"error":"FFmpeg failed",
|
| 429 |
"details": e.stderr.decode("utf-8", errors="ignore")
|
| 430 |
})
|
| 431 |
|
| 432 |
except Exception as e:
|
| 433 |
return jsonify({
|
| 434 |
-
"error":"Processing failed",
|
| 435 |
"details": str(e)
|
| 436 |
})
|
| 437 |
|
|
|
|
| 186 |
const preview = document.getElementById("preview");
|
| 187 |
|
| 188 |
document.getElementById("image").addEventListener("change", function(e){
|
|
|
|
| 189 |
const file = e.target.files[0];
|
|
|
|
| 190 |
if(file){
|
| 191 |
preview.src = URL.createObjectURL(file);
|
| 192 |
preview.style.display = "block";
|
|
|
|
| 194 |
});
|
| 195 |
|
| 196 |
form.addEventListener("submit", async (e)=>{
|
|
|
|
| 197 |
e.preventDefault();
|
| 198 |
|
| 199 |
loading.style.display = "block";
|
|
|
|
| 203 |
const formData = new FormData(form);
|
| 204 |
|
| 205 |
try{
|
|
|
|
| 206 |
const response = await fetch("/generate", {
|
| 207 |
method:"POST",
|
| 208 |
body:formData
|
|
|
|
| 213 |
loading.style.display = "none";
|
| 214 |
|
| 215 |
if(data.video_url){
|
|
|
|
| 216 |
video.src = data.video_url + "?t=" + new Date().getTime();
|
| 217 |
video.style.display = "block";
|
| 218 |
|
| 219 |
downloadBtn.href = data.video_url;
|
| 220 |
downloadDiv.style.display = "block";
|
|
|
|
| 221 |
}else{
|
| 222 |
alert(data.error || "Failed");
|
| 223 |
}
|
| 224 |
|
| 225 |
}catch(err){
|
|
|
|
| 226 |
loading.style.display = "none";
|
| 227 |
alert("Server Error");
|
|
|
|
| 228 |
}
|
|
|
|
| 229 |
});
|
| 230 |
</script>
|
| 231 |
|
|
|
|
| 259 |
def wrap_caption_text(text: str) -> str:
|
| 260 |
text = text.strip()
|
| 261 |
|
| 262 |
+
# Smaller wrap widths to prevent left/right crop
|
| 263 |
if len(text) <= 20:
|
| 264 |
+
width = 12
|
| 265 |
elif len(text) <= 40:
|
| 266 |
+
width = 15
|
| 267 |
elif len(text) <= 70:
|
| 268 |
+
width = 18
|
| 269 |
elif len(text) <= 110:
|
| 270 |
+
width = 22
|
| 271 |
else:
|
| 272 |
+
width = 24
|
| 273 |
|
| 274 |
return textwrap.fill(
|
| 275 |
text,
|
|
|
|
| 289 |
[V4+ Styles]
|
| 290 |
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
| 291 |
|
| 292 |
+
Style: Default,Arial,48,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,1,0,0,0,100,100,0,0,3,0,0,2,90,90,170,1
|
| 293 |
|
| 294 |
[Events]
|
| 295 |
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|
|
|
| 309 |
wrapped = ass_escape(wrapped)
|
| 310 |
wrapped = wrapped.replace("\n", r"\N")
|
| 311 |
|
| 312 |
+
# Solid black background, white text, no glow/blur
|
| 313 |
dialogue = (
|
| 314 |
f"Dialogue: 0,{start},{end},Default,,0,0,0,,"
|
| 315 |
+
r"{\bord0\shad0\blur0\1c&HFFFFFF&\3c&H000000&\4c&H000000&\4a&H00}"
|
| 316 |
f"{wrapped}\n"
|
| 317 |
)
|
| 318 |
|
|
|
|
| 328 |
@app.route("/generate", methods=["POST"])
|
| 329 |
def generate():
|
| 330 |
if "image" not in request.files or "audio" not in request.files:
|
| 331 |
+
return jsonify({"error": "Missing files"})
|
| 332 |
|
| 333 |
image = request.files["image"]
|
| 334 |
audio = request.files["audio"]
|
| 335 |
|
| 336 |
if not image.filename or not audio.filename:
|
| 337 |
+
return jsonify({"error": "Please upload both image and audio"})
|
| 338 |
|
| 339 |
uid = str(uuid.uuid4())
|
| 340 |
|
|
|
|
| 376 |
make_ass_subtitles(transcript, ass_path)
|
| 377 |
safe_ass_path = escape_ffmpeg_path(os.path.abspath(ass_path))
|
| 378 |
|
| 379 |
+
# Simple scale/crop + subtitles
|
| 380 |
vf = (
|
| 381 |
"scale=1080:1920:force_original_aspect_ratio=increase,"
|
| 382 |
"crop=1080:1920,"
|
|
|
|
| 416 |
|
| 417 |
except subprocess.CalledProcessError as e:
|
| 418 |
return jsonify({
|
| 419 |
+
"error": "FFmpeg failed",
|
| 420 |
"details": e.stderr.decode("utf-8", errors="ignore")
|
| 421 |
})
|
| 422 |
|
| 423 |
except Exception as e:
|
| 424 |
return jsonify({
|
| 425 |
+
"error": "Processing failed",
|
| 426 |
"details": str(e)
|
| 427 |
})
|
| 428 |
|