anki update script

This commit is contained in:
caandt 2025-07-13 14:42:38 -05:00
parent 00ad7cb157
commit d18717f7e3
4 changed files with 144 additions and 0 deletions

113
user/bin/anki-update Executable file
View file

@ -0,0 +1,113 @@
#!/bin/sh
afield="sent_a"
aext="ogg"
ifield="img"
iext="avif"
sfield="sent"
rfield="sent_r"
: ${TMPDIR:=/tmp}
lock="$TMPDIR/ankiupdatelock"
log() {
notify-send "$1"
printf "\e[33m%s\e[0m\n" "$1"
}
#####
if ! [ -e "$lock" ] && [ "$1" != "-s" ]; then
log 'recording started'
record-audio \
-af silenceremove=1:0:-50dB \
"$TMPDIR/aud-$$.$aext" &
echo "$$,$!" > "$lock"
exit 0
fi
#####
anki() {
curl -s localhost:8765 -X POST -d "$(printf '{"action":"%s","version":6,"params":%s}' "$1" "$2")"
}
stop_record() {
kill "$pid2"
inotifywait -e close_write "$audio" -qq
}
screenshot() {
window="$(xprop -root | sed -n 's/^_NET_ACTIVE_WINDOW(WINDOW): window id # //p')"
maim -u -i "$window" | ffmpeg -loglevel error -i - -vf scale=-1:200 "$image"
tag="$(xprop -id "$window" | sed -n 's/^WM_NAME(STRING) = "\(.*\)"$/\1/p' | tr ' ' _)"
echo "* tag: $tag"
}
get_note() {
id="$(anki findNotes '{"query":"added:1"}' | jq .result[-1])"
if [ "$id" = "null" ]; then
log "no note to update"
bell
exit 1
fi
note="$(anki notesInfo "$(printf '{"notes":[%d]}' "$id")")"
old_sent="$(echo "$note" | jq -r .result[0].fields.['"'"$sfield"'"'].value)"
echo "* old sentence: $old_sent"
old_term="${old_sent%%</b>*}"
old_term="${old_term##*<b>}"
echo "* old term: $old_term"
}
get_name() {
date="$(date +%Y_%m_%d-%H_%M_%S)"
aname="$tag-$date.$aext"
iname="$tag-$date.$iext"
}
get_reading() {
sent="$(xclip -o -sel clip)"
echo "* clipboard: $sent"
case "$sent" in
*"$old_term"*) ;;
*) log "clipboard does not contain term"; bell; exit 1 ;;
esac
reading="$(furigana "$sent")"
sent="$(echo $sent | a="$old_term" pyp -b 's=os.environ["a"]' 'x.replace(s, "<b>%s</b>"%s)')"
echo "* new sentence: $sent"
echo "* reading: $sent"
}
if [ "$1" = "-s" ]; then
image="$TMPDIR/img-$$.$iext"
trap "rm '$image'" EXIT
screenshot
get_note
get_name
get_reading
else
IFS=, read pid1 pid2 < "$lock"
audio="$TMPDIR/aud-$pid1.$aext"
image="$TMPDIR/img-$pid1.$iext"
trap "rm '$audio' '$image' '$lock'" EXIT
log "stopping recording"
stop_record
screenshot
get_note
get_name
get_reading
append="$(printf ', "audio":{
"filename":"%s",
"path":"%s",
"fields":["%s"]
}' "$aname" "$audio" "$afield")"
fi
anki updateNote "$(printf '{"note":{
"id":%d,
"tags":"%s",
"fields":{"%s":"%s","%s":"%s"},
"picture":{
"filename":"%s",
"path":"%s",
"fields":["%s"]
}%s
}}' "$id" "$tag" "$sfield" "$sent" "$rfield" "$reading" "$iname" "$image" "$ifield" "$append")"
log "updated note $id"

View file

@ -17,6 +17,13 @@ in {
nsxiv-rifle = [nsxiv];
screenshot = [maim xclip];
record-audio = [pulseaudio ffmpeg jq];
}
// lib.optionalAttrs config.u.has.jp {
anki-update = [libnotify inotify-tools pulseaudio ffmpeg jq pyp
(writers.writePython3Bin "furigana" {
libraries = [python312Packages.fugashi python312Packages.unidic-lite];
} (builtins.readFile ./furigana))
];
};
home.file = builtins.listToAttrs (
map

22
user/bin/furigana Executable file
View file

@ -0,0 +1,22 @@
"""
ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろわをんーゎゐゑゕゖゔゝゞ・「」。、
ァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロワヲンーヮヰヱヵヶヴヽヾ・「」。、
"""
import fugashi
import sys
if len(sys.argv) != 2:
print('usage: furigana <word>')
exit(1)
H, K = __doc__.strip().split('\n')
t = {ord(a): ord(b) for a, b in zip(K, H)}
r = []
for x in fugashi.Tagger().parseToNodeList(sys.argv[1]):
if all(c in H + K for c in x.surface) or not x.feature.kana:
r.append(x.surface)
else:
r.append(f" {x.surface}[{x.feature.kana.translate(t)}]")
print(''.join(r).strip())

View file

@ -377,6 +377,8 @@ globalkeys = gears.table.join(
awful.key({ modkey }, "p", function() menubar.show() end,
{description = "show the menubar", group = "launcher"}),
awful.key({ modkey }, "a", function() awful.spawn("anki-update") end, {description = "anki update"}),
awful.key({ modkey, "Shift" }, "a", function() awful.spawn("anki-update -s") end, {description = "anki update screenshot"}),
awful.key({ modkey }, "r", function() awful.spawn(run) end, {description = "run prompt"}),
awful.key({ modkey }, "b", function() awful.spawn(browser) end, {description = "open browser"}),
awful.key({ modkey }, "t", function() awful.spawn(file_manager) end, {description = "open file manager"}),