refactor: remove fps from annotation — path + label only
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -101,12 +101,10 @@ def build_annotation_json_path(folder: str) -> str:
|
|||||||
return os.path.join(folder, "dataset.json")
|
return os.path.join(folder, "dataset.json")
|
||||||
|
|
||||||
|
|
||||||
def upsert_clip_annotation(
|
def upsert_clip_annotation(folder: str, clip_path: str, label: str) -> None:
|
||||||
folder: str, clip_path: str, label: str, fps: float | None
|
|
||||||
) -> None:
|
|
||||||
"""Insert or update one entry in <folder>/dataset.json.
|
"""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
|
Matches on ``path``; if an entry for the same clip already exists it is
|
||||||
replaced (overwrite-export case). Nothing is written when *label* is
|
replaced (overwrite-export case). Nothing is written when *label* is
|
||||||
empty.
|
empty.
|
||||||
@@ -124,8 +122,6 @@ def upsert_clip_annotation(
|
|||||||
entries = []
|
entries = []
|
||||||
rel_path = os.path.relpath(clip_path, folder)
|
rel_path = os.path.relpath(clip_path, folder)
|
||||||
entry: dict = {"path": rel_path, "label": label}
|
entry: dict = {"path": rel_path, "label": label}
|
||||||
if fps is not None:
|
|
||||||
entry["fps"] = fps
|
|
||||||
for i, e in enumerate(entries):
|
for i, e in enumerate(entries):
|
||||||
if e.get("path") == rel_path:
|
if e.get("path") == rel_path:
|
||||||
entries[i] = entry
|
entries[i] = entry
|
||||||
@@ -1591,7 +1587,7 @@ class MainWindow(QMainWindow):
|
|||||||
category=category,
|
category=category,
|
||||||
)
|
)
|
||||||
folder = self._txt_folder.text()
|
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.
|
# For MP4 exports path is a file; for WebP sequence it is a directory.
|
||||||
# build_mask_output_dir handles both correctly via Path.stem.
|
# build_mask_output_dir handles both correctly via Path.stem.
|
||||||
self._last_export_path = path
|
self._last_export_path = path
|
||||||
|
|||||||
+7
-16
@@ -240,18 +240,17 @@ def test_annotation_json_path():
|
|||||||
def test_upsert_creates_file():
|
def test_upsert_creates_file():
|
||||||
with tempfile.TemporaryDirectory() as d:
|
with tempfile.TemporaryDirectory() as d:
|
||||||
clip = os.path.join(d, "clip_001.mp4")
|
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:
|
with open(os.path.join(d, "dataset.json")) as f:
|
||||||
entries = json.load(f)
|
entries = json.load(f)
|
||||||
assert len(entries) == 1
|
assert len(entries) == 1
|
||||||
assert entries[0]["label"] == "dog barking"
|
assert entries[0]["label"] == "dog barking"
|
||||||
assert entries[0]["fps"] == 25.0
|
|
||||||
assert entries[0]["path"] == "clip_001.mp4"
|
assert entries[0]["path"] == "clip_001.mp4"
|
||||||
|
|
||||||
def test_upsert_appends_new_clips():
|
def test_upsert_appends_new_clips():
|
||||||
with tempfile.TemporaryDirectory() as d:
|
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_001.mp4"), "dog barking")
|
||||||
upsert_clip_annotation(d, os.path.join(d, "clip_002.mp4"), "cat meowing", 30.0)
|
upsert_clip_annotation(d, os.path.join(d, "clip_002.mp4"), "cat meowing")
|
||||||
with open(os.path.join(d, "dataset.json")) as f:
|
with open(os.path.join(d, "dataset.json")) as f:
|
||||||
entries = json.load(f)
|
entries = json.load(f)
|
||||||
assert len(entries) == 2
|
assert len(entries) == 2
|
||||||
@@ -259,8 +258,8 @@ def test_upsert_appends_new_clips():
|
|||||||
def test_upsert_replaces_existing():
|
def test_upsert_replaces_existing():
|
||||||
with tempfile.TemporaryDirectory() as d:
|
with tempfile.TemporaryDirectory() as d:
|
||||||
clip = os.path.join(d, "clip_001.mp4")
|
clip = os.path.join(d, "clip_001.mp4")
|
||||||
upsert_clip_annotation(d, clip, "dog barking", 25.0)
|
upsert_clip_annotation(d, clip, "dog barking")
|
||||||
upsert_clip_annotation(d, clip, "cat meowing", 25.0)
|
upsert_clip_annotation(d, clip, "cat meowing")
|
||||||
with open(os.path.join(d, "dataset.json")) as f:
|
with open(os.path.join(d, "dataset.json")) as f:
|
||||||
entries = json.load(f)
|
entries = json.load(f)
|
||||||
assert len(entries) == 1
|
assert len(entries) == 1
|
||||||
@@ -268,21 +267,13 @@ def test_upsert_replaces_existing():
|
|||||||
|
|
||||||
def test_upsert_empty_label_skips():
|
def test_upsert_empty_label_skips():
|
||||||
with tempfile.TemporaryDirectory() as d:
|
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"))
|
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():
|
def test_upsert_missing_folder_creates_it():
|
||||||
with tempfile.TemporaryDirectory() as d:
|
with tempfile.TemporaryDirectory() as d:
|
||||||
nested = os.path.join(d, "subdir", "deep")
|
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"))
|
assert os.path.exists(os.path.join(nested, "dataset.json"))
|
||||||
|
|
||||||
def test_db_stores_label_and_category():
|
def test_db_stores_label_and_category():
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
"""One-shot migration: generate dataset.json files from ~/.8cut.db history.
|
"""One-shot migration: generate dataset.json files from ~/.8cut.db history.
|
||||||
|
|
||||||
For each export folder that has records in the database, writes (or merges
|
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
|
into) a dataset.json with path (relative to the folder) and label per clip.
|
||||||
a <clip>.fps.txt sidecar exists alongside the WAV file.
|
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
python tools/migrate_dataset_json.py [--dry-run]
|
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]
|
return [dict(r) for r in rows]
|
||||||
|
|
||||||
|
|
||||||
def read_fps_sidecar(output_path: str) -> float | None:
|
def build_entries_for_folder(folder: str, records: list[dict]) -> list[dict]:
|
||||||
"""Read fps from <output_path>.fps.txt if it exists."""
|
return [
|
||||||
sidecar = output_path + ".fps.txt"
|
{"path": os.path.relpath(rec["output_path"], folder), "label": rec["label"]}
|
||||||
if os.path.exists(sidecar):
|
for rec in records
|
||||||
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 merge_into_json(json_path: str, new_entries: list[dict], dry_run: bool) -> int:
|
def merge_into_json(json_path: str, new_entries: list[dict], dry_run: bool) -> int:
|
||||||
|
|||||||
Reference in New Issue
Block a user