标题: [原创代码] Python实现的命令行全文翻译工具 [打印本页]
作者: CrLf 时间: 2017-5-5 13:02 标题: Python实现的命令行全文翻译工具
本帖最后由 CrLf 于 2017-5-11 02:23 编辑
因为一直没找到合适的命令行工具,所以自己实现了一个,很适合用来翻译外国命令行工具的自带帮助
(是的,就是用在 Batch-CN 第三方库的搜集整理上)
一年多前写的,用的是百度翻译api,实现了双语对照、全文翻译、参数控制等功能
但因为这个api有限制每月翻译字符数,共用难免会超,所以一直都未公开
现在把源码分享出来,基于 python3,另外需要大家自行注册api,修改【】符号所在位置的 appid 和 secretKey 即可使用
如需使用剪贴板相关功能,还需要 pyperclip 模块支持,请运行 pip3 install pyperclip 自行安装,不安装该模块也不影响其他功能的使用
百度翻译api接入申请地址:http://api.fanyi.baidu.com/api/trans/product/index- #/usr/bin/env python
- # -*- encoding:utf-8 -*-
-
- #by CrLf
-
- import getopt, sys
- import json
- import re
- import http.client
- import hashlib
- import urllib
- import urllib.request
- import urllib.parse
- import random
- import os
- from io import StringIO
-
- try:
- import pyperclip
- except ImportError:
- pyperclip = {}
-
- zhPattern = re.compile(u'[\u4e00-\u9fa5]+')
-
- appid = '20151113000005349' #【测试用的 appid】
- secretKey = 'osubCEzlGjzvw8qdQc41' #【测试用的 secretKey】
-
- jsonPath = jsonPath_base = (os.path.realpath(sys.argv[0]))+'.json'
- #配置文件路径默认为此脚本/程序所在目录
- #有 .json 时,默认使用该文件中的配置
-
- if not ( appid and secretKey ) :
- print("你需要在脚本中设置 appid 和 secretKey", file=sys.stderr)
- print("API接口申请: http://api.fanyi.baidu.com/api/trans/product/index", file=sys.stderr)
- sys.exit(2)
-
- option = {}
- globalVar = {'num':0}
- httpClient = None
- stdinfo = sys.stderr
-
-
- def main():
- if os.path.isfile(jsonPath) :
- getConfig(jsonPath)
-
- try:
- opts,args = getopt.getopt(sys.argv[1:], "hnf:t:DTc", ["help", "from=", "to=", "dict", "text", "num", "debug","web=","clip"])
-
- except Exception as e:
- print("参数错误", file=sys.stderr)
- sys.exit(2)
-
- for opt,arg in opts:
- if opt in ("-h", "--help"):
- usage()
- sys.exit(0)
- elif opt in ("-c","--clip"):
- if not pyperclip :
- print('-c 选项需 pyperclip 模块支持', file=sys.stderr)
- sys.exit(2)
-
- option['clip'] = True
- args = [pyperclip.paste()]
- elif opt in ("-n","--num"):
- option['num'] = True
- elif opt in ("-f", "--form"):
- option['from'] = arg
- if 'to' in option and arg.lower() != 'zh' : option['to']='zh'
- elif opt in ("-t", "--to"):
- option['to'] = arg
- if 'from' in option and arg.lower() != 'zh' : option['from']='zh'
- elif opt in ("-D","--dict"):
- option['dict'] = True
- elif opt in ("-T","--text"):
- option['text'] = True
- elif opt in ("--debug"):
- option['debug'] = True
- elif opt in ("--web"):
- if arg == 'baidu':
- os.system("start http://fanyi.baidu.com/")
- elif arg == 'google':
- os.system("start https://translate.google.cn/")
- elif arg == 'youdao':
- os.system("start http://fanyi.youdao.com/")
- elif arg == 'iciba':
- os.system("start http://fy.iciba.com/")
- elif arg == 'haici':
- os.system("start http://fanyi.dict.cn/")
- else:
- print("--web baidu | google | youdao | iciba | haici")
- sys.exit(0)
- else:
- print("%s ==> %s" %(opt, arg))
-
- if 'debug' in option:
- print("Opts:\r\n%s" % opts, file=stdinfo)
- print("Args:\r\n%s" % args, file=stdinfo)
- print("Option:\r\n%s" % option, file=stdinfo)
-
- fromLang = option['from'] if 'from' in option else 'auto'
- toLang = option['to'] if 'to' in option else 'zh'
-
-
- try:
- if len(args)==0 :
- if sys.stdin.isatty():
- usage()
- else:
- q = re.sub(r'\r?\n','\r\n',sys.stdin.read())
- choiceMode(q, fromLang, toLang)
-
- sys.exit(0)
-
- if len(args)==1 :
- choiceMode(args[0], fromLang, toLang)
- sys.exit(0)
-
-
- if 'dict' in option:
- for arg in args:
-
- if fromLang == 'auto' and zhPattern.search(arg) :
- toLang = 'en'
-
- print('[Dict Mode] %s -> %s\r\n' % (fromLang, toLang), file=stdinfo)
-
- words = re.split(r'\s+',arg)
- for word in words:
- word = word.strip()
- trans_dict(word, fromLang, toLang)
- print('\r\n');
- sys.exit(0)
- else:
- q = '\r\n'.join(args)
-
- if fromLang == 'auto' and zhPattern.search(q) :
- toLang = 'en'
-
- print('[Text Mode] %s -> %s\r\n' % (fromLang, toLang), file=stdinfo)
- trans_text(q, fromLang, toLang)
-
- except Exception as e:
- print(e, file=sys.stderr)
- sys.exit(1)
-
-
-
- def trans_dict(word, fromLang, toLang):
- resp=urllib.request.urlopen(compose_request(word))
- if(resp.status==200):
- handle_result(resp.read().decode('utf-8'))
- else:
- print('Rquest Error: \r\n HTTP Status: %d \r\n Reason: %s \r\n', resp.status, resp.reason)
-
-
- def trans_text(q, fromLang, toLang):
- salt = random.randint(32768, 65536)
-
- sign = appid+q+str(salt)+secretKey
- m1 = hashlib.md5()
- m1.update(bytes(sign, encoding = "utf8") )
- sign = m1.hexdigest()
-
- myurl = '/api/trans/vip/translate?appid='+appid+'&q='+urllib.parse.quote(q)+'&from='+fromLang+'&to='+toLang+'&salt='+str(salt)+'&sign='+sign
-
- try:
- httpClient = http.client.HTTPConnection('api.fanyi.baidu.com')
- httpClient.request('GET', myurl)
-
- #response是HTTPResponse对象
- response = httpClient.getresponse()
- ret = response.read()
- retstr = str(ret,'utf-8')
- retobj= json.loads(retstr)
-
- if 'num' in option:
- str_template='[%05d] -----------------------------------------\r\n%s'
- else:
- str_template='%0.s-------------------------------------------------\r\n%s'
-
- if 'error_code' in retobj and retobj['error_code'] != '52000' :
- print(" 错误码: " + retobj['error_code'], file=sys.stderr)
- print(" 说明: " + retobj['error_msg'], file=sys.stderr)
- sys.exit(retobj['error_code'])
- else :
- for trans_result in retobj['trans_result']:
- globalVar['num']=globalVar['num']+1
- print(str_template % (globalVar['num'], trans_result['src']), file=stdinfo)
- print(trans_result['dst'])
-
- except Exception as e:
- print(e, file=sys.stderr)
- finally:
- if httpClient:
- httpClient.close()
-
-
- def getConfig(configPath):
- config_default = {}
- config = config_default
-
- if os.path.isfile(configPath) :
- try :
- fp = open(configPath,"r")
- config = json.load(fp)
- fp.close()
- except Exception as e:
- config = config_default
-
- global appid, secretKey
-
- if 'appid' in config and 'secretKey' in config and config['appid'] and config['secretKey'] :
- appid = config['appid']
- secretKey = config['secretKey']
-
-
- def usage():
- argshelp = '''
- -f lang --from lang
- 指定翻译前的语言为 lang,可选项详见百度翻译api文档
-
- -t lang --to lang
- 指定翻译后的语言为 lang,可选项详见百度翻译api文档
-
- -T --text
- 以文本形式翻译
-
- -D --dict
- 以字典形式翻译
-
- -n --num
- 以数字形式翻译
-
- -c --clip
- 从剪贴板获取要翻译的内容
-
- --web name
- 用浏览器打开翻译页面(不支持长文本),name 为所用的翻译引擎
- 可选项为 baidu/google/youdao/iciba/haici
-
- --debug
- 调试模式
-
- -h --help
- 显示简单的帮助信息'''
-
- print("Usage:%s [-f lang] [-t lang] [--web name] [-c] [-n|-T|-D] args...." % os.path.basename(sys.argv[0]))
- print(argshelp)
-
- if os.path.isfile(jsonPath) :
- print("\r\n 配置文件: \"%s\"" % jsonPath)
- print(" 该文件用于设置要使用的 appid 和 secretKey")
- print(" 接口申请地址:\"http://api.fanyi.baidu.com/api/trans/product/index\"")
- else :
- print("\r\n 当前无配置文件,默认使用测试 ID")
- print(" 接口申请地址:\"http://api.fanyi.baidu.com/api/trans/product/index\"")
- print("\r\n 如需自定义 appid 和 secretKey,请以 JSON 格式保存到配置文件:")
- print(" \"%s\"" % jsonPath)
- print("\r\n 配置文件的格式为:")
- print(" {'appid': '你的 APPID', 'secretKey': '你的秘钥'}")
-
-
- class Result:
- def __init__(self, src, dest, meanings=None):
- self.src=src
- self.dest=dest
- self.meanings=meanings
-
- def parse_from_json(json_data):
- trans_data=json_data['trans_result']['data'][0]
- try:
- means=None
- if 'simple_means' in json_data['dict_result']:
- dict_data=json_data['dict_result']['simple_means']['symbols'][0]['parts']
- means=list()
- for item in dict_data:
- tmp=item['means']
- if isinstance(tmp[0],dict):
- for t_item in tmp:
- means.append(t_item['word_mean'])
- else:
- means.append(tmp)
- except KeyError:
- means=None
-
- return Result(trans_data['src'],trans_data['dst'],means)
-
- def show(self,file=sys.stdout):
- if 'num' in option:
- str_template='<<<translate\r\n %05d:\t %s ---> %s\r\n\r\n<<<meaning'
- else:
- str_template='<<<translate\r\n %0.s%s ---> %s\r\n\r\n<<<meaning'
-
- globalVar['num']=globalVar['num']+1
-
- print(str_template % (globalVar['num'], self.src, self.dest), file=stdinfo)
- [print(" %s" % meaning) for meaning in self.meanings]
-
-
- def handle_result(content):
- json_data=json.loads(content)
- Result.parse_from_json(json_data).show()
-
- def compose_request(word):
- TRANS_URL='http://fanyi.baidu.com/v2transapi'
- ORIGIN_HOST='fanyi.baidu.com'
-
- r"""
- compose urllib.request.Request object accordingly
-
- """
- body=StringIO()
- body.write('from=zh&to=en' if zhPattern.search(word) else 'from=en&to=zh')
- body.write('&')
- body.write(urllib.parse.urlencode({'query': word }, encoding='utf-8'))
- body.write('&transtype=trans&simple_means_flag=3')
- body=body.getvalue()
-
- headers={'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8',
- 'X-Requested-With':'XMLHttpRequest'
- }
-
- return urllib.request.Request(TRANS_URL, body.encode(encoding='utf-8'), headers=headers, origin_req_host=ORIGIN_HOST, method='POST')
-
-
-
- def choiceMode(q, fromLang, toLang):
- if fromLang == 'auto' and zhPattern.search(q) :
- toLang = 'en'
-
- word = re.sub(r'\r|\r\n','',q).strip()
- if re.search(r'\s',word):
-
- print('[Text Mode] %s -> %s\r\n' % (fromLang, toLang), file=stdinfo)
- trans_text(q, fromLang, toLang)
- else:
- print('[Dict Mode] %s -> %s\r\n' % (fromLang, toLang), file=stdinfo)
- trans_dict(word, fromLang, toLang)
-
- main()
复制代码
作者: CrLf 时间: 2017-5-5 13:10
本帖最后由 CrLf 于 2017-5-5 16:03 编辑
主要用法:- fanyi 测试文本
- ::参数用法
-
- fanyi -c
- ::剪贴板用法,从剪贴板获取要翻译的内容
-
- echo 测试文本 | fanyi
- ::管道用法
-
- fanyi <测试文件.txt
- ::重定向用法
复制代码
如果你只要翻译后的文字,那屏蔽句柄 2 就行了:- fanyi <测试文件.txt 2>nul
- ::重定向用法,并屏蔽原文和其他辅助信息
复制代码
还有些可选参数,含义如下:
Usage:fanyi.py [-f lang] [-t lang] [--web name] [-c] [-n|-T|-D] args....
-f lang --from lang
指定翻译前的语言为 lang,可选项详见百度翻译api文档
-t lang --to lang
指定翻译后的语言为 lang,可选项详见百度翻译api文档
-T --text
以文本形式翻译
-D --dict
以字典形式翻译
-n --num
以数字形式翻译
-c --clip
从剪贴板获取要翻译的内容
--web name
用浏览器打开翻译页面(不支持长文本),name 为所用的翻译引擎
可选项为 baidu/google/youdao/iciba/haici
--debug
调试模式
-h --help
显示简单的帮助信息
作者: CrLf 时间: 2017-5-5 13:11
为了使用方便,我私底下是给打包成 exe 了,好像是用了 PyInstaller
作者: 523066680 时间: 2017-5-5 14:28
C:\>fanyi.py 叼你
[Dict Mode] en -> zh
<<<translate
叼你 ---> f**k you
<<<meaning
'NoneType' object is not iterable
不错不错
作者: CrLf 时间: 2017-5-5 16:05
回复 4# 523066680
修改了一下,改个bug,完善帮助信息,再加上从剪贴板获取来源的选项
作者: happy886rr 时间: 2017-5-5 17:55
翻译的合情合理。- python "%~f1" "In line with the conviction that I will do whatever it takes to serve my country even at the cost of my own life,regardless of fortune or misfortune to myself."
-
- [Text Mode] auto -> zh
-
- -------------------------------------------------
- In line with the conviction that I will do whatever it takes to serve my country
- even at the cost of my own life,regardless of fortune or misfortune to myself.
- 我坚信我将不惜一切代价为国家服务,不惜牺牲自己的生命,无论命运或厄运。
复制代码
作者: codegay 时间: 2017-5-5 19:52
PyInstaller py2exe之类打包出来,体积默认是20多M的吧?
作者: CrLf 时间: 2017-5-5 20:34
回复 7# codegay
3.78MB
作者: codegay 时间: 2017-5-5 21:11
回复 8# CrLf
不错哦。
作者: happy886rr 时间: 2017-5-5 22:08
回复 8# CrLf
不错,最后这个版本几乎完美,就是py体积大,启动慢。百度提供的测试KEY也能用。
作者: CrLf 时间: 2017-5-5 23:07
回复 10# happy886rr
有什么修改可以发出来哈,我本来想做外部配置文件的,但是想想又删了
毕竟 appid 比较私密,如果不能编译到exe里,将配置文件外置的意义也不太大
还是发一个作备份吧:
多了一个 --config 参数
作者: CrLf 时间: 2017-5-5 23:33
回复 10# happy886rr
用 upx 压缩 python35.dll 之后,打包的 fanyi.exe 体积为 1.27MB,够了
作者: codegay 时间: 2017-5-6 02:58
回复 12# CrLf
安装python之前好像是需要VC++组件的,因为官方的cpython windows 版是用的vc++ 编译的。
那么打包后也是需要VC++组件吧?
搜索了一下py2exe是有这种情况。
作者: CrLf 时间: 2017-5-6 12:06
本帖最后由 CrLf 于 2017-5-6 12:13 编辑
回复 13# codegay
用 dlls 查看外部依赖,发现打包后的 fanyi 依赖 msvcr100.dll,这个是 PyInstaller 的问题
内置的 python35.dll 依赖 VCRUNTIME140.dll 和 api-ms-win-crt-conio-l1-1-0.dll 这类 crt 库文件,但是生成 exe 的时候都一并打包了
dlls 工具用于 Batch-CN 依赖关系检测与自动打包:http://www.bathome.net/viewthread.php?tid=38592
作者: pendave 时间: 2017-6-4 22:07
回复 6# happy886rr
我要翻译成 法语怎么写?- python "C:\Program Files\Python 3.5\Python实现的命令行全文翻译工具.py" "%~f1" "叼你" "%~f2" "-t fra"
复制代码
- python "C:\Program Files\Python 3.5\Python实现的命令行全文翻译工具.py" "%~f1" "叼你" -t fra
复制代码
还有 你命令行里突然用复制代码
这个复制代码
是怎么实现的?
作者: pendave 时间: 2017-6-4 23:03
其他的用法我乱摸索中
作者: pendave 时间: 2017-6-4 23:05
为啥我想转.srt 字幕文本就不成功呢,因为里面有多行?空白行?
欢迎光临 批处理之家 (http://www.bathome.net/) |
Powered by Discuz! 7.2 |