Download podcast audio and transcribe it into Markdown. Supports Xiaoyuzhou, Ximalaya, direct audio URLs, local audio, and RSS batch workflows.
By default, SenseVoice-Small is used locally (the chubby_common/funasr.py wrapper shared with video-type skills). Optional cloud backends are Alibaba Cloud Bailian DashScope's qwen3-asr-flash and Groq's whisper-large-v3-turbo; they are enabled only after the user explicitly chooses them; audio will be sent to the cloud and may incur charges.
Environment Requirements
Run the following commands in this skill directory; Python 3.11 or newer is recommended:
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install -r requirements.txt
# macOS: brew install ffmpeg
# Ubuntu: sudo apt install ffmpeg
The local model needs to be downloaded on first use. Using only cloud backends does not require the funasr local dependency; unsupported audio container conversion may still require ffmpeg. Cloud keys are configured through secure environment settings and must not be written into command arguments, transcripts, or version repositories.
Single Episodes and Batch
python3 scripts/transcribe.py "https://www.xiaoyuzhoufm.com/episode/xxxxx" ./output \
--provider local
python3 scripts/transcribe.py "/你的音频目录/episode.mp3" ./output \
--provider local --language zh
python3 scripts/batch_transcribe.py --rss-url "替换为实际 RSS 地址" \
--output ./output --count 10 --provider local
Replace the example URL with the actual source. For single-episode pages, it will try to extract the audio link; if extraction fails, use a direct audio URL or a local file instead. The last line of standard output is the path of the successfully generated Markdown.
Automatic download only accepts direct public HTTP(S) URLs, disables proxies and redirects, and rejects local/private network addresses. For sources that require redirects or proxies, download the audio yourself first, then pass in the local file path.
Local Model Choice: SenseVoice-Small vs Qwen3-ASR-0.6B
--provider local supports two local models:SenseVoiceSmall(default) and qwen3-asr-0.6b:
python3 scripts/transcribe.py "/你的音频目录/episode.mp3" ./output --provider local
python3 scripts/transcribe.py "/你的音频目录/episode.mp3" ./output --provider local --model qwen3-asr-0.6b
qwen3-asr-0.6b is an optional heavy dependency, not included in the default installation, and requires manual pip install qwen-asr transformers torch。
The following comparison data comes from an actual test in 2026-10 on a MacBook Pro (Apple M3 Pro, CPU only) with a 5-minute Chinese podcast:
| SenseVoice-Small (default) | Qwen3-ASR-0.6B (optional) | |
|---|---|---|
| Speed | RTF 0.11 (about 33 seconds for 5 minutes of audio) | RTF 0.80 (about 4 minutes, about 7x slower) |
| Proper nouns | Average ("rock tea" mistaken as "salt tea", inconsistent person names) | More stable ("rock tea" and person names recognized consistently) |
| Main risks | Output includes emotion tags mixed in; formal transcripts need cleanup | Risk of hallucinated rewriting ("gold processing plant" → "emperor family"); no ITN, numbers are output as full words |
| Long audio | VAD automatic segmentation, stable | The entire segment goes into the model; this backend has set max_new_tokens to 4096 to avoid truncation |
| Use cases | Default choice for CPU, friendly to long podcasts | GPU machines, or content sensitive to proper noun/person name accuracy |
The two are comparable in quality, with no generational gap. For CPU scenarios, keep the default SenseVoice-Small; choose Qwen3-ASR-0.6B only when you have a GPU or are sensitive to proper nouns.
Optional Cloud Transcription
| Backend | Credential environment variable | Default model |
|---|---|---|
local |
None | SenseVoiceSmall(only used for metadata recording) |
dashscope |
DASHSCOPE_API_KEY |
qwen3-asr-flash |
groq |
GROQ_API_KEY |
whisper-large-v3-turbo |
Configure DASHSCOPE_API_KEY or GROQ_API_KEY and then:
python3 scripts/transcribe.py "/你的音频目录/episode.mp3" ./output \
--provider dashscope --cloud-timeout 1800 \
--state-dir "$HOME/.local/state/chubbyskills/podcast"
python3 scripts/transcribe.py "/你的音频目录/episode.mp3" ./output \
--provider groq --cloud-timeout 1800 \
--state-dir "$HOME/.local/state/chubbyskills/podcast"
--provider takes precedence over PODCAST_TRANSCRIBE_PROVIDER, and if neither is specified, use localThe batch entry point supports the same provider, model, language, wait, and state directory parameters, and explicitly passes the final options to the single-episode process.
Both cloud backends are synchronous interfaces. DashScope is limited tono more than 10MB after encoding and no more than 5 minutes in duration(the client rejects when the original file exceeds 7 MiB and prompts to switch to local SenseVoice). The Groq free tier has a 25MB per-file limit,long audio reaching the limit is automatically segmented: ffmpeg cuts it into 20-minute segments (about 9.6MB per segment), transcribes segment by segment, then concatenates them in order, with timestamps automatically accumulating offsets; segment progress is persisted segment by segment, so rerunning the same command after an interruption resumes from the breakpoint without resubmitting completed segments; on encountering 429, it waits according to Retry-After/exponential backoff. The segment timestamps returned by Groq are retained as an appendix. Segmentation and container conversion require local ffmpeg。
Cloud task recovery
The default state directory is ~/.local/state/chubbyskills/podcast; when XDG_STATE_HOME is set, the chubbyskills/podcastwithin it is used. It can also be specified with CHUBBY_PODCAST_STATE_DIR or --state-dir . The directory contains tasks and transcription content, and should be stored according to the privacy requirements of the audio content.
When the same audio, provider, model, language, and service address are run again, existing tasks continue to be queried; completed results can be re-exported. When the submission result is unclear or the server reports a failure, it does not automatically resubmit. For ordinary network retries, simply keep the state directory and rerun the original command.
--resubmit explicitly creates a new task and may result in duplicate billing. Do not use it to solve a mere polling timeout. Deleting the state directory or changing the input configuration may also cause the reuse conditions to be lost. A client timeout does not equal a server-side cancellation, nor does it mean there was no billing.
For the complete repository usage instructions, unified ingestion, and verification scope, seeCloud transcription instructions。
Outputs and limitations
The output is Markdown with frontmatter, source, and transcription backend markers. When the backend returns usable segments, timestamps are retained; when there is no time information, no timeline is fabricated.
- Local SenseVoice-Small outputs only plain full text and does not generate segment-by-segment timestamps (the segment count is recorded as 1).
- Local CPU inference time is affected by audio length and machine configuration.
- Transcription may contain errors in proper nouns, numbers, or sentence segmentation; verify against the original audio before quoting.
- Real cloud availability, account permissions, audio compatibility, and costs still require independent acceptance testing.
- This skill does not provide a general guarantee of speaker diarization.
Contributions and references
The optional cloud transcription requirement originally came from PR #3 by binyangzhu000-sudo and PR #5 by Anil-matcha(Atlas / MuAPI experimental backend, now superseded by the DashScope backend, attribution retained).
- FunAudioLLM/SenseVoice
- Alibaba Cloud Bailian Qwen-ASR API reference
- Groq Speech to Text documentation
Compliance statement
Please comply with the terms of the source platforms and respect content copyright, and control request frequency. Before cloud processing, confirm that you have the right to submit the audio to the chosen service; downloading and transcription do not change the copyright ownership of the original content.