File size: 6,982 Bytes
d4b7504 ebe5f6f d4b7504 ebe5f6f d4b7504 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | """
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()
|