from __future__ import annotations from pathlib import Path from typing import Any from torch.utils.data import Dataset from .io_utils import read_jsonl, resolve_path from .qwen_video_utils import build_messages class VideoJsonlDataset(Dataset): def __init__( self, jsonl_path: str | Path, *, video_column: str, target_column: str, prompt: str, video_fps: int | float | None = None, max_frames: int | None = None, max_samples: int | None = None, require_target: bool = True, ) -> None: rows = read_jsonl(jsonl_path) if max_samples is not None and max_samples > 0: rows = rows[:max_samples] self.rows = rows self.video_column = video_column self.target_column = target_column self.prompt = prompt self.video_fps = video_fps self.max_frames = max_frames self.require_target = require_target def __len__(self) -> int: return len(self.rows) def __getitem__(self, idx: int) -> dict[str, Any]: row = self.rows[idx] if self.video_column not in row: raise KeyError(f"Missing video column '{self.video_column}' in row id={row.get('id', idx)}") video_path = resolve_path(row[self.video_column]) if not video_path.exists(): raise FileNotFoundError(f"Video file not found for row id={row.get('id', idx)}: {video_path}") target = row.get(self.target_column) if self.require_target and (target is None or str(target).strip() == ""): raise ValueError(f"Missing target '{self.target_column}' in row id={row.get('id', idx)}") return { "id": row.get("id", str(idx)), "video_path": str(video_path), "target": str(target) if target is not None else None, "messages": build_messages( video_path, self.prompt, target, video_fps=self.video_fps, max_frames=self.max_frames, ), }