README.md (12730 bytes)
1 # SubRead Anki 2 3 A pop-up for Anki cards, used the same way as a dictionary. Select a text 4 in any app (a reader, a browser, an OCR app) and choose "SubRead Anki" in the 5 text selection menu. The pop-up opens over the app and shows the text and 6 the terms at the word, from 7 [SubRead Dictionary](https://github.com/equwal/subread-dictionary). A tap on 8 a character of the text moves the word there. Tap "+ Anki" on a term: the 9 card goes into [AnkiDroid](https://github.com/ankidroid/Anki-Android) with 10 the word, its reading and definition, the sentence around the word, a 11 picture of the screen and the sound of the sentence. 12 13 The app is on its own: it needs no other SubRead app. With SubRead 14 Dictionary, the pop-up has the definitions. With 15 [SubRead Overlay](https://github.com/equwal/subread-overlay), a subtitle 16 word shows its whole line, and the card gets the sound of that line. 17 18 Nothing leaves the device. The app talks to AnkiDroid and SubRead 19 Dictionary on the device, and opens a URL only when a sender gives one for 20 an audio or a picture. 21 22 ## How to use it 23 24 1. Install AnkiDroid and allow SubRead Anki to add cards to it. AnkiDroid 25 must have "Enable AnkiDroid API" on, under Advanced in its settings; it 26 is on by default. 27 2. Choose the deck. Choose the note type, or let the app make its own, 28 "SubRead Anki". The app guesses what goes into each field from the field 29 names: the [Lapis](https://github.com/donkuri/lapis) note type and the 30 basic ones work as they are. "Fields" changes the guess. 31 3. Install SubRead Dictionary and import a dictionary into it, for the 32 reading, the definition and the word audio. 33 4. For a picture and sound on the cards, start the capture, from the app or 34 from its tile in the quick settings. Android asks each time. While the 35 capture is on, the app keeps the last minute and a half of the sound of 36 the device and the newest picture of the screen. Stop it from the 37 notification or the tile. 38 39 Then, in any app: select a sentence, choose "SubRead Anki", tap the word in the 40 pop-up, and tap "+ Anki". A single selected word works too; the card then 41 has no sentence, unless the word is in the subtitle line of SubRead Overlay. 42 The button says "✓ Added", and a term that is in the deck already says 43 "✓ In Anki". A tap outside the pop-up closes it. 44 45 Some browsers (Firefox, Brave) show only some of the apps in the text 46 selection menu. There, choose Share and then "SubRead Anki". The app 47 removes the link of the page and the quotes that the browser adds. 48 49 The line under the text says what the card gets: "Picture ✓ Sound 5.3 s ✓ 50 Deck: SubRead". The picture is the screen at the moment the text was 51 selected, from before the pop-up opened. The sound is the last eight 52 seconds before that moment: the audiobook that plays while you read. A clip 53 of silence is left out. 54 55 Share a picture to "SubRead Anki" within an hour of a card, and it goes onto 56 that card. The option "add the first term at once, with no pop-up" makes a 57 selection a card with no pop-up. 58 59 ## What goes on the card 60 61 | Part | Where it comes from | 62 |---|---| 63 | Expression, reading, definition | The term that you tap "+ Anki" on, from SubRead Dictionary. `食べた` gives the card `食べる`. A sender can give its own. | 64 | Sentence | The sentence around the word in the text of the pop-up. The word is in `<b>` as it is in the sentence. | 65 | Sentence audio | The sender's file, cut to the times it gives. Else the capture: the subtitle line of SubRead Overlay, cut on the clock of the player with a pad on each side, or the eight seconds before the text came in. AAC in `.m4a`: every player and every Anki has it. | 66 | Word audio | The sender, else the audio of SubRead Dictionary (the local audio server, or a remote source when on). | 67 | Picture | The sender, else the screen from the capture at the moment the text came in. JPEG, at most 1600 pixels on the long side. | 68 | Source, tags | The sender, else the name of the app that sent the text. The tags of the settings go on every card. | 69 70 The note type "SubRead Anki" has the fields Expression, Reading, Definition, 71 Sentence, SentenceAudio, WordAudio, Image and Source, and one card: the word 72 and the sentence in front, the rest behind, black on white. 73 74 ## For other apps 75 76 Four doors, all into one activity, `space.subread.anki.AddActivity`. Any 77 part can be left out: the app fills it in as the table above says. A 78 request with a `definition` is a whole card: it goes to AnkiDroid at once, 79 with no pop-up, and a toast says so. A request without one opens the 80 pop-up at the word, so the user picks the term. `confirm` changes that. 81 82 **The text selection menu and the share sheet.** No code: the app has an 83 entry in both. `Intent.ACTION_PROCESS_TEXT` with `EXTRA_PROCESS_TEXT`, or 84 `Intent.ACTION_SEND` with `text/plain` and `EXTRA_TEXT`. The text is the 85 text of the pop-up. `ACTION_SEND` with an `image/*` stream and a text is the 86 picture and the text; the picture alone goes onto the last card. 87 88 **An Intent from an app on the device.** The action 89 `space.subread.anki.action.ADD`, with the keys of the table below as extras, 90 each with the prefix `space.subread.anki.extra.` and in upper case: 91 92 ```kotlin 93 val add = Intent("space.subread.anki.action.ADD").apply { 94 setPackage("space.subread.anki") 95 putExtra("space.subread.anki.extra.EXPRESSION", "食べた") 96 putExtra("space.subread.anki.extra.SENTENCE", "猫が魚を食べた。") 97 putExtra("space.subread.anki.extra.AUDIO", audioUri) // Uri, or a String 98 putExtra("space.subread.anki.extra.AUDIO_START", 96_000L) // Long, or a String 99 putExtra("space.subread.anki.extra.AUDIO_END", 99_500L) 100 putExtra("space.subread.anki.extra.IMAGE", pageUri) 101 putExtra("space.subread.anki.extra.SOURCE", "吾輩は猫である") 102 clipData = ClipData.newRawUri("audio", audioUri).apply { addItem(ClipData.Item(pageUri)) } 103 addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) 104 } 105 startActivityForResult(add, REQUEST_CARD) // or startActivity 106 ``` 107 108 A `content://` Uri must be one the app may grant: its own `FileProvider`, or 109 a document Uri it holds a permission for. A `file://` Uri is refused. The 110 extras of the intent API of the `main` branch are taken too, with their 111 meaning there: `WORD`, `TEXT`, `AUDIO` (the word audio), `SENTENCE_AUDIO`, 112 `SHOW`. The "Anki" buttons of SubRead Overlay and SubRead Dictionary send 113 those. Declare the package in the manifest of the calling app, or Android 11 114 and later hide it: 115 116 ```xml 117 <queries> 118 <package android:name="space.subread.anki" /> 119 </queries> 120 ``` 121 122 The result, for a caller that started the activity for one: `RESULT_OK` with 123 `space.subread.anki.extra.NOTE_ID` (`Long`, the note in AnkiDroid), or 124 `RESULT_CANCELED` with `space.subread.anki.extra.ERROR` (`String`): `empty`, 125 `no_ankidroid`, `no_permission`, `duplicate`, `cancelled`, or what AnkiDroid 126 said. 127 128 **A link on a web page.** The same keys, percent-encoded, in the query of 129 `subreadanki://add`. A web reader that has the sentence, the audiobook and 130 the times makes the card with one link: 131 132 ```javascript 133 const query = new URLSearchParams({ 134 expression: "食べた", 135 sentence: "猫が魚を食べた。", 136 audio: "https://example.com/books/neko.m4b", // the whole file: the app reads the range alone 137 audio_start: "96000", 138 audio_end: "99500", 139 image: "https://example.com/books/neko/page-12.jpg", 140 source: "吾輩は猫である", 141 }).toString(); 142 // Chrome: an intent link, with a fallback for a phone without the app. 143 location.href = "intent://add?" + query + "#Intent;scheme=subreadanki;package=space.subread.anki;" + 144 "S.browser_fallback_url=" + encodeURIComponent("https://github.com/equwal/subread-anki") + ";end"; 145 // Firefox and the others: the plain link. 146 // location.href = "subreadanki://add?" + query; 147 ``` 148 149 The link must come from a tap of the user: browsers block an app link 150 without one. An `http(s)` audio is read with range requests, so a whole 151 audiobook is fine; a picture is fetched whole. A `data:` Uri works for a 152 small file. With a `definition`, the app shows a toast and goes back to the 153 browser; without one, the pop-up opens over the browser. 154 155 **The keys.** Each is optional; a card needs `expression` or `sentence`. 156 157 | Key | Value | 158 |---|---| 159 | `expression` | The word, as it is in the text. The pop-up opens at it. | 160 | `reading` | The reading of the word. | 161 | `definition` | The definition, HTML or text. Given, the card is added at once and the dictionary is not asked. | 162 | `sentence` | The sentence, plain text: the text of the pop-up. | 163 | `audio` | A Uri of the audio of the sentence: `content://`, `https://`, `http://` or `data:`. | 164 | `audio_start`, `audio_end` | With `audio`: the clip, in milliseconds of the audio. Without them, the whole file is the clip. | 165 | `word_audio` | A Uri of the audio of the word alone. | 166 | `image` | A Uri of the picture. | 167 | `source` | Where the sentence is from: the title of the book or the video. | 168 | `tags` | Anki tags, separated by spaces. | 169 | `pitch`, `frequency` | Text for the pitch and frequency fields. | 170 | `position` | The position of the player at the tap, in milliseconds. With the capture on and no `audio`, the clip is cut around it. | 171 | `cue_start`, `cue_end` | The subtitle line, in milliseconds on the clock of the player. With the capture on and no `audio`, the clip is this range. | 172 | `confirm` | `1`: open the pop-up, also with a definition. `0`: add at once, with the first term of the dictionary when there is no definition. | 173 174 ## How it works 175 176 `:core` is plain Kotlin, with no Android in it, and has property tests: the 177 request and its query, the text of the pop-up and the sentence around a 178 word, the clock of a player report, the ring of captured sound and its 179 loudness, the fields of a note type. `:app` has the pop-up, the capture 180 service, the AnkiDroid client and the two content provider clients. 181 182 The pop-up takes the picture when the text comes in, before its own window 183 draws, and cuts the sound at once too. Each "+ Anki" of the same pop-up 184 uses the same picture and sound. 185 186 The sound of the capture is 16-bit PCM in a ring with a clock. A player 187 reports a position, the time of that report and the speed; the app maps the 188 times of the subtitle line onto the clock of the ring and reads that range 189 out. A pause reported a minute ago still maps its media times to the right 190 moments. The clip is encoded with the AAC encoder of Android. 191 192 SubRead Overlay answers `content://space.subread.overlay.player/line` with 193 the line of now and the report of the player. SubRead Dictionary answers 194 `content://space.subread.dictionary.lookup/terms?text=…` with the terms, 195 and gives the word audio through the same provider. Their READMEs have the 196 details. 197 198 ## Build 199 200 ``` 201 ./gradlew :core:test :app:assembleDebug 202 ``` 203 204 `-PplayStore=true` leaves the Ko-fi link out of the build for Google Play. 205 206 ## More projects 207 208 - [SubRead](https://subread.space/): read along with an audiobook, in the browser. 209 Also [for Android](https://github.com/equwal/subread-android/releases/latest), 210 [for YouTube](https://github.com/equwal/subread-extension/releases/latest) 211 and [for KOReader](https://github.com/equwal/subread.koplugin). 212 - [SubRead Overlay](https://github.com/equwal/subread-overlay/releases/latest): subtitle lines over any Android media player. 213 - [SubRead Dictionary](https://github.com/equwal/subread-dictionary/releases/latest): a pop-up dictionary for Android that reads Yomitan dictionaries. 214 - [Subrep](https://github.com/equwal/subrep-android/releases/latest): live captions of the sound of your phone. 215 - [Book Simulator](https://booksimulator.com/): a reading room for Aozora Bunko and Project Gutenberg books. 216 - [honjimaku.com](https://honjimaku.com/): subtitles for Japanese audiobooks. 217 - [sbm Sync](https://sbmsync.com/): your bookmarks, the same on every device, 218 with [sbm](https://github.com/equwal/sbm) for dmenu, 219 [sbm for Android](https://github.com/equwal/sbm-android/releases/latest) 220 and the [sbm add-on](https://github.com/equwal/sbm-extension/releases/latest) for Firefox and Chrome. 221 - [Rebind](https://github.com/equwal/rebind/releases): remap the hardware buttons of e-ink readers and Android, 222 with [Ink Recents](https://github.com/equwal/ink-recents/releases/latest), 223 [Ink Dim](https://github.com/equwal/ink-dim/releases/latest) 224 and [Ink Update](https://github.com/equwal/ink-update/releases/latest). 225 - [dickt.store](https://dickt.store/): language-learning tools, flashcards and web toys. 226 - [hentaibun.online](https://hentaibun.online/): learn kanbun and kobun. 227 - [Recently Written](https://recentlywritten.com/): the blog, and a list of [all projects](https://recentlywritten.com/projects.html). 228 229 ## Licence 230 231 AGPL-3.0. See `LICENSE`.