Metadata-Version: 2.4
Name: nichescan
Version: 0.1.0
Summary: Quantify a short-video niche: transcribe what top creators actually say, rank by save-intent instead of likes.
Project-URL: Homepage, https://github.com/JustinXai/nichescan
Project-URL: Issues, https://github.com/JustinXai/nichescan/issues
Author: JustinXai
License: MIT
License-File: LICENSE
Keywords: asr,content-analysis,douyin,hook,mcp,short-video,tiktok,transcript
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Multimedia :: Video
Classifier: Topic :: Text Processing :: Linguistic
Requires-Python: >=3.10
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Provides-Extra: mcp
Requires-Dist: mcp>=1.2.0; extra == 'mcp'
Provides-Extra: report
Requires-Dist: jinja2>=3.1; extra == 'report'
Description-Content-Type: text/markdown

# nichescan

> **You are ranking by likes, so you are studying the wrong videos.**
> Short-video search APIs never return play counts. nichescan ranks a whole niche
> by **save intent** and reads what creators **actually said** — not their captions.

[English](#english) · [中文](#中文)

```bash
uvx nichescan rank ./data/*.jsonl        # no API key, no ffmpeg, runs now
```

<p align="center">
  <img src="docs/images/report-card.png" width="88%" alt="nichescan report: openings ranked by save intent">
</p>

<p align="center">
  <em>Real output on 60 clips. Highest reach was <code>money-opportunity</code>;
  highest save intent was <code>named-deliverable</code> — copying the
  most-liked clip would have been the wrong move.</em>
</p>

---

## English

### The two mistakes almost every content tool makes

**1. Ranking by likes measures reach, not usefulness.**
A like is one tap. A save means *"I want this again later"* — and revisiting is
what precedes buying. Measured on a real 55-clip cohort in the enterprise-AI
niche:

| Opening style | Mean likes | Mean saves per like |
|---|---:|---:|
| Tutorial / method | 147,336 | **0.81** |
| Money opportunity | 362,146 | 0.49 |
| Direct question | 119,011 | 0.24 |
| Trend prediction | 52,701 | 0.18 |
| Workplace comedy | 12,950 | 0.46 |

The money-opportunity clips won on likes by 2.5x — and lost on save intent.
Six clips in that cohort were **saved more often than liked** (ratio above 1.0).
Like counts cannot see any of this.

**2. Captions are not speech.** The title and the spoken first three seconds are
routinely different. Analysing titles means analysing the wrong object, so
nichescan transcribes the audio.

### Install

```bash
uvx nichescan --help              # zero install
pipx install nichescan            # or keep it around
pip install "nichescan[mcp]"      # with MCP server
```

The core package has **no dependencies**. `rank` works immediately.
`scan` additionally needs `ffmpeg` and an ASR key.

### Use it

nichescan does not crawl. Point it at whatever your crawler produced —
JSONL or CSV, from [MediaCrawler](https://github.com/NanmiCoder/MediaCrawler)
or anything else.

```bash
# 1. Metrics only. No key, no ffmpeg.
nichescan rank ./data/*.jsonl --days 60 --top 60

# 2. Rank by intent rather than reach
nichescan rank ./data/*.jsonl --sort save_ratio

# 3. Full pipeline: download, transcribe, aggregate
export DASHSCOPE_API_KEY=sk-...
nichescan scan ./data/*.jsonl --days 60 --top 60

# 4. Re-render without re-downloading
nichescan report -o ./nichescan-out

# 5. Render the shareable card shown above
nichescan render -o ./nichescan-out
```

Output:

```
nichescan-out/
├── shortlist.csv      ranked, with save_ratio and intent_band
├── transcripts.jsonl  resumable; interrupted runs pick up where they stopped
└── report.md          openings aggregated by save intent
```

### What the report tells you

```
| Opening pattern     | Clips | Mean likes | Mean save/like | What it promises        |
|---------------------|------:|-----------:|---------------:|-------------------------|
| method-promise      |     8 |    147,336 |           0.81 | A repeatable method     |
| already-happening   |     4 |    362,146 |           0.49 | An existing fact        |
| abstract-concept    |     3 |     88,120 |           0.08 | Opens on abstraction    |
```

Plus two lists that matter more than the table: **openings saved more than
expected**, and **popular but disposable** — structures to avoid even though
their like counts look good.

### ASR providers

| Provider | Flag | Key | Notes |
|---|---|---|---|
| Alibaba Qwen ASR | `--provider dashscope` | `DASHSCOPE_API_KEY` | Best Chinese accuracy in our tests |
| OpenAI-compatible | `--provider whisper` | `OPENAI_API_KEY` | Any compatible endpoint |
| FunASR (local) | `--provider funasr` | none | Free, heavier, weaker on proper nouns |

Long clips are segmented automatically (API duration caps) and oversized
payloads are compressed (base64 inflates bytes by a third).

### MCP server

Works in Claude Desktop, Cursor, Cherry Studio — anything speaking MCP.

```json
{
  "mcpServers": {
    "nichescan": {
      "command": "uvx",
      "args": ["--from", "nichescan[mcp]", "nichescan-mcp"],
      "env": { "DASHSCOPE_API_KEY": "sk-..." }
    }
  }
}
```

| Tool | Needs a key | Does |
|---|:--:|---|
| `rank_niche` | no | Rank records by save intent |
| `explain_metrics` | no | Explain the bands and why likes mislead |
| `classify_opening` | no | Label one opening line |
| `transcribe_clips` | yes | Download and transcribe real speech |
| `analyse_niche` | no | Aggregate openings across the cohort |

Then just ask: *"rank this niche by save intent and tell me which openings to copy"*.

### Claude Code skill

```bash
git clone https://github.com/JustinXai/nichescan
cd nichescan && bash install_skill.sh
```

### Python API

```python
from nichescan import load_records, shortlist, analyse, render_markdown

records = load_records(["data/*.jsonl"])
picked = shortlist(records, top=60, sort_by="save_ratio")
print(render_markdown(analyse(picked, transcripts)))
```

### Honest limits

- The engagement score is a **proxy**, not a play count. Nothing here can
  reconstruct plays, and any tool claiming otherwise is guessing.
- Pattern labels are lexical heuristics. They point you at cohorts worth
  reading; they do not judge quality.
- Save ratios vary by platform and niche. Recompute yours instead of importing
  the numbers above.
- Respect each platform's terms and each creator's rights. Study structure;
  do not republish other people's words.

---

## 中文

### 几乎所有内容工具都在犯的两个错

**第一，按点赞排序衡量的是传播，不是有用。**
点赞只是一次点击，收藏意味着「我以后还要用」——而反复回看才是成交的前置行为。
在企业AI赛道 55 条真实样本上实测：

| 开头类型 | 平均点赞 | 平均藏赞比 |
|---|---:|---:|
| 教程 / 给方法 | 147,336 | **0.81** |
| 财富机会 | 362,146 | 0.49 |
| 直接提问 | 119,011 | 0.24 |
| 趋势预测 | 52,701 | 0.18 |
| 职场段子 | 12,950 | 0.46 |

财富机会型点赞高出 2.5 倍，但收藏意图明显更低。这批样本里有 **6 条收藏数超过点赞数**。
只看点赞，这些全都看不见。

**第二，标题不等于口播。** 标题和真实前三秒经常完全不同。
只分析标题等于分析错了对象，所以 nichescan 直接转录音频。

### 安装

```bash
uvx nichescan --help              # 免安装直接跑
pipx install nichescan
pip install "nichescan[mcp]"      # 带 MCP server
```

核心包**零依赖**，`rank` 立刻能用。`scan` 需要 `ffmpeg` 和一个语音识别 key。

### 使用

nichescan **不做爬取**，它读你爬虫的产出（JSONL 或 CSV），
配合 [MediaCrawler](https://github.com/NanmiCoder/MediaCrawler) 等工具使用。

```bash
# 只算指标，不需要 key 和 ffmpeg
nichescan rank ./data/*.jsonl --days 60 --top 60

# 按收藏意图排序，而不是按传播
nichescan rank ./data/*.jsonl --sort save_ratio

# 完整流水线：下载 → 转录 → 汇总
export DASHSCOPE_API_KEY=sk-...
nichescan scan ./data/*.jsonl --days 60 --top 60
```

报告除了统计表，还会给两个更重要的清单：**收藏率异常高的开头**，
以及**点赞高但收藏极低的结构**——后者是即使数据好看也不该抄的写法。

### 说明与边界

- 互动分是**代理指标**，不是播放量。任何声称能还原播放量的工具都在猜。
- 开头分类是词法启发式，用于定位值得细读的样本，不构成质量评判。
- 藏赞比因平台和赛道而异，请用自己的数据重新计算。
- 遵守平台规则与创作者权益：学结构，不要搬运原文。

---

## License

MIT
