AiController.py 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. @Copyright (C) ansjer cop Video Technology Co.,Ltd.All rights reserved.
  5. @software: PyCharm
  6. @Version: python3.6
  7. @MODIFY DECORD:ansjer dev
  8. """
  9. import base64
  10. import json
  11. import logging
  12. import os
  13. import threading
  14. import time
  15. import apns2
  16. import boto3
  17. import jpush
  18. from boto3.session import Session
  19. from django.views.generic.base import View
  20. from pyfcm import FCMNotification
  21. from AnsjerPush.config import AI_IDENTIFICATION_TAGS_DICT, CONFIG_US, CONFIG_EUR
  22. from AnsjerPush.config import APNS_MODE, APNS_CONFIG, BASE_DIR, \
  23. JPUSH_CONFIG, FCM_CONFIG
  24. from AnsjerPush.config import CONFIG_INFO
  25. from AnsjerPush.config import PUSH_BUCKET
  26. from Model.models import UidPushModel, AiService, VodHlsTag, VodHlsTagType
  27. from Object import MergePic
  28. from Object.DynamodbObject import DynamodbObject
  29. from Object.ETkObject import ETkObject
  30. from Object.OCIObjectStorage import OCIObjectStorage
  31. from Object.RedisObject import RedisObject
  32. from Object.ResponseObject import ResponseObject
  33. from Object.SageMakerAiObject import SageMakerAiObject
  34. from Object.TokenObject import TokenObject
  35. from Object.enums.MessageTypeEnum import MessageTypeEnum
  36. from Service.CommonService import CommonService
  37. from Service.DevicePushService import DevicePushService
  38. from Service.EquipmentInfoService import EquipmentInfoService
  39. from Object.NovaImageTagObject import NovaImageTagObject
  40. from datetime import datetime, timedelta
  41. from django.conf import settings
  42. AWS_ACCESS_KEY_ID = settings.AWS_ACCESS_KEY_ID
  43. AWS_SECRET_ACCESS_KEY = settings.AWS_SECRET_ACCESS_KEY
  44. TIME_LOGGER = logging.getLogger('time')
  45. # 1. 声明一个全局变量,用于存放创建好的实例
  46. nova_image_tag_instance = None
  47. # 2. 根据条件,创建实例并赋值给这个全局变量
  48. # 这段代码会放在文件的最顶部,它只在 Gunicorn worker 进程启动时执行一次
  49. if CONFIG_INFO == CONFIG_EUR:
  50. nova_image_tag_instance = NovaImageTagObject(
  51. AWS_ACCESS_KEY_ID[1], AWS_SECRET_ACCESS_KEY[1], 'eu-west-2'
  52. )
  53. else:
  54. nova_image_tag_instance = NovaImageTagObject(
  55. AWS_ACCESS_KEY_ID[1], AWS_SECRET_ACCESS_KEY[1], 'us-east-1'
  56. )
  57. # AI服务
  58. class AiView(View):
  59. def get(self, request, *args, **kwargs):
  60. request.encoding = 'utf-8'
  61. operation = kwargs.get('operation')
  62. return self.validation(request.GET, request, operation)
  63. def post(self, request, *args, **kwargs):
  64. request.encoding = 'utf-8'
  65. operation = kwargs.get('operation')
  66. return self.validation(request.POST, request, operation)
  67. def validation(self, request_dict, request, operation):
  68. response = ResponseObject()
  69. if operation is None:
  70. return response.json(444, 'error path')
  71. elif operation == 'identification': # ai识别
  72. return self.do_ai_identification(request.POST, response)
  73. elif operation == 'aiPushStats': # ai调用统计
  74. return self.ai_push_stats(request_dict, response)
  75. else:
  76. token = request_dict.get('token', None)
  77. # 设备主键uid
  78. tko = TokenObject(token)
  79. response.lang = tko.lang
  80. if tko.code != 0:
  81. return response.json(tko.code)
  82. userID = tko.userID
  83. if operation == 'identification': # ai识别
  84. return self.do_ai_identification(request_dict, response)
  85. else:
  86. return response.json(414)
  87. def do_ai_identification(self, request_dict, response):
  88. etk = request_dict.get('etk', None)
  89. n_time = request_dict.get('n_time', None)
  90. channel = request_dict.get('channel', '1')
  91. receiveTime = int(time.time())
  92. TIME_LOGGER.info('*****进入into----ai--api,etk={etk}'.format(etk=etk))
  93. if not etk:
  94. return response.json(444)
  95. dir_path = ''
  96. uid = ''
  97. try:
  98. # 解密uid及判断长度
  99. eto = ETkObject(etk)
  100. uid = eto.uid
  101. TIME_LOGGER.info(f'etk解析uid={uid},n_time={n_time},etk:{etk}')
  102. if len(uid) != 20 and len(uid) != 14:
  103. return response.json(444)
  104. # 通过uid查出endTime是否过期,并且ai开关是否打开
  105. AiServiceQuery = AiService.objects.filter(uid=uid, detect_status=1, use_status=1, endTime__gt=receiveTime) \
  106. .values('detect_group', 'orders__payType', 'addTime')
  107. if not AiServiceQuery.exists():
  108. TIME_LOGGER.info(f'uid={uid}AI服务未开通或已到期')
  109. return response.json(173)
  110. detect_group = AiServiceQuery[0]['detect_group']
  111. file_post_one = request_dict.get('fileOne', None)
  112. file_post_two = request_dict.get('fileTwo', None)
  113. file_post_three = request_dict.get('fileThree', None)
  114. file_list = [file_post_one, file_post_two, file_post_three]
  115. del file_post_one, file_post_two, file_post_three
  116. if not all(file_list):
  117. for k, val in enumerate(file_list):
  118. if not val:
  119. TIME_LOGGER.error('{}缺少图片{}'.format(uid, k))
  120. return response.json(444, '缺少第{k}张图'.format(k=k + 1))
  121. redis_obj = RedisObject(db=6)
  122. ai_key = f'PUSH:AI:{uid}:{channel}'
  123. ai_data = redis_obj.get_data(ai_key)
  124. if ai_data:
  125. return response.json(0, {'msg': 'Push again in one minute'})
  126. # === AI 调用计数===
  127. n_time_int = int(n_time) if n_time else int(time.time())
  128. date_str = time.strftime("%Y%m%d", time.localtime(n_time_int))
  129. self.ai_push_count(uid, date_str)
  130. # =========================================
  131. # 查询推送数据
  132. uid_push_qs = UidPushModel.objects.filter(uid_set__uid=uid). \
  133. values('token_val', 'app_type', 'appBundleId', 'm_code', 'push_type', 'userID_id',
  134. 'userID__NickName',
  135. 'lang', 'm_code', 'tz', 'uid_set__nickname', 'uid_set__detect_interval',
  136. 'uid_set__detect_group', 'uid_set__new_detect_interval',
  137. 'uid_set__channel', 'uid_set__msg_notify')
  138. if not uid_push_qs.exists():
  139. TIME_LOGGER.info(f'uid={uid},用户没有开启AI推送')
  140. return response.json(173)
  141. ai_server = 'sageMaker'
  142. if AiServiceQuery[0]['orders__payType'] == 10: # AI首次体验前半个月调Rekognition
  143. now_time = int(time.time())
  144. add_time = AiServiceQuery[0]['addTime']
  145. if (now_time - add_time) <= (3600 * 24 * 2):
  146. ai_server = 'rekognition'
  147. elif (now_time - add_time) <= (3600 * 24 * 3):
  148. ai_server = 'novaLite'
  149. APP_NOTIFY_KEY = f'ASJ:NOTIFY:PUSH:{uid}:{channel}' # 推送间隔缓存KEY
  150. push_cache_data = redis_obj.get_data(APP_NOTIFY_KEY)
  151. is_push = False if push_cache_data else True
  152. notify_data = uid_push_qs[0]['uid_set__msg_notify']
  153. # APP推送提醒状态
  154. notify = self.is_ai_push(uid, notify_data) if is_push else is_push
  155. # nova_key = f'PUSH:NOVA:LITE:{uid}'
  156. # nova = redis_obj.get_data(nova_key)
  157. if ai_server == 'novaLite': # AWS AI模型
  158. sage_maker = SageMakerAiObject()
  159. # AI nova识别异步存表&推送
  160. push_thread = threading.Thread(
  161. target=self.async_detection_image_label,
  162. kwargs={'sage_maker': sage_maker, 'uid': uid, 'n_time': n_time, 'uid_push_qs': uid_push_qs,
  163. 'channel': channel, 'file_list': file_list, 'notify': notify, 'detect_group': detect_group})
  164. push_thread.start()
  165. self.add_push_cache(APP_NOTIFY_KEY, redis_obj, push_cache_data,
  166. uid_push_qs[0]['uid_set__new_detect_interval'])
  167. redis_obj.set_data(ai_key, uid, 60)
  168. return response.json(0)
  169. if ai_server == 'sageMaker': # 自建模型sageMaker AI
  170. sage_maker = SageMakerAiObject()
  171. ai_result = sage_maker.sage_maker_ai_server(uid, file_list) # 图片base64识别AI标签
  172. if ai_result:
  173. if ai_result == 'imageError':
  174. return response.json(0)
  175. res = sage_maker.get_table_name(uid, ai_result, detect_group)
  176. if not res: # 当前识别结果未匹配
  177. return response.json(0)
  178. push_thread = threading.Thread(
  179. target=self.async_message_push,
  180. kwargs={'sage_maker': sage_maker, 'uid': uid, 'n_time': n_time, 'uid_push_qs': uid_push_qs,
  181. 'channel': channel, 'res': res, 'file_list': file_list, 'notify': notify})
  182. push_thread.start() # AI识别异步存表&推送
  183. self.add_push_cache(APP_NOTIFY_KEY, redis_obj, push_cache_data,
  184. uid_push_qs[0]['uid_set__new_detect_interval'])
  185. redis_obj.set_data(ai_key, uid, 60)
  186. return response.json(0)
  187. TIME_LOGGER.info(f'uid={uid},sagemakerAI识别失败{ai_result}')
  188. push_thread = threading.Thread(target=self.image_label_detection,
  189. kwargs={'ai_server': ai_server, 'uid': uid, 'file_list': file_list,
  190. 'detect_group': detect_group, 'n_time': n_time,
  191. 'uid_push_qs': uid_push_qs,
  192. 'channel': channel})
  193. push_thread.start() # AI识别异步存表&推送
  194. redis_obj.set_data(ai_key, uid, 60)
  195. return response.json(0)
  196. except Exception as e:
  197. print(e)
  198. data = {
  199. 'errLine': e.__traceback__.tb_lineno,
  200. 'errMsg': repr(e)
  201. }
  202. TIME_LOGGER.info(f'rekognition识别errMsg={data}')
  203. return response.json(48, data)
  204. def async_message_push(self, sage_maker, uid, n_time, uid_push_qs, channel, res, file_list, notify):
  205. # 保存推送消息
  206. sage_maker.save_push_message(uid, n_time, uid_push_qs, channel, res, file_list, notify)
  207. def add_push_cache(self, key, redis_obj, cache_push_data, push_interval):
  208. """
  209. 推送间隔缓存设置
  210. """
  211. if push_interval > 0:
  212. if cache_push_data: # 缓存存在
  213. interval = json.loads(cache_push_data)['interval']
  214. if interval != push_interval:
  215. push_data = {'interval': push_interval}
  216. redis_obj.set_data(key=key, val=json.dumps(push_data), expire=push_interval)
  217. else: # 缓存不存在
  218. push_data = {'interval': push_interval}
  219. redis_obj.set_data(key=key, val=json.dumps(push_data), expire=push_interval)
  220. def image_label_detection(self, ai_server, uid, file_list, detect_group,
  221. n_time, uid_push_qs, channel):
  222. """
  223. :param ai_server: AI服务类型
  224. :param uid: 用户uid
  225. :param file_list: 图片base64列表
  226. :param detect_group: 识别组
  227. :param n_time: 时间戳
  228. :param uid_push_qs: 推送数据
  229. :param channel: 推送通道
  230. :return:
  231. """
  232. try:
  233. start_time = time.time()
  234. redis_obj = RedisObject(db=6)
  235. APP_NOTIFY_KEY = f'ASJ:NOTIFY:PUSH:{uid}:{channel}' # 推送间隔缓存KEY
  236. push_cache_data = redis_obj.get_data(APP_NOTIFY_KEY)
  237. is_push = False if push_cache_data else True
  238. notify_data = uid_push_qs[0]['uid_set__msg_notify']
  239. # APP推送提醒状态
  240. notify = self.is_ai_push(uid, notify_data) if is_push else is_push
  241. TIME_LOGGER.info(f'*****现执行Reko,uid={uid}识别类型={ai_server}')
  242. dir_path = os.path.join(BASE_DIR, 'static/ai/' + uid + '/' + str(n_time))
  243. if not os.path.exists(dir_path):
  244. os.makedirs(dir_path)
  245. file_path_list = []
  246. for i, val in enumerate(file_list):
  247. val = val.replace(' ', '+')
  248. val = base64.b64decode(val)
  249. file_path = "{dir_path}/{n_time}_{i}.jpg".format(dir_path=dir_path, n_time=n_time, i=i)
  250. file_path_list.append(file_path)
  251. with open(file_path, 'wb') as f:
  252. f.write(val)
  253. f.close()
  254. image_size = 0 # 每张小图片的大小,等于0是按原图大小进行合并
  255. image_colnum = 1 # 合并成一张图后,一行有几个小图
  256. image_size = MergePic.merge_images(dir_path, image_size, image_colnum)
  257. photo = open(dir_path + '.jpg', 'rb') # 打开合成图
  258. # rekognition识别合成图片
  259. maxLabels = 50 # 最大标签
  260. minConfidence = 80 # 置信度
  261. client = boto3.client(
  262. 'rekognition',
  263. aws_access_key_id=AWS_ACCESS_KEY_ID[1],
  264. aws_secret_access_key=AWS_SECRET_ACCESS_KEY[1],
  265. region_name='us-east-1')
  266. # 执行AWS Rekognition:
  267. rekognition_res = client.detect_labels(
  268. Image={'Bytes': photo.read()},
  269. MaxLabels=maxLabels,
  270. MinConfidence=minConfidence)
  271. photo.close()
  272. if rekognition_res['ResponseMetadata']['HTTPStatusCode'] != 200:
  273. return False
  274. end_time = time.time()
  275. labels = self.labelsCoords(detect_group, rekognition_res, image_size) # 检查标签是否符合用户选择的识别类型
  276. TIME_LOGGER.info(f'uid={uid},{(end_time - start_time)}s,rekognition Result={labels}')
  277. # 将识别结果存到S3以及DynamoDB
  278. # AiView.store_image_results_to_dynamo_and_s3(file_path_list, uid, channel, n_time, labels, rekognition_res)
  279. eventType = labels['eventType']
  280. label_str = ','.join(labels['label_list'])
  281. new_bounding_box_dict = labels['new_bounding_box_dict']
  282. # 上传缩略图到s3
  283. file_dict = {}
  284. for i, val in enumerate(file_path_list):
  285. file_dict[val] = "{uid}/{channel}/{n_time}_{i}.jpeg".format(uid=uid, channel=channel, # 封面图
  286. n_time=n_time, i=i)
  287. self.upload_s3(file_dict, dir_path)
  288. # 设置推送间隔缓存
  289. self.add_push_cache(APP_NOTIFY_KEY, redis_obj, push_cache_data,
  290. uid_push_qs[0]['uid_set__new_detect_interval'])
  291. self.save_message_and_push(eventType, uid, n_time, uid_push_qs, channel,
  292. label_str, new_bounding_box_dict, notify)
  293. AiView.save_cloud_ai_tag(uid, int(n_time), eventType, 0)
  294. except Exception as e:
  295. data = {
  296. 'errLine': e.__traceback__.tb_lineno,
  297. 'errMsg': repr(e)
  298. }
  299. TIME_LOGGER.info(f'rekognition识别errMsg={data}')
  300. def save_message_and_push(self, eventType, uid, n_time, uid_push_qs, channel, label_str, new_bounding_box_dict,
  301. notify):
  302. """
  303. 保存消息以及推送
  304. """
  305. uid_push_list = []
  306. for qs in uid_push_qs:
  307. uid_push_list.append(qs)
  308. nickname = uid_push_list[0]['uid_set__nickname']
  309. if not nickname:
  310. nickname = uid
  311. userID_ids = []
  312. region = 4 if CONFIG_INFO == CONFIG_EUR else 3
  313. for up in uid_push_list:
  314. push_type = up['push_type']
  315. appBundleId = up['appBundleId']
  316. token_val = up['token_val']
  317. lang = up['lang']
  318. tz = up['tz']
  319. if tz is None or tz == '':
  320. tz = 0
  321. local_date_time = CommonService.get_now_time_str(n_time=n_time, tz=tz, lang='cn')
  322. TIME_LOGGER.info('*****AI消息存库{},{},{}'.format(uid, local_date_time, tz))
  323. # 以下是存库
  324. userID_id = up["userID_id"]
  325. if userID_id not in userID_ids:
  326. now_time = int(time.time())
  327. EquipmentInfoService.randoms_insert_equipment_info(
  328. device_user_id=userID_id,
  329. event_time=n_time,
  330. event_type=eventType,
  331. device_uid=uid,
  332. device_nick_name=nickname,
  333. channel=channel,
  334. alarm=label_str,
  335. is_st=3,
  336. add_time=now_time,
  337. storage_location=region,
  338. border_coords=json.dumps(new_bounding_box_dict)
  339. )
  340. userID_ids.append(userID_id)
  341. if not notify: # 不推送
  342. continue
  343. # 推送标题
  344. msg_title = self.get_msg_title(appBundleId=appBundleId, nickname=nickname)
  345. # 推送内容
  346. msg_text = self.get_msg_text(channel=channel, n_time=n_time, lang=lang, tz=tz, label_list=label_str)
  347. kwargs = {
  348. 'uid': uid,
  349. 'channel': channel,
  350. 'event_type': eventType,
  351. 'n_time': n_time,
  352. 'appBundleId': appBundleId,
  353. 'token_val': token_val,
  354. 'msg_title': msg_title,
  355. 'msg_text': msg_text,
  356. }
  357. try:
  358. # 推送消息
  359. if push_type == 0: # ios apns
  360. self.do_apns(**kwargs)
  361. elif push_type == 1: # android gcm
  362. self.do_fcm(**kwargs)
  363. elif push_type == 2: # android jpush
  364. self.do_jpush(**kwargs)
  365. except Exception as e:
  366. TIME_LOGGER.info('*****error,uid={uid},errLine={errLine}, errMsg={errMsg}'
  367. .format(uid=uid, errLine=e.__traceback__.tb_lineno, errMsg=repr(e)))
  368. continue
  369. def is_ai_push(self, uid, app_push_config):
  370. """
  371. 是否进行APP消息提醒
  372. @return: True|False
  373. """
  374. try:
  375. if not app_push_config:
  376. return True
  377. is_push = app_push_config['appPush']
  378. if is_push != 1: # 1:进行APP提醒,其它则不执行APP提醒
  379. return False
  380. all_day = app_push_config['pushTime']['allDay']
  381. if all_day == 0: # 1:全天提醒,0:自定义时间提醒
  382. push_time_config = app_push_config['pushTime']
  383. # 计算当前时间是否在自定义消息提醒范围内
  384. if not DevicePushService.is_push_notify_allowed_now(push_time_config):
  385. return False
  386. # 在开启接收APP消息提醒时,判断是否勾选云端AI消息提醒
  387. return app_push_config['eventTypes']['aiCloud'] == 1
  388. except Exception as e:
  389. TIME_LOGGER.info('*****error,uid={uid},errLine={errLine}, errMsg={errMsg}'
  390. .format(uid=uid, errLine=e.__traceback__.tb_lineno, errMsg=repr(e)))
  391. return True
  392. def del_path(self, path):
  393. try:
  394. if not os.path.exists(path):
  395. return
  396. if os.path.isfile(path):
  397. os.remove(path)
  398. else:
  399. items = os.listdir(path)
  400. for f in items:
  401. c_path = os.path.join(path, f)
  402. if os.path.isdir(c_path):
  403. self.del_path(c_path)
  404. else:
  405. os.remove(c_path)
  406. os.rmdir(path)
  407. except Exception as e:
  408. print(repr(e))
  409. ## 检查是否有符合条件的标签,并且返回标签坐标位置信息
  410. def labelsCoords(self, user_detect_group, rekognition_res, image_size):
  411. logger = logging.getLogger('info')
  412. labels = rekognition_res['Labels']
  413. label_name = []
  414. label_list = []
  415. logger.info('--------识别到的标签-------')
  416. logger.info(labels)
  417. all_labels_type = {
  418. '1': ['Person', 'Human'], # 人
  419. '2': ['Pet', 'Dog', 'Canine', 'Animal', 'Puppy', 'Cat'], # 动物
  420. '3': ['Vehicle', 'Car', 'Transportation', 'Automobile', 'Bus'], # 车
  421. '4': ['Package', 'Carton', 'Cardboard', 'Package Delivery'] # 包裹
  422. }
  423. # 找出识别的所有标签
  424. for label in labels:
  425. label_name.append(label['Name'])
  426. for Parents in label['Parents']:
  427. label_name.append(Parents['Name'])
  428. logger.info('标签名------')
  429. logger.info(label_name)
  430. # 删除用户没有选择的ai识别类型, 并且得出最终识别结果
  431. user_detect_list = user_detect_group.split(',')
  432. user_detect_list = [i.strip() for i in user_detect_list]
  433. conform_label_list = []
  434. conform_user_d_group = set()
  435. for key, label_type_val in all_labels_type.items():
  436. if key in user_detect_list:
  437. for label in label_type_val:
  438. if label in label_name:
  439. conform_user_d_group.add(key)
  440. conform_label_list.append(label)
  441. # 找出标签边框线位置信息
  442. boundingBoxList = []
  443. for label in labels:
  444. if label['Name'] in conform_label_list:
  445. for boundingBox in label['Instances']:
  446. boundingBoxList.append(boundingBox['BoundingBox'])
  447. # 找出边框位置信息对应的单图位置并重新计算位置比
  448. merge_image_height = image_size['height']
  449. # merge_image_width = image_size['width']
  450. single_height = merge_image_height // image_size['num']
  451. new_bounding_box_dict = {}
  452. new_bounding_box_dict['file_0'] = []
  453. new_bounding_box_dict['file_1'] = []
  454. new_bounding_box_dict['file_2'] = []
  455. # new_bounding_box_dict['file_3'] = []
  456. for k, val in enumerate(boundingBoxList):
  457. boundingBoxTop = merge_image_height * val['Top']
  458. # 找出当前边框属于哪张图片范围
  459. boxDict = {}
  460. for i in range(image_size['num']):
  461. min = i * single_height # 第n张图
  462. max = (i + 1) * single_height
  463. if boundingBoxTop >= min and boundingBoxTop <= max:
  464. # print("属于第{i}张图".format(i=i+1))
  465. boxDict['Width'] = val['Width']
  466. boxDict['Height'] = merge_image_height * val['Height'] / single_height
  467. boxDict['Top'] = ((merge_image_height * val['Top']) - (
  468. i * single_height)) / single_height # 减去前i张图片的高度
  469. boxDict['Left'] = val['Left']
  470. new_bounding_box_dict["file_{i}".format(i=i)].append(boxDict)
  471. # exit(new_bounding_box_list)
  472. conform_user_d_group = list(conform_user_d_group)
  473. if len(conform_user_d_group) > 0:
  474. conform_user_d_group.sort()
  475. # 集成识别标签
  476. for label_key in conform_user_d_group:
  477. label_list.append(AI_IDENTIFICATION_TAGS_DICT[label_key])
  478. eventType = ''.join(conform_user_d_group) # 组合类型
  479. else:
  480. eventType = ''
  481. logger.info('------conform_user_d_group------ {}'.format(conform_user_d_group))
  482. logger.info('------label_list------ {}'.format(label_list))
  483. return {'eventType': eventType, 'label_list': label_list,
  484. 'new_bounding_box_dict': new_bounding_box_dict}
  485. def upload_s3(self, file_dict, dir_path):
  486. try:
  487. # if CONFIG_INFO == CONFIG_US or CONFIG_INFO == CONFIG_EUR:
  488. # # 存国外
  489. # aws_key = AWS_ACCESS_KEY_ID[1]
  490. # aws_secret = AWS_SECRET_ACCESS_KEY[1]
  491. # session = Session(aws_access_key_id=aws_key,
  492. # aws_secret_access_key=aws_secret,
  493. # region_name="us-east-1")
  494. # s3 = session.resource("s3")
  495. # bucket = "foreignpush"
  496. # else:
  497. # # 存国内
  498. # aws_key = AWS_ACCESS_KEY_ID[0]
  499. # aws_secret = AWS_SECRET_ACCESS_KEY[0]
  500. # session = Session(aws_access_key_id=aws_key,
  501. # aws_secret_access_key=aws_secret,
  502. # region_name="cn-northwest-1")
  503. # s3 = session.resource("s3")
  504. # bucket = "push"
  505. #
  506. # for file_path, upload_path in file_dict.items():
  507. # print('-------')
  508. # print(file_path)
  509. # print('-------')
  510. # upload_data = open(file_path, "rb")
  511. # # upload_key = "test"
  512. # s3.Bucket(bucket).put_object(Key=upload_path, Body=upload_data)
  513. region = 'eur' if CONFIG_INFO == CONFIG_EUR else 'us'
  514. oci = OCIObjectStorage(region)
  515. for file_path, upload_path in file_dict.items():
  516. upload_data = open(file_path, "rb")
  517. # OCI上传对象
  518. oci.put_object(PUSH_BUCKET, upload_path, upload_data, 'image/jpeg')
  519. return True
  520. except Exception as e:
  521. TIME_LOGGER.error('rekoAI上传对象异常errLine={errLine}, errMsg={errMsg}'
  522. .format(errLine=e.__traceback__.tb_lineno, errMsg=repr(e)))
  523. return False
  524. def get_msg_title(self, appBundleId, nickname):
  525. package_title_config = {
  526. 'com.ansjer.customizedd_a': 'DVS',
  527. 'com.ansjer.zccloud_a': 'ZosiSmart',
  528. 'com.ansjer.zccloud_ab': '周视',
  529. 'com.ansjer.adcloud_a': 'ADCloud',
  530. 'com.ansjer.adcloud_ab': 'ADCloud',
  531. 'com.ansjer.accloud_a': 'ACCloud',
  532. 'com.ansjer.loocamccloud_a': 'Loocam',
  533. 'com.ansjer.loocamdcloud_a': 'Anlapus',
  534. 'com.ansjer.customizedb_a': 'COCOONHD',
  535. 'com.ansjer.customizeda_a': 'Guardian365',
  536. 'com.ansjer.customizedc_a': 'PatrolSecure',
  537. }
  538. if appBundleId in package_title_config.keys():
  539. return package_title_config[appBundleId] + '(' + nickname + ')'
  540. else:
  541. return nickname
  542. def get_msg_text(self, channel, n_time, lang, tz, label_list):
  543. n_date = CommonService.get_now_time_str(n_time=n_time, tz=tz, lang=lang)
  544. if lang == 'cn':
  545. msg = '摄像头AI识别到了{}'.format(label_list)
  546. send_text = '{msg} 通道:{channel} 日期:{date}'.format(msg=msg, channel=channel, date=n_date)
  547. else:
  548. msg = 'Camera AI recognizes {}'.format(label_list)
  549. send_text = '{msg} channel:{channel} date:{date}'.format(msg=msg, channel=channel, date=n_date)
  550. return send_text
  551. @classmethod
  552. def save_cloud_ai_tag(cls, uid, event_time, types, week=0):
  553. """
  554. 保存云存AI标签
  555. """
  556. try:
  557. types = str(types)
  558. if not types:
  559. return False
  560. n_time = int(time.time())
  561. vod_hls_tag = {"uid": uid, "ai_event_time": event_time, "created_time": n_time, 'tab_num': int(week)}
  562. vod_tag_vo = VodHlsTag.objects.create(**vod_hls_tag)
  563. tag_list = []
  564. if len(types) > 1:
  565. for i in range(1, len(types) + 1):
  566. ai_type = MessageTypeEnum(int(types[i - 1:i]))
  567. vod_tag_type_vo = VodHlsTagType(tag_id=vod_tag_vo.id, created_time=n_time, type=ai_type.value)
  568. tag_list.append(vod_tag_type_vo)
  569. else:
  570. ai_type = MessageTypeEnum(int(types))
  571. vod_tag_type_vo = {"tag_id": vod_tag_vo.id, "created_time": n_time, "type": ai_type.value}
  572. VodHlsTagType.objects.create(**vod_tag_type_vo)
  573. if tag_list:
  574. VodHlsTagType.objects.bulk_create(tag_list)
  575. return True
  576. except Exception as e:
  577. print('AI标签存储异常详情,errLine:{}, errMsg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
  578. return False
  579. def do_jpush(self, uid, channel, appBundleId, token_val, event_type, n_time, msg_title, msg_text):
  580. app_key = JPUSH_CONFIG[appBundleId]['Key']
  581. master_secret = JPUSH_CONFIG[appBundleId]['Secret']
  582. # 此处换成各自的app_key和master_secre
  583. _jpush = jpush.JPush(app_key, master_secret)
  584. push = _jpush.create_push()
  585. push.audience = jpush.registration_id(token_val)
  586. push_data = {"alert": "Motion ", "event_time": n_time, "event_type": event_type, "msg": "",
  587. "received_at": n_time, "sound": "sound.aif", "uid": uid, "zpush": "1", "channel": channel}
  588. android = jpush.android(alert=msg_text, priority=1, style=1, alert_type=7,
  589. big_text=msg_text, title=msg_title,
  590. extras=push_data)
  591. push.notification = jpush.notification(android=android)
  592. push.platform = jpush.all_
  593. res = push.send()
  594. print(res)
  595. return res.status_code
  596. def do_fcm(self, uid, channel, appBundleId, token_val, event_type, n_time, msg_title, msg_text):
  597. try:
  598. serverKey = FCM_CONFIG[appBundleId]
  599. push_service = FCMNotification(api_key=serverKey)
  600. data = {"alert": "Motion ", "event_time": n_time, "event_type": event_type, "msg": "",
  601. "received_at": n_time, "sound": "sound.aif", "uid": uid, "zpush": "1", "channel": channel}
  602. result = push_service.notify_single_device(registration_id=token_val, message_title=msg_title,
  603. message_body=msg_text, data_message=data,
  604. extra_kwargs={
  605. 'default_vibrate_timings': True,
  606. 'default_sound': True,
  607. 'default_light_settings': True
  608. })
  609. print('fcm push ing')
  610. print(result)
  611. return result
  612. except Exception as e:
  613. return 'serverKey abnormal'
  614. def do_apns(self, uid, channel, appBundleId, token_val, event_type, n_time, msg_title, msg_text):
  615. logger = logging.getLogger('info')
  616. logger.info("进来do_apns函数了")
  617. logger.info(token_val)
  618. logger.info(APNS_MODE)
  619. logger.info(os.path.join(BASE_DIR, APNS_CONFIG[appBundleId]['pem_path']))
  620. try:
  621. cli = apns2.APNSClient(mode=APNS_MODE,
  622. client_cert=os.path.join(BASE_DIR, APNS_CONFIG[appBundleId]['pem_path']))
  623. push_data = {"alert": "Motion ", "event_time": n_time, "event_type": event_type, "msg": "",
  624. "received_at": n_time, "sound": "", "uid": uid, "zpush": "1", "channel": channel}
  625. alert = apns2.PayloadAlert(body=msg_text, title=msg_title)
  626. payload = apns2.Payload(alert=alert, custom=push_data, sound="default")
  627. n = apns2.Notification(payload=payload, priority=apns2.PRIORITY_LOW)
  628. res = cli.push(n=n, device_token=token_val, topic=appBundleId)
  629. if res.status_code == 200:
  630. return res.status_code
  631. else:
  632. logger.info('apns push fail')
  633. logger.info(res.reason)
  634. return res.status_code
  635. except (ValueError, ArithmeticError):
  636. return 'The program has a numeric format exception, one of the arithmetic exceptions'
  637. except Exception as e:
  638. print(repr(e))
  639. logger.info(repr(e))
  640. return repr(e)
  641. @staticmethod
  642. def store_image_results_to_dynamo_and_s3(file_path_list, uid, channel, n_time, labels_data,
  643. reko_result):
  644. """
  645. 将图片识别结果存储到dynamoDB并且存储到S3
  646. @param file_path_list: 图片名称列表
  647. @param uid: 设备uid
  648. @param channel: 设备通道号
  649. @param n_time: 设备触发移动侦测时间戳
  650. @param labels_data: 标签数据(经过reko_result结果进行计算后的数据)
  651. @param reko_result: rekognition 响应结果
  652. @return: 保存结果
  653. """
  654. logger = logging.getLogger('info')
  655. try:
  656. file_dict = {}
  657. for i, val in enumerate(file_path_list):
  658. file_dict[val] = "{uid}/{channel}/{n_time}_{i}.jpeg".format(uid=uid, channel=channel, # 封面图
  659. n_time=n_time, i=i)
  660. if not reko_result:
  661. logger.info('{}识别结果为空'.format(uid))
  662. return False
  663. if CONFIG_INFO != CONFIG_US: # 目前只上美洲
  664. return False
  665. # 存美洲
  666. session = Session(aws_access_key_id=AWS_ACCESS_KEY_ID[1],
  667. aws_secret_access_key=AWS_SECRET_ACCESS_KEY[1],
  668. region_name="us-west-1")
  669. s3 = session.resource("s3")
  670. bucket = "rekognition-pic-results"
  671. # 上传到S3 rekognition-pic-results
  672. for file_path, upload_path in file_dict.items():
  673. logger.info('{}文件路径{}'.format(uid, file_path))
  674. upload_data = open(file_path, "rb")
  675. s3.Bucket(bucket).put_object(Key=upload_path, Body=upload_data)
  676. # reko结果存储到dynamoDB
  677. event_type = 0
  678. new_bounding_box_dict = ''
  679. if len(labels_data['label_list']) > 0:
  680. event_type = int(labels_data['eventType'])
  681. new_bounding_box_dict = json.dumps(labels_data['new_bounding_box_dict'])
  682. table_name = 'asj_push_message' # 表名称
  683. dynamo = DynamodbObject(AWS_ACCESS_KEY_ID[1], AWS_SECRET_ACCESS_KEY[1], 'us-west-1')
  684. item = {'device_uid': {'S': uid}, # 设备uid
  685. 'event_time': {'N': str(n_time)}, # 设备触发时间戳,也用作S3资源对象名前缀
  686. 'ai_coordinate': {'S': new_bounding_box_dict}, # ai坐标框信息
  687. 'channel': {'N': str(channel)}, # 设备通道号
  688. 'event_type': {'N': str(event_type)}, # 事件类型
  689. 'is_pic': {'N': '3'}, # 1:图片,2:视频,3:多图
  690. 'reko_result': {'S': json.dumps(reko_result)}, # reko识别结果
  691. 'storage_region': {'N': '2'}, # 存储平台1:阿里云,2:AWS
  692. 'create_time': {'N': str(int(time.time()))} # 记录创建时间
  693. }
  694. result = dynamo.put_item(table_name, item)
  695. logger.info('{}识别后存S3与DynamoDB成功{}'.format(uid, result))
  696. return True
  697. except Exception as e:
  698. logger.info('{}识别后存S3与DynamoDB失败:{}'.format(uid, repr(e)))
  699. return False
  700. @staticmethod
  701. def async_detection_image_label(sage_maker, uid, n_time, uid_push_qs,
  702. channel, file_list, notify, detect_group):
  703. images = file_list
  704. final_results = AiView.get_nova_tag_recognition(file_list, uid)
  705. if not final_results:
  706. return
  707. res = sage_maker.get_table_name(uid, final_results, detect_group)
  708. for i in range(len(images)):
  709. images[i] = images[i].replace(' ', '+')
  710. sage_maker.save_push_message(uid, n_time, uid_push_qs, channel, res, images, notify)
  711. @staticmethod
  712. def get_nova_tag_recognition(base64_images, uid):
  713. TIME_LOGGER.info(f'uid:{uid} Nova标签识别开始')
  714. try:
  715. redis_obj = RedisObject(db=6)
  716. now = datetime.now()
  717. year_month_day = now.strftime("%Y%m%d") # 如20241005(单日标识)
  718. daily_total_key = f"api:recognition:nova:daily:{year_month_day}:total"
  719. # 使用incr命令保证原子操作
  720. redis_obj.incr(daily_total_key, 1, 3600 * 24 * 7)
  721. except Exception as e:
  722. # Redis计数失败不影响主业务(降级处理),仅打日志
  723. TIME_LOGGER.error(f"{uid}Redis统计接口访问量失败:{repr(e)}")
  724. # 定义要检测的类别
  725. categories_to_detect = ["person", "car", "pet", "package"]
  726. # --- 记录process_image_batch执行时间 ---
  727. start_time = time.perf_counter() # 高精度计时
  728. final_results = nova_image_tag_instance.process_image_batch(
  729. base64_images,
  730. categories_to_detect,
  731. uid
  732. )
  733. # 无论是否发生异常,都记录执行时间
  734. end_time = time.perf_counter()
  735. execution_time = end_time - start_time
  736. TIME_LOGGER.info(
  737. f'uid:{uid} Nova标签识别批量处理完成 | '
  738. f'执行时间: {execution_time:.4f}秒'
  739. )
  740. return final_results
  741. @staticmethod
  742. def ai_push_count(uid, date_str):
  743. """ 记录 AI 调用次数(按月累计) """
  744. try:
  745. redis_obj = RedisObject(db=7)
  746. month_str = date_str[:6] # "202511"
  747. key = f"ai:push:monthly:{month_str}"
  748. ttl = 3600 * 24 * 90
  749. redis_obj.hash_field_increment(key, uid, 1, ttl)
  750. except Exception as e:
  751. TIME_LOGGER.error(f"AI调用计数失败 uid={uid} error={repr(e)}")
  752. @staticmethod
  753. def ai_push_stats(request_dict, response):
  754. uid = request_dict.get('uid', None)
  755. start = request_dict.get('start_time')
  756. end = request_dict.get('end_time')
  757. if not all([start, end]):
  758. return response.json(444)
  759. try:
  760. start_dt = datetime.strptime(start, "%Y-%m")
  761. end_dt = datetime.strptime(end, "%Y-%m")
  762. except ValueError:
  763. return response.json(444, '月份格式错误,必须为 YYYY-MM')
  764. if start_dt > end_dt:
  765. return response.json(444, '开始月份不能晚于结束月份')
  766. try:
  767. redis_obj = RedisObject(db=7)
  768. total = 0
  769. monthly = []
  770. # 当前月份从开始月份的1号开始
  771. cur = start_dt
  772. while cur <= end_dt:
  773. month_str = cur.strftime("%Y%m")
  774. key = f"ai:push:monthly:{month_str}"
  775. month_data = {
  776. "month": cur.strftime("%Y-%m"),
  777. "total_count": 0, # 当月总次数
  778. }
  779. if uid:
  780. #只返回该设备的数据
  781. count = redis_obj.CONN.hget(key, uid)
  782. count = int(count) if count else 0
  783. month_data["total_count"] = count
  784. else:
  785. #返回所有设备的明细和总次数
  786. data = redis_obj.CONN.hgetall(key)
  787. user_list = []
  788. total_month = 0
  789. for user_uid, cnt in data.items():
  790. uid_str = user_uid.decode('utf-8')
  791. count_int = int(cnt.decode('utf-8'))
  792. user_list.append({"uid": uid_str, "count": count_int})
  793. total_month += count_int
  794. month_data["total_count"] = total_month
  795. month_data["users"] = user_list
  796. total += month_data["total_count"]
  797. monthly.append(month_data)
  798. cur += timedelta(days=32)
  799. cur = cur.replace(day=1)
  800. result = {
  801. "uid": uid,
  802. "total": total, # 所有月份的总和
  803. "monthly": monthly # 每个月的明细
  804. }
  805. return response.json(0, result)
  806. except Exception as e:
  807. return response.json(500, f"error_line:{e.__traceback__.tb_lineno}, error_msg:{repr(e)}")