Skip to content

patarapolw/ankisync2

master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
February 7, 2022 09:28
February 7, 2022 15:13
February 7, 2022 15:13
February 7, 2022 13:25
February 7, 2022 09:28
February 7, 2022 09:28
July 30, 2019 13:17
February 7, 2022 15:55
February 7, 2022 15:55

AnkiSync 2

PyPI version shields.io PyPI license

*.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.

  1. Model, Deck
  2. Template, Note
  3. 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()

JSON schema of Col.models, Col.decks, Col.conf and Col.dconf

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)

Editing user's collection.anki2

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")

Using peewee framework

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

Related projects

About

Creating and editing *.apkg and *.anki2 safely

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages