Compare commits

..

No commits in common. "4fddddedd26aa8a9434f96faace9e4730b08b7f7" and "c14eb1b4c402238389d6e99154c2b085a92f453a" have entirely different histories.

7 changed files with 27 additions and 472 deletions

View File

@ -1,103 +0,0 @@
import json
import requests
ANKI_URL = "http://127.0.0.1:8765"
def request_anki(action, params=None):
"""Sends a payload to AnkiConnect."""
payload = {"action": action, "version": 6, "params": params or {}}
response = requests.post(ANKI_URL, json=payload)
response.raise_for_status()
return response.json().get("result")
def get_card_proficiency(query="deck:current"):
"""
Finds cards by query, retrieves scheduling data,
and prints proficiency stats for each card.
"""
card_ids = request_anki("findCards", {"query": query})
if not card_ids:
print("No cards found.")
return
card_info = request_anki("cardsInfo", {"cards": card_ids})
card_proficiency = {}
for card in card_info:
card_id = card["cardId"]
# In Anki: 0=new, 1=learning, 2=review, 3=relearning
queue_state = card["queue"]
interval = card["interval"]
reps = card["reps"]
lapses = card["lapses"]
# Anki queue mappings:
# 0 = New, 1 = Learning, 2 = Review, 3 = Relearning
if queue_state == 0:
status = "New"
color = ''
elif queue_state == 1:
status = "Learning"
color = 'orangered'
elif queue_state == 3:
status = "Relearning"
color = 'yellow'
elif queue_state == 2:
# Review card split by 21-day threshold
if interval >= 21:
status = "Mature"
color = 'cyan'
else:
status = "Young"
color = 'lime'
elif queue_state < 0:
status = "Suspended"
color = ''
else:
status = f"Unknown (Queue {queue})"
color = ''
word = ''
if (card['fields'].get('Word')):
print(card['fields']['Word'])
word = card['fields']['Word']['value']
else:
#print(card['fields'])
continue
if (card_proficiency.get(word)):
print("Multiple matches for word")
else:
card_proficiency[word] = {'Status' : status,'State' : status, 'Color' : color}
print(f"Card ID {card_id} (State: {queue_state}):")
print(f" Reps: {reps} | Lapses: {lapses} | Interval: {interval} days | Ease: {status} \n")
return card_proficiency
# Example usage: Evaluate proficiency for all cards in a specific deck
# get_card_proficiency(query='deck:"Your Deck Name Here"')
proficiency_stats = get_card_proficiency(query='deck:"Japanese Vocab" card:0')
if proficiency_stats.get('休み'):
proficiency_stats['休み']['Status']
#Anki determines a card's maturity, learning, or relearning state based on its queue type and interval value.
# Mature: Review card with an interval of 21 days or more.
# Young: Review card with an interval of less than 21 days.
# Relearning: Card currently failing review, placed back into a learning queue.
"""
# Anki queue mappings:
# 0 = New, 1 = Learning, 2 = Review, 3 = Relearning
if queue == 0:
status = "New"
elif queue == 1:
status = "Learning"
elif queue == 3:
status = "Relearning"
elif queue == 2:
# Review card split by 21-day threshold
status = "Mature (Review)" if ivl >= 21 else "Young (Review)"
else:
status = f"Unknown (Queue {queue})"
"""

View File

@ -1,138 +0,0 @@
import re
from pathlib import Path
from math import ceil
#import tkinter as tk
#from tkinter.filedialog import askopenfilename
from pykakasi import kakasi
KANJI_RE = r"[㐀-䶵一-鿋豈-頻]"
NOT_KANJI_RE = r"[^㐀-䶵一-鿋豈-頻]"
K_TIME_RE = r"(\{\\k\d+})"
# open a window for choosing file
def choose_file():
#root = tk.Tk()
#root.withdraw()
#file_path = askopenfilename()
file_path = '/run/media/deck/YF8SD/Video/Yu-Gi-Oh! Duel Monsters/test.ass'
if not file_path:
print("No file is chosen. Exit.")
exit()
return Path(file_path)
# create a dictionary mapping kanji to hiragana
def get_furi(word):
print(word['orig'])
# simple deletion of the following hiraganas
for i in range(-1, (-1*len(word["orig"]) - 1), -1):
if word["orig"][i] != word["hira"][i]:
i += 1
if i != 0:
word["hira"] = word["hira"][:i]
break
# create kanji - hiragana mapping
# initialize
furi_dict = {}
word_kanjis = re.findall(KANJI_RE, word["orig"])
if not word_kanjis:
return furi_dict
# evenly assign hiraganas to each kanji
furi_length = ceil(len(word["hira"]) / len(word_kanjis))
for i, kanji in enumerate(word_kanjis):
start = i * furi_length
end = start + furi_length
furi_dict[kanji] = word["hira"][start:end]
return furi_dict
def has_japanese(text: str) -> bool:
return any(
0x3040 <= ord(ch) <= 0x30FF or
0x3400 <= ord(ch) <= 0x9FFF
for ch in text
)
# process a line in ass file
def process_line(ass_line, kks_obj):
ass_line_items = ass_line.split(",")
text = ass_line_items[-1]
text_items = re.split(K_TIME_RE, text)
lyric = re.sub(K_TIME_RE, "", text)
kks_res = kks_obj.convert(lyric)
cur_word = kks_res.pop(0)
cur_word_end = len(cur_word["orig"])
cur_pos = 0
processed_items = []
for item in text_items:
# if current position reach the end of current word, then get next word
while cur_pos >= cur_word_end:
cur_word = kks_res.pop(0)
cur_word_end += len(cur_word["orig"])
# is japanese?
# if has_japanese(item) == False:
# processed_items.append(item)
# continue
# item is not in the lyric, which means item is k_time
if item not in lyric:
processed_items.append(item)
continue
# item is in the lyric, but it does not contain any kanji
if not re.search(KANJI_RE, item):
processed_items.append(item)
cur_pos += len(item)
continue
# item contains kanji
char_add_furi = []
cur_furi = get_furi(cur_word)
for char in item:
if cur_pos >= cur_word_end:
cur_word = kks_res.pop(0)
cur_furi = get_furi(cur_word)
cur_word_end += len(cur_word["orig"])
if re.match(KANJI_RE, char):
char_add_furi.append(f"{char}|<{cur_furi[char]}")
else:
char_add_furi.append(char)
cur_pos += 1
# calculate and add k_time for each character
item_k_time = re.search(r"\d+", processed_items[-1])
item_k_time = int(item_k_time.group(0))
time_each, time_mod = divmod(item_k_time, len(item))
processed_items[-1] = r"{\k%s}" % (time_each + time_mod)
char_k_time = r"{\k%s}" % time_each
item_add_furi = char_k_time.join(char_add_furi)
processed_items.append(item_add_furi)
processed_text = "".join(processed_items)
ass_line = ass_line_items[:-1]
ass_line.append(processed_text)
return ",".join(ass_line)
if __name__ == "__main__":
# chooose the input file
input_path = choose_file()
ass = open(input_path, "r", encoding="utf-8")
output_path = input_path.with_stem(input_path.stem + "_furigana")
ass_furi = open(output_path, "w", encoding="utf-8")
# process each line
kks = kakasi()
for line in ass:
if not line.startswith("Dialogue: "):
ass_furi.write(line)
else:
ass_furi.write(process_line(line, kks))
ass.close()
ass_furi.close()

View File

@ -1,33 +0,0 @@
from fugashi_parser import *
import os
from pathlib import Path
import shutil
import hashlib
season_path = Path('/run/media/deck/YF8SD/Sync/Notebooks/Obsidian/Japanese/JPod101/3N_Absolute Beginner Season 2/')
pos_list = list()
with open(season_path.parent.joinpath(season_path.stem + '.md'), 'w') as out_file:
for file_path in sorted(season_path.iterdir()):
if file_path.is_file() and file_path.suffix == '.md': # Ensure it's a file, not a subdirectory
#out_file = file_path.parent.parent.joinpath(file_path.parent.stem + '_READ.md')
with open(file_path, "r") as file:
#file.write("This text will be added to the end.")
print(f"###### [[{file_path.name}]]", file=out_file)
file.seek(0)
kanji_block = re.search(r'·+\s*KANJI(.*?)·+\s*[KANA|ROMANIZATION]', file.read(), re.DOTALL)
if (kanji_block):
kanji_block.group(1)
print( kanji_block.group(1).strip().replace('····','') , file=out_file)
else:
print(file_path.stem, 'No Kanji block found')
# else:
# print(pos, ' - no tag')
# with file_path.open('r') as file:
# content = file.read()
# print(f"Content of {file_path.name}: {content}")
unique_values = list(set(pos_list))
print(unique_values)

View File

@ -34,19 +34,19 @@ KATAKANA_TO_HIRAGANA_OFFSET = ord("ぁ") - ord("ァ")
CONTENT_POS1 = {"名詞", "動詞", "形容詞", "形状詞", "副詞", "代名詞"} CONTENT_POS1 = {"名詞", "動詞", "形容詞", "形状詞", "副詞", "代名詞"}
# def is_vocab_token(pos, keep_pronouns=False, keep_numbers=False): def is_vocab_token(pos, keep_pronouns=False, keep_numbers=False):
# pos1, pos2, pos3, pos4 = pos pos1, pos2, pos3, pos4 = pos
# if pos1 not in CONTENT_POS1: if pos1 not in CONTENT_POS1:
# return False return False
# if pos1 == "代名詞" and not keep_pronouns: if pos1 == "代名詞" and not keep_pronouns:
# return False return False
# if pos1 == "名詞" and pos2 == "数詞" and not keep_numbers: if pos1 == "名詞" and pos2 == "数詞" and not keep_numbers:
# return False return False
# return True return True
@ -98,94 +98,6 @@ def term_entries(term :str) -> dict:
response = requests.post(request_url + "/termEntries", json = params, timeout = request_timeout) response = requests.post(request_url + "/termEntries", json = params, timeout = request_timeout)
return json.loads(response.text) return json.loads(response.text)
ANKI_URL = "http://127.0.0.1:8765"
def request_anki(action, params=None):
"""Sends a payload to AnkiConnect."""
payload = {"action": action, "version": 6, "params": params or {}}
response = requests.post(ANKI_URL, json=payload)
response.raise_for_status()
return response.json().get("result")
def get_card_proficiency(query="deck:current"):
"""
Finds cards by query, retrieves scheduling data,
and prints proficiency stats for each card.
"""
card_ids = request_anki("findCards", {"query": query})
if not card_ids:
print("No cards found.")
return
card_info = request_anki("cardsInfo", {"cards": card_ids})
card_proficiency = {}
for card in card_info:
card_id = card["cardId"]
# In Anki: 0=new, 1=learning, 2=review, 3=relearning
queue_state = card["queue"]
interval = card["interval"]
reps = card["reps"]
lapses = card["lapses"]
# Anki queue mappings:
# 0 = New, 1 = Learning, 2 = Review, 3 = Relearning
# https://www.w3.org/TR/webvtt1/#default-text-color
if queue_state == 0:
status = "New"
color = 'red'
elif queue_state == 1:
status = "Learning"
color = 'yellow'
elif queue_state == 3:
status = "Relearning"
color = 'lime'
elif queue_state == 2 or queue_state == -2:
# Review card split by 21-day threshold
if interval >= 21:
status = "Mature"
color = 'magenta'
else:
status = "Young"
color = 'cyan'
elif queue_state == -1:
status = "Suspended"
color = ''
else:
status = f"Unknown (Queue {queue_state})"
color = ''
word = ''
if (card['fields'].get('Word')):
print(card['fields']['Word'])
word = card['fields']['Word']['value']
else:
#print(card['fields'])
continue
if (card_proficiency.get(word)):
print("Multiple matches for word")
if (card_proficiency[word]['State'] < queue_state):
card_proficiency[word] = {'Status' : status,'State' : queue_state, 'Color' : color}
else:
card_proficiency[word] = {'Status' : status,'State' : queue_state, 'Color' : color }
print(f"Card ID {card_id} (State: {queue_state}):")
print(f" Reps: {reps} | Lapses: {lapses} | Interval: {interval} days | Ease: {status} \n")
return card_proficiency
# Example usage: Evaluate proficiency for all cards in a specific deck
# get_card_proficiency(query='deck:"Your Deck Name Here"')
try:
proficiency_stats = get_card_proficiency(query='deck:"Japanese Vocab" card:0')
with open('vocab-cache.json', "w", encoding="utf-8") as f:
json.dump(proficiency_stats, f, ensure_ascii=False, indent=2)
except:
with open("vocab-cache.json", "r", encoding="utf-8") as f:
proficiency_stats = json.load(f)
CONTENT_LINK_TAG = { CONTENT_LINK_TAG = {
'名詞': 'meishi', # noun '名詞': 'meishi', # noun
'動詞': 'doshi', # verb '動詞': 'doshi', # verb
@ -224,38 +136,10 @@ def should_ruby(surface, reading_hiragana):
return True return True
def build_ruby(token :TokenReading) -> str: def build_ruby(surface: str, reading: str) -> str:
if not token.reading_hiragana or not has_japanese(token.surface): if not reading or surface == reading or not has_japanese(surface):
return html.escape(token.surface) return html.escape(surface)
style_attr = '' return f"<ruby>{html.escape(surface)}<rt>{html.escape(reading)}</rt></ruby>"
# First try lemma then try surface
if proficiency_stats.get(token.lemma):
if proficiency_stats[token.lemma]['Color'] != '':
style_attr = f"style=\"color:{proficiency_stats[token.lemma]['Color']}\""
elif proficiency_stats.get(token.surface):
if proficiency_stats[token.surface]['Color'] != '':
style_attr = f"style=\"color:{proficiency_stats[token.surface]['Color']}\""
if token.surface == token.reading_hiragana:
return f"<span {style_attr}>{html.escape(token.surface)}</span>"
return f"<ruby {style_attr}>{html.escape(token.surface)}<rt>{html.escape(token.reading_hiragana)}</rt></ruby>"
def build_sub_cue(token :TokenReading) -> str:
if not token.reading_hiragana or not has_japanese(token.surface):
return html.escape(token.surface)
# Check if color
color_prefix = ''
color_suffix = ''
if proficiency_stats.get(token.lemma):
if proficiency_stats[token.lemma]['Color'] != '':
color_prefix = f"<c.{proficiency_stats[token.lemma]['Color']}>"
color_suffix = f"</c>"
elif proficiency_stats.get(token.surface):
if proficiency_stats[token.surface]['Color'] != '':
color_prefix = f"<c.{proficiency_stats[token.surface]['Color']}>"
color_suffix = f"</c>"
if token.surface == token.reading_hiragana:
return f"{color_prefix}{html.escape(token.surface)}{color_suffix}"
return f"{color_prefix}<ruby>{html.escape(token.surface)}<rt>{html.escape(token.reading_hiragana)}</rt></ruby>{color_suffix}"
def build_kana(surface: str, reading: str) -> str: def build_kana(surface: str, reading: str) -> str:
if not reading or surface == reading or not has_japanese(surface): if not reading or surface == reading or not has_japanese(surface):
@ -264,7 +148,7 @@ def build_kana(surface: str, reading: str) -> str:
def build_vocab_link(surface: str, reading: str, pos) -> str: def build_vocab_link(surface: str, reading: str, pos) -> str:
pos1, pos2, pos3, pos4 = pos pos1, pos2, pos3, pos4 = pos
if not is_vocab_token(surface, pos, keep_pronouns=True,keep_numbers=False): if not is_vocab_token(pos, keep_pronouns=True,keep_numbers=False):
return surface return surface
# if surface == reading: # if surface == reading:
# return f"[[{surface}]]" # return f"[[{surface}]]"
@ -348,7 +232,7 @@ def chunk_predicates(tokens):
return chunks return chunks
## Vocab Filtering ## Vocab Filtering
def is_vocab_token(surface, pos, keep_pronouns=False, keep_numbers=False): def is_vocab_token(pos, keep_pronouns=False, keep_numbers=False):
pos1, pos2, pos3, pos4 = pos pos1, pos2, pos3, pos4 = pos
if pos1 not in {"名詞", "動詞", "形容詞", "形状詞", "副詞", "代名詞", "感動詞"}: if pos1 not in {"名詞", "動詞", "形容詞", "形状詞", "副詞", "代名詞", "感動詞"}:
@ -357,7 +241,7 @@ def is_vocab_token(surface, pos, keep_pronouns=False, keep_numbers=False):
if pos1 == "代名詞" and not keep_pronouns: if pos1 == "代名詞" and not keep_pronouns:
return False return False
if pos1 == "名詞" and pos2 == "数詞" and not has_kanji(surface) and not keep_numbers: if pos1 == "名詞" and pos2 == "数詞" and not keep_numbers:
return False return False
return True return True
@ -415,7 +299,7 @@ def update_vocab(vocab: Dict[str, Dict], tokens: List[TokenReading]):
# if should_skip(t.surface, pos1) or not is_content_word(pos1): # if should_skip(t.surface, pos1) or not is_content_word(pos1):
# continue # continue
if not is_vocab_token(t.surface, t.pos, keep_pronouns=True,keep_numbers=False): if not is_vocab_token(t.pos, keep_pronouns=True,keep_numbers=False):
continue continue
entry = vocab.get(t.lemma) entry = vocab.get(t.lemma)
@ -440,7 +324,6 @@ def analyze(text: str, vocab: Dict[str, Dict]):
tokens: List[TokenReading] = [] tokens: List[TokenReading] = []
ruby_parts = [] ruby_parts = []
subtitle_parts = []
kana_parts = [] kana_parts = []
vocab_link_parts = [] vocab_link_parts = []
@ -475,8 +358,7 @@ def analyze(text: str, vocab: Dict[str, Dict]):
token = TokenReading(surface, reading, lemma, lemma_reading, pos) token = TokenReading(surface, reading, lemma, lemma_reading, pos)
tokens.append(token) tokens.append(token)
ruby_parts.append(build_ruby(token)) ruby_parts.append(build_ruby(surface, reading))
subtitle_parts.append(build_sub_cue(token))
kana_parts.append(build_kana(surface, reading)) kana_parts.append(build_kana(surface, reading))
predicate_chunks = chunk_predicates(tokens) predicate_chunks = chunk_predicates(tokens)
@ -486,7 +368,6 @@ def analyze(text: str, vocab: Dict[str, Dict]):
"original_text": text, "original_text": text,
"kana_reading": "".join(kana_parts), "kana_reading": "".join(kana_parts),
"ruby_html": "".join(ruby_parts), "ruby_html": "".join(ruby_parts),
"sub_cue": "".join(subtitle_parts),
"notes_link":"".join(vocab_link_parts), "notes_link":"".join(vocab_link_parts),
"token_readings": [asdict(t) for t in tokens], "token_readings": [asdict(t) for t in tokens],
"predicate_chunks" : predicate_chunks, "predicate_chunks" : predicate_chunks,

View File

@ -1,49 +0,0 @@
## This is just copied from make_dialog_notes.py
from fugashi_parser import *
import os
from pathlib import Path
import shutil
import hashlib
destination_directory_name = '/run/media/deck/YF8SD/Sync/Notebooks/Obsidian/Japanese/Yu-Gi-Oh/'
destination_path = Path(destination_directory_name)
vocab_destination_path = Path('/run/media/deck/YF8SD/Sync/Notebooks/Obsidian/Japanese/vocab')
with open("helptext_dump.json", "r", encoding="utf-8") as f:
dump = json.load(f)
if not destination_path.exists():
destination_path.mkdir(parents=True, exist_ok=True)
if not destination_path.joinpath('YLOTD_help').exists():
destination_path.joinpath('YLOTD_help').mkdir(parents=True, exist_ok=True)
counter = 0
destination_path = destination_path.joinpath('YLOTD_help')
with open(destination_path.as_posix() + f"/HelpText.md", 'w') as card_note:
for title in dump:
print(title)
counter+=1
## copy the image if it isn't already there
print(title)
card_note.write(f"\n")
#card_note.write(f"\n###### {title}\n\n")
#reformatted = re.sub(r'(\d{2})/(\d{2})/(\d{4})', r'\3-\1-\2', date)
#re.replace('\$R')
text_J = dump[title]['ja']
text_J = re.sub(r'\$R(.*?)\((.*?)\)',r'<ruby>\1<rt>\2</rt></ruby>',text_J)
if text_J[0] == '':
card_note.write(f"#### {text_J}\n\n")
print(f"<details><summary>{text_J}</summary>{dump[title]['en']}</details>",file=card_note)
text_J = dump[title]['ja']
text_J = re.sub(r'\$R(.*?)\((.*?)\)',r'\1',text_J)
text_J = text_J.replace('<br>', '').replace('\n','')
## print raw for vocab lookup
print(f"\n{text_J}\n", file=card_note)
vocab = {} ## flush the vocab variable each time
result = analyze(text_J, vocab)
print(f"> [!summary]- Vocab\n>{result['notes_link']}",file=card_note)
for word in vocab:
write_word_note(word, vocab_destination_path, vocab[word]['pos'][0])
# ## add card names as properties

View File

@ -45,7 +45,7 @@ def main():
result = analyze(lines[i],vocab) result = analyze(lines[i],vocab)
## If the line changed at all ## If the line changed at all
if lines[i] != result['ruby_html']: if lines[i] != result['ruby_html']:
lines[i] = '<span><details><summary>' + result['ruby_html'] + '</summary>' + lines[i] + '</span>' + "\n> [!summary]- Vocab\n>" + result['notes_link'] + "\n\n" # lost the new line lines[i] = '<span>' + result['ruby_html'] + '</span>' + "\n> [!summary]- Vocab\n>" + result['notes_link'] + "\n\n" # lost the new line
file_changed = True file_changed = True
i+=1 i+=1

View File

@ -70,10 +70,11 @@ def main():
if has_japanese(lines[i]): if has_japanese(lines[i]):
print(lines[i]) print(lines[i])
result = analyze(lines[i],vocab) result = analyze(lines[i],vocab)
notes_lines.append(result["sub_cue"] + "\n" ) print(result['kana_reading'])
notes_lines.append(result["ruby_html"] + "\n" )
notes_lines.append (result['notes_link'] + "\n") notes_lines.append (result['notes_link'] + "\n")
if lines[i] != result['sub_cue']: if lines[i] != result['ruby_html']:
lines[i] = result['sub_cue'] + "\n" # lost the new line lines[i] = result['ruby_html'] + "\n" # lost the new line
# if lines[i] != result['kana_reading']: # if lines[i] != result['kana_reading']:
# lines[i] = result['kana_reading'] + "\n" # lost the new line # lines[i] = result['kana_reading'] + "\n" # lost the new line
else: else:
@ -82,20 +83,16 @@ def main():
i+=1 i+=1
## Lookup vocab ## Lookup vocab
try:
for word in vocab: for word in vocab:
write_word_note(word, destination_path) write_word_note(word, destination_path)
except: return
print("Unable to word lookup")
## Dammit
# return
# with open("/run/media/deck/YF8SD/Video/Yu-Gi-Oh! Duel Monsters - 001.vtt", "w", encoding="utf-8") as f: # with open("/run/media/deck/YF8SD/Video/Yu-Gi-Oh! Duel Monsters - 001.vtt", "w", encoding="utf-8") as f:
# f.writelines(lines) # f.writelines(lines)
## Output file with changes if changes ## Output file with changes if changes
out_dir = Path(args.subtitle_file).parent out_dir = Path(args.subtitle_file).parent
changed_file = out_dir.joinpath(Path(args.subtitle_file).stem + '-ruby.vtt') changed_file = out_dir.joinpath(Path(args.subtitle_file).stem + '.txt')
with open(changed_file, "w", encoding="utf-8") as f: with open(changed_file, "w", encoding="utf-8") as f:
f.writelines(lines) f.writelines(lines)