IndexTTS 本地推理、AAC 輕量化轉檔、RSS 2.0 協定規格到 iPhone / Apple Watch 運動無縫同步
在個人 AI 工作流中,我們透過本機大型語音合成模型(如 IndexTTS-2.5 / MLX 1.5 加速版)將投資經典《量價分析》(A Complete Guide to Volume Price Analysis)全書 13 章成功渲染為中文有聲書。第 1 至第 10 章累計產出了超過 7 小時 40 分鐘 的高品質無損語音。
然而,當希望在外出運動(慢跑、健身、散步)時透過手機收聽時,遇到了以下關鍵瓶頸:
很多人誤以為 Podcast 是某個公司的專有應用,但其本質是一個開放、去中心化的 XML 資料傳輸協定。任何一個 Podcast 播放器(Apple Podcasts、Overcast、Pocket Casts)在訂閱時,做的事情只有一件:定期去抓取並解析一個遠端的 XML 檔案。
現代 Podcast 規範建立在 RSS 2.0 之上,並引入 Apple 訂定的 itunes 擴充標籤。整體架構分為兩大部分:
<channel>:代表整檔「節目」或「整本書」的通用設定(書名、作者、封面網址、語言、類別、連載類型)。<item>:代表「單集」或「單一章節」(章節名稱、描述、時長、發布時間、音訊附件直鏈)。<enclosure>
在每個 <item> 中,最關鍵的標籤就是 <enclosure>,它告訴播放器音檔在哪裡、有多大、是什麼類型:
<enclosure url="https://.../01-chapter-one.m4a" length="17591227" type="audio/x-m4a"/>
| 屬性名稱 | 範例值 | 技術意義與重要性 |
|---|---|---|
url |
https://.../01.m4a |
音訊檔案的公開直接下載位址(Direct Link),不得有跳轉登入阻攔。 |
length |
17591227 |
精確到 Byte 的檔案大小。播放器用於計算下載進度條與預分配儲存空間。 |
type |
audio/x-m4a |
MIME Type。告訴播放器解碼器格式(例如 AAC 為 audio/x-m4a,MP3 為 audio/mpeg)。 |
<guid> |
01-chapter-one |
全域唯一識別碼。播放器依據 GUID 判斷「這集是否已聽過」或「是否已下載」,即便檔名改變只要 GUID 不變就不會重複下載。 |
<itunes:duration> |
00:31:50 |
章節總時長(HH:MM:SS)。讓播放器在未開始下載音檔前即可展示正確時長。 |
在自建私人 Podcast 的架構中,音訊儲存與伺服器的選擇是成敗關鍵。傳統使用 AWS S3 或 Google Cloud Storage 面臨高昂的頻寬計費,而 Cloudflare R2 具有決定性的三大優勢:
有聲書單一章節往往達到 15~35 MB,全書完整下載多次會產生數十 GB 的流量。在 AWS S3 上,每 GB 出口流量需支付約 $0.09 美元;而 Cloudflare R2 不收取任何數據傳輸流量費,且提供每月 10 GB 的免費儲存額度,對個人自用而言等同於「永久 0 元」。
這是許多開發者自建串流最容易踩的坑:Apple Podcasts 要求音訊伺服器必須完整支援 HTTP Range 請求。當用戶在 1 小時的章節中拖動播放進度條、或者從上次停止處繼續播放時,播放器會送出 Range: bytes=1048576- 標頭。若伺服器不支援,播放器將被迫從頭下載或直接報錯。
Cloudflare R2 經由全球邊緣節點原生回傳 Accept-Ranges: bytes 與 HTTP 206 Partial Content,完美滿足 Apple 官方技術標準。
我們啟用了 R2 的專屬 Public Dev URL(格式如 https://pub-<hash>.r2.dev)。該網址具備高熵值的亂數特徵,不在任何公共目錄登記,搜尋引擎亦不會索引。只有知道專屬 Feed URL 的設備才能拉取,兼具便利與版權自用安全性。
IndexTTS 生成的無損 WAV 採樣率為 24,000Hz PCM 16-bit,體積龐大。我們採用 AAC 核心編碼器,保留原始 24kHz 單聲道特徵以避免二次重採樣失真,設定碼率為 64 kbps:
ffmpeg -i 01-chapter-one.wav -c:a aac -b:a 64k -vn \
-metadata title="第01章:交易市場中沒有新鮮事" \
-metadata artist="Anna Coulling / 圓圓朗讀" \
-metadata album="量價分析 (Volume Price Analysis)" \
-metadata genre="Audiobook" \
01-chapter-one.m4a -y
成效對比: 第 1 至 10 章原始 WAV 體積為 1.15 GB,轉碼後降至 178 MB,體積縮減 84.5%,外出運動以行動網路下載數秒內即可完成。
Apple Podcasts 要求封面圖必須為 1:1 正方形且解析度介於 1400x1400 至 3000x3000。我們從電子書解出原封面(469x751),利用 FFmpeg 雙層濾鏡自動合成高斯模糊背景:
ffmpeg -i cover_raw.jpeg -filter_complex \
"[0:v]scale=1400:1400,boxblur=20:20[bg];[0:v]scale=1400:1400:force_original_aspect_ratio=decrease[fg];[bg][fg]overlay=(W-w)/2:(H-h)/2" \
-q:v 2 cover.jpg -y
透過 Python 呼叫 ffprobe 取得精確時長與檔案大小,動態拼裝標準 XML:
# 關鍵資訊提取與 enclosure 組裝片段
file_size = os.path.getsize(m4a_path)
duration_sec = int(get_duration(m4a_path))
duration_str = f"{duration_sec // 3600:02d}:{(duration_sec % 3600) // 60:02d}:{duration_sec % 60:02d}"
item_xml = f"""
<item>
<title><![CDATA[{chapter_title}]]></title>
<itunes:author>Anna Coulling / 圓圓朗讀</itunes:author>
<pubDate>{pub_date}</pubDate>
<enclosure url="{r2_url}/{filename}" length="{file_size}" type="audio/x-m4a"/>
<guid isPermaLink="false">{chapter_id}</guid>
<itunes:duration>{duration_str}</itunes:duration>
</item>"""
利用 wrangler CLI,直接指定 --remote 參數與對應 MIME 類型推送:
# 上傳音檔
wrangler r2 object put "howard-audiobooks/01-chapter-one.m4a" \
--file "audio/01-chapter-one.m4a" --content-type "audio/x-m4a" --remote
# 上傳 RSS 清單
wrangler r2 object put "howard-audiobooks/feed.xml" \
--file "audio/feed.xml" --content-type "application/rss+xml" --remote
當所有物件上傳至 Cloudflare R2 後,訂閱與串接完全自動化:
https://pub-43dc4106670549999990fa09d26d8316.r2.dev/feed.xml。運動場景體驗優勢:
所有轉檔、組裝與推送程式碼均已集中維護於專案目錄:
zh-simplified-tts/
├── audio/ # M4A 音訊、cover.jpg、feed.xml 與原始 WAV
├── scripts/ # OpenCC 簡體處理後的章節文本
├── publish_podcast.py # 自動執行 FFmpeg 轉碼並產生 feed.xml
├── upload_to_r2.py # 自動將最新檔案同步至 Cloudflare R2
└── docs/kami_podcast_guide/ # Kami 排版報告原始檔與 PDF
後續章節(第 11、12 章)生成完成後,僅需依序執行:
python3 publish_podcast.py
python3 upload_to_r2.py
手機端 Apple Podcasts 將在下一次背景檢查時自動跳出「新章節已發布」推播通知。