AdwolfCzar commited on
Commit
b0e217e
·
verified ·
1 Parent(s): cbfd144

Upload training/scripts/assign_buckets.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. training/scripts/assign_buckets.py +132 -0
training/scripts/assign_buckets.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Stratified multi-resolution bucket assignment for normalized clips.
3
+
4
+ Creates non-destructive symlink views (small/medium resolutions per user directive):
5
+ train_quick/v288 -> [512,288] ~55%
6
+ train_quick/v384 -> [640,384] ~28%
7
+ train_quick/v480 -> [832,480] ~17%
8
+
9
+ Constraint: a clip is only eligible for a bucket whose target area it can
10
+ cover without upscaling. Deterministic (seed 42). Writes a manifest CSV.
11
+ """
12
+ import csv
13
+ import fractions
14
+ import json
15
+ import random
16
+ import subprocess
17
+ import sys
18
+ from concurrent.futures import ProcessPoolExecutor
19
+ from pathlib import Path
20
+
21
+ SRC = Path("/workspace/datasets/mega_24_ext")
22
+ DST = Path("/workspace/datasets/train_quick")
23
+ MANIFEST = Path("/workspace/projects/h3_loop/configs/bucket_manifest_quick.csv")
24
+
25
+ BUCKETS = { # name -> (w, h, share)
26
+ "v288": (512, 288, 0.55),
27
+ "v384": (640, 384, 0.28),
28
+ "v480": (832, 480, 0.17),
29
+ }
30
+
31
+
32
+ def probe(path: Path):
33
+ out = subprocess.run(
34
+ ["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries",
35
+ "stream=width,height,nb_frames", "-of", "json", str(path)],
36
+ capture_output=True, text=True, timeout=120)
37
+ st = json.loads(out.stdout)["streams"][0]
38
+ return path.name, int(st["width"]), int(st["height"]), int(st.get("nb_frames") or 0)
39
+
40
+
41
+ def main():
42
+ videos = sorted(SRC.glob("*.mp4"))
43
+ print(f"{len(videos)} normalized videos")
44
+ with ProcessPoolExecutor(max_workers=30) as ex:
45
+ info = list(ex.map(probe, videos, chunksize=16))
46
+
47
+ rng = random.Random(42)
48
+ areas = {name: w * h for name, (w, h, _) in BUCKETS.items()}
49
+ total = len(info)
50
+ quota = {name: int(total * share) for name, (_, _, share) in BUCKETS.items()}
51
+ # fill rounding remainder into v384
52
+ quota["v288"] += total - sum(quota.values())
53
+
54
+ # group by source resolution
55
+ groups = {}
56
+ for name, w, h, nb in info:
57
+ groups.setdefault((w, h), []).append(name)
58
+ for g in groups.values():
59
+ rng.shuffle(g)
60
+
61
+ assign = {}
62
+ counts = {b: 0 for b in BUCKETS}
63
+
64
+ def eligible(w, h):
65
+ return [b for b in BUCKETS if w * h >= areas[b]]
66
+
67
+ # 1) clips eligible for only one bucket go there
68
+ flexible = []
69
+ for (w, h), names in sorted(groups.items()):
70
+ el = eligible(w, h)
71
+ if not el:
72
+ for n in names:
73
+ assign[n] = "v288" # tiny source: still train, bucket_no_upscale protects it
74
+ counts["v288"] += 1
75
+ elif len(el) == 1:
76
+ for n in names:
77
+ assign[n] = el[0]
78
+ counts[el[0]] += 1
79
+ else:
80
+ flexible.extend((n, w, h) for n in names)
81
+
82
+ # 2) flexible clips: fill scarcest-eligibility buckets first (v576 needs high-res)
83
+ rng.shuffle(flexible)
84
+ for bucket in ("v480", "v384", "v288"):
85
+ need = quota[bucket] - counts[bucket]
86
+ if need <= 0:
87
+ continue
88
+ rest = []
89
+ for n, w, h in flexible:
90
+ if need > 0 and bucket in eligible(w, h) and n not in assign:
91
+ assign[n] = bucket
92
+ counts[bucket] += 1
93
+ need -= 1
94
+ else:
95
+ rest.append((n, w, h))
96
+ flexible = rest
97
+ # leftovers -> largest eligible bucket below its quota, else v384
98
+ for n, w, h in flexible:
99
+ if n in assign:
100
+ continue
101
+ el = eligible(w, h)
102
+ assign[n] = el[-1] if el else "v288"
103
+ counts[assign[n]] += 1
104
+
105
+ print("assignment:", counts)
106
+
107
+ for b in BUCKETS:
108
+ d = DST / b
109
+ d.mkdir(parents=True, exist_ok=True)
110
+ made = 0
111
+ for name, bucket in assign.items():
112
+ src_mp4 = SRC / name
113
+ src_txt = src_mp4.with_suffix(".txt")
114
+ for s in (src_mp4, src_txt):
115
+ link = DST / bucket / s.name
116
+ if not link.exists() and s.exists():
117
+ link.symlink_to(s)
118
+ made += 1
119
+ print(f"{made} symlinks created")
120
+
121
+ nbmap = {name: (w, h, nb) for name, w, h, nb in info}
122
+ with open(MANIFEST, "w", newline="") as f:
123
+ wr = csv.writer(f)
124
+ wr.writerow(["file", "bucket", "src_width", "src_height", "src_frames_24fps"])
125
+ for name in sorted(assign):
126
+ w, h, nb = nbmap[name]
127
+ wr.writerow([name, assign[name], w, h, nb])
128
+ print(f"manifest: {MANIFEST}")
129
+
130
+
131
+ if __name__ == "__main__":
132
+ main()