Files
majordomo/videogen/chain.go
T
steveandClaude Fable 5 56b5b000a6
CI / Tidy (pull_request) Successful in 9m25s
CI / Build & Test (pull_request) Successful in 9m46s
fix: review findings — chain NaN/Inf + id hygiene, percent-escape jobPath, shared singleVideoResult
- SubmitChain rejects NaN/±Inf segment seconds with ErrUnsupported
  (previously an obscure json.Marshal error; NaN fails every comparison
  and +Inf passed the >= 0 check).
- ChainStatus skips segment entries with no usable id — JSON null
  (which no-op-unmarshals into a string, previously appending ""),
  empty strings, and id-less objects; the unfiltered list survives in
  Raw. ChainJob.SegmentIDs doc now also says ChainSegmentResult takes
  the segment index, not an id string.
- jobPath rejects '%' in job ids — %2F/%2E%2E percent-escapes decode
  back into path structure server-side, bypassing the literal check on
  this upstream-echoed value.
- singleVideoResult moves to video.go next to videoMIME, and the two
  remaining hand-rolled copies of the video-result validation
  (videoModel.Generate, Interpolate) now use it — one validation, one
  message shape.
- videogen.LipSyncer renamed to videogen.Lipsyncer for consistency with
  the rest of the surface's Lipsync* naming (LipsyncProvider,
  LipsyncModel, LipsyncRequest); not yet consumed downstream, so the
  rename is free now and never again.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WWCQcYStWXBYUy5sZWnbLT
2026-07-16 19:12:42 -04:00

104 lines
3.5 KiB
Go

package videogen
import "context"
// ChainSegment is one prompt in a multi-segment ("long video") chain.
type ChainSegment struct {
// Prompt describes this segment. Required.
Prompt string
// Seconds is the segment's requested length; 0 = backend default.
Seconds float64
}
// ChainRequest asks a chain orchestrator to generate a long video as a
// sequence of segments, each continuing from the previous segment's last
// frame. Zero values mean "backend default" (ADR-0025).
type ChainRequest struct {
// Segments are the per-segment prompts in order. At least one required.
Segments []ChainSegment
// InitImage optionally conditions the FIRST segment on a starting frame
// (image-to-video); nil = pure text-to-video.
InitImage []byte
// SmoothJoins asks the orchestrator to interpolate across segment
// boundaries (RIFE-style) so cuts don't pop.
SmoothJoins bool
// Size is the requested resolution, e.g. "1280x704"; "" = backend
// default.
Size string
}
// ChainJob is a chain job's progress snapshot.
type ChainJob struct {
// Status is the backend's job state, passed through verbatim
// (e.g. "queued", "running", "done", "failed").
Status string
// Segment is the segment currently being generated (1-based); Total is
// the segment count.
Segment int
Total int
// SegmentIDs name the COMPLETED per-segment artifacts, in order. A
// mid-chain failure still leaves those segments retrievable via
// ChainSegmentResult — note it takes the segment's index in the chain,
// not an id string; this list tells you WHICH segments completed. So
// multi-minute GPU output is never discarded. Entries the backend
// reports without a usable id are skipped (the unfiltered list survives
// in Raw).
SegmentIDs []string
// Raw is the provider-native job payload. May be nil.
Raw any
}
// Chainer drives a multi-segment video-chain job. Unlike Model.Generate it
// is deliberately ASYNC — a chain runs through multiple GPU loads for many
// minutes, so callers submit, poll, and fetch instead of holding one
// blocking call open.
type Chainer interface {
// SubmitChain starts a chain job and returns its job id.
SubmitChain(ctx context.Context, req ChainRequest) (string, error)
// ChainStatus reports the job's progress. Polling also signals liveness
// to backends that unload idle orchestrators.
ChainStatus(ctx context.Context, jobID string) (*ChainJob, error)
// ChainResult fetches the finished, concatenated clip.
ChainResult(ctx context.Context, jobID string) (*Result, error)
// ChainSegmentResult fetches one completed segment's clip (n indexes the
// job's segment list) — the partial-delivery path when a chain dies
// mid-run.
ChainSegmentResult(ctx context.Context, jobID string, n int) (*Result, error)
}
// ChainerModelOption configures a Chainer at construction time. Reserved for
// future per-model settings.
type ChainerModelOption func(*ChainerModelConfig)
// ChainerModelConfig carries per-model construction settings.
type ChainerModelConfig struct{}
// ApplyChainerModelOptions folds options into a config.
func ApplyChainerModelOptions(opts []ChainerModelOption) ChainerModelConfig {
var cfg ChainerModelConfig
for _, opt := range opts {
opt(&cfg)
}
return cfg
}
// ChainerProvider mints Chainers bound to one backend.
type ChainerProvider interface {
// Name is the registry identifier for the provider.
Name() string
// ChainerModel returns a Chainer bound to the given id (passed through
// to the backend verbatim; no catalog validation).
ChainerModel(id string, opts ...ChainerModelOption) (Chainer, error)
}