录像截图功能提交
This commit is contained in:
parent
9d5bdb5005
commit
7bfe1f5acb
@ -618,47 +618,28 @@ def stop_video_streaming():
|
||||
def start_detection():
|
||||
"""开始检测"""
|
||||
try:
|
||||
if not db_manager:
|
||||
return jsonify({'success': False, 'error': '数据库管理器未初始化'}), 500
|
||||
if not db_manager or not device_manager:
|
||||
return jsonify({'success': False, 'error': '数据库管理器或设备管理器未初始化'}), 500
|
||||
|
||||
data = flask_request.get_json()
|
||||
patient_id = data.get('patientId') or data.get('patient_id')
|
||||
settings = data.get('settings', '{}')
|
||||
patient_id = data.get('patient_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:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': '缺少必要参数: patient_id'
|
||||
}), 400
|
||||
# 调用create_detection_session方法,settings传空字典
|
||||
session_id = db_manager.create_detection_session(patient_id, settings={}, creator_id=creator_id)
|
||||
|
||||
# 解析设置参数
|
||||
if isinstance(settings, str):
|
||||
# 开始同步录制
|
||||
recording_response = None
|
||||
try:
|
||||
settings = json.loads(settings)
|
||||
except json.JSONDecodeError:
|
||||
settings = {}
|
||||
recording_response = device_manager.start_recording(session_id, patient_id)
|
||||
except Exception as rec_e:
|
||||
logger.error(f'开始同步录制失败: {rec_e}')
|
||||
|
||||
# 创建检测会话
|
||||
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
|
||||
start_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
return jsonify({'success': True, 'session_id': session_id, 'detectionStartTime': start_time, 'recording': recording_response})
|
||||
except Exception as e:
|
||||
logger.error(f'开始检测失败: {e}')
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
@ -667,15 +648,50 @@ def start_detection():
|
||||
def stop_detection(session_id):
|
||||
"""停止检测"""
|
||||
try:
|
||||
if not db_manager:
|
||||
return jsonify({'success': False, 'error': '数据库管理器未初始化'}), 500
|
||||
if not db_manager or not device_manager:
|
||||
logger.error('数据库管理器或设备管理器未初始化')
|
||||
return jsonify({'success': False, 'error': '数据库管理器或设备管理器未初始化'}), 500
|
||||
|
||||
if not session_id:
|
||||
logger.error('缺少会话ID')
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': '缺少会话ID'
|
||||
}), 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_recording,session_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')
|
||||
|
||||
@ -686,13 +702,14 @@ def stop_detection(session_id):
|
||||
'message': '检测已停止'
|
||||
})
|
||||
else:
|
||||
logger.error('停止检测失败,更新会话状态失败')
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': '停止检测失败'
|
||||
}), 500
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f'停止检测失败: {e}')
|
||||
logger.error(f'停止检测失败: {e}', exc_info=True)
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/detection/<session_id>/status', methods=['GET'])
|
||||
@ -823,6 +840,7 @@ def stop_sync_recording():
|
||||
|
||||
data = flask_request.get_json()
|
||||
session_id = data.get('session_id')
|
||||
video_data = data.get('videoData') # 新增接收前端传递的视频数据
|
||||
|
||||
if not session_id:
|
||||
return jsonify({
|
||||
@ -830,7 +848,7 @@ def stop_sync_recording():
|
||||
'error': '缺少必要参数: session_id'
|
||||
}), 400
|
||||
|
||||
result = device_manager.stop_recording(session_id)
|
||||
result = device_manager.stop_recording(session_id, video_data_base64=video_data)
|
||||
|
||||
if result['success']:
|
||||
logger.info(f'同步录制已停止 - 会话ID: {session_id}')
|
||||
@ -843,7 +861,6 @@ def stop_sync_recording():
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
|
||||
|
||||
@app.route('/api/history/sessions', methods=['GET'])
|
||||
def get_detection_sessions():
|
||||
"""获取检测会话历史"""
|
||||
|
Binary file not shown.
@ -632,21 +632,19 @@ class DatabaseManager:
|
||||
|
||||
cursor.execute('''
|
||||
INSERT INTO detection_sessions (
|
||||
id, patient_id, creator_id, duration, frequency, settings, status,
|
||||
diagnosis_info, treatment_info, suggestion_info, notes, start_time, created_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
id, patient_id, creator_id, duration, settings, status,
|
||||
diagnosis_info, treatment_info, suggestion_info, start_time, created_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
''', (
|
||||
session_id,
|
||||
patient_id,
|
||||
creator_id,
|
||||
settings.get('duration', 60),
|
||||
settings.get('frequency', 60),
|
||||
json.dumps(settings),
|
||||
'created',
|
||||
'running',
|
||||
settings.get('diagnosis_info', ''),
|
||||
settings.get('treatment_info', ''),
|
||||
settings.get('suggestion_info', ''),
|
||||
settings.get('notes', ''),
|
||||
china_time,
|
||||
china_time
|
||||
))
|
||||
@ -660,7 +658,7 @@ class DatabaseManager:
|
||||
logger.error(f'创建检测会话失败: {e}')
|
||||
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()
|
||||
cursor = conn.cursor()
|
||||
@ -671,15 +669,15 @@ class DatabaseManager:
|
||||
china_time = self.get_china_time()
|
||||
cursor.execute('''
|
||||
UPDATE detection_sessions SET
|
||||
status = ?, data_points = ?, end_time = ?
|
||||
status = ?, end_time = ?
|
||||
WHERE id = ?
|
||||
''', (status, data_points, china_time, session_id))
|
||||
''', (status, china_time, session_id))
|
||||
else:
|
||||
cursor.execute('''
|
||||
UPDATE detection_sessions SET
|
||||
status = ?, data_points = ?
|
||||
status = ?
|
||||
WHERE id = ?
|
||||
''', (status, data_points, session_id))
|
||||
''', (status, session_id))
|
||||
|
||||
conn.commit()
|
||||
logger.info(f'更新会话状态: {session_id} -> {status}')
|
||||
|
@ -24,6 +24,7 @@ from concurrent.futures import ThreadPoolExecutor
|
||||
import logging
|
||||
|
||||
# 数据库管理
|
||||
# from backend.app import get_detection_sessions
|
||||
from database import DatabaseManager
|
||||
|
||||
# FemtoBolt深度相机支持
|
||||
@ -265,10 +266,14 @@ class DeviceManager:
|
||||
|
||||
if platform.system() == "Windows":
|
||||
# 优先使用Orbbec SDK K4A Wrapper(与azure_kinect_image_example.py一致)
|
||||
orbbec_paths = [
|
||||
r"D:\OrbbecSDK_K4A_Wrapper_v1.10.3_windows_202408091749\bin\k4a.dll",
|
||||
]
|
||||
|
||||
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
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标准安装路径(备用)
|
||||
standard_paths = [
|
||||
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:
|
||||
# 1. 采集头部姿态数据(从IMU设备获取)
|
||||
if self.device_status['imu']:
|
||||
head_pose_data = self._collect_head_pose_data()
|
||||
if head_pose_data:
|
||||
data['head_pose'] = json.dumps(head_pose_data)
|
||||
logger.debug(f'头部姿态数据采集成功: {session_id}')
|
||||
# # 1. 采集头部姿态数据(从IMU设备获取)
|
||||
# if self.device_status['imu']:
|
||||
# head_pose_data = self._collect_head_pose_data()
|
||||
# if head_pose_data:
|
||||
# data['head_pose'] = json.dumps(head_pose_data)
|
||||
# logger.debug(f'头部姿态数据采集成功: {session_id}')
|
||||
|
||||
# 2. 采集身体姿态数据(从FemtoBolt深度相机获取)
|
||||
if self.device_status['femtobolt']:
|
||||
body_pose_data = self._collect_body_pose_data()
|
||||
if body_pose_data:
|
||||
data['body_pose'] = json.dumps(body_pose_data)
|
||||
logger.debug(f'身体姿态数据采集成功: {session_id}')
|
||||
# # 2. 采集身体姿态数据(从FemtoBolt深度相机获取)
|
||||
# if self.device_status['femtobolt']:
|
||||
# body_pose_data = self._collect_body_pose_data()
|
||||
# if body_pose_data:
|
||||
# data['body_pose'] = json.dumps(body_pose_data)
|
||||
# logger.debug(f'身体姿态数据采集成功: {session_id}')
|
||||
|
||||
# 3. 采集身体视频截图(从FemtoBolt深度相机获取)
|
||||
if self.device_status['femtobolt']:
|
||||
body_image_path = self._capture_body_image(data_dir)
|
||||
if body_image_path:
|
||||
data['body_image'] = str(body_image_path)
|
||||
logger.debug(f'身体截图保存成功: {body_image_path}')
|
||||
# # 3. 采集身体视频截图(从FemtoBolt深度相机获取)
|
||||
# if self.device_status['femtobolt']:
|
||||
# body_image_path = self._capture_body_image(data_dir)
|
||||
# if body_image_path:
|
||||
# data['body_image'] = str(body_image_path)
|
||||
# logger.debug(f'身体截图保存成功: {body_image_path}')
|
||||
|
||||
# 4. 采集足部压力数据(从压力传感器获取)
|
||||
if self.device_status['pressure']:
|
||||
foot_data = self._collect_foot_pressure_data()
|
||||
if foot_data:
|
||||
data['foot_data'] = json.dumps(foot_data)
|
||||
logger.debug(f'足部压力数据采集成功: {session_id}')
|
||||
# # 4. 采集足部压力数据(从压力传感器获取)
|
||||
# if self.device_status['pressure']:
|
||||
# foot_data = self._collect_foot_pressure_data()
|
||||
# if foot_data:
|
||||
# data['foot_data'] = json.dumps(foot_data)
|
||||
# logger.debug(f'足部压力数据采集成功: {session_id}')
|
||||
|
||||
# 5. 采集足部监测视频截图(从摄像头获取)
|
||||
if self.device_status['camera']:
|
||||
foot_image_path = self._capture_foot_image(data_dir)
|
||||
if foot_image_path:
|
||||
data['foot_image'] = str(foot_image_path)
|
||||
logger.debug(f'足部截图保存成功: {foot_image_path}')
|
||||
# # 5. 采集足部监测视频截图(从摄像头获取)
|
||||
# if self.device_status['camera']:
|
||||
# foot_image_path = self._capture_foot_image(data_dir)
|
||||
# if foot_image_path:
|
||||
# data['foot_image'] = str(foot_image_path)
|
||||
# logger.debug(f'足部截图保存成功: {foot_image_path}')
|
||||
|
||||
# 6. 生成足底压力数据图(从压力传感器数据生成)
|
||||
if self.device_status['pressure']:
|
||||
foot_data_image_path = self._generate_foot_pressure_image(data_dir)
|
||||
if foot_data_image_path:
|
||||
data['foot_data_image'] = str(foot_data_image_path)
|
||||
logger.debug(f'足底压力数据图生成成功: {foot_data_image_path}')
|
||||
# # 6. 生成足底压力数据图(从压力传感器数据生成)
|
||||
# if self.device_status['pressure']:
|
||||
# foot_data_image_path = self._generate_foot_pressure_image(data_dir)
|
||||
# if foot_data_image_path:
|
||||
# data['foot_data_image'] = str(foot_data_image_path)
|
||||
# logger.debug(f'足底压力数据图生成成功: {foot_data_image_path}')
|
||||
|
||||
# 7. 保存屏幕录制截图(从前端传入的base64数据)
|
||||
if screen_image_base64:
|
||||
screen_image_path = self._save_screen_image(data_dir, screen_image_base64)
|
||||
try:
|
||||
logger.debug(f'屏幕截图保存.................{screen_image_base64}')
|
||||
# 保存屏幕截图的base64数据为图片文件
|
||||
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:
|
||||
@ -1073,10 +1104,10 @@ class DeviceManager:
|
||||
body_video_path, fourcc, fps, (1280, 720)
|
||||
)
|
||||
|
||||
# 屏幕录制写入器(默认分辨率,后续根据实际帧调整)
|
||||
self.screen_video_writer = cv2.VideoWriter(
|
||||
screen_video_path, fourcc, fps, (1920, 1080)
|
||||
)
|
||||
# # 屏幕录制写入器(默认分辨率,后续根据实际帧调整)
|
||||
# self.screen_video_writer = cv2.VideoWriter(
|
||||
# screen_video_path, fourcc, fps, (1920, 1080)
|
||||
# )
|
||||
|
||||
# 重置停止事件
|
||||
self.recording_stop_event.clear()
|
||||
@ -1097,14 +1128,14 @@ class DeviceManager:
|
||||
name='BodyRecordingThread'
|
||||
)
|
||||
self.body_recording_thread.start()
|
||||
#屏幕录制
|
||||
if self.screen_video_writer:
|
||||
self.screen_recording_thread = threading.Thread(
|
||||
target=self._screen_recording_thread,
|
||||
daemon=True,
|
||||
name='ScreenRecordingThread'
|
||||
)
|
||||
self.screen_recording_thread.start()
|
||||
# #屏幕录制
|
||||
# if self.screen_video_writer:
|
||||
# self.screen_recording_thread = threading.Thread(
|
||||
# target=self._screen_recording_thread,
|
||||
# daemon=True,
|
||||
# name='ScreenRecordingThread'
|
||||
# )
|
||||
# self.screen_recording_thread.start()
|
||||
|
||||
# 设置录制状态
|
||||
self.sync_recording = True
|
||||
@ -1122,21 +1153,15 @@ class DeviceManager:
|
||||
|
||||
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:
|
||||
session_id: 检测会话ID
|
||||
video_data_base64: 屏幕录制视频的base64编码数据,可选
|
||||
|
||||
Returns:
|
||||
Dict: 录制停止状态和信息
|
||||
{
|
||||
'success': bool,
|
||||
'session_id': str,
|
||||
'recording_duration': float,
|
||||
'video_files': List[str],
|
||||
'message': str
|
||||
}
|
||||
"""
|
||||
result = {
|
||||
'success': False,
|
||||
@ -1158,12 +1183,18 @@ class DeviceManager:
|
||||
|
||||
# 设置停止事件
|
||||
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 = [
|
||||
(self.feet_recording_thread, 'feet'),
|
||||
(self.body_recording_thread, 'body'),
|
||||
(self.screen_recording_thread, 'screen')
|
||||
(self.body_recording_thread, 'body')
|
||||
]
|
||||
|
||||
for thread, name in threads_to_join:
|
||||
@ -1179,16 +1210,30 @@ class DeviceManager:
|
||||
|
||||
# 清理视频写入器并收集文件信息
|
||||
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
|
||||
|
||||
# 更新数据库中的会话信息
|
||||
if self.db_manager and result['recording_duration'] > 0:
|
||||
try:
|
||||
# 更新会话持续时间
|
||||
duration_seconds = int(result['recording_duration'])
|
||||
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')
|
||||
|
||||
logger.debug(f'数据库会话信息更新成功 - 会话ID: {session_id}, 持续时间: {duration_seconds}秒')
|
||||
@ -1207,7 +1252,7 @@ class DeviceManager:
|
||||
logger.debug(f'同步录制已停止 - 会话ID: {session_id}, 录制时长: {result["recording_duration"]:.2f}秒')
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f'停止同步录制失败: {e}')
|
||||
logger.error(f'停止同步录制失败: {e}', exc_info=True)
|
||||
result['message'] = f'停止录制失败: {str(e)}'
|
||||
|
||||
return result
|
||||
|
Loading…
Reference in New Issue
Block a user