Athspi commited on
Commit
21e6f34
·
verified ·
1 Parent(s): 4f59ded

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +108 -155
app.py CHANGED
@@ -1,7 +1,7 @@
1
- # app.py
2
  import os
3
  import time
4
  import tempfile
 
5
  import google.generativeai as genai
6
  import requests
7
  from flask import Flask, request, render_template, send_from_directory, url_for, flash
@@ -10,43 +10,35 @@ from moviepy.audio.io.AudioFileClip import AudioFileClip
10
  from werkzeug.utils import secure_filename
11
  from dotenv import load_dotenv
12
 
13
- # Initialize Flask app
14
  load_dotenv()
15
  app = Flask(__name__)
16
 
17
- # Configuration
18
- app.config.update({
19
- 'GEMINI_API_KEY': os.getenv('GEMINI_API_KEY'),
20
- 'TTS_API_URL': os.getenv('TTS_API_URL'),
21
- 'UPLOAD_FOLDER': 'uploads',
22
- 'DOWNLOAD_FOLDER': 'downloads',
23
- 'MAX_CONTENT_LENGTH': 100 * 1024 * 1024, # 100MB
24
- 'SECRET_KEY': os.urandom(24),
25
- 'ALLOWED_EXTENSIONS': {'mp4', 'mov', 'webm', 'avi'}
26
- })
27
-
28
- # Create directories
29
- os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
30
- os.makedirs(app.config['DOWNLOAD_FOLDER'], exist_ok=True)
31
-
32
- # Initialize Gemini AI
33
- genai.configure(api_key=app.config['GEMINI_API_KEY'])
34
-
35
- # Voice Configuration
36
- VOICE_OPTIONS = {
37
- "Male (Charon)": {
38
- "id": "charon",
39
- "description": "Deep, authoritative male voice",
40
- "tone_options": ["neutral", "serious", "cheerful"]
41
- },
42
- "Female (Zephyr)": {
43
- "id": "zephyr",
44
- "description": "Soft, clear female voice",
45
- "tone_options": ["neutral", "gentle", "energetic"]
46
- }
47
- }
48
-
49
-
50
  GEMINI_PROMPT = """
51
  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.
52
 
@@ -59,142 +51,103 @@ You are an expert AI scriptwriter. Your task is to watch the provided video and
59
  Say happily: வணக்கம்! [laugh] எப்படி இருக்கீங்க? Whisper mysteriously: அந்த ரகசியம் எனக்கு மட்டும் தான் தெரியும்.
60
  """
61
 
62
- def allowed_file(filename):
63
- return '.' in filename and \
64
- filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS']
65
-
66
- def generate_script(video_path):
67
- """Generate Tamil script using Gemini AI"""
68
- try:
69
- print("Uploading video to Gemini...")
70
- video_file = genai.upload_file(video_path, mime_type="video/mp4")
71
-
72
- # Wait for processing
73
- while video_file.state.name == "PROCESSING":
74
- time.sleep(5)
75
- video_file = genai.get_file(video_file.name)
76
-
77
- if video_file.state.name != "ACTIVE":
78
- raise Exception("Gemini processing failed")
79
-
80
- model = genai.GenerativeModel("models/gemini-2.5-flash")
81
- response = model.generate_content([GEMINI_PROMPT, video_file])
82
- genai.delete_file(video_file.name)
83
-
84
- return response.text.strip() if hasattr(response, 'text') else ""
85
- except Exception as e:
86
- print(f"Gemini Error: {str(e)}")
87
- raise
88
-
89
- def generate_audio(script, voice_id, tone):
90
- """Generate audio using TTS API with specific voice"""
 
 
 
 
 
 
91
  try:
92
- response = requests.post(
93
- app.config['TTS_API_URL'],
94
- json={
95
- "text": script,
96
- "voice_name": voice_name,
97
- "cheerful": cheerful,
98
- "sample_rate": 24000,
99
- "channels": 1,
100
- "sample_width": 2
101
- },
102
- timeout=300
103
- )
104
- response.raise_for_status()
105
- return response.content
106
- except requests.exceptions.RequestException as e:
107
- print(f"TTS API Error: {str(e)}")
108
- raise
109
-
110
- def process_video(input_path, audio_data, output_filename):
111
- """Combine video with new audio track"""
112
- try:
113
- with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as audio_temp:
114
- audio_temp.write(audio_data)
115
- audio_temp_path = audio_temp.name
116
-
117
- video = VideoFileClip(input_path)
118
- audio = AudioFileClip(audio_temp_path)
119
-
120
- # Ensure audio matches video duration
121
- if audio.duration > video.duration:
122
- audio = audio.subclip(0, video.duration)
123
-
124
- video.audio = audio
125
- output_path = os.path.join(app.config['DOWNLOAD_FOLDER'], output_filename)
126
- video.write_videofile(
127
- output_path,
128
- codec='libx264',
129
- audio_codec='aac',
130
- threads=4,
131
- logger=None
132
- )
133
-
134
- return output_path
135
- except Exception as e:
136
- print(f"Video Processing Error: {str(e)}")
137
- raise
138
  finally:
139
- if 'video' in locals(): video.close()
140
- if 'audio' in locals(): audio.close()
141
- if os.path.exists(audio_temp_path): os.remove(audio_temp_path)
 
142
 
143
  @app.route('/', methods=['GET'])
144
- def home():
145
- return render_template('index.html', voices=VOICE_OPTIONS)
 
146
 
147
  @app.route('/process', methods=['POST'])
148
- def process():
149
- if 'video' not in request.files:
150
- flash('No file selected', 'error')
151
- return render_template('index.html', voices=VOICE_OPTIONS)
152
-
153
- file = request.files['video']
154
- if file.filename == '':
155
- flash('No file selected', 'error')
156
- return render_template('index.html', voices=VOICE_OPTIONS)
157
-
158
- if not allowed_file(file.filename):
159
- flash('Invalid file type. Allowed: MP4, MOV, WEBM, AVI', 'error')
160
- return render_template('index.html', voices=VOICE_OPTIONS)
161
-
162
  try:
163
- # Save uploaded file
 
 
 
 
164
  filename = secure_filename(file.filename)
165
- input_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
166
- file.save(input_path)
167
-
168
- # Get processing options
169
- voice_name = request.form.get('voice')
170
- voice_id = VOICE_OPTIONS[voice_name]['id']
171
- tone = request.form.get('tone', 'neutral')
172
 
173
- # Generate script and audio
174
- script = generate_script(input_path)
175
- audio_data = generate_audio(script, voice_id, tone)
176
 
177
- # Process video
178
- output_filename = f"dubbed_{filename}"
179
- output_path = process_video(input_path, audio_data, output_filename)
 
180
 
181
- flash('Processing completed successfully!', 'success')
182
- return render_template('result.html',
183
- video_url=url_for('download', filename=output_filename),
184
- script=script,
185
- voice_used=voice_name,
186
- tone_used=tone)
187
-
188
  except Exception as e:
189
- flash(f'Processing failed: {str(e)}', 'error')
190
- return render_template('index.html', voices=VOICE_OPTIONS)
 
191
  finally:
192
- if 'input_path' in locals() and os.path.exists(input_path):
193
- os.remove(input_path)
 
 
194
 
195
  @app.route('/downloads/<filename>')
196
- def download(filename):
 
197
  return send_from_directory(app.config['DOWNLOAD_FOLDER'], filename)
198
 
 
199
  if __name__ == '__main__':
200
  app.run(host="0.0.0.0", port=7860)
 
 
1
  import os
2
  import time
3
  import tempfile
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
 
10
  from werkzeug.utils import secure_filename
11
  from dotenv import load_dotenv
12
 
13
+ # --- 1. INITIALIZE FLASK APP AND LOAD SECRETS ---
14
  load_dotenv()
15
  app = Flask(__name__)
16
 
17
+ # Load secrets from the loaded environment variables
18
+ GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
19
+ TTS_API_URL = os.getenv("TTS_API_URL")
20
+
21
+ # Check if secrets were loaded correctly
22
+ if not GEMINI_API_KEY:
23
+ raise ValueError("SECURITY ERROR: GEMINI_API_KEY not found in .env file!")
24
+ if not TTS_API_URL:
25
+ raise ValueError("CONFIGURATION ERROR: TTS_API_URL not found in .env file!")
26
+
27
+ # Configure the Gemini API
28
+ genai.configure(api_key=GEMINI_API_KEY)
29
+
30
+ # Configure directories and app settings
31
+ UPLOAD_FOLDER = 'uploads'
32
+ DOWNLOAD_FOLDER = 'downloads'
33
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
34
+ os.makedirs(DOWNLOAD_FOLDER, exist_ok=True)
35
+ app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
36
+ app.config['DOWNLOAD_FOLDER'] = DOWNLOAD_FOLDER
37
+ app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # 100 MB upload limit
38
+ app.secret_key = os.urandom(24) # Secure key for flash messages
39
+
40
+ # --- 2. VOICE CHOICES & GEMINI PROMPT ---
41
+ VOICE_CHOICES = {"Male (Charon)": "Charon", "Female (Zephyr)": "Zephyr"}
 
 
 
 
 
 
 
 
42
  GEMINI_PROMPT = """
43
  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.
44
 
 
51
  Say happily: வணக்கம்! [laugh] எப்படி இருக்கீங்க? Whisper mysteriously: அந்த ரகசியம் எனக்கு மட்டும் தான் தெரியும்.
52
  """
53
 
54
+ # --- 3. CORE LOGIC HELPER FUNCTIONS ---
55
+
56
+ def generate_tamil_script(video_file_path):
57
+ """Generates a single, continuous Tamil script from the video using Gemini."""
58
+ print("Uploading file to Gemini for transcription...")
59
+ video_file = genai.upload_file(video_file_path, mime_type="video/mp4")
60
+ print("Waiting for Gemini file processing...")
61
+ while video_file.state.name == "PROCESSING":
62
+ time.sleep(5)
63
+ video_file = genai.get_file(video_file.name)
64
+ if video_file.state.name != "ACTIVE":
65
+ raise Exception(f"Gemini file processing failed: {video_file.state.name}")
66
+ print("Generating narrator script...")
67
+ model = genai.GenerativeModel(model_name="models/gemini-2.5-flash")
68
+ response = model.generate_content([GEMINI_PROMPT, video_file])
69
+ genai.delete_file(video_file.name)
70
+ if hasattr(response, 'text') and response.text:
71
+ return " ".join(response.text.strip().splitlines())
72
+ raise Exception("No valid script was generated by Gemini.")
73
+
74
+ def generate_single_audio_track(dialogue_text, voice_name, is_cheerful, output_path):
75
+ """Generates one audio track for the entire script via TTS API."""
76
+ print(f"Requesting audio from TTS API (Voice: {voice_name}, Cheerful: {is_cheerful})")
77
+ payload = {"text": dialogue_text, "voice_name": voice_name, "cheerful": is_cheerful}
78
+ response = requests.post(TTS_API_URL, json=payload, timeout=300)
79
+ if response.status_code == 200:
80
+ with open(output_path, "wb") as f:
81
+ f.write(response.content)
82
+ return True
83
+ raise Exception(f"Error from TTS API: {response.status_code} - {response.text}")
84
+
85
+ def replace_video_audio(video_path, new_audio_path, output_path):
86
+ """Replaces the audio of a video using the modern, correct MoviePy method."""
87
+ print("Replacing video audio using MoviePy...")
88
+ video_clip, audio_clip = None, None
89
  try:
90
+ video_clip = VideoFileClip(video_path)
91
+ audio_clip = AudioFileClip(new_audio_path)
92
+ video_clip.audio = audio_clip
93
+ video_clip.write_videofile(output_path, codec="libx264", audio_codec="aac", logger='bar')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  finally:
95
+ if audio_clip: audio_clip.close()
96
+ if video_clip: video_clip.close()
97
+
98
+ # --- 4. FLASK WEB ROUTES ---
99
 
100
  @app.route('/', methods=['GET'])
101
+ def index():
102
+ """Renders the main upload page."""
103
+ return render_template('index.html')
104
 
105
  @app.route('/process', methods=['POST'])
106
+ def process_video():
107
+ """Handles video upload, processing, and renders the result."""
108
+ input_video_path, temp_audio_path = None, None
 
 
 
 
 
 
 
 
 
 
 
109
  try:
110
+ if 'video' not in request.files or request.files['video'].filename == '':
111
+ flash("Please upload a video file.")
112
+ return render_template('index.html')
113
+
114
+ file = request.files['video']
115
  filename = secure_filename(file.filename)
116
+ input_video_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
117
+ file.save(input_video_path)
118
+
119
+ voice_choice = request.form['voice_choice']
120
+ is_cheerful = 'cheerful' in request.form
121
+ voice_name = VOICE_CHOICES[voice_choice]
122
+ script = generate_tamil_script(input_video_path)
123
 
124
+ with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_audio:
125
+ temp_audio_path = temp_audio.name
 
126
 
127
+ generate_single_audio_track(script, voice_name, is_cheerful, temp_audio_path)
128
+ final_video_name = f"dubbed_{filename}"
129
+ final_video_path = os.path.join(app.config['DOWNLOAD_FOLDER'], final_video_name)
130
+ replace_video_audio(input_video_path, temp_audio_path, final_video_path)
131
 
132
+ flash("Video processing complete!", "success")
133
+ return render_template('index.html',
134
+ result_video=url_for('serve_video', filename=final_video_name),
135
+ script=script)
 
 
 
136
  except Exception as e:
137
+ print(f"An error occurred during processing: {e}")
138
+ flash(f"An unexpected error occurred: {e}. Please check the console and try again.", "error")
139
+ return render_template('index.html')
140
  finally:
141
+ if input_video_path and os.path.exists(input_video_path):
142
+ os.remove(input_video_path)
143
+ if temp_audio_path and os.path.exists(temp_audio_path):
144
+ os.remove(temp_audio_path)
145
 
146
  @app.route('/downloads/<filename>')
147
+ def serve_video(filename):
148
+ """Serves the final dubbed video."""
149
  return send_from_directory(app.config['DOWNLOAD_FOLDER'], filename)
150
 
151
+ # --- 5. APPLICATION ENTRY POINT ---
152
  if __name__ == '__main__':
153
  app.run(host="0.0.0.0", port=7860)