| """ |
| 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 |
|
|
| |
| 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", |
| ] |
|
|
| |
| 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() |
|
|
| |
| import gearcut_compiler as gc |
|
|
| _MODEL = None |
| def get_model(): |
| global _MODEL |
| if _MODEL is None: |
| from gearcut_infer import GearCutModel |
| _MODEL = GearCutModel() |
| return _MODEL |
|
|
|
|
| |
| 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 |
|
|
|
|
| |
| 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() |
|
|