> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bfl.ml/llms.txt
> Use this file to discover all available pages before exploring further.

# FLUX Video Edit

> Edit an existing clip with a prompt: remove, add or replace objects, change the setting, restyle, change text or dialogue.

export const VideoEditShowcase = ({source = {}, edits = [], stackable = [], results = [], aspectRatio = "16 / 9", title, compact = false, request = null, defaultOverlay = false}) => {
  const makeLogic = ({source = {}, edits = [], stackable = [], results = [], request = null} = {}) => {
    const list = Array.isArray(edits) ? edits : [];
    const stack = (Array.isArray(stackable) ? stackable : []).map(String);
    const rows = Array.isArray(results) ? results : [];
    const keyOf = (e, i) => e && e.key != null ? String(e.key) : String(i);
    const allKeys = list.map(keyOf);
    const findEdit = k => list.find((e, i) => keyOf(e, i) === String(k)) || null;
    const isStackable = k => stack.includes(String(k));
    const sameSet = (a, b) => a.length === b.length && a.every(k => b.includes(k));
    const findResult = sel => rows.find(r => sameSet((r && Array.isArray(r.keys) ? r.keys : []).map(String), sel)) || null;
    const mediaUrl = v => typeof v === "string" && v.trim() ? v.trim() : null;
    const segmentsOf = e => {
      if (e && Array.isArray(e.segments) && e.segments.length) return e.segments;
      if (e && typeof e.prompt === "string" && e.prompt) return [{
        t: e.prompt
      }];
      return [];
    };
    const textOf = segs => Array.isArray(segs) ? segs.map(s => s && s.t != null ? String(s.t) : "").join("") : "";
    const resolveSegments = (fallback, r) => {
      if (r && Array.isArray(r.segments) && r.segments.length) return r.segments;
      if (r && typeof r.prompt === "string" && r.prompt) return [{
        t: r.prompt
      }];
      return fallback;
    };
    const normalise = selected => (Array.isArray(selected) ? selected : []).map(String).filter(k => findEdit(k));
    const itemFor = selected => {
      const sel = normalise(selected);
      if (!sel.length) return null;
      if (sel.length === 1) {
        const e = findEdit(sel[0]);
        const r = findResult(sel);
        const merged = {
          ...e,
          ...r || ({})
        };
        return {
          ...merged,
          label: e.label,
          segments: resolveSegments(segmentsOf(e), r),
          speech: r && r.speech || e.speech || null,
          video: mediaUrl(merged.video),
          poster: mediaUrl(merged.poster),
          note: r && r.note || e.note || null
        };
      }
      const ordered = stack.filter(k => sel.includes(k));
      const parts = ordered.map(findEdit).filter(Boolean);
      const r = findResult(sel) || ({});
      const joined = parts.flatMap((p, i) => {
        const segs = segmentsOf(p);
        return i === 0 ? segs : [{
          t: " "
        }, ...segs];
      });
      const labelOrder = [sel[0], ...ordered.filter(k => k !== sel[0])];
      return {
        ...r,
        label: r.label || labelOrder.map(findEdit).filter(Boolean).map(p => p.label).join(" + "),
        segments: resolveSegments(joined, r),
        speech: r.speech || (parts.find(p => p.speech) || ({})).speech || null,
        video: mediaUrl(r.video),
        poster: mediaUrl(r.poster),
        note: r.note || null
      };
    };
    const promptFor = selected => textOf((itemFor(selected) || ({})).segments);
    const hasClipFor = selected => Boolean((itemFor(selected) || ({})).video);
    const canAdd = (selected, k) => {
      const sel = normalise(selected);
      return sel.length > 0 && !sel.includes(String(k)) && isStackable(k) && sel.every(isStackable);
    };
    const addonsFor = selected => {
      const sel = normalise(selected);
      const base = sel[0];
      if (!base || !isStackable(base)) return [];
      return stack.filter(k => k !== base && findEdit(k));
    };
    const pickBase = k => findEdit(k) ? [String(k)] : [];
    const toggleAddon = (selected, k) => {
      const sel = normalise(selected);
      const key = String(k);
      if (sel.includes(key)) return sel.length > 1 ? sel.filter(x => x !== key) : sel;
      return canAdd(sel, key) ? [...sel, key] : sel;
    };
    const shellQuote = s => "'" + String(s).split("'").join("'\\''") + "'";
    const requestFor = selected => {
      if (!request) return "";
      const fields = request.fields || ({});
      const body = {
        video: mediaUrl(request.source) || mediaUrl(source && source.video) || "<URL of the source clip>",
        prompt: promptFor(selected)
      };
      if (fields.safety_tolerance !== undefined) body.safety_tolerance = fields.safety_tolerance;
      const json = JSON.stringify(body, null, 2).replace(/\n/g, "\n  ");
      const endpoint = mediaUrl(request.endpoint) || "https://api.bfl.ai/v1/flux-tools/video-edit-v1";
      return "curl -X POST " + endpoint + ' \\\n  -H "x-key: $BFL_API_KEY"' + ' \\\n  -H "Content-Type: application/json"' + " \\\n  -d " + shellQuote(json);
    };
    const safeDuration = d => typeof d === "number" && isFinite(d) && d > 0 ? d : 0;
    const clampTime = (t, d) => {
      const max = safeDuration(d);
      const v = typeof t === "number" && isFinite(t) ? t : 0;
      if (!max) return 0;
      return Math.max(0, Math.min(max, v));
    };
    const sliderMax = d => safeDuration(d) || 1;
    const fmt = s => {
      const n = typeof s === "number" && isFinite(s) && s > 0 ? Math.floor(s) : 0;
      return Math.floor(n / 60) + ":" + String(n % 60).padStart(2, "0");
    };
    const safeAspect = a => typeof a === "string" && (/^\s*\d+(?:\.\d+)?\s*\/\s*\d+(?:\.\d+)?\s*$/).test(a) ? a.trim() : "16 / 9";
    const statusFor = (status, id) => status && status.id === id && status.state ? status.state : "loading";
    const DIFF_W = 192;
    const DIFF_H = 108;
    const pixelDiff = (a, b, i) => Math.abs(a[i] - b[i]) + Math.abs(a[i + 1] - b[i + 1]) + Math.abs(a[i + 2] - b[i + 2]);
    const medianDiff = (bins, n) => {
      let acc = 0;
      for (let k = 0; k < bins.length; k += 1) {
        acc += bins[k];
        if (acc >= n / 2) return k << 3;
      }
      return 0;
    };
    const diffCut = med => Math.max(56, Math.min(160, med * 2 + 40));
    const keepAt = cnt => cnt >= 4;
    const edgeAt = cnt => cnt < 9;
    const EDGE_RGBA = [255, 200, 90, 235];
    const FILL_RGBA = [255, 140, 30, 130];
    const CLEAR_RGBA = [0, 0, 0, 0];
    const overlayPixel = cnt => !keepAt(cnt) ? CLEAR_RGBA : edgeAt(cnt) ? EDGE_RGBA : FILL_RGBA;
    const makeScratch = () => ({});
    const diffPass = (a, b, w, h, scratch, out) => {
      const n = w * h;
      const s = scratch || makeScratch();
      if (!s.diffs || s.diffs.length !== n) {
        s.diffs = new Uint16Array(n);
        s.mask = new Uint8Array(n);
        s.raw = new Uint8Array(n);
        s.prev = new Uint8Array(n);
        s.bins = new Uint32Array(96);
      }
      const diffs = s.diffs;
      const mask = s.mask;
      const bins = s.bins;
      bins.fill(0);
      for (let p = 0, i = 0; p < n; (p += 1, i += 4)) {
        const d = pixelDiff(a, b, i);
        diffs[p] = d;
        bins[Math.min(95, d >> 3)] += 1;
      }
      const med = medianDiff(bins, n);
      const cut = diffCut(med);
      const raw = s.raw;
      const prev = s.prev;
      for (let p = 0; p < n; p += 1) {
        const hit = diffs[p] > cut ? 1 : 0;
        mask[p] = hit & prev[p];
        raw[p] = hit;
      }
      s.prev = raw;
      s.raw = prev;
      let changed = 0;
      for (let y = 0; y < h; y += 1) {
        for (let x = 0; x < w; x += 1) {
          const p = y * w + x;
          let cnt = 0;
          if (mask[p]) {
            for (let dy = -1; dy <= 1; dy += 1) {
              const yy = y + dy;
              if (yy < 0 || yy >= h) continue;
              for (let dx = -1; dx <= 1; dx += 1) {
                const xx = x + dx;
                if (xx < 0 || xx >= w) continue;
                cnt += mask[yy * w + xx];
              }
            }
          }
          if (keepAt(cnt)) changed += 1;
          if (out) {
            const px = overlayPixel(cnt);
            const i = p * 4;
            out[i] = px[0];
            out[i + 1] = px[1];
            out[i + 2] = px[2];
            out[i + 3] = px[3];
          }
        }
      }
      return {
        med,
        cut,
        changed,
        pixels: n,
        scratch: s
      };
    };
    const clearScratch = scratch => {
      if (scratch && scratch.prev) scratch.prev.fill(0);
      if (scratch && scratch.raw) scratch.raw.fill(0);
      return scratch;
    };
    return {
      allKeys,
      keyOf,
      findEdit,
      isStackable,
      findResult,
      mediaUrl,
      segmentsOf,
      textOf,
      resolveSegments,
      itemFor,
      promptFor,
      hasClipFor,
      canAdd,
      addonsFor,
      pickBase,
      toggleAddon,
      shellQuote,
      requestFor,
      safeDuration,
      clampTime,
      sliderMax,
      fmt,
      safeAspect,
      statusFor,
      DIFF_W,
      DIFF_H,
      pixelDiff,
      medianDiff,
      diffCut,
      keepAt,
      edgeAt,
      overlayPixel,
      makeScratch,
      diffPass,
      clearScratch
    };
  };
  const L = makeLogic({
    source,
    edits,
    stackable,
    results,
    request
  });
  const [selected, setSelected] = useState(L.allKeys.length ? [L.allKeys[0]] : []);
  const [playing, setPlaying] = useState(true);
  const [sound, setSound] = useState(false);
  const [srcState, setSrcState] = useState("loading");
  const [outStatus, setOutStatus] = useState({
    id: null,
    state: "loading"
  });
  const [time, setTime] = useState(0);
  const [duration, setDuration] = useState(0);
  const [promptCopy, setPromptCopy] = useState("");
  const [requestCopy, setRequestCopy] = useState("");
  const [overlay, setOverlay] = useState(Boolean(defaultOverlay));
  const [overlayDead, setOverlayDead] = useState(false);
  const [corsOk, setCorsOk] = useState(true);
  const srcRef = useRef(null);
  const outRef = useRef(null);
  const itemIdRef = useRef("none");
  const soundRef = useRef(false);
  const pausedRef = useRef(false);
  soundRef.current = sound;
  const canvasRef = useRef(null);
  const workRef = useRef(null);
  const scratchRef = useRef(null);
  const imgRef = useRef(null);
  const rafRef = useRef(0);
  const lastDiffRef = useRef(0);
  const overlayRef = useRef(false);
  const deadRef = useRef(false);
  overlayRef.current = overlay;
  const DRIFT_S = 0.04;
  const SEEK_S = 0.5;
  const DIFF_MS = 80;
  const item = L.itemFor(selected);
  const itemId = selected.join("+") || "none";
  itemIdRef.current = itemId;
  const outState = L.statusFor(outStatus, itemId);
  const resultUrl = item && item.video;
  const sourceUrl = L.mediaUrl(source && source.video);
  const addons = L.addonsFor(selected);
  const base = selected[0] || "";
  const baseLabel = (L.findEdit(base) || ({})).label || "the edit above";
  const combinable = L.allKeys.filter(k => L.isStackable(k)).length > 1;
  const promptText = L.promptFor(selected);
  const requestCommand = L.requestFor(selected);
  const canOverlay = Boolean(sourceUrl && resultUrl && !overlayDead);
  const overlayOn = canOverlay && overlay;
  const resetCopy = () => {
    setPromptCopy("");
    setRequestCopy("");
  };
  const playSafe = el => {
    if (!el || !el.play) return;
    const p = el.play();
    if (p && typeof p.catch === "function") p.catch(() => {});
  };
  const seekTo = (el, t) => {
    if (!el) return;
    try {
      el.currentTime = t;
    } catch (e) {}
  };
  const resync = force => {
    const a = srcRef.current;
    const b = outRef.current;
    if (!a || !b || b.readyState < 1) return;
    const delta = b.currentTime - a.currentTime;
    if (force || Math.abs(delta) > SEEK_S) {
      seekTo(b, a.currentTime);
      b.playbackRate = 1;
    } else if (Math.abs(delta) > DRIFT_S) {
      b.playbackRate = 1 - Math.max(-0.08, Math.min(0.08, delta * 0.5));
    } else {
      b.playbackRate = 1;
    }
    if (a.paused && !b.paused) b.pause();
    if (!a.paused && b.paused) playSafe(b);
  };
  const loopPoint = () => {
    const a = srcRef.current;
    const b = outRef.current;
    const da = L.safeDuration(a && a.duration);
    const db = L.safeDuration(b && b.duration);
    if (!da || !db) return da || db;
    return Math.min(da, db);
  };
  const restart = () => {
    if (pausedRef.current) return;
    const a = srcRef.current;
    if (!a) return;
    seekTo(a, 0);
    seekTo(outRef.current, 0);
    setTime(0);
    playSafe(a);
    playSafe(outRef.current);
  };
  const ensureWork = () => {
    if (workRef.current) return workRef.current;
    if (typeof document === "undefined") return null;
    const a = document.createElement("canvas");
    const b = document.createElement("canvas");
    a.width = L.DIFF_W;
    a.height = L.DIFF_H;
    b.width = L.DIFF_W;
    b.height = L.DIFF_H;
    workRef.current = {
      ca: a.getContext("2d", {
        willReadFrequently: true
      }),
      cb: b.getContext("2d", {
        willReadFrequently: true
      })
    };
    return workRef.current;
  };
  const clearOverlay = () => {
    const cv = canvasRef.current;
    if (cv && cv.getContext) cv.getContext("2d").clearRect(0, 0, cv.width, cv.height);
  };
  const resetOverlay = () => {
    L.clearScratch(scratchRef.current);
    lastDiffRef.current = 0;
    clearOverlay();
  };
  const step = () => {
    const a = srcRef.current;
    const b = outRef.current;
    if (!overlayRef.current || deadRef.current) {
      rafRef.current = 0;
      return;
    }
    if (!a || !b || !a.isConnected || !b.isConnected) {
      rafRef.current = 0;
      return;
    }
    const now = Date.now();
    if (now - lastDiffRef.current >= DIFF_MS && !a.seeking && !b.seeking && a.readyState >= 2 && b.readyState >= 2) {
      lastDiffRef.current = now;
      const w = ensureWork();
      const cv = canvasRef.current;
      if (w && cv) {
        let A;
        let B;
        try {
          w.ca.drawImage(a, 0, 0, L.DIFF_W, L.DIFF_H);
          w.cb.drawImage(b, 0, 0, L.DIFF_W, L.DIFF_H);
          A = w.ca.getImageData(0, 0, L.DIFF_W, L.DIFF_H);
          B = w.cb.getImageData(0, 0, L.DIFF_W, L.DIFF_H);
        } catch (e) {
          deadRef.current = true;
          setOverlayDead(true);
          clearOverlay();
          rafRef.current = 0;
          return;
        }
        const ctx = cv.getContext("2d");
        if (!imgRef.current) imgRef.current = ctx.createImageData(L.DIFF_W, L.DIFF_H);
        if (!scratchRef.current) scratchRef.current = L.makeScratch();
        L.diffPass(A.data, B.data, L.DIFF_W, L.DIFF_H, scratchRef.current, imgRef.current.data);
        ctx.putImageData(imgRef.current, 0, 0);
      }
    }
    rafRef.current = requestAnimationFrame(step);
  };
  const startOverlay = () => {
    if (rafRef.current || deadRef.current || !overlayRef.current) return;
    if (typeof requestAnimationFrame === "undefined") return;
    rafRef.current = requestAnimationFrame(step);
  };
  const toggleOverlay = () => {
    const next = !overlay;
    setOverlay(next);
    overlayRef.current = next;
    resetOverlay();
    if (next) startOverlay();
  };
  const onMediaError = report => {
    if (corsOk) {
      deadRef.current = true;
      setOverlayDead(true);
      setCorsOk(false);
      return;
    }
    report();
  };
  const setSrcRef = el => {
    srcRef.current = el;
    if (!el || el.dataset.vesWired === "1") return;
    el.dataset.vesWired = "1";
    const readMeta = () => {
      setDuration(L.safeDuration(el.duration));
      setTime(el.currentTime || 0);
    };
    if (el.readyState >= 1) readMeta();
    if (el.readyState >= 3) setSrcState("ready");
    el.addEventListener("loadedmetadata", readMeta);
    el.addEventListener("durationchange", readMeta);
    el.addEventListener("timeupdate", () => {
      const end = loopPoint();
      if (!pausedRef.current && end && el.currentTime >= end - 0.12) {
        restart();
        return;
      }
      setTime(el.currentTime || 0);
      resync(false);
    });
    el.addEventListener("ended", restart);
    el.addEventListener("seeked", () => {
      setTime(el.currentTime || 0);
      resync(true);
    });
    el.addEventListener("play", () => {
      if (pausedRef.current) {
        el.pause();
        return;
      }
      setPlaying(true);
      resync(true);
      startOverlay();
    });
    el.addEventListener("pause", () => {
      setPlaying(false);
      resync(true);
    });
    el.addEventListener("canplay", () => setSrcState("ready"));
    el.addEventListener("playing", () => setSrcState("ready"));
    el.addEventListener("waiting", () => setSrcState("loading"));
    el.addEventListener("loadeddata", () => {
      resync(true);
      startOverlay();
    });
    el.addEventListener("error", () => onMediaError(() => setSrcState("error")));
  };
  const setOutRef = el => {
    outRef.current = el;
    if (!el || el.dataset.vesWired === "1") return;
    el.dataset.vesWired = "1";
    const id = itemIdRef.current;
    const mark = state => {
      if (id === itemIdRef.current) setOutStatus({
        id,
        state
      });
    };
    el.muted = !soundRef.current;
    if (el.readyState >= 3) mark("ready");
    el.addEventListener("canplay", () => mark("ready"));
    el.addEventListener("playing", () => mark("ready"));
    el.addEventListener("waiting", () => mark("loading"));
    el.addEventListener("error", () => onMediaError(() => mark("error")));
    el.addEventListener("loadeddata", () => {
      resync(true);
      startOverlay();
    });
  };
  const setCanvasRef = el => {
    canvasRef.current = el;
    if (!el) return;
    if (el.width !== L.DIFF_W || el.height !== L.DIFF_H) {
      el.width = L.DIFF_W;
      el.height = L.DIFF_H;
    }
  };
  const togglePlay = () => {
    const a = srcRef.current;
    if (!a) return;
    if (!playing) {
      pausedRef.current = false;
      playSafe(a);
    } else {
      pausedRef.current = true;
      setPlaying(false);
      a.pause();
    }
    resync(true);
  };
  const toggleSound = () => {
    const next = !sound;
    setSound(next);
    if (outRef.current) outRef.current.muted = !next;
  };
  const onSeek = e => {
    const t = L.clampTime(Number(e.target.value), duration);
    setTime(t);
    seekTo(srcRef.current, t);
    resync(true);
  };
  const onSelectBase = k => {
    resetCopy();
    resetOverlay();
    setSelected(L.pickBase(k));
  };
  const onToggleAddon = k => {
    resetCopy();
    resetOverlay();
    setSelected(L.toggleAddon(selected, k));
  };
  const copyInto = (text, set) => {
    if (!text) return;
    const clear = () => setTimeout(() => set(""), 2400);
    let p = null;
    try {
      if (navigator && navigator.clipboard && navigator.clipboard.writeText) {
        p = navigator.clipboard.writeText(text);
      }
    } catch (e) {
      p = null;
    }
    if (!p || typeof p.then !== "function") {
      set("failed");
      clear();
      return;
    }
    p.then(() => {
      set("copied");
      clear();
    }, () => {
      set("failed");
      clear();
    });
  };
  const copyLabel = (state, idle) => state === "copied" ? "Copied" : state === "failed" ? "Copy failed" : idle;
  const ORANGE = "255, 138, 40";
  const card = {
    background: "#0c0d0e",
    color: "#e8e8e8",
    borderRadius: "1.1rem",
    border: "1px solid rgba(255,255,255,0.08)",
    padding: "0.8rem",
    display: "flex",
    flexDirection: "column",
    gap: "0.7rem",
    margin: "1.25rem 0",
    boxShadow: "0 20px 60px rgba(0,0,0,0.35)"
  };
  const pane = {
    position: "relative",
    aspectRatio: L.safeAspect(aspectRatio),
    borderRadius: "0.7rem",
    overflow: "hidden",
    background: "#000"
  };
  const fill = {
    position: "absolute",
    inset: 0,
    width: "100%",
    height: "100%",
    objectFit: "cover",
    pointerEvents: "none"
  };
  const paneLabel = {
    position: "absolute",
    top: "10px",
    left: "10px",
    padding: "3px 9px",
    borderRadius: "6px",
    background: "rgba(0,0,0,0.6)",
    color: "#fff",
    fontSize: "0.7rem",
    fontWeight: 700,
    letterSpacing: "0.06em",
    textTransform: "uppercase",
    zIndex: 3
  };
  const statusBand = {
    position: "absolute",
    left: 0,
    right: 0,
    bottom: 0,
    zIndex: 4,
    padding: "0.4rem 0.6rem",
    background: "rgba(0,0,0,0.65)",
    color: "#fff",
    fontSize: "0.72rem",
    fontWeight: 600,
    letterSpacing: "0.04em"
  };
  const button = on => ({
    padding: "0.35rem 0.8rem",
    borderRadius: "999px",
    border: `1px solid ${on ? `rgba(${ORANGE}, 0.9)` : "rgba(255,255,255,0.14)"}`,
    background: on ? `rgba(${ORANGE}, 0.16)` : "rgba(255,255,255,0.04)",
    color: on ? `rgb(${ORANGE})` : "#d8d8d8",
    fontSize: "0.74rem",
    fontWeight: 700,
    cursor: "pointer",
    lineHeight: 1.2
  });
  const solidButton = {
    padding: "0.35rem 0.8rem",
    borderRadius: "999px",
    border: "none",
    background: "var(--aspen-evergreen, #486a58)",
    color: "#fff",
    fontSize: "0.74rem",
    fontWeight: 700,
    cursor: "pointer",
    lineHeight: 1.2
  };
  const fieldLabel = {
    fontSize: "0.72rem",
    fontWeight: 700,
    letterSpacing: "0.06em",
    textTransform: "uppercase",
    opacity: 0.6
  };
  const hint = {
    margin: 0,
    fontSize: "0.76rem",
    opacity: 0.7,
    lineHeight: 1.5
  };
  const buttonRow = {
    display: "flex",
    flexWrap: "wrap",
    gap: "0.35rem",
    minWidth: 0
  };
  const showPrompt = Boolean(promptText || item && (item.speech || item.note) || request && !compact);
  const sourceLabel = source && source.label || "Source";
  const sourceStatus = srcState === "error" ? "This source clip could not be loaded." : srcState === "ready" ? "" : "Loading the source clip.";
  const resultStatus = outState === "error" ? "This edited clip could not be loaded." : outState === "ready" ? "" : "Loading the edited clip.";
  const cors = corsOk ? "anonymous" : undefined;
  return <div className="not-prose" style={card}>
      {}
      <div style={{
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
    gap: "0.6rem",
    flexWrap: "wrap",
    padding: "0.1rem 0.2rem"
  }}>
        <div style={{
    display: "flex",
    alignItems: "center",
    gap: "0.6rem",
    minWidth: 0
  }}>
          <span style={fieldLabel}>{title || "Edit"}</span>
          <span style={{
    fontSize: "0.9rem",
    fontWeight: 600,
    whiteSpace: "nowrap",
    overflow: "hidden",
    textOverflow: "ellipsis"
  }}>
            {item && item.label || "Pick an edit"}
          </span>
        </div>
        <div style={{
    display: "flex",
    gap: "0.4rem",
    alignItems: "center",
    flexWrap: "wrap"
  }}>
          {canOverlay ? <button type="button" onClick={toggleOverlay} style={button(overlayOn)} aria-pressed={overlayOn}>
              {overlayOn ? "Hide pixel differences" : "Show pixel differences"}
            </button> : null}
          <button type="button" onClick={toggleSound} style={button(sound)} disabled={!resultUrl} aria-pressed={sound}>
            {sound ? "Sound on" : "Sound off"}
          </button>
          <button type="button" onClick={togglePlay} style={button(false)}>
            {playing ? "Pause" : "Play"}
          </button>
        </div>
      </div>

      {}
      <div style={{
    display: "grid",
    gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 16rem), 1fr))",
    gap: "0.5rem"
  }}>
        <div style={{
    ...pane,
    cursor: sourceUrl ? "pointer" : "default"
  }} onClick={sourceUrl ? togglePlay : undefined}>
          {sourceUrl ? <video key={`source-${corsOk ? 1 : 0}`} ref={setSrcRef} src={sourceUrl} poster={L.mediaUrl(source && source.poster) || undefined} crossOrigin={cors} autoPlay muted playsInline preload="auto" aria-label={sourceLabel} style={fill} /> : <div style={{
    position: "absolute",
    inset: 0,
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    padding: "1rem",
    textAlign: "center",
    fontSize: "0.85rem",
    opacity: 0.8
  }}>
              No source clip was given for this player.
            </div>}
          <span style={paneLabel}>{sourceLabel}</span>
          {sourceUrl && sourceStatus ? <div style={statusBand} role="status">
              {sourceStatus}
            </div> : null}
        </div>

        <div style={{
    ...pane,
    background: resultUrl ? "#000" : "transparent",
    border: resultUrl ? "none" : "1px dashed rgba(255,255,255,0.22)",
    cursor: resultUrl ? "pointer" : "default"
  }} onClick={resultUrl ? togglePlay : undefined}>
          {resultUrl ? <video key={`result-${itemId}-${corsOk ? 1 : 0}`} ref={setOutRef} src={resultUrl} poster={item && item.poster || undefined} crossOrigin={cors} muted={!sound} playsInline preload="auto" aria-label={item && item.label || "Edited"} style={fill} /> : <div style={{
    position: "absolute",
    inset: 0,
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    padding: "1.25rem",
    textAlign: "center"
  }}>
              <span style={fieldLabel}>Not generated yet</span>
            </div>}
          {canOverlay ? <canvas ref={setCanvasRef} aria-hidden="true" style={{
    ...fill,
    zIndex: 2,
    opacity: overlayOn && outState === "ready" ? 1 : 0
  }} /> : null}
          <span style={paneLabel}>Edited</span>
          {resultUrl && resultStatus ? <div style={statusBand} role="status">
              {resultStatus}
            </div> : null}
        </div>
      </div>

      {}
      {overlayOn ? <p style={{
    margin: 0,
    padding: "0 0.2rem",
    fontSize: "0.78rem",
    lineHeight: 1.55,
    opacity: 0.75
  }}>
          Orange shows an approximate pixel difference between the clips. Pause
          playback to inspect a frame, or hide the highlights to see the result.
        </p> : null}

      {}
      <div style={{
    display: "flex",
    alignItems: "center",
    gap: "0.6rem",
    padding: "0 0.2rem"
  }}>
        <span style={{
    fontSize: "0.7rem",
    fontVariantNumeric: "tabular-nums",
    opacity: 0.6,
    width: "2.6rem"
  }}>{L.fmt(time)}</span>
        {}
        <input type="range" min={0} max={L.sliderMax(duration)} step={0.05} value={L.clampTime(time, duration)} onChange={onSeek} onKeyDown={e => e.stopPropagation()} disabled={!L.safeDuration(duration)} aria-label="Seek both clips" aria-valuetext={`${L.fmt(time)} of ${L.fmt(duration)}`} style={{
    flex: 1,
    accentColor: `rgb(${ORANGE})`,
    cursor: "pointer",
    minWidth: 0
  }} />
        <span style={{
    fontSize: "0.7rem",
    fontVariantNumeric: "tabular-nums",
    opacity: 0.6,
    width: "2.6rem",
    textAlign: "right"
  }}>{L.fmt(duration)}</span>
      </div>

      {}
      {edits.length < 2 ? null : <div style={{
    display: "flex",
    flexDirection: "column",
    gap: "0.5rem",
    padding: "0.1rem 0.2rem"
  }}>
          {}
          <div style={{
    display: "flex",
    alignItems: "center",
    gap: "0.5rem",
    flexWrap: "wrap"
  }}>
            <span style={fieldLabel}>Edit</span>
            <div role="group" aria-label="Edit" style={buttonRow}>
              {edits.map((e, i) => {
    const k = L.keyOf(e, i);
    const on = base === k;
    return <button key={k} type="button" onClick={() => onSelectBase(k)} style={button(on)} aria-pressed={on}>
                    {`${e.label}${L.hasClipFor([k]) ? "" : " (not generated yet)"}`}
                  </button>;
  })}
            </div>
          </div>

          {}
          {!combinable || !base ? null : addons.length ? <div style={{
    display: "flex",
    flexDirection: "column",
    gap: "0.4rem"
  }}>
              <div style={{
    display: "flex",
    alignItems: "center",
    gap: "0.5rem",
    flexWrap: "wrap"
  }}>
                <span style={fieldLabel}>Combine edits</span>
                <div role="group" aria-label={`Combine with ${baseLabel}`} style={buttonRow}>
                  {addons.map(k => {
    const e = L.findEdit(k);
    const on = selected.includes(k);
    const allowed = on || L.canAdd(selected, k);
    const has = L.hasClipFor(on ? selected : [...selected, k]);
    return <button key={k} type="button" onClick={() => onToggleAddon(k)} disabled={!allowed} aria-pressed={on} style={{
      ...button(on),
      opacity: allowed ? 1 : 0.45,
      cursor: allowed ? "pointer" : "default"
    }}>
                        {`Also ${e.label}${has ? "" : " (not generated yet)"}`}
                      </button>;
  })}
                </div>
              </div>
              <p style={hint}>
                The additions are asked for in one prompt with the edit above.
                Picking a different edit clears them.
              </p>
            </div> : <p style={hint}>{`No combined examples are available for ${baseLabel}.`}</p>}
        </div>}

      {}
      {showPrompt ? <div style={{
    background: "#16181a",
    borderRadius: "0.8rem",
    padding: "0.95rem 1.1rem",
    display: "flex",
    flexDirection: "column",
    gap: "0.7rem"
  }}>
          {promptText ? <p style={{
    margin: 0,
    fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
    fontSize: "0.94rem",
    lineHeight: 1.8,
    whiteSpace: "pre-wrap"
  }}>
              {promptText}
            </p> : null}

          {item && item.speech ? <div style={{
    display: "grid",
    gridTemplateColumns: "auto 1fr",
    gap: "0.25rem 0.9rem",
    fontSize: "0.85rem",
    lineHeight: 1.5,
    borderTop: "1px solid rgba(255,255,255,0.08)",
    paddingTop: "0.65rem"
  }}>
              <span style={{
    opacity: 0.5
  }}>Before</span>
              <span>{item.speech.from}</span>
              <span style={{
    opacity: 0.5
  }}>After</span>
              <span>{item.speech.to}</span>
            </div> : null}

          {item && item.note ? <p style={{
    margin: 0,
    fontSize: "0.8rem",
    lineHeight: 1.55,
    opacity: 0.75
  }}>{item.note}</p> : null}

          {compact || !promptText ? null : <div style={{
    display: "flex",
    justifyContent: "flex-end",
    alignItems: "center",
    gap: "0.4rem",
    flexWrap: "wrap"
  }}>
              <button type="button" onClick={() => copyInto(promptText, setPromptCopy)} style={solidButton}>
                {copyLabel(promptCopy, "Copy prompt")}
              </button>
            </div>}

          {request && !compact ? <details style={{
    borderTop: "1px solid rgba(255,255,255,0.08)",
    paddingTop: "0.7rem"
  }}>
              <summary style={{
    cursor: "pointer",
    fontSize: "0.8rem",
    fontWeight: 600
  }}>Example request</summary>
              <div style={{
    display: "flex",
    flexDirection: "column",
    gap: "0.5rem",
    marginTop: "0.6rem"
  }}>
                <pre style={{
    margin: 0,
    fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
    fontSize: "0.78rem",
    lineHeight: 1.6,
    whiteSpace: "pre-wrap",
    wordBreak: "break-all",
    opacity: 0.92
  }}>{requestCommand}</pre>
                <div style={{
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
    gap: "0.5rem",
    flexWrap: "wrap"
  }}>
                  <span style={{
    fontSize: "0.75rem",
    opacity: 0.6
  }}>
                    {request.note || "Example request with the prompt above."}
                  </span>
                  <button type="button" onClick={() => copyInto(requestCommand, setRequestCopy)} style={solidButton}>
                    {copyLabel(requestCopy, "Copy request")}
                  </button>
                </div>
              </div>
            </details> : null}
        </div> : null}
    </div>;
};

Send FLUX Video Edit \[fast] a clip and say what should be different. Everything the prompt doesn't
mention stays as shot: length, framing, camera, timing and audio all come from
the source.

## Twelve edits on one clip

Every clip here started as the same ten seconds at the harbor. Pick an edit,
stack a few more on top, and turn on the pixel overlay to see what moved.

<VideoEditShowcase
  title="Harbor"
  request={{}}
  source={{ video: "https://cdn.sanity.io/files/2gpum2i6/production/f1654af36e7694775939b1aa8a2bb0419ff819ea.mp4", poster: "https://cdn.sanity.io/images/2gpum2i6/production/82ececb45ad817f6f2d92bfd031a6d142cf1562b-1280x726.jpg", label: "Source" }}
  edits={[
{
"key": "remove",
"video": "https://cdn.sanity.io/files/2gpum2i6/production/c447e0d2a8b0a516f98e657bf3153b2c6f774a4d.mp4",
"prompt": "Remove the orange bucket.",
"label": "Remove bucket",
"note": "The bucket is absent from the result. Compare the fish and background details as well as the requested edit."
},
{
"key": "colour",
"video": "https://cdn.sanity.io/files/2gpum2i6/production/9b2f03e95d53431d7de9490133f15f6dba32bd09.mp4",
"prompt": "Make the apron red.",
"label": "Change apron color"
},
{
"key": "add",
"video": "https://cdn.sanity.io/files/2gpum2i6/production/da3907304d8ca81e5898bd49a5ede11ab9927ff0.mp4",
"prompt": "Add a seagull standing on the corner of the crate.",
"label": "Add seagull"
},
{
"key": "text",
"video": "https://cdn.sanity.io/files/2gpum2i6/production/c02de88b65419292a1775ca40da536dac5f1ec5a.mp4",
"prompt": "Change the stencil on the crate to read DAILY CATCH.",
"label": "Change crate text"
},
{
"key": "vfx",
"video": "https://cdn.sanity.io/files/2gpum2i6/production/6746b8b4f2b0ca345b63b5d6a9047761a1982b72.mp4",
"prompt": "Make it snow, with flakes settling on the crate and the fish.",
"label": "Add snow",
"note": "Snow is visible on the crate and fish in this result."
},
{
"key": "replace",
"video": "https://cdn.sanity.io/files/2gpum2i6/production/6ea595e5714532b55c2a93972272c9370cd65008.mp4",
"prompt": "Replace the mackerel with lobsters.",
"label": "Replace fish"
},
{
"key": "character",
"video": "https://cdn.sanity.io/files/2gpum2i6/production/0afc50029c97ebba415cd5102d8a888685a2af22.mp4",
"prompt": "Replace the fishmonger with a woman in her thirties wearing the same yellow apron and grey cap.",
"label": "Change character"
},
{
"key": "scene",
"video": "https://cdn.sanity.io/files/2gpum2i6/production/2431d7fcecd4d2acfc109d32d37979cb5124ed2c.mp4",
"prompt": "Replace the harbour behind him with a Mediterranean harbour at midday, bright sun and white buildings.",
"label": "Change setting"
},
{
"key": "restyle",
"video": "https://cdn.sanity.io/files/2gpum2i6/production/690b1eeae99db89b8ad2aecb2f849f21573c1f4b.mp4",
"prompt": "Make the whole clip a hand-drawn watercolour animation.",
"label": "Watercolor"
},
{
"key": "dialogue",
"video": "https://cdn.sanity.io/files/2gpum2i6/production/a57e0921459494b5008db67d82d14030dd49d4da.mp4",
"prompt": "Make him say \"Fresh mackerel, four for ten.\"",
"label": "Change dialogue",
"speech": {
  "from": "Straight off the boat this morning, four for ten.",
  "to": "Fresh mackerel, four for ten."
}
},
{
"key": "translate",
"video": "https://cdn.sanity.io/files/2gpum2i6/production/46467ec2395c7d1e0d9fc2e4e557aed9d194d067.mp4",
"prompt": "Say the same sentence entirely in German, with no English words.",
"label": "Translate dialogue",
"speech": {
  "from": "Straight off the boat this morning, four for ten.",
  "to": "Direkt vom Boot heute Morgen, vier für zehn."
}
},
{
"key": "event",
"video": "https://cdn.sanity.io/files/2gpum2i6/production/257c4060dccce2469356550bc98ee6e5275ac0a8.mp4",
"prompt": "A gull swoops down and steals a fish from the crate.",
"label": "Add an action"
}
]}
  stackable={["remove", "colour", "text", "add", "vfx"]}
  results={[
{
"keys": [
  "add",
  "vfx"
],
"video": "https://cdn.sanity.io/files/2gpum2i6/production/1603003936fe1ce23edfb54635fb2691fc27fe6f.mp4",
"prompt": "Add a seagull standing on the corner of the crate. Make it snow, with flakes settling on the crate and the fish."
},
{
"keys": [
  "colour",
  "add",
  "vfx"
],
"video": "https://cdn.sanity.io/files/2gpum2i6/production/4d71e288b329ad1802a5c5ecbf2c71a32727e86d.mp4",
"prompt": "Make the apron red. Add a seagull standing on the corner of the crate. Make it snow, with flakes settling on the crate and the fish."
},
{
"keys": [
  "colour",
  "add"
],
"video": "https://cdn.sanity.io/files/2gpum2i6/production/64abe8a589361e1443d3998ceb1f967985df83a2.mp4",
"prompt": "Make the apron red. Add a seagull standing on the corner of the crate."
},
{
"keys": [
  "colour",
  "text",
  "add",
  "vfx"
],
"video": "https://cdn.sanity.io/files/2gpum2i6/production/baf8cab2377234fbddddb23ed68c155485085aa9.mp4",
"prompt": "Make the apron red. Change the stencil on the crate to read DAILY CATCH. Add a seagull standing on the corner of the crate. Make it snow, with flakes settling on the crate and the fish."
},
{
"keys": [
  "colour",
  "text",
  "add"
],
"video": "https://cdn.sanity.io/files/2gpum2i6/production/e2af2f7d88cdae96c557b9afe0830d4196d9edd3.mp4",
"prompt": "Make the apron red. Change the stencil on the crate to read DAILY CATCH. Add a seagull standing on the corner of the crate."
},
{
"keys": [
  "colour",
  "text",
  "vfx"
],
"video": "https://cdn.sanity.io/files/2gpum2i6/production/3fd5d56feaa3d28c49f27d5a06a70cb18aff7fdc.mp4",
"prompt": "Make the apron red. Change the stencil on the crate to read DAILY CATCH. Make it snow, with flakes settling on the crate and the fish."
},
{
"keys": [
  "colour",
  "text"
],
"video": "https://cdn.sanity.io/files/2gpum2i6/production/0648fe293d39b1c2b1eaad96744bc1d8a78ed0e7.mp4",
"prompt": "Make the apron red. Change the stencil on the crate to read DAILY CATCH."
},
{
"keys": [
  "colour",
  "vfx"
],
"video": "https://cdn.sanity.io/files/2gpum2i6/production/4f3a03a6d27a0a5a9d43e1fbdd777e23538be0e9.mp4",
"prompt": "Make the apron red. Make it snow, with flakes settling on the crate and the fish."
},
{
"keys": [
  "remove",
  "add",
  "vfx"
],
"video": "https://cdn.sanity.io/files/2gpum2i6/production/214ff46acb12f7358dcdbc62977ef491f6e8ea96.mp4",
"prompt": "Remove the orange bucket. Add a seagull standing on the corner of the crate. Make it snow, with flakes settling on the crate and the fish."
},
{
"keys": [
  "remove",
  "add"
],
"video": "https://cdn.sanity.io/files/2gpum2i6/production/df769a4c04bc5fd96394dff58b8e89e4fffe4628.mp4",
"prompt": "Remove the orange bucket. Add a seagull standing on the corner of the crate."
},
{
"keys": [
  "remove",
  "colour",
  "add",
  "vfx"
],
"video": "https://cdn.sanity.io/files/2gpum2i6/production/5a955c1935947902fc2c82f37a15d565cb892d30.mp4",
"prompt": "Remove the orange bucket. Make the apron red. Add a seagull standing on the corner of the crate. Make it snow, with flakes settling on the crate and the fish."
},
{
"keys": [
  "remove",
  "colour",
  "add"
],
"video": "https://cdn.sanity.io/files/2gpum2i6/production/4fec08f6a9428014319625398c18b4e5d39dff26.mp4",
"prompt": "Remove the orange bucket. Make the apron red. Add a seagull standing on the corner of the crate."
},
{
"keys": [
  "remove",
  "colour",
  "text",
  "add",
  "vfx"
],
"video": "https://cdn.sanity.io/files/2gpum2i6/production/ac8575b377e920bff5e6141b1fd262618ad06c85.mp4",
"prompt": "Remove the orange bucket. Make the apron red. Change the stencil on the crate to read DAILY CATCH. Add a seagull standing on the corner of the crate. Make it snow, with flakes settling on the crate and the fish."
},
{
"keys": [
  "remove",
  "colour",
  "text",
  "add"
],
"video": "https://cdn.sanity.io/files/2gpum2i6/production/65454ef5032d9ffc7e63aeb1c257dd3b90706a8f.mp4",
"prompt": "Remove the orange bucket. Make the apron red. Change the stencil on the crate to read DAILY CATCH. Add a seagull standing on the corner of the crate."
},
{
"keys": [
  "remove",
  "colour",
  "text",
  "vfx"
],
"video": "https://cdn.sanity.io/files/2gpum2i6/production/953d7bf2ee34a791e0d26e5f3f2091ba8cff2fbe.mp4",
"prompt": "Remove the orange bucket. Make the apron red. Change the stencil on the crate to read DAILY CATCH. Make it snow, with flakes settling on the crate and the fish."
},
{
"keys": [
  "remove",
  "colour",
  "text"
],
"video": "https://cdn.sanity.io/files/2gpum2i6/production/eb1692bc3881826c527b501698d5896a463d94d8.mp4",
"prompt": "Remove the orange bucket. Make the apron red. Change the stencil on the crate to read DAILY CATCH."
},
{
"keys": [
  "remove",
  "colour",
  "vfx"
],
"video": "https://cdn.sanity.io/files/2gpum2i6/production/b448fb72dd87b82398159907a0115ed8fbcba0fc.mp4",
"prompt": "Remove the orange bucket. Make the apron red. Make it snow, with flakes settling on the crate and the fish."
},
{
"keys": [
  "remove",
  "colour"
],
"video": "https://cdn.sanity.io/files/2gpum2i6/production/46f0a43ff7d7babfda1682bc4499f8a37f8a4cf0.mp4",
"prompt": "Remove the orange bucket. Make the apron red."
},
{
"keys": [
  "remove",
  "text",
  "add",
  "vfx"
],
"video": "https://cdn.sanity.io/files/2gpum2i6/production/0dd6da7d6a5b1017935232fd8628d311b87d9d76.mp4",
"prompt": "Remove the orange bucket. Change the stencil on the crate to read DAILY CATCH. Add a seagull standing on the corner of the crate. Make it snow, with flakes settling on the crate and the fish."
},
{
"keys": [
  "remove",
  "text",
  "add"
],
"video": "https://cdn.sanity.io/files/2gpum2i6/production/42dafc493b43b3c1a617308f423cd4d35f6cfde9.mp4",
"prompt": "Remove the orange bucket. Change the stencil on the crate to read DAILY CATCH. Add a seagull standing on the corner of the crate."
},
{
"keys": [
  "remove",
  "text",
  "vfx"
],
"video": "https://cdn.sanity.io/files/2gpum2i6/production/1204b023eeef80074b0d9db657edd1cfd3f8ee9c.mp4",
"prompt": "Remove the orange bucket. Change the stencil on the crate to read DAILY CATCH. Make it snow, with flakes settling on the crate and the fish."
},
{
"keys": [
  "remove",
  "text"
],
"video": "https://cdn.sanity.io/files/2gpum2i6/production/ca264191a867c8399d4bba2c8a986cf61d1a7445.mp4",
"prompt": "Remove the orange bucket. Change the stencil on the crate to read DAILY CATCH."
},
{
"keys": [
  "remove",
  "vfx"
],
"video": "https://cdn.sanity.io/files/2gpum2i6/production/2beb1529f57f458b3f34daa1c997aa9b8eb828a7.mp4",
"prompt": "Remove the orange bucket. Make it snow, with flakes settling on the crate and the fish."
},
{
"keys": [
  "text",
  "add",
  "vfx"
],
"video": "https://cdn.sanity.io/files/2gpum2i6/production/3d4b2971ede5d4d37b3953456bb4234a7c660203.mp4",
"prompt": "Change the stencil on the crate to read DAILY CATCH. Add a seagull standing on the corner of the crate. Make it snow, with flakes settling on the crate and the fish."
},
{
"keys": [
  "text",
  "add"
],
"video": "https://cdn.sanity.io/files/2gpum2i6/production/18f75065829021e2bf639643b4e070cb8ffb5d23.mp4",
"prompt": "Change the stencil on the crate to read DAILY CATCH. Add a seagull standing on the corner of the crate."
},
{
"keys": [
  "text",
  "vfx"
],
"video": "https://cdn.sanity.io/files/2gpum2i6/production/c65420777d685bd0d15de790649a8bff0a5f59d7.mp4",
"prompt": "Change the stencil on the crate to read DAILY CATCH. Make it snow, with flakes settling on the crate and the fish."
}
]}
/>

## Quick start

Editing a clip is one POST and a poll: send the clip as an HTTP(S) URL or
base64 in `video` and the instruction in `prompt`, then fetch the result from
the `polling_url` you get back.

<CodeGroup>
  ```bash cURL theme={null}
  # Submit a clip by URL (or send a base64-encoded mp4 in video instead)
  curl -sS -X POST "https://api.bfl.ai/v1/flux-tools/video-edit-v1" \
    -H "Content-Type: application/json" \
    -H "x-key: $BFL_API_KEY" \
    -d '{
      "video": "https://cdn.sanity.io/files/2gpum2i6/production/f1654af36e7694775939b1aa8a2bb0419ff819ea.mp4",
      "prompt": "Remove the orange bucket."
    }'

  # Poll the polling_url from the response until status is "Ready"
  curl -sS "https://api.bfl.ai/v1/get_result?id=YOUR_TASK_ID" \
    -H "x-key: $BFL_API_KEY"
  ```

  ```python Python theme={null}
  import os, time, requests

  BFL_API_KEY = os.environ["BFL_API_KEY"]

  # 1. Submit: returns an id and a polling_url
  submit = requests.post(
      "https://api.bfl.ai/v1/flux-tools/video-edit-v1",
      headers={"x-key": BFL_API_KEY, "Content-Type": "application/json"},
      json={
          "video": "https://cdn.sanity.io/files/2gpum2i6/production/f1654af36e7694775939b1aa8a2bb0419ff819ea.mp4",
          "prompt": "Remove the orange bucket.",
      },
  ).json()

  # 2. Poll the returned URL until the job is Ready
  while True:
      time.sleep(5)
      result = requests.get(submit["polling_url"], headers={"x-key": BFL_API_KEY}).json()
      if result["status"] == "Ready":
          print(result["result"]["sample"])   # signed .mp4 URL
          break
      if result["status"] in ("Error", "Request Moderated", "Content Moderated"):
          raise RuntimeError(result["status"])
  ```
</CodeGroup>

The submit call returns the task id and the URL to poll:

```json theme={null}
{
  "id": "a770da3b-1f6c-4ac8-a0f4-c0d216550e14",
  "polling_url": "https://api.bfl.ai/v1/get_result?id=a770da3b-1f6c-4ac8-a0f4-c0d216550e14"
}
```

While the clip renders, polls come back with `"status": "Pending"`. When it
flips to `Ready`, `result.sample` is a signed URL to the edited mp4.

<Warning>
  Signed delivery URLs expire about **1 hour** after the result is ready. Download your video within this timeframe.
</Warning>

`Error`, `Request Moderated`, and `Content Moderated` are terminal: stop polling and check the payload. The [Errors reference](/api_integration/errors) lists every status.

## Request parameters

Use `video` and `prompt` as the minimum payload. Nothing else is required.

| Parameter          | Type    | Required | Description                                                                                                                                                                                   |
| ------------------ | ------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `video`            | string  | Yes      | The clip to edit: an HTTP(S) URL or a base64-encoded mp4, up to 15 seconds and 50 MiB. Each side at least 160 pixels, and at least 17 frames after normalization to 24 fps, about 0.7 seconds |
| `prompt`           | string  | Yes      | The edit to make, 1 to 4,096 characters                                                                                                                                                       |
| `safety_tolerance` | integer | No       | `0-4`, defaults to `2`. Moderation strictness for the prompt and the delivered frames                                                                                                         |

Nothing else is accepted: `mode`, `version`, `seed`, `duration`, `resolution`,
`aspect_ratio` and `generate_audio` return HTTP 422. Length, aspect ratio and
frame rate come from the source, at 24 fps, and sources larger than 720p are
downscaled to 720p: the 1920 × 1088 harbor clip came back at 1248 × 704. Source
audio is carried through unless the prompt asks for a dialogue or sound change.
A silent source returns a silent result.

## Pricing

Editing is priced per second of output video. The output keeps the source
clip's length, so a ten second clip costs the same whatever the edit.

| Variant                 | Price             |
| ----------------------- | ----------------- |
| FLUX Video Edit \[fast] | \$0.03 per second |

Example: one edit of a 10-second clip is \$0.30. See the [pricing page](/quick_start/pricing#flux-tools-video) for the full rate card.

## Limitations

* Sources longer than 15 seconds are rejected, not trimmed. Sources with fewer than 17 frames or a side under 160 pixels fail during processing.
* Duration, resolution and aspect ratio follow the source and cannot be set. Sources larger than 720p are downscaled to 720p.
* A video as a style or motion reference, image input, audio-only input, masks and extending a clip are not supported. Use [video continuation](/flux_3/flux3_video#video-continuation) to extend.
* Asking for more words than the source's speech has time for won't render them all. Write new dialogue to the length of the line it replaces.

## Related pages

<CardGroup cols={2}>
  <Card title="Video Editing prompting guide" icon="pen" href="/guides/prompting_video_editing">
    How to word an edit: placement, dialogue length, blockouts, settings and restyles, with clips.
  </Card>

  <Card title="Video Prompting Overview" icon="book-open-cover" href="/guides/prompting_video_overview">
    Choose the right workflow across every FLUX 3 video mode.
  </Card>

  <Card title="FLUX Video Edit API" icon="code" href="/api-reference/tools/video-edit">
    Request fields, response shape and polling for the editing tool.
  </Card>

  <Card title="FLUX 3 Video" icon="film" href="/flux_3/flux3_video">
    Text-to-video, image-to-video with keyframes and video continuation.
  </Card>

  <Card title="FLUX Video Upscale" icon="arrow-up-right-dots" href="/flux_tools/flux_video_upscale">
    Sharpen an edited clip up to 4K afterwards.
  </Card>
</CardGroup>
