Files
anki-hsk-creator/src/anki_hsk_creator/anki_generation.py
Wolfang Torres 977bb422c0 solve problerms with menaing trasnlations,
mightneed to change trasnlation engine
2026-06-29 22:49:50 +08:00

399 lines
12 KiB
Python

"""anki_generation.py
Produces anki output
"""
# Standard Library
import random
from pathlib import Path
# Pip
from genanki import Deck, Model, Note, Package
from pinyin_tone_converter.pinyin_tone_converter import PinyinToneConverter
# Local
from .constants import COMPLETED_TYPE, DICTATION_TYPE, DICT_TYPE, PHRASES_TYPE
from .utility import (
DictionaryResult,
ProcessFile,
ProcessFolder,
SimpleResult,
TranslationResult,
)
# Constants
CSS = """
.card {
font-family: arial;
font-size: 20px;
text-align: center;
color: black;
background-color: white;
}
.simple {
font-family: Arial;
font-size: 100px;
}
.trad {
font-family: Arial;
font-size: 75px;
}
"""
# Models
PHRASE_MODEL = Model(
2076166425,
"Phrase Model",
fields=[
{"name": "Translated"},
{"name": "Phrase"},
{"name": "Audio"},
],
templates=[
{
"name": "Card 1",
"qfmt": "{{Translated}}<br>{{Audio}}<br>{{type:Phrase}}",
"afmt": '{{FrontSide}}<hr id="answer">{{Phrase}}',
},
{
"name": "Card 2",
"qfmt": "{{Phrase}}<br>{{Audio}}<br>{{type:Translated}}",
"afmt": '{{FrontSide}}<hr id="answer">{{Translated}}',
},
{
"name": "Card 3",
"qfmt": "{{Audio}}<br>{{type:Phrase}}",
"afmt": '{{FrontSide}}<hr id="answer">{{Phrase}}',
},
],
css=CSS,
)
DICTATION_MODEL = Model(
3187277536,
"Phrase Model",
fields=[
{"name": "Translated"},
{"name": "Phrase"},
{"name": "Audio"},
],
templates=[
{
"name": "Card 1",
"qfmt": "{{Audio}}<br>{{type:Phrase}}",
"afmt": '{{FrontSide}}<hr id="answer">{{Phrase}}<br>{{Translated}}',
},
],
css=CSS,
)
HSK_MODEL = Model(
1708536519,
"HSK Model",
fields=[
{"name": "Translated"},
{"name": "Pinyin"},
{"name": "Simplified"},
{"name": "Traditional"},
{"name": "Audio"},
],
templates=[
{
"name": "Card 1",
"qfmt": (
"<strong>{{Pinyin}}</strong>"
"<br>{{Translated}}"
"<br>{{Audio}}"
"<br>Simplified: {{type:Simplified}}"
),
"afmt": (
"{{FrontSide}}<hr id='answer''><div class='simple'>{{Simplified}}</div>"
"<br><div class='trad'>{{Traditional}}</div>"
),
},
{
"name": "Card 2",
"qfmt": (
"<div class='simple'>{{Simplified}}</div>"
"<br><div class='trad'>{{Traditional}}</div>"
"<br>Pinyin: {{type:Pinyin}}"
# "<br>Translated: {{type:Translated}}"
),
"afmt": (
"{{FrontSide}}<hr id='answer'><strong>{{Pinyin}}</strong>"
"<br>{{Translated}}<br>{{Audio}}"
),
},
{
"name": "Card 3",
"qfmt": (
"{{Audio}}"
# "<br>Pinyin: {{type:Pinyin}}"
"<br>Simplified: {{type:Simplified}}"
),
"afmt": (
"{{FrontSide}}<hr id='answer'><strong>{{Pinyin}}</strong>"
"<br><div class='simple'>{{Simplified}}</div>"
"<br><div class='trad'>{{Traditional}}</div>"
),
},
],
css=CSS,
)
SIMPLE_HSK_MODEL = Model(
2819647620,
"Simple HSK Model",
fields=[
{"name": "Meaning"},
{"name": "Pinyin"},
{"name": "Simplified"},
{"name": "Audio"},
],
templates=[
{
"name": "Card 1",
"qfmt": (
"<strong>{{Pinyin}}</strong>"
"<br>{{Meaning}}"
"<br>{{Audio}}"
"<br>Simplified: {{type:Simplified}}"
),
"afmt": (
"{{FrontSide}}<hr id='answer''><div class='simple'>{{Simplified}}</div>"
),
},
{
"name": "Card 2",
"qfmt": (
"<div class='simple'>{{Simplified}}</div>" "<br>Pinyin: {{type:Pinyin}}"
),
"afmt": (
"{{FrontSide}}<hr id='answer'><strong>{{Pinyin}}</strong>"
"<br>{{Meaning}}<br>{{Audio}}"
),
},
{
"name": "Card 3",
"qfmt": ("{{Audio}}" "<br>Simplified: {{type:Simplified}}"),
"afmt": (
"{{FrontSide}}<hr id='answer'><strong>{{Pinyin}}</strong>"
"<br><div class='simple'>{{Simplified}}</div>"
),
},
],
css=CSS,
)
# Proccess
def output_anki_completed(
process_file: ProcessFile, results: list[SimpleResult]
) -> Path:
"""Creates an anki file for a completed file"""
final_file = (
process_file.output_name.parent / f"{process_file.input_file.stem}.apkg"
)
deck_name = "::".join(process_file.input_file.parts[:-1] + (final_file.stem,))
deck = Deck(
random.randrange(1 << 30, 1 << 31),
deck_name,
f"Deck for {process_file.input_file.stem}, "
"created in https://www.wolfang.info.ve/hskankicreator/",
)
audios = []
for result in results:
note = Note(
model=SIMPLE_HSK_MODEL,
fields=[
result.meaning,
PinyinToneConverter().convert_text(result.pinyin),
result.character,
f"[sound:{result.audio_path.name}]",
],
)
deck.add_note(note)
audios.append(result.audio_path)
package = Package(deck)
package.media_files = audios
package.write_to_file(final_file)
return final_file
def output_anki_dictation(
process_file: ProcessFile, results: list[DictionaryResult]
) -> Path:
"""Creates an anki file for dictation result"""
final_file = process_file.output_name.with_suffix(".apkg")
deck_name = "::".join(
process_file.input_file.parts[:-1] + (process_file.output_name.stem,)
)
deck = Deck(
random.randrange(1 << 30, 1 << 31),
deck_name,
f"Deck for {final_file.name}, "
"created in https://www.wolfang.info.ve/hskankicreator/",
)
audios = []
for result in results:
note = Note(
model=DICTATION_MODEL,
fields=[
result.translated,
result.line,
f"[sound:{result.audio_path.name}]",
],
)
deck.add_note(note)
audios.append(result.audio_path)
package = Package(deck)
package.media_files = audios
package.write_to_file(final_file)
return final_file
def output_anki_dictionary(
process_file: ProcessFile, results: list[DictionaryResult]
) -> Path:
"""Creates an anki file from a dictionary results"""
final_file = process_file.output_name.with_suffix(".apkg")
deck_name = "::".join(
process_file.input_file.parts[:-1] + (process_file.output_name.stem,)
)
deck = Deck(
random.randrange(1 << 30, 1 << 31),
deck_name,
f"Deck for {final_file.name}, "
"created in https://www.wolfang.info.ve/hskankicreator/",
)
package = Package(deck)
audios = []
for result in results:
note = Note(
model=HSK_MODEL,
fields=[
# "\n ".join(f"{n+1}. {m}" for n, m in enumerate(result.meanings)),
result.meaning,
PinyinToneConverter().convert_text(result.pinyin),
result.simplified,
result.traditional,
f"[sound:{result.audio_path.name}]",
],
)
audios.append(result.audio_path)
deck.add_note(note)
package.media_files = audios
package.write_to_file(final_file)
return final_file
def output_anki_phrase(
process_file: ProcessFile, results: list[TranslationResult]
) -> Path:
"""Creates an anki file from a phrases results"""
final_file = process_file.output_name.with_suffix(".apkg")
deck_name = "::".join(
process_file.input_file.parts[:-1] + (process_file.output_name.stem,)
)
deck = Deck(
random.randrange(1 << 30, 1 << 31),
deck_name,
f"Deck for {final_file.name}, "
"created in https://www.wolfang.info.ve/hskankicreator/",
)
package = Package(deck)
audios = []
for result in results:
note = Note(
model=PHRASE_MODEL,
fields=[
result.translated,
result.line,
f"[sound:{result.audio_path.name}]",
],
)
deck.add_note(note)
audios.append(result.audio_path)
package.media_files = audios
package.write_to_file(final_file)
return final_file
def output_anki_package(
process_folder: ProcessFolder, results: dict[ProcessFile, list[TranslationResult]]
):
final_file = process_folder.output_name.with_suffix(".apkg")
decks = []
audios = []
for process_file, results in results.items():
deck_name = "::".join(
process_file.input_file.parts[:-1] + (process_file.output_name.stem,)
)
deck = Deck(
random.randrange(1 << 30, 1 << 31),
deck_name,
f"Deck for {process_file.input_file}, "
"created in https://www.wolfang.info.ve/hskankicreator/",
)
if process_file.file_type is DICTATION_TYPE:
for result in results:
note = Note(
model=DICTATION_MODEL,
fields=[
result.translated,
result.line,
f"[sound:{result.audio_path.name}]",
],
)
deck.add_note(note)
audios.append(result.audio_path)
elif process_file.file_type is DICT_TYPE:
for result in results:
note = Note(
model=HSK_MODEL,
fields=[
# "\n ".join(f"{n+1}. {m}" for n, m in enumerate(result.meanings)),
result.meaning,
PinyinToneConverter().convert_text(result.pinyin),
result.simplified,
result.traditional,
f"[sound:{result.audio_path.name}]",
],
)
deck.add_note(note)
audios.append(result.audio_path)
elif process_file.file_type is PHRASES_TYPE:
for result in results:
note = Note(
model=PHRASE_MODEL,
fields=[
result.translated,
result.line,
f"[sound:{result.audio_path.name}]",
],
)
deck.add_note(note)
audios.append(result.audio_path)
elif process_file.file_type is COMPLETED_TYPE:
for result in results:
note = Note(
model=SIMPLE_HSK_MODEL,
fields=[
result.meaning,
PinyinToneConverter().convert_text(result.pinyin),
result.character,
f"[sound:{result.audio_path.name}]",
],
)
deck.add_note(note)
audios.append(result.audio_path)
decks.append(deck)
if decks:
package = Package(decks)
package.media_files = audios
package.write_to_file(final_file)
return final_file