Skip to content

mnemocards_anki

Mnemocards package with Anki related tasks.

package

Package

Bases: PydanticTask

Create an Anki package.

Attributes:

Name Type Description
path Path

Path (directory + filename) of the output *.apkg file.

Source code in src/mnemocards_anki/package.py
class Package(PydanticTask):
    """Create an Anki package.

    Attributes:
        path: Path (directory + filename) of the output `*.apkg` file.
    """

    path: Path = Path("out.apkg")
    _notes: Dict[Deck, Dict[NoteType, List[NoteDict]]] = pydantic.PrivateAttr(
        defaultdict(lambda: defaultdict(lambda: []))
    )

    def start(self):
        logger.debug("Anki packaging `start` method.")

    def process_one(self, note: NoteDict) -> NoteDict:
        logger.debug(f"Processing Anki note {note}")
        deck = note["deck"]
        note_type = note["note_type"]
        self._notes[deck][note_type] += [note]
        return note

    def end(self):
        logger.debug("Anki packaging `end` method.")
        genanki_decks: List[genanki.Deck] = []
        media_files: list[str] = []
        for deck, types2notes in self._notes.items():
            genanki_deck = genanki.Deck(deck.id, deck.name)
            for note_type, notes in types2notes.items():
                fields = [
                    i
                    for i in note_type.model.__fields__
                    if i not in models.Note.__fields__
                ]
                genanki_note_type = genanki.Model(
                    get_hash_id(note_type.id, 7),
                    note_type.name,
                    [{"name": i} for i in fields],
                    templates=[
                        {"name": i.name, "qfmt": i.front, "afmt": i.back}
                        for i in note_type.card_sides
                    ],
                    css=note_type.css,
                )
                for i in notes:
                    field_values = [i[j] for j in fields]
                    genanki_note = NoteID(
                        i["id"],
                        model=genanki_note_type,
                        fields=field_values,
                        tags=i["tags"],
                    )
                    genanki_deck.add_note(genanki_note)
                    media_files.extend(i["media_files"])
            genanki_decks.append(genanki_deck)
        package = genanki.Package(genanki_decks, media_files)
        package.write_to_file(str(self.path))

pronounce

Pronounce

Bases: PydanticTask

Pronounce text in a note attribute.

Attributes:

Name Type Description
language pydantic.constr(to_lower=True)

Two-letter code of the language to use in the pronunciation.

attribute_to_pronounce str

Note attribute to pronounce.

append_media_file_to str

Append generated media file path to a list attribute. This is needed for later steps that want to access the media files. For example, to package notes in an APKG we need to know where the media files are stored in order to include them in the APKG.

output_dir str

Output directory where generated files will be stored. This directory is relative to the configuration file.

Source code in src/mnemocards_anki/pronounce.py
class Pronounce(PydanticTask):
    """Pronounce text in a note attribute.

    Attributes:
        language: [Two-letter
            code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) of the
            language to use in the pronunciation.
        attribute_to_pronounce: Note attribute to pronounce.
        append_media_file_to: Append generated media file path to a list
            attribute. This is needed for later steps that want to access the
            media files. For example, to package notes in an APKG we need to
            know where the media files are stored in order to include them in
            the APKG.
        output_dir: Output directory where generated files will be stored. This
            directory is relative to the configuration file.
    """

    language: pydantic.constr(to_lower=True)  # type: ignore
    attribute_to_pronounce: str = "language_you_learn_word"
    append_media_file_to: str = "media_files"
    append_pronunciation_to: str = ""
    output_dir: str = "./media_files"

    @pydantic.validator("append_pronunciation_to", always=True)
    def default_append_pronunciation_to(cls, value, values):
        if not value:
            return values.get("attribute_to_pronounce")
        return value

    def start(self):
        if not os.path.exists(self.output_dir):
            os.makedirs(self.output_dir)

    def process_one(self, note):
        to_pronounce = note[self.attribute_to_pronounce]
        # TODO: check if this is needed.
        # to_pronounce = remove_parentheses(to_pronounce)
        if self.language == "ja":
            # If you leave those spaces you get wrong pronunciations, like in
            # `スペイン 人`. Instead of `supein jin` it pronounces it as
            # `supein hito` because the kanji `人` alone is pronounced as
            # `hito`.
            to_pronounce = remove_spaces(to_pronounce)
        hash_text = utils.get_hash_id(to_pronounce, bytes=8)
        sound_file = f"{self.output_dir}/{hash_text}.mp3"
        if not os.path.exists(sound_file):
            logger.debug(
                f"Creating audio file `{sound_file}` pronouncing `{to_pronounce}`."
            )
            tts = gTTS(to_pronounce, lang=self.language)
            tts.save(sound_file)
        note[self.append_pronunciation_to] += f" [sound:{hash_text}.mp3]"
        note[self.append_media_file_to].append(sound_file)
        return note