From 9f1168f74b56a3b610c99f90979567f0d6c1fb91 Mon Sep 17 00:00:00 2001 From: ykb Date: Sun, 20 Sep 2026 16:44:57 +0800 Subject: [PATCH] fix: remove video reference count limits (v1.0.26) --- README.md | 2 +- cmd/generate_video/generate_video.go | 6 +- cmd/generate_video_test.go | 112 +++++++++--------- internal/generate_video/generate_video.go | 15 --- package-lock.json | 4 +- package.json | 2 +- .../xyq-nest-skill/commands/generate-video.md | 6 +- 7 files changed, 67 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 63c0145..40385c7 100644 --- a/README.md +++ b/README.md @@ -227,7 +227,7 @@ pippit-tool-cli generate-video \ --resolution "720p" ``` -命令输出 `thread_id`、`run_id` 和 `web_thread_link`。提交生视频 HTTP 请求时,参考图、参考视频和参考音频会使用上传接口返回的 `pippit_asset_id`,并分别写入 `video_part_tool_param.images`、`video_part_tool_param.videos` 和 `video_part_tool_param.audios`。图片最多 9 张,支持 `.jpg`、`.jpeg`、`.png`、`.gif`、`.bmp`、`.webp`、`.svg`;视频最多 3 个,支持 `.mp4`、`.avi`、`.mov`、`.wmv`、`.flv`、`.webm`、`.mkv`、`.m4v`;音频最多 3 个,仅支持 `.mp3`、`.wav`。普通用户支持模型 `Seedance_2.0_mini_lite`;`seedance2.0_vision`、`seedance2.0_fast_vision`、`Seedance_2.0_mini` 和 `Seedance_2.5` 为 VIP 专属模型。CLI 会在提交前校验 prompt、素材数量和文件后缀;模型、比例、分辨率等语义校验由服务端处理。 +命令输出 `thread_id`、`run_id` 和 `web_thread_link`。提交生视频 HTTP 请求时,参考图、参考视频和参考音频会使用上传接口返回的 `pippit_asset_id`,并分别写入 `video_part_tool_param.images`、`video_part_tool_param.videos` 和 `video_part_tool_param.audios`。图片支持 `.jpg`、`.jpeg`、`.png`、`.gif`、`.bmp`、`.webp`、`.svg`;视频支持 `.mp4`、`.avi`、`.mov`、`.wmv`、`.flv`、`.webm`、`.mkv`、`.m4v`;音频仅支持 `.mp3`、`.wav`。普通用户支持模型 `Seedance_2.0_mini_lite`;`seedance2.0_vision`、`seedance2.0_fast_vision`、`Seedance_2.0_mini` 和 `Seedance_2.5` 为 VIP 专属模型。CLI 会在提交前校验 prompt 和文件后缀;模型、比例、分辨率等语义校验由服务端处理。 首尾帧生视频时,按首帧、尾帧的顺序传入两次 `--image`,并设置 `--generate-type 1`: diff --git a/cmd/generate_video/generate_video.go b/cmd/generate_video/generate_video.go index cf8e9dc..c21cb93 100644 --- a/cmd/generate_video/generate_video.go +++ b/cmd/generate_video/generate_video.go @@ -43,9 +43,9 @@ func NewCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command cmd.SetErr(stderr) flags := cmd.Flags() flags.StringVar(&opts.Prompt, "prompt", "", "video generation prompt") - flags.StringArrayVar(&opts.ImagePaths, "image", nil, "local reference image path; repeat for multiple images, up to 9") - flags.StringArrayVar(&opts.VideoPaths, "video", nil, "local reference video path; repeat for multiple videos, up to 3") - flags.StringArrayVar(&opts.AudioPaths, "audio", nil, "local reference audio path; repeat for multiple audios, up to 3") + flags.StringArrayVar(&opts.ImagePaths, "image", nil, "local reference image path; repeat for multiple images") + flags.StringArrayVar(&opts.VideoPaths, "video", nil, "local reference video path; repeat for multiple videos") + flags.StringArrayVar(&opts.AudioPaths, "audio", nil, "local reference audio path; repeat for multiple audios") flags.IntVar(&durationSec, "duration", 0, "video duration in seconds") flags.StringVar(&opts.Ratio, "ratio", "", "video ratio, such as 9:16, 16:9, 3:4, 4:3") flags.StringVar(&opts.Model, "model", "", "video model; normal users: Seedance_2.0_mini_lite; VIP-only: seedance2.0_vision, seedance2.0_fast_vision, Seedance_2.0_mini, Seedance_2.5") diff --git a/cmd/generate_video_test.go b/cmd/generate_video_test.go index 5d4dda0..07dfd53 100644 --- a/cmd/generate_video_test.go +++ b/cmd/generate_video_test.go @@ -189,72 +189,74 @@ func TestGenerateVideoRequiresPrompt(t *testing.T) { } } -func TestGenerateVideoRejectsTooManyImages(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - t.Fatal("server should not receive request when image count is invalid") - })) - defer server.Close() - - var stdout, stderr bytes.Buffer - root := newTestRootCommand(t, &stdout, &stderr, server.URL) - args := []string{"generate-video", "--prompt", "x"} - for _, path := range mediaPaths("image", ".jpg", 10) { - args = append(args, "--image", path) - } - root.SetArgs(args) - - err := root.Execute() - if err == nil { - t.Fatal("Execute() error = nil, want image count validation") - } - if !strings.Contains(err.Error(), "参考图片最多支持 9 个,当前传入 10 个") { - t.Fatalf("error = %q, want image count validation", err) - } -} - -func TestGenerateVideoRejectsTooManyVideos(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - t.Fatal("server should not receive request when video count is invalid") - })) - defer server.Close() - - var stdout, stderr bytes.Buffer - root := newTestRootCommand(t, &stdout, &stderr, server.URL) +func TestGenerateVideoAcceptsReferencesBeyondFormerLimits(t *testing.T) { + cwd := chdirTemp(t) args := []string{"generate-video", "--prompt", "x"} - for _, path := range mediaPaths("video", ".mp4", 4) { - args = append(args, "--video", path) - } - root.SetArgs(args) - - err := root.Execute() - if err == nil { - t.Fatal("Execute() error = nil, want video count validation") - } - if !strings.Contains(err.Error(), "参考视频最多支持 3 个,当前传入 4 个") { - t.Fatalf("error = %q, want video count validation", err) + var assetIDs []string + for _, media := range []struct { + flag string + ext string + count int + }{ + {"image", ".jpg", 10}, + {"video", ".mp4", 4}, + {"audio", ".mp3", 4}, + } { + for _, name := range mediaPaths(media.flag, media.ext, media.count) { + path := filepath.Join(cwd, name) + if err := os.WriteFile(path, []byte("media-data"), 0o644); err != nil { + t.Fatalf("WriteFile(%s): %v", path, err) + } + args = append(args, "--"+media.flag, path) + assetIDs = append(assetIDs, name) + } } -} -func TestGenerateVideoRejectsTooManyAudios(t *testing.T) { + uploadIndex := 0 + submitted := false server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - t.Fatal("server should not receive request when audio count is invalid") + switch r.URL.Path { + case "/api/biz/v1/skill/upload_file": + if uploadIndex >= len(assetIDs) { + t.Fatalf("unexpected upload %d", uploadIndex) + } + _, _ = w.Write([]byte(`{"ret":"0","data":{"pippit_asset_id":"` + assetIDs[uploadIndex] + `"}}`)) + uploadIndex++ + case "/api/biz/v1/skill/submit_run": + if uploadIndex != len(assetIDs) { + t.Fatalf("uploaded %d references, want %d", uploadIndex, len(assetIDs)) + } + data, err := io.ReadAll(r.Body) + if err != nil { + t.Fatalf("read body: %v", err) + } + var body map[string]any + if err := sonic.Unmarshal(data, &body); err != nil { + t.Fatalf("decode body: %v", err) + } + param, ok := body["video_part_tool_param"].(map[string]any) + if !ok { + t.Fatalf("video_part_tool_param = %#v, want object", body["video_part_tool_param"]) + } + assertAssetRefs(t, param["images"], assetIDs[:10]) + assertAssetRefs(t, param["videos"], assetIDs[10:14]) + assertAssetRefs(t, param["audios"], assetIDs[14:]) + submitted = true + _, _ = w.Write([]byte(`{"ret":"0","data":{"run":{"thread_id":"thread_123","run_id":"run_456"}}}`)) + default: + t.Fatalf("unexpected path %s", r.URL.Path) + } })) defer server.Close() var stdout, stderr bytes.Buffer root := newTestRootCommand(t, &stdout, &stderr, server.URL) - args := []string{"generate-video", "--prompt", "x"} - for _, path := range mediaPaths("audio", ".mp3", 4) { - args = append(args, "--audio", path) - } root.SetArgs(args) - - err := root.Execute() - if err == nil { - t.Fatal("Execute() error = nil, want audio count validation") + if err := root.Execute(); err != nil { + t.Fatalf("Execute() error = %v, stderr = %s", err, stderr.String()) } - if !strings.Contains(err.Error(), "参考音频最多支持 3 个,当前传入 4 个") { - t.Fatalf("error = %q, want audio count validation", err) + if !submitted { + t.Fatal("generate-video did not submit references") } } diff --git a/internal/generate_video/generate_video.go b/internal/generate_video/generate_video.go index e16d298..be2762a 100644 --- a/internal/generate_video/generate_video.go +++ b/internal/generate_video/generate_video.go @@ -9,12 +9,6 @@ import ( "github.com/Pippit-dev/pippit-cli/internal/common" ) -const ( - maxReferenceImages = 9 - maxReferenceVideos = 3 - maxReferenceAudios = 3 -) - var ( allowedImageExtensionList = []string{".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".svg"} allowedAudioExtensionList = []string{".mp3", ".wav"} @@ -70,15 +64,6 @@ func ValidateOptions(opts *Options) error { if strings.TrimSpace(opts.Prompt) == "" { return fmt.Errorf("缺少必填参数 --prompt") } - if len(opts.ImagePaths) > maxReferenceImages { - return fmt.Errorf("参考图片最多支持 %d 个,当前传入 %d 个", maxReferenceImages, len(opts.ImagePaths)) - } - if len(opts.VideoPaths) > maxReferenceVideos { - return fmt.Errorf("参考视频最多支持 %d 个,当前传入 %d 个", maxReferenceVideos, len(opts.VideoPaths)) - } - if len(opts.AudioPaths) > maxReferenceAudios { - return fmt.Errorf("参考音频最多支持 %d 个,当前传入 %d 个", maxReferenceAudios, len(opts.AudioPaths)) - } if err := validateMediaExtensions("图片", opts.ImagePaths, allowedImageExtensions, allowedImageExtensionList); err != nil { return err } diff --git a/package-lock.json b/package-lock.json index 707d14d..6e153bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@pippit-dev/cli", - "version": "1.0.25", + "version": "1.0.26", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@pippit-dev/cli", - "version": "1.0.25", + "version": "1.0.26", "hasInstallScript": true, "license": "MIT", "bin": { diff --git a/package.json b/package.json index c9ce5aa..90fe444 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@pippit-dev/cli", - "version": "1.0.25", + "version": "1.0.26", "description": "Pippit CLI", "bin": { "pippit-tool-cli": "scripts/run.js" diff --git a/skills/xyq-nest-skill/commands/generate-video.md b/skills/xyq-nest-skill/commands/generate-video.md index ec24d9d..36c0714 100644 --- a/skills/xyq-nest-skill/commands/generate-video.md +++ b/skills/xyq-nest-skill/commands/generate-video.md @@ -8,9 +8,9 @@ | --- | --- | --- | | `--prompt` | 是 | 用户原始描述,不能全为空白 | | `--model` | 否 | 用户指定的模型;未提供时省略,由服务端处理默认配置 | -| `--image` | 否 | 本地图片路径,重复参数,最多 9 张 | -| `--video` | 否 | 本地参考视频路径,重复参数,最多 3 个 | -| `--audio` | 否 | 本地 `.mp3/.wav` 音频路径,重复参数,最多 3 个 | +| `--image` | 否 | 本地图片路径,重复参数 | +| `--video` | 否 | 本地参考视频路径,重复参数 | +| `--audio` | 否 | 本地 `.mp3/.wav` 音频路径,重复参数 | | `--duration` | 否 | 整数秒;用户只给时长范围时先确认具体秒数 | | `--ratio` | 否 | 比例字符串,如 `9:16`、`16:9`、`3:4`、`4:3`;不转换为生图枚举 | | `--resolution` | 否 | 用户指定值,如 `720p`、`1080p` |