""" GearCut — Gradio app for HuggingFace Spaces. Deploy on a Space (AMFORGE) so anyone can try GearCut in the browser. The model (gc_editor) and tokenizer are pulled from the AMFORGE HF repos at startup. ffmpeg is provided by packages.txt. Core modules are loaded from a private HF repo at startup using HF_TOKEN secret. Visitors can use the demo but cannot access the proprietary source code. Local run: python app.py """ import os import sys import json import uuid # ---- Bootstrap: download proprietary modules from private HF repo ----------- def _bootstrap_modules(): """Download gearcut_* modules from private AMFORGE/gearcut-core repo.""" token = os.environ.get("HF_TOKEN") if not token: print("[GearCut] WARNING: HF_TOKEN not set — assuming modules are local.") return modules = [ "gearcut_compiler.py", "gearcut_model.py", "gearcut_infer.py", "gearcut_ground.py", ] # Check if already downloaded if all(os.path.exists(m) for m in modules): print("[GearCut] Core modules already present.") return print("[GearCut] Downloading core modules from private repo...") try: from huggingface_hub import hf_hub_download for fname in modules: if not os.path.exists(fname): path = hf_hub_download( repo_id="AMFORGE/gearcut-core", filename=fname, repo_type="dataset", token=token, local_dir=".", ) print(f"[GearCut] ✓ {fname} downloaded") else: print(f"[GearCut] ✓ {fname} already exists") except Exception as e: print(f"[GearCut] ERROR downloading modules: {e}") sys.exit(1) _bootstrap_modules() # ---- Now import the proprietary modules ------------------------------------ import gearcut_compiler as gc _MODEL = None def get_model(): global _MODEL if _MODEL is None: from gearcut_infer import GearCutModel # lazy: only when first used (needs torch) _MODEL = GearCutModel() return _MODEL # ---- core orchestration (no Gradio here, so it stays unit-testable) --------- def process(video_paths, instruction, preset_override=None, model=None): """Returns (output_video_path_or_None, operations_json_str, status_markdown).""" if not video_paths: return None, "", "⚠️ Upload at least one video." if not (instruction or "").strip(): return None, "", "⚠️ Type an instruction, e.g. *remove the first 3 seconds and export as out.mp4*." specs, ctx = [], [] for i, p in enumerate(video_paths, 1): dur = gc.probe_duration(p) if not dur: return None, "", f"⚠️ Couldn't read **{os.path.basename(p)}** — is it a valid video?" cid = f"c{i}" specs.append((cid, p, 0.0, dur)) ctx.append((cid, os.path.basename(p), 0.0, dur)) project = gc.project_from_clips(specs) try: ops, repairs, raw = (model or get_model()).operations(instruction, ctx) except Exception as e: return None, "", f"❌ Model error: {e}" if ops is None: return None, "", f"❌ Couldn't parse the model output:\n```\n{raw}\n```" if not ops: notes = "\n".join(f"- {r}" for r in repairs) return None, "[]", "⚠️ GearCut misread that — nothing safe to do. Try rephrasing.\n\n" + notes try: gc.apply_all(project, ops) if project.export is None: project.export = {"path": "out.mp4", "preset": "youtube_1080p"} if preset_override and preset_override in gc.PRESETS: project.export["preset"] = preset_override os.makedirs("gc_outputs", exist_ok=True) project.export["path"] = os.path.join("gc_outputs", f"{uuid.uuid4().hex}.mp4") gc.render(project) except gc.OpError as e: return None, json.dumps(ops, indent=2), f"❌ Could not apply the edit: {e}" except Exception as e: return None, json.dumps(ops, indent=2), f"❌ Render failed: {e}" status = "✅ **Done.**\n\n```\n" + gc.describe(project) + "\n```" if repairs: status += "\n\n**Safety adjustments (grounding):**\n" + "\n".join(f"- {r}" for r in repairs) return project.export["path"], json.dumps(ops, indent=2), status # ---- Gradio UI -------------------------------------------------------------- def build_ui(): import gradio as gr EXAMPLES = [ "remove the first 3 seconds and export as out.mp4", "keep only 10 to 45 of clip 1 and export as clip.mp4", "cut the last 20 seconds off clip 1", "split clip 1 at 30 seconds", "export in tiktok_vertical", ] PRESET_CHOICES = ["(from instruction)"] + list(gc.PRESETS.keys()) def _run(files, instruction, preset): paths = [f if isinstance(f, str) else getattr(f, "name", None) for f in (files or [])] paths = [p for p in paths if p] override = None if preset in (None, "(from instruction)") else preset return process(paths, instruction, override) theme = gr.themes.Soft(primary_hue="indigo", secondary_hue="violet") with gr.Blocks(title="GearCut", theme=theme) as demo: gr.Markdown( "# 🎬 GearCut\n" "**Edit video with plain language.** Type what you want — GearCut figures out the cuts " "and renders the result. Lightweight, FFmpeg-based, powered by the in-house **gc_editor** model (AMFORGE).") with gr.Row(): with gr.Column(scale=1): videos = gr.File(label="Video(s) — order = sequence on the timeline", file_count="multiple", file_types=["video"], type="filepath") instruction = gr.Textbox(label="What do you want to do?", placeholder="remove the first 3 seconds and export as out.mp4", lines=2) preset = gr.Dropdown(PRESET_CHOICES, value="(from instruction)", label="Output format") run = gr.Button("✂️ Edit", variant="primary") gr.Examples(EXAMPLES, inputs=instruction, label="Try an instruction") with gr.Column(scale=1): result = gr.Video(label="Result") status = gr.Markdown() ops = gr.Code(label="Operations (what the model produced)", language="json") gr.Markdown( "**Supported (v1):** trim (start / end / range), split, join several clips, delete, reorder, export. \n" "**Presets:** youtube_1080p · tiktok_vertical · instagram_square · web_720p · master_4k. \n" "*Transitions, text overlays and effects are coming in v2.*") run.click(_run, [videos, instruction, preset], [result, ops, status]) return demo if __name__ == "__main__": build_ui().launch()