import numpy as np, glob from PIL import Image files = sorted(glob.glob("an/f*.png")) rgb = [np.asarray(Image.open(f).convert("RGB")) for f in files] N = len(rgb) print(f"frames={N} size={rgb[0].shape}") # --- 1. duplicate / twos detection on the SOURCE --- exact = 0; near = 0; diffs = [] for i in range(1, N): d = np.abs(rgb[i].astype(np.int16) - rgb[i-1].astype(np.int16)) m = d.mean() diffs.append(m) if m == 0: exact += 1 elif m < 1.0: near += 1 print(f"\n--- source frame-to-frame (24fps) ---") print(f"exact duplicates : {exact}/{N-1} ({100*exact/(N-1):.1f}%)") print(f"near-dup (<1.0) : {near}/{N-1} ({100*near/(N-1):.1f}%)") print(f"mean abs diff : {np.mean(diffs):.2f}") # --- 2. build a per-scene 256-color palette from the whole clip --- sample = np.concatenate([rgb[i].reshape(-1,3) for i in range(0,N,4)]) pal_img = Image.fromarray(sample.reshape(-1,1,3).astype(np.uint8)) pal = pal_img.quantize(colors=256, method=Image.MEDIANCUT, dither=Image.NONE) palette = pal.getpalette()[:768] ref = Image.new("P", (1,1)); ref.putpalette(palette) idx = [] for a in rgb: q = Image.fromarray(a).quantize(palette=ref, dither=Image.FLOYDSTEINBERG) idx.append(np.asarray(q, dtype=np.uint8)) # quantization error err = np.mean([np.abs(np.asarray(Image.fromarray(idx[i]).convert("P")) ) for i in range(0)]) if False else None # --- 3. drop duplicate frames -> unique frame stream --- keep = [0] for i in range(1, N): if not np.array_equal(idx[i], idx[keep[-1]]): keep.append(i) print(f"\n--- after 256-color quantize + dedupe ---") print(f"unique frames : {len(keep)}/{N} -> effective {len(keep)/12.0:.1f} fps") # --- 4. delta sparsity between consecutive UNIQUE frames --- changed_pct = [] for j in range(1, len(keep)): a, b = idx[keep[j-1]], idx[keep[j]] changed_pct.append(100.0*np.count_nonzero(a != b)/a.size) print(f"pixels changed : mean {np.mean(changed_pct):.1f}% median {np.median(changed_pct):.1f}% p90 {np.percentile(changed_pct,90):.1f}% max {np.max(changed_pct):.1f}%") # --- 5. estimate compressed size: row-span delta + RLE within span --- def encode_size(a, b, gap=4): total = 0 H, W = a.shape for y in range(H): ra, rb = a[y], b[y] diff = np.nonzero(ra != rb)[0] if len(diff) == 0: continue # merge runs separated by < gap spans = []; s = diff[0]; p = diff[0] for x in diff[1:]: if x - p > gap: spans.append((s,p)); s = x p = x spans.append((s,p)) total += 2 # row header: y + span count for (s0,e0) in spans: seg = rb[s0:e0+1] total += 2 # x start + length # RLE within segment i2 = 0; cost = 0 while i2 < len(seg): run = 1 while i2+run < len(seg) and seg[i2+run] == seg[i2] and run < 127: run += 1 cost += 2 if run >= 3 else run i2 += run total += cost return total sizes = [encode_size(idx[keep[j-1]], idx[keep[j]]) for j in range(1, len(keep))] print(f"\n--- codec estimate (row-span delta + RLE) ---") print(f"delta frame bytes: mean {np.mean(sizes):.0f} median {np.median(sizes):.0f} p90 {np.percentile(sizes,90):.0f} max {np.max(sizes):.0f}") raw = 256*192 print(f"vs raw {raw} B/frame -> mean ratio {raw/np.mean(sizes):.1f}:1") fps_eff = len(keep)/12.0 byterate = np.mean(sizes)*fps_eff print(f"\nstream rate : {byterate/1024:.1f} KB/s") print(f"22 min extrapol. : {byterate*22*60/1048576:.0f} MB video") # cycle cost: ~1 word write per changed pixel, movem amortized mean_changed_px = np.mean(changed_pct)/100*raw print(f"mean changed px : {mean_changed_px:.0f} -> blit ~{mean_changed_px*6.5/1000:.0f}k cycles (budget 833k)")