From 23c9c22ef7d1f2ff3ce31aaf8e4bf2cd670c30ce Mon Sep 17 00:00:00 2001 From: Matt Redmond <10541289+redmond2742@users.noreply.github.com> Date: Sat, 14 Jun 2025 22:00:58 -0700 Subject: [PATCH] Fix autosync timezone handling --- README.md | 5 ++++ src/ssoss/ssoss_cli.py | 58 ++++++++++++++++++++++++++++++++++++++++- tests/test_ssoss_cli.py | 19 ++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6581d25..b0a134d 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,12 @@ Collect data simultaneously: --video_file vid.mov \ --sync_frame 456 \ --sync_timestamp 2022-10-24T14:21:54.32Z + +(ssoss_virtual_env) ssoss --video_file 09-15-2023--14-12-24.123-UTC.mov \ + --autosync ``` +The timezone portion of the filename is preserved as an offset; the +timestamp itself is not shifted. #### Sync GPX & Video Process Synchronizing the GPX file and the video could be one of the largest sources of error. The ProcessVideo Class has diff --git a/src/ssoss/ssoss_cli.py b/src/ssoss/ssoss_cli.py index 50964a3..61b245b 100644 --- a/src/ssoss/ssoss_cli.py +++ b/src/ssoss/ssoss_cli.py @@ -1,8 +1,51 @@ import argparse +import re +from datetime import datetime, timedelta, timezone +from pathlib import Path from . import process_road_objects from . import process_video +def _timestamp_from_filename(path: str) -> str: + """Extract ISO 8601 timestamp from ``path``. + + The filename should contain a timestamp formatted as + ``MM-DD-YYYY--HH-MM-SS.sss-ZZZ`` where ``ZZZ`` is a timezone + abbreviation such as ``UTC`` or ``PDT``. The time itself is not shifted + based on the timezone; the offset is simply attached to the parsed + timestamp. + """ + + base = Path(path).stem + m = re.search( + r"(?P\d{2}-\d{2}-\d{4}--\d{2}-\d{2}-\d{2}\.\d+)-(?P[A-Za-z]+)$", + base, + ) + if not m: + raise ValueError("No timestamp found in video filename") + + ts_str = m.group("ts") + zone = m.group("zone") + + dt = datetime.strptime(ts_str, "%m-%d-%Y--%H-%M-%S.%f") + zone = zone.upper() + offset_map = { + "UTC": 0, + "PST": -8, + "PDT": -7, + "MST": -7, + "MDT": -6, + "CST": -6, + "CDT": -5, + "EST": -5, + "EDT": -4, + } + hours = offset_map.get(zone, 0) + tz = timezone(timedelta(hours=hours)) + dt = dt.replace(tzinfo=tz) + return dt.isoformat() + + def args_static_obj_gpx_video( generic_so_file="", gpx_file="", @@ -139,6 +182,11 @@ def main(): help="2. Sync Timestamp ('2022-10-24T14:21:54.988Z') for video. Sync with frame number also", type=str, ) + video_sync_group.add_argument( + "--autosync", + action="store_true", + help="Sync using timestamp embedded in video filename", + ) video_sync_group.add_argument( "--label", @@ -175,7 +223,15 @@ def main(): sync_input = ("", "") frames = ("", "") - if args.sync_frame and args.sync_timestamp: + if args.autosync: + if not args.video_file: + parser.error("--autosync requires --video_file") + try: + ts = _timestamp_from_filename(args.video_file.name) + sync_input = (1, ts) + except ValueError as e: + parser.error(str(e)) + elif args.sync_frame and args.sync_timestamp: sync_input = (args.sync_frame, args.sync_timestamp) if args.frame_extract_start and args.frame_extract_end: frames = (args.frame_extract_start[0], args.frame_extract_end[0]) diff --git a/tests/test_ssoss_cli.py b/tests/test_ssoss_cli.py index a4f6efa..fd9d88c 100644 --- a/tests/test_ssoss_cli.py +++ b/tests/test_ssoss_cli.py @@ -103,3 +103,22 @@ def test_dispatch_extract_frames(monkeypatch, tmp_path): pv_instance.extract_frames_between.assert_called_once_with(1, 2) + +def test_autosync_uses_filename(run_cli, tmp_path): + vid = tmp_path / "09-15-2023--14-12-24.123-UTC.mov" + vid.write_text("data") + + result = run_cli(["--video_file", str(vid), "--autosync"]) + + assert result["vid_sync"][0] == 1 + assert result["vid_sync"][1].startswith("2023-09-15T14:12:24.123000") + + +def test_autosync_preserves_timezone_offset(run_cli, tmp_path): + vid = tmp_path / "09-15-2023--14-12-24.123-PST.mov" + vid.write_text("data") + + result = run_cli(["--video_file", str(vid), "--autosync"]) + + assert result["vid_sync"][1].endswith("-08:00") +