feat: media expansion surfaces — edit mask, upscale, background removal, interpolation, diarization, meshgen (ADR-0020)
- imagegen.EditRequest.Mask -> sd-server img2img inpainting (white=repaint) - imagegen.Upscaler + BackgroundRemover, videogen.Interpolator, audio.DiarizationModel: new optional provider-minted surfaces - NEW meshgen leaf package (image->3D, glb/stl/obj) - provider/llamaswap: all five via the /upstream/<model>/<path> passthrough (upstreamPath helper, shared one-file multipart builder); binary success bodies validated (non-image, non-video, JSON-mesh rejection); diarization pins output=json (vtt/srt drop speaker labels) Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
package llamaswap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// upstreamPath builds a path through llama-swap's generic /upstream/<model>/
|
||||
// passthrough, which pins the model (triggering the normal load/swap queue)
|
||||
// and forwards the remaining path to the upstream verbatim. This is how the
|
||||
// provider reaches upstreams whose native APIs carry no routable `model`
|
||||
// field (rembg, mediautils, WhisperX, ACE-Step, ...) without needing a
|
||||
// llama-swap route per endpoint (ADR-0020).
|
||||
//
|
||||
// Why reject rather than escape: same rationale as Unload — model ids
|
||||
// legitimately contain ":" but never path-structure characters, and escaping
|
||||
// would mask a config error instead of surfacing it.
|
||||
func upstreamPath(model, rest string) (string, error) {
|
||||
if strings.TrimSpace(model) == "" {
|
||||
return "", fmt.Errorf("llama-swap: upstream call requires a model id")
|
||||
}
|
||||
if strings.ContainsAny(model, "/?#") {
|
||||
return "", fmt.Errorf("llama-swap: invalid model id %q for upstream call (contains a path separator)", model)
|
||||
}
|
||||
if !strings.HasPrefix(rest, "/") {
|
||||
rest = "/" + rest
|
||||
}
|
||||
return "/upstream/" + model + rest, nil
|
||||
}
|
||||
|
||||
// filePart is the single file entry of a media multipart form.
|
||||
type filePart struct {
|
||||
field string // form field name ("file", "audio_file", ...)
|
||||
filename string // already sanitized
|
||||
data []byte
|
||||
}
|
||||
|
||||
// buildMultipart assembles a one-file multipart body: the file part first,
|
||||
// then the given fields (optional fields skipped when empty, matching
|
||||
// writeFormFields). wrap labels errors. Returns the body and its content
|
||||
// type.
|
||||
func buildMultipart(wrap string, file filePart, fields []formField) (*bytes.Buffer, string, error) {
|
||||
var buf bytes.Buffer
|
||||
w := multipart.NewWriter(&buf)
|
||||
fw, err := w.CreateFormFile(file.field, file.filename)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("llama-swap: %s: %w", wrap, err)
|
||||
}
|
||||
if _, err := fw.Write(file.data); err != nil {
|
||||
return nil, "", fmt.Errorf("llama-swap: %s: %w", wrap, err)
|
||||
}
|
||||
if err := writeFormFields(w, wrap, fields); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
return nil, "", fmt.Errorf("llama-swap: %s: %w", wrap, err)
|
||||
}
|
||||
return &buf, w.FormDataContentType(), nil
|
||||
}
|
||||
Reference in New Issue
Block a user