Athspi commited on
Commit
9f4b0ee
·
verified ·
1 Parent(s): 5f57252

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +98 -223
app.py CHANGED
@@ -7,11 +7,14 @@ 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 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,12 +41,6 @@ app.secret_key = os.urandom(24)
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,6 +48,7 @@ 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,131 +56,13 @@ You are an AI scriptwriter. Your task is to watch the provided video and transcr
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,33 +70,29 @@ def get_video_duration(video_path):
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,26 +101,17 @@ def generate_tamil_script(video_path):
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,19 +120,25 @@ def generate_audio_track(text, voice, cheerful, output_path):
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,10 +147,9 @@ def replace_video_audio(video_path, audio_path, output_path):
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,123 +159,126 @@ def replace_video_audio(video_path, audio_path, output_path):
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)
 
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.editor import concatenate_videoclips, 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
 
42
  # Processing status tracking
43
  processing_status = {}
 
 
 
 
 
 
44
 
45
  # Voice options
46
  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
  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
  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
  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
  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
  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
  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)