Featured image of post 打造属于孩子的有声绘本世界

打造属于孩子的有声绘本世界

经常从图书馆借一些书来给孩子看,有一些是他们感兴趣的可以反复阅读的,无奈借书终有归期,如何解决这个问题呢?作为一个程序员爸爸,何不将它“保留”下来,并且让老父亲可以给他们朗读呢?

背景

孩子的天性,对于喜欢的绘本,可以反复阅读,一遍又一遍。我还记得有一次带女儿去图书馆还书,有几本她不舍得还,于是我们在一个楼梯边上坐下,一起再读了一遍。之后才恋恋不舍地将书交给“机器人”(自动分检)。我也颇遗憾的是记忆中有几本书,每次读起来,娃们都笑逐颜开。于是我打算“留住”这些特别的书,那是他们的美妙回忆。

原本打算写个微信小程序,但想着优先把核心功能跑起来,还是以网站呈现比较简单些。花了一些闲暇时间,借助于 VideCoding 有个初步可用的版本了。

以下简要介绍一下我的一些探索和实现过程,代码没整理暂时没打算开源。如果你想看看效果,我已经部署在vercel了,可以访问:https://audiobook-web-brown.vercel.app 查看。

首页 阅读页

技术实现解析

需求简要分析

显然我们这个网站有几个基本功能要支持一下,比如绘本管理、绘本语音生成、绘本阅读体验等基本功能,多用户管理也可以考虑。 在绘本管理上,比如:

  • 绘本列表,筛选,收藏,标星
  • 绘本添加,扫描录入,提交PDF/PPT录入
  • 绘本删除,需要有锁,避免小孩误删除
  • 阅读记录,可以看到最近读的绘本的阅读时间

在绘本语音生成上,我们之前也试过多种声音复刻了,今天会推荐另一个简单便宜的渠道(后文),我考虑这样:

  • AI 生成的语音
  • AI 复刻的语音
  • 手工录制的语音(咱可能演绎得惟妙惟肖)

在绘本阅读过程中,希望能提供沉浸式的阅读UI,同时需要支持:

  • 横屏还是坚屏展示(特别是在手机上时)
  • 倍速播放
  • 快速跳转页面

用户管理等就是区分不同的用户和阅读进度等啦,这个就不细说了。我们首先面临的是,绘本从何而来?

绘本录入

我们需要将纸质书迁移到线上,最直接的莫过于拿起手机直接对着绘本拍照了,这里有一点小技巧要注意。为了便于后续照片的处理,我们尽量让书本放在一个比较纯净的背景色下,比如下面垫一张大白纸等。之后是将我们拍的照片处理,这里涉及到一些图像校正,透视校正等,如果可以,一些锐化或超分技术也可以使用上。我尝试了几种方式,最后选择了一个简单的方式,但或许不是最终解:

  • 使用OpenCV等库直接对图片进行处理,这里照片里的内容如果对比比较强,容易识别异常,只识别一小部分等出来。比如下面这样: opencv 当然如果花一些时间仔细研究算法和以书本来定制化识别方案,或许是可以调好的。
  • 使用云厂商提供的图像增强功能,可以快速实现裁剪、锐化、提亮等功能。比如腾讯云的文本图像增强

文本图像增强(Text Image Enhancement)是面向文档类图片提供的图像增强处理能力,包括切边增强、图像矫正、阴影去除、摩尔纹去除等;可以有效优化文档类的图片质量,提升文字的清晰度。 它的效果整体还不错,不过对于一些需要先旋转再裁剪的图片,它不会智能处理,所以交给它前最好先预处理一下。

  • 使用免费的 App 来实现,这里推荐微软的 Lens。它不光可以将图片按我们需要处理,可以预览处理结果,还可以微调之。最后还可以直接整理输出为一份 PDF文档。

以上,对于希望免费使用,推荐使用 Lens来处理。而如果希望批量自动化处理,或许调用 API 是更便捷的。

提取文字

当我们已经准备好图片或绘本的 PDF 后,如何将绘本内的文字提取出来便是个问题。我试过传统的 OCR,老实说可读性和进一步处理都比较麻烦。幸运的是现在各种视觉模型都支持图像识别了,所以我们只需要借助他们之力便可以轻松实现目标。考虑到不同模型可能的识别的差异,这里我做了一个测试,使用如下Prompt:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
你是一个专业的儿童绘本分析助手。请仔细观察这张绘本页面,识别并提取出用于朗读的旁白文字内容。

请注意:
1. 只提取适合朗读的旁白文字,不包括对话气泡中的直接对话
2. 旁白通常是描述性的文字,用来推进故事情节或描述场景
3. 有一些页面是标题页等,需要返回标题的文字
4. 如果页面不是标题页,也没有旁白文字,只有对话,请返回"无旁白文字"
5. 请直接返回提取的文字,不需要额外说明

请提取这一页的旁白文字:

比如有如下这张图:example-page

📊 各模型结果对比:

模型状态识别结果
minimax-io/MiniMax-Text-01✅ 主模型蓝色还是有点怀疑: “天空这么大,要不就留在这里?” 山谷中, 有歌声轻轻响起, 洒在她身上的, 是橘色的晨曦。
gemini/gemini-2.5-flash❌ 备用模型蓝色还是有点怀疑: 山谷中, 有歌声轻轻响起, 洒在她身上的, 是橘色的晨曦。
doubao/doubao-seed-1.6-flash✅ 备用模型蓝色还是有点怀疑:“天空这么大,要不就留在这里?” 山谷中, 有歌声轻轻响起, 洒在她身上的, 是橘色的晨曦。
moonshot/moonshot-v1-8k-vision-preview✅ 备用模型蓝色还是有点怀疑: “天空这么大,要不就留在这里?” 山谷中, 有歌声轻轻响起, 洒在她身上的, 是橘色的晨曦。
azure/gpt-4o✅ 备用模型蓝色还是有点怀疑: “天空这么大,要不就留在这里?” 山谷中, 有歌声轻轻响起, 落在她身上的, 是橘色的晨曦。
tencent/hunyuan-turbos-vision✅ 备用模型蓝色还是有点怀疑: “天空这么大,要不就留在这里?” 山谷中, 有歌声轻轻响起, 洒在她身上的, 是橘色的晨曦。

最终结果: 蓝色还是有点怀疑: “天空这么大,要不就留在这里?” 山谷中, 有歌声轻轻响起, 洒在她身上的, 是橘色的晨曦。

上面是写文章时再跑了一次数据,这次 gpt-4o正确了,过往它的错误率也很高。整体的模型测试挺有意思,发现gpt系列没那么准确时,我试了一下gemini 系列,准确性上升不少,但也有出错时。考虑到我是中文绘本的场景,我换成国内的豆包,居然正确率一下提高更多,豆包出错的地方,我就尝试了一下moonshot的模型,又一次给我惊喜,最后是minimax家模型,在我的几个绘本的识别中,准确率是最高的。咱国内模型在这块也是挺能打的嘛!!

我借用多个模型提取出结果,然后有一个模型来判断哪一个是更大概率正确的内容,这样避免单模型的问题,整体上提升识别成功率。 至此,绘本的文字已经准备好,我们开始着手给它配置语音吧。

语音生成

没有声音,再好的戏也出不来:) 小朋友需要一些声音来辅助读绘本。我在之前的让你的小智AI机器人起飞一文中介绍过如何在腾讯云平台复刻声音并且使用,还有豆包平台也有不少音色可选并且也支持声音复刻。但,等等……我找到一个相对更物美价廉的方案,使用 MiniMax 平台进行 TTS以及复刻。MiniMax平台国内注册即有 15 元可以体验,普通的通过 TTS 生成语音可以花一阵子。复刻一个声音只要 9.9 元,这比豆包和腾讯云上似乎便宜得多。同时我发现它的国际版本 minimax.io 可以申请开发者体验计划,我简单填写了一个表格,很快就到账 50刀,这绝对是很大方了,强烈推荐。 推荐它的原因不光是咱可能免费褥到羊毛,更重要的是它的效果很不错。

MiniMax 这家公司还很早提供了 MCP server,可以直接借助 MCP server测试一下它的能力,使用各种声音(复刻)来从文本生成语音(TTS)。我们甚至都不用写一行代码就可以玩起来了。除了使用 MCP 来复刻,也可以简单的使用几行curl命令来完成。

  • 上传样本声音文件 将准备好要复刻的人声文件放在本地文件中,并上传到平台。
1
2
3
4
5
6
7
8
9
export MINIMAX_API_KEY=`你的MINIMAX_API_KEY`
export GROUP_ID=`你的 GROUPID`
export FILE_PATH="/Users/kevin/Downloads/luoluo-voice/output.m4a"
curl --location "https://api.minimaxi.com/v1/files/upload?GroupId=${GROUP_ID}" \
--header 'authority: api.minimaxi.com' \
--header 'content-type: multipart/form-data'  \
--header "Authorization: Bearer ${MINIMAX_API_KEY}" \
--form 'purpose="voice_clone"' \
--form "file=@${FILE_PATH}"

返回:

{“file”:{“file_id”:281211669533090,“bytes”:259501,“created_at”:1750210557,“filename”:“output.m4a”,“purpose”:“voice_clone”},“base_resp”:{“status_code”:0,“status_msg”:“success”}}

  • 使用上传声音复刻
1
2
3
4
5
6
7
8
9
export GROUP_ID=`你的 GROUPID`
curl --location "https://api.minimaxi.com/v1/voice_clone?GroupId=${GROUP_ID}" \
--header 'authority: api.minimaxi.com' \
--header "Authorization: Bearer ${MINIMAX_API_KEY}" \
--header 'content-type: application/json' \
--data '{
    "file_id": 281211669533090,
    "voice_id": "luoluo-2025-0618"
}'

返回:

{“input_sensitive”:false,“input_sensitive_type”:0,“demo_audio”:"",“base_resp”:{“status_code”:0,“status_msg”:“success”}}

  • 查询音色列表
1
2
3
4
5
6
curl --location 'https://api.minimax.chat/v1/get_voice' \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer $MINIMAX_API_KEY" \
--data '{
    "voice_type":"voice_cloning"
}'

注意,在没有使用此音色时,上面查询不到这个,只有在首次使用后才扣费和能够被查询到

欲查询音色类型,支持以下取值:
“system”(系统音色),
“voice_cloning”(快速复刻的音色),
“voice_generation”(文生音色接口生成的音色),
“music_generation”(音乐生成产生的人声或者伴奏音色),
“all”(以上全部)。

  • 使用复刻声音
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
export GROUP_ID=`你的 GROUPID`
curl --location "https://api.minimax.chat/v1/t2a_v2?GroupId=${GROUP_ID}" \
--header "Authorization: Bearer $MINIMAX_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
    "model":"speech-02-turbo",
    "text":"真正的危险不是计算机开始像人一样思考,而是人开始像计算机一样思考。计算机只是可以帮我们处理一些简单事务。",
    "stream":false,
    "voice_setting":{
        "voice_id":"luoluo-2025-0618",
        "speed":1,
        "vol":1,
        "pitch":0,
        "emotion":"happy"
    },
    "pronunciation_dict":{
        "tone":["处理/(chu3)(li3)", "危险/dangerous"]
    },
    "audio_setting":{
        "sample_rate":32000,
        "bitrate":128000,
        "format":"mp3",
        "channel":1
    }
  }' > resp.json | jq -r '.data.audio' | xxd -r -p  > test1.mp3

这样这个复刻的音色就可以后面随时使用了。它在你第一次使用时会扣除 9.9 元(国内)或 3 刀(国际)费用。

音效生成

在我和 Gemini 探讨如何更好的设计这个绘本功能时,它提出了一个很不错的建议:

在准备配音文案时,我建议你创建一个“配音脚本”,不仅仅是抄录文字,而是像一个导演一样,设计整个声音的蓝图。你可以按照这个格式来整理:

页面角色/旁白文字内容声音特效/拟声词背景音乐建议
P1-2旁白在一个安静的小镇上,住着一只名叫乐乐的小狗。(远处几声狗叫) (风轻轻吹过的声音)舒缓、宁静的钢琴曲
P3旁白一天早上,他被一阵“叩叩叩”的声音吵醒了。叩叩叩 (清晰有力的敲门声)音乐暂停或减弱
P3乐乐“是谁呀?”(乐乐迷糊的、带点奶气的声音)(无)

总结一下,要成为声音的东西,不仅仅是“文字”,而是整个“故事场景”。你需要从一个“阅读者”转变为一个“声音导演”。在看每一页图画时,都可以问自己这几个问题:

  • “这里有什么文字?” -> 变成旁白和对话。
  • “这里能听到什么声音?” -> 变成拟声词和音效。
  • “这里是什么感觉/情绪?” -> 变成背景音乐。

通过这样的思考和准备,你最终创作出的有声绘本,将不仅仅是“读”出来的故事,而是一个生动、立体、能让孩子沉浸其中的声音世界。

如果我们可以在绘本期间追加一些声效,以及控制各个角色的声音情绪表现,那必然是更沉浸的体验。这块我寻找了一些方案:

  • ElevenLabs

    • 地址:https://elevenlabs.io/app/sound-effects/generate
    • 提供强大的AI音效生成API,支持通过文本描述生成音效。
    • 可自定义音效时长、提示影响力等参数。
    • 支持多语言,适合集成到游戏、视频制作等开发流程中。
    • 适合开发者通过API调用实现自动化音效生成
  • Stability AI

    • 地址:https://stableaudio.com/generate
    • 提供Stable Audio系列模型API,支持文本到音频、音频到音频的转换。
    • 适合需要高质量、长音频(如3分钟)生成的应用场景。

测试效果两者效果差不多,从费用上来说,似乎ElevenLabs 更便宜一些。暂时因为时间问题,我还没有将这块和上面 TTS 整合。它需要重新编排识别到的文本,在恰到好处的地方插入音效。未来有时间,我会尝试将这块整合到一起。

部署

这个应用是前后端分离设计的,前端基于 Vue 3 + TypeScript + Vite 构建,提供沉浸式阅读体验和智能用户管理。后端使用 golang来开发,同时将数据保存在对象存储中,这样我们的一些图片音频等可以借助对象存储的 CDN 有更快的响应速度。

我试着让 AI 帮我将前端对接到 Vercel 上,它帮我生成了一个 vercel.json 文件,我试着将它部署到 Vercel 上,结果居然一次成功咯,可以开心的用免费服务了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
{
  "buildCommand": "npm run build:skip-check",
  "rewrites": [
    {
      "source": "/((?!api|static|_next|favicon.ico).*)",
      "destination": "/index.html"
    }
  ],
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "X-Content-Type-Options",
          "value": "nosniff"
        },
        {
          "key": "X-Frame-Options",
          "value": "DENY"
        },
        {
          "key": "X-XSS-Protection",
          "value": "1; mode=block"
        }
      ]
    },
    {
      "source": "/static/(.*)",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "public, max-age=31536000, immutable"
        }
      ]
    }
  ]
} 

后端就自己将程序打包为镜像,部署在我原来的k8s集群上了。

后记

在初步完成后,我给孩子试用了几次,效果还不错,他们惊讶于识别到这是爸爸的声音,这是他自己的声音等,也能认真的听完一本又一本绘本,作为老父亲,几个晚上的熬夜也没有白费了。 我突然想起来,有几本英文绘本,碍于我自己的英文水平和发音,都没怎么给孩子读,那真是误自己子弟了:)不过,现在有这个玩意,我可算找到救星了!文字就搞个双语吧,那发音到底是英式还是美式好呢?

我是个爱折腾技术的工程师,也乐于分享。欢迎点赞、关注、分享,更欢迎一起探讨技术问题,共同学习,共同进步。为了获得更及时的文章推送,欢迎关注我的公众号:爱折腾的风

扫码关注公众号