{"slug":"jianshuo-wjs-segmenting-video-805f84","title":"\u957f\u89c6\u9891\u4e3b\u9898\u5207\u7247\u4e0e\u4ea4\u63a5","description":"\u9762\u5411\u5df2\u6709\u957f\u89c6\u9891\u4e0e SRT \u7684\u573a\u666f\uff0c\u7531\u4eba\u5224\u65ad\u4e3b\u9898\u8fb9\u754c\u5e76\u5199\u6210 segments.json\uff0c\u7528\u7cbe\u786e seek \u91cd\u7f16\u7801\u5207\u51fa 3\u20136 \u6761\u72ec\u7acb\u77ed\u7247\uff0c\u518d\u6309\u5e73\u53f0\u65b9\u5411\u51b3\u5b9a\u662f\u5426\u8f6c\u7ad6\u5c4f\uff0c\u6700\u540e\u5207\u51fa\u6bcf\u6bb5\u72ec\u7acb\u5b57\u5e55\uff0c\u4f5c\u4e3a\u4e0b\u6e38\u5305\u88c5\u6280\u80fd\u7684\u4ea4\u63a5\u5305\u3002","category":"\u526a\u8f91\u4e0e\u5305\u88c5","tags":["jianshuo","\u5efa\u7855","\u526a\u8f91\u4e0e\u5305\u88c5"],"author":"\u5efa\u7855 \u00b7 jianshuo","source_url":"https://github.com/jianshuo/claude-skills/blob/d36bc423286a34eb32adc604121e5c7714b08740/wjs-segmenting-video/SKILL.md","license":"MIT","status":"published","verification":"source_reviewed","verification_notes":"\u5df2\u6838\u5bf9\u539f\u59cb\u4ed3\u5e93\u3001\u56fa\u5b9a\u63d0\u4ea4\u3001\u8bb8\u53ef\u3001\u6280\u80fd\u6b63\u6587\u4e0e\u5305\u7ed3\u6784\uff0c\u5e76\u8865\u5145\u4e2d\u6587\u6267\u884c\u9a8c\u6536\u8bf4\u660e\uff1b\u672a\u8fd0\u884c\u5305\u5185\u811a\u672c\u3002","revision":"547b57c247974a68b0880c9645457b0e","created_at":"2026-11-02T14:47:08+00:00","updated_at":"2026-11-02T14:47:08+00:00","source_repo":"jianshuo/claude-skills","source_path":"wjs-segmenting-video/SKILL.md","source_commit":"d36bc423286a34eb32adc604121e5c7714b08740","source_updated_at":"2026-11-02T14:46:04+00:00","stars":0,"proxy_score":null,"score":null,"grade":null,"dimensions":{},"evaluation":{},"installs":0,"platforms":[],"formats":[],"category_slug":"packaging","category_name":"\u526a\u8f91\u4e0e\u5305\u88c5","quality":{"review_type":"text_and_package_review","readiness":"requires_setup","reviewed_at":"2026-11-02T14:46:04+00:00","review_summary":"\u5206\u6bb5\u5224\u65ad\u6807\u51c6\u3001schema\u3001\u7cbe\u786e seek \u4e0e\u6d41\u62f7\u8d1d\u53d6\u820d\u8bb2\u5f97\u6e05\u695a\uff0c\u5207\u6bb5\u4e0e\u5b57\u5e55\u5207\u7247\u811a\u672c\u9f50\u5168\uff1b\u4f46\u5b8c\u6574\u6d41\u7a0b\u4f9d\u8d56\u591a\u4e2a\u672a\u968f\u5305\u63d0\u4f9b\u7684\u4e0b\u6e38\u6280\u80fd\uff0c\u5355\u72ec\u4f7f\u7528\u65f6\u53ea\u80fd\u5b8c\u6210\u5207\u6bb5\u4e0e\u5b57\u5e55\u5207\u7247\u3002","cost_notes":"\u65e0\u5916\u90e8\u4ed8\u8d39\u670d\u52a1\uff1b\u91cd\u7f16\u7801\u8017\u65f6\u53d6\u51b3\u4e8e\u673a\u5668\u6027\u80fd\uff0c\u6587\u6863\u7ed9\u51fa\u7684\u6bcf\u6bb5\u7ea6 30 \u79d2\u4e3a\u63cf\u8ff0\u503c\uff0c\u9700\u81ea\u884c\u5b9e\u6d4b\u3002","source_sha256":"4e44bb44edf02c1f548c8ac886db933a9a8d8dab01b5153465d12047077a2d45","prerequisites":["ffmpeg \u4e0e ffprobe \u5728 PATH","Python 3 \u8fd0\u884c segment.py \u4e0e burn_subs.py","\u82e5\u9700\u6a2a\u7ad6\u5c4f\u8f6c\u6362\uff0c\u9700 mediapipe\u3001opencv-python\u3001numpy \u7684\u53ef\u7528 Python \u73af\u5883","\u65e0\u9700\u8d26\u53f7\uff1b\u4e0d\u6d89\u53ca\u4ed8\u8d39 API"],"checks":["segments.json \u901a\u8fc7 references/segments_schema.json \u6821\u9a8c\uff0c\u6bcf\u6761 start/end \u843d\u5728 SRT \u5b57\u5e55\u6761\u76ee\u8fb9\u754c\u4e0a\uff0c\u4e0d\u5207\u65ad\u53e5\u5b50","\u7528 ffprobe \u6838\u5bf9\u6bcf\u4e2a clip \u7684\u8d77\u59cb\u65f6\u95f4\u4e0e segments.json \u4e2d start \u4e00\u81f4\uff08\u7cbe\u786e seek \u751f\u6548\uff09\uff0c\u65f6\u957f\u4e0e end-start \u76f8\u7b26","\u6bcf\u6bb5 SRT \u9996\u6761\u65f6\u95f4\u6233\u63a5\u8fd1 0\uff0c\u4e14\u4e0e\u5bf9\u5e94 clip \u7684\u8bed\u97f3\u5bf9\u9f50\uff0c\u65e0\u6574\u4f53\u63d0\u524d\u6216\u6ede\u540e","\u65b9\u5411\u68c0\u67e5\u5df2\u6267\u884c\uff1a\u6e90\u5bbd\u9ad8\u6bd4\u4e0e\u76ee\u6807\u5e73\u53f0\u4e0d\u7b26\u65f6\uff0c\u6709\u660e\u786e\u7684\u8be2\u95ee\u8bb0\u5f55\u6216\u8f6c\u7ad6\u5c4f\u4ea7\u7269","\u4ea4\u63a5\u76ee\u5f55\u53ea\u542b\u7ea6\u5b9a\u7684\u56db\u7c7b\u6587\u4ef6\uff0c\u672a\u5728\u672c\u6280\u80fd\u5185\u70e7\u5f55\u5b57\u5e55\u6216\u751f\u6210\u5c01\u9762"],"limitations":["\u4e3b\u9898\u8fb9\u754c\u5b8c\u5168\u4f9d\u8d56\u4eba\u5de5\u5224\u65ad\uff0c\u811a\u672c\u4e0d\u505a\u8bed\u4e49\u5206\u6bb5\u6216\u7206\u6b3e\u6253\u5206","\u9ed8\u8ba4\u91cd\u7f16\u7801\u5207\u6bb5\uff0c\u957f\u7247\u591a\u6bb5\u65f6\u8017\u65f6\u660e\u663e\uff1b\u6d41\u62f7\u8d1d\u6a21\u5f0f\u9700\u5148\u5f3a\u5236\u5173\u952e\u5e27\uff0c\u5426\u5219\u5b57\u5e55\u4e0e\u97f3\u9891\u9519\u4f4d","\u8f6c\u7ad6\u5c4f\u4f9d\u8d56\u53e6\u4e00\u6280\u80fd\u7684\u811a\u672c\u4e0e\u6a21\u578b\uff0c\u672c\u5305\u672a\u5305\u542b crop.py","\u672a\u5728\u672c\u73af\u5883\u8fd0\u884c\u811a\u672c\uff0c\u5207\u70b9\u7cbe\u5ea6\u4e0e\u5b57\u5e55\u5bf9\u9f50\u9700\u7528\u6237\u81ea\u884c\u62bd\u5e27\u6838\u5bf9"],"issues":["\u6587\u6863\u591a\u5904\u5f15\u7528 /wjs-reframing-video\u3001/wjs-overlaying-video\u3001/wjs-transcribing-audio \u7b49\u672a\u968f\u5305\u63d0\u4f9b\u7684\u6280\u80fd\uff0c\u7f3a\u5c11\u8fd9\u4e9b\u6280\u80fd\u65f6\u6d41\u7a0b\u5728\u7b2c\u4e09\u6b65\u4e2d\u65ad","SKILL.md \u63d0\u5230 make_cover.py \u4e0e compose_cover.py \u4f46\u6b63\u6587\u6d41\u7a0b\u672a\u8bf4\u660e\u5176\u7528\u9014\uff0c\u6587\u4ef6\u6e05\u5355\u4e0e\u6d41\u7a0b\u63cf\u8ff0\u4e0d\u5b8c\u5168\u5bf9\u5e94","\u8f6c\u7ad6\u5c4f\u793a\u4f8b\u4e2d\u5199\u6b7b /tmp/_crop_venv \u4e0e ~/.claude/skills \u8def\u5f84\uff0c\u9700\u6309\u672c\u673a\u73af\u5883\u66ff\u6362"],"missing_files":[]},"quality_url":"https://shiliuai.cn/api/v2/skills/jianshuo-wjs-segmenting-video-805f84/files/SHILIU-QUALITY.md","usage_guide":{"task":"\u628a\u4e00\u6761\u957f\u89c6\u9891\u6309\u4e3b\u9898\u5207\u6210\u82e5\u5e72\u6761\u53ef\u72ec\u7acb\u89c2\u770b\u7684\u77ed\u7247\uff0c\u4ea7\u51fa\u539f\u59cb\u88c1\u526a\u7247\u6bb5\u3001\u6bcf\u6bb5\u72ec\u7acb SRT \u4e0e\u4e2d\u70b9\u5e27\uff0c\u5e76\u660e\u786e\u4ea4\u63a5\u7ed9\u4e0b\u6e38\u505a\u5c01\u9762\u3001\u5b57\u5e55\u4e0e\u6210\u7247\u3002","inputs":"\u957f\u89c6\u9891\u6587\u4ef6\uff08\u5efa\u8bae \u226510 \u5206\u949f\uff09\uff1b\u4e0e\u89c6\u9891\u5bf9\u9f50\u7684 SRT \u5b57\u5e55\uff1b\u76ee\u6807\u5e73\u53f0\uff08\u51b3\u5b9a\u65f6\u957f\u4e0e\u65b9\u5411\uff09\uff1b\u4eba\u5de5\u5224\u5b9a\u7684\u5206\u6bb5\u65b9\u6848\uff08\u8d77\u6b62\u65f6\u95f4\u3001slug\u3001\u6807\u9898\u3001\u6458\u8981\u3001\u5c01\u9762\u63d0\u793a\u8bcd\uff09\u3002\u8fd0\u884c\u9700 ffmpeg/ffprobe \u4e0e Python\uff1b\u82e5\u9700\u8f6c\u7ad6\u5c4f\uff0c\u53e6\u9700 mediapipe \u7b49\u4f9d\u8d56\u3002","outputs":"segments.json\uff08\u5206\u6bb5\u5143\u6570\u636e\uff09\uff1bclip_NN_slug.mp4\uff08\u7cbe\u786e\u8d77\u70b9\u3001\u76ee\u6807\u65b9\u5411\u7684\u539f\u59cb\u7247\u6bb5\uff09\uff1bclip_NN_slug.zh-CN.burn.srt\uff08\u65f6\u95f4\u6233\u5f52\u96f6\u7684\u6bcf\u6bb5\u5b57\u5e55\uff09\uff1bframe_NN_slug.jpg\uff08\u4e2d\u70b9\u5e27\uff0c\u4f9b\u5c01\u9762\u53c2\u8003\uff09\u3002","example_input":"\u8fd9\u662f lecture.mp4 \u548c lecture.zh-CN.srt\uff0c\u5e2e\u6211\u6309\u4e3b\u9898\u5207\u6210 4 \u6bb5\u9002\u5408\u89c6\u9891\u53f7\u7684\u77ed\u7247\uff0c\u6bcf\u6bb5 60\u2013180 \u79d2\uff0c\u5148\u7ed9\u6211 segments.json \u8ba9\u6211\u786e\u8ba4\u518d\u5207\u3002","example_output":"\u793a\u610f\uff08\u975e\u771f\u5b9e\u8fd0\u884c\u7ed3\u679c\uff09\uff1asegments.json \u542b source_video\u3001source_srt\u3001platform \u4e0e 4 \u6761 segment\uff08id\u3001slug\u3001\u4e24\u884c\u6807\u9898\u3001summary\u3001start/end\u3001cover_prompt\uff09\uff1b\u786e\u8ba4\u540e\u8f93\u51fa clip_01_xxx.mp4 \u7b49 4 \u4e2a\u7247\u6bb5\u3001\u5bf9\u5e94 4 \u4e2a\u65f6\u95f4\u6233\u5f52\u96f6\u7684 SRT \u4e0e 4 \u5f20\u4e2d\u70b9\u5e27\uff0c\u5e76\u5217\u51fa\u5f85\u4e0b\u6e38\u5904\u7406\u7684\u4ea4\u63a5\u6e05\u5355\u3002"},"read_url":"https://shiliuai.cn/api/v2/skills/jianshuo-wjs-segmenting-video-805f84","skill_url":"https://shiliuai.cn/api/v2/skills/jianshuo-wjs-segmenting-video-805f84/files/SKILL.md","download_url":"https://shiliuai.cn/api/v2/skills/jianshuo-wjs-segmenting-video-805f84/download","install_url":"https://shiliuai.cn/api/v2/skills/jianshuo-wjs-segmenting-video-805f84/install","files":[{"path":"LICENSE","bytes":1082,"url":"https://shiliuai.cn/api/v2/skills/jianshuo-wjs-segmenting-video-805f84/files/LICENSE"},{"path":"SHILIU-QUALITY.md","bytes":2359,"url":"https://shiliuai.cn/api/v2/skills/jianshuo-wjs-segmenting-video-805f84/files/SHILIU-QUALITY.md"},{"path":"SKILL.md","bytes":14164,"url":"https://shiliuai.cn/api/v2/skills/jianshuo-wjs-segmenting-video-805f84/files/SKILL.md"},{"path":"references/example_segments.json","bytes":1263,"url":"https://shiliuai.cn/api/v2/skills/jianshuo-wjs-segmenting-video-805f84/files/references/example_segments.json"},{"path":"references/platform_sizes.md","bytes":907,"url":"https://shiliuai.cn/api/v2/skills/jianshuo-wjs-segmenting-video-805f84/files/references/platform_sizes.md"},{"path":"references/segments_schema.json","bytes":1892,"url":"https://shiliuai.cn/api/v2/skills/jianshuo-wjs-segmenting-video-805f84/files/references/segments_schema.json"},{"path":"scripts/burn_subs.py","bytes":9480,"url":"https://shiliuai.cn/api/v2/skills/jianshuo-wjs-segmenting-video-805f84/files/scripts/burn_subs.py"},{"path":"scripts/compose_cover.py","bytes":7656,"url":"https://shiliuai.cn/api/v2/skills/jianshuo-wjs-segmenting-video-805f84/files/scripts/compose_cover.py"},{"path":"scripts/make_cover.py","bytes":5446,"url":"https://shiliuai.cn/api/v2/skills/jianshuo-wjs-segmenting-video-805f84/files/scripts/make_cover.py"},{"path":"scripts/prepend_intro.py","bytes":11845,"url":"https://shiliuai.cn/api/v2/skills/jianshuo-wjs-segmenting-video-805f84/files/scripts/prepend_intro.py"},{"path":"scripts/segment.py","bytes":4907,"url":"https://shiliuai.cn/api/v2/skills/jianshuo-wjs-segmenting-video-805f84/files/scripts/segment.py"},{"path":"shiliu-quality.json","bytes":2217,"url":"https://shiliuai.cn/api/v2/skills/jianshuo-wjs-segmenting-video-805f84/files/shiliu-quality.json"},{"path":"shiliu-source.json","bytes":272,"url":"https://shiliuai.cn/api/v2/skills/jianshuo-wjs-segmenting-video-805f84/files/shiliu-source.json"},{"path":"usage.json","bytes":1272,"url":"https://shiliuai.cn/api/v2/skills/jianshuo-wjs-segmenting-video-805f84/files/usage.json"}],"content":"---\nname: wjs-segmenting-video\ndescription: Use when the user has a long-form video (interview / lecture / podcast / conversation) and a transcript SRT, and wants to extract 3\u20136 stand-alone topical short clips from it. This skill ONLY cuts and crops \u2014 it produces raw clips + per-clip SRTs as a hand-off package for downstream post-production (`/wjs-overlaying-video`). Triggers \u2014 \"\u5207\u6210\u51e0\u6bb5\", \"\u5206\u4e3b\u9898\", \"\u62c6\u6210\u77ed\u89c6\u9891\", \"\u5207\u7247\", \"topic segments\", \"split into clips\".\n---\n\n# wjs-segmenting-video\n\nCut a long video + SRT into multiple stand-alone short clips, each\noriented for the target platform. **This skill stops after cutting +\ncropping** \u2014 it hands off the raw clips to `/wjs-overlaying-video` for\ncovers, captions, illustrations, CTA, and final render.\n\n## When to use\n\n- Long-form video (\u226510 min) with an existing SRT transcript.\n- Goal is **stand-alone** short clips (each viewable without context).\n- The user will (or you will) drive post-production separately in\n  `/wjs-overlaying-video`.\n\n## When NOT to use\n\n- Single-topic trimming \u2192 just use `ffmpeg -ss A -to B`.\n- No transcript yet \u2192 run **`/wjs-transcribing-audio`** first (then `/wjs-translating-subtitles` if the segments need a non-source language).\n- Multicam editing \u2192 use **`/wjs-editing-multicam`**.\n- Highlight reel with multiple cuts inside a single topic \u2192 that's\n  editing, not segmentation.\n\n## What this skill IS \u2014 and IS NOT\n\n| Is | Is not |\n|---|---|\n| You (the agent) **read the full SRT and decide the topic boundaries** | A script that runs NLP topic modeling, silence detection, or \"viral moment\" scoring. Topic boundaries are semantic; competing tools (Descript, OpusClip, Riverside Magic Clips) all get this wrong by automating it. |\n| `segment.py` cuts; `/wjs-reframing-video` reorients | An end-to-end \"magic\" pipeline |\n| Accurate-seek cuts by default (re-encode) \u2014 clip starts EXACTLY at requested timestamp | Stream-copy cuts (those produce keyframe-snap drift up to GOP duration) |\n| Hands off **raw cropped clips + per-clip SRTs** | Burned subtitles, covers, intros, CTAs (those live in `/wjs-overlaying-video`) |\n\n## The pipeline\n\n```\nlong video + SRT\n   \u2193     (agent reads SRT, decides topics \u2014 judgment, not parsing)\nsegments.json\n   \u2193     segment.py --reencode (accurate seek; clip starts exactly at requested t)\nclip_NN.mp4 + frame_NN.jpg\n   \u2193     ASK: target platform orientation match source?\n   \u2193     /wjs-reframing-video on each clip (if 16:9 \u2192 9:16, etc.)\n   \u2193     re-extract frames from cropped clips\nclip_NN.mp4 (now in target orientation) + clip_NN.zh-CN.burn.srt\n   \u2193\nHAND OFF \u2192 /wjs-overlaying-video\n   (does covers + captions + illustrations + CTA + final render)\n```\n\n## Step 1 \u2014 Read SRT, write `segments.json`\n\n**Don't outsource topic identification to a script.** For each candidate segment, judge:\n\n- **Self-contained?** A cold viewer must understand it without prior context.\n- **Single thread?** One central question / insight; if the speaker pivots mid-clip, that's two segments.\n- **Length fits platform?** 60\u2013180s for \u89c6\u9891\u53f7 / 30\u201360s for \u6296\u97f3&Shorts. <30s feels truncated; >4min loses retention.\n- **Hook + payoff?** Open on a claim / question / vivid image; close on a takeaway. Never end mid-sentence.\n- **Snap to SRT cue boundaries** \u2014 never cut mid-word.\n\n3\u20136 strong segments from a 10-minute source is normal. Drop boring middles. Quality > quantity.\n\nSchema (full spec in `references/segments_schema.json`, example in `references/example_segments.json`):\n\n```json\n{\n  \"source_video\": \"input.mp4\",\n  \"source_srt\": \"input.zh-CN.srt\",\n  \"platform\": \"wechat_channels\",\n  \"segments\": [{\n    \"id\": 1, \"slug\": \"intent-not-code\",\n    \"title\": \"AI \u65f6\u4ee3\u4e0d\u662f\u5199\u4ee3\u7801\\n\u800c\u662f\u5199\u610f\u56fe\",\n    \"summary\": \"Two-sentence pitch \u2014 what's the insight, what's at stake.\",\n    \"start\": \"00:00:43.460\", \"end\": \"00:02:35.220\",\n    \"cover_prompt\": \"Visual concept for gpt-image-2 (style anchor, not literal scene)\"\n  }]\n}\n```\n\n`slug` = kebab-case English (used in filenames). `title` uses `\\n` for line break, 2 lines max, 8\u201312 Chinese chars per line. `cover_prompt` is consumed downstream by `/wjs-overlaying-video`'s cover-generation step \u2014 keep it written here so the overlay skill can pick it up without re-asking.\n\n## Step 2 \u2014 Accurate-seek cut\n\n```bash\npython3 ~/.claude/skills/wjs-segmenting-video/scripts/segment.py \\\n    --segments segments.json --out output/ --reencode\n```\n\n`--reencode` is the **default recommended mode**. It cuts with\n`ffmpeg -ss N -i src -c:v libx264 -c:a aac` so the output starts\nEXACTLY at the requested timestamp. ~30s per clip on CPU. Also extracts\na midpoint frame per segment to `output/frame_NN_slug.jpg`.\n\n**Why default to `--reencode` and not stream-copy:**\n\nStream-copy via `ffmpeg -ss N -c copy` seeks to the nearest keyframe\n*before* N (it can't re-encode). The output's t=0 then maps to source\nt=keyframe, so the clip plays a fraction of a second of \"lead-in\"\ncontent before the requested speech. Captions sliced from the master\nSRT at boundary N appear **AHEAD of the audio** by exactly that GOP\nfraction \u2014 listeners feel \"subtitles lead the voice.\"\n\nIn practice on H.264 source with GOP=2s: every clip is off by\n0.6\u20131.5s. Looks like a synchronization bug downstream; it's actually\na cut-time bug upstream.\n\n### Stream-copy variant (only if you control the source encode)\n\nIf the source has been re-encoded with `-force_key_frames` at every\nrequested cut boundary, stream-copy IS accurate. Workflow:\n\n```bash\n# Build the comma-separated keyframe list from segments.json\nKF=$(python3 -c \"import json; s=json.load(open('segments.json'))\nts=[]\nfor seg in s['segments']:\n    ts += [seg['start'], seg['end']]\nprint(','.join(ts))\")\n\n# Re-encode master once, forcing keyframes at all segment boundaries\nffmpeg -i master.mp4 \\\n  -c:v libx264 -preset medium -crf 18 \\\n  -force_key_frames \"$KF\" \\\n  -c:a copy master_kf.mp4\n\n# Now stream-copy cuts land exactly:\npython3 segment.py --segments segments.json --source master_kf.mp4 --out output/\n```\n\nUse this only when iterating on segment boundaries (you'll re-cut the\nsame source many times). For one-shot work, `--reencode` is simpler\nand just as correct.\n\n### Diagnosing keyframe-snap on already-cut clips\n\n```bash\nffprobe -v error -select_streams v:0 -read_intervals \"$((N-2))%$((N+5))\" \\\n  -show_entries packet=pts_time,flags -of csv=p=0 master.mp4 | grep \"K_\"\n```\n\nOutput like `360.023,K__   362.023,K__` \u2192 GOP=2s. A `-c copy` cut at\n361.000 actually starts at 360.023, captions are 0.977s ahead of audio.\nThe retroactive fix is a per-clip SRT offset shim\n(`requested_start \u2212 nearest_preceding_keyframe`) added to every cue's\nstart/end, but the root fix is to re-cut with `--reencode`.\n\n## Step 3 \u2014 Orientation check (ask before continuing)\n\nCompare source video aspect ratio to the target platform:\n\n| Platform                  | Native orientation | Aspect |\n|---------------------------|--------------------|--------|\n| \u89c6\u9891\u53f7 (WeChat Channels)  | vertical           | 9:16   |\n| \u6296\u97f3 / TikTok / Reels     | vertical           | 9:16   |\n| \u5c0f\u7ea2\u4e66 (Xiaohongshu video)| vertical           | 9:16   |\n| YouTube Shorts            | vertical           | 9:16   |\n| YouTube (regular)         | horizontal         | 16:9   |\n| B\u7ad9 (Bilibili)            | horizontal         | 16:9   |\n\nProbe with `ffprobe`:\n\n```bash\nffprobe -v error -select_streams v:0 \\\n  -show_entries stream=width,height -of csv=p=0 clip_01_*.mp4\n```\n\nIf source aspect already matches the platform \u2192 **skip this step**.\n\nIf mismatch \u2192 **ASK THE USER** before converting. Sample phrasing:\n\n> \u6e90\u89c6\u9891\u662f\u6a2a\u5c4f (1920\u00d71080)\uff0c\u5e73\u53f0 \u89c6\u9891\u53f7 \u9700\u8981\u7ad6\u5c4f (9:16)\u3002\u662f\u5426\u5bf9\u6bcf\u6bb5\n> \u8c03\u7528 `/wjs-reframing-video` \u8f6c\u6210\u7ad6\u5c4f\uff1f(crop \u4f1a\u7528 MediaPipe \u8ddf\u8e2a\u6b63\u5728\u8bf4\u8bdd\n> \u7684\u4eba\u7684\u8138\uff0c\u4fdd\u6301\u8bf4\u8bdd\u4eba\u59cb\u7ec8\u5728\u753b\u9762\u4e2d)\n\n**Never silently skip the check** \u2014 finding out at upload time that\nyour horizontal clip needs to be vertical is a frustrating failure\nmode the skill exists to prevent.\n\n### Calling `/wjs-reframing-video`\n\nThe crop script needs `mediapipe + opencv + numpy` in a Python 3.12\nvenv (mediapipe doesn't ship wheels for 3.14+). One-time setup:\n\n```bash\nuv venv --python 3.12 /tmp/_crop_venv\n/tmp/_crop_venv/bin/python -m pip install mediapipe opencv-python numpy\n```\n\nPer-clip invocation:\n\n```bash\nfor n in 01 02 03 04 05; do\n  slug=$(ls clip_${n}_*.mp4 | grep -v -E \"_intro|_burned|_vert\" | head -1 | sed -E \"s/clip_${n}_(.+)\\.mp4/\\1/\")\n  /tmp/_crop_venv/bin/python ~/.claude/skills/wjs-reframing-video/scripts/crop.py \\\n    \"clip_${n}_${slug}.mp4\" \\\n    --out \"clip_${n}_${slug}_vert.mp4\" \\\n    --target portrait \\\n    --bitrate 8M    # \u89c6\u9891\u53f7 caps at 10Mbps\ndone\n```\n\nAfter cropping, **swap the cropped versions to canonical names** so\ndownstream pipelines find them:\n\n```bash\nmkdir -p _horizontal_archive\nfor n in 01 02 03 04 05; do\n  base=$(ls clip_${n}_*_vert.mp4 | sed -E \"s/_vert\\.mp4$//\")\n  mv \"${base}.mp4\" \"_horizontal_archive/\"\n  mv \"${base}_vert.mp4\" \"${base}.mp4\"\n  # Re-extract midpoint frame:\n  mid=$(ffprobe -v error -show_entries format=duration -of csv=p=0 \"${base}.mp4\" | awk '{print $1/2}')\n  slug=$(echo \"$base\" | sed -E \"s/^clip_${n}_//\")\n  ffmpeg -hide_banner -loglevel error -ss \"$mid\" -i \"${base}.mp4\" \\\n    -frames:v 1 -q:v 3 \"frame_${n}_${slug}.jpg\" -y\ndone\n```\n\n**Sanity check**: face-on-screen detection rate in the crop log can\nread low (e.g. `face#0: 9.6s on screen (9%)`) when speakers sit\nfurther than ~2 m from the camera. A *low* number is OK \u2014 the\nactive-speaker hysteresis + fallback-to-largest-face still produces\nwell-centered crops. But **`0 face observations` / `(no face /\nfallback): 100%` is NOT OK**: with zero landmarks the crop falls back\nto the frame *center*, which on a two-person interview set lands on the\nbackground between the speakers (fireplace / plant), not on anyone. When\nyou see that, abandon the MediaPipe crop and do a **deterministic fixed\ncrop** on the speaker's known screen position \u2014 see\n`/wjs-reframing-video` \u2192 \"Zero-detection fallback\". **Always verify\nvisually** by extracting a midpoint frame and confirming the speaker is\ncentered before committing.\n\n## Step 4 \u2014 Slice per-clip SRTs\n\n```bash\npython3 ~/.claude/skills/wjs-segmenting-video/scripts/burn_subs.py \\\n    --segments segments.json --out output/ --no-burn\n```\n\nThe `--no-burn` flag emits per-clip SRTs (`clip_NN_slug.zh-CN.burn.srt`)\nwith timestamps already shifted to start at 0 \u2014 exactly the input\n`/wjs-overlaying-video` captions expect (its compositions start the\nbody at t=cover_duration, not the master clock).\n\nDespite the legacy name `burn_subs.py`, this step does NOT burn pixels\nin `--no-burn` mode \u2014 it's just an SRT slicer. (The burn-pixels mode\nexists for the legacy \"Path A\" workflow but is deprecated in favor of\n`/wjs-overlaying-video`'s HTML/CSS caption rendering.)\n\n## Hand-off package \u2014 what to deliver to `/wjs-overlaying-video`\n\nAfter Steps 1\u20134, deliver EXACTLY these per-segment artifacts:\n\n```\noutput/\n  clip_NN_slug.mp4                  # raw cropped clip (target orientation, no subs, no cover)\n  clip_NN_slug.zh-CN.burn.srt       # per-clip SRT, timestamps shifted to start at 0\n  frame_NN_slug.jpg                 # midpoint frame (cover reference)\n  segments.json                     # for slug/title/summary/cover_prompt metadata\n```\n\nThen invoke `/wjs-overlaying-video` to add covers, captions, illustrations,\nCTA, and produce the upload-ready MP4 per clip. The overlay skill\ngenerates ONE final composition per clip and renders it in a single\nencode (no cascade of re-encodes).\n\n## Quick reference\n\n| Task | Command |\n|---|---|\n| Cut clips (accurate, default) | `segment.py --segments S.json --out output/ --reencode` |\n| Probe source aspect | `ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 IN.mp4` |\n| Convert orientation (ask first) | invoke `/wjs-reframing-video` per clip |\n| Slice per-clip SRTs | `burn_subs.py --segments S.json --out output/ --no-burn` |\n| Diagnose keyframe positions | `ffprobe -v error -select_streams v:0 -read_intervals A%B -show_entries packet=pts_time,flags -of csv=p=0 src.mp4 \\| grep K_` |\n\n## Common mistakes\n\n- **Cutting mid-sentence** \u2014 always snap to SRT cue boundaries.\n- **Trying to use 100% of the video** \u2014 3\u20136 strong clips from 10 min is normal. Boring middle = drop.\n- **Letting the LLM write the title** \u2014 the title is judgment, not summary. Review and rewrite before passing to make_cover.\n- **Stream-copy without `--force_key_frames` preprocessing** \u2014 produces clips with audio ahead of captions by up to 1 GOP. Use `--reencode` (default) unless the source was specifically prepared.\n- **Skipping the orientation check** \u2014 getting a horizontal podcast on \u89c6\u9891\u53f7 and finding out at upload time is preventable. Probe aspect and ask the user before cropping.\n- **Burning subs / generating covers in THIS skill** \u2014 those moved to `/wjs-overlaying-video`. This skill stops after Step 4.\n\n## Integration with other skills\n\n- **`/wjs-transcribing-audio`** \u2014 produce the source SRT first if missing. The word-level Whisper output (or Volcano/\u8c46\u5305 ASR output) is preferred for accurate cue timing. If the segments need translating, chain into **`/wjs-translating-subtitles`**.\n- **`/wjs-reframing-video`** \u2014 call in Step 3 when source orientation doesn't match target platform. Face-tracked active-speaker following keeps the talker in frame.\n- **`/wjs-editing-multicam`** \u2014 if the source is multi-cam, render the synced single MP4 first, then segment.\n- **`/wjs-overlaying-video`** \u2014 the **default downstream** for everything after Step 4. Covers, captions, illustrations, CTA, and final render all happen there. Don't add post-production in this skill.\n\n## Files & references\n\n- `scripts/segment.py` \u2014 accurate-seek + stream-copy cutting\n- `scripts/burn_subs.py` \u2014 SRT slicer (`--no-burn` mode); legacy libass burn-in mode is deprecated in favor of `/wjs-overlaying-video`\n- `references/segments_schema.json` \u2014 JSON Schema for segments.json\n- `references/example_segments.json` \u2014 worked example\n","usage":"\u8bfb\u53d6 SKILL.md\uff0c\u5e76\u6309\u9700\u8bfb\u53d6\u914d\u5957\u6587\u4ef6\uff1b\u4e0b\u8f7d ZIP \u83b7\u53d6\u5b8c\u6574\u5305\u3002\u6280\u80fd\u662f\u4efb\u52a1\u65b9\u6cd5\uff0c\u4e0d\u6388\u4e88\u65b0\u589e\u8d26\u6237\u3001\u4ed8\u8d39\u6216\u7cfb\u7edf\u6743\u9650\u3002"}
