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
This commit is contained in:
@@ -22,7 +22,7 @@ import (
|
||||
|
||||
// LipsyncModel implements videogen.LipsyncProvider. The id selects which
|
||||
// upstream llama-swap loads.
|
||||
func (p *Provider) LipsyncModel(id string, opts ...videogen.LipsyncModelOption) (videogen.LipSyncer, error) {
|
||||
func (p *Provider) LipsyncModel(id string, opts ...videogen.LipsyncModelOption) (videogen.Lipsyncer, error) {
|
||||
if err := p.requireBaseURL(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -35,7 +35,7 @@ type lipsyncModel struct {
|
||||
id string
|
||||
}
|
||||
|
||||
// Lipsync implements videogen.LipSyncer.
|
||||
// Lipsync implements videogen.Lipsyncer.
|
||||
func (m *lipsyncModel) Lipsync(ctx context.Context, req videogen.LipsyncRequest, opts ...videogen.LipsyncOption) (*videogen.Result, error) {
|
||||
req = req.Apply(opts...)
|
||||
if len(req.Image.Data) == 0 {
|
||||
@@ -95,19 +95,3 @@ func (m *lipsyncModel) Lipsync(ctx context.Context, req videogen.LipsyncRequest,
|
||||
}
|
||||
return singleVideoResult(m.p.name, m.id, "lipsync", raw, respType)
|
||||
}
|
||||
|
||||
// singleVideoResult wraps one raw video body into a videogen.Result,
|
||||
// requiring positive evidence of video-ness (declared video/* Content-Type
|
||||
// or sniffed mp4/webm magic) so a proxy error page can never become "the
|
||||
// clip" — the video sibling of singleImageResult.
|
||||
func singleVideoResult(provider, model, verb string, raw []byte, contentType string) (*videogen.Result, error) {
|
||||
if len(raw) == 0 {
|
||||
return nil, &llm.APIError{Provider: provider, Model: model, Message: verb + " response contained no video"}
|
||||
}
|
||||
mimeType := videoMIME(contentType, raw)
|
||||
if mimeType == "" {
|
||||
return nil, &llm.APIError{Provider: provider, Model: model,
|
||||
Message: fmt.Sprintf("%s response is not a video (Content-Type %q)", verb, contentType)}
|
||||
}
|
||||
return &videogen.Result{Video: videogen.Video{Data: raw, MIME: mimeType}}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user