feat: wave-3 video surfaces — lipsync, video matte, video upscale, chain jobs (ADR-0025)
- videogen.LipSyncer/LipsyncProvider: SadTalker talking heads via
POST /upstream/<id>/v1/talking_head (multipart image+audio parts,
still/enhance/preprocess flags) -> mp4.
- videogen.VideoBackgroundRemover/VideoBackgroundRemovalProvider:
POST /upstream/<id>/v1/video/matte (output greenscreen_mp4|alpha_webm).
- videogen.VideoUpscaler/VideoUpscaleProvider:
POST /upstream/<id>/v1/video/upscale (scale 2|4).
- videogen.Chainer/ChainerProvider: async long-video chain-job client —
SubmitChain (JSON POST /v1/video/chain, init_image_b64), ChainStatus
(GET /v1/jobs/{id}, tolerant segment-id decode), ChainResult,
ChainSegmentResult (partial delivery after mid-chain failure); hostile
job-id path rejection.
- Shared singleVideoResult validation (positive video evidence) + a
videoInputFilename hint helper; httptest contract tests per surface;
ADR-0025 (index row deferred — MJ-A backfills the ADR index table and
parallel edits would conflict).
Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
package videogen
|
||||
|
||||
import "context"
|
||||
|
||||
// LipsyncRequest asks a talking-head backend (SadTalker style) to animate a
|
||||
// still portrait so it speaks the given audio. Zero values mean "backend
|
||||
// default" (ADR-0025).
|
||||
type LipsyncRequest struct {
|
||||
// Image is the portrait to animate. Required.
|
||||
Image Image
|
||||
|
||||
// Audio is the encoded speech the head lip-syncs to. Required. Carried
|
||||
// as bytes (never a URL), mirroring audio.TranscriptionRequest.
|
||||
Audio []byte
|
||||
|
||||
// AudioMIME is the audio MIME type (e.g. "audio/wav"); "" = let the
|
||||
// backend sniff it.
|
||||
AudioMIME string
|
||||
|
||||
// AudioFilename is the multipart filename hint some backends key their
|
||||
// format detection on; "" derives one from AudioMIME or falls back to
|
||||
// "audio".
|
||||
AudioFilename string
|
||||
|
||||
// Still reduces head motion to blinks and lip movement (less uncanny on
|
||||
// formal portraits); false = backend default motion.
|
||||
Still bool
|
||||
|
||||
// Enhance runs the backend's face enhancer over the output frames.
|
||||
Enhance bool
|
||||
|
||||
// Preprocess selects how the backend frames the face: "crop" (animate
|
||||
// the face crop) or "full" (paste the animated face back into the whole
|
||||
// image); "" = backend default.
|
||||
Preprocess string
|
||||
}
|
||||
|
||||
// LipsyncOption mutates a LipsyncRequest before it is sent.
|
||||
type LipsyncOption func(*LipsyncRequest)
|
||||
|
||||
// WithLipsyncStill reduces head motion to blinks and lip movement.
|
||||
func WithLipsyncStill() LipsyncOption { return func(r *LipsyncRequest) { r.Still = true } }
|
||||
|
||||
// WithLipsyncEnhance runs the backend's face enhancer over the output.
|
||||
func WithLipsyncEnhance() LipsyncOption { return func(r *LipsyncRequest) { r.Enhance = true } }
|
||||
|
||||
// WithLipsyncPreprocess selects the face framing ("crop" or "full").
|
||||
func WithLipsyncPreprocess(p string) LipsyncOption {
|
||||
return func(r *LipsyncRequest) { r.Preprocess = p }
|
||||
}
|
||||
|
||||
// Apply returns a copy of the request with all options applied.
|
||||
func (r LipsyncRequest) Apply(opts ...LipsyncOption) LipsyncRequest {
|
||||
for _, opt := range opts {
|
||||
opt(&r)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// LipSyncer animates still portraits into talking-head clips. Its own small
|
||||
// interface rather than a method on Model: lip-syncers are not text-to-video
|
||||
// generators — they bind to a different backend id entirely.
|
||||
type LipSyncer interface {
|
||||
// Lipsync returns the talking-head clip. Generation is slow (minutes);
|
||||
// bound the call with a context deadline.
|
||||
Lipsync(ctx context.Context, req LipsyncRequest, opts ...LipsyncOption) (*Result, error)
|
||||
}
|
||||
|
||||
// LipsyncModelOption configures a LipSyncer at construction time. Reserved
|
||||
// for future per-model settings.
|
||||
type LipsyncModelOption func(*LipsyncModelConfig)
|
||||
|
||||
// LipsyncModelConfig carries per-model construction settings.
|
||||
type LipsyncModelConfig struct{}
|
||||
|
||||
// ApplyLipsyncModelOptions folds options into a config.
|
||||
func ApplyLipsyncModelOptions(opts []LipsyncModelOption) LipsyncModelConfig {
|
||||
var cfg LipsyncModelConfig
|
||||
for _, opt := range opts {
|
||||
opt(&cfg)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
// LipsyncProvider mints LipSyncers bound to one backend.
|
||||
type LipsyncProvider interface {
|
||||
// Name is the registry identifier for the provider.
|
||||
Name() string
|
||||
|
||||
// LipsyncModel returns a LipSyncer bound to the given id (passed through
|
||||
// to the backend verbatim; no catalog validation).
|
||||
LipsyncModel(id string, opts ...LipsyncModelOption) (LipSyncer, error)
|
||||
}
|
||||
Reference in New Issue
Block a user