01素材
試合動画をPCに置く
スマホやカメラから取り込んだ元動画を、そのまま手元に保存します。
match.mp4動画そのものは各ユーザーのPCに置いたまま。ローカルAIで得点タイミングJSONを作り、ブラウザで微修正し、その場でスコアボード入り動画を書き出します。
スマホやカメラから取り込んだ元動画を、そのまま手元に保存します。
match.mp4動画のラリー終了や選手の反応を読み、得点タイミングの候補をJSON化します。
timeline.jsonページ上で動画ファイルを選び、JSONはファイル選択またはペーストで読み込みます。
アップロードなしタイムライン上の点をドラッグ。クリック&ドラッグで動画をスクラブできます。
0.1秒調整位置・サイズ・表示範囲を調整。プレビューはその場で反映されます。
Scale / X / Y動画フレームとSVGスコアボードをCanvasで合成し、WebMとして保存します。
scored.webm動画ファイルをローカルAIに読ませられる環境で、右のプロンプトをそのまま渡します。 出てきたJSONをこのWebツールに読み込ませると、タイムライン上に得点候補が並びます。
あなたは動画ファイルを扱えるAIアシスタントです。
以下の手順を参考に作業してください。
目的:
テニス動画を解析し、timeline JSON を生成する。
最終的に必要なのはJSONだけ。返答にはJSONだけを出力する。
ユーザーが書き換える入力:
- video: "match.mp4"
- tournament: "FLEEK CUP"
- round: "MATCH"
- team1: "Team1"
- team2: "Team2"
- format: "1set"
- scoringRule: "no-ad"
- matchStartAt: "0:00.0"
- firstServer: 1
Team定義:
- winner: 1 は team1 の得点。
- winner: 2 は team2 の得点。
- team1 はファーストサービスのチーム。スコアカード上段に表示する。
- team2 はもう一方のチーム。スコアカード下段に表示する。
- Upper/Lower はチーム名ではなく、動画画面内のコート位置を表す。upper は画面上側、lower は画面下側。
- 最初の得点ピンには、team1 が最初に Upper/Lower どちら側からサーブしたかを serverCourtSide: "upper" または "lower" で入れる。
- serverCourtSide は team1/team2 の名前を入れ替えるためには使わない。あくまでサーブ位置の記録として使う。
- 練習風景が動画先頭にある場合、試合開始または最初のサーブ時刻を matchStartAt に入れる。
- firstServer は通常 1。
- scoringRule は "no-ad" または "advantage"。指定がなければ "no-ad"。
- 動画内のスコア表示は参照しない前提。
- ユーザー指定のチーム配置、サーブ順、選手の位置、ラリー終了後の反応を手がかりに winner を推定する。
- 得点後または次ポイント開始時のサーバーが分かる場合は serverAfter: 1 または 2 を入れる。
- サーブ動作またはインパクトの時刻が分かる場合は serveAt を入れる。ジェットカットは serveAt の2秒前から始まる。
- 不明な点は削除せず、confidenceを下げて残す。
出力:
- ファイル保存は不要。
- LLMの最終返答としてJSON全文だけを出力する。
- Markdownコードフェンス、説明文、補足コメントは付けない。
- JSONとしてそのままパースできる形にする。
JSONスキーマ:
{
"source": "local-ai-subagents",
"video": "match.mp4",
"match": {
"tournament": "FLEEK CUP",
"round": "MATCH",
"team1": "Team1",
"team2": "Team2",
"format": "1set",
"scoringRule": "no-ad",
"matchStartAt": "0:00.0",
"firstServer": 1
},
"points": [
{ "t": "0:12.3", "serveAt": "0:05.1", "serverCourtSide": "upper", "winner": 1, "confidence": 0.82, "serverAfter": 1, "note": "任意の短い根拠" }
],
"visibilityRanges": [
{ "start": 0, "end": 120 }
]
}
厳守:
- points は時刻昇順。
- t は "分:秒" または "分:秒.小数"。
- serveAt は任意。サーブ動作またはインパクトの時刻を "分:秒" または "分:秒.小数" で入れる。
- serverCourtSide は任意。ただし最初の得点ピンには必ず入れる。値は "upper" または "lower"。
- winner は数値の 1 または 2。
- confidence は 0〜1。
- serverAfter は任意。得点後または次サーブのサーバーが見える場合だけ 1 または 2 を入れる。
- note は任意。分からない場合は省略してよい。
- 末尾カンマは禁止。
- JSON以外の説明をJSONファイル内に入れない。
推奨ワークフロー:
1. ffprobe で動画の長さ、fps、解像度を確認する。
2. 必要なら作業用ディレクトリ .fleek-detect を作る。
3. ffmpeg で確認用フレームを抽出する。
- まず 5秒間隔で全体フレームを抽出する。
- 例:
ffmpeg -i match.mp4 -vf "fps=1/5,scale=960:-1" .fleek-detect/frame-%04d.jpg
4. 複数エージェント、サブエージェント、並列タスク、別チャットなどを使える場合は、動画を時間帯ごとに分割して並列に読む。
- 使えない場合は、同じ作業を1区間ずつ順番に行う。
- 1担当あたり 1〜3分程度を読む。
- 各担当には、その区間のフレーム一覧を見せる。
- 各担当の出力は「得点イベント候補列」にする。
区間担当AIへの依頼テンプレート:
あなたはテニス動画の一部区間だけを読む担当AIです。
担当区間: START_SEC〜END_SEC秒。
渡されたフレームを時刻順に見て、得点が決まった瞬間の候補を抽出してください。
返答はJSON配列のみ:
[
{
"t": 35.0,
"serveAt": 27.4,
"serverCourtSide": "upper",
"winner": 1,
"serverAfter": 1,
"confidence": 0.8,
"evidence": "ラリー終了後にteam1側がポイント取得した反応がある"
}
]
注意:
- 動画内のスコア表示には頼らない。
- 得点が確定した瞬間、またはその直後の選手の反応を手がかりにする。
- サーブ開始またはインパクトが見える場合は serveAt として記録する。
- サーブした側が画面上側なら serverCourtSide:"upper"、画面下側なら serverCourtSide:"lower" を入れる。特に最初の得点候補では必須。
- 得点後、次にサーブする側が分かる場合は serverAfter に入れる。サーバーが変わった場合はゲーム取得の可能性が高い。
- winner が不明な候補も削除せず、推定してconfidenceを下げる。
- 推測ならconfidenceを下げる。
- 返答に説明文を混ぜない。
統合作業:
1. 全担当AI、または自分で順番に読んだ全区間の得点イベント候補を時刻順に並べる。
2. 同じ得点を複数候補として拾っている場合は、最も確からしい1件に畳む。
3. 得点タイミングは、ラリーが終わり得点が確定した瞬間、またはその直後を候補にする。
4. scoringRule と firstServer を使い、サーブ交代、次のサーブ位置、選手の反応と矛盾しないか確認する。
- no-ad では 40-40 の次点でゲームが終わり、サーバーが交代する。
- advantage では 40-40 の次点はADになり、ゲーム終了とは限らない。
- winner が怪しい低confidence候補は、serverAfter とゲーム取得タイミングの辻褄を優先して判断する。
5. 迷う点は削除せず、confidenceを下げて残す。ブラウザ上で人間が修正する。
精度を上げるコツ:
- 動画内のスコア表示には頼らない。ラリー終了、拍手、ボール回収、次サーブ位置、選手の表情や移動を手がかりにする。
- 得点候補の直前直後だけ、追加で1秒間隔または0.5秒間隔のフレームを抽出して再確認する。
- 自信がない場合は confidence を 0.4〜0.6 にする。
検証:
1. 作成したJSONをローカルで JSON.parse できることを確認する。
2. points が時刻昇順であることを確認する。
3. winner が 1 または 2 以外になっていないことを確認する。
4. serverAfter を入れる場合は 1 または 2 以外になっていないことを確認する。
5. serveAt がある場合は、同じ point の t より後になっていないことを確認する。
6. 最初の points[0] に serverCourtSide があることを確認する。
7. visibilityRanges が空なら、動画全体を覆う { "start": 0, "end": 動画秒数 } を入れる。
最終出力:
JSON全文のみを返す。