Home

Awesome

Anki Deck Manager

Anki Deck Manager (anki-dm) is a tool for creating and maintaining multilingual multi-variant Anki decks. It reads files organized in a specific way and generates CrowdAnki JSON decks. I.e. it doesn't create some installable *.apkg files for Anki, you should use CrowdAnki add-on to install JSON decks.

How it worked before (CrowdAnki only)

CrowAnki add-on defines a JSON format for storing decks data and can export/import into it.

But it's actually not that convenient to edit big JSON files manually and there are no tools which would considerably simplify the process. In other words, CrowdAnki doesn't change the way how you manage decks, the editing workflow remains the same: you edit your deck inside Anki application.

How it works now (CrowdAnki + Anki DM)

Anki Deck Manager disassembles CrowdAnki deck into a collection of files and directories which are really easy to maintain. Specifically, given a CrowdAnki deck at its input it generates a file structure like this:

src/
  decks/
    DeckName/
      build.json
      desc.html
  fields/
    Front.json
    Back.json
  media/
    Card-1.png
    Card-2.png
  templates/
    Card 1.html
  config.json
  data.csv
  deck.json
  desc.html
  model.json
  style.css

These files are then used to create CrowdAnki decks during the build process (see below). They are divided into two levels: global and deck-specific.

Global level files

It contains files which are shared between decks (or deck variants):

These files are considered to be common for all the decks, but some of them can be overridden at the deck-specific level.

Deck level files

You can create unlimited amount of deck variants. They are stored inside the decks/ directory. Each can have its own list of fields and templates as well as description, options and styles.

{
  "deck": {
    "uuid": "a49aa3a4-8f80-11e8-a999-c86000cb6fe2"
  },
  "config": {
    "uuid": "a49aa3a4-8f80-11e8-a999-c86000cb6fe2",
    "name": "Default"
  },
  "model": {
    "uuid": "a49aa3a4-8f80-11e8-a999-c86000cb6fe2"
  },
  "fields": [
    "Front",
    "Back"
  ],
  "templates": [
    "Card 1"
  ]
}

The files: desc.html, style.css, config.json, model.json and deck.json - serve the same purpose as their parents at the global level but override them.

You cannot override however data.csv file. That one, along with field and template definitions cannot be redefined at the deck level. This is by design.

Data file structure

The data file - data.csv - is just a regular CSV file with the header. Each field from the fields/*.json list must be represented here as a column.

In addition there are two special columns: guid and tags:

guid,       Name,     Flag,                           tags
e+/O]%*qfk, England,  "<img src=""England.png"" />",  Europe
h~5xz+=ke~, Scotland, "<img src=""Scotland.png"" />", Europe

The tags column contains whitespace-separated lists of tags.

The guid column is an identifier for a row. For the same data it should always be the same or deck update for this row won't work. When you add new rows just leave the guid cells empty and run anki-dm index once you're done.

Data translation

To localize a deck simply add new columns with translation. Anki DM will parse the column names trying to extract language code which is specified via suffix: <FieldName>:fr.

Consider the example (spaces are added for readability):

guid,       Name,  Color,   tags
qSYi3}_Rdg, Red,   #ff0000, ""
q`KoKFfXAS, Green, #00ff00, ""
qSYi3}_Rdg, Blue,  #0000ff, ""

It defines 3 rows of the data and 2 columns without language specification. In this form the language is considered to be undefined or default. Let's now add French color names:

guid,       Name,  Name:fr, Color,   tags
qSYi3}_Rdg, Red,   Rouge,   #ff0000, ""
q`KoKFfXAS, Green, Vert,    #00ff00, ""
qSYi3}_Rdg, Blue,  Bleu,    #0000ff, ""

Important GUID notes

Initial implementation allowed you to preserve original notes IDs. When you imported a deck with existing data, GUID values were encoded using the model's UUID:

new_guid = original_guid - model_uuid

Apparently when we built a deck that we'd imported, we could restore the original GUIDs by a reverse procedure - decoding:

original_guid = new_guid + model_uuid

This made it possible to continue studying our favorite deck when we migrated it from *.apkg to Anki DM. And while this perfectly worked in Anki for Desktop it turned to be broken in AnkiDroid which is very bad at updating decks.

For this reason we basically cannot continue using original UUIDs anymore and they are deliberately overwritten with new values. What this means is that if you imported some deck and then built it using Anki DM, it will be treated as a new deck by Anki Desktop/Droid.

What is guaranteed however is the persistence of notes across builds.

Installation

Anki DM is a PHP script. To run it you need to install:

Then create the composer.json file with the following minimal content:

{
  "repositories": [
    {
      "type": "vcs",
      "url": "https://github.com/OnkelTem/anki-dm"
    }
  ],
  "require": {
    "onkeltem/anki-dm": "@dev"
  }
}

Now if you run composer install it should download and install everything. The anki-dm executable will be installed under ./vendor/bin/ directory.

Usage

You can get the full list of options and commands via anki-dm --help.

Usually you do something like this:

Instead of starting with an existing deck you can use init command which imports an empty deck from one of predefined templates, e.g.:

$ anki-dm init Default

Making a copy of a deck can be done it two ways. The first one is obvious: just copy the deck's directory with a new name:

$ cp -r decks/ExistingDeck decks/NewDeck

But doing so you're also copying UUIDs of the deck, model and config (they are stored in the build.json file). If you then build two decks with different models (i.e. different lists of fields) but with the same Model UUID it can drive Anki mad and not without a reason, eh?

Instead please use the copy command:

$ anki-dm copy ExistingDeck NewDeck

It will not only copy files recursively, but will also generate appropriate UUIDs.