Package: ankisync2
AnkiSync 2*.apkg and *.anki2 file structure is very simple, but with some quirks of incompleteness.*.apkg file structureis 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 ofsome JSON schemasinstead ofsome tablesAlso, *.anki2 is used internally atos.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.Themediafile is a text file of at least a string of{}, which is actually a dictionary of keys -- stringified int; and values -- filenames.UsageSomeextra tablesare created if not exists.fromankisync2importApkgwithApkg("example.apkg")asapkg:# 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 theApkgandApkg.iter_mediaobject.forcardinapkg:print(card)Creating a new *.apkgYou can create a new *.apkg viaApkgwith any custom filename (and *.anki2 viaAnki2()). A folder required to create *.apkg needs to be created first.apkg=Apkg("example")# Create example folderAfter 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, DeckTemplate, NoteCardwithApkg("example.apkg")asapkg: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)fori,_inenumerate(t)]You can also add media, which is not related to the SQLite database.apkg.add_media("path/to/media.jpg")Finally, finalize withapkg.export("example1.apkg")Updating an *.apkgThis is also possible, by modifyingdb.Notes.dataassqlite_ext.JSONField, withpeewee.signals.It is now as simple as,withApkg("example1.apkg")asapkg:forninapkg.db.Notes.filter(db.Notes.data["field1"]=="data1"):n.data["field3"]="data2"n.save()apkg.close()JSON schema ofCol.models,Col.decks,Col.confandCol.dconfI have createddataclassesfor this at/ankisync2/builder.py. To serialize it, usedataclasses.asdictorfromankisync2importDataclassJSONEncoderimportjsonjson.dumps(dataclassObject,cls=DataclassJSONEncoder)Editing user'scollection.anki2This can be found at${ankiPath}/${user}/collection.anki2. Of course, do this at your own risk. Always backup first.fromankisync2importAnkiDesktopAnkiDesktop.backup("/path/to/anki-desktop.db")anki=AnkiDesktop(filename="/path/to/anki-desktop.db")...# Edit as you pleaseAnkiDesktop.restore("/path/to/anki-desktop.db")UsingpeeweeframeworkThis is based onpeeweeORM framework. You can use Dataclasses and Lists directly, without converting them to string first.ExamplesPlease see/__examples__, and/tests.Installationpipinstallankisync2Related projectshttps://github.com/patarapolw/ankisynchttps://github.com/patarapolw/AnkiTools