ulduldp commited on
Commit
e502d42
·
verified ·
1 Parent(s): 0d51b0d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +30 -31
app.py CHANGED
@@ -18,7 +18,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
- # Smallest + fastest Whisper model for CPU
22
  model = WhisperModel(
23
  "tiny",
24
  device="cpu",
@@ -148,13 +148,10 @@ video{
148
  <body>
149
 
150
  <div class="container">
151
-
152
  <h1>Photo + Audio → Video</h1>
153
 
154
  <form id="form">
155
-
156
  <div class="upload-box">
157
-
158
  <label>Select Photo</label>
159
  <input type="file" id="image" name="image" accept="image/*" required>
160
 
@@ -162,11 +159,9 @@ video{
162
 
163
  <label>Select Audio (mp3/wav)</label>
164
  <input type="file" name="audio" accept="audio/*" required>
165
-
166
  </div>
167
 
168
  <button type="submit">Generate Video</button>
169
-
170
  </form>
171
 
172
  <div id="loading">Generating Video...</div>
@@ -176,7 +171,6 @@ video{
176
  <div class="download-btn" id="downloadDiv">
177
  <a id="downloadBtn" download>Download Video</a>
178
  </div>
179
-
180
  </div>
181
 
182
  <script>
@@ -211,7 +205,6 @@ form.addEventListener("submit", async (e)=>{
211
  });
212
 
213
  const data = await response.json();
214
-
215
  loading.style.display = "none";
216
 
217
  if(data.video_url){
@@ -222,11 +215,12 @@ form.addEventListener("submit", async (e)=>{
222
  downloadDiv.style.display = "block";
223
  }else{
224
  alert(data.error || "Failed");
 
225
  }
226
-
227
  }catch(err){
228
  loading.style.display = "none";
229
  alert("Server Error");
 
230
  }
231
  });
232
  </script>
@@ -251,7 +245,6 @@ def ass_escape(text: str) -> str:
251
  return text
252
 
253
  def escape_ffmpeg_path(path: str) -> str:
254
- # For ffmpeg filter strings
255
  return (
256
  path
257
  .replace("\\", "\\\\")
@@ -262,25 +255,34 @@ def escape_ffmpeg_path(path: str) -> str:
262
  def wrap_caption_text(text: str) -> str:
263
  text = text.strip()
264
 
265
- # Smaller wrap widths to prevent left/right crop
266
- if len(text) <= 20:
267
- width = 12
268
- elif len(text) <= 40:
269
- width = 15
270
- elif len(text) <= 70:
 
 
271
  width = 18
272
- elif len(text) <= 110:
273
- width = 22
274
  else:
275
- width = 24
276
 
277
- return textwrap.fill(
278
  text,
279
  width=width,
280
  break_long_words=False,
281
  break_on_hyphens=False
282
  )
283
 
 
 
 
 
 
 
 
284
  def make_ass_subtitles(segments, ass_path):
285
  header = """[Script Info]
286
  ScriptType: v4.00+
@@ -292,7 +294,7 @@ WrapStyle: 2
292
  [V4+ Styles]
293
  Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
294
 
295
- Style: Default,Arial,48,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,1,0,0,0,100,100,0,0,3,0,0,2,90,90,170,1
296
 
297
  [Events]
298
  Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
@@ -301,21 +303,20 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
301
  lines = [header]
302
 
303
  for seg in segments:
304
- start = ass_time(seg["start"])
305
- end = ass_time(seg["end"])
306
  text = seg["text"].strip()
307
-
308
  if not text:
309
  continue
310
 
 
 
 
311
  wrapped = wrap_caption_text(text)
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}"
319
  f"{wrapped}\n"
320
  )
321
 
@@ -369,7 +370,6 @@ def generate():
369
  audio.save(audio_path)
370
 
371
  try:
372
- # Fast low CPU transcription
373
  segments_iter, info = model.transcribe(
374
  audio_path,
375
  beam_size=1,
@@ -394,7 +394,6 @@ def generate():
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,"
@@ -423,7 +422,7 @@ def generate():
423
  output_path
424
  ]
425
 
426
- result = subprocess.run(
427
  cmd,
428
  stdout=subprocess.PIPE,
429
  stderr=subprocess.PIPE,
 
18
  os.makedirs(OUTPUT_FOLDER, exist_ok=True)
19
  os.makedirs(SUBTITLE_FOLDER, exist_ok=True)
20
 
21
+ # Fast CPU model
22
  model = WhisperModel(
23
  "tiny",
24
  device="cpu",
 
148
  <body>
149
 
150
  <div class="container">
 
151
  <h1>Photo + Audio → Video</h1>
152
 
153
  <form id="form">
 
154
  <div class="upload-box">
 
155
  <label>Select Photo</label>
156
  <input type="file" id="image" name="image" accept="image/*" required>
157
 
 
159
 
160
  <label>Select Audio (mp3/wav)</label>
161
  <input type="file" name="audio" accept="audio/*" required>
 
162
  </div>
163
 
164
  <button type="submit">Generate Video</button>
 
165
  </form>
166
 
167
  <div id="loading">Generating Video...</div>
 
171
  <div class="download-btn" id="downloadDiv">
172
  <a id="downloadBtn" download>Download Video</a>
173
  </div>
 
174
  </div>
175
 
176
  <script>
 
205
  });
206
 
207
  const data = await response.json();
 
208
  loading.style.display = "none";
209
 
210
  if(data.video_url){
 
215
  downloadDiv.style.display = "block";
216
  }else{
217
  alert(data.error || "Failed");
218
+ console.log(data.details || "");
219
  }
 
220
  }catch(err){
221
  loading.style.display = "none";
222
  alert("Server Error");
223
+ console.error(err);
224
  }
225
  });
226
  </script>
 
245
  return text
246
 
247
  def escape_ffmpeg_path(path: str) -> str:
 
248
  return (
249
  path
250
  .replace("\\", "\\\\")
 
255
  def wrap_caption_text(text: str) -> str:
256
  text = text.strip()
257
 
258
+ # tighter wrapping to prevent crop on 9:16 frame
259
+ if len(text) <= 18:
260
+ width = 10
261
+ elif len(text) <= 35:
262
+ width = 13
263
+ elif len(text) <= 55:
264
+ width = 16
265
+ elif len(text) <= 80:
266
  width = 18
267
+ elif len(text) <= 120:
268
+ width = 20
269
  else:
270
+ width = 22
271
 
272
+ wrapped = textwrap.fill(
273
  text,
274
  width=width,
275
  break_long_words=False,
276
  break_on_hyphens=False
277
  )
278
 
279
+ # keep captions from getting too tall
280
+ lines = wrapped.splitlines()
281
+ if len(lines) > 4:
282
+ wrapped = "\n".join(lines[:4])
283
+
284
+ return wrapped
285
+
286
  def make_ass_subtitles(segments, ass_path):
287
  header = """[Script Info]
288
  ScriptType: v4.00+
 
294
  [V4+ Styles]
295
  Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
296
 
297
+ Style: Default,Arial,40,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,1,0,0,0,100,100,0,0,3,0,0,2,140,140,260,1
298
 
299
  [Events]
300
  Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
 
303
  lines = [header]
304
 
305
  for seg in segments:
 
 
306
  text = seg["text"].strip()
 
307
  if not text:
308
  continue
309
 
310
+ start = ass_time(seg["start"])
311
+ end = ass_time(seg["end"])
312
+
313
  wrapped = wrap_caption_text(text)
314
+ wrapped = ass_escape(wrapped).replace("\n", r"\N")
 
315
 
316
+ # Opaque black background box + white text
317
  dialogue = (
318
  f"Dialogue: 0,{start},{end},Default,,0,0,0,,"
319
+ r"{\bord0\shad0\blur0\be0\1c&HFFFFFF&\3c&H000000&\4c&H000000&\3a&H00&\4a&H00}"
320
  f"{wrapped}\n"
321
  )
322
 
 
370
  audio.save(audio_path)
371
 
372
  try:
 
373
  segments_iter, info = model.transcribe(
374
  audio_path,
375
  beam_size=1,
 
394
  make_ass_subtitles(transcript, ass_path)
395
  safe_ass_path = escape_ffmpeg_path(os.path.abspath(ass_path))
396
 
 
397
  vf = (
398
  "scale=1080:1920:force_original_aspect_ratio=increase,"
399
  "crop=1080:1920,"
 
422
  output_path
423
  ]
424
 
425
+ subprocess.run(
426
  cmd,
427
  stdout=subprocess.PIPE,
428
  stderr=subprocess.PIPE,