summary refs log tree commit diff
path: root/bin/grimocr
diff options
context:
space:
mode:
Diffstat (limited to 'bin/grimocr')
-rwxr-xr-xbin/grimocr208
1 files changed, 208 insertions, 0 deletions
diff --git a/bin/grimocr b/bin/grimocr
new file mode 100755
index 0000000..5b40257
--- /dev/null
+++ b/bin/grimocr
@@ -0,0 +1,208 @@
+#!/bin/bash
+# adapted for wayland (grim/slurp/wl-copy) from
+# https://github.com/tatsumoto-ren/dotfiles/blob/main/.local/bin/maimocr
+
+set -e
+[ -n "$DEBUG" ] && set -x
+
+TESSDATA_PREFIX=$HOME/.local/share/tessdata
+
+langs=
+method=tesseract
+while getopts l:m:h opt; do
+	case "$opt" in
+	l) langs="${OPTARG}" ;;
+	m) method="${OPTARG}" ;;
+	h | ?)
+		printf "usage: %s [-h]\n" "$(basename "$0")"
+		printf "\n"
+		printf "  -l  LANG[+LANG]\n"
+		printf "  -m  METHOD\n"
+		printf "\n"
+		exit 2
+		;;
+	esac
+done
+shift $((OPTIND - 1))
+
+for c in curl unzip grim slurp convert wl-copy; do
+	if ! command -v "$c" >/dev/null; then
+		notify "$c must be installed."
+		exit 1
+	fi
+done
+
+notify() {
+	echo "$*"
+	notify-send "㊗️ grimOCR" "$*"
+}
+
+notify_ask() {
+	notif_args="gdbus call --session \
+		--dest org.freedesktop.Notifications \
+		--object-path /org/freedesktop/Notifications"
+	ok_action_key=ok__$(mktemp -u | cut -d. -f2)
+	cancel_action_key=cancel__$(mktemp -u | cut -d. -f2)
+
+	APP_NAME=grimocr
+	REPLACE_ID=0
+	ICON=
+	SUMMARY=grimOCR
+	BODY="$*"
+	ACTIONS="[\"$ok_action_key\", \"OK\", \"$cancel_action_key\", \"Cancel\"]"
+	HINTS='{}'
+	EXPIRE_TIME=0
+
+	id=$($notif_args --method org.freedesktop.Notifications.Notify -- \
+		"$APP_NAME" "$REPLACE_ID" "$ICON" "$SUMMARY" "$BODY" \
+		"$ACTIONS" "$HINTS" "int32 $EXPIRE_TIME" | sed 's/(uint32 \([0-9]\+\),)/\1/g')
+
+	pidfile=$(mktemp /tmp/grimocr_notification_monitor.XXXXX)
+	result=$(mktemp /tmp/grimocr_notification_result.XXXXX)
+	trap '{ kill "$(cat "$pidfile")" ; rm -- "$pidfile" "$result"; }' EXIT
+	(
+		gdbus monitor --session \
+			--dest org.freedesktop.Notifications \
+			--object-path /org/freedesktop/Notifications &
+		printf "%d" $! >&3
+	) 3>"$pidfile" |
+		while read -r line; do
+			closed_id=$(printf "%s" "$line" | sed '/^\/org\/freedesktop\/Notifications: org.freedesktop.Notifications.NotificationClosed (uint32 \([0-9]\+\), uint32 [0-9]\+)$/!d;s//\1/')
+			if [ -n "$closed_id" ] && [ "$closed_id" = "$id" ]; then
+				$notif_args --method org.freedesktop.Notifications.CloseNotification "$id" >/dev/null
+				break
+			fi
+			action_invoked=$(printf "%s" "$line" | sed "/\/org\/freedesktop\/Notifications: org.freedesktop.Notifications.ActionInvoked (uint32 \([0-9]\+\), '\(.*\)')\$/!d;s//\1:\2/")
+			if [ -n "$action_invoked" ]; then
+				invoked_id=$(printf "%s" "$action_invoked" | cut -d: -f1)
+				action_id=$(printf "%s" "$action_invoked" | cut -d: -f2)
+				if [ "$invoked_id" = "$id" ]; then
+					case "$action_id" in
+					"$ok_action_key") printf "0" >"$result" ;;
+					"$cancel_action_key") printf "1" >"$result" ;;
+					esac
+					$notif_args --method org.freedesktop.Notifications.CloseNotification "$id" >/dev/null
+					break
+				fi
+			fi
+		done
+	return "$(cat "$result")"
+}
+
+download_tessdata() {
+	osd_url="https://github.com/tesseract-ocr/tessdata_best/blob/main/osd.traineddata"
+	eng_url="https://github.com/tesseract-ocr/tessdata_best/raw/main/eng.traineddata"
+	jpn_url="https://g33k.se/_matrix/media/r0/download/g33k.se/chvDlHzjiXgDEdeGTFnyOExv"
+
+	if ! notify_ask "Downloading Tesseract data?"; then
+		notify "Download aborted."
+		exit 0
+	else
+		notify "Download started."
+	fi
+
+	# download osd tessdata
+	curl -LsSf -o "$TESSDATA_PREFIX/osd.tessdata" "$osd_url"
+	curl -LsSf -o "$TESSDATA_PREFIX/eng.tessdata" "$eng_url"
+
+	# download jpn tessdata
+	jpn_zip_path=$(mktemp /tmp/grimocr_tesseract_data.XXXXX)
+	trap '{ rm -- "$jpn_zip_path"; }' EXIT
+	curl -LsSf -o "$jpn_zip_path" "$jpn_url"
+	mkdir -p -- "$TESSDATA_PREFIX"
+	unzip -o -j "$jpn_zip_path" -d "$TESSDATA_PREFIX"
+
+	notify "Download complete. Run again..."
+}
+
+take_screenshot() {
+	# https://tesseract-ocr.github.io/tessdoc/ImproveQuality.html#missing-borders
+	grim -g "$(slurp)" -t png -l 1 - |
+		convert png:- -alpha off -bordercolor White -border 10x10 png:-
+
+	notify "recognizing..." >/dev/null
+}
+
+installed_languages() {
+	if [ -n "$langs" ]; then
+		printf "%s" "$langs"
+	else
+		find "$TESSDATA_PREFIX" -type f -name '*.traineddata' -printf '%f+' |
+			sed 's|\.traineddata||g; s|\+$||'
+	fi
+}
+
+tesseract_recognize() {
+	# https://tesseract-ocr.github.io/tessdoc/Command-Line-Usage.html
+	tesseract stdin stdout \
+		--tessdata-dir "$TESSDATA_PREFIX" \
+		-l "$(installed_languages)" \
+		--psm 1 |
+		tr -d '\f'
+
+	# NOTE: by removing this, we can make it work for other langs too
+	# tr -d ' ' |
+	# tr -d '\n' |
+	# grep -P '[ヲ-゚ァ-ヶぁ-ゞA-z0-9ァ-ン゙゚ァ-ンぁ-ん一-龯]+'
+}
+
+mangaocr_recognize() {
+	python3 -c "$(
+		cat <<-EOF
+			from manga_ocr import MangaOcr
+
+			text = MangaOcr()("/dev/stdin")
+			print(text)
+		EOF
+	)"
+}
+
+recognize() {
+	case "${method}" in
+	tesseract) tesseract_recognize ;;
+	mangaocr) mangaocr_recognize ;;
+	esac
+}
+
+run_ocr() {
+	result=$(take_screenshot | recognize 2>/dev/null)
+	if [ $? -eq 0 ]; then
+		printf "%s" "$result" | wl-copy -n
+		notify "$result"
+	else
+		notify "Failed."
+	fi
+}
+
+## main
+
+case "${method}" in
+tesseract)
+	if ! command -v tesseract >/dev/null; then
+		notify "tesseract must be installed."
+		exit 1
+	fi
+	if [ ! -d "$TESSDATA_PREFIX" ]; then
+		download_tessdata
+	fi
+	;;
+mangaocr)
+	if ! command -v python3 >/dev/null; then
+		notify "python3 must be installed."
+		exit 1
+	fi
+	notify "setting up manga-ocr..."
+	script_path="$(dirname "$(readlink -f "$0")")"
+	venv_path="${script_path}"/"$(basename "$0")".mangaocr
+	python3 -m venv "${venv_path}"
+	source "${venv_path}"/bin/activate
+	pip3 install -r "${script_path}"/"$(basename "$0")".requirements >/dev/null 2>&1
+	;;
+*)
+	notify "unknown METHOD ${method}"
+	exit 1
+	;;
+esac
+
+notify "select some japanese text"
+run_ocr