diff --git a/plugins/douyin_parser/main.py b/plugins/douyin_parser/main.py index 096c76f..1180413 100644 --- a/plugins/douyin_parser/main.py +++ b/plugins/douyin_parser/main.py @@ -11,7 +11,7 @@ from urllib.parse import urlparse from loguru import logger from pathlib import Path -from PIL import Image, ImageDraw, ImageFont +from PIL import Image from base.plugin_common.message_plugin_interface import MessagePluginInterface from base.plugin_common.plugin_interface import PluginStatus @@ -171,17 +171,27 @@ class DouyinParserPlugin(MessagePluginInterface): img_bytes_list.append(b) if not img_bytes_list: return False, "下载图片失败" - merged_pages = self._merge_images_vertical_paged(img_bytes_list, 1242, 65000) - if not merged_pages: - return False, "图片合并失败" - title = media_info.get('title') or "" - # 按你的需求,图文类型不再单独发送一条文本消息。 - # 这里把文案直接绘制到合并后第一页的顶部,让“文字 + 图片”作为同一条图片消息的一部分发送。 - if len(title) > 0: - merged_pages[0] = self._append_title_to_image(merged_pages[0], title) - for page in merged_pages: - await self.bot.send_image_message((roomid if roomid else sender), page) - return True, f"发送合并图片成功({len(merged_pages)}页)" + target_id = roomid if roomid else sender + + # 图文作品改回“文本与图片分离发送”: + # 1. 文本单独发送,可读性更强,也方便用户直接复制文案; + # 2. 图片数量较少时保留原始逐张展示,避免小图文被强行拼成长图; + # 3. 图片较多时再合并,兼顾刷屏控制与浏览体验。 + note_text = self._build_note_text(media_info) + if note_text: + await bot.send_text_message(target_id, note_text) + + if len(img_bytes_list) > 3: + merged_pages = self._merge_images_vertical_paged(img_bytes_list, 1242, 65000) + if not merged_pages: + return False, "图片合并失败" + for page in merged_pages: + await bot.send_image_message(target_id, page) + return True, f"发送合并图片成功({len(merged_pages)}页)" + + for image_bytes in img_bytes_list: + await bot.send_image_message(target_id, image_bytes) + return True, f"发送原图成功({len(img_bytes_list)}张)" else: video_url = media_info.get('url', '') title = media_info.get('title', '无标题') @@ -847,95 +857,20 @@ class DouyinParserPlugin(MessagePluginInterface): return image_bytes return None - def _append_title_to_image(self, image_bytes: bytes, title: str) -> bytes: + def _build_note_text(self, media_info: Dict[str, Any]) -> str: """ - 将标题绘制到图片顶部,返回新的图片二进制数据。 + 构建图文作品的单独文本说明。 设计说明: - 1) 微信接口没有“单条消息同时携带纯文本+图片”的通用发送 API; - 2) 为了满足“图文合并发送”,这里把标题渲染为图片顶部文字区域; - 3) 渲染失败时直接回退原图,避免影响主流程可用性。 + 1) 作者和文案分开展示,用户看到消息时更容易快速理解内容来源; + 2) 不再把文本写进图片,避免图文较多时首图被额外改造; + 3) 空字段会自动跳过,防止发出大段无意义占位文本。 """ - if not title: - return image_bytes - try: - source = Image.open(io.BytesIO(image_bytes)) - if source.mode in ("RGBA", "P"): - source = source.convert("RGB") - - width, height = source.size - # 文字区域留出左右/上下内边距,保证可读性。 - pad_x = 36 - pad_y = 26 - font = self._load_chinese_font(44) - wrapped_lines = self._wrap_text_for_image(title.strip(), font, max(100, width - pad_x * 2)) - if not wrapped_lines: - return image_bytes - - # 行高按字体大小动态计算,并增加少量行间距。 - line_height = max(44, int(font.size * 1.4)) - text_block_height = pad_y * 2 + line_height * len(wrapped_lines) - - # 新建画布:上方白底承载文案,下方保留原图内容。 - canvas = Image.new("RGB", (width, height + text_block_height), (255, 255, 255)) - canvas.paste(source, (0, text_block_height)) - - draw = ImageDraw.Draw(canvas) - y = pad_y - for line in wrapped_lines: - draw.text((pad_x, y), line, font=font, fill=(34, 34, 34)) - y += line_height - - output = io.BytesIO() - canvas.save(output, format="JPEG", quality=88) - return output.getvalue() - except Exception as e: - self.LOG.warning(f"标题绘制失败,回退原图: {e}") - return image_bytes - - def _load_chinese_font(self, size: int) -> ImageFont.FreeTypeFont: - """ - 尝试加载常见中文字体,保证标题在不同系统尽量可读。 - 如果都不可用,则回退到 Pillow 默认字体(可能不支持完整中文)。 - """ - font_candidates = [ - "C:/Windows/Fonts/msyh.ttc", - "C:/Windows/Fonts/simhei.ttf", - "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc", - "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", - "/System/Library/Fonts/PingFang.ttc", - ] - for font_path in font_candidates: - if os.path.exists(font_path): - try: - return ImageFont.truetype(font_path, size=size) - except Exception: - continue - return ImageFont.load_default() - - def _wrap_text_for_image(self, text: str, font: ImageFont.ImageFont, max_width: int) -> List[str]: - """ - 按像素宽度将文本自动换行,避免标题超宽被截断。 - - 实现策略: - - 逐字追加,超过最大宽度就换行; - - 保留原有换行语义(按行分段后再逐字处理)。 - """ - draw = ImageDraw.Draw(Image.new("RGB", (10, 10))) + author = str(media_info.get("author", "") or "").strip() + title = str(media_info.get("title", "") or "").strip() lines: List[str] = [] - for para in text.splitlines(): - if not para: - lines.append("") - continue - current = "" - for ch in para: - test = current + ch - text_width = int(draw.textlength(test, font=font)) - if current and text_width > max_width: - lines.append(current) - current = ch - else: - current = test - if current: - lines.append(current) - return lines + if author: + lines.append(f"作者:{author}") + if title: + lines.append(f"文案:{title}") + return "\n".join(lines).strip()