def main(fd, username, password): ankipath = "./empty-user/collection.anki2" col = anki.Collection(ankipath, log=True) k = col.backend.sync_login(username=username, password=password) col.backend.full_download(k) with fd: decks = json.load(fd)['decks'] decks = list(map(lambda deck: parseDeck(deck), decks)) model = get_model(col) anki_deck = next( filter(lambda d: d['name'] == "Roam2Anki", col.decks.all())) anki_deck_id = anki_deck['id'] notes = [anki.notes.Note(col, id=noteid) for noteid in col.find_notes("")] for card in decks[0].cards: success = update_note(notes, card) if not success: print("should create this card", card) add_note(col, model, anki_deck_id, card) col.save(trx=False) col.backend.full_upload(k)
def test_make_anki_deck_from_srt_file(self): with tempfile.TemporaryDirectory() as tmpdir: apkg_path = make_anki_deck_from_srt_file(self.TEST_SRT_PATH, name="test", output_dir=Path(tmpdir)) self.assertTrue(apkg_path.is_file()) with zipfile.ZipFile(apkg_path) as zip: zip.extractall(tmpdir) anki2_path = Path(tmpdir) / "collection.anki2" collection = anki.Collection(str(anki2_path)) anki_cards = [ collection.getCard(card_id) for card_id in collection.findCards("") ] self.assertEqual(len(anki_cards), 6) anki_cards_as_text = [ get_anki_card_as_text(card) for card in anki_cards ] self.assertEqual( {f"{card.front}{card.back}" for card in self.EXPECTED_CARDS}, set(anki_cards_as_text))
def main(): args = parse_arguments() validate_args(args) if args.url: # Download the CSV to a tempfile csv_path = download_csv(args.url) elif args.path: # Use an existing CSV file. We convert this to an absolute path because # CWD might change later csv_path = os.path.abspath(args.path) else: assert False # Should never reach here if args.no_anki_connect: import anki # Normally you use aqt.mw.col to access the collection, but we don't want # to use the GUI. Note that this changes the cwd to the Anki # collection.media directory col = anki.Collection(args.col) import_csv(col, csv_path, args.deck, args.note, args.allow_html, args.skip_header) print('[W] Cards cannot be automatically synced, ' 'open Anki to sync them manually') else: send_to_anki_connect(csv_path, args.deck, args.note) print('[+] Syncing') invoke_ac("sync") # If we downloaded this file from a URL, clean it up if args.url: os.remove(csv_path) print('[+] Removed temporary files')
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()
def _init_load_collection(self, base, path): """Load the Anki collection""" from pathlib import Path from sqlite3 import OperationalError import anki from aqt.profiles import ProfileManager import click # Save CWD (because Anki changes it) save_cwd = os.getcwd() if path is None: basepath = Path(base) if not (basepath / 'prefs21.db').exists(): click.echo('Invalid base path!') click.echo(f'path = {basepath.absolute()}') raise click.Abort() # Initialize a profile manager to get an interface to the profile # settings and main database path; also required for syncing self.pm = ProfileManager(base) self.pm.setupMeta() self.pm.load(self.pm.profiles()[0]) # Load the main Anki database/collection path = self.pm.collectionPath() else: self.pm = None try: self.col = anki.Collection(path) except AssertionError: click.echo('Path to database is not valid!') click.echo(f'path = {path}') raise click.Abort() except OperationalError: click.echo('Database is NA/locked!') raise click.Abort() # Restore CWD (because Anki changes it) os.chdir(save_cwd)
def _init_load_collection(self, base, path, profile): """Load the Anki collection""" # Save CWD (because Anki changes it) save_cwd = os.getcwd() if path is None: if base is None: click.echo('Base path is not properly set!') raise click.Abort() basepath = Path(base) if not (basepath / 'prefs21.db').exists(): click.echo('Invalid base path!') click.echo(f'path = {basepath.absolute()}') raise click.Abort() # Initialize a profile manager to get an interface to the profile # settings and main database path; also required for syncing self.pm = ProfileManager(base) self.pm.setupMeta() if profile is None: profile = self.pm.profiles()[0] # Load the main Anki database/collection self.pm.load(profile) path = self.pm.collectionPath() else: self.pm = None try: self.col = anki.Collection(path) except AssertionError: click.echo('Path to database is not valid!') click.echo(f'path = {path}') raise click.Abort() except anki.errors.DBError: click.echo('Database is NA/locked!') raise click.Abort() # Restore CWD (because Anki changes it) os.chdir(save_cwd)
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)
cmd_folder = "/usr/share/anki" if cmd_folder not in sys.path: sys.path.insert(0, cmd_folder) try: import anki import anki.sync except ImportError: sys.exit( 'anki must be installed to /usr/share/anki. first cd /usr/share/. Then ' + 'git clone https://github.com/dae/anki') # the collection should be in the current directory collection_filename = os.path.dirname( os.path.realpath(__file__)) + '/' + config['collection_filename'] collection = anki.Collection(collection_filename, log=True) def tts(text): global ankitts_file print "saying: " + text write_to_log("saying: " + text) # subprocess.call(['flite', '-t', text]) # subprocess.call(['anki-tts.sh', text]) subprocess.call([ankitts_file, text]) # print "done tts" def crap_tts(text): print "saying this crappily: " + text write_to_log("saying this crappily: " + text)
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)
#!/usr/bin/env python3 # Example how to make the guids already imported cards compatible with genanki import sys sys.path.insert(0, "/usr/share/anki/") import anki sys.path.insert(0, "../genanki/") import genanki.util as util deck_name = "General::IT" ac = anki.Collection("/home/tr/.local/share/Anki2/test/collection.anki2") deck_id = ac.decks.id(deck_name) ac.decks.select(deck_id) notes = ac.findNotes("deck:" + deck_name) changed = 0 for nid in notes: note = ac.getNote(nid) new_guid = util.guid_for(note["example"]) if note.guid != new_guid: note.guid = new_guid note.flush(mod=True) print("updated " + note["example"] + ": " + str(note.guid))
def setup(self): # TODO make this less messy colf = tempfile.NamedTemporaryFile(suffix='.anki2') colf_name = colf.name colf.close() # colf is deleted self.col = anki.Collection(colf_name)
DOCS_PATH = '/home/cowley/Documents/' TMP_PATH = '/tmp/' APKG_PATH = DOCS_PATH + 'accelerated_spanish_empty.apkg' CSV_PATH = DOCS_PATH + 'spanish5.csv' TMP_COL_PATH = TMP_PATH DECK_NAME = 'accelerated spanish' DECK_ID = None #1518389138178 MODEL_NAME = 'Basic' QUESTION_FIELD = 'Front' ANSWER_FIELD = 'Back' MEDIA_PATH = '/home/cowley/.local/share/Anki2/User 1/collection.media/' with open(CSV_PATH) as f: reader = csv.reader(f, delimiter='\t') collection = anki.Collection(TMP_COL_PATH + str(random.randint(10**9, 10**10)) + '.anki2') api = AnkiPackageImporter(collection, APKG_PATH) api.run() collection = api.col # get deck try: deck_id = collection.decks.id(DECK_NAME) except: print('invalid deck name') raise collection.decks.select(deck_id) # get model to use try:
col: anki.collection._Collection): change_model_to_cloze(col) note = col.newNote(forDeck=False) note.model()['did'] = col.decks.id("200") # type: ignore left_sap, right_sap = [sap.remove_subs() for sap in dap.split()] if left_sap.get_size()[1] > 400: left_sap, right_sap = (left_sap.resize(scale=0.5), right_sap.resize(scale=0.5)) left_pic = col.media.addFile(left_sap.get_filename()) right_pic = col.media.addFile(right_sap.get_filename()) note.fields[0] = get_cloze_field(left_pic, dap.left_adjective, right_pic, dap.right_adjective) note.tags = (list(SHARED_TAGS) + [dap.left_adjective, dap.right_adjective] + ['double']) print("Adding an image for " + str((dap.left_adjective, dap.right_adjective))) col.addNote(note) note.flush() def main(anki_collection: anki.collection._Collection): daps = load_images() for dap in daps: add_a_double_cloze_note(dap, col) col.save() if __name__ == "__main__": with closing(anki.Collection(str(ANKI_DB))) as col: main(col)
import sys import tempfile import anki from anki.exporting import AnkiPackageExporter from anki.importing.csvfile import TextImporter if len(sys.argv) < 3: sys.exit('Usage: {0} INPUT.csv OUTPUT.apkg'.format(sys.argv[0])) csv_file = os.path.abspath(sys.argv[1]) apkg_file = os.path.abspath(sys.argv[2]) temp_dir = tempfile.gettempdir() collection = anki.Collection(os.path.join(temp_dir, 'collection.anki2')) # Create a new deck in the collection # TODO: it would be nice to use a more user-friendly deck name (e.g. from a README?) deck_id = collection.decks.id(os.path.splitext(os.path.basename(apkg_file))[0]) # Set note type to Basic (and reversed card) model = collection.models.byName('Basic (and reversed card)') # This is necessary so that the import saves new cards into the new deck we just created model['did'] = deck_id # Save above changes to the model collection.models.save(model) # Add a description to the deck
#!/usr/bin/env python3 """Transform cards from "Fluent Forever" to my format.""" # Download https://github.com/dae/anki and extract anki/anki import anki col = anki.Collection('grzesiek/collection.anki2') minimal_pair_model = col.models.byName('MinimalPair') gmp_notes = col.findNotes('"deck:Foreign Languages::*German Minimal Pairs*"') german_deck = col.decks.byName('Foreign Languages::German') processed = set() for gmp_note in [col.getNote(n_id) for n_id in gmp_notes]: gmp_note_fields = dict(gmp_note.items()) new_note = anki.notes.Note(col, minimal_pair_model) new_note.model()['did'] = german_deck['id'] new_note.fields[0] = gmp_note_fields['Recording 1'] new_note.fields[1] = '{0}<br/>/{1}/'.format( gmp_note_fields['Word 1'], gmp_note_fields['Word 1 IPA'], ) new_note.fields[2] = gmp_note_fields['Recording 2'] new_note.fields[3] = '{0}<br/>/{1}/'.format( gmp_note_fields['Word 2'], gmp_note_fields['Word 2 IPA'], ) new_note_key = (new_note.fields[0], new_note.fields[1]) if new_note_key not in processed: col.addNote(new_note) processed.add(new_note_key) picture_words_model = col.models.byName('Final 2. Picture Words') basic_fb_model = col.models.byName('BasicFB') pwm_notes = [
# addon.write(entry.path, entry.name) # addon.close() if __name__ == "__main__": print("Rebuilding...") if not config.BUILDDIR.exists(): config.BUILDDIR.mkdir() if not config.DISTDIR.exists(): config.DECKFILE.mkdir() # creating new collection file print(f"Saving to {config.DECKFILE}...") config.DECKFILE.unlink(missing_ok=True) coll = anki.Collection(config.DECKFILE, server=False, log=False) coll.decks.add_config("Interactive Demo") create_models(coll) print(f"Added {len(coll.models.all())} models") create_notes(coll) print(f"Added {coll.noteCount()} notes") # remove all default models for m in coll.models.all(): if m['name'] not in models_def.MODELS: coll.models.remove(m['id']) coll.close()
import anki from anki.exporting import AnkiPackageExporter import os TMPDIR = "/tmp" FBASENAME = "test" FONAME = "/home/rainman/testdata/test.apkg" collection = anki.Collection(os.path.join(TMPDIR, 'collection.anki2')) deck_id = collection.decks.id(FBASENAME + "_deck") deck = collection.decks.get(deck_id) deck['mid'] = collection.models.byName('Cloze') model = collection.models.new(FBASENAME + "_model") model['tags'].append(FBASENAME + "_tag") model['did'] = deck_id model['css'] = """ .card { font-family: arial; font-size: 20px; text-align: center; color: black; background-color: white; } .from { font-style: italic; } """