Example #1
0
    def test_resync(self):
        from anki.exporting import AnkiPackageExporter
        from anki.utils import intTime

        # create a new collection with a single note
        src_collection = anki.storage.Collection(
            os.path.join(self.temp_dir, 'src_collection.anki2'))
        add_note(
            src_collection, {
                'model': 'Basic',
                'fields': {
                    'Front': 'The front',
                    'Back': 'The back',
                },
                'tags': 'Tag1 Tag2',
            })
        note_id = src_collection.findNotes('')[0]
        note = src_collection.getNote(note_id)
        self.assertEqual(note.id, note_id)
        self.assertEqual(note['Front'], 'The front')
        self.assertEqual(note['Back'], 'The back')

        # export to an .apkg file
        dst1_path = os.path.join(self.temp_dir, 'export1.apkg')
        exporter = AnkiPackageExporter(src_collection)
        exporter.exportInto(dst1_path)

        # import it into the main collection
        import_file(get_importer_class('apkg'), self.collection, dst1_path)

        # make sure the note exists
        note = self.collection.getNote(note_id)
        self.assertEqual(note.id, note_id)
        self.assertEqual(note['Front'], 'The front')
        self.assertEqual(note['Back'], 'The back')

        # now we change the source collection and re-export it
        note = src_collection.getNote(note_id)
        note['Front'] = 'The new front'
        note.tags.append('Tag3')
        note.flush(intTime() + 1)
        dst2_path = os.path.join(self.temp_dir, 'export2.apkg')
        exporter = AnkiPackageExporter(src_collection)
        exporter.exportInto(dst2_path)

        # first, import it without allow_update - no change should happen
        import_file(get_importer_class('apkg'), self.collection, dst2_path)
        note = self.collection.getNote(note_id)
        self.assertEqual(note['Front'], 'The front')
        self.assertEqual(note.tags, ['Tag1', 'Tag2'])

        # now, import it with allow_update=True, so the note should change
        import_file(get_importer_class('apkg'),
                    self.collection,
                    dst2_path,
                    allow_update=True)
        note = self.collection.getNote(note_id)
        self.assertEqual(note['Front'], 'The new front')
        self.assertEqual(note.tags, ['Tag1', 'Tag2', 'Tag3'])
Example #2
0
 def createPackage(self, deck, path, includeSched):
     collection = self.collection()
     if collection is not None:
         deck = collection.decks.byName(deck)
         if deck is not None:
             exporter = AnkiPackageExporter(collection)
             exporter.did = deck["id"]
             exporter.includeSched = includeSched
             exporter.exportInto(path)
             return True
     return False
    def test_resync(self):
        from anki.exporting import AnkiPackageExporter
        from anki.utils import intTime

        # create a new collection with a single note
        src_collection = anki.storage.Collection(os.path.join(self.temp_dir, 'src_collection.anki2'))
        add_note(src_collection, {
            'model': 'Basic',
            'fields': {
              'Front': 'The front',
              'Back': 'The back',
            },
            'tags': 'Tag1 Tag2',
        })
        note_id = src_collection.findNotes('')[0]
        note = src_collection.getNote(note_id)
        self.assertEqual(note.id, note_id)
        self.assertEqual(note['Front'], 'The front')
        self.assertEqual(note['Back'], 'The back')

        # export to an .apkg file
        dst1_path = os.path.join(self.temp_dir, 'export1.apkg')
        exporter = AnkiPackageExporter(src_collection)
        exporter.exportInto(dst1_path)

        # import it into the main collection
        import_file(get_importer_class('apkg'), self.collection, dst1_path)

        # make sure the note exists
        note = self.collection.getNote(note_id)
        self.assertEqual(note.id, note_id)
        self.assertEqual(note['Front'], 'The front')
        self.assertEqual(note['Back'], 'The back')

        # now we change the source collection and re-export it
        note = src_collection.getNote(note_id)
        note['Front'] = 'The new front'
        note.tags.append('Tag3')
        note.flush(intTime()+1)
        dst2_path = os.path.join(self.temp_dir, 'export2.apkg')
        exporter = AnkiPackageExporter(src_collection)
        exporter.exportInto(dst2_path)

        # first, import it without allow_update - no change should happen
        import_file(get_importer_class('apkg'), self.collection, dst2_path, allow_update=False)
        note = self.collection.getNote(note_id)
        self.assertEqual(note['Front'], 'The front')
        self.assertEqual(note.tags, ['Tag1', 'Tag2'])

        # now, import it with allow_update=True, so the note should change
        import_file(get_importer_class('apkg'), self.collection, dst2_path, allow_update=True)
        note = self.collection.getNote(note_id)
        self.assertEqual(note['Front'], 'The new front')
        self.assertEqual(note.tags, ['Tag1', 'Tag2', 'Tag3'])
Example #4
0
def main():
    # opens the collection
    log.info('open collection: %s', options.file)
    cwd = os.getcwd()
    col = anki.Collection(path=options.file)
    work_dir = os.getcwd()
    os.chdir(cwd)

    kd = KanjiDamage(col, log)

    # should update kanji damage deck?
    if options.reset_kd:
        kd.reset(options.kd_file)

    # finds the kanji damage deck and model
    if not kd.get_deck():
        sys.exit('{0}: error: {1}'.format(
            sys.argv[0],
            'couldn\'t find KanjiDamage[ Reordered] deck in the collection, try using option -r to import it'
        ))
    kd_model = kd.get_model()
    if not kd_model:
        sys.exit('{0}: error: {1}'.format(
            sys.argv[0],
            'couldn\'t find KanjiDamage model in the collection, try using option -r to import it'
        ))

    # updates kanji damage deck
    if options.update_kd:
        log.info('updating %s...', kd_model['name'])
        # removes media files
        if options.force_download:
            shutil.rmtree(os.path.join(col.media.dir(), 'assets'),
                          ignore_errors=True)
            shutil.rmtree(os.path.join(col.media.dir(), 'visualaids'),
                          ignore_errors=True)
        kd.update()

    # recreates the kanji damage words deck
    _, kdw_deck = kdw_create(col, kd)

    log.info('writing output file %s...', options.output)
    exporter = AnkiPackageExporter(col)
    exporter.includeSched = False
    exporter.includeMedia = True
    exporter.includeTags = True
    exporter.did = kdw_deck['id']

    out_path = os.path.join(os.getcwd(), options.output)
    os.chdir(work_dir)
    exporter.exportInto(out_path)
    log.info('all is well!')
    col.close()
Example #5
0
def main():
    parser = argparser()
    args = parser.parse_args()

    csv_filename = args.csv_filename
    deck_name = args.deck_name
    apkg_filename = args.apkg_filename
    media_directory = args.media_directory
    model_name = args.model_name

    # this is removed at the end of the program
    TMPDIR = tempfile.mkdtemp()

    collection = anki.Collection(os.path.join(TMPDIR, 'collection.anki2'))

    deck_id = collection.decks.id(deck_name)
    deck = collection.decks.get(deck_id)

    if model_name == 'Image Occlusion Enhanced':
        model = add_io_model(collection)
    else:
        model = collection.models.byName(model_name).copy()

    model['did'] = deck_id

    collection.models.update(model)
    collection.models.setCurrent(model)
    collection.models.save(model)

    importer = TextImporter(collection, csv_filename)
    importer.allowHTML = True
    importer.initMapping()
    importer.run()

    for media_file in os.listdir(media_directory):
        os.symlink(os.path.join(media_directory, media_file),
                   os.path.join(TMPDIR, 'collection.media', media_file))

    export = AnkiPackageExporter(collection)
    export.exportInto(apkg_filename)

    shutil.rmtree(TMPDIR)
def export_deck(did):
    # inspired by aqt.exporting.ExportDialog.accept
    exporter = AnkiPackageExporter(mw.col)
    exporter.includeSched = False
    exporter.includeMedia = gc("include media that starts with underscore"
                               )  # get _styles_for_syntax_highlighting.css
    exporter.includeTags = False
    exporter.includeHTML = False
    exporter.did = did
    now = time.strftime("-%Y-%m-%d@%H-%M-%S", time.localtime(time.time()))
    while 1:
        file = getSaveFile(
            mw,  # parent
            _("Export"),  # windowtitle
            "debug_export_all_notetypes",  # dir_description - used to remember last user choice for dir
            exporter.key,  # key
            exporter.ext,  # ext
            'export_all_notetypes__%s.apkg' % now)  # filename
        if not file:
            return
        if checkInvalidFilename(os.path.basename(file), dirsep=False):
            continue
        break
    if file:
        mw.progress.start(immediate=True)
        try:
            f = open(file, "wb")
            f.close()
        except (OSError, IOError) as e:
            showWarning(_("Couldn't save file: %s") % str(e))
        else:
            os.unlink(file)
            exportedMedia = lambda cnt: mw.progress.update(label=ngettext(
                "Exported %d media file", "Exported %d media files", cnt) % cnt
                                                           )
            addHook("exportedMediaFiles", exportedMedia)
            exporter.exportInto(file)
            remHook("exportedMediaFiles", exportedMedia)
            tooltip("Exporting finished", 3000)
        finally:
            mw.progress.finish()
Example #7
0
def create_apkg(meetup_event_url, meetup_api_key, yes, verbose, debug, noop):
    parse_result = urllib.parse.urlparse(meetup_event_url)
    urlname, _, event_id = parse_result.path.split('/')[1:4]
    client = meetup.api.Client(meetup_api_key)

    event = client.GetEvent(id=event_id, urlname=urlname)

    date = datetime.fromtimestamp(event.time / 1000).strftime('%Y-%m-%d')
    filename = 'meetup-rsvps-{}-{}.apkg'.format(urlname, date)

    ### Output confirmation to user

    if verbose or not yes:
        confirmation_details = """\
            We are using the following configuration:
              * Meetup Group: {group}
              * Event Name:   {name}
                    * Date:   {date}
                    * RSVPs:  {rsvps}"""
        confirmation_details = confirmation_details.format(
            group=urlname,
            name=event.name,
            date=date,
            rsvps=event.yes_rsvp_count)
        click.echo(textwrap.dedent(confirmation_details), err=True)

    if not yes:
        click.confirm('Do you want to continue?', abort=True)

    create_path('outputs')

    with tempfile.TemporaryDirectory() as tmpdir:
        collection = anki.Collection(os.path.join(tmpdir, 'collection.anki2'))

        deck_id = collection.decks.id('Meetup: {}'.format(event.name))
        deck = collection.decks.get(deck_id)

        model = collection.models.new("meetup_model")
        model['did'] = deck_id
        model['css'] = ''
        collection.models.addField(model,
                                   collection.models.newField('person_photo'))
        collection.models.addField(model,
                                   collection.models.newField('person_name'))

        tmpl = collection.models.newTemplate('meetup attendee')
        tmpl['qfmt'] = '{{person_photo}}'
        tmpl['afmt'] = '{{FrontSide}}\n\n<hr>\n\n{{person_name}}'
        collection.models.addTemplate(model, tmpl)

        model['id'] = 123456789
        collection.models.update(model)
        collection.models.setCurrent(model)
        collection.models.save(model)

        note = anki.notes.Note(collection, model)
        note['person_photo'] = ''
        note['person_name'] = ''
        note.guid = 0
        collection.addNote(note)

        response = client.GetRsvps(event_id=event_id)
        for rsvp in response.results:
            if not rsvp.get('member_photo'):
                if verbose:
                    click.echo('Skipping {} (no photo)'.format(
                        rsvp['member']['name']),
                               err=True)
                continue
            if verbose:
                click.echo('Fetching ' + rsvp['member']['name'], err=True)
            name = rsvp['member']['name']
            url = rsvp['member_photo']['photo_link']
            path, contents = retrieveURL(url)
            local_file = collection.media.writeData(path, contents)

            note = collection.newNote()
            note['person_photo'] = '<img src="{}" />'.format(local_file)
            note['person_name'] = name
            note.guid = rsvp['rsvp_id']
            collection.addNote(note)

        # Update media database, just in case.
        # Not sure if this is necessary
        collection.media.findChanges()

        output_filepath = collection.media._oldcwd + '/outputs/' + filename

        export = AnkiPackageExporter(collection)
        export.exportInto(output_filepath)
        click.echo('output_filepath: ' + output_filepath)
Example #8
0
    if len(tweeted_card_ids) > 0:
        tweeted_cards_query = ' or '.join(tweeted_card_ids)

        subdeck_name = "%s::%s" % (BASE_DECK, title)
        subdeck_id = col.decks.newDyn(subdeck_name)
        subdeck = col.decks.get(subdeck_id)

        # Fill the deck with appropriate notes
        subdeck['terms'] = [[tweeted_cards_query, 100, 0]]

        col.decks.save(subdeck)
        col.sched.rebuildDyn()

col.save()

# Export the filtered deck
exporter = AnkiPackageExporter(col)
exporter.did = base_deck_id
exporter.exportInto(OUTPUT_PATH)

# Clean up collection
for did in col.decks.allIds():
    col.decks.rem(did, True)
col.save()

print('Export Deck: Success')

# Clean up
client.close()
Example #9
0
    def convert(self, in_file_str: str, out_file_str: str = None) -> None:
        """Convert a single file to ANKI deck."""

        log.info(
            f"Converting file '{in_file_str}' to ANKI deck @ '{out_file_str}'")

        # Create paths
        in_file = pathlib.Path(in_file_str)
        if out_file_str is not None:
            assert out_file_str.endswith(AnkiConvertor.ANKI_EXT)
            out_file = pathlib.Path(out_file_str).resolve()
        else:
            out_file = in_file.with_suffix(AnkiConvertor.ANKI_EXT).resolve()
        tmp_out_file = out_file.with_suffix(".tmp.apkg").resolve()

        # Convert the org nodes into list of Notes
        cards: typing.List[genanki.Note] = []

        # Preprocess and parse the file
        preprocessed_source = self.preprocessor.preprocess_file(str(in_file))
        org_file = orgparse.loads(preprocessed_source)

        # If user did not supplied the convert mode - try to get the convert mode
        # from the org file header fall back to NORMAL mode
        if not self.user_supplied_convert_mode:
            try:
                self.convert_mode = AnkiConvertMode[org_file._special_comments[
                    self.COMMENT_ANKI_CONVERT_MODE][0].upper()]
            except KeyError:
                self.convert_mode = AnkiConvertMode.NORMAL
        else:
            self.convert_mode = self._convert_mode

        self._get_cards(org_file, cards)

        # Try to set the deck name to a org file title comment
        try:
            deck_name = org_file._special_comments["TITLE"][0]
        except (KeyError, IndexError):
            deck_name = out_file.stem

        # TODO: Hash should be calculated from the cards
        deck = genanki.Deck(random.randrange(1 << 30, 1 << 31), deck_name)

        for c in cards:
            deck.add_note(c)

        package = genanki.Package(deck)
        package.media_files = self.node_convertor.media_files
        package.write_to_file(str(tmp_out_file))

        # Import and export the collection using Anki
        # This is neccessary to make mobile version work (include rendered equations)
        with utils.create_empty_anki_collection() as col:
            log.debug("Importing to tmp ANKI collection")
            imp = AnkiPackageImporter(col, str(tmp_out_file))
            imp.run()
            log.debug("Exporting from tmp ANKI collection")
            exp = AnkiPackageExporter(col)
            exp.exportInto(str(out_file))

        tmp_out_file.unlink()
Example #10
0
 def export(self, archive_file):
     # Exporting the collection
     e = AnkiPackageExporter(self.col)
     e.exportInto(archive_file)
Example #11
0
    def export(deck,filename,tmpPath = '',mediaPath = '',ignoreMedia = False):
        wdir = os.getcwd()

        def cleanFolder():
            os.chdir(wdir)
            # Cleans out the old tmp collection
            if os.path.exists(tmpPath + 'tmp.media'):
                shutil.rmtree(tmpPath + 'tmp.media')

            for f in ['tmp.media.db2','tmp.anki2','tmp.anki2-journal']:
                if os.path.exists(tmpPath + f):
                    try:
                        os.remove(tmpPath + f)
                    except:
                        continue

        cleanFolder()

        # Makes a new one
        tcol = Collection(tmpPath + 'tmp.anki2')

        os.chdir(wdir)

        # Copies media over

        if not ignoreMedia:
            copytree(mediaPath,tmpPath + 'tmp.media')

        os.chdir(wdir)

        # Sets up the decks and pulls them in

        def makeDeck(parent,prefix,deck):
            name = deck.csvname
            csvfile = "%s%s%s.csv" % (tmpPath,prefix,name)

            if not os.path.exists(csvfile) and deck.cardType != None:
                print('Skipping deck "%s" because no file "%s" was found.' % (name, csvfile))
                return
                # raise Exception('No csv file "' + csvfile + '" found.')

            did = tcol.decks.id(parent + deck.name)
            d = tcol.decks.get(did)
            tcol.decks.select(did)

            confId = tcol.decks.confId(parent + deck.name, cloneFrom=deck.conf)

            if not deck.cardType:
                conf = tcol.decks.getConf(confId)
                conf['new']['perDay'] = 999
                tcol.decks.updateConf(conf)
            elif deck.perDay:
                conf = tcol.decks.getConf(confId)
                conf['new']['perDay'] = deck.perDay
                tcol.decks.updateConf(conf)

            tcol.decks.setConf(d,confId)

            if deck.cardType:
                ct = deck.cardType

                if not tcol.models.byName(ct.name):
                    m = tcol.models.new(ct.name)
                    m['req'] = [[0, 'all', [0]]]
                    m['css'] = ct.css()
                    m['tmpls'] = [
                        {
                            'name': 'Card 1',
                            'qfmt': ct.front(),
                            'afmt': ct.back(),
                            'bfont': 'Lucida Sans Unicode',
                            'bamft': '',
                            'bqmft': '',
                            'ord': 0,
                            'did': None,
                            'bsize': 12
                        }
                    ]
                    tcol.models.add(m)

                    for i,field in enumerate(ct.fields):
                        f = tcol.models.newField(field.anki_name)
                        f['ord'] = i
                        tcol.models.addField(m,f)
                else:
                    m = tcol.models.byName(ct.name)

                # So that we can reuse already-present models
                # todo: this doesn't actually work but would be a big part of
                # updating
                # if m['id'] != ct.mid:
                # 	m = tcol.models.get(m['id'])
                # 	m['id'] = ct.mid
                # 	m.save(m)

                tcol.save()

                m['did'] = did
                tcol.decks.select(did)
                ti = TextImporter(tcol,csvfile)
                ti.model = m
                ti.allowHTML = True
                ti.initMapping()
                ti.delimiter = "\t"
                ti.updateDelimiter()

                ti.run()
                tcol.save()

            for sd in deck.subdecks:
                makeDeck(parent + deck.name + '::',prefix + name + '-', sd)

        makeDeck('','',deck)

        os.chdir(wdir)
        apkge = AnkiPackageExporter(tcol)
        apkge.includeSched = True
        apkge.exportInto(filename)

        cleanFolder()
Example #12
0
# Ignore duplicates
importer.importMode = anki.importing.noteimp.ADD_MODE

# Allow HTML in fields
importer.allowHTML = True

# Import notes (cards) into the collection
importer.run()

# The collection needs to be saved before it can be exported
collection.save()

# Export to apkg
exporter = AnkiPackageExporter(collection)
exporter.exportInto(apkg_file)

# Delete temporary files that were created when the collection was created
os.remove(os.path.join(temp_dir, 'collection.anki2'))
os.remove(os.path.join(temp_dir, 'collection.anki2-wal'))
os.rmdir(os.path.join(temp_dir, 'collection.media'))




# TODO: delete this
# https://apps.ankiweb.net/docs/addons20.html

# file = u"/path/to/text.txt"
# # select deck
# did = mw.col.decks.id("ImportDeck")
Example #13
0
        # do we need additional media?
        # if so, download it to the media directory
        if q.startswith('http'):
            url = q
            q = re.search('[^/]*$', q)[0]
            # no need to download if file already exists
            if not pathlib.Path(MEDIA_PATH + q).is_file():
                q_f = open(MEDIA_PATH + q, 'wb')
                print("Downloading", url)
                q_f.write(requests.get(url).content)
                q_f.close()

        if (q.endswith('.jpg') or q.endswith('.jpeg') or q.endswith('.png')
                or q.endswith('.gif')):
            q = '<img src="' + q + '">'

        if q.endswith('.mp3'):
            q = '[sound:' + q + ']'

        note = anki.notes.Note(collection, model)

        note[QUESTION_FIELD] = q
        note[ANSWER_FIELD] = a
        note.guid = abs(hash(q))

        collection.addNote(note)

    export = AnkiPackageExporter(collection)
    export.exportInto(DOCS_PATH + 'out.apkg')
Example #14
0
tmpl = collection.models.newTemplate('en -> ru')
tmpl['qfmt'] = '<div class="from">{{en}}</div>'
tmpl['afmt'] = '{{FrontSide}}\n\n<hr id=answer>\n\n{{ru}}'
collection.models.addTemplate(model, tmpl)
tmpl = collection.models.newTemplate('ru -> en')
tmpl['qfmt'] = '{{ru}}'
tmpl[
    'afmt'] = '{{FrontSide}}\n\n<hr id=answer>\n\n<div class="from">{{en}}</div>'
collection.models.addTemplate(model, tmpl)

model['id'] = 12345678  # essential for upgrade detection
collection.models.update(model)
collection.models.setCurrent(model)
collection.models.save(model)

note = anki.notes.Note(collection, model)
note['en'] = "hello"
note['ru'] = u"[heləʊ]\nint. привет"
note.guid = "xxx1"
collection.addNote(note)

note = collection.newNote()
note['en'] = "bye"
note['ru'] = u"[baɪ]\nint. пока"
note.guid = "xxx2"
collection.addNote(note)

export = AnkiPackageExporter(collection)
export.exportInto(FONAME)