fix: address review — fail loud on non-video bodies, dedupe form plumbing, doc parity
- videoMIME no longer hard-falls-back to video/mp4: a 2xx body that is neither declared nor sniffable as video (JSON job envelope, HTML error page) is now an APIError instead of a 'successful' corrupt clip. - Resolution rides the wire as width/height AND the OpenAI-style size string, so either upstream convention honors an explicit request. - writeFormFields + mimeFromContentType shared helpers replace the copied multipart loop (audio.go/video.go) and Content-Type branch. - ADR-0019 indexed in docs/adr/README.md; README gains the videogen section + support-matrix mention (docs-parity rule). Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01AXQxVhXBw8PwFAtsVrXSmj
This commit is contained in:
+26
-19
@@ -64,30 +64,24 @@ func (m *videoModel) Generate(ctx context.Context, req videogen.Request, opts ..
|
||||
|
||||
var buf bytes.Buffer
|
||||
w := multipart.NewWriter(&buf)
|
||||
// Required fields always go on the wire (an empty model id should fail
|
||||
// loudly upstream, not silently vanish); optional ones only when set.
|
||||
fields := []struct {
|
||||
key, value string
|
||||
required bool
|
||||
}{
|
||||
if err := writeFormFields(w, "build video form", []formField{
|
||||
{"model", m.id, true},
|
||||
{"prompt", req.Prompt, true},
|
||||
{"negative_prompt", req.NegativePrompt, false},
|
||||
// Resolution rides the wire twice: width/height (vLLM-Omni's
|
||||
// names) AND the equivalent OpenAI-style size string, since
|
||||
// upstreams silently ignore fields they don't understand and the
|
||||
// values can never disagree.
|
||||
{"width", formatInt(width), false},
|
||||
{"height", formatInt(height), false},
|
||||
{"size", strings.TrimSpace(req.Size), false},
|
||||
{"num_frames", formatNonZero(req.NumFrames), false},
|
||||
{"fps", formatNonZero(req.FPS), false},
|
||||
{"num_inference_steps", formatInt(req.Steps), false},
|
||||
{"guidance_scale", formatFloat(req.GuidanceScale), false},
|
||||
{"seed", formatInt64(req.Seed), false},
|
||||
}
|
||||
for _, f := range fields {
|
||||
if !f.required && f.value == "" {
|
||||
continue
|
||||
}
|
||||
if err := w.WriteField(f.key, f.value); err != nil {
|
||||
return nil, fmt.Errorf("llama-swap: build video form: %w", err)
|
||||
}
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if req.InitImage != nil {
|
||||
fw, err := w.CreateFormFile("input_reference", initImageFilename(req.InitImage.MIME))
|
||||
@@ -109,20 +103,33 @@ func (m *videoModel) Generate(ctx context.Context, req videogen.Request, opts ..
|
||||
if len(videoBytes) == 0 {
|
||||
return nil, &llm.APIError{Provider: m.p.name, Model: m.id, Message: "video response contained no video"}
|
||||
}
|
||||
return &videogen.Result{Video: videogen.Video{Data: videoBytes, MIME: videoMIME(contentType, videoBytes)}}, nil
|
||||
mimeType := videoMIME(contentType, videoBytes)
|
||||
if mimeType == "" {
|
||||
// A 2xx body that is neither declared nor sniffable as video is a
|
||||
// misconfigured upstream (a JSON job envelope, an HTML error page
|
||||
// behind a proxy) — fail loud rather than hand back garbage as a
|
||||
// playable clip.
|
||||
return nil, &llm.APIError{
|
||||
Provider: m.p.name,
|
||||
Model: m.id,
|
||||
Message: fmt.Sprintf("video response is not a video (content-type %q)", contentType),
|
||||
}
|
||||
}
|
||||
return &videogen.Result{Video: videogen.Video{Data: videoBytes, MIME: mimeType}}, nil
|
||||
}
|
||||
|
||||
// videoMIME resolves the result MIME type: the response Content-Type when it
|
||||
// is a concrete video type, else content sniffing, else video/mp4 (the
|
||||
// endpoint's default container).
|
||||
// is a concrete video type, else content sniffing (mp4/webm magic bytes),
|
||||
// else "" — the caller treats undetectable as an upstream error, unlike the
|
||||
// audio path where the request's format param implies the container.
|
||||
func videoMIME(contentType string, data []byte) string {
|
||||
if mt, _, err := mime.ParseMediaType(contentType); err == nil && strings.HasPrefix(mt, "video/") {
|
||||
if mt := mimeFromContentType(contentType, "video/"); mt != "" {
|
||||
return mt
|
||||
}
|
||||
if mt := http.DetectContentType(data); strings.HasPrefix(mt, "video/") {
|
||||
return mt
|
||||
}
|
||||
return "video/mp4"
|
||||
return ""
|
||||
}
|
||||
|
||||
// initImageFilename picks the multipart filename hint for the conditioning
|
||||
|
||||
Reference in New Issue
Block a user