papercut-voice について
papercut-voice は、Papercut の動画制作ワークフローでナレーションを作るスキルです。ローカルの Qwen3-TTS、自分の録音や別の TTS、仮音声に対応し、単語単位のタイミング、字幕、シーンプランを生成します。
主な機能
- 013 種類のナレーション制作方法
- 02行ごとの WAV 生成と選定
- 03ASR による単語タイミング生成
- 04字幕とシーンプランの作成
- 05仮音声の後からの差し替え
活用シーン
- 01台本からナレーション音声を作成
- 02発話に合わせて字幕タイミングを調整
- 03仮音声で動画を組み立ててから再録音
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ 0xs4n4tg/ papercut-explainer --skill papercut-voiceインストール後、Claude Code で/papercut-voiceを入力して読み込まれたか確認します。
詳細
Papercut の音声
後続の各ステージでは、ナレーションフォルダーに次の2つが必要です。voice/final_narrator/<id>.wav(スクリプトの各行につき1つの WAV)とtiming_narrator.js(各単語の開始時刻と終了時刻)。このスキルでは、3つの方法のいずれかでこれらを作成し、その後、字幕とシーンプランを作成します。スクリプトの完全なリファレンスはlibrary/voice/README.mdです。
事前確認
- ライブラリ:
$PAPERCUT_LIB、それがなければ~/.config/papercut-explainer/config.env内のPAPERCUT_LIB=の行、それもなければ一度だけ確認します。環境変数に設定し、. "$PAPERCUT_LIB/env.sh"を実行すると、ASR_PY、QWEN_TTS_PYやその他の設定値がシェルに読み込まれます。 - 足場を作成済みのプロジェクト(
papercut-build、ステップ1)。narration/script.jsonと、scenesを含むpapercut/episode.jsonが必要です。new_episode.shはbuild_timing.pyをnarration/にコピーします。 - ユーザーがすでに TTS をインストールしている場合は、そのまま使用し、再構築しないでください(パス B、または Qwen3-TTS の場合は A)。何を使っていて、どこで実行するのかを一度だけ確認してください。そうでない場合は、どのパスにするか尋ね、必要なものを伝えてください。
| パス | 必要なもの | インストール方法 |
|---|---|---|
| A. ローカル Qwen3-TTS | library/voice/setup_tts.shの音声用 venv(モデルのダウンロードは 2.3~4.2 GB) | フル |
| B. 自分の録音または別の TTS | ASR バックエンド(mlx-whisper または faster-whisper)、およびピッチ処理用の librosa + numpy。ASR_PYとQWEN_TTS_PYが指定する Python 環境に必要です | フル、またはこれらのパッケージを手動でインストール |
| C. 仮音声 | ffmpeg。--sayには macOS が必要です | 標準 |
選択したパスに、インストール済みの環境以上のものが必要な場合は、./install.sh --doctorを実行してpapercut-setupに引き継いでください。ASR がない場合、build_timing.pyは1秒あたり2.75語として推定するだけなので、字幕のタイミングが実際の音声とずれていきます。
パス A:ローカル Qwen3-TTS
シリーズフォルダーから実行します。
cp $PAPERCUT_LIB/voice/voice.example.json voice/voice.json # once per series; edit it
$PAPERCUT_LIB/voice/narrate.sh voice/voice.json episodes/NN-slug/narration
$PAPERCUT_LIB/voice/finalize.sh voice/voice.json episodes/NN-slug/narration episodes/NN-slug/narration/voice/takes/narrator [num.json]
voice.json:speaker(CustomVoice:Aiden、Dylan、Eric、Ryan、Serena、Vivian、Sohee、Ono_anna、Uncle_fu)とinstructを指定します。推奨設定:CustomVoice 1.7B、Ryan または Aiden、指示文は 「friendly, natural, conversational, like telling a friend; clear pauses between sentences; stress the key word of each line」。takes_per_lineのデフォルトは3です。tempoは1.0のままにします(ほかの値にはrubberbandが必要です)。qa_targetsでは、ピッチ範囲と ASR の一致度(0.9)を設定します。- スクリプトの行には、
tts(発音用に書き換えたテキスト。字幕ではtextを維持)と、その行専用のinstructを指定できます。num.jsonでは数字を読み上げ用の語句に対応付けます({"13,000": "thirteen thousand"})。これにより、数字が原因で ASR の照合に失敗するのを防げます。 - TTS プロセスは一度に1つだけ実行してください。1.7B モデルは多くのメモリを消費します。
finalize.shは各テイクで ASR とピッチ処理を実行し、行ごとに1つ選択して、無音部分をトリミングし、音量を-16 LUFSに正規化します。その後、最終音声でもう一度 ASR を実行し、timing_narrator.jsを書き出します。音声を聞いた後、修正をvoice/picks_override.json({"l3_name": 2})に記入し、SKIP_PICK=1を指定して再実行してください。- Voice Design(言葉で音声を説明する機能)は Qwen3-TTS のモードですが、
narrate.pyでは制御できません。ほかの場所で生成し、パス B を使用してください。
パス B:自分の録音または別の TTS
- 1行につき1つの WAV を
voice/final_narrator/<id>.wavに配置するか、未処理のテイクをvoice/takes/narrator/<id>_<k>.wavに配置し、上記のとおりfinalize.shを実行します。 - 完成した音声については、書き起こしを作成し、
voice/chosen_narrator.jsonを{id: "voice/final_narrator/<id>.wav"}として記述してから、narration/で次を実行します。
$ASR_PY $PAPERCUT_LIB/voice/asr.py voice/final_narrator/*.wav > voice/asr_narrator_final.json
CHOSEN=voice/chosen_narrator.json TIMING_OUT=timing_narrator.js python3 build_timing.py voice/asr_narrator_final.json
パス C:仮音声
narration/フォルダーから実行します。
python3 $PAPERCUT_LIB/tools/placeholder_vo.py [--wps 3.0] [--say [VOICE]]
script.jsonだけを使って、無音の WAV(または--sayを指定した場合は macOS のsay音声)とtiming_narrator.jsを書き出します。単語の長さに応じて単語ごとの時間を割り当てます。--wps 3.5から4を指定すると、目標のペースに近づきます(デフォルトは3.0)。リールを先に作成してタイミングを調整し、後から音声を差し替えることもできます。WAV を差し替え、finalize.shを再実行し、build_vo.pyを再実行すると、単語のキューも追従します。
語り方のルール
語りは人間らしく、ゆったりとしていて、強調が感じられるものにします。視聴者が初めて聞いたときに、すべての行を追える必要があります。
- 間を取る。 文末ごと、およびリストの各項目の間(「そうです。そうです。そうです。」)に、しっかり間を置きます。まずは行間を0.3〜0.4秒に設定し(
episode.jsonのgap。デフォルトは0.22。gapsは指定した行の後に追加の間を入れます)、耳で確かめて調整します。間は文や節の切れ目にのみ入れます。「それをコピーした、| そうすることで……」のように、フレーズの途中に0.2秒の間を入れると、遅れているように聞こえます。間を削るのではなく、尺は上限の30秒を使い、テンポを落として調整することは避けてください。 - 強調。 各行で強く聞かせる1〜2語(数字、意外性のある言葉、結論)は、スクリプトの演出表に従います。行ごとの
instructとして渡します。 - トーン。 会話調で、少し楽しげにしますが、アナウンサーのような勢いは出しません。質問は本当の質問らしく、決めの一言は好奇心を誘い、CTAは温かみのあるものにします。ナレーター自身は冗談を言わず、映像が冗談を担います。
- クローンモデル(指示なし)。 クローン音声(
ref_audio+ref_text、Baseモデル)では、調整できるのはテキストとテイクです。句読点で間を表現します(リストの各項目の間には句点を置き、短い質問は独立した文にします)。連続したテイクを複数のシード(SEED_BASE、takes_per_line)で生成し、狙った語のピッチ変化が最も大きく、ASRが0.9以上のものを選びます。平板な行は単独で録り直し(ONLY=l4_peak)、文の切れ目でつなぎます。使用する権利のある音声のみをクローンしてください。 - スクリプト全体を一度に生成すると、韻律に一貫性が出ます。テイクを1つ生成し、
split_take.pyで行ごとに分割してから、episode.jsonで"gap": 0を設定します。ユーザーが希望する場合は、シーンごとの生成でもかまいません。 - 全体を実行する前に、最初の3行をユーザーに聞かせてください:
ONLY=l1_hook,l2_...,l3_... narrate.sh ...。
字幕とタイミング
papercut/ から、episode.json に scenes が設定された後に実行します:
python3 $PAPERCUT_LIB/tools/build_vo.py # public/vo/voiceover.wav, public/vo/captions.json, src/plan.json
python3 $PAPERCUT_LIB/tools/timing_md.py # Timing section of STORYBOARD.md
captions.json はRemotionの Caption[] です。シーンの境界は単語のタイムスタンプから決まり、コンポジションの長さは plan.json の durationInFrames です。build_vo.py は1.2秒未満のシーンがあると警告します。ライブラリがない場合は、Remotion captions skill(whisper.cpp)または単語のタイムスタンプを出力できる任意の音声認識ツールでWAVを書き起こします。
字幕は単語ごとに破れたタグを1つ配置し、1ページあたり1〜3語、毎秒3.5〜4語にします(../papercut-explainer/references/style-guide.md のセクション11および13)。
引き継ぎ
次は papercut-build(スタブ、シーン、音声、レンダリング)です。src/plan.json と papercut/STORYBOARD.md のTimingセクションを読み込みます。後から音声を変更した場合は、build_vo.py と timing_md.py を再実行してください。その後、シーンキャッシュによってすべてが再レンダリングされます。