Giter Club home page Giter Club logo

ankisync2's Introduction

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

ankisync2's People

Contributors

patarapolw avatar sosie-js avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

ankisync2's Issues

FileNotFoundError: [Errno 2] No such file or directory: '.../collection.anki21'

When opening an Apkg (deck exported from Anki2.1.15 (442df9d6), with ankisync2 0.3.4 and python 3.10.6), I get the following error :

  File "xxx.py", line 4, in <module>
    with Apkg('xxxx.apkg') as apkg:
  File "xxx/venv/lib/python3.10/site-packages/ankisync2/apkg.py", line 49, in __init__
    shutil.copy(
  File "/usr/lib/python3.10/shutil.py", line 417, in copy
    copyfile(src, dst, follow_symlinks=follow_symlinks)
  File "/usr/lib/python3.10/shutil.py", line 254, in copyfile
    with open(src, 'rb') as fsrc:
FileNotFoundError: [Errno 2] No such file or directory: 'xxxx/collection.anki21'
python-BaseException

Any idea why ?

Adding deck requires common

Hi,
I am trying to create a Deck. I need to do the trick with the connection (I thought it would be merged), so I won't fail on collation.
The thing is that it requires common that shouldn't be NULL.

Do you happen to know what that means and how can I add this?
Do you want to provide a better interface for it?

NEWDECK= "Muldeck"
def unicase_compare(x, y):
    x_ = unidecode(x).lower()
    y_ = unidecode(y).lower()
    return 1 if x_ > y_ else -1 if x_ < y_ else 0
connection=a.db.database.connection()
connection.create_collation("unicase", unicase_compare)
try:
    nd= a.db.Decks.get(a.db.Decks.name.collate("BINARY")==NEWDECK)
except:
    nd = a.db.Decks.create(name=NEWDECK.encode('utf8'),common='???')

How to get field names?

I am really struggling with this, and I feel that the answer is very simple. For each note I can get n.flds to see the field values, but how do I get the field names?

ImportError: cannot import name 'db' from 'ankisync2.apkg'

I am not sure what is going on here (I am not the greatest with python stuff). I installed ankysinc2, like in the README: pip install ankisync2, and ran the first example. There I got an ImportError: cannot import name 'db' from 'ankisync2.apkg' (/home/username/.local/lib/python3.9/site-packages/ankisync2/apkg.py).

I tried it on 2 machines, one running arch and the other debian. No luck on either.

Testing data

I need some test data, and it should be able to be automated and committed to the repo.

NOT NULL constraint failed: col.tags when reexporting a deck without changes

The deck was created with Yomichan, and was exported from Anki. I will add an example db in a bit to make this easily reproducible.

Traceback (most recent call last):
  File "/usr/lib/python3.10/site-packages/peewee.py", line 3177, in execute_sql
    cursor.execute(sql, params or ())
sqlite3.IntegrityError: NOT NULL constraint failed: col.tags

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "...", in <module>
    apkg.export('out.apkg')
  File "/home/user/.local/lib/python3.10/site-packages/ankisync2/apkg.py", line 82, in export
    Anki20(self.orignal_anki2).finalize()
  File "/home/user/.local/lib/python3.10/site-packages/ankisync2/anki20/__init__.py", line 111, in finalize
    c.save()
  File "/usr/lib/python3.10/site-packages/playhouse/signals.py", line 71, in save
    ret = super(Model, self).save(*args, **kwargs)
  File "/usr/lib/python3.10/site-packages/peewee.py", line 6638, in save
    rows = self.update(**field_dict).where(self._pk_expr()).execute()
  File "/usr/lib/python3.10/site-packages/peewee.py", line 1918, in inner
    return method(self, database, *args, **kwargs)
  File "/usr/lib/python3.10/site-packages/peewee.py", line 1989, in execute
    return self._execute(database)
  File "/usr/lib/python3.10/site-packages/peewee.py", line 2496, in _execute
    cursor = database.execute(self)
  File "/usr/lib/python3.10/site-packages/peewee.py", line 3190, in execute
    return self.execute_sql(sql, params, commit=commit)
  File "/usr/lib/python3.10/site-packages/peewee.py", line 3174, in execute_sql
    with __exception_wrapper__:
  File "/usr/lib/python3.10/site-packages/peewee.py", line 2950, in __exit__
    reraise(new_type, new_type(exc_value, *exc_args), traceback)
  File "/usr/lib/python3.10/site-packages/peewee.py", line 191, in reraise
    raise value.with_traceback(tb)
  File "/usr/lib/python3.10/site-packages/peewee.py", line 3177, in execute_sql
    cursor.execute(sql, params or ())
peewee.IntegrityError: NOT NULL constraint failed: col.tags

TypeError: col_pre_save() got an unexpected keyword argument 'created'

Traceback (most recent call last):
  File "main3.py", line 4, in <module>
    apkg = Apkg("/home/mvanaltvorst/Downloads/sample.apkg")
  File "/usr/lib/python3.7/site-packages/ankisync2/anki.py", line 101, in __init__
    super().__init__(self.folder.joinpath("collection.anki2"))
  File "/usr/lib/python3.7/site-packages/ankisync2/anki.py", line 21, in __init__
    db.Col.create()
  File "/usr/lib/python3.7/site-packages/peewee.py", line 6012, in create
    inst.save(force_insert=True)
  File "/usr/lib/python3.7/site-packages/playhouse/signals.py", line 70, in save
    pre_save.send(self, created=created)
  File "/usr/lib/python3.7/site-packages/playhouse/signals.py", line 51, in send
    responses.append((r, r(sender, instance, *args, **kwargs)))
TypeError: col_pre_save() got an unexpected keyword argument 'created'

Hanging and taking insane amounts of memory on loading apkg

$ ps aux | grep python
louis     8416 47.6 66.6 14788800 11071084 pts/1 S  17:34   5:54 python3 main.py

I took this straight from the README, nothing else:

from ankisync2.apkg import Apkg, db     
apkg = Apkg("nouns.apkg")

nouns.apkg is a 5.5mb apkg file with audio media.

basically the program started hanging

$  ps aux | grep python  
louis     8416 47.6 66.6 14788800 11071084

The fourth and fifth columns are, unfortunately, in kilobytes

so that's about 14gb of memory + swap being taken if i'm not mistaken (i left it running for a while to see what would happen before it started freezing up my machine)

anyway -- seems to indicate that my DB (it's a shared deck) is doing something that makes this program apparently very unhappy and looping. It got as far as creating a a 13.6mb collection.anki20, after that the program just seemed to be repeatedly consuming more memory.

happy to attach nouns.apkg if you want to look into it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.