Athspi commited on
Commit
893e301
·
verified ·
1 Parent(s): 21e6f34

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +84 -47
app.py CHANGED
@@ -14,20 +14,20 @@ from dotenv import load_dotenv
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)
@@ -35,109 +35,146 @@ 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
 
45
  **CRITICAL INSTRUCTIONS:**
46
- 1. **Single Script:** Combine all dialogue from all speakers into one continuous script.
47
- 2. **NO Timestamps or Speaker Labels:** Do NOT include any timestamps or speaker identifiers.
48
- 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.
49
 
50
  **EXAMPLE OUTPUT:**
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):
@@ -145,7 +182,7 @@ def process_video():
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 ---
 
14
  load_dotenv()
15
  app = Flask(__name__)
16
 
17
+ # Load secrets from environment variables
18
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
19
  TTS_API_URL = os.getenv("TTS_API_URL")
20
 
21
+ # Validate required configurations
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 Gemini AI
28
  genai.configure(api_key=GEMINI_API_KEY)
29
 
30
+ # Configure directories
31
  UPLOAD_FOLDER = 'uploads'
32
  DOWNLOAD_FOLDER = 'downloads'
33
  os.makedirs(UPLOAD_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. APPLICATION CONFIGURATION ---
41
+ VOICE_CHOICES = {
42
+ "Male (Charon)": "Charon",
43
+ "Female (Zephyr)": "Zephyr"
44
+ }
45
 
 
 
46
  GEMINI_PROMPT = """
47
  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.
48
 
49
  **CRITICAL INSTRUCTIONS:**
50
+ 1. **Single Script:** Combine all dialogue from all speakers into one continuous script.
51
+ 2. **NO Timestamps or Speaker Labels:** Do NOT include any timestamps or speaker identifiers.
52
+ 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.
53
 
54
  **EXAMPLE OUTPUT:**
55
  Say happily: வணக்கம்! [laugh] எப்படி இருக்கீங்க? Whisper mysteriously: அந்த ரகசியம் எனக்கு மட்டும் தான் தெரியும்.
56
  """
57
 
58
+ # --- 3. CORE APPLICATION FUNCTIONS ---
59
 
60
  def generate_tamil_script(video_file_path):
61
+ """Generates a Tamil script from the video using Gemini AI."""
62
+ print("Uploading video to Gemini for transcription...")
63
  video_file = genai.upload_file(video_file_path, mime_type="video/mp4")
64
+
65
+ # Wait for file processing
66
  while video_file.state.name == "PROCESSING":
67
  time.sleep(5)
68
  video_file = genai.get_file(video_file.name)
69
+
70
  if video_file.state.name != "ACTIVE":
71
  raise Exception(f"Gemini file processing failed: {video_file.state.name}")
72
+
73
+ print("Generating script...")
74
+ model = genai.GenerativeModel(model_name="models/gemini-1.5-pro-latest")
75
  response = model.generate_content([GEMINI_PROMPT, video_file])
76
  genai.delete_file(video_file.name)
77
+
78
  if hasattr(response, 'text') and response.text:
79
  return " ".join(response.text.strip().splitlines())
80
  raise Exception("No valid script was generated by Gemini.")
81
 
82
+ def generate_audio_track(script_text, voice_name, is_cheerful, output_path):
83
+ """Generates audio from script using TTS API."""
84
+ print(f"Generating audio (Voice: {voice_name}, Cheerful: {is_cheerful})")
85
+ payload = {
86
+ "text": script_text,
87
+ "voice_name": voice_name,
88
+ "cheerful": is_cheerful
89
+ }
90
+
91
  response = requests.post(TTS_API_URL, json=payload, timeout=300)
92
  if response.status_code == 200:
93
  with open(output_path, "wb") as f:
94
  f.write(response.content)
95
  return True
96
+ raise Exception(f"TTS API Error: {response.status_code} - {response.text}")
97
 
98
  def replace_video_audio(video_path, new_audio_path, output_path):
99
+ """Replaces the audio track of a video file."""
100
+ print("Replacing video audio...")
101
+ video_clip = None
102
+ audio_clip = None
103
+
104
  try:
105
  video_clip = VideoFileClip(video_path)
106
  audio_clip = AudioFileClip(new_audio_path)
107
  video_clip.audio = audio_clip
108
+ video_clip.write_videofile(
109
+ output_path,
110
+ codec="libx264",
111
+ audio_codec="aac",
112
+ logger='bar'
113
+ )
114
  finally:
115
+ if audio_clip:
116
+ audio_clip.close()
117
+ if video_clip:
118
+ video_clip.close()
119
 
120
+ # --- 4. FLASK ROUTES ---
121
 
122
  @app.route('/', methods=['GET'])
123
  def index():
124
+ """Render the main upload page."""
125
+ return render_template('index.html', voices=VOICE_CHOICES)
126
 
127
  @app.route('/process', methods=['POST'])
128
  def process_video():
129
+ """Handle video upload and processing."""
130
+ input_video_path = None
131
+ temp_audio_path = None
132
+
133
  try:
134
+ # Validate file upload
135
  if 'video' not in request.files or request.files['video'].filename == '':
136
+ flash("Please upload a video file.", "error")
137
+ return render_template('index.html', voices=VOICE_CHOICES)
138
+
139
+ # Save uploaded file
140
  file = request.files['video']
141
  filename = secure_filename(file.filename)
142
  input_video_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
143
  file.save(input_video_path)
144
+
145
+ # Get processing options
146
+ voice_choice = request.form.get('voice', 'Charon')
147
+ is_cheerful = request.form.get('tone') == 'on'
148
+
149
+ # Generate script and audio
150
  script = generate_tamil_script(input_video_path)
151
 
152
+ # Create temporary audio file
153
  with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_audio:
154
  temp_audio_path = temp_audio.name
155
 
156
+ generate_audio_track(script, voice_choice, is_cheerful, temp_audio_path)
157
+
158
+ # Create dubbed video
159
  final_video_name = f"dubbed_{filename}"
160
  final_video_path = os.path.join(app.config['DOWNLOAD_FOLDER'], final_video_name)
161
  replace_video_audio(input_video_path, temp_audio_path, final_video_path)
162
 
163
  flash("Video processing complete!", "success")
164
+ return render_template(
165
+ 'index.html',
166
+ voices=VOICE_CHOICES,
167
+ result_video=url_for('serve_video', filename=final_video_name),
168
+ script=script
169
+ )
170
+
171
  except Exception as e:
172
+ print(f"Processing error: {str(e)}")
173
+ flash(f"An error occurred: {str(e)}", "error")
174
+ return render_template('index.html', voices=VOICE_CHOICES)
175
+
176
  finally:
177
+ # Clean up temporary files
178
  if input_video_path and os.path.exists(input_video_path):
179
  os.remove(input_video_path)
180
  if temp_audio_path and os.path.exists(temp_audio_path):
 
182
 
183
  @app.route('/downloads/<filename>')
184
  def serve_video(filename):
185
+ """Serve the processed video file."""
186
  return send_from_directory(app.config['DOWNLOAD_FOLDER'], filename)
187
 
188
  # --- 5. APPLICATION ENTRY POINT ---