19. 3. 3. 获取更新的过去24小时内的文件 Get files updated last 24 hours

本文详细阐述了使用Java进行HTTP请求的过程,并通过实例展示了如何利用URL类、URLConnection类及输入输出流等技术获取网页内容。

import java.io.BufferedInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.net.URL; import java.net.URLConnection; import java.util.Date; public class URLtest3 { public static void main(String[] args) throws Exception{ Date today = new Date(); System.out.println(today);//Fri Oct 21 16:58:50 CST 2011 //PerDay:每天 long millisecondsPerDay = 24 * 60 * 60 * 1000;//milli:毫、千分之一、这是一天 URL u = new URL("http://www.163.com"); URLConnection uc = u.openConnection(); //将此 URLConnection 的 ifModifiedSince 字段的值设置为指定的值。 uc.setIfModifiedSince((new Date(today.getTime() - millisecondsPerDay)).getTime()); InputStream in = new BufferedInputStream(uc.getInputStream()); Reader r = new InputStreamReader(in); int c ; while((c = r.read()) != -1){ System.out.print((char)c); } } }


# E:\AI_System\monitoring\hardware_monitor.py import time import threading import logging import psutil import platform import random import json import os import pythoncom import subprocess import re import traceback from pathlib import Path from collections import deque from datetime import datetime from typing import Dict, Any, List, Optional # 导入工具箱 from tools.cpu_tools import CPUTools from tools.memory_tools import MemoryTools from tools.disk_tools import DiskTools from tools.gpu_tools import GPUTools from tools.temperature_tools import TemperatureTools from tools.fan_tools import FanTools logger = logging.getLogger(&#39;HardwareMonitor&#39;) class HardwareMonitor: """智能硬件监控器,用于主动监控系统硬件状态""" # 历史数据存储配置 HISTORY_FILE = "hardware_history.json" MAX_HISTORY_DAYS = 30 # 异常检测阈值 CPU_THRESHOLD = 90.0 # CPU使用率超过90%警告 TEMP_THRESHOLD = 85.0 # 温度超过85°C警告 MEMORY_THRESHOLD = 90.0 # 内存使用率超过90%警告 FAN_THRESHOLD = 3000 # 风扇转速超过3000RPM警告 def __init__(self, update_interval=2): self.logger = logger self.update_interval = update_interval self._running = False self._thread = None self._stop_event = threading.Event() self._hardware_data = { "cpu_usage": 0.0, "memory_usage": 0.0, "temperature": 0.0, "fan_speed": 0, "monitor_status": "stopped", "alerts": [], "last_updated": time.time() } self.history = deque(maxlen=1000) # 存储最近1000个数据点 self.logger.info("✅ 智能硬件监控模块初始化完成") # 初始化COM状态 self._com_initialized = False self._use_real_data = self._check_real_data_support() # 加载历史数据 self._load_history() # 初始化工具箱 self.tools = { "硬件信息": self.get_hardware_info, "CPU工具": CPUTools().get_tools, "内存工具": MemoryTools().get_tools, "磁盘工具": DiskTools().get_tools, "显卡工具": GPUTools().get_tools, "温度监控": TemperatureTools(self).get_tools, "风扇控制": FanTools(self).get_tools } def _check_real_data_support(self): """检查系统是否支持获取真实硬件数据""" if platform.system() == "Windows": try: import wmi # 安全初始化COM self._safe_com_initialize() try: c = wmi.WMI() open_hwmon_running = False for process in c.Win32_Process(): if "OpenHardwareMonitor" in process.Name: open_hwmon_running = True break if open_hwmon_running: # 测试WMI连接 try: w = wmi.WMI(namespace="root\\OpenHardwareMonitor") sensors = w.Sensor() if len(sensors) > 0: self.logger.info("✅ OpenHardwareMonitor已运行且可访问") return True except Exception as test_e: self.logger.error(f"❌ 无法访问OpenHardwareMonitor WMI接口: {str(test_e)}") self.logger.warning("⚠️ OpenHardwareMonitor未运行或不可访问") except Exception as e: self.logger.error(f"❌ 检查OpenHardwareMonitor时出错: {str(e)}") finally: self._safe_com_uninitialize() except ImportError: self.logger.warning("⚠️ 未安装wmi库,无法获取真实硬件数据") return False def _safe_com_initialize(self): """安全初始化COM组件""" try: if not self._com_initialized: pythoncom.CoInitialize() self._com_initialized = True self.logger.debug("🔧 COM组件初始化成功") except Exception as e: self.logger.error(f"❌ COM组件初始化失败: {str(e)}") self._com_initialized = False def _safe_com_uninitialize(self): """安全卸载COM组件""" try: if (self._com_initialized): # 修复:移除多余的字符 pythoncom.CoUninitialize() self._com_initialized = False self.logger.debug("🔧 COM组件卸载成功") except Exception as e: self.logger.error(f"❌ COM组件卸载失败: {str(e)}") def _load_history(self): """从文件加载历史数据""" try: history_path = Path(self.HISTORY_FILE) if history_path.exists(): with open(history_path, &#39;r&#39;) as f: data = json.load(f) # 只保留最近MAX_HISTORY_DAYS天的数据 cutoff = time.time() - (self.MAX_HISTORY_DAYS * 24 * 3600) self.history = deque( [entry for entry in data if entry[&#39;timestamp&#39;] > cutoff], maxlen=1000 ) self.logger.info(f"📖 已加载 {len(self.history)} 条历史数据") else: self.logger.info("📖 未找到历史数据文件,将创建新文件") except Exception as e: self.logger.error(f"❌ 加载历史数据失败: {str(e)}") def _save_history(self): """保存历史数据到文件""" try: with open(self.HISTORY_FILE, &#39;w&#39;) as f: json.dump(list(self.history), f, indent=2) self.logger.info(f"💾 已保存 {len(self.history)} 条历史数据") except Exception as e: self.logger.error(f"❌ 保存历史数据失败: {str(e)}") def start(self): """启动硬件监控器""" if self._running: self.logger.warning("⚠️ 硬件监控器已在运行中") return False self._running = True self._stop_event.clear() self._hardware_data["monitor_status"] = "running" self.logger.info("🚀 硬件监控已启动") # 启动监控线程 self._thread = threading.Thread( target=self._monitor_loop, daemon=True, name="HardwareMonitorThread" ) self._thread.start() return True def stop(self): """停止硬件监控器""" if not self._running: self.logger.warning("⚠️ 硬件监控器未运行") return False self._running = False self._stop_event.set() if self._thread and self._thread.is_alive(): self._thread.join(timeout=5.0) if self._thread.is_alive(): self.logger.warning("⚠️ 硬件监控线程未在超时时间内退出") self._hardware_data["monitor_status"] = "stopped" self._save_history() # 停止时保存历史数据 self.logger.info("🛑 硬件监控已停止") return True def restart(self): """重启硬件监控器""" self.logger.info("🔄 重启硬件监控器") self.stop() return self.start() def get_status(self) -> Dict[str, Any]: """获取硬件监控器状态""" return { **self._hardware_data, "update_interval": self.update_interval, "timestamp": time.time(), "history_count": len(self.history) } def get_history(self, hours: int = 24) -> List[Dict]: """获取指定时间范围内的历史数据""" current_time = time.time() cutoff = current_time - (hours * 3600) return [entry for entry in self.history if entry["timestamp"] >= cutoff] def run_tool(self, tool_name: str) -> Dict: """运行指定的硬件工具""" if tool_name in self.tools: return self.tools[tool_name]() return {"error": f"未找到工具: {tool_name}"} def _monitor_loop(self): """监控循环,定期更新硬件数据""" self.logger.debug("🔁 硬件监控线程启动") # 修复:将 log极 改为 logger # 安全初始化COM self._safe_com_initialize() # 初始化WMI连接 wmi_conn = None if self._use_real_data: try: import wmi wmi_conn = wmi.WMI(namespace="root\\OpenHardwareMonitor") self.logger.debug("🔧 已创建WMI连接") except Exception as e: self.logger.error(f"❌ 创建WMI连接失败: {str(e)}") wmi_conn = None try: while self._running and not self._stop_event.is_set(): try: # 获取真实硬件数据 self._update_real_hardware_data(wmi_conn) # 检测异常 self._detect_anomalies() # 保存当前数据到历史记录 self._save_current_to_history() except Exception as inner_e: self.logger.error(f"❌ 监控循环内部错误: {str(inner_e)}") self.logger.debug(traceback.format_exc()) # 等待直到下一个更新周期或收到停止信号 self._stop_event.wait(timeout=self.update_interval) except Exception as outer_e: self.logger.error(f"❌ 硬件监控异常: {str(outer_e)}") self.logger.debug(traceback.format_exc()) self._hardware_data["monitor_status"] = "error" finally: # 清理资源 wmi_conn = None self._safe_com_uninitialize() self.logger.debug("🔁 硬件监控线程退出") def _update_real_hardware_data(self, wmi_conn=None): """更新真实硬件数据""" try: # 获取CPU使用率 cpu_usage = psutil.cpu_percent(interval=None) # 获取内存使用率 memory_info = psutil.virtual_memory() memory_usage = memory_info.percent # 获取温度和风扇转速 - 使用改进的方法 temperature, fan_speed = self._get_temperature_and_fan(wmi_conn) # 更新数据 self._hardware_data.update({ "cpu_usage": cpu_usage, "memory_usage": memory_usage, "temperature": temperature, "fan_speed": fan_speed, "monitor_status": "running", "last_updated": time.time() }) except Exception as e: self.logger.error(f"❌ 更新硬件数据失败: {str(e)}") self._hardware_data["monitor_status"] = "error" def _get_temperature_and_fan(self, wmi_conn=None): """获取温度和风扇转速(改进版)""" if not self._use_real_data: # 回退到模拟数据 return random.uniform(40.0, 80.0), random.randint(800, 2000) try: # 尝试使用WMI获取数据 temperature, fan_speed = self._get_wmi_temperature_and_fan(wmi_conn) # 如果温度异常低,使用备用方法 if temperature < 10: self.logger.warning("⚠️ 检测到异常低温,使用备用温度获取方法") temperature = self._get_cpu_temperature_backup() # 如果风扇转速为0,尝试使用备用方法 if fan_speed == 0: self.logger.warning("⚠️ 风扇转速为0,使用备用风扇获取方法") fan_speed = self._get_fan_speed_backup() return temperature, fan_speed except Exception as e: self.logger.error(f"❌ 获取温度和风扇转速失败: {str(e)}") # 修复:移除多余的字符 return random.uniform(40.0, 80.0), random.randint(800, 2000) def _get_wmi_temperature_and_fan(self, wmi_conn): """通过WMI获取温度和风扇数据""" if wmi_conn is None: return 0, 0 try: temperatures = [] fan_speeds = [] # 获取所有温度传感器 for sensor in wmi_conn.Sensor(): if sensor.SensorType == &#39;Temperature&#39; and sensor.Value is not None: try: temp_value = float(sensor.Value) temperatures.append(temp_value) except ValueError: continue # 获取所有风扇传感器 for sensor in wmi_conn.Sensor(): if sensor.SensorType == &#39;Fan&#39; and sensor.Value is not None: try: fan_value = int(sensor.Value) fan_speeds.append(fan_value) except ValueError: continue # 计算平均温度和最大风扇转速 avg_temperature = sum(temperatures) / len(temperatures) if temperatures else 0 max_fan_speed = max(fan_speeds) if fan_speeds else 0 self.logger.debug(f"📊 WMI获取温度: {avg_temperature:.1f}°C, 风扇: {max_fan_speed} RPM") return avg_temperature, max_fan_speed except Exception as e: self.logger.error(f"❌ WMI获取温度和风扇转速失败: {str(e)}") return 0, 0 def _get_cpu_temperature_backup(self): """备用CPU温度获取方法(改进版)""" try: # 方法1: 使用psutil获取温度(如果平台支持) if hasattr(psutil, "sensors_temperatures"): temps = psutil.sensors_temperatures() if &#39;coretemp&#39; in temps: core_temps = [t.current for t in temps[&#39;coretemp&#39;]] return sum(core_temps) / len(core_temps) # 方法2: 使用WMI获取CPU温度 self._safe_com_initialize() try: import wmi w = wmi.WMI(namespace="root\\cimv2") for sensor in w.MSAcpi_ThermalZoneTemperature(): if sensor.CurrentTemperature: temp_celsius = (float(sensor.CurrentTemperature) - 2732) / 10.0 if temp_celsius > 10: # 过滤不合理值 return temp_celsius except Exception as wmi_error: self.logger.error(f"❌ WMI获取温度失败: {str(wmi_error)}") # 方法3: 使用系统API获取温度 if platform.system() == "Windows": try: from ctypes import windll, byref from ctypes.wintypes import DWORD # 使用Windows API获取温度 class ThermalInfo(Structure): _fields_ = [("Temperature", DWORD)] thermal_info = ThermalInfo() if windll.kernel32.GetSystemThermalInformation(byref(thermal_info)): return thermal_info.Temperature / 10.0 except Exception as api_error: self.logger.error(f"❌ Windows API获取温度失败: {str(api_error)}") # 方法4: 使用模拟数据 return random.uniform(40.0, 60.0) except Exception: return random.uniform(40.0, 60.0) def _get_fan_speed_backup(self): """备用风扇转速获取方法(改进版)""" try: # 尝试使用SpeedFan命令行工具 fan_speed = self._get_fan_speed_from_command() if fan_speed > 0: return fan_speed # 尝试使用WMI获取风扇数据 self._safe_com_initialize() try: import wmi w = wmi.WMI(namespace="root\\cimv2") for fan in w.Win32_Fan(): if fan.DesiredSpeed: return int(fan.DesiredSpeed) except Exception as wmi_error: self.logger.error(f"❌ WMI获取风扇转速失败: {str(wmi_error)}") # 使用系统API获取风扇转速 if platform.system() == "Windows": try: from ctypes import windll, byref from ctypes.wintypes import DWORD # 使用Windows API获取风扇转速 class FanInfo(Structure): _fields_ = [("Speed", DWORD)] fan_info = FanInfo() if windll.kernel32.GetSystemFanInformation(byref(fan_info)): return fan_info.Speed except Exception as api_error: self.logger.error(f"❌ Windows API获取风扇转速失败: {str(api_error)}") # 使用模拟数据 return random.randint(1000, 2000) except Exception: return random.randint(1000, 2000) def _get_fan_speed_from_command(self): """通过命令行获取风扇转速(备用方法)""" try: # 查找SpeedFan安装路径 if not hasattr(self, &#39;_speedfan_path&#39;): self._speedfan_path = self._find_speedfan_path() if not self._speedfan_path: return 0 # 执行SpeedFan命令行 result = subprocess.run( [self._speedfan_path, &#39;/minimized&#39;, &#39;/nosmb&#39;, &#39;/log&#39;], capture_output=True, text=True, timeout=5 ) # 解析输出中的风扇转速 for line in result.stdout.split(&#39;\n&#39;): if "Fan" in line and "RPM" in line: match = re.search(r&#39;Fan\d+:\s+(\d+)\s+RPM&#39;, line) if match: return int(match.group(1)) return 0 except Exception as e: self.logger.error(f"❌ 命令行获取风扇转速失败: {str(e)}") return 0 def _find_speedfan_path(self): """查找SpeedFan安装路径""" try: # 尝试从注册表获取安装路径 import winreg key = winreg.OpenKey( winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\SpeedFan" ) path, _ = winreg.QueryValueEx(key, "InstallLocation") winreg.CloseKey(key) return os.path.join(path, "speedfan.exe") except: # 尝试常见安装路径 common_paths = [ r"C:\Program Files\SpeedFan\speedfan.exe", r"C:\Program Files (x86)\SpeedFan\speedfan.exe" ] for path in common_paths: if os.path.exists(path): return path self.logger.warning("⚠️ 未找到SpeedFan安装路径") return None def _detect_anomalies(self): """检测硬件异常并生成警报""" alerts = [] data = self._hardware_data # CPU使用率过高 if data["cpu_usage"] > self.CPU_THRESHOLD: alerts.append({ "type": "cpu", "message": f"CPU使用率过高: {data[&#39;cpu_usage&#39;]:.1f}% (阈值: {self.CPU_THRESHOLD}%)", "severity": "warning" }) # 温度过高 if data["temperature"] > self.TEMP_THRESHOLD: alerts.append({ "type": "temperature", "message": f"温度过高: {data[&#39;temperature&#39;]:.1f}°C (阈值: {self.TEMP_THRESHOLD}°C)", "severity": "critical" }) # 内存使用率过高 if data["memory_usage"] > self.MEMORY_THRESHOLD: alerts.append({ "type": "memory", "message": f"内存使用率过高: {data[&#39;memory_usage&#39;]:.1f}% (阈值: {self.MEMORY_THRESHOLD}%)", "severity": "warning" }) # 风扇转速异常 if data["fan_speed"] > self.FAN_THRESHOLD: alerts.append({ "type": "fan", "message": f"风扇转速异常: {data[&#39;fan_speed&#39;]} RPM (阈值: {self.FAN_THRESHOLD} RPM)", "severity": "warning" }) # 更新警报数据 self._hardware_data["alerts"] = alerts if alerts: for alert in alerts: self.logger.warning(f"⚠️ {alert[&#39;message&#39;]}") def _save_current_to_history(self): """保存当前数据到历史记录""" try: self.history.append({ "timestamp": time.time(), "cpu_usage": self._hardware_data["cpu_usage"], "memory_usage": self._hardware_data["memory_usage"], "temperature": self._hardware_data["temperature"], "fan_speed": self._hardware_data["fan_speed"], "alerts": self._hardware_data["alerts"] }) except Exception as e: self.logger.error(f"❌ 保存历史数据失败: {str(e)}") def get_hardware_info(self): """获取详细的硬件信息(改进版)""" try: # 初始化COM组件 pythoncom.CoInitialize() try: import wmi import GPUtil w = wmi.WMI() # CPU信息 cpus = [] for cpu in w.Win32_Processor(): cpus.append({ "name": cpu.Name.strip() if cpu.Name else "Unknown CPU", "cores": cpu.NumberOfCores, "threads": cpu.NumberOfLogicalProcessors, "max_clock": cpu.MaxClockSpeed, "current_clock": cpu.CurrentClockSpeed }) # 内存信息 memory = [] for mem in w.Win32_PhysicalMemory(): capacity = int(mem.Capacity) / (1024 ** 3) if mem.Capacity else 0 memory.append({ "capacity_gb": capacity, "speed": mem.Speed, "manufacturer": mem.Manufacturer.strip() if mem.Manufacturer else "Unknown" }) # 磁盘信息 disks = [] for disk in w.Win32_DiskDrive(): size = int(disk.Size) / (1024 ** 3) if disk.Size else 0 disks.append({ "model": disk.Model.strip() if disk.Model else "Unknown", "size_gb": size, "interface": disk.InterfaceType.strip() if disk.InterfaceType else "Unknown" }) # 显卡信息 gpus = [] try: for gpu in GPUtil.getGPUs(): gpus.append({ "name": gpu.name.strip() if gpu.name else "Unknown GPU", "memory_total": gpu.memoryTotal, "memory_free": gpu.memoryFree, "driver": gpu.driver.strip() if gpu.driver else "Unknown" }) except Exception as gpu_err: self.logger.error(f"❌ 获取显卡信息失败: {str(gpu_err)}") gpus = [{"error": str(gpu_err)}] return { "cpu": cpus, "memory": memory, "disks": disks, "gpus": gpus } except Exception as e: self.logger.error(f"❌ 获取硬件信息失败: {str(e)}") return {"error": str(e)} except Exception as outer_e: self.logger.error(f"❌ 硬件信息获取失败: {str(outer_e)}") return {"error": str(outer_e)} finally: # 确保卸载COM组件 try: pythoncom.CoUninitialize() except: pass # 确保卸载COM组件 try: pythoncom.CoUninitialize() except: pass 好烦 改不明白 看不懂 你改好了 给我发完整版 很难吗 少给我发废话 和建议 我看不懂也没兴趣 # test_hardware_monitor_unit.py这种东西 你不告诉我路径 我就不会存 你发那么多代码碎片有意义吗?你自己留着记笔记还行 我不会看的!让我干什么 说不清楚 就不用写了!
08-24
import json import requests import os import time from datetime import datetime, timedelta import pytz from mutagen.mp3 import MP3 from mutagen.mp4 import MP4 import subprocess import platform # 全局变量,用于缓存 tenant_access_token 和上次更新的时间 tenant_access_token = None token_last_updated = None def get_broadcast_data(): """ 获取并提取播报数据 """ # 获取 tenant_access_token global tenant_access_token, token_last_updated # 检查 token 是否过期,过期则重新获取 if tenant_access_token is None or token_last_updated is None or (datetime.now() - token_last_updated) > timedelta(hours=2): tenant_access_token = get_auth_token() if tenant_access_token: token_last_updated = datetime.now() if not tenant_access_token: print("获取 tenant_access_token 失败!") return [] # 获取 Feishu Bitable 数据 url = &#39;https://open.feishu.cn/open-apis/bitable/v1/apps/E1zybPqiqa0TaesZjKKch5ZcnJd/tables/tblwFY4k3pmrV5WK/records/search&#39; headers = { &#39;Content-Type&#39;: &#39;application/json&#39;, &#39;Authorization&#39;: f&#39;Bearer {tenant_access_token}&#39; # 使用获取到的 token } data = {} # 如果需要传递查询条件,可以在这里添加 try: response = requests.post(url, headers=headers, json=data) response.raise_for_status() # 如果响应失败,将抛出异常 response_dict = response.json() # 将返回的 JSON 数据转换为字典 items = response_dict.get("data", {}).get("items", []) data = [] for item in items: fields = item.get("fields", {}) data.append({ "播音日期": extract_broadcast_date(fields, &#39;播音日期&#39;), "时间段": extract_time_segment(fields, &#39;时间段&#39;), "开播音乐file_token": extract_file_token(fields, &#39;开播音乐&#39;), "开场白-播报file_token": extract_file_token(fields, &#39;开场白-播报&#39;), "需更新文案-播报file_token": extract_file_token(fields, &#39;需更新文案-播报&#39;), "壹首歌file_token": extract_file_token(fields, &#39;壹首歌&#39;), "需更新文案2-播报file_token": extract_file_token(fields, &#39;需更新文案2-播报&#39;), "贰首歌file_token": extract_file_token(fields, &#39;贰首歌&#39;), "结束语-播报file_token": extract_file_token(fields, &#39;结束语-播报&#39;), "结束音乐file_token": extract_file_token(fields, &#39;结束音乐&#39;) }) return data except requests.exceptions.HTTPError as http_err: print(f"HTTP 错误发生: {http_err}") except Exception as err: print(f"其他错误发生: {err}") return [] def extract_file_token(fields, field_name): """提取 file_token""" field_data = fields.get(field_name, []) if isinstance(field_data, list) and len(field_data) > 0: value = field_data[0] if isinstance(value, dict): return value.get("file_token", "") return &#39;&#39; def extract_time_segment(fields, field_name): """提取时间段字段""" field_data = fields.get(field_name, []) if isinstance(field_data, list) and len(field_data) > 0: value = field_data[0] if isinstance(value, dict): return value.get("text", "") return None def extract_broadcast_date(fields, field_name): """提取播音日期字段""" field_data = fields.get(field_name, 0) if isinstance(field_data, int): try: timestamp = field_data / 1000 # 时间戳转化为秒 parsed_date = datetime.fromtimestamp(timestamp, tz=pytz.utc).astimezone(pytz.timezone(&#39;Asia/Shanghai&#39;)) return parsed_date.strftime("%Y-%m-%d") # 转换为 "YYYY-MM-DD" 格式 except (ValueError, OverflowError): pass return None def get_auth_token(): """获取认证 token""" url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal" headers = {"Content-Type": "application/json; charset=utf-8"} payload = {"app_id": "cli_a882683e8779d00c", "app_secret": "3NKkALA7vyMRVnpKJinmrb1LJ7YuK4H0"} try: response = requests.post(url, json=payload, headers=headers) response.raise_for_status() data = response.json() if data["code"] == 0: return data["tenant_access_token"] else: print(f"请求失败:{data[&#39;msg&#39;]}(错误码:{data[&#39;code&#39;]})") except requests.exceptions.RequestException as e: print(f"请求异常:{e}") return None def create_folder(folder_name): """创建文件夹""" if not os.path.exists(folder_name): os.makedirs(folder_name) def download_file(file_token, save_path, authorization): """下载文件""" url = f"https://open.feishu.cn/open-apis/drive/v1/medias/{file_token}/download" headers = {"Authorization": "Bearer " + authorization} try: response = requests.get(url, headers=headers, stream=True) if response.status_code == 200: with open(save_path, &#39;wb&#39;) as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) print(f"文件已成功下载到: {save_path}") return True else: print(f"请求失败,状态码: {response.status_code}") print(f"错误信息: {response.text}") except Exception as e: print(f"发生异常: {str(e)}") return False def get_audio_duration(file_path): """获取音频时长""" try: if file_path.endswith(".mp3"): audio = MP3(file_path) elif file_path.endswith(".mp4"): audio = MP4(file_path) else: print(f"不支持的文件格式: {file_path}") return 0 return audio.info.length except Exception as e: print(f"获取音频时长失败: {e}") return 0 def kill_previous_players(): """清理之前残留的播放器进程""" system = platform.system() # 获取当前操作系统类型 try: if system == "Windows": subprocess.run([&#39;taskkill&#39;, &#39;/F&#39;, &#39;/IM&#39;, &#39;wmplayer.exe&#39;], stdout=subprocess.PIPE, stderr=subprocess.PIPE) subprocess.run([&#39;taskkill&#39;, &#39;/F&#39;, &#39;/IM&#39;, &#39;vlc.exe&#39;], stdout=subprocess.PIPE, stderr=subprocess.PIPE) elif system == "Darwin": # macOS subprocess.run([&#39;killall&#39;, &#39;afplay&#39;], stdout=subprocess.PIPE, stderr=subprocess.PIPE) elif system == "Linux": subprocess.run([&#39;pkill&#39;, &#39;mpg123&#39;], stdout=subprocess.PIPE, stderr=subprocess.PIPE) print("已清理之前残留的播放器进程") except Exception as e: print(f"清理播放器进程时发生错误: {e}") def play_music_in_folder(folder_path): """播放文件夹中的音频文件,并在播放完成后关闭播放器""" audio_files = [f for f in os.listdir(folder_path) if f.endswith((".mp3", ".mp4"))] processes = [] # 用于存储播放器进程 for file_name in audio_files: full_file_path = os.path.join(folder_path, file_name) try: duration = get_audio_duration(full_file_path) if duration <= 0: print(f"无法获取 {file_name} 的时长,跳过播放") continue print(f"播放 {file_name},预计播放时长:{duration} 秒") if os.name == &#39;nt&#39;: # Windows 系统 process = subprocess.Popen([&#39;start&#39;, &#39;&#39;, full_file_path], shell=True) elif os.name == &#39;posix&#39;: # MacOS 或 Linux 系统 process = subprocess.Popen([&#39;afplay&#39;, full_file_path]) # 对于 MacOS 使用 afplay else: print(f"不支持的操作系统类型: {os.name}") continue processes.append(process) # 保存进程对象 time.sleep(duration) # 等待音频播放完成 except Exception as e: print(f"无法播放 {full_file_path}: {e}") # 关闭所有播放器进程 for process in processes: try: if process.poll() is None: # 检查进程是否仍在运行 process.kill() # 强制终止进程 print("播放器已强制关闭") except Exception as e: print(f"关闭播放器失败: {e}") def process_time_segment(segment, download_offset, play_offset, data, authorization, folder_name): """处理一个时间段的下载和播放""" target_data = next((entry for entry in data if entry["时间段"] == segment), None) if not target_data: print(f"未找到时间段 {segment} 的文件数据!") return current_time = datetime.now(pytz.timezone(&#39;Asia/Shanghai&#39;)).strftime(&#39;%H:%M&#39;) segment_start_time = segment.split("-")[0] # 如果当前时间已经超过该时间段的开始时间,则跳过 if current_time > segment_start_time: print(f"当前时间已超过时间段 {segment} 的开始时间,跳过该时间段的处理") return # 等待到达下载时间 download_time = (datetime.strptime(segment_start_time, "%H:%M") - timedelta(minutes=download_offset)).strftime("%H:%M") while current_time != download_time: current_time = datetime.now(pytz.timezone(&#39;Asia/Shanghai&#39;)).strftime(&#39;%H:%M&#39;) time.sleep(1) print(f"开始下载 {segment} 的文件") files = [] file_tokens = [ ("开播音乐file_token", "mp3"), ("开场白-播报file_token", "mp4"), ("需更新文案-播报file_token", "mp4"), ("壹首歌file_token", "mp3"), ("需更新文案2-播报file_token", "mp4"), ("贰首歌file_token", "mp3"), ("结束语-播报file_token", "mp4"), ("结束音乐file_token", "mp3") ] for i, (key, file_format) in enumerate(file_tokens): token = target_data.get(key) if token: save_path = os.path.join(folder_name, f"file_{i+1}.{file_format}") if download_file(token, save_path, authorization): files.append(save_path) # 清理之前残留的播放器进程(调整到这里) kill_previous_players() # 等待到达播放时间 play_time = (datetime.strptime(segment_start_time, "%H:%M") - timedelta(minutes=play_offset)).strftime("%H:%M") while current_time != play_time: current_time = datetime.now(pytz.timezone(&#39;Asia/Shanghai&#39;)).strftime(&#39;%H:%M&#39;) time.sleep(1) print(f"开始播放 {segment} 的文件") play_music_in_folder(folder_name) # 播放结束后再次清理播放器进程 kill_previous_players() # 删除下载的文件 for file in files: os.remove(file) print(f"已删除文件: {file}") def main(): """主函数""" data = get_broadcast_data() if not data: print("未获取到有效的数据!") return global tenant_access_token authorization = tenant_access_token if not authorization: return folder_name = "bobao" create_folder(folder_name) segments = [ ("08:10-08:15", 10, 0), # 提前10分钟下载文件,0-准点播放 ("10:30-10:40", 10, 0), ("13:00-13:10", 10, 0), ("15:00-15:10", 10, 0), ("16:30-16:35", 10, 0), ("19:00-19:14", 10, 0) ] for segment, download_offset, play_offset in segments: process_time_segment(segment, download_offset, play_offset, data, authorization, folder_name) # 主程序结束后,再清理一次播放器进程 kill_previous_players() if __name__ == "__main__": main() 上述代码可以实现tenant_access_token如果过期,自动获取tenant_access_token吗?
05-28
对代码进行标准化以及压缩。减少不必要的计算及冗余,压缩内存及代码量,前提是不影响程序稳定以及精度:import os import sys import re import json import gc import time import tempfile import concurrent.futures import difflib import threading import traceback import numpy as np import librosa import torch import psutil import requests import hashlib import shutil from typing import List, Dict, Tuple, Optional, Set from threading import Lock, Semaphore, RLock from datetime import datetime from pydub import AudioSegment from pydub.silence import split_on_silence from modelscope.pipelines import pipeline from modelscope.utils.constant import Tasks from transformers import AutoModelForSequenceClassification, AutoTokenizer from torch.utils.data import TensorDataset, DataLoader from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QLineEdit, QTextEdit, QFileDialog, QProgressBar, QGroupBox, QMessageBox, QListWidget, QSplitter, QTabWidget, QTableWidget, QTableWidgetItem, QHeaderView, QAction, QMenu, QToolBar, QCheckBox, QComboBox, QSpinBox, QDialog, QDialogButtonBox, QStatusBar) from PyQt5.QtCore import QThread, pyqtSignal, Qt, QTimer, QSize from PyQt5.QtGui import QFont, QTextCursor, QColor, QIcon # ====================== 资源监控器 ====================== class ResourceMonitor: """统一资源监控器(增强版)""" def __init__(self): self.gpu_available = torch.cuda.is_available() def memory_percent(self) -> Dict[str, float]: """获取内存使用百分比,同时返回CPU和GPU信息""" try: result = { "cpu": psutil.virtual_memory().percent } if self.gpu_available: allocated = torch.cuda.memory_allocated() / (1024 ** 3) total = torch.cuda.get_device_properties(0).total_memory / (1024 ** 3) result["gpu"] = (allocated / total) * 100 if total > 0 else 0 return result except Exception as e: print(f"获取内存使用百分比失败: {str(e)}") return {"cpu": 0, "gpu": 0} # ====================== 方言配置中心(优化版) ====================== class DialectConfig: """集中管理方言配置,便于维护和扩展(带缓存)""" # 标准关键词 STANDARD_KEYWORDS = { "opening": ["您好", "很高兴为您服务", "请问有什么可以帮您"], "closing": ["感谢来电", "祝您生活愉快", "再见"], "forbidden": ["不知道", "没办法", "你投诉吧", "随便你"] } # 贵州方言关键词 GUIZHOU_KEYWORDS = { "opening": ["麻烦您喽", "请问搞哪样", "有咋个可以帮您", "多谢喽"], "closing": ["搞归一喽", "麻烦您喽", "再见喽", "慢走喽"], "forbidden": ["搞不成", "没得法", "随便你喽", "你投诉吧喽"] } # 方言到标准表达的映射(扩展更多贵州方言) DIALECT_MAPPING = { "恼火得很": "非常生气", "鬼火戳": "很愤怒", "搞不成": "无法完成", "没得": "没有", "搞哪样嘛": "做什么呢", "归一喽": "完成了", "咋个": "怎么", "克哪点": "去哪里", "麻烦您喽": "麻烦您了", "多谢喽": "多谢了", "憨包": "傻瓜", "归一": "结束", "板扎": "很好", "鬼火冒": "非常生气", "背时": "倒霉", "吃豁皮": "占便宜" } # 类属性缓存 _combined_keywords = None _compiled_opening = None _compiled_closing = None _hotwords = None _dialect_trie = None # 使用Trie树替换正则表达式 class TrieNode: """Trie树节点类""" def __init__(self): self.children = {} self.is_end = False self.value = "" @classmethod def _build_dialect_trie(cls): """构建方言Trie树""" root = cls.TrieNode() # 按长度降序添加关键词 for dialect, standard in sorted(cls.DIALECT_MAPPING.items(), key=lambda x: len(x[0]), reverse=True): node = root for char in dialect: if char not in node.children: node.children[char] = cls.TrieNode() node = node.children[char] node.is_end = True node.value = standard return root @classmethod def get_combined_keywords(cls) -> Dict[str, List[str]]: """获取合并后的关键词集(带缓存)""" if cls._combined_keywords is None: cls._combined_keywords = { "opening": cls.STANDARD_KEYWORDS["opening"] + cls.GUIZHOU_KEYWORDS["opening"], "closing": cls.STANDARD_KEYWORDS["closing"] + cls.GUIZHOU_KEYWORDS["closing"], "forbidden": cls.STANDARD_KEYWORDS["forbidden"] + cls.GUIZHOU_KEYWORDS["forbidden"] } return cls._combined_keywords @classmethod def get_compiled_opening(cls) -> List[re.Pattern]: """获取预编译的开场关键词正则表达式(带缓存)""" if cls._compiled_opening is None: keywords = cls.get_combined_keywords()["opening"] cls._compiled_opening = [re.compile(re.escape(kw)) for kw in keywords] return cls._compiled_opening @classmethod def get_compiled_closing(cls) -> List[re.Pattern]: """获取预编译的结束关键词正则表达式(带缓存)""" if cls._compiled_closing is None: keywords = cls.get_combined_keywords()["closing"] cls._compiled_closing = [re.compile(re.escape(kw)) for kw in keywords] return cls._compiled_closing @classmethod def get_asr_hotwords(cls) -> List[str]: """获取ASR热词列表(带缓存)""" if cls._hotwords is None: combined = cls.get_combined_keywords() cls._hotwords = sorted(set( combined["opening"] + combined["closing"] )) return cls._hotwords @classmethod def preprocess_text(cls, texts: List[str]) -> List[str]: """将方言文本转换为标准表达(使用Trie树优化)""" if cls._dialect_trie is None: cls._dialect_trie = cls._build_dialect_trie() processed_texts = [] for text in texts: # 使用Trie树进行高效替换 processed = [] i = 0 n = len(text) while i < n: node = cls._dialect_trie j = i found = False # 查找最长匹配 while j < n and text[j] in node.children: node = node.children[text[j]] j += 1 if node.is_end: processed.append(node.value) i = j found = True break if not found: processed.append(text[i]) i += 1 processed_texts.append(&#39;&#39;.join(processed)) return processed_texts # ====================== 系统配置管理器 ====================== class ConfigManager: """管理应用程序配置""" _instance = None def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) cls._instance._init_config() return cls._instance def _init_config(self): """初始化默认配置""" self.config = { "model_paths": { "asr": "./models/iic-speech_paraformer-large-vad-punc-spk_asr_nat-zh-cn", "sentiment": "./models/IDEA-CCNL-Erlangshen-Roberta-110M-Sentiment" }, "sample_rate": 16000, "silence_thresh": -40, "min_silence_len": 1000, "max_concurrent": 1, "dialect_config": "guizhou", "max_audio_duration": 3600 # 最大音频时长(秒) } self.load_config() def load_config(self): """从文件加载配置""" try: if os.path.exists("config.json"): with open("config.json", "r") as f: self.config.update(json.load(f)) except: pass def save_config(self): """保存配置到文件""" try: with open("config.json", "w") as f: json.dump(self.config, f, indent=2) except: pass def get(self, key: str, default=None): """获取配置值""" return self.config.get(key, default) def set(self, key: str, value): """设置配置值""" self.config[key] = value self.save_config() # ====================== 音频处理工具(优化版) ====================== class AudioProcessor: """处理音频转换和特征提取(避免重复加载)""" SUPPORTED_FORMATS = (&#39;.mp3&#39;, &#39;.wav&#39;, &#39;.amr&#39;, &#39;.m4a&#39;) @staticmethod def convert_to_wav(input_path: str, temp_dir: str) -> Optional[List[str]]: """将音频转换为WAV格式(在静音处分割)""" try: os.makedirs(temp_dir, exist_ok=True) # 检查文件格式 if not any(input_path.lower().endswith(ext) for ext in AudioProcessor.SUPPORTED_FORMATS): raise ValueError(f"不支持的音频格式: {os.path.splitext(input_path)[1]}") if input_path.lower().endswith(&#39;.wav&#39;): return [input_path] # 已经是WAV格式 # 检查ffmpeg是否可用 try: AudioSegment.converter = "ffmpeg" # 显式指定ffmpeg audio = AudioSegment.from_file(input_path) except FileNotFoundError: print("错误: 未找到ffmpeg,请安装并添加到环境变量") return None # 检查音频时长是否超过限制 max_duration = ConfigManager().get("max_audio_duration", 3600) * 1000 # 毫秒 if len(audio) > max_duration: return AudioProcessor._split_long_audio(audio, input_path, temp_dir) else: return AudioProcessor._convert_single_audio(audio, input_path, temp_dir) except Exception as e: print(f"格式转换失败: {str(e)}") return None @staticmethod def _split_long_audio(audio: AudioSegment, input_path: str, temp_dir: str) -> List[str]: """分割长音频文件""" wav_paths = [] # 在静音处分割音频 chunks = split_on_silence( audio, min_silence_len=ConfigManager().get("min_silence_len", 1000), silence_thresh=ConfigManager().get("silence_thresh", -40), keep_silence=500 ) # 合并小片段,避免分段过多 merged_chunks = [] current_chunk = AudioSegment.empty() for chunk in chunks: if len(current_chunk) + len(chunk) < 5 * 60 * 1000: # 5分钟 current_chunk += chunk else: if len(current_chunk) > 0: merged_chunks.append(current_chunk) current_chunk = chunk if len(current_chunk) > 0: merged_chunks.append(current_chunk) # 导出分段音频 sample_rate = ConfigManager().get("sample_rate", 16000) for i, chunk in enumerate(merged_chunks): chunk = chunk.set_frame_rate(sample_rate).set_channels(1) chunk_path = os.path.join( temp_dir, f"{os.path.splitext(os.path.basename(input_path))[0]}_part{i + 1}.wav" ) chunk.export(chunk_path, format="wav") wav_paths.append(chunk_path) return wav_paths @staticmethod def _convert_single_audio(audio: AudioSegment, input_path: str, temp_dir: str) -> List[str]: """转换单个短音频文件""" sample_rate = ConfigManager().get("sample_rate", 16000) audio = audio.set_frame_rate(sample_rate).set_channels(1) wav_path = os.path.join(temp_dir, os.path.splitext(os.path.basename(input_path))[0] + ".wav") audio.export(wav_path, format="wav") return [wav_path] @staticmethod def extract_features_from_audio(y: np.ndarray, sr: int) -> Dict[str, float]: """从音频数据中提取特征(流式处理优化)""" try: duration = librosa.get_duration(y=y, sr=sr) segment_length = 60 # 60秒分段 total_segments = max(1, int(np.ceil(duration / segment_length))) syllable_rates = [] volume_stabilities = [] total_samples = len(y) samples_per_segment = int(segment_length * sr) # 流式处理每个分段 for i in range(total_segments): start = i * samples_per_segment end = min((i + 1) * samples_per_segment, total_samples) y_segment = y[start:end] if len(y_segment) == 0: continue # 语速计算(使用VAD检测语音段) intervals = librosa.effects.split(y_segment, top_db=20) speech_samples = sum(end - start for start, end in intervals) speech_duration = speech_samples / sr if speech_duration > 0.1: syllable_rate = len(intervals) / speech_duration else: syllable_rate = 0 syllable_rates.append(syllable_rate) # 音量稳定性(使用RMS能量) rms = librosa.feature.rms(y=y_segment, frame_length=2048, hop_length=512)[0] if len(rms) > 0 and np.mean(rms) > 0: volume_stability = np.std(rms) / np.mean(rms) volume_stabilities.append(volume_stability) # 计算加权平均值(按时长加权) valid_syllable = [r for r in syllable_rates if r > 0] valid_volume = [v for v in volume_stabilities if v > 0] return { "duration": duration, "syllable_rate": round(np.mean(valid_syllable) if valid_syllable else 0, 2), "volume_stability": round(np.mean(valid_volume) if valid_volume else 0, 4) } except Exception as e: print(f"特征提取错误: {str(e)}") return {"duration": 0, "syllable_rate": 0, "volume_stability": 0} # ====================== 模型加载器(优化版) ====================== class ModelLoader: """加载和管理AI模型(使用RLock)""" asr_pipeline = None sentiment_model = None sentiment_tokenizer = None model_lock = RLock() # 使用RLock代替Lock models_loaded = False # 添加模型加载状态标志 @classmethod def load_models(cls): """加载所有模型""" config = ConfigManager() # 加载ASR模型 if not cls.asr_pipeline: with cls.model_lock: if not cls.asr_pipeline: # 双重检查锁定 cls.load_asr_model(config.get("model_paths")["asr"]) # 加载情感分析模型 if not cls.sentiment_model: with cls.model_lock: if not cls.sentiment_model: # 双重检查锁定 cls.load_sentiment_model(config.get("model_paths")["sentiment"]) cls.models_loaded = True @classmethod def reload_models(cls): """重新加载模型(配置变更后)""" with cls.model_lock: cls.asr_pipeline = None cls.sentiment_model = None cls.sentiment_tokenizer = None gc.collect() if torch.cuda.is_available(): torch.cuda.empty_cache() cls.load_models() @classmethod def load_asr_model(cls, model_path: str): """加载语音识别模型""" try: if not os.path.exists(model_path): raise FileNotFoundError(f"ASR模型路径不存在: {model_path}") asr_kwargs = {} if hasattr(torch, &#39;quantization&#39;): asr_kwargs[&#39;quantize&#39;] = &#39;int8&#39; print("启用ASR模型量化") cls.asr_pipeline = pipeline( task=Tasks.auto_speech_recognition, model=model_path, device=&#39;cuda&#39; if torch.cuda.is_available() else &#39;cpu&#39;, **asr_kwargs ) print("ASR模型加载完成") except Exception as e: print(f"加载ASR模型失败: {str(e)}") raise @classmethod def load_sentiment_model(cls, model_path: str): """加载情感分析模型""" try: if not os.path.exists(model_path): raise FileNotFoundError(f"情感分析模型路径不存在: {model_path}") cls.sentiment_model = AutoModelForSequenceClassification.from_pretrained(model_path) cls.sentiment_tokenizer = AutoTokenizer.from_pretrained(model_path) if torch.cuda.is_available(): cls.sentiment_model = cls.sentiment_model.cuda() print("情感分析模型加载完成") except Exception as e: print(f"加载情感分析模型失败: {str(e)}") raise # ====================== 核心分析线程(优化版) ====================== class AnalysisThread(QThread): progress_updated = pyqtSignal(int, str, str) result_ready = pyqtSignal(dict) finished_all = pyqtSignal() error_occurred = pyqtSignal(str, str) memory_warning = pyqtSignal() resource_cleanup = pyqtSignal() def __init__(self, audio_paths: List[str], temp_dir: str = "temp_wav"): super().__init__() self.audio_paths = audio_paths self.temp_dir = temp_dir self.is_running = True self.current_file = "" self.max_concurrent = min( ConfigManager().get("max_concurrent", 1), self.get_max_concurrent_tasks() ) self.resource_monitor = ResourceMonitor() self.semaphore = Semaphore(self.max_concurrent) os.makedirs(temp_dir, exist_ok=True) def run(self): try: if not ModelLoader.models_loaded: self.error_occurred.emit("模型未加载", "请等待模型加载完成后再开始分析") return self.progress_updated.emit(0, f"最大并行任务数: {self.max_concurrent}", "") # 使用线程池并行处理 with concurrent.futures.ThreadPoolExecutor(max_workers=self.max_concurrent) as executor: # 创建任务 future_to_path = {} for path in self.audio_paths: if not self.is_running: break # 使用信号量控制并发 self.semaphore.acquire() batch_size = self.get_available_batch_size() future = executor.submit(self.analyze_audio, path, batch_size) future_to_path[future] = path future.add_done_callback(lambda f: self.semaphore.release()) # 处理完成的任务 for i, future in enumerate(concurrent.futures.as_completed(future_to_path)): if not self.is_running: break path = future_to_path[future] self.current_file = os.path.basename(path) # 内存检查 if self.check_memory_usage(): self.memory_warning.emit() self.is_running = False break try: result = future.result() if result: self.result_ready.emit(result) # 更新进度 progress = int((i + 1) / len(self.audio_paths) * 100) self.progress_updated.emit( progress, f"完成: {self.current_file} ({i + 1}/{len(self.audio_paths)})", self.current_file ) except Exception as e: result = { "file_name": self.current_file, "status": "error", "error": f"分析失败: {str(e)}" } self.result_ready.emit(result) # 分析完成后 if self.is_running: self.finished_all.emit() except Exception as e: self.error_occurred.emit("系统错误", str(e)) traceback.print_exc() finally: # 确保资源清理 self.resource_cleanup.emit() self.cleanup_resources() def analyze_audio(self, audio_path: str, batch_size: int) -> Dict: """分析单个音频文件(整合所有优化)""" result = { "file_name": os.path.basename(audio_path), "status": "processing" } wav_paths = [] try: # 1. 音频格式转换 wav_paths = AudioProcessor.convert_to_wav(audio_path, self.temp_dir) if not wav_paths: result["error"] = "格式转换失败(请检查ffmpeg是否安装)" result["status"] = "error" return result # 2. 提取音频特征(合并所有分段) audio_features = self._extract_audio_features(wav_paths) result.update(audio_features) result["duration_str"] = self._format_duration(audio_features["duration"]) # 3. 语音识别与处理(使用批处理优化) all_segments, full_text = self._process_asr_segments(wav_paths) # 4. 说话人区分(使用优化后的方法) agent_segments, customer_segments = self.identify_speakers(all_segments) # 5. 生成带说话人标签的文本 labeled_text = self._generate_labeled_text(all_segments, agent_segments, customer_segments) result["asr_text"] = labeled_text.strip() # 6. 文本分析(包含方言预处理) text_analysis = self._analyze_text(agent_segments, customer_segments, batch_size) result.update(text_analysis) # 7. 服务规范检查(使用方言适配的关键词) service_check = self._check_service_rules(agent_segments) result.update(service_check) # 8. 问题解决率(上下文关联) result["issue_resolved"] = self._check_issue_resolution(customer_segments, agent_segments) result["status"] = "success" except Exception as e: result["error"] = f"分析失败: {str(e)}" result["status"] = "error" finally: # 清理临时文件(使用优化后的清理方法) self._cleanup_temp_files(wav_paths) # 显式内存清理 self.cleanup_resources() return result def identify_speakers(self, segments: List[Dict]) -> Tuple[List[Dict], List[Dict]]: """区分客服与客户(增强版)""" if not segments: return [], [] # 1. 基于关键词的识别 agent_id = self._identify_by_keywords(segments) # 2. 基于说话模式的识别(如果关键词识别失败) if agent_id is None and len(segments) >= 4: agent_id = self._identify_by_speech_patterns(segments) # 3. 使用说话频率最高的作为客服(最后手段) if agent_id is None: spk_counts = {} for seg in segments: spk_id = seg["spk_id"] spk_counts[spk_id] = spk_counts.get(spk_id, 0) + 1 agent_id = max(spk_counts, key=spk_counts.get) if spk_counts else None if agent_id is None: return [], [] # 使用集合存储agent的spk_id agent_spk_ids = {agent_id} return ( [seg for seg in segments if seg["spk_id"] in agent_spk_ids], [seg for seg in segments if seg["spk_id"] not in agent_spk_ids] ) def _identify_by_keywords(self, segments: List[Dict]) -> Optional[str]: """基于关键词识别客服""" opening_patterns = DialectConfig.get_compiled_opening() closing_patterns = DialectConfig.get_compiled_closing() # 策略1:在前3段中查找开场白关键词 for seg in segments[:3]: text = seg["text"] for pattern in opening_patterns: if pattern.search(text): return seg["spk_id"] # 策略2:在后3段中查找结束语关键词 for seg in reversed(segments[-3:] if len(segments) >= 3 else segments): text = seg["text"] for pattern in closing_patterns: if pattern.search(text): return seg["spk_id"] return None def _identify_by_speech_patterns(self, segments: List[Dict]) -> Optional[str]: """基于说话模式识别客服""" # 分析说话模式特征 speaker_features = {} for seg in segments: spk_id = seg["spk_id"] if spk_id not in speaker_features: speaker_features[spk_id] = { "total_duration": 0.0, "turn_count": 0, "question_count": 0 } features = speaker_features[spk_id] features["total_duration"] += (seg["end"] - seg["start"]) features["turn_count"] += 1 # 检测问题(包含疑问词) if any(q_word in seg["text"] for q_word in ["吗", "呢", "?", "?", "如何", "怎样"]): features["question_count"] += 1 # 客服通常说话时间更长、提问更多 if speaker_features: # 计算说话时间占比 max_duration = max(f["total_duration"] for f in speaker_features.values()) # 计算提问频率 question_rates = { spk_id: features["question_count"] / features["turn_count"] for spk_id, features in speaker_features.items() } # 综合评分 candidates = [] for spk_id, features in speaker_features.items(): score = ( 0.6 * (features["total_duration"] / max_duration) + 0.4 * question_rates[spk_id] ) candidates.append((spk_id, score)) # 返回得分最高的说话人 return max(candidates, key=lambda x: x[1])[0] return None def _analyze_text(self, agent_segments: List[Dict], customer_segments: List[Dict], batch_size: int) -> Dict: """文本情感分析(优化版:向量化批处理)""" def analyze_speaker(segments: List[Dict], speaker_type: str) -> Dict: if not segments: return { f"{speaker_type}_negative": 0.0, f"{speaker_type}_neutral": 1.0, f"{speaker_type}_positive": 0.0, f"{speaker_type}_emotions": "无" } # 方言预处理 - 使用优化的一次性替换 texts = [seg["text"] for seg in segments] processed_texts = DialectConfig.preprocess_text(texts) # 使用DataLoader进行批处理 with ModelLoader.model_lock: inputs = ModelLoader.sentiment_tokenizer( processed_texts, padding=True, truncation=True, max_length=128, return_tensors="pt" ) # 创建TensorDataset和DataLoader dataset = TensorDataset(inputs[&#39;input_ids&#39;], inputs[&#39;attention_mask&#39;]) dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=False) device = "cuda" if torch.cuda.is_available() else "cpu" sentiment_dist = [] emotions = [] # 批量处理 for batch in dataloader: input_ids, attention_mask = batch inputs = { &#39;input_ids&#39;: input_ids.to(device), &#39;attention_mask&#39;: attention_mask.to(device) } with torch.no_grad(): outputs = ModelLoader.sentiment_model(**inputs) batch_probs = torch.nn.functional.softmax(outputs.logits, dim=-1) sentiment_dist.append(batch_probs.cpu()) # 情绪识别(批量) emotion_keywords = ["愤怒", "生气", "鬼火", "不耐烦", "搞哪样嘛", "恼火", "背时"] for text in processed_texts: if any(kw in text for kw in emotion_keywords): if any(kw in text for kw in ["愤怒", "生气", "鬼火", "恼火"]): emotions.append("愤怒") elif any(kw in text for kw in ["不耐烦", "搞哪样嘛"]): emotions.append("不耐烦") elif "背时" in text: emotions.append("沮丧") # 合并结果 if sentiment_dist: all_probs = torch.cat(sentiment_dist, dim=0) avg_sentiment = torch.mean(all_probs, dim=0).tolist() else: avg_sentiment = [0.0, 1.0, 0.0] # 默认值 return { f"{speaker_type}_negative": round(avg_sentiment[0], 4), f"{speaker_type}_neutral": round(avg_sentiment[1], 4), f"{speaker_type}_positive": round(avg_sentiment[2], 4), f"{speaker_type}_emotions": ",".join(set(emotions)) if emotions else "无" } return { **analyze_speaker(agent_segments, "agent"), **analyze_speaker(customer_segments, "customer") } # ====================== 辅助方法 ====================== def get_available_batch_size(self) -> int: """根据GPU内存动态调整batch size(考虑并行)""" if not torch.cuda.is_available(): return 4 # CPU默认批次 total_mem = torch.cuda.get_device_properties(0).total_memory / (1024 ** 3) # GB per_task_mem = total_mem / self.max_concurrent # 修正批次大小逻辑:显存越少,批次越小 if per_task_mem < 2: return 2 elif per_task_mem < 4: return 4 else: return 8 def get_max_concurrent_tasks(self) -> int: """根据系统资源计算最大并行任务数""" if torch.cuda.is_available(): total_mem = torch.cuda.get_device_properties(0).total_memory / (1024 ** 3) if total_mem < 6: return 1 elif total_mem < 12: return 2 else: return 3 else: # CPU模式下根据核心数设置 return max(1, os.cpu_count() // 2) def check_memory_usage(self) -> bool: try: mem_percent = self.resource_monitor.memory_percent() return mem_percent.get("cpu", 0) > 85 or mem_percent.get("gpu", 0) > 85 except: return False def _extract_audio_features(self, wav_paths: List[str]) -> Dict[str, float]: """提取音频特征(合并所有分段)""" combined_y = np.array([], dtype=np.float32) sr = ConfigManager().get("sample_rate", 16000) for path in wav_paths: y, _ = librosa.load(path, sr=sr) combined_y = np.concatenate((combined_y, y)) return AudioProcessor.extract_features_from_audio(combined_y, sr) def _process_asr_segments(self, wav_paths: List[str]) -> Tuple[List[Dict], str]: """处理ASR分段(批处理优化)""" segments = [] full_text = "" # 分批处理(根据GPU内存动态调整批次大小) batch_size = min(4, len(wav_paths), self.get_available_batch_size()) for i in range(0, len(wav_paths), batch_size): if not self.is_running: break batch_paths = wav_paths[i:i + batch_size] try: # 批处理调用ASR模型 results = ModelLoader.asr_pipeline( batch_paths, hotwords=DialectConfig.get_asr_hotwords(), output_dir=None, batch_size=batch_size ) for result in results: for seg in result[0]["sentences"]: segments.append({ "start": seg["start"], "end": seg["end"], "text": seg["text"], "spk_id": seg.get("spk_id", "0") }) full_text += seg["text"] + " " except Exception as e: print(f"ASR批处理错误: {str(e)}") # 失败时回退到单文件处理 for path in batch_paths: try: result = ModelLoader.asr_pipeline( path, hotwords=DialectConfig.get_asr_hotwords(), output_dir=None ) for seg in result[0]["sentences"]: segments.append({ "start": seg["start"], "end": seg["end"], "text": seg["text"], "spk_id": seg.get("spk_id", "0") }) full_text += seg["text"] + " " except: continue return segments, full_text.strip() def _generate_labeled_text(self, all_segments: List[Dict], agent_segments: List[Dict], customer_segments: List[Dict]) -> str: """生成带说话人标签的文本""" agent_spk_id = agent_segments[0]["spk_id"] if agent_segments else None customer_spk_id = customer_segments[0]["spk_id"] if customer_segments else None labeled_text = [] for seg in all_segments: if seg["spk_id"] == agent_spk_id: speaker = "客服" elif seg["spk_id"] == customer_spk_id: speaker = "客户" else: speaker = f"说话人{seg[&#39;spk_id&#39;]}" labeled_text.append(f"[{speaker}]: {seg[&#39;text&#39;]}") return "\n".join(labeled_text) def _check_service_rules(self, agent_segments: List[Dict]) -> Dict: """检查服务规范""" forbidden_keywords = DialectConfig.get_combined_keywords()["forbidden"] found_forbidden = [] found_opening = False found_closing = False # 检查开场白(前3段) for seg in agent_segments[:3]: text = seg["text"] if any(kw in text for kw in DialectConfig.get_combined_keywords()["opening"]): found_opening = True break # 检查结束语(后3段) for seg in reversed(agent_segments[-3:] if len(agent_segments) >= 3 else agent_segments): text = seg["text"] if any(kw in text for kw in DialectConfig.get_combined_keywords()["closing"]): found_closing = True break # 检查禁用词 for seg in agent_segments: text = seg["text"] for kw in forbidden_keywords: if kw in text: found_forbidden.append(kw) break return { "opening_found": found_opening, "closing_found": found_closing, "forbidden_words": ", ".join(set(found_forbidden)) if found_forbidden else "无" } def _check_issue_resolution(self, customer_segments: List[Dict], agent_segments: List[Dict]) -> bool: """检查问题是否解决(增强版)""" if not customer_segments or not agent_segments: return False # 提取所有文本 customer_texts = [seg["text"] for seg in customer_segments] agent_texts = [seg["text"] for seg in agent_segments] full_conversation = " ".join(customer_texts + agent_texts) # 问题解决关键词 resolution_keywords = ["解决", "处理", "完成", "已", "好了", "可以了", "没问题"] thank_keywords = ["谢谢", "感谢", "多谢"] negative_keywords = ["没解决", "不行", "不对", "还是", "仍然", "再"] # 检查是否有负面词汇 has_negative = any(kw in full_conversation for kw in negative_keywords) if has_negative: return False # 检查客户最后是否表达感谢 last_customer_text = customer_segments[-1]["text"] if any(kw in last_customer_text for kw in thank_keywords): return True # 检查是否有解决关键词 if any(kw in full_conversation for kw in resolution_keywords): return True # 检查客服是否确认解决 for agent_text in reversed(agent_texts[-3:]): # 检查最后3段 if any(kw in agent_text for kw in resolution_keywords): return True return False def _cleanup_temp_files(self, paths: List[str]): """清理临时文件(增强兼容性)""" def safe_remove(path): """安全删除文件(多平台兼容)""" try: if os.path.exists(path): if sys.platform == &#39;win32&#39;: # Windows系统需要特殊处理 os.chmod(path, 0o777) # 确保有权限 for _ in range(5): # 最多尝试5次 try: os.remove(path) break except PermissionError: time.sleep(0.2) else: os.remove(path) except Exception: pass # 使用线程池并行删除 with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor: executor.map(safe_remove, paths) # 额外清理:删除超过1小时的临时文件 now = time.time() for file in os.listdir(self.temp_dir): file_path = os.path.join(self.temp_dir, file) if os.path.isfile(file_path): file_age = now - os.path.getmtime(file_path) if file_age > 3600: # 1小时 safe_remove(file_path) def _format_duration(self, seconds: float) -> str: """将秒转换为时分秒格式""" minutes, seconds = divmod(int(seconds), 60) hours, minutes = divmod(minutes, 60) return f"{hours:02d}:{minutes:02d}:{seconds:02d}" def cleanup_resources(self): """显式清理资源""" gc.collect() if torch.cuda.is_available(): torch.cuda.empty_cache() def stop(self): """停止分析""" self.is_running = False # ====================== 模型加载线程 ====================== class ModelLoadThread(QThread): progress_updated = pyqtSignal(int, str) finished = pyqtSignal(bool, str) def run(self): try: # 检查模型路径 config = ConfigManager().get("model_paths") if not os.path.exists(config["asr"]): self.finished.emit(False, "ASR模型路径不存在") return if not os.path.exists(config["sentiment"]): self.finished.emit(False, "情感分析模型路径不存在") return self.progress_updated.emit(20, "加载语音识别模型...") ModelLoader.load_asr_model(config["asr"]) self.progress_updated.emit(60, "加载情感分析模型...") ModelLoader.load_sentiment_model(config["sentiment"]) self.progress_updated.emit(100, "模型加载完成") self.finished.emit(True, "模型加载成功。建议:可通过设置界面修改模型路径") except Exception as e: self.finished.emit(False, f"模型加载失败: {str(e)}。建议:检查模型路径是否正确,或重新下载模型文件") # ====================== GUI主界面 ====================== class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("贵州方言客服质检系统") self.setGeometry(100, 100, 1200, 800) self.setup_ui() self.setup_menu() self.analysis_thread = None self.model_load_thread = None self.temp_dir = "temp_wav" os.makedirs(self.temp_dir, exist_ok=True) self.model_loaded = False def setup_ui(self): """设置用户界面""" # 主布局 main_widget = QWidget() main_layout = QVBoxLayout() main_widget.setLayout(main_layout) self.setCentralWidget(main_widget) # 工具栏 toolbar = QToolBar("主工具栏") toolbar.setIconSize(QSize(24, 24)) self.addToolBar(toolbar) # 添加文件按钮 add_file_action = QAction(QIcon("icons/add.png"), "添加文件", self) add_file_action.triggered.connect(self.add_files) toolbar.addAction(add_file_action) # 开始分析按钮 analyze_action = QAction(QIcon("icons/start.png"), "开始分析", self) analyze_action.triggered.connect(self.start_analysis) toolbar.addAction(analyze_action) # 停止按钮 stop_action = QAction(QIcon("icons/stop.png"), "停止分析", self) stop_action.triggered.connect(self.stop_analysis) toolbar.addAction(stop_action) # 设置按钮 settings_action = QAction(QIcon("icons/settings.png"), "设置", self) settings_action.triggered.connect(self.open_settings) toolbar.addAction(settings_action) # 分割布局 splitter = QSplitter(Qt.Horizontal) main_layout.addWidget(splitter) # 左侧文件列表 left_widget = QWidget() left_layout = QVBoxLayout() left_widget.setLayout(left_layout) file_list_label = QLabel("待分析文件列表") file_list_label.setFont(QFont("Arial", 12, QFont.Bold)) left_layout.addWidget(file_list_label) self.file_list = QListWidget() self.file_list.setSelectionMode(QListWidget.ExtendedSelection) left_layout.addWidget(self.file_list) # 右侧结果区域 right_widget = QWidget() right_layout = QVBoxLayout() right_widget.setLayout(right_layout) # 进度条 progress_label = QLabel("分析进度") progress_label.setFont(QFont("Arial", 12, QFont.Bold)) right_layout.addWidget(progress_label) self.progress_bar = QProgressBar() self.progress_bar.setRange(0, 100) self.progress_bar.setTextVisible(True) right_layout.addWidget(self.progress_bar) # 当前文件标签 self.current_file_label = QLabel("当前文件: 无") right_layout.addWidget(self.current_file_label) # 结果标签页 self.tab_widget = QTabWidget() right_layout.addWidget(self.tab_widget, 1) # 文本结果标签页 text_tab = QWidget() text_layout = QVBoxLayout() text_tab.setLayout(text_layout) self.text_result = QTextEdit() self.text_result.setReadOnly(True) text_layout.addWidget(self.text_result) self.tab_widget.addTab(text_tab, "文本结果") # 详细结果标签页 detail_tab = QWidget() detail_layout = QVBoxLayout() detail_tab.setLayout(detail_layout) self.result_table = QTableWidget() self.result_table.setColumnCount(10) self.result_table.setHorizontalHeaderLabels([ "文件名", "时长", "语速", "音量稳定性", "客服情感", "客户情感", "开场白", "结束语", "禁用词", "问题解决" ]) self.result_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) detail_layout.addWidget(self.result_table) self.tab_widget.addTab(detail_tab, "详细结果") # 添加左右部件到分割器 splitter.addWidget(left_widget) splitter.addWidget(right_widget) splitter.setSizes([300, 900]) def setup_menu(self): """设置菜单栏""" menu_bar = self.menuBar() # 文件菜单 file_menu = menu_bar.addMenu("文件") add_file_action = QAction("添加文件", self) add_file_action.triggered.connect(self.add_files) file_menu.addAction(add_file_action) export_action = QAction("导出结果", self) export_action.triggered.connect(self.export_results) file_menu.addAction(export_action) exit_action = QAction("退出", self) exit_action.triggered.connect(self.close) file_menu.addAction(exit_action) # 分析菜单 analysis_menu = menu_bar.addMenu("分析") start_action = QAction("开始分析", self) start_action.triggered.connect(self.start_analysis) analysis_menu.addAction(start_action) stop_action = QAction("停止分析", self) stop_action.triggered.connect(self.stop_analysis) analysis_menu.addAction(stop_action) # 设置菜单 settings_menu = menu_bar.addMenu("设置") config_action = QAction("系统配置", self) config_action.triggered.connect(self.open_settings) settings_menu.addAction(config_action) model_action = QAction("加载模型", self) model_action.triggered.connect(self.load_models) settings_menu.addAction(model_action) def add_files(self): """添加文件到分析列表""" files, _ = QFileDialog.getOpenFileNames( self, "选择音频文件", "", "音频文件 (*.mp3 *.wav *.amr *.m4a)" ) if files: for file in files: self.file_list.addItem(file) def start_analysis(self): """开始分析""" if self.file_list.count() == 0: QMessageBox.warning(self, "警告", "请先添加要分析的音频文件") return if not self.model_loaded: QMessageBox.warning(self, "警告", "模型未加载,请先加载模型") return # 获取文件路径 audio_paths = [self.file_list.item(i).text() for i in range(self.file_list.count())] # 清空结果 self.text_result.clear() self.result_table.setRowCount(0) # 创建分析线程 self.analysis_thread = AnalysisThread(audio_paths, self.temp_dir) # 连接信号 self.analysis_thread.progress_updated.connect(self.update_progress) self.analysis_thread.result_ready.connect(self.handle_result) self.analysis_thread.finished_all.connect(self.analysis_finished) self.analysis_thread.error_occurred.connect(self.show_error) self.analysis_thread.memory_warning.connect(self.handle_memory_warning) self.analysis_thread.resource_cleanup.connect(self.cleanup_resources) # 启动线程 self.analysis_thread.start() def stop_analysis(self): """停止分析""" if self.analysis_thread and self.analysis_thread.isRunning(): self.analysis_thread.stop() self.analysis_thread.wait() QMessageBox.information(self, "信息", "分析已停止") def load_models(self): """加载模型""" if self.model_load_thread and self.model_load_thread.isRunning(): return self.model_load_thread = ModelLoadThread() self.model_load_thread.progress_updated.connect( lambda value, msg: self.progress_bar.setValue(value) ) self.model_load_thread.finished.connect(self.handle_model_load_result) self.model_load_thread.start() def update_progress(self, progress: int, message: str, current_file: str): """更新进度""" self.progress_bar.setValue(progress) self.current_file_label.setText(f"当前文件: {current_file}") def handle_result(self, result: Dict): """处理分析结果""" # 添加到文本结果 self.text_result.append(f"文件: {result[&#39;file_name&#39;]}") self.text_result.append(f"状态: {result[&#39;status&#39;]}") if result["status"] == "success": self.text_result.append(f"时长: {result[&#39;duration_str&#39;]}") self.text_result.append(f"语速: {result[&#39;syllable_rate&#39;]} 音节/秒") self.text_result.append(f"音量稳定性: {result[&#39;volume_stability&#39;]}") self.text_result.append(f"客服情感: 负面({result[&#39;agent_negative&#39;]:.2%}) " f"中性({result[&#39;agent_neutral&#39;]:.2%}) " f"正面({result[&#39;agent_positive&#39;]:.2%})") self.text_result.append(f"客服情绪: {result[&#39;agent_emotions&#39;]}") self.text_result.append(f"客户情感: 负面({result[&#39;customer_negative&#39;]:.2%}) " f"中性({result[&#39;customer_neutral&#39;]:.2%}) " f"正面({result[&#39;customer_positive&#39;]:.2%})") self.text_result.append(f"客户情绪: {result[&#39;customer_emotions&#39;]}") self.text_result.append(f"开场白: {&#39;有&#39; if result[&#39;opening_found&#39;] else &#39;无&#39;}") self.text_result.append(f"结束语: {&#39;有&#39; if result[&#39;closing_found&#39;] else &#39;无&#39;}") self.text_result.append(f"禁用词: {result[&#39;forbidden_words&#39;]}") self.text_result.append(f"问题解决: {&#39;是&#39; if result[&#39;issue_resolved&#39;] else &#39;否&#39;}") self.text_result.append("\n=== 对话文本 ===\n") self.text_result.append(result["asr_text"]) self.text_result.append("\n" + "=" * 50 + "\n") # 添加到结果表格 row = self.result_table.rowCount() self.result_table.insertRow(row) self.result_table.setItem(row, 0, QTableWidgetItem(result["file_name"])) self.result_table.setItem(row, 1, QTableWidgetItem(result["duration_str"])) self.result_table.setItem(row, 2, QTableWidgetItem(str(result["syllable_rate"]))) self.result_table.setItem(row, 3, QTableWidgetItem(str(result["volume_stability"]))) self.result_table.setItem(row, 4, QTableWidgetItem( f"负:{result[&#39;agent_negative&#39;]:.2f} 中:{result[&#39;agent_neutral&#39;]:.2f} 正:{result[&#39;agent_positive&#39;]:.2f}" )) self.result_table.setItem(row, 5, QTableWidgetItem( f"负:{result[&#39;customer_negative&#39;]:.2f} 中:{result[&#39;customer_neutral&#39;]:.2f} 正:{result[&#39;customer_positive&#39;]:.2f}" )) self.result_table.setItem(row, 6, QTableWidgetItem("是" if result["opening_found"] else "否")) self.result_table.setItem(row, 7, QTableWidgetItem("是" if result["closing_found"] else "否")) self.result_table.setItem(row, 8, QTableWidgetItem(result["forbidden_words"])) self.result_table.setItem(row, 9, QTableWidgetItem("是" if result["issue_resolved"] else "否")) # 根据结果着色 if not result["opening_found"]: self.result_table.item(row, 6).setBackground(QColor(255, 200, 200)) if not result["closing_found"]: self.result_table.item(row, 7).setBackground(QColor(255, 200, 200)) if result["forbidden_words"] != "无": self.result_table.item(row, 8).setBackground(QColor(255, 200, 200)) if not result["issue_resolved"]: self.result_table.item(row, 9).setBackground(QColor(255, 200, 200)) def analysis_finished(self): """分析完成""" QMessageBox.information(self, "完成", "所有音频分析完成") self.progress_bar.setValue(100) def show_error(self, title: str, message: str): """显示错误信息""" QMessageBox.critical(self, title, message) def handle_memory_warning(self): """处理内存警告""" QMessageBox.warning(self, "内存警告", "内存使用过高,分析已停止。请关闭其他应用程序后重试") def cleanup_resources(self): """清理资源""" gc.collect() if torch.cuda.is_available(): torch.cuda.empty_cache() def handle_model_load_result(self, success: bool, message: str): """处理模型加载结果""" if success: self.model_loaded = True QMessageBox.information(self, "成功", message) else: QMessageBox.critical(self, "错误", message) def open_settings(self): """打开设置对话框""" settings_dialog = QDialog(self) settings_dialog.setWindowTitle("系统设置") settings_dialog.setFixedSize(500, 400) layout = QVBoxLayout() # ASR模型路径 asr_layout = QHBoxLayout() asr_label = QLabel("ASR模型路径:") asr_line = QLineEdit(ConfigManager().get("model_paths")["asr"]) asr_browse = QPushButton("浏览...") def browse_asr(): path = QFileDialog.getExistingDirectory(self, "选择ASR模型目录") if path: asr_line.setText(path) asr_browse.clicked.connect(browse_asr) asr_layout.addWidget(asr_label) asr_layout.addWidget(asr_line) asr_layout.addWidget(asr_browse) layout.addLayout(asr_layout) # 情感分析模型路径 sentiment_layout = QHBoxLayout() sentiment_label = QLabel("情感模型路径:") sentiment_line = QLineEdit(ConfigManager().get("model_paths")["sentiment"]) sentiment_browse = QPushButton("浏览...") def browse_sentiment(): path = QFileDialog.getExistingDirectory(self, "选择情感模型目录") if path: sentiment_line.setText(path) sentiment_browse.clicked.connect(browse_sentiment) sentiment_layout.addWidget(sentiment_label) sentiment_layout.addWidget(sentiment_line) sentiment_layout.addWidget(sentiment_browse) layout.addLayout(sentiment_layout) # 并发设置 concurrent_layout = QHBoxLayout() concurrent_label = QLabel("最大并发任务:") concurrent_spin = QSpinBox() concurrent_spin.setRange(1, 8) concurrent_spin.setValue(ConfigManager().get("max_concurrent", 1)) concurrent_layout.addWidget(concurrent_label) concurrent_layout.addWidget(concurrent_spin) layout.addLayout(concurrent_layout) # 方言设置 dialect_layout = QHBoxLayout() dialect_label = QLabel("方言设置:") dialect_combo = QComboBox() dialect_combo.addItems(["标准普通话", "贵州方言"]) dialect_combo.setCurrentIndex(1 if ConfigManager().get("dialect_config") == "guizhou" else 0) dialect_layout.addWidget(dialect_label) dialect_layout.addWidget(dialect_combo) layout.addLayout(dialect_layout) # 音频时长限制 duration_layout = QHBoxLayout() duration_label = QLabel("最大音频时长(秒):") duration_spin = QSpinBox() duration_spin.setRange(60, 86400) # 1分钟到24小时 duration_spin.setValue(ConfigManager().get("max_audio_duration", 3600)) duration_layout.addWidget(duration_label) duration_layout.addWidget(duration_spin) layout.addLayout(duration_layout) # 按钮 button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) button_box.accepted.connect(settings_dialog.accept) button_box.rejected.connect(settings_dialog.reject) layout.addWidget(button_box) settings_dialog.setLayout(layout) if settings_dialog.exec_() == QDialog.Accepted: # 保存设置 ConfigManager().set("model_paths", { "asr": asr_line.text(), "sentiment": sentiment_line.text() }) ConfigManager().set("max_concurrent", concurrent_spin.value()) ConfigManager().set("dialect_config", "guizhou" if dialect_combo.currentIndex() == 1 else "standard") ConfigManager().set("max_audio_duration", duration_spin.value()) # 重新加载模型 ModelLoader.reload_models() def export_results(self): """导出结果""" if self.result_table.rowCount() == 0: QMessageBox.warning(self, "警告", "没有可导出的结果") return path, _ = QFileDialog.getSaveFileName( self, "保存结果", "", "CSV文件 (*.csv)" ) if path: try: with open(path, "w", encoding="utf-8") as f: # 写入表头 headers = [] for col in range(self.result_table.columnCount()): headers.append(self.result_table.horizontalHeaderItem(col).text()) f.write(",".join(headers) + "\n") # 写入数据 for row in range(self.result_table.rowCount()): row_data = [] for col in range(self.result_table.columnCount()): item = self.result_table.item(row, col) row_data.append(item.text() if item else "") f.write(",".join(row_data) + "\n") QMessageBox.information(self, "成功", f"结果已导出到: {path}") except Exception as e: QMessageBox.critical(self, "错误", f"导出失败: {str(e)}") def closeEvent(self, event): """关闭事件处理""" if self.analysis_thread and self.analysis_thread.isRunning(): self.analysis_thread.stop() self.analysis_thread.wait() # 清理临时目录(增强兼容性) try: for file in os.listdir(self.temp_dir): file_path = os.path.join(self.temp_dir, file) if os.path.isfile(file_path): # Windows系统可能需要多次尝试 for _ in range(3): try: os.remove(file_path) break except PermissionError: time.sleep(0.1) os.rmdir(self.temp_dir) except: pass event.accept() # ====================== 程序入口 ====================== if __name__ == "__main__": torch.set_num_threads(4) # 限制CPU线程数 app = QApplication(sys.argv) # 设置应用样式 app.setStyle(&#39;Fusion&#39;) window = MainWindow() window.show() sys.exit(app.exec_())
08-05
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值