Yi's Blog

胸中有丘壑,眼里存山河

「鼠须管」导入搜狗 Bin 词库

rime

「鼠须管」真是个好用的输入法。最大的优势就是反应很快,快到换用「搜狗输入法」就会有输入明显变卡的感觉。但是因为在搜狗的用户词库里有了一些自己输入过的词,所以每次想要换用「鼠须管」,总是使用时间不长就换回了搜狗。

没办法,只能自己想办法把用户词库加到「鼠须管」里了。

准备阶段

问:搜狗输入法的用户词库在哪里?

答:输入一个会被记录的词,这个文件就会更新:

/Users/用户名/Library/Input Methods/Sogou/SogouPY.users/00000001/sgim_usr_v1.bin

一搜文件名,果然是它。

问:这可是个二进制文件啊,大哥。要怎么解析它?

答:使用 Hex Field 看一下,确实头大:

sogou_bin_file

但是搜了一下发现居然有人解析过,就是这个开源项目 studyzy/imewlconverter (真佩服他二进制解读的能力)。唯一比较遗憾的是这是一个 Windows 下的程序,Mac 下没法直接使用。

这下开源的好处就体现出了来,自己动手丰衣足食吧。

导出搜狗词库

解析的工作按照 imewlconverter 中的 SougouPinyinBin.cs 来就可以了。

二进制的文件是大端还是小端

Hex Field 查看发现小端的输出比较合理,所以认定是小端。在读取的时候,需要使用 Python 的 Struct 包读取时指明是小端:

def read_int_32(f):
	    byte = f.read(4)
	    byte = struct.unpack("<L", byte)
	    return byte[0]

二进制中的文字使用的是什么编码?

我用户词库的第一个字是“厑”,使用 UnicodeChecker 就能看到使用的应该是Unicode Hex 或者 UTF-16 Hex。

cjk_a

使用 Codes 模块就可以了:

word = codecs.utf_16_le_decode(f.read(word_count))[0]

导出词库的格式

导入词库的时候,要将信息保存在 .dic 的文件里,.dic 文件的格式为:

中文词组[Tab]以空格分割的拼音组[Tab]频率

具体的代码

# -*- coding: UTF-8 -*-
import sys
import struct
import codecs

PinYinDic = [
"a",
"ai",
"an",
"ang",
"ao",
"ba",
"bai",
"ban",
"bang",
"bao",
"bei",
"ben",
"beng",
"bi",
"bian",
"biao",
"bie",
"bin",
"bing",
"bo",
"bu",
"ca",
"cai",
"can",
"cang",
"cao",
"ce",
"cen",
"ceng",
"cha",
"chai",
"chan",
"chang",
"chao",
"che",
"chen",
"cheng",
"chi",
"chong",
"chou",
"chu",
"chua",
"chuai",
"chuan",
"chuang",
"chui",
"chun",
"chuo",
"ci",
"cong",
"cou",
"cu",
"cuan",
"cui",
"cun",
"cuo",
"da",
"dai",
"dan",
"dang",
"dao",
"de",
"dei",
"den",
"deng",
"di",
"dia",
"dian",
"diao",
"die",
"ding",
"diu",
"dong",
"dou",
"du",
"duan",
"dui",
"dun",
"duo",
"e",
"ei",
"en",
"eng",
"er",
"fa",
"fan",
"fang",
"fei",
"fen",
"feng",
"fiao",
"fo",
"fou",
"fu",
"ga",
"gai",
"gan",
"gang",
"gao",
"ge",
"gei",
"gen",
"geng",
"gong",
"gou",
"gu",
"gua",
"guai",
"guan",
"guang",
"gui",
"gun",
"guo",
"ha",
"hai",
"han",
"hang",
"hao",
"he",
"hei",
"hen",
"heng",
"hong",
"hou",
"hu",
"hua",
"huai",
"huan",
"huang",
"hui",
"hun",
"huo",
"ji",
"jia",
"jian",
"jiang",
"jiao",
"jie",
"jin",
"jing",
"jiong",
"jiu",
"ju",
"juan",
"jue",
"jun",
"ka",
"kai",
"kan",
"kang",
"kao",
"ke",
"kei",
"ken",
"keng",
"kong",
"kou",
"ku",
"kua",
"kuai",
"kuan",
"kuang",
"kui",
"kun",
"kuo",
"la",
"lai",
"lan",
"lang",
"lao",
"le",
"lei",
"leng",
"li",
"lia",
"lian",
"liang",
"liao",
"lie",
"lin",
"ling",
"liu",
"lo",
"long",
"lou",
"lu",
"luan",
"lue",
"lun",
"luo",
"lv",
"ma",
"mai",
"man",
"mang",
"mao",
"me",
"mei",
"men",
"meng",
"mi",
"mian",
"miao",
"mie",
"min",
"ming",
"miu",
"mo",
"mou",
"mu",
"na",
"nai",
"nan",
"nang",
"nao",
"ne",
"nei",
"nen",
"neng",
"ni",
"nian",
"niang",
"niao",
"nie",
"nin",
"ning",
"niu",
"nong",
"nou",
"nu",
"nun",
"nuan",
"nue",
"nuo",
"nv",
"o",
"ou",
"pa",
"pai",
"pan",
"pang",
"pao",
"pei",
"pen",
"peng",
"pi",
"pian",
"piao",
"pie",
"pin",
"ping",
"po",
"pou",
"pu",
"qi",
"qia",
"qian",
"qiang",
"qiao",
"qie",
"qin",
"qing",
"qiong",
"qiu",
"qu",
"quan",
"que",
"qun",
"ran",
"rang",
"rao",
"re",
"ren",
"reng",
"ri",
"rong",
"rou",
"ru",
"rua",
"ruan",
"rui",
"run",
"ruo",
"sa",
"sai",
"san",
"sang",
"sao",
"se",
"sen",
"seng",
"sha",
"shai",
"shan",
"shang",
"shao",
"she",
"shei",
"shen",
"sheng",
"shi",
"shou",
"shu",
"shua",
"shuai",
"shuan",
"shuang",
"shui",
"shun",
"shuo",
"si",
"song",
"sou",
"su",
"suan",
"sui",
"sun",
"suo",
"ta",
"tai",
"tan",
"tang",
"tao",
"te",
"tei",
"teng",
"ti",
"tian",
"tiao",
"tie",
"ting",
"tong",
"tou",
"tu",
"tuan",
"tui",
"tun",
"tuo",
"wa",
"wai",
"wan",
"wang",
"wei",
"wen",
"weng",
"wo",
"wu",
"xi",
"xia",
"xian",
"xiang",
"xiao",
"xie",
"xin",
"xing",
"xiong",
"xiu",
"xu",
"xuan",
"xue",
"xun",
"ya",
"yan",
"yang",
"yao",
"ye",
"yi",
"yin",
"ying",
"yo",
"yong",
"you",
"yu",
"yuan",
"yue",
"yun",
"za",
"zai",
"zan",
"zang",
"zao",
"ze",
"zei",
"zen",
"zeng",
"zha",
"zhai",
"zhan",
"zhang",
"zhao",
"zhe",
"zhei",
"zhen",
"zheng",
"zhi",
"zhong",
"zhou",
"zhu",
"zhua",
"zhuai",
"zhuan",
"zhuang",
"zhui",
"zhun",
"zhuo",
"zi",
"zong",
"zou",
"zu",
"zuan",
"zui",
"zun",
"zuo",
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z"
]

def read_int_16(f):
		byte = f.read(2)
		byte = struct.unpack("<h", byte)
		return byte[0]


def read_int_32(f):
		byte = f.read(4)
		byte = struct.unpack("<L", byte)
		return byte[0]


def read_bin_file(path):
	with open(path, "rb") as f:
			f.seek(0x18, 0)
			total_word_count = read_int_32(f)
			print "Total word is %s" % total_word_count
			current_word_num = 0
			f.seek(0x30, 0)

			res = []
			while current_word_num < total_word_count :
				same_py_count = read_int_16(f)
				unknow_var = read_int_16(f)
				py_count = read_int_16(f) / 2

				py_str = []

				for i in range(py_count):
					idx = read_int_16(f)
					if idx < len(PinYinDic) and idx >= 0:
						py_str.append(PinYinDic[idx])
					else:
						py_str.append("--")

				for i in range(same_py_count):
					word_count = read_int_16(f)
					word = codecs.utf_16_le_decode(f.read(word_count))[0]
					count = read_int_16(f)
					count2 = read_int_16(f)
					unknow_var2 = read_int_32(f)
					word_dic = {"count":count, "word": word, "pinyin": py_str}
					res.append(word_dic)
					print "current_word_num: %s and pinyin %s" % (current_word_num, py_str)
					current_word_num += 1
			return res

def try_pinyin():
	print 'Number of arguments:', len(sys.argv), 'arguments.'
	print 'Argument List:', str(sys.argv)
	if len(sys.argv) == 2:
		print PinYinDic[int(sys.argv[1])]

def save_words_to_path(filename, words):
	f = codecs.open(filename, "w", "utf-8")
	for word in words:
		f.write(u"%s\t%s\t%d\n" % (word["word"], " ".join(word["pinyin"]), word["count"]))
	print "共写入 %d 词组" % len(words)	
	f.close()


if __name__ == "__main__":
	path = "/Users/用户名/Library/Input Methods/Sogou/SogouPY.users/00000001/sgim_usr_v1.bin"
	res = read_bin_file(path)
	print res[0]
	save_words_to_path("user.dic", res)

导入 Rime 词库

有了 user.dic 文件以后:

  • 找到 rime_dict_manager。把这个 bash 脚本保存到 ~/Library/Rime/rime_dict_manager:
  • 拷贝转化后的鼠须管词库(user.dic)至~/Library/Rime目录;
  • 打开终端,先kill:killall Squirrel
  • 单个导入:./rime_dict_manager -i luna_pinyin user.dic

更多链接

- EOF -