- TypeScript 36.4%
- C# 32.7%
- JavaScript 25.6%
- HTML 5.3%
| .impeccable/live | ||
| docs | ||
| mockups | ||
| src/Jellyfin.Plugin.Kurageyomi | ||
| tests/Jellyfin.Plugin.Kurageyomi.Tests | ||
| web | ||
| .gitattributes | ||
| .gitignore | ||
| bp2.cs | ||
| CONTEXT.md | ||
| DESIGN.md | ||
| jc.zip | ||
| Kurageyomi.sln | ||
| LICENSE | ||
| PRODUCT.md | ||
| README.md | ||
| se.cs | ||
| t11.json | ||
| tr.js | ||
Kurageyomi
海月読み — "jellyfish reading".
Sentence mining from Japanese Jellyfin subtitles, straight into Anki.
A Jellyfin plugin serves a page to your phone. While an episode plays, the page shows the Japanese subtitle line currently on screen. Tap an unknown word: playback pauses, a Yomichan-style lookup deinflects it and shows the entry, and one more tap saves it with the whole sentence. Later, an export cuts the audio and a still frame from the source file and pushes finished notes into Anki.
The design is:
- CONTEXT.md — glossary
- docs/PRD.md — what it does and why
- docs/adr/ — the decisions that shape it
Clients
The plugin follows whatever is playing on any Jellyfin client, so reading works with every client: the Cue strip, lookups, and mining only ever read the server's own view of a Playback Session.
Controlling the show is different. Pausing, seeking, and stepping a Cue at a time are Jellyfin playstate commands, and a client only receives those if it registers as remote-controllable. A client that does not will accept the command and do nothing — the endpoints answer, the picture does not move.
Check before assuming: start something playing, then open the Jellyfin web UI's Play On menu.
If the session is listed there, Jellyfin considers it controllable and this plugin's transport will
work. That list reads the same SupportsRemoteControl the app does.
| Client | Reading | Control | Notes |
|---|---|---|---|
| Official Jellyfin Android TV | works | works | Confirmed on dionysus: pause, unpause, and per-Cue stepping all drive it. This is the client to use. |
| Moonfin (Android TV) | works | no | Confirmed on dionysus: absent from Play On, and Jellyfin's own UI cannot drive it either. Moonfin casts to other devices; it does not accept being cast to. |
| Anything else | works | test it | One minute with Play On settles it. |
When the followed client cannot be driven, the transport controls are disabled with a reason rather than silently doing nothing, and auto-pause has no effect. Reading is unaffected.
Building
The phone app is TypeScript compiled by tsc straight into the plugin's Web/ folder, which
the C# build then embeds. No bundler — the browser loads native ES modules.
cd web && npm install && npm run build # emits .js into src/Jellyfin.Plugin.Kurageyomi/Web
dotnet test # C# suite
cd web && npm test # phone-side suite
dotnet build -c Release # produces the dll and meta.json to deploy
The emitted .js files are committed so dotnet build works without Node installed. Re-run
npm run build after changing anything under web/src, or the plugin ships the old script — a
test fails if the shell references an asset that is not compiled in, which is the usual symptom.
Deploying
Jellyfin runs in a container on dionysus (10.11.11) and is reached over https via
tailscale serve, so the page runs in a secure context — Wake Lock and home-screen install
work, and the app's "served over http" warning stays hidden.
Nothing is built on dionysus. dex-win builds and publishes the artifacts over the tailnet:
cd web && npm run build && cd ..
dotnet build -c Release
cp src/Jellyfin.Plugin.Kurageyomi/bin/Release/net9.0/Jellyfin.Plugin.Kurageyomi.dll \
src/Jellyfin.Plugin.Kurageyomi/bin/Release/net9.0/meta.json \
../tailscale-artifacts/kurageyomi/
cd ../tailscale-artifacts/kurageyomi && sha256sum Jellyfin.Plugin.Kurageyomi.dll meta.json > SHA256SUMS
tailscale serve publishes F:\Projects\tailscale-artifacts tailnet-only at
https://dex-win.tail1ee5d.ts.net, so those land at /kurageyomi/. dionysus fetches them per
docs/deploy-brief-hermes.md, which is discovery-first about the
container's paths rather than assuming them.
Two things that will waste your time if forgotten: dex-win has to be awake for the fetch, and
the phone caches main.js — hard-reload after a deploy or you will debug the previous build.
State
Working end to end: the phone follows what is playing, shows one Cue at a time, and resolves a tapped word — deinflecting it and showing JMdict entries with the matched Surface Form painted in the Cue. Playback controls exist for clients that accept them (see Clients).
Mining is not built. Capture, the duplicate check, and Export to Anki are specified in docs/PRD.md and settled by the ADRs, but nothing writes a Capture yet. Tapping a word lets you read it; it does not keep it. The Mining Queue's store exists and the status bar reads its count from the server, so the number is currently always zero.
The design the interface is built against is DESIGN.md; what the product is for is PRODUCT.md.
Licence
GPL-3.0. Dictionary data is JMdict, used under the EDRDG licence — attribution to the Electronic Dictionary Research and Development Group is displayed in the app wherever definitions appear.