GearCut / app.py
ameforge's picture
app.py: bootstrap modules from private repo at startup
ebe5f6f verified
"""
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()