yapsnap README

The project's README from github.com/kouhxp/yapsnap. As one markdown file: README.md.

yapsnap

Snap any video URL or audio file into plaintext. No GPU. No cloud. One command.

Python License Platforms

yapsnap "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

That's it. You get a .txt next to your shell, transcribed on your CPU, in less time than it took the video to play.


Why yapsnap


Quickstart

# 1. ffmpeg on PATH (one-time, per OS — see below)
# 2. Install (from PyPI, or `pip install .` from a clone)
pip install yapsnap

# 3. Snap something
yapsnap https://www.tiktok.com/@user/video/7234567890123456789
yapsnap meeting.mp4 --timestamps
yapsnap interview.mp3 --diarize          # label speakers
yapsnap interview.mp3 --lang fr          # skip detection, force French
yapsnap podcast.mp3 -o ~/notes/episode.txt

The language is detected automatically — you don't have to say what you're feeding it.

The first run downloads the language-ID head (20 MB) and the ASR model for whatever language it hears (80 MB). Every run after is offline.


What it handles

Any URL yt-dlp understands works. The big ones:

Source Example
YouTube https://www.youtube.com/watch?v=...
YouTube Shorts https://www.youtube.com/shorts/...
X / Twitter https://x.com/user/status/.../video/1
TikTok https://www.tiktok.com/@user/video/...
Instagram Reels https://www.instagram.com/reel/.../
Direct media URL https://example.com/clip.mp4

Plus any local file ffmpeg can decode: .mp3, .mp4, .m4a, .wav, .webm, .mov, .mkv, .aac, .opus, .ogg, .flac, and friends.


Install

1. ffmpeg

OS Command
macOS brew install ffmpeg
Linux sudo apt install ffmpeg or sudo dnf install ffmpeg
Windows winget install ffmpeg or choco install ffmpeg

2. yapsnap

yt-dlp is installed as a dependency but is not version-pinned on purpose — sites change their extractors constantly, so the newest release is always the one you want. If a URL suddenly fails, pip install -U yt-dlp first.

From PyPI (recommended):

pip install yapsnap

From source:

git clone https://github.com/kouhxp/yapsnap
cd yapsnap
pip install .

Installs two equivalent commands on your PATH: yapsnap (canonical) and transcribe (alias, for when the name slips your mind).


Usage

# Local file
yapsnap path/to/audio.mp3

# Any video URL
yapsnap "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

# Sentence-level timestamps
yapsnap input.mp4 --timestamps

# Speaker labels ("who spoke when")
yapsnap interview.mp3 --diarize

# Speaker labels with a known speaker count (more reliable than auto-detect)
yapsnap call.mp3 --diarize --num-speakers 2

# Language is auto-detected; pin it to skip detection (or to override it)
yapsnap interview.mp3 --lang fr
yapsnap podcast.mp3 --lang de --timestamps

# Custom output path
yapsnap input.mp4 -o ./transcripts/talk.txt

# Don't speed audio up before transcribing (default is 1.4x, pitch preserved)
yapsnap input.mp4 --speed 1.0

# Keep the downloaded audio (URL inputs only)
yapsnap "https://..." --keep-audio

Output

Plaintext, UTF-8. Default location is ./transcripts/ (created if missing) under the current working directory; override with -o. For URL inputs the filename is derived from the video ID (dQw4w9WgXcQ_transcript.txt, etc.).

Without --timestamps — one paragraph of recognized text:

Welcome to the show. Today we're talking about transcription. Let's get started.

With --timestamps — one sentence per line, timed against the original audio:

[00:00] Welcome to the show.
[00:03] Today we're talking about transcription.
[00:08] Let's get started.

Timestamps stay in original-audio time even at --speed 1.4 or higher.

With --diarize — one sentence per line, each tagged with a speaker and timestamp:

SPEAKER_00 [00:00]: Welcome to the show.
SPEAKER_01 [00:03]: Glad to be here, thanks for having me.
SPEAKER_00 [00:08]: Let's get started.

Speaker numbers are assigned in order of appearance and are stable within a single run, but they carry no identity across files — SPEAKER_00 in one transcript is unrelated to SPEAKER_00 in another.


Flags

Flag Description
-o, --output Output .txt path. Default: ./transcripts/<input>_transcript.txt.
--timestamps Emit [MM:SS] sentence. lines instead of a single paragraph.
--diarize Label speakers (SPEAKER_00 [MM:SS]: …). Implies --timestamps.
--diarize-model Segmentation model: pyannote (default) or reverb. See below.
--diarize-model-dir Path to a local directory with diarization models. Bypasses auto-download.
--num-speakers Known speaker count for --diarize. Default -1 (auto-detect).
--speed Pre-transcription speedup factor, pitch preserved. Default 1.4.
--workers Chunks to decode in parallel processes. Default 0 (autodetect).
--threads ONNX threads per worker. Default 0 (autodetect). See Performance.
--keep-audio Keep the downloaded audio (URL inputs only).
--model Override the model directory. Also reads KROKO_MODEL env var.
--lang Language code (e.g. fr, de), or auto (default) to detect it from the audio. Auto-downloads the matching Kroko model. See Languages. Ignored if --model is given.

How it works

  1. Fetch. If the input is a URL, yt-dlp grabs the best audio-only stream to a temp directory. If it's a local path, this step is skipped.
  2. Identify. Under the default --lang auto, two 5-second windows — cut at roughly 15% and 55% into the file, each its own keyframe-seeked ffmpeg call — are decoded at original speed and run through a whisper-tiny language-ID head pruned to yapsnap's ten languages (INT8 ONNX, ~20 MB), via sherpa-onnx's spoken-language-ID API. If the two windows agree, that's the language; if not, they're concatenated and identified once more as a tiebreak. Skipped entirely when --lang CODE or --model is given.
  3. Decode. ffmpeg pipes the media into 16 kHz mono PCM. The optional atempo filter speeds it up without raising pitch.
  4. Recognize. A streaming Zipformer2 transducer (Kroko, INT8 ONNX, ~80 MB) eats the PCM in chunks. CPU-only. Greedy decode.
  5. Format. Plain text by default. With --timestamps, token timestamps are grouped on .!? into sentences and scaled back to original-audio time.

With --diarize, a second pass runs the audio (decoded at original speed) through a speaker-segmentation model and a speaker-embedding model, clusters the voiceprints into speakers, and tags each sentence with the speaker active at its start. All ONNX, all CPU.

No frame is sent anywhere. No state is kept between runs except the cached model.


Performance

yapsnap tunes itself to your CPU — you shouldn't need to touch any flags.

On first decode it detects your physical core count (not the logical/hyperthread count, which oversubscribes and runs slower) and splits the work into a few parallel decode processes that, together, use about one thread per physical core. On a 4-core laptop that's two workers of two threads each; on an 8-core machine, four workers of two threads. Short clips skip the split entirely and decode as a single stream, since chunking only pays off once there's enough audio to outweigh its per-chunk warmup.

Two knobs let you override the autotuning if you want to experiment:

--speed is the other lever: it shortens the audio before decoding, so higher values mean a faster run (at some accuracy cost on hard audio). The default 1.4 balances speed and readability.

YAPSNAP_THREADS overrides the detected core budget for a run if autodetection guesses wrong on an unusual machine.


Model & cache

Models are downloaded on first run to:

To use a different streaming transducer (other languages, larger Kroko variants, etc.), point --model at a directory containing encoder(.int8).onnx, decoder(.int8).onnx, joiner(.int8).onnx, and tokens.txt. Or set KROKO_MODEL in your environment.

The language-ID head lands in a kouhxp__whisper-tiny-lid/ subfolder of the same cache; each ASR model gets its own kouhxp__sherpa-onnx-streaming-zipformer-<lang>-kroko/ folder, so several languages can live side by side. If you use --diarize, the segmentation and embedding models download to diarization-models/. Everything is reused offline thereafter.

Set YAPSNAP_LID_MODEL_DIR to a directory holding lid-encoder.int8.onnx and lid-decoder.int8.onnx to bypass the language-ID download entirely.


Languages

yapsnap detects the spoken language by default (--lang auto) and downloads the matching Kroko model — nothing to pass:

yapsnap interview.mp3                     # figures out it's French, fetches the French model

Detection runs a whisper-tiny language-ID head (INT8 ONNX, ~20 MB, MIT) pruned to exactly these ten languages, through sherpa-onnx — no extra runtime dependency, same as diarization. It listens to two 5-second windows taken from different points in the file (never the whole recording), so it costs a fraction of a second and a couple of keyframe seeks even on a three-hour podcast. Near-silent windows are skipped; if the two windows disagree, they're identified together as a tiebreak.

Available language codes:

Code Language Code Language
de German iw Hebrew
en English nl Dutch
es Spanish pt Portuguese
fr French sv Swedish
it Italian tr Turkish

Pin a language to skip detection entirely — faster, and the right move when you already know or when the audio is code-switched:

yapsnap interview.mp3 --lang fr          # French
yapsnap podcast.mp3 --lang de            # German
yapsnap meeting.mp4 --lang es --timestamps  # Spanish with timestamps

--lang is ignored if --model is also given — an explicit model always wins.

When detection falls back

yapsnap transcribes with English and prints a note on stderr when detection can't commit:

Detection never aborts a run. If a fallback is wrong for your file, --lang CODE settles it.

Manual model setup

If you prefer to manage models yourself, or want to use a model not in the table above (Swiss German, larger Kroko variants, or any other sherpa-onnx streaming transducer), download it, unpack it into its own folder, and run:

# Per-run: pass the model folder explicitly
yapsnap interview.mp3 --model /path/to/kroko-french

# Or set it once as your default for the session
export KROKO_MODEL=/path/to/kroko-french
yapsnap interview.mp3

Kroko publishes streaming models for a growing list of languages on Hugging Face: https://huggingface.co/Banafo/Kroko-ASR/tree/main. --model skips language detection too.

Each model is single-language, so to work across several languages keep them in separate folders and switch with --model (or re-export KROKO_MODEL) as you go. Any other sherpa-onnx streaming transducer with the standard encoder / decoder / joiner / tokens.txt layout works too, not just the Kroko ones.


Diarization

--diarize adds speaker labels to the transcript — "who spoke when" — so each line is prefixed with SPEAKER_00, SPEAKER_01, and so on:

yapsnap interview.mp3 --diarize
SPEAKER_00 [00:00]: Welcome to the show.
SPEAKER_01 [00:03]: Glad to be here, thanks for having me.
SPEAKER_00 [00:08]: Let's get started.

It stays true to yapsnap's design: CPU-only, ONNX, no PyTorch, no extra runtime dependencies beyond the sherpa-onnx you already have. Two small models download once on first use (a speaker-segmentation model plus a speaker-embedding model) and cache alongside the ASR model.

How the labels are produced

--diarize implies --timestamps — the two share a clock. Transcription runs on the sped-up audio as usual, while diarization runs on the same source decoded at original speed (1.0x), because speeding audio up degrades both speaker-boundary detection and the voiceprint embeddings. Each transcript sentence is then matched to whichever speaker was active at its start time.

Because diarization needs sentence timestamps to attach labels to, --diarize will stop with an error if your sherpa-onnx build doesn't expose timestamp data, rather than silently dropping the speaker labels.

Speaker count

By default the number of speakers is detected automatically. Auto-detection is solid up to about seven speakers and degrades above that. If you know the count, pass it — it's more reliable:

yapsnap call.mp3 --diarize --num-speakers 2

Choosing a segmentation model

Model --diarize-model License Notes
pyannote 3.0 pyannote (default) CC-BY-4.0 Attribution only; the safe default.
Reverb v1 reverb Non-commercial Same architecture, fine-tuned for accuracy.
yapsnap panel.mp4 --diarize --diarize-model reverb

pyannote is the default because its license is clean for most uses. reverb (Rev's fine-tune of the same architecture) can be more accurate but is distributed under a non-commercial license — yapsnap prints a reminder the first time you download it. Check the Rev model card before using it for anything commercial.

Limits

To override the embedding model (for example if the default asset name ever changes), set YAPSNAP_EMBEDDING_MODEL to a different .onnx filename from the diarization model repo.

To skip auto-download entirely and use local diarization models, pass --diarize-model-dir pointing to a directory that contains the segmentation model (an extracted subdirectory with model.onnx) and the embedding .onnx file.


Notes & limits


License

Apache-2.0 for this project. The Kroko model is distributed under its own license — see https://huggingface.co/Banafo/Kroko-ASR. The language-ID head is derived from OpenAI's whisper-tiny and is MIT licensed. Powered by sherpa-onnx and yt-dlp.

The optional diarization models carry their own licenses, separate from yapsnap's: the default pyannote segmentation model is CC-BY-4.0 (attribution), the speaker-embedding model is Apache-2.0, and the opt-in reverb segmentation model (--diarize-model reverb) is non-commercial. If you use diarization, review the license of the model you select before relying on it.

yapsnap (kouhxp/yapsnap)

Searching Database…

这是什么?一个命令行工具,可以把任意视频链接或本地的音频/视频文件转成纯文字稿,全部在你自己的 CPU 上运行——不用 GPU、不连云端,一条命令搞定。
它是有界面的应用,还是命令行工具?纯命令行工具:安装后会在 PATH 里加入两个等价的命令——yapsnap 和它的别名 transcribe——在终端里把任意一个指向链接或文件路径即可。
怎么拿到我的第一份文字稿?先安装 ffmpeg,然后从 PyPI 上 pip install yapsnap。第一次运行会下载语言检测模型(约 20 MB)和对应的语音识别模型(约 80 MB);之后每次运行都完全离线。
实际使用中有什么容易出问题的地方?各网站会不断改动解析方式,而 yt-dlp 刻意不锁定版本,所以某个链接突然失效时,通常执行 pip install -U yt-dlp 就能修好。另外必须先装好 ffmpeg。
我的音频会被上传到什么地方吗?不会。抓取、解码和识别全都在本地完成;唯一的网络流量是把模型一次性下载到你的缓存里,而且没有 API 密钥,也没有用量限制。
它能接受哪些网站和文件格式作为输入?凡是 yt-dlp 支持的都行——YouTube、YouTube Shorts、X/Twitter、TikTok、Instagram Reels、直链媒体地址——再加上 ffmpeg 能解码的本地文件:.mp3、.mp4、.m4a、.wav、.webm、.mov、.mkv、.aac、.opus、.ogg、.flac 等等。
它能识别哪些语言?我需要自己指定吗?十种:英语、德语、西班牙语、法语、意大利语、希伯来语、荷兰语、葡萄牙语、瑞典语、土耳其语。--lang auto 会用两段 5 秒的音频样本自动检测语言;用 --lang fr 可以指定语言、跳过检测。如果检测不出结果,会回退到英语并在 stderr 上提示一句——运行不会中断。
对话里能标出谁在什么时候说话吗?可以——--diarize 会用分段模型加声纹嵌入模型给每句话标注 SPEAKER00、SPEAKER01……同样只用 CPU 跑 ONNX。说话人数自动检测(大约七人以内效果可靠),同一文件里编号是稳定的,但在不同文件之间不对应同一个真人。
文字稿输出出来是什么样的?UTF-8 纯文本 .txt 文件,默认放在当前目录的 ./transcripts/ 下(可用 -o 改)。默认是单个段落;加 --timestamps 会给每句话加上 [MM:SS],即使 2 倍速播放也准确;--diarize 自带时间戳,并在句前加上说话人标签。
Creator
kouhxp
Size
0.1 MB
Stars
379
Updated
CommitSep 15
Age
5mo
Commits · 90 Days