dever 4 years ago
parent
commit
745aba97e6

+ 0 - 1
admin.py

@@ -1,4 +1,3 @@
-#!/usr/bin/env python
 # -*- coding: utf-8 -*-
 """
     demeter web

+ 12 - 0
conf/dev.conf

@@ -29,6 +29,18 @@ port				= 6379
 name				= office_file
 prefix				= convert_
 
+[robot]
+asr					= tencent
+;ai 				= tuling
+;tts 				= baidu
+;nlu 				= unit
+
+[tencent]
+appid				= 1
+secret_id			= 1232
+secret_key			= 22
+region				= ap-guangzhou
+
 ;定义一些tornado的配置,可为空
 [tornado]
 

+ 0 - 1
cron.py

@@ -1,4 +1,3 @@
-#!/usr/bin/env python
 # -*- coding: utf-8 -*-
 import time
 from demeter.core import *

+ 0 - 1
front.py

@@ -1,4 +1,3 @@
-#!/usr/bin/env python
 # -*- coding: utf-8 -*-
 """
     demeter web

+ 0 - 1
function/nlu/__init__.py

@@ -1 +0,0 @@
-# -*- coding: utf-8 -*-

+ 0 - 1
function/snowboy/__init__.py

@@ -1 +0,0 @@
-# -*- coding: utf-8 -*-

+ 0 - 1
function/tts/__init__.py

@@ -1 +0,0 @@
-# -*- coding: utf-8 -*-

+ 0 - 15
run.py

@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-from demeter.core import *
-
-print('''
-********************************************************
-*          vecan-robot - 神算终端机器人                *
-*          (c) 2019 rabin <rabin@dever.cc>             *
-*     https://github.com/shemic/vecan.git              *
-********************************************************
-            如需退出,可以按 Ctrl-4 组合键。
-''')
-
-
-logger = Log.get(__name__)
-logger.info('测试测试')

+ 0 - 0
function/__init__.py → service/asr/__init__.py


+ 11 - 0
service/asr/base.py

@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+from __future__ import division
+from ..__load__ import *
+
+class Base(object):
+
+	def __init__(self):
+		pass
+
+	def setting(self, **args):
+		pass

+ 26 - 0
service/asr/tencent.py

@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+from __future__ import division
+from ..__load__ import *
+from base import Base
+
+class Tencent(Base):
+
+	def __init__(self):
+		super(self.__class__, self).__init__()
+		self.sdk = Demeter.service('tencent', 'sdk')
+
+	def setting(self, appid, secret_id, secret_key, region, **args):
+		self.engine = self.sdk.init(secret_id, secret_key)
+		self.region = region
+
+	def transcribe(self, fp):
+		mp3_path = utils.convert_wav_to_mp3(fp)
+		r = self.engine.ASR(mp3_path, 'mp3', '1', self.region)
+		utils.check_and_delete(mp3_path)
+		res = json.loads(r)
+		if 'Response' in res and 'Result' in res['Response']:
+			logger.info('{} 语音识别到了:{}'.format(self.SLUG, res['Response']['Result']))
+			return res['Response']['Result']
+		else:
+			logger.critical('{} 语音识别出错了'.format(self.SLUG), exc_info=True)
+			return ''

+ 0 - 373
service/convert.py

@@ -1,373 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import division
-from .__load__ import *
-
-class Convert(object):
-
-	def crearSignature(self):
-		num = Demeter.time() - 3600*24
-		model = Demeter.model('signature')
-		model.cdate.assign(num, exp='<=')
-		model.delete()
-
-	def signature(self, site_id, appid, appsecret, timestamp, nonce, file, file_id, uid, status = 1):
-		sign = Demeter.sha1(appid + '&' + appsecret + '&' + str(timestamp) + '&' + str(nonce) + '&' + file + '&' + str(file_id) + '&' + str(uid));
-
-		time = Demeter.time()
-		num = 3600*12
-		if time - int(timestamp) > num:
-			return False
-
-		# sign 只能使用一次
-		if status < 10:
-			model = Demeter.model('signature')
-			model.signature = sign
-			model.site_id = site_id
-			info = model.select(type='fetchone')
-			if info:
-				return False
-
-		param = {}
-		param['signature'] = sign
-		param['appid'] = appid
-		param['appsecret'] = appsecret
-		param['timestamp'] = timestamp
-		param['nonce'] = nonce
-		param['file'] = file
-		param['file_id'] = file_id
-		param['uid'] = uid
-
-		if status < 10:
-			model.signature = sign
-			model.site_id = site_id
-			model.insert()
-
-		return param
-
-	def update(self, site, appid, file, file_id, file_type, uid):
-
-		info = self.getFile(appid, file)
-
-		convert = Demeter.model('convert')
-		convert.site_id = site
-		convert.key = info['key']
-
-		data = convert.select(type='fetchone')
-		if not data:
-			convert.site_id = site
-			convert.uid = uid
-			convert.file_id = file_id
-			convert.file_type = file_type
-			convert.file_size = 0
-			convert.file = info['file']
-			convert.key = info['key']
-			convert.name = info['name']
-			convert.ext = info['ext']
-
-			convert.local = info['local']
-			convert.path = info['path']
-			convert.pdf = info['pdf']
-			convert.html = info['html']
-			convert.url = info['url']
-
-			id = convert.insert()
-
-			info['status'] = 1
-			info['id'] = id
-
-		else:
-			info['id'] = data['id']
-			info['status'] = data['status']
-
-		if uid:
-			self.auth(site, uid, info['id'], 1)
-
-		return info
-
-	def getAuth(self, site, uid, convert_id):
-		user = Demeter.model('user')
-		user.uid = uid
-		user.site_id = site
-		user.convert_id = convert_id
-
-		data = user.select(type='fetchone')
-
-		return data
-
-
-	def auth(self, site, uid, convert_id, status):
-		user = Demeter.model('user')
-		user.uid = uid
-		user.site_id = site
-		user.convert_id = convert_id
-
-		data = user.select(type='fetchone')
-		if not data:
-			user.site_id = site
-			user.uid = uid
-			user.convert_id = convert_id
-			user.status = status
-			user.insert()
-		elif data['status'] != status:
-			# 适用于文档转让
-			user.id = data['id']
-			update = {}
-			update['status'] = status
-			user.update(update)
-
-		return True
-
-	def get(self, site, appid, file):
-		convert = Demeter.model('convert')
-		convert.site_id = site
-		#convert.key = self.getKey(appid, file)
-		convert.key = file
-
-		data = convert.select(type='fetchone')
-
-		return data
-
-	def getKey(self, appid, file):
-		return Demeter.sha1(str(appid) + '_' + str(file))
-
-	def getFile(self, appid, file):
-		info = {}
-
-		(filepath,temp) = os.path.split(file)
-		(filename,extension) = os.path.splitext(temp)
-
-		info['file'] = file
-		info['key'] = self.getKey(appid, file)
-		info['ext'] = extension
-		info['name'] = filename
-
-		info = self.getLocalFile(appid, file, info)
-
-		return info
-
-	def getLocalFile(self, appid, file, info):
-
-		day = str(date.today())
-		day = day.split('-')
-
-		#filename =  Demeter.md5(str(uuid.uuid5(uuid.uuid1(), info['key'])))
-		filename =  info['key']
-		filepath = str(appid) + '/' + day[0] + '/' + day[1] + '/' + day[2]
-		path = ''
-
-		if 'save' in Demeter.config['setting']:
-			filepath = File.mkdirs(os.path.join(Demeter.config['setting']['save'], filepath)) + '/' + filename
-		else:
-			filepath = File.mkdirs(os.path.join(Demeter.path, 'runtime','files', filepath)) + '/' + filename
-
-		local = filepath + info['ext']
-
-		info['local'] = local
-		info['pdf'] = filepath + '/' + filename + '.pdf'
-		# 这里要增加权限控制 html不能直接访问
-		info['html'] = filepath + '/' + filename + '.html'
-		info['url'] = info['html'].replace(Demeter.path + 'runtime', '')
-		info['path'] = filepath + '/'
-
-		return info
-		if File.exists(local):
-			return info
-		else:
-			self.download(file, local);
-			return info
-
-	def download(self, file, local):
-		if 'http' in file:
-			import requests
-			r = requests.get(file, stream=True)
-			with open(local, 'wb') as up:
-				for chunk in r.iter_content(chunk_size=1024):
-					if chunk:
-						up.write(chunk)
-
-		else:
-			import shutil
-			shutil.copyfile(file, local)
-
-		if File.exists(local):
-			return True
-		return False
-
-	def command(self, info):
-		File.mkdir(info['path'])
-		convert = 'cd ' + info['path'] + ' && '
-
-		if info['ext'] != '.pdf':
-			#convert = convert + 'libreoffice  --invisible --convert-to pdf ' + info['local']
-			convert = convert + 'soffice --headless --invisible --convert-to pdf ' + info['local']
-			convert = convert + ' && '
-		else:
-			info['pdf'] = info['local']
-		convert = convert + 'pdf2htmlEX --zoom 1 --use-cropbox 0 --no-drm 1 --split-pages 1 '
-		convert = convert + '--embed-css 0  --embed-javascript 0 --embed-image 0 --embed-font 1 --process-outline 0 '
-		convert = convert + '--embed-external-font 0 --dest-dir '+info['path']+' --page-filename %d.page ' + info['pdf']
-
-		return convert
-
-
-	def total(self, path):
-		page = 0
-		for parentdir,dirname,filenames in os.walk(path):  
-			for filename in filenames:
-				if os.path.splitext(filename)[1]=='.page':
-					page = page + 1
-		return page
-
-	def handle(self, id):
-		model = Demeter.model('convert')
-		model.id = id
-		info = model.select(type='fetchone')
-
-		if not info:
-			return
-		siteModel = Demeter.model('site')
-		siteModel.id = info['site_id']
-		site = siteModel.select(type='fetchone')
-
-		status = True
-		if info['status'] == 1 or info['status'] == 4:
-			status = False
-
-		if info and status == False:
-			model.id = id
-			update = {}
-			update['status'] = 2
-			model.update(update)
-
-			if not File.exists(info['local']):
-				self.download(info['file'], info['local'])
-
-			if not File.exists(info['html']):
-			#if info:
-				handle = self.command(info)
-				Shell.popen(handle)
-				if File.exists(info['html']):
-
-					# 截屏
-					self.cut(info)
-					#self.string_switch(info['html'], "taste", "tasting")
-					# 获取有多少页
-					page = self.total(info['path'])
-					model.id = id
-					size = os.path.getsize(info['local'])
-					update = {}
-					update['file_size'] = size
-					update['page'] = page
-					update['status'] = 3
-					model.update(update)
-
-					# 通知接口 通知应用成功转换
-					info['page'] = page
-					info['file_size'] = size
-					info['convert_status'] = 1
-					self.api(info, site)
-					return
-
-			model.id = id
-			update = {}
-			update['status'] = 4
-			model.update(update)
-			# 通知接口 通知应用失败转换
-			info['page'] = 0
-			info['file_size'] = 0
-			info['convert_status'] = 2
-			self.api(info, site)
-
-	def api(self, info, site):
-		if 'file_id' in info and info['file_id']:
-			api = site['api']
-
-			appid = site['appid']
-			appsecret = site['appsecret']
-			timestamp = Demeter.time()
-			nonce = Demeter.hash()
-			file = info['key']
-			file_id = info['file_id']
-			uid = info['uid']
-
-			param = self.signature(site['id'], appid, appsecret, timestamp, nonce, file, file_id, uid)
-
-			param['url'] = 'main/view'
-			param['img'] = info['url'] + '.jpg'
-			param['page'] = info['page']
-			param['ext'] = info['ext']
-			param['file_size'] = info['file_size']
-			param['status'] = info['convert_status']
-
-			Demeter.curl(api, param, 'post')
-
-	def cut(self, info):
-		from pdf2jpg import pdf2jpg
-		from wand.image import Image
-		dest = info['html'] + '.photo'
-		result = pdf2jpg.convert_pdf2jpg(info['pdf'], dest, pages="0")
-		source = result[0]['output_jpgfiles'][0]
-		dest = info['html'] + '.jpg'
-		command = 'mv '+source+' ' + dest
-		Shell.popen(command)
-		target_width = 300
-		with Image(filename=dest) as img:
-			target_width, target_height = self.getSize(target_width, img.width, img.height)
-			img.sample(target_width, target_height)
-			img.save(filename=dest)
-
-		'''
-		from wand.image import Image
-		pdf = Image(filename=source, resolution=50)
-		jpg = pdf.convert('jpg')
-		req_image = []
-		i = 0
-		for img in jpg.sequence:
-			if i == 0:
-				img_page = Image(image=img)
-				req_image.append(img_page.make_blob('jpg'))
-			i = i+1
-
-		for img in req_image:
-			ff = open(dest, 'wb')
-			ff.write(img)
-			ff.close()
-		'''
-
-
-	def getSize(self, target_width, img_width, img_height):
-		if img_width > target_width:
-			ratio = target_width / img_width
-			target_height = int(ratio * img_height)
-		else:
-			target_width = img_width
-			target_height = img_height
-		return target_width, target_height
-
-	def string_switch(self, x,y,z,s=1):
-		with open(x, "r", encoding="utf-8") as f:
-			#readlines以列表的形式将文件读出
-			lines = f.readlines()
-	 
-		with open(x, "w", encoding="utf-8") as f_w:
-			#定义一个数字,用来记录在读取文件时在列表中的位置
-			n = 0
-			#默认选项,只替换第一次匹配到的行中的字符串
-			if s == 1:
-				for line in lines:
-					if y in line:
-						line = line.replace(y,z)
-						f_w.write(line)
-						n += 1
-						break
-					f_w.write(line)
-					n += 1
-				#将剩余的文本内容继续输出
-				for i in range(n,len(lines)):
-					f_w.write(lines[i])
-			#全局匹配替换
-			elif s == 'g':
-				for line in lines:
-					if y in line:
-						line = line.replace(y,z)
-					f_w.write(line)

+ 29 - 0
service/robot.py

@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+from __future__ import division
+from .__load__ import *
+
+class Robot(object):
+
+	def __init__(self, profiling=False):
+		self.start()
+
+	def say(self, content):
+		print content
+
+	def start(self):
+		self.config = Demeter.config['robot']
+		self.reload()
+
+	def reload(self):
+		try:
+			for item in self.config:
+				setting = self.setting(self.config[item])
+				self.config[item] = Demeter.service(self.config[item], item)
+				self.config[item].setting(**setting)
+		except Exception as e:
+			Demeter.logger.critical("机器人初始化失败:{}".format(e))
+
+	def setting(self, name):
+		# 后续要改成数据库读取
+		config = Demeter.config[name]
+		return config

+ 92 - 0
service/sdk/AbstractPlugin.py

@@ -0,0 +1,92 @@
+from abc import ABCMeta, abstractmethod
+from robot import constants
+from robot import logging
+import sys
+
+logger = logging.getLogger(__name__)
+
+try:
+    sys.path.append(constants.CONTRIB_PATH)
+except Exception as e:
+    logger.debug("未检测到插件目录,Error:{}".format(e))
+    
+class AbstractPlugin(metaclass=ABCMeta):
+    """ 技能插件基类 """
+
+    SLUG = 'AbstractPlugin'
+    IS_IMMERSIVE = False
+
+    def __init__(self, con):
+        if self.IS_IMMERSIVE is not None:
+            self.isImmersive = self.IS_IMMERSIVE
+        else:
+            self.isImmersive = False
+        self.priority = 0
+        self.con = con
+        self.nlu = self.con.nlu
+
+    def play(self, src, delete=False, onCompleted=None, volume=1):
+        self.con.play(src, delete, onCompleted, volume)
+
+    def say(self, text, cache=False, onCompleted=None):
+        self.con.say(text, cache=cache, plugin=self.SLUG, onCompleted=onCompleted)
+
+    def activeListen(self, silent=False):
+        return self.con.activeListen(silent)
+
+    def clearImmersive(self):
+        self.con.setImmersiveMode(None)
+
+    @abstractmethod
+    def isValid(self, query, parsed):
+        """
+        是否适合由该插件处理
+
+        参数:
+        query -- 用户的指令字符串
+        parsed -- 用户指令经过 NLU 解析后的结果
+
+        返回:
+        True: 适合由该插件处理
+        False: 不适合由该插件处理
+        """
+        return False
+
+    @abstractmethod
+    def handle(self, query, parsed):
+        """
+        处理逻辑
+
+        参数:
+        query -- 用户的指令字符串
+        parsed -- 用户指令经过 NLU 解析后的结果
+        """
+        pass
+
+    def isValidImmersive(self, query, parsed):
+        """
+        是否适合在沉浸模式下处理,
+        仅适用于有沉浸模式的插件(如音乐等)
+        当用户唤醒时,可以响应更多指令集。
+        例如:“"上一首"、"下一首" 等
+        """
+        return False
+
+    def pause(self):
+        """
+        暂停当前正在处理的任务,
+        当处于该沉浸模式下且被唤醒时,
+        将自动触发这个方法,
+        可以用于强制暂停一个耗时的操作        
+        """
+        return
+
+    def restore(self):
+        """
+        恢复当前插件,
+        仅适用于有沉浸模式的插件(如音乐等)
+        当用户误唤醒或者唤醒进行闲聊后,
+        可以自动恢复当前插件的处理逻辑
+        """
+        return
+    

+ 141 - 0
service/sdk/AliSpeech.py

@@ -0,0 +1,141 @@
+# -*- coding: UTF-8 -*-
+
+import http.client
+import urllib.parse
+import json
+from robot import utils
+from robot import logging
+
+logger = logging.getLogger(__name__)
+
+def processGETRequest(appKey, token, voice, text, format, sampleRate) :
+    host = 'nls-gateway.cn-shanghai.aliyuncs.com'
+    url = 'https://' + host + '/stream/v1/tts'
+    # 设置URL请求参数
+    url = url + '?appkey=' + appKey
+    url = url + '&token=' + token
+    url = url + '&text=' + text
+    url = url + '&format=' + format
+    url = url + '&sample_rate=' + str(sampleRate)
+    url = url + '&voice=' + voice
+    logger.debug(url)
+    conn = http.client.HTTPSConnection(host)
+    conn.request(method='GET', url=url)
+    # 处理服务端返回的响应
+    response = conn.getresponse()
+    logger.debug('Response status and response reason:')
+    logger.debug(response.status ,response.reason)
+    contentType = response.getheader('Content-Type')
+    logger.debug(contentType)
+    body = response.read()
+    if 'audio/mpeg' == contentType :
+        logger.debug('The GET request succeed!')
+        tmpfile = utils.write_temp_file(body, '.mp3')
+        conn.close()
+        return tmpfile
+    else :
+        logger.debug('The GET request failed: ' + str(body))
+        conn.close()
+        return None
+
+def processPOSTRequest(appKey, token, voice, text, format, sampleRate) :
+    host = 'nls-gateway.cn-shanghai.aliyuncs.com'
+    url = 'https://' + host + '/stream/v1/tts'
+    # 设置HTTPS Headers
+    httpHeaders = {
+        'Content-Type': 'application/json'
+        }
+    # 设置HTTPS Body
+    body = {'appkey': appKey, 'token': token, 'text': text, 'format': format, 'sample_rate': sampleRate, 'voice': voice}
+    body = json.dumps(body)
+    logger.debug('The POST request body content: ' + body)
+    # Python 2.x 请使用httplib
+    # conn = httplib.HTTPSConnection(host)
+    # Python 3.x 请使用http.client
+    conn = http.client.HTTPSConnection(host)
+    conn.request(method='POST', url=url, body=body, headers=httpHeaders)
+    # 处理服务端返回的响应
+    response = conn.getresponse()
+    logger.debug('Response status and response reason:')
+    logger.debug(response.status ,response.reason)
+    contentType = response.getheader('Content-Type')
+    logger.debug(contentType)
+    body = response.read()
+    if 'audio/mpeg' == contentType :
+        logger.debug('The POST request succeed!')
+        tmpfile = utils.write_temp_file(body, '.mp3')
+        conn.close()
+        return tmpfile
+    else :
+        logger.critical('The POST request failed: ' + str(body))
+        conn.close()
+        return None
+
+def process(request, token, audioContent) :
+    # 读取音频文件
+    host = 'nls-gateway.cn-shanghai.aliyuncs.com'
+    # 设置HTTP请求头部
+    httpHeaders = {
+        'X-NLS-Token': token,
+        'Content-type': 'application/octet-stream',
+        'Content-Length': len(audioContent)
+        }
+    conn = http.client.HTTPConnection(host)
+    conn.request(method='POST', url=request, body=audioContent, headers=httpHeaders)
+    response = conn.getresponse()
+    logger.debug('Response status and response reason:')
+    logger.debug(response.status ,response.reason)
+    body = response.read()
+    try:
+        logger.debug('Recognize response is:')
+        body = json.loads(body)
+        logger.debug(body)
+        status = body['status']
+        if status == 20000000 :
+            result = body['result']
+            logger.debug('Recognize result: ' + result)
+            conn.close()
+            return result
+        else :
+            logger.critical('Recognizer failed!')
+            conn.close()
+            return None
+    except ValueError:
+        logger.debug('The response is not json format string')
+        conn.close()
+        return None
+
+def tts(appKey, token, voice, text):
+    # 采用RFC 3986规范进行urlencode编码
+    textUrlencode = text
+    textUrlencode = urllib.parse.quote_plus(textUrlencode)
+    textUrlencode = textUrlencode.replace("+", "%20")
+    textUrlencode = textUrlencode.replace("*", "%2A")
+    textUrlencode = textUrlencode.replace("%7E", "~")
+    format = 'mp3'
+    sampleRate = 16000
+    return processPOSTRequest(appKey, token, voice, text, format, sampleRate)
+
+def asr(appKey, token, wave_file):
+    # 服务请求地址
+    url = 'http://nls-gateway.cn-shanghai.aliyuncs.com/stream/v1/asr'
+    pcm = utils.get_pcm_from_wav(wave_file)
+    # 音频文件
+    format = 'pcm'
+    sampleRate = 16000
+    enablePunctuationPrediction  = True
+    enableInverseTextNormalization = True
+    enableVoiceDetection  = False
+    # 设置RESTful请求参数
+    request = url + '?appkey=' + appKey
+    request = request + '&format=' + format
+    request = request + '&sample_rate=' + str(sampleRate)
+    if enablePunctuationPrediction :
+        request = request + '&enable_punctuation_prediction=' + 'true'
+    if enableInverseTextNormalization :
+        request = request + '&enable_inverse_text_normalization=' + 'true'
+    if enableVoiceDetection :
+        request = request + '&enable_voice_detection=' + 'true'
+    logger.debug('Request: ' + request)
+    return process(request, token, pcm)
+    

+ 137 - 0
service/sdk/RASRsdk.py

@@ -0,0 +1,137 @@
+# -*- coding:utf-8 -*-
+import urllib.request
+import hmac
+import hashlib
+import base64
+import time
+import random
+import os
+import json
+
+
+def formatSignString(param):
+    signstr = "POSTaai.qcloud.com/asr/v1/"
+    for t in param:
+        if 'appid' in t:
+            signstr += str(t[1])
+            break
+    signstr += "?"
+    for x in param:
+        tmp = x
+        if 'appid' in x:
+            continue
+        for t in tmp:
+            signstr += str(t)
+            signstr += "="
+        signstr = signstr[:-1]
+        signstr += "&"
+    signstr = signstr[:-1]
+    # print 'signstr',signstr
+    return signstr
+
+
+def sign(signstr, secret_key):
+    sign_bytes= bytes(signstr , 'utf-8')
+    secret_bytes = bytes(secret_key, 'utf-8')
+    hmacstr = hmac.new(secret_bytes, sign_bytes, hashlib.sha1).digest()
+    s = base64.b64encode(hmacstr).decode('utf-8')
+    return s
+
+
+def randstr(n):
+    seed = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+    sa = []
+    for i in range(n):
+        sa.append(random.choice(seed))
+    salt = ''.join(sa)
+    # print salt
+    return salt
+
+
+def sendVoice(secret_key, secretid, appid, engine_model_type, res_type, result_text_format, voice_format, filepath, cutlength, template_name=""):
+    if len(str(secret_key)) == 0:
+        print('secretKey can not empty')
+        return
+    if len(str(secretid)) == 0:
+        print('secretid can not empty')
+        return
+    if len(str(appid)) == 0:
+        print('appid can not empty')
+        return
+    if len(str(engine_model_type)) == 0 or (
+            str(engine_model_type) != '8k_0' and str(engine_model_type) != '16k_0' and str(
+        engine_model_type) != '16k_en'):
+        print('engine_model_type is not right')
+        return
+    if len(str(res_type)) == 0 or (str(res_type) != '0' and str(res_type) != '1'):
+        print('res_type is not right')
+        return
+    if len(str(result_text_format)) == 0 or (str(result_text_format) != '0' and str(result_text_format) != '1' and str(
+            result_text_format) != '2' and str(result_text_format) != '3'):
+        print('result_text_format is not right')
+        return
+    if len(str(voice_format)) == 0 or (
+            str(voice_format) != '1' and str(voice_format) != '4' and str(voice_format) != '6'):
+        print('voice_format is not right')
+        return
+    if len(str(filepath)) == 0:
+        print('filepath can not empty')
+        return
+    if len(str(cutlength)) == 0 or str(cutlength).isdigit() == False or cutlength > 200000:
+        print('cutlength can not empty')
+        return
+    # secret_key = "oaYWFO70LGDmcpfwo8uF1IInayysGtgZ"
+    query_arr = dict()
+    query_arr['appid'] = appid
+    query_arr['projectid'] = 1013976
+    if len(template_name) > 0:
+        query_arr['template_name'] = template_name
+    query_arr['sub_service_type'] = 1
+    query_arr['engine_model_type'] = engine_model_type
+    query_arr['res_type'] = res_type
+    query_arr['result_text_format'] = result_text_format
+    query_arr['voice_id'] = randstr(16)
+    query_arr['timeout'] = 100
+    query_arr['source'] = 0
+    query_arr['secretid'] = secretid
+    query_arr['timestamp'] = str(int(time.time()))
+    query_arr['expired'] = int(time.time()) + 24 * 60 * 60
+    query_arr['nonce'] = query_arr['timestamp'][0:4]
+    query_arr['voice_format'] = voice_format
+    file_object = open(filepath, 'rb')
+    file_object.seek(0, os.SEEK_END)
+    datalen = file_object.tell()
+    file_object.seek(0, os.SEEK_SET)
+    seq = 0
+    response = []
+    while (datalen > 0):
+        end = 0
+        if (datalen < cutlength):
+            end = 1
+        query_arr['end'] = end
+        query_arr['seq'] = seq
+        query = sorted(query_arr.items(), key=lambda d: d[0])
+        signstr = formatSignString(query)
+        autho = sign(signstr, secret_key)
+
+        if (datalen < cutlength):
+            content = file_object.read(datalen)
+        else:
+            content = file_object.read(cutlength)
+        seq = seq + 1
+        datalen = datalen - cutlength
+        headers = dict()
+        headers['Authorization'] = autho
+        headers['Content-Length'] = len(content)
+        requrl = "http://"
+        requrl += signstr[4::]
+        req = urllib.request.Request(requrl, data=content, headers=headers)        
+        res_data = urllib.request.urlopen(req)
+        r = res_data.read().decode('utf-8')
+        res = json.loads(r)
+        if res['code'] == 0:
+            response.append(res['text'])    
+    file_object.close()
+    return response[len(response)-1]
+
+

+ 0 - 0
function/ai/__init__.py → service/sdk/__init__.py


+ 212 - 0
service/sdk/tencent.py

@@ -0,0 +1,212 @@
+# -*- coding: utf-8 -*-
+from __future__ import division
+from ..__load__ import *
+
+'Tencent ASR && TTS API'
+__author__ = 'Charles Li, Joseph Pan'
+
+import time
+import uuid
+import json
+import random
+import requests
+import hmac
+import base64
+import urllib
+class Tencent(object):
+	__slots__ = 'SECRET_ID', 'SECRET_KEY', 'SourceType', 'URL', 'VoiceFormat', 'PrimaryLanguage', 'Text', 'VoiceType', 'Region'
+
+	def init(self, SECRET_ID, SECRET_KEY):
+		self.SECRET_KEY, self.SECRET_ID = SECRET_KEY, SECRET_ID
+	@property
+	def secret_id(self):
+		return self.SECRET_ID
+	@secret_id.setter
+	def secret_id(self, SECRET_ID):
+		if not isinstance(SECRET_ID, str):
+			raise ValueError('SecretId must be a string!')
+		if len(SECRET_ID)==0:
+			raise ValueError('SecretId can not be empty!')
+		self.SECRET_ID = SECRET_ID
+	@property
+	def secret_key(self):
+		return self.SECRET_KEY
+	@secret_key.setter
+	def secret_key(self, SECRET_KEY):
+		if not isinstance(SECRET_KEY, str):
+			raise ValueError('SecretKey must be a string!')
+		if len(SECRET_KEY)==0:
+			raise ValueError('SecretKey can not be empty!')
+		self.SECRET_KEY = SECRET_KEY
+	@property
+	def source_type(self):
+		return self.sourcetype
+	@source_type.setter
+	def source_type(self, SourceType):
+		if not isinstance(SourceType, str):
+			raise ValueError('SecretType must be an string!')
+		if len(SourceType)==0:
+			raise ValueError('SourceType can not be empty!')
+		self.SourceType = SourceType
+	@property
+	def url(self):
+		return self.URL
+	@url.setter
+	def url(self, URL):
+		if not isinstance(URL, str):
+			raise ValueError('url must be an string!')
+		if len(URL)==0:
+			raise ValueError('url can not be empty!')
+		self.URL = URL
+	@property
+	def voiceformat(self):
+		return self.VoiceFormat
+	@voiceformat.setter
+	def voiceformat(self, VoiceFormat):
+		if not isinstance(VoiceFormat, str):
+			raise ValueError('voiceformat must be an string!')
+		if len(VoiceFormat)==0:
+			raise ValueError('voiceformat can not be empty!')
+		self.VoiceFormat = VoiceFormat
+	@property
+	def text(self):
+		return self.Text
+	@text.setter
+	def text(self, Text):
+		if not isinstance(Text, str):
+			raise ValueError('text must be an string!')
+		if len(Text)==0:
+			raise ValueError('text can not be empty!')
+		self.Text = Text
+	@property
+	def region(self):
+		return self.Region
+	@region.setter
+	def region(self, Region):
+		if not isinstance(Region, str):
+			raise ValueError('region must be an string!')
+		if len(Region)==0:
+			raise ValueError('region can not be empty!')
+		self.Region = Region
+	@property
+	def primarylanguage(self):
+		return self.PrimaryLanguage
+	@primarylanguage.setter
+	def primarylanguage(self, PrimaryLanguage):
+		self.PrimaryLanguage = PrimaryLanguage
+	@property
+	def voicetype(self):
+		return self.VoiceType
+	@voicetype.setter
+	def voicetype(self, VoiceType):		
+		self.VoiceType = VoiceType
+	def TTS(self, text, voicetype, primarylanguage, region):
+		self.text, self.voicetype, self.primarylanguage, self.region = text, voicetype, primarylanguage, region
+		return self.textToSpeech()
+	def textToSpeech(self):
+		#生成body
+		def make_body(config_dict, sign_encode):
+			##注意URL编码的时候分str编码,整段编码会丢data
+			body = ''
+			for a, b in config_dict:
+				body += urllib.parse.quote(a) + '=' + urllib.parse.quote(str(b)) + '&'
+			return body + 'Signature=' + sign_encode
+		HOST = 'aai.tencentcloudapi.com'
+		config_dict= {
+						'Action'		 : 'TextToVoice',
+						'Version'		: '2018-05-22',
+						'ProjectId'	  : 0,
+						'Region'		 : self.Region,
+						'VoiceType'	  : self.VoiceType,
+						'Timestamp'	  : int(time.time()),
+						'Nonce'		  : random.randint(100000, 200000),
+						'SecretId'	   : self.SECRET_ID,
+						'Text'		   : self.Text,
+						'PrimaryLanguage': self.PrimaryLanguage,
+						'ModelType'	  : 1,
+						'SessionId'	  : uuid.uuid1()
+		}
+		#按key排序
+		config_dict = sorted(config_dict.items())
+		signstr = self.formatSignString(config_dict)
+		sign_encode = urllib.parse.quote(self.encode_sign(signstr, self.SECRET_KEY))
+		body = make_body(config_dict, sign_encode)
+		#Get URL
+		req_url = "https://aai.tencentcloudapi.com"
+		header = {
+			'Host' : HOST,
+			'Content-Type' : 'application/x-www-form-urlencoded',
+			'Charset' : 'UTF-8'
+		}
+		request = requests.post(req_url, headers = header, data = body)
+		#有些音频utf8解码失败,存在编码错误
+		s = request.content.decode("utf8","ignore")
+		return json.loads(s)
+	def ASR(self, URL, voiceformat, sourcetype, region):
+		self.url, self.voiceformat, self.source_type, self.region = URL, voiceformat, sourcetype, region
+		return self.oneSentenceRecognition()
+	def oneSentenceRecognition(self):
+		#生成body
+		def make_body(config_dict, sign_encode):
+			##注意URL编码的时候分str编码,整段编码会丢data
+			body = ''
+			for a, b in config_dict:
+				body += urllib.parse.quote(a) + '=' + urllib.parse.quote(str(b)) + '&'
+			return body + 'Signature=' + sign_encode
+		HOST = 'aai.tencentcloudapi.com'
+		config_dict= {
+						'Action'		 : 'SentenceRecognition',
+						'Version'		: '2018-05-22',
+						'Region'		 : self.Region,
+						'ProjectId'	  : 0,
+						'SubServiceType' : 2,
+						'EngSerViceType' : '16k',
+						'VoiceFormat'	: self.VoiceFormat,
+						'UsrAudioKey'	: random.randint(0, 20),
+						'Timestamp'	  : int(time.time()),
+						'Nonce'		  : random.randint(100000, 200000),
+						'SecretId'	   : self.SECRET_ID,
+						'SourceType'	 : self.SourceType
+		}
+		if self.SourceType == '0':
+			config_dict['Url'] = urllib.parse.quote(str(self.url))
+		else:
+			#不能大于1M
+			file_path = self.URL
+			file = open(file_path, 'rb')
+			content = file.read()
+			config_dict['DataLen'] = len(content)
+			config_dict['Data'] = base64.b64encode(content).decode()
+			#config_dict['Data'] = content
+			file.close()
+		#按key排序
+		config_dict = sorted(config_dict.items())
+		signstr = self.formatSignString(config_dict)
+		sign_encode = urllib.parse.quote(self.encode_sign(signstr, self.SECRET_KEY))
+		body = make_body(config_dict, sign_encode)
+		#Get URL
+		req_url = "https://aai.tencentcloudapi.com"
+		header = {
+			'Host' : HOST,
+			'Content-Type' : 'application/x-www-form-urlencoded',
+			'Charset' : 'UTF-8'
+		}
+		request = requests.post(req_url, headers = header, data = body)
+		#有些音频utf8解码失败,存在编码错误
+		s = request.content.decode("utf8","ignore")
+		return s
+	#拼接url和参数
+	def formatSignString(self, config_dict):
+		signstr="POSTaai.tencentcloudapi.com/?"
+		argArr = []
+		for a, b in config_dict:
+			argArr.append(a + "=" + str(b))
+		config_str = "&".join(argArr)
+		return signstr + config_str
+	#生成签名
+	def encode_sign(self, signstr, SECRET_KEY):
+		myhmac = hmac.new(SECRET_KEY.encode(), signstr.encode(), digestmod = 'sha1')
+		code = myhmac.digest()
+		#hmac() 完一定要decode()和 python 2 hmac不一样
+		signature = base64.b64encode(code).decode()
+		return signature

+ 161 - 0
service/sdk/unit.py

@@ -0,0 +1,161 @@
+# encoding:utf-8
+import requests
+import datetime
+import uuid
+import json
+import os
+from dateutil import parser as dparser
+from robot import constants, logging
+
+logger = logging.getLogger(__name__)
+
+def get_token(api_key, secret_key):
+    cache = open(os.path.join(constants.TEMP_PATH, 'baidustt.ini'), 'a+')
+    try:
+        pms = cache.readlines()
+        if len(pms) > 0:
+            time = pms[0]
+            tk = pms[1]
+            # 计算token是否过期 官方说明一个月,这里保守29天
+            time = dparser.parse(time)
+            endtime = datetime.datetime.now()
+            if (endtime - time).days <= 29:
+                return tk
+    finally:
+        cache.close()
+    URL = 'http://openapi.baidu.com/oauth/2.0/token'
+    params = {'grant_type': 'client_credentials',
+              'client_id': api_key,
+              'client_secret': secret_key}
+    r = requests.get(URL, params=params)
+    try:
+        r.raise_for_status()
+        token = r.json()['access_token']
+        return token
+    except requests.exceptions.HTTPError:
+        return ''
+
+def getUnit(query, service_id, api_key, secret_key):
+    """ 
+    NLU 解析
+
+    :param query: 用户的指令字符串
+    :param service_id: UNIT 的 service_id
+    :param api_key: UNIT apk_key
+    :param secret_key: UNIT secret_key
+    :returns: UNIT 解析结果。如果解析失败,返回 None
+    """
+    access_token = get_token(api_key, secret_key)
+    url = 'https://aip.baidubce.com/rpc/2.0/unit/service/chat?access_token=' + access_token
+    request={
+        "query":query,
+        "user_id":"888888",
+    }
+    body={
+        "log_id": str(uuid.uuid1()),
+        "version":"2.0",
+        "service_id": service_id,
+        "session_id": str(uuid.uuid1()),
+        "request":request
+    }
+    try:
+        headers = {'Content-Type': 'application/json'}
+        request = requests.post(url, json=body, headers=headers)
+        return json.loads(request.text)
+    except Exception:
+        return None
+
+
+def getIntent(parsed):
+    """ 
+    提取意图
+
+    :param parsed: UNIT 解析结果
+    :returns: 意图数组
+    """
+    if parsed is not None and 'result' in parsed and \
+       'response_list' in parsed['result']:
+        return parsed['result']['response_list'][0]['schema']['intent']
+    else:
+        return ''
+
+
+def hasIntent(parsed, intent):
+    """ 
+    判断是否包含某个意图
+
+    :param parsed: UNIT 解析结果
+    :param intent: 意图的名称
+    :returns: True: 包含; False: 不包含
+    """
+    if parsed is not None and 'result' in parsed and \
+       'response_list' in parsed['result']:
+        response_list = parsed['result']['response_list']
+        for response in response_list:
+            if response['schema']['intent'] == intent:
+                return True
+        return False
+    else:
+        return False
+
+
+def getSlots(parsed, intent=''):
+    """ 
+    提取某个意图的所有词槽
+    
+    :param parsed: UNIT 解析结果
+    :param intent: 意图的名称
+    :returns: 词槽列表。你可以通过 name 属性筛选词槽,
+再通过 normalized_word 属性取出相应的值
+    """
+    if parsed is not None and 'result' in parsed and \
+       'response_list' in parsed['result']:
+        response_list = parsed['result']['response_list']
+        if intent == '':
+            return parsed['result']['response_list'][0]['schema']['slots']
+        for response in response_list:
+            if response['schema']['intent'] == intent:
+                return response['schema']['slots']
+    else:
+        return []
+
+def getSlotWords(parsed, intent, name):
+    """ 
+    找出命中某个词槽的内容
+    
+    :param parsed: UNIT 解析结果
+    :param intent: 意图的名称
+    :param name: 词槽名
+    :returns: 命中该词槽的值的列表。
+    """
+    slots = getSlots(parsed, intent)
+    words = []
+    for slot in slots:
+        if slot['name'] == name:
+            words.append(slot['normalized_word'])
+    return words
+
+def getSay(parsed, intent=''):
+    """
+    提取 UNIT 的回复文本
+
+    :param parsed: UNIT 解析结果
+    :param intent: 意图的名称
+    :returns: UNIT 的回复文本
+    """
+    if parsed is not None and 'result' in parsed and \
+       'response_list' in parsed['result']:
+        response_list = parsed['result']['response_list']
+        if intent == '':
+            return response_list[0]['action_list'][0]['say']
+        for response in response_list:
+            if response['schema']['intent'] == intent:
+                return response['action_list'][0]['say']
+        return ''
+    else:
+        return ''
+
+
+if __name__ == '__main__':
+    parsed = getUnit('今天的天气', "S13442", 'w5v7gUV3iPGsGntcM84PtOOM', 'KffXwW6E1alcGplcabcNs63Li6GvvnfL')
+    print(parsed)

+ 0 - 0
function/asr/__init__.py → service/tts/__init__.py


+ 14 - 0
service/tts/load.py

@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+from __future__ import division
+from .__load__ import *
+
+class Robot(object):
+
+	def __init__(self, profiling=False):
+        self.start()
+
+	def say(self, content):
+		print content
+
+	def start(self):
+		self.asr = Demeter.service('arc.load')

+ 37 - 0
vecan.py

@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+from demeter.core import *
+
+class Vecan(object):
+
+	@classmethod
+	def init(self):
+		print('''
+		********************************************************
+		*          vecan-robot - 神算终端机器人                *
+		*          (c) 2019 rabin <rabin@dever.cc>             *
+		*     https://github.com/shemic/vecan.git              *
+		********************************************************
+		            如需退出,可以按 Ctrl-4 组合键。
+		''')
+
+
+		Log.init(__name__)
+		Demeter.logger.info('测试测试')
+
+		self.robot = Demeter.service('robot')
+		self.robot.say('你好')
+
+		WatchDog.init()
+
+	@classmethod
+	def stop(self, signal, frame):
+		Demeter.dog.stop()
+
+	@classmethod
+	def run(self):
+		self.init()
+		signal.signal(signal.SIGINT, self.stop)
+
+if __name__ == '__main__':
+	vecan = Vecan()
+	vecan.run()