Communitygithub.com

tennis-rally-cut / テニスラリー分割

長いテニス練習動画(壁打ち、球出し機、対ラリー可)を打球音で自動的に個々のラリーに切り分け、球拾いや歩き、停止部分を除去します。

テニスラリー分割 とは?

このスキルは、テニス練習動画の長時間の映像から、打球音に基づいて自動的に打球と打球の間の無駄な部分(球拾い、ウォーキング、停止)を取り除き、各ラリーを個別のクリップとして切り出す処理を行います。 主な特徴: - 音声分析のみでラリーを検出するため、映像の動きの大きさに影響されず、安定して動作します。 - パラメータ調整(`--k`, `--gap`, `--min-hits`)により、環境音に合わせた感度調整が可能です。 - 対応プラットフォーム: コマンドラインツール(Python + ffmpeg/ffprobe)として独立して動作し、Claude Code, Cursor, その他のAIエージェントからの呼び出しに最適です。 - 対応スポーツ: テニスを中心に、バドミントン、卓球、スカッシュなど、打球音がはっきりしているラケットスポーツの練習動画に応用可能です。 ワークフロー: 1. **probe**: 解像度やビットレートを確認し、SNSアプリで圧縮されていないかチェック。 2. **analyze**: 音声解析で打球音を検出し、ラリーの区切りを決定してJSONに保存。 3. **sheets**: ラリーの開始・終了フレームを画像で確認し、手動で修正可能。 4. **cut**: 修正済みのセグメント情報に基づき、最終的なクリップを出力。 このスキルは、ユーザーが「練習動画をラリーごとに分割して」「球拾いをカットして」「打球部分だけを抜き出して」といった指示を出した場合に自動的に起動するよう設計されています。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/layyyback/tennis-rally-cut/tree/HEAD/tennis-rally-cut

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

テニスラリー分割(tennis-rally-cut)

2026年11月8日の実際の編集セッションから得られた知見を基にしています。2本の壁打ち練習動画から14のラリーを切り出しました。 中心となる考え方:音でラリーを見つけ、映像で確認する。

ユーザーの要件(厳守ルール)

  • 1ラリー = 最初の打撃からボールが止まるまで。ラリー全体を1つのクリップに収める。
  • ラリーを分割してはいけない。 壁打ちで10打連続したら、それが1クリップであり、2打、3打ずつに分割してはいけない。
  • 球拾い、歩行、停止、スマホ操作、機器の片付けは全てカットする。
  • 元の動画は変更しない。成果物は元動画と同じディレクトリの 編集/ フォルダに保存する。

音声を使う理由(映像ではない理由)

  • 各打撃には「ラケットでボールを打つ音」と「ボールが壁/地面に当たる音」の2つの明確な音がある。球拾いや歩行中にはこれらの音は発生しない。
  • 映像の動きの大きさは頼りにならない:球拾いや歩行時の映像の変化は打球時よりも激しいため、区別がつかない。
  • 映像は確認のためにのみ使用する。

ツール

スクリプト: scripts/rally_cut.py。依存関係は ffmpeg、ffprobe、numpy のみ。

サブコマンド機能
probe解像度、ビットレート、向きを確認。SNSアプリで圧縮されていないか警告。
analyze打球音を検出し、ラリーにまとめ、<名前>.segments.json を生成。
sheets各ラリーの開始と終了フレームの画像を生成し、目視確認を支援。
cutラリーごとに独立したmp4ファイルに切り出し、片断清单.txt を生成。
align高画質の原版に差し替えた際、新旧の時間オフセットを計算。

以降、RC はスクリプトの絶対パスを指します。中間ファイルはセッションの一時ディレクトリに配置し、ユーザーの動画ディレクトリには置かないでください。

ワークフロー

1. まず画質を確認

python3 -I $RC probe <動画...>
  • 「SNSで圧縮された可能性があります」と表示された場合、その旨をユーザーに通知する。編集処理自体は画質をほとんど劣化させないが、元の動画がぼやけていれば出力もぼやける。
  • WeChat から送られてきた動画の場合、オリジナルはWeChatのキャッシュにあり、ファイル名に _raw が付加されることが多い: ~/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/<アカウント>/msg/video/<年-月>/<名前>_raw.mp4 ユーザーがWeChatで「元の動画を見る」をタップした後にダウンロードされる。mdfind で追加日時から検索可能: mdfind -onlyin ~ 'kMDItemContentTypeTree == "public.movie" && kMDItemDateAdded >= $time.iso(<日付>)'
  • 高画質の原版を見つけたら、align でオフセットが0であることを確認し、既に計算済みのカットポイントをそのまま流用できる。

2. ラリーを見つける

python3 -I $RC analyze <動画...> --work <一時ディレクトリ>

1秒あたりの打球音の数の時間経過バーとラリーのリストを出力する。デフォルトパラメータ:

パラメータデフォルト値説明
--k2.5背景音より何倍大きければ打球音とみなすか。環境が騒がしい場合は大きくする。
--gap62つの打球音の間隔が何秒以上ならラリー終了とみなすか。「分割禁止」の鍵。
--min-hits6これより少ない打撃数の連続はノイズとして破棄(足音、スマホの取り出しなど)。
--pre / --post1 / 1.5ラリーの前後に追加で残す秒数。スイングや着地点が欠けないようにする。

--gap のトレードオフ:

  • 4秒:ボールが止まった後、数秒以内にすぐに次の打球を始めた場合、それを2つのラリーに分割してしまう。
  • 6秒:それらを1つのラリーにまとめる。ユーザーは分割禁止を要求しているので、デフォルトは6秒。
  • ユーザーが「新しいラリーは毎回別のクリップにして」と明示した場合のみ、4秒に変更する。

3. 映像で確認(必須ステップ)

python3 -I $RC sheets <動画> --segments <json> --work <一時ディレクトリ>/sheets

画像を1枚ずつ確認し、特に以下をチェック:

  • 動画の最後のラリー: 終了時に人がカメラに向かって歩いたりスマホを手に取る動作があり、その音が打球音として誤検出されることがある。見つけたら、end を最後の打撃から約1.5秒後に修正。
  • 打球音が一桁しかない短いラリー: ノイズの可能性がある。映像にスイングしている人がいなければ削除。
  • ラリー間の約5秒の空白: ボールが止まって再開したことの確認であり、見逃し検出ではないことの確認。

segments.json 内の segments 配列を直接編集:start/end を修正するか、要素全体を削除する。

4. カット

python3 -I $RC cut <動画> --segments <json> --out <動画ディレクトリ>/編集 --label A
  • 複数の元動画がある場合は、異なる --label(A、B…)を使用。ファイル名は 動画A_03_2m54s-3m22s.mp4 のようになり、時間は原動画内での開始・終了時間。
  • 必ず再エンコードする(デフォルト H.264、CRF 17)。原動画のキーフレーム間隔は2〜3秒であることが多く、直接コピーするとカット位置がずれる。
  • 縦向きに撮影された動画の向き情報はffmpegが自動処理するので、出力は正しい向きになる。
  • 出力先ディレクトリに既存の成果物がある場合、直接上書きや削除はせず、編集_旧版 にリネームしてバックアップする。
  • 1080p 60fpsの動画はエンコードに時間がかかるため、バックグラウンドで実行する。

5. 確認と報告

  • ffprobe で各成果物の長さ、解像度、フレームレートを確認。
  • 1〜2つの成果物からフレームを切り出し、向きが正しく、映像が鮮明であることを確認。
  • 報告時には:クリップ数、各クリップの長さの範囲、成果物ディレクトリ(Finderで開ける file:// リンクを添付。日本語パスはパーセントエンコードすること)。
  • 確信が持てないクリップ(例:打球音が非常に少ない短いラリー)については、ユーザー自身に削除を判断してもらうよう明記する。
  • 映像は抜き打ちで確認したものであり、すべてのクリップを最初から最後まで見たわけではないことを明記する。

経験済みの落とし穴

落とし穴結果対策
ループ内でffmpegを呼び出す際に -nostdin を付けなかったffmpegがリストの後続行を読み込み、カット位置がずれたりクラッシュしたスクリプトに追加済み
時間を「フレーム数×10ms」で換算した1フレームが実際には220サンプルであり、7分で1秒ずれるスクリプトではサンプルポイントで換算
映像の動きの大きさで打球を判断した球拾いの方が打球よりも動きが激しかった音声のみで判断
圧縮された転送動画を使用した出力がぼやけ、ユーザーが編集ミスだと思ったステップ1でプローブする

関連スキル