diff --git a/src/anki_hsk_creator/__main__.py b/src/anki_hsk_creator/__main__.py
index 0d6a62a..8d089ac 100644
--- a/src/anki_hsk_creator/__main__.py
+++ b/src/anki_hsk_creator/__main__.py
@@ -9,13 +9,21 @@ from .api import (
is_file,
list_input_files,
pre_process_a_dictionary_file,
+ proccess_a_completed_file,
process_a_dictation_file,
process_a_dictionary_file,
process_a_phrases_file,
select_file,
select_folder,
)
-from .constants import DICTATION_TYPE, DICT_TYPE, INPUT, LANGUAGES, PHRASES_TYPE
+from .constants import (
+ COMPLETED_TYPE,
+ DICTATION_TYPE,
+ DICT_TYPE,
+ INPUT,
+ LANGUAGES,
+ PHRASES_TYPE,
+)
from .utility import ProcessFile, ProcessFolder
@@ -24,7 +32,7 @@ def cli_choose_work_type() -> str:
option = None
while not option in ["s", "f", "e"]:
option = input(
- "Please select the work option:\n" "s: single file, f:folder, e:exit\n"
+ "Please select the work option:\n" "s:single file, f:folder, e:exit\n"
)
return option
@@ -66,7 +74,7 @@ def cli_select_folder() -> ProcessFolder:
print(f"Selected {selected}")
s = None
while s not in ("yes", "y", "no", "n"):
- s = input("if this the folder? (yes/no, go inside)")
+ s = input("if this the folder? (yes / no, go inside) : ")
if s in ("yes", "y"):
in_folder = selected
else:
@@ -100,9 +108,10 @@ def cli_select_language(languages: list = None) -> str:
for language_id, language in avaliable_languages:
if languages and language_id in languages:
print(f"{language_id} - {language}")
+ print("all - All languages")
s = None
- while not s or s not in LANGUAGES.AvailableLanguages:
- lan_codes = [lan_id for lan_id, lan in avaliable_languages]
+ while not s or s not in LANGUAGES.AvailableLanguages + ("all",):
+ lan_codes = [lan_id for lan_id, lan in avaliable_languages] + ["all"]
s = input(f"Please select the language {', '.join(lan_codes)}: ")
return s
@@ -137,8 +146,16 @@ def main():
language_id = cli_select_language()
print(f"processing file {input_file} with language {language_id}")
process_a_dictation_file(input_file, language_id)
+ elif COMPLETED_TYPE in input_file.input_file.suffixes:
+ language_id = input_file.input_file.suffixes[1][1:]
+ print(f"processing file {input_file} with language {language_id}")
+ proccess_a_completed_file(input_file, language_id)
elif option == "f":
- for language_id in LANGUAGES.AvailableLanguages:
+ language_id = cli_select_language()
+ langs = (
+ LANGUAGES.AvailableLanguages if language_id == "all" else [language_id]
+ )
+ for language_id in langs:
in_folder = cli_select_folder()
print(f"Selected: {in_folder} with language {language_id}")
for dirpath, dirnames, filenames in tuple(
diff --git a/src/anki_hsk_creator/anki_generation.py b/src/anki_hsk_creator/anki_generation.py
index 1982926..f7da4f6 100644
--- a/src/anki_hsk_creator/anki_generation.py
+++ b/src/anki_hsk_creator/anki_generation.py
@@ -13,7 +13,13 @@ from pinyin_tone_converter.pinyin_tone_converter import PinyinToneConverter
# Local
from .constants import DICTATION_TYPE, DICT_TYPE, PHRASES_TYPE
-from .utility import DictionaryResult, ProcessFile, ProcessFolder, TranslationResult
+from .utility import (
+ DictionaryResult,
+ ProcessFile,
+ ProcessFolder,
+ SimpleResult,
+ TranslationResult,
+)
# Constants
@@ -137,9 +143,86 @@ HSK_MODEL = Model(
css=CSS,
)
+SIMPLE_HSK_MODEL = Model(
+ 2819647620,
+ "Simple HSK Model",
+ fields=[
+ {"name": "Meaning"},
+ {"name": "Pinyin"},
+ {"name": "Simplified"},
+ {"name": "Audio"},
+ ],
+ templates=[
+ {
+ "name": "Card 1",
+ "qfmt": (
+ "{{Pinyin}}"
+ "
{{Meaning}}"
+ "
{{Audio}}"
+ "
Simplified: {{type:Simplified}}"
+ ),
+ "afmt": (
+ "{{FrontSide}}
{{Simplified}}
"
+ ),
+ },
+ {
+ "name": "Card 2",
+ "qfmt": (
+ "{{Simplified}}
" "
Pinyin: {{type:Pinyin}}"
+ ),
+ "afmt": (
+ "{{FrontSide}}
{{Pinyin}}"
+ "
{{Meaning}}
{{Audio}}"
+ ),
+ },
+ {
+ "name": "Card 3",
+ "qfmt": ("{{Audio}}" "
Simplified: {{type:Simplified}}"),
+ "afmt": (
+ "{{FrontSide}}
{{Pinyin}}"
+ "
{{Simplified}}
"
+ ),
+ },
+ ],
+ 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:
diff --git a/src/anki_hsk_creator/api.py b/src/anki_hsk_creator/api.py
index ffe9a11..5100835 100644
--- a/src/anki_hsk_creator/api.py
+++ b/src/anki_hsk_creator/api.py
@@ -9,12 +9,14 @@ from pathlib import Path
# Local
from . import DATA_FOLDER
from .anki_generation import (
+ output_anki_completed,
output_anki_dictation,
output_anki_dictionary,
output_anki_package,
output_anki_phrase,
)
from .constants import (
+ COMPLETED_TYPE,
DICTATION_TYPE,
DICT_TYPE,
INPUT,
@@ -24,6 +26,7 @@ from .constants import (
RESOURCES,
)
from .proccessor import (
+ completed_process,
dictation_process,
dictionary_bulk_process,
dictionary_pre_process,
@@ -249,6 +252,18 @@ def process_a_phrases_file(process_file: ProcessFile, language_id: str) -> Path:
return output_anki_phrase(process_file, results)
+def proccess_a_completed_file(process_file: ProcessFile, language_id: str) -> Path:
+ """Process a completed file"""
+ process_file.language_id = language_id
+ TTS.create_tts()
+ with process_file.absolute_input_file.open(
+ "r", encoding="utf8", newline="\n"
+ ) as file:
+ text_lines = [line.strip() for line in file.readlines() if line.strip()]
+ results = completed_process(text_lines, process_file)
+ return output_anki_completed(process_file, results)
+
+
def folder_proccess(process_folder: ProcessFolder, language_id: str):
process_folder.language_id = language_id
TTS.create_tts()
@@ -278,4 +293,11 @@ def folder_proccess(process_folder: ProcessFolder, language_id: str):
) as file:
text_lines = [line.strip() for line in file.readlines() if line.strip()]
results[process_file] = translator_process(text_lines, process_file)
+ elif process_file.file_type is COMPLETED_TYPE:
+ process_file.language_id = process_file.input_file.suffixes[1][1:]
+ with process_file.absolute_input_file.open(
+ "r", encoding="utf8", newline="\n"
+ ) as file:
+ text_lines = [line.strip() for line in file.readlines() if line.strip()]
+ results[process_file] = completed_process(text_lines, process_file)
return output_anki_package(process_folder, results)
diff --git a/src/anki_hsk_creator/constants.py b/src/anki_hsk_creator/constants.py
index b3cbf0e..e7414e4 100644
--- a/src/anki_hsk_creator/constants.py
+++ b/src/anki_hsk_creator/constants.py
@@ -22,6 +22,7 @@ GWEN_TTS = GWEN_FOLDER / "Qwen3-TTS-12Hz-0.6B-CustomVoice"
PHRASES_TYPE = ".phrases"
DICT_TYPE = ".dictionary"
DICTATION_TYPE = ".dictation"
+COMPLETED_TYPE = ".completed"
class LANGUAGES:
@@ -35,7 +36,7 @@ class LANGUAGES:
TR = "tr"
TH = "th"
JP = "jp"
- AvailableLanguages = (EN, ES, FR, RU, TR, TH, JP)
+ AvailableLanguages = (ES, EN, FR, RU, TR, TH, JP)
LanguageNames = {
EN: "English",
ES: "Spanish",
diff --git a/src/anki_hsk_creator/proccessor.py b/src/anki_hsk_creator/proccessor.py
index 11e1d37..30b3fb6 100644
--- a/src/anki_hsk_creator/proccessor.py
+++ b/src/anki_hsk_creator/proccessor.py
@@ -10,7 +10,14 @@ import soundfile as sf
# Local
from .constants import LANGUAGES
-from .utility import CCCEDICT, TTS, DictionaryResult, ProcessFile, TranslationResult
+from .utility import (
+ CCCEDICT,
+ TTS,
+ DictionaryResult,
+ ProcessFile,
+ SimpleResult,
+ TranslationResult,
+)
# Constants
@@ -68,7 +75,26 @@ def translator_process(
return results
+def completed_process(
+ text_lines: list[str], process_file: ProcessFile
+) -> list[TranslationResult]:
+ """Process a complete file, not translation necesary"""
+ results = []
+ dictionary = CCCEDICT.create_cedict()
+ for n, line in enumerate(text_lines):
+ char, meaning = line.split(maxsplit=1)
+ entries = dictionary.get(char)
+ pinying = entries[0].pinyin
+ audio_path = process_file.resources / f"{char}.wav"
+ if not audio_path.is_file():
+ wavs, sr = TTS.generate(f"{char}。")
+ sf.write(audio_path, wavs[0], sr)
+ results.append(SimpleResult(char, pinying, meaning, audio_path))
+ return results
+
+
def dictionary_bulk_process(words_list: list[str], process_file: ProcessFile):
+ dictionary_en = CCCEDICT.create_cedict(LANGUAGES.EN)
dictionary = CCCEDICT.create_cedict(process_file.language_id)
with process_file.dictionary_resource_file.open(
"w", encoding="utf8", newline=""
@@ -81,30 +107,25 @@ def dictionary_bulk_process(words_list: list[str], process_file: ProcessFile):
for words in words_list:
word = words.split()[0]
hint = " ".join(words.split()[1:]) if len(words.split()) > 1 else None
- if entries := dictionary.get(word):
- if len(entries) > 1:
- print(f"\nWARNING: {word} has multiple meanings:")
+ entries_en = dictionary_en.get(word)
+ entries = dictionary.get(word)
+ if entries:
pos_meanings = (
[
- entry
- for entry in entries
- for meaning in entry.meanings
- if hint in meaning
+ meaning
+ for entry, entry_en in zip(entries, entries_en)
+ for meaning, meaning_en in zip(
+ entry.meanings, entry_en.meanings
+ )
+ if hint in meaning_en or hint in meaning
]
if hint
- else []
+ else [meaning for entry in entries for meaning in entry.meanings]
)
- pos_entries = pos_meanings or entries
- meanings = []
- for entry in pos_entries:
- for meaning in entry.meanings:
- if hint and hint in meaning:
- meanings.append(meaning)
- if not meanings:
- meanings = [
- meaning for entry in pos_entries for meaning in entry.meanings
- ]
- meanings_text = meanings[0]
+ meanings_text = "\n".join(
+ f"{n}: {meaning}" for n, meaning in enumerate(pos_meanings)
+ )
+ entry = entries[0]
tsv_writer.writerow(
{
"n": number,
diff --git a/src/anki_hsk_creator/utility.py b/src/anki_hsk_creator/utility.py
index b5f460e..4c137a5 100644
--- a/src/anki_hsk_creator/utility.py
+++ b/src/anki_hsk_creator/utility.py
@@ -18,6 +18,7 @@ from qwen_tts import Qwen3TTSModel
# Local
from .constants import (
CCCEDICT_PATH,
+ COMPLETED_TYPE,
DICTATION_TYPE,
DICT_TYPE,
GWEN_TTS,
@@ -55,6 +56,7 @@ class TRANS:
)
print(f"available packages {packages[:5]}")
packages_to_install = []
+ ready = False
for in_package in packages:
if in_package.from_code == from_code:
if in_package.to_code == to_code:
@@ -64,7 +66,12 @@ class TRANS:
f"->{in_package.to_code}"
)
packages_to_install.append(in_package)
+ ready = True
break
+ for in_package in packages:
+ if ready:
+ break
+ if in_package.from_code == from_code:
for out_package in packages:
if out_package.to_code == to_code:
if in_package.to_code == out_package.from_code:
@@ -78,6 +85,7 @@ class TRANS:
)
packages_to_install.append(in_package)
packages_to_install.append(out_package)
+ ready = True
break
for package in packages_to_install:
print(f"instaling package {package}")
@@ -110,16 +118,17 @@ class TranslatedEntry:
@property
def meanings(self):
"""Entry translated meaning list"""
- for meaning in self.entry.meanings:
- if self.language_id != LANGUAGES.EN:
- print(f"translating from {LANGUAGES.EN} to {self.language_id}")
- print(f"-> {meaning}")
- trans_meaning = argostranslate.translate.translate(
- meaning, LANGUAGES.EN, self.language_id
- )
- else:
- trans_meaning = meaning
- self._translated_meanings.append(trans_meaning)
+ if not self._translated_meanings:
+ for meaning in self.entry.meanings:
+ if self.language_id != LANGUAGES.EN:
+ print(f"translating from {LANGUAGES.EN} to {self.language_id}")
+ print(f"-> {meaning}")
+ trans_meaning = argostranslate.translate.translate(
+ meaning, LANGUAGES.EN, self.language_id
+ )
+ else:
+ trans_meaning = meaning
+ self._translated_meanings.append(trans_meaning)
return self._translated_meanings
@@ -227,7 +236,7 @@ class ProcessFolder:
def input_files(self):
input_files = []
for file in self.absolute_input_folder.glob(f"*.txt"):
- for file_type in (DICT_TYPE, PHRASES_TYPE, DICTATION_TYPE):
+ for file_type in (DICT_TYPE, PHRASES_TYPE, DICTATION_TYPE, COMPLETED_TYPE):
if file_type in file.suffixes:
input_files.append(
ProcessFile(
@@ -264,9 +273,18 @@ class ProcessFile:
# process file type
self.out_folder = OUTPUT / input_file.parent
self.out_folder.mkdir(parents=True, exist_ok=True)
- resources = RESOURCES / input_file
- self.resources = resources.parent / resources.stem
- self.resources.mkdir(parents=True, exist_ok=True)
+
+ @property
+ def resources(self):
+ resources = RESOURCES / self.input_file
+ if self.file_type is COMPLETED_TYPE:
+ resources = (
+ resources.parent / f"{self.input_file.name.split(".")[0]}.dictionary"
+ )
+ else:
+ resources = resources.parent / resources.stem
+ resources.mkdir(parents=True, exist_ok=True)
+ return resources
@property
def file_type(self):
@@ -276,6 +294,8 @@ class ProcessFile:
return DICT_TYPE
elif PHRASES_TYPE in self.input_file.suffixes:
return PHRASES_TYPE
+ elif COMPLETED_TYPE in self.input_file.suffixes:
+ return COMPLETED_TYPE
else:
raise ValueError("File type not recognized")
@@ -320,6 +340,16 @@ class ProcessFile:
return f"Proccess:{self.input_file}"
+class SimpleResult:
+ """Simplied result class"""
+
+ def __init__(self, character: str, pinyin: str, meaning: str, audio_path: Path):
+ self.character = character
+ self.pinyin = pinyin
+ self.meaning = meaning
+ self.audio_path = audio_path
+
+
class TranslationResult:
"""Result of a translated process"""