OptionAction.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789
  1. # -*- coding:utf-8 -*-
  2. import os
  3. from BaseLog import CBaseLog
  4. from ExtraData import CExtraData
  5. from OptionExcel import COptionExcel
  6. from OptionConfig import COptionConfig
  7. from OptionFocus import COptionFocus
  8. from OptionOCR import COptionOCR
  9. from ssat_sdk.tv_detect import *
  10. from ssat_sdk.device_manage.capturecard_manager import CCardManager
  11. from ssat_sdk.utils.string_util import strcmp
  12. g_level = ['First', 'Second', 'Third', 'Fourth', 'Fifth', 'Sixth',
  13. 'Seventh', 'Eighth', 'Ninth', 'Tenth', 'Eleventh', 'Twelfth']
  14. def strSplit(text):
  15. ret = []
  16. str_int = ''
  17. str_ch = ''
  18. ch_last = ' '
  19. for ch in text:
  20. if 47 < ord(ch) < 58:
  21. str_int += ch
  22. if str_ch.__len__():
  23. ret.append(str_ch)
  24. str_ch = ''
  25. else:
  26. if 47 < ord(ch_last) < 58 and ch == '.':
  27. str_int += ch
  28. if str_ch.__len__():
  29. ret.append(str_ch)
  30. str_ch = ''
  31. else:
  32. str_ch += ch
  33. if str_int.__len__():
  34. ret.append(str_int)
  35. str_int = ''
  36. ch_last = ch
  37. if str_ch.__len__():
  38. ret.append(str_ch)
  39. if str_int.__len__():
  40. ret.append(str_int)
  41. return ret
  42. # 注意:所有不对外暴露的变量和函数需要私有化,以明确哪些接口和参数是对外的。
  43. # 这样便于后期维护时,根据对外的变量和函数来做处理。
  44. class COptionAction(CBaseLog):
  45. # ==============设备对象============== #
  46. # 红老鼠遥控对象;
  47. __redRat3 = TvOperator()
  48. # 创建视频采集对象
  49. __ccard = CCardManager()
  50. # 图片切割对象
  51. __imgCMP = ImageCMP()
  52. def __init__(self, optionName, optionValue, optionConfig, optionExcel):
  53. CBaseLog.__init__(self)
  54. # 层级位置;
  55. self.__pos = 0
  56. # 目标option;
  57. self.__optionName = optionName
  58. # __optionValue可空;
  59. self.__optionValue = optionValue
  60. self.__optionExcel = optionExcel
  61. self.__optionConfig = optionConfig
  62. # 焦点定位及文字识别;
  63. self.__optionFocus = COptionFocus(optionConfig)
  64. self.__optionOCR = COptionOCR(optionConfig, optionExcel)
  65. if self.__optionExcel is None:
  66. self.error(u"表格对象空")
  67. # ==============常用对象数据==============;
  68. self.__optionPaths = self.__optionExcel.getOptionPaths(self.__optionName)
  69. # 如果__optionValue空则不取value表任何内容;
  70. if self.__optionValue != "":
  71. self.__optionValueInfo = self.__optionExcel.getOptionValueInfo(self.__optionName, self.__optionValue)
  72. self.__optionInfo = self.__optionExcel.getOptionInfo(self.__optionName)
  73. # 当前状态下的变量,与__pos对应;
  74. self.__curOptionName = ''
  75. self.__curOptionInfo = None
  76. # 到达value节点后,记录value值(一般只用于range() 数值记录,其他值无意义);
  77. self.__optionValueText = ""
  78. # 获取一次当前层级信息;
  79. self.getCurOptionInfo()
  80. @property
  81. def pos(self):
  82. return self.__pos
  83. @property
  84. def curOptionName(self):
  85. return self.__curOptionName
  86. @property
  87. def curOptionInfo(self):
  88. return self.__curOptionInfo
  89. @property
  90. def optionValueText(self):
  91. return self.__optionValueText
  92. '''
  93. 函数:截图;
  94. 参数:无
  95. 返回:无
  96. '''
  97. def takePicture(self):
  98. img = os.path.join(getSATTmpDIR(), "menutree_runpath.png")
  99. COptionAction.__ccard.takePicture(img)
  100. if os.path.exists(img):
  101. self.error(u"截图失败:%s" % img)
  102. return img
  103. '''
  104. 函数:调用根节点快捷键(中间节点不需要快捷键;);
  105. 参数:
  106. 返回:
  107. '''
  108. def callFirstOptionShortCutKey(self):
  109. if 'shortcut_key' in self.__optionPaths['First']:
  110. self.sendKey(self.__optionPaths['First']['shortcut_key'])
  111. else:
  112. self.sendKey(self.__optionPaths['First']['parent'])
  113. self.warn(u"表格没有shortcut_key字段,执行默认的parent按键:%s" % self.__optionPaths['First']['parent'])
  114. '''
  115. 函数:调用当前结点的toparent_key
  116. 参数:
  117. 返回:
  118. '''
  119. def callCurOptionBackKey(self, curOptionName):
  120. curOptionInfo = self.__optionExcel.getOptionInfo(curOptionName)
  121. if 'toparent_key' in self.__optionPaths[curOptionInfo['level']]:
  122. self.sendKey(self.__optionPaths[curOptionInfo['level']]['toparent_key'])
  123. else:
  124. self.error(u"表格没有toparent_key字段,执行默认按键return")
  125. self.sendKey('return')
  126. '''
  127. 函数:是否在父节点菜单上。一般在执行了callFirstOptionShortCutKey后调用;
  128. 参数:
  129. 返回:
  130. 注意:由于所有父节点上的子项都共用一个图片定位参数,所以只要随意一个父节点的子项option即可获取定位参数;
  131. 示例:
  132. 测试:。
  133. '''
  134. def isOnFirstOption(self):
  135. pic = self.takePicture()
  136. return self.__optionFocus.findFocusByIcon(pic, self.__optionPaths['First']['option'])[0]
  137. '''
  138. 函数:是否在当前节点(移动后,判断是否移动到下一目标节点)上.
  139. 说明:
  140. 每次移动到下一目标节点(option)上时,self.__pos + 1,表示移动到下一层路径。
  141. 当self.__pos >= self.__optionPaths.__len__()时,表示到达value表格;
  142. 所以,该类的重点在self.__pos的移动;
  143. 参数:
  144. 返回:Boolean, 识别的文本/数字;
  145. 示例:
  146. 测试:。
  147. '''
  148. def isOnTargetNode(self):
  149. # 是否在value表中;
  150. isValueSheet = self.isOnValueSheet()
  151. self.info(u"当前层级在:%s" % ("value表" if isValueSheet else "路径表"))
  152. # 析出参数;
  153. if isValueSheet:
  154. curLevel = 'value'
  155. curParent = self.__optionPaths[g_level[self.__pos-1]]['parent']
  156. curOption = self.__optionValueInfo['option']
  157. curOthers = self.__optionValueInfo['others']
  158. else:
  159. curLevel = g_level[self.__pos]
  160. curParent = self.__optionPaths[curLevel]['parent']
  161. curOption = self.__optionPaths[curLevel]['option']
  162. curOthers = self.__optionPaths[curLevel]['others']
  163. self.info("当前[%s]others=[%s]" % (curOption, curOthers))
  164. if curOthers.__len__() == 0:
  165. curOthers = {}
  166. else:
  167. curOthers = json.loads(curOthers)
  168. firstParent = self.__optionPaths['First']['parent']
  169. # 获取文本识别的参数;
  170. ocrConfigList = self.__optionConfig.getOptionOCRConfig(curOption)
  171. ocrThreshold = self.__optionConfig.getThresholdDict(firstParent) # 注意,此处使用firstParent;
  172. # 获取当前option的ocr值/value name下所有ocr值;
  173. if isValueSheet:
  174. if curOption.lower() == self.__optionName.lower():
  175. optionTextList = self.__optionExcel.getOptionValueText(curOption, self.__optionValue)
  176. else:
  177. optionTextList = self.__optionExcel.getOptionValueText(curOption)
  178. else:
  179. optionTextList = self.__optionExcel.getOptionText(curOption)
  180. # 获取option下所有兄弟项的ocr:option字典内容;
  181. siblingTextDict = self.__optionExcel.getOptionAllSiblingItemDict(curOption, not isValueSheet)
  182. # 获取所有option兄弟项(包括自己)的ocr值;
  183. siblingTextList = list(siblingTextDict.keys())
  184. # 是否获取数值文本;
  185. isNumberText = False
  186. # 如果是value表,且兄弟项文本为range
  187. # 注:value表中的option实际并没有兄弟项,取的是所有value项
  188. if isValueSheet and siblingTextList.__len__():
  189. if siblingTextList[0].startswith('range('):
  190. self.info(u"识别的内容是value表数字内容(range(0,xx)类型)")
  191. isNumberText = True
  192. # 清除之前的value值;
  193. self.__optionValueText = ""
  194. # 是否为静态焦点识别(动态则为跑马灯);
  195. if curOthers.__len__() and 'marquee' in curOthers:
  196. return self.__getDynamicPicText(curOption, optionTextList, siblingTextList, ocrConfigList,
  197. ocrThreshold, curOthers['marquee'],
  198. isNumberText, isValueSheet)
  199. else:
  200. return self.__getStaticPicText(self.takePicture(), curOption, optionTextList, siblingTextList, ocrConfigList,
  201. ocrThreshold,
  202. isNumberText, isValueSheet)
  203. # endif
  204. '''
  205. 函数:是否移到目标节点上(在isOnOption后,判断__pos位置是否在__paths最后);
  206. 参数:
  207. 返回:Boolean.
  208. '''
  209. def isOnTargetOption(self):
  210. return True if self.__pos == (self.__optionPaths.__len__() - 1) else False
  211. '''
  212. 函数:当前节点是否在value sheet层级中;
  213. 参数:无
  214. 返回:Boolean
  215. '''
  216. def isOnValueSheet(self):
  217. if self.__optionValue == "": # 该值空,表明不会移动到value sheet中;
  218. return False
  219. else:
  220. return True if self.__pos >= self.__optionPaths.__len__() else False
  221. '''
  222. 函数:移动到下一兄弟节点;
  223. 参数:无
  224. 返回:无
  225. 注意:
  226. sendKey的等待时间太短,会导致画面未响应,截图时还是截上一状态的,比如0.1秒就很经常出现这问题。
  227. 同时,按键等待时间,应该有所区分。
  228. 如果不截图,可以不考虑sendKey的等待时间.
  229. '''
  230. def move2NextSiblingNode(self):
  231. # 获取当前option信息;
  232. if self.getCurOptionInfo():
  233. # 析出move key;
  234. optionMoveKey = self.__curOptionInfo['option_move_key']
  235. if optionMoveKey.__len__() == 0:
  236. self.sendKey(self.__curOptionInfo['move_key'][1], 1, 1)
  237. else:
  238. self.sendKey(optionMoveKey[1], 1, 1)
  239. else:
  240. valueMoveKey = self.__optionValueInfo['move_key']
  241. self.sendKey(valueMoveKey[1], 1, 1)
  242. '''
  243. 函数:移动到上一兄弟节点
  244. 参数:无
  245. 返回:无
  246. 注意:
  247. sendKey的等待时间太短,会导致画面未响应,截图时还是截上一状态的,比如0.1秒就很经常出现这问题。
  248. 同时,按键等待时间,应该有所区分。
  249. 如果不截图,可以不考虑sendKey的等待时间.
  250. '''
  251. def move2PrevSiblingNode(self):
  252. # 获取当前option信息;
  253. if self.getCurOptionInfo():
  254. # 析出move key;
  255. optionMoveKey = self.__curOptionInfo['option_move_key']
  256. if optionMoveKey.__len__() == 0:
  257. self.sendKey(self.__curOptionInfo['move_key'][0], 1, 1)
  258. else:
  259. self.sendKey(optionMoveKey[0], 1, 1)
  260. else:
  261. valueMoveKey = self.__optionValueInfo['move_key']
  262. self.sendKey(valueMoveKey[0], 1, 1)
  263. '''
  264. 函数:返回到父节点
  265. 参数:无
  266. 返回:无
  267. 注意:
  268. sendKey的等待时间太短,会导致画面未响应,截图时还是截上一状态的,比如0.1秒就很经常出现这问题。
  269. 同时,按键等待时间,应该有所区分。
  270. 如果不截图,可以不考虑sendKey的等待时间.
  271. '''
  272. def back2ParentNode(self):
  273. # 获取当前option信息;
  274. if self.getCurOptionInfo():
  275. backKey = self.__curOptionInfo['back_key']
  276. else:
  277. backKey = self.__optionValueInfo['back_key']
  278. if backKey.__len__() == 0:
  279. self.sendKey('return', 1, 1)
  280. else:
  281. self.sendKey(backKey, 1, 1)
  282. # 返回,自减;
  283. self.__pos -= 1
  284. '''
  285. 函数:进入当前节点,只对路径节点有效,value节点不处理;
  286. 参数:无
  287. 返回:无
  288. '''
  289. def enterNode(self):
  290. # 获取当前option信息;
  291. if self.getCurOptionInfo():
  292. # 是否有等待时间
  293. waitTime = self.__optionConfig.getParentWaitTime(self.__curOptionInfo['parent'])
  294. # 析出enter key;
  295. optionEnterKey = self.__curOptionInfo['option_enter_key']
  296. if optionEnterKey.__len__() == 0:
  297. self.sendKey(self.__curOptionInfo['enter_key'], 1, waitTime)
  298. else:
  299. self.sendKey(optionEnterKey, 1, waitTime)
  300. # 进入下层,自增
  301. self.__pos += 1
  302. else:
  303. self.info(u"节点已在value上,无法再进入")
  304. '''
  305. 函数:设置当前节点位置;
  306. 参数:
  307. pos: 外部节点位置值。
  308. 返回:无
  309. 注意:此函数用于外部创建两个【路径具体子集关系】的COptionAction对象,才可以使用此函数。
  310. 假设, a对象路径{p,p1,p2,p3,p4, v1},b = {p, p1, p2, p3, p4, p5, p6, v2}, c = {p, p2, p5, p6, v3}
  311. 其中, v表示value值,不属于路径。那么,a和b具有子集关系, a与c或b与c都不具有子集关系。
  312. a移动到p4上,完成了v1设置后,a.back2ParentNode()后,此时如果要操作b并设置v2,就要b.SetCurPos(a.pos).
  313. '''
  314. def setCurPos(self, pos):
  315. if pos < 0 or pos > self.__optionPaths.__len__():
  316. self.error(u"pos值[%d]超出路径范围:[0-%d]" % (pos, self.__optionPaths.__len__()))
  317. return
  318. self.__pos = pos
  319. '''
  320. 函数:设置目标option的值, 只设置数值型value和输入型value(选择型value不需要此步骤).
  321. 参数:
  322. 返回:
  323. 注意:此函数必须是已聚焦在目标value节点上,否则无效。
  324. 重要:
  325. 在此函数enter后,UI是否返回到上一层父节点上,还是停留在本层节点不返回。
  326. 建议在excel中配置这个关键信息,以便此函数可以正确更改self.__pos的值。
  327. '''
  328. def setOptionValue(self):
  329. self.info(u"【在此函数enter后,UI是否返回到上一层父节点上,还是停留在本层节点不返回。\
  330. 建议在excel中配置这个关键信息,以便此函数可以正确更改self.__pos的值。】")
  331. if type(self.__optionValue) == str and self.__optionValue.__len__() == 0:
  332. self.error(u"[%s]的值为空,没有设置的值" % self.__optionName)
  333. return
  334. enterKey = self.__optionValueInfo['enter_key']
  335. moveKey = self.__optionValueInfo['move_key']
  336. valueText = self.__optionValueInfo['value_for_ocr']
  337. others = self.__optionValueInfo['others']
  338. if others.__len__():
  339. others = json.loads(others)
  340. else:
  341. others = {}
  342. # 是否有按键延时值;
  343. duration = float(others['duration']) if "duration" in others else 0.1
  344. # 是否为数字文本(特指:range(0, 100));
  345. isNumberText = self.isNumberText(valueText)
  346. # 数值型value;
  347. if isNumberText:
  348. if moveKey[0] == 'input':
  349. # 将数值转成字符;
  350. optionValue = self.__optionValue
  351. if type(optionValue) == int or type(optionValue) == float:
  352. optionValue = str(self.__optionValue)
  353. # 再将字符转成list;
  354. chList = list(optionValue)
  355. self.sendKey(chList, 1, duration)
  356. else:
  357. # 相差值;
  358. num = int(self.__optionValue) - int(self.__optionValueText)
  359. # 正->往右或下,负->往左或上;
  360. self.sendKey(moveKey[1] if num > 0 else moveKey[0], abs(num), duration)
  361. elif moveKey[0] == 'input':
  362. # 将字符转成list;
  363. chList = list(self.__optionValue)
  364. self.sendKey(chList, 1, duration)
  365. # 最后,如果有进入键执行;
  366. if enterKey != 'default':
  367. self.sendKey(enterKey, 1, 0.1)
  368. '''
  369. 函数:
  370. 参数:
  371. 返回:
  372. '''
  373. def getCurOptionInfo(self):
  374. if self.__optionPaths.__len__() == 0:
  375. self.error(u"paths路径空")
  376. return False
  377. if self.__pos >= self.__optionPaths.__len__():
  378. self.warn(u"已到达value节点,无法获取路径信息")
  379. return False
  380. # 只有第一次或层级移动了才需要更新;
  381. if self.__curOptionInfo is None or self.__pos != self.__curOptionInfo['layers']:
  382. self.__curOptionName = self.__optionPaths[g_level[self.__pos]]['option']
  383. outResult, outData = self.__optionExcel.getOptionInfo(self.__curOptionName, self.__optionPaths)
  384. if outResult is False:
  385. return False
  386. self.__curOptionInfo = outData
  387. return True
  388. '''
  389. 函数:检测路径是否有效;
  390. 参数:
  391. 返回:Boolean
  392. '''
  393. def checkRunOptionPath(self):
  394. outData = self.__optionExcel.checkOptionPaths(self.__optionPaths)
  395. if str(outData[1]) == 'NoExit':
  396. self.error(u"表格中不存在到达Option:[%s]的路径,在表格中排查到达该Option路径" % self.__optionName)
  397. return False
  398. if str(outData[1]) == 'Fail':
  399. self.error(u"表格中到达Option:[%s]的路径出现数据断层找不到First层级,在表格中排查到达该Option路径" % self.__optionName)
  400. return False
  401. return True
  402. '''
  403. 函数:
  404. 参数:
  405. 返回:
  406. '''
  407. def isNumberText(self, textList):
  408. # 是否获取数值文本;
  409. isNumberText = False
  410. # 如果是value表,且兄弟项文本为range
  411. # 注:value表中的option实际并没有兄弟项,取的是所有value项
  412. if self.isOnValueSheet() and textList.__len__():
  413. if textList[0].startswith('range('):
  414. self.info(u"识别的内容是value表数字内容(range(0,xx)类型)")
  415. isNumberText = True
  416. return isNumberText
  417. '''
  418. 函数:获取静态图片文本内容
  419. 参数:
  420. 注意:
  421. 返回:Boolean、文本识别内容
  422. 测试:。
  423. '''
  424. def __getStaticPicText(self, pic, optionName, optionTextList, siblingTextList, ocrConfigList, ocrThreshold,
  425. isNumberText,
  426. isValueSheet, aliveKey=None):
  427. # 获取图片焦点框;
  428. found, focusBox = self.__optionFocus.findFocusByIcon(pic, optionName, isValueSheet)
  429. if found is False:
  430. self.debug(u"未找到[%s]聚集框" % optionName)
  431. return False, None
  432. # 如果有鲜活键;
  433. self.sendAliveKey(aliveKey)
  434. # 获取文本区域框;
  435. textBox = self.__optionFocus.getFocusTextBox(optionName, focusBox, isValueSheet)
  436. # 配置文本图片路径,保存文本区域截图;
  437. text_pic = os.path.join(getSATTmpDIR(), "meuttree_area_text.png")
  438. self.__imgCMP.saveCropPic(pic, text_pic, textBox)
  439. if not os.path.exists(text_pic):
  440. self.error(u"%s截取文本图片失败:%s" % (optionName, text_pic))
  441. return False, None
  442. # 是否在某个兄弟项中;
  443. isOnSibling = False
  444. # 遍历所有ocr识别选项;
  445. for ocrConfig in ocrConfigList:
  446. # 如果有鲜活键;
  447. self.sendAliveKey(aliveKey)
  448. # 识别出当前聚焦文本;
  449. curFocusText = self.__optionOCR.getImageText(text_pic, ocrConfig, ocrThreshold)
  450. # 判断识别文本是来正常;
  451. if curFocusText == "ERR<Exp>" or curFocusText.__len__() == 0:
  452. continue
  453. # 转成小写;
  454. curFocusText = curFocusText.lower()
  455. self.info("[%s]当前识别出的文本=%s" % (optionName, curFocusText))
  456. # 是否取数字文本(肯定在value节点上);
  457. if isNumberText is True:
  458. # 特殊情况处理:某些情况下,会将包含数字以外的区域一起识别;
  459. curFocusText = curFocusText.strip('>')
  460. # 将数字分组
  461. numberTextList = strSplit(curFocusText)
  462. # 只判断最后一位是否为数字;
  463. if numberTextList.__len__() < 1:
  464. self.error(u"当前识别的文本不是数字文本:%s" % curFocusText)
  465. continue
  466. try:
  467. numberText = float(numberTextList[numberTextList.__len__() - 1])
  468. # 记录value值;
  469. self.__optionValueText = numberText
  470. return True, numberText
  471. except Exception:
  472. continue
  473. else:
  474. # 当前option识别的文本与所有兄弟项文本比较;
  475. for siblingText in siblingTextList:
  476. # 转为小写,保证所有比较都是小写;
  477. siblingText = siblingText.lower()
  478. # 兄弟项文本是否被包含在curFocusText中或相同;
  479. if siblingText in curFocusText or strcmp(siblingText, curFocusText):
  480. isOnSibling = True
  481. self.info(u"当前焦点在[%s], 目标焦点为[%s]" % (siblingText, optionName))
  482. # 再判断,该兄弟项是否为目标节点(curOption);
  483. for optionText in optionTextList:
  484. optionText = optionText.lower()
  485. # 若当前兄弟项为目标option返回True、文本;
  486. if strcmp(optionText, siblingText):
  487. return True, curFocusText
  488. # endif
  489. # endfor
  490. # 在兄弟项中,退出循环;
  491. break
  492. # endif
  493. # endfor
  494. if isOnSibling is False:
  495. self.error(u"未聚集到任何[%s]的兄弟项中" % optionName)
  496. else:
  497. self.info("未聚集到目标节点[%s],当前文本=%s" % (optionName, curFocusText))
  498. return False, curFocusText
  499. # endif
  500. # endfor
  501. # 默认返回;
  502. return False, 0 if isNumberText else ""
  503. '''
  504. 函数:获取动态图片文本内容
  505. 参数:
  506. 返回:返回:Boolean、文本识别内容
  507. '''
  508. def __getDynamicPicText(self, optionName, optionTextList, siblingTextList, ocrConfigList, ocrThreshold, marqueeDict,
  509. isNumberText, isValueSheet):
  510. # 判断图片是否动态:截图两次,判断两次文本内容是否相同;
  511. firstRetsult, firstText = self.__getStaticPicText(self.takePicture(), optionName, optionTextList,
  512. siblingTextList, ocrConfigList,
  513. ocrThreshold, isNumberText, isValueSheet)
  514. if firstRetsult is False:
  515. self.error(u"[%s]第一次截图未识别出聚焦框" % optionName)
  516. return False, None
  517. # 发送鲜活键, 保证界面鲜活;
  518. self.sendAliveKey(marqueeDict['alive_key'])
  519. # 第二次截图;
  520. secondRetsult, secondText = self.__getStaticPicText(self.takePicture(), optionName, optionTextList,
  521. siblingTextList, ocrConfigList,
  522. ocrThreshold, isNumberText, isValueSheet)
  523. if secondRetsult is False:
  524. self.error(u"[%s]第二次截图未识别出聚焦框" % optionName)
  525. return False, None
  526. # 发送鲜活键, 保证界面鲜活;
  527. self.sendAliveKey(marqueeDict['alive_key'])
  528. # 比较两文本是否相同;
  529. if firstText.__len__() and firstText == secondText:
  530. self.info(u"截图两次,文本(%s)识别相同,聚焦的不是跑马灯Option" % firstText)
  531. return False, firstText
  532. # 文本不相同,为动态图片;
  533. menuList = marqueeDict['menu']
  534. # 如果只有一项跑马灯,且目标option亦在跑马灯列表中,则直接返回成功结果
  535. if menuList.__len__() == 1 and (optionName in menuList):
  536. self.info(u"该层菜单只有一项跑马灯, 找到即成功返回")
  537. return True, firstText
  538. picList = []
  539. # 如果有多项同级option都是跑马灯, 要成功识别文本需要间隔截图5次(大概会成功截图到最全的文本);
  540. for i in range(0, 5):
  541. picList.append(self.takePicture())
  542. # 间隔多久截图;
  543. time.sleep(marqueeDict['sleep_time'])
  544. # 发送鲜活键;
  545. self.sendAliveKey(marqueeDict['alive_key'])
  546. ocrTextList = []
  547. # 对截图进行文本识别分析;
  548. for pic in picList:
  549. result, text = self.__getStaticPicText(pic, optionName, optionTextList, siblingTextList, ocrConfigList,
  550. ocrThreshold, isNumberText, isValueSheet, marqueeDict['alive_key'])
  551. if result is True:
  552. ocrTextList.append(text)
  553. # 发送鲜活键;
  554. self.sendAliveKey(marqueeDict['alive_key'])
  555. # 过滤重复的字符;
  556. ocrTextList = self.__removeDuplicateString(ocrTextList)
  557. self.info(u"识别到的跑马灯ocr文字列表:%s" % ocrTextList)
  558. # 获取动态文本的option字典;
  559. dynamicOptionOcrDict = self.__getOptionInfoDict(menuList)
  560. self.info(u"获取到的跑马灯Option对应的ocr字典:%s" % dynamicOptionOcrDict)
  561. # 遍历:识别结果与xls结果进行比较;
  562. for dynamicOption in dynamicOptionOcrDict:
  563. dynamicOptionOcrList = dynamicOptionOcrDict[dynamicOption]
  564. for dynamicOptionOcr in dynamicOptionOcrList:
  565. # 只要有3张满足,判断找到option;
  566. count = 0
  567. for ocrText in ocrTextList:
  568. if ocrText.lower() in dynamicOptionOcr:
  569. count += 1
  570. if count >= 3 and optionName == dynamicOption:
  571. return True, ocrText
  572. else:
  573. self.info(u"当前聚焦的跑马灯实际为:%s" % dynamicOption)
  574. return False, ocrText
  575. # endfor
  576. # endfor
  577. self.info("未能识别到当前聚焦的跑马灯Option")
  578. return False, 0 if isNumberText else None
  579. '''
  580. 函数:
  581. 参数:
  582. 返回:
  583. '''
  584. def __getOptionInfoDict(self, optionNameList):
  585. OptionInfoDict = {}
  586. for optionName in optionNameList:
  587. found, optionDict = self.__optionExcel(optionName)
  588. if found:
  589. OptionInfoDict[optionName] = optionDict
  590. # endif
  591. # endfor
  592. return OptionInfoDict
  593. '''
  594. 函数:找到两个字符串左边或者右边相同的部分
  595. 参数:
  596. 返回:
  597. '''
  598. def __findDuplicateString(self, str1, str2, direction="right"):
  599. index = 0
  600. if direction == "right":
  601. while True:
  602. index -= 1
  603. if abs(index) > str1.__len__() or abs(index) > str2.__len__():
  604. break
  605. if not str1[index] == str2[index]:
  606. break
  607. if index == -1:
  608. self.info(u"没有找到重复文本")
  609. return ""
  610. return str1[index + 1:]
  611. elif direction == "left":
  612. while True:
  613. if abs(index) >= str1.__len__() or abs(index) >= str2.__len__():
  614. break
  615. if not str1[index] == str2[index]:
  616. break
  617. index += 1
  618. return str1[:index]
  619. '''
  620. 函数:去掉字符串数组中每个字符串 左边或右边相同的部分
  621. 参数:
  622. 返回:
  623. '''
  624. def __removeDuplicateString(self, strList):
  625. finishedList = strList
  626. directionList = ["left", "right"]
  627. for direction in directionList:
  628. same_str = self.__findDuplicateString(strList[0], strList[1], direction)
  629. if same_str == "":
  630. continue
  631. else:
  632. for i in range(2, strList.__len__()):
  633. same_str = self.__findDuplicateString(same_str, strList[i], direction)
  634. if same_str == "":
  635. break
  636. if same_str != "":
  637. finishedList = []
  638. for text in strList:
  639. if direction == "left":
  640. text = str[same_str.__len__():]
  641. else:
  642. text = str[:-same_str.__len__()]
  643. finishedList.append(text)
  644. # endfor
  645. # endif
  646. # endif
  647. # endfor
  648. return finishedList
  649. '''
  650. 函数:发送红老鼠按键;
  651. 参数:
  652. key 1、如果是字符串时,当作单个按键; 2、如果是list时,当作多个按键;
  653. count 执行多少次key;
  654. wait 1、执行单个key后,等待时长(因为电视机响应遥控需要时间);
  655. 2、执行list多个key后,每个key的等待时间;
  656. 返回:无
  657. '''
  658. def sendKey(self, key, count=1, wait=1):
  659. if key is not None and key.__len__() > 0:
  660. if type(key) == list:
  661. for k in key:
  662. # 清除前后空格;
  663. k = k.lstrip()
  664. k = k.rstrip()
  665. COptionAction.__redRat3.sendKey(k, 1, wait)
  666. else:
  667. key = str(key)
  668. # 清除前后空格;
  669. key = key.lstrip()
  670. key = key.rstrip()
  671. COptionAction.__redRat3.sendKey(key, count, wait)
  672. else:
  673. self.error(u"error:按键内容空")
  674. '''
  675. 函数:发送鲜活键;
  676. 参数:
  677. aliveKey 鲜活按键;
  678. 返回:无
  679. 注意:鲜活键等待时间是0.1秒,因为不考虑截图。
  680. '''
  681. def sendAliveKey(self, aliveKey):
  682. self.sendKey(aliveKey, 1, 0.1)
  683. if __name__ == "__main__":
  684. exData = CExtraData()
  685. optionExcel = COptionExcel(exData)
  686. optionConfig = COptionConfig(exData, optionExcel)
  687. optionAction = COptionAction('picture', '', optionConfig, optionExcel)
  688. # ====================================== #
  689. optionAction.callFirstOptionShortCutKey()