Open dragancevs opened 2 years ago
Easy. Just import genanki then create a new package class that inherits from genanki's package class. Then simply override the requisite write_to_file function to a custom one that writes media files from either memory or db or whatever by switching out the outzip.write to a outzip.writestr with a bytes array of the media data you want to write.
Here is something off the cuff totally untested but should give you something to start with: You then must specify a media_function with three args (outzip, path, idx) that will use outzip.writestr to write whatever you want to resultant apkg/zip file.
from genanki import package
from typing import Optional
import zipfile
from copy import deepcopy
import os
import json
class Package(package.Package):
def __init__(self, deck_or_decks=None, media_files=None, media_function=None):
super().__init__(deck_or_decks, media_files)
self.media_function = media_function
def write_to_file(self, file, timestamp: Optional[float] = None):
if callable(self.media_function):
media_files = deepcopy(self.media_files)
self.media_files = []
ret = super().write_to_file(file, timestamp)
self.media_files = media_files
with zipfile.ZipFile(file, 'a') as outzip:
media_file_idx_to_path = dict(enumerate(self.media_files))
media_json = {idx: os.path.basename(path) for idx, path in media_file_idx_to_path.items()}
outzip.writestr('media', json.dumps(media_json))
for idx, path in media_file_idx_to_path.items():
self.media_function(outzip, path, idx)
return ret
else:
return super().write_to_file(file, timestamp)
Thank you very much for your help. I will try it.
Hey, so don't use what I previously wrote. it will leave duplicate media files (not sure if this can cause errors, but just to be safe here is alternative). Use this instead the following. When tested on my own decks it worked fine. Also below that is an example of a media_function that I used to get values from db. Notice how genanki only really needs idx and data for said idx, while path really can be whatever you want it to be in collection.media anki folder. Hence use path to be unique identifier for you to identify the correct asset to write in media function.
from genanki import package
from typing import Optional
import zipfile
from copy import deepcopy
import os
import json
class Package(package.Package):
def __init__(self, deck_or_decks=None, media_files=None, media_function=None):
super().__init__(deck_or_decks, media_files)
self.media_function = media_function
def write_to_file(self, file, timestamp: Optional[float] = None):
with tempfile.NamedTemporaryFile() as dbfilename:
with sqlite3.connect(dbfilename.name) as conn:
cursor = conn.cursor()
if timestamp is None: timestamp = time.time()
id_gen = itertools.count(int(timestamp * 1000))
self.write_to_db(cursor, timestamp, id_gen)
with tempfile.NamedTemporaryFile(dir=os.path.dirname(file), suffix='.apkg', delete=False) as tempapkg:
with zipfile.ZipFile(tempapkg.name, 'w') as outzip:
outzip.write(dbfilename.name, 'collection.anki2')
media_file_idx_to_path = dict(enumerate(self.media_files))
media_json = {idx: os.path.basename(path) for idx, path in media_file_idx_to_path.items()}
outzip.writestr('media', json.dumps(media_json))
for idx, path in media_file_idx_to_path.items():
if callable(self.media_function):
self.media_function(outzip, idx, path)
else:
outzip.write(path, str(idx))
try:
shutil.move(tempapkg.name, file)
except Exception as e:
tempapkg.close()
raise e
def media_function(outzip, idx, path):
hash_id, table = media_files[path]
data = con.execute(f'select data from {table} where hash_id = ?', (hash_id,)).fetchone()[0]
outzip.writestr(str(idx), data)
Hello, is there a way how to insert media file from memory instead of hdd? I want copy every image to the memory and then insert it to the card. Now i’m importing many images to the cards, the problem is that i must rename every image before i can insert it to the card. Or is there another better way? I know i can create renamed copy of every image but it would unnecessarily strains the hdd.