[Coze] 剪映小助手整理时间线和素材的脚本
简单说一下米核那个插件的用法:
就是生成一些音视频放在云端,然后生成一个draft.json的脚本,
// 在这里,您可以通过 ‘params’ 获取节点中的输入变量,并通过 'ret' 输出结果
// 'params' 和 'ret' 已经被正确地注入到环境中
// 下面是一个示例,获取节点输入中参数名为‘input’的值:
// const input = params.input;
// 下面是一个示例,输出一个包含多种数据类型的 'ret' 对象:
// const ret = { "name": ‘小明’, "hobbies": [“看书”, "旅游"] };
// 定义异步主函数,接收参数params,返回Promise
// 合并三组音频列表为一个完整的音频列表
// audio_list: 合并后的音频URL列表
const audio_list = [...audio_list1, ...audio_list2, ...audio_list3];
// 合并三组时长列表为一个完整的时长列表
// duration_list: 合并后的音频时长列表
const duration_list = [...duration_list1, ...duration_list2, ...duration_list3];
// 初始化音频开始时间为0
let audioStartTime = 0;
// 初始化最大时长为0
let maxDuration = 0;
// 初始化起始时间为0
let initialStartTime = 0;
// 创建起始音频数据数组
const initialAudioData = [];
// 遍历起始音频列表
for(let i=0; i< start_audio_list.length;i++){
// 获取当前起始音频的时长
const duration = start_duration_list[i]; // 引用start_duration_list数组的第i个元素
// 将起始音频数据推入数组
initialAudioData.push({
audio_url: start_audio_list[i], // 引用start_audio_list数组的第i个元素
duration, // 当前音频时长
volume: 2, // 音量设置为2
start: audioStartTime, // 引用当前audioStartTime变量
end: audioStartTime + duration // 结束时间 = 开始时间 + 时长
});
// 更新音频开始时间,累加当前音频时长
audioStartTime += duration; // 更新audioStartTime变量
// 更新最大时长
maxDuration = audioStartTime; // 将maxDuration设置为当前audioStartTime
// 更新起始时间
initialStartTime += duration; // 更新initialStartTime变量
}
// 创建起始文本时间线数组1
const start_text_timelines1 = [];
// 向起始文本时间线数组1推入时间范围对象
start_text_timelines1.push(
{
start: 0, // 开始时间为0
end: start_duration_list[0] // 引用start_duration_list数组的第一个元素
}
);
// 创建起始音频文本数组1,包含一个固定音频URL
const start_audio_text1 =[];
// 向起始音频文本数组1推入音频数据对象
start_audio_text1.push({
audio_url: "https://p9-bot-workflow-sign.byteimg.com/tos-cn-i-mdko3gqilj/373c3e491f214adfa3934a507e1c92e4.MP3~tplv-mdko3gqilj-image.image?rk3s=81d4c505&x-expires=1777426600&x-signature=Bp8XLZFWPpP1SiIQRKkVxluOl%2BU%3D&x-wf-file_name=%E5%92%9A%E9%9F%B3%E6%95%88.MP3", // 固定音频URL
duration: 548571, // 固定时长548571微秒
volume: 2, // 音量设置为2
start: 0, // 开始时间为0
end: 548571 // 结束时间=开始时间+时长
});
// 创建起始文本时间线数组2
const start_text_timelines2 = [];
// 向起始文本时间线数组2推入时间范围对象
start_text_timelines2.push(
{
start: start_duration_list[0], // 开始时间为第一个起始音频的时长
end: start_duration_list[0]+start_duration_list[1] // 结束时间为前两个起始音频时长的总和
}
);
// 创建起始音频文本数组2
const start_audio_text2 =[];
// 向起始音频文本数组2推入音频数据对象
start_audio_text2.push({
audio_url: "https://p9-bot-workflow-sign.byteimg.com/tos-cn-i-mdko3gqilj/2ae7ccd877ea4c79b4fcbcfe38170b54.MP3~tplv-mdko3gqilj-image.image?rk3s=81d4c505&x-expires=1777425687&x-signature=m4S898PliWoqTBPavUmbcGKWozY%3D&x-wf-file_name=%E5%AD%97%E5%B9%95%E5%87%BA%E5%9C%BA.MP3", // 固定音频URL
duration: 862040, // 固定时长862040微秒
volume: 2, // 音量设置为2
start: start_duration_list[0], // 开始时间为第一个起始音频的时长
end: start_duration_list[0] + 862040 // 结束时间=开始时间+固定时长
});
// 创建起始文本时间线数组3
const start_text_timelines3 = [];
// 向起始文本时间线数组3推入时间范围对象
start_text_timelines3.push(
{
start: start_duration_list[0]+start_duration_list[1], // 开始时间为前两个起始音频时长的总和
end: start_duration_list[0]+start_duration_list[1]+start_duration_list[2] // 结束时间为前三个起始音频时长的总和
}
);
// 创建主音频数据数组
const audioData = [];
// 遍历合并后的音频列表
for (let i = 0; i < audio_list.length && i < duration_list.length; i++) {
// 获取当前音频的时长
const duration = duration_list[i]; // 引用duration_list数组的第i个元素
// 向音频数据数组推入音频数据对象
audioData.push({
audio_url: audio_list[i], // 引用audio_list数组的第i个元素
duration, // 当前音频时长
volume: 2, // 音量设置为2
start: audioStartTime, // 引用当前audioStartTime变量
end: audioStartTime + duration // 结束时间 = 开始时间 + 时长
});
// 更新音频开始时间,累加当前音频时长
audioStartTime += duration; // 更新audioStartTime变量
// 更新最大时长
maxDuration = audioStartTime; // 将maxDuration设置为当前audioStartTime
}
// 定义角色1的图片URL(书籍拟人化图片)
var role1_img_url = "https://p9-bot-workflow-sign.byteimg.com/tos-cn-i-mdko3gqilj/7153ce955e1b4d51ab2e3972846fb13e.png~tplv-mdko3gqilj-image.image?rk3s=81d4c505&x-expires=1777338929&x-signature=LLDTeL38s5rfHt7%2BG1%2FvGFrwa%2FQ%3D&x-wf-file_name=CodeL.png";
// 定义角色2的图片URL
var role2_img_url = "https://s.coze.cn/t/MRdXPFzGLOw/";
// 调用generateVideoTimeline函数生成视频时间线
// 传递参数:数据对象、角色1图片URL、角色2图片URL、关键词列表、初始开始时间
const result = generateVideoTimeline(
{
role_list: role_list, // 引用role_list变量
text_list: text_list, // 引用text_list变量
audio_list: audio_list, // 引用audio_list变量
duration_list: duration_list // 引用duration_list变量
},
role1_img_url, // 引用role1_img_url变量
role2_img_url, // 引用role2_img_url变量
keywords, // 引用keywords变量
initialStartTime // 引用initialStartTime变量
);
// 处理背景图片数据
const bgImageData = [
{
image_url: bg_image_url, // 引用bg_image_url变量
width: 1920, // 宽度1920像素
height: 1080, // 高度1080像素
start: 0, // 开始时间为0
end: maxDuration + 1000000 // 结束时间为最大时长加1000000微秒(1秒)
}
];
// 构建输出对象
const ret = {
"role": result.role, // 引用result对象的role属性
"bgImageData": JSON.stringify(bgImageData), // 将bgImageData转换为JSON字符串
"audioData": JSON.stringify(audioData), // 将audioData转换为JSON字符串
"initialAudioData": JSON.stringify(initialAudioData), // 将initialAudioData转换为JSON字符串
"roleImage1": JSON.stringify(result.role[0].images), // 引用result.role[0].images并转换为JSON字符串
"roleImage2": JSON.stringify(result.role[1].images), // 引用result.role[1].images并转换为JSON字符串
"caption_text1": {
"text1": result.role[0].texts, // 引用result.role[0].texts
"timelines1": result.role[0].timelines // 引用result.role[0].timelines
},
"caption_text2": {
"text2": result.role[1].texts, // 引用result.role[1].texts
"timelines2": result.role[1].timelines // 引用result.role[1].timelines
},
"captions1": JSON.stringify(result.role[0].captions), // 引用result.role[0].captions并转换为JSON字符串
"captions2": JSON.stringify(result.role[1].captions), // 引用result.role[1].captions并转换为JSON字符串
"start_timelines1": start_text_timelines1, // 引用start_text_timelines1变量
"start_timelines2": start_text_timelines2, // 引用start_text_timelines2变量
"start_timelines3": start_text_timelines3, // 引用start_text_timelines3变量
"start_audio_text1": JSON.stringify(start_audio_text1), // 将start_audio_text1转换为JSON字符串
"start_audio_text2": JSON.stringify(start_audio_text2) // 将start_audio_text2转换为JSON字符串
};
// 返回输出对象
return ret;
}
// 定义generateVideoTimeline函数,用于生成视频时间线
// 参数:data(包含角色列表、文本列表、时长列表的对象)、imageUrl1(角色1图片URL)、imageUrl2(角色2图片URL)、keywords(关键词列表)、initialStartTime(初始开始时间)
function generateVideoTimeline(data, imageUrl1, imageUrl2, keywords = [], initialStartTime = 0) {
// 解构data对象,获取角色列表、文本列表、时长列表
const { role_list, text_list, duration_list } = data;
// 按order字段对文本列表进行升序排序
const sortedTexts = [...text_list].sort((a, b) => a.order - b.order);
// 初始化时间轴起点为传入的初始开始时间
let currentEnd = initialStartTime; // 使用传入的初始时间
// 创建Map对象用于存储角色数据
const roleMap = new Map();
// 定义换行处理函数,将长文本按指定长度换行
const splitLongLines = (line) => {
const maxLength = 10; // 最大行长为10个字符
if (line.length <= maxLength) return line; // 如果文本长度不超过最大长度,直接返回
// 使用正则表达式每maxLength个字符插入一个换行符
return line.replace(new RegExp(`(.{${maxLength}})`, "g"), "$1\n").replace(/\n$/, "");
};
// 遍历排序后的文本列表
for (const text of sortedTexts) {
// 获取当前台词的持续时间,根据order索引获取时长
const duration = duration_list[text.order - 1]; // 引用duration_list数组
const start = currentEnd; // 起始时间基于当前累计值
const end = start + duration; // 结束时间 = 开始时间 + 时长
currentEnd = end; // 更新当前结束时间
// 标准化角色名称匹配(处理方括号)
// 在角色列表中查找与当前文本角色名称匹配的角色
const matchedRole = role_list.find(role =>
role.role_name.replace(/[[\]]/g, "") === text.role_name // 移除角色名称中的方括号后比较
);
// 如果未找到匹配的角色,跳过当前文本
if (!matchedRole) continue;
// 获取匹配到的角色名称作为Map的key
const roleKey = matchedRole.role_name;
// 初始化角色数据结构(如果Map中不存在该角色)
if (!roleMap.has(roleKey)) {
roleMap.set(roleKey, {
role_name: roleKey, // 角色名称
texts: [], // 文本数组
timelines: [], // 时间线数组
images: [], // 图片数组
captions: [] // 字幕数组
});
}
// 获取该角色的数据对象
const roleData = roleMap.get(roleKey);
// 处理台词文本(换行分割)
const processedLine = splitLongLines(text.line); // 调用splitLongLines函数处理文本
roleData.texts.push(processedLine); // 将处理后的文本推入角色文本数组
roleData.timelines.push({ start, end }); // 将时间范围推入角色时间线数组
// 生成captions对象
// 过滤出当前文本中包含的关键词
const matchedKeywords = keywords.filter(keyword => processedLine.includes(keyword)); // 引用keywords数组
const uniqueKeywords = [...new Set(matchedKeywords)]; // 去重获取唯一关键词
const caption = {
start, // 使用调整后的时间
end, // 使用调整后的时间
text: processedLine, // 处理后的文本
in_animation: "羽化向右擦开" // 入场动画效果
};
// 如果有唯一关键词,添加关键词相关属性
if (uniqueKeywords.length > 0) {
caption.keyword = uniqueKeywords.join('|'); // 用|连接所有关键词
caption.keyword_color = "#fe8a80"; // 关键词颜色
caption.keyword_font_size = 10; // 关键词字体大小
caption.font_size = 10; // 字体大小
}
roleData.captions.push(caption); // 将字幕对象推入角色字幕数组
// 添加图片配置
// 根据角色在role_list中的索引决定使用哪个图片URL
const imageUrl = role_list.indexOf(matchedRole) === 0 ? imageUrl1 : imageUrl2;
const lastImage = roleData.images[roleData.images.length - 1]; // 获取角色图片数组的最后一个元素
// 如果存在上一个图片且结束时间与当前开始时间衔接,则延长上一个图片的显示时间
if (lastImage && lastImage.end === start) { // 时间衔接判断依然有效
lastImage.end = end; // 更新上一个图片的结束时间
} else {
// 否则添加新的图片配置
roleData.images.push({
image_url: imageUrl, // 图片URL
in_animation: "轻微放大", // 入场动画效果
width: 300, // 宽度300像素
height: 300, // 高度300像素
start, // 使用调整后的时间
end // 使用调整后的时间
});
}
}
// 返回包含角色数组的对象
return { role: Array.from(roleMap.values()) };
}
// 以下是注释掉的旧版本generateVideoTimeline函数,功能与当前版本类似但起始时间计算方式不同
// function generateVideoTimeline(data, imageUrl1, imageUrl2, keywords = []) {
// const { role_list, text_list, duration_list } = data;
// // 按order排序台词
// const sortedTexts = [...text_list].sort((a, b) => a.order - b.order);
// // 计算时间轴并分组
// let currentEnd = 0;
// const roleMap = new Map();
// // 换行处理函数
// const splitLongLines = (line) => {
// const maxLength = 10;
// if (line.length <= maxLength) return line;
// return line.replace(new RegExp((.{${maxLength}}), "g"), "$1\n").replace(/\n$/, "");
// };
// for (const text of sortedTexts) {
// // 获取当前台词的持续时间
// const duration = duration_list[text.order - 1];
// const start = currentEnd;
// const end = start + duration;
// currentEnd = end;
// // 标准化角色名称匹配(处理方括号)
// const matchedRole = role_list.find(role =>
// role.role_name.replace(/[[]]/g, "") === text.role_name
// );
// if (!matchedRole) continue;
// const roleKey = matchedRole.role_name;
// // 初始化角色数据结构
// if (!roleMap.has(roleKey)) {
// roleMap.set(roleKey, {
// role_name: roleKey,
// texts: [],
// timelines: [],
// images: [],
// captions: [] // 新增 captions 集合
// });
// }
// const roleData = roleMap.get(roleKey);
// // 处理台词文本(换行分割)
// const processedLine = splitLongLines(text.line);
// roleData.texts.push(processedLine);
// roleData.timelines.push({ start, end });
// // 生成 captions 对象(含关键词匹配逻辑)
// const matchedKeywords = keywords.filter(keyword => processedLine.includes(keyword));
// const uniqueKeywords = [...new Set(matchedKeywords)];
// const caption = {
// start,
// end,
// text: processedLine,
// in_animation: "羽化向右擦开"
// };
// if (uniqueKeywords.length > 0) {
// caption.keyword = uniqueKeywords.join('|');
// caption.keyword_color = "#fe8a80";
// caption.keyword_font_size = 10;
// caption.font_size = 10;
// }
// roleData.captions.push(caption); // 添加到 captions 集合
// // 添加图片配置(合并连续时间段逻辑不变)
// const imageUrl = role_list.indexOf(matchedRole) === 0 ? imageUrl1 : imageUrl2;
// const lastImage = roleData.images[roleData.images.length - 1];
// if (lastImage && lastImage.end === start) {
// lastImage.end = end;
// } else {
// roleData.images.push({
// image_url: imageUrl,
// in_animation: "轻微放大",
// width: 300,
// height: 300,
// start,
// end
// });
// }
// }
// return { role: Array.from(roleMap.values()) };
// }

浙公网安备 33010602011771号