Athspi commited on
Commit
2064a8b
·
verified ·
1 Parent(s): 0d61db6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +223 -98
app.py CHANGED
@@ -7,14 +7,11 @@ import requests
7
  from flask import Flask, request, render_template, send_from_directory, url_for, flash, jsonify
8
  from moviepy.video.io.VideoFileClip import VideoFileClip
9
  from moviepy.audio.io.AudioFileClip import AudioFileClip
10
- from moviepy.video.VideoClip import ImageClip
11
  from werkzeug.utils import secure_filename
12
  from dotenv import load_dotenv
13
  import threading
14
  from datetime import datetime, timedelta
15
  import logging
16
- import schedule
17
- import time as t
18
 
19
  # Initialize Flask app and load secrets
20
  load_dotenv()
@@ -41,6 +38,12 @@ app.secret_key = os.urandom(24)
41
 
42
  # Processing status tracking
43
  processing_status = {}
 
 
 
 
 
 
44
 
45
  # Voice options
46
  VOICE_CHOICES = {
@@ -48,7 +51,6 @@ VOICE_CHOICES = {
48
  "Female (Zephyr)": "Zephyr"
49
  }
50
 
51
- # Gemini Prompt
52
  GEMINI_PROMPT = """
53
  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.
54
 
@@ -56,13 +58,131 @@ You are an AI scriptwriter. Your task is to watch the provided video and transcr
56
  1. **Single Script:** Combine all dialogue into one continuous script.
57
  2. **NO Timestamps or Speaker Labels:** Do NOT include any timestamps or speaker identifiers.
58
  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.
 
 
 
59
  """
60
 
61
  # Configure logging
62
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
63
  logger = logging.getLogger(__name__)
64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  def get_video_duration(video_path):
 
66
  try:
67
  with VideoFileClip(video_path) as video:
68
  return video.duration
@@ -70,29 +190,33 @@ def get_video_duration(video_path):
70
  return 0
71
 
72
  def generate_tamil_script(video_path):
 
73
  max_retries = 3
74
- retry_delay = 10
 
75
  for attempt in range(max_retries):
76
  try:
77
  video_file = genai.upload_file(video_path, mime_type="video/mp4")
 
 
78
  start_wait = time.time()
79
  while video_file.state.name == "PROCESSING":
80
- if time.time() - start_wait > 300:
81
  raise TimeoutError("Gemini processing timed out")
82
  time.sleep(5)
83
  video_file = genai.get_file(video_file.name)
84
-
85
  if video_file.state.name != "ACTIVE":
86
  raise Exception(f"Gemini processing failed: {video_file.state.name}")
87
-
88
  model = genai.GenerativeModel(model_name="models/gemini-2.5-flash")
89
  response = model.generate_content([GEMINI_PROMPT, video_file])
90
  genai.delete_file(video_file.name)
91
-
92
  if hasattr(response, 'text') and response.text:
93
  return " ".join(response.text.strip().splitlines())
94
  raise Exception("No valid script generated")
95
-
96
  except Exception as e:
97
  if attempt < max_retries - 1:
98
  logger.warning(f"Gemini error (attempt {attempt+1}/{max_retries}): {str(e)}")
@@ -101,17 +225,26 @@ def generate_tamil_script(video_path):
101
  raise
102
 
103
  def generate_audio_track(text, voice, cheerful, output_path):
 
104
  max_retries = 3
105
- retry_delay = 5
 
106
  for attempt in range(max_retries):
107
  try:
108
- payload = {"text": text, "voice_name": voice, "cheerful": cheerful}
 
 
 
 
 
109
  response = requests.post(TTS_API_URL, json=payload, timeout=300)
110
  if response.status_code != 200:
111
  raise Exception(f"TTS API error: {response.status_code} - {response.text}")
 
112
  with open(output_path, "wb") as f:
113
  f.write(response.content)
114
  return
 
115
  except Exception as e:
116
  if attempt < max_retries - 1:
117
  logger.warning(f"TTS error (attempt {attempt+1}/{max_retries}): {str(e)}")
@@ -120,25 +253,19 @@ def generate_audio_track(text, voice, cheerful, output_path):
120
  raise
121
 
122
  def replace_video_audio(video_path, audio_path, output_path):
123
- """Replace video audio track, extending video if audio is longer"""
124
  video = None
125
  audio = None
126
  try:
 
127
  video = VideoFileClip(video_path)
128
  audio = AudioFileClip(audio_path)
129
-
130
- # If audio is longer, extend video with last frame
131
- if audio.duration > video.duration:
132
- freeze_duration = audio.duration - video.duration
133
- last_frame = video.to_ImageClip(t=video.duration - 0.04)
134
- last_frame = last_frame.set_duration(freeze_duration)
135
- final_video = concatenate_videoclips([video, last_frame])
136
- else:
137
- final_video = video
138
-
139
- final_video = final_video.set_audio(audio)
140
-
141
- final_video.write_videofile(
142
  output_path,
143
  codec="libx264",
144
  audio_codec="aac",
@@ -147,9 +274,10 @@ def replace_video_audio(video_path, audio_path, output_path):
147
  preset='medium',
148
  ffmpeg_params=['-crf', '23', '-movflags', '+faststart']
149
  )
150
-
151
  except Exception as e:
152
  logger.error(f"Video processing error: {str(e)}")
 
153
  if os.path.exists(output_path):
154
  os.unlink(output_path)
155
  raise
@@ -159,126 +287,123 @@ def replace_video_audio(video_path, audio_path, output_path):
159
  if audio:
160
  audio.close()
161
 
162
- def process_video_background(task_id, video_path, voice, cheerful):
163
- try:
164
- processing_status[task_id] = {
165
- 'status': 'processing',
166
- 'progress': 0,
167
- 'message': 'Starting transcription',
168
- 'start_time': time.time(),
169
- 'video_duration': get_video_duration(video_path)
170
- }
171
-
172
- script = generate_tamil_script(video_path)
173
- processing_status[task_id]['progress'] = 25
174
- processing_status[task_id]['script'] = script
175
-
176
- with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_audio:
177
- audio_path = temp_audio.name
178
-
179
- generate_audio_track(script, voice, cheerful, audio_path)
180
- processing_status[task_id]['progress'] = 50
181
-
182
- final_filename = f"dubbed_{task_id}.mp4"
183
- final_path = os.path.join(app.config['DOWNLOAD_FOLDER'], final_filename)
184
-
185
- replace_video_audio(video_path, audio_path, final_path)
186
- processing_status[task_id]['progress'] = 100
187
- processing_status[task_id].update({
188
- 'status': 'complete',
189
- 'result_path': final_path,
190
- 'message': 'Processing complete'
191
- })
192
-
193
- os.unlink(audio_path)
194
-
195
- except Exception as e:
196
- logger.error(f"Task {task_id} failed: {str(e)}")
197
- processing_status[task_id].update({
198
- 'status': 'error',
199
- 'message': str(e)
200
- })
201
- if 'audio_path' in locals() and os.path.exists(audio_path):
202
- os.unlink(audio_path)
203
-
204
  @app.route('/')
205
  def index():
 
206
  return render_template('index.html', voices=VOICE_CHOICES)
207
 
208
  @app.route('/upload', methods=['POST'])
209
  def upload_video():
 
210
  if 'video' not in request.files:
211
  return jsonify({'error': 'No file uploaded'}), 400
 
212
  file = request.files['video']
213
  if file.filename == '':
214
  return jsonify({'error': 'No file selected'}), 400
215
-
 
216
  task_id = str(uuid.uuid4())
217
  filename = secure_filename(f"{task_id}_{file.filename}")
218
  video_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
219
  file.save(video_path)
220
-
 
221
  voice = request.form.get('voice', 'Charon')
222
  cheerful = request.form.get('cheerful', 'false') == 'true'
223
-
 
224
  processing_status[task_id] = {
225
  'status': 'uploaded',
226
  'progress': 0,
227
  'message': 'Starting processing',
 
228
  'start_time': time.time(),
229
  'video_duration': get_video_duration(video_path)
230
  }
231
-
232
- thread = threading.Thread(target=process_video_background, args=(task_id, video_path, voice, cheerful))
 
 
 
233
  thread.start()
234
-
235
- return jsonify({'task_id': task_id, 'video_duration': processing_status[task_id]['video_duration']})
 
 
 
236
 
237
  @app.route('/status/<task_id>')
238
  def get_status(task_id):
 
239
  if task_id not in processing_status:
240
  return jsonify({'error': 'Invalid task ID'}), 404
241
-
242
  status = processing_status[task_id]
 
 
 
 
 
 
 
 
 
243
  response = {
244
  'status': status['status'],
245
  'progress': status.get('progress', 0),
246
- 'message': status.get('message', '')
 
247
  }
 
248
  if status['status'] == 'complete':
249
  response['result_url'] = url_for('download', filename=os.path.basename(status['result_path']))
250
  response['script'] = status.get('script', '')
251
-
252
  return jsonify(response)
253
 
254
  @app.route('/download/<filename>')
255
  def download(filename):
 
256
  return send_from_directory(app.config['DOWNLOAD_FOLDER'], filename)
257
 
258
  @app.route('/cleanup', methods=['POST'])
259
  def cleanup():
 
260
  try:
261
- for folder, age_limit in [(UPLOAD_FOLDER, 3600), (DOWNLOAD_FOLDER, 86400)]:
262
- for filename in os.listdir(folder):
263
- file_path = os.path.join(folder, filename)
264
- if os.path.getmtime(file_path) < time.time() - age_limit:
265
- os.unlink(file_path)
266
- return jsonify({'status': 'success'})
 
 
 
 
 
 
 
267
  except Exception as e:
268
  return jsonify({'status': 'error', 'message': str(e)}), 500
269
 
270
- def cleanup_job():
271
- with app.app_context():
272
- app.test_client().post('/cleanup')
273
-
274
- schedule.every().hour.do(cleanup_job)
275
-
276
- def scheduler_thread():
277
- while True:
278
- schedule.run_pending()
279
- t.sleep(1)
280
-
281
- threading.Thread(target=scheduler_thread, daemon=True).start()
282
-
283
  if __name__ == '__main__':
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
  app.run(host="0.0.0.0", port=7860, threaded=True)
 
7
  from flask import Flask, request, render_template, send_from_directory, url_for, flash, jsonify
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
  import threading
13
  from datetime import datetime, timedelta
14
  import logging
 
 
15
 
16
  # Initialize Flask app and load secrets
17
  load_dotenv()
 
38
 
39
  # Processing status tracking
40
  processing_status = {}
41
+ processing_times = {
42
+ 'upload': 0,
43
+ 'transcription': 0,
44
+ 'tts': 0,
45
+ 'dubbing': 0
46
+ }
47
 
48
  # Voice options
49
  VOICE_CHOICES = {
 
51
  "Female (Zephyr)": "Zephyr"
52
  }
53
 
 
54
  GEMINI_PROMPT = """
55
  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.
56
 
 
58
  1. **Single Script:** Combine all dialogue into one continuous script.
59
  2. **NO Timestamps or Speaker Labels:** Do NOT include any timestamps or speaker identifiers.
60
  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.
61
+
62
+ **EXAMPLE OUTPUT:**
63
+ Say happily: வணக்கம்! [laugh] எப்படி இருக்கீங்க? Whisper mysteriously: அந்த ரகசியம் எனக்கு மட்டும் தான் தெரியும்.
64
  """
65
 
66
  # Configure logging
67
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
68
  logger = logging.getLogger(__name__)
69
 
70
+ def track_processing_time(task_id, stage, duration):
71
+ """Track processing times for each stage"""
72
+ processing_times[stage] = duration
73
+ if task_id in processing_status:
74
+ processing_status[task_id]['timings'][stage] = duration
75
+
76
+ def estimate_remaining_time(task_id):
77
+ """Estimate remaining processing time"""
78
+ if task_id not in processing_status:
79
+ return "Calculating..."
80
+
81
+ status = processing_status[task_id]
82
+ completed_stages = [s for s in status['timings'] if status['timings'][s] is not None]
83
+
84
+ if len(completed_stages) == 0:
85
+ return "Starting soon..."
86
+
87
+ # Weighted average based on stage complexity
88
+ weights = {
89
+ 'transcription': 2.0,
90
+ 'tts': 1.5,
91
+ 'dubbing': 1.0
92
+ }
93
+
94
+ total_weighted_time = 0
95
+ total_weights = 0
96
+
97
+ for stage in completed_stages:
98
+ weight = weights.get(stage, 1.0)
99
+ total_weighted_time += status['timings'][stage] * weight
100
+ total_weights += weight
101
+
102
+ if total_weights == 0:
103
+ return "Estimating..."
104
+
105
+ avg_time = total_weighted_time / total_weights
106
+ remaining_stages = 4 - len(completed_stages)
107
+ return remaining_stages * avg_time
108
+
109
+ def process_video_background(task_id, video_path, voice, cheerful):
110
+ """Background processing function with enhanced logging"""
111
+ try:
112
+ start_time = time.time()
113
+ processing_status[task_id] = {
114
+ 'status': 'processing',
115
+ 'progress': 0,
116
+ 'message': 'Starting transcription',
117
+ 'timings': {'upload': None, 'transcription': None, 'tts': None, 'dubbing': None},
118
+ 'start_time': start_time,
119
+ 'video_duration': get_video_duration(video_path)
120
+ }
121
+
122
+ # Stage 1: Transcription
123
+ processing_status[task_id]['message'] = 'Transcribing video content'
124
+ logger.info(f"Task {task_id}: Starting transcription")
125
+ script_start = time.time()
126
+ script = generate_tamil_script(video_path)
127
+ transcription_time = time.time() - script_start
128
+ track_processing_time(task_id, 'transcription', transcription_time)
129
+ processing_status[task_id]['progress'] = 25
130
+ processing_status[task_id]['script'] = script
131
+ logger.info(f"Task {task_id}: Transcription completed in {transcription_time:.1f}s")
132
+
133
+ # Stage 2: TTS Generation
134
+ processing_status[task_id]['message'] = 'Generating audio narration'
135
+ logger.info(f"Task {task_id}: Starting TTS generation")
136
+ with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as temp_audio:
137
+ audio_path = temp_audio.name
138
+
139
+ tts_start = time.time()
140
+ generate_audio_track(script, voice, cheerful, audio_path)
141
+ tts_time = time.time() - tts_start
142
+ track_processing_time(task_id, 'tts', tts_time)
143
+ processing_status[task_id]['progress'] = 50
144
+ logger.info(f"Task {task_id}: TTS completed in {tts_time:.1f}s")
145
+
146
+ # Stage 3: Dubbing
147
+ processing_status[task_id]['message'] = 'Creating dubbed video'
148
+ logger.info(f"Task {task_id}: Starting dubbing")
149
+ final_filename = f"dubbed_{task_id}.mp4"
150
+ final_path = os.path.join(app.config['DOWNLOAD_FOLDER'], final_filename)
151
+
152
+ dubbing_start = time.time()
153
+ replace_video_audio(video_path, audio_path, final_path)
154
+ dubbing_time = time.time() - dubbing_start
155
+ track_processing_time(task_id, 'dubbing', dubbing_time)
156
+ processing_status[task_id]['progress'] = 75
157
+ logger.info(f"Task {task_id}: Dubbing completed in {dubbing_time:.1f}s")
158
+
159
+ # Cleanup
160
+ os.unlink(audio_path)
161
+
162
+ # Finalize
163
+ processing_status[task_id].update({
164
+ 'status': 'complete',
165
+ 'progress': 100,
166
+ 'message': 'Processing complete',
167
+ 'result_path': final_path,
168
+ 'end_time': time.time()
169
+ })
170
+ logger.info(f"Task {task_id}: Processing completed successfully")
171
+
172
+ except Exception as e:
173
+ logger.error(f"Task {task_id} failed: {str(e)}")
174
+ processing_status[task_id].update({
175
+ 'status': 'error',
176
+ 'message': f'Error: {str(e)}'
177
+ })
178
+ # Cleanup temporary files
179
+ if 'video_path' in locals() and os.path.exists(video_path):
180
+ os.unlink(video_path)
181
+ if 'audio_path' in locals() and os.path.exists(audio_path):
182
+ os.unlink(audio_path)
183
+
184
  def get_video_duration(video_path):
185
+ """Get duration of video in seconds"""
186
  try:
187
  with VideoFileClip(video_path) as video:
188
  return video.duration
 
190
  return 0
191
 
192
  def generate_tamil_script(video_path):
193
+ """Generate Tamil script using Gemini with retry logic"""
194
  max_retries = 3
195
+ retry_delay = 10 # seconds
196
+
197
  for attempt in range(max_retries):
198
  try:
199
  video_file = genai.upload_file(video_path, mime_type="video/mp4")
200
+
201
+ # Wait for file processing with timeout
202
  start_wait = time.time()
203
  while video_file.state.name == "PROCESSING":
204
+ if time.time() - start_wait > 300: # 5 minutes timeout
205
  raise TimeoutError("Gemini processing timed out")
206
  time.sleep(5)
207
  video_file = genai.get_file(video_file.name)
208
+
209
  if video_file.state.name != "ACTIVE":
210
  raise Exception(f"Gemini processing failed: {video_file.state.name}")
211
+
212
  model = genai.GenerativeModel(model_name="models/gemini-2.5-flash")
213
  response = model.generate_content([GEMINI_PROMPT, video_file])
214
  genai.delete_file(video_file.name)
215
+
216
  if hasattr(response, 'text') and response.text:
217
  return " ".join(response.text.strip().splitlines())
218
  raise Exception("No valid script generated")
219
+
220
  except Exception as e:
221
  if attempt < max_retries - 1:
222
  logger.warning(f"Gemini error (attempt {attempt+1}/{max_retries}): {str(e)}")
 
225
  raise
226
 
227
  def generate_audio_track(text, voice, cheerful, output_path):
228
+ """Generate audio using TTS API with retry logic"""
229
  max_retries = 3
230
+ retry_delay = 5 # seconds
231
+
232
  for attempt in range(max_retries):
233
  try:
234
+ payload = {
235
+ "text": text,
236
+ "voice_name": voice,
237
+ "cheerful": cheerful
238
+ }
239
+
240
  response = requests.post(TTS_API_URL, json=payload, timeout=300)
241
  if response.status_code != 200:
242
  raise Exception(f"TTS API error: {response.status_code} - {response.text}")
243
+
244
  with open(output_path, "wb") as f:
245
  f.write(response.content)
246
  return
247
+
248
  except Exception as e:
249
  if attempt < max_retries - 1:
250
  logger.warning(f"TTS error (attempt {attempt+1}/{max_retries}): {str(e)}")
 
253
  raise
254
 
255
  def replace_video_audio(video_path, audio_path, output_path):
256
+ """Replace video audio track with enhanced error handling"""
257
  video = None
258
  audio = None
259
  try:
260
+ # Open video and audio files
261
  video = VideoFileClip(video_path)
262
  audio = AudioFileClip(audio_path)
263
+
264
+ # Set video audio
265
+ video.audio = audio
266
+
267
+ # Write output with optimized settings
268
+ video.write_videofile(
 
 
 
 
 
 
 
269
  output_path,
270
  codec="libx264",
271
  audio_codec="aac",
 
274
  preset='medium',
275
  ffmpeg_params=['-crf', '23', '-movflags', '+faststart']
276
  )
277
+
278
  except Exception as e:
279
  logger.error(f"Video processing error: {str(e)}")
280
+ # Cleanup partially created file
281
  if os.path.exists(output_path):
282
  os.unlink(output_path)
283
  raise
 
287
  if audio:
288
  audio.close()
289
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
  @app.route('/')
291
  def index():
292
+ """Main page"""
293
  return render_template('index.html', voices=VOICE_CHOICES)
294
 
295
  @app.route('/upload', methods=['POST'])
296
  def upload_video():
297
+ """Handle video upload and start processing"""
298
  if 'video' not in request.files:
299
  return jsonify({'error': 'No file uploaded'}), 400
300
+
301
  file = request.files['video']
302
  if file.filename == '':
303
  return jsonify({'error': 'No file selected'}), 400
304
+
305
+ # Generate unique task ID
306
  task_id = str(uuid.uuid4())
307
  filename = secure_filename(f"{task_id}_{file.filename}")
308
  video_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
309
  file.save(video_path)
310
+
311
+ # Get processing options
312
  voice = request.form.get('voice', 'Charon')
313
  cheerful = request.form.get('cheerful', 'false') == 'true'
314
+
315
+ # Start background processing
316
  processing_status[task_id] = {
317
  'status': 'uploaded',
318
  'progress': 0,
319
  'message': 'Starting processing',
320
+ 'timings': {'upload': time.time(), 'transcription': None, 'tts': None, 'dubbing': None},
321
  'start_time': time.time(),
322
  'video_duration': get_video_duration(video_path)
323
  }
324
+
325
+ thread = threading.Thread(
326
+ target=process_video_background,
327
+ args=(task_id, video_path, voice, cheerful)
328
+ )
329
  thread.start()
330
+
331
+ return jsonify({
332
+ 'task_id': task_id,
333
+ 'video_duration': processing_status[task_id]['video_duration']
334
+ })
335
 
336
  @app.route('/status/<task_id>')
337
  def get_status(task_id):
338
+ """Check processing status"""
339
  if task_id not in processing_status:
340
  return jsonify({'error': 'Invalid task ID'}), 404
341
+
342
  status = processing_status[task_id]
343
+
344
+ # Calculate ETA if processing
345
+ eta = None
346
+ if status['status'] == 'processing':
347
+ elapsed = time.time() - status['start_time']
348
+ remaining = estimate_remaining_time(task_id)
349
+ if isinstance(remaining, (int, float)):
350
+ eta = str(timedelta(seconds=int(remaining)))
351
+
352
  response = {
353
  'status': status['status'],
354
  'progress': status.get('progress', 0),
355
+ 'message': status.get('message', ''),
356
+ 'eta': eta
357
  }
358
+
359
  if status['status'] == 'complete':
360
  response['result_url'] = url_for('download', filename=os.path.basename(status['result_path']))
361
  response['script'] = status.get('script', '')
362
+
363
  return jsonify(response)
364
 
365
  @app.route('/download/<filename>')
366
  def download(filename):
367
+ """Serve processed video"""
368
  return send_from_directory(app.config['DOWNLOAD_FOLDER'], filename)
369
 
370
  @app.route('/cleanup', methods=['POST'])
371
  def cleanup():
372
+ """Cleanup old files"""
373
  try:
374
+ # Cleanup uploads older than 1 hour
375
+ for filename in os.listdir(UPLOAD_FOLDER):
376
+ file_path = os.path.join(UPLOAD_FOLDER, filename)
377
+ if os.path.getmtime(file_path) < time.time() - 3600:
378
+ os.unlink(file_path)
379
+
380
+ # Cleanup downloads older than 24 hours
381
+ for filename in os.listdir(DOWNLOAD_FOLDER):
382
+ file_path = os.path.join(DOWNLOAD_FOLDER, filename)
383
+ if os.path.getmtime(file_path) < time.time() - 86400:
384
+ os.unlink(file_path)
385
+
386
+ return jsonify({'status': 'success', 'message': 'Cleanup completed'})
387
  except Exception as e:
388
  return jsonify({'status': 'error', 'message': str(e)}), 500
389
 
 
 
 
 
 
 
 
 
 
 
 
 
 
390
  if __name__ == '__main__':
391
+ # Schedule cleanup thread
392
+ import schedule
393
+ import time as t
394
+ def cleanup_job():
395
+ with app.app_context():
396
+ app.test_client().post('/cleanup')
397
+
398
+ schedule.every().hour.do(cleanup_job)
399
+
400
+ # Start scheduler in background thread
401
+ def scheduler_thread():
402
+ while True:
403
+ schedule.run_pending()
404
+ t.sleep(1)
405
+
406
+ threading.Thread(target=scheduler_thread, daemon=True).start()
407
+
408
+ # Start Flask app
409
  app.run(host="0.0.0.0", port=7860, threaded=True)