| """mise — the film-structure reference finder, live. |
| |
| Upload a clip (or browse the public-domain atlas) and find films that are structurally kin — PER AXIS, |
| because the axes are measured near-independent (tone vs rhythm distance correlation: 0.11). There is no |
| single "style similarity" and this instrument refuses to invent one: a work can share a palette with one film |
| and a cutting rhythm with another, and both facts are true. |
| |
| Design law (mise): descriptive only. No scores, no rankings of quality, no "correct" anything. |
| """ |
| import os, pickle |
| import numpy as np |
| import matplotlib |
| matplotlib.use("Agg") |
| import matplotlib.pyplot as plt |
| import gradio as gr |
| from huggingface_hub import hf_hub_download |
|
|
| import mise |
| from mise.aggregate import artist_signature |
|
|
| AXES = {"A": "composition — where visual mass sits", |
| "E": "tone — light, warmth, palette", |
| "D": "rhythm — the cut", |
| "B": "staging — bodies in the frame", |
| "M": "motion — the camera's gesture"} |
|
|
| BG, INK, ACC = "#0e0d0b", "#e8e4da", "#c8a24a" |
|
|
| |
| _local = os.path.join(os.path.dirname(__file__), "atlas_v2.pkl") |
| if os.path.exists(_local): |
| _d = pickle.load(open(_local, "rb")) |
| else: |
| _p = hf_hub_download("Chucks90/football-gsr-data", "mise_corpus/atlas_v2.pkl", repo_type="dataset") |
| _d = pickle.load(open(_p, "rb")) |
| SIGS, META = _d["sigs"], _d["meta"] |
| NAMES = sorted(SIGS) |
|
|
| |
| BLOCKS = {} |
| for ax in AXES: |
| E = np.array([artist_signature(n, [SIGS[n]], axes={ax}).embedding for n in NAMES], float) |
| if E.std(0).max() > 1e-9: |
| mu, sd = E.mean(0), E.std(0) |
| sd[sd < 1e-9] = 1.0 |
| BLOCKS[ax] = (E, mu, sd) |
|
|
|
|
| def _neighbours_for(emb_by_axis, exclude=None, k=3): |
| out = {} |
| for ax, (E, mu, sd) in BLOCKS.items(): |
| if ax not in emb_by_axis: |
| continue |
| z = (E - mu) / sd |
| q = (np.array(emb_by_axis[ax], float) - mu) / sd |
| d = np.linalg.norm(z - q, axis=1) |
| order = [i for i in np.argsort(d) if NAMES[i] != exclude][:k] |
| out[ax] = [(NAMES[i], float(d[i])) for i in order] |
| return out |
|
|
|
|
| def _md(neigh, title): |
| lines = [f"### {title}", "_Each axis answers separately — there is no single 'style match'._", ""] |
| for ax, label in AXES.items(): |
| if ax not in neigh: |
| continue |
| lines.append(f"**{label}**") |
| for n, d in neigh[ax]: |
| m = META.get(n, {}) |
| lines.append(f"- {n} · *{m.get('genre', m.get('collection', ''))}* `{d:.2f}`") |
| lines.append("") |
| return "\n".join(lines) |
|
|
|
|
| def _portrait(sig, title): |
| """A small structural portrait: shot lengths coloured by tonal warmth. Description, not verdict.""" |
| shots = sig.per_shot |
| L = [s["duration"] for s in shots] |
| warm = [s["E"].warm_fraction if s.get("E") else 0.5 for s in shots] |
| key = [s["E"].key[0] if s.get("E") else 0.5 for s in shots] |
| fig, ax = plt.subplots(figsize=(9.5, 2.6), facecolor=BG) |
| ax.set_facecolor(BG) |
| cols = [(0.35 + 0.6 * w, 0.42 + 0.25 * k, 0.85 - 0.55 * w) for w, k in zip(warm, key)] |
| ax.bar(range(len(L)), L, width=0.92, color=cols) |
| ax.set_xticks([]); ax.set_yticks([]) |
| for s in ax.spines.values(): |
| s.set_visible(False) |
| ax.set_title(f"{title} — every bar a shot; height = duration, colour = tonal warmth/key", |
| color=INK, fontsize=10, pad=8) |
| fig.tight_layout() |
| return fig |
|
|
|
|
| def browse(name): |
| sig = SIGS[name] |
| emb = {ax: artist_signature(name, [sig], axes={ax}).embedding for ax in BLOCKS} |
| return _portrait(sig, name), _md(_neighbours_for(emb, exclude=name), f"structural kin of “{name}”") |
|
|
|
|
| def analyze(file): |
| if not file: |
| raise gr.Error("Upload a clip first — a scene or a whole film both work.") |
| path = file if isinstance(file, str) else file.name |
| tl, _ = mise.analyze_windows(path, n_windows=3, frames_per_window=300, motion=True, max_width=560) |
| if len(tl.shots) < 3: |
| raise gr.Error(f"Only {len(tl.shots)} shots detected — the clip may be too short or a single take.") |
| sig = mise.compose_signature(tl) |
| emb = {ax: artist_signature("you", [sig], axes={ax}).embedding for ax in BLOCKS if ax != "B"} |
| md = _md(_neighbours_for(emb), "films structurally kin to your clip") |
| md += ("\n_Staging (B) is compared only within the atlas — the upload path skips person detection to stay " |
| "fast._\n\n_Your clip is analysed in memory and not stored._") |
| return _portrait(sig, "your clip"), md |
|
|
|
|
| with gr.Blocks(title="mise — reference finder") as demo: |
| gr.Markdown("# mise — find your film's structural kin\n" |
| "An instrument, not a judge: it reads **composition · tone · rhythm · staging · motion** as " |
| "separate axes (they are measured near-independent) and finds public-domain films that share " |
| "each one. *Palette like yours, cutting unlike yours — both answers, separately.*") |
| with gr.Tab("Your clip"): |
| with gr.Row(): |
| with gr.Column(scale=3): |
| up_plot = gr.Plot(label="") |
| up_file = gr.File(label="video clip", file_types=["video"], type="filepath") |
| up_btn = gr.Button("read the structure", variant="primary") |
| with gr.Column(scale=2): |
| up_md = gr.Markdown() |
| up_btn.click(analyze, [up_file], [up_plot, up_md]) |
| with gr.Tab("Browse the atlas"): |
| with gr.Row(): |
| with gr.Column(scale=3): |
| b_plot = gr.Plot(label="") |
| b_name = gr.Dropdown(NAMES, value=NAMES[0], label=f"{len(NAMES)} public-domain films") |
| with gr.Column(scale=2): |
| b_md = gr.Markdown() |
| b_name.change(browse, [b_name], [b_plot, b_md]) |
| demo.load(browse, [b_name], [b_plot, b_md]) |
|
|
| if __name__ == "__main__": |
| demo.launch(theme=gr.themes.Base(primary_hue="amber", neutral_hue="stone")) |
|
|