Podcast / 錄製 → 精華影片
確定性工作(音頻、API 呼叫、時間計算、渲染)由腳本完成。 你需要執行兩個判斷步驟:校正逐字稿 (1b) 和選擇精華片段 (2)。有兩個人類檢查點。絕對不能跳過。
下面的 SKILL_DIR 指的是包含此檔案的目錄。使用 uv 執行腳本,它會從任何工作目錄使用此技能的獨立鎖定環境:
uv run --project "$SKILL_DIR" "$SKILL_DIR/scripts/<name>.py" <episode_dir>。
在以下步驟中,<name>.py <ep> 是該指令的縮寫。每個腳本都會印出它製作了什麼或跳過了什麼。重新執行永遠是安全的:已完成的步驟會被跳過,且當輸入變更時衍生的檔案會重新建置。--force 會重做某個步驟。
集節目錄結構(每集一個目錄)
episodes/<slug>/
input.mp4 由使用者提供(音頻 podcast 或影片錄製)
shownote.txt 由使用者提供(建議:姓名、產品、主題)
episode.json 你為影片錄製撰寫:{"layout": "stacked"}
audio.mp3, transcript.raw.json 步驟 1a (transcribe.py)
corrections.md 步驟 1b (你撰寫)
transcript.json 步驟 1b (apply_corrections.py)
clip.draft.json 步驟 2 (你撰寫)
clip.json 步驟 2 (build_clip.py)
layout.preview.png 步驟 2 (preview_layout.py, 影片錄製)
digest.ass, digest.mp4 步驟 3 (render.py)
兩種集節目,同一條管線
| 音頻 Podcast | 影片錄製 (StreamYard 雙人畫面) | |
|---|---|---|
episode.json | 無 (或 {"layout": "background"}) | {"layout": "stacked"} |
| 圖片 | assets/background.png | 左側講者方塊在上,右側方塊在下,字幕在中間帶狀區域 |
| 聲音 | audio.mp3 | 從 input.mp4 中裁切,與畫面同步 |
其他所有部分(轉錄、校正、精華選擇、字幕、音量)都相同。50 分鐘、約 1.5 GB 的錄製也沒問題:只會上傳其音頻,且渲染會直接跳到每個精華片段。
步驟 0:設定
- 執行
check_env.py(不需集節目參數)。它會檢查 ffmpeg/libass、背景圖片、字型,以及OPENAI_API_KEY是否已在 shell 中設定或存在於$SKILL_DIR/.env。如果它退出碼為 1,請停止並引導使用者參閱 README → 設定。- 絕對不要讀取、列印、
cat或grep.env檔案,也絕對不要要求使用者將金鑰貼到對話中。使用者自行將金鑰放入.env:cp .env.example .env,然後編輯該檔案。
- 絕對不要讀取、列印、
- 建立
episodes/<slug>/(簡短的 ASCII slug,例如ep042)。將錄製檔案複製到其中,命名為input.mp4,並將節目筆記複製為shownote.txt。如果沒有節目筆記,請向使用者詢問來賓和關鍵名稱/術語,並自行將其寫入shownote.txt。 不要移動或複製你未被給予路徑的錄製檔案。 - 如果是兩人並排的影片錄製,請撰寫
episode.json:{"layout": "stacked"}。 - 執行
run_all.py episodes/<slug>。它會執行步驟 1a 並在步驟 1b 處以退出碼 10 停止。這個暫停是預期的。退出碼 1 表示錯誤:請閱讀錯誤訊息。
步驟 1a:轉錄(腳本)
run_all.py 已執行 transcribe.py。它會提取單聲道 16 kHz 音頻,並使用字詞時間戳、language="ja" 以及從節目筆記建構的詞彙提示來呼叫 whisper-1。長檔案會自動分割成重疊的區塊。whisper-1 有時會跳過一段語音:已辨識字詞之間長時間的停頓但仍有聲音的部分會單獨重新發送,並插入復原的片段(gap ... recovered 行)。若要查看提示詞,請執行 transcribe.py <ep> --print-prompt。若要覆蓋提示詞,請將文字放入 <ep>/prompt.txt 並使用 --force 重新執行。重新執行會花費 API 費用,因此請先詢問使用者。
步驟 1b:校正逐字稿(你)
目標:使用 shownote.txt 作為名稱、產品和術語的正確來源,修正每個片段 text 中辨識錯誤的字詞。
-
完整讀取
shownote.txt。列出其中提到的名稱(主持人、來賓)、產品、公司和技術術語。 -
讀取逐字稿:
show_transcript.py <ep> --raw。對於長集節目,請分段讀取(--from-id/--to-id),並 grep 搜尋每個術語可能聽錯的詞(--grep クロード --grep claude)。Whisper 通常會用片假名寫英文名稱、拼錯(Wisper)、漏掉字母(NTHROPIC),或選錯同音漢字(制度→精度)。 -
撰寫
corrections.md:一個 Markdown 表格。每一行取代所列片段中的一個確切子字串:| id | before | after | reason | |----|--------|-------|--------| | 6 | クロットコード | Claude Code | 節目筆記中的產品名稱 | | 7, 8, 12 | エーデント | エージェント | エージェント的辨識錯誤 |id:片段 ID,以逗號分隔,允許範圍(10-12)。before必須存在於該片段的當前文字中。行按順序套用。- 僅修正辨識錯誤。不要改寫、摘要、移除填充詞或「改進」日文。根據上下文修正明顯的同音詞,並在
reason中註明「(context)」如果節目筆記無法確定。 - 如果某個片段是 whisper 的幻覺(例如在靜音中聽到「ご視聴ありがとうございました」),請將其設為空:
| 41 | ご視聴ありがとうございました | | hallucination |。 - 絕對不要合併、分割、重新排序或重新調整片段時間。只要你透過
corrections.md進行,腳本就能防止這種情況。不要手動編輯transcript.json。 - 如果沒有任何需要修正,僅撰寫標題行即可。
-
執行
apply_corrections.py <ep>。它會寫出transcript.json並執行validate_transcript.py。如果找不到before,請修正該行並重新執行。 -
檢查點 1。向使用者顯示校正表格(或摘要,加上標記為「(context)」的行),並請他們審查
corrections.md。透過編輯corrections.md並重新執行apply_corrections.py來套用他們的變更。只有在他們批准後才能繼續。
步驟 2:選擇精華片段(你)
此步驟需要使用者的指示:一個主題(例如「料金の話をしているところ」)或一個請求(例如「大事なポイントをまとめて」)。如果他們尚未提供,請詢問他們。不要猜測主題。
一個短片包含一個或多個片段區間(集節目的連續部分),按時間順序播放,並以硬切銜接。總長:最多 180 秒。
-
搜尋:使用指示的關鍵字加上同義詞和可能的拼寫,執行
show_transcript.py <ep> --grep <keyword> ... -C 5。然後,充分閱讀每個命中處前後的內容(--from-id/--to-id),以查看每個討論實際開始和結束的位置。對於「重要重點」,請先分段讀取整個逐字稿,並在選擇前列出候選時刻及其 ID。 -
選擇片段區間:
- 一個片段區間:當指示命名一個主題且其討論適合 180 秒內時。
- 多個片段區間(通常 2–4 個):當指示要求精華片段,或一個主題分散各處且中間的談話離題時。每個片段區間必須獨自合理,並且緊接在前一個之後也有意義:硬切不提供上下文。
- 對於每個片段區間:開始於要點被引入的地方,而不是在想法中間或回溯性參考(例如「さっきの」「前回」「それで」)。使用
start_at修剪前導的填充詞或轉折詞(例如「では本題です。」「で、」)。結束於一個完整的句子;使用end_after在片段內停止。 - 偏好較少但完整的片段區間,而非許多碎片;少於約 8 秒的片段區間很少能獨立存在。
- 如果素材超過 180 秒,保留最精彩的部分,並在
reason中說明。 - 最多新增 2 個
alternatives(其他不錯的片段區間),並附上理由。 - 如果沒有任何匹配,停止並告訴使用者你搜尋了什麼以及集節目實際涵蓋的內容。不要撰寫草稿。
-
撰寫
clip.draft.json。你選擇片段 ID。腳本負責時間計算:{ "instruction": "<使用者的指示,逐字>", "title": "スキル機能と文字起こしの要点", "reason": "為何選擇這些部分,作為一個整體。", "spans": [ { "start_id": 6, "end_id": 8, "start_at": "今日は、Claude Codeのスキル機能について", "title": "スキル機能とは", "reason": "介紹主題。" }, { "start_id": 12, "end_id": 12, "title": "分担が大事", "reason": "核心重點。" } ], "alternatives": [ { "start_id": 120, "end_id": 131, "title": "...", "reason": "..." } ] }start_at和end_after是片段區間第一個和最後一個片段文字的可選確切子字串。替代方案也可以使用它們。每個片段區間在多個時都需要一個reason。(單一片段區間的草稿也可以將start_id/end_id/... 放在頂層。) -
執行
build_clip.py <ep>。它會將每個邊界對齊到字詞時間戳,增加約 0.3 秒的內邊距(絕不會超過相鄰字詞),排序片段區間,檢查它們是否重疊且總長 ≤ 180 秒,寫出clip.json,並驗證它。它會列出每個片段區間及其時間範圍和長度。閱讀它印出的任何warn行。相鄰的片段區間(接觸的片段 ID)視為重疊:將它們合併為一個片段區間。 -
僅限影片錄製:執行
preview_layout.py <ep>並查看layout.preview.png(以及layout.source.png,其中方塊輪廓分別為紅色 = 上方,藍色 = 下方)。每個面板必須顯示一個人,居中,沒有畫面邊緣或相鄰方塊。如果沒有,請在episode.json中設定"tiles"(原始畫面的分數比例)並重新執行:{"layout": "stacked", "tiles": [{"x": 0.0, "y": 0.14, "w": 0.497, "h": 0.70}, {"x": 0.504, "y": 0.14, "w": 0.496, "h": 0.70}]}第一個方塊放在上方。交換兩個項目以交換講者。來自相同錄音室設定的錄製共用相同的方塊,因此可以重複使用有效的
episode.json。 -
檢查點 2。向使用者顯示計畫:整體標題和總長度,然後依序顯示每個片段區間及其標題、
mm:ss–mm:ss、持續時間、理由,以及它的第一個和最後一個句子(show_transcript.py <ep> --from-id A --to-id B),然後是替代方案。對於影片,也顯示layout.preview.png。請他們批准、交換、新增或刪除片段區間。build_clip.py <ep> --promote N:單一片段區間時,將替代方案 N 交換進去;多個片段區間時,將替代方案 N 新增為另一個片段區間(注意 180 秒總長)。- 任何其他變更:編輯
clip.draft.json並重新執行build_clip.py。 - 只有在他們批准後才能繼續。
步驟 3:渲染(腳本)
執行 run_all.py <ep> --render(或 render.py <ep>)。輸出:digest.mp4,1080x1920,30 fps,H.264 + AAC,音量標準化至 -14 LUFS,並從 digest.ass 內嵌字幕。片段區間以硬切銜接。每個剪輯處會有 50 毫秒的音頻淡出以避免爆音。
然後檢查結果:
ffprobe -v error -show_entries format=duration -of csv=p=0 <ep>/digest.mp4必須與build_clip.py印出的總長度相符。- 從每個片段區間提取一個影格並查看,以確保字幕有渲染、不是 tofu 方塊,且(影片中)面板取景正確:
ffmpeg -y -v error -ss 5 -i <ep>/digest.mp4 -frames:v 1 /tmp/frame.png。 - 如果渲染警告找不到字型
Noto Sans JP,請告知使用者(README → 設定)。
回報 digest.mp4 的路徑、標題,以及每個片段區間在原始錄製中的時間範圍。
故障排除
| 症狀 | 修復方式 |
|---|---|
OPENAI_API_KEY is not set | 使用者將其添加到 $SKILL_DIR/.env(模板 .env.example)或匯出它。絕對不要自己處理金鑰 |
before ... not found | before 必須是該片段當前文字中的確切子字串(前面的行可能已更改它) |
segments changed but not listed | 每個更改的片段 ID 必須出現在 corrections.md 的一行中 |
spans total ... > 180s / exceeds 180s | 刪除一個片段區間,選擇較短的片段區間,或使用 start_at / end_after 修剪 |
| 在文字間隙復原功能存在之前製作的逐字稿中缺少語音(人們說話的時間點有長時間跳躍) | transcribe.py <ep> --fill-gaps 僅重新發送間隙。它會印出 ID 如何偏移:重新編號 corrections.md / clip.draft.json 以匹配,然後執行 apply_corrections.py <ep>。將任何復原的「ご視聴ありがとうございました」視為幻覺並清空 |
| 從同一集製作第二個短片 | 重新命名已完成的 digest.mp4、digest.ass、clip.json、clip.draft.json(例如 digest-keypoints.mp4),然後撰寫一個新的 clip.draft.json |
spans overlap | 兩個片段區間共享或接觸片段:將它們合併為一個片段區間 |
opens with 'さっき' (warn) | 稍後開始或使用 start_at 修剪,使片段區間能夠獨立存在 |
| 面板顯示錯誤區域 / 邊框 | 修復 episode.json 中的 tiles,使用 preview_layout.py 檢查,然後 render.py <ep> |
layout 'stacked' but input.mp4 has no video stream | 這是音頻 podcast:移除 episode.json |
| 字幕使用備用或細字型 | assets/fonts/NotoSansJP-Bold.ttf 必須存在(靜態 Bold 檔案,非可變字型);請參閱 README → 設定 |
更改了 background.png / 字型設定 | render.py <ep> 會偵測到並重新渲染;可使用 --force 確保 |