Hello there, and welcome to my blog! Since I've been inside a lot more recently, I've been re-kindling my love of programming. I hadn't touched my website in years, so I decided to get it back up and running and start this blog.
One of my favorite programs that I get nearly daily use out of is Anki. I've always been mildly curious about writing scripts to create flash cards, but never had reason to get started. As it turns out, my girlfriend has been learning Chinese in her spare time, and to do that she's been using the examples posted on this website (http://chugokugo-script.net/tango/). I thought it would be fun to try and create an Anki deck for her that she could use. What I thought would take a few hours of course took a couple weeks, but that's how it goes with coding. I'm going to outline the steps I took, and if you're reading this, hopefully give you some idea how to do it yourself.
After some scrounging around online, the most useful information I could find was from a programmer in France who had done some work on this back in 2016. Julien's blog is very detailed and in depth, and I recommend reading it if you have the time. He suggests two methods for making flashcards, the first is inserting cards directly into the database via SQL commands, or by writing a python script using Anki's python library. I didn't want to go the SQL path, so that left me with trying to use the Anki API. Now a lot seems to have changed since 2016, so I ran into a few hurdles.
The biggest change is to the code of Anki itself. Anki doesn't ship a standalone API you can use, so you first need to install the Anki repository, as well as any dependencies for Anki's python library. However, when I started referencing the library in my script, I ran into a few errors. Fixing these errors led to more errors, and so on. I thought I would be able to hack it to work with my script, but actually the python library depends on a versioning file which is created by the build process, and so to fix all the errors, I would have to build all of Anki! This is a pain in the ass, and I can't recommend attempting this. You will have to find all the dependencies used by Anki (quite a few), as well as installing the rust compiler. Anyway, after a few hours hunting everything down, I was finally able to get it to build (which on my decade-old Macbook Pro, took about 45 minutes) and at this point I was exhuasted with the whole thing, and needed to take a step back.
Previously you could reference Anki's python library directly in your script, but around the end of 2019, the Anki team rewrote the backend in rust, which is in turn called by the python library. So if you want Anki's python library to actually do something, you need to also build the rust library. This isn't really practical for most people, and honestly I didn't want this to be my solution either. I started thinking about writing a library to add cards to Anki, but surely someone else has already done it...
After some searching on the web, I found genanki,
which is a python library for creating Anki cards. Run the following command with python to grab genanki.
pip install genanki
Genanki will create Anki cards for you by making a standalone Anki package, which you then import into Anki.
This is much simpler than writing SQL commands or trying to get Anki's python library to work.
I will detail the steps below.
I used BeautifulSoup to parse out the data on the website I wanted. There are many guides online on how to do this so I won't go into depth here.
Some Anki terms here:
My Anki package will consist of 3 decks, one each for nouns, verbs and adjectives, as well as 2 note types, one for testing the word,
and the other for testing listening comprehension using the example sentence for that word.
To create a note, you first need to define a model using genanki, which instructs Anki how you want your data (fields) organized, and which cards you would like to create.
sentence_model = genanki.Model(
random.randrange(1 << 30, 1 << 31), #random id number
'Sentence Note', #name of the card
fields=[
{'name': 'Example Sentence'},
{'name': 'Pinyin'},
{'name': 'Translation'},
{'name': 'Audio File'},
{'name': 'Grammar Point'}
],
templates = [
{
name: 'Card 1: Test Listening Comprehension',
qfmt: 'HTML representing front side of the card',
afmt: 'HTML representing back side of the card',
}
]
)
Each card template is a dictionary consisting of name, qfmt (Front), and afmt (Back).
Once you have your model defined, you call the genanki.Note constructor, passing in your model, along with data you've scraped off the web.
After that, you add that note into a deck, which you create with the genanki.Deck constructor.
Make sure to include your fields in the same order as you defined them in the model. For any audio files, you need to enclose the name of the file with '[sound:filename.mp3]'
so Anki knows to expect an audio file.
aNote = genanki.Note(
model=sentence_model,
fields=[
sentence.sentence,
sentence.pronunciation,
sentence.translation,
'[sound:sentence_recording.mp3]',
grammar_field
]
)
deck = genanki.Deck(
random.randrange(1 << 30, 1 << 31),
'Name of Deck'
)
deck.add_note(aNote)
Once you have inserted your notes into your deck, you will need to collect a list of file paths to any media files referenced in your deck(s).
Then you pass this along to the genanki.Package constructor along with your list of deck(s) to create an Anki package.
media_files = []
for word in (nouns + verbs + adjectives):
media_files += word.get_audio_file_paths() #returns local file paths for any audio files associated with the word.
my_package = genanki.Package([noun_deck, verb_deck, adj_deck], media_files)
output_path = 'Chinese_Level_1.apkg' #in the source code I create a package for each level of difficulty (1,2,3)
my_package.write_to_file(output_path)
This is easy, just open Anki, and go to File -> Import... and select your pacakge. Once loaded, test it out to make sure it works as expected.
I included some custom javascript in my Anki cards so I could include buttons that when clicked, show additional information (eg. translation of a word into your native language). This can be useful when you are just starting to learn a card, but eventually you shouldn't need it, so I wanted them hidden by default. I found a little code snippet and included it into my cards. To get jquery to work, you need to download the jquery javascript file, and include it in your Anki package. You need to prefix the filename with an underscore (_), or else Anki will try to delete the file, since the file is only reference in script, Anki thinks it isn't being used (if this behaviour has changed, let me know!). Note I only needed this extra code to get jQuery to work on AnkiDroid. On iOS or PC, it worked fine without explicitly added the jQuery file. In your python code:
media_files.append(local_path + '_script.jquery-3.5.1.min.js') #same array you added your audio files to.
my_package = genanki.Package([noun_deck, verb_deck, adj_deck], media_files)
Then in the html for your card, you can reference jQuery as below:
<script type="text/javascript" src="_script.jquery-3.5.1.min.js"></script>
Get the source code here