ulduldp commited on
Commit
2635203
·
verified ·
1 Parent(s): 0925d9b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +32 -75
app.py CHANGED
@@ -243,27 +243,21 @@ form.addEventListener("submit", async (e)=>{
243
  """
244
 
245
  def ass_time(seconds: float) -> str:
246
-
247
  if seconds < 0:
248
  seconds = 0
249
-
250
  h = int(seconds // 3600)
251
  m = int((seconds % 3600) // 60)
252
  s = seconds % 60
253
-
254
  return f"{h}:{m:02d}:{s:05.2f}"
255
 
256
  def ass_escape(text: str) -> str:
257
-
258
  text = text.replace("\\", "\\\\")
259
  text = text.replace("{", "\\{")
260
  text = text.replace("}", "\\}")
261
  text = text.replace("\n", " ")
262
-
263
  return text
264
 
265
  def escape_ffmpeg_path(path: str) -> str:
266
-
267
  return (
268
  path
269
  .replace("\\", "\\\\")
@@ -271,8 +265,29 @@ def escape_ffmpeg_path(path: str) -> str:
271
  .replace("'", r"\'")
272
  )
273
 
274
- def make_ass_subtitles(segments, ass_path):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
 
 
276
  header = """[Script Info]
277
  ScriptType: v4.00+
278
  PlayResX: 1080
@@ -283,7 +298,7 @@ WrapStyle: 2
283
  [V4+ Styles]
284
  Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
285
 
286
- Style: Default,Arial,62,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,1,0,0,0,100,100,0,0,3,0,0,2,90,90,160,1
287
 
288
  [Events]
289
  Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
@@ -292,37 +307,18 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
292
  lines = [header]
293
 
294
  for seg in segments:
295
-
296
  start = ass_time(seg["start"])
297
  end = ass_time(seg["end"])
298
-
299
  text = seg["text"].strip()
300
 
301
  if not text:
302
  continue
303
 
304
- # Dynamic wrapping
305
- if len(text) <= 25:
306
- wrap_width = 18
307
- elif len(text) <= 60:
308
- wrap_width = 24
309
- elif len(text) <= 100:
310
- wrap_width = 30
311
- else:
312
- wrap_width = 36
313
-
314
- wrapped = textwrap.fill(
315
- text,
316
- width=wrap_width,
317
- break_long_words=False,
318
- break_on_hyphens=False
319
- )
320
-
321
  wrapped = ass_escape(wrapped)
322
-
323
  wrapped = wrapped.replace("\n", r"\N")
324
 
325
- # Pure white text + solid black box
326
  dialogue = (
327
  f"Dialogue: 0,{start},{end},Default,,0,0,0,,"
328
  r"{\bord0\shad0\blur0\1c&HFFFFFF&\4c&H000000&}"
@@ -340,7 +336,6 @@ def home():
340
 
341
  @app.route("/generate", methods=["POST"])
342
  def generate():
343
-
344
  if "image" not in request.files or "audio" not in request.files:
345
  return jsonify({"error":"Missing files"})
346
 
@@ -355,33 +350,16 @@ def generate():
355
  image_name = secure_filename(image.filename)
356
  audio_name = secure_filename(audio.filename)
357
 
358
- image_path = os.path.join(
359
- UPLOAD_FOLDER,
360
- f"{uid}_{image_name}"
361
- )
362
-
363
- audio_path = os.path.join(
364
- UPLOAD_FOLDER,
365
- f"{uid}_{audio_name}"
366
- )
367
-
368
  output_filename = f"{uid}.mp4"
369
-
370
- output_path = os.path.join(
371
- OUTPUT_FOLDER,
372
- output_filename
373
- )
374
-
375
- ass_path = os.path.join(
376
- SUBTITLE_FOLDER,
377
- f"{uid}.ass"
378
- )
379
 
380
  image.save(image_path)
381
  audio.save(audio_path)
382
 
383
  try:
384
-
385
  # Fast low CPU transcription
386
  segments_iter, info = model.transcribe(
387
  audio_path,
@@ -393,9 +371,7 @@ def generate():
393
  full_text_parts = []
394
 
395
  for segment in segments_iter:
396
-
397
  text = segment.text.strip()
398
-
399
  if not text:
400
  continue
401
 
@@ -404,16 +380,12 @@ def generate():
404
  "end": round(segment.end, 2),
405
  "text": text
406
  })
407
-
408
  full_text_parts.append(text)
409
 
410
  make_ass_subtitles(transcript, ass_path)
 
411
 
412
- safe_ass_path = escape_ffmpeg_path(
413
- os.path.abspath(ass_path)
414
- )
415
-
416
- # Optimized low CPU video rendering
417
  vf = (
418
  "scale=1080:1920:force_original_aspect_ratio=increase,"
419
  "crop=1080:1920,"
@@ -423,27 +395,17 @@ def generate():
423
  cmd = [
424
  "ffmpeg",
425
  "-y",
426
-
427
  "-loop", "1",
428
  "-i", image_path,
429
-
430
  "-i", audio_path,
431
-
432
  "-vf", vf,
433
-
434
  "-c:v", "libx264",
435
-
436
  "-preset", "ultrafast",
437
-
438
  "-pix_fmt", "yuv420p",
439
-
440
  "-r", "24",
441
-
442
  "-c:a", "aac",
443
  "-b:a", "128k",
444
-
445
  "-shortest",
446
-
447
  output_path
448
  ]
449
 
@@ -462,17 +424,12 @@ def generate():
462
  })
463
 
464
  except subprocess.CalledProcessError as e:
465
-
466
  return jsonify({
467
  "error":"FFmpeg failed",
468
- "details": e.stderr.decode(
469
- "utf-8",
470
- errors="ignore"
471
- )
472
  })
473
 
474
  except Exception as e:
475
-
476
  return jsonify({
477
  "error":"Processing failed",
478
  "details": str(e)
 
243
  """
244
 
245
  def ass_time(seconds: float) -> str:
 
246
  if seconds < 0:
247
  seconds = 0
 
248
  h = int(seconds // 3600)
249
  m = int((seconds % 3600) // 60)
250
  s = seconds % 60
 
251
  return f"{h}:{m:02d}:{s:05.2f}"
252
 
253
  def ass_escape(text: str) -> str:
 
254
  text = text.replace("\\", "\\\\")
255
  text = text.replace("{", "\\{")
256
  text = text.replace("}", "\\}")
257
  text = text.replace("\n", " ")
 
258
  return text
259
 
260
  def escape_ffmpeg_path(path: str) -> str:
 
261
  return (
262
  path
263
  .replace("\\", "\\\\")
 
265
  .replace("'", r"\'")
266
  )
267
 
268
+ def wrap_caption_text(text: str) -> str:
269
+ text = text.strip()
270
+
271
+ # shorter wrap widths to prevent left/right crop
272
+ if len(text) <= 20:
273
+ width = 14
274
+ elif len(text) <= 40:
275
+ width = 18
276
+ elif len(text) <= 70:
277
+ width = 22
278
+ elif len(text) <= 110:
279
+ width = 26
280
+ else:
281
+ width = 30
282
+
283
+ return textwrap.fill(
284
+ text,
285
+ width=width,
286
+ break_long_words=False,
287
+ break_on_hyphens=False
288
+ )
289
 
290
+ def make_ass_subtitles(segments, ass_path):
291
  header = """[Script Info]
292
  ScriptType: v4.00+
293
  PlayResX: 1080
 
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,56,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,1,0,0,0,100,100,0,0,3,0,0,2,120,120,150,1
302
 
303
  [Events]
304
  Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
 
307
  lines = [header]
308
 
309
  for seg in segments:
 
310
  start = ass_time(seg["start"])
311
  end = ass_time(seg["end"])
 
312
  text = seg["text"].strip()
313
 
314
  if not text:
315
  continue
316
 
317
+ wrapped = wrap_caption_text(text)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
  wrapped = ass_escape(wrapped)
 
319
  wrapped = wrapped.replace("\n", r"\N")
320
 
321
+ # Solid black box, white text, no glow/blur
322
  dialogue = (
323
  f"Dialogue: 0,{start},{end},Default,,0,0,0,,"
324
  r"{\bord0\shad0\blur0\1c&HFFFFFF&\4c&H000000&}"
 
336
 
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
 
 
350
  image_name = secure_filename(image.filename)
351
  audio_name = secure_filename(audio.filename)
352
 
353
+ image_path = os.path.join(UPLOAD_FOLDER, f"{uid}_{image_name}")
354
+ audio_path = os.path.join(UPLOAD_FOLDER, f"{uid}_{audio_name}")
 
 
 
 
 
 
 
 
355
  output_filename = f"{uid}.mp4"
356
+ output_path = os.path.join(OUTPUT_FOLDER, output_filename)
357
+ ass_path = os.path.join(SUBTITLE_FOLDER, f"{uid}.ass")
 
 
 
 
 
 
 
 
358
 
359
  image.save(image_path)
360
  audio.save(audio_path)
361
 
362
  try:
 
363
  # Fast low CPU transcription
364
  segments_iter, info = model.transcribe(
365
  audio_path,
 
371
  full_text_parts = []
372
 
373
  for segment in segments_iter:
 
374
  text = segment.text.strip()
 
375
  if not text:
376
  continue
377
 
 
380
  "end": round(segment.end, 2),
381
  "text": text
382
  })
 
383
  full_text_parts.append(text)
384
 
385
  make_ass_subtitles(transcript, ass_path)
386
+ safe_ass_path = escape_ffmpeg_path(os.path.abspath(ass_path))
387
 
388
+ # No zoompan, only simple scale/crop
 
 
 
 
389
  vf = (
390
  "scale=1080:1920:force_original_aspect_ratio=increase,"
391
  "crop=1080:1920,"
 
395
  cmd = [
396
  "ffmpeg",
397
  "-y",
 
398
  "-loop", "1",
399
  "-i", image_path,
 
400
  "-i", audio_path,
 
401
  "-vf", vf,
 
402
  "-c:v", "libx264",
 
403
  "-preset", "ultrafast",
 
404
  "-pix_fmt", "yuv420p",
 
405
  "-r", "24",
 
406
  "-c:a", "aac",
407
  "-b:a", "128k",
 
408
  "-shortest",
 
409
  output_path
410
  ]
411
 
 
424
  })
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)