How to Include Media in Anki Cards Using Genanki
Today I Learnt
03 December 2024
This blog post has been migrated from errbufferoverfl.me to garden.errbufferoverfl.me.
Welcome to the first TIL ✌🏻, where I share small lessons I’ve learned. These generally aren’t substantial enough to be fully-featured blog posts but are worth sharing, just in case someone else is tackling the same problem.

Genanki is a Python package that allows you to programmatically generate decks for Anki, a popular spaced-repetition flashcard program. Recently, I have been working on refactoring a Python application that generates a deck based on a list of words given.
Part of this work involved storing all the image and sound resources to a tmp/ directory so at the end, cleaning temporary resources was easy. Another important note about this new implementation is it is Object Oriented (OO). So I have two key classes that are of importance to this TIL: AnkiCard and AnkiDeck.
Downloading and Storing Images
After downloading the image, we store it:
# AnkiCard.py
def __download_image(self, url: str):
img_data = requests.get(url, headers={'User-Agent': 'Mozilla/5.0'})
img_data.raise_for_status()
# Identify MIME type and then set the extension type
img_mime_type = mimetypes.guess_type(url.split("?")[0], strict=True)
img_extension = mimetypes.guess_extension(img_mime_type[0], strict=True)
# OUTPUT_DIRECTORY = Path("./tmp").absolute()
img_path = self.OUTPUT_DIRECTORY / "images"
img_name = f"{slugify(self.translation_dict['de'], separator='_')}{img_extension}"
img_file = img_path / img_name
if not img_path.exists():
os.makedirs(img_path)
with open(img_file, "wb") as f:
logging.info(f"Downloading: {img_name}")
f.write(img_data.content)
self.__resize_image(img_file)
return img_fileOnce an image is saved, for example: /tmp/images/die_tante.png, we can continue to do other tasks and finally create an Anki note:
Why Only the Media Name?
When creating the note, we don’t use the full path to the media, just the media name. After debugging, I learned that if you use the full path for the media, it won’t load (even if the media exists at the provided absolute path). You just need to provide the media name.
An important note is that when creating the media library, you do need to provide the full path to the media:
# AnkiDeck.py
def __build_media_lib(self):
media = list()
for card in self.anki_cards:
media.append(card.image)
media.append(card.audio)When your deck is created, the media library (__build_media_lib(self)) copies the resources into your Anki package, while the card field:
fields=[card.translation_dict["de"],
card.translation_dict["en"],
f'<img src="{card.image.name}">',
f"[sound:{card.audio.name}]"])simply references the media path in the package.
You can observe this by renaming an .apkg file to .zip and unzipping it. You’ll see files named after numbers, e.g., 5988, 5989. These are the media files that the cards use. You’ll also find a file called media, which contains a JSON file mapping images to their original names:
{"5988": "sapi5-c11ad087-de1c62eb-797700be-a324a996-51fe3627.mp3",
"5989": "sapi5com-1bd3bb72-9bc0090b-9216f53b-d44a2ebe-b8bb1fe8.mp3"}So, in this example, if you locate file 5989 and rename it with the .mp3 extension, you’ll have a working audio file.
To summarize: when these files are saved in the root directory of the .apkg and not embedded in the card itself using the absolute path, it makes sense that when adding the card field, you only need to use the name and not the path.