From 6a81fd40b5dec1f0220b75680854ee53425c9648 Mon Sep 17 00:00:00 2001 From: tt-P607 <68868379+tt-P607@users.noreply.github.com> Date: Wed, 22 Oct 2025 01:54:13 +0800 Subject: [PATCH] =?UTF-8?q?refactor(chat):=20=E7=AE=80=E5=8C=96=20GIF=20?= =?UTF-8?q?=E6=8A=BD=E5=B8=A7=E9=80=BB=E8=BE=91=E4=B8=BA=E5=9D=87=E5=8C=80?= =?UTF-8?q?=E9=87=87=E6=A0=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 旧的基于均方误差(MSE)的 GIF 抽帧逻辑过于复杂,其参数难以调整且结果不可预测。 现在,该逻辑被重构为一个简单的均匀采样算法,固定抽取 4 帧来代表整个动画。这不仅简化了代码、移除了不必要的参数,还确保了对于任何 GIF 都能生成一致且有代表性的预览图,同时提升了处理性能。 BREAKING CHANGE: `transform_gif` 函数签名已更改,移除了 `similarity_threshold` 和 `max_frames` 参数。 --- src/chat/utils/utils_image.py | 45 ++++++++++------------------------- 1 file changed, 13 insertions(+), 32 deletions(-) diff --git a/src/chat/utils/utils_image.py b/src/chat/utils/utils_image.py index 20a0304ab..d104c3cd5 100644 --- a/src/chat/utils/utils_image.py +++ b/src/chat/utils/utils_image.py @@ -288,14 +288,12 @@ class ImageManager: return "[图片(处理失败)]" @staticmethod - def transform_gif(gif_base64: str, similarity_threshold: float = 1000.0, max_frames: int = 15) -> str | None: + def transform_gif(gif_base64: str) -> str | None: # sourcery skip: use-contextlib-suppress - """将GIF转换为水平拼接的静态图像, 跳过相似的帧 + """将GIF转换为水平拼接的静态图像, 均匀抽取4帧。 Args: gif_base64: GIF的base64编码字符串 - similarity_threshold: 判定帧相似的阈值 (MSE),越小表示要求差异越大才算不同帧,默认1000.0 - max_frames: 最大抽取的帧数,默认15 Returns: Optional[str]: 拼接后的JPG图像的base64编码字符串, 或者在失败时返回None @@ -323,34 +321,17 @@ class ImageManager: logger.warning("GIF中没有找到任何帧") return None # 空的GIF直接返回None - # --- 新的帧选择逻辑 --- - selected_frames = [] - last_selected_frame_np = None - - for i, current_frame in enumerate(all_frames): - current_frame_np = np.array(current_frame) - - # 第一帧总是要选的 - if i == 0: - selected_frames.append(current_frame) - last_selected_frame_np = current_frame_np - continue - - # 计算和上一张选中帧的差异(均方误差 MSE) - if last_selected_frame_np is not None: - mse = np.mean((current_frame_np - last_selected_frame_np) ** 2) - # logger.debug(f"帧 {i} 与上一选中帧的 MSE: {mse}") # 可以取消注释来看差异值 - - # 如果差异够大,就选它! - if mse > similarity_threshold: - selected_frames.append(current_frame) - last_selected_frame_np = current_frame_np - # 检查是不是选够了 - if len(selected_frames) >= max_frames: - # logger.debug(f"已选够 {max_frames} 帧,停止选择。") - break - # 如果差异不大就跳过这一帧啦 - + # --- 新的帧选择逻辑:均匀抽取4帧 --- + num_frames = len(all_frames) + if num_frames <= 4: + # 如果总帧数小于等于4,则全部选中 + selected_frames = all_frames + else: + # 使用linspace计算4个均匀分布的索引 + indices = np.linspace(0, num_frames - 1, 4, dtype=int) + selected_frames = [all_frames[i] for i in indices] + + logger.debug(f"GIF Frame Analysis: Total frames={num_frames}, Selected indices={indices if num_frames > 4 else list(range(num_frames))}") # --- 帧选择逻辑结束 --- # 如果选择后连一帧都没有(比如GIF只有一帧且后续处理失败?)或者原始GIF就没帧,也返回None