Update app.py
Browse files
app.py
CHANGED
@@ -392,48 +392,59 @@ def process_entry(entry, i, video_width, video_height, add_voiceover, target_lan
|
|
392 |
audio_segment = None
|
393 |
|
394 |
return i, txt_clip, audio_segment, error_message
|
|
|
|
|
|
|
395 |
|
396 |
-
|
397 |
-
|
398 |
-
|
399 |
-
|
400 |
-
try:
|
401 |
-
txt_clip = create_subtitle_clip_pil(entry["translated"], entry["start"], entry["end"], video_width, video_height, font_path)
|
402 |
-
except Exception as e:
|
403 |
-
error_message = f"❌ Failed to create subtitle clip for entry {i}: {e}"
|
404 |
-
logger.error(error_message)
|
405 |
-
txt_clip = None
|
406 |
|
407 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
408 |
if add_voiceover:
|
409 |
-
|
410 |
-
segment_audio_path = f"segment_{i}_voiceover.wav"
|
411 |
-
desired_duration = entry["end"] - entry["start"]
|
412 |
-
speaker = entry.get("speaker", "default")
|
413 |
-
speaker_wav_path = f"speaker_{speaker}_sample.wav"
|
414 |
-
|
415 |
-
generate_voiceover_clone([entry], desired_duration, target_language, speaker_wav_path, segment_audio_path)
|
416 |
|
417 |
-
|
418 |
-
raise FileNotFoundError(f"Voiceover file not generated at: {segment_audio_path}")
|
419 |
-
|
420 |
-
audio_clip = AudioFileClip(segment_audio_path)
|
421 |
-
logger.debug(f"Audio clip duration: {audio_clip.duration}, Desired duration: {desired_duration}")
|
422 |
-
|
423 |
-
if audio_clip.duration < desired_duration:
|
424 |
-
silence_duration = desired_duration - audio_clip.duration
|
425 |
-
audio_clip = concatenate_audioclips([audio_clip, silence(duration=silence_duration)])
|
426 |
-
logger.info(f"Padded audio with {silence_duration} seconds of silence.")
|
427 |
|
428 |
-
|
429 |
-
|
430 |
-
|
431 |
-
|
432 |
-
|
433 |
-
|
434 |
-
|
435 |
-
|
436 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
437 |
|
438 |
import os
|
439 |
import traceback
|
|
|
392 |
audio_segment = None
|
393 |
|
394 |
return i, txt_clip, audio_segment, error_message
|
395 |
+
def add_transcript_voiceover(video_path, translated_json, output_path, add_voiceover=False, target_language="en", speaker_sample_paths=None):
|
396 |
+
video = VideoFileClip(video_path)
|
397 |
+
font_path = "./NotoSansSC-Regular.ttf"
|
398 |
|
399 |
+
text_clips = []
|
400 |
+
audio_segments = []
|
401 |
+
error_messages = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
402 |
|
403 |
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
404 |
+
futures = [executor.submit(process_entry, entry, i, video.w, video.h, add_voiceover, target_language, font_path, speaker_sample_paths)
|
405 |
+
for i, entry in enumerate(translated_json)]
|
406 |
+
|
407 |
+
results = []
|
408 |
+
for future in concurrent.futures.as_completed(futures):
|
409 |
+
try:
|
410 |
+
i, txt_clip, audio_segment, error = future.result()
|
411 |
+
results.append((i, txt_clip, audio_segment))
|
412 |
+
if error:
|
413 |
+
error_messages.append(f"[Entry {i}] {error}")
|
414 |
+
except Exception as e:
|
415 |
+
err = f"❌ Unexpected error in future result: {e}"
|
416 |
+
logger.error(err)
|
417 |
+
error_messages.append(err)
|
418 |
+
|
419 |
+
# Sort by entry index to ensure order
|
420 |
+
results.sort(key=lambda x: x[0])
|
421 |
+
text_clips = [clip for _, clip, _ in results if clip]
|
422 |
if add_voiceover:
|
423 |
+
audio_segments = [segment for _, _, segment in results if segment]
|
|
|
|
|
|
|
|
|
|
|
|
|
424 |
|
425 |
+
final_video = CompositeVideoClip([video] + text_clips)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
426 |
|
427 |
+
if add_voiceover:
|
428 |
+
if audio_segments:
|
429 |
+
final_audio = CompositeAudioClip(audio_segments).set_duration(video.duration)
|
430 |
+
final_video = final_video.set_audio(final_audio)
|
431 |
+
else:
|
432 |
+
logger.warning("⚠️ No audio segments available. Adding silent fallback.")
|
433 |
+
silent_audio = AudioClip(lambda t: 0, duration=video.duration)
|
434 |
+
final_video = final_video.set_audio(silent_audio)
|
435 |
+
|
436 |
+
logger.info(f"Saving the final video to: {output_path}")
|
437 |
+
final_video.write_videofile(output_path, codec="libx264", audio_codec="aac")
|
438 |
+
|
439 |
+
logger.info("Video processing completed successfully.")
|
440 |
+
|
441 |
+
# Optional: return errors
|
442 |
+
if error_messages:
|
443 |
+
logger.warning("⚠️ Errors encountered during processing:")
|
444 |
+
for msg in error_messages:
|
445 |
+
logger.warning(msg)
|
446 |
+
|
447 |
+
return error_messages
|
448 |
|
449 |
import os
|
450 |
import traceback
|