TTokenySpace
返回 Skills 列表

tierlist-video-maker

Turn a published TierVibe tier list into a narrated video. Fetches tier list data and card images from a TierVibe URL, captures a HIGH-RESOLUTION board image from the public page (Playwright, no serve

#中文
0

安装到 Tokeny(自动)

下载 ZIP
安装"tierlist-maker"技能
技能信息:
- 名称: tierlist-video-maker
- 标识: tierlist-maker
- 描述: Turn a published TierVibe tier list into a narrated video. Fetches tier list data and card images from a TierVibe URL, captures a HIGH-RESOLUTION board image from the public page (Playwright, no serve
- 版本: 1.0.0
下载地址:
https://www.tokeny.space/api/skills/tierlist-maker/download
继续

复制上方内容到 Tokeny 客户端并在会话中发送即可自动安装;也可直接 下载 ZIP并拖动到技能窗口安装。

SKILL.md

TierList Video Maker

Turn any published TierVibe tier list into a narrated video with a scrolling high-resolution background.

Transparent image handling

Video composition preserves PNG/WebP alpha for card overlays. Transparent card images are resized with alpha-aware premultiplication and composited on an integrated semi-transparent dark glass backing with a soft dark edge, so cutout/standee images stay readable without black halos or a harsh rectangular box.

⚠️ Only published posts — drafts / still-editing posts cannot be used

This skill works on published TierVibe posts only. The video needs a board image and card images, which exist only once a post is published — a draft / a post you're still editing in the editor is not publicly readable and has no board image, so the scripts cannot fetch it.

If the user gives you a post that isn't published yet: stop and tell them — "先把它在 TierVibe 上发布,再用这个技能做成视频" / "publish it on TierVibe first, then make the video." Do not try to make a video from an editor URL or a draft. fetch_tierlist.py checks the status field and will fail fast with this same message if the post isn't published.

⚠️ Image-card boards need multimodal vision — text-card boards don't

First check which kind of board this is — vision may not be needed at all. After Step 2, look at each card's image_url and detail in manifest.json:

  • text:... → a TEXT card. The label is right there in the data (TierVibe encodes it as text:<urlencoded label>#<fg>#<bg>); fetch_tierlist.py has already rendered the swatch and filled label, tagging it label_source: "text_card_data". Skip the vision work in Steps 4 and 5 — read the labels straight from manifest.json. A model with no image recognition can complete a text board end to end.

  • an http(s):// URL means an IMAGE card. Whether vision is needed depends on that card's detail:

    • Image card WITH a non-empty detail - vision-free. The author's commentary names or clearly references the item, so you can DERIVE a short card label from the detail text (e.g. its first markdown heading, or the subject of the first sentence) without looking at the image. Write that label into the card's label field with label_source: "derived_from_detail", then skip the vision work in Steps 4 and 5 and go straight to Step 6. A model with no image recognition can complete these cards end to end, just like text cards.
    • Image card WITHOUT detail - vision IS needed. The API returns no text label for image cards, so the only way to know what the card depicts is to look at it. Everything below applies to these cards only.

If the board mixes the two, handle each card by its own case: derive labels from detail where present, and use vision only for the no-detail image cards.

For no-detail image cards this skill needs AI vision: the API returns no text labels, so card names are baked into the images and you (the model running this skill) must look at each such card image and identify what it depicts before writing narration.

If you do NOT support image recognition (you can't view/identify images) AND there are no-detail image cards you can't otherwise name, stop at Step 4 and tell the user plainly - do NOT fabricate card names or hallucinate narration. Say, in the user's language:

"I can't make this video - some card images have no detail and I can't recognize images. Either: (1) use a multimodal model that supports image recognition (e.g. GPT-4o, Claude with vision, Gemini), (2) add a detail (讲解) explanation to those cards in the TierVibe post and re-run, or (3) tell me what each of those cards is and I'll take it from there."

Image recognition is the model's own capability, not something this skill can work around - same principle as image search in the TierList-Maker skill. Be honest; do not pretend. But remember: a card with detail never needs vision - derive its label from the text.

How the board image is obtained (read this first)

The TierVibe server only stores a 600px thumbnail of the board, not a high-resolution one. For a sharp 1080p background, this skill reproduces the user-side "download whole image" action in a script: a headless Chromium opens the public read page and runs the same html-to-image library the in-page button uses. No TierVibe server call is made for the board image — the export is a client-side action, just automated. See references/tiervibe-api.md.

Workflow (follow in order)

Step 1 — Capture the high-resolution board (visual source of truth)

python <skill_dir>/scripts/capture_board.py "<URL_OR_SLUG>" -o <work_dir> --pixel-ratio 2

Runs headless Chromium on the public https://tiervibe.com/t/<slug> page, captures [data-testid="tier-grid"] to board_hd.png (~2560px wide). This is captured FIRST because it is the visual source of truth for tier assignment and card order — recognizing cards against this single image (with tier labels and neighbors as context) is far more accurate than recognizing each card image in isolation (user-reported bug: isolated recognition misidentifies cards).

If this fails ("tier-grid not found"), the TierVibe deploy has not shipped the data-testid attribute yet. Fall back to the 600px thumb from Step 2 — the video still works, the background is just softer — and note it to the user. Board-first recognition (Step 4) then falls back to per-card-only.

Step 2 — Fetch tier list data + card images

python <skill_dir>/scripts/fetch_tierlist.py "<URL_OR_SLUG>" -o <work_dir>

Downloads all card images + the 600px server thumb (fallback background), writes manifest.json. Verify: total_cards > 0 and images/ is populated.

Step 3 — (Last resort) Render an approximate board

Only when Playwright cannot be installed at all. Step 1 is the real path and it works — data-testid="tier-grid" is live in production. This step produces a board that does NOT match TierVibe's layout, so reaching for it when Step 1 was merely not tried gives a visibly worse video:

python <skill_dir>/scripts/render_board.py <work_dir> --width 1920

Builds an APPROXIMATE board (tier labels + card grid) from card images. It will not match TierVibe's exact layout, so prefer Step 1.

Step 4 — Board-first recognition (produce board_layout.json)

Vision-free shortcut: if every card is either a text card or an image card with a non-empty detail (verify in manifest.json), SKIP Steps 4 and 5 - no board_layout.json, no reconcile. Derive each image card's label from its detail (first markdown heading, or the subject of the first sentence; keep it short), write it into manifest.json with label_source: "derived_from_detail", and go straight to Step 6. The API order already equals the board's visual order, so the manifest is already in the right order; build_card_manifest.py and generate_video.py both run fine without reconcile (board_pos just shows -). If even one image card lacks detail, use the vision path below for that card.

View board_hd.png (the whole board at once). In ONE pass, identify every card in context — tier labels are the row headers, neighbors give context that an isolated card image lacks. Output the visual layout to <work_dir>/board_layout.json:

{
  "board_title": "编程语言天梯榜 · 2026",
  "tiers": [
    { "tier": "T1", "cards": [{"position": 1, "label": "PHP"}, {"position": 2, "label": "C++"}] },
    { "tier": "T2", "cards": [{"position": 1, "label": "C"}, {"position": 2, "label": "Rust"}] }
  ]
}

Rules:

  • Visual reading order: top tier → bottom tier, within each tier left → right.
  • tier = the tier label as shown on the board (e.g. "T1", or a custom name like "S"/"夯"). position = 1-based left-to-right index within that tier.
  • label = what the card depicts. If you genuinely cannot read a card on the board (small/blurry), leave its label empty — Step 5's per-card pass (higher-res individual image) will fill it.
  • This whole-board pass is the primary recognition. It is more accurate than per-card because the board carries tier + neighbor context.

If board_hd.png is missing (Step 1 failed): skip this step, leave board_layout.json unwritten. Step 5's per-card recognition becomes the only source; reconcile_cards.py will keep the API order.

Step 5 — Per-card confirmation + reconcile

  1. Read manifest.json for card image files in images/.
  2. View each card_XXX.webp (higher-res than the board thumbnail) and confirm the label. Write it back into each card's label field in manifest.json. This confirms the board recognition; if it disagrees, that's fine — the board wins (Step 4 is the truth), but the disagreement is flagged for review. Exception: text cards. Their label comes from the card's own data (label_source: "text_card_data"), not from reading an image, so it beats the board label — reconcile enforces this. Tier and order still come from the board either way.
  3. Reconcile the two:
python <skill_dir>/scripts/reconcile_cards.py <work_dir>

Reads board_layout.json (board truth: tier + order) + manifest.json (per-card labels), matches each board slot to its card image by label, and rewrites manifest.json so tiers are in board visual order with each card tagged board_tier / board_position / matched / label_disagreement. Card index (the card_XXX file number) is preserved, so narration indices and the video overlay still map to the right image files. A manifest.pre_reconcile.json backup is written. If matching drops below 50% (board and API structures diverge too far), it keeps the API order and warns — review manually instead of trusting a half-matched reorder.

If a card cannot be recognized on either the board or its individual image, ask the user for that one card rather than guessing — a wrong label misleads the entire narration.

Step 6 — Generate narration script

Create <work_dir>/narration_script.json:

{
  "title": "中国动画电影龙虎榜",
  "language": "zh",
  "intro": "大家好,今天来看看中国动画电影的排名...",
  "segments": [
    { "index": 0, "tier": "夯", "label": "哪吒之魔童降世",
      "narration": "第一名,哪吒之魔童降世。这部电影..." }
  ],
  "outro": "以上就是今天的排名,你觉得合理吗?"
}

Rules:

  • Match the language of the tier list title (Chinese title → Chinese narration).
  • Each segment: 1-3 sentences, concise and engaging.
  • Group by tier: introduce each tier before its cards.
  • Use the author's detail as reference when present. Each card in manifest.json may carry a non-empty detail - the original author's own explanation for why that card sits in that tier. If present, ground the narration in it: prefer the facts/reasons/viewpoints the author wrote, compressed into spoken-language sentences. If detail is empty, fall back to a neutral explanation from the card label + tier. This is reference, not verbatim copy: compress and make it conversational, but do NOT add facts the author didn't state or contradict their assessment.
  • index must match the card index in manifest.json (unchanged by reconcile).
  • intro and outro are REQUIRED and must be non-empty. If either is empty, the video's opening/closing title frame will be SILENT (no voiceover) — the TTS step skips empty text silently and the compose step has no audio to attach. Always write a 1-2 sentence intro and outro.

Step 6½ — Generate the card manifest table (file ↔ name ↔ tier ↔ narration)

After the narration is written, generate a human-readable mapping so you and the user can verify every image file maps to the right card name, tier, board position, and narration — no "which card_003.png was that?" confusion:

python <skill_dir>/scripts/build_card_manifest.py <work_dir>

Reads manifest.json (file + tier + board_position + board vs card label) + narration_script.json (label + narration), writes card_manifest.md — one row per card:

indeximage filetierboard_poscard namedetail?narration (preview)
0card_000.webp1哪吒之魔童降世第一名,哪吒之魔童降世。这部电影...

Rows where the board recognition and per-card recognition disagreed are shown as ⚠ board=<x> | card=<y> so you can resolve them. Show this table to the user in Step 7 review — it's the single source of truth for "which file is which card". If a row is wrong, fix it in manifest.json (label) or narration_script.json (narration) and re-run this script. The detail? column marks cards carrying an author detail explanation - at review, check those narrations actually reflect it instead of generic filler.

Step 7 — User review

Present the narration script in readable form; ask the user to confirm or modify. Revise until approved. Write the final version to narration_script.json.

Step 8 — Generate TTS audio

python <skill_dir>/scripts/tts_narration.py generate <work_dir>/narration_script.json -o <work_dir> [-v VOICE]

Voice by language:

  • Chinese: zh-CN-YunxiNeural (male, default) / zh-CN-XiaoxiaoNeural (female)
  • English: en-US-GuyNeural / en-US-JennyNeural
  • Japanese: ja-JP-NanamiNeural / ja-JP-KeitaNeural
  • List all: python <skill_dir>/scripts/tts_narration.py voices -l <lang_prefix>

Generates narration_<NNN>.mp3 per segment PLUS narration_intro.mp3 / narration_outro.mp3 from the (required, non-empty) intro/outro text. If intro/outro are empty it warns loudly — fix narration_script.json and re-run.

Step 9 — Compose video

python <skill_dir>/scripts/generate_video.py <work_dir> -o <output.mp4> [--resolution 1920x1080] [--fps 24] [--scroll-threshold 0.25]

Options:

  • --resolution 1920x1080 (landscape) or 1080x1920 (vertical/shorts)
  • --intro-duration 3.0 seconds for intro/outro (only used when no intro/outro audio is attached; with audio, the frame follows the audio's real length)

Features:

  • Background priority: board_hd.png (Step 1) → server thumb (Step 2) → board.png (Step 3).
  • Card order follows the reconciled manifest.json (board visual order).
  • Scrolling background: if the board is taller than the frame, scrolls top→bottom.
  • Vivid board background: the board stays full-color and SHARP during card narration - NEVER blur or darken it. The colorful board is the attraction; the card's own dark backing keeps it readable. (A blurred mid-frame had no pull.)
  • Card overlay: each card zoomed to center with a tier badge + label.
  • Subtitles: auto-generates subtitles.srt alongside the video, timed to the ACTUAL audio durations (intro/outro/segments) — not a fixed 3.0s guess.
  • Prints an audio summary at start (intro: attached | outro: attached | cards: N/N with audio); if intro/outro audio is missing it warns — that's the "silent title frame" failure mode, catch it here.
  • Cross-platform: Windows / macOS / Linux.

Step 10 — Deliver

Provide the output video + subtitles file to the user.

Dependencies

  • Python 3.10+
  • Pillow (usually pre-installed)
  • edge-tts (auto-installed; Microsoft online TTS, cross-platform)
  • moviepy>=2.0 + numpy (auto-installed; moviepy bundles ffmpeg)
  • playwright + Chromium (for the high-res board capture; auto-installed on first run of capture_board.py; first install downloads ~150MB browser)

Everything above installs once per Python environment, not once per run: each script imports first and pip-installs only what's missing, and capture_board.py downloads Chromium only when it isn't already on disk. A second run — or a fresh session — reuses what's there. Switching interpreters (a different venv, another machine) means installing into that environment once.

Cross-platform font detection in the render/compose scripts:

  • Windows: Microsoft YaHei (msyh.ttc)
  • macOS: PingFang SC
  • Linux: Noto Sans CJK

Multi-language

  1. Set "language" in narration_script.json.
  2. Choose a matching TTS voice.
  3. Subtitles are generated in the narration language.
  4. Card labels and tier names are preserved as-is.

Troubleshooting

  • No card images: the URL must be a published (not draft) post.
  • The video is only an intro and an outro, nothing in between: no card had an image on disk. generate_video.py now refuses to write that video and says so. Usually a TEXT-card board fetched before this was supported — re-run fetch_tierlist.py, which renders text: cards locally. Check its summary line: Cards: N total — X downloaded, Y text cards rendered, Z FAILED.
  • The board is cut off at the bottom (or the title bar is cut off at the top): older builds always scaled the board to frame width and cropped, so a board only slightly taller than the frame silently lost its last tier row. fit_board() now scrolls only when the board is >25% taller than the frame and otherwise fits it whole. Tune with --scroll-threshold; the log says contain (no scroll) or scroll Npx.
  • Portrait (--resolution 1080x1920) is mostly black: same root cause — a wide board scaled to 1080 wide is far shorter than 1920, and PIL pads the out-of-bounds crop with black. Fixed by the same fit_board().
  • Rendering takes far longer than the video is: every clip is a still frame, so most rendered frames are identical. --fps 12 roughly halves the time with no visible difference.
  • capture_board.py says "tier-grid not found": the TierVibe deploy hasn't shipped the data-testid="tier-grid" attribute yet. Use the 600px thumb fallback (Step 2) and report it. Board-first recognition (Step 4) then has no board to read — fall back to per-card-only (Step 5).
  • pip install fails with "externally-managed-environment": PEP 668, on Debian/Ubuntu system Python or Homebrew Python. The auto-install can't write to that interpreter. Re-run the same command with pip install --user <pkg>, or create a venv (python -m venv .venv + activate) and run the scripts from it.
  • TTS fails: needs network; edge-tts uses Microsoft's online service.
  • Video encoding fails: moviepy bundles ffmpeg; if issues, install ffmpeg.
  • Font missing: scripts fall back to a default font; install Noto Sans CJK on Linux.
  • Card unclear: ask the user to help identify specific cards.
  • Model can't recognize images: if you (the running model) have no image recognition, you can STILL finish the video as long as every image card has a non-empty detail - derive each label from its detail text (vision-free path, see the "Image-card boards" section above). Only cards with NO detail block you: do NOT fabricate names for them - stop and tell the user to switch to a multimodal model, add a detail (讲解) explanation to those cards in the post, or name them for you.

评论

加载中…