bigbossmonster commited on
Commit
88a9829
·
verified ·
1 Parent(s): 520c62d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +163 -1
app.py CHANGED
@@ -231,4 +231,166 @@ def process_batch_gemini(api_key, items, model_name):
231
  model=model_name,
232
  contents=prompt_parts,
233
  config=types.GenerateContentConfig(
234
- response_mime_type="application/json"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  model=model_name,
232
  contents=prompt_parts,
233
  config=types.GenerateContentConfig(
234
+ response_mime_type="application/json"
235
+ )
236
+ )
237
+
238
+ text = response.text.replace("```json", "").replace("```", "").strip()
239
+
240
+ try:
241
+ return json.loads(text)
242
+ except json.JSONDecodeError as e:
243
+ # Handle Truncated JSON (Output Token Limit Exceeded)
244
+ logger.warning(f"JSON Parse Error (likely truncated response): {e}. Attempting repair...")
245
+
246
+ # Repair Strategy: Find the last closing brace '}', discard everything after, and close the array ']'
247
+ last_object_idx = text.rfind("}")
248
+ if last_object_idx != -1:
249
+ repaired_text = text[:last_object_idx+1] + "]"
250
+ try:
251
+ repaired_data = json.loads(repaired_text)
252
+ logger.info(f"Successfully repaired JSON. Recovered {len(repaired_data)}/{len(items)} items.")
253
+ return repaired_data
254
+ except json.JSONDecodeError:
255
+ logger.error("JSON repair failed.")
256
+
257
+ return None # Fail gracefully if repair is impossible
258
+
259
+ except Exception as e:
260
+ logger.error(f"Gemini API Error with key ...{api_key[-4:]}: {e}")
261
+ return None
262
+
263
+ # --- Main Endpoint ---
264
+
265
+ @app.post("/api/analyze")
266
+ async def analyze_subtitles(
267
+ srt_file: UploadFile = File(...),
268
+ media_files: list[UploadFile] = File(...),
269
+ api_keys: str = Form(...),
270
+ batch_size: int = Form(20),
271
+ model_name: str = Form("gemini-2.0-flash"), # Updated default model hint
272
+ compression_quality: float = Form(0.7)
273
+ ):
274
+ temp_dir = tempfile.mkdtemp()
275
+ try:
276
+ # Convert float quality (0.1-1.0) to integer (10-100) for PIL
277
+ pil_quality = max(10, min(100, int(compression_quality * 100)))
278
+
279
+ # 1. Read SRT
280
+ srt_content = (await srt_file.read()).decode('utf-8', errors='ignore')
281
+ srt_data = parse_srt(srt_content)
282
+ srt_data.sort(key=lambda x: x['startTimeMs'])
283
+
284
+ # 2. Process Media
285
+ images = []
286
+ for file in media_files:
287
+ file_path = os.path.join(temp_dir, file.filename)
288
+ with open(file_path, "wb") as f:
289
+ shutil.copyfileobj(file.file, f)
290
+
291
+ if file.filename.lower().endswith('.rar'):
292
+ try:
293
+ with rarfile.RarFile(file_path) as rf:
294
+ rf.extractall(temp_dir)
295
+ except rarfile.RarCannotExec:
296
+ raise HTTPException(status_code=500, detail="Unrar executable not found in container.")
297
+ elif file.filename.lower().endswith('.zip'):
298
+ with zipfile.ZipFile(file_path, 'r') as zf:
299
+ zf.extractall(temp_dir)
300
+
301
+ for root, _, files in os.walk(temp_dir):
302
+ for filename in files:
303
+ if filename.lower().endswith(('.jpg', '.jpeg', '.png', '.webp', '.bmp')):
304
+ full_path = os.path.join(root, filename)
305
+ ms = parse_filename_to_ms(filename)
306
+ if ms is not None:
307
+ with open(full_path, "rb") as f:
308
+ raw_bytes = f.read()
309
+ compressed = compress_image(raw_bytes, quality=pil_quality)
310
+ if compressed:
311
+ images.append({
312
+ "filename": filename,
313
+ "timeMs": ms,
314
+ "data": compressed
315
+ })
316
+
317
+ images.sort(key=lambda x: x['timeMs'])
318
+
319
+ # 3. Pair
320
+ pairs = []
321
+ for i in range(len(images)):
322
+ img = images[i]
323
+ srt = srt_data[i] if i < len(srt_data) else None
324
+
325
+ if srt:
326
+ # Create Thumbnail (lower quality for UI speed)
327
+ thumb_bytes = compress_image(img['data'], quality=50, max_width=300)
328
+ thumb_b64 = base64.b64encode(thumb_bytes).decode('utf-8')
329
+
330
+ pairs.append({
331
+ "index": i,
332
+ "image_data": img['data'],
333
+ "expected_text": srt['text'],
334
+ "srt_id": srt['id'],
335
+ "srt_time": srt['time'],
336
+ "filename": img['filename'],
337
+ "thumb": f"data:image/jpeg;base64,{thumb_b64}",
338
+ "status": "pending"
339
+ })
340
+
341
+ if not pairs:
342
+ return {"status": "error", "message": "No valid image/subtitle pairs found."}
343
+
344
+ # 4. Process Gemini
345
+ keys = [k.strip() for k in api_keys.split('\n') if k.strip()]
346
+ if not keys:
347
+ raise HTTPException(status_code=400, detail="No API Keys provided")
348
+
349
+ results_map = {}
350
+ batches = [pairs[i:i + batch_size] for i in range(0, len(pairs), batch_size)]
351
+
352
+ def worker(batch_idx, batch):
353
+ key = keys[batch_idx % len(keys)]
354
+ return process_batch_gemini(key, batch, model_name)
355
+
356
+ with ThreadPoolExecutor(max_workers=len(keys)) as executor:
357
+ futures = [executor.submit(worker, i, b) for i, b in enumerate(batches)]
358
+ for future in futures:
359
+ res = future.result()
360
+ if res:
361
+ for item in res:
362
+ results_map[item['index']] = item
363
+
364
+ # 5. Build Output
365
+ final_output = []
366
+ for p in pairs:
367
+ analysis = results_map.get(p['index'])
368
+ status = "pending"
369
+ reason = ""
370
+ detected = ""
371
+ if analysis:
372
+ status = "match" if analysis['match'] else "mismatch"
373
+ reason = analysis.get('reason', '')
374
+ detected = analysis.get('detected_text', '')
375
+
376
+ final_output.append({
377
+ "id": p['index'],
378
+ "filename": p['filename'],
379
+ "thumb": p['thumb'],
380
+ "expected": p['expected_text'],
381
+ "detected": detected,
382
+ "status": status,
383
+ "reason": reason,
384
+ "srt_id": p['srt_id'],
385
+ "srt_time": p['srt_time']
386
+ })
387
+
388
+ return {"status": "success", "results": final_output}
389
+
390
+ except Exception as e:
391
+ logger.error(f"Server Error: {e}")
392
+ raise HTTPException(status_code=500, detail=str(e))
393
+ finally:
394
+ shutil.rmtree(temp_dir)
395
+
396
+ app.mount("/", StaticFiles(directory="static", html=True), name="static")