Skip to content

Instantly share code, notes, and snippets.

@jordancrawford
Created August 2, 2026 04:48
Show Gist options
  • Select an option

  • Save jordancrawford/2f97f7cbc20444fa36287a62bde20422 to your computer and use it in GitHub Desktop.

Select an option

Save jordancrawford/2f97f7cbc20444fa36287a62bde20422 to your computer and use it in GitHub Desktop.
Video chapter CSV to text
#!/usr/bin/env python3
# Converts a CSV of video chapters into a text file, converting HH:MM:SS:FF to HH:MM:SS.ms
# From https://jc.kiwi/modernising-camcorder-footage/
#
# Run with:
# ./clips_csv_to_txt.py input.csv output.txt --framerate 50
import argparse
import csv
from pathlib import Path
from typing import List
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Convert a clip CSV directly into the plain-text timecode list format."
)
parser.add_argument("csv_path", help="Path to the input CSV file")
parser.add_argument("output_path", help="Path to the output .txt file")
parser.add_argument(
"--framerate",
required=True,
type=float,
help="Source framerate used to convert frame numbers to milliseconds",
)
return parser.parse_args()
def format_timecode(timecode: str, framerate: float) -> str:
parts = timecode.split(":")
if len(parts) != 4:
raise ValueError(f"Invalid timecode '{timecode}'. Expected HH:MM:SS:FF format.")
hours, minutes, seconds, frames = (int(part) for part in parts)
milliseconds = round((frames / framerate) * 1000)
milliseconds = min(milliseconds, 999)
return f"{hours:02d}:{minutes:02d}:{seconds:02d}.{milliseconds:03d}"
def build_lines(csv_path: Path, framerate: float) -> List[str]:
lines = []
with csv_path.open(newline="", encoding="utf-8-sig") as handle:
reader = csv.DictReader(handle)
for row in reader:
name = (row.get("Name") or "").strip()
in_timecode = (row.get("Timecode In") or "").strip()
if not name:
raise ValueError("Each CSV row must include a Name value.")
if not in_timecode:
raise ValueError("Each CSV row must include a Timecode In value.")
formatted_timecode = format_timecode(in_timecode, framerate)
lines.append(f"{formatted_timecode} {name}")
return lines
def main() -> int:
args = parse_args()
csv_path = Path(args.csv_path)
output_path = Path(args.output_path)
if not csv_path.exists():
raise SystemExit(f"CSV file not found: {csv_path}")
if args.framerate <= 0:
raise SystemExit("Framerate must be a positive number")
lines = build_lines(csv_path, args.framerate)
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
print(f"Written to {output_path}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment