| name | sentence-mining |
| description | Build Japanese sentence-mining cards for Ray's Anki deck in two modes. (1) Video mode โ paste any Instagram reel, YouTube video/Short, TikTok, Twitter video, or local file โ yt-dlp + AssemblyAI + mecab + AnkiMorphs i+1 diff produces draft cards. (2) Bank mode โ give a list of target words โ search across Ray's locally-indexed subs2srs .apkg banks (Tokyo Ghoul, Harry Potter, LOGH, etc.) for natural example sentences, reusing the bank's original audio + screenshot when available. Both modes stage drafts for Ray's approval before pushing via AnkiConnect. Use proactively whenever input is (a) a Japanese-language video URL or (b) a list of Japanese words โ don't ask if Ray wants cards, start drafting. Trigger phrases include "mine this video", "make sentence cards from <url>", "turn this reel into cards", "mine these words", "find sentences for [w1, w2, โฆ]", "i keep forgetting <word>", "pull cards from my <show> bank", "leech these", "search the banks for X", `/sentence-mining`, or any video URL paired with a mention of Anki / cards / morphs / i+1. |
Sentence Mining
Two ways in:
โโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Video URL or file โ โ List of target words โ
โ "mine this reel" โ โ "make a card for ๅๆ" โ
โโโโโโโโโโโโฌโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโ
โ โ
โผ โผ
โโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ VIDEO MODE โ โ BANK MODE โ
โ yt-dlp โ โ โ search bank indexes โ
โ AssemblyAI โ โ โ for each word โ โ
โ mecab + i+1 diff โ โ pick best sentence โ
โโโโโโโโโโโโฌโโโโโโโโโโโโ โโโโโโโโโโโโโโฌโโโโโโโโโโโโโ
โ โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Shared post-process โ
โ curate โ explain โ โ
โ media โ draft โ push โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโ
Route the request. Look at what Ray gave you:
The mode-specific reference walks through Steps 1โ3 (and the mode-specific bits of Step 5). Then come back here for the shared post-processing.
Inputs and required env
API keys live in <skill-dir>/.env (git-ignored). First-time setup:
cp <skill-dir>/.env.example <skill-dir>/.env
Required keys:
ASSEMBLYAI_API_KEY โ video mode only
GEMINI_API_KEY (Google AI Studio works) โ both modes (explanation TTS + sentence TTS fallback)
Real environment variables override .env, so a key already in the shell still wins.
Also required:
- Anki running with AnkiConnect (port 8765) โ verify with
curl -s http://localhost:8765 -d '{"action":"version","version":6}'
yt-dlp, ffmpeg, mecab on PATH (all present on Ray's machine)
- Python:
pip3 install --break-system-packages google-genai jamdict jamdict-data
If a key is missing, the script exits with a clear message pointing at .env. Don't fall back to alternatives without asking.
For bank mode, the banks must be indexed first โ see references/bank-mode.md ยง"One-time setup".
Steps 1โ3 (mode-specific)
Follow the reference for the mode you routed into. By the end of those steps you have a candidates.json (or banksearch.json) shaped as:
{
"source": "video" | "bank-search",
"source_id": "...",
"source_url": "...",
"candidates": [
{ "lemma": "...", "sentence": "...", "deck": "Ray's Sentence Cards", "i_level": "i1" | "i?", ... },
...
]
}
Read it. Zero candidates? Tell Ray why (all words known, all dupes, no hits across banks, etc.) and stop.
Step 3.5 โ Curate (shared, inline)
Walk the candidate list and drop entries that aren't worth a card. Filter aggressively โ a Ray-quality card teaches a generalizable word he'll hit again, not a one-off label from this specific source. Drop:
- Pop-culture proper nouns โ anime/manga/game titles, character names, song titles, group/idol names. Real-world brands or places (
ในใฟใผใใใฏในใณใผใใผ, ๅฏๅฃซๅฑฑ, ๆฑไบฌ) are fine; pop-culture-specific titles are not.
- mecab fragments โ lemmas that are clearly mid-word cuts (
ใใใฃใฆ from "ใใใใฃใฆ", ใใใฝ from "ใใใใใใฝใ"). Tell: starts with a particle, ends mid-syllable, no JMDict entry. (Video mode only โ bank mode doesn't tokenize.)
- Transcription garbage โ nonsense given the sentence's clear topic, especially when JPDB rank is
1000000000 (no entry). Don't try to rescue a contaminated sentence; drop the candidate. (Video mode only.)
- Trail-off / partial sentences โ sentence ends mid-clause or starts with a connecting particle; the audio clip will sound broken.
- Subs2srs concatenated frames โ bank-mode sentences like
(line1) (line2): keep only the chunk containing the target word.
- Compound katakana redundant with components โ if both
ใขใคในใขใกใชใซใผใ and ใขใกใชใซใผใ are candidates, drop the compound.
When dropping is judgment-call, lean toward dropping. Ray would rather mine 3 great cards than 15 mediocre ones.
Apply by deleting entries from data["candidates"] and saving back. Print a short "kept N / dropped M because โฆ" summary.
Step 4 โ Generate explanations (shared, inline)
For each candidate, generate the Japanese explanation inline โ don't shell out. Use this prompt verbatim, swapping {word} and {sentence} (it's the prompt from Ray's ai-language-explainer addon, so cards match the style of his 9000+ existing ones):
Please write a short explanation of the word '{word}' using the context of the original sentence: '{sentence}'.
Write an explanation that helps a Japanese beginner understand the word and how it is used with this context as an example.
Explain it in the same way a native would explain it to a 13-year-old. Don't use any English, only use simpler Japanese.
1. Don't write the furigana for any of the words in brackets after the word.
2. Don't start with stuff like ใจใใ่จ่ใ็ฐกๅใซ่ชฌๆใใใญ, just dive straight into explaining after starting with the word.
Write each explanation into the candidate's explanation field. Keep each under ~250 Japanese characters โ it gets read aloud by TTS.
Why Claude (you) writes this and not a script: the prompt depends on contextual Japanese fluency. You produce more natural output than a separate API call would, and you can react to tone (formal vs casual vs anime-speak). See references/explanation-prompt.md for the canonical addon prompt if you ever need to verify.
Step 5 โ Generate media (mode-specific)
Different script per mode โ both write the same draft.json shape so Step 7 (push) is shared.
| Mode | Script | What it does |
|---|
| video | scripts/generate_media.py | ffmpeg clip + screenshot from the video, Gemini TTS on explanation |
| bank | scripts/generate_media_bank.py | copy bank's audio/image (or Gemini TTS sentence if absent), TTS explanation |
Both write sentenceAudio_file, picture_file, explanationAudio_file (the latter two may be empty strings for bank cards where the bank shipped no image). Media lands in /Users/ray/Library/Application Support/Anki2/User 1/collection.media/.
See references/video-mode.md ยง"Step 5" or references/bank-mode.md ยง"Step 5" for the script invocation.
Step 6 โ Push to Anki (auto), then summarize
Default behavior: push immediately after Step 5 succeeds. No approval gate. Ray confirmed in June 2026 that the curation + explanation pass in Steps 3.5 + 4 has been reliable enough that asking "say push to commit" was just adding friction. Anki's own review queue is the real gate โ bad cards get suspended or deleted there. Push first; show the result.
Skip auto-push only if Ray explicitly said "draft only" / "don't push" / "let me review first" in the originating message. In that case, fall through to the legacy approval flow at the bottom of this section.
Go to Step 7, then print the summary in this shape (video mode):
Pushed 17 cards from <SOURCE_ID> to Anki โ
โ "Ray's Sentence Cards" (i+1): 12
1. ๆฐ่ฟซ (ใใฏใ) โ "ๅฝผใฏๆฐ่ฟซใฎใใใฃใ็ฎใง..." [JPDB rank 4823]
...
โ "Ray's Sentence Mining Deferred" (i+2/i+3): 5
13. ๆถๆใ (ใใใใ) โ "..." [JPDB rank 12044]
...
Skipped during curation: <N> (ads / mecab fragments / transcription errors).
Draft: ~/Downloads/sentence-mining/<source>.draft.json
Example (bank mode):
Pushed 2 bank cards (word list: ๅๆ, ่ฅฟๆฆ, ๅๆฆ) โ
1. ๅๆ [tokyo_ghoul_season_1] ๐๐ผ
"ๅๆใงใฏไบไบบ ไบไบบๅ
ฑ ่กๆใงๅผทใๆๆใๆใฃใๅฅณๆงใงใใ"
2. ่ฅฟๆฆ [legend_of_the_galactic_heroes_eng_jp]
"่ฅฟๆฆ2166ๅนดใซใฏ ๆจๆใฎ่กๆ ใคใชใซ" (sentence TTS synthesized; no image)
Misses: ๅๆฆ โ no hit across N indexed banks.
If push.py reports any failed, list them with the reason from the response so Ray knows what didn't make it in.
Legacy approval flow (only when Ray says "draft only" / "don't push")
Print the same summary but with "Mined N candidate cards" and a "Say 'push' to commit, or tell me which to drop" line. Then wait. He may:
- Say "push" โ run Step 7
- Say "drop 3, 7, 11" โ remove those, ask again
- Say "regenerate explanation for 5" โ redo, regen TTS for that one
- Say "try a different sentence for X" โ look at runner-up bank hits and re-stage
- Say "no" โ leave draft.json on disk; he can come back to it
Step 7 โ Push to Anki (shared)
python3 <skill-dir>/scripts/push.py --draft ~/Downloads/sentence-mining/<source>.draft.json
This:
-
Calls AnkiConnect addNotes with the full card list
-
Tags every card with two tags:
claude-sentence-mining (video) OR claude-sentence-bank (bank) โ the permanent kind tag
i1 / i2 / i3 / i? โ the current i-level (count of unknown content words in the sentence) so Ray can filter by complexity in Anki
Other context โ per-run source:*, speaker:*, bank:*, auto-mined:* โ is intentionally NOT promoted to tags (Ray asked these be dropped in June 2026 because they cluttered the tag tree without adding study value). The full data still lives in the draft JSON for debugging.
-
Sentence field is prefixed with <b>A:</b> for video diarized cards so it's clear who's talking
-
Nothing is suspended โ Ray studies them all and decides per-card
If any addNote fails (usually a late-detected duplicate), push.py reports which and skips it without aborting the batch.
Step 8 โ Cleanup
Leave the video / draft / intermediate JSONs in ~/Downloads/sentence-mining/. Ray asked for this โ it lets him re-run, re-watch, or scrub for context. Don't auto-delete.
Reference files
Scripts inventory
| script | mode | purpose |
|---|
transcribe.py | video | AssemblyAI Universal-3 Pro JP transcription with diarization |
analyze.py | video | mecab tokenize + AnkiMorphs diff + JPDB rank |
generate_media.py | video | ffmpeg clip + screenshot + Gemini TTS explanation |
extract_bank.py | bank | parse .apkg โ local index JSON + media dir |
search_banks.py | bank | word-list โ top-N sentence candidates across indexed banks |
generate_media_bank.py | bank | copy bank media (or TTS fallback) + Gemini TTS explanation |
push.py | both | AnkiConnect addNotes; tags driven off source in draft |
_env.py | both | loads .env into os.environ |
Gotchas (universal)
- AnkiConnect must be running. If
curl http://localhost:8765 fails, Anki is closed or the addon is disabled. Tell Ray rather than retrying.
- Don't push cards with empty explanation. If Step 4 failed for a card (you got confused, refused, etc.), drop it from the draft rather than pushing a hollow one.
- Gemini TTS preview model rate-limits. Free tier is 10 RPM.
generate_media.py and generate_media_bank.py both cap concurrency at 2 with exponential 429 backoff. If you still hit limits, drop to 1 or serialize.
allowDuplicate: False in push.py means re-pushing the same wordForm is silently rejected. To check ahead of time: query wordForm:<lemma> deck:"Ray's Sentence Cards" against AnkiConnect during curation.