persona-community-5/pkg/mediagen/adapters/laozhang.go
jordan bd2f591b98
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/manual/woodpecker Pipeline was successful
Initialize project from skeleton template
2026-02-24 07:39:46 +00:00

190 lines
4.9 KiB
Go

package adapters
import (
"context"
"encoding/base64"
"fmt"
"git.threesix.ai/jordan/persona-community-5/pkg/laozhang"
"git.threesix.ai/jordan/persona-community-5/pkg/mediagen"
)
// LaoZhangProvider adapts pkg/laozhang to the mediagen interfaces.
type LaoZhangProvider struct {
client *laozhang.Client
}
// NewLaoZhangProvider creates a new LaoZhang provider adapter.
func NewLaoZhangProvider(client *laozhang.Client) *LaoZhangProvider {
return &LaoZhangProvider{client: client}
}
// Name implements mediagen.ImageGenerator and mediagen.VideoGenerator.
func (p *LaoZhangProvider) Name() string {
return "laozhang"
}
// Health implements mediagen.ImageGenerator and mediagen.VideoGenerator.
func (p *LaoZhangProvider) Health(ctx context.Context) error {
if err := p.client.Health(ctx); err != nil {
return mediagen.NewProviderError("laozhang", "Health", err)
}
return nil
}
// GenerateImage implements mediagen.ImageGenerator.
// Supports reference images for identity consistency via Gemini native API.
// Seed support varies by model - it will be sent but may be ignored.
func (p *LaoZhangProvider) GenerateImage(ctx context.Context, req mediagen.ImageRequest) (*mediagen.ImageResponse, error) {
// Map unified request to LaoZhang-specific request
lzReq := laozhang.ImageRequest{
Prompt: req.Prompt,
Model: req.Model,
Size: req.Size,
AspectRatio: req.AspectRatio,
N: req.Count,
Seed: req.Seed,
}
// Add reference image for identity consistency if provided
if len(req.ReferenceImage) > 0 {
mimeType := req.ReferenceMime
if mimeType == "" {
mimeType = "image/png"
}
lzReq.ReferenceImages = []laozhang.ReferenceImage{
{
Data: req.ReferenceImage,
MimeType: mimeType,
},
}
}
// Set defaults if not provided
if lzReq.N == 0 {
lzReq.N = 1
}
// Call LaoZhang client
lzResp, err := p.client.GenerateImage(ctx, lzReq)
if err != nil {
return nil, mediagen.NewProviderError("laozhang", "GenerateImage", err)
}
// Convert response to unified format
images := make([]mediagen.Image, 0, len(lzResp.Data))
for _, img := range lzResp.Data {
var data []byte
var mimeType string
var url string
if img.B64JSON != "" {
// Decode base64 to bytes
decoded, err := base64.StdEncoding.DecodeString(img.B64JSON)
if err != nil {
return nil, fmt.Errorf("decode base64 image: %w", err)
}
data = decoded
mimeType = "image/png" // LaoZhang returns PNG by default
} else if img.URL != "" {
url = img.URL
mimeType = "image/png"
}
images = append(images, mediagen.Image{
Data: data,
MimeType: mimeType,
URL: url,
})
}
return &mediagen.ImageResponse{
Images: images,
Seed: req.Seed, // Echo back seed (LaoZhang API doesn't return it)
}, nil
}
// GenerateVideo implements mediagen.VideoGenerator.
func (p *LaoZhangProvider) GenerateVideo(ctx context.Context, req mediagen.VideoRequest) (*mediagen.VideoResponse, error) {
// Determine model based on aspect ratio
// Veo 3.1: default is portrait (9:16), add "-landscape" suffix for 16:9
model := req.Model
if model == "" {
model = "veo-3.1"
}
if req.AspectRatio == "16:9" {
model = model + "-landscape"
}
// Map unified request to LaoZhang-specific request
lzReq := laozhang.VideoRequest{
Prompt: req.Prompt,
Model: model,
N: req.Count,
}
// Set defaults if not provided
if lzReq.N == 0 {
lzReq.N = 1
}
// Convert reference images to base64 data URLs if provided
if len(req.ReferenceImages) > 0 {
lzReq.ReferenceImages = make([]string, 0, len(req.ReferenceImages))
for _, img := range req.ReferenceImages {
if len(img.Data) > 0 {
// Convert bytes to data URL
b64 := base64.StdEncoding.EncodeToString(img.Data)
mimeType := img.MimeType
if mimeType == "" {
mimeType = "image/png"
}
dataURL := fmt.Sprintf("data:%s;base64,%s", mimeType, b64)
lzReq.ReferenceImages = append(lzReq.ReferenceImages, dataURL)
} else if img.URL != "" {
lzReq.ReferenceImages = append(lzReq.ReferenceImages, img.URL)
}
}
}
// Call LaoZhang client
lzResp, err := p.client.GenerateVideo(ctx, lzReq)
if err != nil {
return nil, mediagen.NewProviderError("laozhang", "GenerateVideo", err)
}
// Convert response to unified format - download video bytes from URLs
videos := make([]mediagen.Video, 0, len(lzResp.Data))
for _, vid := range lzResp.Data {
if vid.URL == "" {
continue
}
// Download video from URL
data, err := downloadVideo(ctx, vid.URL)
if err != nil {
return nil, fmt.Errorf("download video from %s: %w", vid.URL, err)
}
videos = append(videos, mediagen.Video{
Data: data,
URL: vid.URL,
MimeType: "video/mp4",
})
}
if len(videos) == 0 {
return nil, fmt.Errorf("no videos returned from LaoZhang")
}
return &mediagen.VideoResponse{
Videos: videos,
}, nil
}
// Compile-time interface check
var (
_ mediagen.ImageGenerator = (*LaoZhangProvider)(nil)
_ mediagen.VideoGenerator = (*LaoZhangProvider)(nil)
)