summaryrefslogtreecommitdiff
path: root/internal/video
diff options
context:
space:
mode:
Diffstat (limited to 'internal/video')
-rw-r--r--internal/video/generate_selected.go44
-rw-r--r--internal/video/veo.go325
-rw-r--r--internal/video/veo_test.go273
3 files changed, 0 insertions, 642 deletions
diff --git a/internal/video/generate_selected.go b/internal/video/generate_selected.go
deleted file mode 100644
index b29f578..0000000
--- a/internal/video/generate_selected.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package video
-
-import (
- "context"
- "fmt"
-)
-
-// GenerateSelectedVideos animates gallery PNG files into MP4 clips using
-// Google's Veo model. It processes pages sequentially (Veo generation is slow
-// and API quotas make parallelism impractical).
-//
-// apiKey is the Google/Gemini API key passed by the caller.
-// selectedPaths contains the absolute (or relative) paths of the PNGs to animate.
-//
-// Each page prints a "Generating…" line before the API call and a "Video saved:"
-// line with the output path on success. The MP4 is written next to its source
-// PNG so that gallery images and their videos stay in the same directory.
-// The function stops and returns on the first error so the caller can log it.
-func GenerateSelectedVideos(apiKey string, selectedPaths []string) error {
- if len(selectedPaths) == 0 {
- return nil
- }
-
- gen, err := NewVeoGenerator(apiKey)
- if err != nil {
- return fmt.Errorf("video: initialising Veo generator: %w", err)
- }
-
- ctx := context.Background()
-
- for _, imgPath := range selectedPaths {
- fmt.Printf("Generating video for: %s\n", imgPath)
-
- // GenerateVideoFromPath applies an operation-level deadline when ctx has none.
- mp4Path, err := gen.GenerateVideoFromPath(ctx, imgPath)
- if err != nil {
- return fmt.Errorf("video: generating video for %s: %w", imgPath, err)
- }
-
- fmt.Printf("Video saved: %s\n", mp4Path)
- }
-
- return nil
-}
diff --git a/internal/video/veo.go b/internal/video/veo.go
deleted file mode 100644
index 5c62ec0..0000000
--- a/internal/video/veo.go
+++ /dev/null
@@ -1,325 +0,0 @@
-// Package video provides Veo-based MP4 generation from selected PNGs.
-package video
-
-import (
- "context"
- "fmt"
- "log"
- "os"
- "path/filepath"
- "strings"
- "time"
-
- "google.golang.org/genai"
-
- "codeberg.org/snonux/totalrecall/internal/httpctx"
-)
-
-const (
- // DefaultVeoModel is the Veo model used for video generation.
- // veo-2.0-generate-001 is the current stable Gemini-API-accessible model.
- DefaultVeoModel = "veo-2.0-generate-001"
-
- // videoDurationSeconds is the clip length requested from Veo.
- // 8 seconds is the minimum duration supported by the Veo API and produces
- // clips long enough to convey the flashcard content without excess.
- videoDurationSeconds = int32(8)
-
- // videoAspectRatio is the target aspect ratio for generated clips.
- // 16:9 matches the landscape orientation of the comic-style gallery panels.
- videoAspectRatio = "16:9"
-
- // pollInterval is the time to wait between operation status checks.
- // Veo generation typically takes 1–3 minutes; 15 s keeps polling overhead low.
- pollInterval = 15 * time.Second
-
- // maxPollAttempts caps the number of polling iterations so that a hung or
- // stalled Veo operation does not block the process indefinitely.
- // At 15 s per attempt, 40 attempts ≈ 10 minutes — well above the observed
- // worst-case generation time of ~3 minutes.
- maxPollAttempts = 40
-)
-
-// VeoGenerator wraps the Google GenAI client for Veo video generation.
-type VeoGenerator struct {
- client *genai.Client
- model string
-}
-
-// newGenaiClient is the constructor used in production and can be replaced in
-// unit tests to inject a mock transport.
-var newGenaiClient = httpctx.NewGenAIClient
-
-// NewVeoGenerator creates a new VeoGenerator backed by the Gemini API.
-// It returns an error if the API key is empty or the SDK client cannot be
-// initialised (e.g. due to network or credential issues).
-func NewVeoGenerator(apiKey string) (*VeoGenerator, error) {
- apiKey = strings.TrimSpace(apiKey)
- if apiKey == "" {
- return nil, fmt.Errorf("veo: API key is required")
- }
-
- client, err := newGenaiClient(context.Background(), &genai.ClientConfig{
- APIKey: apiKey,
- Backend: genai.BackendGeminiAPI,
- })
- if err != nil {
- return nil, fmt.Errorf("veo: failed to create genai client: %w", err)
- }
-
- return &VeoGenerator{
- client: client,
- model: DefaultVeoModel,
- }, nil
-}
-
-// GenerateVideoFromGallery reads the gallery PNG for pageNum, calls the Veo API,
-// polls until the operation completes, then writes the resulting MP4 to outputDir.
-// It returns the absolute path of the saved MP4 file, or an error.
-//
-// galleryPath is the directory containing files named
-// "<slug>_gallery_<N>.png" (e.g. /stories/ябълка/ябълка_gallery_1.png).
-// outputDir is where the output MP4 will be written.
-// pageNum selects which gallery page to animate (1-based).
-func (g *VeoGenerator) GenerateVideoFromGallery(ctx context.Context, galleryPath string, outputDir string, pageNum int) (string, error) {
- ctx, cancel := httpctx.WithTimeoutUnlessSet(ctx, httpctx.VeoCLIPerVideoTimeout)
- defer cancel()
-
- imgPath, imgBytes, err := loadGalleryImage(galleryPath, pageNum)
- if err != nil {
- return "", err
- }
-
- prompt := buildVeoPrompt()
-
- log.Printf("veo: generating video from %s (page %d)", imgPath, pageNum)
-
- mp4Path, err := g.generateAndSave(ctx, imgBytes, prompt, outputDir, imgPath, pageNum)
- if err != nil {
- return "", err
- }
-
- return mp4Path, nil
-}
-
-// GenerateVideoFromPath reads the gallery PNG at the given absolute (or
-// relative) imgPath, calls the Veo API, and writes the resulting MP4 to the
-// same directory that contains imgPath. It returns the absolute path of the
-// saved MP4 or an error.
-//
-// This variant is preferred over GenerateVideoFromGallery when the caller
-// already knows the exact image path (e.g. from a recursive directory walk),
-// because it avoids a second glob search and always writes the video next to
-// its source image.
-func (g *VeoGenerator) GenerateVideoFromPath(ctx context.Context, imgPath string) (string, error) {
- ctx, cancel := httpctx.WithTimeoutUnlessSet(ctx, httpctx.VeoCLIPerVideoTimeout)
- defer cancel()
-
- imgBytes, err := os.ReadFile(imgPath)
- if err != nil {
- return "", fmt.Errorf("veo: reading gallery image %s: %w", imgPath, err)
- }
-
- // Derive the page number from the file name for saveMP4 naming purposes.
- pageNum := pageNumFromPath(imgPath)
-
- prompt := buildVeoPrompt()
-
- log.Printf("veo: generating video from %s", imgPath)
-
- // Write the MP4 next to the source image so gallery + video stay together.
- outputDir := filepath.Dir(imgPath)
-
- return g.generateAndSave(ctx, imgBytes, prompt, outputDir, imgPath, pageNum)
-}
-
-// pageNumFromPath extracts the gallery page number from a file name of the
-// form "<slug>_gallery_<N>.png". Returns 0 when the name does not match.
-func pageNumFromPath(imgPath string) int {
- base := filepath.Base(imgPath)
- name := strings.TrimSuffix(base, ".png")
- const marker = "_gallery_"
- idx := strings.LastIndex(name, marker)
- if idx < 0 {
- return 0
- }
- numStr := name[idx+len(marker):]
- var n int
- if _, err := fmt.Sscanf(numStr, "%d", &n); err != nil || n <= 0 {
- return 0
- }
- return n
-}
-
-// loadGalleryImage finds the gallery PNG for the given page number and returns
-// its path and raw bytes. It searches galleryPath for any file whose name
-// matches the pattern "*_gallery_<N>.png".
-func loadGalleryImage(galleryPath string, pageNum int) (string, []byte, error) {
- pattern := filepath.Join(galleryPath, fmt.Sprintf("*_gallery_%d.png", pageNum))
- matches, err := filepath.Glob(pattern)
- if err != nil {
- return "", nil, fmt.Errorf("veo: glob for gallery image: %w", err)
- }
- if len(matches) == 0 {
- return "", nil, fmt.Errorf("veo: no gallery image found for page %d in %s", pageNum, galleryPath)
- }
-
- imgPath := matches[0]
- imgBytes, err := os.ReadFile(imgPath)
- if err != nil {
- return "", nil, fmt.Errorf("veo: reading gallery image %s: %w", imgPath, err)
- }
-
- return imgPath, imgBytes, nil
-}
-
-// buildVeoPrompt returns the text prompt sent alongside the gallery image.
-// The prompt asks Veo to animate the comic panel while preserving the style
-// and characters so that the result fits naturally into a flashcard context.
-func buildVeoPrompt() string {
- return "Animate this comic-style flashcard illustration as a short, loopable 8-second clip. " +
- "Preserve the hand-drawn comic art style exactly — bold outlines, flat colours, speech bubbles. " +
- "Add subtle motion: characters breathe or gesture gently, the Bulgarian word label pulses softly, " +
- "and background elements drift slowly. Keep the mood educational and friendly. " +
- "No scene cuts, no camera moves — a single steady wide shot throughout. " +
- "Do not change the characters, layout, or colour palette."
-}
-
-// generateAndSave calls the Veo API, polls the long-running operation, downloads
-// the resulting video bytes, and writes them to an MP4 file in outputDir.
-func (g *VeoGenerator) generateAndSave(ctx context.Context, imgBytes []byte, prompt, outputDir, srcPath string, pageNum int) (string, error) {
- op, err := g.startOperation(ctx, imgBytes, prompt)
- if err != nil {
- return "", err
- }
-
- op, err = g.pollUntilDone(ctx, op)
- if err != nil {
- return "", err
- }
-
- videoBytes, err := g.downloadVideo(ctx, op)
- if err != nil {
- return "", err
- }
-
- return saveMP4(videoBytes, outputDir, srcPath, pageNum)
-}
-
-// startOperation submits the image + prompt to the Veo API and returns the
-// initial operation descriptor (which will have Done == false).
-func (g *VeoGenerator) startOperation(ctx context.Context, imgBytes []byte, prompt string) (*genai.GenerateVideosOperation, error) {
- dur := videoDurationSeconds
- cfg := &genai.GenerateVideosConfig{
- AspectRatio: videoAspectRatio,
- DurationSeconds: &dur,
- NumberOfVideos: 1,
- }
-
- source := &genai.GenerateVideosSource{
- Prompt: prompt,
- Image: &genai.Image{
- ImageBytes: imgBytes,
- MIMEType: "image/png",
- },
- }
-
- op, err := g.client.Models.GenerateVideosFromSource(ctx, g.model, source, cfg)
- if err != nil {
- return nil, fmt.Errorf("veo: failed to start video generation: %w", err)
- }
-
- log.Printf("veo: operation started (done=%v)", op.Done)
- return op, nil
-}
-
-// pollUntilDone repeatedly calls GetVideosOperation until the operation reports
-// completion, the context is cancelled, or maxPollAttempts is reached.
-// It sleeps pollInterval between checks to avoid hammering the API.
-func (g *VeoGenerator) pollUntilDone(ctx context.Context, op *genai.GenerateVideosOperation) (*genai.GenerateVideosOperation, error) {
- for attempt := 0; !op.Done; attempt++ {
- if attempt >= maxPollAttempts {
- return nil, fmt.Errorf("veo: operation did not complete after %d attempts (%s each)", maxPollAttempts, pollInterval)
- }
-
- log.Printf("veo: operation in progress, waiting %s (attempt %d/%d)...", pollInterval, attempt+1, maxPollAttempts)
-
- select {
- case <-ctx.Done():
- return nil, fmt.Errorf("veo: context cancelled while polling: %w", ctx.Err())
- case <-time.After(pollInterval):
- }
-
- var err error
- op, err = g.client.Operations.GetVideosOperation(ctx, op, nil)
- if err != nil {
- return nil, fmt.Errorf("veo: polling operation failed: %w", err)
- }
- }
-
- log.Printf("veo: operation completed")
- return op, nil
-}
-
-// downloadVideo extracts the video from a completed operation, downloading bytes
-// via the Files API when the response contains only a URI reference.
-func (g *VeoGenerator) downloadVideo(ctx context.Context, op *genai.GenerateVideosOperation) ([]byte, error) {
- // Surface any API-level error (e.g. content policy or geographic restriction)
- // before checking for videos, so the caller gets a meaningful message.
- if len(op.Error) > 0 {
- msg, _ := op.Error["message"].(string)
- if msg == "" {
- msg = fmt.Sprintf("%v", op.Error)
- }
- return nil, fmt.Errorf("veo: %s", msg)
- }
- if op.Response == nil || len(op.Response.GeneratedVideos) == 0 {
- return nil, fmt.Errorf("veo: operation completed but no videos in response")
- }
-
- gv := op.Response.GeneratedVideos[0]
- if gv == nil || gv.Video == nil {
- return nil, fmt.Errorf("veo: generated video entry is empty")
- }
-
- // When the Gemini API returns a URI, download bytes via the Files API.
- if gv.Video.URI != "" {
- log.Printf("veo: downloading video from URI %s", gv.Video.URI)
- data, err := g.client.Files.Download(ctx, genai.NewDownloadURIFromGeneratedVideo(gv), nil)
- if err != nil {
- return nil, fmt.Errorf("veo: downloading video: %w", err)
- }
- return data, nil
- }
-
- // Inline bytes path (used in some Vertex AI configurations).
- if len(gv.Video.VideoBytes) > 0 {
- return gv.Video.VideoBytes, nil
- }
-
- return nil, fmt.Errorf("veo: no video bytes or URI available in response")
-}
-
-// saveMP4 writes videoBytes to a file in outputDir, deriving the file name from
-// the source gallery image path and the page number.
-// It returns the absolute path of the written file.
-func saveMP4(videoBytes []byte, outputDir, srcPath string, pageNum int) (string, error) {
- if err := os.MkdirAll(outputDir, 0o755); err != nil {
- return "", fmt.Errorf("veo: creating output dir %s: %w", outputDir, err)
- }
-
- // Derive base name from the source image, e.g. "ябълка_gallery_1.png" → "ябълка_gallery_1.mp4"
- base := strings.TrimSuffix(filepath.Base(srcPath), ".png")
- if base == "" || base == srcPath {
- // Fallback when the source name is unexpected.
- base = fmt.Sprintf("gallery_%d", pageNum)
- }
-
- outPath := filepath.Join(outputDir, base+".mp4")
- if err := os.WriteFile(outPath, videoBytes, 0o644); err != nil {
- return "", fmt.Errorf("veo: writing mp4 to %s: %w", outPath, err)
- }
-
- log.Printf("veo: saved MP4 to %s (%d bytes)", outPath, len(videoBytes))
- return outPath, nil
-}
diff --git a/internal/video/veo_test.go b/internal/video/veo_test.go
deleted file mode 100644
index ce653e3..0000000
--- a/internal/video/veo_test.go
+++ /dev/null
@@ -1,273 +0,0 @@
-// Package video_test provides unit tests for the Veo video generator.
-// All tests are mock-based — no real API calls are made.
-package video
-
-import (
- "context"
- "errors"
- "os"
- "path/filepath"
- "strings"
- "testing"
-
- "google.golang.org/genai"
-)
-
-// TestNewVeoGenerator_EmptyKey verifies that an empty API key is rejected.
-func TestNewVeoGenerator_EmptyKey(t *testing.T) {
- t.Parallel()
-
- _, err := NewVeoGenerator("")
- if err == nil {
- t.Fatal("expected error for empty API key, got nil")
- }
-}
-
-// TestNewVeoGenerator_WhitespaceKey verifies that a whitespace-only API key is
-// treated the same as an empty key.
-func TestNewVeoGenerator_WhitespaceKey(t *testing.T) {
- t.Parallel()
-
- _, err := NewVeoGenerator(" ")
- if err == nil {
- t.Fatal("expected error for whitespace API key, got nil")
- }
-}
-
-// TestNewVeoGenerator_ClientInitFailure verifies that a genai client
-// initialisation error propagates as a wrapped error.
-func TestNewVeoGenerator_ClientInitFailure(t *testing.T) {
- // Do not use t.Parallel: this test replaces the package-global newGenaiClient hook.
-
- // Temporarily replace the genai client constructor with one that always fails.
- orig := newGenaiClient
- newGenaiClient = func(_ context.Context, _ *genai.ClientConfig) (*genai.Client, error) {
- return nil, errors.New("injected init error")
- }
- t.Cleanup(func() { newGenaiClient = orig })
-
- _, err := NewVeoGenerator("test-api-key")
- if err == nil {
- t.Fatal("expected error from client init failure, got nil")
- }
- if !strings.Contains(err.Error(), "injected init error") {
- t.Fatalf("unexpected error text: %v", err)
- }
-}
-
-// TestLoadGalleryImage_Missing verifies that loadGalleryImage returns an error
-// when no matching file exists in the given directory.
-func TestLoadGalleryImage_Missing(t *testing.T) {
- t.Parallel()
-
- dir := t.TempDir()
- _, _, err := loadGalleryImage(dir, 1)
- if err == nil {
- t.Fatal("expected error for missing gallery image, got nil")
- }
-}
-
-// TestLoadGalleryImage_Found verifies that loadGalleryImage returns the correct
-// path and bytes when the expected file exists.
-func TestLoadGalleryImage_Found(t *testing.T) {
- t.Parallel()
-
- dir := t.TempDir()
- imgFile := filepath.Join(dir, "ябълка_gallery_2.png")
- wantBytes := []byte("fake-png-data")
- if err := os.WriteFile(imgFile, wantBytes, 0o644); err != nil {
- t.Fatalf("setup: write test image: %v", err)
- }
-
- gotPath, gotBytes, err := loadGalleryImage(dir, 2)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- if gotPath != imgFile {
- t.Errorf("path: got %q, want %q", gotPath, imgFile)
- }
- if string(gotBytes) != string(wantBytes) {
- t.Errorf("bytes: got %q, want %q", gotBytes, wantBytes)
- }
-}
-
-// TestBuildVeoPrompt verifies that the prompt is non-empty and contains the
-// key terms that shape Veo's output style.
-func TestBuildVeoPrompt(t *testing.T) {
- t.Parallel()
-
- prompt := buildVeoPrompt()
- if prompt == "" {
- t.Fatal("buildVeoPrompt returned empty string")
- }
-
- keywords := []string{"comic", "Bulgarian", "educational", "8-second"}
- for _, kw := range keywords {
- if !strings.Contains(prompt, kw) {
- t.Errorf("expected prompt to contain %q", kw)
- }
- }
-}
-
-// TestSaveMP4_WritesFile verifies that saveMP4 creates the expected MP4 file and
-// returns its absolute path.
-func TestSaveMP4_WritesFile(t *testing.T) {
- t.Parallel()
-
- outDir := t.TempDir()
- fakeVideo := []byte{0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70} // minimal ftyp box bytes
-
- // Simulate source path like the real gallery image would produce.
- srcPath := "/stories/ябълка/ябълка_gallery_3.png"
-
- got, err := saveMP4(fakeVideo, outDir, srcPath, 3)
- if err != nil {
- t.Fatalf("saveMP4 failed: %v", err)
- }
-
- if !strings.HasSuffix(got, ".mp4") {
- t.Errorf("expected .mp4 suffix, got %q", got)
- }
-
- data, err := os.ReadFile(got)
- if err != nil {
- t.Fatalf("reading saved MP4: %v", err)
- }
- if string(data) != string(fakeVideo) {
- t.Errorf("file contents mismatch")
- }
-}
-
-// TestSaveMP4_CreatesOutputDir verifies that saveMP4 creates the output directory
-// when it does not already exist.
-func TestSaveMP4_CreatesOutputDir(t *testing.T) {
- t.Parallel()
-
- base := t.TempDir()
- outDir := filepath.Join(base, "nested", "output")
- fakeVideo := []byte("video-data")
-
- _, err := saveMP4(fakeVideo, outDir, "word_gallery_1.png", 1)
- if err != nil {
- t.Fatalf("saveMP4 failed: %v", err)
- }
-
- if _, statErr := os.Stat(outDir); os.IsNotExist(statErr) {
- t.Error("expected output directory to be created")
- }
-}
-
-// TestSaveMP4_FallbackName verifies that saveMP4 uses a fallback name when the
-// source path lacks a recognisable gallery file name (no .png suffix).
-func TestSaveMP4_FallbackName(t *testing.T) {
- t.Parallel()
-
- outDir := t.TempDir()
- fakeVideo := []byte("video-data")
-
- // srcPath with no .png extension triggers the fallback naming path.
- got, err := saveMP4(fakeVideo, outDir, "unusual_source", 5)
- if err != nil {
- t.Fatalf("saveMP4 failed: %v", err)
- }
-
- if !strings.HasSuffix(got, ".mp4") {
- t.Errorf("expected .mp4 suffix even for fallback name, got %q", got)
- }
-}
-
-// ---------------------------------------------------------------------------
-// pageNumFromPath
-// ---------------------------------------------------------------------------
-
-// TestPageNumFromPath verifies that pageNumFromPath correctly extracts the
-// gallery page number from various file name patterns.
-func TestPageNumFromPath(t *testing.T) {
- t.Parallel()
-
- cases := []struct {
- path string
- want int
- }{
- {"/stories/ябълка/ябълка_gallery_1.png", 1},
- {"/stories/word/word_gallery_10.png", 10},
- // Non-gallery path — should return 0.
- {"/stories/word/word_cover.png", 0},
- // Missing trailing number — should return 0.
- {"/stories/word/word_gallery_.png", 0},
- // Page number zero — should return 0 (non-positive).
- {"/stories/word/word_gallery_0.png", 0},
- // Nested gallery name with multiple "_gallery_" tokens — last one wins.
- {"/comics/slug/slug_gallery_3.png", 3},
- }
-
- for _, tc := range cases {
- got := pageNumFromPath(tc.path)
- if got != tc.want {
- t.Errorf("pageNumFromPath(%q) = %d, want %d", tc.path, got, tc.want)
- }
- }
-}
-
-// ---------------------------------------------------------------------------
-// loadGalleryImage — additional edge cases
-// ---------------------------------------------------------------------------
-
-// TestLoadGalleryImage_MultipleMatchesUsesFirst verifies that when several
-// gallery files share the same page number, loadGalleryImage returns the
-// lexicographically first match without error.
-func TestLoadGalleryImage_MultipleMatchesUsesFirst(t *testing.T) {
- t.Parallel()
-
- dir := t.TempDir()
-
- // Two files for page 1 — alphabetical order determines which is returned.
- files := []string{"aaa_gallery_1.png", "zzz_gallery_1.png"}
- for _, name := range files {
- if err := os.WriteFile(filepath.Join(dir, name), []byte(name), 0o644); err != nil {
- t.Fatalf("setup: %v", err)
- }
- }
-
- gotPath, gotBytes, err := loadGalleryImage(dir, 1)
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
-
- // filepath.Glob returns results in sorted order, so aaa_gallery_1.png comes first.
- expectedName := "aaa_gallery_1.png"
- if filepath.Base(gotPath) != expectedName {
- t.Errorf("expected first match %q, got %q", expectedName, filepath.Base(gotPath))
- }
- if string(gotBytes) != expectedName {
- t.Errorf("bytes mismatch: got %q, want %q", gotBytes, expectedName)
- }
-}
-
-// ---------------------------------------------------------------------------
-// VeoGenerator — constructor with valid mock client
-// ---------------------------------------------------------------------------
-
-// TestNewVeoGenerator_WithMockClient verifies that NewVeoGenerator succeeds
-// when the genai client factory does not return an error.
-func TestNewVeoGenerator_WithMockClient(t *testing.T) {
- // Do not use t.Parallel: this test replaces the package-global newGenaiClient hook.
-
- orig := newGenaiClient
- newGenaiClient = func(_ context.Context, _ *genai.ClientConfig) (*genai.Client, error) {
- // Return a zero-value client pointer — sufficient for construction.
- return &genai.Client{}, nil
- }
- t.Cleanup(func() { newGenaiClient = orig })
-
- gen, err := NewVeoGenerator("valid-api-key")
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- if gen == nil {
- t.Fatal("expected non-nil VeoGenerator")
- }
- if gen.model != DefaultVeoModel {
- t.Errorf("model: got %q, want %q", gen.model, DefaultVeoModel)
- }
-}