Athspi commited on
Commit
a2554b6
·
verified ·
1 Parent(s): bc4ecb1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +116 -155
app.py CHANGED
@@ -1,190 +1,151 @@
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
8
- from moviepy.video.io.VideoFileClip import VideoFileClip
9
- from moviepy.audio.io.AudioFileClip import AudioFileClip
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 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)
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. APPLICATION CONFIGURATION ---
41
  VOICE_CHOICES = {
42
- "Male (Charon)": "Charon",
43
- "Female (Zephyr)": "Zephyr"
44
  }
45
 
46
  GEMINI_PROMPT = """
47
- 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.
48
 
49
  **CRITICAL INSTRUCTIONS:**
50
- 1. **Single Script:** Combine all dialogue 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-2.5-flash")
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):
181
- os.remove(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 ---
189
- if __name__ == '__main__':
190
- app.run(host="0.0.0.0", port=7860)
 
1
  import os
 
2
  import tempfile
3
  import uuid
4
+ from fastapi import FastAPI, UploadFile, File, Form, HTTPException
5
+ from fastapi.responses import FileResponse
6
+ from fastapi.staticfiles import StaticFiles
7
+ from moviepy.editor import VideoFileClip, AudioFileClip
8
  import google.generativeai as genai
9
  import requests
 
 
 
 
10
  from dotenv import load_dotenv
11
+ from pathlib import Path
12
 
13
+ # Load environment variables
14
  load_dotenv()
 
15
 
16
+ app = FastAPI()
 
 
17
 
18
+ # Configure directories
19
+ UPLOAD_DIR = "uploads"
20
+ DOWNLOAD_DIR = "downloads"
21
+ Path(UPLOAD_DIR).mkdir(exist_ok=True)
22
+ Path(DOWNLOAD_DIR).mkdir(exist_ok=True)
23
 
24
+ # Mount static files
25
+ app.mount("/downloads", StaticFiles(directory="downloads"), name="downloads")
26
 
27
+ # Configuration
28
+ GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
29
+ TTS_API_URL = os.getenv("TTS_API_URL")
30
+ genai.configure(api_key=GEMINI_API_KEY)
 
 
 
 
 
31
 
 
32
  VOICE_CHOICES = {
33
+ "male": "Charon",
34
+ "female": "Zephyr"
35
  }
36
 
37
  GEMINI_PROMPT = """
38
+ 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.
39
 
40
  **CRITICAL INSTRUCTIONS:**
41
+ 1. Combine all dialogue into one continuous script.
42
+ 2. NO timestamps or speaker labels.
43
+ 3. Add performance cues (e.g., [laugh], [sigh]) and directions (e.g., "Say happily:").
 
 
 
44
  """
45
 
46
+ @app.post("/process")
47
+ async def process_video(
48
+ file: UploadFile = File(...),
49
+ voice: str = Form("male"),
50
+ cheerful: bool = Form(False)
51
+ ):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  try:
53
+ # Save uploaded file
54
+ file_ext = Path(file.filename).suffix
55
+ file_name = f"{uuid.uuid4()}{file_ext}"
56
+ file_path = os.path.join(UPLOAD_DIR, file_name)
57
+
58
+ with open(file_path, "wb") as buffer:
59
+ buffer.write(await file.read())
 
 
 
 
 
 
 
 
 
60
 
61
+ # Generate script using Gemini
62
+ script = await generate_script(file_path)
63
+
64
+ # Generate audio
65
+ audio_path = os.path.join(UPLOAD_DIR, f"audio_{uuid.uuid4()}.wav")
66
+ await generate_audio(script, voice, cheerful, audio_path)
67
+
68
+ # Create dubbed video
69
+ output_name = f"dubbed_{file_name}"
70
+ output_path = os.path.join(DOWNLOAD_DIR, output_name)
71
+ await create_dubbed_video(file_path, audio_path, output_path)
72
+
73
+ # Cleanup
74
+ os.remove(file_path)
75
+ os.remove(audio_path)
76
+
77
+ return {
78
+ "video_url": f"/downloads/{output_name}",
79
+ "script": script
80
+ }
81
+
82
+ except Exception as e:
83
+ raise HTTPException(status_code=500, detail=str(e))
84
 
85
+ async def generate_script(video_path: str) -> str:
 
 
 
 
 
86
  try:
87
+ video_file = genai.upload_file(video_path, mime_type="video/mp4")
 
 
 
88
 
89
+ while video_file.state.name == "PROCESSING":
90
+ video_file = genai.get_file(video_file.name)
 
 
 
91
 
92
+ if video_file.state.name != "ACTIVE":
93
+ raise Exception("Gemini processing failed")
 
94
 
95
+ model = genai.GenerativeModel("models/gemini-1.5-pro-latest")
96
+ response = model.generate_content([GEMINI_PROMPT, video_file])
97
+ genai.delete_file(video_file.name)
98
 
99
+ if hasattr(response, 'text'):
100
+ return " ".join(response.text.strip().splitlines())
101
+ raise Exception("No script generated")
102
+ except Exception as e:
103
+ raise Exception(f"Script generation failed: {str(e)}")
104
+
105
+ async def generate_audio(text: str, voice: str, cheerful: bool, output_path: str):
106
+ try:
107
+ voice_name = VOICE_CHOICES.get(voice, "Charon")
108
+ payload = {
109
+ "text": text,
110
+ "voice_name": voice_name,
111
+ "cheerful": cheerful
112
+ }
113
 
114
+ response = requests.post(TTS_API_URL, json=payload, timeout=300)
115
+ if response.status_code != 200:
116
+ raise Exception(f"TTS API error: {response.text}")
117
+
118
+ with open(output_path, "wb") as f:
119
+ f.write(response.content)
120
+ except Exception as e:
121
+ raise Exception(f"Audio generation failed: {str(e)}")
122
+
123
+ async def create_dubbed_video(video_path: str, audio_path: str, output_path: str):
124
+ try:
125
+ video = VideoFileClip(video_path)
126
+ audio = AudioFileClip(audio_path)
127
 
128
+ # Ensure audio matches video duration
129
+ if audio.duration > video.duration:
130
+ audio = audio.subclip(0, video.duration)
 
131
 
132
+ video = video.set_audio(audio)
133
+ video.write_videofile(
134
+ output_path,
135
+ codec="libx264",
136
+ audio_codec="aac",
137
+ threads=4,
138
+ preset="fast"
139
  )
140
 
141
+ video.close()
142
+ audio.close()
143
  except Exception as e:
144
+ raise Exception(f"Video processing failed: {str(e)}")
145
+
146
+ @app.get("/downloads/{file_name}")
147
+ async def download_file(file_name: str):
148
+ file_path = os.path.join(DOWNLOAD_DIR, file_name)
149
+ if not os.path.exists(file_path):
150
+ raise HTTPException(status_code=404, detail="File not found")
151
+ return FileResponse(file_path)