From d34467abc51d225b52788338749379c38ec85b3c Mon Sep 17 00:00:00 2001 From: tt-P607 <68868379+tt-P607@users.noreply.github.com> Date: Mon, 25 Aug 2025 01:21:50 +0800 Subject: [PATCH 1/7] =?UTF-8?q?feat(video):=20=E6=96=B0=E5=A2=9E=E6=8C=89?= =?UTF-8?q?=E6=97=B6=E9=97=B4=E9=97=B4=E9=9A=94=E6=8A=BD=E5=B8=A7=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 增加了按时间间隔提取视频帧的新模式,与原有的固定总帧数模式并存。用户现在可以通过配置文件选择最适合其需求的抽帧方式。 - 新增 `frame_extraction_mode` 配置项,可选值为 "fixed_number" (固定总帧数) 或 "time_interval" (按时间间隔)。 - 新增 `frame_interval_seconds` 配置项,用于指定时间间隔模式下的抽帧秒数。 - `max_frames` 配置项现在仅在 "fixed_number" 模式下生效。 - 更新了 `VideoAnalyzer` 类以支持两种抽帧逻辑,并从配置中读取新参数。 - 相应地更新了配置文件模板 `bot_config_template.toml`。 --- src/chat/utils/utils_video.py | 71 ++++++++++++++++++----- src/plugins/built_in/tts_plugin/plugin.py | 2 + template/bot_config_template.toml | 6 +- 3 files changed, 61 insertions(+), 18 deletions(-) diff --git a/src/chat/utils/utils_video.py b/src/chat/utils/utils_video.py index 179231f84..5e177e9f0 100644 --- a/src/chat/utils/utils_video.py +++ b/src/chat/utils/utils_video.py @@ -61,6 +61,8 @@ class VideoAnalyzer: self.max_image_size = config.max_image_size self.enable_frame_timing = config.enable_frame_timing self.batch_analysis_prompt = config.batch_analysis_prompt + self.frame_extraction_mode = config.frame_extraction_mode + self.frame_interval_seconds = config.frame_interval_seconds # 将配置文件中的模式映射到内部使用的模式名称 config_mode = config.analysis_mode @@ -92,6 +94,8 @@ class VideoAnalyzer: self.batch_size = 3 # 批处理时每批处理的帧数 self.timeout = 60.0 # 分析超时时间(秒) self.enable_frame_timing = True + self.frame_extraction_mode = "fixed_number" + self.frame_interval_seconds = 2.0 self.batch_analysis_prompt = """请分析这个视频的内容。这些图片是从视频中按时间顺序提取的关键帧。 请提供详细的分析,包括: @@ -191,24 +195,59 @@ class VideoAnalyzer: logger.info(f"视频信息: {total_frames}帧, {fps:.2f}FPS, {duration:.2f}秒") - # 动态计算帧间隔 - if duration > 0: - frame_interval = max(1, int(duration / self.max_frames * fps)) - else: - frame_interval = 30 # 默认间隔 - frame_count = 0 extracted_count = 0 - while cap.isOpened() and extracted_count < self.max_frames: - ret, frame = cap.read() - if not ret: - break + if self.frame_extraction_mode == "time_interval": + # 新模式:按时间间隔抽帧 + time_interval = self.frame_interval_seconds + next_frame_time = 0.0 + + while cap.isOpened(): + ret, frame = cap.read() + if not ret: + break - if frame_count % frame_interval == 0: - # 转换为PIL图像并压缩 - frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) - pil_image = Image.fromarray(frame_rgb) + current_time = cap.get(cv2.CAP_PROP_POS_MSEC) / 1000.0 + + if current_time >= next_frame_time: + # 转换为PIL图像并压缩 + frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + pil_image = Image.fromarray(frame_rgb) + + # 调整图像大小 + if max(pil_image.size) > self.max_image_size: + ratio = self.max_image_size / max(pil_image.size) + new_size = tuple(int(dim * ratio) for dim in pil_image.size) + pil_image = pil_image.resize(new_size, Image.Resampling.LANCZOS) + + # 转换为base64 + buffer = io.BytesIO() + pil_image.save(buffer, format='JPEG', quality=self.frame_quality) + frame_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8') + + frames.append((frame_base64, current_time)) + extracted_count += 1 + + logger.debug(f"提取第{extracted_count}帧 (时间: {current_time:.2f}s)") + + next_frame_time += time_interval + else: + # 旧模式:固定总帧数 + if duration > 0: + frame_interval = max(1, int(total_frames / self.max_frames)) + else: + frame_interval = 1 # 如果无法获取时长,则逐帧提取直到达到max_frames + + while cap.isOpened() and extracted_count < self.max_frames: + ret, frame = cap.read() + if not ret: + break + + if frame_count % frame_interval == 0: + # 转换为PIL图像并压缩 + frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + pil_image = Image.fromarray(frame_rgb) # 调整图像大小 if max(pil_image.size) > self.max_image_size: @@ -227,8 +266,8 @@ class VideoAnalyzer: extracted_count += 1 logger.debug(f"提取第{extracted_count}帧 (时间: {timestamp:.2f}s)") - - frame_count += 1 + + frame_count += 1 cap.release() logger.info(f"✅ 成功提取{len(frames)}帧") diff --git a/src/plugins/built_in/tts_plugin/plugin.py b/src/plugins/built_in/tts_plugin/plugin.py index 4e4d3648b..30748a9ff 100644 --- a/src/plugins/built_in/tts_plugin/plugin.py +++ b/src/plugins/built_in/tts_plugin/plugin.py @@ -34,6 +34,8 @@ class TTSAction(BaseAction): # 动作使用场景 action_require = [ "当需要发送语音信息时使用", + "当用户要求你说话时使用", + "当用户要求听你声音时使用", "当用户明确要求使用语音功能时使用", "当表达内容更适合用语音而不是文字传达时使用", "当用户想听到语音回答而非阅读文本时使用", diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index f5ec06493..38a1ca176 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -1,5 +1,5 @@ [inner] -version = "6.4.3" +version = "6.4.4" #----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读---- #如果你想要修改配置文件,请递增version的值 @@ -373,7 +373,9 @@ enable_friend_chat = false # 是否启用好友聊天 [video_analysis] # 视频分析配置 enable = true # 是否启用视频分析功能 analysis_mode = "batch_frames" # 分析模式:"frame_by_frame"(逐帧分析,非常慢 "建议frames大于8时不要使用这个" ...但是详细)、"batch_frames"(批量分析,快但可能略简单 -其实效果也差不多)或 "auto"(自动选择) -max_frames = 16 # 最大分析帧数 +frame_extraction_mode = "fixed_number" # 抽帧模式: "fixed_number" (固定总帧数) 或 "time_interval" (按时间间隔) +frame_interval_seconds = 2.0 # 按时间间隔抽帧的秒数(仅在 mode = "time_interval" 时生效) +max_frames = 16 # 最大分析帧数(仅在 mode = "fixed_number" 时生效) frame_quality = 80 # 帧图像JPEG质量 (1-100) max_image_size = 800 # 单帧最大图像尺寸(像素) enable_frame_timing = true # 是否在分析中包含帧的时间信息 From 46c5eda503bfc084dbf725077076555bd2e12b97 Mon Sep 17 00:00:00 2001 From: tt-P607 <68868379+tt-P607@users.noreply.github.com> Date: Mon, 25 Aug 2025 01:36:45 +0800 Subject: [PATCH 2/7] =?UTF-8?q?feat(config):=20=E8=A7=86=E9=A2=91=E5=88=86?= =?UTF-8?q?=E6=9E=90=E6=B7=BB=E5=8A=A0=E6=8A=BD=E5=B8=A7=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E5=92=8C=E9=97=B4=E9=9A=94=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为视频分析配置增加了 `frame_extraction_mode` 和 `frame_interval_seconds` 两个新选项。这允许用户选择是按固定数量抽帧还是按时间间隔抽帧,提供了更灵活的视频处理方式。 --- src/config/official_configs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/config/official_configs.py b/src/config/official_configs.py index 1ee7cd305..5f422dfc6 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -619,6 +619,8 @@ class VideoAnalysisConfig(ValidatedConfigBase): enable: bool = Field(default=True, description="启用") analysis_mode: str = Field(default="batch_frames", description="分析模式") + frame_extraction_mode: str = Field(default="fixed_number", description="抽帧模式") + frame_interval_seconds: float = Field(default=2.0, description="抽帧时间间隔") max_frames: int = Field(default=8, description="最大帧数") frame_quality: int = Field(default=85, description="帧质量") max_image_size: int = Field(default=800, description="最大图像大小") From 21aec70e929f32a781bcf3817a5d545336a42812 Mon Sep 17 00:00:00 2001 From: tt-P607 <68868379+tt-P607@users.noreply.github.com> Date: Mon, 25 Aug 2025 01:55:18 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E6=88=91=E6=88=96=E8=AE=B8=E4=BF=AE?= =?UTF-8?q?=E5=A5=BD=E4=BA=86=E5=8F=8D=E6=88=AA=E6=96=AD=EF=BC=9F=E4=BD=86?= =?UTF-8?q?=E6=88=91=E6=97=A0=E6=B3=95=E9=AA=8C=E8=AF=81=EF=BC=8C=E7=94=A8?= =?UTF-8?q?gemini=202.5=20pro=E7=94=9F=E6=88=90=E6=97=A5=E7=A8=8B=E6=88=AA?= =?UTF-8?q?=E6=96=AD=E5=A4=AA=E4=B8=A5=E9=87=8D=E4=BA=86=EF=BC=8C=E8=BF=9E?= =?UTF-8?q?=E5=AE=8C=E6=95=B4=E7=9A=84=E4=BA=8C=E5=8D=81=E5=9B=9B=E5=B0=8F?= =?UTF-8?q?=E6=97=B6=E9=83=BD=E6=97=A0=E6=B3=95=E8=BE=93=E5=87=BA=EF=BC=8C?= =?UTF-8?q?gemini=202.5=20Flash=E6=88=AA=E6=96=AD=E5=A5=BD=E5=BE=88?= =?UTF-8?q?=E5=A4=9A=EF=BC=8C=E6=88=91=E8=AF=95=E4=BA=865=E6=AC=A1?= =?UTF-8?q?=E7=94=9F=E6=88=90=E7=9A=84=E9=83=BD=E6=98=AF=E5=AE=8C=E6=95=B4?= =?UTF-8?q?=E7=9A=84=EF=BC=8C=E5=8F=AF=E8=83=BD=E4=BF=AE=E5=A5=BD=E4=BA=86?= =?UTF-8?q?=EF=BC=9F=E4=BD=86=E6=9C=89=E5=BE=85=E9=AA=8C=E8=AF=81=EF=BC=8C?= =?UTF-8?q?=E5=BB=BA=E8=AE=AE=E5=9C=A8=E5=BE=85=E5=8A=9E=E4=B8=8A=E6=89=93?= =?UTF-8?q?=E4=B8=AA=E9=97=AE=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/llm_models/utils_model.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/llm_models/utils_model.py b/src/llm_models/utils_model.py index 8432302c5..d4ed32fb8 100644 --- a/src/llm_models/utils_model.py +++ b/src/llm_models/utils_model.py @@ -340,16 +340,22 @@ class LLMRequest: is_truncated = True logger.warning("未检测到 [done] 标记,判定为截断") - if (is_empty_reply or is_truncated) and empty_retry_count < max_empty_retry: - empty_retry_count += 1 - reason = "空回复" if is_empty_reply else "截断" - logger.warning(f"检测到{reason},正在进行第 {empty_retry_count}/{max_empty_retry} 次重新生成") + if is_empty_reply or is_truncated: + if empty_retry_count < max_empty_retry: + empty_retry_count += 1 + reason = "空回复" if is_empty_reply else "截断" + logger.warning(f"检测到{reason},正在进行第 {empty_retry_count}/{max_empty_retry} 次重新生成") - if empty_retry_interval > 0: - await asyncio.sleep(empty_retry_interval) + if empty_retry_interval > 0: + await asyncio.sleep(empty_retry_interval) - model_info, api_provider, client = self._select_model() - continue + model_info, api_provider, client = self._select_model() + continue + else: + # 已达到最大重试次数,但仍然是空回复或截断 + reason = "空回复" if is_empty_reply else "截断" + # 抛出异常,由外层重试逻辑或最终的异常处理器捕获 + raise RuntimeError(f"经过 {max_empty_retry + 1} 次尝试后仍然是{reason}的回复") # 记录使用情况 if usage := response.usage: From 9adcc2e07d85eb3c955fd22d0ed8a52ce479784e Mon Sep 17 00:00:00 2001 From: tt-P607 <68868379+tt-P607@users.noreply.github.com> Date: Mon, 25 Aug 2025 02:14:34 +0800 Subject: [PATCH 4/7] =?UTF-8?q?feat(log):=20=E4=BC=98=E5=8C=96=E5=94=A4?= =?UTF-8?q?=E9=86=92=E5=92=8C=E4=BC=91=E7=9C=A0=E6=97=A5=E5=BF=97=E7=9A=84?= =?UTF-8?q?=E8=BE=93=E5=87=BA=E9=A2=91=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 对唤醒度和休眠检查的日志记录进行节流,以减少在消息密集时产生的日志数量。 - 在 `WakeUpManager` 中,唤醒度变化的日志现在每30秒最多输出一次 INFO 级别的日志,其余时间的日志降为 DEBUG 级别。 - 在 `ScheduleManager` 中,休眠期间被唤醒的日志也增加了类似的节流逻辑,以避免日志刷屏。 --- src/chat/chat_loop/wakeup_manager.py | 9 ++++++++- src/manager/schedule_manager.py | 7 ++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/chat/chat_loop/wakeup_manager.py b/src/chat/chat_loop/wakeup_manager.py index fa755e882..46af60249 100644 --- a/src/chat/chat_loop/wakeup_manager.py +++ b/src/chat/chat_loop/wakeup_manager.py @@ -26,6 +26,8 @@ class WakeUpManager: self.angry_start_time = 0.0 # 愤怒状态开始时间 self.last_decay_time = time.time() # 上次衰减时间 self._decay_task: Optional[asyncio.Task] = None + self.last_log_time = 0 + self.log_interval = 30 # 从配置文件获取参数 wakeup_config = global_config.wakeup_system @@ -123,7 +125,12 @@ class WakeUpManager: # 群聊未被艾特,不增加唤醒度 return False - logger.info(f"{self.context.log_prefix} 唤醒度变化: {old_value:.1f} -> {self.wakeup_value:.1f} (阈值: {self.wakeup_threshold})") + current_time = time.time() + if current_time - self.last_log_time > self.log_interval: + logger.info(f"{self.context.log_prefix} 唤醒度变化: {old_value:.1f} -> {self.wakeup_value:.1f} (阈值: {self.wakeup_threshold})") + self.last_log_time = current_time + else: + logger.debug(f"{self.context.log_prefix} 唤醒度变化: {old_value:.1f} -> {self.wakeup_value:.1f} (阈值: {self.wakeup_threshold})") # 检查是否达到唤醒阈值 if self.wakeup_value >= self.wakeup_threshold: diff --git a/src/manager/schedule_manager.py b/src/manager/schedule_manager.py index 27d0d0e04..4a708aeab 100644 --- a/src/manager/schedule_manager.py +++ b/src/manager/schedule_manager.py @@ -418,7 +418,12 @@ class ScheduleManager: if is_in_time_range: # 检查是否被唤醒 if wakeup_manager and wakeup_manager.is_in_angry_state(): - logger.info(f"在休眠活动 '{activity}' 期间,但已被唤醒。") + current_timestamp = datetime.now().timestamp() + if current_timestamp - self.last_sleep_log_time > self.sleep_log_interval: + logger.info(f"在休眠活动 '{activity}' 期间,但已被唤醒。") + self.last_sleep_log_time = current_timestamp + else: + logger.debug(f"在休眠活动 '{activity}' 期间,但已被唤醒。") return False current_timestamp = datetime.now().timestamp() From 139f037d2435834607db997f63e5de7458db399e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=85=E8=AF=BA=E7=8B=90?= <212194964+foxcyber907@users.noreply.github.com> Date: Mon, 25 Aug 2025 04:18:18 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=E6=8D=9F=E5=9D=8F=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 通过更改638行,使数据库处于损坏状态 --- src/common/database/sqlalchemy_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/database/sqlalchemy_models.py b/src/common/database/sqlalchemy_models.py index 95ecb4e41..d2cc76a31 100644 --- a/src/common/database/sqlalchemy_models.py +++ b/src/common/database/sqlalchemy_models.py @@ -635,7 +635,7 @@ def get_db_session(): _, SessionLocal = initialize_database() session = SessionLocal() yield session - #session.commit() + session.commit() except Exception: if session: session.rollback() From ae3c871fb193ee256cf08f1e5745a3350257c735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=85=E8=AF=BA=E7=8B=90?= <212194964+foxcyber907@users.noreply.github.com> Date: Mon, 25 Aug 2025 05:28:43 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20sqlalchemy=5Fmodels.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/database/sqlalchemy_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/database/sqlalchemy_models.py b/src/common/database/sqlalchemy_models.py index d2cc76a31..95ecb4e41 100644 --- a/src/common/database/sqlalchemy_models.py +++ b/src/common/database/sqlalchemy_models.py @@ -635,7 +635,7 @@ def get_db_session(): _, SessionLocal = initialize_database() session = SessionLocal() yield session - session.commit() + #session.commit() except Exception: if session: session.rollback() From e3947589600e426cb7b81115b36c4d2e07f5c019 Mon Sep 17 00:00:00 2001 From: Furina-1013-create <189647097+Furina-1013-create@users.noreply.github.com> Date: Mon, 25 Aug 2025 12:09:31 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=E4=B8=BA=E5=90=8E=E7=BB=AD=E5=8F=91?= =?UTF-8?q?=E5=B8=83release=E7=89=88=E6=9C=AC=E5=81=9A=E9=80=82=E9=85=8D?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=8A=A0=E4=B8=80=E4=B8=AA=E5=B0=86maibot?= =?UTF-8?q?=E4=BD=9C=E4=B8=BA=E4=B8=80=E4=B8=AA=E5=8C=85=E8=BF=9B=E8=A1=8C?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E7=9A=84=E5=85=A5=E5=8F=A3=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __main__.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 __main__.py diff --git a/__main__.py b/__main__.py new file mode 100644 index 000000000..4ce570197 --- /dev/null +++ b/__main__.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +"""Bot项目的主入口点""" + +if __name__ == "__main__": + # 设置Python路径并执行bot.py + import sys + import os + from pathlib import Path + + # 添加当前目录到Python路径 + current_dir = Path(__file__).parent + sys.path.insert(0, str(current_dir)) + + # 执行bot.py的代码 + bot_file = current_dir / "bot.py" + with open(bot_file, 'r', encoding='utf-8') as f: + exec(f.read()) + + +# 这个文件是为了适配一键包使用的,在一键包项目之外没有用 \ No newline at end of file