录像截图功能提交

This commit is contained in:
zhaozilong12 2025-08-06 08:48:38 +08:00
parent 9d5bdb5005
commit 7bfe1f5acb
4 changed files with 182 additions and 122 deletions

View File

@ -618,47 +618,28 @@ def stop_video_streaming():
def start_detection(): def start_detection():
"""开始检测""" """开始检测"""
try: try:
if not db_manager: if not db_manager or not device_manager:
return jsonify({'success': False, 'error': '数据库管理器未初始化'}), 500 return jsonify({'success': False, 'error': '数据库管理器或设备管理器未初始化'}), 500
data = flask_request.get_json() data = flask_request.get_json()
patient_id = data.get('patientId') or data.get('patient_id') patient_id = data.get('patient_id')
settings = data.get('settings', '{}')
creator_id = data.get('creator_id') creator_id = data.get('creator_id')
if not patient_id or not creator_id:
return jsonify({'success': False, 'error': '缺少患者ID或创建人ID'}), 400
if not patient_id: # 调用create_detection_session方法settings传空字典
return jsonify({ session_id = db_manager.create_detection_session(patient_id, settings={}, creator_id=creator_id)
'success': False,
'error': '缺少必要参数: patient_id'
}), 400
# 解析设置参数 # 开始同步录制
if isinstance(settings, str): recording_response = None
try: try:
settings = json.loads(settings) recording_response = device_manager.start_recording(session_id, patient_id)
except json.JSONDecodeError: except Exception as rec_e:
settings = {} logger.error(f'开始同步录制失败: {rec_e}')
# 创建检测会话 start_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
session_id = db_manager.create_detection_session(
patient_id=str(patient_id),
settings=settings,
creator_id=creator_id
)
if session_id:
logger.info(f'检测会话已创建 - 会话ID: {session_id}, 患者ID: {patient_id}')
return jsonify({
'success': True,
'session_id': session_id,
'message': '检测会话创建成功'
})
else:
return jsonify({
'success': False,
'error': '创建检测会话失败'
}), 500
return jsonify({'success': True, 'session_id': session_id, 'detectionStartTime': start_time, 'recording': recording_response})
except Exception as e: except Exception as e:
logger.error(f'开始检测失败: {e}') logger.error(f'开始检测失败: {e}')
return jsonify({'success': False, 'error': str(e)}), 500 return jsonify({'success': False, 'error': str(e)}), 500
@ -667,15 +648,50 @@ def start_detection():
def stop_detection(session_id): def stop_detection(session_id):
"""停止检测""" """停止检测"""
try: try:
if not db_manager: if not db_manager or not device_manager:
return jsonify({'success': False, 'error': '数据库管理器未初始化'}), 500 logger.error('数据库管理器或设备管理器未初始化')
return jsonify({'success': False, 'error': '数据库管理器或设备管理器未初始化'}), 500
if not session_id: if not session_id:
logger.error('缺少会话ID')
return jsonify({ return jsonify({
'success': False, 'success': False,
'error': '缺少会话ID' 'error': '缺少会话ID'
}), 400 }), 400
data = flask_request.get_json()
logger.debug(f'接收到停止检测请求session_id: {session_id}, 请求数据: {data}')
# video_data = data.get('videoData') if data else None
video_data = data['videoData']
mime_type = data.get('mimeType', 'video/webm;codecs=vp9') # 默认webm格式
# 验证base64视频数据格式
if not video_data.startswith('data:video/'):
return jsonify({
'success': False,
'message': '无效的视频数据格式'
}), 400
# try:
# header, encoded = video_data.split(',', 1)
# video_bytes = base64.b64decode(encoded)
# except Exception as e:
# return jsonify({
# 'success': False,
# 'message': f'视频数据解码失败: {str(e)}'
# }), 400
# 停止同步录制,传递视频数据
try:
logger.debug(f'调用device_manager.stop_recordingsession_id: {session_id}, video_data长度: {len(video_data) if video_data else 0}')
if video_data is None:
logger.warning(f'视频数据为空session_id: {session_id}')
else:
logger.debug(f'视频数据长度: {len(video_data)} 字符,约 {len(video_data)*3/4/1024:.2f} KB, session_id: {session_id}')
restrt=device_manager.stop_recording(session_id, video_data_base64=video_data)
logger.error(restrt)
except Exception as rec_e:
logger.error(f'停止同步录制失败: {rec_e}', exc_info=True)
raise
# 更新会话状态为已完成 # 更新会话状态为已完成
success = db_manager.update_session_status(session_id, 'completed') success = db_manager.update_session_status(session_id, 'completed')
@ -686,13 +702,14 @@ def stop_detection(session_id):
'message': '检测已停止' 'message': '检测已停止'
}) })
else: else:
logger.error('停止检测失败,更新会话状态失败')
return jsonify({ return jsonify({
'success': False, 'success': False,
'error': '停止检测失败' 'error': '停止检测失败'
}), 500 }), 500
except Exception as e: except Exception as e:
logger.error(f'停止检测失败: {e}') logger.error(f'停止检测失败: {e}', exc_info=True)
return jsonify({'success': False, 'error': str(e)}), 500 return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/detection/<session_id>/status', methods=['GET']) @app.route('/api/detection/<session_id>/status', methods=['GET'])
@ -823,6 +840,7 @@ def stop_sync_recording():
data = flask_request.get_json() data = flask_request.get_json()
session_id = data.get('session_id') session_id = data.get('session_id')
video_data = data.get('videoData') # 新增接收前端传递的视频数据
if not session_id: if not session_id:
return jsonify({ return jsonify({
@ -830,7 +848,7 @@ def stop_sync_recording():
'error': '缺少必要参数: session_id' 'error': '缺少必要参数: session_id'
}), 400 }), 400
result = device_manager.stop_recording(session_id) result = device_manager.stop_recording(session_id, video_data_base64=video_data)
if result['success']: if result['success']:
logger.info(f'同步录制已停止 - 会话ID: {session_id}') logger.info(f'同步录制已停止 - 会话ID: {session_id}')
@ -843,7 +861,6 @@ def stop_sync_recording():
return jsonify({'success': False, 'error': str(e)}), 500 return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/history/sessions', methods=['GET']) @app.route('/api/history/sessions', methods=['GET'])
def get_detection_sessions(): def get_detection_sessions():
"""获取检测会话历史""" """获取检测会话历史"""

Binary file not shown.

View File

@ -632,21 +632,19 @@ class DatabaseManager:
cursor.execute(''' cursor.execute('''
INSERT INTO detection_sessions ( INSERT INTO detection_sessions (
id, patient_id, creator_id, duration, frequency, settings, status, id, patient_id, creator_id, duration, settings, status,
diagnosis_info, treatment_info, suggestion_info, notes, start_time, created_at diagnosis_info, treatment_info, suggestion_info, start_time, created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', ( ''', (
session_id, session_id,
patient_id, patient_id,
creator_id, creator_id,
settings.get('duration', 60), settings.get('duration', 60),
settings.get('frequency', 60),
json.dumps(settings), json.dumps(settings),
'created', 'running',
settings.get('diagnosis_info', ''), settings.get('diagnosis_info', ''),
settings.get('treatment_info', ''), settings.get('treatment_info', ''),
settings.get('suggestion_info', ''), settings.get('suggestion_info', ''),
settings.get('notes', ''),
china_time, china_time,
china_time china_time
)) ))
@ -660,7 +658,7 @@ class DatabaseManager:
logger.error(f'创建检测会话失败: {e}') logger.error(f'创建检测会话失败: {e}')
raise raise
def update_session_status(self, session_id: str, status: str, data_points: int = 0): def update_session_status(self, session_id: str, status: str):
"""更新会话状态""" """更新会话状态"""
conn = self.get_connection() conn = self.get_connection()
cursor = conn.cursor() cursor = conn.cursor()
@ -671,15 +669,15 @@ class DatabaseManager:
china_time = self.get_china_time() china_time = self.get_china_time()
cursor.execute(''' cursor.execute('''
UPDATE detection_sessions SET UPDATE detection_sessions SET
status = ?, data_points = ?, end_time = ? status = ?, end_time = ?
WHERE id = ? WHERE id = ?
''', (status, data_points, china_time, session_id)) ''', (status, china_time, session_id))
else: else:
cursor.execute(''' cursor.execute('''
UPDATE detection_sessions SET UPDATE detection_sessions SET
status = ?, data_points = ? status = ?
WHERE id = ? WHERE id = ?
''', (status, data_points, session_id)) ''', (status, session_id))
conn.commit() conn.commit()
logger.info(f'更新会话状态: {session_id} -> {status}') logger.info(f'更新会话状态: {session_id} -> {status}')

View File

@ -24,6 +24,7 @@ from concurrent.futures import ThreadPoolExecutor
import logging import logging
# 数据库管理 # 数据库管理
# from backend.app import get_detection_sessions
from database import DatabaseManager from database import DatabaseManager
# FemtoBolt深度相机支持 # FemtoBolt深度相机支持
@ -265,10 +266,14 @@ class DeviceManager:
if platform.system() == "Windows": if platform.system() == "Windows":
# 优先使用Orbbec SDK K4A Wrapper与azure_kinect_image_example.py一致 # 优先使用Orbbec SDK K4A Wrapper与azure_kinect_image_example.py一致
orbbec_paths = [ base_dir = os.path.dirname(os.path.abspath(__file__))
r"D:\OrbbecSDK_K4A_Wrapper_v1.10.3_windows_202408091749\bin\k4a.dll", dll_path = os.path.join(base_dir, "dll", "bin", "k4a.dll")
] orbbec_paths = []
if os.path.exists(dll_path):
orbbec_paths.append(dll_path)
# orbbec_paths = [
# r"D:\BodyBalanceEvaluation\backend\dll\bin\k4a.dll",
# ]
# Azure Kinect SDK标准安装路径备用 # Azure Kinect SDK标准安装路径备用
standard_paths = [ standard_paths = [
r"C:\Program Files\Azure Kinect SDK v1.4.1\sdk\windows-desktop\amd64\release\bin\k4a.dll", r"C:\Program Files\Azure Kinect SDK v1.4.1\sdk\windows-desktop\amd64\release\bin\k4a.dll",
@ -498,54 +503,80 @@ class DeviceManager:
} }
try: try:
# 1. 采集头部姿态数据从IMU设备获取 # # 1. 采集头部姿态数据从IMU设备获取
if self.device_status['imu']: # if self.device_status['imu']:
head_pose_data = self._collect_head_pose_data() # head_pose_data = self._collect_head_pose_data()
if head_pose_data: # if head_pose_data:
data['head_pose'] = json.dumps(head_pose_data) # data['head_pose'] = json.dumps(head_pose_data)
logger.debug(f'头部姿态数据采集成功: {session_id}') # logger.debug(f'头部姿态数据采集成功: {session_id}')
# 2. 采集身体姿态数据从FemtoBolt深度相机获取 # # 2. 采集身体姿态数据从FemtoBolt深度相机获取
if self.device_status['femtobolt']: # if self.device_status['femtobolt']:
body_pose_data = self._collect_body_pose_data() # body_pose_data = self._collect_body_pose_data()
if body_pose_data: # if body_pose_data:
data['body_pose'] = json.dumps(body_pose_data) # data['body_pose'] = json.dumps(body_pose_data)
logger.debug(f'身体姿态数据采集成功: {session_id}') # logger.debug(f'身体姿态数据采集成功: {session_id}')
# 3. 采集身体视频截图从FemtoBolt深度相机获取 # # 3. 采集身体视频截图从FemtoBolt深度相机获取
if self.device_status['femtobolt']: # if self.device_status['femtobolt']:
body_image_path = self._capture_body_image(data_dir) # body_image_path = self._capture_body_image(data_dir)
if body_image_path: # if body_image_path:
data['body_image'] = str(body_image_path) # data['body_image'] = str(body_image_path)
logger.debug(f'身体截图保存成功: {body_image_path}') # logger.debug(f'身体截图保存成功: {body_image_path}')
# 4. 采集足部压力数据(从压力传感器获取) # # 4. 采集足部压力数据(从压力传感器获取)
if self.device_status['pressure']: # if self.device_status['pressure']:
foot_data = self._collect_foot_pressure_data() # foot_data = self._collect_foot_pressure_data()
if foot_data: # if foot_data:
data['foot_data'] = json.dumps(foot_data) # data['foot_data'] = json.dumps(foot_data)
logger.debug(f'足部压力数据采集成功: {session_id}') # logger.debug(f'足部压力数据采集成功: {session_id}')
# 5. 采集足部监测视频截图(从摄像头获取) # # 5. 采集足部监测视频截图(从摄像头获取)
if self.device_status['camera']: # if self.device_status['camera']:
foot_image_path = self._capture_foot_image(data_dir) # foot_image_path = self._capture_foot_image(data_dir)
if foot_image_path: # if foot_image_path:
data['foot_image'] = str(foot_image_path) # data['foot_image'] = str(foot_image_path)
logger.debug(f'足部截图保存成功: {foot_image_path}') # logger.debug(f'足部截图保存成功: {foot_image_path}')
# 6. 生成足底压力数据图(从压力传感器数据生成) # # 6. 生成足底压力数据图(从压力传感器数据生成)
if self.device_status['pressure']: # if self.device_status['pressure']:
foot_data_image_path = self._generate_foot_pressure_image(data_dir) # foot_data_image_path = self._generate_foot_pressure_image(data_dir)
if foot_data_image_path: # if foot_data_image_path:
data['foot_data_image'] = str(foot_data_image_path) # data['foot_data_image'] = str(foot_data_image_path)
logger.debug(f'足底压力数据图生成成功: {foot_data_image_path}') # logger.debug(f'足底压力数据图生成成功: {foot_data_image_path}')
# 7. 保存屏幕录制截图从前端传入的base64数据 # 7. 保存屏幕录制截图从前端传入的base64数据
if screen_image_base64: if screen_image_base64:
screen_image_path = self._save_screen_image(data_dir, screen_image_base64) try:
if screen_image_path: logger.debug(f'屏幕截图保存.................{screen_image_base64}')
data['screen_image'] = str(screen_image_path) # 保存屏幕截图的base64数据为图片文件
logger.debug(f'屏幕截图保存成功: {screen_image_path}') screen_image_path = None
if screen_image_base64:
try:
if screen_image_base64.startswith('data:image/'):
base64_data = screen_image_base64.split(',')[1]
else:
base64_data = screen_image_base64
image_data = base64.b64decode(base64_data)
image_path = data_dir / 'screen_image.png'
with open(image_path, 'wb') as f:
f.write(image_data)
abs_image_path = image_path.resolve()
abs_cwd = Path.cwd().resolve()
screen_image_path = str(abs_image_path.relative_to(abs_cwd))
logger.debug(f'屏幕截图保存成功: {screen_image_path}')
except Exception as e:
logger.error(f'屏幕截图保存失败: {e}')
import traceback
logger.error(traceback.format_exc())
if screen_image_path:
data['screen_image'] = str(screen_image_path)
logger.debug(f'屏幕截图保存成功: {screen_image_path}')
except Exception as e:
logger.error(f'屏幕截图保存失败: {e}')
import traceback
logger.error(traceback.format_exc())
# 更新最新数据 # 更新最新数据
with self.data_lock: with self.data_lock:
@ -1073,10 +1104,10 @@ class DeviceManager:
body_video_path, fourcc, fps, (1280, 720) body_video_path, fourcc, fps, (1280, 720)
) )
# 屏幕录制写入器(默认分辨率,后续根据实际帧调整) # # 屏幕录制写入器(默认分辨率,后续根据实际帧调整)
self.screen_video_writer = cv2.VideoWriter( # self.screen_video_writer = cv2.VideoWriter(
screen_video_path, fourcc, fps, (1920, 1080) # screen_video_path, fourcc, fps, (1920, 1080)
) # )
# 重置停止事件 # 重置停止事件
self.recording_stop_event.clear() self.recording_stop_event.clear()
@ -1097,14 +1128,14 @@ class DeviceManager:
name='BodyRecordingThread' name='BodyRecordingThread'
) )
self.body_recording_thread.start() self.body_recording_thread.start()
#屏幕录制 # #屏幕录制
if self.screen_video_writer: # if self.screen_video_writer:
self.screen_recording_thread = threading.Thread( # self.screen_recording_thread = threading.Thread(
target=self._screen_recording_thread, # target=self._screen_recording_thread,
daemon=True, # daemon=True,
name='ScreenRecordingThread' # name='ScreenRecordingThread'
) # )
self.screen_recording_thread.start() # self.screen_recording_thread.start()
# 设置录制状态 # 设置录制状态
self.sync_recording = True self.sync_recording = True
@ -1122,21 +1153,15 @@ class DeviceManager:
return result return result
def stop_recording(self, session_id: str) -> Dict[str, Any]: def stop_recording(self, session_id: str, video_data_base64: str = None) -> Dict[str, Any]:
"""停止同步录制 """停止同步录制
Args: Args:
session_id: 检测会话ID session_id: 检测会话ID
video_data_base64: 屏幕录制视频的base64编码数据可选
Returns: Returns:
Dict: 录制停止状态和信息 Dict: 录制停止状态和信息
{
'success': bool,
'session_id': str,
'recording_duration': float,
'video_files': List[str],
'message': str
}
""" """
result = { result = {
'success': False, 'success': False,
@ -1158,12 +1183,18 @@ class DeviceManager:
# 设置停止事件 # 设置停止事件
self.recording_stop_event.set() self.recording_stop_event.set()
session_data = self.db_manager.get_session_data(session_id)
base_path = os.path.join('data', 'patients', session_data['patient_id'], session_id)
# 定义视频文件路径
feet_video_path = os.path.join(base_path, 'feet.mp4')
body_video_path = os.path.join(base_path, 'body.mp4')
screen_video_path = os.path.join(base_path, 'screen.mp4')
# 等待录制线程结束 # 等待录制线程结束
threads_to_join = [ threads_to_join = [
(self.feet_recording_thread, 'feet'), (self.feet_recording_thread, 'feet'),
(self.body_recording_thread, 'body'), (self.body_recording_thread, 'body')
(self.screen_recording_thread, 'screen')
] ]
for thread, name in threads_to_join: for thread, name in threads_to_join:
@ -1179,16 +1210,30 @@ class DeviceManager:
# 清理视频写入器并收集文件信息 # 清理视频写入器并收集文件信息
video_files = self._cleanup_video_writers() video_files = self._cleanup_video_writers()
# 保存传入的屏幕录制视频数据,替代原有屏幕录制视频保存逻辑
if video_data_base64:
try:
video_bytes = base64.b64decode(video_data_base64)
with open(screen_video_path, 'wb') as f:
f.write(video_bytes)
video_files.append(screen_video_path)
logger.info(f'屏幕录制视频保存成功,路径: {screen_video_path}, 文件大小: {os.path.getsize(screen_video_path)} 字节')
except Exception as e:
logger.error(f'保存屏幕录制视频失败: {e}', exc_info=True)
logger.debug(f'视频数据长度: {len(video_data_base64)}')
raise
result['video_files'] = video_files result['video_files'] = video_files
# 更新数据库中的会话信息 # 更新数据库中的会话信息
if self.db_manager and result['recording_duration'] > 0: if self.db_manager and result['recording_duration'] > 0:
try: try:
# 更新会话持续时间
duration_seconds = int(result['recording_duration']) duration_seconds = int(result['recording_duration'])
self.db_manager.update_session_duration(session_id, duration_seconds) self.db_manager.update_session_duration(session_id, duration_seconds)
self.db_manager.update_session_normal_video_path(session_id, feet_video_path)
# 更新会话状态为已完成 self.db_manager.update_session_femtobolt_video_path(session_id, body_video_path)
self.db_manager.update_session_screen_video_path(session_id, screen_video_path)
self.db_manager.update_session_status(session_id, 'completed') self.db_manager.update_session_status(session_id, 'completed')
logger.debug(f'数据库会话信息更新成功 - 会话ID: {session_id}, 持续时间: {duration_seconds}') logger.debug(f'数据库会话信息更新成功 - 会话ID: {session_id}, 持续时间: {duration_seconds}')
@ -1207,7 +1252,7 @@ class DeviceManager:
logger.debug(f'同步录制已停止 - 会话ID: {session_id}, 录制时长: {result["recording_duration"]:.2f}') logger.debug(f'同步录制已停止 - 会话ID: {session_id}, 录制时长: {result["recording_duration"]:.2f}')
except Exception as e: except Exception as e:
logger.error(f'停止同步录制失败: {e}') logger.error(f'停止同步录制失败: {e}', exc_info=True)
result['message'] = f'停止录制失败: {str(e)}' result['message'] = f'停止录制失败: {str(e)}'
return result return result