Athspi commited on
Commit
ab46005
·
verified ·
1 Parent(s): c84a6da

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +29 -44
app.py CHANGED
@@ -6,9 +6,7 @@ import google.generativeai as genai
6
  import requests
7
  import yt_dlp
8
  from flask import Flask, request, render_template, send_from_directory, url_for, flash
9
- from moviepy.video.io.VideoFileClip import VideoFileClip
10
- from moviepy.audio.io.AudioFileClip import AudioFileClip
11
- from moviepy.video.compositing.CompositeVideoClip import CompositeVideoClip
12
  from werkzeug.utils import secure_filename
13
  from dotenv import load_dotenv
14
 
@@ -16,29 +14,41 @@ from dotenv import load_dotenv
16
  load_dotenv()
17
  app = Flask(__name__)
18
 
 
19
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
20
  TTS_API_URL = os.getenv("TTS_API_URL")
21
 
 
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
  genai.configure(api_key=GEMINI_API_KEY)
28
 
 
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
36
- app.secret_key = os.urandom(24)
37
 
38
  # --- 2. VOICE CHOICES & GEMINI PROMPT ---
39
  VOICE_CHOICES = {"Male (Charon)": "Charon", "Female (Zephyr)": "Zephyr"}
40
  GEMINI_PROMPT = """
41
- You are an AI scriptwriter... (The full prompt remains the same as before)
 
 
 
 
 
 
 
 
42
  """
43
 
44
  # --- 3. CORE LOGIC HELPER FUNCTIONS ---
@@ -46,41 +56,27 @@ You are an AI scriptwriter... (The full prompt remains the same as before)
46
  def download_youtube_video(url, output_folder):
47
  """Downloads a YouTube video to a specified folder using the yt-dlp library."""
48
  print(f"📥 Downloading video from YouTube URL: {url}")
49
-
50
- # Generate a unique filename to avoid collisions
51
  unique_filename = f"{uuid.uuid4()}.mp4"
52
  output_path_template = os.path.join(output_folder, os.path.splitext(unique_filename)[0])
53
-
54
  ydl_opts = {
55
  'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best',
56
- 'outtmpl': output_path_template,
57
- 'merge_output_format': 'mp4',
58
- 'quiet': True,
59
- 'noplaylist': True,
60
  }
61
-
62
  try:
63
  with yt_dlp.YoutubeDL(ydl_opts) as ydl:
64
  ydl.download([url])
65
-
66
- # The actual filename will have a .mp4 extension
67
  downloaded_file_path = f"{output_path_template}.mp4"
68
  if os.path.exists(downloaded_file_path):
69
  print(f"✅ Download complete! File saved to: {downloaded_file_path}")
70
  return downloaded_file_path
71
- else:
72
- # Fallback for some edge cases where the extension might differ
73
- for f in os.listdir(output_folder):
74
- if f.startswith(os.path.splitext(unique_filename)[0]):
75
- return os.path.join(output_folder, f)
76
- raise FileNotFoundError("Downloaded video file not found.")
77
-
78
  except Exception as e:
79
  print(f"❌ An error occurred during YouTube download: {e}")
80
- raise # Re-raise the exception to be caught by the main processing block
81
 
82
  def generate_tamil_script(video_file_path):
83
- # (This function remains the same as the previous version)
84
  print("Uploading file to Gemini for transcription...")
85
  video_file = genai.upload_file(video_file_path, mime_type="video/mp4")
86
  print("Waiting for Gemini file processing...")
@@ -90,7 +86,7 @@ def generate_tamil_script(video_file_path):
90
  if video_file.state.name != "ACTIVE":
91
  raise Exception(f"Gemini file processing failed: {video_file.state.name}")
92
  print("Generating narrator script...")
93
- model = genai.GenerativeModel(model_name="models/gemini-2.5-flash")
94
  response = model.generate_content([GEMINI_PROMPT, video_file])
95
  genai.delete_file(video_file.name)
96
  if hasattr(response, 'text') and response.text:
@@ -98,7 +94,7 @@ def generate_tamil_script(video_file_path):
98
  raise Exception("No valid script was generated by Gemini.")
99
 
100
  def generate_single_audio_track(dialogue_text, voice_name, is_cheerful, output_path):
101
- # (This function remains the same)
102
  print(f"Requesting audio from TTS API (Voice: {voice_name}, Cheerful: {is_cheerful})")
103
  payload = {"text": dialogue_text, "voice_name": voice_name, "cheerful": is_cheerful}
104
  response = requests.post(TTS_API_URL, json=payload, timeout=300)
@@ -109,10 +105,9 @@ def generate_single_audio_track(dialogue_text, voice_name, is_cheerful, output_p
109
  raise Exception(f"Error from TTS API: {response.status_code} - {response.text}")
110
 
111
  def replace_video_audio(video_path, new_audio_path, output_path):
112
- # (This function remains the same, using the correct .audio attribute)
113
  print("Replacing video audio using MoviePy...")
114
- video_clip = None
115
- audio_clip = None
116
  try:
117
  video_clip = VideoFileClip(video_path)
118
  audio_clip = AudioFileClip(new_audio_path)
@@ -132,16 +127,11 @@ def index():
132
  @app.route('/process', methods=['POST'])
133
  def process_video():
134
  """Handles video input (upload or URL), processing, and renders the result."""
135
- input_video_path = None
136
- temp_audio_path = None
137
-
138
  try:
139
- # Determine input source: YouTube URL or File Upload
140
  youtube_url = request.form.get('youtube_url', '').strip()
141
-
142
  if youtube_url:
143
  input_video_path = download_youtube_video(youtube_url, app.config['UPLOAD_FOLDER'])
144
- # Generate a secure filename based on the unique downloaded file name
145
  filename = os.path.basename(input_video_path)
146
  elif 'video' in request.files and request.files['video'].filename != '':
147
  file = request.files['video']
@@ -152,33 +142,28 @@ def process_video():
152
  flash("Please either upload a video file or provide a YouTube URL.")
153
  return render_template('index.html')
154
 
155
- # Continue with the processing pipeline
156
  voice_choice = request.form['voice_choice']
157
  is_cheerful = 'cheerful' in request.form
158
  voice_name = VOICE_CHOICES[voice_choice]
159
-
160
  script = generate_tamil_script(input_video_path)
161
 
162
  with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_audio:
163
  temp_audio_path = temp_audio.name
164
 
165
  generate_single_audio_track(script, voice_name, is_cheerful, temp_audio_path)
166
-
167
  final_video_name = f"dubbed_{filename}"
168
  final_video_path = os.path.join(app.config['DOWNLOAD_FOLDER'], final_video_name)
169
-
170
  replace_video_audio(input_video_path, temp_audio_path, final_video_path)
171
 
 
172
  return render_template('index.html',
173
  result_video=url_for('serve_video', filename=final_video_name),
174
  script=script)
175
-
176
  except Exception as e:
177
  print(f"An error occurred during processing: {e}")
178
- flash(f"An unexpected error occurred: {e}. Please check the console and try again.")
179
  return render_template('index.html')
180
  finally:
181
- # Clean up all temporary files
182
  if input_video_path and os.path.exists(input_video_path):
183
  os.remove(input_video_path)
184
  if temp_audio_path and os.path.exists(temp_audio_path):
@@ -191,4 +176,4 @@ def serve_video(filename):
191
 
192
  # --- 5. APPLICATION ENTRY POINT ---
193
  if __name__ == '__main__':
194
- app.run(host="0.0.0.0", port=7860)
 
6
  import requests
7
  import yt_dlp
8
  from flask import Flask, request, render_template, send_from_directory, url_for, flash
9
+ from moviepy.editor import VideoFileClip, AudioFileClip
 
 
10
  from werkzeug.utils import secure_filename
11
  from dotenv import load_dotenv
12
 
 
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
+
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 ---
 
56
  def download_youtube_video(url, output_folder):
57
  """Downloads a YouTube video to a specified folder using the yt-dlp library."""
58
  print(f"📥 Downloading video from YouTube URL: {url}")
 
 
59
  unique_filename = f"{uuid.uuid4()}.mp4"
60
  output_path_template = os.path.join(output_folder, os.path.splitext(unique_filename)[0])
 
61
  ydl_opts = {
62
  'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best',
63
+ 'outtmpl': output_path_template, 'merge_output_format': 'mp4',
64
+ 'quiet': False, 'noplaylist': True, 'progress': True,
 
 
65
  }
 
66
  try:
67
  with yt_dlp.YoutubeDL(ydl_opts) as ydl:
68
  ydl.download([url])
 
 
69
  downloaded_file_path = f"{output_path_template}.mp4"
70
  if os.path.exists(downloaded_file_path):
71
  print(f"✅ Download complete! File saved to: {downloaded_file_path}")
72
  return downloaded_file_path
73
+ raise FileNotFoundError("Downloaded video file not found after yt-dlp process.")
 
 
 
 
 
 
74
  except Exception as e:
75
  print(f"❌ An error occurred during YouTube download: {e}")
76
+ raise
77
 
78
  def generate_tamil_script(video_file_path):
79
+ """Generates a single, continuous Tamil script from the video using Gemini."""
80
  print("Uploading file to Gemini for transcription...")
81
  video_file = genai.upload_file(video_file_path, mime_type="video/mp4")
82
  print("Waiting for Gemini file processing...")
 
86
  if video_file.state.name != "ACTIVE":
87
  raise Exception(f"Gemini file processing failed: {video_file.state.name}")
88
  print("Generating narrator script...")
89
+ model = genai.GenerativeModel(model_name="models/gemini-1.5-pro-latest")
90
  response = model.generate_content([GEMINI_PROMPT, video_file])
91
  genai.delete_file(video_file.name)
92
  if hasattr(response, 'text') and response.text:
 
94
  raise Exception("No valid script was generated by Gemini.")
95
 
96
  def generate_single_audio_track(dialogue_text, voice_name, is_cheerful, output_path):
97
+ """Generates one audio track for the entire script via TTS API."""
98
  print(f"Requesting audio from TTS API (Voice: {voice_name}, Cheerful: {is_cheerful})")
99
  payload = {"text": dialogue_text, "voice_name": voice_name, "cheerful": is_cheerful}
100
  response = requests.post(TTS_API_URL, json=payload, timeout=300)
 
105
  raise Exception(f"Error from TTS API: {response.status_code} - {response.text}")
106
 
107
  def replace_video_audio(video_path, new_audio_path, output_path):
108
+ """Replaces the audio of a video using the modern, correct MoviePy method."""
109
  print("Replacing video audio using MoviePy...")
110
+ video_clip, audio_clip = None, None
 
111
  try:
112
  video_clip = VideoFileClip(video_path)
113
  audio_clip = AudioFileClip(new_audio_path)
 
127
  @app.route('/process', methods=['POST'])
128
  def process_video():
129
  """Handles video input (upload or URL), processing, and renders the result."""
130
+ input_video_path, temp_audio_path = None, None
 
 
131
  try:
 
132
  youtube_url = request.form.get('youtube_url', '').strip()
 
133
  if youtube_url:
134
  input_video_path = download_youtube_video(youtube_url, app.config['UPLOAD_FOLDER'])
 
135
  filename = os.path.basename(input_video_path)
136
  elif 'video' in request.files and request.files['video'].filename != '':
137
  file = request.files['video']
 
142
  flash("Please either upload a video file or provide a YouTube URL.")
143
  return render_template('index.html')
144
 
 
145
  voice_choice = request.form['voice_choice']
146
  is_cheerful = 'cheerful' in request.form
147
  voice_name = VOICE_CHOICES[voice_choice]
 
148
  script = generate_tamil_script(input_video_path)
149
 
150
  with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_audio:
151
  temp_audio_path = temp_audio.name
152
 
153
  generate_single_audio_track(script, voice_name, is_cheerful, temp_audio_path)
 
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(input_video_path, temp_audio_path, final_video_path)
157
 
158
+ flash("Video processing complete!", "success")
159
  return render_template('index.html',
160
  result_video=url_for('serve_video', filename=final_video_name),
161
  script=script)
 
162
  except Exception as e:
163
  print(f"An error occurred during processing: {e}")
164
+ flash(f"An unexpected error occurred: {e}. Please check the console and try again.", "error")
165
  return render_template('index.html')
166
  finally:
 
167
  if input_video_path and os.path.exists(input_video_path):
168
  os.remove(input_video_path)
169
  if temp_audio_path and os.path.exists(temp_audio_path):
 
176
 
177
  # --- 5. APPLICATION ENTRY POINT ---
178
  if __name__ == '__main__':
179
+