Skip to main content
ClaudeWave
Skill82 estrellas del repoactualizado 2d ago

wjs-burning-subtitles

Use when the user has a video + an SRT and wants the subtitles either burned into the pixels (libass, always-visible) or soft-muxed as a togglable track. Also handles the final composite step for the localization pipeline — burn subs, mix a dub track, and keep the original audio as a low-volume bed, all in ONE ffmpeg encode (no cascade). Verifies libass availability and auto-downloads a static evermeet ffmpeg build when Homebrew's stripped binary lacks it. Triggers — "烧字幕", "硬字幕", "burn subtitles", "burn-in subs", "embed subtitle", "soft mux SRT", "把字幕烧进视频", "做最终合成".

Instalar en Claude Code
Copiar
git clone --depth 1 https://github.com/jianshuo/claude-skills /tmp/wjs-burning-subtitles && cp -r /tmp/wjs-burning-subtitles/wjs-burning-subtitles ~/.claude/skills/wjs-burning-subtitles
Después abre una sesión nueva de Claude Code; el skill carga automáticamente.

SKILL.md

# wjs-burning-subtitles

Video + SRT → video with subtitles. Also the final-encode stage for the localization pipeline: takes a video, an optional dub track from `/wjs-dubbing-video`, and an optional SRT to burn, and produces the upload-ready MP4 in **one** ffmpeg pass. No cascade of decodes/re-encodes.

## When to use

- User has an SRT and wants it always-visible on the video (burn-in for 微信视频号 / 抖音 / WeChat — players that won't honor embedded subtitle tracks).
- User wants a togglable subtitle track (soft-mux) for QuickTime / VLC / IINA / mobile players that support `mov_text`.
- Final composite after `/wjs-dubbing-video`: burn target-language subs + mix dub over original-as-bed in one encode.

## When NOT to use

- No SRT yet → run `/wjs-transcribing-audio` then `/wjs-translating-subtitles` first.
- HTML/CSS captions (kinetic, per-word highlights, custom fonts) on a clip composed in HyperFrames → use `/wjs-overlaying-video` instead. Don't mix libass burn-in with HyperFrames captions on the same output.
- The "subtitles" are actually motion graphics (animated callouts, lower-thirds with logos, kinetic typography) → that's `/wjs-overlaying-video`, not this skill.

## The 3 modes of `render.py`

`scripts/render.py` auto-detects mode from flags:

1. **Subtitles only** — `--video + --srt` → re-encodes video with burned subs, original audio passes through.
2. **Dub only** — `--video + --dub` → keeps original video stream; replaces or mixes the audio track.
3. **Full localized cut** — `--video + --srt + --dub` → burns subs AND mixes dub. By default keeps original audio at low volume as a "bed" under the dub (set `--bed-volume 0` or `--no-original-audio` to drop it).

Burn-in requires an ffmpeg built with libass. The script auto-downloads a static libass-enabled build from evermeet.cx into `/tmp/ff_bin/` on first use if needed.

## Soft-mux (togglable subtitle track)

Player apps can show/hide. Works with any `ffmpeg` build — does **not** need libass:

```bash
ffmpeg -i input.mp4 -i input.zh-CN.srt \
  -map 0:v -map 0:a -map 1:0 \
  -c:v copy -c:a copy -c:s mov_text \
  -metadata:s:s:0 language=zho -metadata:s:s:0 title="中文" \
  output.mp4
```

This is fast (stream-copy) and reversible. Use it when:

- Target platform supports embedded subs (YouTube auto-detects; VLC/QuickTime honors).
- User wants viewers to be able to toggle off.
- You don't want to re-encode the video.

`render.py --video IN.mp4 --srt SUB.srt --soft-mux` runs this path.

## Hardcoded burn-in (always visible, libass)

Required for WeChat/抖音/朋友圈 etc. where the player will not honor embedded subtitle tracks.

### Verify libass is available BEFORE promising burn-in

```bash
ffmpeg -filters 2>&1 | grep -E "subtitles|^.. ass "
```

If neither `subtitles` nor `ass` shows up, the build lacks libass. Homebrew's default `ffmpeg` formula is often stripped (no `--enable-libass`, no `--enable-libfreetype`, no `drawtext`). Don't waste time fighting the comma-escaping inside `force_style` — it will fail with `No such filter: 'subtitles'` no matter how the shell quotes it.

### Fastest fix on macOS — drop in a static build, no system changes

```bash
curl -fsSL -o /tmp/ff.zip https://evermeet.cx/ffmpeg/getrelease/zip
unzip -o /tmp/ff.zip -d /tmp/ff_bin >/dev/null
FF=/tmp/ff_bin/ffmpeg
$FF -version | grep -oE -- "--enable-(libass|libfreetype)"
```

Then use `$FF` instead of `ffmpeg` for the render. The brew binary is fine for everything else (probe, audio extraction, soft-mux). `render.py` does this auto-fallback if its default ffmpeg lacks libass.

### Burn-in render with style overrides

🛑 **Checkpoint — confirm before full-render.** Burn-in re-encodes the entire video (minutes of CPU on a 5-min clip). Before kicking it off:

1. Render only the first 30s with `-t 30` for a fast preview.
2. Extract a frame from the longest-line cue (see Fontsize calibration below) and Read it.
3. Show the user the preview frame + the cue text, ask: "字号/字体/边距 OK 吗?OK 才跑全片。" Wait for explicit confirmation.

Skip the checkpoint only if the user has already approved a full render of this exact video at this exact font config in the same conversation.

```bash
$FF -i input.mp4 \
  -vf "subtitles=input.zh-CN.srt:force_style='Fontname=PingFang SC\,Fontsize=12\,PrimaryColour=&H00FFFFFF\,OutlineColour=&H00000000\,BorderStyle=1\,Outline=2\,Shadow=1\,MarginL=20\,MarginR=20\,MarginV=40'" \
  -c:v libx264 -crf 18 -preset medium -pix_fmt yuv420p \
  -c:a copy output.mp4
```

Inside `force_style`, escape every comma as `\,` (the filter graph parser eats the bare comma as a chain separator). All other special chars are fine.

### Fontsize calibration — critical

libass scales its internal PlayRes up to the actual video resolution. The number you pass is **not pixels** in the output. As a starting calibration on a 544×960 vertical phone video, `Fontsize=22` rendered each Chinese character at ~55px wide and overflowed the frame, while `Fontsize=12` rendered at ~30–35px wide and fit cleanly with 15-char lines.

Rule of thumb: start at `Fontsize=12`, render, then **always** extract a frame and look:

```bash
$FF -ss 30 -i output.mp4 -frames:v 1 /tmp/frame.png -y
# then Read /tmp/frame.png to verify the longest-line cue fits
```

Pick a timestamp that lands on the cue with the most characters per line — short lines won't expose overflow. Add `MarginL=20 MarginR=20` as a safety inset; never trust default left/right margins.

### Style cheatsheet

Keys that matter (libass `force_style`):

- `Fontname=PingFang SC` — macOS default CJK; alternates: `Songti SC`, `Heiti SC`, `STHeiti`, `Hiragino Sans GB`.
- `Fontsize=12` — start small, scale up only after frame check.
- `PrimaryColour=&H00FFFFFF` — white text (BBGGRR + alpha).
- `OutlineColour=&H00000000` — black outline.
- `BorderStyle=1` — outline only (clean over varied backgrounds). Use `BorderStyle=3` for an opaque box behind text when the background is busy.
- `Outline=2` — 2px outline thickness.
- `Shadow=1` — subtle drop
skill-quality-reviewerSubagent

Repo-wide drift detector for the wjs-* Claude Code skills in this marketplace. Sweeps every SKILL.md, scores it against the repo's own conventions (V-ing naming, trigger-phrase density, companion files, description shape), and returns a grouped punch list ordered by severity. Read-only — never edits files. Use before pushing a batch of skill changes, or whenever you wonder "are these skills still internally consistent?

wangjianshuo-perspectiveSkill

|

wjs-auditing-projectSkill

Use when the user asks to audit what's wrong with a project, "make it right", "看看项目出了什么问题", "为什么用户的需求还没上线", "为什么没提交App Store", "为什么没新build", or wants a holistic state-of-the-project check covering unmerged branches, stalled PRs, failed GitHub Actions, stale builds, plan drift (TODOS.md / ROADMAP), unreleased commits, and log errors. Runs read-only investigation, presents a grouped checklist, fixes only after explicit user confirmation. Aware of the Cathier iOS app workflow (Xcode + fastlane + auto-merge @claude PRs from in-app feedback).

wjs-cleaning-spamSkill

Use when the user complains about spam on his X/Twitter posts — 同城面付 / 寻固炮 / 线下上门 / 免费破处 这类引流号在他推文下刷的 emoji 垃圾回复 — and wants them removed. Covers the last 7 days (X recent-search window). Triggers — "把这些spam删掉", "清理X垃圾回复", "推文下面好多引流号", "clean spam replies", "/wjs-cleaning-spam".

wjs-converting-text-to-videoSkill

Use when the user wants a 王建硕-style WeChat article (article.md) turned into a narrated short MP4 video — TTS voiceover via 火山引擎 Volcano TTS, HyperFrames CSS/GSAP animation per scene, subtle SFX, abstract watercolor background, full pipeline rendering to 1080×1920 portrait MP4 (30-90s). Triggers — "把这篇文章做成视频", "做一个解说视频", "讲解视频", "/wjs-converting-text-to-video".

wjs-converting-wp-to-hugoSkill

Use when migrating a WordPress site to a Hugo static site on GitHub Pages from a WXR export (.xml) plus the wp-content/uploads folder — preserving /archives/<id>/ URLs, localizing images, and deploying via GitHub Actions. Triggers — "把 WordPress 迁成 Hugo", "wordpress 转静态站", "migrate WordPress to Hugo", "WXR to Hugo", "publish WordPress to GitHub Pages", "/wjs-converting-wp-to-hugo".

wjs-dubbing-videoSkill

Use when the user has a video + a target-language SRT and wants the video to actually speak that language — generates a time-aligned TTS voice dub. Routes by voice ID — Volcano (豆包) TTS for Chinese, edge-tts neural for any language. Defaults to one voice (single-speaker); opt-in multi-speaker via visual diarization. Outputs `*_<lang>_dub.mp4` with the dub audio in place of the original. Final mixing (audio bed + burn-in) is handed off to `/wjs-burning-subtitles`. Triggers — "配音", "中文配音", "Chinese dub", "voice over this", "dub the video", "TTS this SRT", "different voice for each speaker".

wjs-eating-and-growingSkill

吃一堑长一智 — 走完 5 步交互式反思(堑 → 自动输出 → 旧权重 → 新参数 → 替代动作),从「情绪复盘」推进到「行为训练」,把第一反应这一层 L3 权重练新。Use when 王建硕 reflects on a personal setback, mistake, or recurring pattern (反思, 复盘, 回顾, 总结教训, 吃一堑, 长一智, "这次又栽了", "怎么又这样", "为什么我总是…", "想开点都做不到", "知道道理但做不到"). For the user as a human, not for Claude's task post-mortems.