multimodalart HF Staff commited on
Commit
91c5e5d
Β·
verified Β·
1 Parent(s): b02f2a2

Upload folder using huggingface_hub

Browse files
.gitattributes CHANGED
@@ -33,3 +33,8 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ examples/37aabc657f3b.mp4 filter=lfs diff=lfs merge=lfs -text
37
+ examples/3e9350958273.mp4 filter=lfs diff=lfs merge=lfs -text
38
+ examples/670e2b9f02bf.mp4 filter=lfs diff=lfs merge=lfs -text
39
+ examples/b039e8c9fafd.mp4 filter=lfs diff=lfs merge=lfs -text
40
+ examples/ba6ce3240abf.mp4 filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,13 +1,53 @@
1
  ---
2
- title: Procobject Video Qa
3
- emoji: 🏒
4
  colorFrom: red
5
- colorTo: green
6
  sdk: gradio
7
  sdk_version: 6.31.0
8
- python_version: '3.12'
9
  app_file: app.py
10
- pinned: false
 
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: ProcObject-Qwen3-VL-4B Video QA
3
+ emoji: 🍳
4
  colorFrom: red
5
+ colorTo: indigo
6
  sdk: gradio
7
  sdk_version: 6.31.0
 
8
  app_file: app.py
9
+ short_description: Object-centric video QA with temporal grounding
10
+ python_version: "3.12"
11
+ startup_duration_timeout: 30m
12
  ---
13
 
14
+ # ProcObject-Qwen3-VL-4B β€” Object-Centric Video QA & Temporal Grounding
15
+
16
+ Demo of **[BrightGuo/ProcObject-Qwen3-VL-4B](https://huggingface.co/BrightGuo/ProcObject-Qwen3-VL-4B)**, the fine-tuned
17
+ model of *ProcObject-10K: Benchmarking and Learning Object-Centric Procedural Understanding in Instructional Videos*
18
+ (NeurIPS 2026, Evaluations & Datasets Track).
19
+
20
+ Upload an instructional video clip and ask a question about an **object** β€” how its state evolves, what happened to it
21
+ before a step, what went wrong with it, whether it is ready for the next step. The model answers **and** localizes the
22
+ supporting moments in the clip, returning `[[start_second, end_second], ...]` evidence intervals.
23
+
24
+ ## How it works
25
+
26
+ - **Frames**: 48 frames sampled uniformly over the clip (the model's training frame budget), resized to 512Γ—512.
27
+ - **Prompt**: each frame is sent as an image preceded by `[Timestamp: <t>s]` (seconds from the clip start), followed by
28
+ the question β€” the exact protocol of the [released benchmark harness](https://github.com/WenliangGuo/ProcObject-10K).
29
+ - **Decoding**: greedy (temperature 0).
30
+ - **Output**: a JSON object `{"answer": "...", "evidence": [[start, end], ...]}`, parsed into an answer plus a
31
+ highlighted timeline of the grounded evidence spans.
32
+
33
+ ## The five object-centric reasoning types
34
+
35
+ | Type | Asks |
36
+ |---|---|
37
+ | Precondition Grounding | what state an object had to reach before a step |
38
+ | Object State Evolution | how an object's state changes across steps |
39
+ | Counterfactual Reasoning | what would happen to an object if a step were skipped or done differently |
40
+ | Mistake Recognition | what went wrong with an object, and why it is a mistake |
41
+ | Readiness Assessment | whether an object is ready for the next step, and which visible cues show it |
42
+
43
+ ## Example clips
44
+
45
+ The bundled example clips come from the [CaptainCook4D](https://captaincook4d.github.io/captain-cook/) dataset
46
+ (Apache-2.0 code license; videos distributed by its authors via the [public downloader](https://github.com/CaptainCook4D/downloader)),
47
+ one of the four sources of ProcObject-10K. They were cut to the exact benchmark clip windows used by the test split.
48
+
49
+ ## Paper
50
+
51
+ [Benchmarking and Learning Object-Centric Procedural Understanding in Instructional Videos](https://arxiv.org/abs/2512.03479) β€”
52
+ Wenliang Guo, Yu Kong (Michigan State University). Code: [github.com/WenliangGuo/ProcObject-10K](https://github.com/WenliangGuo/ProcObject-10K);
53
+ dataset: [BrightGuo/ProcObject-10K](https://huggingface.co/datasets/BrightGuo/ProcObject-10K).
app.py ADDED
@@ -0,0 +1,367 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Object-centric procedural video QA with temporal grounding, using
3
+ BrightGuo/ProcObject-Qwen3-VL-4B (Qwen3-VL-4B-Instruct fine-tuned on ProcObject-10K,
4
+ NeurIPS 2026 Evaluations & Datasets Track).
5
+
6
+ The inference protocol replicates the authors' released benchmark harness
7
+ (https://github.com/WenliangGuo/ProcObject-10K) exactly:
8
+ * 48 frames sampled uniformly over the clip (the fine-tuned model's training
9
+ frame budget), resized to 512x512;
10
+ * each frame is sent as an image preceded by "[Timestamp: <t>s]" (seconds from
11
+ the clip start), then the question;
12
+ * the system prompt from benchmark/prompts/system_prompt.txt;
13
+ * greedy decoding (temperature 0);
14
+ * the model returns {"answer": "...", "evidence": [[start, end], ...]} JSON,
15
+ parsed with the same fallback logic as benchmark/utils.parse_model_response.
16
+ """
17
+
18
+ import json
19
+ import os
20
+ import re
21
+ import time
22
+ from typing import Any, Dict, List, Optional, Tuple
23
+
24
+ os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
25
+
26
+ import spaces # noqa: E402 (must come before torch)
27
+ import torch # noqa: E402
28
+
29
+ import cv2 # noqa: E402
30
+ import gradio as gr # noqa: E402
31
+ from PIL import Image, ImageDraw # noqa: E402
32
+ from transformers import AutoProcessor, Qwen3VLForConditionalGeneration # noqa: E402
33
+
34
+ MODEL_ID = "BrightGuo/ProcObject-Qwen3-VL-4B"
35
+ NUM_FRAMES = 48 # the fine-tuned model's frame budget
36
+ RESIZE = (512, 512) # frame size used by the benchmark protocol
37
+ MAX_NEW_TOKENS = 1024 # same as the harness's --max_tokens
38
+
39
+ SYSTEM_PROMPT = """You are a video question-answering assistant specializing in procedural, object-centric videos.
40
+
41
+ You will be given a series of uniformly sampled frames from a video clip. Each frame is labeled with its timestamp in seconds relative to the start of the clip (e.g., [Timestamp: 0.0s] is the first moment, [Timestamp: 45.2s] is 45.2 seconds into the clip). Use these timestamps to localize events temporally.
42
+
43
+ Your task:
44
+ 1. Carefully analyze all provided frames and their timestamps.
45
+ 2. Answer the question in detail, describing what you observe across the video frames.
46
+ 3. Identify the relevant time intervals as [start_second, end_second] pairs using the frame timestamps. The evidence intervals should correspond to the timestamps shown in the frames. For example, if the relevant action occurs between frames labeled [Timestamp: 10.0s] and [Timestamp: 25.0s], report evidence as [10, 25].
47
+
48
+ Return your response ONLY as a valid JSON object with exactly these two fields:
49
+ - "answer": a detailed string describing what you observed to answer the question
50
+ - "evidence": a list of [start_second, end_second] pairs (integers) indicating the relevant temporal segments based on the frame timestamps
51
+
52
+ Example output: (json format)
53
+ {"answer": "The apple is first visible on the cutting board and is then sliced into pieces.", "evidence": [[10, 25], [40, 55]]}
54
+
55
+ Do not include any text outside the JSON object. Do not wrap the JSON in markdown code fences."""
56
+
57
+ processor = AutoProcessor.from_pretrained(MODEL_ID, min_pixels=256 * 28 * 28, max_pixels=512 * 512)
58
+ model = Qwen3VLForConditionalGeneration.from_pretrained(
59
+ MODEL_ID,
60
+ dtype=torch.bfloat16,
61
+ attn_implementation="sdpa",
62
+ ).to("cuda").eval()
63
+
64
+
65
+ # ─────────────────────────────────────────────────────────────────────────────
66
+ # Frame sampling β€” identical to benchmark/utils.extract_frames (cv2-based,
67
+ # uniform over the whole clip, resized to 512x512, timestamps 0-based).
68
+ # ─────────────────────────────────────────────────────────────────────────────
69
+
70
+ def extract_frames(video_path: str, num_frames: int = NUM_FRAMES,
71
+ resize: Optional[Tuple[int, int]] = RESIZE) -> Tuple[List[Dict[str, Any]], float]:
72
+ """Uniformly sample num_frames frames from the video, with 0-based timestamps.
73
+
74
+ Returns (frames, clip_duration); each frame is {"image": PIL, "timestamp": float}.
75
+ """
76
+ cap = cv2.VideoCapture(video_path)
77
+ if not cap.isOpened():
78
+ raise RuntimeError(f"Cannot open video: {video_path}")
79
+
80
+ fps = cap.get(cv2.CAP_PROP_FPS)
81
+ cap.set(cv2.CAP_PROP_POS_AVI_RATIO, 1)
82
+ clip_duration = cap.get(cv2.CAP_PROP_POS_MSEC) / 1000.0
83
+ cap.set(cv2.CAP_PROP_POS_MSEC, 0)
84
+ if clip_duration <= 0 or fps <= 0:
85
+ cap.release()
86
+ return [], 0.0
87
+
88
+ if num_frames == 1:
89
+ timestamps = [clip_duration / 2]
90
+ else:
91
+ timestamps = [clip_duration * i / (num_frames - 1) for i in range(num_frames)]
92
+
93
+ frames: List[Dict[str, Any]] = []
94
+ for ts in timestamps:
95
+ cap.set(cv2.CAP_PROP_POS_MSEC, ts * 1000)
96
+ ret, frame = cap.read()
97
+ back = 1
98
+ while not ret and back <= 5: # cut clips can end before the last timestamp
99
+ cap.set(cv2.CAP_PROP_POS_MSEC, max(0.0, ts - back / fps) * 1000)
100
+ ret, frame = cap.read()
101
+ back += 1
102
+ if not ret:
103
+ continue
104
+ img = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
105
+ if resize:
106
+ img = img.resize(resize, Image.LANCZOS)
107
+ frames.append({"image": img, "timestamp": round(ts, 2)})
108
+
109
+ cap.release()
110
+ return frames, clip_duration
111
+
112
+
113
+ # ─────────────────────────────────────────────────────────────────────────────
114
+ # Response parsing β€” identical to benchmark/utils.parse_model_response.
115
+ # ─────────────────────────────────────────────────────────────────────────────
116
+
117
+ def parse_model_response(raw_text: str) -> Dict[str, Any]:
118
+ text = raw_text.strip()
119
+ text = re.sub(r"^```(?:json)?\s*", "", text)
120
+ text = re.sub(r"\s*```$", "", text)
121
+ text = text.strip()
122
+
123
+ try:
124
+ parsed = json.loads(text)
125
+ return {"answer": str(parsed.get("answer", "")), "evidence": parsed.get("evidence", [])}
126
+ except json.JSONDecodeError:
127
+ pass
128
+
129
+ match = re.search(r'\{[^{}]*"answer"[^{}]*\}', text, re.DOTALL)
130
+ if match:
131
+ try:
132
+ parsed = json.loads(match.group())
133
+ return {"answer": str(parsed.get("answer", "")), "evidence": parsed.get("evidence", [])}
134
+ except json.JSONDecodeError:
135
+ pass
136
+
137
+ return {"answer": text, "evidence": []}
138
+
139
+
140
+ # ─────────────────────────────────────────────────────────────────────────────
141
+ # Evidence timeline visualization
142
+ # ─────────────────────────────────────────────────────────────────────────────
143
+
144
+ def _clean_intervals(evidence: List, duration: float) -> List[Tuple[float, float]]:
145
+ spans = []
146
+ for item in evidence or []:
147
+ try:
148
+ s, e = float(item[0]), float(item[1])
149
+ except (TypeError, ValueError, IndexError):
150
+ continue
151
+ s, e = max(0.0, min(s, duration)), max(0.0, min(e, duration))
152
+ if e > s:
153
+ spans.append((s, e))
154
+ return spans
155
+
156
+
157
+ def render_timeline(duration: float, spans: List[Tuple[float, float]]) -> Image.Image:
158
+ """A thin timeline bar with the grounded evidence spans highlighted."""
159
+ W, H = 780, 92
160
+ pad_l, pad_r, pad_t, bar_h = 14, 14, 38, 28
161
+ img = Image.new("RGB", (W, H), (249, 249, 247))
162
+ d = ImageDraw.Draw(img)
163
+
164
+ track = (222, 222, 218)
165
+ d.rectangle([pad_l, pad_t, W - pad_r, pad_t + bar_h], fill=track)
166
+ if duration > 0:
167
+ for (s, e) in spans:
168
+ x0 = pad_l + (s / duration) * (W - pad_l - pad_r)
169
+ x1 = pad_l + (e / duration) * (W - pad_l - pad_r)
170
+ d.rectangle([x0, pad_t, max(x0 + 2.0, x1), pad_t + bar_h], fill=(224, 78, 46))
171
+
172
+ # second ticks + labels
173
+ if duration > 0:
174
+ step = 5 if duration <= 60 else (10 if duration <= 180 else 30)
175
+ for t in range(0, int(duration) + 1, step):
176
+ x = pad_l + (t / duration) * (W - pad_l - pad_r)
177
+ d.line([x, pad_t + bar_h + 3, x, pad_t + bar_h + 9], fill=(160, 160, 156))
178
+ label = f"{t}s"
179
+ tw = d.textlength(label)
180
+ d.text((min(max(x - tw / 2, 2), W - tw - 2), pad_t + bar_h + 12), label, fill=(110, 110, 106))
181
+
182
+ span_txt = ", ".join(f"[{s:.0f}–{e:.0f}s]" for s, e in spans) if spans else "no evidence returned"
183
+ d.text((pad_l, 12), span_txt, fill=(60, 60, 58))
184
+ return img
185
+
186
+
187
+ def format_result(answer: str, spans: List[Tuple[float, float]]) -> str:
188
+ evidence = ", ".join(f"[{s:.0f}s – {e:.0f}s]" for s, e in spans)
189
+ return f"{answer}\n\n\n⏱ Grounded evidence: {evidence if evidence else 'none returned'}"
190
+
191
+
192
+ # ─────────────────────────────────────────────────────────────────────────────
193
+ # Inference
194
+ # ─────────────────────────────────────────────────────────────────────────────
195
+
196
+ def _estimate_duration(video, question, num_frames=NUM_FRAMES, *args, **kwargs) -> int:
197
+ """Video length drives the runtime: ~1s per 8 frames plus generation."""
198
+ try:
199
+ n = int(num_frames)
200
+ except (TypeError, ValueError):
201
+ n = NUM_FRAMES
202
+ return min(240, 45 + int(n * 1.6))
203
+
204
+
205
+ @spaces.GPU(duration=_estimate_duration)
206
+ def answer_question(video, question, num_frames=NUM_FRAMES, max_new_tokens=MAX_NEW_TOKENS):
207
+ """Answer an object-centric question about a video clip and ground the evidence.
208
+
209
+ Args:
210
+ video: an instructional video clip (file path).
211
+ question: a question about an object in the clip.
212
+ num_frames: number of frames sampled uniformly from the clip (48 = the
213
+ model's training budget).
214
+ max_new_tokens: maximum number of tokens to generate.
215
+
216
+ Returns:
217
+ (formatted answer + evidence, evidence timeline image, raw model JSON)
218
+ """
219
+ t0 = time.perf_counter()
220
+ if not video:
221
+ raise gr.Error("Please upload a video clip first.")
222
+ question = (question or "").strip()
223
+ if not question:
224
+ raise gr.Error("Please enter a question about the clip.")
225
+ try:
226
+ num_frames = max(4, min(int(num_frames), 64))
227
+ except (TypeError, ValueError):
228
+ num_frames = NUM_FRAMES
229
+ try:
230
+ max_new_tokens = max(128, min(int(max_new_tokens), 2048))
231
+ except (TypeError, ValueError):
232
+ max_new_tokens = MAX_NEW_TOKENS
233
+
234
+ frames, clip_duration = extract_frames(video, num_frames=num_frames)
235
+ if not frames:
236
+ raise gr.Error("Could not read any frames from this video. Try an .mp4 file.")
237
+
238
+ # Message layout identical to the benchmark's VLLMRunner: one
239
+ # "[Timestamp: <t>s]" text part before every frame image, then the question.
240
+ content: List[Dict[str, Any]] = []
241
+ for f in frames:
242
+ content.append({"type": "text", "text": f"[Timestamp: {f['timestamp']}s]"})
243
+ content.append({"type": "image", "image": f["image"]})
244
+ content.append({"type": "text", "text": f"\nQuestion: {question}"})
245
+ messages = [
246
+ {"role": "system", "content": SYSTEM_PROMPT},
247
+ {"role": "user", "content": content},
248
+ ]
249
+
250
+ chat_text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
251
+ images = [f["image"] for f in frames]
252
+ inputs = processor(
253
+ text=[chat_text], images=images if images else None, padding=True, return_tensors="pt"
254
+ ).to("cuda")
255
+
256
+ with torch.inference_mode():
257
+ out_ids = model.generate(
258
+ **inputs,
259
+ max_new_tokens=max_new_tokens,
260
+ do_sample=False,
261
+ temperature=None,
262
+ top_p=None,
263
+ top_k=None,
264
+ )
265
+ gen_ids = out_ids[:, inputs["input_ids"].shape[1]:]
266
+ raw = processor.batch_decode(gen_ids, skip_special_tokens=True)[0].strip()
267
+
268
+ parsed = parse_model_response(raw)
269
+ spans = _clean_intervals(parsed["evidence"], clip_duration)
270
+ timeline = render_timeline(clip_duration, spans)
271
+ elapsed = time.perf_counter() - t0
272
+
273
+ payload = {"answer": parsed["answer"], "evidence": parsed["evidence"],
274
+ "clip_duration_s": round(clip_duration, 2), "frames": len(frames),
275
+ "inference_s": round(elapsed, 1)}
276
+ return format_result(parsed["answer"], spans), timeline, json.dumps(payload, ensure_ascii=False, indent=2)
277
+
278
+
279
+ # ─────────────────────────────────────────────────────────────────────────────
280
+ # UI
281
+ # ─────────────────────────────────────────────────────────────────────────────
282
+
283
+ EXAMPLES = [
284
+ ["examples/3e9350958273.mp4",
285
+ "How does the state of the egg change from the start of the whisking action to the end of this sequence?"],
286
+ ["examples/ba6ce3240abf.mp4",
287
+ "What mistake occurs on the pan with cheese, and why is it a mistake?"],
288
+ ["examples/b039e8c9fafd.mp4",
289
+ "How does the state of the bowl contents change from being stirred initially to the end of this sequence?"],
290
+ ["examples/37aabc657f3b.mp4",
291
+ "How does the state of the coffee grounds change from the start of the blooming phase to the end of this sequence?"],
292
+ ["examples/670e2b9f02bf.mp4",
293
+ "Before cutting the tomato into two pieces begins, is the tomato ready, and what visible cues show this?"],
294
+ ]
295
+
296
+ CSS = """
297
+ #col-container { max-width: 1150px; margin: 0 auto; }
298
+ .dark .gradio-container { color: var(--body-text-color); }
299
+ """
300
+
301
+ with gr.Blocks(theme=gr.themes.Citrus(), css=CSS) as demo:
302
+ with gr.Column(elem_id="col-container"):
303
+ gr.Markdown(
304
+ """
305
+ # 🍳 ProcObject-Qwen3-VL-4B β€” Object-Centric Video QA & Temporal Grounding
306
+
307
+ Ask a question about an **object** in an instructional video clip. The model answers and localizes the
308
+ supporting moments as `[start, end]` second intervals β€” the object-centric reasoning of
309
+ [ProcObject-10K](https://huggingface.co/datasets/BrightGuo/ProcObject-10K)
310
+ ([paper](https://arxiv.org/abs/2512.03479), NeurIPS 2026 Datasets & Benchmarks).
311
+ """
312
+ )
313
+
314
+ with gr.Row():
315
+ with gr.Column(scale=1):
316
+ video = gr.Video(label="Video clip")
317
+ question = gr.Textbox(
318
+ label="Question",
319
+ placeholder="e.g. How does the state of the egg change from the start of the whisking to the end?",
320
+ lines=3,
321
+ )
322
+ run = gr.Button("Answer", variant="primary")
323
+ with gr.Accordion("Advanced settings", open=False):
324
+ num_frames = gr.Slider(minimum=8, maximum=64, value=NUM_FRAMES, step=2,
325
+ label="Frames sampled", info="48 is the fine-tuned model's training budget")
326
+ max_new_tokens = gr.Slider(minimum=128, maximum=2048, value=MAX_NEW_TOKENS, step=64,
327
+ label="Max new tokens")
328
+
329
+ with gr.Column(scale=1):
330
+ answer_out = gr.Textbox(label="Answer + grounded evidence", lines=12, show_copy_button=True)
331
+ timeline_out = gr.Image(label="Evidence timeline", height=140, show_download_button=False)
332
+ json_out = gr.Textbox(label="Model output (JSON)", lines=6, show_copy_button=True)
333
+
334
+ gr.Markdown(
335
+ """
336
+ #### Tips
337
+ - Ask about an **object** (the egg, the pan, the coffee grounds…), not only about the person: the model is
338
+ trained for object-centric reasoning β€” preconditions, state evolution, counterfactuals, mistakes, readiness.
339
+ - Evidence intervals are in seconds from the start of the clip, matching the `[Timestamp: …s]` frame labels.
340
+ - The bundled example clips are from [CaptainCook4D](https://captaincook4d.github.io/captain-cook/), one of
341
+ the four source datasets of ProcObject-10K, cut to the exact benchmark clip windows.
342
+ """
343
+ )
344
+
345
+ gr.Examples(
346
+ examples=EXAMPLES,
347
+ inputs=[video, question],
348
+ outputs=[answer_out, timeline_out, json_out],
349
+ fn=answer_question,
350
+ cache_examples=True,
351
+ cache_mode="lazy",
352
+ )
353
+
354
+ run.click(
355
+ answer_question,
356
+ inputs=[video, question, num_frames, max_new_tokens],
357
+ outputs=[answer_out, timeline_out, json_out],
358
+ api_name="answer",
359
+ )
360
+ question.submit(
361
+ answer_question,
362
+ inputs=[video, question, num_frames, max_new_tokens],
363
+ outputs=[answer_out, timeline_out, json_out],
364
+ api_name="answer_submit",
365
+ )
366
+
367
+ demo.launch(mcp_server=True)
examples/37aabc657f3b.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:27ea590907fd8a1aa3f7ca99bf7d07a64a75c6859008924736c2416aa3a339fe
3
+ size 3367037
examples/3e9350958273.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f3ef5d93e27fa4030c12d247825e070d463b48c3dfc5c7514b64f8cf85edd530
3
+ size 2770374
examples/670e2b9f02bf.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:caa7a7356fee5db46e9bb0e13bb7ac9605d3f78350da58916502f8fa18f22352
3
+ size 5379405
examples/b039e8c9fafd.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f8caad6330292e0c8c08a23dba0dea9c508ce9b507d41067bae753347da7d553
3
+ size 2735653
examples/ba6ce3240abf.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1a9edd79eb9f75471805b38aa8c587ae5f6d619c7bb6fbd781d50c562f9eb810
3
+ size 3773719
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ transformers>=4.57.6
2
+ accelerate
3
+ sentencepiece
4
+ einops
5
+ opencv-python-headless
6
+ pillow
7
+ numpy
8
+ timm