AnkiSync 2
*.apkg and *.anki2 file structure is very simple, but with some quirks of incompleteness.
*.apkg file structure is a zip of at least two files.
.
├── example
│ ├── collection.anki2
│ ├── collection.anki21 # newer Anki Desktop creates and uses this file instead, while retaining the old one as stub.
│ ├── media # JSON of dict[int, str]
│ ├── 1 # Media files with the names masked as integers
│ ├── 2
│ ├── 3
| └── ...
└── example.apkg
*.anki2 is a SQLite file with foreign key disabled, and the usage of some JSON schemas instead of some tables
Also, *.anki2 is used internally at os.path.join(appdirs.user_data_dir('Anki2'), 'User 1', 'collection.anki2')
, so editing the SQLite there will also edit the database.
However, internal *.anki2 has recently changed. If you need to edit internally, if maybe safer to do in Anki<=2.1.26. If you have trouble running two Anki versions (latest and 2.1.26), see /__utils__/anki2.1.26
.
The media
file is a text file of at least a string of {}
, which is actually a dictionary of keys -- stringified int; and values -- filenames.
Usage
Some extra tables are created if not exists.
from ankisync2 import Apkg
with Apkg("example.apkg") as apkg:
# Or Apkg("example/") also works - the folder named 'example' will be created.
apkg.db.database.execute_sql(SQL, PARAMS)
apkg.zip(output="example1.apkg")
I also support adding media.
apkg.add_media("path/to/media.jpg")
To find the wanted cards and media, iterate though the Apkg
and Apkg.iter_media
object.
for card in apkg:
print(card)
Creating a new *.apkg
You can create a new *.apkg via Apkg
with any custom filename (and *.anki2 via Anki2()
). A folder required to create *.apkg needs to be created first.
apkg = Apkg("example") # Create example folder
After that, the Apkg will require at least 1 card, which is connected to at least 1 note, 1 model, 1 template, and 1 deck; which should be created in this order.
- Model, Deck
- Template, Note
- Card
with Apkg("example.apkg") as apkg:
m = apkg.db.Models.create(name="foo", flds=["field1", "field2"])
d = apkg.db.Decks.create(name="bar::baz")
t = [
apkg.db.Templates.create(name="fwd", mid=m.id, qfmt="{{field1}}", afmt="{{field2}}"),
apkg.db.Templates.create(name="bwd", mid=m.id, qfmt="{{field2}}", afmt="{{field1}}")
]
n = apkg.db.Notes.create(mid=m.id, flds=["data1", "<img src='media.jpg'>"], tags=["tag1", "tag2"])
c = [
apkg.db.Cards.create(nid=n.id, did=d.id, ord=i)
for i, _ in enumerate(t)
]
You can also add media, which is not related to the SQLite database.
apkg.add_media("path/to/media.jpg")
Finally, finalize with
apkg.export("example1.apkg")
Updating an *.apkg
This is also possible, by modifying db.Notes.data
as sqlite_ext.JSONField
, with peewee.signals
.
It is now as simple as,
with Apkg("example1.apkg") as apkg:
for n in apkg.db.Notes.filter(db.Notes.data["field1"] == "data1"):
n.data["field3"] = "data2"
n.save()
apkg.close()
Col.models
, Col.decks
, Col.conf
and Col.dconf
JSON schema of I have created dataclasses
for this at /ankisync2/builder.py. To serialize it, use dataclasses.asdict
or
from ankisync2 import DataclassJSONEncoder
import json
json.dumps(dataclassObject, cls=DataclassJSONEncoder)
collection.anki2
Editing user's This can be found at ${ankiPath}/${user}/collection.anki2
. Of course, do this at your own risk. Always backup first.
from ankisync2 import AnkiDesktop
AnkiDesktop.backup("/path/to/anki-desktop.db")
anki = AnkiDesktop(filename="/path/to/anki-desktop.db")
... # Edit as you please
AnkiDesktop.restore("/path/to/anki-desktop.db")
peewee
framework
Using This is based on peewee
ORM framework. You can use Dataclasses and Lists directly, without converting them to string first.
Examples
Please see /__examples__
, and /tests.
Installation
pip install ankisync2