[Coze] 剪映小助手整理时间线和素材的脚本

简单说一下米核那个插件的用法:

就是生成一些音视频放在云端,然后生成一个draft.json的脚本,

// 在这里,您可以通过 ‘params’ 获取节点中的输入变量,并通过 'ret' 输出结果
// 'params' 和 'ret' 已经被正确地注入到环境中
// 下面是一个示例,获取节点输入中参数名为‘input’的值:
// const input = params.input;
// 下面是一个示例,输出一个包含多种数据类型的 'ret' 对象:
// const ret = { "name": ‘小明’, "hobbies": [“看书”, "旅游"] };

// 定义异步主函数,接收参数params,返回Promise类型的对象
async function main({ params }: Args): Promise {
// 解构params对象,获取所有输入变量
// bg_image_url: 背景图片URL
// audio_list1, audio_list2, audio_list3: 三组音频URL列表
// duration_list1, duration_list2, duration_list3: 三组音频时长列表
// role_list: 角色列表
// text_list: 文本(台词)列表
// keywords: 关键词列表
// start_audio_list: 起始音频列表
// start_duration_list: 起始音频时长列表
const { bg_image_url, audio_list1, duration_list1, role_list, text_list,audio_list2, duration_list2,audio_list3, duration_list3,keywords,
start_audio_list,start_duration_list } = params;

// 合并三组音频列表为一个完整的音频列表
// 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()) };
// }

posted @ 2026-01-30 10:14  lincats  阅读(6)  评论(0)    收藏  举报