coze扣子工作流实战,从0搭建梦核意境视频工作流
效果展示
搭建过程
创建工作流
登录扣子官网,创建一个工作流地址:https://www.coze.cn/home
:自定义文案,不填就让 AI 自动生成AIPkey(字符串):必填,填你的生成图片、视频、语音用的密钥subject(字符串):选题,比如偷窥

第 2 步:判断用户有没有自己填文案
- 从「开始」拉出一个新的
【选择器】节点。重命名为:"2-选择器"
参数设置:
- 分支条件:引用「开始」输出的
wenan - 条件逻辑:当
wenan为空,或者wenan等于-时,走生成文案分支 - 否则:直接走用户自定义文案分支

第 3 步:没填文案时,根据选题自动生成口播稿
- 从「2-选择器」的满足条件分支拉出一个新的
【大模型】节点。重命名为:"3-根据主题生成文案"
模型选择:
- 模型:豆包·1.8·深度思考
参数设置:
subject(字符串):引用「开始」输出的subject
系统提示词:
你是一个不仅没有感情,甚至对人类社会的虚伪感到生理性厌恶的观察者。你不仅能看到事物的表象,更能像手术刀一样划开社会契约的表皮,暴露出内里涌动的欲望、腐烂的真相和令人作呕的潜规则。你的语言风格属于“梦核(Dreamcore)/核类(Corecore)”美学:冷漠、致郁、充满生物性的隐喻和对伪善的极致嘲讽。
# Goal:
接收用户给定的【选题/关键词】,将其作为“输入”,输出一段具有强烈的视听认知失调感、揭露人性阴暗面或社会荒诞性的“定义”。
# Rules & Constraints:
1. **结构强制:** 必须严格遵守最后仅输出口播稿,此外不要输出任何其他解释。
2. **拒绝说教:** 绝对不要出现“我们要”、“你应该”、“切记”这种建议性的话术。你是来审判的,不是来当人生导师的。
3. **生物性隐喻(关键):** 必须使用具有实体感、痛感或令人不适的名词/动词(例如:蛆虫、撕开、福尔马林、缝合、流脓、窥视、咀嚼、寒毛直竖、腥味)。
4. **反转与解构:** “输出”的内容必须是对“输入”词汇的背叛。如果输入是美好的,输出就是恐怖的;如果输入是中性的,输出就是荒谬的。
5. **冷漠的句式:** 多用陈述句,或者带有嘲讽意味的反问句。不要用感叹号,情绪要压抑在文字底下,像死水下的暗流。
6. **篇幅控制:** 每句话小于10个字, 总字数在50-100字左右,短小精悍,像一记耳光。
# Methodology (思维路径):
1. **剥皮:** 拿到关键词,先思考大众对它的常规理解是什么。
2. **透视:** 思考这个词在极端现实、利益交换或人性本恶的视角下,真正意味着什么?
3. **通感:** 给这个残酷的真相配上气味、触觉或视觉画面。
4. **封装:** 用最冷静的口吻把这个画面描述出来。
# 示例输入/输出:
## 示例输入:
偷窥
## 示例输出:
有的人长得像人,但未必真的是人。他们不出声,在黑暗中交换他人的裸影取乐。畜生是没有偷窥习惯的,撕开人皮,内里不过是一只只的蛆虫,在腐烂的欲望里蠕动着。
## 示例输入:
做自己
## 示例输出:
人们总是劝你做自己,但当你真的做了自己,他们又开始评价你的自己是否符合他们的标准。原来那句话完整的是:请在我允许的范围内做你自己。
## 示例输入:
真招笑
## 示例输出:
你的鼻子已经闻到了欺骗的味道,你的耳朵也听到了谎言的警告,甚至连皮肤都警惕地竖起了寒毛,但你最终却选择相信你的眼睛。真招笑。
## 示例输入:
怀旧
## 示例输出:
你怀念的根本不是那个夏天,而是那个还没被现实阉割的自己。记忆像是加了柔光滤镜的尸体,你抱着它痛哭,却忘了它活着的时候,你也曾想过掐死它。
用户提示词:
故事主题:{{subject}}
输出变量:
wenan(字符串)

第 4 步:把“AI生成文案”和“用户自填文案”合并成一份正文
- 从「3-根据主题生成文案」拉到新的
【变量聚合】节点。重命名为:"4-变量聚合" - 再从「2-选择器」的否则分支也拉到这个
【变量聚合】节点。
参数设置:
content(字符串):优先接「3-根据主题生成文案」输出的wenancontent(字符串):否则接「开始」输出的wenan

第 5 步:把整段口播稿切成一句一句
- 从「4-变量聚合」拉出一个新的
【插件】节点。插件市场搜剪映小助手辅助工具,选text_splitter。重命名为:"5-播稿分割"
参数设置:
text(字符串):引用「4-变量聚合」输出的content

第 6 步:给每一句话生成画面提示词、视频提示词、音效关键词
- 从「5-播稿分割」拉出一个新的
【大模型】节点。重命名为:"6-图像提示词"
模型选择:
- 模型:豆包·1.8·深度思考
参数设置:
content(字符串列表):引用「5-播稿分割」输出的segments
系统提示词:
# 角色
将输入的文案内容按每一句话生成对应的分镜字幕绘画提示词desc_prompt和视频提示词video_prompt。
## 技能
### 技能 1: 生成绘图提示词
1. 把输入的整体文案“{{content}}”按每一句话(每个句号为一句话), 然后生成一个提示词,针对每一句文案,生成对应字幕“{{cap}}”的绘画提示词 desc_promopt。梦核风格,拼接艺术,叠加老电视信号干扰的颗粒噪点,加入角色,融入复古拼贴元素,带剪纸边缘轮廓,营造出一种奇幻又略带暗黑诡异的氛围,契合梦核风格追求的梦幻且神秘的视觉感受。
【音效要求】
- 必须生成真实自然环境音效,与画面内容完全匹配
- 自然声音:鸟鸣、蝉声、青蛙叫、风吹叶响、小溪水流声、大雨声、虫鸣
- 交通工具:火车鸣笛声、火车铁轨声、汽车引擎声、汽车喇叭、火车运行声
- 生活音效:脚步声、开门声、茶具声、翻书声、呼吸声、心跳声
- 城市环境:远处车流、工地声、人声喧哗、钟声
- 严禁生成莫名其妙的噪音或不明音效
- 只能输出一种声音
举例:
1:梦核风格,拼接艺术,背景是蓝色渐变褶皱纸张纹理,叠加老电视信号干扰的颗粒噪点,左侧:高饱和粉紫色带剪纸边缘轮廓的手,从屏幕外手指前伸指向右侧,营造手指从无到有的动画效果,右侧:高饱和无面部齐刘海黑发女性,极简剪影化,融入白色的复古拼贴元素,带剪纸边缘轮廓 ,营造超现实怀旧日 +荒诞感,模拟 复古电子屏的颗粒纹理与
灵感向导 "sound_kv": "信号干扰声音"
### 技能2:生成视频提示词
1. 根据技能1生成的绘图提示词,生成对应字幕“{{cap}}”的视频提示词 video_prompt,按照以下规则详细描述:
- 对于每个镜头,明确给出镜头的时间区间,如“第 X 个镜头(X - X 秒)”。
- 说明镜头的景别,如“远景(平面)”“中景(平面)”“特写(平面)”“近景(平面)” 。
- 详细描述场景元素,包括背景、角色形象(如发型、面部颜色、服装颜色等)、周边装饰等的颜色及样式。
- 描述角色的动作,例如双手捧出、躺倒、双手交叉等。
- 强调镜头整体呈现出的风格特点,例如画面色调氛围等方面所传达出的情感风格。
### 技能3:检查空字符
1. 输出的json中cap和desc_promopt都不能为空
输出json格式示例:
{
"cap": "{{cap}}",
"desc_promopt": "分镜图像提示词",
"video_prompt": "视频提示词",
"sound_kv": "车厢轻摇声"
}
## 限制:
- 只对用户提供的json内容补充desc_prompt字段,不能更改原文
- 严格检查输出的 json 格式正确性并进行修正,特别注意 json 格式不要少括号,逗号等
- 输出的提示词必须是中文
-禁止出现违规词语
输出变量:
outputList(对象数组)sound_kv(字符串)video_prompt(字符串)cap(字符串)desc_promopt(字符串)

第 7 步:过滤掉没有字幕的空镜头
- 从「6-图像提示词」拉出一个新的
【代码】节点。重命名为:"7-过滤输出列表非空字幕项"
参数设置:
outputList(对象数组):引用「6-图像提示词」输出的outputList
代码直接抄:
async function main({ params }: Args): Promise<Output> {
const list = params.outputList;
const ret = {
"outputList": list.filter(item => item.cap != null && item.cap !== "")
};
return ret;
}
输出变量:
outputList(对象数组)cap(字符串)desc_promopt(字符串)sound_kv(字符串)video_prompt(字符串)

第 8 步:批量给每个分镜生成图片和音效
- 从「7-过滤输出列表非空字幕项」拉出一个新的
【批处理】节点。重命名为:"8-批处理"
参数设置:
scenes(对象数组):引用「7-过滤输出列表非空字幕项」输出的outputList- 批处理大小:
100 - 并发数量:
1
输出变量:
image_list(字符串列表)selected_url_list(字符串列表)

第 8.1 步:在批处理里先按音效关键词搜索音效
- 在「8-批处理」内部,从「【入口】」拉出一个新的
【插件】节点。插件市场搜搜索音效,选search_sound_effect。重命名为:"8.1-搜索音频"
参数设置:
keyword(字符串):引用「8-批处理」当前项的scenes.sound_kv

第 8.2 步:从搜索结果里拿第一个可用音效链接
- 在「8-批处理」内部,从「8.1-搜索音频」拉出一个新的
【代码】节点。重命名为:"8.2-选择音效URL"
参数设置:
data(对象数组):引用「8.1-搜索音频」输出的data
代码直接抄:
async function main({ params }: Args): Promise<Output> {
const ret = {
selected_url: ""
};
try {
if (!params.data || !Array.isArray(params.data) || params.data.length === 0) {
throw new Error("输入数据不能为空且必须为数组");
}
const validSongs = params.data.filter(song =>
song && typeof song.url === 'string' && song.url.trim() !== ''
);
if (validSongs.length === 0) {
throw new Error("没有找到包含有效URL的歌曲");
}
const selectedSong = validSongs[0];
ret.selected_url = selectedSong.url.trim();
} catch (error) {
console.error("随机选择歌曲URL时发生错误:", error);
ret.selected_url = "";
}
return ret;
}
输出变量:
selected_url(字符串)

第 8.3 步:准备 5 张固定参考图
- 在「8-批处理」内部,从「【入口】」拉出一个新的
【代码】节点。重命名为:"8.3-图片路径组合"
参数设置:
input1(字符串):图片链接1input2(字符串):图片连接2input3(字符串):图片链接3input4(字符串):图片链接4input5(字符串):图片链接5
代码直接抄:
async function main({ params }: Args): Promise<Output> {
var img_url1 = params.input1
var img_url2 = params.input2
var img_url3 = params.input3
var img_url4 = params.input4
var img_url5 = params.input5
let image_url_list = []
image_url_list.push(img_url1,img_url2,img_url3,img_url4,img_url5)
const ret = {
"images_urls": image_url_list,
};
return ret;
}
输出变量:
images_urls(字符串列表)

第 8.4 步:拼出图片生成提示词
- 在「8-批处理」内部,从「8.3-图片路径组合」拉出一个新的
【文本处理】节点。重命名为:"8.4-图片提示词"
参数设置:
String1(字符串):引用「8-批处理」当前项的scenes.desc_promopt
拼接模板这样填:
参照图片风格,色彩,边框{{String1}}

第 8.5 步:第一次尝试生成分镜图片
- 在「8-批处理」内部,从「8.4-图片提示词」拉出一个新的
【插件】节点。插件市场搜即梦图片生成,选jimeng_generate_image。重命名为:"8.5-图片生成"
参数设置:
key(字符串):引用「开始」输出的AIPkeyprompt(字符串):引用「8.4-图片提示词」输出的outputimage_urls(字符串列表):引用「8.3-图片路径组合」输出的images_urlsmodel(字符串):图片4.0ratio(字符串):4:3

第 8.6 步:判断第一次图片有没有生成成功
- 在「8-批处理」内部,从「8.5-图片生成」拉出一个新的
【选择器】节点。重命名为:"8.6-选择器"
参数设置:
- 条件:引用「8.5-图片生成」输出的
url - 如果
url不为空:直接走成功分支 - 否则:走提示词优化后重试分支

第 8.7 步:第一次图片失败时,优化图片提示词
- 在「8-批处理」内部,从「8.6-选择器」的否则分支拉出一个新的
【插件】节点。插件市场搜提示词优化,选sd_better_prompt。重命名为:"8.7-优化提示词"
参数设置:
prompt(字符串):引用「8-批处理」当前项的scenes.desc_promopt

第 8.8 步:用优化后的提示词再生成一次图片
- 在「8-批处理」内部,从「8.7-优化提示词」拉出一个新的
【插件】节点。插件市场搜即梦图片生成,选jimeng_generate_image。重命名为:"8.8-图片生成"
参数设置:
key(字符串):引用「开始」输出的AIPkeyprompt(字符串):引用「8.7-优化提示词」输出的dataimage_urls(字符串列表):引用「8.3-图片路径组合」输出的images_urlsmodel(字符串):图片4.0ratio(字符串):4:3

第 8.9 步:统一拿到最终图片链接
- 在「8-批处理」内部,从「8.6-选择器」成功分支拉出一个新的
【代码】节点。重命名为:"8.9-图片路径" - 在「8-批处理」内部,再从「8.8-图片生成」拉线到「8.9-图片路径」
参数设置:
image1(字符串):引用「8.5-图片生成」输出的urlimage2(字符串):引用「8.8-图片生成」输出的url
代码直接抄:
async function main({ params }: Args): Promise<Output> {
var image1 = params.image1;
var image2 = params.image2;
if(!image1){
image1 = image2;
}
const ret = {
"image_url": image1
};
return ret;
}
输出变量:
image_url(字符串)

第 9 步:批量把图片变成视频
- 从「8-批处理」拉出一个新的
【批处理】节点。重命名为:"9-批处理视频"
参数设置:
images_url(字符串列表):引用「8-批处理」输出的image_listscenes(对象数组):引用「7-过滤输出列表非空字幕项」输出的outputList- 批处理大小:
100 - 并发数量:
1
输出变量:
video_urls(字符串列表)

第 9.1 步:先直接按视频提示词生成视频
- 在「9-批处理视频」内部,从「【入口】」拉出一个新的
【插件】节点。插件市场搜即梦视频生成,选jimeng_generate_video。重命名为:"9.1-视频生成"
参数设置:
key(字符串):引用「开始」输出的AIPkeyprompt(字符串):引用「9-批处理视频」当前项的scenes.video_promptduration(整数):5image_url(字符串):引用「9-批处理视频」当前项的images_urlmodel(字符串):视频3.0resolution(字符串):720p

第 9.2 步:判断第一次视频有没有生成成功
- 在「9-批处理视频」内部,从「9.1-视频生成」拉出一个新的
【选择器】节点。重命名为:"9.2-选择器"
参数设置:
- 条件:引用「9.1-视频生成」输出的
url - 如果
url不为空:直接走成功分支 - 否则:走提示词优化后重试分支

第 9.3 步:第一次视频失败时,优化视频提示词
- 在「9-批处理视频」内部,从「9.2-选择器」的否则分支拉出一个新的
【插件】节点。插件市场搜提示词优化,选sd_better_prompt。重命名为:"9.3-视频提示词优化"
参数设置:
prompt(字符串):引用「9-批处理视频」当前项的scenes.video_prompt

第 9.4 步:用优化后的提示词第二次生成视频
- 在「9-批处理视频」内部,从「9.3-视频提示词优化」拉出一个新的
【插件】节点。插件市场搜即梦视频生成,选jimeng_generate_video。重命名为:"9.4-视频生成"
参数设置:
key(字符串):引用「开始」输出的AIPkeyprompt(字符串):引用「9.3-视频提示词优化」输出的dataduration(整数):5image_url(字符串):引用「9-批处理视频」当前项的images_urlmodel(字符串):视频3.0resolution(字符串):720p

第 9.5 步:判断第二次视频有没有生成成功
- 在「9-批处理视频」内部,从「9.4-视频生成」拉出一个新的
【选择器】节点。重命名为:"9.5-选择器"
参数设置:
- 条件:引用「9.4-视频生成」输出的
url - 如果
url不为空:走成功分支 - 否则:继续优化提示词再重试

第 9.6 步:第二次失败时,再优化一次视频提示词
- 在「9-批处理视频」内部,从「9.5-选择器」的否则分支拉出一个新的
【插件】节点。插件市场搜提示词优化,选sd_better_prompt。重命名为:"9.6-视频提示词优化"
参数设置:
prompt(字符串):引用「9.3-视频提示词优化」输出的data
第 9.7 步:第三次生成视频
- 在「9-批处理视频」内部,从「9.6-视频提示词优化」拉出一个新的
【插件】节点。插件市场搜即梦视频生成,选jimeng_generate_video。重命名为:"9.7-视频生成"
参数设置:
key(字符串):引用「开始」输出的AIPkeyprompt(字符串):引用「9.6-视频提示词优化」输出的dataduration(整数):5image_url(字符串):引用「9-批处理视频」当前项的images_urlmodel(字符串):视频3.0resolution(字符串):720p

第 9.8 步:把三次可能生成出来的视频链接合并成最终视频链接
- 在「9-批处理视频」内部,从「9.2-选择器」成功分支拉到新的
【变量聚合】节点。重命名为:"9.8变量聚合" - 在「9-批处理视频」内部,从「9.5-选择器」成功分支也拉到「9.8变量聚合」
- 在「9-批处理视频」内部,从「9.7-视频生成」也拉到「9.8变量聚合」
参数设置:
Group1(字符串):引用「9.1-视频生成」输出的urlGroup1(字符串):引用「9.4-视频生成」输出的urlGroup1(字符串):引用「9.7-视频生成」输出的url

第 10 步:循环给每一句字幕生成配音
- 从「9-批处理视频」拉出一个新的
【循环】节点。重命名为:"10-循环"
参数设置:
scenes(字符串列表):引用「5-播稿分割」输出的segments- 循环类型:数组循环
- 循环次数:
10

第 10.1 步:每次循环生成一段语音
- 在「10-循环」内部,从「【入口】」拉出一个新的
【插件】节点。插件市场搜fish语音,选generate_voice。重命名为:"10.1-音频"
参数设置:
key(字符串):引用「开始」输出的AIPkeytext(字符串):引用「10-循环」当前项的scenesreference_id(字符串):561fcedfdf0e4e1399d1bc4930d50c0e

第 10.2 步:获取每段语音的时长
- 在「10-循环」内部,从「10.1-音频」拉出一个新的
【插件】节点。插件市场搜获取音频时长,选get_audio_duration。重命名为:"10.2-音频时长"
参数设置:
mp3_url(字符串):引用「10.1-音频」输出的data

第 11 步:根据配音列表生成音频时间线
- 从「10-循环」拉出一个新的
【插件】节点。插件市场搜剪映小助手,选audio_timelines。重命名为:"11-音频时间线"
参数设置:
links(字符串列表):引用「10-循环」输出的link_list

第 12 步:让字幕和配音时长对齐
- 从「11-音频时间线」拉出一个新的
【插件】节点。插件市场搜字幕音频对齐,选align_text_to_audio。重命名为:"12-音频字幕时间线对齐"
参数设置:
duration_list(整数列表):引用「10-循环」输出的duration_listtexts(字符串列表):引用「5-播稿分割」输出的segments

第 13 步:统一构建音频、图片、字幕、视频的时间轴数据
- 从「12-音频字幕时间线对齐」拉出一个新的
【代码】节点。重命名为:"13-构建时间轴"
参数设置:
image_list(字符串列表):引用「8-批处理」输出的image_listaudio_list(字符串列表):引用「10-循环」输出的link_listduration_list(小数列表):引用「10-循环」输出的duration_listscenes(对象数组):引用「6-图像提示词」输出的outputListvideo_list(字符串列表):引用「9-批处理视频」输出的video_urls
代码直接抄:
async function main({ params }: Args): Promise<Output> {
const { image_list, audio_list, duration_list, scenes ,video_list} = params;
const audioData = [];
let audioStartTime = 0;
const aideoTimelines = [];
let maxDuration = 0;
const imageData = [];
const video_timeline =[];
let video_start = 0;
for (let i = 0; i < video_list.length; i++) {
video_timeline.push({
start:video_start,
end: video_start+5000000
});
video_start = video_start+5000000;
}
for (let i = 0; i < audio_list.length && i < duration_list.length; i++) {
const duration = duration_list[i];
audioData.push({
audio_url: audio_list[i],
duration,
start: audioStartTime,
end: audioStartTime + duration,
volume: 5
});
aideoTimelines.push({
start: audioStartTime,
end: audioStartTime + duration
});
if((i-1)%2==0){
imageData.push({
image_url: image_list[i],
start: audioStartTime,
end: audioStartTime + duration,
width: 1024,
height: 576,
in_animation: "轻微放大",
in_animation_duration: 100000
});
} else{
imageData.push({
image_url: image_list[i],
start: audioStartTime,
end: audioStartTime + duration,
width: 1024,
height: 576
});
}
audioStartTime += duration;
maxDuration = audioStartTime;
}
const captions = scenes.map(item => item.cap);
const subtitleDurations = duration_list;
const { textTimelines, processedSubtitles } = processSubtitles(
captions,
subtitleDurations
);
var kc_audio_url = "https://p9-bot-workflow-sign.byteimg.com/tos-cn-i-mdko3gqilj/c04e7b48586a48f1863e421be4b10cf1.MP3~tplv-mdko3gqilj-image.image?rk3s=81d4c505&x-expires=1777550323&x-signature=T%2BNjvPHPyHnGICvWRFDeFaj17UM%3D&x-wf-file_name=%E6%95%85%E4%BA%8B%E5%BC%80%E5%9C%BA%E9%9F%B3%E6%95%88.MP3";
var bg_audio_url ="https://p3-bot-workflow-sign.byteimg.com/tos-cn-i-mdko3gqilj/5603dc783a6c4b75a4bf4e1b44086ad5.MP3~tplv-mdko3gqilj-image.image?rk3s=81d4c505&x-expires=1777550332&x-signature=E1123RzPTMD%2BipseRN4itYxhZyc%3D&x-wf-file_name=%E6%95%85%E4%BA%8B%E8%83%8C%E6%99%AF%E9%9F%B3%E4%B9%90.MP3";
const bg_audio_data = [];
bg_audio_data.push({
audio_url: bg_audio_url,
duraion: maxDuration,
start: 0,
end: maxDuration,
volume: 2
});
const kc_audio_data = [];
kc_audio_data.push({
audio_url: kc_audio_url,
duration: 4884897,
start: 0,
end: 4884897
});
const common_timelines = [{
start: 0,
end: maxDuration
}];
const ret = {
"audioData": JSON.stringify(audioData),
"bgAudioData": JSON.stringify(bg_audio_data),
"kcAudioData": JSON.stringify(kc_audio_data),
"imageData": JSON.stringify(imageData),
"text_timielines":textTimelines,
"text_captions":processedSubtitles,
"common_timelines": common_timelines,
"max_time": maxDuration,
"aideoTimelines":aideoTimelines,
"video_timeline": video_timeline
};
return ret;
}
const SUB_CONFIG = {
MAX_LINE_LENGTH: 25,
SPLIT_PRIORITY: ['。','!','?',',',',',':',':','、',';',';',' '],
TIME_PRECISION: 3
};
function splitLongPhrase(text, maxLen) {
if (text.length <= maxLen) return [text];
for (const delimiter of SUB_CONFIG.SPLIT_PRIORITY) {
const pos = text.lastIndexOf(delimiter, maxLen - 1);
if (pos > 0) {
const splitPos = pos + 1;
return [
text.substring(0, splitPos).trim(),
...splitLongPhrase(text.substring(splitPos).trim(), maxLen)
];
}
}
const startPos = Math.min(maxLen, text.length) - 1;
for (let i = startPos; i > 0; i--) {
if (/[\p{Unified_Ideograph}]/u.test(text[i])) {
return [
text.substring(0, i + 1).trim(),
...splitLongPhrase(text.substring(i + 1).trim(), maxLen)
];
}
}
const splitPos = Math.min(maxLen, text.length);
return [
text.substring(0, splitPos).trim(),
...splitLongPhrase(text.substring(splitPos).trim(), maxLen)
];
}
const processSubtitles = (
captions,
subtitleDurations,
startTimeμs = 0
) => {
const cleanRegex = /[\u3000\u3002-\u303F\uff00-\uffef\u2000-\u206F!"#$%&'()*+\-./=?@\\^_`{|}~]/g;
let processedSubtitles = [];
let processedSubtitleDurations = [];
captions.forEach((text, index) => {
const totalDuration = subtitleDurations[index];
let phrases = splitLongPhrase(text, SUB_CONFIG.MAX_LINE_LENGTH);
phrases = phrases.map(p => p.replace(cleanRegex, '').trim())
.filter(p => p.length > 0);
if (phrases.length === 0) {
processedSubtitles.push('[无内容]');
processedSubtitleDurations.push(totalDuration);
return;
}
const totalChars = phrases.reduce((sum, p) => sum + p.length, 0);
let accumulatedμs = 0;
phrases.forEach((phrase, i) => {
const ratio = phrase.length / totalChars;
let durationμs = i === phrases.length - 1
? totalDuration - accumulatedμs
: Math.round(totalDuration * ratio);
processedSubtitles.push(phrase);
processedSubtitleDurations.push(durationμs);
accumulatedμs += durationμs;
});
});
const textTimelines = [];
let currentTime = startTimeμs;
processedSubtitleDurations.forEach(durationμs => {
const start = currentTime;
const end = start + durationμs;
textTimelines.push({
start: start,
end: end
});
currentTime = end;
});
return { textTimelines, processedSubtitles };
};
输出变量:
common_timelines(对象数组)text_captions(字符串列表)video_timeline(对象数组)max_time(整数)text_timielines(对象数组)audioData(字符串)aideoTimelines(对象数组)bgAudioData(字符串)imageData(字符串)kcAudioData(字符串)

第 14 步:准备背景音乐链接文本
- 从「13-构建时间轴」拉出一个新的
【文本处理】节点。重命名为:"14-文本处理"
参数设置:
String1(字符串):https://p3-bot-workflow-sign.byteimg.com/tos-cn-i-mdko3gqilj/de5afe4cbd304b8498e8f422a3bf400a.mp3~tplv-mdko3gqilj-image.image?rk3s=81d4c505&x-expires=1803879346&x-signature=lOOn2S0JXwkyDpreS8RiqEKBPmU%3D
拼接模板这样填:
{{String1}}

第 15 步:把背景音乐字符串转成列表
- 从「14-文本处理」拉出一个新的
【插件】节点。插件市场搜剪映小助手,选str_to_list。重命名为:"15-字符转列表"
参数设置:
obj(字符串):引用「14-文本处理」输出的output

第 16 步:给开场背景音乐做一段固定时间线
- 从「15-字符转列表」拉出一个新的
【插件】节点。插件市场搜剪映小助手,选timelines。重命名为:"16-时间线"
参数设置:
duration(整数):1500000num(整数):1start(整数):0

第 17 步:把正文配音整理成剪映可添加的数据
- 从「16-时间线」拉出一个新的
【插件】节点。插件市场搜剪映小助手,选audio_infos。重命名为:"17-正文数据"
参数设置:
mp3_urls(字符串列表):引用「10-循环」输出的link_listtimelines(对象数组):引用「13-构建时间轴」输出的aideoTimelinesaudio_effect(字符串):复古电视volume(小数):2

第 18 步:把背景音乐整理成剪映可添加的数据
- 从「17-正文数据」拉出一个新的
【插件】节点。插件市场搜剪映小助手,选audio_infos。重命名为:"18-背景音乐"
参数设置:
mp3_urls(字符串列表):引用「15-字符转列表」输出的infostimelines(对象数组):引用「13-构建时间轴」输出的common_timelinesvolume(小数):0.5

第 19 步:把每个镜头的音效整理成剪映可添加的数据
- 从「18-背景音乐」拉出一个新的
【插件】节点。插件市场搜剪映小助手,选audio_infos。重命名为:"19-音效数据"
参数设置:
mp3_urls(字符串列表):引用「8-批处理」输出的selected_url_listtimelines(对象数组):引用「11-音频时间线」输出的timelinesvolume(小数):0.5

第 20 步:把视频链接和时间线整理成剪映视频数据
- 从「19-音效数据」拉出一个新的
【插件】节点。插件市场搜剪映小助手,选video_infos。重命名为:"20-视频数据"
参数设置:
timelines(对象数组):引用「11-音频时间线」输出的timelinesvideo_urls(字符串列表):引用「9-批处理视频」输出的video_urls

第 21 步:把字幕文本和字幕时间线整理成剪映字幕数据
- 从「20-视频数据」拉出一个新的
【插件】节点。插件市场搜剪映小助手,选caption_infos。重命名为:"21-字幕数据"
参数设置:
texts(字符串列表):引用「12-音频字幕时间线对齐」输出的text_captionstimelines(对象数组):引用「12-音频字幕时间线对齐」输出的text_timelines

第 22 步:创建剪映草稿
- 从「21-字幕数据」拉出一个新的
【插件】节点。插件市场搜剪映小助手,选create_draft。重命名为:"22-草稿"
参数设置:
height(整数):1728width(整数):2304

第 23 步:把正文配音添加进草稿
- 从「22-草稿」拉出一个新的
【插件】节点。插件市场搜剪映小助手,选add_audios。重命名为:"23-添加正文音频"
参数设置:
audio_infos(字符串):引用「17-正文数据」输出的infosdraft_id(字符串):引用「22-草稿」输出的draft_id

第 24 步:把背景音乐添加进草稿
- 从「23-添加正文音频」拉出一个新的
【插件】节点。插件市场搜剪映小助手,选add_audios。重命名为:"24-添加背景音乐"
参数设置:
audio_infos(字符串):引用「18-背景音乐」输出的infosdraft_id(字符串):引用「22-草稿」输出的draft_id

第 25 步:把音效添加进草稿
- 从「24-添加背景音乐」拉出一个新的
【插件】节点。插件市场搜剪映小助手,选add_audios。重命名为:"25-添加音效"
参数设置:
audio_infos(字符串):引用「19-音效数据」输出的infosdraft_id(字符串):引用「22-草稿」输出的draft_id

第 26 步:把视频添加进草稿
- 从「25-添加音效」拉出一个新的
【插件】节点。插件市场搜剪映小助手,选add_videos。重命名为:"26-添加视频"
参数设置:
draft_id(字符串):引用「22-草稿」输出的draft_idvideo_infos(字符串):引用「20-视频数据」输出的infos

第 27 步:把字幕添加进草稿
- 从「26-添加视频」拉出一个新的
【插件】节点。插件市场搜剪映小助手,选add_captions。重命名为:"27-添加字幕"
参数设置:
captions(字符串):引用「21-字幕数据」输出的infosdraft_id(字符串):引用「22-草稿」输出的draft_idalignment(整数):1border_color(字符串):#fffffffont(字符串):楚辰体font_size(整数):9scale_x(小数):1.8scale_y(小数):1.8text_color(字符串):#414242transform_x(小数):0transform_y(小数):-1250

第 28 步:结束并返回草稿链接
- 从「27-添加字幕」拉出一个新的
【结束】节点。节点名字保持:"结束"
参数设置:
draft_url(字符串):引用「22-草稿」输出的draft_id
这就是整个工作流搭建的过程,接下来我们只需要点击试运行,成功的话就会得到这样一个草稿ID
只需要把这个草稿ID复制下来,然后打开我们的剪映小助手把草稿ID复制进去,然后点击创建剪映草稿(注意不要复制引号)
稍等一下,创建成功之后打开剪映,就会看到剪映里面多出了一个黑色的草稿,只要点进去就能看到我们的视频了

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)