You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
136 lines
4.6 KiB
Python
136 lines
4.6 KiB
Python
import argparse
|
|
import csv
|
|
import shutil
|
|
import zipfile
|
|
from pathlib import Path
|
|
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser(
|
|
description="Package extracted frame folders into per-video ZIP archives for CVAT."
|
|
)
|
|
parser.add_argument("--frames-root", required=True, help="Root with extracted frames, e.g. D:\\MAI_CVAT_frames_s15\\frames_all")
|
|
parser.add_argument("--output-root", required=True, help="Where ZIP archives and manifests will be written")
|
|
parser.add_argument("--group-filter", action="append", default=[], help="Optional substring filter for group names")
|
|
parser.add_argument("--max-files-per-zip", type=int, default=0, help="Optional limit; skip items above this count when > 0")
|
|
parser.add_argument("--overwrite", action="store_true")
|
|
return parser.parse_args()
|
|
|
|
|
|
def main():
|
|
args = parse_args()
|
|
frames_root = Path(args.frames_root).expanduser().resolve()
|
|
output_root = Path(args.output_root).expanduser().resolve()
|
|
|
|
if not frames_root.exists():
|
|
raise SystemExit(f"Frames root not found: {frames_root}")
|
|
|
|
filters = [s.strip().lower() for s in args.group_filter if s.strip()]
|
|
zip_root = output_root / "zips"
|
|
zip_root.mkdir(parents=True, exist_ok=True)
|
|
|
|
rows = []
|
|
video_dirs = list(find_video_dirs(frames_root))
|
|
for video_dir in video_dirs:
|
|
rel_parts = video_dir.relative_to(frames_root).parts
|
|
if len(rel_parts) < 5:
|
|
continue
|
|
|
|
split, role, group_name, video_name, interval_name = rel_parts[:5]
|
|
if filters and not any(token in group_name.lower() for token in filters):
|
|
continue
|
|
|
|
files = sorted(
|
|
[
|
|
p for p in video_dir.iterdir()
|
|
if p.is_file() and p.suffix.lower() in {".jpg", ".jpeg", ".png"}
|
|
]
|
|
)
|
|
file_count = len(files)
|
|
if file_count == 0:
|
|
continue
|
|
if args.max_files_per_zip > 0 and file_count > args.max_files_per_zip:
|
|
continue
|
|
|
|
safe_group = sanitize(group_name)
|
|
safe_video = sanitize(video_name)
|
|
safe_interval = sanitize(interval_name)
|
|
zip_name = f"{safe_group}__{safe_video}__{safe_interval}.zip"
|
|
zip_path = zip_root / zip_name
|
|
|
|
if zip_path.exists():
|
|
if args.overwrite:
|
|
zip_path.unlink()
|
|
else:
|
|
rows.append(build_row(split, role, group_name, video_name, interval_name, file_count, zip_path, video_dir))
|
|
continue
|
|
|
|
with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_STORED) as zf:
|
|
for file_path in files:
|
|
zf.write(file_path, arcname=file_path.name)
|
|
|
|
rows.append(build_row(split, role, group_name, video_name, interval_name, file_count, zip_path, video_dir))
|
|
|
|
manifest_path = output_root / "cvat_task_packages.csv"
|
|
write_csv(
|
|
manifest_path,
|
|
rows,
|
|
[
|
|
"split",
|
|
"role",
|
|
"group_name",
|
|
"video_name",
|
|
"interval_name",
|
|
"frame_count",
|
|
"zip_path",
|
|
"source_dir",
|
|
],
|
|
)
|
|
print(f"Wrote {len(rows)} package rows to {manifest_path}")
|
|
print(f"ZIP archives are in {zip_root}")
|
|
|
|
|
|
def find_video_dirs(frames_root: Path):
|
|
for split_dir in sorted(p for p in frames_root.iterdir() if p.is_dir()):
|
|
for role_dir in sorted(p for p in split_dir.iterdir() if p.is_dir()):
|
|
for group_dir in sorted(p for p in role_dir.iterdir() if p.is_dir()):
|
|
for video_dir in sorted(p for p in group_dir.iterdir() if p.is_dir()):
|
|
for interval_dir in sorted(p for p in video_dir.iterdir() if p.is_dir()):
|
|
yield interval_dir
|
|
|
|
|
|
def sanitize(value: str):
|
|
out = []
|
|
for ch in value:
|
|
if ch in '<>:"/\\|?*':
|
|
out.append("_")
|
|
else:
|
|
out.append(ch)
|
|
return "".join(out).strip().rstrip(".")
|
|
|
|
|
|
def build_row(split, role, group_name, video_name, interval_name, file_count, zip_path: Path, source_dir: Path):
|
|
return {
|
|
"split": split,
|
|
"role": role,
|
|
"group_name": group_name,
|
|
"video_name": video_name,
|
|
"interval_name": interval_name,
|
|
"frame_count": file_count,
|
|
"zip_path": str(zip_path),
|
|
"source_dir": str(source_dir),
|
|
}
|
|
|
|
|
|
def write_csv(path: Path, rows, fieldnames):
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
with path.open("w", newline="", encoding="utf-8-sig") as f:
|
|
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
|
writer.writeheader()
|
|
for row in rows:
|
|
writer.writerow(row)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|