This commit is contained in:
@ -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
|
|
||||||
Reference in New Issue
Block a user