第3篇:COZE 爆品剖析【“假如书籍会说话” 爆款视频,单条播放破 50 万】工作流全面解析

目录

轻松制作 “假如书籍会说话” 爆款视频,单条播放破 50 万,读书博主逆袭必备攻略

1.  前言

2.  Coze工作流设计思路

2.1  整体架构规划​

2.2  完整的工作流程

3.  Coze工作流具体实现

3.1  开始节点:

3.2  生成对话文案:

3.3  台词拆分:

3.4  台词整理:

3.5  文案拆分数据合并

3.6  语音合成

3.7  提取开场文案音频时间线:

3.8  提取正文文案音频时间线:

3.9  时间线合并:

3.10  背景设置:

3.11  剪映小助手数据生成-背景图片信息:

3.12  剪映小助手数据生成-开场素材:

3.13  代码节点-处理Q(A)文案分镜:

3.14  代码节点-处理主持人(书籍)配图:

3.15  创建剪映草稿:

3.16  将所有元素添加进草稿:

3.17  结束节点:

3.18  剪映小助手下载预览文件

3.20代码

3.21  复盘智能体工作流流程

4.  资料领取

5.  结语


轻松制作 “假如书籍会说话” 爆款视频,单条播放破 50 万,读书博主逆袭必备攻略

1.  前言

        翻开一本旧书,指尖摩挲泛黄的纸页时,你是否曾幻想过,那些沉淀着智慧与故事的文字能开口诉说?从作者创作时的灵感迸发,到不同读者的感悟共鸣,每一本书都承载着跨越时空的对话。在 Coze 的世界里,这个浪漫想象正变为现实。

​今天,我们将带你解锁 “如果书籍能说话” 的COZE工作流,让静默的书籍化身智能伙伴,开启一场前所未有的阅读交互之旅。

效果展示:

2.  Coze工作流设计思路

2.1  整体架构规划​

        我们设计的工作流主要包含五个核心环节:根据书名生成对话文案将文案按角色进行台词拆分将台词进行语音合成时间线合并,最后将素材、文案和配音通过剪映小助手工具创建剪映草稿。

2.2  完整的工作流程

3.  Coze工作流具体实现

3.1  开始节点:

作为工作流的起始点,其主要作用是接收用户输入。我们设置4个输入变量,book_name、logo_txt、host、book,分别为书名、右下角的文字、主持人形象和书籍的形象。

3.2  生成对话文案

该节点使用豆包大模型节点,将根据用户输入的参数生成对话文案。​

3.3  台词拆分

该节点负责将获得的文案按照指定的分隔符进行拆分。

3.4  台词整理

该组节点负责去除台词中的无效空白文案,然后再按角色拆分台词。

3.5  文案拆分数据合并

该节点通过代码将对话文案组合起来,输出Q数组和A数组,为后续合成语音做准备。

3.6  语音合成

        通过合成语音插件将前一个数组中的Q文案和A文案分别合成语音,这里可以选择自己喜欢的音色。

3.7  提取开场文案音频时间线

3.8  提取正文文案音频时间线

3.9  时间线合并

3.10  背景设置

3.11  剪映小助手数据生成-背景图片信息

3.12  剪映小助手数据生成-开场素材

3.13  代码节点-处理Q(A)文案分镜

3.14  代码节点-处理主持人(书籍)配图

3.15  创建剪映草稿

3.16  将所有元素添加进草稿

3.17  结束节点:

工作流的最终节点,用于返回草稿的链接。

3.18  剪映小助手下载预览文件

运行完成后,复制output链接,通过剪映小助手下载素材文件,最后打开剪映即可发布。

3.20代码

1.文案拆分数据归一化

async function main({ params }: Args): Promise<Output> {
    const ret: {
      preWenan: string[];
      Q: { role: string; idx: number; content: string }[];
      A: { role: string; idx: number; content: string }[];
    } = {
      Q: [],
      A: [],
      preWenan: ["假如书籍会说话", '今日对话', params.book_name]
    };
  
    try {
      // 参数校验
      if (!Array.isArray(params.roleDialogs)) {
        throw new Error("roleDialogs字段必须是数组");
      }
  
      for (const item of params.roleDialogs) {
        if (!item || typeof item.role !== "string" || !Array.isArray(item.dialogs)) {
          continue;
        }
  
        const role = item.role;
        const dialogs = item.dialogs;
  
        for (const dialog of dialogs) {
          if (
            typeof dialog.role === "string" &&
            typeof dialog.content === "string" &&
            typeof dialog.idx === "number"
          ) {
            const entry = {
              role: dialog.role,
              idx: dialog.idx,
              content: dialog.content
            };
  
            if (role === "Q") {
              ret.Q.push(entry);
            } else if (role === "A") {
              ret.A.push(entry);
            }
          }
        }
      }
    } catch (e) {
      // 错误处理,返回空结构
      return {
        preWenan: [],
        Q: [],
        A: []
      };
    }
  
    return ret;
  }
  

2. 处理文案

async function main({ params }: Args): Promise<Output> {

    const ret: any = {
      pre_timeline1: [params.pre_timeline.at(0)],
      pre_timeline2: [params.pre_timeline.at(1)],
      pre_book_timeline: [
        {
          start: params.pre_timeline.at(2).start - 500_000,
          end: params.pre_timeline.at(2).end,
        },
      ],
      pre_timeline2_all: [
        {
          start: params.pre_timeline.at(1).start,
          end: params.pre_timeline.at(2).end,
        },
      ],
    };
  

    const { roleWenanTimeline = [] } = params;
  
    // 容器初始化
    const qWenan: string[] = [];
    const aWenan: string[] = [];
    const qTimelines: { start: number; end: number }[] = [];
    const aTimelines: { start: number; end: number }[] = [];
  
    // 第一组 → q,第二组 → a
    roleWenanTimeline.forEach((group: any, idx: number) => {
      const tgtWenan = idx === 0 ? qWenan : aWenan;
      const tgtTlines = idx === 0 ? qTimelines : aTimelines;
  
      (group.wenan ?? []).forEach((w: string) => tgtWenan.push(w));
      (group.timelines ?? []).forEach((tl: any) =>
        tgtTlines.push({ start: tl.start, end: tl.end })
      );
    });
  
    // 写回到 ret
    Object.assign(ret, { qWenan, aWenan, qTimelines, aTimelines });
  
    return ret;
  }
  

 3.处理Q文案分镜

async function main({ params }: Args): Promise<Output> {
    try {
      const {
        font_size,
        in_animation,
        keyword_color,
        keyword_font_size,
        keywords,
        texts,
        timelines,
        out_animation,
        out_animation_duration
      } = params ?? {};
  
      // 基本校验
      if (!Array.isArray(texts) || !Array.isArray(timelines) || texts.length !== timelines.length || texts.length === 0) {
        throw new Error("texts 与 timelines 数量必须一致且非空");
      }
      if (typeof keywords !== "string" || !keywords.trim()) {
        throw new Error("keywords 必须为非空字符串");
      }
  
      // 拆分关键词并去重
      const kwList: string[] = [...new Set(keywords.split("|").map(s => s.trim()).filter(Boolean))];
  
      const infos = texts.map((text: string, idx: number) => {
        const { start, end } = timelines[idx] || {};
        if (typeof start !== "number" || typeof end !== "number") {
          throw new Error(`timelines 第 ${idx} 项 start/end 格式错误`);
        }
        const matched = kwList.filter(kw => text.includes(kw));
        return {
          start,
          end,
          text,
          keyword: matched.join("|"),
          keyword_color,
          keyword_font_size,
          font_size,
          in_animation,
          out_animation,
          out_animation_duration
        };
      });
  
      return { infos: JSON.stringify(infos) };
    } catch (e: any) {
      return { infos: "[]", error: String(e.message || e) } as any;
    }
  }
  

4.主持人配图

async function main({ params }: Args): Promise<Output> {
    const { timeline = [], Q = [], img } = params;
  
    // 基本校验
    if (!Array.isArray(timeline) || !Array.isArray(Q) || timeline.length !== Q.length) {
      throw new Error("timeline 和 Q 数组需一一对应且长度一致");
    }
  
    /** 1️⃣ 先把 timeline 和 Q 绑定到一起:[{ idx, start, end }] */
    const combined = timeline.map((t, i) => ({
      idx: Q[i]?.idx,   // Q 里给出的 idx
      start: t.start,
      end: t.end,
    }));
  
    /** 2️⃣ 按 idx 升序排序 */
    combined.sort((a, b) => a.idx - b.idx);
  
    /** 3️⃣ 遍历合并连续段 */
    const merged: any[] = [];
    let group: any = null;
  
    for (const seg of combined) {
      if (!group) {
        group = { image_url: img, start: seg.start, end: seg.end, lastIdx: seg.idx };
        continue;
      }
  
      if (seg.idx === group.lastIdx + 1) {
        // 连续 idx:只更新 end 与 lastIdx
        group.end = seg.end;
        group.lastIdx = seg.idx;
      } else {
        // 断开:推入旧组,开启新组
        delete group.lastIdx;
        merged.push(group);
        group = { image_url: img, start: seg.start, end: seg.end, lastIdx: seg.idx };
      }
    }
  
    if (group) {
      delete group.lastIdx;
      merged.push(group);
    }
  
    /** 4️⃣ 结果 JSON 字符串化返回 */
    return { infos: JSON.stringify(merged) };
  }
  

3.21  复盘智能体工作流流程

4.  资料领取


        在使用大模型时若感觉体验不佳,很可能是提示词撰写方式有待优化。为此,我们整理了丰富的提示词模板与 Coze系列操作教程,涉及的代码和提示词、完整工作流程已同步至 Coze 空间,感兴趣的朋友可以私信微信详细了解~


5.  结语


        通过这套 “如果书籍能说话” 的 Coze 工作流,我们不仅赋予了书籍 “声音”,更创造了一种全新的知识交互方式。无论是深度解析经典著作,还是收集个性化阅读建议,它都能让每一本书籍真正 “活” 起来。现在,快将这份教程付诸实践,让你的藏书成为会思考、能对话的智慧宝库。未来,也期待你探索更多创意玩法,打造独属于自己的书籍智能交互生态!​

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值