cerevox 4.75.0 → 4.76.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/ai.d.ts +9 -34
- package/dist/core/ai.d.ts.map +1 -1
- package/dist/core/ai.js +10 -544
- package/dist/core/ai.js.map +1 -1
- package/dist/mcp/servers/prompts/skills//344/270/200/351/224/256/346/210/220/347/211/207.md +4 -2
- package/dist/mcp/servers/zerocut.d.ts.map +1 -1
- package/dist/mcp/servers/zerocut.js +6 -5
- package/dist/mcp/servers/zerocut.js.map +1 -1
- package/dist/utils/videokit.d.ts +4 -4
- package/package.json +1 -1
package/dist/core/ai.js
CHANGED
|
@@ -365,59 +365,6 @@ let AI = class AI extends base_1.BaseClass {
|
|
|
365
365
|
return item;
|
|
366
366
|
}) || [];
|
|
367
367
|
delete options.reference_images;
|
|
368
|
-
if (reference_images?.[0]) {
|
|
369
|
-
const responses = await this.getResponses({
|
|
370
|
-
model: 'Doubao-Seed-2.0-pro',
|
|
371
|
-
input: [
|
|
372
|
-
{
|
|
373
|
-
role: 'system',
|
|
374
|
-
content: `你根据主体信息,优化用户指令,使描述中的内容正确引用主体名称。
|
|
375
|
-
|
|
376
|
-
具体方式为:
|
|
377
|
-
1. 将用户指令中引用主体信息中主体名称的部分,用 “@主体名(图i)” 的形式替代,i是图片编号,注意它和前后内容之间需要用**空格**分隔
|
|
378
|
-
2. 如果主体名直接是“图1、图2”等编号,则不需要替换
|
|
379
|
-
|
|
380
|
-
## 例子
|
|
381
|
-
|
|
382
|
-
### 输入:
|
|
383
|
-
|
|
384
|
-
参考图:
|
|
385
|
-
[
|
|
386
|
-
{"url": "...girl.png", "name": "女孩"},
|
|
387
|
-
{"url": "...fan.png", "name": "扇子"}
|
|
388
|
-
]
|
|
389
|
-
|
|
390
|
-
用户指令
|
|
391
|
-
- 女孩手持扇子
|
|
392
|
-
|
|
393
|
-
### 输出:
|
|
394
|
-
@女孩(图1)手持 @扇子(图2)
|
|
395
|
-
|
|
396
|
-
---
|
|
397
|
-
|
|
398
|
-
## 要求与约束
|
|
399
|
-
|
|
400
|
-
- 只输出替换主体名后的用户指令,不要输出其他任何额外内容
|
|
401
|
-
`,
|
|
402
|
-
},
|
|
403
|
-
{
|
|
404
|
-
role: 'user',
|
|
405
|
-
content: `## 主体信息
|
|
406
|
-
|
|
407
|
-
参考图:
|
|
408
|
-
${JSON.stringify([...reference_images])}
|
|
409
|
-
|
|
410
|
-
## 用户指令
|
|
411
|
-
${options.prompt.trim()}`,
|
|
412
|
-
},
|
|
413
|
-
],
|
|
414
|
-
});
|
|
415
|
-
const optimizedPrompt = responses.output?.find((item) => item.type === 'message')?.content?.[0]?.text;
|
|
416
|
-
options.prompt = optimizedPrompt?.trim() || options.prompt;
|
|
417
|
-
}
|
|
418
|
-
else if (options.model === 'qwen') {
|
|
419
|
-
throw new Error('Must provide 1-3 reference_images');
|
|
420
|
-
}
|
|
421
368
|
if (options.type === 'storyboard-sketch') {
|
|
422
369
|
options.size = '2160x3840';
|
|
423
370
|
options.model = options.model ?? 'gpt-image-vip-md';
|
|
@@ -649,200 +596,25 @@ let AI = class AI extends base_1.BaseClass {
|
|
|
649
596
|
}
|
|
650
597
|
: item) ?? [];
|
|
651
598
|
let prompt = options.prompt;
|
|
652
|
-
if (named_ref_images.length && type.startsWith('wan')) {
|
|
653
|
-
const replacer = type === 'wan-flash' ? 'character' : '图';
|
|
654
|
-
const responses = await this.getResponses({
|
|
655
|
-
model: 'Doubao-Seed-2.0-pro',
|
|
656
|
-
input: [
|
|
657
|
-
{
|
|
658
|
-
role: 'system',
|
|
659
|
-
content: `你根据参考图、参考视频的信息,优化用户指令,使指令描述中的内容正确引用主体名称。
|
|
660
|
-
|
|
661
|
-
具体方式为:
|
|
662
|
-
1. 将用户指令中引用参考图名字的部分,统一用 “主体名(${replacer}<i>)” 的形式替代,<i>是参考图片编号,注意它和前后内容之间需要用**空格**分隔
|
|
663
|
-
2. 将用户指令中引用参考视频名字的部分,统一用 “视频<i>” 的形式替代,<i>是参考视频编号,注意它和前后内容之间需要用**空格**分隔
|
|
664
|
-
|
|
665
|
-
## 例子
|
|
666
|
-
|
|
667
|
-
### 输入:
|
|
668
|
-
|
|
669
|
-
参考图:
|
|
670
|
-
[
|
|
671
|
-
{"type": "reference", "url": "...dog.png", "name": "狗"},
|
|
672
|
-
{"type": "reference", "url": "...cat.png", "name": "猫"}
|
|
673
|
-
]
|
|
674
|
-
|
|
675
|
-
用户指令
|
|
676
|
-
- 一只狗和一只猫在玩耍
|
|
677
|
-
|
|
678
|
-
### 输出:
|
|
679
|
-
一只狗(${replacer}1) 和 一只猫(${replacer}2) 在玩耍
|
|
680
|
-
|
|
681
|
-
---
|
|
682
|
-
|
|
683
|
-
## 要求与约束
|
|
684
|
-
|
|
685
|
-
- 只输出替换主体名后的用户指令,不要输出其他任何额外内容
|
|
686
|
-
`,
|
|
687
|
-
},
|
|
688
|
-
{
|
|
689
|
-
role: 'user',
|
|
690
|
-
content: `## 参考图
|
|
691
|
-
${JSON.stringify([...named_ref_images])}
|
|
692
|
-
|
|
693
|
-
## 参考视频
|
|
694
|
-
${JSON.stringify([...reference_videos])}
|
|
695
|
-
|
|
696
|
-
## 用户指令
|
|
697
|
-
${prompt}`,
|
|
698
|
-
},
|
|
699
|
-
],
|
|
700
|
-
});
|
|
701
|
-
const optimizedPrompt = responses.output?.find((item) => item.type === 'message')?.content?.[0]?.text;
|
|
702
|
-
prompt = optimizedPrompt?.trim() || prompt;
|
|
703
|
-
}
|
|
704
|
-
else if ((named_ref_images.length || reference_videos.length) &&
|
|
705
|
-
(type.startsWith('viduq3') || type.startsWith('seedance-2.0'))) {
|
|
706
|
-
const responses = await this.getResponses({
|
|
707
|
-
model: 'Doubao-Seed-2.0-pro',
|
|
708
|
-
input: [
|
|
709
|
-
{
|
|
710
|
-
role: 'system',
|
|
711
|
-
content: `你根据主体信息,优化用户指令,使描述中的内容正确引用主体名称。
|
|
712
|
-
|
|
713
|
-
具体方式为:
|
|
714
|
-
- 将用户指令中引用主体信息中主体名称的部分,统一用 “主体名(@i)” 的形式替代,<i>是参考素材数组的编号,注意它和前后内容之间需要用**空格**分隔
|
|
715
|
-
|
|
716
|
-
## 例子
|
|
717
|
-
|
|
718
|
-
### 输入:
|
|
719
|
-
|
|
720
|
-
参考素材:
|
|
721
|
-
[
|
|
722
|
-
{"type": "reference", "url": "...dog.png", "name": "狗"},
|
|
723
|
-
{"type": "reference", "url": "...cat.png", "name": "猫"}
|
|
724
|
-
]
|
|
725
|
-
|
|
726
|
-
用户指令
|
|
727
|
-
- 一只狗和一只猫在玩耍
|
|
728
|
-
|
|
729
|
-
### 输出:
|
|
730
|
-
一只狗(@1) 和 一只猫(@2) 在玩耍
|
|
731
|
-
|
|
732
|
-
---
|
|
733
|
-
|
|
734
|
-
## 要求与约束
|
|
735
|
-
|
|
736
|
-
- 只输出替换主体名后的用户指令,不要输出其他任何额外内容
|
|
737
|
-
`,
|
|
738
|
-
},
|
|
739
|
-
{
|
|
740
|
-
role: 'user',
|
|
741
|
-
content: `## 主体信息
|
|
742
|
-
|
|
743
|
-
参考素材:
|
|
744
|
-
${JSON.stringify([...named_ref_images, ...reference_videos, ...reference_audios])}
|
|
745
|
-
|
|
746
|
-
## 用户指令
|
|
747
|
-
${prompt}`,
|
|
748
|
-
},
|
|
749
|
-
],
|
|
750
|
-
});
|
|
751
|
-
const optimizedPrompt = responses.output?.find((item) => item.type === 'message')?.content?.[0]?.text;
|
|
752
|
-
prompt = optimizedPrompt?.trim() || prompt;
|
|
753
|
-
}
|
|
754
|
-
else if (named_ref_images.length || reference_videos.length) {
|
|
755
|
-
const repl1 = type === 'kling-v3' ? '<<<image_i>>>' : '图i';
|
|
756
|
-
const repl2 = type === 'kling-v3' ? '<<<video_i>>>' : '视频i';
|
|
757
|
-
const outputExample = type === 'kling-v3'
|
|
758
|
-
? `1. 一只 @狗(<<<image_1>>>) 在 @房间(<<<image_2>>>) 里玩耍
|
|
759
|
-
2. @女孩(<<<image_1>>>)参考 <<<video_1>>> 的特效、动作和运镜。`
|
|
760
|
-
: `1. 一只 @狗(图1) 在 @房间(图2) 里玩耍
|
|
761
|
-
2. @女孩(图1)参考 @视频1 的特效、动作和运镜。`;
|
|
762
|
-
const responses = await this.getResponses({
|
|
763
|
-
model: 'Doubao-Seed-2.0-pro',
|
|
764
|
-
input: [
|
|
765
|
-
{
|
|
766
|
-
role: 'system',
|
|
767
|
-
content: `你根据主体信息,优化用户指令,使描述中的内容正确引用主体名称。
|
|
768
|
-
|
|
769
|
-
具体方式为:
|
|
770
|
-
1. 将用户指令中引用主体信息中主体名称的部分,用 “@主体名(${repl1})” 的形式替代,i是图片编号(从1开始),注意它和前后内容之间需要用**空格**分隔
|
|
771
|
-
2. 如果参考图主体名直接是“图1、图2”等编号,则不需要括号,直接用“${repl1}”形式
|
|
772
|
-
3. 参考视频不用引用主体名,不需要括号,直接用“${repl2}”替代,i为视频编号
|
|
773
|
-
|
|
774
|
-
## 例子
|
|
775
|
-
|
|
776
|
-
### 输入:
|
|
777
|
-
|
|
778
|
-
参考图:
|
|
779
|
-
[
|
|
780
|
-
{"type": "reference", "url": "...dog.png", "name": "狗"},
|
|
781
|
-
{"type": "reference", "url": "...room.png", "name": "房间"}
|
|
782
|
-
]
|
|
783
|
-
|
|
784
|
-
参考视频
|
|
785
|
-
[
|
|
786
|
-
{"type": "video", "fileName": "video1.mp4", "name": "特效"}
|
|
787
|
-
]
|
|
788
|
-
|
|
789
|
-
用户指令
|
|
790
|
-
1. 一只狗在房间里玩耍
|
|
791
|
-
2. 女孩参考视频1的特效、动作和运镜。
|
|
792
|
-
|
|
793
|
-
### 输出:
|
|
794
|
-
${outputExample}
|
|
795
|
-
|
|
796
|
-
---
|
|
797
|
-
|
|
798
|
-
## 要求与约束
|
|
799
|
-
|
|
800
|
-
- 只输出替换主体名后的用户指令,不要输出其他任何额外内容
|
|
801
|
-
`,
|
|
802
|
-
},
|
|
803
|
-
{
|
|
804
|
-
role: 'user',
|
|
805
|
-
content: `## 主体信息
|
|
806
|
-
|
|
807
|
-
参考图:
|
|
808
|
-
${JSON.stringify([...named_ref_images])}
|
|
809
|
-
|
|
810
|
-
参考视频:
|
|
811
|
-
${JSON.stringify([...reference_videos])}
|
|
812
|
-
|
|
813
|
-
## 用户指令
|
|
814
|
-
${prompt}`,
|
|
815
|
-
},
|
|
816
|
-
],
|
|
817
|
-
});
|
|
818
|
-
const optimizedPrompt = responses.output?.find((item) => item.type === 'message')?.content?.[0]?.text;
|
|
819
|
-
prompt = optimizedPrompt?.trim() || prompt;
|
|
820
|
-
}
|
|
821
599
|
let optimize_camera = false;
|
|
822
600
|
// 用户已经传入了 storyboard
|
|
823
601
|
const storyboard = reference_images?.find(item => item.type === 'storyboard');
|
|
824
|
-
if (storyboard ||
|
|
602
|
+
if (storyboard ||
|
|
603
|
+
(options.optimizeCameraMotion &&
|
|
604
|
+
options.optimizeCameraMotion.strategy !== 'none')) {
|
|
825
605
|
optimize_camera = true;
|
|
826
606
|
let panelCount = options.storyboardPanelCount;
|
|
827
607
|
if (!panelCount && options.duration > 15) {
|
|
828
608
|
panelCount = 6 + Math.ceil((options.duration - 15) / 5);
|
|
829
609
|
}
|
|
830
610
|
if (!storyboard) {
|
|
831
|
-
const imageModel = 'gpt-image-vip';
|
|
832
|
-
const imageSize = '2560x1440';
|
|
833
|
-
// const imageSize =
|
|
834
|
-
// aspect_ratio === '1:1'
|
|
835
|
-
// ? '2048x2048'
|
|
836
|
-
// : aspect_ratio === '9:16' || aspect_ratio === '3:4'
|
|
837
|
-
// ? '1440x2560'
|
|
838
|
-
// : '2560x1440';
|
|
839
611
|
const res = await this.generateImage({
|
|
840
612
|
prompt,
|
|
841
|
-
type: '
|
|
842
|
-
|
|
613
|
+
type: options.optimizeCameraMotion?.strategy === 'director'
|
|
614
|
+
? 'director-note'
|
|
615
|
+
: 'storyboard-sketch',
|
|
843
616
|
panel_count: panelCount,
|
|
844
617
|
for_video_duration: options.duration,
|
|
845
|
-
size: imageSize,
|
|
846
618
|
reference_images: reference_images?.map(item => ({
|
|
847
619
|
url: item.url,
|
|
848
620
|
name: item.name,
|
|
@@ -1275,13 +1047,6 @@ ${prompt}`,
|
|
|
1275
1047
|
prompt = `【${options.narration.tone}】旁白:“${options.narration.text}”\n\n${prompt}`;
|
|
1276
1048
|
}
|
|
1277
1049
|
}
|
|
1278
|
-
if (options.optimizeCameraMotion) {
|
|
1279
|
-
prompt = await this.optimizeCameraMotion({
|
|
1280
|
-
prompt,
|
|
1281
|
-
duration: options.duration,
|
|
1282
|
-
withBGM: options.enableBGM || false,
|
|
1283
|
-
});
|
|
1284
|
-
}
|
|
1285
1050
|
if (options.type !== 'seedance-1.5-pro' && options.duration === 0) {
|
|
1286
1051
|
const durationPrompt = `根据用户提供的视频提示词内容为视频确定时长,规则为:
|
|
1287
1052
|
|
|
@@ -1391,56 +1156,6 @@ ${prompt}`,
|
|
|
1391
1156
|
}
|
|
1392
1157
|
return item;
|
|
1393
1158
|
});
|
|
1394
|
-
if (reference_images?.[0]) {
|
|
1395
|
-
const responses = await this.getResponses({
|
|
1396
|
-
model: 'Doubao-Seed-2.0-pro',
|
|
1397
|
-
input: [
|
|
1398
|
-
{
|
|
1399
|
-
role: 'system',
|
|
1400
|
-
content: `你根据主体信息,优化用户指令,使描述中的内容正确引用主体名称。
|
|
1401
|
-
|
|
1402
|
-
具体方式为:
|
|
1403
|
-
1. 将用户指令中引用主体信息中主体名称的部分,用 “@主体名(图i)” 的形式替代,i是图片编号,注意它和前后内容之间需要用**空格**分隔
|
|
1404
|
-
2. 如果主体名直接是“图1、图2”等编号,则不需要替换
|
|
1405
|
-
|
|
1406
|
-
## 例子
|
|
1407
|
-
|
|
1408
|
-
### 输入:
|
|
1409
|
-
|
|
1410
|
-
参考图:
|
|
1411
|
-
[
|
|
1412
|
-
{"url": "...girl.png", "name": "女孩"},
|
|
1413
|
-
{"url": "...fan.png", "name": "扇子"}
|
|
1414
|
-
]
|
|
1415
|
-
|
|
1416
|
-
用户指令
|
|
1417
|
-
- 女孩手持扇子
|
|
1418
|
-
|
|
1419
|
-
### 输出:
|
|
1420
|
-
@女孩(图1)手持 @扇子(图2)
|
|
1421
|
-
|
|
1422
|
-
---
|
|
1423
|
-
|
|
1424
|
-
## 要求与约束
|
|
1425
|
-
|
|
1426
|
-
- 只输出替换主体名后的用户指令,不要输出其他任何额外内容
|
|
1427
|
-
`,
|
|
1428
|
-
},
|
|
1429
|
-
{
|
|
1430
|
-
role: 'user',
|
|
1431
|
-
content: `## 主体信息
|
|
1432
|
-
|
|
1433
|
-
参考图:
|
|
1434
|
-
${JSON.stringify([...reference_images])}
|
|
1435
|
-
|
|
1436
|
-
## 用户指令
|
|
1437
|
-
${options.prompt.trim()}`,
|
|
1438
|
-
},
|
|
1439
|
-
],
|
|
1440
|
-
});
|
|
1441
|
-
const optimizedPrompt = responses.output?.find((item) => item.type === 'message')?.content?.[0]?.text;
|
|
1442
|
-
options.prompt = optimizedPrompt?.trim() || options.prompt;
|
|
1443
|
-
}
|
|
1444
1159
|
const workflowId = '7618124429907197990';
|
|
1445
1160
|
const parameters = {
|
|
1446
1161
|
prompt: options.prompt,
|
|
@@ -1498,9 +1213,7 @@ ${prompt}`,
|
|
|
1498
1213
|
const prompt = type === 'seed-3d'
|
|
1499
1214
|
? '--subdivisionlevel medium --fileformat glb'
|
|
1500
1215
|
: '--material Shaded --fileformat glb';
|
|
1501
|
-
const res = await this.session.sandbox.request(`/ai/references/to/video/generate`,
|
|
1502
|
-
// `/api/xyq/generate-video`,
|
|
1503
|
-
{
|
|
1216
|
+
const res = await this.session.sandbox.request(`/ai/references/to/video/generate`, {
|
|
1504
1217
|
method: 'POST',
|
|
1505
1218
|
headers: {
|
|
1506
1219
|
'Content-Type': 'application/json',
|
|
@@ -1529,254 +1242,6 @@ ${prompt}`,
|
|
|
1529
1242
|
return { error: error.message };
|
|
1530
1243
|
}
|
|
1531
1244
|
}
|
|
1532
|
-
async generateSeedanceVideo(options) {
|
|
1533
|
-
try {
|
|
1534
|
-
let { prompt, aspect_ratio: ratio = '16:9', resolution = '720p', duration = 0, model = 'seedance-2.0', waitForFinish = true, timeout = 3_600_000, optimizeCameraMotion = true, storyboardPanelCount, enableBGM = true, mute = false, } = options;
|
|
1535
|
-
const references = options.references_images?.map((item, i) => {
|
|
1536
|
-
if (!item.name) {
|
|
1537
|
-
item.name = `图${i + 1}`;
|
|
1538
|
-
}
|
|
1539
|
-
return item;
|
|
1540
|
-
}) || [];
|
|
1541
|
-
const videos = options.references_videos?.map((item, i) => {
|
|
1542
|
-
if (!item.name) {
|
|
1543
|
-
item.name = `视频${i + 1}`;
|
|
1544
|
-
}
|
|
1545
|
-
return { ...item, type: 'video' };
|
|
1546
|
-
}) || [];
|
|
1547
|
-
const audios = options.references_audios?.map((item, i) => {
|
|
1548
|
-
if (!item.name) {
|
|
1549
|
-
item.name = `音频${i + 1}`;
|
|
1550
|
-
}
|
|
1551
|
-
return { ...item, type: 'audio' };
|
|
1552
|
-
}) || [];
|
|
1553
|
-
references.push(...[...videos, ...audios]);
|
|
1554
|
-
const named_refs = references;
|
|
1555
|
-
const start_frame = references?.find(item => item.type === 'first_frame');
|
|
1556
|
-
const end_frame = references?.find(item => item.type === 'last_frame');
|
|
1557
|
-
if (!start_frame && end_frame) {
|
|
1558
|
-
throw new Error('Cannot provide last_frame only without first_frame');
|
|
1559
|
-
}
|
|
1560
|
-
let type = 'omni_reference';
|
|
1561
|
-
if (start_frame) {
|
|
1562
|
-
if ((end_frame && references?.length > 2) ||
|
|
1563
|
-
(!end_frame && references?.length > 1)) {
|
|
1564
|
-
throw new Error('Can only provide first_frame or last_frame in i2v mode');
|
|
1565
|
-
}
|
|
1566
|
-
type = 'first_last_frames';
|
|
1567
|
-
references.length = 0;
|
|
1568
|
-
references.push(start_frame);
|
|
1569
|
-
let prefix = `以 @1 作为首帧`;
|
|
1570
|
-
if (end_frame) {
|
|
1571
|
-
prefix = `${prefix},以 @2 作为尾帧`;
|
|
1572
|
-
references.push(end_frame);
|
|
1573
|
-
}
|
|
1574
|
-
prompt = `${prefix} ${prompt}`;
|
|
1575
|
-
optimizeCameraMotion = false;
|
|
1576
|
-
}
|
|
1577
|
-
let optimize_camera = false;
|
|
1578
|
-
// 用户已经传入了 storyboard
|
|
1579
|
-
const storyboard = references?.find(item => item.type === 'storyboard');
|
|
1580
|
-
if (storyboard || optimizeCameraMotion) {
|
|
1581
|
-
optimize_camera = true;
|
|
1582
|
-
if (!storyboard) {
|
|
1583
|
-
const imageModel = 'banana2';
|
|
1584
|
-
const imageSize = ratio === '1:1'
|
|
1585
|
-
? '3840x3840'
|
|
1586
|
-
: ratio === '9:16' || ratio === '3:4'
|
|
1587
|
-
? '2160x3840'
|
|
1588
|
-
: '3840x2160';
|
|
1589
|
-
const { urls } = await this.generateImage({
|
|
1590
|
-
prompt,
|
|
1591
|
-
type: 'storyboard-sketch',
|
|
1592
|
-
model: imageModel,
|
|
1593
|
-
panel_count: storyboardPanelCount,
|
|
1594
|
-
for_video_duration: options.duration,
|
|
1595
|
-
size: imageSize,
|
|
1596
|
-
reference_images: references?.map(item => ({
|
|
1597
|
-
url: item.url,
|
|
1598
|
-
name: item.name,
|
|
1599
|
-
})),
|
|
1600
|
-
onProgress: options.onProgress,
|
|
1601
|
-
timeout,
|
|
1602
|
-
mask_asset: true,
|
|
1603
|
-
});
|
|
1604
|
-
if (urls[0]) {
|
|
1605
|
-
references.push({
|
|
1606
|
-
type: 'storyboard',
|
|
1607
|
-
url: urls[0],
|
|
1608
|
-
});
|
|
1609
|
-
// options.onProgress?.({ storyboard: { url: urls[0] } });
|
|
1610
|
-
}
|
|
1611
|
-
}
|
|
1612
|
-
const idx = references?.findIndex(item => item.type === 'storyboard');
|
|
1613
|
-
const hasReferenceImage = references?.some(item => item.type === 'reference');
|
|
1614
|
-
const storyboardRef = references[idx];
|
|
1615
|
-
prompt = await this.optimizeCameraMotion({
|
|
1616
|
-
prompt,
|
|
1617
|
-
storyboard: storyboardRef,
|
|
1618
|
-
maxShots: storyboardPanelCount || 6,
|
|
1619
|
-
duration: duration,
|
|
1620
|
-
hasReferenceImage,
|
|
1621
|
-
withBGM: enableBGM || false,
|
|
1622
|
-
});
|
|
1623
|
-
if (idx >= 0 && idx !== references.length - 1) {
|
|
1624
|
-
references.splice(idx, 1); // 分镜图要放在最后
|
|
1625
|
-
references.push(storyboardRef);
|
|
1626
|
-
}
|
|
1627
|
-
}
|
|
1628
|
-
if (!start_frame && references?.[0]) {
|
|
1629
|
-
const responses = await this.getResponses({
|
|
1630
|
-
model: 'Doubao-Seed-2.0-pro',
|
|
1631
|
-
input: [
|
|
1632
|
-
{
|
|
1633
|
-
role: 'system',
|
|
1634
|
-
content: `你根据主体信息,优化用户指令,使描述中的内容正确引用主体名称。
|
|
1635
|
-
|
|
1636
|
-
具体方式为:
|
|
1637
|
-
- 将用户指令中引用主体信息中主体名称的部分,统一用 “主体名(@i)” 的形式替代,<i>是参考素材数组的编号,注意它和前后内容之间需要用**空格**分隔
|
|
1638
|
-
|
|
1639
|
-
## 例子
|
|
1640
|
-
|
|
1641
|
-
### 输入:
|
|
1642
|
-
|
|
1643
|
-
参考素材:
|
|
1644
|
-
[
|
|
1645
|
-
{"type": "reference", "url": "...dog.png", "name": "狗"},
|
|
1646
|
-
{"type": "reference", "url": "...cat.png", "name": "猫"}
|
|
1647
|
-
]
|
|
1648
|
-
|
|
1649
|
-
用户指令
|
|
1650
|
-
- 一只狗和一只猫在玩耍
|
|
1651
|
-
|
|
1652
|
-
### 输出:
|
|
1653
|
-
一只狗(@1) 和 一只猫(@2) 在玩耍
|
|
1654
|
-
|
|
1655
|
-
---
|
|
1656
|
-
|
|
1657
|
-
## 要求与约束
|
|
1658
|
-
|
|
1659
|
-
- 只输出替换主体名后的用户指令,不要输出其他任何额外内容
|
|
1660
|
-
`,
|
|
1661
|
-
},
|
|
1662
|
-
{
|
|
1663
|
-
role: 'user',
|
|
1664
|
-
content: `## 主体信息
|
|
1665
|
-
|
|
1666
|
-
参考素材:
|
|
1667
|
-
${JSON.stringify([...named_refs])}
|
|
1668
|
-
|
|
1669
|
-
## 用户指令
|
|
1670
|
-
${prompt}`,
|
|
1671
|
-
},
|
|
1672
|
-
],
|
|
1673
|
-
});
|
|
1674
|
-
const optimizedPrompt = responses.output?.find((item) => item.type === 'message')?.content?.[0]?.text;
|
|
1675
|
-
prompt = optimizedPrompt?.trim() || prompt;
|
|
1676
|
-
}
|
|
1677
|
-
if (!mute) {
|
|
1678
|
-
if (!enableBGM) {
|
|
1679
|
-
prompt = `${prompt} 生成环境音效,除非上文明确提到生成BGM,否则禁止背景音乐;`;
|
|
1680
|
-
}
|
|
1681
|
-
prompt = `${prompt} 禁止字幕`;
|
|
1682
|
-
}
|
|
1683
|
-
if (duration === 0) {
|
|
1684
|
-
const durationPrompt = `根据用户提供的视频提示词内容为视频确定时长,规则为:
|
|
1685
|
-
|
|
1686
|
-
- 分镜视频时长应根据场景表现需要而设定,范围在1~16秒
|
|
1687
|
-
- 设置的原则:
|
|
1688
|
-
1) 如有人物对话或旁白,根据声音按正常语速估算时长,并在不超过16秒的前提下,留有25%左右的buffer(即视频时长比音频估算时长稍长25%)
|
|
1689
|
-
2) 如无人物对话或旁白,根据场景表现视觉需要设定时长
|
|
1690
|
-
|
|
1691
|
-
## 返回 JSON 格式
|
|
1692
|
-
|
|
1693
|
-
{
|
|
1694
|
-
"duration": 10,
|
|
1695
|
-
"reason": ""
|
|
1696
|
-
}
|
|
1697
|
-
`;
|
|
1698
|
-
const schema = {
|
|
1699
|
-
name: 'choose_duration',
|
|
1700
|
-
schema: {
|
|
1701
|
-
type: 'object',
|
|
1702
|
-
properties: {
|
|
1703
|
-
duration: {
|
|
1704
|
-
type: 'integer',
|
|
1705
|
-
description: '视频时长,范围在1~16秒',
|
|
1706
|
-
},
|
|
1707
|
-
reason: {
|
|
1708
|
-
type: 'string',
|
|
1709
|
-
description: '视频时长的选择理由',
|
|
1710
|
-
},
|
|
1711
|
-
},
|
|
1712
|
-
required: ['duration'],
|
|
1713
|
-
},
|
|
1714
|
-
};
|
|
1715
|
-
const payload = {
|
|
1716
|
-
model: 'Doubao-Seed-2.0-pro',
|
|
1717
|
-
input: [
|
|
1718
|
-
{
|
|
1719
|
-
role: 'system',
|
|
1720
|
-
content: durationPrompt,
|
|
1721
|
-
},
|
|
1722
|
-
{
|
|
1723
|
-
role: 'user',
|
|
1724
|
-
content: `视频提示词:\n\n${prompt}`,
|
|
1725
|
-
},
|
|
1726
|
-
],
|
|
1727
|
-
text: {
|
|
1728
|
-
format: {
|
|
1729
|
-
type: 'json_schema',
|
|
1730
|
-
...schema,
|
|
1731
|
-
},
|
|
1732
|
-
},
|
|
1733
|
-
};
|
|
1734
|
-
const responses = await this.getResponses(payload);
|
|
1735
|
-
// console.log(JSON.stringify(responses.output));
|
|
1736
|
-
const result = responses.output?.find((item) => item.type === 'message')?.content?.[0]?.text;
|
|
1737
|
-
if (!result) {
|
|
1738
|
-
throw new Error(`No response from AI model: ${JSON.stringify(responses)}`);
|
|
1739
|
-
}
|
|
1740
|
-
duration = JSON.parse(result).duration;
|
|
1741
|
-
if (isNaN(duration) || duration < 1 || duration > 16) {
|
|
1742
|
-
throw new Error(`Invalid duration from AI model: ${result}`);
|
|
1743
|
-
}
|
|
1744
|
-
}
|
|
1745
|
-
const res = await this.session.sandbox.request(`/api/seedance/generate-video`,
|
|
1746
|
-
// `/api/xyq/generate-video`,
|
|
1747
|
-
{
|
|
1748
|
-
method: 'POST',
|
|
1749
|
-
headers: {
|
|
1750
|
-
'Content-Type': 'application/json',
|
|
1751
|
-
},
|
|
1752
|
-
body: JSON.stringify({
|
|
1753
|
-
prompt,
|
|
1754
|
-
ratio,
|
|
1755
|
-
resolution,
|
|
1756
|
-
duration,
|
|
1757
|
-
model,
|
|
1758
|
-
references: references?.map(item => item.url),
|
|
1759
|
-
type,
|
|
1760
|
-
mute,
|
|
1761
|
-
optimize_camera,
|
|
1762
|
-
}),
|
|
1763
|
-
});
|
|
1764
|
-
const data = await res.json();
|
|
1765
|
-
if (data.taskUrl && waitForFinish) {
|
|
1766
|
-
return this.waitForTaskComplete({
|
|
1767
|
-
taskUrl: data.taskUrl,
|
|
1768
|
-
onProgress: options.onProgress,
|
|
1769
|
-
timeout,
|
|
1770
|
-
traceWorkflow: true,
|
|
1771
|
-
});
|
|
1772
|
-
}
|
|
1773
|
-
return data;
|
|
1774
|
-
}
|
|
1775
|
-
catch (error) {
|
|
1776
|
-
this.logger.error('generate video error', error);
|
|
1777
|
-
return { error: error.message };
|
|
1778
|
-
}
|
|
1779
|
-
}
|
|
1780
1245
|
// 统一模型
|
|
1781
1246
|
async generateVideo(options) {
|
|
1782
1247
|
let source_id;
|
|
@@ -1934,7 +1399,8 @@ ${prompt}`,
|
|
|
1934
1399
|
bgm = sound;
|
|
1935
1400
|
}
|
|
1936
1401
|
const isSeedance2 = model.startsWith('seedance-2.0');
|
|
1937
|
-
const isSeedance2Vip = isSeedance2 && (model.endsWith('-vip') || model.endsWith('-hd'))
|
|
1402
|
+
const isSeedance2Vip = (isSeedance2 && (model.endsWith('-vip') || model.endsWith('-hd'))) ||
|
|
1403
|
+
model.includes('-4k');
|
|
1938
1404
|
const references = images?.filter(item => item.type === 'reference');
|
|
1939
1405
|
// 自动检查素材中的人脸
|
|
1940
1406
|
const persons = images?.filter(item => item.type === 'person') || [];
|
|
@@ -2021,7 +1487,6 @@ ${prompt}`,
|
|
|
2021
1487
|
enableBGM,
|
|
2022
1488
|
seed: options.seed,
|
|
2023
1489
|
onProgress,
|
|
2024
|
-
optimizeCameraMotion: options.optimize_camera,
|
|
2025
1490
|
web_search: options.web_search,
|
|
2026
1491
|
waitForFinish,
|
|
2027
1492
|
timeout: options.timeout,
|
|
@@ -2046,6 +1511,7 @@ ${prompt}`,
|
|
|
2046
1511
|
'seedance-2.0',
|
|
2047
1512
|
'seedance-2.0-hd',
|
|
2048
1513
|
'seedance-2.0-fast',
|
|
1514
|
+
'seedance-2.0-fast-hd',
|
|
2049
1515
|
'seedance-2.0-vip',
|
|
2050
1516
|
'seedance-2.0-fast-vip',
|
|
2051
1517
|
];
|