Communitygithub.com

layyyback/tennis-rally-cut

把一段很长的网球练球视频(对墙练、发球机、对拉都行)按击球声自动剪成一个个完整回合,去掉捡球、走动、停顿。当用户说「帮我把练球视频剪成一段段」「把打球的部分剪出来」「去掉捡球」「按回合切网球视频」,或给出一个网球视频目录要求剪辑时使用。也适用于乒乓球、羽毛球、壁球等击球声清楚的练习视频。

tennis-rally-cut 是什么?

tennis-rally-cut is a Claude Code agent skill that 把一段很长的网球练球视频(对墙练、发球机、对拉都行)按击球声自动剪成一个个完整回合,去掉捡球、走动、停顿。当用户说「帮我把练球视频剪成一段段」「把打球的部分剪出来」「去掉捡球」「按回合切网球视频」,或给出一个网球视频目录要求剪辑时使用。也适用于乒乓球、羽毛球、壁球等击球声清楚的练习视频。.

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/layyyback/tennis-rally-cut/tree/HEAD/tennis-rally-cut

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

Tennis Rally Cut (tennis-rally-cut)

Distilled from a real editing session on 2026-11-08: two wall-drill videos, cut into 14 rallies. Core idea: Use sound to find rallies, use video to verify.

What the User Wants (Hard Rules)

  • A rally = from the first hit until the ball is dead. The entire rally goes into one clip.
  • Never split a rally. If someone hits 10 consecutive shots against a wall, that's one clip; do not break it into several 2- or 3-shot pieces.
  • Ball retrieval, walking, pauses, picking up a phone, grabbing equipment — all removed.
  • Original videos remain untouched. Output goes into a 剪辑/ folder in the same directory as the original video.

Why Use Sound, Not Visuals

  • Every hit produces two crisp sounds: "racket hitting ball" and "ball hitting wall/ground." No such sounds during ball pickup or walking.
  • Visual motion doesn't work: walking and reaching for the ball create more motion than playing.
  • Video is only used for spot-checking and verification.

Tools

Script: scripts/rally_cut.py. Only depends on ffmpeg, ffprobe, and numpy.

SubcommandPurpose
probeCheck resolution, bitrate, orientation; warn if compressed by chat apps
analyzeDetect hit sounds, stitch into rallies, write <name>.segments.json
sheetsGenerate frame sheets (start and end frame of each rally) for visual review
cutCut by rally into separate mp4s, write 片段清单.txt
alignWhen a high-quality original is available, compute time offset between old and new versions

Hereafter, RC refers to the absolute path of the script. Intermediate files go into a session temp directory, not into the user's video directory.

Workflow

1. Check Quality First

python3 -I $RC probe <video...>
  • If it says "suspected compression by chat app," inform the user first. Editing itself hardly reduces quality; if the original is blurry, the output will be too.
  • Videos from WeChat often have the original in the WeChat cache, with filename suffix _raw: ~/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/<account>/msg/video/<yyyy-mm>/<name>_raw.mp4 The user must tap "View Original" in WeChat before it downloads. Use mdfind to search by date added: mdfind -onlyin ~ 'kMDItemContentTypeTree == "public.movie" && kMDItemDateAdded >= $time.iso(<date>)'
  • Once the high-quality original is found, use align to confirm offset is 0, then the computed cut points can be reused directly.

2. Find Rallies

python3 -I $RC analyze <video...> --work <temp_dir>

It prints a timeline of hits per second and a rally list. Default parameters:

ParameterDefaultMeaning
--k2.5How many times louder than background to count as a hit. Increase in noisy environments
--gap6If two hits are more than 6 seconds apart, it's the end of a rally. This is key to not splitting rallies.
--min-hits6Sequences with fewer than 6 hits are discarded as noise (footsteps, phone pickups)
--pre / --post1 / 1.5Seconds to pad before and after each rally to ensure swing and landing are complete

Trade-off for --gap:

  • 4 seconds would split scenarios where "ball is dead but player restarts within a few seconds" into two clips.
  • 6 seconds keeps them together. The user requires not splitting, so default is 6.
  • Only change to 4 if the user explicitly says "each restart is a separate clip."

3. Visual Verification (Must Not Skip)

python3 -I $RC sheets <video> --segments <json> --work <temp_dir>/sheets

Look at each frame sheet, focus on:

  • The last rally of the video: often ends with someone walking toward the camera or picking up a phone, whose sounds can be mistaken for hits. If you see this, change end to about 1.5 seconds after the last real hit.
  • Short rallies with only a few hits: could be noise. Delete if nobody is swinging in the frame.
  • Gaps of about 5 seconds between rallies: confirm they are dead-ball restarts, not missed detections.

Edit the segments array in segments.json directly: change start/end, or remove the entire segment.

4. Cut

python3 -I $RC cut <video> --segments <json> --out <video_dir>/剪辑 --label A
  • Multiple source videos use different --label (A, B…), file names like 视频A_03_2m54s-3m22s.mp4, where time is from the original.
  • Must re-encode (default H.264, CRF 17). Original keyframes are often 2–3 seconds apart; direct copy would misalign cuts.
  • For portrait videos, ffmpeg will automatically handle rotation; output will be upright.
  • If the target directory already has old output, rename them to 剪辑_old_version as backup — do not overwrite or delete.
  • 1080p 60fps encoding is slow; run it in the background.

5. Verify, Then Report

  • Use ffprobe to check each output's duration, resolution, and frame rate.
  • Extract a frame from one or two outputs: check orientation and clarity.
  • Report: number of clips, duration range per clip, output directory (provide a file:// link that Finder can open; percent-encode Chinese paths).
  • Flag any questionable clips (e.g., very short rallies with few hits) so the user can decide whether to delete them.
  • State that you only spot-checked the visuals, not watched every clip in full.

Pitfalls Encountered

PitfallConsequenceFix
Calling ffmpeg in a loop without -nostdinffmpeg ate subsequent lines from the pipeline, causing wrong cut points and crashesScript now includes it
Time calculated as "frames × 10ms"One frame is actually 220 samples; drifts by 1 second over 7 minutesScript converts by samples
Using visual motion to detect playingPicking up the ball creates more motion than playingUse sound only
Using a compressed forwarded videoOutput is blurry; user thinks editing ruined itStep 1: probe first

相关技能