Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
from flask import Flask, render_template_string, request, jsonify
|
| 2 |
import os
|
| 3 |
import uuid
|
| 4 |
import subprocess
|
|
@@ -8,9 +8,11 @@ from faster_whisper import WhisperModel
|
|
| 8 |
|
| 9 |
app = Flask(__name__)
|
| 10 |
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
|
|
|
|
|
|
| 14 |
|
| 15 |
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
| 16 |
os.makedirs(OUTPUT_FOLDER, exist_ok=True)
|
|
@@ -169,7 +171,7 @@ video{
|
|
| 169 |
|
| 170 |
<div id="loading">Generating Video...</div>
|
| 171 |
|
| 172 |
-
<video id="video" controls></video>
|
| 173 |
|
| 174 |
<div class="download-btn" id="downloadDiv">
|
| 175 |
<a id="downloadBtn" download>Download Video</a>
|
|
@@ -249,6 +251,7 @@ def ass_escape(text: str) -> str:
|
|
| 249 |
return text
|
| 250 |
|
| 251 |
def escape_ffmpeg_path(path: str) -> str:
|
|
|
|
| 252 |
return (
|
| 253 |
path
|
| 254 |
.replace("\\", "\\\\")
|
|
@@ -309,7 +312,7 @@ 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
|
| 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}"
|
|
@@ -325,6 +328,21 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|
| 325 |
def home():
|
| 326 |
return render_template_string(HTML)
|
| 327 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 328 |
@app.route("/generate", methods=["POST"])
|
| 329 |
def generate():
|
| 330 |
if "image" not in request.files or "audio" not in request.files:
|
|
@@ -376,39 +394,50 @@ def generate():
|
|
| 376 |
make_ass_subtitles(transcript, ass_path)
|
| 377 |
safe_ass_path = escape_ffmpeg_path(os.path.abspath(ass_path))
|
| 378 |
|
| 379 |
-
#
|
| 380 |
vf = (
|
| 381 |
"scale=1080:1920:force_original_aspect_ratio=increase,"
|
| 382 |
"crop=1080:1920,"
|
| 383 |
-
f"
|
| 384 |
)
|
| 385 |
|
| 386 |
cmd = [
|
| 387 |
"ffmpeg",
|
| 388 |
"-y",
|
| 389 |
"-loop", "1",
|
|
|
|
| 390 |
"-i", image_path,
|
| 391 |
"-i", audio_path,
|
| 392 |
"-vf", vf,
|
|
|
|
|
|
|
| 393 |
"-c:v", "libx264",
|
| 394 |
"-preset", "ultrafast",
|
|
|
|
| 395 |
"-pix_fmt", "yuv420p",
|
| 396 |
"-r", "24",
|
| 397 |
"-c:a", "aac",
|
| 398 |
"-b:a", "128k",
|
|
|
|
| 399 |
"-shortest",
|
| 400 |
output_path
|
| 401 |
]
|
| 402 |
|
| 403 |
-
subprocess.run(
|
| 404 |
cmd,
|
| 405 |
stdout=subprocess.PIPE,
|
| 406 |
stderr=subprocess.PIPE,
|
| 407 |
check=True
|
| 408 |
)
|
| 409 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 410 |
return jsonify({
|
| 411 |
-
"video_url": f"/
|
| 412 |
"transcript": transcript,
|
| 413 |
"full_text": " ".join(full_text_parts).strip(),
|
| 414 |
"language": getattr(info, "language", None)
|
|
@@ -427,4 +456,4 @@ def generate():
|
|
| 427 |
})
|
| 428 |
|
| 429 |
if __name__ == "__main__":
|
| 430 |
-
app.run(host="0.0.0.0", port=7860)
|
|
|
|
| 1 |
+
from flask import Flask, render_template_string, request, jsonify, send_from_directory, abort
|
| 2 |
import os
|
| 3 |
import uuid
|
| 4 |
import subprocess
|
|
|
|
| 8 |
|
| 9 |
app = Flask(__name__)
|
| 10 |
|
| 11 |
+
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 12 |
+
|
| 13 |
+
UPLOAD_FOLDER = os.path.join(BASE_DIR, "uploads")
|
| 14 |
+
OUTPUT_FOLDER = os.path.join(BASE_DIR, "static", "videos")
|
| 15 |
+
SUBTITLE_FOLDER = os.path.join(BASE_DIR, "subtitles")
|
| 16 |
|
| 17 |
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
| 18 |
os.makedirs(OUTPUT_FOLDER, exist_ok=True)
|
|
|
|
| 171 |
|
| 172 |
<div id="loading">Generating Video...</div>
|
| 173 |
|
| 174 |
+
<video id="video" controls playsinline></video>
|
| 175 |
|
| 176 |
<div class="download-btn" id="downloadDiv">
|
| 177 |
<a id="downloadBtn" download>Download Video</a>
|
|
|
|
| 251 |
return text
|
| 252 |
|
| 253 |
def escape_ffmpeg_path(path: str) -> str:
|
| 254 |
+
# For ffmpeg filter strings
|
| 255 |
return (
|
| 256 |
path
|
| 257 |
.replace("\\", "\\\\")
|
|
|
|
| 312 |
wrapped = ass_escape(wrapped)
|
| 313 |
wrapped = wrapped.replace("\n", r"\N")
|
| 314 |
|
| 315 |
+
# Solid black background, white text
|
| 316 |
dialogue = (
|
| 317 |
f"Dialogue: 0,{start},{end},Default,,0,0,0,,"
|
| 318 |
r"{\bord0\shad0\blur0\1c&HFFFFFF&\3c&H000000&\4c&H000000&\4a&H00}"
|
|
|
|
| 328 |
def home():
|
| 329 |
return render_template_string(HTML)
|
| 330 |
|
| 331 |
+
@app.route("/video/<path:filename>")
|
| 332 |
+
def serve_video(filename):
|
| 333 |
+
file_path = os.path.join(OUTPUT_FOLDER, filename)
|
| 334 |
+
if not os.path.exists(file_path):
|
| 335 |
+
abort(404)
|
| 336 |
+
|
| 337 |
+
response = send_from_directory(
|
| 338 |
+
OUTPUT_FOLDER,
|
| 339 |
+
filename,
|
| 340 |
+
as_attachment=False,
|
| 341 |
+
conditional=True
|
| 342 |
+
)
|
| 343 |
+
response.headers["Cache-Control"] = "no-store"
|
| 344 |
+
return response
|
| 345 |
+
|
| 346 |
@app.route("/generate", methods=["POST"])
|
| 347 |
def generate():
|
| 348 |
if "image" not in request.files or "audio" not in request.files:
|
|
|
|
| 394 |
make_ass_subtitles(transcript, ass_path)
|
| 395 |
safe_ass_path = escape_ffmpeg_path(os.path.abspath(ass_path))
|
| 396 |
|
| 397 |
+
# Scale/crop image to 9:16 + burn subtitles
|
| 398 |
vf = (
|
| 399 |
"scale=1080:1920:force_original_aspect_ratio=increase,"
|
| 400 |
"crop=1080:1920,"
|
| 401 |
+
f"ass='{safe_ass_path}'"
|
| 402 |
)
|
| 403 |
|
| 404 |
cmd = [
|
| 405 |
"ffmpeg",
|
| 406 |
"-y",
|
| 407 |
"-loop", "1",
|
| 408 |
+
"-framerate", "1",
|
| 409 |
"-i", image_path,
|
| 410 |
"-i", audio_path,
|
| 411 |
"-vf", vf,
|
| 412 |
+
"-map", "0:v:0",
|
| 413 |
+
"-map", "1:a:0",
|
| 414 |
"-c:v", "libx264",
|
| 415 |
"-preset", "ultrafast",
|
| 416 |
+
"-crf", "20",
|
| 417 |
"-pix_fmt", "yuv420p",
|
| 418 |
"-r", "24",
|
| 419 |
"-c:a", "aac",
|
| 420 |
"-b:a", "128k",
|
| 421 |
+
"-movflags", "+faststart",
|
| 422 |
"-shortest",
|
| 423 |
output_path
|
| 424 |
]
|
| 425 |
|
| 426 |
+
result = subprocess.run(
|
| 427 |
cmd,
|
| 428 |
stdout=subprocess.PIPE,
|
| 429 |
stderr=subprocess.PIPE,
|
| 430 |
check=True
|
| 431 |
)
|
| 432 |
|
| 433 |
+
if not os.path.exists(output_path) or os.path.getsize(output_path) == 0:
|
| 434 |
+
return jsonify({
|
| 435 |
+
"error": "Video file not created",
|
| 436 |
+
"details": "FFmpeg ran but output file is missing or empty."
|
| 437 |
+
})
|
| 438 |
+
|
| 439 |
return jsonify({
|
| 440 |
+
"video_url": f"/video/{output_filename}",
|
| 441 |
"transcript": transcript,
|
| 442 |
"full_text": " ".join(full_text_parts).strip(),
|
| 443 |
"language": getattr(info, "language", None)
|
|
|
|
| 456 |
})
|
| 457 |
|
| 458 |
if __name__ == "__main__":
|
| 459 |
+
app.run(host="0.0.0.0", port=7860, debug=True)
|