Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -161,25 +161,70 @@ def generate_translated_audio(srt_path, target_lang):
|
|
161 |
audio_file = os.path.join(temp_dir, f"chunk_{i:04d}.mp3")
|
162 |
|
163 |
try:
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
168 |
except Exception as e:
|
169 |
logger.warning(f"Failed to generate TTS for: {text}. Error: {str(e)}")
|
170 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
171 |
# Create a silent audio track the same length as the original video
|
172 |
silence_file = os.path.join(temp_dir, "silence.wav")
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
|
|
|
|
|
|
|
|
183 |
|
184 |
# Create silent audio track
|
185 |
silent_cmd = [
|
@@ -229,68 +274,179 @@ def generate_translated_audio(srt_path, target_lang):
|
|
229 |
|
230 |
# Run the command
|
231 |
logger.info(f"Combining audio segments: {' '.join(cmd)}")
|
232 |
-
subprocess.run(cmd, capture_output=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
233 |
|
234 |
# Clean up temporary files
|
235 |
-
|
|
|
|
|
|
|
236 |
|
237 |
return output_audio
|
238 |
except Exception as e:
|
239 |
logger.error(f"Audio translation failed: {str(e)}", exc_info=True)
|
240 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
241 |
|
242 |
def combine_video_audio_subtitles(video_path, audio_path, srt_path, output_path):
|
243 |
"""Combine video with translated audio and subtitles"""
|
244 |
try:
|
245 |
logger.info(f"Combining video, audio, and subtitles")
|
246 |
|
247 |
-
#
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
'-map', '1:a', # Use audio from second input
|
257 |
-
'-vf', f"subtitles={escaped_srt_path}:force_style='FontSize=24,PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,BorderStyle=3'", # Burn subtitles
|
258 |
-
'-c:v', 'libx264', # Video codec
|
259 |
-
'-c:a', 'aac', # Audio codec
|
260 |
-
'-shortest', # End when shortest input ends
|
261 |
-
'-y', # Overwrite output file
|
262 |
-
output_path
|
263 |
-
]
|
264 |
|
265 |
-
|
266 |
-
|
267 |
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
# Try alternative method
|
272 |
-
logger.info("Trying alternative method")
|
273 |
cmd = [
|
274 |
'ffmpeg',
|
275 |
-
'-i', video_path,
|
276 |
-
'-i', audio_path,
|
277 |
-
'-map', '0:v',
|
278 |
-
'-map', '1:a',
|
279 |
-
'-vf', f"subtitles=
|
280 |
-
'-c:v', 'libx264',
|
281 |
-
'-c:a', 'aac',
|
282 |
-
'-shortest',
|
283 |
-
'-y',
|
284 |
output_path
|
285 |
]
|
286 |
|
287 |
-
logger.info(f"Running
|
288 |
process = subprocess.run(cmd, capture_output=True, text=True)
|
289 |
|
290 |
if process.returncode != 0:
|
291 |
-
logger.
|
292 |
-
raise Exception(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
293 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
294 |
return output_path
|
295 |
except Exception as e:
|
296 |
logger.error(f"Combining failed: {str(e)}", exc_info=True)
|
@@ -340,6 +496,11 @@ def process_video(video_file, source_lang, target_langs, progress=gr.Progress())
|
|
340 |
logger.info(f"Generating translated audio for {lang_code}")
|
341 |
translated_audio = generate_translated_audio(sub_path, lang_code)
|
342 |
|
|
|
|
|
|
|
|
|
|
|
343 |
# Combine video, translated audio, and subtitles
|
344 |
output_path = os.path.join(OUTPUT_DIR, f"output_{lang_code}.mp4")
|
345 |
logger.info(f"Creating final video with {lang_code} audio and subtitles")
|
|
|
161 |
audio_file = os.path.join(temp_dir, f"chunk_{i:04d}.mp3")
|
162 |
|
163 |
try:
|
164 |
+
# Add a retry mechanism for Hindi and other potentially problematic languages
|
165 |
+
retry_count = 0
|
166 |
+
max_retries = 3
|
167 |
+
while retry_count < max_retries:
|
168 |
+
try:
|
169 |
+
# For Hindi, use slower speed which might improve reliability
|
170 |
+
slow_option = target_lang == "hi"
|
171 |
+
tts = gTTS(text=text, lang=target_lang, slow=slow_option)
|
172 |
+
tts.save(audio_file)
|
173 |
+
break
|
174 |
+
except Exception as e:
|
175 |
+
retry_count += 1
|
176 |
+
logger.warning(f"TTS attempt {retry_count} failed for {target_lang}: {str(e)}")
|
177 |
+
time.sleep(1) # Wait before retrying
|
178 |
+
|
179 |
+
# If still failing after retries, try with shorter text
|
180 |
+
if retry_count == max_retries and len(text) > 100:
|
181 |
+
logger.warning(f"Trying with shortened text for {target_lang}")
|
182 |
+
shortened_text = text[:100] + "..."
|
183 |
+
tts = gTTS(text=shortened_text, lang=target_lang, slow=True)
|
184 |
+
tts.save(audio_file)
|
185 |
+
|
186 |
+
if os.path.exists(audio_file) and os.path.getsize(audio_file) > 0:
|
187 |
+
audio_files.append(audio_file)
|
188 |
+
timings.append((start_time, end_time, duration, audio_file))
|
189 |
+
else:
|
190 |
+
logger.warning(f"Generated audio file is empty or does not exist: {audio_file}")
|
191 |
+
|
192 |
except Exception as e:
|
193 |
logger.warning(f"Failed to generate TTS for: {text}. Error: {str(e)}")
|
194 |
|
195 |
+
# Check if we actually generated any audio files
|
196 |
+
if not audio_files:
|
197 |
+
logger.warning(f"No audio files were generated for {target_lang}")
|
198 |
+
# Create a silent audio file as fallback
|
199 |
+
silent_audio = os.path.join(OUTPUT_DIR, f"translated_audio_{target_lang}.wav")
|
200 |
+
silent_cmd = [
|
201 |
+
'ffmpeg',
|
202 |
+
'-f', 'lavfi',
|
203 |
+
'-i', f'anullsrc=r=44100:cl=stereo',
|
204 |
+
'-t', '180', # 3 minutes default
|
205 |
+
'-q:a', '0',
|
206 |
+
'-y',
|
207 |
+
silent_audio
|
208 |
+
]
|
209 |
+
subprocess.run(silent_cmd, capture_output=True)
|
210 |
+
return silent_audio
|
211 |
+
|
212 |
# Create a silent audio track the same length as the original video
|
213 |
silence_file = os.path.join(temp_dir, "silence.wav")
|
214 |
+
try:
|
215 |
+
video_duration_cmd = [
|
216 |
+
'ffprobe',
|
217 |
+
'-v', 'error',
|
218 |
+
'-show_entries', 'format=duration',
|
219 |
+
'-of', 'default=noprint_wrappers=1:nokey=1',
|
220 |
+
os.path.join(OUTPUT_DIR, "base_video.mp4")
|
221 |
+
]
|
222 |
+
|
223 |
+
duration_result = subprocess.run(video_duration_cmd, capture_output=True, text=True)
|
224 |
+
video_duration = float(duration_result.stdout.strip())
|
225 |
+
except Exception as e:
|
226 |
+
logger.warning(f"Could not determine video duration: {str(e)}. Using default of 180 seconds.")
|
227 |
+
video_duration = 180.0
|
228 |
|
229 |
# Create silent audio track
|
230 |
silent_cmd = [
|
|
|
274 |
|
275 |
# Run the command
|
276 |
logger.info(f"Combining audio segments: {' '.join(cmd)}")
|
277 |
+
process = subprocess.run(cmd, capture_output=True)
|
278 |
+
|
279 |
+
if process.returncode != 0:
|
280 |
+
logger.error(f"Audio combination failed: {process.stderr}")
|
281 |
+
# Create a fallback silent audio as last resort
|
282 |
+
silent_audio = os.path.join(OUTPUT_DIR, f"translated_audio_{target_lang}.wav")
|
283 |
+
silent_cmd = [
|
284 |
+
'ffmpeg',
|
285 |
+
'-f', 'lavfi',
|
286 |
+
'-i', f'anullsrc=r=44100:cl=stereo',
|
287 |
+
'-t', str(video_duration),
|
288 |
+
'-q:a', '0',
|
289 |
+
'-y',
|
290 |
+
silent_audio
|
291 |
+
]
|
292 |
+
subprocess.run(silent_cmd, capture_output=True)
|
293 |
+
output_audio = silent_audio
|
294 |
+
|
295 |
+
# Verify the output file exists
|
296 |
+
if not os.path.exists(output_audio):
|
297 |
+
logger.error(f"Output audio file does not exist: {output_audio}")
|
298 |
+
# Create emergency fallback
|
299 |
+
silent_audio = os.path.join(OUTPUT_DIR, f"translated_audio_{target_lang}.wav")
|
300 |
+
silent_cmd = [
|
301 |
+
'ffmpeg',
|
302 |
+
'-f', 'lavfi',
|
303 |
+
'-i', f'anullsrc=r=44100:cl=stereo',
|
304 |
+
'-t', '180',
|
305 |
+
'-q:a', '0',
|
306 |
+
'-y',
|
307 |
+
silent_audio
|
308 |
+
]
|
309 |
+
subprocess.run(silent_cmd, capture_output=True)
|
310 |
+
output_audio = silent_audio
|
311 |
|
312 |
# Clean up temporary files
|
313 |
+
try:
|
314 |
+
shutil.rmtree(temp_dir)
|
315 |
+
except Exception as e:
|
316 |
+
logger.warning(f"Failed to clean up temp directory: {str(e)}")
|
317 |
|
318 |
return output_audio
|
319 |
except Exception as e:
|
320 |
logger.error(f"Audio translation failed: {str(e)}", exc_info=True)
|
321 |
+
# Create an emergency fallback silent audio
|
322 |
+
try:
|
323 |
+
silent_audio = os.path.join(OUTPUT_DIR, f"translated_audio_{target_lang}.wav")
|
324 |
+
silent_cmd = [
|
325 |
+
'ffmpeg',
|
326 |
+
'-f', 'lavfi',
|
327 |
+
'-i', f'anullsrc=r=44100:cl=stereo',
|
328 |
+
'-t', '180',
|
329 |
+
'-q:a', '0',
|
330 |
+
'-y',
|
331 |
+
silent_audio
|
332 |
+
]
|
333 |
+
subprocess.run(silent_cmd, capture_output=True)
|
334 |
+
return silent_audio
|
335 |
+
except:
|
336 |
+
raise Exception(f"Audio translation failed: {str(e)}")
|
337 |
|
338 |
def combine_video_audio_subtitles(video_path, audio_path, srt_path, output_path):
|
339 |
"""Combine video with translated audio and subtitles"""
|
340 |
try:
|
341 |
logger.info(f"Combining video, audio, and subtitles")
|
342 |
|
343 |
+
# Verify that all input files exist
|
344 |
+
if not os.path.exists(video_path):
|
345 |
+
raise Exception(f"Video file does not exist: {video_path}")
|
346 |
+
if not os.path.exists(audio_path):
|
347 |
+
raise Exception(f"Audio file does not exist: {audio_path}")
|
348 |
+
if not os.path.exists(srt_path):
|
349 |
+
raise Exception(f"Subtitle file does not exist: {srt_path}")
|
350 |
+
|
351 |
+
logger.info(f"Input files verified: Video: {os.path.getsize(video_path)} bytes, Audio: {os.path.getsize(audio_path)} bytes, Subtitles: {os.path.getsize(srt_path)} bytes")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
352 |
|
353 |
+
# Create a safe version of the subtitle path
|
354 |
+
safe_srt_path = srt_path.replace(" ", "\\ ").replace(":", "\\:")
|
355 |
|
356 |
+
# Command to combine video with translated audio and subtitles
|
357 |
+
try:
|
358 |
+
# Attempt method 1: Using subtitles filter
|
|
|
|
|
359 |
cmd = [
|
360 |
'ffmpeg',
|
361 |
+
'-i', video_path, # Input video
|
362 |
+
'-i', audio_path, # Input translated audio
|
363 |
+
'-map', '0:v', # Use video from first input
|
364 |
+
'-map', '1:a', # Use audio from second input
|
365 |
+
'-vf', f"subtitles={safe_srt_path}:force_style='FontSize=24,PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,BorderStyle=3'", # Burn subtitles
|
366 |
+
'-c:v', 'libx264', # Video codec
|
367 |
+
'-c:a', 'aac', # Audio codec
|
368 |
+
'-shortest', # End when shortest input ends
|
369 |
+
'-y', # Overwrite output file
|
370 |
output_path
|
371 |
]
|
372 |
|
373 |
+
logger.info(f"Running command: {' '.join(cmd)}")
|
374 |
process = subprocess.run(cmd, capture_output=True, text=True)
|
375 |
|
376 |
if process.returncode != 0:
|
377 |
+
logger.warning(f"First method failed: {process.stderr}")
|
378 |
+
raise Exception("First method failed")
|
379 |
+
|
380 |
+
except Exception as e:
|
381 |
+
logger.warning(f"First method failed: {str(e)}")
|
382 |
+
|
383 |
+
try:
|
384 |
+
# Attempt method 2: Using hardcoded subtitles approach
|
385 |
+
temp_srt_dir = os.path.join(OUTPUT_DIR, "temp_srt")
|
386 |
+
os.makedirs(temp_srt_dir, exist_ok=True)
|
387 |
+
|
388 |
+
# Copy the SRT file to the temp directory
|
389 |
+
temp_srt_path = os.path.join(temp_srt_dir, "temp.srt")
|
390 |
+
shutil.copy(srt_path, temp_srt_path)
|
391 |
+
|
392 |
+
cmd = [
|
393 |
+
'ffmpeg',
|
394 |
+
'-i', video_path,
|
395 |
+
'-i', audio_path,
|
396 |
+
'-map', '0:v',
|
397 |
+
'-map', '1:a',
|
398 |
+
'-vf', f"subtitles={temp_srt_path}",
|
399 |
+
'-c:v', 'libx264',
|
400 |
+
'-c:a', 'aac',
|
401 |
+
'-shortest',
|
402 |
+
'-y',
|
403 |
+
output_path
|
404 |
+
]
|
405 |
+
|
406 |
+
logger.info(f"Running second method: {' '.join(cmd)}")
|
407 |
+
process = subprocess.run(cmd, capture_output=True, text=True)
|
408 |
+
|
409 |
+
if process.returncode != 0:
|
410 |
+
logger.warning(f"Second method failed: {process.stderr}")
|
411 |
+
raise Exception("Second method failed")
|
412 |
+
|
413 |
+
# Clean up temp directory
|
414 |
+
shutil.rmtree(temp_srt_dir)
|
415 |
+
|
416 |
+
except Exception as e:
|
417 |
+
logger.warning(f"Second method failed: {str(e)}")
|
418 |
+
|
419 |
+
# Attempt method 3: No subtitles as last resort
|
420 |
+
cmd = [
|
421 |
+
'ffmpeg',
|
422 |
+
'-i', video_path,
|
423 |
+
'-i', audio_path,
|
424 |
+
'-map', '0:v',
|
425 |
+
'-map', '1:a',
|
426 |
+
'-c:v', 'libx264',
|
427 |
+
'-c:a', 'aac',
|
428 |
+
'-shortest',
|
429 |
+
'-y',
|
430 |
+
output_path
|
431 |
+
]
|
432 |
+
|
433 |
+
logger.info(f"Running fallback method (no subtitles): {' '.join(cmd)}")
|
434 |
+
process = subprocess.run(cmd, capture_output=True, text=True)
|
435 |
+
|
436 |
+
if process.returncode != 0:
|
437 |
+
logger.error(f"All methods failed: {process.stderr}")
|
438 |
+
raise Exception(f"Failed to combine video and audio: {process.stderr}")
|
439 |
+
else:
|
440 |
+
logger.warning("Created video without subtitles as fallback")
|
441 |
|
442 |
+
# Verify the output file exists and has a reasonable size
|
443 |
+
if not os.path.exists(output_path):
|
444 |
+
raise Exception(f"Output file does not exist: {output_path}")
|
445 |
+
|
446 |
+
if os.path.getsize(output_path) < 1000:
|
447 |
+
raise Exception(f"Output file is too small: {os.path.getsize(output_path)} bytes")
|
448 |
+
|
449 |
+
logger.info(f"Successfully created output file: {output_path} ({os.path.getsize(output_path)} bytes)")
|
450 |
return output_path
|
451 |
except Exception as e:
|
452 |
logger.error(f"Combining failed: {str(e)}", exc_info=True)
|
|
|
496 |
logger.info(f"Generating translated audio for {lang_code}")
|
497 |
translated_audio = generate_translated_audio(sub_path, lang_code)
|
498 |
|
499 |
+
# Verify audio file exists
|
500 |
+
if not os.path.exists(translated_audio):
|
501 |
+
logger.error(f"Translated audio file does not exist: {translated_audio}")
|
502 |
+
continue
|
503 |
+
|
504 |
# Combine video, translated audio, and subtitles
|
505 |
output_path = os.path.join(OUTPUT_DIR, f"output_{lang_code}.mp4")
|
506 |
logger.info(f"Creating final video with {lang_code} audio and subtitles")
|