| from __future__ import annotations |
|
|
| import argparse |
| import json |
| from pathlib import Path |
|
|
| import pandas as pd |
|
|
|
|
| VIDEO_COLUMNS = [ |
| "sentence_name", "SENTENCE_NAME", |
| "video_path", "path", "file", "filename", "video", |
| "video_name", "VIDEO_NAME", |
| "sentence_id", "SENTENCE_ID", |
| "clip_id", "CLIP_ID", |
| ] |
|
|
| TARGET_COLUMNS = [ |
| "translation", "text", "sentence", "transcript", |
| "raw_text", "normalized_text", |
| "TRANSLATION", "TEXT", "SENTENCE", "TRANSCRIPT", |
| ] |
|
|
| ID_COLUMNS = [ |
| "id", "sentence_id", "clip_id", "video_id", |
| "ID", "SENTENCE_ID", "CLIP_ID", "VIDEO_ID", "SENTENCE_NAME", |
| ] |
|
|
|
|
| def pick_column(df: pd.DataFrame, candidates: list[str], label: str) -> str: |
| lower = {c.lower(): c for c in df.columns} |
| for c in candidates: |
| if c.lower() in lower: |
| return lower[c.lower()] |
|
|
| raise ValueError( |
| f"Could not find {label} column.\n" |
| f"Tried: {candidates}\n" |
| f"Available columns: {list(df.columns)}" |
| ) |
|
|
|
|
| def build_video_index(video_root: Path) -> dict[str, Path]: |
| index: dict[str, Path] = {} |
|
|
| for path in video_root.rglob("*.mp4"): |
| keys = { |
| path.name, |
| path.stem, |
| path.stem.replace("-rgb_front", ""), |
| path.name.replace("-rgb_front.mp4", ""), |
| } |
|
|
| for key in keys: |
| index[key] = path |
|
|
| return index |
|
|
|
|
| def find_video(value: object, video_root: Path, index: dict[str, Path]) -> Path: |
| raw = str(value).strip() |
| name = Path(raw).name |
| stem = Path(raw).stem |
|
|
| candidates = [ |
| raw, |
| name, |
| stem, |
| f"{raw}.mp4", |
| f"{name}.mp4", |
| f"{raw}-rgb_front.mp4", |
| f"{name}-rgb_front.mp4", |
| raw.replace("-rgb_front", ""), |
| stem.replace("-rgb_front", ""), |
| ] |
|
|
| for candidate in candidates: |
| if candidate in index: |
| return index[candidate] |
|
|
| direct_path = video_root / name |
| if direct_path.exists(): |
| return direct_path |
|
|
| raise FileNotFoundError(f"Could not find video for '{raw}' under {video_root}") |
|
|
|
|
| def write_split(csv_path: Path, video_root: Path, out_path: Path, split: str) -> None: |
| if not csv_path.exists(): |
| raise FileNotFoundError(f"Missing CSV: {csv_path}") |
|
|
| if not video_root.exists(): |
| raise FileNotFoundError(f"Missing video directory: {video_root}") |
|
|
| df = pd.read_csv(csv_path, sep="\t") |
| print(f"{split} columns: {list(df.columns)}") |
|
|
| video_col = pick_column(df, VIDEO_COLUMNS, "video") |
| target_col = pick_column(df, TARGET_COLUMNS, "translation/text") |
|
|
| id_col = None |
| try: |
| id_col = pick_column(df, ID_COLUMNS, "id") |
| except ValueError: |
| pass |
|
|
| print(f"{split}: using video column = {video_col}") |
| print(f"{split}: using target column = {target_col}") |
| print(f"{split}: using id column = {id_col}") |
|
|
| video_index = build_video_index(video_root) |
| print(f"{split}: indexed {len(video_index)} video lookup keys") |
|
|
| out_path.parent.mkdir(parents=True, exist_ok=True) |
|
|
| written = 0 |
| skipped_missing_video = 0 |
| skipped_empty_target = 0 |
| missing_examples = [] |
|
|
| missing_report_path = out_path.with_suffix(".missing_videos.txt") |
|
|
| with out_path.open("w", encoding="utf-8") as f: |
| for i, row in df.iterrows(): |
| target = str(row[target_col]).strip() |
|
|
| if not target or target.lower() == "nan": |
| skipped_empty_target += 1 |
| continue |
|
|
| try: |
| video_path = find_video(row[video_col], video_root, video_index) |
| except FileNotFoundError: |
| skipped_missing_video += 1 |
| if len(missing_examples) < 50: |
| missing_examples.append(str(row[video_col])) |
| continue |
|
|
| example = { |
| "id": str(row[id_col]).strip() if id_col else str(i), |
| "video_path": str(video_path), |
| "translation": target, |
| "split": split, |
| } |
|
|
| f.write(json.dumps(example, ensure_ascii=False) + "\n") |
| written += 1 |
|
|
| with missing_report_path.open("w", encoding="utf-8") as f: |
| for item in missing_examples: |
| f.write(item + "\n") |
|
|
| total = len(df) |
| print(f"{split}: total metadata rows = {total}") |
| print(f"{split}: wrote usable rows = {written}") |
| print(f"{split}: skipped missing videos = {skipped_missing_video}") |
| print(f"{split}: skipped empty targets = {skipped_empty_target}") |
| print(f"{split}: missing-video examples saved to {missing_report_path}") |
|
|
| if total > 0: |
| kept_pct = 100.0 * written / total |
| skipped_pct = 100.0 * skipped_missing_video / total |
| print(f"{split}: kept {kept_pct:.2f}% of rows") |
| print(f"{split}: missing-video skip rate {skipped_pct:.2f}%") |
|
|
|
|
| def main() -> None: |
| parser = argparse.ArgumentParser() |
|
|
| parser.add_argument("--train_metadata", required=True) |
| parser.add_argument("--val_metadata", required=True) |
| parser.add_argument("--train_video_root", required=True) |
| parser.add_argument("--val_video_root", required=True) |
| parser.add_argument("--out_train", default="data/how2sign_train.jsonl") |
| parser.add_argument("--out_val", default="data/how2sign_val.jsonl") |
|
|
| args = parser.parse_args() |
|
|
| write_split( |
| csv_path=Path(args.train_metadata), |
| video_root=Path(args.train_video_root), |
| out_path=Path(args.out_train), |
| split="train", |
| ) |
|
|
| write_split( |
| csv_path=Path(args.val_metadata), |
| video_root=Path(args.val_video_root), |
| out_path=Path(args.out_val), |
| split="val", |
| ) |
|
|
|
|
| if __name__ == "__main__": |
| main() |