Download training/scripts/assign_buckets.py from AdwolfCzar/minih33-loop-i2v: direct link, hf CLI and curl.
- Browser
- Download file 4.33 kB
-
https://huggingface.co/AdwolfCzar/minih33-loop-i2v/resolve/main/training/scripts/assign_buckets.py
- Command line
-
hf download hf://AdwolfCzar/minih33-loop-i2v/training/scripts/assign_buckets.py
-
curl -L -o assign_buckets.py https://huggingface.co/AdwolfCzar/minih33-loop-i2v/resolve/main/training/scripts/assign_buckets.py
4.33 kB
| #!/usr/bin/env python3 | |
| """Stratified multi-resolution bucket assignment for normalized clips. | |
| Creates non-destructive symlink views (small/medium resolutions per user directive): | |
| train_quick/v288 -> [512,288] ~55% | |
| train_quick/v384 -> [640,384] ~28% | |
| train_quick/v480 -> [832,480] ~17% | |
| Constraint: a clip is only eligible for a bucket whose target area it can | |
| cover without upscaling. Deterministic (seed 42). Writes a manifest CSV. | |
| """ | |
| import csv | |
| import fractions | |
| import json | |
| import random | |
| import subprocess | |
| import sys | |
| from concurrent.futures import ProcessPoolExecutor | |
| from pathlib import Path | |
| SRC = Path("/workspace/datasets/mega_24_ext") | |
| DST = Path("/workspace/datasets/train_quick") | |
| MANIFEST = Path("/workspace/projects/h3_loop/configs/bucket_manifest_quick.csv") | |
| BUCKETS = { # name -> (w, h, share) | |
| "v288": (512, 288, 0.55), | |
| "v384": (640, 384, 0.28), | |
| "v480": (832, 480, 0.17), | |
| } | |
| def probe(path: Path): | |
| out = subprocess.run( | |
| ["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries", | |
| "stream=width,height,nb_frames", "-of", "json", str(path)], | |
| capture_output=True, text=True, timeout=120) | |
| st = json.loads(out.stdout)["streams"][0] | |
| return path.name, int(st["width"]), int(st["height"]), int(st.get("nb_frames") or 0) | |
| def main(): | |
| videos = sorted(SRC.glob("*.mp4")) | |
| print(f"{len(videos)} normalized videos") | |
| with ProcessPoolExecutor(max_workers=30) as ex: | |
| info = list(ex.map(probe, videos, chunksize=16)) | |
| rng = random.Random(42) | |
| areas = {name: w * h for name, (w, h, _) in BUCKETS.items()} | |
| total = len(info) | |
| quota = {name: int(total * share) for name, (_, _, share) in BUCKETS.items()} | |
| # fill rounding remainder into v384 | |
| quota["v288"] += total - sum(quota.values()) | |
| # group by source resolution | |
| groups = {} | |
| for name, w, h, nb in info: | |
| groups.setdefault((w, h), []).append(name) | |
| for g in groups.values(): | |
| rng.shuffle(g) | |
| assign = {} | |
| counts = {b: 0 for b in BUCKETS} | |
| def eligible(w, h): | |
| return [b for b in BUCKETS if w * h >= areas[b]] | |
| # 1) clips eligible for only one bucket go there | |
| flexible = [] | |
| for (w, h), names in sorted(groups.items()): | |
| el = eligible(w, h) | |
| if not el: | |
| for n in names: | |
| assign[n] = "v288" # tiny source: still train, bucket_no_upscale protects it | |
| counts["v288"] += 1 | |
| elif len(el) == 1: | |
| for n in names: | |
| assign[n] = el[0] | |
| counts[el[0]] += 1 | |
| else: | |
| flexible.extend((n, w, h) for n in names) | |
| # 2) flexible clips: fill scarcest-eligibility buckets first (v576 needs high-res) | |
| rng.shuffle(flexible) | |
| for bucket in ("v480", "v384", "v288"): | |
| need = quota[bucket] - counts[bucket] | |
| if need <= 0: | |
| continue | |
| rest = [] | |
| for n, w, h in flexible: | |
| if need > 0 and bucket in eligible(w, h) and n not in assign: | |
| assign[n] = bucket | |
| counts[bucket] += 1 | |
| need -= 1 | |
| else: | |
| rest.append((n, w, h)) | |
| flexible = rest | |
| # leftovers -> largest eligible bucket below its quota, else v384 | |
| for n, w, h in flexible: | |
| if n in assign: | |
| continue | |
| el = eligible(w, h) | |
| assign[n] = el[-1] if el else "v288" | |
| counts[assign[n]] += 1 | |
| print("assignment:", counts) | |
| for b in BUCKETS: | |
| d = DST / b | |
| d.mkdir(parents=True, exist_ok=True) | |
| made = 0 | |
| for name, bucket in assign.items(): | |
| src_mp4 = SRC / name | |
| src_txt = src_mp4.with_suffix(".txt") | |
| for s in (src_mp4, src_txt): | |
| link = DST / bucket / s.name | |
| if not link.exists() and s.exists(): | |
| link.symlink_to(s) | |
| made += 1 | |
| print(f"{made} symlinks created") | |
| nbmap = {name: (w, h, nb) for name, w, h, nb in info} | |
| with open(MANIFEST, "w", newline="") as f: | |
| wr = csv.writer(f) | |
| wr.writerow(["file", "bucket", "src_width", "src_height", "src_frames_24fps"]) | |
| for name in sorted(assign): | |
| w, h, nb = nbmap[name] | |
| wr.writerow([name, assign[name], w, h, nb]) | |
| print(f"manifest: {MANIFEST}") | |
| if __name__ == "__main__": | |
| main() | |