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'])
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 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()
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'])
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)
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()
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()
def export(self, archive_file): # Exporting the collection e = AnkiPackageExporter(self.col) e.exportInto(archive_file)
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()
print('importer.foreignNotes(): {0}'.format(len(importer.foreignNotes()))) # 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
# 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')
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)