There is a newer version of this article, Anki Scripting for Non-Programmers. This new post is shorter and is a great introduction to the subject. You can come back to this older article if you want more details about Anki and/or learn about some advanced use cases like converting an ebook into flashcards.
A look inside the Anki model
This is the first part in this lengthy article on Anki and how to use it to create your flashcards programmatically. We will begin by presenting the inner model of Anki, how the different classes interact and when the database is updated when we are using the desktop application. In the next part, we will be doing the same thing without using the GUI, using only the internal API of Anki. We will also cover how to script Anki to bulk load flashcards, useful for example when you need to learn the 5000 most common words in a new language. Let’s get started!
Anki Reverse Engineering
A good start is to begin by cloning the official repository of Anki:
When the clone of the repository is finished, enter into this new directory anki et let’s see how the code is organized.
Project organization
Not all directories are pertinent for our analysis. Here is an annotated description of each folder:
The two most important folders are anki and aqt: the first contains all the core logic behind Anki (model, SRS algorithm) while the second contains all the code concerning the Desktop GUI, using the QT library.
When you click on the Anki icon, the script runanki.py is executed. This script does not do much except launching the GUI:
Where are stored my cards?
Anki stores your decks in the folder associated with your profile. This folder stores all of your Anki data in a single location, to make backups easy. By default, Anki uses the folder ~/Documents/Anki under your home directory:
To tell Anki to use a different location, please see this manual. This could be very useful when you experiment with Anki to avoid corrupting our data!
Now, we know where is stored our database, let’s inspect the different files present under this directory.
The most important files are clearly the SQLite databases (collection.anki2 and collection.media.db2).
What is SQLite?
SQLite is a self-contained, zero-configuration, transactional SQL database engine. SQLite is not directly comparable to client/server SQL database engines such as MySQL, Oracle, PostgreSQL, or SQL Server since SQLite is trying to solve a different problem.
Client/server SQL database engines strive to implement a shared repository of enterprise data. They emphasis scalability, concurrency, centralization, and control. SQLite strives to provide local data storage for individual applications and devices. SQLite emphasizes economy, efficiency, reliability, independence, and simplicity.
So, SQLite does not compete with client/server databases. SQLite competes with fopen() and is particularly well adapted in these situations: as the datastore for an embedded devices, as an application file format (such as Anki), or as the main storage for a medium website or just as a file archive.
A apkg file is just an archive (you could use 7zip or your favorite decompression utility to see its content) containing the anki2 file that we just described and a file media. This media file is a simple text file containing an empty JSON object. As mentioned inside the Preferences dialog, Medias are not backed up. We need to create a periodic backup of our Anki folder to be safe. If you use AnkiWeb, our media are already synchronized in the cloud but be careful, decks stored in your account may be archived and eventually deleted if they are not accessed for 3 months or longer. If you are not planning to study for a few months, the manual backup of the content on your own computer is still required.
To understand Anki, we need at least a basic understanding of how the data are organized. Let’s inspect the database to determine its schema and the different tables.
Open the collection.anki2 with SQLiteBrowser (launch the executable on Windows) (be sure to consider all file extensions)
You should see a dialog like this:
What follows is the complete annotated schema. You could safely skim this part and use it as a reference. Note: the model in Python files is rarely documented.
Where:
Field conf contains various deck configuration options used by the SRS algorithm:
Whose definition follows:
Field models contains the JSON representation of a ModelManager (anki/models.py):
Whose definition follows:
Field decks contains the JSON representation of a DeckManager (anki/decks.py). One key-value for each deck present in this collection:
Whose definition follows:
Field dconf contains the JSON representation of a Deck Configuration:
Field tags contains the JSON representation of TagManager (anki/tags.py). Contains all tags in the collection with the usn number. (see above):
Anki Media Database Schema
Step by Step
In this part, we will create through the GUI a new deck and add a new card. We use a fresh anki installation.
How to inspect database changes when using Anki?
We could use the sqlite CLI to generate a dump before and after each operation executed through the Anki Desktop application.
Download the executable : https://www.sqlite.org/download.html (make sure that the mention “including the command-line shell program” is present on the binary description).
Place the sqlite3.exe along your collection.anki2 file.
Open an interpreter prompt (cmd on Windows)
By default, sqlite3 sends query results to standard output. So, we use the “.once” command to redirect query results to a file.
(Use the “.output” option to redirect all commands and not just the next one). Check the official documentation for help about the CLI options: https://www.sqlite.org/cli.html
Finally, we can use the generated dumps to compare the data and determine what was exactly updated by Anki.
To force Anki to use a fresh installation, we will override the folder location to point to an empty directory (see https://ankisrs.net/docs/manual.html#startupopts for more information about the option -b).
On Windows, just click right on the icon and update the target field to add the option. Ex:
"C:\Program Files (x86)\Anki\anki.exe" -b "D:\AnkiTmp"
Relaunch Anki, select your language, and the home screen should appear, containing only the deck “Default”:
By default, Anki creates a default collection. This is the only row present in database at first:
Let’s try to create a new deck.
Deck creation
Internally Anki just update the default collection to add the new deck in the dconf field:
The code: (aqt/deckbrowser.py#53)
And in anki/decks.py#125:
Card creation
Anki updates the collection to add the new tag (field tags):
(-1 is used to tell Anki to synchronize this tag with the server on the next synchronization with AnkiWeb)
Anki inserts a new row in the table notes:
And a new row in the table cards as it is a Basic card:
The code: (aqt/addcards.py)
On dialog opening:
In anki/collection.py:
Let’s try a card of type “Reversed”:
Adding a reversed card does not change anything in the UI code. The only difference is that the previous method addNode defined in collection.py will returned two cards instead of one in our first example. In database, two rows will be added in the table cards:
Studying
Studying is the act of reviewing our previously created card. After each review, the SRS algorithm runs to reschedule the card. The metadata required by the algorithm is updated to reflect the new due date.
For this card, we choose the second button, to study again the card in 10 minutes from now.
We register the review in the table revlog:
In the code: (aqt/reviewer.py#259)
Where answerCard is defined in anki/sched.py#58:
In anki/collection.py#136:
This method autosave explain why Anki need to synchronize when we quit the application. Anki does not save systematically after each command but wait 5 minutes between two saves to minimize the interaction with the database.
This marks the end of the reverse engineering of Anki. We have seen how Anki works under the hood — when we add a new deck or a new card and what happens when we study. We understand the database schema and have a better comprehension of the internal API of Anki.
In the next part, we are going to use this knowledge to create programmatically flashcards in record time, without interacting with the Anki application!
Anki Scripting Background
Motivations
Creating a flashcard is the first step to learn something new. To make it more memorable, you could customize the content of your flashcard: add a sound with the pronunciation of a new word, add a funny picture, and so one. Therefore, creating flashcards manually is important. But creating flashcards through the UI is also time-consuming in some cases. What if we need to create thousands of flashcards to learn the 5000 most common words in a new language?
Introduction
Anki is an open-source solution, published on GitHub. The code source is accessible to anyone going on the repository. With minimal programming skills, it is easy to script Anki to add new flashcards. Several options are possible:
Insert data directly in the SQLite database used by Anki
Write a Python program using the internal API of Anki
This post will concentrate on the second option. The first option could be implemented based on the information published in the first part but is not the optimal solution (high coupling, necessity to specify all fields when most of them are irrelevant). This solution was already partially documented online. The Python solution is more powerful. You could use a Python module to read a PDF or an Epub and generate the associated flashcards. You could use the Google Images API to retrieve funny pictures to integrate in our flashcards. The only limit is our imagination.
Python Scripting
The Anki source is not intended to be reused in other program so the code is not packaged as a Python module we could install as easily as:
One solution is to clone the official repository and put our code along the Anki code source but this solution is not optimal. To decouple our code from the Anki Source, a better strategy is to use a Git submodule.
First, create new project:
Clone Anki using the following command:
Then, we need to download Anki dependencies. In this post, we will use Pip, the classic tool for installing Python packages. To avoid polluting existing Python projects you could have in local, we will use virtualenv. The recommended way to do that in Python is to use virtualenv to isolate our project from other projects, so two projects could depend on conflicting versions of the same dependency. To install virtualenv globally, run the following command:
virtualenv is probably the only dependency you need to install globally when doing Python. Once installed, add a new virtual environment to each Python project, so their dependencies get installed under their directory.
At the root of the repository, execute the following command to create a new virtual environment:
When you need to switch to another project, just run the commands:
So, to install Anki dependencies, everything we need to do is run the Pip installer, passing the requirements.txt file present at the root of the Anki repository. This file contains the list of Python modules required by Anki.
We could now create our first script. Add a new file at the root of the project:
1
We need to add the Anki project in our path
2
We use the default installation folder of Anki on Linux. Please update the variable PROFILE_HOME to match your configuration.
3
We filter the cards to only keep the card concerning the English language. Use tag:* to select all of your cards.
When running the script, you will see the “Front” field of each card displayed to the console:
Congratulations! You just have created your first Python script using the Anki API.
As time passes, Anki source will be updated. To update our submodule, just move to the submodule directory and update the HEAD reference like any other repository. Do not forget to commit in order to update the reference maintained by the parent repository:
This ends our introduction of the Anki API. We covered enough information to get started but before tackling the problem of bulk loading, let’s begin with a simple use case: export all our flashcards to HTML, probably the most universal format today. If one day, we choose an alternative solution to Anki, it would be easy to import-export our cards to this other tool (most modern tool like Evernote, Google Keep, and many others offers a REST API for this purpose).
Case Study: Exporting flashcards in HTML
Let’s begin with a basic version to dump each card answer in its own HTML file.
First, we create a new file generate_site.py inside a new folder userscripts at the root of the project. The folder hierarchy should be:
As for the previous example, we need to include the anki source in our path to be able to exploit the Anki API:
We define constants to configure our environment:
We start by loading the existing anki collection:
The class Collection contains a long list of methods and attributes to access the notes, the cards, and the models.
We use the method findCards to restrict the cards to export:
The code iterates over the card identifiers and begin by collecting required information about the card (template, css, …). Once this is done, we create the HTML content by injecting the model CSS and the rendered card content (fields are replaced by values with the method renderQA).
When running, the program generates a list of files inside the folder defined by the constant OUTPUT_DIRECTORY. Here is the content of the file card-1429876617511.html:
To generate an index page listing all the exported cards, we need to update the previous code to store the list of processed card:
Next, we iterate over this list to generate an HTML list before injecting it in an HTML document:
When running the program, a new file index.html is generated in the target directory:
The code works but there remains a concern to address: the medias. Indeed, cards could reference external resources like images or sounds, which are all stored in a single folder collection.media under your profile directory. So, we need to extract there resources too and update the links inside the card text to reflect the new location.
A basic strategy could be to duplicate the whole folder. To avoid copying resources from cards that we don’t want to export, we will instead copy each file independently while processing the card. So, we need to update again the card processing code again:
When running the program again, a new folder medias will be created inside the target directory:
If we open the associated card in our browser, we should see this picture displayed correctly:
Great! We have successfully exported our Anki collection into a standard format but the result does not look good. Let’s add a little bit of JavaScript and CSS to make the application looks better.
What we want is a basic single-page application (SPA) that displays our flashcards. A search field will be available at the top of the page to help us filter the cards. Flashcard content will only be displayed when selecting the flashcard title in the list. The following is a draft of this application demo:
To add dynamic behavior to our SPA, we will use AngularJS. AngularJS keep our code clean by separating our model from the view and controller (Pattern MVC). To do that, we are going to convert the static HTML list of cards to JSON format:
We also exploit the card’s tags, easily retrieved from the note object:
We could now redesign our HTML template to integrate the new layout:
We iterate over the JSON array containing the cards we just created. When the user clicks on a card title, the method select defined in the controller is called. This method stores the selected card in the model. AngularJS refreshes our page and the iframe is updated with the content of the selected flashcard. Last thing to notice, we only display the flashcards matching the query entered by the user in the search field.
Let’s add the “final touch”, the CSS:
The layout is divided in three section: the search bar at the top, the list of flashcards on the left and the currently selected flashcard on the right. We use fixed positioning to keep all sections always present on the screen. The results now looks like:
This ends our first case study. We have seen how to exploit the Anki API to retrieve our data and export them to another format. In the next case study, we are going to use the Anki API to load a batch of cards, created from a book.
Here is the full listing of the code:
Case Study: Convert an EPUB to Flashcards
Motivations
Let’s me present you the context. We just bought a new book to learn the common English expressions. This book contains around 4000 expressions. If we consider it takes two minutes to create a flashcard, more than 100 hours will be required to overcome this daunting task. So, what can we do? One solution is to script the creation of the flashcards and this is exactly what we are going to do here.
This case study will be divided in two sections. In the first part, we are going to create a small program to read an Epub file in Python. In the second part, we will extend this program to insert the content directly into our Anki collection.
But does it not preferable to manually create the flashcards in order to retain more?
Of course! Creating manually a flashcard is always better than automating its creation. When you enter the words on the keyboard, or when you search on Google Images a memorable picture, you create connections inside your memory and this considerably help you to start fixing this new information. The manual creation is perfectly fine when learning your first words in a new language because it is easy to find a great picture or a personal story about it. Here, we are interested in common idioms, phrases that often does not mean what common sense would say. Relevant memorable pictures are difficult to find, so creating the flashcards manually does not help that much to fix the information in your brain. It is better to spend the 100 or more hours on studying the flashcards than on creating them.
Part I: Reading the Ebook (ePub)
The book is available in ePub format. The term is short for electronic publication. EPUB 3 is currently the most portable ebook format (Amazon has its own proprietary format for its Kindle but every other software readers such as Kobo or Bookeen supports this format).
For our task, we only need to know that an ePub is just a ZIP archive containing a website written in HTML5, including HTML files, images, CSS stylesheets, and other assets like video.
The ebook is subject to a copyright, so to avoid any violation, I rewrite a short version by customizing the text. This demonstration ebook is available in the repository associated to this post. To inspect its content, just unzip the archive:
The first file in the archive must be the mimetype file. It must be uncompressed so that non-ZIP utilities can read the mimetype. The mimetype file must be an ASCII file that contains the string application/epub+zip.
There must be a META-INF directory containing container.xml. This file points to the file defining the contents of the book:
Apart from mimetype and META-INF/container.xml, the other files (HTML, CSS and images files) are traditionally put in a directory named OEBPS. This directory contains the volume.opf file referenced in the previous file. Here is an example of this file:
In the manifest section, we can see all web resources included in this epub. This are these files that interested us, in particular the HTML files. If you open the book with an ebook reader (your device or an application like Calibre), you could see the book content:
If you want to know more about the epub format, O’Reilly published whole books on the subject.
Let’s see how the HTML looks like. The Page_1.html page contains only a picture with the cover of the book. We could ignore it. The next page Page_2.html is an example of page we need to parse to extract the English expressions. Here is an extract of this file:
If we simplify the HTML definition, we get something like this:
We now have all the necessary information to begin our program. As usual, we will write our program in Python, the same language used by Anki.
First, we need to open each HTML page and check if this page contains idioms or not:
Then, for each block of idioms, the function process_block is called. This method takes two parameters:
the BeautifulSoup HTML parser,
the working HTML element
As some idioms cross two pages, we need to keep the chapter number (idioms are grouped by general subjects), the category (each subject is divided into many related categories) and the current idiom to complete it when we will parsed the next page. To do so, we will use global variables (not good OO-design but an adequate choice for such a simple program). The code consists of a loop to iterate over paragraphs and uses CSS classes to determine the type of each paragraph (idiom, example or warning). Here is the code:
To avoid being polluted with all the span tag present in the source (the original epub contains a lot more span!), we use the method get_text() of the Soup parser to only extract the raw text. If the paragraph is an idiom, we know the english and french translations are separated by the special character ●. If the paragraph is an example, we search for a phrase separator (dot, question mark, exclamation point, etc). If the paragraph is a warning, we just have the keep the whole text.
For each idiom, we create a new object of type Idiom to group all the information about a given idiom. The collection of idioms is defined globally and will be reused in the second section of this blog post. Here is the definition of the class Idiom:
This ends the first section of this post. We have extracted all the relevant text from the ebook. The next big task is to load all of these idioms directly inside Anki.
Part II: Bulk loading the flashcards
Before importing a flashcard, we need to design the card template. By default, Anki includes only simple card types: Basic, Basic (with reversed card). These card types have only two fields: the front text, and the back text. We need something more evolved to be able to includes examples and/or note information. We want our cards to look like the following picture:
We have two solutions: either we create the card type manually using the Anki API directly in Python, or create the card type through the GUI to benefit the direct feedback when defining the CSS declarations. We will choose the second solution but implementing the first one is relatively easy using code similar to the code we already wrote.
So, run the Anki program, and go to “Tools > Manage Note Types…”, click on “Add”, and choose “Clone: Basic (with reversed card)” as the model to clone. Name our note type Idiom.
Return to the “Manage Note Types” screen and click on “Fields…“. Remove the Front and Back fields and add four new fields like this:
Close the dialog and click on “Cards…“. Again, we need the update the content to match the following screenshot:
Here is the full CSS code:
The back card is very similar. You only need to invert the French and English fields as shown in the following screenshot:
Then, close Anki to force it to write the changes to disk. Let’s go back to our program to add a new line at the end of the source code:
The function bulk_loading_anki iterate over the idioms, and create a new note for each of them. Before that, we need to retrieve our new note type Idiom to define it as the default (like we do when we use Anki through the UI). We also need to retrieve the deck in which to create the notes (English in our example but any deck would works too). Here is the code:
1
Load the collection from the local disk.
2
The code reflects the Anki terminology (note, card, field, deck, tag, etc). If some term are unclear to you, check the official documentation.
3
CSS classes defined in the note type could be used to stylize our cards. Unlike the desktop application, HTML is not escaped.
4
The order of the fields should follow the same order as defined in the GUI.
5
Without the explicit call to the save method, the flashcards would not be saved to disk. Indeed, the Anki application schedules a task every 5 minutes to call this method.
The function highlight_qualifier used in the previous code is defined like this:
Rerun the program and open Anki again. You should see thousands of new card to study! (Go to “Browse” and select the “idiom” tag to see them all).
Our case study is finished. We have converted an ebook purchased online into thousands of flashcards with just one hundred line of code. In the next case study, we are going to create flashcards for the most common words in a language (the first step in practice before learning idioms but this use case is far more complex to automate, the reason why we invert the order in this post).
Case Study: Learn the 5000 most frequent words
Learning the vocabulary of a new language is probably one the best use case for Anki. Many famous polyglot use it daily to practice their vocabulary. To help us identify the list of frequent words in our target language, you can buy a frequency book on Amazon, or you can use free resources like Wikipedia: https://en.wiktionary.org/wiki/Wiktionary:Frequency_lists.
In this post, we are targeting the English language and will use Wikipedia.
The frequency list
Wikipedia currently offers multiple frequency lists based on different sources: films, project Gutenberg containing thousand of freely available classic romans, and even the integral of the Simpsons episodes. The lists are split in several pages (1-999, 1000-1999, and so on). By using the browser developer tool, we could easily extract the HTML to create a single HTML file containing the entire list. Here is a sample of the extracted list based on the project Gutenberg:
Like your browser, most HTML parsers are very tolerant concerning HTML syntax. We don’t need to create a perfectly valid HTML document to be able to parse it. We will continue to use the Python language, and its most popular HTML parser BeautifulSoup.
Here is a basic program to parse this HTML file and generate a csv file:
To convert the previous HTML file to a CSV file, launch the program using the command:
Project Gutenberg is a wonderful place to find famous old roman but we could miss usual words nowadays, so we do the same task with the TV frequency list. This results in two files 40000_frequency_list_gutenberg.csv and 40000_frequency_list_tv.csv having most of the words in common but with different rankings. We need to merge the two list.
Here is a small program to produce a unique CSV file containing only two fields: the rank and the word.
To generate the new list:
If we inspect the resulting file, we notice numerous unwanted words such as “Mickey.” Let’s remove them by updating our program:
We still have not finished. If we look again at the output, we notice words sharing the same radical (ex: bill/bills, displease/displeased). These words could not be filtered as before but could only be removed at the end of the program (when we are sure we have found the different syntaxes).
So, let’s update our program to add the following code just before printing the result:
The definitions
When learning a new language, it is better to left out completely your tongue language of our flashcards. Popular flashcard application Memrise does exactly that. Instead, we will include the definitions written in the same language as the word. To do so, we will use Wiktionary. Like its sister project Wikipedia, Wiktionary is run by the Wikimedia Foundation, and is edited collaboratively by volunteers. This dictionary is available in 172 languages and probably contains the most exhaustive list of words (500 000 words for the english dictionary!).
Reading the data
Wiktionary, like other Wikimedia projects, offers a REST API to retrieve a single page. The API is still in active development. Another option is to exploit the generated dumps. Indeed, Wikimedia hosts numerous dumps of its database (useful for example in natural language processing tasks or for reseach project). The one that interest us is the enwiktionary dump, in particular the first archive described as “Articles, templates, media/file descriptions, and primary meta-pages”.
Once downloaded, we extract the tar.gz archive to find a single XML file with a size of 4,5 GB!
Parsing the Wiktionary dump
The downloaded file contains every dictionary written in the english language (English => English, Spanish => English, …). To avoid parsing the whole file many times, we need to extract only the relevant information.
Here is a preview of its content:
When parsing such a large file, we should be careful and not load the full file in memory. A solution is to use a SAX Parser. But unlike a DOM parser, it is not possible to simply traverse the XML document to extract the relevant information. SAX Parsers are event-driven. We need to listen to each new tag, each character text, and so on. We also need to remember our position inside the file to answer question such as “Does the text correspond to the title tag?“.
The following program extracts the id, title, and text and generates another XML file containing only the English words. The result is a smaller file (less than 300 Mb), that does not take 45 minutes to be parsed on my machine…
When running the program, a new file enwiktionary-frequency.xml appears in the current folder. Here is an excerpt of its content:
This file could quickly be parsed (10s on my local machine). The next step is to parse the value of the text tag to extract all relevant information.
Parsing the x-wiki text to generate a JSON file
The <text> tag contains a x-wiki document whose main syntax is defined here. Wikipedia add a lot of semantic above this syntax through the use of numerous labels. I didn’t find a document that describes the Wiktionary syntax, so we need to revert to reverse engineering.
Here is an shortened example of a x-wiki document (about the word keyboard):
1
A wiktionary entry contains the text of multiple dictionaries (English-English, English-Dutch). Only the English-English dictionary is relevant when learning the English language.
2
A given word could be used as a noun, a verb, etc. We need to extract the definition for each type while keeping note of the type.
3
Label syntax such as {{t+|fr|clavier|m}} is used exhaustively inside the wiki text. Most of the content is present inside these labels but the syntax is not easily parseable.
4
There are many translation blocks. Often, the first one is the only translation we are interested.
On this example, the definitions are easily parseable but not all the words are so simple in practice. Consider the word ‘car’:
Here the same content as x-wiki:
1
Quotes could be on a line starting with #:, be surrounded with ''
2
While other quotes could be on a line containing a label with the attribute passage containing the quote text.
This is only one example of subtlety among many others. Wiktionary syntax is full of surprise but also full of interesting content.
So, we will create another Python program to parse the previously generated XML file. The aim of to generate a structured JSON file with only the required information. For example:
The code behind this program contains a lot of parsing logic. The result is not the most beautiful code that I have written and I am not going to present it in this post but if you are curious, the full listing is available in the project repository in GitHub.
Creating the flashcards
Unlike the previous case study on idioms, creating flashcards for vocabulary is most complicated for different reasons:
Some word contains more than ten definitions or examples. Should we add them all?
Some word would benefit from a memorable image (ex: the word “dog”)
Some word have a pronunciation sound in Wiktionary
Moreover, which type of notes should we select in Anki? The “Basic (with reversed card)” is probably too simplistic. Why not find the word from the image only, the sound only, a given definition, or ask for the translation of the word, etc. There are so many possibilities.
All of these points highlight that we cannot write a program to automatically take all the decisions. So, we will create a basic web application to display the definitions, the examples, and a list of candidate images retrieved from Google Images. Each of these elements will be accompanied with several checkboxes to choose the most relevant definitions, examples and select the most memorable picture.
The Assistant
The assistant reads the JSON dictionary file created previously, displays its content to the user and save a new annotated JSON file containing the choices made by the user. We will return to this file later.
Here is a screenshot of this application:
To launch locally the web application, you need to first retrieve the source and build the archive: (JDK 1.7 > required)
Then, run the following command to start the web application:
Google CSE API
The application uses the Google Custom Search API (GSE) to retrieve a list of images related to the selected word, so you need credentials to access the API. Google limits to 100 free searches per day.
Please visit the official site for more information.
Open your browser and enter the URL http://localhost:8080. You should have the same output as the previous screenshot.
Requisites
In this part, we will use Spring Boot and the Java language for the backend and AngularJS for the frontend. If these technologies are new to you, do not hesitate to read a Getting Starting on these technologies first. You do not need to be expert in these technologies to follow the code along. The presented code uses only basic features of these frameworks.
The entry point is the class Application:
The application defines a unique web controller ApiController that loads the full dictionary in-memory at start-up:
The class Dictionary is a wrapper around the JSON document generated at the end of the previous section:
You could access any word from its rank using the method searchWord:
This method is exposed in REST:
For example, if you want to retrieve the information about the word daughter (340), enter the following URL into your browser: http://localhost:8080/api/word/340
We could now create an application based on AngularJS 1.x and hosted in the same project for convenience. By default, every resource present under the directory src/main/resources/public will be exposed as static resources by Spring Boot. So, let’s create the index page:
1
We add a property include to true when the user select an element.
The HTML is self-descriptive. Let’s inspect the JavaScript code used to bootstrap the AngularJS application:
1
At initialization, we check the URL to determine the word to load before calling the main method showWord.
2
We emit an HTTP request to retrieve the JSON containing the word information. The result is stored in the model to be accessible by the view.
The user could then check every definition, quotation, sound that interesting him. Once this is done, the user presses the touch ‘Enter’ to save the current word and trigger the next word loading.
So, let’s add the code to listen the key press:
The code makes an HTTP request to a new service we need to define in our controller ApiController:
1
We parse the input JSON before passing it to the constructor of the class Word. Like the class Dictionary, this class acts as a Wrapper around the raw JSON document.
2
We use the method save on this object to persist the selection of the user.
The main logic is encapsulated inside the class Word. Let’s see its definition:
1
We download the sound from Wikipedia locally
2
We save the JSON to disk
In definitive, the assistant application reads the full JSON dictionary, exposes its information through a REST API, allows the user to choose the most pertinent information before saving an annotated JSON document to disk such as the following one:
Why saving and not create the flashcards directly inside the method ‘save’?
The problem is that Anki exposes only its internal API written in Python. If the backend of our application was written in Python too, we could have create the flashcards at the same time. Here, we dump the result to a file that we will need to read by another program written in Python. This is task of the Importer.
The full source of the application is available here.
The Importer
This aim of this Python script is to convert a collection of JSON documents (one per word) to flashcards. As for the English idioms, we will use a custom note type that we called Word that we will defined manually through Anki Desktop. The note type Word defines a long list of fields that we will used inside the card templates:
Then, we define the templates for the different possible cards.
Do you know this word?
The Card 1 displays the English word:
Where the front template is defined like this:
And the back template:
Check the official Anki manual to know more about the syntax used in these templates.
What is it?
The Card 2 displays the picture associated the word:
Where the front template is defined like this:
1
We need an additional field to determine if this card is required because some words have a picture but the user only want this picture to be displayed in other cards.
And the back template:
What is the translation?
The Card 3 displays the translation of the word:
Where the front template is defined like this:
And the back template:
Complete this sentence…
The Card 4, Card 5, and Card 6 display a sentence with a missing word and the user should find it (the same concept as Cloze Deletion in Anki):
Where the front template is defined like this:
1
Replace SampleA by SampleB, SampleC
And the back template:
1
Replace AnswerA by AnswerB, AnswerC
Find the word from its definitions
The Card 7 displays a list of definitions:
Where the front template is defined like this:
And the back template:
And finally, the CSS used to stylize the cards:
What we just did could be done through Anki internal API too but using the application is very easy and this task needs to be done only once. So, everything is ready for us to load the flashcards.
Here is the full listing. We already have seen most of the API in the previous case studies.
To launch the program, first close our Anki application if running and enter the following command:
Relaunch Anki. You should see new cards under the English deck.
Congratulations! We finally comes to the end of this lengthy post. We have seen how the internal API of Anki could be used in various scenarios. This is not complete, however. It remains the most important task — study all of these newly created flashcards!
To remember
Anki is written in Python and do not expose a REST API. We need to interact directly with its internal API.
Anki persists the flashcard texts in a SQL database. Media resources are persisted on disk along the database and are referenced directly inside the card text.
The Anki underlying model is partially documented. Use this post as a reference to determine the role of each field or property.
Creating flashcards manually is the recommended way to start learning but when it comes to creating hundreds or thousand of flashcards, using a small program could be welcome.
Creating a flashcard programmatically is relatively easy. The most difficult part is extracting the information you want to add (book, website, …)
Basic programming competency is necessary to get started. The good news is that Python is the most used language in the world to introduce new people to programming. You could find many resource online (MOOC, tutorial, ebook, blog) to get started. Khan Academy is a good start.
About the author
Julien Sobczak works as a software developer for Scaleway, a French cloud provider. He is a passionate reader who likes to see the world differently to measure the extent of his ignorance. His main areas of interest are productivity (doing less and better), human potential, and everything that contributes in being a better person (including a better dad and a better developer).