From 8148971aae147c0097849611f4455d9a8d9059fd Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Tue, 7 Apr 2026 14:09:08 +0200 Subject: [PATCH] =?UTF-8?q?refactor:=20remove=20fps=20from=20annotation=20?= =?UTF-8?q?=E2=80=94=20path=20+=20label=20only?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- main.py | 10 +++------- tests/test_utils.py | 23 +++++++---------------- tools/migrate_dataset_json.py | 32 ++++++-------------------------- 3 files changed, 16 insertions(+), 49 deletions(-) diff --git a/main.py b/main.py index c017dff..36667d2 100755 --- a/main.py +++ b/main.py @@ -101,12 +101,10 @@ def build_annotation_json_path(folder: str) -> str: return os.path.join(folder, "dataset.json") -def upsert_clip_annotation( - folder: str, clip_path: str, label: str, fps: float | None -) -> None: +def upsert_clip_annotation(folder: str, clip_path: str, label: str) -> None: """Insert or update one entry in /dataset.json. - Each entry stores a path relative to *folder*, the sound label, and fps. + Each entry stores a path relative to *folder* and the sound label. Matches on ``path``; if an entry for the same clip already exists it is replaced (overwrite-export case). Nothing is written when *label* is empty. @@ -124,8 +122,6 @@ def upsert_clip_annotation( entries = [] rel_path = os.path.relpath(clip_path, folder) entry: dict = {"path": rel_path, "label": label} - if fps is not None: - entry["fps"] = fps for i, e in enumerate(entries): if e.get("path") == rel_path: entries[i] = entry @@ -1591,7 +1587,7 @@ class MainWindow(QMainWindow): category=category, ) folder = self._txt_folder.text() - upsert_clip_annotation(folder, path, label, self._fps) + upsert_clip_annotation(folder, path, label) # For MP4 exports path is a file; for WebP sequence it is a directory. # build_mask_output_dir handles both correctly via Path.stem. self._last_export_path = path diff --git a/tests/test_utils.py b/tests/test_utils.py index 36c0eab..263b422 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -240,18 +240,17 @@ def test_annotation_json_path(): def test_upsert_creates_file(): with tempfile.TemporaryDirectory() as d: clip = os.path.join(d, "clip_001.mp4") - upsert_clip_annotation(d, clip, "dog barking", 25.0) + upsert_clip_annotation(d, clip, "dog barking") with open(os.path.join(d, "dataset.json")) as f: entries = json.load(f) assert len(entries) == 1 assert entries[0]["label"] == "dog barking" - assert entries[0]["fps"] == 25.0 assert entries[0]["path"] == "clip_001.mp4" def test_upsert_appends_new_clips(): with tempfile.TemporaryDirectory() as d: - upsert_clip_annotation(d, os.path.join(d, "clip_001.mp4"), "dog barking", 25.0) - upsert_clip_annotation(d, os.path.join(d, "clip_002.mp4"), "cat meowing", 30.0) + upsert_clip_annotation(d, os.path.join(d, "clip_001.mp4"), "dog barking") + upsert_clip_annotation(d, os.path.join(d, "clip_002.mp4"), "cat meowing") with open(os.path.join(d, "dataset.json")) as f: entries = json.load(f) assert len(entries) == 2 @@ -259,8 +258,8 @@ def test_upsert_appends_new_clips(): def test_upsert_replaces_existing(): with tempfile.TemporaryDirectory() as d: clip = os.path.join(d, "clip_001.mp4") - upsert_clip_annotation(d, clip, "dog barking", 25.0) - upsert_clip_annotation(d, clip, "cat meowing", 25.0) + upsert_clip_annotation(d, clip, "dog barking") + upsert_clip_annotation(d, clip, "cat meowing") with open(os.path.join(d, "dataset.json")) as f: entries = json.load(f) assert len(entries) == 1 @@ -268,21 +267,13 @@ def test_upsert_replaces_existing(): def test_upsert_empty_label_skips(): with tempfile.TemporaryDirectory() as d: - upsert_clip_annotation(d, os.path.join(d, "clip_001.mp4"), "", 25.0) + upsert_clip_annotation(d, os.path.join(d, "clip_001.mp4"), "") assert not os.path.exists(os.path.join(d, "dataset.json")) -def test_upsert_no_fps(): - with tempfile.TemporaryDirectory() as d: - clip = os.path.join(d, "clip_001.mp4") - upsert_clip_annotation(d, clip, "dog barking", None) - with open(os.path.join(d, "dataset.json")) as f: - entries = json.load(f) - assert "fps" not in entries[0] - def test_upsert_missing_folder_creates_it(): with tempfile.TemporaryDirectory() as d: nested = os.path.join(d, "subdir", "deep") - upsert_clip_annotation(nested, os.path.join(nested, "clip_001.mp4"), "dog barking", 25.0) + upsert_clip_annotation(nested, os.path.join(nested, "clip_001.mp4"), "dog barking") assert os.path.exists(os.path.join(nested, "dataset.json")) def test_db_stores_label_and_category(): diff --git a/tools/migrate_dataset_json.py b/tools/migrate_dataset_json.py index 86fb31f..7e0b23e 100644 --- a/tools/migrate_dataset_json.py +++ b/tools/migrate_dataset_json.py @@ -2,8 +2,7 @@ """One-shot migration: generate dataset.json files from ~/.8cut.db history. For each export folder that has records in the database, writes (or merges -into) a dataset.json with path (relative to the folder), label, and fps when -a .fps.txt sidecar exists alongside the WAV file. +into) a dataset.json with path (relative to the folder) and label per clip. Usage: python tools/migrate_dataset_json.py [--dry-run] @@ -29,30 +28,11 @@ def load_db_records(db_path: Path) -> list[dict]: return [dict(r) for r in rows] -def read_fps_sidecar(output_path: str) -> float | None: - """Read fps from .fps.txt if it exists.""" - sidecar = output_path + ".fps.txt" - if os.path.exists(sidecar): - try: - return float(Path(sidecar).read_text().strip()) - except ValueError: - pass - return None - - -def build_entries_for_folder( - folder: str, records: list[dict] -) -> list[dict]: - entries = [] - for rec in records: - output_path = rec["output_path"] - rel = os.path.relpath(output_path, folder) - entry: dict = {"path": rel, "label": rec["label"]} - fps = read_fps_sidecar(output_path) - if fps is not None: - entry["fps"] = fps - entries.append(entry) - return entries +def build_entries_for_folder(folder: str, records: list[dict]) -> list[dict]: + return [ + {"path": os.path.relpath(rec["output_path"], folder), "label": rec["label"]} + for rec in records + ] def merge_into_json(json_path: str, new_entries: list[dict], dry_run: bool) -> int: