seriesmith with no arguments lists every command, and
each has --help.
Every command
build, check, refs, assemble and all take several episode folders at once, or a
parent with --each; render and master take several folders.
The modules
seriesmith/core/ (parser, story IR, series config, speech pacing), jobs.py,
takes.py, refs.py, source.py (the authored files), track.py (recordings and
alignment) and peaks.py (waveforms) are the library the commands and the editor’s routes
share. backends/ (local, cloud, deployments, Router), queue.py and runner.py (the
job queue, and what puts renders, refs, upscales, posts and tours on it), finishing.py
(the upscale and post graphs made portable) and server/ (the editor’s server) are how
work gets done; comfy_nodes/ is what runs inside ComfyUI. settings.py is this
machine’s settings, limits.py a model’s limits, and router_inspect.py reads a Comfy
Router model’s schema (the editor’s Models window; no command of its own).
What you write
series.json— the series config: style, characters and props (with adesignsentence each), locations (one entry per camera angle, with the light), voices, audio mode, and optionally the series target, render profiles, therefsimage targets and anegativeprompt. A character who changes clothes gets a second entry withof:— a wardrobe variant, its owndesignand its own sheet, everything else inherited. Seeexamples/series_example.json.epNN.md— the script:# sqsequences,## shshots withwho:,with:,size:,dur:oraudio:,plate:,camera:,sound:,music:, optionallytarget:,first:/last:, action prose andNAME:lines. Seeexamples/script_example.md.negative.txt(optional, beside the script): the episode’s negative prompt, for the targets that take one.
= ep01 Title header, at least one
# sq01 <location> naming a location the series config defines, and per ## shNNN a unique
id, action or dialogue, and a length (dur: seconds / auto / model, or an audio:
window). Everything else has a default — a speaker joins the cast from their own NAME:
line, size: is medium, plate: is the sequence’s location, and the camera holds still.
The series config must have a series block, style.look, subjects (each with name,
and design unless they are only ever a voice), and locations with a description and a
plate each. --check names whichever is missing.
Drafting with an AI assistant: python tools/make_prompts.py packages the guide as a skill,
seriesmith-episode-script. build/skill/seriesmith-episode-script/ (generated, not in the repo) is the whole skill (the
guide, a worked screenplay conversion from docs/SCRIPT_CONVERSION.md, and a copy of build to
validate with): copy the folder into .claude/skills/ for Claude Code, or upload
build/skill/seriesmith-episode-script.zip on claude.ai. prompts/SKILL.md is the skill’s
text alone, and prompts/seriesmith-script.instructions.md the same guide for Cursor, Copilot or
any system prompt. Edit the docs, not the copies. Whatever writes the script, --check
and --pace are what say it is correct.
Change the series config or script and run build again rather than editing the generated
files: steps:, model:, lora:, profile: and target: are script fields, so
experiments survive a rebuild, and overrides.json holds the editor’s tweaks. If you do
hand-edit a shotlist, all --skip-build and the individual stages leave it alone.
build
- Snaps every shot to its target’s legal lengths (H3: 17k+5 frames at 24fps, so 2.33, 3.04, 3.75, 4.46, 5.17s…; LTX: 8k+1; Wan: 4k+1).
dur: autosizes a shot from its dialogue.--paceflags lines crammed into too short a shot.- Seeds come from the episode, sequence and shot IDs, so untouched shots render identically after edits.
refs_todo.mdlists every image and voice sample still missing, with the prompt to make it.- Writes
defaults.steps,defaults.modelanddefaults.lorainto each shotlist, and warns when a shot overrides them or when the step count does not match the LoRA.
Steps, model and LoRA
Each pass has its own sampling setup, and the render stage applies it per shot, in memory — the workflow file on disk is never rewritten. The table is H3 Ref2VA’s; every target has its own presets in itstarget.json, and a series written for H3 doesn’t hand another
target H3’s model, LoRA or steps.
A distilled LoRA is trained on a fixed set of timesteps, so it does its best work at its own
step count: the 8-step v1.0 was distilled at 768p for 8 steps, and the proxy keeps the 4-step
v0.1 both for speed and because its 544p training is closer to the animatic’s 512×288.
Override per pass in the series config (
series for the final, proxy for the animatic):
# header — in the script:
lora: none (or off) sets the LoRA strength to 0 instead of swapping files, which renders
that shot on the base model — the honest comparison for what the turbo LoRA costs you.
A shot’s line beats its sequence’s, which beats the pass default. --check lists every
override so an experiment left in a script cannot surprise you later.
Names must match ComfyUI’s dropdowns exactly, subfolder prefix included. A wrong name fails
the first shot instead of quietly rendering with something else. ComfyUI reloads whenever the
model or LoRA changes, so group experiments rather than scattering them through an episode.
kreagen
-
Each take is a job on this command’s own queue:
--backendpicks where (local,cloud,deployment:<target>; default each image target’s own, so a hosted one goes to Router).--targetpicks the image model, a hosted one included. -
Skips anything already on disk.
--redomakes a new take (kept inrefs/_takes/) and leaves the live file alone unless you add--pick; combine it with--only.--allworks from the whole series config instead ofrefs_todo.json. -
--onlymatches part of the file path, sodeanalso matchesdean_grown— which is also how a character and their wardrobe variants regenerate together. -
Wardrobe variants come second. A variant’s views are edited from the character’s own
(see Reference sheets), and a whole pass plans every job before any take is picked —
so in the first pass the variant has nothing to edit from and is drawn cold. Run the pass,
let the character’s views pick (anything with no live file takes its first usable
candidate automatically), then regenerate just the variant on a model that reads
references:
--only gina_towel --target flux2_klein_edit --redo. Set that target once on the ref instead — in the editor’s per-ref target picker — and every later generate of it uses the edit model while everything else stays on krea2. The new take does not go live on its own once the variant already has a file: pick it (--pick, or in the Refs tab). -
Voices. Images only, unless
--voices: then each character’s voice ref is generated on the audio target (--voice-target,--voice-seconds; defaultltx2_voice) intorefs/voices/<char>.wav, as takes like any other ref.--from-take sh020:2:1.5-6.0needs no model at all — it cuts those seconds out of a rendered take’s sound into a voice candidate. -
--discard subject:ada:02_side:2moves a candidate torefs/_takes/_trash/(nothing is deleted; if it was the pick, the ref is cleared). -
--edit location:kitchen --instruction "night, the lights off"changes a picture the ref already has on an edit model, into new candidates (--withbrings in another ref’s picture,--count,--seed,--as-typed). -
Each character is made as four square views sharing a seed, then joined by mksheet.
Every view is a take in
refs/_takes/subject__<char>/, so one angle can be redone and re-picked in the editor’s Refs tab (seriesmith/refs.py). -
Negative prompts do nothing on krea2 turbo: no negative field, and no guidance branch at
cfg 1. kreagen leaves a workflow’s negative side as saved, so a NAG or negpip setup passes
through;
--negative-fileonly bites above--cfg 1.0, on a model that expects guidance. H3 takes no negative prompt at all. Details in KREAGEN.md. The other image targets take the episode’snegative.txtor the series config’snegative(inert at cfg 1 too);--negative-filebeats both for its run. -
--target z_image_turbo(or any image target) draws with another model;--clear location:kitchenunpicks a ref (its file goes, the takes stay). -
No style LoRA by default.
--lora <file> --lora-strength 0.7adds one,--unetswaps the image model. Match the LoRA to the look inseries.json: a realism LoRA helps live action and hurts a flat 2D show. -
With krea2, drives your own ComfyUI workflow when it finds one. Save a text-to-image graph as
krea2_refs_t2i.jsonamong ComfyUI’s workflows (or set$KREA_WORKFLOW, or pass--workflow), and kreagen sets the prompt, size, seed, steps and cfg on it per image instead of using its built-in graph. That is the no-Python way to change the model, LoRA, sampler or scheduler for reference art.--no-workflowforces the built-in graph. The graph needs one KSampler, one EmptyLatentImage and one SaveImage; positive and negative are found by following the sampler’s own links. At cfg 1.0 the negative encoder is replaced withConditioningZeroOut, since guidance is off and encoding it would be wasted; above 1.0 with--negative-file, a graph wired straight toConditioningZeroOutgets a text encoder put back.seriesmith/targets/image/krea2/workflow.jsonis a working example with no LoRA — point its loaders at your own models.--lorasplices a LoRA node into a graph that has none, or sets the one that is already there. If a graph carries a step-distilled LoRA, pass the matching--steps.
render
- Runs each shot through its target on its backend (
--backend, else the shot’s, the episode’s, the series config’sseries.backend, else the target’s own; see SETUP.md;seriesmith backendslists them). A workflow target’s graph goes as a job, with the shot and its reference pictures as uploads; the files that come back are written into the take. A hosted target (Seedance, Kling, Veo, hosted H3, …) is one Router call, and its takes are markedreproducible: false. Each take is a job on this command’s own queue, run one at a time and waited for; acontinuous: latentshot holds the take rendered just before it. - It runs anywhere the episode folder is: the backend never reads it. It checks the render folders to skip finished shots, so Ctrl-C (which cancels the running job) and re-running resumes where it stopped.
- Every take gets a sidecar,
<shot>_tNN.json, holding its seed, model, LoRAs, steps, the refs it used (with checksums) and its status (queued,ok,failed). It also gets<shot>_tNN.shotlist.json, the exact shot it rendered. Nothing about a take is lost when the script changes. --redorenders a new take (t02, t03…) and never overwrites.--take Noverwrites that take.- Seeds: a shot’s first take uses its built seed.
--redopicks a new seed, so a redo is really a new attempt.--same-seedkeeps the built one,--seed Nuses N, and--new-seedforces a fresh one. - Takes the model, LoRA and step count from each shot, falling back to the shotlist defaults,
then applies the episode’s
overrides.json(seeseriesmith overridebelow).--model,--loraand--stepsoverride all of that for one run.--lorarepeats to stack LoRAs (--lora a.safetensors --lora b.safetensors:0.6); the extras are chained after the workflow’s LoRA loader.--notestores a remark in each take’s sidecar. It prints what it will use, and warns when one episode needs more than one model or LoRA. - Targets. Each shot renders with its own target: H3 shots through the shot loader
(the shot as JSON, its pictures as uploads),
ltx2shots with every value patched into the LTX-2.5 graph, a hosted target as a model call.--target ltx2renders the chosen shots on another target for this run (their story is compiled for it on the spot, no rebuild);seriesmith override <ep> <shot> --target ltx2does it for every run. An LTX shot’s keyframes (refs/shots/<shot>/first.png,last.png) go up with its job.--dry-runwrites what the first shot would send (render_graph.json, orrender_call.jsonfor a hosted model) and runs nothing. - Failed shots are reported and skipped;
--stop-on-errorhalts instead. A shot whose reference images are still missing is blocked, and the report names the exact files;--allow-missing-refsrenders it against flat grey stand-ins. - Other flags:
--panel-mode,--save-frames,--latent/--no-latent,--size WxH,--allow-model-mismatch. - Each target’s workflow comes from the running ComfyUI’s saved workflows (the name in its
target.jsonbinding:H3_Ref2VA_Shotlist_v1.jsonfor H3,krea2_refs_t2i.jsonfor reference images), so it always matches your ComfyUI’s node versions; failing that,$COMFYUI_PATH’s workflows folder, then the copy in this repo. The target’s own env var ($SERIESMITH_MINIMAX_H3_REF2VA_WORKFLOW,$KREA_WORKFLOW, …) beats all of those (kreagen also takes--workflow). Every target’s name and variable is in INSTALL.md;seriesmith targets --install-workflow <target>copies a target’s graph into ComfyUI to edit on the canvas. Keep the savedkrea2_refs_t2i.jsonfree of style LoRAs (experiment under another name), since the references must follow the series config’s look. - It converts the canvas workflow to API format itself. If ComfyUI rejects it, save Workflow → Export (API) and point the target’s env var at that file.
assemble
- Follows the episode’s
cut.jsonif there is one (a list per pass of{"shot", "take", "pass", "trim_in", "trim_out", "audio"}entries). The list sets the order and, withtake: N, the exact take. Withoutcut.json, or for a shot the list leaves out, a shot goes in script order and uses its latest usable take: statusokand the mp4 is there, so a--redostill queued or failed is skipped. Takes from before sidecars count as ok. - A picked take that is queued, failed or has no mp4 makes the shot missing, and the report
says why.
--take Nforces take N for every shot and beatscut.json. - Entries for shots deleted from the script are skipped with a note. An entry with
"pass": "proxy"in the final list is a placeholder: the proxy take is scaled to the final size.trim_in/trim_outdrop frames from the head and tail. - The pass comes from the shotlist’s name (
_proxymeans proxy);--passoverrides it. --checklists the resolved cut (order, take, pass, trims, placeholders, orphans) and writes nothing.--namesets the output filename,--shotlistpicks the proxy files,--subfolderreads this pass’s takes from another folder.- Mute clips (dub, clone) get their
_h3.wavadded so the join works.--audio auto|mp4|h3|none|masterchooses the sound for the clips that have none of their own. A clip can have its own:cut.json’saudiolays another take’s sound, a file’s, or silence under it, cut or padded to the clip so the length never changes (the Timeline’s Audio from…, orseriesmith cut --audio).--audio masterandnoneoverride those; the others leave them alone. - Shots with an
audio:window are trimmed to it (re-encoded, x264 CRF 16) so the cut lines up with the recording.--no-trimkeeps the padding. epNN_shots.txtlists every shot’s start time in the cut.--upscaled(either pass) plays each clip’s fresh upscale (seriesmith upscale) and writesepNN_up.mp4at the upscales’ size beside the plain cut; a clip without one is scaled up and named in the report.--size WxHsets the cut’s size (1920x1080for a 1920×1088 upscale), letterboxing a clip whose aspect differs.- This is a review cut. Mix the final audio in Resolve.
Takes, picks and overrides
takesmarks a take stale when rendering the shot now would differ:script(the shot changed in the script or series config),ref(a reference image or voice changed) orpreset(model, LoRA, steps or size defaults changed). Stale is a hint, not an error.overrides.jsonis how you tweak one shot without touching the script: the exact prompt, a seed, model, LoRAs, steps. Prompt, model, LoRAs and steps are per pass (final unless--proxy;--bothsets both), because the proxy’s prompt differs when it generates its audio. The seed applies to both passes. A rebuild never loses an override. If the shot has changed since you wrote it,takesandoverrideflag it STALE, and it is still applied.- Model, LoRAs and steps belong in the script when they’re decisions about the film.
overrides.jsonis for the tweak loop. When a tweak turns out to be a decision,seriesmith promotemoves it into the script or the series config for you — it prints the plan and the diffs first, and--all(or--item shot:sh020:steps) writes them, drops those overrides and rebuilds. The old files are kept in<episode>\_history\.
Editing the cut
cut.json holds it all — order, the take each shot uses, trims, locks and per-clip audio —
per pass, and assemble follows it. The editor’s Timeline writes the same file.
Finishing: upscale, post, master, publish
upscalerefines each take of the pass’s cut that has no fresh upscale into<stem>.up.mp4beside it:--method latent(re-sampled late in the take’s own schedule; the default where the target has one),pixel(an upscale model;--pixel-model rtx_vsrfor NVIDIA RTX Video Super Resolution) orseedvr2.--start-step,--scale,--vae,--deliver WxH,--quality review|master,--redo.postfinishes an upscale into<stem>.post.mp4:--enhance draft|production|cinematic(orpixel|seedvr2|supir|none),--blur,--grain; with neither, the series config’spost.masterrecipe (POST_PROCESSING.md).masterruns the series config’supscale.masterrecipe (andpost.masterwith--post) on every shot of the cut, waits for them, then assembles and titles the master into<episode>/master/.--conform,--allow-gaps,--order cut|target,--prores/--prores-only.publishputs the show’s_titles/INTRO.mp4andOUTRO.mp4around a cut (--input, default the review cut) with the episode title, into<episode>/publish/.upscale,postandmasterqueue on a backend like a render (--backend, defaultlocal);publishis ffmpeg on this machine.
Models
target-from-workflowreads a saved workflow (a name among ComfyUI’s, or a.jsonpath) against the running ComfyUI’s/object_info, and proposes a video or image model: which widget takes the prompt, size, length, seed, steps and each model file, and which output node saves the take.--savewrites it into<show>/targets/<id>/target.json.- A model for every show, a Comfy Router model, limits, and switching on a built-in draft are done in the editor’s Models window (EDITOR.md, “The Models window”).
Backends and the editor’s server
backendsreads this machine’s settings andCOMFY_API_KEY(SETUP.md):localalways,cloudandrouterwith a key, adeployment:<target>per deployment.servestarts comfy-api-proxy for the local backend and picks up the jobs the last run left.--port(default 8190),--host,--root(repeatable),--auto-launch(open the editor in the browser once it is listening, as the .bat launchers do).stopasks the server on this machine to stop (--port);--restartrestarts it when it runs underrun_seriesmith.bat/run_seriesmith_ext.bat(a bareseriesmith servejust stops). Jobs still running on a backend are picked up on the next start.
Recorded dialogue (lip sync)
Full guide: RECORDED_DIALOGUE.md.- The editor does the same from its Recording window: attach the file (browse the
ComfyUI machine, type a path, or drop it in), then align, with progress and the report.
It runs align in whichever Python has
faster-whisperandnumpy— see INSTALL.md if it picks the wrong one. - align transcribes the recording with word timings, matches it to the script, and cuts it into one continuous run of windows with cuts at the quietest point of each pause.
- It writes
audio: in-outon every shot, points the series config at the recording, keeps the old copy of each in<episode>\_history\(the editor’s history), and writesalign_report.md. - Speaking shots then default to
dub_keep_foley: H3 lip-syncs to your line and keeps its own sound effects as_foley.wav. - The
retentionsetting controls how much of your line H3 copies:
Set it per shot (
retention: fully_copy) or for the episode ("audio": {"retention": ...} in
the series config). clone and generate shots don’t use it.