claude/skills/remotion-qwen-tts/SKILL.md
Remotion + Qwen-TTS(MLX)で動画を生成する際の完全ガイド。音声生成、口パク同期、フレーム計算、そして必須の検証フェーズまでカバー。動画生成時に使用。
npx skillsauth add kazuph/dotfiles remotion-qwen-ttsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Apple Silicon Mac上でQwen3-TTSを使用してRemotionで動画を生成するための完全ワークフロー。
重要: このスキルの核心は検証フェーズ。生成物を自分で確認せずに「完了」と報告してはならない。
より詳細な情報は以下のルールファイルを参照:
┌─────────────────────────────────────────────────────────────────┐
│ 動画生成ワークフロー │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. スクリプト作成 │
│ └─ src/data/script.ts にセリフを記述 │
│ │
│ 2. 音声生成 ───────────────────────────────────────────────────│
│ └─ scripts/generate-voices-qwen.py │
│ ├─ Qwen3-TTSで音声生成 │
│ ├─ 音声波形から口パクデータ抽出 │
│ └─ durations.json, mouth-data.generated.ts 出力 │
│ │
│ 3. 【検証1】個別音声検証 ─────────────────────────────────────│
│ └─ scripts/verify-voices.py (Whisper) │
│ ├─ 各音声を文字起こし │
│ ├─ 元テキストと類似度比較 │
│ └─ 70%未満は警告 → 再生成検討 │
│ │
│ 4. 動画ビルド │
│ └─ npm run build │
│ │
│ 5. 【検証2】動画音声検証 ─────────────────────────────────────│
│ └─ scripts/analyze-video-audio.py │
│ ├─ 動画から音声抽出 │
│ ├─ 無音区間検出 │
│ ├─ 末尾無音チェック(3秒以上は問題) │
│ └─ セリフ途切れ検出 │
│ │
│ 6. 目視確認 │
│ └─ open out/video.mp4 で実際に視聴 │
│ │
│ 7. 完了報告(検証パスした場合のみ) │
│ │
└─────────────────────────────────────────────────────────────────┘
# Python仮想環境
python3 -m venv .venv
source .venv/bin/activate
# MLX関連
pip install git+https://github.com/Blaizzy/mlx-audio.git soundfile numpy
# 検証用(Whisper)
pip install mlx-whisper
# Node依存
npm install
.venv/bin/python scripts/generate-voices-qwen.py
# config.ts
export const VIDEO_CONFIG = {
fps: 30,
playbackRate: 1.2, # 音声を1.2倍速で再生
};
キャラクター設定は characters.yaml から動的に読み込まれます:
# characters.yaml
characters:
zundamon:
name: ずんだもん
voice_instruct: "元気で明るく可愛らしい若い女の子の声。語尾に特徴があり、ハキハキとした話し方"
metan:
name: 四国めたん
voice_instruct: "落ち着いた大人っぽい女性の声。上品で穏やかな話し方"
# 新しいキャラクターを追加するにはここに定義
characters.yaml がない場合はデフォルト設定(zundamon/metan)が使用されます。
絶対に避けるべき罠: durationInFramesを複数箇所でplaybackRateで調整してしまう
┌─────────────────────────────────────────────────────────────────┐
│ generate-voices-qwen.py │
│ frames = int(duration / PLAYBACK_RATE * FPS) │
│ → durationInFramesは「playbackRate考慮済み」の値 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ script.ts │
│ durationInFrames: 180 // すでに1.2倍速考慮済み │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ Main.tsx / Root.tsx │
│ そのまま使用! 二度割りしない! │
│ const getAdjustedFrames = (frames) => frames; // OK │
│ const getAdjustedFrames = (frames) => frames / playbackRate; // NG!
└─────────────────────────────────────────────────────────────────┘
| 症状 | 原因 | |------|------| | 末尾に長い無音(10秒以上) | Root.tsxでplaybackRate調整してない | | セリフが途中で切れる | Main.tsx/Root.tsxで二重にplaybackRate調整してる | | 音声と映像がずれる | durationInFramesの計算式が間違ってる |
音声長: 7.2秒
playbackRate: 1.2
FPS: 30
正しい計算:
動画上の再生時間 = 7.2 / 1.2 = 6秒
フレーム数 = 6 * 30 = 180フレーム
durationInFrames = 180
間違い1(調整忘れ):
frames = 7.2 * 30 = 216フレーム → 末尾に無音
間違い2(二重調整):
durationInFrames = 180(正しい)
Main.tsx: 180 / 1.2 = 150フレーム → セリフ切れ
動画フレームに正確に対応する口パクデータを抽出します。 playbackRateを考慮した時間範囲でRMSを計算することで、ズレを解消します。
def extract_mouth_data_for_video(
audio: np.ndarray,
sample_rate: int,
video_fps: int,
playback_rate: float,
threshold: float = 0.015 # 低めで口パク多め
) -> list[bool]:
"""
動画フレームに正確に対応する口パクデータを抽出
重要: 動画の各フレームに対応する音声範囲を計算し、そのRMSで口パクを判定
これにより、playbackRateによるズレを完全に解消する
"""
audio_duration = len(audio) / sample_rate # 音声の長さ(秒)
video_duration = audio_duration / playback_rate # 動画上の再生時間(秒)
video_frames = int(video_duration * video_fps) # 動画のフレーム数
abs_audio = np.abs(audio)
mouth_data = []
for frame in range(video_frames):
# 動画フレームに対応する音声の時間範囲
# 動画のフレームi → 音声時間 = i / video_fps * playback_rate
audio_start_time = frame / video_fps * playback_rate
audio_end_time = (frame + 1) / video_fps * playback_rate
# サンプルインデックスに変換
start_sample = int(audio_start_time * sample_rate)
end_sample = int(audio_end_time * sample_rate)
chunk = abs_audio[start_sample:min(end_sample, len(abs_audio))]
if len(chunk) > 0:
rms = np.sqrt(np.mean(chunk ** 2))
mouth_data.append(bool(rms > threshold)) # Python boolに変換!
else:
mouth_data.append(False)
return mouth_data
.venv/bin/python ~/.claude/skills/remotion-qwen-tts/scripts/verify-voices.py
Whisperは完璧ではない。 書き起こし精度には限界があり、以下を理解して使用すること:
| 検出できること | 検出できないこと | |---------------|----------------| | 音声が途中で切れている(無音区間が長い) | 細かい誤字脱字 | | 完全に別の内容が話されている | 固有名詞の表記揺れ(例:ずんだもん→すんだもん) | | 大幅な発音ミス | 句読点の有無 | | | カタカナ英語の表記差(例:ギットハブ→キッドハブ) |
検証の目的: 音声が最後まで正しく生成されているか(途中で切れていないか)を確認すること。 類似度スコアが低くても、音声を実際に聴いて問題なければOK。
| 類似度 | 判定 | アクション | |--------|------|-----------| | 70%+ | 許容 | 実際に聴いて確認、問題なければOK | | 50-70% | 要確認 | 音声を聴いて途切れがないか確認 | | 50%未満 | 要注意 | 音声が途中で切れている可能性大。再生して確認 |
注意: 類似度が低い=壊れている、ではない。Whisperの誤認識の可能性もある。 最終判断は実際に音声を聴くことで行う。
より高精度な音声認識モデルを使用すれば、TTSの発音問題も検出可能。 検出した場合の修正手法:
漢字を開く: 難読漢字や固有名詞をひらがなに変換
生成 → せいせい(「先生」と誤認識される場合)アルファベットをカタカナ化: 英語表記を発音に近いカタカナに
GitHub → ギットハブClaude Code → クロードコードこれを自動化するには、Whisper検証で発音ミスを検出 → script.tsのtextフィールドを自動修正 → 再生成、というループが必要。
.venv/bin/python ~/.claude/skills/remotion-qwen-tts/scripts/analyze-video-audio.py out/video.mp4
| 末尾無音 | 判定 | アクション | |----------|------|-----------| | 2秒未満 | OK | 問題なし | | 2-3秒 | 許容 | 許容範囲 | | 3秒以上 | 問題 | Root.tsx/script.ts確認 | | 10秒以上 | 重大 | playbackRate二重調整疑い |
open out/video.mp4
原因: max_tokensが短すぎる
# NG: 固定値や小さめの計算
max_tokens = 300
# or
estimated_duration = max(len(text) * 0.4, 3.0)
max_tokens = int(estimated_duration * 12.5) + 100
# OK: 大きく余裕を持たせた計算(推奨)
estimated_duration = max(len(text) * 1.0, 5.0) # 最低5秒、1文字1.0秒
max_tokens = int(estimated_duration * 15) + 300 # 大きめの余裕
ポイント: 日本語は話し方によって長さが大きく変動する(特にゆっくり話すキャラ)。 短すぎると途切れるので、余裕を大きく持たせる。
原因: playbackRate調整の不整合
frames = duration / PLAYBACK_RATE * FPSdurationInFrames + pauseAfterを使用原因: 二重playbackRate調整
Main.tsxのgetAdjustedFramesを確認:
// NG: 二重調整
const getAdjustedFrames = (frames) => Math.ceil(frames / playbackRate);
// OK: そのまま使用
const getAdjustedFrames = (frames) => frames;
# NG
mouth_data.append(rms > threshold)
# OK
mouth_data.append(bool(rms > threshold))
| スクリプト | 用途 | |-----------|------| | scripts/generate-voices-qwen.py | 音声生成テンプレート | | scripts/verify-voices.py | Whisper検証 | | scripts/analyze-video-audio.py | 動画音声解析 |
動画生成完了前に必ず確認:
verify-voices.py で平均類似度70%以上analyze-video-audio.py で末尾無音3秒未満open out/video.mp4 で目視確認すべてパスするまで「完了」と報告してはならない。
tools
X (Twitter) API read-only CLI. Bookmarks retrieval, tweet search, engagement analytics (likes/RT aggregation), mentions, user lookup. Use when: reading X bookmarks, searching tweets, aggregating likes/retweets, checking mentions, looking up users. Triggers: bookmark, bookmarks, X search, Twitter search, likes count, RT count, engagement, tweet analytics.
testing
単体テスト方針の要約。Kiro流で使うときは本文を必ず参照・展開する。
tools
Send prompts to other AI CLIs (Codex, Claude Code) running in sibling tmux panes and receive results back. Use this skill when the user asks to send a question or task to Codex or another Claude Code instance in a tmux pane. Handles pane discovery, CLI startup if needed, prompt delivery with proper Enter timing, delivery verification, and result return via tmux send-keys.
data-ai
TAKT ピースエンジン。Agent Team を使ったマルチエージェントオーケストレーション。ピースYAMLワークフローに従ってマルチエージェントを実行する。