VoiceDrop inbox (jianshuo.dev/files on VoiceDrop-*.m4a) → transcribe one by one → hand to wjs-mining-articles to produce 公众号 drafts. This is the Mac-side closed loop for the VoiceDrop iOS app (record on open, upload on stop).
This skill's own output = ① 公众号 drafts (~/code/wechat-publish/) + ② local audio/SRT archive (~/code/voicedrop/archive/) + ③ processing markers on R2 (articles/<stem>.json or .empty) + ④ a batch report (how many processed, how many articles each produced, which were marked as no speech and why, how many remain unprocessed). The full interface contract is at agents/interface.yaml。
Core Principle
Reuse, don't rewrite. This skill only does the two things that don't exist on their own:inbox in/out(list/download/mark) andper-item orchestration. Transcription goes to wjs-transcribing-audio, article writing goes to wjs-mining-articles, not one line is rewritten.
R2 is never deleted; marker files indicate processing status. Audio stays on R2 until the user deletes it in the app. "Unprocessed" = no articles/<stem>.json(article written) or articles/<stem>.empty(no speech) marker yet of VoiceDrop-*.m4a;list automatically lists only unprocessed ones. Once an itemsucceedsand becomes an article, write mark-done,no speech/corruptedwrite mark-empty——both prevent this item from being processed again.never delete(delete is reserved for the user to manually clean up in the app).
When This Skill Fires
- User says "process VoiceDrop recordings", "dig new recordings into articles", "process my dictations"
- User runs
/wjs-mining-voicedrop
When NOT to use
- already has SRT → directly
wjs-mining-articles - audio not in R2 inbox(local loose files) → directly
wjs-transcribing-audioto produce SRT, thenwjs-mining-articles - the bucket contains non-recording files uploaded by other machines → this skill only recognizes the
VoiceDrop-*.m4aprefix, touches nothing else
Prerequisites
~/code/.envcontainsFILES_TOKEN(inbox authentication) and Volcano ASR creds (VOLC_ASR_*/VOLC_TTS_*, for transcription).set -a; source ~/code/.env; set +a。
Workflow
The only new code:scripts/voicedrop-inbox.sh(list / download / mark-done / mark-empty / delete, token is read from ~/code/.env at runtime, never committed to code).list lists only unprocessed;mark-done/mark-empty writes processing markers;delete is only for manual cleanup, the article-writing flow doesn't call it.
Step 0 · Locate script + load environment (does not depend on current directory)
INBOX=~/.claude/skills/wjs-mining-voicedrop/scripts/voicedrop-inbox.sh
set -a; source ~/code/.env; set +a # FILES_TOKEN + 火山 ASR creds
Call the script with the absolute path $INBOX ——do notwrite it as scripts/voicedrop-inbox.sh, that relies on the hidden assumption that "the person happens to be in the skill root directory", and it breaks when the directory changes.
Step 1 · List the inbox
"$INBOX" list # 打印未处理的 VoiceDrop-*.m4a,一行一个
- Commandexits non-zero(network unreachable / token invalid) → report "inbox unreachable or FILES_TOKEN invalid, check
~/code/.env" and stop,do not enter the loop。 - Output empty → report "no new recordings in the inbox" and end.
- Non-empty → get this batch's file names.
Step 2 · Close the loop one by one (serial, finish one before the next)
Serial。Batch resilience: if any single step of one item fails → log the reason, skip to the next item, never abort the whole batch, never leave an item unmarked. Each recording must ultimately end up in one of three terminal states:transcribed (mark-done) / no speech (mark-empty) / failed (not marked, left for next time)——never "processed but marked nothing". For each <name>:
- Download and archive:
Audio lands in"$INBOX" download <name> ~/code/voicedrop/archive~/code/voicedrop/archive/<name>. The original on R2 is always retained; this local copy is just an offline duplicate. - Take a quick look to check whether it's a real recording:
dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$audio" 2>/dev/null)duris empty (not audio/corrupt) →"$INBOX" mark-empty <name> corrupt;< 1.0seconds (misupload/silence) →"$INBOX" mark-empty <name> silent. After marking, report to the user,skip to the next item——this item is already in a terminal state, do not process it again. - Transcribe → SRT: load
wjs-transcribing-audio(for Chinese, use Volcano Doubaovolc_asr_stream.py+build_srt_from_asr.py+ do AI polishing within the session to fix typos). SRT lands in~/code/voicedrop/archive/<stem>.srt。- the transcription comes out empty(there's sound but the ASR outputs not a single word, usually ambient noise) →
"$INBOX" mark-empty <name> no-speech, report, skip to the next item.
- the transcription comes out empty(there's sound but the ASR outputs not a single word, usually ambient noise) →
- Mine articles: hand this SRT to
wjs-mining-articlesto run its full pipeline—produce a topic list (its human gate, follow it as usual, don't skip), write the article, create the WeChat draft. Voice memos are mostly short single-topic monologues, the list often has only 1 item, still let the user confirm as usual. - After successful transcription, write the processing marker(at least one draft produced, user didn't abort): assemble the articles mined from this item into a v2 JSON (
{"schema":2,"status":"ready","sourceAudio":"<name>","articles":[{"title","body"},…]}, may includetranscript/srt) write it to a temp file, then:
This item is then marked as transcribed, visible in the app, and won't be mined again by the server or next time. Transcription failed / user didn't check any topic / no article mined → mark neither done nor empty, leave it unprocessed, come back next time, report the reason."$INBOX" mark-done <name> /tmp/<stem>.json
Step 3 · Report
How many items were processed, how many drafts each mined (landing in ~/code/wechat-publish/), which were marked no-speech and why (corrupt/silent/no-speech), local archive paths, how many items remain unprocessed on R2.
Marking safety red lines
download(存档) → 判别 → 成文 ? mark-done : 无语音 ? mark-empty : 留着不标
↑ 绝不 delete
- Never delete. Audio always stays on R2; deletion is only a manual action by the user in the app.
- Every item has a terminal state. Transcribed →
mark-done; corrupt/silent/no-speech →mark-empty(with reason); true failure (transcription error, user abort, no article mined) → not marked, left unprocessed to retry next time.Never let "ran through a pass but left no marker at all" happen——that would cause this item to be reprocessed every time.
Reuse boundaries
| Reuse | Usage |
|---|---|
wjs-transcribing-audio |
Each audio → SRT (Chinese Volcano Doubao, including polishing to fix typos) |
wjs-mining-articles |
Each SRT → topic list → article → WeChat draft (including its own human gate) |
~/code/.env |
FILES_TOKEN + Volcano ASR creds |
| VoiceDrop app | Upstream: filename looks like VoiceDrop-<时间戳>-<时长>-<星期>-<时段>[-<城市-城区>].m4a(all ASCII). This skill claims items by the VoiceDrop- prefix + .m4a suffix; the duration/weekday/time period/location in the middle are context, which can be used when writing to judge when and where this recording was dictated. |
Server miner (~/code/voicedrop/mining/mine.py, every 2h) |
Same set of marking conventions: written as articles/<stem>.json, no speech written as articles/<stem>.empty, never delete audio. It automatically processes the inbox, so when this skill runs, list is often already empty—this is expected, this skill is a manual fill-in |
The only new code in this skill:scripts/voicedrop-inbox.sh。
Common Mistakes
- delete audio — red line. The writing workflow never deletes, only
mark-done/mark-empty; delete is only used manually by the user in the app. - Ran one but didn't mark it — no-speech/corrupted ones also need to be
mark-empty, otherwise it gets re-downloaded and re-transcribed every time, forever "pending". - Transcription failed/user didn't check it but still force-marked done — if it truly failed, leave it unprocessed (don't mark it), try again next time; only mark
mark-done。 - skip
wjs-mining-articles's topic gate and force-write yourself — that gate is its design, follow it. - Treating non-VoiceDrop files in the bucket as sources too — only recognize the
VoiceDrop-*.m4aprefix;listalso only lists unprocessed ones. - Mistakenly uploaded/0-second/ambient sound treated as real recordings and retried repeatedly — first ffprobe to check the duration, if empty/corrupted/<1s directly
mark-empty, don't send it for transcription.