Athspi commited on
Commit
d6208ae
·
verified ·
1 Parent(s): c8f0a70

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +85 -80
app.py CHANGED
@@ -3,42 +3,40 @@ import time
3
  import tempfile
4
  import google.generativeai as genai
5
  import requests
6
- from flask import Flask, request, render_template, send_from_directory, url_for, jsonify
7
  from moviepy.video.io.VideoFileClip import VideoFileClip
8
  from moviepy.audio.io.AudioFileClip import AudioFileClip
9
- from moviepy.video.compositing.CompositeVideoClip import CompositeVideoClip
10
- from pydub import AudioSegment
11
  from werkzeug.utils import secure_filename
12
- from google.generativeai.types import HarmCategory, HarmBlockThreshold
13
 
14
- # --- 1. INITIALIZE FLASK APP AND LOAD SECRETS ---
 
15
  app = Flask(__name__)
16
 
17
- # Load secrets from environment variables for security
18
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
19
  TTS_API_URL = os.getenv("TTS_API_URL")
20
 
21
- # Application needs a secret key for flashing messages
22
- app.config['SECRET_KEY'] = os.urandom(24)
23
-
24
- # Check if the secrets were loaded correctly
25
  if not GEMINI_API_KEY:
26
- raise ValueError("GEMINI_API_KEY secret not found! Please set it as an environment variable.")
27
  if not TTS_API_URL:
28
- raise ValueError("TTS_API_URL secret not found! Please set it as an environment variable.")
29
 
30
- # Configure the Gemini API with the loaded key
31
  genai.configure(api_key=GEMINI_API_KEY)
32
 
33
- # Configure directories for file uploads and processed downloads
34
  UPLOAD_FOLDER = 'uploads'
35
  DOWNLOAD_FOLDER = 'downloads'
36
  os.makedirs(UPLOAD_FOLDER, exist_ok=True)
37
  os.makedirs(DOWNLOAD_FOLDER, exist_ok=True)
38
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
39
  app.config['DOWNLOAD_FOLDER'] = DOWNLOAD_FOLDER
 
 
 
 
40
 
41
- # --- 2. DEFINE VOICE CHOICES AND GEMINI PROMPT ---
42
  VOICE_CHOICES = {
43
  "Male (Charon)": "Charon",
44
  "Female (Zephyr)": "Zephyr"
@@ -48,70 +46,82 @@ GEMINI_PROMPT = """
48
  You are an AI scriptwriter. Your task is to watch the provided video and transcribe ALL spoken dialogue into a SINGLE, CONTINUOUS block of modern, colloquial Tamil.
49
 
50
  **CRITICAL INSTRUCTIONS:**
51
- 1. **Single Script:** Combine all dialogue from all speakers into one continuous script.
52
  2. **NO Timestamps or Speaker Labels:** Do NOT include any timestamps or speaker identifiers.
53
- 3. **Incorporate Performance:** Add English style prompts (e.g., `Say happily:`, `Whisper mysteriously:`) and performance tags (e.g., `[laugh]`, `[sigh]`) directly into the text.
54
 
55
  **EXAMPLE OUTPUT:**
56
- Say happily: வணக்கம்! [laugh] எப்படி இருக்கீங்க? Whisper mysteriously: அந்த ரகசியம் எனக்கு மட்டும் தான் தெரியும்.
57
  """
58
 
59
- # --- 3. HELPER FUNCTIONS (CORE LOGIC) ---
60
 
61
  def generate_tamil_script(video_file_path):
62
- """Generates a single, continuous Tamil script from the video."""
63
  print("Uploading file to Gemini for transcription...")
64
- video_file = genai.upload_file(video_file_path, mime_type="video/mp4")
65
 
66
- print("Waiting for file processing...")
67
  while video_file.state.name == "PROCESSING":
68
  time.sleep(5)
69
  video_file = genai.get_file(video_file.name)
70
  if video_file.state.name != "ACTIVE":
71
- raise Exception(f"File {video_file.name} failed to process on Gemini's side.")
72
 
73
- print("Generating single narrator script...")
74
  model = genai.GenerativeModel(model_name="models/gemini-2.5-flash")
75
  response = model.generate_content([GEMINI_PROMPT, video_file])
76
-
77
  genai.delete_file(video_file.name)
78
- print("Deleted file from Gemini.")
79
-
80
  if response.text:
81
  return " ".join(response.text.strip().splitlines())
82
- raise Exception("No valid script was generated by Gemini. The video might be silent or have unrecognized speech.")
83
 
84
  def generate_single_audio_track(dialogue_text, voice_name, is_cheerful, output_path):
85
- """Generates one continuous audio track for the entire script."""
86
- print(f"Generating audio with voice '{voice_name}' | Cheerful: {is_cheerful}")
87
  payload = {"text": dialogue_text, "voice_name": voice_name, "cheerful": is_cheerful}
88
- response = requests.post(TTS_API_URL, json=payload, timeout=300) # Added timeout
89
  if response.status_code == 200:
90
  with open(output_path, "wb") as f:
91
  f.write(response.content)
92
- print(f"Audio track saved successfully to {output_path}")
93
  return True
94
  raise Exception(f"Error from TTS API: {response.status_code} - {response.text}")
95
 
96
  def replace_video_audio(video_path, new_audio_path, output_path):
97
- """Replaces the audio of a video with a new audio file."""
98
- print("Replacing video audio...")
 
 
 
 
 
 
99
  try:
100
  video_clip = VideoFileClip(video_path)
101
  audio_clip = AudioFileClip(new_audio_path)
 
 
102
  final_clip = video_clip.set_audio(audio_clip)
 
 
103
  final_clip.write_videofile(output_path, codec="libx264", audio_codec="aac", logger='bar')
 
 
 
 
 
104
  finally:
105
- # Ensure clips are closed to release file handles
106
- if 'video_clip' in locals():
107
  video_clip.close()
108
- if 'audio_clip' in locals():
109
  audio_clip.close()
110
- if 'final_clip' in locals():
111
  final_clip.close()
112
- print(f"Final video saved to {output_path}")
113
 
114
- # --- 4. FLASK ROUTES ---
115
 
116
  @app.route('/', methods=['GET'])
117
  def index():
@@ -120,60 +130,55 @@ def index():
120
 
121
  @app.route('/process', methods=['POST'])
122
  def process_video():
123
- """Handles the video upload and dubbing process."""
124
  if 'video' not in request.files:
125
- flash("No video file part in the request.", "error")
126
  return render_template('index.html')
127
 
128
  file = request.files['video']
129
  if file.filename == '':
130
- flash("No file was selected. Please choose a video to upload.", "error")
131
  return render_template('index.html')
132
 
133
- if file:
134
  filename = secure_filename(file.filename)
135
  upload_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
136
- temp_audio_path = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
 
138
- try:
139
- file.save(upload_path)
140
-
141
- voice_choice = request.form['voice_choice']
142
- is_cheerful = 'cheerful' in request.form
143
- voice_name = VOICE_CHOICES[voice_choice]
144
-
145
- # Generate the script
146
- script = generate_tamil_script(upload_path)
147
-
148
- # Generate the audio track in a temporary file
149
- temp_audio_fd, temp_audio_path = tempfile.mkstemp(suffix=".wav")
150
- os.close(temp_audio_fd) # Close file descriptor
151
- generate_single_audio_track(script, voice_name, is_cheerful, temp_audio_path)
152
-
153
- # Create the final video
154
- final_video_name = f"dubbed_{filename}"
155
- final_video_path = os.path.join(app.config['DOWNLOAD_FOLDER'], final_video_name)
156
- replace_video_audio(upload_path, temp_audio_path, final_video_path)
157
-
158
- # Render the page again with the results
159
- return render_template('index.html',
160
- result_video=url_for('serve_video', filename=final_video_name),
161
- script=script)
162
-
163
- except Exception as e:
164
- print(f"An error occurred: {e}")
165
- flash(f"An error occurred during processing: {e}", "error")
166
- return render_template('index.html')
167
- finally:
168
- # Clean up temporary audio file if it exists
169
- if temp_audio_path and os.path.exists(temp_audio_path):
170
- os.remove(temp_audio_path)
171
 
172
  @app.route('/downloads/<filename>')
173
  def serve_video(filename):
174
- """Serves the final dubbed video from the downloads directory."""
175
  return send_from_directory(app.config['DOWNLOAD_FOLDER'], filename)
176
 
 
177
  if __name__ == '__main__':
178
- # Use host='0.0.0.0' to make it accessible on your local network
179
  app.run(host="0.0.0.0", port=7860)
 
3
  import tempfile
4
  import google.generativeai as genai
5
  import requests
6
+ from flask import Flask, request, render_template, send_from_directory, url_for, flash
7
  from moviepy.video.io.VideoFileClip import VideoFileClip
8
  from moviepy.audio.io.AudioFileClip import AudioFileClip
 
 
9
  from werkzeug.utils import secure_filename
 
10
 
11
+ # --- 1. INITIALIZATION & CONFIGURATION ---
12
+
13
  app = Flask(__name__)
14
 
15
+ # Load secrets from environment variables
16
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
17
  TTS_API_URL = os.getenv("TTS_API_URL")
18
 
19
+ # Check if secrets were loaded correctly and provide clear error messages
 
 
 
20
  if not GEMINI_API_KEY:
21
+ raise ValueError("SECURITY ERROR: GEMINI_API_KEY secret not found! Please set it as an environment variable.")
22
  if not TTS_API_URL:
23
+ raise ValueError("CONFIGURATION ERROR: TTS_API_URL secret not found! Please set it as an environment variable.")
24
 
25
+ # Configure the Gemini API
26
  genai.configure(api_key=GEMINI_API_KEY)
27
 
28
+ # Configure directories for temporary file storage
29
  UPLOAD_FOLDER = 'uploads'
30
  DOWNLOAD_FOLDER = 'downloads'
31
  os.makedirs(UPLOAD_FOLDER, exist_ok=True)
32
  os.makedirs(DOWNLOAD_FOLDER, exist_ok=True)
33
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
34
  app.config['DOWNLOAD_FOLDER'] = DOWNLOAD_FOLDER
35
+ app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # 100 MB upload limit
36
+ app.secret_key = 'supersecretkey' # Required for flash messages
37
+
38
+ # --- 2. VOICE CHOICES & GEMINI PROMPT ---
39
 
 
40
  VOICE_CHOICES = {
41
  "Male (Charon)": "Charon",
42
  "Female (Zephyr)": "Zephyr"
 
46
  You are an AI scriptwriter. Your task is to watch the provided video and transcribe ALL spoken dialogue into a SINGLE, CONTINUOUS block of modern, colloquial Tamil.
47
 
48
  **CRITICAL INSTRUCTIONS:**
49
+ 1. **Single Script:** Combine all dialogue into one continuous script.
50
  2. **NO Timestamps or Speaker Labels:** Do NOT include any timestamps or speaker identifiers.
51
+ 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.
52
 
53
  **EXAMPLE OUTPUT:**
54
+ Say happily: வணக்கம்! [laugh] எப்படி இருக்கீங்க? Whisper mysteriously: அந்த ரகசியம் எனக்கு மட்டும் தான் தெரியும். Shout angrily: உடனே இங்கிருந்து போ!
55
  """
56
 
57
+ # --- 3. CORE LOGIC HELPER FUNCTIONS ---
58
 
59
  def generate_tamil_script(video_file_path):
60
+ """Generates a single, continuous Tamil script from the video using Gemini."""
61
  print("Uploading file to Gemini for transcription...")
62
+ video_file = genai.upload_file(video_file_path)
63
 
64
+ print("Waiting for Gemini file processing...")
65
  while video_file.state.name == "PROCESSING":
66
  time.sleep(5)
67
  video_file = genai.get_file(video_file.name)
68
  if video_file.state.name != "ACTIVE":
69
+ raise Exception(f"Gemini file processing failed: {video_file.state.name}")
70
 
71
+ print("Generating narrator script...")
72
  model = genai.GenerativeModel(model_name="models/gemini-2.5-flash")
73
  response = model.generate_content([GEMINI_PROMPT, video_file])
 
74
  genai.delete_file(video_file.name)
75
+
 
76
  if response.text:
77
  return " ".join(response.text.strip().splitlines())
78
+ raise Exception("No valid script was generated by Gemini.")
79
 
80
  def generate_single_audio_track(dialogue_text, voice_name, is_cheerful, output_path):
81
+ """Generates one audio track for the entire script via TTS API."""
82
+ print(f"Requesting audio from TTS API (Voice: {voice_name}, Cheerful: {is_cheerful})")
83
  payload = {"text": dialogue_text, "voice_name": voice_name, "cheerful": is_cheerful}
84
+ response = requests.post(TTS_API_URL, json=payload, timeout=300) # 5-minute timeout
85
  if response.status_code == 200:
86
  with open(output_path, "wb") as f:
87
  f.write(response.content)
88
+ print(f"Audio track saved to {output_path}")
89
  return True
90
  raise Exception(f"Error from TTS API: {response.status_code} - {response.text}")
91
 
92
  def replace_video_audio(video_path, new_audio_path, output_path):
93
+ """
94
+ Replaces the audio of a video with a new audio file.
95
+ This function is robustly designed to ensure file handles are always closed.
96
+ """
97
+ print("Replacing video audio using MoviePy...")
98
+ video_clip = None
99
+ audio_clip = None
100
+ final_clip = None
101
  try:
102
  video_clip = VideoFileClip(video_path)
103
  audio_clip = AudioFileClip(new_audio_path)
104
+
105
+ # The standard and correct way to set audio in modern moviepy
106
  final_clip = video_clip.set_audio(audio_clip)
107
+
108
+ # Write the final video file with a progress bar in the console
109
  final_clip.write_videofile(output_path, codec="libx264", audio_codec="aac", logger='bar')
110
+ print(f"Successfully created final video at {output_path}")
111
+
112
+ except Exception as e:
113
+ print(f"FATAL ERROR in replace_video_audio: {e}")
114
+ raise # Re-raise the exception to be caught by the Flask route
115
  finally:
116
+ # This block ensures that all files are closed, preventing file lock issues.
117
+ if video_clip:
118
  video_clip.close()
119
+ if audio_clip:
120
  audio_clip.close()
121
+ if final_clip:
122
  final_clip.close()
 
123
 
124
+ # --- 4. FLASK WEB ROUTES ---
125
 
126
  @app.route('/', methods=['GET'])
127
  def index():
 
130
 
131
  @app.route('/process', methods=['POST'])
132
  def process_video():
133
+ """Handles video upload, processing, and renders the result."""
134
  if 'video' not in request.files:
135
+ flash("No video file selected. Please choose a file to upload.")
136
  return render_template('index.html')
137
 
138
  file = request.files['video']
139
  if file.filename == '':
140
+ flash("No video file selected. Please choose a file to upload.")
141
  return render_template('index.html')
142
 
143
+ try:
144
  filename = secure_filename(file.filename)
145
  upload_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
146
+ file.save(upload_path)
147
+
148
+ voice_choice = request.form['voice_choice']
149
+ is_cheerful = 'cheerful' in request.form
150
+ voice_name = VOICE_CHOICES[voice_choice]
151
+
152
+ # Core Logic Execution
153
+ script = generate_tamil_script(upload_path)
154
+
155
+ with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_audio:
156
+ temp_audio_path = temp_audio.name
157
+
158
+ generate_single_audio_track(script, voice_name, is_cheerful, temp_audio_path)
159
+
160
+ final_video_name = f"dubbed_{filename}"
161
+ final_video_path = os.path.join(app.config['DOWNLOAD_FOLDER'], final_video_name)
162
 
163
+ replace_video_audio(upload_path, temp_audio_path, final_video_path)
164
+
165
+ # Cleanup temporary audio file
166
+ os.remove(temp_audio_path)
167
+
168
+ return render_template('index.html',
169
+ result_video=url_for('serve_video', filename=final_video_name),
170
+ script=script)
171
+
172
+ except Exception as e:
173
+ print(f"An error occurred during processing: {e}")
174
+ flash(f"An error occurred: {e}")
175
+ return render_template('index.html')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
 
177
  @app.route('/downloads/<filename>')
178
  def serve_video(filename):
179
+ """Serves the final dubbed video for display on the webpage."""
180
  return send_from_directory(app.config['DOWNLOAD_FOLDER'], filename)
181
 
182
+ # --- 5. APPLICATION ENTRY POINT ---
183
  if __name__ == '__main__':
 
184
  app.run(host="0.0.0.0", port=7860)