{"record":{"id":"919436e7137fc92b","repo":"docling-project/docling","slug":"max-frames-must-be-0-when-set","errorCode":null,"errorMessage":"max_frames must be > 0 when set","messagePattern":"max_frames must be > 0 when set","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"docling/utils/video_frame_sampling.py","lineNumber":288,"sourceCode":"    frames: list[tuple[float, Image.Image]] = []\n    for i in range(count):\n        chunk = buf[i * frame_bytes : (i + 1) * frame_bytes]\n        frames.append((i / fps, Image.frombytes(\"RGB\", (size, size), chunk)))\n    return frames\n\n\nclass FixedIntervalFrameSampler:\n    \"\"\"Sample one frame every ``interval_seconds`` from time zero.\"\"\"\n\n    def __init__(\n        self,\n        interval_seconds: float = 10.0,\n        max_frames: int | None = None,\n    ):\n        if interval_seconds <= 0:\n            raise ValueError(\"interval_seconds must be > 0\")\n        if max_frames is not None and max_frames <= 0:\n            raise ValueError(\"max_frames must be > 0 when set\")\n        self.interval_seconds = interval_seconds\n        self.max_frames = max_frames\n\n    def sample(self, video_path: Path) -> list[VideoFrame]:\n        _require_ffmpeg()\n        duration = _probe_duration(video_path)\n\n        frames: list[VideoFrame] = []\n        t = 0.0\n        # If duration is unknown (0.0), rely on extraction returning None at EOF.\n        while duration == 0.0 or t < duration:\n            if self.max_frames is not None and len(frames) >= self.max_frames:\n                break\n            image = _extract_frame(video_path, t)\n            if image is None:\n                break\n            frames.append(VideoFrame(timestamp=t, image=image))\n            t += self.interval_seconds","sourceCodeStart":270,"sourceCodeEnd":306,"githubUrl":"https://github.com/docling-project/docling/blob/61d76f1ff3f8428065465889f7b4577da7df704c/docling/utils/video_frame_sampling.py#L270-L306","documentation":"ValueError raised by FixedIntervalFrameSampler.__init__ in docling/utils/video_frame_sampling.py when max_frames is provided and is <= 0. max_frames caps the number of extracted frames to bound memory and runtime; None means unlimited, so zero or negative values are treated as configuration errors rather than as 'no frames'.","triggerScenarios":"Explicitly passing max_frames=0 or a negative int, e.g. FixedIntervalFrameSampler(interval_seconds=5, max_frames=0). Passing max_frames=None is valid (unlimited).","commonSituations":"CLI/config plumbing where an unset integer option defaults to 0 instead of None; computing max_frames as a budget-derived value (frames = budget // cost) that evaluates to 0 or negative; misreading 0 as 'no cap' when the API uses None for that.","solutions":["Omit max_frames or pass None when you want no cap; pass a positive int such as max_frames=100 to cap extraction.","Normalize configuration inputs: convert 0/negative to None if 'unlimited' was intended: max_frames = value or None.","Validate derived budgets before construction and surface a clear error if the budget cannot afford at least one frame."],"exampleFix":"# before\nsampler = FixedIntervalFrameSampler(max_frames=cfg.max_frames)  # cfg default 0 -> ValueError\n\n# after\ncap = cfg.max_frames if isinstance(cfg.max_frames, int) and cfg.max_frames > 0 else None\nsampler = FixedIntervalFrameSampler(max_frames=cap)","handlingStrategy":"validation","validationCode":"cap = max_frames if isinstance(max_frames, int) and max_frames > 0 else None\nsampler = FixedIntervalFrameSampler(interval_seconds=10.0, max_frames=cap)","typeGuard":"def is_valid_frame_cap(value: int | None) -> bool:\n    return value is None or value > 0","tryCatchPattern":null,"preventionTips":["Use None (not 0) to mean 'unlimited frames' in configs and CLIs.","Map sentinel values like 0 or -1 to None before constructing samplers.","Validate derived budgets so a cap of 0 fails loudly in your config layer with a better message."],"tags":["video","validation","constructor"],"backgroundTag":null,"analyzedSha":"61d76f1ff3f8428065465889f7b4577da7df704c","analyzedAt":"2026-08-14T23:53:18.727Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}