掘金 人工智能 05月16日 16:58
Coze实战:《如果书籍会说话》保姆级教程!全流程拆解(附源码)
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文详细拆解了制作“假如书籍会说话”短视频的技术流程,从素材准备到工作流实现,提供了保姆级教程。内容涵盖主持人、书籍素材的抠图与处理,以及开场白和对话两大部分工作流的搭建。文章深入讲解了如何利用插件进行语音合成、草稿创建、音视频及字幕的批量添加,并提供了关键代码示例。此外,还介绍了如何使用大模型生成生动有趣的对话文案,旨在帮助读者快速掌握这一创意视频的制作方法。

🎬 **开场白制作流程**:详细介绍了开场白部分的工作流,包括利用开始节点接收参数(书籍图片、主持人图片、背景图、书名、作者名、开场视频、时间游标),通过语音合成插件生成音频,获取音频时长,创建草稿,并通过代码节点将参数转换为剪映小助手插件所需的格式。

🗣️ **对话内容生成**: 借助大模型生成主持人与书籍之间生动有趣的对话文案,并强调了提示词的重要性。虽然没有直接给出提示词,但鼓励读者通过逆向工程的方式,利用给出的对话结果反推出提示词,避免内容同质化。

🔄 **人物与书籍对话工作流**:详细讲解了人物与书籍对话的子工作流,重点介绍了开始节点(接收对话元素、书籍图片、主持人图片、背景图、作者名、对话主角类型、草稿地址)和循环节点(遍历对话数组,判断对话主角类型,合成对应音频,并写入剪映小助手插件)。

🧩 **关键代码解析**:提供了多个关键代码段,例如用于组装数据的代码,该代码可以将循环生成的内容转换为剪映小助手需要的输入格式,从而实现音频、视频、字幕的批量添加。

🛠️ **剪映小助手插件应用**:多次提及并详细介绍了add_audios、add_videos、add_captions等剪映小助手插件的应用,这些插件能够承接代码输出的参数,实现在剪映草稿中批量添加音频、视频和字幕,极大地提高了视频制作效率。

1. 前言

最近,一种新型的短视频形式在社交平台上引起了极大关注——《假如书籍会说话》。这类视频通过赋予书籍“生命”,让它们以动画形式与主持人互动,突破了传统的阅读方式,给观众带来了沉浸式的体验。

今天,我将带领大家进行一次全面的技术拆解,从素材准备到工作流实现,我将提供一个保姆级教程,帮助大家快速掌握这一工作流。无论你是视频制作初学者,还是有一定经验的创作者,都能在这篇文章中找到实用的技巧与方法,让我们一起探索这一创意视频背后的技术实现吧!

2. 效果展示及前期准备

2.1 效果展

效果如下面的视频所示,我这边控制了让它显示一轮对话,因为token伤不起......

2.2 素材准备

主持人(自己抠图,不喜欢这种风格就丢即梦里面改一下):

书(自己抠图,不喜欢这种风格就丢即梦里面改一下):

开场动画:

p3-bot-workflow-sign.byteimg.com

3. 工作流实现

整体工作流为两大部分,分别为开场白部分以及主持人和书籍的对话部分,完整工作流如下图所示:

3.1. 开场白工作流

开场白这部分的工作流就是视频开头的书籍出场部分,还是按照惯例来讲一下工作流的核心节点:

开始节点: 开始节点的接收参数有book(书籍图片)、host(主持人图片)、background(背景图)、book_name(书名,这个视频将围绕这本书展开)、logo(视频作者名)、video(开场视频)、start(时间游标,这个时间游标是开场视频播放完后时间到来到的秒数、单位为微秒,图里为3500000,可以理解为视频开场视频播放完以后,时间来到了3.5秒,我们需要继续在3.5秒这个时间后面追加新内容,如书名,主持人和书籍的对白。)

speech_synthesis(插件): 这个插件主要是基于开始节点中的book_names生成音频,可以点击插件详情,修改音色和其他参数设置。

get_audio_duration(插件): 获取朗读书名这段音频的时长。

create_draft(插件): 创建草稿,需要设置一下草稿视频的宽和高,我设置的是1920*1080。

代码(组装数据): 我们前置的参数,需要基于剪映小助手插件放入到剪映草稿中,我们就需要转换一下,将其转换为剪映小助手插件要求的输入格式,以下图的add_audios工具为例,我们需要传递给插件的参数为audio_infos和draft_url,audio_infos为一个json数组:

我们就需要基于一个代码节点将我们的前置参数转换为剪映小助手需要的格式,代码的输入参数为:

完整代码为:

async function main({ params }) {    const time_value = params.time;  // 整数类型    const width = parseInt(params.width); // 转换为整数    const height = parseInt(params.height); // 转换为整数    const start = params.start; // 整数类型        // 字符串类型的参数 - 需要从JSON字符串转换为JavaScript对象    let video_url = [];    try {        video_url = JSON.parse(params.video_url);    } catch {        video_url = [params.video_url]; // 如果解析失败,假设它是单个URL字符串    }    let text = [];    try {        text = JSON.parse(params.text);    } catch {        text = [{ sentence: params.text }]; // 如果解析失败,假设它是单个文本字符串    }    let audio_url = [];    try {        audio_url = JSON.parse(params.audio_url);    } catch {        audio_url = [{ data: { link: params.audio_url } }]; // 如果解析失败,假设它是单个URL字符串    }    // 初始化结果数组    const captions = [];    const videos = [];    const audios = [];        let current_time = start;    let end_time = 0;    // 获取所有数组的最小长度,以防数组长度不一致    const data_length = Math.min(video_url.length, text.length, audio_url.length);        for (let idx = 0; idx < data_length; idx++) {        // 所有元素使用相同的时间长度        const duration = time_value; // 转为微秒        end_time = current_time + duration;                try {            // 获取音频URL            let audioLink = audio_url[idx];            if (audioLink && audioLink.data && audioLink.data.link) {                audioLink = audioLink.data.link;            } else {                audioLink = String(audio_url[idx]);            }            // 获取文本            let sentence = text[idx];            if (sentence && sentence.sentence) {                sentence = sentence.sentence;            } else {                sentence = String(text[idx]);            }            // 获取视频URL            const videoLink = String(video_url[idx]);            // 添加到结果数组            audios.push({                audio_url: audioLink,                duration: duration,                start: current_time,                end: end_time            });                        captions.push({                text: sentence,                start: current_time,                end: end_time,                in_animation: "放大",                in_animation_duration: 500000,                loop_animation: "颤抖",                loop_animation_duration: duration            });                        videos.push({                video_url: videoLink,                width: width,                height: height,                start: 0,                end: end_time,                duration: duration            });        } catch (e) {            // 如果处理某个元素时出错,继续处理下一个            console.log(`处理第${idx}个元素时出错: ${e}`);            continue;        }    }    // 返回结果    return {        captions: JSON.stringify(captions),        videos: JSON.stringify(videos),        audios: JSON.stringify(audios),        cursor_time: end_time    };}

add_audios(插件)、add_videos(插件)、add_captions(插件):这三个插件我放在一起写,他们的作用就是承接代码输出的audios、videos、captions参数,实现在剪映草稿中批量添加音频、视频和字幕:

大模型 _生成对话文案: 这个节点的作用是基于书名生成生动有趣的对话口播字幕文案。

提示词我就不放了,放出去以后会有很多同质化的内容出来,比如我前段时间写的治愈老奶奶手把手教程,更离谱的是很多人直接拿我文章洗稿发出去,体验太差,提示词我之后也不会放,下面提示词输出的结果,大家可以投喂给大模型让大模型反推出提示词就行。

{  "text_list": [{  "role_name": "主持人",  "line": [    "观众朋友们,欢迎来到思想的盛宴!",    "今天我们有幸请到《[书籍名称]》老师,老师,您好!",    "据说您能洞察人性的[核心奥秘]?"  ]},{  "role_name": "[书籍名称]",  "line": [    "主持人谬赞了。",    "我只是记录者,观察时代的变迁,和人心的轨迹。",    "关于[核心奥秘],其实并不神秘。"  ]},{  "role_name": "主持人",  "line": [    "哦?此话怎讲?",    "我以为会是非常复杂的理论!",    "那您认为,现代人最大的困惑是什么呢?"  ]},{  "role_name": "[书籍名称]",  "line": [    "现代人啊,信息太多,选择也太多,反而迷失了内心的方向。",    "书中第二章,就曾预言过..."  ]},{  "role_name": "主持人",  "line": [    "天啊!太准了!",    "这简直就是我们当下的写照!",    "那我们该如何找回方向呢?"  ]}  ]}

3.2. 人物与书籍对话工作流

人物与书籍对话是工作流是基于大模型生成的对话数组,是一个子工作流,这个工作流的大概思路和主工作流的思路一样,也是构造内容,输入到剪映小助手插件中。我这里只说几个核心节点。完整工作流如下:

开始节点: 开始节点的输入有audio_text(大模型生成的对话元素)、book(书籍图片)、host(主持人图片)、img(背景图)、log_text(视频右下角作者名)、type(对话的主角:主持人或者书籍)、draft_url(草稿地址)。

循环: 这个节点接收的是一个对话元素,形如:

{  "role_name": "主持人",  "line": [    "观众朋友们,欢迎来到思想的盛宴!",    "今天我们有幸请到《[书籍名称]》老师,老师,您好!",    "据说您能洞察人性的[核心奥秘]?"  ]}

我们需要遍历line数组,将line数组元素生成音频,写入到剪映小助手插件当中,如下图,首先我们要先判断role_name为是人为主持人,如果是主持人则合成主持人音频,否则合成书籍音频:

组装数据(代码): 这个节点的内容和主工作流一样,目的就是将循环生成的内容转换为剪映小助手需要的输入格式:

完整代码为:

async function main({ params }) {    // 定义必需参数    const requiredParams = [        "duration_list", "texts", "audio_urls", "width", "height",         "cursor_time", "duration_all", "img_url", "blackground",         "logo_text", "type"    ];        // 验证必需的参数是否存在    for (let param of requiredParams) {        if (!(param in params)) {            return { error: `缺少必需参数: ${param}` };        }    }    // 解析所有输入参数为数组    let duration_list = params.duration_list;    let texts = params.texts;    let audio_urls = params.audio_urls;    try {        if (typeof duration_list === "string") {            duration_list = JSON.parse(duration_list);        }        if (typeof texts === "string") {            texts = JSON.parse(texts);        }        if (typeof audio_urls === "string") {            audio_urls = JSON.parse(audio_urls);        }    } catch (e) {        return { error: `解析输入参数时出错: ${e.message}` };    }    // 保证所有参数都是列表类型    if (!Array.isArray(duration_list)) {        duration_list = [duration_list];    }    if (!Array.isArray(texts)) {        texts = [texts];    }    if (!Array.isArray(audio_urls)) {        audio_urls = [audio_urls];    }    // 获取其他参数    let width = parseInt(params.width);    let height = parseInt(params.height);    let cursor_time = parseInt(params.cursor_time);    let duration_all = parseInt(params.duration_all);    let img_url = params.img_url;    let blackground_url = params.blackground;    let logo_text = params.logo_text;    let type = params.type;    // 初始化结果数组    const captions = [];    const videos = [];  // 如果不需要,可以移除此变量    const audios = [];    const img = [];    const effect = [];    const blackground = [];    const logo = [];    let current_time = cursor_time;    let total_duration = 0;    // 获取所有数组的最小长度    const data_length = Math.min(duration_list.length, texts.length, audio_urls.length);    for (let idx = 0; idx < data_length; idx++) {        try {            let duration = duration_list[idx];            let end_time = current_time + duration;            // 获取音频URL            let audio_url = audio_urls[idx];            if (typeof audio_url === "object" && audio_url.data && audio_url.data.link) {                audio_url = audio_url.data.link;            } else {                audio_url = String(audio_url);            }            // 获取文本            let text = texts[idx];            if (typeof text === "object" && text.sentence) {                text = text.sentence;            } else {                text = String(text);            }            // 添加到音频数组            audios.push({                audio_url: audio_url,                duration: duration,                start: current_time,                end: end_time            });            // 添加到字幕数组            captions.push({                text: text,                start: current_time,                end: end_time,                in_animation: "打字机 II",                in_animation_duration: duration            });            // 更新下一个开始时间            current_time = end_time;            total_duration += duration;        } catch (e) {            console.error(`处理第${idx}个元素时出错: ${e.message}`);            continue;        }    }    let final_time = cursor_time + total_duration;    // 处理图片    if (type === "主持人") {        img.push({            image_url: img_url,            width: width,            height: height,            start: cursor_time,            end: final_time,            in_animation: "向右滑动",             in_animation_duration: 700000        });    } else {        img.push({            image_url: img_url,            width: width,            height: height,            start: cursor_time,            end: final_time,            in_animation: "向左滑动",             in_animation_duration: 700000        });    }    blackground.push({        image_url: blackground_url,        width: width,        height: height,        start: cursor_time,        end: final_time    });    effect.push({        effect_title: "回弹摇摆",        start: cursor_time,        end: final_time    });    logo.push({        text: logo_text,        start: cursor_time,        end: final_time,        loop_animation: "扫光",        loop_animation_duration: total_duration    });    return {        captions: JSON.stringify(captions),        videos: JSON.stringify(videos),        audios: JSON.stringify(audios),        img: JSON.stringify(img),        effect: JSON.stringify(effect),        cursor_time: final_time,        blackground: JSON.stringify(blackground),        logo: JSON.stringify(logo)    };}

退出循环后,我们就将音频、图片、特效、字幕依此写入剪映小助手插件就行。

4. 资料领取

你觉得大模型不好用,可能是你不会写提示词,小肥肠为你准备了海量提示词模板和DeepSeek相关教程,只需关注gzh后端小肥肠,点击底部【资源】菜单即可领取。

本文的 工作流 及提示词已经上传至coze空间,感兴趣的朋友可以私信小肥肠详细了解~

5. 结语

在本文中,我们深入探讨了如何利用 Coze 平台制作《假如书籍会说话》风格的视频内容。从素材准备到工作流实现,每一步都力求详细呈现,旨在帮助创作者快速上手,打造出具有创意和吸引力的 AI 视频作品。在未来,随着 AI 技术的不断发展,我们可以期待 Coze 平台在智能体构建、内容生成等方面带来更多的创新和突破。希望本文能为大家提供有价值的参考,激发更多创作灵感,共同探索 AI 内容创作的无限可能。

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

短视频制作 AI动画 剪映 技术拆解 工作流
相关文章