voice_process.py 64 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648
  1. import pickle
  2. from multiprocessing.dummy import current_process
  3. from datetime import datetime
  4. from django.db.models.expressions import result
  5. from django.http import HttpResponse,JsonResponse
  6. from django.utils.text import normalize_newlines
  7. from django.views.decorators.csrf import csrf_exempt
  8. from datetime import datetime, timedelta
  9. from django.core import serializers
  10. import time
  11. import json
  12. import os
  13. import jwt
  14. from .models import DogTrainingNew
  15. os.environ.setdefault("DJANGO_SETTINGS_MODULE", "aiDogProject.settings")
  16. import django
  17. django.setup()
  18. from aiDogApp.models import *
  19. from . import aidog_tools,aidog_request_tools,wave_compare,generalRequest,dbOperate_upload_Uvoice
  20. # from .wave_compare_algorithm import wave_compare
  21. # APPId = "527f831f"
  22. # APISecret = "YWE5MjI0MzA4NmM3MTNmNTNiMWJkYzE4"
  23. # APIKey = "be5c37f3db1569737934240a0e3ad02d"
  24. APPId = "ed8eb862"
  25. APISecret = "YzEyMDYyMzljMDViNWJlZDdlOWJhYjVi"
  26. APIKey = "b4c831160d8933221e95bda817547e99"
  27. SECRET_KEY='#tdfnrcn1s610h4*csa2-p+=lfqz-ol+=uo$+n2sa'
  28. #科大讯飞校验声纹接口:客户端上传用户id、wav格式文件流,转换文件流为MP3缓存在本地固定路径下
  29. # 提交用户id到数据库遍历全部声纹id
  30. # 每个声纹id+用户id+mp3路径上传到科大校验接口,筛选出得分最高的声纹id,并返回其口令类型,删除此mp3文件
  31. # 2024/12/02 无token
  32. # author Feng
  33. @csrf_exempt
  34. def check_Uvoice(request):
  35. user_uid=request.POST.get("user_id",'')# 用户ID
  36. file_stream = request.FILES.get("file_stream") # Get the uploaded file
  37. best_res = None # 存储符合条件的最大 score 结果
  38. max_score = 0.3 # 初始为0.3, 只记录比0.3大的score
  39. # file_path = './audio_cache/voice_JTYNZP9O_1729843681.mp3'
  40. # 当前时间
  41. date_now=datetime.now()
  42. # 校验上传的文件流
  43. if not file_stream:
  44. return JsonResponse({
  45. "status": "error",
  46. "message": "File not uploaded.",
  47. "date": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  48. })
  49. # 确保用户id不为空
  50. if not user_uid :
  51. return JsonResponse({
  52. "status": "error",
  53. "date": datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  54. "message": "Please ensure all required fields are provided!",
  55. "end_time": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  56. })
  57. # 定义本地缓存路径
  58. # TODO----------迁移到登录/注册逻辑内 确保缓存目录只在应用启动时创建
  59. cache_directory = "audio_cache"
  60. os.makedirs(cache_directory, exist_ok=True)
  61. # 创建一个唯一的文件名
  62. timestamp = int(time.time())
  63. mp3_path = os.path.join(cache_directory, f"voice_check_{user_uid}_{timestamp}.mp3")
  64. # 将上传的文件流转换为 MP3 并保存
  65. if not dbOperate_upload_Uvoice.process_audio_file(file_stream, mp3_path):
  66. return JsonResponse({
  67. "status": "error",
  68. "message": "Failed to process the audio file.",
  69. "date": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  70. })
  71. voices = VoiceFeatureInfo.objects.filter(uid=user_uid)
  72. for voice in voices:
  73. message_response = generalRequest.req_url(
  74. api_name='searchScoreFea', APPId=APPId, APIKey=APIKey, APISecret=APISecret,
  75. file_path=mp3_path, featureId=voice.voice_feature_id, groupId=user_uid
  76. )
  77. if message_response.get('code') == 0:
  78. data_dict = json.loads(message_response.get('msg'))
  79. score = data_dict.get('score')
  80. # print(score)
  81. # 更新最大 score 和对应的结果
  82. if score and score > max_score:
  83. max_score = score
  84. best_res = {
  85. "status": "success",
  86. "date": date_now.strftime('%Y-%m-%d %H:%M:%S'),
  87. "message": message_response,
  88. "score": score,
  89. "command_type": voice.command_type,
  90. "end_time": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  91. }
  92. # 清理临时 mp3 文件
  93. os.remove(mp3_path)
  94. # 检查是否找到符合条件的结果
  95. if best_res:
  96. return JsonResponse(best_res)
  97. else:
  98. return JsonResponse({"message": "请重新发出口令."})
  99. #上传声纹接口:客户端上传用户id、command_type、文件流
  100. # 2024/12/02 无token
  101. # author Feng
  102. @csrf_exempt
  103. def upload_Uvoice(request):
  104. # if request.method != "POST":
  105. # return JsonResponse({
  106. # "status": "error",
  107. # "message": "Only POST method is allowed.",
  108. # "date": datetime.strftime(datetime.today(), '%Y-%m-%d %H:%M:%S')
  109. # })
  110. user_uid = request.POST.get("user", '') # 用户ID
  111. command_type = request.POST.get("command_type", '')
  112. file_stream = request.FILES.get("file_stream") # Get the uploaded file
  113. feature_id = str(time.time())[:10] # 随机 voice feature ID
  114. res = None
  115. # user_uid = 'iFLYTEK_examples_groupId'
  116. # command_type = 3
  117. if not file_stream:
  118. return JsonResponse({
  119. "status": "error",
  120. "message": "File not uploaded.",
  121. "date": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  122. })
  123. # 确保用户id和声纹id不为空
  124. if not user_uid or not command_type:
  125. return JsonResponse({
  126. "status": "error",
  127. "date": datetime.strftime(datetime.today(), '%Y-%m-%d %H:%M:%S'),
  128. "message": "Please ensure all required fields are provided!",
  129. "end_time": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  130. })
  131. try:
  132. # 尝试将 command_type 转换为整数
  133. # 使用INT 类型 查询和索引也会更高效
  134. command_type = int(command_type)
  135. except ValueError:
  136. # 如果转换失败,返回错误信息
  137. return JsonResponse({
  138. "status": "error",
  139. "message": "Invalid value for command_type. It must be an integer.",
  140. "date": datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  141. "end_time": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  142. })
  143. # 成功的情况下处理请求
  144. res = {
  145. "status": "success",
  146. "date": datetime.strftime(datetime.today(), '%Y-%m-%d %H:%M:%S'),
  147. "message": dbOperate_upload_Uvoice.process_voice_recording(user_uid, feature_id, command_type,file_stream),
  148. "end_time": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  149. }
  150. return JsonResponse(res)
  151. #上传声纹接口:客户端上传用户id、command_type、wav.zip文件流,access_token
  152. # 2024/12/02 无token
  153. # author Feng
  154. # 2025/3/31更新
  155. # 处理用户声纹上传和比对
  156. # 处理流程:
  157. # 1. 首次上传:保存为源文件,提取特征并存储
  158. # 2. 二次上传:与源文件进行比对,决定是否通过验证
  159. @csrf_exempt
  160. def upload_Uvoice_latest1(request):
  161. # 记录请求时间
  162. current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  163. try:
  164. # 获取并验证请求参数
  165. params = _validate_request_params(request)
  166. if isinstance(params, JsonResponse):
  167. return params
  168. access_token = params['access_token']
  169. user_id = params['user_id']
  170. dog_id = params['dog_id']
  171. file_stream = params['file_stream']
  172. voice_type = params['voice_type']
  173. timestamp = params['timestamp']
  174. current_times = params['current_times']
  175. total_times = params['total_times']
  176. feature_id = str(time.time())[:10] # 生成声纹特征ID
  177. # 验证访问令牌
  178. token_validation = aidog_tools.validate_access_token(access_token)
  179. if isinstance(token_validation, JsonResponse):
  180. return token_validation
  181. user_id = token_validation['user_id']
  182. # 取出数据库的uid:
  183. try:
  184. dog_info = Doginfo.objects.get(owner_id=user_id,d_id=dog_id)
  185. except Doginfo.DoesNotExist:
  186. return {"status": "error", "message": f"No dog found with uid={user_id}"}
  187. # 根据上传次数处理请求
  188. if int(current_times) < int(total_times):
  189. # 首次上传:处理并保存源文件
  190. return _handle_first_upload(user_id, dog_id,feature_id, voice_type, file_stream, current_time)
  191. else:
  192. # 二次上传:与源文件比对
  193. return _handle_second_upload(user_id, dog_id,feature_id, voice_type, file_stream, current_time)
  194. except Exception as e:
  195. # 全局异常处理
  196. import traceback
  197. traceback.print_exc()
  198. return JsonResponse({
  199. "status": "error",
  200. "message": f"Server processing exception: {str(e)}",
  201. "date": current_time
  202. })
  203. def _handle_first_upload(user_id, dog_id,feature_id, command_type, file_stream, current_time):
  204. """处理第一次声音上传,保存文件并提取特征"""
  205. try:
  206. # 生成唯一的文件路径
  207. output_mp3_path = _get_file_path(user_id, feature_id,True)
  208. # 处理声音文件,转换为MP3并存储
  209. process_result = dbOperate_upload_Uvoice.process_voice_recording(
  210. user_id, feature_id, command_type, file_stream,output_mp3_path
  211. )
  212. print(f"process_voice_recording complete. API response: {process_result}")
  213. if process_result['status'] != 'success':
  214. return JsonResponse({
  215. "status": "error",
  216. "message": process_result['msg']+"audio file failed to be processed",
  217. "date": current_time
  218. })
  219. # 提取声音特征
  220. mfcc_origin = wave_compare.extract_mfcc(output_mp3_path)
  221. character_value_origin = pickle.dumps(mfcc_origin)
  222. # 保存声纹信息到数据库
  223. try:
  224. # 检查声纹信息是否存在
  225. voice_feature_info, created = VoiceFeatureInfo.objects.get_or_create(
  226. uid=user_id,
  227. d_id=dog_id,
  228. command_type=command_type,
  229. defaults={
  230. 'mfcc': character_value_origin,
  231. 'voice_path': output_mp3_path,
  232. 'voice_feature_id': feature_id,
  233. 'voice_group_id': user_id,
  234. }
  235. )
  236. if not created:
  237. # 更新声纹信息
  238. voice_feature_info.voice_path = output_mp3_path
  239. voice_feature_info.voice_feature_id = feature_id
  240. voice_feature_info.mfcc = character_value_origin
  241. voice_feature_info.save()
  242. # print(f"voice_feature_info11. API response: {voice_feature_info1.__dict__}")
  243. return JsonResponse({
  244. "status": "success",
  245. "date": current_time,
  246. "message": "in progress",
  247. })
  248. except Exception as db_error:
  249. return JsonResponse({
  250. "status": "error",
  251. "message": f"Database operation error: {str(db_error)}",
  252. "date": current_time
  253. })
  254. except Exception as e:
  255. return JsonResponse({
  256. "status": "error",
  257. "message": f"error occurred while processing the first upload: {str(e)}",
  258. "date": current_time
  259. })
  260. # 封装---更新狗的训练状态
  261. # 参数 dog_id: 狗的ID,command_type: 命令类型,mapping: 命令类型到训练参数的映射
  262. # 2025/6/11 更新狗的训练状态,包含上限检查和优化的业务逻辑
  263. def _update_dog_training(dog_id, command_type):
  264. # 定义上限常量
  265. MAX_VOICE_CALL = 100
  266. MAX_VOICE_COMMAND = 100
  267. # 获取或创建训练记录
  268. dog_training, created = DogTrainingNew.objects.get_or_create(
  269. d_id=dog_id,
  270. defaults={
  271. 'voicecall': 0,
  272. 'voicecallenable': 0,
  273. 'voicecommand': 0,
  274. 'voicecommandenable': 0,
  275. 'commandsit': 0,
  276. 'commandstand': 0,
  277. 'commandbark': 0,
  278. 'commandliedown': 0,
  279. 'commandshake': 0,
  280. 'commandtouch': 0,
  281. 'commanddeath': 0,
  282. 'commandturnl': 0,
  283. 'commandturnr': 0,
  284. }
  285. )
  286. # 处理 voiceCall 训练逻辑
  287. if command_type == 'voiceCall':
  288. current_voice_call = dog_training.voicecall
  289. current_voice_call_enable = dog_training.voicecallenable
  290. # 首次为全新d_id录制音频
  291. if current_voice_call_enable == 0 and current_voice_call == 0:
  292. dog_training.voicecall = min(10, MAX_VOICE_CALL)
  293. dog_training.voicecallenable = 1
  294. # voiceCallEnable==1的情况下,每次录音+4
  295. elif current_voice_call_enable == 1:
  296. new_value = current_voice_call + 4
  297. dog_training.voicecall = min(new_value, MAX_VOICE_CALL)
  298. # 重置了录音数据情况下(voiceCallEnable==0但voiceCall>0)
  299. elif current_voice_call_enable == 0 and current_voice_call > 0:
  300. new_value = current_voice_call + 4
  301. dog_training.voicecall = min(new_value, MAX_VOICE_CALL)
  302. dog_training.voicecallenable = 1 # 重新启用
  303. # 处理各种命令类型的训练逻辑
  304. elif command_type in ['commandSit', 'commandStand', 'commandBark', 'commandLieDown',
  305. 'commandShake', 'commandTouch', 'commandDeath', 'commandTurnL', 'commandTurnR']:
  306. current_voice_command = dog_training.voicecommand
  307. current_voice_command_enable = dog_training.voicecommandenable
  308. # 获取对应的命令字段名(转换为小写)
  309. command_field_map = {
  310. 'commandSit': 'commandsit',
  311. 'commandStand': 'commandstand',
  312. 'commandBark': 'commandbark',
  313. 'commandLieDown': 'commandliedown',
  314. 'commandShake': 'commandshake',
  315. 'commandTouch': 'commandtouch',
  316. 'commandDeath': 'commanddeath',
  317. 'commandTurnL': 'commandturnl',
  318. 'commandTurnR': 'commandturnr',
  319. }
  320. command_field = command_field_map.get(command_type)
  321. if command_field:
  322. # 设置对应命令字段为1
  323. setattr(dog_training, command_field, 1)
  324. # 首次为全新d_id录制口令音频
  325. if current_voice_command_enable == 0 and current_voice_command == 0:
  326. dog_training.voicecommand = min(10, MAX_VOICE_COMMAND)
  327. dog_training.voicecommandenable = 1
  328. # voiceCallEnable==1的情况下,每次录音+4
  329. elif current_voice_command_enable == 1:
  330. new_value = current_voice_command + 4
  331. dog_training.voicecall = min(new_value, MAX_VOICE_CALL)
  332. # 重置了录音数据情况下(voiceCommandEnable==0但voiceCommand>0)
  333. elif current_voice_command_enable == 0 and current_voice_command > 0:
  334. new_value = current_voice_command + 4
  335. dog_training.voicecommand = min(new_value, MAX_VOICE_COMMAND)
  336. dog_training.voicecommandenable = 1 # 重新启用
  337. # 保存更新
  338. dog_training.save()
  339. # 处理第二次声音上传,与源文件进行比对
  340. def _handle_second_upload(user_id, dog_id,feature_id,command_type, file_stream, current_time):
  341. # 定义常量
  342. MATCH_THRESHOLD = 0.6 # 声纹匹配阈值
  343. output_mp3_path = None
  344. try:
  345. # 生成唯一的文件路径
  346. output_mp3_path = _get_file_path(user_id, feature_id)
  347. # 处理上传的音频文件
  348. wav_to_mp3_result = dbOperate_upload_Uvoice.process_zip_audio_file(file_stream, output_mp3_path)
  349. if not wav_to_mp3_result:
  350. return JsonResponse({
  351. "status": "error",
  352. "message": "audio file failed to be processed",
  353. "date": current_time
  354. })
  355. score=0
  356. best_res = False
  357. try:
  358. # 获取用户原始声纹信息
  359. voice = VoiceFeatureInfo.objects.get(uid=user_id, d_id=dog_id,command_type=command_type)
  360. dog_info = Doginfo.objects.filter(owner_id=user_id)
  361. second_fail_details = {
  362. 'voice_files_deleted': 0,
  363. 'iflytek_features_deleted': 0,
  364. }
  365. # 调用科大讯飞声纹比对接口
  366. message_response = generalRequest.req_url(
  367. api_name='searchScoreFea',
  368. APPId=APPId,
  369. APIKey=APIKey,
  370. APISecret=APISecret,
  371. file_path=output_mp3_path,
  372. featureId=voice.voice_feature_id,
  373. groupId=user_id
  374. )
  375. # 处理第三方比对结果
  376. if message_response.get('code') == 0:
  377. data_dict = json.loads(message_response.get('msg'))
  378. score = data_dict.get('score')
  379. # 更新最大 score 和对应的结果
  380. if score and score > MATCH_THRESHOLD:
  381. best_res = True
  382. else:
  383. return JsonResponse({
  384. "status": "error",
  385. "message": f" error occurred during call KEDA API",
  386. "date": current_time
  387. })
  388. # print(f"KEDA score. API response: {score}")todo--------存储得分到数据库
  389. # 使用本地比对算法
  390. sound_wave_1 = voice.voice_path
  391. sound_wave_2 = output_mp3_path
  392. mfcc_1 = wave_compare.extract_mfcc(sound_wave_1)
  393. # mfcc_1=voice.mfcc
  394. mfcc_2 = wave_compare.extract_mfcc(sound_wave_2)
  395. #
  396. similarity = wave_compare.compute_similarity(mfcc_2, mfcc_1)
  397. if similarity['result'] and similarity['result'] > MATCH_THRESHOLD:
  398. best_res = True
  399. # 更新狗的训练状态
  400. _update_dog_training(dog_id, command_type)
  401. else:
  402. #如果没有比对结果,或者比对得分太低,则删除数据库中路径信息,mfcc,feature_id,删除路径信息对应的本地mp3文件;并且调用科大讯飞删除声纹api,
  403. # 处理VoiceFeatureInfo表中的所有记录
  404. # 删除本地音频文件
  405. if voice.voice_path and delete_local_audio_file(voice.voice_path):
  406. second_fail_details['voice_files_deleted'] += 1
  407. # 调用科大讯飞API删除音频特征
  408. if voice.voice_feature_id:
  409. if delete_iflytek_feature(user_id, voice):
  410. second_fail_details['iflytek_features_deleted'] += 1
  411. # 清空相关字段
  412. voice.mfcc = None
  413. voice.voice_path = None
  414. voice.voice_feature_id = None
  415. voice.save()
  416. print(f"Audio similarity. API response: {similarity}")
  417. except VoiceFeatureInfo.DoesNotExist:
  418. return JsonResponse({
  419. "status": "error",
  420. "message": "No matching voice was found",
  421. "date": current_time
  422. })
  423. except Exception as e:
  424. return JsonResponse({
  425. "status": "error",
  426. "message": f" error occurred during voice comparison: {str(e)}",
  427. "date": current_time
  428. })
  429. # 返回比对结果
  430. res_msg = 'pass' if best_res else 'fail'
  431. if best_res:
  432. res_msg = 'pass'
  433. dog_list = []
  434. for dog in dog_info:
  435. dog_serialized = serializers.serialize('json', [dog])
  436. dog_data = json.loads(dog_serialized)[0]['fields']
  437. dog_id = dog_data['d_id']
  438. # 获取狗的信息并转换为字典
  439. # 获取狗的信息并转换为字典
  440. new_dog_status = aidog_tools.model_to_dict_without_id(DogStatus.objects.filter(d_id=dog_id).first())
  441. new_dog_training = aidog_tools.model_to_dict_without_id(DogTrainingNew.objects.filter(d_id=dog_id).first())
  442. new_dog_body_attributes = aidog_tools.model_to_dict_without_id(DogBodyAttributes.objects.filter(d_id=dog_id).first())
  443. new_dog_personality_relationship = aidog_tools.model_to_dict_without_id(DogPersonalityRelationship.objects.filter(d_id=dog_id).first())
  444. # 合并所有相关信息
  445. dog_info_combined = {
  446. **dog_data,
  447. **new_dog_status,
  448. **new_dog_training,
  449. **new_dog_body_attributes,
  450. **new_dog_personality_relationship,
  451. }
  452. # 只保留字段而不是数据库内部的元数据(如 '_state')
  453. dog_info_clean = {key: value for key, value in dog_info_combined.items() if not key.startswith('_')}
  454. dog_list.append(dog_info_clean)
  455. # 保存比对结果
  456. VoiceCompareScores.objects.create(
  457. uid=user_id,
  458. voice_feature_id=feature_id,
  459. keda_score=score,
  460. mfcc_score=similarity['result'],
  461. kedacompare_spend_time=message_response['time_diff'],
  462. mfcccompare_spend_time=similarity['time_diff'],
  463. createtime=current_time
  464. )
  465. return JsonResponse({
  466. "status": "success",
  467. "date": current_time,
  468. "message": res_msg,
  469. "mfcc_score": similarity['result'],
  470. "keda_score": score,
  471. "user_id": user_id,
  472. "voice_type": command_type,
  473. "date_time": datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  474. "dogs": dog_list
  475. })
  476. else:
  477. res_msg='fail'
  478. return JsonResponse({
  479. "status": "success",
  480. "date": current_time,
  481. "message": res_msg,
  482. "second_fail_details":second_fail_details,
  483. "date_time": datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  484. })
  485. except Exception as e:
  486. return JsonResponse({
  487. "status": "error",
  488. "message": f"error occurred while processing the secondary upload: {str(e)}",
  489. "date": current_time
  490. })
  491. finally:
  492. # 清理临时文件
  493. if output_mp3_path and os.path.exists(output_mp3_path):
  494. try:
  495. os.remove(output_mp3_path)
  496. except Exception:
  497. # 记录但不中断流程
  498. pass
  499. # 生成并发安全的文件路径
  500. # 参数:
  501. # user_id: 用户ID
  502. # feature_id: 声纹ID
  503. # is_temp: 是否为临时文件
  504. #
  505. # 返回:
  506. # 文件存储路径
  507. # 2025-4-8 Author Feng
  508. def _get_file_path(user_id, feature_id, is_temp=False):
  509. # 基础目录
  510. base_directory = "audio_cache"
  511. # 按用户ID分目录
  512. user_directory = os.path.join(base_directory, f"user_{user_id}")
  513. # 确保目录存在
  514. os.makedirs(user_directory, exist_ok=True)
  515. # 确保文件名全局唯一,防止多用户并发冲突
  516. timestamp = int(time.time())
  517. # 文件命名包含必要的标识信息
  518. file_name = f"{'voice_' if is_temp else 'tempvoice_'}{user_id}_{feature_id}_{timestamp}.mp3"
  519. return os.path.join(user_directory, file_name)
  520. def _validate_request_params(request):
  521. # 验证请求参数的完整性
  522. # 获取请求参数
  523. access_token = request.POST.get("access_token", '')
  524. user_id = request.POST.get("user_id", '')
  525. dog_id = request.POST.get("dog_id", '')
  526. # file_stream = request.FILES.get("voice")
  527. # 从form-data到binary切换:修改这里的文件获取方式,从'voice'改为'file'
  528. # 尝试从FILES获取文件(multipart/form-data方式)
  529. file_stream = request.FILES.get("voice") or request.FILES.get("file")
  530. # 如果FILES中没有找到文件,并且Content-Type是application/octet-stream,
  531. # 则从请求体中读取二进制数据
  532. if not file_stream and request.content_type == 'application/octet-stream':
  533. from django.core.files.uploadedfile import SimpleUploadedFile
  534. file_stream = SimpleUploadedFile("voice.zip", request.body, content_type="application/octet-stream")
  535. voice_type = request.POST.get("voice_type", '')
  536. timestamp = request.POST.get("datetime", '')
  537. current_times = request.POST.get("current_times", '') # 当前第几次上传音频源文件
  538. total_times = request.POST.get("total_times", '') # 总共会上传几次
  539. current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  540. # 验证文件是否存在
  541. if not file_stream:
  542. return JsonResponse({
  543. "status": "error",
  544. "message": "voice file was not uploaded",
  545. "date": current_time
  546. })
  547. # 验证必要参数是否存在
  548. if not all([user_id, dog_id,voice_type, access_token, current_times, total_times]):
  549. return JsonResponse({
  550. "status": "error",
  551. "date": current_time,
  552. "message": "Please ensure all params are provided",
  553. "end_time": current_time
  554. })
  555. return {
  556. 'access_token': access_token,
  557. 'user_id': user_id,
  558. 'dog_id': dog_id,
  559. 'file_stream': file_stream,
  560. 'voice_type': voice_type,
  561. 'timestamp': timestamp,
  562. 'current_times': current_times,
  563. 'total_times': total_times
  564. }
  565. #语音呼唤发送接口:客户端上传用户id、wav.zip文件流,access_token
  566. # 2025/4/21
  567. # author Feng
  568. # 将语音发送给服务器,返回所有狗的得分,得分最高的狗进入交互状态
  569. # 得分类型float保留2位小数(减少得分一样概率)
  570. @csrf_exempt
  571. def voice_call_training(request):
  572. try:
  573. # 获取请求参数
  574. access_token = request.POST.get("access_token", '')
  575. user_id = request.POST.get("user_id", '')
  576. # 尝试从FILES获取文件(multipart/form-data方式)
  577. file_stream = request.FILES.get("voice") or request.FILES.get("file")
  578. # 如果FILES中没有找到文件,并且Content-Type是application/octet-stream,
  579. # 则从请求体中读取二进制数据
  580. if not file_stream and request.content_type == 'application/octet-stream':
  581. from django.core.files.uploadedfile import SimpleUploadedFile
  582. file_stream = SimpleUploadedFile("voice.zip", request.body, content_type="application/octet-stream")
  583. feature_id = str(time.time())[:12] # 生成声纹特征ID
  584. max_score = 0.6 # 初始为0.3, 只记录比0.3大的score
  585. score=0
  586. max_MFCC_score=0.6
  587. best_res = None # 存储符合条件的最大 score 结果
  588. res_msg=''
  589. # 记录请求时间
  590. current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  591. # 验证请求参数
  592. # 验证文件是否存在
  593. if not file_stream:
  594. return JsonResponse({
  595. "status": "error",
  596. "message": "voice file was not uploaded",
  597. "date": current_time
  598. })
  599. if not all([user_id, access_token]):
  600. return JsonResponse({
  601. "status": "error",
  602. "date": current_time,
  603. "message": "Please ensure all required fields are provided!",
  604. "end_time": current_time
  605. })
  606. # 验证访问令牌
  607. token_validation = aidog_tools.validate_access_token(access_token)
  608. if isinstance(token_validation, JsonResponse):
  609. return token_validation
  610. user_id = token_validation['user_id']
  611. # 当前时间
  612. date_now=datetime.now()
  613. # 生成唯一的文件路径
  614. output_mp3_path = _get_file_path(user_id, feature_id)
  615. # 处理上传的音频文件
  616. wav_to_mp3_result = dbOperate_upload_Uvoice.process_zip_audio_file(file_stream, output_mp3_path)
  617. if not wav_to_mp3_result:
  618. return JsonResponse({
  619. "status": "error",
  620. "message": "audio file failed to be processed",
  621. "date": current_time
  622. })
  623. try:
  624. # 获取用户原始声纹信息
  625. voices = VoiceFeatureInfo.objects.filter(uid=user_id, command_type='voiceCall')
  626. dog_info = Doginfo.objects.filter(owner_id=user_id)
  627. # 创建一个列表来存储所有voice的得分结果
  628. voice_MFCC_score = {
  629. }
  630. voice_KEDA_score = {
  631. }
  632. for voice in voices:
  633. # 使用本地比对算法
  634. sound_wave_1 = voice.voice_path
  635. sound_wave_2 = output_mp3_path
  636. mfcc_1 = wave_compare.extract_mfcc(sound_wave_1)
  637. mfcc_2 = wave_compare.extract_mfcc(sound_wave_2)
  638. similarity = wave_compare.compute_similarity(mfcc_2, mfcc_1)
  639. voice_MFCC_score[voice.d_id] = similarity['result']
  640. print(f"Audio similarity. API response: {similarity}")
  641. # 使用科大算法
  642. message_response = generalRequest.req_url(
  643. api_name='searchScoreFea', APPId=APPId, APIKey=APIKey, APISecret=APISecret,
  644. file_path=voice.voice_path, featureId=voice.voice_feature_id, groupId=user_id
  645. )
  646. if message_response.get('code') == 0:
  647. data_dict = json.loads(message_response.get('msg'))
  648. score = data_dict.get('score')
  649. voice_KEDA_score[voice.d_id] = score
  650. else:
  651. return JsonResponse({
  652. "status": "error",
  653. "code":400,
  654. "message": message_response.get('msg'),
  655. "date": current_time
  656. })
  657. # 找出MFCC得分最高的狗ID
  658. highest_mfcc_score = 0
  659. highest_mfcc_dog_id = None
  660. for dog_id, mfcc_score in voice_MFCC_score.items():
  661. if mfcc_score and float(mfcc_score) > highest_mfcc_score:
  662. highest_mfcc_score = float(mfcc_score)
  663. highest_mfcc_dog_id = dog_id
  664. # 如果找到了最高分数的狗ID,更新其voicecall值
  665. # if highest_mfcc_dog_id:
  666. # try:
  667. # dog_training = DogTrainingNew.objects.get(d_id=highest_mfcc_dog_id)
  668. # dog_training.voicecall += 4
  669. # dog_training.save()
  670. # print(f"Updated voicecall for dog ID {highest_mfcc_dog_id}, new value: {dog_training.voicecall}")
  671. # except DogTrainingNew.DoesNotExist:
  672. # # 如果找不到对应的训练记录
  673. # return JsonResponse({
  674. # "status": "error",
  675. # "message": "No matching dog was found",
  676. # "date": current_time
  677. # })
  678. # 如果找到了最高分数的狗ID,更新其voicecall值和personality属性
  679. if highest_mfcc_dog_id:
  680. try:
  681. # 更新voicecall,每日最多+12
  682. dog_training = DogTrainingNew.objects.get(d_id=highest_mfcc_dog_id)
  683. # 获取今天的日期(年-月-日)
  684. today = date_now.date()
  685. # 检查是否有今天的训练记录
  686. training_log = DogTrainingLog.objects.filter(
  687. d_id=highest_mfcc_dog_id,
  688. owner_id=user_id,
  689. command_type='voiceCall',
  690. training_date=today
  691. ).first()
  692. # 如果没有今天的记录,创建一个新的
  693. if not training_log:
  694. training_log = DogTrainingLog.objects.create(
  695. d_id=highest_mfcc_dog_id,
  696. owner_id=user_id,
  697. command_type='voiceCall',
  698. training_date=today,
  699. training_count=0
  700. )
  701. # 检查是否达到每日上限
  702. if training_log.training_count < 3: # 每次+4,最多+12,所以最多3次
  703. # voicecall是TINYINT类型,最大值限制为100
  704. max_value = 100
  705. # 检查更新后的值是否超过最大值
  706. new_value = dog_training.voicecall + 4
  707. if new_value > max_value:
  708. # 如果超过最大值,设置为最大值
  709. dog_training.voicecall = max_value
  710. else:
  711. dog_training.voicecall = new_value
  712. dog_training.save()
  713. # 更新训练记录
  714. training_log.training_count += 1
  715. training_log.save()
  716. print(
  717. f"Updated voicecall for dog ID {highest_mfcc_dog_id}, new value: {dog_training.voicecall}")
  718. # 更新亲密度、友好度和服从度
  719. try:
  720. dog_relationship = DogPersonalityRelationship.objects.get(d_id=highest_mfcc_dog_id)
  721. # 检查是否有今天的亲密度、友好度和服从度记录
  722. relationship_log = DogPersonalityRelationshipLog.objects.filter(
  723. d_id=highest_mfcc_dog_id,
  724. log_date=today
  725. ).first()
  726. # 如果没有今天的记录,创建一个新的
  727. if not relationship_log:
  728. relationship_log = DogPersonalityRelationshipLog.objects.create(
  729. d_id=highest_mfcc_dog_id,
  730. log_date=today,
  731. intimate_count=0,
  732. friendly_count=0,
  733. obedience_count=0
  734. )
  735. # 检查并更新亲密度
  736. if relationship_log.intimate_count < 2:
  737. dog_relationship.intimate += 1
  738. relationship_log.intimate_count += 1
  739. # 检查并更新友好度
  740. if relationship_log.friendly_count < 2:
  741. dog_relationship.friendly += 1
  742. relationship_log.friendly_count += 1
  743. # 检查并更新服从度
  744. if relationship_log.obedience_count < 2:
  745. dog_relationship.obedience += 1
  746. relationship_log.obedience_count += 1
  747. # 保存更新
  748. dog_relationship.save()
  749. relationship_log.save()
  750. print(f"Updated relationship attributes for dog ID {highest_mfcc_dog_id}")
  751. except DogPersonalityRelationship.DoesNotExist:
  752. print(f"No personality relationship found for dog ID {highest_mfcc_dog_id}")
  753. except DogTrainingNew.DoesNotExist:
  754. # 如果找不到对应的训练记录
  755. return JsonResponse({
  756. "status": "error",
  757. "message": "No matching dog was found",
  758. "date": current_time
  759. })
  760. except VoiceFeatureInfo.DoesNotExist:
  761. return JsonResponse({
  762. "status": "error",
  763. "message": "No matching voice was found",
  764. "date": current_time
  765. })
  766. except Exception as e:
  767. return JsonResponse({
  768. "status": "error",
  769. "message": f" error occurred during voice comparison: {str(e)}",
  770. "date": current_time
  771. })
  772. finally:
  773. # 清理临时文件
  774. if os.path.exists(output_mp3_path):
  775. os.remove(output_mp3_path)
  776. # 保存比对结果
  777. voice_compare = VoiceCompareScores.objects.create(
  778. uid=user_id,
  779. voice_feature_id=feature_id,
  780. keda_score=score,
  781. mfcc_score=similarity['result'],
  782. kedacompare_spend_time=message_response['time_diff'],
  783. mfcccompare_spend_time=similarity['time_diff'],
  784. createtime=current_time
  785. )
  786. dog_list = []
  787. for dog in dog_info:
  788. dog_serialized = serializers.serialize('json', [dog])
  789. dog_data = json.loads(dog_serialized)[0]['fields']
  790. dog_id = dog_data['d_id']
  791. # 获取狗的信息并转换为字典
  792. # 获取狗的信息并转换为字典
  793. new_dog_status = aidog_tools.model_to_dict_without_id(DogStatus.objects.filter(d_id=dog_id).first())
  794. new_dog_training = aidog_tools.model_to_dict_without_id(DogTrainingNew.objects.filter(d_id=dog_id).first())
  795. new_dog_body_attributes = aidog_tools.model_to_dict_without_id(
  796. DogBodyAttributes.objects.filter(d_id=dog_id).first())
  797. new_dog_personality_relationship = aidog_tools.model_to_dict_without_id(
  798. DogPersonalityRelationship.objects.filter(d_id=dog_id).first())
  799. # 合并所有相关信息
  800. dog_info_combined = {
  801. **dog_data,
  802. **new_dog_status,
  803. **new_dog_training,
  804. **new_dog_body_attributes,
  805. **new_dog_personality_relationship,
  806. }
  807. # 只保留字段而不是数据库内部的元数据(如 '_state')
  808. dog_info_clean = {key: value for key, value in dog_info_combined.items() if not key.startswith('_')}
  809. dog_list.append(dog_info_clean)
  810. # 返回包含所有voice得分的结果
  811. return JsonResponse({
  812. "status": "success",
  813. "code":200,
  814. "date": current_time,
  815. "message": 'voice call result',
  816. "call Score MFCC":voice_MFCC_score,
  817. "call Score Xunfei":voice_KEDA_score,
  818. "MFCC response time": similarity['time_diff'],
  819. "XunFei response time": message_response.get('time_diff') ,
  820. "dogs": dog_list
  821. })
  822. except Exception as e:
  823. # 全局异常处理
  824. import traceback
  825. traceback.print_exc()
  826. return JsonResponse({
  827. "status": "error",
  828. "message": f"Server processing exception: {str(e)}",
  829. "date": current_time
  830. })
  831. #语音指令发送接口:客户端上传用户id、wav.zip文件流,access_token,dog_id
  832. # 2025/4/28
  833. # author Feng
  834. # 将语音发送给服务器,并附上狗的id,返回狗的所有动作中得分最高的
  835. # commandScore 对应每一个命令得分。如果这个指令没有开启,得分0。如果开启最低得分1,最高得分99。得分类型float保留2位小数(减少得分一样概率)
  836. # 这个接口返回的是语音识别得分。得分+属性,由客户端判断结果。
  837. @csrf_exempt
  838. def voice_command_training(request):
  839. try:
  840. # 初始化全局变量
  841. output_mp3_path = None
  842. # 记录请求时间
  843. current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  844. similarity_time_diff = None
  845. xunfei_time_diff = None
  846. # 获取请求参数
  847. access_token = request.POST.get("access_token", '')
  848. user_id = request.POST.get("user_id", '')
  849. d_id = request.POST.get("dog_id", '')
  850. # 尝试从FILES获取文件(multipart/form-data方式)
  851. file_stream = request.FILES.get("voice") or request.FILES.get("file")
  852. # 如果FILES中没有找到文件,并且Content-Type是application/octet-stream,
  853. # 则从请求体中读取二进制数据
  854. if not file_stream and request.content_type == 'application/octet-stream':
  855. from django.core.files.uploadedfile import SimpleUploadedFile
  856. file_stream = SimpleUploadedFile("voice.zip", request.body, content_type="application/octet-stream")
  857. feature_id = str(time.time())[:12] # 生成声纹特征ID
  858. # 验证请求参数
  859. # 验证文件是否存在
  860. if not file_stream:
  861. return JsonResponse({
  862. "status": "error",
  863. "message": "voice file was not uploaded",
  864. "date": current_time
  865. })
  866. if not all([user_id,d_id, access_token]):
  867. return JsonResponse({
  868. "status": "error",
  869. "date": current_time,
  870. "message": "Please ensure all required fields are provided!",
  871. "end_time": current_time
  872. })
  873. # 验证访问令牌
  874. token_validation = aidog_tools.validate_access_token(access_token)
  875. if isinstance(token_validation, JsonResponse):
  876. return token_validation
  877. user_id = token_validation['user_id']
  878. # 当前时间
  879. date_now=datetime.now()
  880. # 生成唯一的文件路径
  881. output_mp3_path = _get_file_path(user_id, feature_id)
  882. # 处理上传的音频文件
  883. wav_to_mp3_result = dbOperate_upload_Uvoice.process_zip_audio_file(file_stream, output_mp3_path)
  884. if not wav_to_mp3_result:
  885. return JsonResponse({
  886. "status": "error",
  887. "message": "audio file failed to be processed",
  888. "date": current_time
  889. })
  890. try:
  891. # 获取用户原始声纹信息
  892. voices = VoiceFeatureInfo.objects.filter(uid=user_id,d_id=d_id)
  893. dog_info = Doginfo.objects.filter(owner_id=user_id)
  894. dog_training=DogTrainingNew.objects.filter(d_id=d_id)
  895. # 创建一个列表来存储所有voice的得分结果
  896. commandScoreMFCC = {
  897. }
  898. commandScoreXunFei = {
  899. }
  900. # 默认初始化这些变量,以防在下面的代码中没有被赋值
  901. similarity_time_diff = 0
  902. xunfei_time_diff = 0
  903. for voice in voices:
  904. # 使用本地比对算法,只比对非voiceCall类型
  905. if voice.command_type!= 'voiceCall' :
  906. sound_wave_1 = voice.voice_path
  907. sound_wave_2 = output_mp3_path
  908. mfcc_1 = wave_compare.extract_mfcc(sound_wave_1)
  909. mfcc_2 = wave_compare.extract_mfcc(sound_wave_2)
  910. similarity = wave_compare.compute_similarity(mfcc_2, mfcc_1)
  911. commandScoreMFCC[voice.command_type] = similarity['result']
  912. similarity_time_diff = similarity['time_diff'] # 保存最后一次比对的时间差
  913. print(f"Audio similarity. API response: {similarity}")
  914. # 使用科大算法
  915. message_response = generalRequest.req_url(
  916. api_name='searchScoreFea', APPId=APPId, APIKey=APIKey, APISecret=APISecret,
  917. file_path=voice.voice_path, featureId=voice.voice_feature_id, groupId=user_id
  918. )
  919. if message_response.get('code') == 0:
  920. data_dict = json.loads(message_response.get('msg'))
  921. score = data_dict.get('score')
  922. xunfei_time_diff = message_response.get('time_diff') # 保存科大API的时间差
  923. commandScoreXunFei[voice.command_type] = float(data_dict.get('score', 0))
  924. else:
  925. return JsonResponse({
  926. "status": "error",
  927. "code":400,
  928. "message": message_response.get('msg'),
  929. "date": current_time
  930. })
  931. # 保存比对结果
  932. voice_compare = VoiceCompareScores.objects.create(
  933. uid=user_id,
  934. voice_feature_id=voice.voice_feature_id,
  935. keda_score=float(data_dict.get('score', 0)),
  936. mfcc_score=similarity['result'],
  937. kedacompare_spend_time=message_response['time_diff'],
  938. mfcccompare_spend_time=similarity['time_diff'],
  939. createtime=current_time
  940. )
  941. # 找出MFCC得分最高的狗ID
  942. highest_mfcc_score = 0
  943. highest_mfcc_command_type= None
  944. for command_type, mfcc_score in commandScoreMFCC.items():
  945. if mfcc_score and float(mfcc_score) > highest_mfcc_score:
  946. highest_mfcc_score = float(mfcc_score)
  947. highest_mfcc_command_type = command_type
  948. # 如果找到了最高分数的狗ID,更新其voicecommand值和personality属性
  949. if highest_mfcc_command_type:
  950. try:
  951. # 更新voicecall,每日最多+12
  952. dog_training = DogTrainingNew.objects.get(d_id=d_id)
  953. # 获取今天的日期(年-月-日)
  954. today = date_now.date()
  955. # 检查是否有今天的训练记录
  956. training_log = DogTrainingLog.objects.filter(
  957. d_id=d_id,
  958. owner_id=user_id,
  959. command_type='voiceCommand',
  960. training_date=today
  961. ).first()
  962. # 如果没有今天的记录,创建一个新的
  963. if not training_log:
  964. training_log = DogTrainingLog.objects.create(
  965. d_id=d_id,
  966. owner_id=user_id,
  967. command_type='voiceCommand',
  968. training_date=today,
  969. training_count=0
  970. )
  971. # 检查是否达到每日上限
  972. if training_log.training_count < 3: # 每次+4,最多+12,所以最多3次
  973. # voicecommand是TINYINT类型,最大值限制为100
  974. max_value = 100
  975. # 检查更新后的值是否超过最大值
  976. new_value = dog_training.voicecommand + 4
  977. if new_value > max_value:
  978. # 如果超过最大值,设置为最大值
  979. dog_training.voicecommand = max_value
  980. else:
  981. dog_training.voicecommand = new_value
  982. dog_training.save()
  983. # 更新训练记录
  984. training_log.training_count += 1
  985. training_log.save()
  986. print(
  987. f"Updated voicecommand for dog ID {d_id}, new value: {dog_training.voicecommand}")
  988. # 更新亲密度、友好度和服从度
  989. try:
  990. dog_relationship = DogPersonalityRelationship.objects.get(d_id=d_id)
  991. # 检查是否有今天的亲密度、友好度和服从度记录
  992. relationship_log = DogPersonalityRelationshipLog.objects.filter(
  993. d_id=d_id,
  994. log_date=today
  995. ).first()
  996. # 如果没有今天的记录,创建一个新的
  997. if not relationship_log:
  998. relationship_log = DogPersonalityRelationshipLog.objects.create(
  999. d_id=d_id,
  1000. log_date=today,
  1001. intimate_count=0,
  1002. friendly_count=0,
  1003. obedience_count=0
  1004. )
  1005. # 检查并更新亲密度
  1006. if relationship_log.intimate_count < 2:
  1007. dog_relationship.intimate += 1
  1008. relationship_log.intimate_count += 1
  1009. # 检查并更新友好度
  1010. if relationship_log.friendly_count < 2:
  1011. dog_relationship.friendly += 1
  1012. relationship_log.friendly_count += 1
  1013. # 检查并更新服从度
  1014. if relationship_log.obedience_count < 2:
  1015. dog_relationship.obedience += 1
  1016. relationship_log.obedience_count += 1
  1017. # 保存更新
  1018. dog_relationship.save()
  1019. relationship_log.save()
  1020. print(f"Updated relationship attributes for dog ID {d_id}")
  1021. except DogPersonalityRelationship.DoesNotExist:
  1022. print(f"No personality relationship found for dog ID {d_id}")
  1023. except DogTrainingNew.DoesNotExist:
  1024. # 如果找不到对应的训练记录
  1025. return JsonResponse({
  1026. "status": "error",
  1027. "message": "No matching dog was found",
  1028. "date": current_time
  1029. })
  1030. except VoiceFeatureInfo.DoesNotExist:
  1031. return JsonResponse({
  1032. "status": "error",
  1033. "message": "No matching voice was found",
  1034. "date": current_time
  1035. })
  1036. except Exception as e:
  1037. return JsonResponse({
  1038. "status": "error",
  1039. "message": f" error occurred during voice comparison: {str(e)}",
  1040. "date": current_time
  1041. })
  1042. finally:
  1043. # 清理临时文件
  1044. if os.path.exists(output_mp3_path):
  1045. os.remove(output_mp3_path)
  1046. dog_list = []
  1047. for dog in dog_info:
  1048. dog_serialized = serializers.serialize('json', [dog])
  1049. dog_data = json.loads(dog_serialized)[0]['fields']
  1050. dog_id = dog_data['d_id']
  1051. # 获取狗的信息并转换为字典
  1052. # 获取狗的信息并转换为字典
  1053. new_dog_status = aidog_tools.model_to_dict_without_id(DogStatus.objects.filter(d_id=dog_id).first())
  1054. new_dog_training = aidog_tools.model_to_dict_without_id(DogTrainingNew.objects.filter(d_id=dog_id).first())
  1055. new_dog_body_attributes = aidog_tools.model_to_dict_without_id(
  1056. DogBodyAttributes.objects.filter(d_id=dog_id).first())
  1057. new_dog_personality_relationship = aidog_tools.model_to_dict_without_id(
  1058. DogPersonalityRelationship.objects.filter(d_id=dog_id).first())
  1059. # 合并所有相关信息
  1060. dog_info_combined = {
  1061. **dog_data,
  1062. **new_dog_status,
  1063. **new_dog_training,
  1064. **new_dog_body_attributes,
  1065. **new_dog_personality_relationship,
  1066. }
  1067. # 只保留字段而不是数据库内部的元数据(如 '_state')
  1068. dog_info_clean = {key: value for key, value in dog_info_combined.items() if not key.startswith('_')}
  1069. dog_list.append(dog_info_clean)
  1070. # 返回包含所有voice得分的结果
  1071. return JsonResponse({
  1072. "status": "success",
  1073. "code":200,
  1074. "date": current_time,
  1075. "message": 'voice result',
  1076. "commandScoreMFCC":commandScoreMFCC,
  1077. "commandScoreXunFei":commandScoreXunFei,
  1078. "MFCC response time": similarity_time_diff,
  1079. "XunFei response time": xunfei_time_diff,
  1080. "dogs": dog_list
  1081. })
  1082. except Exception as e:
  1083. # 全局异常处理
  1084. import traceback
  1085. traceback.print_exc()
  1086. return JsonResponse({
  1087. "status": "error",
  1088. "message": f"Server processing exception: {str(e)}",
  1089. "date": current_time
  1090. })
  1091. #重制语音训练接口:客户端上传user_id、dog_id,access_token,date_time,type
  1092. # POST /api/voice/reset/
  1093. # 2025/5/30
  1094. # author Feng
  1095. # type==call 重制voiceCall
  1096. # type==command 重制voiceCommand,以及下面所有指令
  1097. # type==all 重制所有
  1098. # 这个接口返回的是重置结果。
  1099. @csrf_exempt
  1100. def voice_reset(request):
  1101. # 记录请求时间
  1102. current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  1103. try:
  1104. # 获取请求参数
  1105. params = aidog_request_tools.handle_post_request_parameters(
  1106. request,
  1107. ["access_token", "user_id", "dog_id", "type"]
  1108. )
  1109. date_time = request.POST.get("date_time", '')
  1110. access_token = params["access_token"]
  1111. dog_id = params["dog_id"]
  1112. type = params["type"]
  1113. # 验证访问令牌
  1114. token_validation = aidog_tools.validate_access_token(access_token)
  1115. if isinstance(token_validation, JsonResponse):
  1116. return token_validation
  1117. user_id = token_validation['user_id']
  1118. # try:
  1119. # 根据type类型,删除VoiceFeatureInfo表中该type类型下的mfcc字段清空,path字段和path对应的本地mp3音频文件,
  1120. # voice_feature_id清空并根据此voice_feature_id通过调用科大讯飞API删除对应id下的音频文件message_response = generalRequest.req_url(
  1121. # api_name='deleteFeature', APPId=APPId, APIKey=APIKey, APISecret=APISecret,
  1122. # file_path=voice.voice_path, featureId=voice.voice_feature_id, groupId=user_id
  1123. # )
  1124. # 根据dog_id,查找DogTrainingNew表中该type类型下的数值并清零
  1125. # 如果type=all,则清除DogTrainingNew、VoiceFeatureInfo表中该dog_id下的所有type数值以及本地音频,科大讯飞音频
  1126. try:
  1127. # voices = VoiceFeatureInfo.objects.filter(uid=user_id,d_id=dog_id)
  1128. # dog_training=DogTrainingNew.objects.filter(d_id=dog_id)
  1129. dog_info = Doginfo.objects.filter(owner_id=user_id)
  1130. reset_details = {
  1131. 'voice_files_deleted': 0,
  1132. 'iflytek_features_deleted': 0,
  1133. 'training_fields_reset': []
  1134. }
  1135. if type == "all":
  1136. # 重置所有类型的数据
  1137. result = reset_all_data(user_id, dog_id, reset_details)
  1138. else:
  1139. # 重置特定类型的数据
  1140. result = reset_specific_type_data(user_id, dog_id, type, reset_details)
  1141. if not result:
  1142. return {
  1143. 'error': result,
  1144. 'message': 'Reset failed',
  1145. 'details': reset_details
  1146. }
  1147. except Exception as e:
  1148. return {
  1149. 'success': False,
  1150. 'message': f'Error during reset operation: {str(e)}',
  1151. 'details': {}
  1152. }
  1153. dog_list = []
  1154. for dog in dog_info:
  1155. dog_serialized = serializers.serialize('json', [dog])
  1156. dog_data = json.loads(dog_serialized)[0]['fields']
  1157. dog_id = dog_data['d_id']
  1158. # 获取狗的信息并转换为字典
  1159. new_dog_status = aidog_tools.model_to_dict_without_id(DogStatus.objects.filter(d_id=dog_id).first())
  1160. new_dog_training = aidog_tools.model_to_dict_without_id(DogTrainingNew.objects.filter(d_id=dog_id).first())
  1161. new_dog_body_attributes = aidog_tools.model_to_dict_without_id(
  1162. DogBodyAttributes.objects.filter(d_id=dog_id).first())
  1163. new_dog_personality_relationship = aidog_tools.model_to_dict_without_id(
  1164. DogPersonalityRelationship.objects.filter(d_id=dog_id).first())
  1165. # 合并所有相关信息
  1166. dog_info_combined = {
  1167. **dog_data,
  1168. **new_dog_status,
  1169. **new_dog_training,
  1170. **new_dog_body_attributes,
  1171. **new_dog_personality_relationship,
  1172. }
  1173. # 只保留字段而不是数据库内部的元数据(如 '_state')
  1174. dog_info_clean = {key: value for key, value in dog_info_combined.items() if not key.startswith('_')}
  1175. dog_list.append(dog_info_clean)
  1176. # 返回包含所有voice得分的结果
  1177. return JsonResponse({
  1178. "status": "success",
  1179. "code":200,
  1180. "date": current_time,
  1181. "message": 'reset success',
  1182. "dogs": dog_list,
  1183. "reset_details": reset_details
  1184. })
  1185. except Exception as e:
  1186. # 全局异常处理
  1187. import traceback
  1188. traceback.print_exc()
  1189. return JsonResponse({
  1190. "status": "error",
  1191. "message": f"Server processing exception: {str(e)}",
  1192. "date": current_time
  1193. })
  1194. # 重置所有类型的数据
  1195. def reset_all_data(user_id, dog_id, reset_details):
  1196. try:
  1197. # 1. 处理VoiceFeatureInfo表中的所有记录
  1198. voices = VoiceFeatureInfo.objects.filter(uid=user_id, d_id=dog_id)
  1199. for voice in voices:
  1200. # 删除本地音频文件
  1201. if voice.voice_path and delete_local_audio_file(voice.voice_path):
  1202. reset_details['voice_files_deleted'] += 1
  1203. # 调用科大讯飞API删除音频特征
  1204. if voice.voice_feature_id:
  1205. if delete_iflytek_feature(user_id, voice):
  1206. reset_details['iflytek_features_deleted'] += 1
  1207. # 清空相关字段
  1208. voice.mfcc = None
  1209. voice.voice_path = None
  1210. voice.voice_feature_id = None
  1211. voice.save()
  1212. # 2. 重置DogTrainingNew表中的所有训练数据
  1213. dog_training = DogTrainingNew.objects.filter(d_id=dog_id).first()
  1214. if dog_training:
  1215. training_fields = get_all_training_fields()
  1216. for field in training_fields:
  1217. if hasattr(dog_training, field):
  1218. setattr(dog_training, field, 0)
  1219. reset_details['training_fields_reset'].append(field)
  1220. dog_training.voicecallenable=0
  1221. dog_training.voicecommandenable=0
  1222. dog_training.save()
  1223. return True
  1224. except Exception as e:
  1225. print(f"Error in reset_all_data: {str(e)}")
  1226. return False
  1227. # 重置特定类型的数据
  1228. def reset_specific_type_data(user_id, dog_id, reset_type, reset_details):
  1229. try:
  1230. # 1. 处理VoiceFeatureInfo表中特定type的记录
  1231. voices = VoiceFeatureInfo.objects.filter(uid=user_id, d_id=dog_id, command_type=reset_type)
  1232. for voice in voices:
  1233. # 删除本地音频文件
  1234. if voice.voice_path and delete_local_audio_file(voice.voice_path):
  1235. reset_details['voice_files_deleted'] += 1
  1236. # 调用科大讯飞API删除音频特征
  1237. if voice.voice_feature_id:
  1238. if delete_iflytek_feature(user_id, voice):
  1239. reset_details['iflytek_features_deleted'] += 1
  1240. # 清空相关字段
  1241. voice.mfcc = None
  1242. voice.voice_path = None
  1243. voice.voice_feature_id = None
  1244. voice.save()
  1245. # 2. 重置DogTrainingNew表中特定type的数值
  1246. dog_training = DogTrainingNew.objects.filter(d_id=dog_id).first()
  1247. if dog_training and hasattr(dog_training, reset_type):
  1248. setattr(dog_training, reset_type, 0)
  1249. dog_training.save()
  1250. reset_details['training_fields_reset'].append(reset_type)
  1251. return True
  1252. except Exception as e:
  1253. print(f"Error in reset_specific_type_data: {str(e)}")
  1254. return False
  1255. # 获取所有训练相关的字段名
  1256. def get_all_training_fields():
  1257. return [
  1258. 'commandsit', 'commandstand', 'commandliedown', 'commandbark', 'commandturnl', 'commandtouch', 'commandturnr',
  1259. 'commanddeath','commandshake',
  1260. ]
  1261. # 删除本地音频文件
  1262. def delete_local_audio_file(file_path):
  1263. try:
  1264. if os.path.exists(file_path):
  1265. os.remove(file_path)
  1266. print(f"Successfully deleted local file: {file_path}")
  1267. return True
  1268. else:
  1269. print(f"File not found: {file_path}")
  1270. return False
  1271. except Exception as e:
  1272. print(f"Error deleting file {file_path}: {str(e)}")
  1273. return False
  1274. # 调用科大讯飞API删除音频特征
  1275. def delete_iflytek_feature(user_id, voice):
  1276. try:
  1277. # 科大讯飞配置
  1278. message_response = generalRequest.req_url(
  1279. api_name='deleteFeature',
  1280. APPId=APPId, # 需要定义
  1281. APIKey=APIKey, # 需要定义
  1282. APISecret=APISecret, # 需要定义
  1283. file_path=voice.voice_path,
  1284. featureId=voice.voice_feature_id,
  1285. groupId=user_id
  1286. )
  1287. # 根据API响应判断是否成功
  1288. if message_response and message_response.get('status') == 'success':
  1289. print(f"Successfully deleted Iflytek feature: {voice.voice_feature_id}")
  1290. return True
  1291. else:
  1292. print(f"Failed to delete Iflytek feature: {voice.voice_feature_id}")
  1293. return False
  1294. except Exception as e:
  1295. print(f"Error calling Iflytek API: {str(e)}")
  1296. return False
  1297. #科大讯飞校验声纹接口:客户端上传用户id、wav格式文件流,转换文件流为MP3缓存在本地固定路径下
  1298. # 提交用户id到数据库遍历全部声纹id
  1299. # 每个声纹id+用户id+mp3路径上传到科大校验接口,筛选出得分最高的声纹id,并返回其口令类型,删除此mp3文件
  1300. # 2024/12/02 无token
  1301. # author Feng
  1302. # 2025/3/31更新
  1303. def check_Uvoice_latest(request):
  1304. user_uid = request.POST.get("user_id", '')
  1305. file_stream = request.FILES.get("file_stream")
  1306. best_res = None
  1307. max_score = 0.3 # Threshold score
  1308. date_now = datetime.now()
  1309. current_time = date_now.strftime('%Y-%m-%d %H:%M:%S')
  1310. # Validate inputs
  1311. if not file_stream:
  1312. return JsonResponse({
  1313. "status": "error",
  1314. "message": "File not uploaded.",
  1315. "date": current_time
  1316. })
  1317. if not user_uid:
  1318. return JsonResponse({
  1319. "status": "error",
  1320. "date": current_time,
  1321. "message": "Please ensure all required fields are provided!",
  1322. "end_time": current_time
  1323. })
  1324. # Set up cache directory
  1325. cache_directory = "audio_cache"
  1326. os.makedirs(cache_directory, exist_ok=True)
  1327. # Create temporary file
  1328. timestamp = int(time.time())
  1329. mp3_path = os.path.join(cache_directory, f"voice_check_{user_uid}_{timestamp}.mp3")
  1330. # Process audio file
  1331. if not dbOperate_upload_Uvoice.process_audio_file(file_stream, mp3_path):
  1332. return JsonResponse({
  1333. "status": "error",
  1334. "message": "Failed to process the audio file.",
  1335. "date": current_time
  1336. })
  1337. # Get all voice features for the user
  1338. voices = VoiceFeatureInfo.objects.filter(uid=user_uid)
  1339. # Check the audio against each stored feature
  1340. for voice in voices:
  1341. message_response = generalRequest.req_url(
  1342. api_name='searchScoreFea',
  1343. APPId=APPId,
  1344. APIKey=APIKey,
  1345. APISecret=APISecret,
  1346. file_path=mp3_path,
  1347. featureId=voice.voice_feature_id,
  1348. groupId=user_uid
  1349. )
  1350. if message_response.get('code') == 0:
  1351. data_dict = json.loads(message_response.get('msg'))
  1352. score = data_dict.get('score')
  1353. # Update best match
  1354. if score and score > max_score:
  1355. max_score = score
  1356. best_res = {
  1357. "status": "success",
  1358. "date": current_time,
  1359. "message": message_response,
  1360. "score": score,
  1361. "command_type": voice.command_type,
  1362. "end_time": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  1363. }
  1364. # Award points if match is found
  1365. try:
  1366. # Get or create points tracker
  1367. audio_points, created = AudioPointsTracker.objects.get_or_create(
  1368. uid=user_uid,
  1369. command_type=voice.command_type,
  1370. defaults={'points': 0}
  1371. )
  1372. # Add 10 points
  1373. audio_points.points += 10
  1374. audio_points.save()
  1375. best_res["points"] = audio_points.points
  1376. best_res["points_added"] = 10
  1377. except Exception as db_error:
  1378. best_res["points_error"] = str(db_error)
  1379. # Clean up temporary file
  1380. os.remove(mp3_path)
  1381. # Return results
  1382. if best_res:
  1383. return JsonResponse(best_res)
  1384. else:
  1385. return JsonResponse({
  1386. "status": "error",
  1387. "message": "请重新发出口令.",
  1388. "date": current_time
  1389. })