调整 ytpgif 使用共用方法读取图片
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
2025-10-09 19:56:16 +08:00
parent c35ee57976
commit b4e400b626

View File

@ -1,9 +1,9 @@
import os from io import BytesIO
import tempfile
from typing import Optional from typing import Optional
from PIL import Image, ImageSequence from PIL import Image
from nonebot.adapters import Event as BaseEvent from nonebot.adapters import Event as BaseEvent
from nonebot.adapters import Bot as BaseBot
from nonebot.plugin import PluginMetadata from nonebot.plugin import PluginMetadata
from nonebot_plugin_alconna import ( from nonebot_plugin_alconna import (
Alconna, Alconna,
@ -12,6 +12,9 @@ from nonebot_plugin_alconna import (
UniMessage, UniMessage,
on_alconna, on_alconna,
) )
from returns.result import Failure, Success
from konabot.common.nb.extract_image import extract_image_from_message
__plugin_meta__ = PluginMetadata( __plugin_meta__ = PluginMetadata(
name="ytpgif", name="ytpgif",
@ -60,7 +63,7 @@ async def get_image_url(event: BaseEvent) -> Optional[str]:
if seg.type == "image" and seg.data.get("url"): if seg.type == "image" and seg.data.get("url"):
return str(seg.data["url"]) return str(seg.data["url"])
if hasattr(event, "reply") and (reply := event.reply): if hasattr(event, "reply") and (reply := getattr(event, "reply")):
reply_msg = reply.message reply_msg = reply.message
for seg in reply_msg: for seg in reply_msg:
if seg.type == "image" and seg.data.get("url"): if seg.type == "image" and seg.data.get("url"):
@ -89,7 +92,7 @@ def resize_frame(frame: Image.Image) -> Image.Image:
@ytpgif_cmd.handle() @ytpgif_cmd.handle()
async def handle_ytpgif(event: BaseEvent, speed: float = 1.0): async def handle_ytpgif(event: BaseEvent, bot: BaseBot, speed: float = 1.0):
# === 校验 speed 范围 === # === 校验 speed 范围 ===
if not (MIN_SPEED <= speed <= MAX_SPEED): if not (MIN_SPEED <= speed <= MAX_SPEED):
await ytpgif_cmd.send( await ytpgif_cmd.send(
@ -97,40 +100,26 @@ async def handle_ytpgif(event: BaseEvent, speed: float = 1.0):
) )
return return
img_url = await get_image_url(event) match await extract_image_from_message(event.get_message(), event, bot):
if not img_url: case Success(img):
src_img = img
case Failure(msg):
await ytpgif_cmd.send( await ytpgif_cmd.send(
await UniMessage.text( await UniMessage.text(msg).export()
"请发送一张图片或回复一张图片来生成镜像动图。"
).export()
) )
return return
try: case _:
image_data = await download_image(img_url)
except Exception as e:
print(f"[YTPGIF] 下载失败: {e}")
await ytpgif_cmd.send(
await UniMessage.text("❌ 图片下载失败,请重试。").export()
)
return return
input_path = output_path = None
try: try:
with tempfile.NamedTemporaryFile(delete=False, suffix=".gif") as tmp_in:
tmp_in.write(image_data)
input_path = tmp_in.name
with tempfile.NamedTemporaryFile(delete=False, suffix=".gif") as tmp_out:
output_path = tmp_out.name
with Image.open(input_path) as src_img:
# === 判断是否为动图 ===
try: try:
n_frames = getattr(src_img, "n_frames", 1) n_frames = getattr(src_img, "n_frames", 1)
is_animated = n_frames > 1 is_animated = n_frames > 1
except Exception: except Exception:
is_animated = False is_animated = False
n_frames = 1
output_frames = [] output_frames = []
output_durations_ms = [] output_durations_ms = []
@ -190,7 +179,7 @@ async def handle_ytpgif(event: BaseEvent, speed: float = 1.0):
for img, dur in frames_with_duration: for img, dur in frames_with_duration:
if accumulated + dur > max_dur or frame_count >= MAX_FRAMES_PER_SEGMENT: if accumulated + dur > max_dur or frame_count >= MAX_FRAMES_PER_SEGMENT:
break break
flipped = img.transpose(Image.FLIP_LEFT_RIGHT) flipped = img.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
output_frames.append(flipped) output_frames.append(flipped)
output_durations_ms.append(int(dur * 1000)) output_durations_ms.append(int(dur * 1000))
accumulated += dur accumulated += dur
@ -205,7 +194,7 @@ async def handle_ytpgif(event: BaseEvent, speed: float = 1.0):
duration_ms = int(interval_sec * 1000) duration_ms = int(interval_sec * 1000)
frame1 = resized_frame frame1 = resized_frame
frame2 = resized_frame.transpose(Image.FLIP_LEFT_RIGHT) frame2 = resized_frame.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
output_frames = [frame1, frame2] output_frames = [frame1, frame2]
output_durations_ms = [duration_ms, duration_ms] output_durations_ms = [duration_ms, duration_ms]
@ -247,22 +236,12 @@ async def handle_ytpgif(event: BaseEvent, speed: float = 1.0):
if need_transparency: if need_transparency:
save_kwargs["transparency"] = 0 save_kwargs["transparency"] = 0
output_frames[0].save(output_path, **save_kwargs) bio = BytesIO()
output_frames[0].save(bio, **save_kwargs)
# 发送结果 result_image = UniMessage.image(raw=bio)
with open(output_path, "rb") as f:
result_image = UniMessage.image(raw=f.read())
await ytpgif_cmd.send(await result_image.export()) await ytpgif_cmd.send(await result_image.export())
except Exception as e: except Exception as e:
print(f"[YTPGIF] 处理失败: {e}") print(f"[YTPGIF] 处理失败: {e}")
await ytpgif_cmd.send( await ytpgif_cmd.send(
await UniMessage.text("❌ 处理失败,可能是图片格式不支持、文件损坏或过大。").export() await UniMessage.text("❌ 处理失败,可能是图片格式不支持、文件损坏或过大。").export()
) )
finally:
for path in filter(None, [input_path, output_path]):
if os.path.exists(path):
try:
os.unlink(path)
except: # noqa
pass