Nishur commited on
Commit
100847c
·
verified ·
1 Parent(s): 969bd9d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +236 -44
app.py CHANGED
@@ -9,6 +9,9 @@ import logging
9
  import sys
10
  import shutil
11
  from pathlib import Path
 
 
 
12
 
13
  # Set up logging
14
  logging.basicConfig(level=logging.INFO,
@@ -27,6 +30,16 @@ LANGUAGES = {
27
  "Hindi": "hi"
28
  }
29
 
 
 
 
 
 
 
 
 
 
 
30
  # Create a permanent output directory
31
  OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "outputs")
32
  os.makedirs(OUTPUT_DIR, exist_ok=True)
@@ -110,20 +123,141 @@ def translate_subtitles(srt_path, target_langs):
110
  logger.error(f"Translation failed: {str(e)}", exc_info=True)
111
  raise Exception(f"Translation failed: {str(e)}")
112
 
113
- def burn_subtitles_ffmpeg(video_path, srt_path, output_path):
114
- """Burn subtitles directly into the video using ffmpeg"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  try:
116
- logger.info(f"Burning subtitles into video using ffmpeg")
117
 
118
  # Escape special characters in paths for ffmpeg filters
119
  escaped_srt_path = srt_path.replace(":", "\\:").replace("'", "\\'").replace(" ", "\\ ")
120
 
121
- # Command to burn subtitles into video
122
  cmd = [
123
  'ffmpeg',
124
  '-i', video_path, # Input video
 
 
 
125
  '-vf', f"subtitles={escaped_srt_path}:force_style='FontSize=24,PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,BorderStyle=3'", # Burn subtitles
126
- '-c:a', 'copy', # Copy audio codec
 
 
127
  '-y', # Overwrite output file
128
  output_path
129
  ]
@@ -132,16 +266,21 @@ def burn_subtitles_ffmpeg(video_path, srt_path, output_path):
132
  process = subprocess.run(cmd, capture_output=True, text=True)
133
 
134
  if process.returncode != 0:
135
- logger.error(f"Subtitle burning failed: {process.stderr}")
136
 
137
  # Try alternative method
138
- logger.info("Trying alternative method for subtitle burning")
139
  cmd = [
140
  'ffmpeg',
141
- '-i', video_path, # Input video
142
- '-vf', f"subtitles='{srt_path}'", # Simpler subtitle filter
143
- '-c:a', 'copy', # Copy audio codec
144
- '-y', # Overwrite output file
 
 
 
 
 
145
  output_path
146
  ]
147
 
@@ -149,18 +288,18 @@ def burn_subtitles_ffmpeg(video_path, srt_path, output_path):
149
  process = subprocess.run(cmd, capture_output=True, text=True)
150
 
151
  if process.returncode != 0:
152
- logger.error(f"Alternative subtitle burning failed: {process.stderr}")
153
- raise Exception(f"Failed to burn subtitles: {process.stderr}")
154
 
155
  return output_path
156
  except Exception as e:
157
- logger.error(f"Subtitle burning failed: {str(e)}", exc_info=True)
158
- raise Exception(f"Subtitle burning failed: {str(e)}")
159
 
160
  def process_video(video_file, source_lang, target_langs, progress=gr.Progress()):
161
- """Process video with translation"""
162
  try:
163
- progress(0.1, "Starting processing...")
164
  logger.info(f"Processing video: {video_file}")
165
 
166
  # Make sure we have ffmpeg installed
@@ -173,32 +312,44 @@ def process_video(video_file, source_lang, target_langs, progress=gr.Progress())
173
  return None, error_msg
174
 
175
  # Extract audio
176
- progress(0.2, "Extracting audio...")
177
  audio_path = extract_audio(video_file)
178
 
179
  # Generate subtitles
180
- progress(0.4, "Generating subtitles...")
181
  srt_path = generate_subtitles(audio_path)
182
 
183
  # Translate subtitles
184
- progress(0.6, "Translating subtitles...")
185
  target_lang_codes = [LANGUAGES[lang] for lang in target_langs]
186
  translated_subs = translate_subtitles(srt_path, target_lang_codes)
187
 
188
- # Add subtitles to video for each language
189
- progress(0.8, "Creating output videos...")
190
- output_videos = []
191
-
192
- # Create a copy of the video file in our output directory first
193
  base_video = os.path.join(OUTPUT_DIR, "base_video.mp4")
194
  shutil.copy(video_file, base_video)
195
 
196
- for lang_code, sub_path in translated_subs.items():
197
- output_path = os.path.join(OUTPUT_DIR, f"output_{lang_code}.mp4")
198
- logger.info(f"Adding {lang_code} subtitles to video: {output_path}")
 
 
 
199
 
200
  try:
201
- output_video = burn_subtitles_ffmpeg(base_video, sub_path, output_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
202
 
203
  # Verify the output file exists and has content
204
  if os.path.exists(output_video) and os.path.getsize(output_video) > 1000:
@@ -207,25 +358,28 @@ def process_video(video_file, source_lang, target_langs, progress=gr.Progress())
207
  else:
208
  logger.warning(f"Output file is missing or too small: {output_video}")
209
  except Exception as e:
210
- logger.error(f"Failed to create video with {lang_code} subtitles: {str(e)}")
211
-
212
  # If all output videos failed, return the original
213
  if not output_videos:
214
- logger.warning("All subtitle additions failed, returning original video")
215
- return base_video, "Failed to add subtitles to video, returning original"
216
 
217
  progress(1.0, "Done!")
218
- return output_videos[0], f"Processing complete. Video saved to: {output_videos[0]}"
 
 
219
 
220
  except Exception as e:
221
  logger.error(f"Processing failed: {str(e)}", exc_info=True)
222
  return None, f"Processing failed: {str(e)}"
223
 
224
  with gr.Blocks() as demo:
225
- gr.Markdown("# Video Translation System")
 
226
 
227
  with gr.Row():
228
- with gr.Column():
229
  video_input = gr.Video(label="Upload Video")
230
  source_lang = gr.Dropdown(
231
  label="Source Language",
@@ -233,15 +387,16 @@ with gr.Blocks() as demo:
233
  value="English"
234
  )
235
  target_langs = gr.CheckboxGroup(
236
- label="Target Languages",
237
  choices=list(LANGUAGES.keys()),
238
  value=["Spanish"]
239
  )
240
- submit_btn = gr.Button("Translate")
241
 
242
- with gr.Column():
243
  output_video = gr.Video(label="Translated Video")
244
  status_text = gr.Textbox(label="Status", interactive=False)
 
245
 
246
  submit_btn.click(
247
  process_video,
@@ -250,13 +405,50 @@ with gr.Blocks() as demo:
250
  )
251
 
252
  if __name__ == "__main__":
253
- # Display ffmpeg version at startup
 
 
 
254
  try:
255
  version_info = subprocess.run(['ffmpeg', '-version'], capture_output=True, text=True)
256
- # Fix for the backslash in f-string issue
257
- first_line = version_info.stdout.split('\n')[0]
258
- logger.info(f"ffmpeg version info: {first_line}")
259
  except:
260
- logger.warning("Could not determine ffmpeg version")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
 
 
262
  demo.launch()
 
9
  import sys
10
  import shutil
11
  from pathlib import Path
12
+ import time
13
+ from tqdm import tqdm
14
+ from gtts import gTTS
15
 
16
  # Set up logging
17
  logging.basicConfig(level=logging.INFO,
 
30
  "Hindi": "hi"
31
  }
32
 
33
+ # TTS voice mapping for different languages
34
+ TTS_VOICES = {
35
+ "en": "en-US",
36
+ "es": "es-ES",
37
+ "fr": "fr-FR",
38
+ "de": "de-DE",
39
+ "ja": "ja-JP",
40
+ "hi": "hi-IN"
41
+ }
42
+
43
  # Create a permanent output directory
44
  OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "outputs")
45
  os.makedirs(OUTPUT_DIR, exist_ok=True)
 
123
  logger.error(f"Translation failed: {str(e)}", exc_info=True)
124
  raise Exception(f"Translation failed: {str(e)}")
125
 
126
+ def generate_translated_audio(srt_path, target_lang):
127
+ """Generate translated audio using text-to-speech"""
128
+ try:
129
+ logger.info(f"Generating translated audio for {target_lang}")
130
+ subs = pysrt.open(srt_path, encoding="utf-8")
131
+ translated_text = [sub.text for sub in subs]
132
+
133
+ # Create temporary directory for audio chunks
134
+ temp_dir = os.path.join(OUTPUT_DIR, f"temp_audio_{target_lang}")
135
+ os.makedirs(temp_dir, exist_ok=True)
136
+
137
+ # Generate TTS for each subtitle
138
+ audio_files = []
139
+ timings = []
140
+
141
+ for i, sub in enumerate(tqdm(subs, desc=f"Generating {target_lang} speech")):
142
+ text = sub.text.strip()
143
+ if not text:
144
+ continue
145
+
146
+ # Get timing information
147
+ start_time = (sub.start.hours * 3600 +
148
+ sub.start.minutes * 60 +
149
+ sub.start.seconds +
150
+ sub.start.milliseconds / 1000)
151
+
152
+ end_time = (sub.end.hours * 3600 +
153
+ sub.end.minutes * 60 +
154
+ sub.end.seconds +
155
+ sub.end.milliseconds / 1000)
156
+
157
+ duration = end_time - start_time
158
+
159
+ # Generate TTS audio
160
+ tts_lang = TTS_VOICES.get(target_lang, target_lang)
161
+ audio_file = os.path.join(temp_dir, f"chunk_{i:04d}.mp3")
162
+
163
+ try:
164
+ tts = gTTS(text=text, lang=target_lang, slow=False)
165
+ tts.save(audio_file)
166
+ audio_files.append(audio_file)
167
+ timings.append((start_time, end_time, duration, audio_file))
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
+ video_duration_cmd = [
174
+ 'ffprobe',
175
+ '-v', 'error',
176
+ '-show_entries', 'format=duration',
177
+ '-of', 'default=noprint_wrappers=1:nokey=1',
178
+ os.path.join(OUTPUT_DIR, "base_video.mp4")
179
+ ]
180
+
181
+ duration_result = subprocess.run(video_duration_cmd, capture_output=True, text=True)
182
+ video_duration = float(duration_result.stdout.strip())
183
+
184
+ # Create silent audio track
185
+ silent_cmd = [
186
+ 'ffmpeg',
187
+ '-f', 'lavfi',
188
+ '-i', f'anullsrc=r=44100:cl=stereo',
189
+ '-t', str(video_duration),
190
+ '-q:a', '0',
191
+ '-y',
192
+ silence_file
193
+ ]
194
+ subprocess.run(silent_cmd, capture_output=True)
195
+
196
+ # Create a file with the audio mixing commands
197
+ filter_complex = []
198
+ input_count = 1 # Starting with 1 because 0 is the silence track
199
+
200
+ # Start with silent track
201
+ filter_parts = ["[0:a]"]
202
+
203
+ # Add each audio segment
204
+ for start_time, end_time, duration, audio_file in timings:
205
+ filter_parts.append(f"[{input_count}:a]adelay={int(start_time*1000)}|{int(start_time*1000)}")
206
+ input_count += 1
207
+
208
+ # Mix all audio tracks
209
+ filter_parts.append(f"amix=inputs={input_count}:dropout_transition=0:normalize=0[aout]")
210
+ filter_complex = ";".join(filter_parts)
211
+
212
+ # Build the ffmpeg command with all audio chunks
213
+ cmd = ['ffmpeg', '-y']
214
+
215
+ # Add silent base track
216
+ cmd.extend(['-i', silence_file])
217
+
218
+ # Add all audio chunks
219
+ for audio_file in audio_files:
220
+ cmd.extend(['-i', audio_file])
221
+
222
+ # Add filter complex and output
223
+ output_audio = os.path.join(OUTPUT_DIR, f"translated_audio_{target_lang}.wav")
224
+ cmd.extend([
225
+ '-filter_complex', filter_complex,
226
+ '-map', '[aout]',
227
+ output_audio
228
+ ])
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
+ shutil.rmtree(temp_dir)
236
+
237
+ return output_audio
238
+ except Exception as e:
239
+ logger.error(f"Audio translation failed: {str(e)}", exc_info=True)
240
+ raise Exception(f"Audio translation failed: {str(e)}")
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
  # Escape special characters in paths for ffmpeg filters
248
  escaped_srt_path = srt_path.replace(":", "\\:").replace("'", "\\'").replace(" ", "\\ ")
249
 
250
+ # Command to combine video with translated audio and subtitles
251
  cmd = [
252
  'ffmpeg',
253
  '-i', video_path, # Input video
254
+ '-i', audio_path, # Input translated audio
255
+ '-map', '0:v', # Use video from first input
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
  ]
 
266
  process = subprocess.run(cmd, capture_output=True, text=True)
267
 
268
  if process.returncode != 0:
269
+ logger.error(f"Combining failed: {process.stderr}")
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='{srt_path}'",
280
+ '-c:v', 'libx264',
281
+ '-c:a', 'aac',
282
+ '-shortest',
283
+ '-y',
284
  output_path
285
  ]
286
 
 
288
  process = subprocess.run(cmd, capture_output=True, text=True)
289
 
290
  if process.returncode != 0:
291
+ logger.error(f"Alternative method failed: {process.stderr}")
292
+ raise Exception(f"Failed to combine video, audio, and subtitles: {process.stderr}")
293
 
294
  return output_path
295
  except Exception as e:
296
+ logger.error(f"Combining failed: {str(e)}", exc_info=True)
297
+ raise Exception(f"Combining failed: {str(e)}")
298
 
299
  def process_video(video_file, source_lang, target_langs, progress=gr.Progress()):
300
+ """Process video with translation of both subtitles and audio"""
301
  try:
302
+ progress(0.05, "Starting processing...")
303
  logger.info(f"Processing video: {video_file}")
304
 
305
  # Make sure we have ffmpeg installed
 
312
  return None, error_msg
313
 
314
  # Extract audio
315
+ progress(0.1, "Extracting audio...")
316
  audio_path = extract_audio(video_file)
317
 
318
  # Generate subtitles
319
+ progress(0.25, "Generating subtitles...")
320
  srt_path = generate_subtitles(audio_path)
321
 
322
  # Translate subtitles
323
+ progress(0.4, "Translating subtitles...")
324
  target_lang_codes = [LANGUAGES[lang] for lang in target_langs]
325
  translated_subs = translate_subtitles(srt_path, target_lang_codes)
326
 
327
+ # Create a copy of the video file in our output directory
 
 
 
 
328
  base_video = os.path.join(OUTPUT_DIR, "base_video.mp4")
329
  shutil.copy(video_file, base_video)
330
 
331
+ # Process each target language
332
+ output_videos = []
333
+
334
+ for i, (lang_code, sub_path) in enumerate(translated_subs.items()):
335
+ lang_name = next(name for name, code in LANGUAGES.items() if code == lang_code)
336
+ progress(0.5 + (i * 0.5 / len(translated_subs)), f"Processing {lang_name}...")
337
 
338
  try:
339
+ # Generate translated audio
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")
346
+
347
+ output_video = combine_video_audio_subtitles(
348
+ base_video,
349
+ translated_audio,
350
+ sub_path,
351
+ output_path
352
+ )
353
 
354
  # Verify the output file exists and has content
355
  if os.path.exists(output_video) and os.path.getsize(output_video) > 1000:
 
358
  else:
359
  logger.warning(f"Output file is missing or too small: {output_video}")
360
  except Exception as e:
361
+ logger.error(f"Failed to process {lang_code}: {str(e)}")
362
+
363
  # If all output videos failed, return the original
364
  if not output_videos:
365
+ logger.warning("All translations failed, returning original video")
366
+ return base_video, "Failed to translate video, returning original"
367
 
368
  progress(1.0, "Done!")
369
+ message = f"Processing complete. Created {len(output_videos)} translated videos."
370
+ logger.info(message)
371
+ return output_videos[0], message
372
 
373
  except Exception as e:
374
  logger.error(f"Processing failed: {str(e)}", exc_info=True)
375
  return None, f"Processing failed: {str(e)}"
376
 
377
  with gr.Blocks() as demo:
378
+ gr.Markdown("# Complete Video Translation System")
379
+ gr.Markdown("Translates both subtitles and audio to target languages")
380
 
381
  with gr.Row():
382
+ with gr.Column(scale=1):
383
  video_input = gr.Video(label="Upload Video")
384
  source_lang = gr.Dropdown(
385
  label="Source Language",
 
387
  value="English"
388
  )
389
  target_langs = gr.CheckboxGroup(
390
+ label="Target Languages (Both Audio & Subtitles)",
391
  choices=list(LANGUAGES.keys()),
392
  value=["Spanish"]
393
  )
394
+ submit_btn = gr.Button("Translate", variant="primary")
395
 
396
+ with gr.Column(scale=2):
397
  output_video = gr.Video(label="Translated Video")
398
  status_text = gr.Textbox(label="Status", interactive=False)
399
+ output_info = gr.Markdown("Output videos will be saved in the 'outputs' directory")
400
 
401
  submit_btn.click(
402
  process_video,
 
405
  )
406
 
407
  if __name__ == "__main__":
408
+ # Check dependencies at startup
409
+ missing_deps = []
410
+
411
+ # Check ffmpeg
412
  try:
413
  version_info = subprocess.run(['ffmpeg', '-version'], capture_output=True, text=True)
414
+ ffmpeg_version = version_info.stdout.split('\n')[0]
415
+ logger.info(f"ffmpeg version: {ffmpeg_version}")
 
416
  except:
417
+ logger.warning("ffmpeg not found - required for video processing")
418
+ missing_deps.append("ffmpeg")
419
+
420
+ # Check Python dependencies
421
+ try:
422
+ import assemblyai
423
+ logger.info("AssemblyAI package found")
424
+ except ImportError:
425
+ logger.warning("AssemblyAI package not found - required for transcription")
426
+ missing_deps.append("assemblyai")
427
+
428
+ try:
429
+ import gtts
430
+ logger.info("gTTS package found")
431
+ except ImportError:
432
+ logger.warning("gTTS package not found - required for text-to-speech")
433
+ missing_deps.append("gtts")
434
+
435
+ try:
436
+ import deep_translator
437
+ logger.info("deep_translator package found")
438
+ except ImportError:
439
+ logger.warning("deep_translator package not found - required for translation")
440
+ missing_deps.append("deep_translator")
441
+
442
+ # Print installation instructions if dependencies are missing
443
+ if missing_deps:
444
+ logger.warning("Missing dependencies detected. Please install:")
445
+ if "ffmpeg" in missing_deps:
446
+ logger.warning("- ffmpeg: https://ffmpeg.org/download.html")
447
+
448
+ python_deps = [dep for dep in missing_deps if dep != "ffmpeg"]
449
+ if python_deps:
450
+ deps_str = " ".join(python_deps)
451
+ logger.warning(f"- Python packages: pip install {deps_str}")
452
 
453
+ # Start the app
454
  demo.launch()