bigbossmonster commited on
Commit
2fdba39
·
verified ·
1 Parent(s): 8d4bbca

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +44 -111
app.py CHANGED
@@ -9,15 +9,14 @@ import base64
9
  from concurrent.futures import ThreadPoolExecutor
10
  from PIL import Image, ImageOps
11
 
 
12
  from fastapi import FastAPI, UploadFile, File, Form, HTTPException
13
  from fastapi.staticfiles import StaticFiles
14
  from fastapi.middleware.cors import CORSMiddleware
 
15
  import rarfile
16
  import zipfile
17
-
18
- # --- MIGRATION: New SDK Imports ---
19
- from google import genai
20
- from google.genai import types
21
 
22
  # Configure logging
23
  logging.basicConfig(level=logging.INFO)
@@ -52,112 +51,49 @@ def parse_filename_to_ms(filename):
52
  return (h * 3600000) + (m * 60000) + (s * 1000) + ms
53
 
54
  def parse_srt(content: str):
55
- """
56
- Robust State Machine Parser for SRT (Backend Version).
57
- Matches Frontend logic to handle blank subtitles correctly.
58
- """
59
  content = content.replace('\r\n', '\n').replace('\r', '\n')
60
- lines = content.split('\n')
 
61
 
62
  parsed = []
63
- current_item = {"id": None, "time": None, "text_lines": []}
64
- state = 'SEARCH_ID' # States: SEARCH_ID, SEARCH_TIME, READ_TEXT
65
-
66
- for i, line in enumerate(lines):
67
- line = line.strip()
68
- next_line = lines[i+1].strip() if i + 1 < len(lines) else None
69
-
70
- if state == 'SEARCH_ID':
71
- if line.isdigit():
72
- current_item["id"] = line
73
- state = 'SEARCH_TIME'
74
-
75
- elif state == 'SEARCH_TIME':
76
- if '-->' in line:
77
- current_item["time"] = line
78
- state = 'READ_TEXT'
79
-
80
- # EDGE CASE: Immediate Blank Subtitle
81
- # If next line is a number (start of new ID), current block is blank
82
- if next_line and next_line.isdigit():
83
- # Check 2 lines ahead to confirm it's really an ID (followed by timestamp)
84
- line_after_next = lines[i+2].strip() if i + 2 < len(lines) else None
85
- if line_after_next and '-->' in line_after_next:
86
- # Close current blank block
87
- start_ms = parse_srt_time_to_ms(current_item["time"].split('-->')[0].strip())
88
- parsed.append({
89
- "id": current_item["id"],
90
- "time": current_item["time"],
91
- "startTimeMs": start_ms,
92
- "text": ""
93
- })
94
- current_item = {"id": None, "time": None, "text_lines": []}
95
- state = 'SEARCH_ID'
96
- elif line.isdigit():
97
- # Recover from missing timestamp
98
- current_item["id"] = line
99
 
100
- elif state == 'READ_TEXT':
101
- # Check for start of new block (ID line followed by Time line)
102
- is_new_block_start = (
103
- line.isdigit() and
104
- next_line and '-->' in next_line
105
- )
106
 
107
- # Check for standard blank line separator
108
- is_blank_separator = (
109
- line == '' and
110
- next_line and next_line.isdigit() and
111
- (i + 2 < len(lines) and '-->' in lines[i+2])
112
- )
113
-
114
- if is_new_block_start:
115
- # Missing blank separator, force close
116
- text = "\n".join(current_item["text_lines"]).strip() or ""
117
- start_ms = parse_srt_time_to_ms(current_item["time"].split('-->')[0].strip())
118
- parsed.append({
119
- "id": current_item["id"],
120
- "time": current_item["time"],
121
- "startTimeMs": start_ms,
122
- "text": text
123
- })
124
- current_item = {"id": line, "time": None, "text_lines": []}
125
- state = 'SEARCH_TIME'
126
 
127
- elif is_blank_separator:
128
- # Standard closure
129
- text = " ".join(current_item["text_lines"]).strip() or "[BLANK SUBTITLE]"
130
- start_ms = parse_srt_time_to_ms(current_item["time"].split('-->')[0].strip())
131
- parsed.append({
132
- "id": current_item["id"],
133
- "time": current_item["time"],
134
- "startTimeMs": start_ms,
135
- "text": text
136
- })
137
- current_item = {"id": None, "time": None, "text_lines": []}
138
- state = 'SEARCH_ID'
139
 
140
- else:
141
- if line:
142
- current_item["text_lines"].append(line)
143
-
144
- # Push last item
145
- if current_item["id"] and current_item["time"]:
146
- text = " ".join(current_item["text_lines"]).strip() or "[BLANK SUBTITLE]"
147
- start_ms = parse_srt_time_to_ms(current_item["time"].split('-->')[0].strip())
148
- parsed.append({
149
- "id": current_item["id"],
150
- "time": current_item["time"],
151
- "startTimeMs": start_ms,
152
- "text": text
153
- })
154
-
155
  return parsed
156
 
157
 
 
 
158
  def compress_image(image_bytes, max_width=800, quality=80):
159
  """
160
  Compresses an image to WebP (best) or optimized JPEG.
 
161
  """
162
  try:
163
  img = Image.open(io.BytesIO(image_bytes))
@@ -168,6 +104,7 @@ def compress_image(image_bytes, max_width=800, quality=80):
168
  buffer = io.BytesIO()
169
 
170
  # 2. Try WebP first (Best quality/size ratio)
 
171
  use_webp = True
172
 
173
  if use_webp:
@@ -199,14 +136,14 @@ def compress_image(image_bytes, max_width=800, quality=80):
199
 
200
  except Exception as e:
201
  logger.error(f"Image compression failed: {e}")
 
 
202
  return None
203
 
204
- # --- MIGRATION: Updated Gemini Processing Function ---
205
  def process_batch_gemini(api_key, items, model_name):
206
  try:
207
- # 1. Instantiate the Client (New SDK pattern)
208
- # This replaces genai.configure()
209
- client = genai.Client(api_key=api_key)
210
 
211
  prompt_parts = [
212
  "You are a Subtitle Quality Control (QC) bot.",
@@ -221,18 +158,13 @@ def process_batch_gemini(api_key, items, model_name):
221
  prompt_parts.append(f"Index: {item['index']}")
222
  prompt_parts.append(f"Expected Text: \"{item['expected_text']}\"")
223
  prompt_parts.append(f"Image:")
224
-
225
- # The new SDK handles PIL images directly in the contents list just like the old one
226
  img = Image.open(io.BytesIO(item['image_data']))
227
  prompt_parts.append(img)
228
 
229
- # 2. Call generate_content via the client
230
- response = client.models.generate_content(
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()
@@ -241,6 +173,7 @@ def process_batch_gemini(api_key, items, model_name):
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 ']'
@@ -268,7 +201,7 @@ async def analyze_subtitles(
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()
 
9
  from concurrent.futures import ThreadPoolExecutor
10
  from PIL import Image, ImageOps
11
 
12
+
13
  from fastapi import FastAPI, UploadFile, File, Form, HTTPException
14
  from fastapi.staticfiles import StaticFiles
15
  from fastapi.middleware.cors import CORSMiddleware
16
+ from PIL import Image
17
  import rarfile
18
  import zipfile
19
+ import google.generativeai as genai
 
 
 
20
 
21
  # Configure logging
22
  logging.basicConfig(level=logging.INFO)
 
51
  return (h * 3600000) + (m * 60000) + (s * 1000) + ms
52
 
53
  def parse_srt(content: str):
54
+ # Normalize line endings
 
 
 
55
  content = content.replace('\r\n', '\n').replace('\r', '\n')
56
+ # Split by double newline (standard SRT block separator)
57
+ blocks = re.split(r'\n\n+', content.strip())
58
 
59
  parsed = []
60
+ for block in blocks:
61
+ lines = [l.strip() for l in block.split('\n') if l.strip()]
62
+ if len(lines) < 2:
63
+ continue
64
+
65
+ srt_id = lines[0]
66
+ time_range = lines[1]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
+ # Check if there is actually text after the timestamp
69
+ # If there are lines after index 1, join them; otherwise, it's a blank sub
70
+ if len(lines) > 2:
71
+ text = "\n".join(lines[2:])
72
+ else:
73
+ text = "" # Explicitly blank
74
 
75
+ try:
76
+ start_time_str = time_range.split('-->')[0].strip()
77
+ start_ms = parse_srt_time_to_ms(start_time_str)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
+ parsed.append({
80
+ "id": srt_id,
81
+ "time": time_range,
82
+ "startTimeMs": start_ms,
83
+ "text": text
84
+ })
85
+ except Exception as e:
86
+ logger.warning(f"Skipping malformed SRT block: {block[:50]}... Error: {e}")
 
 
 
 
87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  return parsed
89
 
90
 
91
+ logger = logging.getLogger(__name__)
92
+
93
  def compress_image(image_bytes, max_width=800, quality=80):
94
  """
95
  Compresses an image to WebP (best) or optimized JPEG.
96
+ Renamed back to 'compress_image' to fix your error.
97
  """
98
  try:
99
  img = Image.open(io.BytesIO(image_bytes))
 
104
  buffer = io.BytesIO()
105
 
106
  # 2. Try WebP first (Best quality/size ratio)
107
+ # If you strictly need JPEG, change use_webp to False
108
  use_webp = True
109
 
110
  if use_webp:
 
136
 
137
  except Exception as e:
138
  logger.error(f"Image compression failed: {e}")
139
+ # If logging isn't setup, print the error so you can see it
140
+ print(f"Error: {e}")
141
  return None
142
 
 
143
  def process_batch_gemini(api_key, items, model_name):
144
  try:
145
+ genai.configure(api_key=api_key)
146
+ model = genai.GenerativeModel(model_name)
 
147
 
148
  prompt_parts = [
149
  "You are a Subtitle Quality Control (QC) bot.",
 
158
  prompt_parts.append(f"Index: {item['index']}")
159
  prompt_parts.append(f"Expected Text: \"{item['expected_text']}\"")
160
  prompt_parts.append(f"Image:")
 
 
161
  img = Image.open(io.BytesIO(item['image_data']))
162
  prompt_parts.append(img)
163
 
164
+ # Enforce JSON mode
165
+ response = model.generate_content(
166
+ prompt_parts,
167
+ generation_config={"response_mime_type": "application/json"}
 
 
 
168
  )
169
 
170
  text = response.text.replace("```json", "").replace("```", "").strip()
 
173
  return json.loads(text)
174
  except json.JSONDecodeError as e:
175
  # Handle Truncated JSON (Output Token Limit Exceeded)
176
+ # This happens if the batch size is too large for the model's output window
177
  logger.warning(f"JSON Parse Error (likely truncated response): {e}. Attempting repair...")
178
 
179
  # Repair Strategy: Find the last closing brace '}', discard everything after, and close the array ']'
 
201
  media_files: list[UploadFile] = File(...),
202
  api_keys: str = Form(...),
203
  batch_size: int = Form(20),
204
+ model_name: str = Form("gemini-3-flash-preview"),
205
  compression_quality: float = Form(0.7)
206
  ):
207
  temp_dir = tempfile.mkdtemp()