效果展示:

工作流全流程预览

创建工作流

登录扣子官网,创建一个工作流地址:扣子 - AI Agent智能办公平台 - 扣子用AI重塑生产力与工作效率

第 1 步:设置工作流入口参数

  • 先放好系统自带的【开始】节点,不用改名字。

参数设置:

  • APIkey(字符串):调用插件生成图片的秘钥,必填

  • theme(字符串):只输入故事主题,系统自动生成剧情,必填

  • wenan(字符串):自定义故事文本,非必填

第 2 步:判断用户有没有自己填文案

  • 从「开始」拉出一个新的【选择器】节点。重命名为:"2-选择器"

参数设置:

  • 条件分支:判断「开始」输出的wenan

  • 条件 1:wenan为空

  • 条件 2:wenan等于

  • 条件 3:wenan长度小于20

  • 满足上面任意条件:走“是”分支

  • 不满足:走“否则”分支

第 3 步:如果没填文案,就根据主题自动写爽文

  • 从「2-选择器」的“是”分支拉出一个新的【大模型】节点。重命名为:"3-根据主题生成文案"

模型选择:

  • 模型:豆包·2.0·lite

参数设置:

  • input(字符串):引用「开始」输出的theme

系统提示词:

你是一位擅长撰写屌丝/草根逆袭、(古代和都市)修仙奇遇、一统江湖、爽文男主、扮猪吃老虎、穷小子迎娶白富美、霸道女总裁爱上我、走上人生巅峰、修为被废成功复仇等爽文题材文案的专家。

你熟读各种修仙题材小说和都市逆袭小说,并能熟练运用小说中的各种奇遇情节,融入到生成的文案当中。

请根据用户提供的【故事情节】,分析并结合其中的故事碎片内容,按照以下结构整理生成一段不少于800个汉字的爽文题词短视频口播文案:

1. 反差开场:以“【初始情况】+【遭遇】+【现在情况】+ 【提出疑问/原因总结】”开篇,激发观众兴趣。

2. 身份代入:用第二人称“你叫小帅”描述主角身份,用“你正在(现在)XXXXX”,对【初始情况】进行描述,内容要与【现在情况】形成强烈反差。

3. 遭遇描述:对【遭遇】部分内容进行描述,文案要有喜感,能够吸引人。

4. 遇险/奇遇故事:描述主角从【初始情况】过度到现在情况】中间的奇遇时间,情节惊心动魄、内容吸引人。至少选取1个遇险情节、3个的奇遇情节和1个扮猪吃老虎情节。遇险情节要紧张刺激、惊心动魄、紧扣心弦,最终能成功化解。奇遇故事要充满幻想色彩,脑洞大开,让观众眼前一亮。

5. 博弈故事:描述主角从【初始情况】过度到现在情况】中最后的一个过度情节,要有对抗/战斗/心理博弈故事。如,与情敌较量,战胜宗门大佬/魔族等。

6. 现在情况:描述主角的【现在情况】,内容要与【初始情况】形成强烈反差和转折,充分利用爽文特点,让观众直呼过瘾。如,大仇得报、迎娶白富美、成就一方大佬、走上人生巅峰等情节。

7. 主题收尾:回顾主角的一生,引出金句,以反转幽默的形式结尾,或以人生哲理性总结结尾。

要求:
- 每段不超过3句话,多用短句
- 在关键转折点使用感官描写(气味/触感/视觉冲击)
- 结尾以“回顾这一生…”句式点题
- 必须生成至少800个汉字以上口播文案
- 文案由长短句构成,遇到长句会用逗号分隔成短句,每个短句不能超过19个汉字

输出要求:只输出口播字幕文案,不要输出其他任何额外内容,不输出分段说明

用户提示词:

故事情节:{{input}}

输出变量:

  • content(字符串)

  • reasoning_content(字符串)

第 4 步:把“自动生成文案”和“用户自定义文案”合并成一份正文

  • 从「3-根据主题生成文案」拉出一个新的【变量聚合】节点。重命名为:"4-变量聚合"

  • 再从「2-选择器」的“否则”分支也连到「4-变量聚合」

参数设置:

  • 聚合变量名:content

  • 来源 1:引用「3-根据主题生成文案」输出的content

  • 来源 2:引用「开始」输出的wenan

第 5 步:从故事正文里提炼 2 个字标题

  • 从「4-变量聚合」拉出一个新的【大模型】节点。重命名为:"5-主题生成"

模型选择:

  • 模型:豆包·2.0·mini

参数设置:

  • content(字符串):引用「4-变量聚合」输出的content

系统提示词:

# 角色
能够深入理解故事文案的情节、人物、场景, 根据故事内容提炼出简洁精准的2个字故事主题素。

## 技能
### 技能1:生成2个字的主题
1. 从故事文案中提炼出能够精准概括故事核心内容的2个字故事主题

## 限制:
- 只围绕用户提供的故事文案进行分镜绘画提示词生成和主题提炼,拒绝回答与该任务无关的话题。
- 主题必须为2个字。 
- 直接输出主题,不要回复其他额外内容

用户提示词:

故事原文内容:
{{content}}

输出变量:

  • reasoning_content(字符串)

  • title(字符串)

第 6 步:把故事正文拆成分镜字幕

  • 从「5-主题生成」拉出一个新的【大模型】节点。重命名为:"6- 分镜"

模型选择:

  • 模型:豆包·2.0·mini

参数设置:

  • content(字符串):引用「4-变量聚合」输出的content

系统提示词:

# 角色
你是一位专业的故事创意转化师,你能够深入理解故事文案的情节、人物、场景等元素,用生动且具体的语言为绘画创作提供清晰的指引。

## 技能
### 技能1: 生成分镜字幕
1. 当用户提供故事文案时,仔细分析文案中的关键情节、人物形象、场景特点等要素。
2. 文案分镜,生成字幕cap:
    - 字幕文案分段:第一段为开场,需要生成4个分镜。即第一个换行换行的内容,生成4个分镜。
    后续每个段落均由2句话构成,语句简洁明了,表达清晰流畅,同时具备节奏感。
    - 分割文案后特别注意前后文的关联性与一致性,必须与用户提供的原文完全一致,不得进行任何修改、删减。字幕文案必须严格按照用户给的文案拆分,不能修改提供的内容更不能删除内容

===回复示例===
[
  {
    "cap":"字幕文案"
  }
]
===示例结束===

## 限制:
- 只围绕用户提供的故事文案进行分镜绘画提示词生成和主题提炼,拒绝回答与该任务无关的话题。
- 所输出的内容必须条理清晰,分镜绘画提示词要尽可能详细描述画面,主题必须为2个字。
- 视频文案及分镜描述必须保持一致。
- 输出内容必须严格按照给定的 JSON 格式进行组织,不得偏离框架要求。
- 只对用户提示的内容进行分镜,不能更改原文
- 严格检查输出的json格式正确性并进行修正,特别注意json格式不要少括号,逗号等

用户提示词:

故事原文内容:
{{content}}

输出变量:

  • reasoning_content(字符串)

  • scenes(对象数组)
    • cap(字符串)

第 7 步:提炼角色和故事信息,后面统一画风用

  • 从「6- 分镜」拉出一个新的【大模型】节点。重命名为:"7-角色/故事信息提炼"

模型选择:

  • 模型:豆包·2.0·mini

参数设置:

  • content(字符串):引用「4-变量聚合」输出的content

系统提示词:

# 角色
你是一位专业的故事信息提炼师,能够深入剖析包含字幕cap和desc_prompt的json分镜信息,精准提炼出通用的简短故事情节以及角色描述信息。

## 技能
### 技能1:提炼故事情节与角色描述
1. 当用户提供包含字幕cap和desc_prompt的json分镜信息时,仔细分析其中的情节发展、角色行为及特征等要素。
2. 提炼出通用的简短故事情节和角色描述信息,故事情节需涵盖主要事件发展脉络,角色描述需突出关键特征。
3. 确保提炼出的信息准确反映json分镜信息的核心内容,不进行无根据的编造或歪曲。

## 限制:
- 只围绕用户提供的包含字幕cap和desc_prompt的json分镜信息进行故事情节和角色描述信息提炼,拒绝回答与该任务无关的话题。
- 所输出的内容必须简洁明了,故事情节和角色描述信息要精准概括核心要点。
- 输出内容必须基于给定的json分镜信息,不得随意添加或修改关键信息。

用户提示词:

分镜json信息如下:
{{content}}

输出变量:

  • desc(字符串)

  • reasoning_content(字符串)

第 8 步:给每条分镜生成配图提示词

  • 从「7-角色/故事信息提炼」拉出一个新的【大模型】节点。重命名为:"8-图像提示词"

模型选择:

  • 模型:豆包·2.0·mini

参数设置:

  • 开启批量处理:是

  • 批量来源:引用「6- 分镜」输出的scenes

  • cap(字符串):引用当前批次里的scenes.cap

  • desc(字符串):引用「7-角色/故事信息提炼」输出的desc

系统提示词:

# 角色
根据整个故事内容分镜字幕"{{cap}}"生成绘画提示词desc_prompt。

## 技能
### 技能 1:  生成绘画提示
1. 结合故事情节及角色描述:{{desc}},生成对应字幕{{cap}}的绘画提示词 desc_promopt,每个提示词要详细描述画面内容,包括人物动作、表情、服装,场景布置、色彩风格等细节。
  - 风格要求:3D写实动画风格,人物为3D拟人化动物,写实风格,4K电影效果,电影打光,毛发真实,超写实细节纹理,所有人物站立,主角固定为一只胖橘猫形象,可爱滑稽。其他人物为虎斑、奶牛猫、黑猫、老虎、狮子、熊猫、大象等拟人化随机形象。背景为卡通彩色风服,如故事背景为古代,则稍加彩色水墨画风格。服装根据故事内容、时代背景自动生成,如现代都市背景,服装则为西装、T恤、长裙等,古代需要有修仙者服装,仙风道骨等元素体现。

===回复示例===
{
  "cap": "字幕文案",
  "desc_promopt": "分镜图像提示词"
}
===示例结束===

## 限制:
尽量保持人物绘画一致性,同文案中的同一人物,使用相同的人物描述词。

## 限制:
- 只对用户提供的json内容补充desc_prompt字段,不能更改原文
- 严格检查输出的 json 格式正确性并进行修正,特别注意 json 格式不要少括号,逗号等

用户提示词:


输出变量:

  • outputList(对象数组)
    • reasoning_content(字符串)

    • cap(字符串)

    • desc_promopt(字符串)

第 9 步:单独生成第一张无人物背景图提示词

  • 从「8-图像提示词」拉出一个新的【大模型】节点。重命名为:"9-图像提示词_首图背景"

模型选择:

  • 模型:豆包·2.0·mini

参数设置:

  • cap(字符串):引用「6- 分镜」输出的scenes.cap

  • desc(字符串):引用「7-角色/故事信息提炼」输出的desc

系统提示词:

# 角色
根据整个故事内容分镜字幕"{{cap}}"生成绘画提示词desc_prompt。

## 技能
### 技能 1:  生成绘画提示
1. 结合故事情节及角色描述:{{desc}},生成对应字幕{{cap}}的绘画提示词 desc_promopt,每个提示词要详细描述画面内容,包括人物动作、表情、服装,场景布置、色彩风格等细节。
  - 风格要求:3D写实动画风格,人物为3D拟人化动物,写实风格,电影效果,电影打光,毛发真实,超写实细节纹理,所有人物站立,主角固定为一只橘猫形象,可爱。其他人物为虎斑、奶牛猫、黑猫、老虎、狮子、熊猫、大象等拟人化随机形象。背景为卡通彩色风服,如故事背景为古代,则稍加彩色水墨画风格。服装根据故事内容、时代背景自动生成,如现代都市背景,服装则为西装、T恤、长裙等,古代需要有修仙者服装,仙风道骨等元素体现。
  - 第一个分镜画面中不要出现人物,只需要一个画面背景

===回复示例===
{
  "cap": "字幕文案",
  "desc_promopt": "分镜图像提示词"
}
===示例结束===

## 限制:
- 只对用户提供的json内容补充desc_prompt字段,不能更改原文
- 严格检查输出的 json 格式正确性并进行修正,特别注意 json 格式不要少括号,逗号等

用户提示词:


输出变量:

  • desc_promopt(字符串)

  • reasoning_content(字符串)

  • cap(字符串)

第 10 步:给首图背景提示词加上统一画风前缀

  • 从「9-图像提示词_首图背景」拉出一个新的【文本处理】节点。重命名为:"10-文本处理"

参数设置:

  • String1(字符串):引用「9-图像提示词_首图背景」输出的desc_promopt

拼接模板这样填:

3D写实动画风格:{{String1}}

第 11 步:生成第一张无人物背景图

  • 从「10-文本处理」拉出一个新的【插件】节点,插件市场搜即梦图片生成,选即梦图片生成里的jimeng_generate_image。重命名为:"11-图像生成_第一张无人物背景图"

参数设置:

  • key(字符串):引用「开始」输出的APIkey

  • prompt(字符串):引用「10-文本处理」输出的output

  • model(字符串):图片4.0

  • ratio(字符串):16:9

第 12 步:生成主角透明图的绘画提示词

  • 从「11-图像生成_第一张无人物背景图」拉出一个新的【大模型】节点。重命名为:"12-主角首图"

模型选择:

  • 模型:豆包·2.0·mini

参数设置:

  • content(字符串):引用「4-变量聚合」输出的content

系统提示词:

# 角色
根据故事信息生成故事主角开场绘画提示词desc_prompt。

## 技能
### 技能 1:  生成绘画提示
1. 根据故事信息,生成主角任务绘画提示词 desc_promopt,详细描述人物动作、表情、服装,色彩风格等细节。
 - 风格要求:3D写实动画风格,人物为3D拟人化动物,写实风格,电影效果,电影打光,毛发真实,超写实细节纹理,所有人物站立,主角固定为一只橘猫形象,可爱。其他人物为虎斑、奶牛猫、黑猫、老虎、狮子、熊猫、大象等拟人化随机形象。背景为卡通彩色风服,如故事背景为古代,则稍加彩色水墨画风格。服装根据故事内容、时代背景自动生成,如现代都市背景,服装则为西装、T恤、长裙等,古代需要有修仙者服装,仙风道骨等元素体现。
  - 画面只需要出现一个人物,背景留白
  - 人物需正对屏幕,人物在画面正中间

# 限制
1. 只输出绘画提示词,不要输出其他额外内容

用户提示词:

故事信息:{{content}}

输出变量:

  • reasoning_content(字符串)

  • desc_promopt(字符串)

第 13 步:生成主角首图

  • 从「12-主角首图」拉出一个新的【插件】节点,插件市场搜即梦图片生成,选即梦图片生成里的jimeng_generate_image。重命名为:"13-图像生成_主角首图"

参数设置:

  • key(字符串):引用「开始」输出的APIkey

  • prompt(字符串):引用「12-主角首图」输出的desc_promopt

  • model(字符串):图片4.0

  • ratio(字符串):16:9

第 14 步:把主角图抠成透明背景

  • 从「13-图像生成_主角首图」拉出一个新的【插件】节点,插件市场搜智能抠图,选智能抠图里的cutout。重命名为:"14-抠图"

参数设置:

  • url(字符串):引用「13-图像生成_主角首图」输出的url

  • only_mask(字符串):0

  • output_mode(字符串):0

第 15 步:批量给每个分镜生成图片、配音和时长

  • 从「14-抠图」拉出一个新的【批处理】节点。重命名为:"15-批处理"

参数设置:

  • 批处理数据源 scenes(对象数组):引用「8-图像提示词」输出的outputList

  • 单批数量:100

  • 并发数量:2

输出变量:

  • duration_list(整数列表)

  • image_list(字符串列表)

  • link_list(字符串列表)

第 15.1 步:在批处理里给每条图片提示词加画风前缀

  • 在「15-批处理」内部,从「【入口】」拉出一个新的【文本处理】节点。重命名为:"15.1-文本处理"

参数设置:

  • String1(字符串):引用「15-批处理」当前批次的scenes.desc_promopt

拼接模板这样填:

3D写实动画风格:{{String1}}

第 15.2 步:先直接用原提示词生成图片

  • 在「15-批处理」内部,从「15.1-文本处理」拉出一个新的【插件】节点,插件市场搜即梦图片生成,选即梦图片生成里的jimeng_generate_image。重命名为:"15.2图像生成"

参数设置:

  • key(字符串):引用「开始」输出的APIkey

  • prompt(字符串):引用「15.1-文本处理」输出的output

  • model(字符串):图片4.0

  • ratio(字符串):16:9

第 15.3 步:判断刚才有没有生成出图片

  • 在「15-批处理」内部,从「15.2图像生成」拉出一个新的【选择器】节点。重命名为:"15.3选择器"

参数设置:

  • 条件:判断「15.2图像生成」输出的url是否为空

  • 如果不为空:走“是”分支

  • 如果为空:走“否则”分支

第 15.4 步:如果图片失败,就先优化提示词

  • 在「15-批处理」内部,从「15.3选择器」的“否则”分支拉出一个新的【插件】节点,插件市场搜提示词优化,选提示词优化里的sd_better_prompt。重命名为:"15.4-优化提示词"

参数设置:

  • prompt(字符串):引用「15-批处理」当前批次的scenes.desc_promopt

第 15.5 步:用优化后的提示词再生成一次图片

  • 在「15-批处理」内部,从「15.4-优化提示词」拉出一个新的【插件】节点,插件市场搜即梦图片生成,选即梦图片生成里的jimeng_generate_image。重命名为:"15.5图像生成"

参数设置:

  • key(字符串):引用「开始」输出的APIkey

  • prompt(字符串):引用「15.4-优化提示词」输出的data

  • model(字符串):图片4.0

  • ratio(字符串):16:9

第 15.6 步:给当前分镜字幕生成配音

  • 在「15-批处理」内部,从「【入口】」拉出一个新的【插件】节点,插件市场搜语音合成,选语音合成里的speech_synthesis。重命名为:"15.6-音频"

参数设置:

  • text(字符串):引用「15-批处理」当前批次的scenes.cap

  • speed_ratio(小数):1.2

  • voice_id(字符串):7468512265134932019

第 15.7 步:获取这段配音的时长

  • 在「15-批处理」内部,从「15.6-音频」拉出一个新的【插件】节点,插件市场搜获取音频时长,选获取音频时长里的get_audio_duration。重命名为:"15.7-音频时长"

参数设置:

  • mp3_url(字符串):引用「15.6-音频」输出的data.link

第 15.8 步:整理当前分镜最终图片地址

  • 在「15-批处理」内部,从「15.3选择器」的“是”分支拉出一个新的【代码】节点。重命名为:"15.8-整合"

  • 在「15-批处理」内部,再从「15.5图像生成」也连到「15.8-整合」

参数设置:

  • image1(字符串):引用「15.2图像生成」输出的url

  • image2(字符串):引用「15.5图像生成」输出的url

代码直接抄:

async function main(args) {
    const params = args.params;

    var image1 = params.image1;
    var image2 = params.image2;

    if (!image1) {
        image1 = image2;
    }

    const ret = {
        "image_url": image1
    };

    return ret;
}

输出变量:

  • image_url(字符串)

第 16 步:准备开场音效链接

  • 从「15-批处理」拉出一个新的【文本处理】节点。重命名为:"16-文本处理"

参数设置:

  • String1(字符串):固定音频链接

拼接模板这样填:

{{String1}}

第 17 步:准备背景音乐链接

  • 从「16-文本处理」拉出一个新的【文本处理】节点。重命名为:"17-文本处理_BGM"

参数设置:

  • String1(字符串):固定音频链接

拼接模板这样填:

{{String1}}

第 18 步:把图片、音频、字幕、标题全部整理成剪映能吃的数据

  • 从「17-文本处理_BGM」拉出一个新的【代码】节点。重命名为:"18-整合素材"

  • 同时把「15-批处理」「5-主题生成」「14-抠图」「11-图像生成_第一张无人物背景图」「16-文本处理」这些节点的数据都引用进来。

参数设置:

  • image_list(字符串列表):引用「15-批处理」输出的image_list

  • audio_list(字符串列表):引用「15-批处理」输出的link_list

  • duration_list(小数列表):引用「15-批处理」输出的duration_list

  • scenes(对象数组):引用「8-图像提示词」输出的outputList

  • title(字符串):引用「5-主题生成」输出的title

  • role_img_url(字符串):引用「14-抠图」输出的data

  • first_bg_image(字符串):引用「11-图像生成_第一张无人物背景图」输出的url

  • output(字符串):引用「16-文本处理」输出的output

  • output1(字符串):引用「17-文本处理_BGM」输出的output

代码直接抄:

async function main(args) {
    const params = args.params;

    const image_list = params.image_list;
    const audio_list = params.audio_list;
    const duration_list = params.duration_list;
    const scenes = params.scenes;
    const output = params.output;
    const output1 = params.output1;

    image_list[0] = params.first_bg_image;

    const audioData = [];
    let audioStartTime = 0;
    const aideoTimelines = [];
    let maxDuration = 0;

    const imageData = [];

    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: duration,
            start: audioStartTime,
            end: audioStartTime + duration
        });

        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 roleImgData = [];
    roleImgData.push({
        image_url: params.role_img_url,
        start: 0,
        end: duration_list[0],
        width: 1024,
        height: 576
    });

    const captions = scenes.map(item => item.cap);
    const subtitleDurations = duration_list;

    const subResult = processSubtitles(captions, subtitleDurations);
    const textTimelines = subResult.textTimelines;
    const processedSubtitles = subResult.processedSubtitles;

    const title = params.title;
    const title_list = [];
    title_list.push(title);

    const title_timelimes = [
        {
            start: 0,
            end: duration_list[0]
        }
    ];

    var kc_audio_url = output;
    var bg_audio_url = output1;

    const bg_audio_data = [];
    bg_audio_data.push({
        audio_url: bg_audio_url,
        duraion: maxDuration,
        start: 0,
        end: maxDuration,
        volume: 1
    });

    const kc_audio_data = [];
    kc_audio_data.push({
        audio_url: kc_audio_url,
        duration: 4884897,
        start: 0,
        end: 4884897
    });

    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,
        "title_list": title_list,
        "title_timelimes": title_timelimes,
        "roleImgData": JSON.stringify(roleImgData)
    };

    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 splitPos = Math.min(maxLen, text.length);
    return [
        text.substring(0, splitPos).trim(),
        ...splitLongPhrase(text.substring(splitPos).trim(), maxLen)
    ];
}

const processSubtitles = (
    captions,
    subtitleDurations,
    startTimeValue = 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 accumulatedValue = 0;

        phrases.forEach((phrase, i) => {
            const ratio = phrase.length / totalChars;
            let durationValue = i === phrases.length - 1
                ? totalDuration - accumulatedValue
                : Math.round(totalDuration * ratio);

            processedSubtitles.push(phrase);
            processedSubtitleDurations.push(durationValue);
            accumulatedValue += durationValue;
        });
    });

    const textTimelines = [];
    let currentTime = startTimeValue;

    processedSubtitleDurations.forEach(durationValue => {
        const start = currentTime;
        const end = start + durationValue;

        textTimelines.push({
            start: start,
            end: end
        });

        currentTime = end;
    });

    return { textTimelines, processedSubtitles };
};

输出变量:

  • text_captions(字符串列表)

  • text_timielines(对象数组)

  • title_timelimes(对象数组)

  • audioData(字符串)

  • title_list(字符串列表)

  • kcAudioData(字符串)

  • roleImgData(字符串)

  • bgAudioData(字符串)

  • imageData(字符串)

第 19 步:创建剪映草稿

  • 从「18-整合素材」拉出一个新的【插件】节点,插件市场搜剪映小助手,选剪映小助手里的create_draft。重命名为:"19-草稿"

参数设置:

  • height(整数):576

  • width(整数):1024

第 20 步:把所有口播音频添加到草稿

  • 从「19-草稿」拉出一个新的【插件】节点,插件市场搜剪映小助手,选剪映小助手里的add_audios。重命名为:"20-添加音频"

参数设置:

  • audio_infos(字符串):引用「18-整合素材」输出的audioData

  • draft_id(字符串):引用「19-草稿」输出的draft_id

第 21 步:把所有分镜图片添加到草稿

  • 从「20-添加音频」拉出一个新的【插件】节点,插件市场搜剪映小助手,选剪映小助手里的add_images。重命名为:"21-添加图片"

参数设置:

  • draft_id(字符串):引用「19-草稿」输出的draft_id

  • image_infos(字符串):引用「18-整合素材」输出的imageData

  • scale_x(小数):1

  • scale_y(小数):1

第 22 步:把开场主角透明图叠上去

  • 从「21-添加图片」拉出一个新的【插件】节点,插件市场搜剪映小助手,选剪映小助手里的add_images。重命名为:"22-添加首图角色"

参数设置:

  • draft_id(字符串):引用「19-草稿」输出的draft_id

  • image_infos(字符串):引用「18-整合素材」输出的roleImgData

  • scale_x(小数):2

  • scale_y(小数):2

第 23 步:添加开场音效

  • 从「22-添加首图角色」拉出一个新的【插件】节点,插件市场搜剪映小助手,选剪映小助手里的add_audios。重命名为:"23-添加开场音效"

参数设置:

  • audio_infos(字符串):引用「18-整合素材」输出的kcAudioData

  • draft_id(字符串):引用「19-草稿」输出的draft_id

第 24 步:添加背景音乐

  • 从「23-添加开场音效」拉出一个新的【插件】节点,插件市场搜剪映小助手,选剪映小助手里的add_audios。重命名为:"24-添加背景音乐"

参数设置:

  • audio_infos(字符串):引用「18-整合素材」输出的bgAudioData

  • draft_id(字符串):引用「19-草稿」输出的draft_id

第 25 步:生成图片缩放关键帧数据

  • 从「24-添加背景音乐」拉出一个新的【代码】节点。重命名为:"25-关键帧"

参数设置:

  • segment_ids(字符串列表):引用「21-添加图片」输出的segment_ids

  • duration_list(小数列表):引用「15-批处理」输出的duration_list

  • segment_infos(对象数组):引用「22-添加首图角色」输出的segment_infos

代码直接抄:

import json

async def main(args: Args) -> Output:
    params = args.params
    segment_ids = params['segment_ids']
    times = params['duration_list']
    seg = params['segment_infos']
  
    if len(segment_ids) != len(times):
        raise ValueError("segment_ids与times数组长度不一致")
  
    keyframes = []
  
    for idx, seg_id in enumerate(segment_ids):
        if idx == 0:
            continue
      
        audio_duration = int(float(times[idx]))
        cycle_idx = idx - 1
      
        if cycle_idx % 2 == 0:
            start_scale = 1.0
            end_scale = 1.5
        else:
            start_scale = 1.5
            end_scale = 1.0
      
        keyframes.append({
            "offset": 0,
            "property": "UNIFORM_SCALE",
            "segment_id": seg_id,
            "value": start_scale,
            "easing": "linear"
        })
      
        keyframes.append({
            "offset": audio_duration,
            "property": "UNIFORM_SCALE",
            "segment_id": seg_id,
            "value": end_scale,
            "easing": "linear"
        })
  
    keyframes.append({
        "offset": 0,
        "property": "UNIFORM_SCALE",
        "segment_id": seg[0]['id'],
        "value": 2,
        "easing": "linear"
    })

    keyframes.append({
        "offset": 533333,
        "property": "UNIFORM_SCALE",
        "segment_id": seg[0]['id'],
        "value": 1.2,
        "easing": "linear"
    })
  
    keyframes.append({
        "offset": seg[0]['end'] - seg[0]['start'],
        "property": "UNIFORM_SCALE",
        "segment_id": seg[0]['id'],
        "value": 1.0,
        "easing": "linear"
    })
  
    return {
        "keyFrames": json.dumps(keyframes)
    }

输出变量:

  • keyFrames(字符串)

第 26 步:把关键帧真正加进剪映草稿

  • 从「25-关键帧」拉出一个新的【插件】节点,插件市场搜剪映小助手,选剪映小助手里的add_keyframes。重命名为:"26-添加关键帧"

参数设置:

  • draft_id(字符串):引用「19-草稿」输出的draft_id

  • keyframes(字符串):引用「25-关键帧」输出的keyFrames

第 27 步:生成正文字幕数据

  • 从「26-添加关键帧」拉出一个新的【插件】节点,插件市场搜剪映小助手,选剪映小助手里的caption_infos。重命名为:"27-字幕数据"

参数设置:

  • texts(字符串列表):引用「18-整合素材」输出的text_captions

  • timelines(对象数组):引用「18-整合素材」输出的text_timielines

第 28 步:生成开场 2 个字标题数据

  • 从「27-字幕数据」拉出一个新的【插件】节点,插件市场搜剪映小助手,选剪映小助手里的caption_infos。重命名为:"28-标题数据"

参数设置:

  • texts(字符串列表):引用「18-整合素材」输出的title_list

  • timelines(对象数组):引用「18-整合素材」输出的title_timelimes

  • in_animation(字符串):弹入

第 29 步:把正文字幕添加到草稿

  • 从「28-标题数据」拉出一个新的【插件】节点,插件市场搜剪映小助手,选剪映小助手里的add_captions。重命名为:"29-添加字幕"

参数设置:

  • captions(字符串):引用「27-字幕数据」输出的infos

  • draft_id(字符串):引用「19-草稿」输出的draft_id

  • alignment(整数):1

  • border_color(字符串):#000000

  • font_size(整数):7

  • text_color(字符串):#FFFFFF

  • transform_x(小数):0

  • transform_y(小数):-470

第 30 步:把开场 2 个字标题添加到草稿

  • 从「29-添加字幕」拉出一个新的【插件】节点,插件市场搜剪映小助手,选剪映小助手里的add_captions。重命名为:"30-添加开场2个字"

参数设置:

  • captions(字符串):引用「28-标题数据」输出的infos

  • draft_id(字符串):引用「19-草稿」输出的draft_id

  • alignment(整数):1

  • border_color(字符串):#000000

  • font(字符串):梦桃体

  • font_size(整数):40

  • letter_spacing(小数):26

  • text_color(字符串):#FFFFFF

  • transform_x(小数):0

  • transform_y(小数):0

第 31 步:结束并返回剪映草稿 ID

  • 从「30-添加开场2个字」拉出一个新的【结束】节点。重命名为:"结束"

参数设置:

  • output(字符串):引用「19-草稿」输出的draft_id

这就是整个工作流搭建的过程,接下来我们只需要点击试运行,成功的话就会得到这样一个草稿ID

只需要把这个草稿ID复制下来,然后打开我们的剪映小助手把草稿ID复制进去,然后点击创建剪映草稿(注意不要复制引号)

稍等一下,创建成功之后打开剪映,就会看到剪映里面多出了一个黑色的草稿,只要点进去就能看到我们的视频了

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐