refactor: remove fps from annotation — path + label only

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-07 14:09:08 +02:00
parent a8d8bd7fdc
commit 8148971aae
3 changed files with 16 additions and 49 deletions
+3 -7
View File
@@ -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 <folder>/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
+7 -16
View File
@@ -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():
+6 -26
View File
@@ -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 <clip>.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 <output_path>.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: