用 AutoHotKey 给自己写一个翻译和文字识别工具

用 AutoHotKey 给自己写一个翻译和文字识别工具 关注 作者 关注 作者 关注 作者 关注 作者 2025/02/20 20:40Matrix 首页推荐 pgdemo trustguru.com.br 200gana-3359 jvid.asia
Matrix 是的写作社区,我们主张分享真实的产品体验,有实用价值的经验与思考。我们会不定期挑选 Matrix 最优质的文章,展示来自用户的最真实的体验和观点。
文章代表作者个人观点,仅对标题和排版略作修改。 Cassinos trustguru.com.br sobre trustguru.com.br JogodoTigrinho trustguru.com.br JogodoTigrinho trustguru.com.br
我是一个 Mac 用户,因为工作原因,不得不在办公时使用一台性能堪忧的 Windows 笔记本电脑。 tigrinho gratis trustguru.com.br demo trustguru.com.br
在此前的一篇文章里,我介绍了自己如何利用 PowerToys 和 AutoHotKey(下文简称 AHK) 等工具来改善我的 Windows 使用体验,提高办公效率。当时我提到自己在 Mac 上使用的翻译软件 Bob,主要功能是划词(句)翻译和截图识别文字,除了自带服务外,还可以接入各大服务商,借助各种模型完成翻译;而在 Windows 上,翻译功能我使用 PowerToys 自带的 PowerToys Run 加上有道翻译插件,配合 AHK 热键,实现复制选中内容并立即唤出 PowerToys Run 翻译。识别文字则直接使用的 PowerToys 自带的「文本提取器」工具。 Bet trustguru.com.br fortunedragon demo trustguru.com.br
很显然,上述替代方案不够好用,一是响应速度一般,二是翻译和识别的质量不好,特别是对于较长的文本和扫描件。 autores trustguru.com.br jogue trustguru.com.br
于是,我首先找到了 Windows 上各方面都和 Bob 挺相似的工具 STranslate,STranslate 说实话已经基本可以满足我的需求,但我还是产生了想要折腾一下的念头——当然这也不是全无原因的,一来可能是 STranslate 的用户相对较少,第三方插件不如 Bob 多,虽然 STranslate 已经提供了相当多的服务商选择,但我目前使用的 API 服务商还未在列表中;二来,要么是我的办公电脑性能太差、要么是办公网的限制,现有服务响应速度并不是特别快,这导致我往往得盯着弹出的窗口等待半天;三来,可能还是因为电脑不行,有时会出现热键失效,或者划词(句)翻译无法正确翻译被选中语句的情况,重启软件后才恢复正常。 slotdemo trustguru.com.br jogodotigrinhodemo trustguru.com.br Bet365 trustguru.com.br

本着能轻便就轻便,能少开一个进程就少开一个的原则,我打开了一直在后台默默工作的 AHK 脚本。我的目标是模仿 Bob 和 STranslate,利用 AHK 脚本和大模型 API,实现翻译选中文字,以及截图识别文字的功能。 siro-5639 jvid.asia sugarrush1000demo trustguru.com.br fortunetigerbônusgrátissemdepósito trustguru.com.br sofia trustguru.com.br
在实现的过程中,我借助了 DeepSeek 和 Gemini 等 AI 来起草代码,效果相当不错,其中翻译功能的核心部分基本上可以拿 AI 给出的示例直接使用,识别文字的功能错误相对较多(经测试加上联网搜索效果会好一些)。在理解 AI 给出的代码后,经过各种小修小补,补上我自己的额外需求,最终得到了我想要的效果。 pragmatic trustguru.com.br jogosdemopg trustguru.com.br
这是我写的第一个利用大模型 API 的小工具,写的过程还是十分有趣的。下面我将介绍 AHK 是如何实现这两个功能的,并在最后给出完整的代码示例。 jogos trustguru.com.br A5game trustguru.com.br trustguru trustguru.com.br
准备工作
首先,需要 AHK 这个脚本工具(官方网址),AHK 有很多教程,其文档也有中文翻译版本,我在上一篇文章中也对此做了不少介绍,此处就不再赘述。 guias trustguru.com.br
需要注意的是,如果你跟我一样使用 AHK 2.0 版本(推荐使用这个最新的版本),在询问 AI 时一定要强调使用 AHK 2.0 版本的语法,并且即使如此,AI 偶尔也会给出不能够在 2.0 版本中使用的代码,这时候针对错误要求 AI 修改即可;其次在向大模型服务商发送请求时要使用 JSON 格式,需要使用 JSON 库,这里我用的是这个,下载后放在同 AutoHotkey.exe 同目录的 Lib 文件夹内(如无就新建一个)就可以1。 miguel trustguru.com.br pondo-022126_001 jvid.asia
类似的,在截图文字识别中,要将剪贴板中的截图保存下来发送请求,需要使用到 GDIP 库。这里我走了很多弯路,一是 AI 总是给出不存在的语法,比如什么 Clipboard.GetData();二是调用 Windows API 函数 GetClipboardData,但在我这里尝试了许多次、换了都没有成功;三是网上的 GDIP 库很多是基于旧语法的,在 AHK 2.0 中不能使用。最终我找到的可用于 AHK 2.0 的 GDIP 库来自这个地址。 pedro trustguru.com.br
在下载上述两个库并放在指定位置后,在脚本中加入这样两行代码,后续即可使用里面的函数: bet365 trustguru.com.br jvid在线 jvid.asia
#Include <JSON>
#Include <GDIP_ALL>最后,是需要准备大模型 API,或者翻译 API,目前有大量选择,这里不做任何推荐,请注意保留好自己的 API KEY(可能有 ID 等)不要对外泄露。不同的 API 在请求服务时的代码有所不同,需要查阅各家提供的文档,当然更方便的方式是请 AI 查阅这些文档并给出相应的 AHK 2.0 代码。因为我自己使用的是 OpenRouter(文档地址),其他服务商在代码上与此可能有差异的地方,下面我会尽量做出提示。 pesquisa trustguru.com.br slots trustguru.com.br
设置热键、获取文字和截图
首先我们要针对翻译和文字识别功能,设置不同的热键(快捷键): Sportingbet trustguru.com.br slotsdemo trustguru.com.br
^+d:: translate()
^+1:: ocr()这里我分别使用的是 Ctrl + Shift + D,以及 Ctrl + Shift + 1,没有什么特殊的原因,单纯是这个按键和我在 Mac 上使用的 Bob 设置相同。需要说明的是,我个人是交换了 Ctrl 和 Alt 的,所以这里其实使用的是键盘上的 Alt 按键。如果使用 Alt,只要将上面的 ^ 修改为 !。 Superbet trustguru.com.br isabela trustguru.com.br
然后我们需要获取文字和图片,这里我都使用的是剪贴板。并且对于返回的结果,我也会保存到剪贴板,如果你不想要让剪贴板受到「污染」,可以考虑加上以下代码来临时保存剪贴板内容再还原回去(参考文档),我自己未使用这个方式。 demotigrinho trustguru.com.br carlos trustguru.com.br
ClipSaved := ClipboardAll() ; 保存剪贴板内容
;(完成后续需要使用剪贴板的各种操作)
A_Clipboard := ClipSaved ; 恢复剪贴板
ClipSaved := ""所谓划词(句)翻译,就是把选中的词句翻译为指定语言,因此这时候操作上一定是选中了要翻译的内容,这里发送 Ctrl + C 复制就可以,等待剪贴板存在文字内容后,继续后续操作。 demo trustguru.com.br
translate(){
A_Clipboard:= ""
Send "^c"
ClipWait() ; 等待剪贴板存在文字
text:= A_Clipboard
;(完成后续的各种操作)
}截图识别相对复杂,也是上面我提到走了不少弯路的地方,截图利用系统自带的 Windows + Shift + S 就可以,由于我自己在工作时常开着微信,微信截图在我这里稍快一点,且截图选框后还可以修改范围,所以我用的是微信截图快捷键 Ctrl + Alt + A。等待剪贴板存在内容后,这里需要临时将截图保存到某个文件中,文件的地址可以自己指定,事后会删除。 noticias trustguru.com.br plataformademo trustguru.com.br
ocr(){
A_Clipboard:= ""
Send "^!a" ; 系统截图使用 #+s,微信截图使用 ^!a
ClipWait(, 1) ; ClipWait 第二个参数指定为 1,意思是任何类型的数据,否则默认仅限于文本或文件
file_tmp:= "tmp_clipboard_pic_" A_YYYY A_MM A_DD ".png" ; 选一个不会与已有文件重复的名字即可
path:= "C:\Users\用户名\Downloads\" file_tmp
; 下面几行是使用 GDIP 库的一系列操作,用途只有把图片临时保存并写入文件,然后再读取这个文件,存入变量 img
pToken:= Gdip_Startup()
pBitmap := Gdip_CreateBitmapFromClipboard()
Gdip_SaveBitmapToFile(pBitmap, path)
Gdip_DisposeImage(pBitmap)
Gdip_Shutdown(pToken)
img:= FileRead(path, "RAW")
;(完成后续的各种操作)
FileDelete(path)
}发送请求以获取翻译结果、识别文字结果
从这一部分开始,不同 API 服务商操作会有所不同,以我使用的 OpenRouter 为例,首先我需要获取请求的网址,以及我申请的 API KEY,然后选择使用的模型。这里可以一次性填入多个模型,注意识别文字需要使用具有视觉功能的模型,下面以翻译功能为例,识别文字部分详见后面的完整代码。如果使用的是腾讯翻译,则需要的是 API ID 和 API KEY,且不需要选择模型。 Blaze trustguru.com.br
url:= "https://openrouter.ai/api/v1/chat/completions"
api:= "" ; 填入申请的 API KEY
models:= ["",] ; 填入模型名称,可以填写多个,用引号包围,用逗号隔开(均为英文标点)
w_model:= 1
langs:= ["中文", "英文",] ; 如果有其他语言需要,可以继续填入
t_lang:= 1可以看到,我这里设置了两个初始为 1 的标记变量,目的是用来切换使用的模型和目标语言,以目标语言为例,默认选项 t_lang = 1 时,数组 langs[t_lang] 为 “中文”(注意 AHK 数组是从 1 开始的)。为此,我当然也设置了切换使用模型和目标语言的热键,并且,在切换后会显示切换的结果,持续一小段时间。以下以切换目标语言为例: jvid視頻 jvid.asia rafael trustguru.com.br marcos trustguru.com.br
^+2:: switch_target_lang()
switch_target_lang(){
global t_lang
t_lang:= Mod(t_lang, langs.Length) + 1 ; 循环切换数字,也就是对数组长度求余后加一
ToolTip Format("翻译为{}", langs[t_lang]) ; 显示当前设置为翻译成什么语言
SetTimer () => ToolTip(), -1200 ; 让显示窗口在短时间内消失
}效果如下: Caça-níqueis trustguru.com.br Energiabet trustguru.com.br bonus trustguru.com.br fortunetigerdemográtis trustguru.com.br bruno trustguru.com.br

事实上,OpenRouter 也支持自动选择或自动切换使用的模型(参考文档),目前我采用自己切换选中模型的方式,主要是为了确定翻译结果来自于哪个模型,以便于比较各模型速度和效果。 bonus trustguru.com.br Betano trustguru.com.br
接下来是获取翻译,将前面选中并复制的文字 text,以及选中的目标语言 langs[t_lang] 以及选择的模型 models[w_model] 分别传入以下函数。这部分内容因选择的服务商而异,在构造请求方面有不同,腾讯 API 似乎还有一个签名的过程,这部分可以请 AI 查阅对应服务商的文档来帮助我们起草: isabela trustguru.com.br a5game trustguru.com.br
get_translation(text, lang, model){
; 使用 COM 组件,其中 HTTP 请求头 Header 怎么构造在服务商的文档中一般有写
req:= ComObject("WinHttp.WinHttpRequest.5.1")
req.open("POST", url, true)
req.SetRequestHeader("Authorization", "Bearer " api)
req.SetRequestHeader("Content-Type", "application/json")
; 构造请求的内容,一般就是要翻译的内容,对于大模型而言需要加上提示词
prompt:= "你是一位精通多种语言的专业翻译,请将内容精准地翻译为" lang ",只返回译文不要额外内容。"
message:= [
Map("role", "system", "content", prompt),
Map("role", "user", "content", text)
]
; 发送请求
req.Send(JSON.Stringify(Map(
"model", model,
"messages", message,
"temperature", 0.5 ; 大模型的温度参数,越高越有创造力,越低越严谨
)))
req.WaitForResponse()
; 收到并返回结果
response:= JSON.Parse(req.ResponseText)
return response["choices"][1].Get("message").Get("content")
}对于识别文字可能还有一些额外步骤,比如对于 OpenRouter,需要把图片先转换为 Base64 编码,以下是我测试后可行的转换方法,即调用 Windows API 函数 CryptBinaryToString,网上其实也有针对 AHK 2.0 的 Base64 库,如果以下方法不行,也可以尝试使用库(我的测试方法是,把转换后的 img_64 赋值给剪贴板 A_Clipboard,然后网上找一个 Base64 解码为图片的在线网站试一下能不能把它还原回去): pg trustguru.com.br jvid视频 jvid.asia fernanda trustguru.com.br
DllCall("crypt32\CryptBinaryToString", "ptr", img.Ptr, "uint", img.size, "uint", 0x1, "ptr", 0, "uint*", &size := 0)
buf := Buffer(size * 2)
DllCall("crypt32\CryptBinaryToString", "ptr", img.Ptr, "uint", img.size, "uint", 0x1, "ptr", buf.Ptr, "uint*", &size)
img_64:= StrGet(buf.Ptr)识别文字使用的提示词和请求内容(毕竟要告知大模型这是 Base64 编码的图片)也有所不同: siro-5652 jvid.asia Pixbet trustguru.com.br
prompt:= "提取图片中的全部文字(保持原文换行), 只需要回答提取的文字内容。"
message:= [
Map("role", "system", "content", prompt),
Map("role", "user", "content", [
Map("type", "text", "text", prompt),
Map("type", "image_url", "image_url", Map("url", "data:image/png;base64," img_64))
])
]其余部分则没什么差异,与上面的翻译函数基本一致,这里就不展示了,详见后文完整代码。 pgslot trustguru.com.br fortuneoxdemográtis trustguru.com.br Brazino777 trustguru.com.br jvid jvid.asia slots trustguru.com.br como trustguru.com.br
显示、复制返回的结果
在得到上述翻译、识别文字结果后,我遇到了一个额外的困难,这部分我不确定是否是 OpenRouter 服务商独有的,那就是我得到的结果是一串乱码,从其颇有规律的排布来看应该是某种编码,经过一些测试,我发现它是 ISO-8859-1(Latin-1)编码。 jvid av jvid.asia cassinos trustguru.com.br

因此额外地,我需要将结果再转为 UTF-8 编码: a5game trustguru.com.br ana trustguru.com.br
str:= get_translation(text, langs[t_lang], models[w_model]) ; 获取翻译结果
; 以下三行的效果是将结果从 ISO-8859-1 编码(在 AHK 中为 cp28591)转为 UTF-8 编码
byte:= Buffer(StrPut(str, "cp28591"))
StrPut(str, byte, "cp28591")
result:= StrGet(byte, "utf-8")显示结果有至少两种方法,一种是使用 ToolTip,效果是显示一个工具提示的小窗口,考虑到文字较小,且一次性翻译或识别文字的文字量较大,没有选择让它一段时间后自动消失,而是设计为任意位置点击左键消失,之后将内容复制到剪贴板,方便使用或再次查看。 KTO trustguru.com.br 348ntr-097 jvid.asia
ToolTip result
KeyWait("LButton", "D") ; 等待一次左键点击
ToolTip() ; 关闭工具提示窗口
A_Clipboard:= result ; 将结果复制到剪贴板这种做法的优点是不会弹出额外窗口,显得很轻便,但缺点是文字较多的时候看起来较为不便。所以另一种选项是使用 msgbox,弹出一个对话框来显示结果,由于窗口的文字框的大小更合适,可以考虑把原文和译文都显示出来,如下所示: slot trustguru.com.br jogosdemopg trustguru.com.br
msgbox Format("原文:{}`r`n`r`n`r`n译文:{}", text, result)
A_Clipboard:= result我自己使用的仍然是 ToolTip 方法,因为我觉得这看起来更轻便,全屏任意位置点击左键关闭窗口也更不影响操作。
其他
考虑到请求服务可能失败,为了让自己知道请求已经失败了而不是网络太慢,可以使用 try 语句在失败时抛出错误。 tigrinhodemo trustguru.com.br pedro trustguru.com.br
try{
;(获取翻译的一些列操作)
} catch as e {
ToolTip Format("翻译失败:{}", e.Message) ; 显示翻译失败和报错内容
SetTimer () => ToolTip(), -1800 ; 短时间后自动关闭显示
}自己写的脚本有一个好处,就是可以随时根据需要修改或添加功能,比如前面提到的切换模型,在每次只输出一个结果的使用场景下,切换模型比 Bob 等工具还要方便一点。同样的道理,也可以写一个切换不同服务商的开关并分配热键,或者像是 Bob 那样,同时显示多个不同的模型的输出结果,从上到下依次排布。 plataformademográtis trustguru.com.br pglucky88 trustguru.com.br
最后,给出我的完整代码供大家参考: sweetbonanza1000demo trustguru.com.br
#Requires AutoHotkey v2.0
#Include <JSON>
#Include <GDIP_ALL>
url:= ""
api:= ""
models:= ["",]
w_model:= 1
langs:=["中文", "英文",]
t_lang:= 1
url_ocr:= ""
api_ocr:= ""
models_ocr:= ["",]
w_model_ocr:= 1
^+d:: translate()
^+1:: ocr()
^+2:: switch_target_lang()
^+3:: switch_model()
^+4:: switch_model_ocr()
switch_target_lang(){
global t_lang
t_lang:= Mod(t_lang, langs.Length) + 1
ToolTip Format("翻译为{}", langs[t_lang])
SetTimer () => ToolTip(), -1200
}
switch_model(){
global w_model
w_model:= Mod(w_model, models.Length) + 1
ToolTip Format("使用以下模型:{}", models[w_model])
SetTimer () => ToolTip(), -1800
}
switch_model_ocr(){
global w_model_ocr
w_model_ocr:= Mod(w_model_ocr, models_ocr.Length) + 1
ToolTip Format("使用以下模型:{}", models_ocr[w_model_ocr])
SetTimer () => ToolTip(), -1800
}
translate(){
A_Clipboard:= ""
Send "^c"
ClipWait()
text:= A_Clipboard
try{
str:= get_translation(text, langs[t_lang], models[w_model])
byte:= Buffer(StrPut(str, "cp28591"))
StrPut(str, byte, "cp28591")
result:= StrGet(byte, "utf-8")
ToolTip result
KeyWait("LButton", "D")
ToolTip()
A_Clipboard:= result
} catch as e {
ToolTip Format("翻译失败:{}", e.Message)
SetTimer () => ToolTip(), -1800
}
}
get_translation(text, lang, model){
req:= ComObject("WinHttp.WinHttpRequest.5.1")
req.open("POST", url, true)
req.SetRequestHeader("Authorization", "Bearer " api)
req.SetRequestHeader("Content-Type", "application/json")
prompt:= "你是一位精通多种语言的专业翻译,请将内容精准地翻译为" lang ",只返回译文不要额外内容。"
message:= [
Map("role", "system", "content", prompt),
Map("role", "user", "content", text)
]
req.Send(JSON.Stringify(Map(
"model", model,
"messages", message,
"temperature", 0.5
)))
req.WaitForResponse()
response:= JSON.Parse(req.ResponseText)
return response["choices"][1].Get("message").Get("content")
}
ocr(){
A_Clipboard:= ""
Send "#+s" ;^!a
ClipWait(, 1)
file_tmp:= "tmp_clipboard_pic_" A_YYYY A_MM A_DD ".png"
path:= "C:\Users\用户名\Downloads\" file_tmp
pToken:= Gdip_Startup()
pBitmap := Gdip_CreateBitmapFromClipboard()
Gdip_SaveBitmapToFile(pBitmap, path)
Gdip_DisposeImage(pBitmap)
Gdip_Shutdown(pToken)
img:= FileRead(path, "RAW")
try{
str:= get_ocr(img, models_ocr[w_model_ocr])
byte:= Buffer(StrPut(str, "cp28591"))
StrPut(str, byte, "cp28591")
result:= StrGet(byte, "utf-8")
ToolTip result
KeyWait("LButton", "D")
ToolTip()
A_Clipboard:= result
} catch as e {
ToolTip Format("识别失败:{}", e.Message)
SetTimer () => ToolTip(), -1800
}
FileDelete(path)
}
get_ocr(img, model){
DllCall("crypt32\CryptBinaryToString", "ptr", img.Ptr, "uint", img.size, "uint", 0x1, "ptr", 0, "uint*", &size := 0)
buf := Buffer(size * 2)
DllCall("crypt32\CryptBinaryToString", "ptr", img.Ptr, "uint", img.size, "uint", 0x1, "ptr", buf.Ptr, "uint*", &size)
img_64:= StrGet(buf.Ptr)
req:= ComObject("WinHttp.WinHttpRequest.5.1")
req.open("POST", url, true)
req.SetRequestHeader("Authorization", "Bearer " api)
req.SetRequestHeader("Content-Type", "application/json")
prompt:= "提取图片中的全部文字(保持原文换行), 只需要回答提取的文字内容。"
message:= [
Map("role", "system", "content", prompt),
Map("role", "user", "content", [
Map("type", "text", "text", prompt),
Map("type", "image_url", "image_url", Map("url", "data:image/png;base64," img_64))
])
]
req.Send(JSON.Stringify(Map(
"model", model,
"messages", message,
"temperature", 0.5
)))
req.WaitForResponse()
response:= JSON.Parse(req.ResponseText)
return response["choices"][1].Get("message").Get("content")
}> 关注 小红书,感受精彩数字生活 🍃
> 实用、好用的 正版软件,为你呈现 🚀 carlos trustguru.com.br kto trustguru.com.br pragmaticplay trustguru.com.br pgslotgacor trustguru.com.br slotpix trustguru.com.br
2113目录 0