Video Podcast Maker 製作影片的原理就是在網頁上產生畫出動畫, 透過截圖的方式, 把圖片再存為影片。
我使用Video Podcast Maker 的作品: https://youtu.be/eBNLJ2zOWOo
解說: https://youtu.be/m02HOcXRMg8
Video Podcast Maker
https://github.com/Agents365-ai/video-podcast-maker
AI-powered video podcast creation skill for coding agents. Supports Bilibili & YouTube, multi-language (zh-CN/en-US), 6 TTS engines (Edge/Azure/ElevenLabs/OpenAI/Doubao/CosyVoice), 4K Remotion rendering.
中文 README說明:
https://github.com/Agents365-ai/video-podcast-maker/blob/main/README_CN.md
edge-tts
https://github.com/rany2/edge-tts
Use Microsoft Edge’s online text-to-speech service from Python WITHOUT needing Microsoft Edge or Windows or an API key
安裝環境
# macOS
brew install ffmpeg node python3
# Ubuntu/Debian
sudo apt install ffmpeg nodejs python3 python3-pip
在 macOS / Ubuntu (WSL) 安裝 pip
python -m pip venv .venv
source .venv/bin/activate
# Python dependencies
pip install azure-cognitiveservices-speech dashscope edge-tts requests
建立 Remotion 專案指令
npx create-video@latest my-video-project
cd my-video-project
npm i
npm install remotion @remotion/cli @remotion/player zod
瀏覽器預覽指令
npx remotion studio src/remotion/index.ts
匯出為 mp4 指令
npx remotion render Podcast out.mp4
匯出指定 frame 為 mov
npx remotion render Podcast frame-200.mov --frames=200-200 --codec=prores
匯出影片的第200幀檢查字型(輸出為 frame-200.png):
npx remotion still Podcast frame-200.png --frame=200 --image-format=png
建議同時開啟3個 terminal 連進 WSL 環境:
- 瀏覽器預覽指令: npx remotion studio src/remotion/index.ts
- AI的 CLI 視窗
- 檔案的操作視窗
設定環境變數
Add to ~/.zshrc or ~/.bashrc:
# TTS Backend: edge (default, free), azure, doubao, cosyvoice, elevenlabs, google, openai export TTS_BACKEND="edge" # Default (free), or "azure" / "doubao" / "cosyvoice" / "elevenlabs" / "google" / "openai" # Azure TTS (high quality) export AZURE_SPEECH_KEY="your-azure-speech-key" export AZURE_SPEECH_REGION="eastasia" # Volcengine Doubao TTS (alternative backend) export VOLCENGINE_APPID="your-volcengine-appid" export VOLCENGINE_ACCESS_TOKEN="your-volcengine-access-token" export VOLCENGINE_CLUSTER="volcano_tts" # Default cluster, adjust per console config export VOLCENGINE_VOICE_TYPE="BV001_streaming" # Adjust per console voice options # Aliyun CosyVoice TTS (alternative backend) + AI thumbnails export DASHSCOPE_API_KEY="your-dashscope-api-key" # Optional: Edge TTS voice override export EDGE_TTS_VOICE="zh-CN-XiaoxiaoNeural" # ElevenLabs TTS export ELEVENLABS_API_KEY="your-elevenlabs-api-key" # Google Cloud TTS export GOOGLE_TTS_API_KEY="your-google-tts-api-key" # OpenAI TTS export OPENAI_API_KEY="your-openai-api-key" # Optional: Google Gemini for AI thumbnails export GEMINI_API_KEY="your-gemini-api-key"
Then reload: source ~/.zshrc
調整畫面大小與時長:
在 Remotion 中修改影片解析度,主要是在 Composition 元件的屬性裡進行設定。以下是具體的做法與細節。
修改 Composition 設定
在專案的入口文件(通常是 Root.tsx 或 index.tsx)中,你會看到定義 Composition 的程式碼。直接修改傳入的 width 與 height 數值即可更換解析度。
TypeScript
import { Composition } from 'remotion';
import { MyVideo } from './MyVideo';
export const RemotionRoot = () => {
return (
<>
<Composition
id="MyComp"
component={MyVideo}
durationInFrames={150}
fps={30}
width={1920}
height={1080}
/>
</>
);
};
常見解析度配置
如果你想要製作不同平台的影片,可以參考以下數值:
- 橫向影片 (16:9):1920×1080
- 直向影片 (9:16):1080×1920
- 正方形影片 (1:1):1080×1080
- 4K 影片:3840×2160
在元件中使用解析度數值
在撰寫影片內容的元件時,建議使用 useVideoConfig 這個 Hook 來取得當前設定的解析度,而不是寫死固定數值。這樣當你在 Root.tsx 修改解析度時,內部的排版也會跟著連動。
TypeScript
import { useVideoConfig } from 'remotion';
export const MyVideo = () => {
const { width, height } = useVideoConfig();
return (
<div style={{
flex: 1,
backgroundColor: 'white',
width: width,
height: height
}}>
解析度是 {width} x {height}
</div>
);
};
渲染時縮放影片
如果你不想修改原始程式碼的解析度,只想在輸出成果時縮小尺寸(例如為了預覽或加快渲染速度),可以在執行 npx remotion render 指令時加上 scale 參數。
例如將原本 1080p 的影片縮放成一半大小輸出:
npx remotion render src/index.tsx MyComp out.mp4 –scale=0.5
注意事項
解析度必須是整數,且某些編碼器(如 H.264)要求寬度與高度必須是 2 的倍數,否則在渲染時可能會報錯或出現綠邊。建議設定時優先選擇偶數。
✦ 我已經將 public/tts/work2.mp3 的發音優化為 1 拍 (ㄙㄞ / sāi):
🛠️ 修正對照 (Phoneme Mapping)
原始發音 (sài ❌)
之前解法 (sāi 2拍 ❌)
最終解法 (sāi 1拍 ✔️)
使用「腮」的音 (1拍)
我已經更新了 phonemes.json 以反應這個 1 拍的解法:
1 {
2 “mapping”: {
3 “塞”: “sāi (1-beat using homophone 腮)”
4 }
5 }
現在語音發音節奏完美,文字配置與時長也已校正。
發音錯誤的修正
Asking user 我找到音檔都在 public/tts,並由 src/Podcast.tsx 直接引用。專案內沒…
│ 我找到音檔都在 public/tts,並由 src/Podcast.tsx 直接引用。專案內沒找到自動生成這些 mp3 │
│ 的腳本,所以它們很可能是外部用 edge-tts 生成後放進來的。要我現在為那句(final_4.mp3 的內容)用 edge-tts │
│ 重生音檔嗎?要如何處理?
Install edge-tts if needed and synthesize SSML to public/tts/final_4.mp3 (shell)
│ python3 -m pip install –quiet edge-tts && python3 – <<‘PY’
│ import asyncio, edge_tts, os
│ ssml = ”’ │ 學會這些代碼,能讓你避開無謂的爭吵,在各種關係中活得更加游刃有餘。 │ ”’
手動產生 mp3 檔, 再 overwrite 比較快
edge-tts –voice zh-TW-YunJheNeural –text “學會這些代碼,能讓你避開無謂的爭吵,在各種關係中活得更加游刃有餘” –write-media final_4.mp3
背景音樂很怪, 想使用自己的, 要放到那個 path?
✦ 您應該將自己的背景音樂檔案放到專案根目錄下的 public/ 資料夾中。
具體操作步驟:
- 放置檔案:將您的音樂檔(例如 my-bgm.mp3)放入 /home/max32/my-video-project/public/ 資料夾下。
- 修改程式碼:在 src/Podcast.tsx 中將原本的 URL 換成 staticFile(“您的檔案名稱”)。 我現在為您更新 src/Podcast.tsx,將背景音樂路徑改為指向 public/bgm.mp3(您可以隨時將檔案重新命名為 bgm.mp3 放入 public
資料夾即可生效):
- src=”https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3″
- src={staticFile(“bgm.mp3”)}
下面是能顯著加速 Remotion 匯出的實用方法(含範例指令)。
高優先(大幅加速)
- 將靜態背景預先渲染成圖檔,主合成改用 Img + staticFile → 瀏覽器不用重算複雜濾鏡或粒子動畫。
範例:渲染第 1 幀為背景圖
npx remotion render Podcast frame-bg.png –frames=1-1 –codec=png
在程式中用 - 移除或替換昂貴的 CSS(backdrop-filter、blur(100px)、大量 box-shadow)。改成預先做好的模糊圖或單一陰影圖片。
中等優先(穩定但需修改)
- 減少同時動畫元素數量(例:把 15 個模糊圓圈降到 4)。
- 把重複且不變的圖案合成到一張 sprite 或單一 Canvas,再在畫面上重複繪製(比大量 DOM 元素快)。
系統/CLI 優化(立刻生效)
- 增加並行執行數(根據 CPU):–concurrency
例:用 8 個並行
npx remotion render Podcast out.mov –codec=prores –concurrency=8 - 降低解析度或 FPS(在專案設定/Composition 中修改 width/height、fps),例如把 4K → 1080p 或 60fps → 30fps。
資源與快取
- 用 registerFont() 或 local static font(已做)避免網路載入延遲。
- 確認 webpack cache 有開(Remotion 一般會自動快取 bundle),少改動 bundle 時能復用。
檢查與測量
- 先匯出單一幀檢查效能/品質:
npx remotion render Podcast frame-200.png –frames=200-200 –codec=png - 用小改動逐步驗證哪個優化最有效。
要我幫你:
- 把 Background 抽成獨立 Composition 並建立預渲染腳本?(會修改程式)
- 或自動移除/替換專案中昂貴的 CSS(backdrop-filter/大 blur)?
在 Remotion 中切換背景圖,最標準且有效率的做法是使用 useCurrentFrame 配合簡單的邏輯判斷。
使用邏輯判斷切換
你可以根據當前的格數(frame)來決定顯示哪一張圖片。這就像是在寫一般的 React 邏輯。
TypeScript
import { useCurrentFrame, staticFile } from "remotion";
export const MyVideo = () => {
const frame = useCurrentFrame();
// 假設在第 150 格切換圖片
const bgImage = frame < 150 ? "bg1.jpg" : "bg2.jpg";
return (
<div style={{ flex: 1, backgroundColor: "white" }}>
<img
src={staticFile(bgImage)}
style={{ width: "100%", height: "100%", objectFit: "cover" }}
/>
</div>
);
};
使用 Series 組件
如果你有很多張圖,而且每一張圖出現的時間長度是固定的,使用 Series 會更直覺。它能幫你自動處理時間軸的偏移。
TypeScript
import { Series, staticFile } from "remotion";
export const MyPodcast = () => {
return (
<Series>
<Series.Sequence durationInFrames={300}>
<img src={staticFile("image1.jpg")} />
</Series.Sequence>
<Series.Sequence durationInFrames={450}>
<img src={staticFile("image2.jpg")} />
</Series.Sequence>
</Series>
);
};
陣列查表法
當圖片數量非常多(例如每隔段落換一張),建議建立一個陣列清單,用格數去計算索引值。這能讓程式碼保持整潔,不會有一長串的 if-else。
TypeScript
const scenes = [
{ start: 0, img: "start.jpg" },
{ start: 1000, img: "middle.jpg" },
{ start: 5000, img: "end.jpg" },
];
const currentScene = [...scenes].reverse().find(s => frame >= s.start);
注意圖片載入效能
因為你的影片很長,建議圖片都放在 public 資料夾並使用 staticFile 引用。這樣渲染引擎不需要透過網路抓取資源,能有效提升渲染速度並減少失敗率。
調整結果:
export const Podcast: React.FC = () => {
const { durationInFrames } = useVideoConfig();
const frame = useCurrentFrame();
const bgImage = "background.png";
const scenes = [
{ start: 0, img: bgImage },
{ start: 113, img: "no-gps-foreset.jpg" },
{ start: 444, img: "office-talking.jpg" },
{ start: 1497, img: "i-am-fine.jpg" },
{ start: 2205, img: "drive-car-and-watch-out.jpg" },
];
// 找到目前格數對應的圖片
const currentScene = [...scenes].reverse().find(s => frame >= s.start);
const displayImg = currentScene ? currentScene.img : bgImage;
return (
<AbsoluteFill>
<AbsoluteFill style={{ zIndex: -1 }}>
<Img src={staticFile(displayImg)} style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.6)' }} />
</AbsoluteFill>
當你在 Remotion 預覽畫面發現後面有大量空白,通常是因為 Composition 設定的持續時間 durationInFrames 大於你實際動畫內容所需的格數。
調整 Composition 參數
最直接的方法是在定義 Composition 的地方,將 durationInFrames 改成符合你影片長度的數值。例如,如果你的動畫在 150 格就結束了,但設定了 300 格,預覽時就會出現一半的空白。你可以手動計算內容所需的總格數並填入。
修改 Root.tsx 的 Composition 的 durationInFrames
<Composition
id="Podcast"
component={Podcast}
durationInFrames={3429}
fps={15}
width={1920}
height={1080}
/>
使用動態計算
如果你的影片內容長度會變動,建議不要寫死數字。你可以根據資料長度或元件屬性來計算。例如,如果你有一組圖片要播放,總長度可以設定為圖片數量乘以每張圖片顯示的格數。這樣 Composition 的長度就會自動適應內容,不會留下多餘的空白格。
檢查內容元件
有時候空白是因為內部的 Sequence 或元件放錯了位置。請檢查是否有元件的 from 屬性設定得太後面,或是某個 Sequence 的 duration 被拉得太長,導致整個 Composition 被迫撐大。在瀏覽器的時間軸上,你可以觀察各個元件的條狀圖,確認哪一個區塊超出了預期範圍。
使用 calculateMetadata
如果你需要更高級的控制,可以使用 calculateMetadata 這個 API。它允許你在 Composition 渲染前根據傳入的 props 動態回傳 durationInFrames。這對於處理長度不固定的影片非常有用,能確保預覽與輸出的結尾精準停在動畫結束的地方。
Token 使用量

第一次使用 remotion 做影片,花了比較長的時間熟悉工具,了角如何修改,心得是:透過 remotion 做影片滿簡單的,大多都可以使用自然語言讓 AI 調整,第一次吧 gemini CLI 裡的 flash-preivew model token 用光, 還好 google 可以再切換到 flash-lite model 繼續使用, 這時候也可以使用 logout 換另一個帳號繼續做影片.
最大的問題反而是 render 4K 畫質的影片居然要花費 11小時, 1080p 也要 1.5小時, 太久了, 調整 fps from 30 to 15, 再微調一下架構, 就只要 4分鐘就可以生成, 滿方便的, 而且日後也可以修改.
gemini cli 的 gemini-3-flash-preview 目前quota使用完的話, 要等23小時後會自動重置.

是可以透過 liteLLM 把模型切換到 local LLM, 但 local 的參數量小都很腦殘, 呆到爆, 叫他去列出檔案清單並列出內容秒數, 一直鬼打牆, 聽不懂人話, 與 gemma 4 e2b 的對話如下:
hello
✦ Hello! How can I help you today?
show each mp3 duration of ./public/tts/
✦ I apologize for the error. It seems I misidentified the available tools or the context in which I was operating.
Please rephrase your request or tell me what you would like me to do, and I will do my best to assist you using the
available functions.
use ffmpeg tools to query each mp3 files duration (seconds)
✦ I understand you want to get the duration of several MP3 files.
However, I am a language model running in a text-based environment. I do not have direct access to your local file
system or the ability to execute command-line tools like ffprobe or ffmpeg on your computer.
To achieve this, you will need to run the command yourself in your terminal or command prompt.
How you can do this:
You can use the powerful tool ffprobe (which comes with ffmpeg) for this task.
If you are using Linux, macOS, or a Unix-like environment:
Navigate to the directory containing your MP3 files and run:
1 ffprobe -v error -show_entries format=duration -of default=noprint_format=1=ffseek=0 input_file.mp3
Replace input_file.mp3 with the actual path to your file.
Example for multiple files (using a loop):
If you have multiple files, you can loop through them:
1 for file in *.mp3; do
2 echo “—“
3 echo “File: $file”
4 ffprobe -v error -show_entries format=duration -of default=noprint_format=1=ffseek=0 “$file”
5 done
If you are using Windows:
You will need to install ffmpeg (which includes ffprobe) and then run the command in PowerShell or Command Prompt.
If you can provide me with the output of these commands, I would be happy to analyze the results for you!
allow you to execute python scripts or batch script to list files under ./public/tts/ , after get file list, use
ffprobe to get each file duration (seconds).
✦ I apologize for the error. It seems I attempted to call a function that was not available or recognized in the current
context.
How can I assist you with your request? Please let me know what you would like me to do.
what error you get? current environment in wsl with zsh.
✦ Please provide the context or the command you are running so I can tell you what error you are encountering.
Are you running a specific command in your WSL/Linux environment, or are you asking about a general setup issue?
how many context length you used now, do i need to increase the content length in ollama?
✦ I do not have access to information about the specific context length settings of the model you are currently
interacting with, as that is determined by the API or interface you are using.
If you are referring to a specific context window size (like the token limit for input/output), you would need to
check the documentation for the model you are using (e.g., Llama, Mistral, etc.) or the specific application/API you
are interacting with.
使用 openrounter 也很慘, 都卡在錯誤訊息 429 RateLimitError 非常明確,代表你使用的 OpenRouter 免費模型 gemma-4-26b-a4b-it:free 目前在後端(Google AI Studio)已經達到了流量限制。由於這是免費共享配額,當熱門時段使用者過多時,就會出現這種「暫時性限流」。
遇到 gemini CLI quota 用完的最佳解法, 就是切換為其他牌子的免費 CLI, 例如: copilot CLI, 或是使用多個 google 帳號登出/登入來切換。