Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to import media file from memory instead of from hdd? #102

Open
dragancevs opened this issue Jan 29, 2022 · 4 comments
Open

How to import media file from memory instead of from hdd? #102

dragancevs opened this issue Jan 29, 2022 · 4 comments

Comments

@dragancevs
Copy link

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.

@yash-fn
Copy link

yash-fn commented Feb 3, 2022

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.

@yash-fn
Copy link

yash-fn commented Feb 3, 2022

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)

@dragancevs
Copy link
Author

Thank you very much for your help. I will try it.

yash-fn added a commit to yash-fn/genanki that referenced this issue Feb 7, 2022
yash-fn added a commit to yash-fn/genanki that referenced this issue Feb 7, 2022
yash-fn added a commit to yash-fn/genanki that referenced this issue Feb 7, 2022
yash-fn added a commit to yash-fn/genanki that referenced this issue Feb 8, 2022
@yash-fn
Copy link

yash-fn commented Feb 8, 2022

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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants