rairo Claude Opus 4.8 commited on
Commit
f5ec933
·
1 Parent(s): 5c4d18d

Trust Gemini timestamps over unreliable ffprobe duration

Browse files

The container duration probe under-reported a ~5min video as 209s
(bad WhatsApp/edited MP4 metadata), causing a valid 230-329s clip to be
skipped. Stop using the probe to skip/clamp or to constrain Gemini's
prompt; trust Gemini's timestamps (it watched the real file) and let
-c copy fail gracefully on genuinely out-of-range segments. Probe is now
diagnostic-only. Raise clip cap to 300s so long steps aren't truncated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Files changed (1) hide show
  1. main.py +11 -30
main.py CHANGED
@@ -183,8 +183,9 @@ def _storage_signed_url(path: str, minutes: int = 120) -> str:
183
  return ""
184
 
185
 
186
- # Maximum length (seconds) of a tutorial clip we will cut. Long answers stay text.
187
- MAX_CLIP_SECONDS = 120
 
188
  # Padding added to the end of a clip so the demonstrated step is not cut off abruptly.
189
  CLIP_TAIL_PAD = 2
190
 
@@ -1242,29 +1243,6 @@ def tutorial_ingest():
1242
  logger.info("[tutorial] STEP 1/5: received '%s' (%.2f MB, mime=%s)",
1243
  video_title, len(video_bytes) / 1024 / 1024, mime_type)
1244
 
1245
- # Probe the real duration up front so we can constrain Gemini's timestamps to it —
1246
- # otherwise the model sometimes invents segments past the end of the video.
1247
- vid_dur_hint = 0.0
1248
- try:
1249
- with tempfile.NamedTemporaryFile(suffix=ext, delete=False) as _pt:
1250
- _pt.write(video_bytes)
1251
- _probe_path = _pt.name
1252
- vid_dur_hint = _video_duration(_probe_path)
1253
- logger.info("[tutorial] probed duration %.1fs, codec %s",
1254
- vid_dur_hint, _video_codec(_probe_path))
1255
- os.remove(_probe_path)
1256
- except Exception as e:
1257
- logger.warning("[tutorial] pre-probe failed: %s", e)
1258
-
1259
- extraction_prompt = TUTORIAL_VIDEO_PROMPT
1260
- if vid_dur_hint and vid_dur_hint > 0:
1261
- extraction_prompt += (
1262
- f"\n\nIMPORTANT: This video is exactly {int(vid_dur_hint)} seconds long. "
1263
- f"Every timestamp_start and timestamp_end MUST be between 0 and {int(vid_dur_hint)}. "
1264
- f"Do not output any timestamp greater than {int(vid_dur_hint)}, and only describe "
1265
- f"steps that actually occur within the video's length."
1266
- )
1267
-
1268
  # Upload to Gemini Files API and wait for processing
1269
  logger.info("[tutorial] STEP 2/5: uploading to Gemini Files API + waiting for ACTIVE...")
1270
  gemini_file = _upload_video_to_gemini(video_bytes, mime_type, display_name=video_title)
@@ -1280,7 +1258,7 @@ def tutorial_ingest():
1280
  t_extract = time.time()
1281
  resp = _gemini_client.models.generate_content(
1282
  model=GEMINI_MODEL,
1283
- contents=[gemini_file, extraction_prompt],
1284
  config=genai_types.GenerateContentConfig(
1285
  response_mime_type="application/json",
1286
  http_options=genai_types.HttpOptions(timeout=GEMINI_VIDEO_TIMEOUT_MS),
@@ -1338,9 +1316,12 @@ def tutorial_ingest():
1338
  with tempfile.NamedTemporaryFile(suffix=ext, delete=False) as src_tmp:
1339
  src_tmp.write(video_bytes)
1340
  src_path = src_tmp.name
1341
- vid_dur = _video_duration(src_path)
1342
- logger.info("[tutorial] wrote source video to temp: %s (duration %.1fs, codec %s)",
1343
- src_path, vid_dur, _video_codec(src_path))
 
 
 
1344
  for idx, a in enumerate(articles, 1):
1345
  start = a.get("timestamp_start", 0)
1346
  end = a.get("timestamp_end", 0)
@@ -1351,7 +1332,7 @@ def tutorial_ingest():
1351
  out_path = os.path.join(tempfile.gettempdir(), f"clip_{uuid.uuid4().hex}.mp4")
1352
  clip_path = f"kb_clips/{uuid.uuid4().hex}.mp4"
1353
  logger.info("[tutorial] clip %d/%d: cropping %ss–%ss...", idx, len(articles), start, end)
1354
- if _crop_video_segment(src_path, start, end, out_path, video_duration=vid_dur):
1355
  try:
1356
  clip_bytes = os.path.getsize(out_path)
1357
  logger.info("[tutorial] clip %d/%d: uploading %d bytes to %s...",
 
183
  return ""
184
 
185
 
186
+ # Maximum length (seconds) of a tutorial clip we will cut. Generous so full steps
187
+ # aren't truncated; stream-copied clips stay well under Twilio's ~16MB media limit.
188
+ MAX_CLIP_SECONDS = 300
189
  # Padding added to the end of a clip so the demonstrated step is not cut off abruptly.
190
  CLIP_TAIL_PAD = 2
191
 
 
1243
  logger.info("[tutorial] STEP 1/5: received '%s' (%.2f MB, mime=%s)",
1244
  video_title, len(video_bytes) / 1024 / 1024, mime_type)
1245
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1246
  # Upload to Gemini Files API and wait for processing
1247
  logger.info("[tutorial] STEP 2/5: uploading to Gemini Files API + waiting for ACTIVE...")
1248
  gemini_file = _upload_video_to_gemini(video_bytes, mime_type, display_name=video_title)
 
1258
  t_extract = time.time()
1259
  resp = _gemini_client.models.generate_content(
1260
  model=GEMINI_MODEL,
1261
+ contents=[gemini_file, TUTORIAL_VIDEO_PROMPT],
1262
  config=genai_types.GenerateContentConfig(
1263
  response_mime_type="application/json",
1264
  http_options=genai_types.HttpOptions(timeout=GEMINI_VIDEO_TIMEOUT_MS),
 
1316
  with tempfile.NamedTemporaryFile(suffix=ext, delete=False) as src_tmp:
1317
  src_tmp.write(video_bytes)
1318
  src_path = src_tmp.name
1319
+ # Probe is logged for diagnostics only — container metadata is sometimes
1320
+ # wrong (esp. WhatsApp/edited MP4s), so we do NOT use it to skip segments.
1321
+ # We trust Gemini's timestamps (it watched the real file) and let ffmpeg
1322
+ # copy fail gracefully if a segment is genuinely past EOF.
1323
+ logger.info("[tutorial] wrote source video to temp: %s (reported duration %.1fs, codec %s)",
1324
+ src_path, _video_duration(src_path), _video_codec(src_path))
1325
  for idx, a in enumerate(articles, 1):
1326
  start = a.get("timestamp_start", 0)
1327
  end = a.get("timestamp_end", 0)
 
1332
  out_path = os.path.join(tempfile.gettempdir(), f"clip_{uuid.uuid4().hex}.mp4")
1333
  clip_path = f"kb_clips/{uuid.uuid4().hex}.mp4"
1334
  logger.info("[tutorial] clip %d/%d: cropping %ss–%ss...", idx, len(articles), start, end)
1335
+ if _crop_video_segment(src_path, start, end, out_path):
1336
  try:
1337
  clip_bytes = os.path.getsize(out_path)
1338
  logger.info("[tutorial] clip %d/%d: uploading %d bytes to %s...",