ulduldp commited on
Commit
9782092
·
verified ·
1 Parent(s): 3beecbd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +36 -35
app.py CHANGED
@@ -2,8 +2,6 @@ from flask import Flask, render_template_string, request, jsonify
2
  import os
3
  import uuid
4
  import subprocess
5
- import tempfile
6
- import shutil
7
  import textwrap
8
  from werkzeug.utils import secure_filename
9
  from faster_whisper import WhisperModel
@@ -18,7 +16,7 @@ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
18
  os.makedirs(OUTPUT_FOLDER, exist_ok=True)
19
  os.makedirs(SUBTITLE_FOLDER, exist_ok=True)
20
 
21
- # Load model once
22
  model = WhisperModel(
23
  "base",
24
  device="cpu",
@@ -180,9 +178,9 @@ form.addEventListener("submit", async (e)=>{
180
  const formData = new FormData(form);
181
 
182
  try{
183
- const response = await fetch("/generate",{
184
- method:"POST",
185
- body:formData
186
  });
187
 
188
  const data = await response.json();
@@ -198,7 +196,6 @@ form.addEventListener("submit", async (e)=>{
198
  }else{
199
  alert(data.error || "Failed");
200
  }
201
-
202
  }catch(err){
203
  loading.style.display = "none";
204
  alert("Server Error");
@@ -218,12 +215,16 @@ def ass_time(seconds: float) -> str:
218
  return f"{h}:{m:02d}:{s:05.2f}"
219
 
220
  def ass_escape(text: str) -> str:
221
- # Escape ASS special chars
222
  text = text.replace("\\", "\\\\")
223
  text = text.replace("{", "\\{").replace("}", "\\}")
224
  text = text.replace("\n", " ")
225
  return text
226
 
 
 
 
 
 
227
  def make_ass_subtitles(segments, ass_path):
228
  header = """[Script Info]
229
  ScriptType: v4.00+
@@ -244,11 +245,16 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
244
  start = ass_time(seg["start"])
245
  end = ass_time(seg["end"])
246
 
247
- text = textwrap.fill(seg["text"].strip(), width=36)
248
- text = ass_escape(text).replace("\n", r"\N")
 
 
 
 
 
249
 
250
  lines.append(
251
- f"Dialogue: 0,{start},{end},Default,,0,0,0,,{text}\n"
252
  )
253
 
254
  with open(ass_path, "w", encoding="utf-8") as f:
@@ -274,19 +280,17 @@ def generate():
274
  image_name = secure_filename(image.filename)
275
  audio_name = secure_filename(audio.filename)
276
 
277
- image_path = os.path.join(UPLOAD_FOLDER, uid + "_" + image_name)
278
- audio_path = os.path.join(UPLOAD_FOLDER, uid + "_" + audio_name)
279
-
280
- output_filename = uid + ".mp4"
281
  output_path = os.path.join(OUTPUT_FOLDER, output_filename)
282
-
283
- ass_path = os.path.join(SUBTITLE_FOLDER, uid + ".ass")
284
 
285
  image.save(image_path)
286
  audio.save(audio_path)
287
 
288
  try:
289
- # 1) Transcribe audio
290
  segments_iter, info = model.transcribe(
291
  audio_path,
292
  beam_size=5,
@@ -308,20 +312,19 @@ def generate():
308
  })
309
  full_text_parts.append(text)
310
 
311
- full_text = " ".join(full_text_parts).strip()
312
-
313
- # 2) Create ASS subtitles
314
  make_ass_subtitles(transcript, ass_path)
315
 
316
- # 3) FFmpeg command with smooth animation + subtitles
317
- # Subtle zoom/pan effect + subtitles at bottom
318
- filter_complex = (
319
- f"[0:v]scale=1280:720:force_original_aspect_ratio=increase,"
320
- f"crop=1280:720,"
321
- f"zoompan=z='min(zoom+0.0008,1.08)':"
322
- f"x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':"
323
- f"d=1:s=1280x720:fps=30,"
324
- f"subtitles='{ass_path.replace(\"'\", \"\\\\'\")}'[v]"
 
 
325
  )
326
 
327
  cmd = [
@@ -330,9 +333,7 @@ def generate():
330
  "-loop", "1",
331
  "-i", image_path,
332
  "-i", audio_path,
333
- "-filter_complex", filter_complex,
334
- "-map", "[v]",
335
- "-map", "1:a:0",
336
  "-c:v", "libx264",
337
  "-pix_fmt", "yuv420p",
338
  "-c:a", "aac",
@@ -341,7 +342,7 @@ def generate():
341
  output_path
342
  ]
343
 
344
- subprocess.run(
345
  cmd,
346
  stdout=subprocess.PIPE,
347
  stderr=subprocess.PIPE,
@@ -351,7 +352,7 @@ def generate():
351
  return jsonify({
352
  "video_url": f"/static/videos/{output_filename}",
353
  "transcript": transcript,
354
- "full_text": full_text,
355
  "language": getattr(info, "language", None)
356
  })
357
 
 
2
  import os
3
  import uuid
4
  import subprocess
 
 
5
  import textwrap
6
  from werkzeug.utils import secure_filename
7
  from faster_whisper import WhisperModel
 
16
  os.makedirs(OUTPUT_FOLDER, exist_ok=True)
17
  os.makedirs(SUBTITLE_FOLDER, exist_ok=True)
18
 
19
+ # Load Whisper once
20
  model = WhisperModel(
21
  "base",
22
  device="cpu",
 
178
  const formData = new FormData(form);
179
 
180
  try{
181
+ const response = await fetch("/generate", {
182
+ method: "POST",
183
+ body: formData
184
  });
185
 
186
  const data = await response.json();
 
196
  }else{
197
  alert(data.error || "Failed");
198
  }
 
199
  }catch(err){
200
  loading.style.display = "none";
201
  alert("Server Error");
 
215
  return f"{h}:{m:02d}:{s:05.2f}"
216
 
217
  def ass_escape(text: str) -> str:
 
218
  text = text.replace("\\", "\\\\")
219
  text = text.replace("{", "\\{").replace("}", "\\}")
220
  text = text.replace("\n", " ")
221
  return text
222
 
223
+ def escape_ffmpeg_path(path: str) -> str:
224
+ # Escape for FFmpeg subtitles filter
225
+ # Works well for local Linux paths
226
+ return path.replace("\\", "\\\\").replace(":", "\\:").replace("'", r"\'")
227
+
228
  def make_ass_subtitles(segments, ass_path):
229
  header = """[Script Info]
230
  ScriptType: v4.00+
 
245
  start = ass_time(seg["start"])
246
  end = ass_time(seg["end"])
247
 
248
+ text = seg["text"].strip()
249
+ if not text:
250
+ continue
251
+
252
+ # Wrap long subtitles nicely
253
+ wrapped = textwrap.fill(text, width=36)
254
+ wrapped = ass_escape(wrapped).replace("\n", r"\N")
255
 
256
  lines.append(
257
+ f"Dialogue: 0,{start},{end},Default,,0,0,0,,{wrapped}\n"
258
  )
259
 
260
  with open(ass_path, "w", encoding="utf-8") as f:
 
280
  image_name = secure_filename(image.filename)
281
  audio_name = secure_filename(audio.filename)
282
 
283
+ image_path = os.path.join(UPLOAD_FOLDER, f"{uid}_{image_name}")
284
+ audio_path = os.path.join(UPLOAD_FOLDER, f"{uid}_{audio_name}")
285
+ output_filename = f"{uid}.mp4"
 
286
  output_path = os.path.join(OUTPUT_FOLDER, output_filename)
287
+ ass_path = os.path.join(SUBTITLE_FOLDER, f"{uid}.ass")
 
288
 
289
  image.save(image_path)
290
  audio.save(audio_path)
291
 
292
  try:
293
+ # Transcribe audio
294
  segments_iter, info = model.transcribe(
295
  audio_path,
296
  beam_size=5,
 
312
  })
313
  full_text_parts.append(text)
314
 
 
 
 
315
  make_ass_subtitles(transcript, ass_path)
316
 
317
+ safe_ass_path = escape_ffmpeg_path(os.path.abspath(ass_path))
318
+
319
+ # Smooth zoom animation + subtitles
320
+ vf = (
321
+ "scale=1280:720:force_original_aspect_ratio=increase,"
322
+ "crop=1280:720,"
323
+ "zoompan=z='min(zoom+0.0008,1.08)':"
324
+ "x='iw/2-(iw/zoom/2)':"
325
+ "y='ih/2-(ih/zoom/2)':"
326
+ "d=999999:s=1280x720:fps=30,"
327
+ f"subtitles='{safe_ass_path}'"
328
  )
329
 
330
  cmd = [
 
333
  "-loop", "1",
334
  "-i", image_path,
335
  "-i", audio_path,
336
+ "-vf", vf,
 
 
337
  "-c:v", "libx264",
338
  "-pix_fmt", "yuv420p",
339
  "-c:a", "aac",
 
342
  output_path
343
  ]
344
 
345
+ result = subprocess.run(
346
  cmd,
347
  stdout=subprocess.PIPE,
348
  stderr=subprocess.PIPE,
 
352
  return jsonify({
353
  "video_url": f"/static/videos/{output_filename}",
354
  "transcript": transcript,
355
+ "full_text": " ".join(full_text_parts).strip(),
356
  "language": getattr(info, "language", None)
357
  })
358