返回博客

用 DeepSeek + MiniMax 为 Next.js 博客开发「AI 播客」功能

2025-12-26
AI
9 分钟
播客
访谈播报
0:00

摘要:在这个快节奏的时代,长文阅读逐渐成为奢侈。本文将分享如何利用 DeepSeek(作为编剧)和 MiniMax(作为配音),在 Next.js 项目中构建一套自动化的播客生成系统,让你的技术博客也能“开口说话”。

为什么要做这个?

在一次锻炼的路上,打开微信文章,里面不仅可以看,还能听,这让我在路上边听,边锻炼,让我感觉十分舒服。

这让我萌生了一个想法:如果能把技术文章自动转换成双人对谈形式的播客,这样也不是很好吗?

不是枯燥的 TTS 朗读,而是像大佬面对面那样,有主持人引导、有博主解答的真实对话感。经过一番折腾,我基于 DeepSeek 和 MiniMax 实现了一套低成本、高质量的解决方案。

技术架构概览

我们的目标很简单:输入文章内容 → 输出 MP3 音频。但为了保证听感自然,中间需要经过精细的处理流程:

1.编剧阶段:提取文章核心,改编成口语化的双人逐字稿(DeepSeek)。 2.配音阶段:根据角色分配不同的音色,并发/串行合成语音(MiniMax)。 3.后期阶段:拼接音频片段,生成完整文件。

第一步:“AI 编剧” (DeepSeek)

要让播客听起来自然,剧本是灵魂。直接朗读文章会像听论文汇报,我们需要把书面语转化为口语。

这里我选择了 DeepSeek,它的中文理解能力极强,且 API 成本极低(性价比吊打 GPT-4)。

1. 定义剧本结构 (Schema) 使用 Vercel AI SDK 的 generateObject 配合 zod,我们可以强制模型输出严格的 JSON 格式,避免后续解析出错。

import { z } from 'zod';

// 定义严格的对话结构
const scriptSchema = z.object({
  dialogue: z.array(
    z.object({
      speaker: z.enum(['Host', 'Blogger']), // 限定只有主持人和博主
      text: z.string(),
    })
  ),
});

2. Prompt 工程 这是最关键的一步。我们需要通过 System Prompt 赋予 AI “制作人”的人格。

export async function generateScript(content: string) {
  // 截取前 1w 字符,防止 Token 溢出
  const truncatedContent = content.slice(0, 10000);

  const result = await generateObject({
    model: deepseek('deepseek-chat'),
    schema: scriptSchema,
    system: `你是一个深受欢迎的技术播客制作人。请将这篇技术文章改编成一段 3 分钟左右的双人对谈脚本。
    
    角色设定:
    - Host (Jimmy): 好奇心强,代表初学者提问,语气轻松活泼。
    - Blogger (小官): 技术博主本人,负责解答,语气专业但幽默。
    
    核心要求:
    1. **口语化!口语化!** 去掉所有书面转折词(如“然而”、“综上所述”)。
    2. 开场白固定为:"欢迎收听 AI 码记,我是 Jimmy..."。
    3. 避免尴尬的互夸,多一些基于内容的真实互动。`,
    prompt: `文章内容:\n${truncatedContent}`,
  });

  return result.object.dialogue;
}

第二步:寻找“声优” (MiniMax)

有了剧本,接下来需要声音。MiniMax 的 speech-01 模型是目前中文 TTS 的第一梯队,语调起伏非常像真人。

为了区分角色,我选用了两款反差鲜明的音色:

  • Host (Jimmy): female-shaonv (少女音,活泼)
  • Blogger (小官): male-qn-qingse (青涩男音,沉稳)

语音合成核心代码

注意:MiniMax 返回的是音频 URL,我们需要在服务端将其下载为 Buffer 以便后续拼接。


const VOICE_MAP = {
  Host: 'female-shaonv',
  Blogger: 'male-qn-qingse',
};

export async function synthesizeSegment(text: string, speaker: 'Host' | 'Blogger'): Promise<Buffer> {
  // ... API Key 配置 ...
  
  const response = await fetch(`${MINIMAX_URL}?GroupId=${groupId}`, {
    method: 'POST',
    body: JSON.stringify({
      text,
      model: 'speech-01',
      voice_id: VOICE_MAP[speaker],
      audio_sample_rate: 32000, // 32k 采样率保证音质
      vol: 1.0,
    }),
    // ... headers ...
  });

  const result = await response.json();
  
  // 关键:获取 URL 后再次 fetch 拿到二进制数据
  const audioResponse = await fetch(result.audio_file);
  const arrayBuffer = await audioResponse.arrayBuffer();
  
  return Buffer.from(arrayBuffer);
}

注:为什么需要拼接,由于我们需要在对话中生成2种不同声的对话,一个男声,及主持人女声,所以在一次性生成中没办法同时输出2种声音,所以我们需要把转换好的声音在重新拼接。

第三步:后期合成与流式传输

这一步就像导演剪辑,我们需要处理并发控制、音频拼接以及向前端的数据传输。

1. 并发控制与限流

TTS 接口通常有 QPS 限制。为了避免 429 Too Many Requests,我采用了一种简单的“串行+等待”策略:

const audioBuffers: Buffer[] = [];

for (let i = 0; i < script.length; i++) {
  const line = script[i];
  
  // 合成当前片段
  const buffer = await synthesizeSegment(line.text, line.speaker);
  audioBuffers.push(buffer);
  
  // 简单的防限流策略:每句之间暂停一下
  if (i < script.length - 1) {
    await new Promise(resolve => setTimeout(resolve, 500)); 
  }
}

2. Buffer 拼接

Node.js 的 Buffer.concat 可以完美将多段音频无缝“胶水”粘合在一起。

const finalAudio = Buffer.concat(audioBuffers);

3. Server Action 的数据传输陷阱

这是最容易踩坑的地方! Next.js 的 Server Actions 不支持直接返回 Buffer 对象给客户端。我们需要将其转换为普通数组。

// ✅ 正确做法:转换为数字数组
const audioData = Array.from(finalAudio);

return { success: true, audioData };

4.前端如何还原 因为你传过去的是一个“数字数组”,浏览器不能直接播放它。所以在前端代码中,必须有逆向还原的一步:

// 前端接收到的 result.audioData 是一个巨大的数字数组 [12, 244, 11, ...]

// 1. 还原为二进制数组 (Uint8Array)
const buffer = new Uint8Array(result.audioData);

// 2. 封装为 Blob 对象,并告诉浏览器这是 'audio/mpeg' (即 MP3)
const blob = new Blob([buffer], { type: 'audio/mpeg' });

// 3. 生成可播放的 URL
const url = URL.createObjectURL(blob);

总结

  • MiniMax API:生成了 MP3 数据。
  • Buffer.concat:拼接了 MP3 数据。
  • Array.from:打包 MP3 数据(为了过安检/网络传输)。
  • 前端 new Blob:解包 MP3 数据(为了播放)。

现在,当用户访问我的博客文章时,只需点击顶部的「🎙️ 播客」按钮,随后就能得到一段娓娓道来的技术对谈。

留言