【技巧分享】 Emacs 调用 macOS OCR

适合轻度使用。

我只用了四五次就发现 Apple 的这个 OCR 碰到文字有背景变化就拉垮的情况。

(defun my/siri-ocr ()
    (interactive)
    (shell-command "shortcuts run \"OCR Selected Area\"")
    (do-applescript "tell application id \"org.gnu.Emacs\" to activate")
  )
(keymap-global-set "C-c M-o" #'my/siri-ocr)

依赖

快捷指令 “OCR Selected Area”

https://www.icloud.com/shortcuts/b1fa8a227df74eafa78814b25e6f8837

不喜欢 iCloud 链接的也可以自己手搓,非常简单:

视频演示

碎碎念

相比上次调用 Apple 翻译服务,这次不依赖外部插件了,快捷指令也可以被其他软件调用,而不是仅 Emacs 可用。

Bonus

搭配翻译功能一起食用风味更佳

9 个赞

为什么绑定 C-c M-o?因为 C-c o 和 C-c O 被占用了。

而且我的右 Command 被定义为了 Meta,所以可以左手 C-c 右手 M-o :laughing: 左右右手一个慢动作,OCR 它就完成了~

不错不错,还挺好用。

可以试试这个: schappim/macOCR: Get any text on your screen into your clipboard. (github.com)

1 个赞

嘿嘿,谢谢捧场。

感觉现在的方案就足够了,轻量,不用额外装其他工具,也不会唤起快捷指令 App 的 GUI。

批量处理的话才需要考虑其他工具。

稍微修改了一下,支持在 echo area 显示 OCR 结果。

(use-package simple
  :ensure nil
  :bind
  ("C-c H-o" . my/siri-ocr)
  :config
  ;; Siri Shortcuts: OCR
  (defun my/siri-ocr ()
    (interactive)
    (shell-command "shortcuts run \"OCR Selected Area\"")
    (do-applescript "tell application id \"org.gnu.Emacs\" to activate")
    (shell-command "pbpaste")
    )
  )

async-shell-command 的结果总是创建新 buffer,不如 shell-command 好配置。

BONUS

如果你需要翻译:

(defun async-shell-command (command &optional output-buffer error-buffer)
...
...
)

output-buffer的位置写成nil应该就没buffer了: (async-shell-command your-command nil nil)

不是想 erase buffer,而是想复用 buffer,shell-command 可以设置在命令的输出在 buffer 开头追加、在结尾追加、紧随上个命令的结果追加。

async-shell-command 就只有 new buffer 和 rename buffer。

我也学习了一下 async-shell-command (src: simple.el), 发现它最终还是会调用shell-command的(看最后一行):

(defun async-shell-command (command &optional output-buffer error-buffer)
  "Execute string COMMAND asynchronously in background.

Like `shell-command', but adds `&' at the end of COMMAND
to execute it asynchronously.

The output appears in the buffer whose name is stored in the
variable `shell-command-buffer-name-async'.  That buffer is in
shell mode.

You can configure `async-shell-command-buffer' to specify what to do
when the buffer specified by `shell-command-buffer-name-async' is
already taken by another running shell command.

To run COMMAND without displaying the output in a window you can
configure `display-buffer-alist' to use the action
`display-buffer-no-window' for the buffer given by
`shell-command-buffer-name-async'.

In Elisp, you will often be better served by calling `start-process'
directly, since it offers more control and does not impose the use of
a shell (with its need to quote arguments)."
  (interactive
   (list
    (read-shell-command (if shell-command-prompt-show-cwd
                            (format-message "Async shell command in `%s': "
                                            (abbreviate-file-name
                                             default-directory))
                          "Async shell command: ")
                        nil nil
			(let ((filename
			       (cond
				(buffer-file-name)
				((eq major-mode 'dired-mode)
				 (dired-get-filename nil t)))))
			  (and filename (file-relative-name filename))))
    current-prefix-arg
    shell-command-default-error-buffer))
  (unless (string-match "&[ \t]*\\'" command)
    (setq command (concat command " &")))
  (shell-command command output-buffer error-buffer))   

于是我猜shell-command的设置应该也对async-shell-command有效,于是把simple.el里相关的参数都扒了一下:

(setq 
 async-shell-command-buffer 'confirm-new-buffer
 async-shell-command-display-buffer t
 async-shell-command-width nil
 shell-command-prompt-show-cwd nil
 shell-command-dont-erase-buffer t ; default is `nil <-- 只改了这个
 )

测试:

;; 以下执行两次:
(async-shell-command "echo \"hello from async-shell-command\"" (get-buffer-create "test"))
;; 以下执行两次:
(shell-command "echo \"hello from shell-command\"" (get-buffer-create "test")) ; 执次两次

以上结果都出现在名为"test"的buffer里:

image

1 个赞

厉害 :+1: 大佬一出手,就知有没有。

我是通过 Easy Customization 才知道 shell-command-dont-erase-buffer 的参数可以是 'end-last-out

够用了,自己能力也有限,就没再深入看(主要是看不懂)。

这几天看自己的配置(org 文件写了一万行了)、看对应文档、插件代码,看的头大,……正在做减法。

我也是刚看了下源码才发现的。 :crazy_face: