# Competitor Video Tagging

> 竞品KOL视频打标分析。给一个YouTube/TikTok视频链接，自动下载、AI分析内容与评论、 按11个维度结构化打标、写入飞书多维表格，低置信度自动通知人工复核。 用法: /kol <视频链接> | /kol check 触发词: 分析视频, 打标, 竞品分析, KOL分析, 视频分析, 标签, tag, 看看这个视频, 帮我分析一下

- Skill: `arcocodes/competitor-video-tagging` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add arcocodes/competitor-video-tagging`
- Raw SKILL.md: https://api.skillmd.com/api/skills/arcocodes/competitor-video-tagging/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: arcocodes (https://skillmd.com/u/arcocodes)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/arcocodes/competitor-video-tagging

---


# KOL Video Analysis

Analyze competitive KOL videos: download → frame extraction → visual analysis → structured tagging → write to Feishu.

## Constants

These values are hardcoded — do NOT read them from env vars:

```
LARK_APP_ID       = cli_a90f1add2f7adbd9
FEISHU_WEBHOOK    = https://open.feishu.cn/open-apis/bot/v2/hook/f843afd5-611b-4cc4-8f25-17c34c906fb0
BITABLE_APP_TOKEN = WEcDbjFnKa48YbsKa8qc8auQnlc
BITABLE_TABLE_ID  = tbl6azeK9h2l6ugm
```

## Step 0: Update Check (every run, silent)

If the skill directory is a git repo (not a symlink target check — check the resolved path), check for upstream updates:

```bash
SKILL_DIR="$(cd "${CLAUDE_SKILL_DIR}" && pwd -P)"
if [ -d "$SKILL_DIR/.git" ]; then
  git -C "$SKILL_DIR" fetch --dry-run 2>&1
fi
```

**If fetch shows new commits** → print a one-line notice and ask the user:
```
🔄 KOL Skill 有新版本可用。是否更新？（更新不会影响本次运行）
```

If user confirms → run `git -C "$SKILL_DIR" pull --ff-only`. If pull fails (local changes), print:
```
更新失败：本地有未提交的修改。请手动处理后重试。
```

If user declines or fetch shows nothing → proceed silently.

## Step 0b: Environment Gate (every run)

Run this silent preflight:

```bash
lark-cli api GET /open-apis/bitable/v1/apps/WEcDbjFnKa48YbsKa8qc8auQnlc/tables/tbl6azeK9h2l6ugm/fields \
  --as bot --profile kol-bot --jq '.data.total' 2>/dev/null
```

**If it returns a number** → environment is ready, proceed to Subcommand Routing silently. Do NOT print anything.

**If it fails** → run the setup flow below:

### Setup Flow

**Phase 1: Check CLI tools**

```bash
MISSING_TOOLS=""
command -v yt-dlp   >/dev/null || MISSING_TOOLS="${MISSING_TOOLS} yt-dlp"
command -v ffmpeg   >/dev/null || MISSING_TOOLS="${MISSING_TOOLS} ffmpeg"
command -v lark-cli >/dev/null || MISSING_TOOLS="${MISSING_TOOLS} lark-cli"
```

If any tools are missing, install them directly:
```bash
# for yt-dlp / ffmpeg:
brew install <missing>
# for lark-cli:
npm install -g lark-cli
```

**Phase 2: Check secrets (only two needed from user)**

Check env vars `LARK_APP_SECRET` and `RENOISE_API_KEY`:

```bash
test -n "$LARK_APP_SECRET" && test -n "$RENOISE_API_KEY"
```

If either is missing, ask the user to provide them **one at a time** using the AskUserQuestion tool:

- If `LARK_APP_SECRET` is missing → ask: "请输入飞书应用的 App Secret（找项目负责人获取）："
- If `RENOISE_API_KEY` is missing → ask: "请输入 Renoise API Key（在 Claude Code 中运行 /install-plugin renoise 安装后获取）："

After the user provides each value, **immediately** append it to `~/.zshrc` and load it:
```bash
echo 'export LARK_APP_SECRET="<user-provided-value>"' >> ~/.zshrc
export LARK_APP_SECRET="<user-provided-value>"
```
```bash
echo 'export RENOISE_API_KEY="<user-provided-value>"' >> ~/.zshrc
export RENOISE_API_KEY="<user-provided-value>"
```

**Phase 3: Configure lark-cli bot profile**

```bash
echo "$LARK_APP_SECRET" | lark-cli config init --app-id "cli_a90f1add2f7adbd9" --app-secret-stdin --name kol-bot
```

Retry the preflight. If it still fails, print:
```
飞书应用认证失败。请检查：
1. App Secret 是否正确
2. 飞书应用是否已开通「多维表格」权限
3. 多维表格是否已将该应用添加为协作者
联系项目负责人排查。
```

All `lark-cli` commands in this skill MUST use `--as bot --profile kol-bot`.

## Subcommand Routing

Parse the skill args to determine the action:

1. If args starts with `check` → run environment check (see Check section)
2. If args is a URL → run full pipeline
3. Otherwise → show usage: `/kol <youtube-or-tiktok-url>` or `/kol check`

## Platform Detection & Download

Check the URL pattern to determine platform and download tool:

| URL Pattern | Platform | Download Method |
|------------|----------|----------------|
| `youtube.com/watch?v=`, `youtu.be/`, `youtube.com/shorts/` | YouTube | yt-dlp |
| `tiktok.com` | TikTok | tikhub (via scripts/fetch-tikhub.sh if available) or yt-dlp |
| `instagram.com` | Instagram | Report "Instagram support coming soon" and stop |
| Other | — | Report "Unsupported platform" and stop |

## Full Pipeline

### Step 1: Download Video & Metadata

Create working directory:
```bash
VIDEO_ID="<extracted-video-id>"
WORK_DIR="/tmp/kol-work/$VIDEO_ID"
mkdir -p "$WORK_DIR"
```

**YouTube:**
```bash
bash ${CLAUDE_SKILL_DIR}/scripts/fetch-youtube.sh "<url>"
```
Outputs to `/tmp/kol-work/<video-id>/`:
- `metadata.json` — yt-dlp JSON with all video/channel data and comments
- `video.mp4` — downloaded video file
- `subtitles.srt` — YouTube subtitles (auto-generated or manual, if available)

**TikTok:**
```bash
yt-dlp -f best -o "$WORK_DIR/video.mp4" --write-info-json -o "$WORK_DIR/metadata.json" "<url>"
```

Read `metadata.json` and extract metadata fields:

| yt-dlp JSON key | Bitable Field | Format |
|-----------------|---------------|--------|
| `channel` or `uploader` + `channel_url` | KOL/账号 | `{"link":"<channel_url>","text":"<channel_name>"}` |
| `channel_follower_count` | 粉丝量 | integer string, e.g. `"298000"` |
| `title` | 内容标题 | plain string |
| `duration` | 视频时长 | `"MM:SS"`, e.g. `"13:09"` |
| `upload_date` | 发布日期 | `"YYYY-MM-DD"` |
| `view_count` | 视频播放量 | integer string, e.g. `"40195"` |
| `language` or detect from title/description | 语言 | plain string |

Also set: 平台 = detected platform, 内容链接 = `{"link":"<url>","text":"<url>"}`.

### Step 2: Frame Extraction & Visual Analysis

Extract key frames from the video for visual analysis:

```bash
bash ${CLAUDE_SKILL_DIR}/scripts/analyze-video.sh "$WORK_DIR/video.mp4"
```

This produces:
- `$WORK_DIR/frames/frame_NNN.jpg` — extracted frames (every 3 seconds)
- `$WORK_DIR/audio.srt` — transcript (uses yt-dlp subtitles if available, otherwise falls back to Whisper)

**Primary analysis path — Gemini:**

```bash
GEMINI_SCRIPT="${CLAUDE_PLUGIN_ROOT:-/Users/l13/.claude/plugins/cache/renoise-plugins-official/renoise/0.2.1}/skills/gemini-gen/scripts/gemini.mjs"
```

Send the video file directly to Gemini for comprehensive analysis:
```bash
node "$GEMINI_SCRIPT" --file "$WORK_DIR/video.mp4" --mode video-script
```

Then run a structured analysis prompt with the tagging dimensions:
```bash
node "$GEMINI_SCRIPT" --file "$WORK_DIR/video.mp4" \
  "Analyze this video for content tagging. For each dimension below, identify the best match:

1. 应用场景 (Application Scenario): What is this video used for? Options: 付费广告投放, 电商站内转化, 社媒账号运营, 品牌与企业传播, 影视与娱乐内容, 音乐与演出内容, 艺术与动漫创作, 个人与实验创作, 其他
2. 内容方向 (Content Direction): What is the core narrative about? Options: 商品展示, 功能演示, 使用教程, 产品测评/对比, 用户体验/口碑, 痛点解决, 成本/效率对比, 变现/赚钱叙事, 品牌故事, 剧情/娱乐叙事, 创意/视觉展示, 功能发布/新品预热, 其他
3. 呈现形式 (Presentation Format, multi-select): How is the content presented? Options: 人物口播, UGC体验分享, 商品展示, 屏幕录制, 操作演示, 教程讲解, 前后对比, 多产品横评, 案例展示, 剧情演绎, 采访/对谈, 素材混剪, 动画/动态图形, 音乐视觉/MV, 静态图文, 其他
4. 制作主体 (Production Subject): Who appears? Options: 真人出镜, AI虚拟人/AI网红/虚拟IP, 实拍为主（无真人出镜，录屏为主）
5. 视觉风格 (Visual Style): What is the visual aesthetic? Options: UGC真实感, 电影感, 动漫风, 写实商业, 艺术化, 其他
6. 媒介规格 (Media Spec): Options: 静态图片, 短视频, 长视频, 横屏, 竖屏
7. 证据卖点 (Selling Points): What product advantages are highlighted? Options: 生成质量, 角色一致性, 产品一致性, 生成速度, 操作简单, 模型丰富, 成本更低, 批量生产, 广告转化效果, 免费/低门槛, 模板丰富, 工作流自动化, 多语言, 商用能力, 其他
8. CTA (Call to Action): What actions are viewers asked to take? Options: 点击链接, 访问官网, 免费试用, 注册账号, 订阅/付费, 使用优惠码, 评论关键词, 私信获取, 下载资料, 加入社群, 关注账号, 点赞/收藏/转发, 观看完整教程, 无明确CTA, 其他
9. 品牌/竞品: Which AI video tool brand is featured or promoted? Options: Higgsfield, OpenArt, SeaArt, Arcads, Pollo
10. 项目来源: Is this 自有项目, 客户委托, 合作项目, or 竞品样本?
11. 语言: What language is the video primarily in?

For dimensions with 主要/次要: identify both if applicable. 主要 is the core narrative; 次要 must have clear screen time, otherwise leave empty.
For multi-select dimensions: list all that apply.
Also assess: how confident are you in this tagging overall (1-10 scale)? If below 5, explain what's ambiguous."
```

**Fallback path — Frame-by-frame analysis:**

If Gemini direct video analysis fails, analyze extracted frames in batches of 10:
```bash
node "$GEMINI_SCRIPT" --file frame_001.jpg --file frame_002.jpg ... --file frame_010.jpg \
  "These are sequential frames from a video (one frame every 3 seconds). Describe the content, visual style, presentation format, and any text/CTA visible."
```

Read the `.srt` subtitle file content directly. Combine frame analysis + subtitle text to produce the analysis.

### Step 3: Tagging

Read `${CLAUDE_SKILL_DIR}/references/tagging-rules.md` for the complete tagging dictionary.

Based on the analysis from Step 2, assign tags for each dimension. Rules:

1. **Only use values defined in tagging-rules.md** — never invent new values
2. **主要/次要 fields**: 主要 = core narrative; 次要 must have clear screen time, otherwise leave empty; the two MUST be different
3. **Multi-select fields**: join values with `｜` (full-width pipe), no sorting, no duplicates
4. **置信度**: assign 1-10 integer based on overall tagging confidence
5. **Agent的困惑**: if 置信度 < 5, MUST explain what's ambiguous (see tagging-rules.md for format)

Build the fields object mapping analysis results to Bitable field names:

```
记录ID          → auto-generate or leave to system
品牌/竞品        → SingleSelect: "Higgsfield" | "OpenArt" | "SeaArt" | "Arcads" | "Pollo"
平台            → "YouTube" | "TikTok" | etc.
KOL/账号        → {"link":"<channel-url>","text":"<channel-name>"} (Url field)
粉丝量          → follower count as integer string, e.g. "298000" (not "29.8万" or "298K")
内容标题         → video title
内容链接         → {"link":"<url>","text":"<url>"} (Url field)
视频时长         → "MM:SS" format, e.g. "13:09" (not "13m 09s")
视频播放量       → view count as integer string, e.g. "40195" (not "4万" or "40.2K")
语言            → detected language
发布日期         → "YYYY-MM-DD"
项目来源         → tagged value
应用场景-主要     → tagged value
应用场景-次要     → tagged value or empty
内容方向-主要     → tagged value
内容方向-次要     → tagged value or empty
呈现形式（多选）   → "value1｜value2｜..."
制作主体（单选）   → tagged value
视觉风格（多选）   → "value1｜value2" or single value
媒介规格（多选）   → "value1｜value2" or single value
证据卖点-主要     → primary selling point
证据卖点-次要     → secondary selling point or empty
CTA（多选）      → "value1｜value2｜..."
置信度（1-10）    → confidence score as string
Agent的困惑      → confusion explanation (required if confidence < 5)
评论分析         → comment analysis summary (from Step 3b)
```

### Step 3b: Comment Analysis

Extract comments from `metadata.json` (fetched in Step 1).

先看评论数量，结合播放量给出受众互动判断（如：11000+播放仅2条评论，说明受众参与度低、社区粘性弱）。然后再分析评论内容：

1. **核心观点** — 评论中反复出现的观点或情绪倾向
2. **用户痛点** — 观众提到的问题、吐槽或未被满足的需求
3. **产品反馈** — 对视频中展示的产品/工具的具体评价（正面或负面）
4. **竞品对比** — 评论中是否提到其他竞品工具？怎么说的？
5. **需求信号** — 功能请求、"要是能..."之类的信号

根据实际评论内容如实总结。评论少就简要概括能看出什么，评论多就详细展开。不需要凑字数，有什么说什么。

Output format (plain text, written to 评论分析 field):
```
受众互动：<对评论数量的判断，如"XX次播放仅X条评论，受众参与度较低">
核心观点：<summary>
用户痛点：<summary or "无">
产品反馈：<summary or "无">
竞品对比：<summary or "无">
需求信号：<summary or "无">
（基于 <N> 条评论）
```

If the video has no comments, write `"无评论"`.

### Step 4: Write to Feishu

Read `${CLAUDE_SKILL_DIR}/references/bitable-schema.md` for field IDs and value formats.

First, check if a record with the same URL already exists:
```bash
lark-cli api GET /open-apis/bitable/v1/apps/WEcDbjFnKa48YbsKa8qc8auQnlc/tables/tbl6azeK9h2l6ugm/records \
  --as bot --profile kol-bot --params '{"filter":"CurrentValue.[内容链接]=\"<url>\"","page_size":"1"}'
```

If a record exists, use PATCH to update (preserve human-added 备注):
```bash
lark-cli api PATCH /open-apis/bitable/v1/apps/WEcDbjFnKa48YbsKa8qc8auQnlc/tables/tbl6azeK9h2l6ugm/records/<record_id> \
  --as bot --profile kol-bot --data '{"fields":{ ... }}'
```

If no record exists, use POST to create:
```bash
lark-cli api POST /open-apis/bitable/v1/apps/WEcDbjFnKa48YbsKa8qc8auQnlc/tables/tbl6azeK9h2l6ugm/records \
  --as bot --profile kol-bot --data '{"fields":{ ... }}'
```

**IMPORTANT field value notes:**
- Most fields are Text (type=1) → plain string values
- 内容链接 is Url (type=15) → `{"link":"<url>","text":"<url>"}`
- 呈现形式/制作主体/视觉风格 are SingleSelect (type=3) → string matching an existing option
- For SingleSelect "multi-select" fields (呈现形式, 视觉风格): the value is a single option string with `｜` separators (e.g. `"人物口播｜屏幕录制"`). Check if the exact option already exists; if not, pick the closest existing option or write as Text if the field allows

### Step 5: Escalation (if confidence < 5)

If 置信度 < 5, send escalation notification:

```bash
curl -s -X POST "https://open.feishu.cn/open-apis/bot/v2/hook/f843afd5-611b-4cc4-8f25-17c34c906fb0" \
  -H 'Content-Type: application/json' \
  -d '{
    "msg_type": "interactive",
    "card": {
      "header": {
        "title": { "tag": "plain_text", "content": "KOL视频打标 - 低置信度" },
        "template": "orange"
      },
      "elements": [
        {
          "tag": "markdown",
          "content": "**视频:** [<title>](<url>)\n**频道:** <channel>\n**置信度:** <score>/10\n\n**Agent困惑:**\n<confusion_text>"
        },
        {
          "tag": "action",
          "actions": [
            {
              "tag": "button",
              "text": { "tag": "plain_text", "content": "查看飞书表格" },
              "url": "https://kcnz0vt5pezm.feishu.cn/base/WEcDbjFnKa48YbsKa8qc8auQnlc?table=tbl6azeK9h2l6ugm",
              "type": "primary"
            }
          ]
        }
      ]
    }
  }'
```

If the curl request fails, print a warning but do not block the pipeline.

### Step 6: Cleanup & Report

Remove the temp directory `/tmp/kol-work/<video-id>/` on success.

Print a summary:
```
=== KOL Analysis Complete ===
Video: <title>
Channel: <channel> (<follower_count> followers)
Platform: <platform>
Views: <view_count>
Confidence: <score>/10
Feishu record: <created/updated>
```

If confidence < 5, also print:
```
⚠ Low confidence — Agent困惑 has been logged.
```

On failure at any step, keep temp files and print:
```
ERROR: Pipeline failed at <step>. Temp files kept at /tmp/kol-work/<video-id>/ for debugging.
```

## Check (`/kol check`)

Verify each dependency and report status:

```bash
echo "=== KOL Skill Environment Check ==="
command -v yt-dlp    && echo "✓ yt-dlp $(yt-dlp --version)"    || echo "✗ yt-dlp — brew install yt-dlp"
command -v ffmpeg    && echo "✓ ffmpeg installed"               || echo "✗ ffmpeg — brew install ffmpeg"
command -v whisper   && echo "✓ whisper installed"              || echo "⚠ whisper — will auto-install on first run"
command -v lark-cli  && echo "✓ lark-cli installed"             || echo "✗ lark-cli — npm install -g lark-cli"
test -n "$LARK_APP_SECRET"      && echo "✓ LARK_APP_SECRET set"       || echo "✗ LARK_APP_SECRET not set"
test -n "$RENOISE_API_KEY"      && echo "✓ RENOISE_API_KEY set"       || echo "✗ RENOISE_API_KEY not set"
```

Verify lark-cli bot auth and bitable access:
```bash
echo "$LARK_APP_SECRET" | lark-cli config init --app-id "cli_a90f1add2f7adbd9" --app-secret-stdin --name kol-bot 2>/dev/null
lark-cli api GET /open-apis/bitable/v1/apps/WEcDbjFnKa48YbsKa8qc8auQnlc/tables/tbl6azeK9h2l6ugm/fields --as bot --profile kol-bot --jq '.data.total'
```

