- 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
104 lines
3.5 KiB
Go
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)
|
|
}
|