On this page

Chapter 8 of 9. Prerequisite: Directed prompts. You have a folder of beats that each look good in isolation and read as a broken mess when you concatenate them. Nothing in this chapter touches the API. Every fix is free.

Each beat you rendered in chapter 7 was its own generation task: one POST, one clip, no memory of the task before it. The Seedance 2.0 launch post describes the model as a "unified multimodal audio-video joint generation architecture", and that unity is per clip. Between clips there is no unity at all. Your character assets hold identity across tasks and your scene plate holds the environment, but motion state, stance, screen position and camera framing reset with every submission. Straight concatenation exposes all of it.

The practice film's first assembly proved the point at full scale: 35 seconds of parallel-rendered fight footage went from broken to a clean 13.5-second cut with no re-render at all. The entire repair was editing. This chapter is that repair, generalised into rules you can apply to any batch of AI renders.

The two failure modes: overlap breaks and resets

Every broken AI edit I have shipped or salvaged decomposes into two failure modes. Learn to name them on sight, because each has a different fix.

Overlap break. When two bodies intersect on screen, the model fuses them: limbs merge, a third arm appears, faces smear across the contact. In the practice film the grappling takedown rendered as two fighters melting into a single morphing blob. This is the number-one failure surface of AI-generated action footage, and no prompt reliably prevents it once the bodies genuinely intertwine. The fix is never rendered. You keep the fighters at distance in the source material, trim the tangle out of the shot, and split any large contact into windup, cut-on-impact and aftermath. The intertwine itself never reaches the screen.

Reset at cuts. Each independent render re-establishes its subjects from a neutral state. In a fight that means both fighters return to a square-up guard at the top of every beat, and because there is no persistent camera, left and right can flip between renders. Concatenate ten of those and the film reads as the same confrontation restarting ten times. The fix is the rest of this chapter: treat your renders as coverage, then cut them the way an editor cuts coverage.

Neither failure is a model defect you can wait out. A generation task is a self-contained clip by design (the create-task API has no cross-task state parameter), so the seams between tasks are permanently your problem. Post-production is not the last 10% of this pipeline. It is half of it.

Editing AI generated video shots together: coverage, not takes

Film editors solved discontinuity a century ago. A scene is shot as coverage: the same action from several framings, cut together so each cut reads as a deliberate change of viewpoint rather than a jump in reality. Your batch of AI renders is accidental coverage. Every beat is a slightly different staging of the same fight in the same locked arena, so the reset problem converts directly into an editing problem with known solutions.

Two API constraints make coverage the only viable grammar, and both reject with exact 400s you met in earlier chapters. You cannot chain shots by feeding one render's last frame as the next render's first frame while character assets and motion references are in play, because the API treats the modes as mutually exclusive:

first/last frame content cannot be mixed with reference media content

And you cannot lock the camera at render time in reference mode, because 2.0 rejects the parameter whenever any reference media is present:

HTTP 400 "camera_fixed is not supported for dreamina-seedance-2-0 in r2v, must be empty"

Frame chaining is blocked and camera consistency is unpurchasable, so continuity must be manufactured in the edit. Here is the recut sequence that took the practice film from 35 broken seconds to 13.5 clean ones:

  1. Trim the reset head off every beat

    The first fraction of each render is the model settling its subjects into a neutral stance. Cut it off. The settle-cut method below finds the exact frame; until then, assume roughly the first half-second of every beat is establishment you do not want.

  2. Convert framing variety from a problem into coverage

    You rendered wides. Crop them. A wide plus a punched-in medium and a punched-in close from the same render are three camera angles from the same setup, and alternating them makes each cut read as a viewpoint change instead of a reset. The recut command and the punch-in factors are in the impact toolkit below.

  3. Route axis flips through a neutral shot

    Screen direction is a promise: a fighter who exits frame right must enter the next shot from the left. When two adjacent beats flip left and right, do not cut them together. Reorder so a close-up or other neutral framing sits between the flipped axes; the close-up carries no strong left-right geography, so the flip lands invisibly.

  4. Cut on motion, at the peak

    Place every cut mid-movement, at the top of a strike or the peak of a lunge. A cut during motion inherits the motion's momentum and the eye forgives the discontinuity. A cut on a static pose invites frame-by-frame comparison, a contest AI footage always loses.

  5. Mask every remaining seam

    A 2-frame white flash plus a shake pulse into the cut hides whatever the first four steps could not. Exact recipes below.

Hide every contact: windup, cut on impact, reaction

The overlap break has a structural fix: the moment of contact never appears on screen. Any impact becomes three shots. The windup, rendered clean because the bodies are still separate. A cut at the exact impact frame. The aftermath, rendered clean because the bodies have separated again. The audience's brain renders the collision better than the model ever will.

The practice film's takedown is the worked example. The grapple was unrenderable (every attempt fused the fighters), so the beat was rebuilt as a hide-the-contact split: only the windup was rendered, from a source segment covering the strike and drive before any tangle, using a directed prompt over that segment per the video generation tutorial request shape. One submission, one poll to completion, roughly $0.50. The shot was trimmed to the drive's peak, and the takedown impact itself landed off-screen behind a 3-frame white flash. Total on-screen contact: zero frames. Read of the scene: a completed takedown.

One trap from that render, carried over from chapter 7 because it cost a rejected generation here: the first attempt prompted "the two fighters stay clearly separate" while the motion reference showed a drive into a clinch. The prompt contradicted the reference, the model abandoned the choreography and invented generic motion, and the render was wasted. Control overlap by trimming the source clip before the clinch. Never fight the reference in the prompt.

The honest limit: one segment of the source fight, a ground clinch spanning roughly 26.5 to 31 seconds, could not be rendered at all. Every framing of it is sustained body overlap. The finished film bridges it with the takedown cut instead of faking it. Some beats do not survive the transfer to AI footage, and the professional move is a bridge, never a morph.

Settle-cuts: finding the trough with signalstats

Trimming beats by eye does not scale past a handful of shots, and arbitrary trims reintroduce the mid-move cut. The practice film's v05 pass cut mid-strike constantly, and the root cause was a duration mismatch: renders were requested at --duration 5 while the underlying motion-reference beats ran only 2 to 3.7 seconds. Seedance 2.0's duration floor is 4 seconds (the create-task API documents the duration parameter; the 2.0 series tutorial covers the 2.0 window), so every render over-ran its reference and improvised a tail of invented motion. Trimming those tails at fixed offsets produced cuts that landed mid-action.

The free fix is to measure motion per frame and cut at the troughs, so every shot resolves a move before it ends. ffmpeg computes the measurement in one filter chain:

ffmpeg -i beat03.mp4 \
  -vf "tblend=all_mode=difference,extractplanes=y,signalstats,metadata=print:key=lavfi.signalstats.YAVG:file=-" \
  -f null - 2>/dev/null

tblend=all_mode=difference subtracts each frame from its predecessor, extractplanes=y keeps luma, and signalstats prints each difference-frame's average luma as lavfi.signalstats.YAVG. High YAVG means the frame changed a lot (fast motion); a trough means the subjects settled. The printout is line-pairs of pts_time and the YAVG value; a few lines of Python turn those pairs into cut points:

import re, subprocess, sys

out = subprocess.run(
    ["ffmpeg", "-i", sys.argv[1], "-vf",
     "tblend=all_mode=difference,extractplanes=y,signalstats,"
     "metadata=print:key=lavfi.signalstats.YAVG:file=-",
     "-f", "null", "-"],
    capture_output=True, text=True).stdout

frames = re.findall(r"pts_time:([\d.]+)\n.*?YAVG=([\d.]+)", out)
series = [(float(t), float(y)) for t, y in frames]

def trough(lo, hi):
    window = [p for p in series if lo <= p[0] <= hi]
    return min(window, key=lambda p: p[1])[0]

ref = float(sys.argv[2])          # usable seconds of real choreography
cut_in  = trough(0.1, 0.6)
cut_out = trough(ref * 0.55, min(ref + 0.15, 3.6))
print(f"-ss {cut_in:.2f} -to {cut_out:.2f}")

The two windows are the load-bearing part. The IN point is the lowest trough inside 0.1 to 0.6 seconds: it drops the establishment head without eating the first real move. The OUT point is the lowest trough between 55% of the reference's usable length and a hard cap of the reference length plus 0.15 seconds, never later than 3.6 seconds. The cap exists because everything after the reference runs out is improvised tail, and no trough inside invented motion is worth keeping. Applied across the practice film's full-fight pass, this produced a 21.5-second complete cut in which every shot resolves its move before the cut lands.

The impact toolkit: flashes, shakes, after-images

Four recipes mask the seams the coverage pass leaves behind. All of them run in ffmpeg, all were used in the shipped recut, and none costs a token.

The framing recut. Wide, medium and close from one render:

ffmpeg -ss S -t D -i shot.mp4 -vf "crop=iw*F:ih*F,scale=854:480,fps=24" beat.mp4

S and D come from the settle-cut. F is the punch-in factor. 0.95 is the shipped value for a wide with a little headroom; the medium and close factors are yours to pick per shot, and around 0.8 for a medium with around 0.65 for a close are reasonable starting points. Normalising everything to 854x480 at 24 fps means the concat demuxer never renegotiates stream parameters mid-film.

The white impact flash. Two frames of pure white at every hard cut:

ffmpeg -f lavfi -i "color=c=white:s=854x480:r=24" -frames:v 2 flash2.mp4

Two frames (-frames:v 2) reads as an impact; the eye registers the hit and misses the discontinuity behind it. Reserve a 3-frame version for hero beats: the practice film used the big flash after the aura reveal and behind the takedown.

The shake-into-cut. A crop that starts centred and vibrates into the cut, selling the camera reacting to the hit. The production form, exactly as shipped in the v02 recut:

crop=iw*0.95:ih*0.95:'(iw-ow)/2+18*gt(t,T-0.22)*sin(80*t)*((t-(T-0.22))/0.22)':'(ih-oh)/2+18*gt(t,T-0.22)*cos(80*t)*((t-(T-0.22))/0.22)'

Replace T with the beat's end time in seconds. Decoded: the crop takes 95% of the frame, sits centred via (iw-ow)/2, and gt(t,T-0.22) gates a shake that only exists in the final 220 ms. 18*sin(80*t) is an 18-pixel oscillation, and the ramp ((t-(T-0.22))/0.22) scales it from zero to full amplitude into the cut, so the shake builds rather than pops. The first assembly used the same idea as a 6% crop-headroom variant with x='iw*0.03 + gt(t,D-0.25)*18*sin(74*t)*((t-(D-0.25))/0.25)' across 4 of its 5 cuts.

After-images. Models flatten prompted speed-lines and after-images back into ordinary motion, so echo trails belong in post, where they are deterministic:

ffmpeg -i beat.mp4 -vf "lagfun=decay=0.86" beat_trail.mp4

lagfun=decay=0.86 holds a decaying ghost of each bright frame, which reads as a motion trail on fast strikes. Apply it per beat, only to the fast ones.

Assembly is the stock concat demuxer, with flashes interleaved as their own list entries:

file 'b01_cut.mp4'
file 'flash2.mp4'
file 'b02_cut.mp4'
file 'flash2.mp4'
file 'b03_close.mp4'
ffmpeg -f concat -safe 0 -i beats.txt -c:v libx264 -pix_fmt yuv420p cut.mp4

Slow motion and tracking crops: the -t placement trap

Two ffmpeg traps from the wider arc bite hardest during recuts, so they live in this chapter.

First, argument order. When you trim an input for a slow-motion treatment, -t must come before -i. Placed after the input, -t caps the output duration instead and truncates a setpts=PTS/0.5 slow-down to half the footage you meant to keep. The failure is silent: the command exits 0 and the file simply ends early.

# correct: -t bounds the INPUT read
ffmpeg -ss 2.0 -t 1.5 -i shot.mp4 -vf "setpts=PTS/0.5,minterpolate=fps=24:mi_mode=mci" slow.mp4

Second, never ship setpts slow-motion alone. Halving the presentation speed of 24 fps footage without interpolation holds every frame twice and judders. Pair it with minterpolate=fps=24:mi_mode=mci, which synthesises the in-between frames via motion-compensated interpolation.

Third, if the slow-motion subject moves across frame, a static punch-in crop loses it. The fix is an animated tracking crop: the same crop filter with x and y written as expressions in t, easing the window along the subject's path across the shot. It is the shake-into-cut machinery pointed at a different job.

Cut before drift: the economics of the recut

The strategic rule that falls out of all of the above: prefer fewer, longer renders. Seedance 2.0 generates up to 15 seconds in a single continuous take (duration window per the 2.0 series tutorial), and one 12-second render contains zero seams where three 4-second renders contain two. Cap --duration to your reference's usable length, because past the reference's end the model improvises: the practice film's 12-second continuous fight drifted in its final 2 seconds, inventing a solo pose and a phone prop, and shipped as a trimmed 9-second cut.

Then respect the asymmetry the pricing page implies: every re-render bills tokens, and every recut is free. The real numbers from the practice film's ledger: the faithful 11-shot pass cost about $5, the whole fight R&D arc about $16, and a proper 12-to-18-shot coverage pass estimates at $6 to $9 at 480p. Against those figures, the v01-to-v02 repair, which fixed both an overlap break and systemic resets, cost $0.00 because it touched only the edit. Exhaust the free grammar before you spend: trim, crop, reorder, flash, shake. Re-render only the beat that no cut can save, and when you do re-render several at once, remember the batch is bounded by account concurrency: excess task submissions sit in queued status rather than erroring (the task lifecycle documents the queued-to-running progression), while sustained request rates above your account's limit return 429s (see handling burst traffic for how ModelArk rate limiting responds and how to back off).

The final chapter assembles the whole course into the constraint sheet and the money sheet: every hard limit, every verbatim rejection, and what a finished short film actually costs end to end.

// EXERCISE

Recut a broken batch without spending a token

Take any batch of 4 or more independently rendered beats from your own pipeline (chapter 7's output works) and run the full edit grammar on it: settle-cut every beat via the YAVG method, build coverage from crops of your own renders, route any axis flip through a neutral framing, and mask every hard cut with the flash-plus-shake toolkit. The constraint is absolute: zero new generation tasks.

Expected behaviour
  • A trace script that runs the tblend difference chain and parses per-frame YAVG values into IN and OUT points per beat
  • Every beat trimmed to a low-motion IN inside the first 0.6 seconds and an OUT capped before the improvised tail
  • At least three framings in the final cut produced by cropping existing renders rather than rendering new angles
  • A 2-frame white flash and a shake-into-cut crop pulse on every hard cut, with the 3-frame flash reserved for at most one hero beat
  • A before-and-after pair: the straight concat and the recut, built from identical source files

PROVE IT Play the straight concat and the recut back to back and state both runtimes. Then show your task history for the editing window to demonstrate that the recut created zero new generation tasks.

// CHECKPOINT — EDIT GRAMMAR
multiple choice · auto-checked

Two adjacent beats concatenate into what looks like the fight restarting, and the fighters have swapped sides of the frame. What is the fix?

exact answer · auto-checked

The settle-cut method prints one per-frame metadata value and parses it to find low-motion troughs. Name the value.

open · self-checked

Why does this pipeline never put the actual moment of contact between two bodies on screen, and what does it render instead?

Show answer

Intersecting bodies trigger the overlap break: the model fuses them into a morphing blob with merged or extra limbs, and no prompt reliably prevents it once the bodies intertwine. So every impact is built from a rendered windup while the bodies are still separate, a cut placed at the impact frame and masked with a white flash, and a rendered aftermath once they have separated again. The collision itself happens off screen and the viewer's brain completes it.

↺ re-read: “Hide every contact: windup, cut on impact, reaction”

Sources

  • Create a video generation task
    BytePlus (ModelArk API reference)
    Each POST is an independent generation task, which is why every cut between renders is a seam the edit has to hide
    docs.byteplus.com
  • Dreamina Seedance 2.0 series tutorial
    BytePlus (ModelArk documentation)
    The 2.0 duration window and multimodal reference inputs behind the fewer-longer-renders rule
    docs.byteplus.com
  • Pricing
    BytePlus (ModelArk documentation)
    Every re-render bills tokens; the recut grammar in this chapter costs zero
    docs.byteplus.com
  • Seedance 2.0 official launch announcement
    ByteDance Seed Team
    What one generation is (a single joint audio-video clip), which defines where edit grammar has to take over
    seed.bytedance.com
Back to guide overview →