|
import asyncio |
|
import copy |
|
from argparse import ArgumentParser |
|
from pathlib import Path |
|
from typing import Any |
|
|
|
import filetype |
|
|
|
from meme_generator.app import run_server |
|
from meme_generator.config import meme_config |
|
from meme_generator.download import check_resources |
|
from meme_generator.exception import MemeGeneratorException, NoSuchMeme |
|
from meme_generator.log import setup_logger |
|
from meme_generator.manager import get_meme, get_memes |
|
|
|
parser = ArgumentParser("meme") |
|
subparsers = parser.add_subparsers(dest="handle") |
|
|
|
list_parser = subparsers.add_parser("list", aliases=["ls"], help="查看表情列表") |
|
|
|
show_parser = subparsers.add_parser("info", aliases=["show"], help="查看表情详情") |
|
show_parser.add_argument("key", type=str, help="表情名") |
|
|
|
preview_parser = subparsers.add_parser("preview", help="生成表情预览") |
|
preview_parser.add_argument("key", type=str, help="表情名") |
|
|
|
generate_parser = subparsers.add_parser("generate", aliases=["make"], help="制作表情") |
|
memes_subparsers = generate_parser.add_subparsers(dest="key", help="表情名") |
|
|
|
run_parser = subparsers.add_parser("run", aliases=["start"], help="启动 web server") |
|
|
|
download_parser = subparsers.add_parser("download", help="下载内置表情图片") |
|
download_parser.add_argument( |
|
"--url", type=str, help="指定资源链接", default=meme_config.resource.resource_url |
|
) |
|
|
|
|
|
def add_parsers(): |
|
for meme in get_memes(): |
|
meme_parser = ( |
|
copy.deepcopy(meme.params_type.args_type.parser) |
|
if meme.params_type.args_type |
|
else ArgumentParser() |
|
) |
|
meme_parser.add_argument("--images", nargs="+", default=[], help="输入图片路径") |
|
meme_parser.add_argument("--texts", nargs="+", default=[], help="输入文字") |
|
memes_subparsers.add_parser( |
|
meme.key, |
|
parents=[meme_parser], |
|
add_help=False, |
|
prefix_chars=meme_parser.prefix_chars, |
|
) |
|
|
|
|
|
def list_memes() -> str: |
|
memes = sorted(get_memes(), key=lambda meme: meme.key) |
|
return "\n".join( |
|
f"{i}. {meme.key} ({'/'.join(meme.keywords)})" |
|
for i, meme in enumerate(memes, start=1) |
|
) |
|
|
|
|
|
def meme_info(key: str) -> str: |
|
try: |
|
meme = get_meme(key) |
|
except NoSuchMeme: |
|
return f'表情 "{key}" 不存在!' |
|
|
|
keywords = "、".join([f'"{keyword}"' for keyword in meme.keywords]) |
|
|
|
patterns = "、".join([f'"{pattern}"' for pattern in meme.patterns]) |
|
|
|
image_num = f"{meme.params_type.min_images}" |
|
if meme.params_type.max_images > meme.params_type.min_images: |
|
image_num += f" ~ {meme.params_type.max_images}" |
|
|
|
text_num = f"{meme.params_type.min_texts}" |
|
if meme.params_type.max_texts > meme.params_type.min_texts: |
|
text_num += f" ~ {meme.params_type.max_texts}" |
|
|
|
default_texts = ", ".join([f'"{text}"' for text in meme.params_type.default_texts]) |
|
|
|
def arg_info(name: str, info: dict[str, Any]) -> str: |
|
text = ( |
|
f' "{name}"\n' |
|
f" 描述:{info.get('description', '')}\n" |
|
f" 类型:`{info.get('type', '')}`\n" |
|
f" 默认值:`{info.get('default', '')}`" |
|
) |
|
if enum := info.get("enum", []): |
|
assert isinstance(enum, list) |
|
text += "\n 可选值:" + "、".join([f'"{e}"' for e in enum]) |
|
return text |
|
|
|
if args := meme.params_type.args_type: |
|
model = args.model |
|
properties: dict[str, dict[str, Any]] = model.schema().get("properties", {}) |
|
properties.pop("user_infos") |
|
args_info = "\n" + "\n".join( |
|
[arg_info(name, info) for name, info in properties.items()] |
|
) |
|
else: |
|
args_info = "" |
|
|
|
return ( |
|
f"表情名:{meme.key}\n" |
|
+ f"关键词:{keywords}\n" |
|
+ (f"正则表达式:{patterns}\n" if patterns else "") |
|
+ "参数:\n" |
|
+ f" 需要图片数目:{image_num}\n" |
|
+ f" 需要文字数目:{text_num}\n" |
|
+ (f" 默认文字:[{default_texts}]\n" if default_texts else "") |
|
+ (f" 其他参数:{args_info}\n" if args_info else "") |
|
) |
|
|
|
|
|
def generate_meme_preview(key: str) -> str: |
|
try: |
|
meme = get_meme(key) |
|
except NoSuchMeme: |
|
return f'表情 "{key}" 不存在!' |
|
|
|
try: |
|
loop = asyncio.new_event_loop() |
|
result = loop.run_until_complete(meme.generate_preview()) |
|
content = result.getvalue() |
|
ext = filetype.guess_extension(content) |
|
filename = f"result.{ext}" |
|
with open(filename, "wb") as f: |
|
f.write(content) |
|
return f'表情制作成功!生成的表情文件为 "{filename}"' |
|
except MemeGeneratorException as e: |
|
return str(e) |
|
|
|
|
|
def generate_meme( |
|
key: str, images: list[str], texts: list[str], args: dict[str, Any] |
|
) -> str: |
|
try: |
|
meme = get_meme(key) |
|
except NoSuchMeme: |
|
return f'表情 "{key}" 不存在!' |
|
|
|
for image in images: |
|
if not Path(image).exists(): |
|
return f'图片路径 "{image}" 不存在!' |
|
|
|
try: |
|
loop = asyncio.new_event_loop() |
|
result = loop.run_until_complete(meme(images=images, texts=texts, args=args)) |
|
content = result.getvalue() |
|
ext = filetype.guess_extension(content) |
|
filename = f"result.{ext}" |
|
with open(filename, "wb") as f: |
|
f.write(content) |
|
return f'表情制作成功!生成的表情文件为 "{filename}"' |
|
except MemeGeneratorException as e: |
|
return str(e) |
|
|
|
|
|
def main(): |
|
setup_logger() |
|
add_parsers() |
|
|
|
args = parser.parse_args() |
|
handle = str(args.handle) |
|
|
|
if handle in ["list", "ls"]: |
|
print(list_memes()) |
|
|
|
elif handle in ["info", "show"]: |
|
key = str(args.key) |
|
print(meme_info(key)) |
|
|
|
elif handle in ["preview"]: |
|
key = str(args.key) |
|
print(generate_meme_preview(key)) |
|
|
|
elif handle in ["generate", "make"]: |
|
kwargs = vars(args) |
|
kwargs.pop("handle") |
|
key: str = kwargs.pop("key") |
|
images: list[str] = kwargs.pop("images") |
|
texts: list[str] = kwargs.pop("texts") |
|
print(generate_meme(key, images, texts, kwargs)) |
|
|
|
elif handle in ["run", "start"]: |
|
run_server() |
|
|
|
elif handle in ["download"]: |
|
meme_config.resource.resource_url = args.url |
|
loop = asyncio.new_event_loop() |
|
loop.run_until_complete(check_resources()) |
|
|
|
else: |
|
print(parser.format_help()) |
|
|
|
|
|
if __name__ == "__main__": |
|
main() |
|
|