├── .gitignore ├── README.md ├── __init__.py ├── actions ├── __init__.py ├── command.py └── index │ ├── __init__.py │ ├── browser.py │ ├── cam.py │ ├── infrate.py │ ├── ppt.py │ └── singsongs.py ├── customnotify ├── __init__.py └── gntpnotify.py ├── ex ├── __init__.py └── exception.py ├── gl.py ├── gntp ├── __init__.py ├── cli.py ├── config.py ├── core.py ├── errors.py ├── notifier.py ├── shim.py └── version.py ├── inputs ├── __init__.py └── microphone.py ├── install.md ├── jieba ├── __init__.py ├── analyse │ ├── __init__.py │ ├── analyzer.py │ └── idf.txt ├── dict.txt ├── finalseg │ ├── __init__.py │ ├── prob_emit.py │ ├── prob_start.py │ └── prob_trans.py └── posseg │ ├── __init__.py │ ├── char_state_tab.py │ ├── prob_emit.py │ ├── prob_start.py │ ├── prob_trans.py │ └── viterbi.py ├── outputs ├── __init__.py ├── debug.py └── speaker.py ├── proofread ├── __init__.py └── javaproofread.py ├── seg ├── __init__.py └── jiebaseg.py ├── startup.py ├── stt ├── __init__.py └── google.py ├── thd ├── Consumer.py ├── Producer.py └── __init__.py └── tts ├── README.md ├── __init__.py └── picotts.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.wav 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 申明 2 | =================== 3 | 项目转移到 [autohome](https://github.com/apanly/autohome),目的是全新重构智能家居系统,是系统更具有扩展性 4 | 5 | 6 | 家居声控系统 7 | ================== 8 | # 目标 9 | * 开发一个全智能的语音识别机器人,期望安装在树莓派上,然后控制家里的家电,并且可以控制linux内核的笔记本等设备识别指令集 10 | 11 | # 开发语言 12 | * python 13 | 14 | # python依赖包(推荐使用easy_install安装依赖) 15 | * requests 16 | * pyzmq 17 | * pyaudio(如若提示portaudio错误,请安装就可以了) 18 | * PIL(Python Imaging Library) 19 | * v4l2capture 20 | * wave 21 | * Internet connection 22 | * gntp 23 | * wolframalpha 24 | 25 | # linux依赖包 26 | * growl for linux 27 | * pico2wave 28 | * sudo apt-get install espeak 29 | 30 | # 如何使用 31 | * 启动gol(growl on linux) 我编译安装之后路径如下/usr/local/bin/gol 32 | * python startup.py 33 | 34 | # 系统架构图 35 | ![家居声控系统图](http://www.echocool.net/wp-content/uploads/2013/09/sys.png) 36 | 37 | # Todolist 38 | * 静音判断 39 | * 搜索指令集需要分类(可以借助dbpedia),例如人物,音乐,学习,编程手册等等 40 | * 语音识别本地化,Julius speech recogition是一个开源的项目 41 | * 加入学习模式,例如大耳朵、可可、沪江等网站,可以获取感兴趣的每天开始学习 42 | * 加入新闻机器人的功能,以后看新闻就可以不用那么多网站找了(想法是可以找英文和科技)英文我发现有个拓词和百词斩非常不错 43 | * 命令分类:电视,空调,唱歌,编程,新闻,图片,天气预报(目前就这几类,后面可以添加),先找命令类型,然后执行详细命令 例如电视频道50(分词结果电视频道,50) 电视 先找到电视类别,然后执行频道50 44 | 45 | # has done 46 | * 录音功能,最长录音时间5S,如果中间停顿次数多余15次会提前终止此次录音 47 | * stt功能,将上一步的录音通过google api 翻译音频内容 48 | * command功能 根据上一步google api 返回的内容,进行简单指令操作 49 | * 加入Usage命令提示功能 50 | * 实现了 start/stop 命令功能 51 | * 实现了同时只有一个应用程序启动的判断 52 | * 在桌面环境使用growl提示用户 53 | * 使用pico实现了tts->修改成e-speak (例如:espeak -vzh "郭威 我爱你") 54 | * gntp 和 growl 共同结合 给用户有好提示信息 55 | * 多线程,网络模型如下:有一个栈专门用于接受音频,有很多个子线程(或者多个进程)从栈中抢取音频指令,对于阻塞的指令可能需要特殊处理,例如播放音乐 56 | * 利用树莓派嵌入式的优势,然后开发控制tv,空调等指令 --PS:这个已经实现了,请关注红外控制系统[piInfrated](https://github.com/apanly/piInfrated) 57 | 58 | 59 | # doing 60 | * Yahoo由14个基本大类组成,包括 61 | Art&Humanities(艺术与人文)、Business&Economy(商业与经济)、 62 | Computers&Internet(电脑与网际网路/网络)、Education(教育)、 63 | Entertainment(娱乐)、Government(政府)、Health(健康与医药)、 64 | News&Media(新闻与媒体)、Recreation&Sports(休闲与运动)、 65 | Reference(参考资料)、Regional(国家与地区)、 66 | Science(科学)、SocialScience(社会科学)、 67 | Society&Culture(社会与文化) 68 | * 静音判断,正在研究vad技术 69 | * 中文文本自动纠错 70 | * 语音识别可以修改成Kaldi(google被墙了) 71 | 72 | 73 | # 参考文档如下 74 | * [Linux音频编程指南](http://www.ibm.com/developerworks/cn/linux/l-audio/index.html) 75 | * [python pyaudio doc](http://people.csail.mit.edu/hubert/pyaudio/#docs) 76 | * [Gordons Projects](https://projects.drogon.net/raspberry-pi/wiringpi/) 77 | * [text to speach](http://translate.google.com/translate_tts?q=%E6%AC%A2%E8%BF%8E%E5%85%89%E4%B8%B4%E4%B8%83%E5%93%A5%E7%9A%84%E5%8D%9A%E5%AE%A2&tl=zh-CN) 78 | * 搜索引擎Yahoo的分类体系及性能评价 79 | * [静音判断参考资料](http://ibillxia.github.io/blog/2013/05/22/audio-signal-processing-time-domain-Voice-Activity-Detection/) 80 | * [基于短时能量与过零率的端点检测的matlab分析 ](http://blog.csdn.net/ziyuzhao123/article/details/8932336) 81 | * [Google speech recognition with python](http://campus.albion.edu/squirrel/2012/03/01/google-speech-recognition-with-python/) 82 | * [gntp](https://github.com/kfdm/gntp) 83 | * [Growl For Linux](https://github.com/apanly/growl-for-linux) 84 | * [语音识别实现方式之一](http://a7b.cn/2013/%E8%AF%AD%E9%9F%B3%E8%AF%86%E5%88%AB/#toc_3) 85 | * [树莓派客厅中央控制](http://www.peiqianhuo.net/?p=28) 86 | * [在Ubuntu的系统下通过串口连接IOIO-OTG](http://www.oschina.net/question/1174645_116717) 87 | * [豆瓣书屋]https://api.douban.com/v2/book/search?q=%E6%B5%AA%E6%BD%AE%E4%B9%8B%E5%B7%85 88 | * [天气查询]http://blog.mynook.info/2012/08/18/weather-com-cn-api.html 89 | * [天气城市id]http://www.xiaoningmeng.com/2012/10/androids-china-weather-city-id-data/ 90 | * [浅谈中文文本自动纠错在影视剧搜索中应用与Java实现]http://www.cnblogs.com/wuren/archive/2012/12/21/2828649.html 91 | * [espeak跨平台语音合成器](http://www.oschina.net/p/espeak/) 92 | * [Raspberry Pi创意大荟萃](http://guiquanz.github.io/2013/01/04/projects-of-raspberry-pi/) 93 | * [几个常见的语音交互平台的简介和比较](http://blog.csdn.net/lchunli/article/details/18504799) 94 | 95 | # How to Contact 96 | ## QQ:36405410 97 | ## Email:apanly@163.com 98 | 99 | 100 | # Copying 101 | ### Free use of this software is granted under the terms of the GNU Lesser General Public License (LGPL) 102 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'vincent' 2 | -------------------------------------------------------------------------------- /actions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apanly/piRobot/8f0788b95d0321811f4359560c3173a4b94ad181/actions/__init__.py -------------------------------------------------------------------------------- /actions/command.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | from index.infrate import innerInfrated 5 | from index.ppt import innerppt 6 | from tts.picotts import picotts 7 | from proofread.javaproofread import proofreadClient 8 | #from index.cam import camera 9 | 10 | class InstructionSet: 11 | 12 | def __init__(self,txt,debug): 13 | self.txt=txt 14 | self.debug=debug 15 | self.pico=picotts("welcome to google world") 16 | 17 | def docmd(self): 18 | print self.txt 19 | self.pico.onPlayer() 20 | prooftarget=proofreadClient(self.txt) 21 | result=prooftarget.do() 22 | commands=result.split(",") 23 | cmdtype=commands[3] 24 | cmddetail=commands[2] 25 | if cmdtype=="tv": 26 | self.dotvair(cmddetail) 27 | elif cmdtype=="air": 28 | self.dotvair(cmddetail) 29 | elif cmdtype=="ppt": 30 | self.doppt(cmddetail) 31 | #测试照相功能 32 | #camtarget=camera() 33 | #camtarget.do() 34 | 35 | def dotvair(self,command): 36 | target=innerInfrated() 37 | target.gogo(command) 38 | def doppt(self,command): 39 | target=innerppt() 40 | target.gogo(command) 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /actions/index/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apanly/piRobot/8f0788b95d0321811f4359560c3173a4b94ad181/actions/index/__init__.py -------------------------------------------------------------------------------- /actions/index/browser.py: -------------------------------------------------------------------------------- 1 | __author__ = 'vincent' 2 | 3 | import os 4 | 5 | 6 | class Browser: 7 | def docmd(self,url): 8 | os.system("firefox %s"%url) #webbrowser.open(url) 9 | 10 | # def docmdback(self): 11 | # self.pico.onPlayer() 12 | # if self.getCmdFlag()==False: 13 | # self.debug.saytxt("cmd:browser") 14 | # self.browser() 15 | # if self.getCmdFlag()==False: 16 | # self.debug.saytxt("cmd:terminal") 17 | # self.terminal() 18 | # if self.getCmdFlag()==False: 19 | # self.debug.saytxt("cmd:songs") 20 | # self.singhappy() 21 | # if self.getCmdFlag()==False: 22 | # self.googleSearch() 23 | # 24 | # def browser(self): 25 | # target=Browser() 26 | # if self.txt.find("谷歌") > -1 or self.txt.find("google")>-1 : 27 | # self.setCmdFlag(True) 28 | # target.docmd("www.google.com.hk") 29 | # elif self.txt.find("百度") > -1: 30 | # self.setCmdFlag(True) 31 | # target.docmd("www.baidu.com") 32 | # elif self.txt.find("新浪") > -1: 33 | # self.setCmdFlag(True) 34 | # target.docmd("www.sina.com.cn") 35 | # elif self.txt.find("火狐") > -1: 36 | # self.setCmdFlag(True) 37 | # target.docmd("www.echocool.net") 38 | # 39 | # 40 | # def terminal(self): 41 | # if self.txt.find("终端") > -1 or self.txt.find("ternimal") > -1 : 42 | # self.setCmdFlag(True) 43 | # os.system("gnome-terminal") 44 | # 45 | # def singhappy(self): 46 | # target=SingSongs() 47 | # if self.txt.find("首歌")>-1 or self.txt.find("听歌")>-1: 48 | # self.setCmdFlag(True) 49 | # target.docmd() 50 | # 51 | # def googleSearch(self): 52 | # global SEARCH 53 | # q=urllib.quote_plus((u'%s'%self.txt).encode('utf8')) 54 | # searchquri=SEARCH%(q,q) 55 | # target=Browser() 56 | # target.docmd(searchquri) 57 | # 58 | # def setCmdFlag(self,modeFlag): 59 | # self.Flag=modeFlag 60 | # 61 | # def getCmdFlag(self): 62 | # return self.Flag 63 | 64 | -------------------------------------------------------------------------------- /actions/index/cam.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | import Image 4 | import select 5 | import v4l2capture 6 | import time 7 | import traceback 8 | import sys,pygame 9 | from gl import PICPATH 10 | 11 | class camera: 12 | def __init__(self): 13 | self.flag=True 14 | def do(self): 15 | size = width, height = 1280,1024 16 | speed = [2, 2] 17 | # Open the video device. 18 | video = v4l2capture.Video_device("/dev/video1") 19 | size_x, size_y = video.set_format(1280,1024) 20 | video.create_buffers(1) 21 | video.queue_all_buffers() 22 | video.start() 23 | pygame.init() 24 | pygame.display.set_caption('视频拍照') 25 | screen = pygame.display.set_mode(size) 26 | while self.flag: 27 | try: 28 | readable , writable , exceptional =select.select([video,], [], []) 29 | if not (readable or writable or exceptional) : 30 | print "Time out ! " 31 | break; 32 | for s in readable : 33 | if s is video: 34 | try: 35 | image_data = video.read_and_queue() 36 | image = Image.fromstring("RGB", (size_x, size_y), image_data) 37 | image.save(PICPATH+"image.bmp") 38 | print "Saved image.jpg (Size: " + str(size_x) + " x " + str(size_y) + ")" 39 | #加载图像 40 | pyimage = pygame.image.load(PICPATH+"image.bmp") 41 | for event in pygame.event.get(): 42 | if event.type == pygame.QUIT: 43 | pygame.quit() 44 | break 45 | #传送画面 46 | screen.blit(pyimage, speed) 47 | #显示图像 48 | pygame.display.flip() 49 | except IOError as e: 50 | if e.errno == 11: 51 | print "error" 52 | time.sleep(0.01) 53 | for s in writable: 54 | pass 55 | for s in exceptional: 56 | print " exception condition on ", s.getpeername() 57 | except: 58 | traceback.print_exc() 59 | video.close() 60 | sys.exit(1) 61 | def stop(self): 62 | self.flag=False -------------------------------------------------------------------------------- /actions/index/infrate.py: -------------------------------------------------------------------------------- 1 | import zmq 2 | 3 | 4 | class innerInfrated: 5 | def __init__(self): 6 | context = zmq.Context() 7 | socket = context.socket(zmq.REQ) 8 | socket.connect ("tcp://localhost:5555") 9 | self.socket=socket 10 | 11 | def gogo(self,command): 12 | self.socket.send (command) 13 | return self.socket.recv() 14 | -------------------------------------------------------------------------------- /actions/index/ppt.py: -------------------------------------------------------------------------------- 1 | import zmq 2 | 3 | 4 | class innerppt: 5 | def __init__(self): 6 | context = zmq.Context() 7 | socket = context.socket(zmq.REQ) 8 | socket.connect ("tcp://localhost:5558") 9 | self.socket=socket 10 | 11 | def gogo(self,command): 12 | self.socket.send (command) 13 | return self.socket.recv() 14 | -------------------------------------------------------------------------------- /actions/index/singsongs.py: -------------------------------------------------------------------------------- 1 | __author__ = 'vincent' 2 | import os 3 | import random 4 | import urllib2 5 | import urllib 6 | import json 7 | class SingSongs: 8 | def docmd(self): 9 | print os.getpid() 10 | os.system("mplayer %s"%self.douban()) 11 | return 12 | def douban(self): 13 | songrui='' 14 | f = urllib2.urlopen("http://douban.fm/j/mine/playlist?type=n&channel=1") 15 | res = f.read() 16 | f.close() 17 | doubansongs = json.loads(res)['song'] 18 | if doubansongs: 19 | for item in doubansongs: 20 | songrui=item['url'] 21 | if songrui: 22 | break 23 | return songrui 24 | 25 | 26 | if __name__=="__main__": 27 | target=SingSongs() 28 | target.docmd() 29 | 30 | -------------------------------------------------------------------------------- /customnotify/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apanly/piRobot/8f0788b95d0321811f4359560c3173a4b94ad181/customnotify/__init__.py -------------------------------------------------------------------------------- /customnotify/gntpnotify.py: -------------------------------------------------------------------------------- 1 | from gntp import notifier 2 | 3 | class gntpnotify: 4 | def __init__(self): 5 | pass 6 | def ownnotify(self): 7 | #notifications using growl 8 | growl = notifier.GrowlNotifier( 9 | applicationName = "piRobot", 10 | notifications = ["Speech","New Updates","New Messages"], 11 | defaultNotifications = ["Speech"] 12 | # hostname = "computer.example.com", # Defaults to localhost 13 | # password = "123456" # Defaults to a blank password 14 | ) 15 | growl.register() 16 | # Send one message 17 | growl.notify( 18 | noteType = "New Messages", 19 | title = "You have a new message", 20 | description = "A longer message description", 21 | icon = "http://www.baidu.com/img/bdlogo.gif", 22 | sticky = False, 23 | priority = 1, 24 | ) 25 | growl.notify( 26 | noteType = "New Updates", 27 | title = "There is a new update to download", 28 | description = "A longer message description", 29 | icon = "http://www.baidu.com/img/bdlogo.gif", 30 | sticky = False, 31 | priority = -1, 32 | ) -------------------------------------------------------------------------------- /ex/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apanly/piRobot/8f0788b95d0321811f4359560c3173a4b94ad181/ex/__init__.py -------------------------------------------------------------------------------- /ex/exception.py: -------------------------------------------------------------------------------- 1 | class NotUnderstoodException(Exception): 2 | pass 3 | 4 | class NoResultsFoundException(Exception): 5 | pass 6 | -------------------------------------------------------------------------------- /gl.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from Queue import Queue 3 | import os 4 | queue = Queue() #用于当作存放用户音频的队列,后面的处理线程从这个队列取数据 5 | # Temporaries files 6 | CACHEFOLDER = os.getenv('HOME') + '/.cache/piRobot/' 7 | if not os.path.exists(CACHEFOLDER): 8 | os.makedirs(CACHEFOLDER) 9 | 10 | WAVPATH=CACHEFOLDER #用于指定录音文件存放在什么地方 11 | 12 | SEARCH="https://www.google.com.hk/search?newwindow=1\&safe=active\&hl=en\&site=webhp\&source=hp\&q=%s&oq=%s" 13 | 14 | PIDLOG="%spiRobot.pid"%CACHEFOLDER 15 | 16 | SPEECHTTS="%spicotts.wav"%CACHEFOLDER 17 | 18 | DOUBANFM="http://douban.fm/j/mine/playlist?type=n&channel=1" 19 | 20 | PICPATH="/home/vincent/opensource/record/" -------------------------------------------------------------------------------- /gntp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apanly/piRobot/8f0788b95d0321811f4359560c3173a4b94ad181/gntp/__init__.py -------------------------------------------------------------------------------- /gntp/cli.py: -------------------------------------------------------------------------------- 1 | # Copyright: 2013 Paul Traylor 2 | # These sources are released under the terms of the MIT license: see LICENSE 3 | 4 | import logging 5 | import os 6 | import sys 7 | from optparse import OptionParser, OptionGroup 8 | 9 | from gntp.notifier import GrowlNotifier 10 | from gntp.shim import RawConfigParser 11 | from gntp.version import __version__ 12 | 13 | DEFAULT_CONFIG = os.path.expanduser('~/.gntp') 14 | 15 | config = RawConfigParser({ 16 | 'hostname': 'localhost', 17 | 'password': None, 18 | 'port': 23053, 19 | }) 20 | config.read([DEFAULT_CONFIG]) 21 | if not config.has_section('gntp'): 22 | config.add_section('gntp') 23 | 24 | 25 | class ClientParser(OptionParser): 26 | def __init__(self): 27 | OptionParser.__init__(self, version="%%prog %s" % __version__) 28 | 29 | group = OptionGroup(self, "Network Options") 30 | group.add_option("-H", "--host", 31 | dest="host", default=config.get('gntp', 'hostname'), 32 | help="Specify a hostname to which to send a remote notification. [%default]") 33 | group.add_option("--port", 34 | dest="port", default=config.getint('gntp', 'port'), type="int", 35 | help="port to listen on [%default]") 36 | group.add_option("-P", "--password", 37 | dest='password', default=config.get('gntp', 'password'), 38 | help="Network password") 39 | self.add_option_group(group) 40 | 41 | group = OptionGroup(self, "Notification Options") 42 | group.add_option("-n", "--name", 43 | dest="app", default='Python GNTP Test Client', 44 | help="Set the name of the application [%default]") 45 | group.add_option("-s", "--sticky", 46 | dest='sticky', default=False, action="store_true", 47 | help="Make the notification sticky [%default]") 48 | group.add_option("--image", 49 | dest="icon", default=None, 50 | help="Icon for notification (URL or /path/to/file)") 51 | group.add_option("-m", "--message", 52 | dest="message", default=None, 53 | help="Sets the message instead of using stdin") 54 | group.add_option("-p", "--priority", 55 | dest="priority", default=0, type="int", 56 | help="-2 to 2 [%default]") 57 | group.add_option("-d", "--identifier", 58 | dest="identifier", 59 | help="Identifier for coalescing") 60 | group.add_option("-t", "--title", 61 | dest="title", default=None, 62 | help="Set the title of the notification [%default]") 63 | group.add_option("-N", "--notification", 64 | dest="name", default='Notification', 65 | help="Set the notification name [%default]") 66 | group.add_option("--callback", 67 | dest="callback", 68 | help="URL callback") 69 | self.add_option_group(group) 70 | 71 | # Extra Options 72 | self.add_option('-v', '--verbose', 73 | dest='verbose', default=0, action='count', 74 | help="Verbosity levels") 75 | 76 | def parse_args(self, args=None, values=None): 77 | values, args = OptionParser.parse_args(self, args, values) 78 | 79 | if values.message is None: 80 | print('Enter a message followed by Ctrl-D') 81 | try: 82 | message = sys.stdin.read() 83 | except KeyboardInterrupt: 84 | exit() 85 | else: 86 | message = values.message 87 | 88 | if values.title is None: 89 | values.title = ' '.join(args) 90 | 91 | # If we still have an empty title, use the 92 | # first bit of the message as the title 93 | if values.title == '': 94 | values.title = message[:20] 95 | 96 | values.verbose = logging.WARNING - values.verbose * 10 97 | 98 | return values, message 99 | 100 | 101 | def main(): 102 | (options, message) = ClientParser().parse_args() 103 | logging.basicConfig(level=options.verbose) 104 | if not os.path.exists(DEFAULT_CONFIG): 105 | logging.info('No config read found at %s', DEFAULT_CONFIG) 106 | 107 | growl = GrowlNotifier( 108 | applicationName=options.app, 109 | notifications=[options.name], 110 | defaultNotifications=[options.name], 111 | hostname=options.host, 112 | password=options.password, 113 | port=options.port, 114 | ) 115 | result = growl.register() 116 | if result is not True: 117 | exit(result) 118 | 119 | # This would likely be better placed within the growl notifier 120 | # class but until I make _checkIcon smarter this is "easier" 121 | if options.icon is not None and not options.icon.startswith('http'): 122 | logging.info('Loading image %s', options.icon) 123 | f = open(options.icon) 124 | options.icon = f.read() 125 | f.close() 126 | 127 | result = growl.notify( 128 | noteType=options.name, 129 | title=options.title, 130 | description=message, 131 | icon=options.icon, 132 | sticky=options.sticky, 133 | priority=options.priority, 134 | callback=options.callback, 135 | identifier=options.identifier, 136 | ) 137 | if result is not True: 138 | exit(result) 139 | 140 | if __name__ == "__main__": 141 | main() 142 | -------------------------------------------------------------------------------- /gntp/config.py: -------------------------------------------------------------------------------- 1 | # Copyright: 2013 Paul Traylor 2 | # These sources are released under the terms of the MIT license: see LICENSE 3 | 4 | """ 5 | The gntp.config module is provided as an extended GrowlNotifier object that takes 6 | advantage of the ConfigParser module to allow us to setup some default values 7 | (such as hostname, password, and port) in a more global way to be shared among 8 | programs using gntp 9 | """ 10 | import logging 11 | import os 12 | 13 | import gntp.notifier 14 | import gntp.shim 15 | 16 | __all__ = [ 17 | 'mini', 18 | 'GrowlNotifier' 19 | ] 20 | 21 | logger = logging.getLogger(__name__) 22 | 23 | 24 | class GrowlNotifier(gntp.notifier.GrowlNotifier): 25 | """ 26 | ConfigParser enhanced GrowlNotifier object 27 | 28 | For right now, we are only interested in letting users overide certain 29 | values from ~/.gntp 30 | 31 | :: 32 | 33 | [gntp] 34 | hostname = ? 35 | password = ? 36 | port = ? 37 | """ 38 | def __init__(self, *args, **kwargs): 39 | config = gntp.shim.RawConfigParser({ 40 | 'hostname': kwargs.get('hostname', 'localhost'), 41 | 'password': kwargs.get('password'), 42 | 'port': kwargs.get('port', 23053), 43 | }) 44 | 45 | config.read([os.path.expanduser('~/.gntp')]) 46 | 47 | # If the file does not exist, then there will be no gntp section defined 48 | # and the config.get() lines below will get confused. Since we are not 49 | # saving the config, it should be safe to just add it here so the 50 | # code below doesn't complain 51 | if not config.has_section('gntp'): 52 | logger.info('Error reading ~/.gntp config file') 53 | config.add_section('gntp') 54 | 55 | kwargs['password'] = config.get('gntp', 'password') 56 | kwargs['hostname'] = config.get('gntp', 'hostname') 57 | kwargs['port'] = config.getint('gntp', 'port') 58 | 59 | super(GrowlNotifier, self).__init__(*args, **kwargs) 60 | 61 | 62 | def mini(description, **kwargs): 63 | """Single notification function 64 | 65 | Simple notification function in one line. Has only one required parameter 66 | and attempts to use reasonable defaults for everything else 67 | :param string description: Notification message 68 | """ 69 | kwargs['notifierFactory'] = GrowlNotifier 70 | gntp.notifier.mini(description, **kwargs) 71 | 72 | 73 | if __name__ == '__main__': 74 | # If we're running this module directly we're likely running it as a test 75 | # so extra debugging is useful 76 | logging.basicConfig(level=logging.INFO) 77 | mini('Testing mini notification') 78 | -------------------------------------------------------------------------------- /gntp/core.py: -------------------------------------------------------------------------------- 1 | # Copyright: 2013 Paul Traylor 2 | # These sources are released under the terms of the MIT license: see LICENSE 3 | 4 | import hashlib 5 | import re 6 | import time 7 | 8 | import gntp.shim 9 | import gntp.errors as errors 10 | 11 | __all__ = [ 12 | 'GNTPRegister', 13 | 'GNTPNotice', 14 | 'GNTPSubscribe', 15 | 'GNTPOK', 16 | 'GNTPError', 17 | 'parse_gntp', 18 | ] 19 | 20 | #GNTP/ [:][ :.] 21 | GNTP_INFO_LINE = re.compile( 22 | 'GNTP/(?P\d+\.\d+) (?PREGISTER|NOTIFY|SUBSCRIBE|\-OK|\-ERROR)' + 23 | ' (?P[A-Z0-9]+(:(?P[A-F0-9]+))?) ?' + 24 | '((?P[A-Z0-9]+):(?P[A-F0-9]+).(?P[A-F0-9]+))?\r\n', 25 | re.IGNORECASE 26 | ) 27 | 28 | GNTP_INFO_LINE_SHORT = re.compile( 29 | 'GNTP/(?P\d+\.\d+) (?PREGISTER|NOTIFY|SUBSCRIBE|\-OK|\-ERROR)', 30 | re.IGNORECASE 31 | ) 32 | 33 | GNTP_HEADER = re.compile('([\w-]+):(.+)') 34 | 35 | GNTP_EOL = gntp.shim.b('\r\n') 36 | GNTP_SEP = gntp.shim.b(': ') 37 | 38 | 39 | class _GNTPBuffer(gntp.shim.StringIO): 40 | """GNTP Buffer class""" 41 | def writeln(self, value=None): 42 | if value: 43 | self.write(gntp.shim.b(value)) 44 | self.write(GNTP_EOL) 45 | 46 | def writeheader(self, key, value): 47 | if not isinstance(value, str): 48 | value = str(value) 49 | self.write(gntp.shim.b(key)) 50 | self.write(GNTP_SEP) 51 | self.write(gntp.shim.b(value)) 52 | self.write(GNTP_EOL) 53 | 54 | 55 | class _GNTPBase(object): 56 | """Base initilization 57 | 58 | :param string messagetype: GNTP Message type 59 | :param string version: GNTP Protocol version 60 | :param string encription: Encryption protocol 61 | """ 62 | def __init__(self, messagetype=None, version='1.0', encryption=None): 63 | self.info = { 64 | 'version': version, 65 | 'messagetype': messagetype, 66 | 'encryptionAlgorithmID': encryption 67 | } 68 | self.headers = {} 69 | self.resources = {} 70 | 71 | def __str__(self): 72 | return self.encode() 73 | 74 | def _parse_info(self, data): 75 | """Parse the first line of a GNTP message to get security and other info values 76 | 77 | :param string data: GNTP Message 78 | :return dict: Parsed GNTP Info line 79 | """ 80 | 81 | match = GNTP_INFO_LINE.match(data) 82 | 83 | if not match: 84 | raise errors.ParseError('ERROR_PARSING_INFO_LINE') 85 | 86 | info = match.groupdict() 87 | if info['encryptionAlgorithmID'] == 'NONE': 88 | info['encryptionAlgorithmID'] = None 89 | 90 | return info 91 | 92 | def set_password(self, password, encryptAlgo='MD5'): 93 | """Set a password for a GNTP Message 94 | 95 | :param string password: Null to clear password 96 | :param string encryptAlgo: Supports MD5, SHA1, SHA256, SHA512 97 | """ 98 | hash_algo = { 99 | 'MD5': hashlib.md5, 100 | 'SHA1': hashlib.sha1, 101 | 'SHA256': hashlib.sha256, 102 | 'SHA512': hashlib.sha512, 103 | } 104 | 105 | if not password: 106 | self.info['encryptionAlgorithmID'] = None 107 | self.info['keyHashAlgorithm'] = None 108 | return 109 | 110 | self.password = gntp.shim.b(password) 111 | self.encryptAlgo = encryptAlgo.upper() 112 | 113 | if not self.encryptAlgo in hash_algo: 114 | raise errors.UnsupportedError('INVALID HASH "%s"' % self.encryptAlgo) 115 | 116 | hashfunction = hash_algo.get(self.encryptAlgo) 117 | 118 | password = password.encode('utf8') 119 | seed = time.ctime().encode('utf8') 120 | salt = hashfunction(seed).hexdigest() 121 | saltHash = hashfunction(seed).digest() 122 | keyBasis = password + saltHash 123 | key = hashfunction(keyBasis).digest() 124 | keyHash = hashfunction(key).hexdigest() 125 | 126 | self.info['keyHashAlgorithmID'] = self.encryptAlgo 127 | self.info['keyHash'] = keyHash.upper() 128 | self.info['salt'] = salt.upper() 129 | 130 | def _decode_hex(self, value): 131 | """Helper function to decode hex string to `proper` hex string 132 | 133 | :param string value: Human readable hex string 134 | :return string: Hex string 135 | """ 136 | result = '' 137 | for i in range(0, len(value), 2): 138 | tmp = int(value[i:i + 2], 16) 139 | result += chr(tmp) 140 | return result 141 | 142 | def _decode_binary(self, rawIdentifier, identifier): 143 | rawIdentifier += '\r\n\r\n' 144 | dataLength = int(identifier['Length']) 145 | pointerStart = self.raw.find(rawIdentifier) + len(rawIdentifier) 146 | pointerEnd = pointerStart + dataLength 147 | data = self.raw[pointerStart:pointerEnd] 148 | if not len(data) == dataLength: 149 | raise errors.ParseError('INVALID_DATA_LENGTH Expected: %s Recieved %s' % (dataLength, len(data))) 150 | return data 151 | 152 | def _validate_password(self, password): 153 | """Validate GNTP Message against stored password""" 154 | self.password = password 155 | if password is None: 156 | raise errors.AuthError('Missing password') 157 | keyHash = self.info.get('keyHash', None) 158 | if keyHash is None and self.password is None: 159 | return True 160 | if keyHash is None: 161 | raise errors.AuthError('Invalid keyHash') 162 | if self.password is None: 163 | raise errors.AuthError('Missing password') 164 | 165 | password = self.password.encode('utf8') 166 | saltHash = self._decode_hex(self.info['salt']) 167 | 168 | keyBasis = password + saltHash 169 | key = hashlib.md5(keyBasis).digest() 170 | keyHash = hashlib.md5(key).hexdigest() 171 | 172 | if not keyHash.upper() == self.info['keyHash'].upper(): 173 | raise errors.AuthError('Invalid Hash') 174 | return True 175 | 176 | def validate(self): 177 | """Verify required headers""" 178 | for header in self._requiredHeaders: 179 | if not self.headers.get(header, False): 180 | raise errors.ParseError('Missing Notification Header: ' + header) 181 | 182 | def _format_info(self): 183 | """Generate info line for GNTP Message 184 | 185 | :return string: 186 | """ 187 | info = 'GNTP/%s %s' % ( 188 | self.info.get('version'), 189 | self.info.get('messagetype'), 190 | ) 191 | if self.info.get('encryptionAlgorithmID', None): 192 | info += ' %s:%s' % ( 193 | self.info.get('encryptionAlgorithmID'), 194 | self.info.get('ivValue'), 195 | ) 196 | else: 197 | info += ' NONE' 198 | 199 | if self.info.get('keyHashAlgorithmID', None): 200 | info += ' %s:%s.%s' % ( 201 | self.info.get('keyHashAlgorithmID'), 202 | self.info.get('keyHash'), 203 | self.info.get('salt') 204 | ) 205 | 206 | return info 207 | 208 | def _parse_dict(self, data): 209 | """Helper function to parse blocks of GNTP headers into a dictionary 210 | 211 | :param string data: 212 | :return dict: Dictionary of parsed GNTP Headers 213 | """ 214 | d = {} 215 | for line in data.split('\r\n'): 216 | match = GNTP_HEADER.match(line) 217 | if not match: 218 | continue 219 | 220 | key = match.group(1).strip() 221 | val = match.group(2).strip() 222 | d[key] = val 223 | return d 224 | 225 | def add_header(self, key, value): 226 | self.headers[key] = value 227 | 228 | def add_resource(self, data): 229 | """Add binary resource 230 | 231 | :param string data: Binary Data 232 | """ 233 | data = gntp.shim.b(data) 234 | identifier = hashlib.md5(data).hexdigest() 235 | self.resources[identifier] = data 236 | return 'x-growl-resource://%s' % identifier 237 | 238 | def decode(self, data, password=None): 239 | """Decode GNTP Message 240 | 241 | :param string data: 242 | """ 243 | self.password = password 244 | self.raw = gntp.shim.u(data) 245 | parts = self.raw.split('\r\n\r\n') 246 | self.info = self._parse_info(self.raw) 247 | self.headers = self._parse_dict(parts[0]) 248 | 249 | def encode(self): 250 | """Encode a generic GNTP Message 251 | 252 | :return string: GNTP Message ready to be sent. Returned as a byte string 253 | """ 254 | 255 | buff = _GNTPBuffer() 256 | 257 | buff.writeln(self._format_info()) 258 | 259 | #Headers 260 | for k, v in self.headers.items(): 261 | buff.writeheader(k, v) 262 | buff.writeln() 263 | 264 | #Resources 265 | for resource, data in self.resources.items(): 266 | buff.writeheader('Identifier', resource) 267 | buff.writeheader('Length', len(data)) 268 | buff.writeln() 269 | buff.write(data) 270 | buff.writeln() 271 | buff.writeln() 272 | 273 | return buff.getvalue() 274 | 275 | 276 | class GNTPRegister(_GNTPBase): 277 | """Represents a GNTP Registration Command 278 | 279 | :param string data: (Optional) See decode() 280 | :param string password: (Optional) Password to use while encoding/decoding messages 281 | """ 282 | _requiredHeaders = [ 283 | 'Application-Name', 284 | 'Notifications-Count' 285 | ] 286 | _requiredNotificationHeaders = ['Notification-Name'] 287 | 288 | def __init__(self, data=None, password=None): 289 | _GNTPBase.__init__(self, 'REGISTER') 290 | self.notifications = [] 291 | 292 | if data: 293 | self.decode(data, password) 294 | else: 295 | self.set_password(password) 296 | self.add_header('Application-Name', 'pygntp') 297 | self.add_header('Notifications-Count', 0) 298 | 299 | def validate(self): 300 | '''Validate required headers and validate notification headers''' 301 | for header in self._requiredHeaders: 302 | if not self.headers.get(header, False): 303 | raise errors.ParseError('Missing Registration Header: ' + header) 304 | for notice in self.notifications: 305 | for header in self._requiredNotificationHeaders: 306 | if not notice.get(header, False): 307 | raise errors.ParseError('Missing Notification Header: ' + header) 308 | 309 | def decode(self, data, password): 310 | """Decode existing GNTP Registration message 311 | 312 | :param string data: Message to decode 313 | """ 314 | self.raw = gntp.shim.u(data) 315 | parts = self.raw.split('\r\n\r\n') 316 | self.info = self._parse_info(self.raw) 317 | self._validate_password(password) 318 | self.headers = self._parse_dict(parts[0]) 319 | 320 | for i, part in enumerate(parts): 321 | if i == 0: 322 | continue # Skip Header 323 | if part.strip() == '': 324 | continue 325 | notice = self._parse_dict(part) 326 | if notice.get('Notification-Name', False): 327 | self.notifications.append(notice) 328 | elif notice.get('Identifier', False): 329 | notice['Data'] = self._decode_binary(part, notice) 330 | #open('register.png','wblol').write(notice['Data']) 331 | self.resources[notice.get('Identifier')] = notice 332 | 333 | def add_notification(self, name, enabled=True): 334 | """Add new Notification to Registration message 335 | 336 | :param string name: Notification Name 337 | :param boolean enabled: Enable this notification by default 338 | """ 339 | notice = {} 340 | notice['Notification-Name'] = name 341 | notice['Notification-Enabled'] = enabled 342 | 343 | self.notifications.append(notice) 344 | self.add_header('Notifications-Count', len(self.notifications)) 345 | 346 | def encode(self): 347 | """Encode a GNTP Registration Message 348 | 349 | :return string: Encoded GNTP Registration message. Returned as a byte string 350 | """ 351 | 352 | buff = _GNTPBuffer() 353 | 354 | buff.writeln(self._format_info()) 355 | 356 | #Headers 357 | for k, v in self.headers.items(): 358 | buff.writeheader(k, v) 359 | buff.writeln() 360 | 361 | #Notifications 362 | if len(self.notifications) > 0: 363 | for notice in self.notifications: 364 | for k, v in notice.items(): 365 | buff.writeheader(k, v) 366 | buff.writeln() 367 | 368 | #Resources 369 | for resource, data in self.resources.items(): 370 | buff.writeheader('Identifier', resource) 371 | buff.writeheader('Length', len(data)) 372 | buff.writeln() 373 | buff.write(data) 374 | buff.writeln() 375 | buff.writeln() 376 | 377 | return buff.getvalue() 378 | 379 | 380 | class GNTPNotice(_GNTPBase): 381 | """Represents a GNTP Notification Command 382 | 383 | :param string data: (Optional) See decode() 384 | :param string app: (Optional) Set Application-Name 385 | :param string name: (Optional) Set Notification-Name 386 | :param string title: (Optional) Set Notification Title 387 | :param string password: (Optional) Password to use while encoding/decoding messages 388 | """ 389 | _requiredHeaders = [ 390 | 'Application-Name', 391 | 'Notification-Name', 392 | 'Notification-Title' 393 | ] 394 | 395 | def __init__(self, data=None, app=None, name=None, title=None, password=None): 396 | _GNTPBase.__init__(self, 'NOTIFY') 397 | 398 | if data: 399 | self.decode(data, password) 400 | else: 401 | self.set_password(password) 402 | if app: 403 | self.add_header('Application-Name', app) 404 | if name: 405 | self.add_header('Notification-Name', name) 406 | if title: 407 | self.add_header('Notification-Title', title) 408 | 409 | def decode(self, data, password): 410 | """Decode existing GNTP Notification message 411 | 412 | :param string data: Message to decode. 413 | """ 414 | self.raw = gntp.shim.u(data) 415 | parts = self.raw.split('\r\n\r\n') 416 | self.info = self._parse_info(self.raw) 417 | self._validate_password(password) 418 | self.headers = self._parse_dict(parts[0]) 419 | 420 | for i, part in enumerate(parts): 421 | if i == 0: 422 | continue # Skip Header 423 | if part.strip() == '': 424 | continue 425 | notice = self._parse_dict(part) 426 | if notice.get('Identifier', False): 427 | notice['Data'] = self._decode_binary(part, notice) 428 | #open('notice.png','wblol').write(notice['Data']) 429 | self.resources[notice.get('Identifier')] = notice 430 | 431 | 432 | class GNTPSubscribe(_GNTPBase): 433 | """Represents a GNTP Subscribe Command 434 | 435 | :param string data: (Optional) See decode() 436 | :param string password: (Optional) Password to use while encoding/decoding messages 437 | """ 438 | _requiredHeaders = [ 439 | 'Subscriber-ID', 440 | 'Subscriber-Name', 441 | ] 442 | 443 | def __init__(self, data=None, password=None): 444 | _GNTPBase.__init__(self, 'SUBSCRIBE') 445 | if data: 446 | self.decode(data, password) 447 | else: 448 | self.set_password(password) 449 | 450 | 451 | class GNTPOK(_GNTPBase): 452 | """Represents a GNTP OK Response 453 | 454 | :param string data: (Optional) See _GNTPResponse.decode() 455 | :param string action: (Optional) Set type of action the OK Response is for 456 | """ 457 | _requiredHeaders = ['Response-Action'] 458 | 459 | def __init__(self, data=None, action=None): 460 | _GNTPBase.__init__(self, '-OK') 461 | if data: 462 | self.decode(data) 463 | if action: 464 | self.add_header('Response-Action', action) 465 | 466 | 467 | class GNTPError(_GNTPBase): 468 | """Represents a GNTP Error response 469 | 470 | :param string data: (Optional) See _GNTPResponse.decode() 471 | :param string errorcode: (Optional) Error code 472 | :param string errordesc: (Optional) Error Description 473 | """ 474 | _requiredHeaders = ['Error-Code', 'Error-Description'] 475 | 476 | def __init__(self, data=None, errorcode=None, errordesc=None): 477 | _GNTPBase.__init__(self, '-ERROR') 478 | if data: 479 | self.decode(data) 480 | if errorcode: 481 | self.add_header('Error-Code', errorcode) 482 | self.add_header('Error-Description', errordesc) 483 | 484 | def error(self): 485 | return (self.headers.get('Error-Code', None), 486 | self.headers.get('Error-Description', None)) 487 | 488 | 489 | def parse_gntp(data, password=None): 490 | """Attempt to parse a message as a GNTP message 491 | 492 | :param string data: Message to be parsed 493 | :param string password: Optional password to be used to verify the message 494 | """ 495 | data = gntp.shim.u(data) 496 | match = GNTP_INFO_LINE_SHORT.match(data) 497 | if not match: 498 | raise errors.ParseError('INVALID_GNTP_INFO') 499 | info = match.groupdict() 500 | if info['messagetype'] == 'REGISTER': 501 | return GNTPRegister(data, password=password) 502 | elif info['messagetype'] == 'NOTIFY': 503 | return GNTPNotice(data, password=password) 504 | elif info['messagetype'] == 'SUBSCRIBE': 505 | return GNTPSubscribe(data, password=password) 506 | elif info['messagetype'] == '-OK': 507 | return GNTPOK(data) 508 | elif info['messagetype'] == '-ERROR': 509 | return GNTPError(data) 510 | raise errors.ParseError('INVALID_GNTP_MESSAGE') 511 | -------------------------------------------------------------------------------- /gntp/errors.py: -------------------------------------------------------------------------------- 1 | # Copyright: 2013 Paul Traylor 2 | # These sources are released under the terms of the MIT license: see LICENSE 3 | 4 | class BaseError(Exception): 5 | pass 6 | 7 | 8 | class ParseError(BaseError): 9 | errorcode = 500 10 | errordesc = 'Error parsing the message' 11 | 12 | 13 | class AuthError(BaseError): 14 | errorcode = 400 15 | errordesc = 'Error with authorization' 16 | 17 | 18 | class UnsupportedError(BaseError): 19 | errorcode = 500 20 | errordesc = 'Currently unsupported by gntp.py' 21 | 22 | 23 | class NetworkError(BaseError): 24 | errorcode = 500 25 | errordesc = "Error connecting to growl server" 26 | -------------------------------------------------------------------------------- /gntp/notifier.py: -------------------------------------------------------------------------------- 1 | # Copyright: 2013 Paul Traylor 2 | # These sources are released under the terms of the MIT license: see LICENSE 3 | 4 | """ 5 | The gntp.notifier module is provided as a simple way to send notifications 6 | using GNTP 7 | 8 | .. note:: 9 | This class is intended to mostly mirror the older Python bindings such 10 | that you should be able to replace instances of the old bindings with 11 | this class. 12 | `Original Python bindings `_ 13 | 14 | """ 15 | import logging 16 | import platform 17 | import socket 18 | import sys 19 | 20 | from gntp.version import __version__ 21 | import gntp.core 22 | import gntp.errors as errors 23 | import gntp.shim 24 | 25 | __all__ = [ 26 | 'mini', 27 | 'GrowlNotifier', 28 | ] 29 | 30 | logger = logging.getLogger(__name__) 31 | 32 | 33 | class GrowlNotifier(object): 34 | """Helper class to simplfy sending Growl messages 35 | 36 | :param string applicationName: Sending application name 37 | :param list notification: List of valid notifications 38 | :param list defaultNotifications: List of notifications that should be enabled 39 | by default 40 | :param string applicationIcon: Icon URL 41 | :param string hostname: Remote host 42 | :param integer port: Remote port 43 | """ 44 | 45 | passwordHash = 'MD5' 46 | socketTimeout = 3 47 | 48 | def __init__(self, applicationName='Python GNTP', notifications=[], 49 | defaultNotifications=None, applicationIcon=None, hostname='localhost', 50 | password=None, port=23053): 51 | 52 | self.applicationName = applicationName 53 | self.notifications = list(notifications) 54 | if defaultNotifications: 55 | self.defaultNotifications = list(defaultNotifications) 56 | else: 57 | self.defaultNotifications = self.notifications 58 | self.applicationIcon = applicationIcon 59 | 60 | self.password = password 61 | self.hostname = hostname 62 | self.port = int(port) 63 | 64 | def _checkIcon(self, data): 65 | ''' 66 | Check the icon to see if it's valid 67 | 68 | If it's a simple URL icon, then we return True. If it's a data icon 69 | then we return False 70 | ''' 71 | logger.info('Checking icon') 72 | return gntp.shim.u(data).startswith('http') 73 | 74 | def register(self): 75 | """Send GNTP Registration 76 | 77 | .. warning:: 78 | Before sending notifications to Growl, you need to have 79 | sent a registration message at least once 80 | """ 81 | logger.info('Sending registration to %s:%s', self.hostname, self.port) 82 | register = gntp.core.GNTPRegister() 83 | register.add_header('Application-Name', self.applicationName) 84 | for notification in self.notifications: 85 | enabled = notification in self.defaultNotifications 86 | register.add_notification(notification, enabled) 87 | if self.applicationIcon: 88 | if self._checkIcon(self.applicationIcon): 89 | register.add_header('Application-Icon', self.applicationIcon) 90 | else: 91 | resource = register.add_resource(self.applicationIcon) 92 | register.add_header('Application-Icon', resource) 93 | if self.password: 94 | register.set_password(self.password, self.passwordHash) 95 | self.add_origin_info(register) 96 | self.register_hook(register) 97 | return self._send('register', register) 98 | 99 | def notify(self, noteType, title, description, icon=None, sticky=False, 100 | priority=None, callback=None, identifier=None, custom={}): 101 | """Send a GNTP notifications 102 | 103 | .. warning:: 104 | Must have registered with growl beforehand or messages will be ignored 105 | 106 | :param string noteType: One of the notification names registered earlier 107 | :param string title: Notification title (usually displayed on the notification) 108 | :param string description: The main content of the notification 109 | :param string icon: Icon URL path 110 | :param boolean sticky: Sticky notification 111 | :param integer priority: Message priority level from -2 to 2 112 | :param string callback: URL callback 113 | :param dict custom: Custom attributes. Key names should be prefixed with X- 114 | according to the spec but this is not enforced by this class 115 | 116 | .. warning:: 117 | For now, only URL callbacks are supported. In the future, the 118 | callback argument will also support a function 119 | """ 120 | logger.info('Sending notification [%s] to %s:%s', noteType, self.hostname, self.port) 121 | assert noteType in self.notifications 122 | notice = gntp.core.GNTPNotice() 123 | notice.add_header('Application-Name', self.applicationName) 124 | notice.add_header('Notification-Name', noteType) 125 | notice.add_header('Notification-Title', title) 126 | if self.password: 127 | notice.set_password(self.password, self.passwordHash) 128 | if sticky: 129 | notice.add_header('Notification-Sticky', sticky) 130 | if priority: 131 | notice.add_header('Notification-Priority', priority) 132 | if icon: 133 | if self._checkIcon(icon): 134 | notice.add_header('Notification-Icon', icon) 135 | else: 136 | resource = notice.add_resource(icon) 137 | notice.add_header('Notification-Icon', resource) 138 | 139 | if description: 140 | notice.add_header('Notification-Text', description) 141 | if callback: 142 | notice.add_header('Notification-Callback-Target', callback) 143 | if identifier: 144 | notice.add_header('Notification-Coalescing-ID', identifier) 145 | 146 | for key in custom: 147 | notice.add_header(key, custom[key]) 148 | 149 | self.add_origin_info(notice) 150 | self.notify_hook(notice) 151 | 152 | return self._send('notify', notice) 153 | 154 | def subscribe(self, id, name, port): 155 | """Send a Subscribe request to a remote machine""" 156 | sub = gntp.core.GNTPSubscribe() 157 | sub.add_header('Subscriber-ID', id) 158 | sub.add_header('Subscriber-Name', name) 159 | sub.add_header('Subscriber-Port', port) 160 | if self.password: 161 | sub.set_password(self.password, self.passwordHash) 162 | 163 | self.add_origin_info(sub) 164 | self.subscribe_hook(sub) 165 | 166 | return self._send('subscribe', sub) 167 | 168 | def add_origin_info(self, packet): 169 | """Add optional Origin headers to message""" 170 | packet.add_header('Origin-Machine-Name', platform.node()) 171 | packet.add_header('Origin-Software-Name', 'gntp.py') 172 | packet.add_header('Origin-Software-Version', __version__) 173 | packet.add_header('Origin-Platform-Name', platform.system()) 174 | packet.add_header('Origin-Platform-Version', platform.platform()) 175 | 176 | def register_hook(self, packet): 177 | pass 178 | 179 | def notify_hook(self, packet): 180 | pass 181 | 182 | def subscribe_hook(self, packet): 183 | pass 184 | 185 | def _send(self, messagetype, packet): 186 | """Send the GNTP Packet""" 187 | 188 | packet.validate() 189 | data = packet.encode() 190 | 191 | logger.debug('To : %s:%s <%s>\n%s', self.hostname, self.port, packet.__class__, data) 192 | 193 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 194 | s.settimeout(self.socketTimeout) 195 | try: 196 | s.connect((self.hostname, self.port)) 197 | s.send(data) 198 | recv_data = s.recv(1024) 199 | while not recv_data.endswith(gntp.shim.b("\r\n\r\n")): 200 | recv_data += s.recv(1024) 201 | except socket.error: 202 | # Python2.5 and Python3 compatibile exception 203 | exc = sys.exc_info()[1] 204 | raise errors.NetworkError(exc) 205 | 206 | response = gntp.core.parse_gntp(recv_data) 207 | s.close() 208 | 209 | logger.debug('From : %s:%s <%s>\n%s', self.hostname, self.port, response.__class__, response) 210 | 211 | if type(response) == gntp.core.GNTPOK: 212 | return True 213 | logger.error('Invalid response: %s', response.error()) 214 | return response.error() 215 | 216 | 217 | def mini(description, applicationName='PythonMini', noteType="Message", 218 | title="Mini Message", applicationIcon=None, hostname='localhost', 219 | password=None, port=23053, sticky=False, priority=None, 220 | callback=None, notificationIcon=None, identifier=None, 221 | notifierFactory=GrowlNotifier): 222 | """Single notification function 223 | 224 | Simple notification function in one line. Has only one required parameter 225 | and attempts to use reasonable defaults for everything else 226 | :param string description: Notification message 227 | 228 | .. warning:: 229 | For now, only URL callbacks are supported. In the future, the 230 | callback argument will also support a function 231 | """ 232 | try: 233 | growl = notifierFactory( 234 | applicationName=applicationName, 235 | notifications=[noteType], 236 | defaultNotifications=[noteType], 237 | applicationIcon=applicationIcon, 238 | hostname=hostname, 239 | password=password, 240 | port=port, 241 | ) 242 | result = growl.register() 243 | if result is not True: 244 | return result 245 | 246 | return growl.notify( 247 | noteType=noteType, 248 | title=title, 249 | description=description, 250 | icon=notificationIcon, 251 | sticky=sticky, 252 | priority=priority, 253 | callback=callback, 254 | identifier=identifier, 255 | ) 256 | except Exception: 257 | # We want the "mini" function to be simple and swallow Exceptions 258 | # in order to be less invasive 259 | logger.exception("Growl error") 260 | 261 | if __name__ == '__main__': 262 | # If we're running this module directly we're likely running it as a test 263 | # so extra debugging is useful 264 | logging.basicConfig(level=logging.INFO) 265 | mini('Testing mini notification') 266 | -------------------------------------------------------------------------------- /gntp/shim.py: -------------------------------------------------------------------------------- 1 | # Copyright: 2013 Paul Traylor 2 | # These sources are released under the terms of the MIT license: see LICENSE 3 | 4 | """ 5 | Python2.5 and Python3.3 compatibility shim 6 | 7 | Heavily inspirted by the "six" library. 8 | https://pypi.python.org/pypi/six 9 | """ 10 | 11 | import sys 12 | 13 | PY3 = sys.version_info[0] == 3 14 | 15 | if PY3: 16 | def b(s): 17 | if isinstance(s, bytes): 18 | return s 19 | return s.encode('utf8', 'replace') 20 | 21 | def u(s): 22 | if isinstance(s, bytes): 23 | return s.decode('utf8', 'replace') 24 | return s 25 | 26 | from io import BytesIO as StringIO 27 | from configparser import RawConfigParser 28 | else: 29 | def b(s): 30 | if isinstance(s, unicode): 31 | return s.encode('utf8', 'replace') 32 | return s 33 | 34 | def u(s): 35 | if isinstance(s, unicode): 36 | return s 37 | if isinstance(s, int): 38 | s = str(s) 39 | return unicode(s, "utf8", "replace") 40 | 41 | from StringIO import StringIO 42 | from ConfigParser import RawConfigParser 43 | 44 | b.__doc__ = "Ensure we have a byte string" 45 | u.__doc__ = "Ensure we have a unicode string" 46 | -------------------------------------------------------------------------------- /gntp/version.py: -------------------------------------------------------------------------------- 1 | # Copyright: 2013 Paul Traylor 2 | # These sources are released under the terms of the MIT license: see LICENSE 3 | 4 | __version__ = '1.0.1' 5 | -------------------------------------------------------------------------------- /inputs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apanly/piRobot/8f0788b95d0321811f4359560c3173a4b94ad181/inputs/__init__.py -------------------------------------------------------------------------------- /inputs/microphone.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import pyaudio 3 | import wave 4 | from array import array 5 | 6 | class microphone: 7 | 8 | def __init__(self,savepath,debug): 9 | self.savepath=savepath 10 | self.debug=debug 11 | self.sleepflag=0 12 | def getSleepFLag(self): 13 | return self.sleepflag 14 | def getRate(self): 15 | return 16000 16 | 17 | def silent(self,recorddata): 18 | return max(array('h', recorddata))=NUM_SILENT: 51 | self.debug.saytxt("* sleeping too long") 52 | break 53 | if counter>longtime: 54 | self.debug.saytxt("* record time out") 55 | break 56 | self.debug.saytxt("* done recording") 57 | stream.stop_stream() 58 | stream.close() 59 | p.terminate() 60 | wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb') 61 | wf.setnchannels(CHANNELS) 62 | wf.setsampwidth(p.get_sample_size(FORMAT)) 63 | wf.setframerate(RATE) 64 | wf.writeframes(b''.join(frames)) 65 | wf.close() -------------------------------------------------------------------------------- /install.md: -------------------------------------------------------------------------------- 1 | 安装注意的地方 2 | ================ 3 | * 安装pyaduio,遇到以下错误 4 | Searching for pyaudio 5 | Reading http://pypi.python.org/simple/pyaudio/ 6 | Reading http://people.csail.mit.edu/hubert/pyaudio/ 7 | Best match: pyaudio 0.2.7 8 | Downloading http://people.csail.mit.edu/hubert/pyaudio/packages/pyaudio-0.2.7.tar.gz 9 | Processing pyaudio-0.2.7.tar.gz 10 | Writing /var/folders/vg/98k5hfl52m16wm45ckdx1_5c0000gp/T/easy_install-s1wLkT/PyAudio-0.2.7/setup.cfg 11 | Running PyAudio-0.2.7/setup.py -q bdist_egg --dist-dir /var/folders/vg/98k5hfl52m16wm45ckdx1_5c0000gp/T/easy_install-s1wLkT/PyAudio-0.2.7/egg-dist-tmp-pFDrFR 12 | warning: no files found matching '*.c' under directory 'test' 13 | clang: warning: argument unused during compilation: '-mno-fused-madd' 14 | src/_portaudiomodule.c:29:10: fatal error: 'portaudio.h' file not found 15 | # include "portaudio.h" 16 | ^ 17 | 1 error generated. 18 | 19 | 20 | 请安装portaudio即可 21 | 22 | * 安装PIL,遇到以下错误(on max os) 23 | clang: warning: argument unused during compilation: '-mno-fused-madd' 24 | _imagingft.c:73:10: fatal error: 'freetype/fterrors.h' file not found 25 | # include 26 | ^ 27 | 1 error generated. 28 | 29 | 解决方案:ln -s /usr/local/include/freetype2 /usr/local/include/freetype -------------------------------------------------------------------------------- /jieba/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | import re 3 | import math 4 | import os,sys 5 | import pprint 6 | import finalseg 7 | import time 8 | import tempfile 9 | import marshal 10 | from math import log 11 | import random 12 | import threading 13 | from functools import wraps 14 | 15 | DICTIONARY = "dict.txt" 16 | DICT_LOCK = threading.RLock() 17 | trie = None # to be initialized 18 | FREQ = {} 19 | min_freq = 0.0 20 | total =0.0 21 | user_word_tag_tab={} 22 | initialized = False 23 | 24 | def gen_trie(f_name): 25 | lfreq = {} 26 | trie = {} 27 | ltotal = 0.0 28 | with open(f_name, 'rb') as f: 29 | lineno = 0 30 | for line in f.read().rstrip().decode('utf-8').split('\n'): 31 | lineno += 1 32 | try: 33 | word,freq,_ = line.split(' ') 34 | freq = float(freq) 35 | lfreq[word] = freq 36 | ltotal+=freq 37 | p = trie 38 | for c in word: 39 | if not c in p: 40 | p[c] ={} 41 | p = p[c] 42 | p['']='' #ending flag 43 | except ValueError, e: 44 | print >> sys.stderr, f_name, ' at line', lineno, line 45 | raise e 46 | return trie, lfreq,ltotal 47 | 48 | def initialize(*args): 49 | global trie, FREQ, total, min_freq, initialized 50 | if len(args)==0: 51 | dictionary = DICTIONARY 52 | else: 53 | dictionary = args[0] 54 | with DICT_LOCK: 55 | if initialized: 56 | return 57 | if trie: 58 | del trie 59 | trie = None 60 | _curpath=os.path.normpath( os.path.join( os.getcwd(), os.path.dirname(__file__) ) ) 61 | 62 | abs_path = os.path.join(_curpath,dictionary) 63 | print >> sys.stderr, "Building Trie..., from " + abs_path 64 | t1 = time.time() 65 | if abs_path == os.path.join(_curpath,"dict.txt"): #defautl dictionary 66 | cache_file = os.path.join(tempfile.gettempdir(),"jieba.cache") 67 | else: #customer dictionary 68 | cache_file = os.path.join(tempfile.gettempdir(),"jieba.user."+str(hash(abs_path))+".cache") 69 | 70 | load_from_cache_fail = True 71 | if os.path.exists(cache_file) and os.path.getmtime(cache_file)>os.path.getmtime(abs_path): 72 | #print >> sys.stderr, "loading model from cache " + cache_file 73 | try: 74 | trie,FREQ,total,min_freq = marshal.load(open(cache_file,'rb')) 75 | load_from_cache_fail = False 76 | except: 77 | load_from_cache_fail = True 78 | 79 | if load_from_cache_fail: 80 | trie,FREQ,total = gen_trie(abs_path) 81 | FREQ = dict([(k,log(float(v)/total)) for k,v in FREQ.iteritems()]) #normalize 82 | min_freq = min(FREQ.itervalues()) 83 | print >> sys.stderr, "dumping model to file cache " + cache_file 84 | try: 85 | tmp_suffix = "."+str(random.random()) 86 | with open(cache_file+tmp_suffix,'wb') as temp_cache_file: 87 | marshal.dump((trie,FREQ,total,min_freq),temp_cache_file) 88 | if os.name=='nt': 89 | import shutil 90 | replace_file = shutil.move 91 | else: 92 | replace_file = os.rename 93 | replace_file(cache_file+tmp_suffix,cache_file) 94 | except: 95 | print >> sys.stderr, "dump cache file failed." 96 | import traceback 97 | print >> sys.stderr, traceback.format_exc() 98 | 99 | initialized = True 100 | 101 | #print >> sys.stderr, "loading model cost ", time.time() - t1, "seconds." 102 | #print >> sys.stderr, "Trie has been built succesfully." 103 | 104 | 105 | def require_initialized(fn): 106 | global initialized,DICTIONARY 107 | 108 | @wraps(fn) 109 | def wrapped(*args, **kwargs): 110 | if initialized: 111 | return fn(*args, **kwargs) 112 | else: 113 | initialize(DICTIONARY) 114 | return fn(*args, **kwargs) 115 | return wrapped 116 | 117 | def __cut_all(sentence): 118 | dag = get_DAG(sentence) 119 | old_j = -1 120 | for k,L in dag.iteritems(): 121 | if len(L)==1 and k>old_j: 122 | yield sentence[k:L[0]+1] 123 | old_j = L[0] 124 | else: 125 | for j in L: 126 | if j>k: 127 | yield sentence[k:j+1] 128 | old_j = j 129 | 130 | 131 | def calc(sentence,DAG,idx,route): 132 | N = len(sentence) 133 | route[N] = (0.0,'') 134 | for idx in xrange(N-1,-1,-1): 135 | candidates = [ ( FREQ.get(sentence[idx:x+1],min_freq) + route[x+1][0],x ) for x in DAG[idx] ] 136 | route[idx] = max(candidates) 137 | 138 | @require_initialized 139 | def get_DAG(sentence): 140 | N = len(sentence) 141 | i,j=0,0 142 | p = trie 143 | DAG = {} 144 | while i=N: 154 | i+=1 155 | j=i 156 | p=trie 157 | else: 158 | p = trie 159 | i+=1 160 | j=i 161 | for i in xrange(len(sentence)): 162 | if not i in DAG: 163 | DAG[i] =[i] 164 | return DAG 165 | 166 | 167 | def __cut_DAG(sentence): 168 | DAG = get_DAG(sentence) 169 | route ={} 170 | calc(sentence,DAG,0,route=route) 171 | x = 0 172 | buf =u'' 173 | N = len(sentence) 174 | while x0: 181 | if len(buf)==1: 182 | yield buf 183 | buf=u'' 184 | else: 185 | if not (buf in FREQ): 186 | regognized = finalseg.cut(buf) 187 | for t in regognized: 188 | yield t 189 | else: 190 | for elem in buf: 191 | yield elem 192 | buf=u'' 193 | yield l_word 194 | x =y 195 | 196 | if len(buf)>0: 197 | if len(buf)==1: 198 | yield buf 199 | else: 200 | if not (buf in FREQ): 201 | regognized = finalseg.cut(buf) 202 | for t in regognized: 203 | yield t 204 | else: 205 | for elem in buf: 206 | yield elem 207 | 208 | def cut(sentence,cut_all=False): 209 | if not isinstance(sentence, unicode): 210 | try: 211 | sentence = sentence.decode('utf-8') 212 | except UnicodeDecodeError: 213 | sentence = sentence.decode('gbk','ignore') 214 | re_han, re_skip = re.compile(ur"([\u4E00-\u9FA5a-zA-Z0-9+#&\._]+)"), re.compile(ur"(\s+)") 215 | if cut_all: 216 | re_han, re_skip = re.compile(ur"([\u4E00-\u9FA5]+)"), re.compile(ur"[^a-zA-Z0-9+#\n]") 217 | blocks = re_han.split(sentence) 218 | cut_block = __cut_DAG 219 | if cut_all: 220 | cut_block = __cut_all 221 | for blk in blocks: 222 | if re_han.match(blk): 223 | #pprint.pprint(__cut_DAG(blk)) 224 | for word in cut_block(blk): 225 | yield word 226 | else: 227 | tmp = re_skip.split(blk) 228 | for x in tmp: 229 | if re_skip.match(x): 230 | yield x 231 | elif not cut_all: 232 | for xx in x: 233 | yield xx 234 | else: 235 | yield x 236 | 237 | def cut_for_search(sentence): 238 | words = cut(sentence) 239 | for w in words: 240 | if len(w)>2: 241 | for i in xrange(len(w)-1): 242 | gram2 = w[i:i+2] 243 | if gram2 in FREQ: 244 | yield gram2 245 | if len(w)>3: 246 | for i in xrange(len(w)-2): 247 | gram3 = w[i:i+3] 248 | if gram3 in FREQ: 249 | yield gram3 250 | yield w 251 | 252 | @require_initialized 253 | def load_userdict(f): 254 | global trie,total,FREQ 255 | if isinstance(f, (str, unicode)): 256 | f = open(f, 'rb') 257 | content = f.read().decode('utf-8') 258 | line_no = 0 259 | for line in content.split("\n"): 260 | line_no+=1 261 | if line.rstrip()=='': continue 262 | tup =line.split(" ") 263 | word,freq = tup[0],tup[1] 264 | if line_no==1: 265 | word = word.replace(u'\ufeff',u"") #remove bom flag if it exists 266 | if len(tup)==3: 267 | user_word_tag_tab[word]=tup[2].strip() 268 | freq = float(freq) 269 | FREQ[word] = log(freq / total) 270 | p = trie 271 | for c in word: 272 | if not c in p: 273 | p[c] ={} 274 | p = p[c] 275 | p['']='' #ending flag 276 | 277 | __ref_cut = cut 278 | __ref_cut_for_search = cut_for_search 279 | 280 | def __lcut(sentence): 281 | return list(__ref_cut(sentence,False)) 282 | def __lcut_all(sentence): 283 | return list(__ref_cut(sentence,True)) 284 | def __lcut_for_search(sentence): 285 | return list(__ref_cut_for_search(sentence)) 286 | 287 | @require_initialized 288 | def enable_parallel(processnum): 289 | global pool,cut,cut_for_search 290 | if os.name=='nt': 291 | raise Exception("jieba: parallel mode only supports posix system") 292 | 293 | from multiprocessing import Pool 294 | pool = Pool(processnum) 295 | 296 | def pcut(sentence,cut_all=False): 297 | parts = re.compile('([\r\n]+)').split(sentence) 298 | if cut_all: 299 | result = pool.map(__lcut_all,parts) 300 | else: 301 | result = pool.map(__lcut,parts) 302 | for r in result: 303 | for w in r: 304 | yield w 305 | 306 | def pcut_for_search(sentence): 307 | parts = re.compile('([\r\n]+)').split(sentence) 308 | result = pool.map(__lcut_for_search,parts) 309 | for r in result: 310 | for w in r: 311 | yield w 312 | 313 | cut = pcut 314 | cut_for_search = pcut_for_search 315 | 316 | def disable_parallel(): 317 | global pool,cut,cut_for_search 318 | if 'pool' in globals(): 319 | pool.close() 320 | pool = None 321 | cut = __ref_cut 322 | cut_for_search = __ref_cut_for_search 323 | 324 | def set_dictionary(dictionary_path): 325 | global initialized, DICTIONARY 326 | with DICT_LOCK: 327 | abs_path = os.path.normpath( os.path.join( os.getcwd(), dictionary_path ) ) 328 | if not os.path.exists(abs_path): 329 | raise Exception("jieba: path does not exists:" + abs_path) 330 | DICTIONARY = abs_path 331 | initialized = False 332 | 333 | def get_abs_path_dict(): 334 | _curpath=os.path.normpath( os.path.join( os.getcwd(), os.path.dirname(__file__) ) ) 335 | abs_path = os.path.join(_curpath,DICTIONARY) 336 | return abs_path 337 | 338 | def tokenize(unicode_sentence,mode="default"): 339 | #mode ("default" or "search") 340 | if not isinstance(unicode_sentence, unicode): 341 | raise Exception("jieba: the input parameter should unicode.") 342 | start = 0 343 | if mode=='default': 344 | for w in cut(unicode_sentence): 345 | width = len(w) 346 | yield (w,start,start+width) 347 | start+=width 348 | else: 349 | for w in cut(unicode_sentence): 350 | width = len(w) 351 | if len(w)>2: 352 | for i in xrange(len(w)-1): 353 | gram2 = w[i:i+2] 354 | if gram2 in FREQ: 355 | yield (gram2,start+i,start+i+2) 356 | if len(w)>3: 357 | for i in xrange(len(w)-2): 358 | gram3 = w[i:i+3] 359 | if gram3 in FREQ: 360 | yield (gram3,start+i,start+i+3) 361 | yield (w,start,start+width) 362 | start+=width 363 | 364 | -------------------------------------------------------------------------------- /jieba/analyse/__init__.py: -------------------------------------------------------------------------------- 1 | import jieba 2 | import os 3 | from analyzer import ChineseAnalyzer 4 | 5 | _curpath=os.path.normpath( os.path.join( os.getcwd(), os.path.dirname(__file__) ) ) 6 | f_name = os.path.join(_curpath,"idf.txt") 7 | content = open(f_name,'rb').read().decode('utf-8') 8 | 9 | idf_freq = {} 10 | lines = content.split('\n') 11 | for line in lines: 12 | word,freq = line.split(' ') 13 | idf_freq[word] = float(freq) 14 | 15 | median_idf = sorted(idf_freq.values())[len(idf_freq)/2] 16 | stop_words= set([ 17 | "the","of","is","and","to","in","that","we","for","an","are","by","be","as","on","with","can","if","from","which","you","it","this","then","at","have","all","not","one","has","or","that" 18 | ]) 19 | 20 | def extract_tags(sentence,topK=20): 21 | words = jieba.cut(sentence) 22 | freq = {} 23 | for w in words: 24 | if len(w.strip())<2: continue 25 | if w.lower() in stop_words: continue 26 | freq[w]=freq.get(w,0.0)+1.0 27 | total = sum(freq.values()) 28 | freq = [(k,v/total) for k,v in freq.iteritems()] 29 | 30 | tf_idf_list = [(v * idf_freq.get(k,median_idf),k) for k,v in freq] 31 | st_list = sorted(tf_idf_list,reverse=True) 32 | 33 | top_tuples= st_list[:topK] 34 | tags = [a[1] for a in top_tuples] 35 | return tags 36 | -------------------------------------------------------------------------------- /jieba/analyse/analyzer.py: -------------------------------------------------------------------------------- 1 | #encoding=utf-8 2 | from whoosh.analysis import RegexAnalyzer,LowercaseFilter,StopFilter 3 | from whoosh.analysis import Tokenizer,Token 4 | 5 | import jieba 6 | import re 7 | 8 | STOP_WORDS = frozenset(('a', 'an', 'and', 'are', 'as', 'at', 'be', 'by', 'can', 9 | 'for', 'from', 'have', 'if', 'in', 'is', 'it', 'may', 10 | 'not', 'of', 'on', 'or', 'tbd', 'that', 'the', 'this', 11 | 'to', 'us', 'we', 'when', 'will', 'with', 'yet', 12 | 'you', 'your',u'的',u'了',u'和')) 13 | 14 | accepted_chars = re.compile(ur"[\u4E00-\u9FA5]+") 15 | 16 | class ChineseTokenizer(Tokenizer): 17 | def __call__(self,text,**kargs): 18 | words = jieba.tokenize(text,mode="search") 19 | token = Token() 20 | for (w,start_pos,stop_pos) in words: 21 | if not accepted_chars.match(w): 22 | if len(w)>1: 23 | pass 24 | else: 25 | continue 26 | token.text = w 27 | token.pos = start_pos 28 | token.startchar = start_pos 29 | token.endchar = stop_pos 30 | yield token 31 | 32 | def ChineseAnalyzer(stoplist=STOP_WORDS,minsize=1): 33 | return ChineseTokenizer() | LowercaseFilter() | StopFilter(stoplist=stoplist,minsize=minsize) -------------------------------------------------------------------------------- /jieba/finalseg/__init__.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | from math import log 4 | import prob_start 5 | import prob_trans 6 | import prob_emit 7 | 8 | MIN_FLOAT=-3.14e100 9 | 10 | PrevStatus = { 11 | 'B':('E','S'), 12 | 'M':('M','B'), 13 | 'S':('S','E'), 14 | 'E':('B','M') 15 | } 16 | 17 | def viterbi(obs, states, start_p, trans_p, emit_p): 18 | V = [{}] #tabular 19 | path = {} 20 | for y in states: #init 21 | V[0][y] = start_p[y] + emit_p[y].get(obs[0],MIN_FLOAT) 22 | path[y] = [y] 23 | for t in range(1,len(obs)): 24 | V.append({}) 25 | newpath = {} 26 | for y in states: 27 | em_p = emit_p[y].get(obs[t],MIN_FLOAT) 28 | (prob,state ) = max([(V[t-1][y0] + trans_p[y0].get(y,MIN_FLOAT) + em_p ,y0) for y0 in PrevStatus[y] ]) 29 | V[t][y] =prob 30 | newpath[y] = path[state] + [y] 31 | path = newpath 32 | 33 | (prob, state) = max([(V[len(obs) - 1][y], y) for y in ('E','S')]) 34 | 35 | return (prob, path[state]) 36 | 37 | 38 | def __cut(sentence): 39 | prob, pos_list = viterbi(sentence,('B','M','E','S'), prob_start.P, prob_trans.P, prob_emit.P) 40 | begin, next = 0,0 41 | #print pos_list, sentence 42 | for i,char in enumerate(sentence): 43 | pos = pos_list[i] 44 | if pos=='B': 45 | begin = i 46 | elif pos=='E': 47 | yield sentence[begin:i+1] 48 | next = i+1 49 | elif pos=='S': 50 | yield char 51 | next = i+1 52 | if next0: 101 | if len(buf)==1: 102 | yield pair(buf,word_tag_tab.get(buf,'x')) 103 | buf=u'' 104 | else: 105 | if not (buf in jieba.FREQ): 106 | regognized = __cut_detail(buf) 107 | for t in regognized: 108 | yield t 109 | else: 110 | for elem in buf: 111 | yield pair(elem,word_tag_tab.get(elem,'x')) 112 | buf=u'' 113 | yield pair(l_word,word_tag_tab.get(l_word,'x')) 114 | x =y 115 | 116 | if len(buf)>0: 117 | if len(buf)==1: 118 | yield pair(buf,word_tag_tab.get(buf,'x')) 119 | else: 120 | if not (buf in jieba.FREQ): 121 | regognized = __cut_detail(buf) 122 | for t in regognized: 123 | yield t 124 | else: 125 | for elem in buf: 126 | yield pair(elem,word_tag_tab.get(elem,'x')) 127 | 128 | def __cut_internal(sentence): 129 | if not ( type(sentence) is unicode): 130 | try: 131 | sentence = sentence.decode('utf-8') 132 | except: 133 | sentence = sentence.decode('gbk','ignore') 134 | re_han, re_skip = re.compile(ur"([\u4E00-\u9FA5a-zA-Z0-9+#&\._]+)"), re.compile(ur"(\s+)") 135 | re_eng,re_num = re.compile(ur"[a-zA-Z0-9]+"), re.compile(ur"[\.0-9]+") 136 | blocks = re_han.split(sentence) 137 | for blk in blocks: 138 | if re_han.match(blk): 139 | for word in __cut_DAG(blk): 140 | yield word 141 | else: 142 | tmp = re_skip.split(blk) 143 | for x in tmp: 144 | if re_skip.match(x): 145 | yield pair(x,'x') 146 | else: 147 | for xx in x: 148 | if re_num.match(xx): 149 | yield pair(xx,'m') 150 | elif re_eng.match(x): 151 | yield pair(xx,'eng') 152 | else: 153 | yield pair(xx,'x') 154 | 155 | def __lcut_internal(sentence): 156 | return list(__cut_internal(sentence)) 157 | 158 | def cut(sentence): 159 | if (not hasattr(jieba,'pool')) or (jieba.pool==None): 160 | for w in __cut_internal(sentence): 161 | yield w 162 | else: 163 | parts = re.compile('([\r\n]+)').split(sentence) 164 | result = jieba.pool.map(__lcut_internal,parts) 165 | for r in result: 166 | for w in r: 167 | yield w 168 | 169 | -------------------------------------------------------------------------------- /jieba/posseg/prob_start.py: -------------------------------------------------------------------------------- 1 | P={('B', 'a'): -4.762305214596967, 2 | ('B', 'ad'): -6.680066036784177, 3 | ('B', 'ag'): -3.14e+100, 4 | ('B', 'an'): -8.697083223018778, 5 | ('B', 'b'): -5.018374362109218, 6 | ('B', 'bg'): -3.14e+100, 7 | ('B', 'c'): -3.423880184954888, 8 | ('B', 'd'): -3.9750475297585357, 9 | ('B', 'df'): -8.888974230828882, 10 | ('B', 'dg'): -3.14e+100, 11 | ('B', 'e'): -8.563551830394255, 12 | ('B', 'en'): -3.14e+100, 13 | ('B', 'f'): -5.491630418482717, 14 | ('B', 'g'): -3.14e+100, 15 | ('B', 'h'): -13.533365129970255, 16 | ('B', 'i'): -6.1157847275557105, 17 | ('B', 'in'): -3.14e+100, 18 | ('B', 'j'): -5.0576191284681915, 19 | ('B', 'jn'): -3.14e+100, 20 | ('B', 'k'): -3.14e+100, 21 | ('B', 'l'): -4.905883584659895, 22 | ('B', 'ln'): -3.14e+100, 23 | ('B', 'm'): -3.6524299819046386, 24 | ('B', 'mg'): -3.14e+100, 25 | ('B', 'mq'): -6.78695300139688, 26 | ('B', 'n'): -1.6966257797548328, 27 | ('B', 'ng'): -3.14e+100, 28 | ('B', 'nr'): -2.2310495913769506, 29 | ('B', 'nrfg'): -5.873722175405573, 30 | ('B', 'nrt'): -4.985642733519195, 31 | ('B', 'ns'): -2.8228438314969213, 32 | ('B', 'nt'): -4.846091668182416, 33 | ('B', 'nz'): -3.94698846057672, 34 | ('B', 'o'): -8.433498702146057, 35 | ('B', 'p'): -4.200984132085048, 36 | ('B', 'q'): -6.998123858956596, 37 | ('B', 'qe'): -3.14e+100, 38 | ('B', 'qg'): -3.14e+100, 39 | ('B', 'r'): -3.4098187790818413, 40 | ('B', 'rg'): -3.14e+100, 41 | ('B', 'rr'): -12.434752841302146, 42 | ('B', 'rz'): -7.946116471570005, 43 | ('B', 's'): -5.522673590839954, 44 | ('B', 't'): -3.3647479094528574, 45 | ('B', 'tg'): -3.14e+100, 46 | ('B', 'u'): -9.163917277503234, 47 | ('B', 'ud'): -3.14e+100, 48 | ('B', 'ug'): -3.14e+100, 49 | ('B', 'uj'): -3.14e+100, 50 | ('B', 'ul'): -3.14e+100, 51 | ('B', 'uv'): -3.14e+100, 52 | ('B', 'uz'): -3.14e+100, 53 | ('B', 'v'): -2.6740584874265685, 54 | ('B', 'vd'): -9.044728760238115, 55 | ('B', 'vg'): -3.14e+100, 56 | ('B', 'vi'): -12.434752841302146, 57 | ('B', 'vn'): -4.3315610890163585, 58 | ('B', 'vq'): -12.147070768850364, 59 | ('B', 'w'): -3.14e+100, 60 | ('B', 'x'): -3.14e+100, 61 | ('B', 'y'): -9.844485675856319, 62 | ('B', 'yg'): -3.14e+100, 63 | ('B', 'z'): -7.045681111485645, 64 | ('B', 'zg'): -3.14e+100, 65 | ('E', 'a'): -3.14e+100, 66 | ('E', 'ad'): -3.14e+100, 67 | ('E', 'ag'): -3.14e+100, 68 | ('E', 'an'): -3.14e+100, 69 | ('E', 'b'): -3.14e+100, 70 | ('E', 'bg'): -3.14e+100, 71 | ('E', 'c'): -3.14e+100, 72 | ('E', 'd'): -3.14e+100, 73 | ('E', 'df'): -3.14e+100, 74 | ('E', 'dg'): -3.14e+100, 75 | ('E', 'e'): -3.14e+100, 76 | ('E', 'en'): -3.14e+100, 77 | ('E', 'f'): -3.14e+100, 78 | ('E', 'g'): -3.14e+100, 79 | ('E', 'h'): -3.14e+100, 80 | ('E', 'i'): -3.14e+100, 81 | ('E', 'in'): -3.14e+100, 82 | ('E', 'j'): -3.14e+100, 83 | ('E', 'jn'): -3.14e+100, 84 | ('E', 'k'): -3.14e+100, 85 | ('E', 'l'): -3.14e+100, 86 | ('E', 'ln'): -3.14e+100, 87 | ('E', 'm'): -3.14e+100, 88 | ('E', 'mg'): -3.14e+100, 89 | ('E', 'mq'): -3.14e+100, 90 | ('E', 'n'): -3.14e+100, 91 | ('E', 'ng'): -3.14e+100, 92 | ('E', 'nr'): -3.14e+100, 93 | ('E', 'nrfg'): -3.14e+100, 94 | ('E', 'nrt'): -3.14e+100, 95 | ('E', 'ns'): -3.14e+100, 96 | ('E', 'nt'): -3.14e+100, 97 | ('E', 'nz'): -3.14e+100, 98 | ('E', 'o'): -3.14e+100, 99 | ('E', 'p'): -3.14e+100, 100 | ('E', 'q'): -3.14e+100, 101 | ('E', 'qe'): -3.14e+100, 102 | ('E', 'qg'): -3.14e+100, 103 | ('E', 'r'): -3.14e+100, 104 | ('E', 'rg'): -3.14e+100, 105 | ('E', 'rr'): -3.14e+100, 106 | ('E', 'rz'): -3.14e+100, 107 | ('E', 's'): -3.14e+100, 108 | ('E', 't'): -3.14e+100, 109 | ('E', 'tg'): -3.14e+100, 110 | ('E', 'u'): -3.14e+100, 111 | ('E', 'ud'): -3.14e+100, 112 | ('E', 'ug'): -3.14e+100, 113 | ('E', 'uj'): -3.14e+100, 114 | ('E', 'ul'): -3.14e+100, 115 | ('E', 'uv'): -3.14e+100, 116 | ('E', 'uz'): -3.14e+100, 117 | ('E', 'v'): -3.14e+100, 118 | ('E', 'vd'): -3.14e+100, 119 | ('E', 'vg'): -3.14e+100, 120 | ('E', 'vi'): -3.14e+100, 121 | ('E', 'vn'): -3.14e+100, 122 | ('E', 'vq'): -3.14e+100, 123 | ('E', 'w'): -3.14e+100, 124 | ('E', 'x'): -3.14e+100, 125 | ('E', 'y'): -3.14e+100, 126 | ('E', 'yg'): -3.14e+100, 127 | ('E', 'z'): -3.14e+100, 128 | ('E', 'zg'): -3.14e+100, 129 | ('M', 'a'): -3.14e+100, 130 | ('M', 'ad'): -3.14e+100, 131 | ('M', 'ag'): -3.14e+100, 132 | ('M', 'an'): -3.14e+100, 133 | ('M', 'b'): -3.14e+100, 134 | ('M', 'bg'): -3.14e+100, 135 | ('M', 'c'): -3.14e+100, 136 | ('M', 'd'): -3.14e+100, 137 | ('M', 'df'): -3.14e+100, 138 | ('M', 'dg'): -3.14e+100, 139 | ('M', 'e'): -3.14e+100, 140 | ('M', 'en'): -3.14e+100, 141 | ('M', 'f'): -3.14e+100, 142 | ('M', 'g'): -3.14e+100, 143 | ('M', 'h'): -3.14e+100, 144 | ('M', 'i'): -3.14e+100, 145 | ('M', 'in'): -3.14e+100, 146 | ('M', 'j'): -3.14e+100, 147 | ('M', 'jn'): -3.14e+100, 148 | ('M', 'k'): -3.14e+100, 149 | ('M', 'l'): -3.14e+100, 150 | ('M', 'ln'): -3.14e+100, 151 | ('M', 'm'): -3.14e+100, 152 | ('M', 'mg'): -3.14e+100, 153 | ('M', 'mq'): -3.14e+100, 154 | ('M', 'n'): -3.14e+100, 155 | ('M', 'ng'): -3.14e+100, 156 | ('M', 'nr'): -3.14e+100, 157 | ('M', 'nrfg'): -3.14e+100, 158 | ('M', 'nrt'): -3.14e+100, 159 | ('M', 'ns'): -3.14e+100, 160 | ('M', 'nt'): -3.14e+100, 161 | ('M', 'nz'): -3.14e+100, 162 | ('M', 'o'): -3.14e+100, 163 | ('M', 'p'): -3.14e+100, 164 | ('M', 'q'): -3.14e+100, 165 | ('M', 'qe'): -3.14e+100, 166 | ('M', 'qg'): -3.14e+100, 167 | ('M', 'r'): -3.14e+100, 168 | ('M', 'rg'): -3.14e+100, 169 | ('M', 'rr'): -3.14e+100, 170 | ('M', 'rz'): -3.14e+100, 171 | ('M', 's'): -3.14e+100, 172 | ('M', 't'): -3.14e+100, 173 | ('M', 'tg'): -3.14e+100, 174 | ('M', 'u'): -3.14e+100, 175 | ('M', 'ud'): -3.14e+100, 176 | ('M', 'ug'): -3.14e+100, 177 | ('M', 'uj'): -3.14e+100, 178 | ('M', 'ul'): -3.14e+100, 179 | ('M', 'uv'): -3.14e+100, 180 | ('M', 'uz'): -3.14e+100, 181 | ('M', 'v'): -3.14e+100, 182 | ('M', 'vd'): -3.14e+100, 183 | ('M', 'vg'): -3.14e+100, 184 | ('M', 'vi'): -3.14e+100, 185 | ('M', 'vn'): -3.14e+100, 186 | ('M', 'vq'): -3.14e+100, 187 | ('M', 'w'): -3.14e+100, 188 | ('M', 'x'): -3.14e+100, 189 | ('M', 'y'): -3.14e+100, 190 | ('M', 'yg'): -3.14e+100, 191 | ('M', 'z'): -3.14e+100, 192 | ('M', 'zg'): -3.14e+100, 193 | ('S', 'a'): -3.9025396831295227, 194 | ('S', 'ad'): -11.048458480182255, 195 | ('S', 'ag'): -6.954113917960154, 196 | ('S', 'an'): -12.84021794941031, 197 | ('S', 'b'): -6.472888763970454, 198 | ('S', 'bg'): -3.14e+100, 199 | ('S', 'c'): -4.786966795861212, 200 | ('S', 'd'): -3.903919764181873, 201 | ('S', 'df'): -3.14e+100, 202 | ('S', 'dg'): -8.948397651299683, 203 | ('S', 'e'): -5.942513006281674, 204 | ('S', 'en'): -3.14e+100, 205 | ('S', 'f'): -5.194820249981676, 206 | ('S', 'g'): -6.507826815331734, 207 | ('S', 'h'): -8.650563207383884, 208 | ('S', 'i'): -3.14e+100, 209 | ('S', 'in'): -3.14e+100, 210 | ('S', 'j'): -4.911992119644354, 211 | ('S', 'jn'): -3.14e+100, 212 | ('S', 'k'): -6.940320595827818, 213 | ('S', 'l'): -3.14e+100, 214 | ('S', 'ln'): -3.14e+100, 215 | ('S', 'm'): -3.269200652116097, 216 | ('S', 'mg'): -10.825314928868044, 217 | ('S', 'mq'): -3.14e+100, 218 | ('S', 'n'): -3.8551483897645107, 219 | ('S', 'ng'): -4.913434861102905, 220 | ('S', 'nr'): -4.483663103956885, 221 | ('S', 'nrfg'): -3.14e+100, 222 | ('S', 'nrt'): -3.14e+100, 223 | ('S', 'ns'): -3.14e+100, 224 | ('S', 'nt'): -12.147070768850364, 225 | ('S', 'nz'): -3.14e+100, 226 | ('S', 'o'): -8.464460927750023, 227 | ('S', 'p'): -2.9868401813596317, 228 | ('S', 'q'): -4.888658618255058, 229 | ('S', 'qe'): -3.14e+100, 230 | ('S', 'qg'): -3.14e+100, 231 | ('S', 'r'): -2.7635336784127853, 232 | ('S', 'rg'): -10.275268591948773, 233 | ('S', 'rr'): -3.14e+100, 234 | ('S', 'rz'): -3.14e+100, 235 | ('S', 's'): -3.14e+100, 236 | ('S', 't'): -3.14e+100, 237 | ('S', 'tg'): -6.272842531880403, 238 | ('S', 'u'): -6.940320595827818, 239 | ('S', 'ud'): -7.728230161053767, 240 | ('S', 'ug'): -7.5394037026636855, 241 | ('S', 'uj'): -6.85251045118004, 242 | ('S', 'ul'): -8.4153713175535, 243 | ('S', 'uv'): -8.15808672228609, 244 | ('S', 'uz'): -9.299258625372996, 245 | ('S', 'v'): -3.053292303412302, 246 | ('S', 'vd'): -3.14e+100, 247 | ('S', 'vg'): -5.9430181843676895, 248 | ('S', 'vi'): -3.14e+100, 249 | ('S', 'vn'): -11.453923588290419, 250 | ('S', 'vq'): -3.14e+100, 251 | ('S', 'w'): -3.14e+100, 252 | ('S', 'x'): -8.427419656069674, 253 | ('S', 'y'): -6.1970794699489575, 254 | ('S', 'yg'): -13.533365129970255, 255 | ('S', 'z'): -3.14e+100, 256 | ('S', 'zg'): -3.14e+100} 257 | -------------------------------------------------------------------------------- /jieba/posseg/viterbi.py: -------------------------------------------------------------------------------- 1 | import operator 2 | MIN_FLOAT=-3.14e100 3 | 4 | def get_top_states(t_state_v,K=4): 5 | items = t_state_v.items() 6 | topK= sorted(items,key=operator.itemgetter(1),reverse=True)[:K] 7 | return [x[0] for x in topK] 8 | 9 | def viterbi(obs, states, start_p, trans_p, emit_p): 10 | V = [{}] #tabular 11 | mem_path = [{}] 12 | all_states = trans_p.keys() 13 | for y in states.get(obs[0],all_states): #init 14 | V[0][y] = start_p[y] + emit_p[y].get(obs[0],MIN_FLOAT) 15 | mem_path[0][y] = '' 16 | for t in range(1,len(obs)): 17 | V.append({}) 18 | mem_path.append({}) 19 | prev_states = get_top_states(V[t-1]) 20 | prev_states =[ x for x in mem_path[t-1].keys() if len(trans_p[x])>0 ] 21 | 22 | prev_states_expect_next = set( (y for x in prev_states for y in trans_p[x].keys() ) ) 23 | obs_states = states.get(obs[t],all_states) 24 | obs_states = set(obs_states) & set(prev_states_expect_next) 25 | 26 | if len(obs_states)==0: obs_states = all_states 27 | for y in obs_states: 28 | (prob,state ) = max([(V[t-1][y0] + trans_p[y0].get(y,MIN_FLOAT) + emit_p[y].get(obs[t],MIN_FLOAT) ,y0) for y0 in prev_states]) 29 | V[t][y] =prob 30 | mem_path[t][y] = state 31 | 32 | last = [(V[-1][y], y) for y in mem_path[-1].keys() ] 33 | #if len(last)==0: 34 | #print obs 35 | (prob, state) = max(last) 36 | 37 | route = [None] * len(obs) 38 | i = len(obs)-1 39 | while i>=0: 40 | route[i] = state 41 | state = mem_path[i][state] 42 | i-=1 43 | return (prob, route) -------------------------------------------------------------------------------- /outputs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apanly/piRobot/8f0788b95d0321811f4359560c3173a4b94ad181/outputs/__init__.py -------------------------------------------------------------------------------- /outputs/debug.py: -------------------------------------------------------------------------------- 1 | 2 | class debug: 3 | 4 | def saytxt(self,txt): 5 | print txt -------------------------------------------------------------------------------- /outputs/speaker.py: -------------------------------------------------------------------------------- 1 | import pyaudio 2 | import wave 3 | import sys 4 | 5 | class aerophone: 6 | def __init__(self,filepath): 7 | self.filepath=filepath 8 | 9 | 10 | def play(self): 11 | 12 | print("* speaker") 13 | 14 | CHUNK = 1024 15 | 16 | wf = wave.open(self.filepath, 'rb') 17 | 18 | p = pyaudio.PyAudio() 19 | 20 | stream = p.open(format=p.get_format_from_width(wf.getsampwidth()), 21 | channels=wf.getnchannels(), 22 | rate=wf.getframerate(), 23 | output=True) 24 | 25 | data = wf.readframes(CHUNK) 26 | 27 | while data != '': 28 | stream.write(data) 29 | data = wf.readframes(CHUNK) 30 | 31 | stream.stop_stream() 32 | stream.close() 33 | 34 | p.terminate() -------------------------------------------------------------------------------- /proofread/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apanly/piRobot/8f0788b95d0321811f4359560c3173a4b94ad181/proofread/__init__.py -------------------------------------------------------------------------------- /proofread/javaproofread.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #!/usr/bin/python 3 | #coding=utf-8 4 | import socket 5 | 6 | class proofreadClient(): 7 | def __init__(self,txt): 8 | self.txt=txt 9 | def do(self): 10 | txt=self.txt 11 | address = ('127.0.0.1', 10000) 12 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 13 | s.settimeout(10) 14 | try: 15 | s.connect(address) 16 | s.send("%s\r"%txt) 17 | result=s.recv(1024) 18 | if result=="correct": 19 | return txt 20 | else: 21 | return result 22 | except IOError: 23 | return txt 24 | 25 | 26 | # target=proofreadClient("上一页") 27 | # print target.do() -------------------------------------------------------------------------------- /seg/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apanly/piRobot/8f0788b95d0321811f4359560c3173a4b94ad181/seg/__init__.py -------------------------------------------------------------------------------- /seg/jiebaseg.py: -------------------------------------------------------------------------------- 1 | import jieba 2 | 3 | class jiebaseg: 4 | def __init__(self,txt): 5 | self.txt=txt 6 | 7 | def cut(self,mode=False): 8 | return jieba.cut(self.txt,cut_all=mode) 9 | 10 | -------------------------------------------------------------------------------- /startup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | import sys 4 | import os 5 | import atexit 6 | import signal 7 | from ex.exception import NotUnderstoodException 8 | from outputs.debug import debug 9 | from thd.Consumer import Consumer 10 | from thd.Producer import Producer 11 | from customnotify.gntpnotify import gntpnotify 12 | from gl import PIDLOG 13 | global PIDLOG 14 | def Usage(): 15 | helpContent='Usage: python startup.py start/stop/restart' 16 | print helpContent 17 | exit() 18 | #为了防止并发加入PID文件 19 | def writePID(): 20 | fd=os.open(PIDLOG,os.O_CREAT|os.O_EXCL|os.O_RDWR) 21 | os.write(fd,"%s" % str(os.getpid())) 22 | os.close(fd) 23 | #删除本应用程序的PID 24 | def deletePID(): 25 | try: 26 | os.remove(PIDLOG) 27 | except: 28 | pass 29 | 30 | def main(): 31 | atexit.register(lambda:deletePID()) 32 | writePID() 33 | debugInit=debug() 34 | ''' 35 | 如果用户是桌面系统,就可以提示用户信息 36 | ''' 37 | #gntptarget=gntpnotify() 38 | #gntptarget.ownnotify() 39 | #test 40 | ''' 41 | 目前准备是1个生产者 5个消费者的模型 42 | ''' 43 | try: 44 | tList = [] 45 | producerTarget=Producer(debugInit) 46 | tList.append(producerTarget) 47 | for i in range(5): 48 | consumerTarget=Consumer(debugInit) 49 | tList.append(consumerTarget) 50 | for t in tList: 51 | t.setDaemon(1) 52 | t.start() 53 | except NotUnderstoodException: 54 | debugInit.saytxt("error") 55 | #Controller+C也可以终止程序 56 | while True: 57 | try: 58 | sys.stdin.read() 59 | except KeyboardInterrupt: 60 | stop() 61 | 62 | 63 | def stop(): 64 | if os.path.exists(PIDLOG): 65 | fd=open(PIDLOG) 66 | pid=int(fd.readline()) 67 | fd.close() 68 | try: 69 | deletePID() 70 | os.kill(pid,signal.SIGTERM) 71 | except: 72 | raise SystemExit('1:收到终止命令,退出程序') 73 | raise SystemExit('2:收到终止命令,退出程序') 74 | 75 | if __name__ == "__main__": 76 | reload(sys) 77 | sys.setdefaultencoding('utf-8') 78 | sys.path.append(".")#加入默认的扫描路径 79 | params=sys.argv 80 | if len(params)<2: 81 | Usage() 82 | else: 83 | #signal.signal(signal.SIGINT,stop) #当按下Ctrl+C 终止进程 84 | #signal.signal(signal.SIGKILL,stop) #kill 85 | #signal.signal(signal.SIGTERM,stop) 86 | if params[1]=="start": 87 | if os.path.exists(PIDLOG): 88 | print("Program is running") 89 | Usage() 90 | else: 91 | main() 92 | elif params[1]=="stop": 93 | stop() 94 | elif params[1]=="restart": 95 | pass -------------------------------------------------------------------------------- /stt/__init__.py: -------------------------------------------------------------------------------- 1 | #sound to text -------------------------------------------------------------------------------- /stt/google.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | 4 | class google: 5 | 6 | def __init__(self,path,debug): 7 | self.path=path 8 | self.debug=debug 9 | 10 | def sst_google(self): 11 | lang_code='zh-CN' 12 | google_speech_url = 'https://www.google.com.hk/speech-api/v1/recognize?xjerr=1&client=chromium&pfilter=2&lang=%s&maxresults=6'%(lang_code) 13 | hrs = {"User-Agent": "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.63 Safari/535.7",'Content-type': 'audio/L16; rate=16000'} 14 | recording_flac_data = open(self.path, 'rb').read() 15 | r = requests.post(google_speech_url, data=recording_flac_data, headers=hrs) 16 | response = r.text 17 | resArray=json.loads(response) 18 | if resArray['status'] == 0 : 19 | return resArray['hypotheses'][0]['utterance'] 20 | else : 21 | return False 22 | 23 | -------------------------------------------------------------------------------- /thd/Consumer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | 4 | import threading 5 | import time 6 | from stt.google import google 7 | from actions.command import InstructionSet 8 | from gl import queue 9 | import os 10 | class Consumer(threading.Thread): 11 | def __init__(self,debugInit): 12 | threading.Thread.__init__(self) 13 | self.debug=debugInit 14 | 15 | def run(self): 16 | global queue 17 | while 1: 18 | if queue.qsize()>0 : 19 | wavpath=queue.get() 20 | self.debug.saytxt("comsumer file:%s"%wavpath) 21 | speech_to_text=google(wavpath,self.debug) 22 | command=speech_to_text.sst_google() 23 | #command="下一页" 24 | os.remove(wavpath) 25 | if command: 26 | self.debug.saytxt(command) 27 | cmd=InstructionSet(command,self.debug) 28 | cmd.docmd() 29 | else: 30 | self.debug.saytxt("Sorry, I couldn't understand what you said") 31 | else : 32 | self.debug.saytxt('Time:%s/n' %(time.ctime())) 33 | time.sleep(5) 34 | 35 | -------------------------------------------------------------------------------- /thd/Producer.py: -------------------------------------------------------------------------------- 1 | import threading 2 | from inputs.microphone import microphone 3 | from gl import queue 4 | from gl import WAVPATH 5 | import time 6 | import os 7 | class Producer(threading.Thread): 8 | def __init__(self,debugInit): 9 | threading.Thread.__init__(self) 10 | self.debug=debugInit 11 | 12 | def run(self): 13 | global queue 14 | global WAVPATH 15 | while 1: 16 | timestamps=int(time.time()); 17 | wavefilepath=WAVPATH+"output%d.wav"%timestamps 18 | audioInput = microphone(wavefilepath,self.debug) 19 | audioInput.recordAudio() 20 | if audioInput.getSleepFLag() == 0: 21 | os.remove(wavefilepath) 22 | else: 23 | self.debug.saytxt("producter file:%s"%wavefilepath) 24 | queue.put(wavefilepath) 25 | time.sleep(5) 26 | 27 | -------------------------------------------------------------------------------- /thd/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tts/README.md: -------------------------------------------------------------------------------- 1 | # 依赖pico2wave 2 | * sudo apt-get install libttspico-utils -------------------------------------------------------------------------------- /tts/__init__.py: -------------------------------------------------------------------------------- 1 | #Text To Sound tts -------------------------------------------------------------------------------- /tts/picotts.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | from outputs.speaker import aerophone 4 | from gl import SPEECHTTS 5 | 6 | 7 | class picotts: 8 | def __init__(self,txt): 9 | self.txt=txt 10 | self.lang="en-US" 11 | 12 | def onPlayer(self): 13 | global SPEECHTTS 14 | os.system('pico2wave -l %s -w %s \"%s\" ' % ( self.lang, SPEECHTTS, self.txt )) 15 | player=aerophone(SPEECHTTS) 16 | player.play() 17 | 18 | 19 | 20 | --------------------------------------------------------------------------------