Athspi commited on
Commit
2094e3e
·
verified ·
1 Parent(s): be0d1aa

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +199 -336
app.py CHANGED
@@ -4,393 +4,256 @@ import tempfile
4
  import uuid
5
  import google.generativeai as genai
6
  import requests
7
- import re
8
- from flask import Flask, request, render_template, send_from_directory, jsonify
9
  from moviepy.video.io.VideoFileClip import VideoFileClip
10
  from moviepy.audio.io.AudioFileClip import AudioFileClip
 
11
  from werkzeug.utils import secure_filename
12
  from dotenv import load_dotenv
13
- import threading
14
- import logging
15
- from gtts import gTTS
16
- import io
17
- from pathlib import Path
18
 
19
- # Initialize Flask app
20
  load_dotenv()
21
  app = Flask(__name__)
22
 
23
- # Configuration
24
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
25
- TTS_API_URL = os.getenv("TTS_API_URL", "") # Optional
26
- MAX_CONTENT_LENGTH = 500 * 1024 * 1024 # 500MB
27
- MAX_TTS_RETRIES = 3
28
- TTS_CHUNK_SIZE = 2000 # Characters per chunk
29
- MAX_WAIT_TIME = 300 # 5 minutes max wait for file processing
30
 
31
- # File storage setup
 
 
 
 
 
 
 
 
 
32
  UPLOAD_FOLDER = 'uploads'
33
  DOWNLOAD_FOLDER = 'downloads'
34
- Path(UPLOAD_FOLDER).mkdir(exist_ok=True)
35
- Path(DOWNLOAD_FOLDER).mkdir(exist_ok=True)
36
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
37
  app.config['DOWNLOAD_FOLDER'] = DOWNLOAD_FOLDER
38
- app.config['MAX_CONTENT_LENGTH'] = MAX_CONTENT_LENGTH
39
- app.secret_key = os.urandom(24)
40
-
41
- # Processing status tracking
42
- processing_status = {}
43
 
44
- # Language and voice options
45
- LANGUAGE_MAPPING = {
46
- "Arabic (Egyptian)": "ar-EG",
47
- "English (US)": "en-US",
48
- "Hindi (India)": "hi-IN",
49
- "Tamil (India)": "ta-IN",
50
- "Telugu (India)": "te-IN"
51
  }
52
 
53
- VOICE_TYPES = {
54
- "Male": "male",
55
- "Female": "female"
56
- }
57
 
58
- GEMINI_PROMPTS = {
59
- "api": """
60
- You are an expert AI scriptwriter. Transcribe ALL spoken dialogue into a SINGLE,
61
- CONTINUOUS block of modern {language}. Include natural speech patterns and
62
- performance directions (e.g., [pause], [laugh]) where appropriate.
63
- """,
64
- "gtts": """
65
- You are an expert AI scriptwriter. Transcribe ALL spoken dialogue into a SINGLE,
66
- CONTINUOUS block of modern {language}. Return ONLY the clean transcribed text.
67
- """
68
- }
69
 
70
- # Configure logging
71
- logging.basicConfig(
72
- level=logging.INFO,
73
- format='%(asctime)s - %(levelname)s - %(message)s'
74
- )
75
- logger = logging.getLogger(__name__)
76
 
77
- def split_text_into_chunks(text, chunk_size=TTS_CHUNK_SIZE):
78
- """Split text into chunks respecting sentence boundaries"""
79
- sentences = re.split(r'(?<=[.!?])\s+', text)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  chunks = []
81
- current_chunk = ""
 
82
 
83
- for sentence in sentences:
84
- if len(current_chunk) + len(sentence) < chunk_size:
85
- current_chunk += sentence + " "
 
 
 
86
  else:
87
- chunks.append(current_chunk.strip())
88
- current_chunk = sentence + " "
89
 
90
  if current_chunk:
91
- chunks.append(current_chunk.strip())
92
 
93
  return chunks
94
 
95
- def generate_tts_audio(text, language_code, voice_type, tts_provider):
96
- """Generate TTS audio using selected provider with retry logic"""
97
- chunks = split_text_into_chunks(text)
98
- audio_segments = []
99
-
100
- for chunk in chunks:
101
- for attempt in range(MAX_TTS_RETRIES):
102
- try:
103
- if tts_provider == "api":
104
- # Use custom TTS API
105
- payload = {
106
- "text": chunk,
107
- "language": language_code,
108
- "voice_type": voice_type
109
- }
110
- response = requests.post(TTS_API_URL, json=payload, timeout=300)
111
-
112
- if response.status_code == 200:
113
- audio_segments.append(io.BytesIO(response.content))
114
- break
115
- elif response.status_code == 429: # Rate limit
116
- retry_after = int(response.headers.get('Retry-After', 5))
117
- logger.warning(f"TTS API rate limited. Retrying after {retry_after}s")
118
- time.sleep(retry_after)
119
- continue
120
- else:
121
- raise Exception(f"TTS API error: {response.status_code}")
122
- else:
123
- # Use gTTS
124
- tts = gTTS(
125
- text=chunk,
126
- lang=language_code.split('-')[0],
127
- slow=False
128
- )
129
- buffer = io.BytesIO()
130
- tts.write_to_fp(buffer)
131
- buffer.seek(0)
132
- audio_segments.append(buffer)
133
- break
134
-
135
- except Exception as e:
136
- logger.warning(f"TTS attempt {attempt + 1} failed: {str(e)}")
137
- if attempt == MAX_TTS_RETRIES - 1:
138
- raise Exception(f"Failed to generate TTS after {MAX_TTS_RETRIES} attempts")
139
- time.sleep(2 ** attempt) # Exponential backoff
140
-
141
- # Combine audio segments
142
- combined_audio = io.BytesIO()
143
- for segment in audio_segments:
144
- combined_audio.write(segment.getvalue())
145
- combined_audio.seek(0)
146
- return combined_audio
147
-
148
- def wait_for_file_processing(file):
149
- """Wait for file to be processed with timeout handling"""
150
- start_time = time.time()
151
- while file.state.name == "PROCESSING":
152
- if time.time() - start_time > MAX_WAIT_TIME:
153
- raise TimeoutError("File processing timed out")
154
- time.sleep(5)
155
- file = genai.get_file(file.name)
156
- return file
157
-
158
- def generate_transcription(video_path, prompt):
159
- """Generate transcript using Gemini with enhanced file handling"""
160
- max_retries = 3
161
  for attempt in range(max_retries):
162
  try:
163
- # Upload file with explicit timeout
164
- video_file = genai.upload_file(video_path, mime_type="video/mp4")
 
 
 
165
 
166
- # Wait for processing with timeout
167
- video_file = wait_for_file_processing(video_file)
168
 
169
- if video_file.state.name != "ACTIVE":
170
- raise Exception(f"File processing failed: {video_file.state.name}")
171
 
172
- model = genai.GenerativeModel("models/gemini-2.5-flash")
173
- response = model.generate_content([prompt, video_file])
174
-
175
- if hasattr(response, 'text'):
176
- return response.text.strip()
177
- raise Exception("No valid transcription generated")
178
-
179
- except Exception as e:
180
- logger.warning(f"Transcription attempt {attempt + 1} failed: {str(e)}")
181
- if attempt == max_retries - 1:
182
- raise
183
- time.sleep(5 * (attempt + 1))
184
- finally:
185
- # Always delete the file if it was created
186
- if 'video_file' in locals() and hasattr(video_file, 'name'):
187
- try:
188
- genai.delete_file(video_file.name)
189
- except Exception as delete_error:
190
- logger.error(f"Failed to delete file: {str(delete_error)}")
191
-
192
- def dub_video(video_path, audio_buffer):
193
- """Dub video with new audio"""
194
- video = None
195
- audio = None
196
- temp_audio_path = None
197
 
198
  try:
199
- # Save audio buffer to temp file
200
- temp_audio_path = f"temp_audio_{uuid.uuid4().hex}.mp3"
201
- with open(temp_audio_path, 'wb') as f:
202
- f.write(audio_buffer.read())
203
-
204
- # Process video
205
- video = VideoFileClip(video_path)
206
- audio = AudioFileClip(temp_audio_path)
207
-
208
- # Ensure audio length matches video
209
- if audio.duration > video.duration:
210
- audio = audio.subclip(0, video.duration)
 
211
 
212
- video = video.set_audio(audio)
 
 
 
213
 
214
- # Save output
215
- output_filename = f"dubbed_{uuid.uuid4().hex}.mp4"
216
- output_path = os.path.join(app.config['DOWNLOAD_FOLDER'], output_filename)
 
 
217
 
218
- video.write_videofile(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
  output_path,
220
  codec="libx264",
221
  audio_codec="aac",
222
- threads=4,
223
- verbose=False,
224
- preset='medium',
225
- ffmpeg_params=['-crf', '23', '-movflags', '+faststart']
226
  )
227
-
228
- return output_path
229
-
230
- finally:
231
- # Cleanup resources
232
- if video:
233
- video.close()
234
- if audio:
235
- audio.close()
236
- if temp_audio_path and os.path.exists(temp_audio_path):
237
- try:
238
- os.unlink(temp_audio_path)
239
- except Exception as e:
240
- logger.error(f"Failed to delete temp audio: {str(e)}")
241
-
242
- def process_video_background(task_id, video_path, language, voice_type, tts_provider):
243
- """Background video processing with enhanced error handling"""
244
- try:
245
- processing_status[task_id] = {
246
- 'status': 'processing',
247
- 'progress': 0,
248
- 'message': 'Starting transcription',
249
- 'start_time': time.time()
250
- }
251
-
252
- # Stage 1: Transcription
253
- processing_status[task_id]['message'] = 'Transcribing video content'
254
- prompt = GEMINI_PROMPTS[tts_provider].format(language=language)
255
- script = generate_transcription(video_path, prompt)
256
- processing_status[task_id]['progress'] = 33
257
- processing_status[task_id]['script'] = script
258
-
259
- # Stage 2: Audio Generation
260
- processing_status[task_id]['message'] = 'Generating audio narration'
261
- language_code = LANGUAGE_MAPPING.get(language, "en-US")
262
- audio_buffer = generate_tts_audio(script, language_code, voice_type, tts_provider)
263
- processing_status[task_id]['progress'] = 66
264
-
265
- # Stage 3: Video Dubbing
266
- processing_status[task_id]['message'] = 'Creating dubbed video'
267
- output_path = dub_video(video_path, audio_buffer)
268
- processing_status[task_id]['progress'] = 100
269
- processing_status[task_id]['status'] = 'complete'
270
- processing_status[task_id]['result_path'] = output_path
271
-
272
- except Exception as e:
273
- processing_status[task_id]['status'] = 'error'
274
- processing_status[task_id]['message'] = str(e)
275
- logger.error(f"Processing failed: {str(e)}")
276
  finally:
277
- # Cleanup original video
278
- if os.path.exists(video_path):
279
- try:
280
- os.unlink(video_path)
281
- except Exception as e:
282
- logger.error(f"Failed to delete video: {str(e)}")
283
 
 
284
 
285
- @app.route('/')
286
  def index():
287
- """Render main page"""
288
- return render_template(
289
- 'index.html',
290
- languages=list(LANGUAGE_MAPPING.keys()),
291
- voice_types=list(VOICE_TYPES.keys()),
292
- default_language="English (US)",
293
- tts_api_available=bool(TTS_API_URL)
294
- )
295
-
296
- @app.route('/upload', methods=['POST'])
297
- def upload_video():
298
- """Handle video upload"""
299
- if 'video' not in request.files:
300
- return jsonify({'error': 'No file uploaded'}), 400
301
-
302
- file = request.files['video']
303
- if file.filename == '':
304
- return jsonify({'error': 'No file selected'}), 400
305
-
306
- # Validate file extension
307
- allowed_extensions = {'mp4', 'mov', 'webm', 'avi'}
308
- if '.' not in file.filename or file.filename.rsplit('.', 1)[1].lower() not in allowed_extensions:
309
- return jsonify({'error': 'Invalid file type'}), 400
310
-
311
- # Save file with unique name
312
- task_id = uuid.uuid4().hex
313
- filename = secure_filename(f"{task_id}_{file.filename}")
314
- video_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
315
-
316
- try:
317
- file.save(video_path)
318
- except Exception as e:
319
- return jsonify({'error': f'Failed to save file: {str(e)}'}), 500
320
-
321
- # Get processing options
322
- language = request.form.get('language', 'English (US)')
323
- voice_type = request.form.get('voice_type', 'Male')
324
- tts_provider = request.form.get('tts_provider', 'gtts')
325
-
326
- # Validate TTS provider selection
327
- if tts_provider == "api" and not TTS_API_URL:
328
- return jsonify({'error': 'TTS API is not configured'}), 400
329
-
330
- # Start background processing
331
- processing_status[task_id] = {
332
- 'status': 'uploaded',
333
- 'progress': 0,
334
- 'message': 'Starting processing',
335
- 'start_time': time.time()
336
- }
337
-
338
- thread = threading.Thread(
339
- target=process_video_background,
340
- args=(task_id, video_path, language, voice_type, tts_provider)
341
- )
342
- thread.start()
343
-
344
- return jsonify({'task_id': task_id})
345
 
346
- @app.route('/status/<task_id>')
347
- def get_status(task_id):
348
- """Check processing status"""
349
- if task_id not in processing_status:
350
- return jsonify({'error': 'Invalid task ID'}), 404
351
-
352
- status = processing_status[task_id]
353
- response = {
354
- 'status': status['status'],
355
- 'progress': status.get('progress', 0),
356
- 'message': status.get('message', ''),
357
- }
358
-
359
- if status['status'] == 'complete':
360
- response['result_url'] = url_for(
361
- 'download',
362
- filename=os.path.basename(status['result_path'])
363
- )
364
- response['script'] = status.get('script', '')
365
- elif status['status'] == 'error':
366
- response['error_details'] = status.get('message', 'Unknown error')
367
 
368
- return jsonify(response)
369
-
370
- @app.route('/download/<filename>')
371
- def download(filename):
372
- """Serve processed video with security checks"""
373
  try:
374
- # Security check
375
- if not filename.startswith('dubbed_') or not filename.endswith('.mp4'):
376
- return "Invalid file", 400
 
377
 
378
- # Validate path
379
- download_path = Path(app.config['DOWNLOAD_FOLDER']) / filename
380
- if not download_path.exists():
381
- return "File not found", 404
382
-
383
- return send_from_directory(
384
- app.config['DOWNLOAD_FOLDER'],
385
- filename,
386
- as_attachment=True,
387
- mimetype='video/mp4'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
388
  )
 
389
  except Exception as e:
390
- logger.error(f"Download failed: {str(e)}")
391
- return "Download error", 500
 
 
 
 
 
 
 
 
 
 
 
 
 
392
 
 
393
  if __name__ == '__main__':
394
- if not GEMINI_API_KEY:
395
- raise ValueError("GEMINI_API_KEY is required in .env file")
396
- app.run(host="0.0.0.0", port=7860, threaded=True)
 
4
  import uuid
5
  import google.generativeai as genai
6
  import requests
7
+ from flask import Flask, request, render_template, send_from_directory, url_for, flash
 
8
  from moviepy.video.io.VideoFileClip import VideoFileClip
9
  from moviepy.audio.io.AudioFileClip import AudioFileClip
10
+ from moviepy.audio.AudioClip import concatenate_audioclips
11
  from werkzeug.utils import secure_filename
12
  from dotenv import load_dotenv
 
 
 
 
 
13
 
14
+ # --- 1. INITIALIZE FLASK APP AND LOAD SECRETS ---
15
  load_dotenv()
16
  app = Flask(__name__)
17
 
18
+ # Load secrets from environment variables
19
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
20
+ TTS_API_URL = os.getenv("TTS_API_URL")
21
+ TTS_MAX_TOKENS = 30000 # Conservative limit below 32k token threshold
 
 
 
22
 
23
+ # Validate required configurations
24
+ if not GEMINI_API_KEY:
25
+ raise ValueError("SECURITY ERROR: GEMINI_API_KEY not found in .env file!")
26
+ if not TTS_API_URL:
27
+ raise ValueError("CONFIGURATION ERROR: TTS_API_URL not found in .env file!")
28
+
29
+ # Configure Gemini AI
30
+ genai.configure(api_key=GEMINI_API_KEY)
31
+
32
+ # Configure directories
33
  UPLOAD_FOLDER = 'uploads'
34
  DOWNLOAD_FOLDER = 'downloads'
35
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
36
+ os.makedirs(DOWNLOAD_FOLDER, exist_ok=True)
37
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
38
  app.config['DOWNLOAD_FOLDER'] = DOWNLOAD_FOLDER
39
+ app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # 100 MB upload limit
40
+ app.secret_key = os.urandom(24) # Secure key for flash messages
 
 
 
41
 
42
+ # --- 2. APPLICATION CONFIGURATION ---
43
+ VOICE_CHOICES = {
44
+ "Male (Charon)": "Charon",
45
+ "Female (Zephyr)": "Zephyr"
 
 
 
46
  }
47
 
48
+ GEMINI_PROMPT = """
49
+ You are an expert AI scriptwriter. Your task is to watch the provided video and transcribe ALL spoken dialogue into a SINGLE, CONTINUOUS block of modern, colloquial Tamil.
 
 
50
 
51
+ **CRITICAL INSTRUCTIONS:**
52
+ 1. **Single Script:** Combine all dialogue from all speakers into one continuous script.
53
+ 2. **NO Timestamps or Speaker Labels:** Do NOT include any timestamps or speaker identifiers.
54
+ 3. **Incorporate Performance:** Add English style prompts (e.g., `Say happily:`, `Whisper mysteriously:`) and performance tags (e.g., `[laugh]`, `[sigh]`) directly into the text for an expressive narration.
 
 
 
 
 
 
 
55
 
56
+ **EXAMPLE OUTPUT:**
57
+ Say happily: வணக்கம்! [laugh] எப்படி இருக்கீங்க? Whisper mysteriously: அந்த ரகசியம் எனக்கு மட்டும் தான் தெரியும்.
58
+ """
 
 
 
59
 
60
+ # --- 3. CORE APPLICATION FUNCTIONS ---
61
+
62
+ def generate_tamil_script(video_file_path):
63
+ """Generates a Tamil script from the video using Gemini AI."""
64
+ print("Uploading video to Gemini for transcription...")
65
+ video_file = genai.upload_file(video_file_path, mime_type="video/mp4")
66
+
67
+ # Wait for file processing
68
+ while video_file.state.name == "PROCESSING":
69
+ time.sleep(5)
70
+ video_file = genai.get_file(video_file.name)
71
+
72
+ if video_file.state.name != "ACTIVE":
73
+ raise Exception(f"Gemini file processing failed: {video_file.state.name}")
74
+
75
+ print("Generating script...")
76
+ model = genai.GenerativeModel(model_name="models/gemini-1.5-pro-latest")
77
+ response = model.generate_content([GEMINI_PROMPT, video_file])
78
+ genai.delete_file(video_file.name)
79
+
80
+ if hasattr(response, 'text') and response.text:
81
+ return " ".join(response.text.strip().splitlines())
82
+ raise Exception("No valid script was generated by Gemini.")
83
+
84
+ def split_text_for_tts(text, max_tokens=TTS_MAX_TOKENS):
85
+ """Splits text into chunks that fit within TTS token limits."""
86
+ words = text.split()
87
  chunks = []
88
+ current_chunk = []
89
+ current_length = 0
90
 
91
+ for word in words:
92
+ word_length = len(word) + 1 # +1 for space
93
+ if current_length + word_length > max_tokens:
94
+ chunks.append(" ".join(current_chunk))
95
+ current_chunk = [word]
96
+ current_length = word_length
97
  else:
98
+ current_chunk.append(word)
99
+ current_length += word_length
100
 
101
  if current_chunk:
102
+ chunks.append(" ".join(current_chunk))
103
 
104
  return chunks
105
 
106
+ def generate_audio_with_retry(text_chunk, voice_name, is_cheerful, max_retries=3, retry_delay=2):
107
+ """Generates audio with retry logic for API failures."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  for attempt in range(max_retries):
109
  try:
110
+ payload = {
111
+ "text": text_chunk,
112
+ "voice_name": voice_name,
113
+ "cheerful": is_cheerful
114
+ }
115
 
116
+ response = requests.post(TTS_API_URL, json=payload, timeout=300)
117
+ response.raise_for_status()
118
 
119
+ if response.status_code == 200:
120
+ return response.content
121
 
122
+ except requests.exceptions.RequestException as e:
123
+ print(f"TTS API attempt {attempt + 1} failed: {str(e)}")
124
+ if attempt < max_retries - 1:
125
+ time.sleep(retry_delay * (attempt + 1)) # Exponential backoff
126
+ else:
127
+ raise Exception(f"TTS API failed after {max_retries} attempts: {str(e)}")
128
+
129
+ def generate_long_audio(script_text, voice_name, is_cheerful, output_path):
130
+ """Handles long audio generation by splitting text and combining results."""
131
+ print("Processing long audio generation...")
132
+ text_chunks = split_text_for_tts(script_text)
133
+ audio_clips = []
134
+ temp_files = []
 
 
 
 
 
 
 
 
 
 
 
 
135
 
136
  try:
137
+ for i, chunk in enumerate(text_chunks):
138
+ print(f"Processing chunk {i+1}/{len(text_chunks)}")
139
+ chunk_audio = generate_audio_with_retry(chunk, voice_name, is_cheerful)
140
+
141
+ # Save chunk to temporary file
142
+ temp_file = f"temp_chunk_{i}.wav"
143
+ with open(temp_file, "wb") as f:
144
+ f.write(chunk_audio)
145
+ temp_files.append(temp_file)
146
+
147
+ # Load audio clip
148
+ audio_clip = AudioFileClip(temp_file)
149
+ audio_clips.append(audio_clip)
150
 
151
+ # Combine all audio clips
152
+ print("Combining audio chunks...")
153
+ final_audio = concatenate_audioclips(audio_clips)
154
+ final_audio.write_audiofile(output_path)
155
 
156
+ finally:
157
+ # Clean up temporary files
158
+ for temp_file in temp_files:
159
+ if os.path.exists(temp_file):
160
+ os.remove(temp_file)
161
 
162
+ # Close audio clips
163
+ for clip in audio_clips:
164
+ clip.close()
165
+
166
+ def replace_video_audio(video_path, new_audio_path, output_path):
167
+ """Replaces the audio track of a video file."""
168
+ print("Replacing video audio...")
169
+ video_clip = None
170
+ audio_clip = None
171
+
172
+ try:
173
+ video_clip = VideoFileClip(video_path)
174
+ audio_clip = AudioFileClip(new_audio_path)
175
+ video_clip.audio = audio_clip
176
+ video_clip.write_videofile(
177
  output_path,
178
  codec="libx264",
179
  audio_codec="aac",
180
+ logger='bar'
 
 
 
181
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  finally:
183
+ if audio_clip:
184
+ audio_clip.close()
185
+ if video_clip:
186
+ video_clip.close()
 
 
187
 
188
+ # --- 4. FLASK ROUTES ---
189
 
190
+ @app.route('/', methods=['GET'])
191
  def index():
192
+ """Render the main upload page."""
193
+ return render_template('index.html', voices=VOICE_CHOICES)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
 
195
+ @app.route('/process', methods=['POST'])
196
+ def process_video():
197
+ """Handle video upload and processing."""
198
+ input_video_path = None
199
+ temp_audio_path = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
 
 
 
 
 
201
  try:
202
+ # Validate file upload
203
+ if 'video' not in request.files or request.files['video'].filename == '':
204
+ flash("Please upload a video file.", "error")
205
+ return render_template('index.html', voices=VOICE_CHOICES)
206
 
207
+ # Save uploaded file
208
+ file = request.files['video']
209
+ filename = secure_filename(file.filename)
210
+ input_video_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
211
+ file.save(input_video_path)
212
+
213
+ # Get processing options
214
+ voice_choice = request.form.get('voice', 'Charon')
215
+ is_cheerful = request.form.get('tone') == 'on'
216
+
217
+ # Generate script
218
+ script = generate_tamil_script(input_video_path)
219
+
220
+ # Create temporary audio file
221
+ with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_audio:
222
+ temp_audio_path = temp_audio.name
223
+
224
+ # Generate audio with retry and chunking
225
+ generate_long_audio(script, voice_choice, is_cheerful, temp_audio_path)
226
+
227
+ # Create dubbed video
228
+ final_video_name = f"dubbed_{filename}"
229
+ final_video_path = os.path.join(app.config['DOWNLOAD_FOLDER'], final_video_name)
230
+ replace_video_audio(input_video_path, temp_audio_path, final_video_path)
231
+
232
+ flash("Video processing complete!", "success")
233
+ return render_template(
234
+ 'index.html',
235
+ voices=VOICE_CHOICES,
236
+ result_video=url_for('serve_video', filename=final_video_name),
237
+ script=script
238
  )
239
+
240
  except Exception as e:
241
+ print(f"Processing error: {str(e)}")
242
+ flash(f"An error occurred: {str(e)}", "error")
243
+ return render_template('index.html', voices=VOICE_CHOICES)
244
+
245
+ finally:
246
+ # Clean up temporary files
247
+ if input_video_path and os.path.exists(input_video_path):
248
+ os.remove(input_video_path)
249
+ if temp_audio_path and os.path.exists(temp_audio_path):
250
+ os.remove(temp_audio_path)
251
+
252
+ @app.route('/downloads/<filename>')
253
+ def serve_video(filename):
254
+ """Serve the processed video file."""
255
+ return send_from_directory(app.config['DOWNLOAD_FOLDER'], filename)
256
 
257
+ # --- 5. APPLICATION ENTRY POINT ---
258
  if __name__ == '__main__':
259
+ app.run(host="0.0.0.0", port=7860)