Spaces:
Sleeping
Sleeping
LeafLeafLeaf
commited on
Commit
·
1d20b52
1
Parent(s):
f371e42
fix: 同步https://github.com/MeetWq/meme-generator/releases/tag/v0.0.20
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitignore +1 -0
- .pre-commit-config.yaml +2 -13
- Dockerfile +17 -17
- Dockerfile.memebot +58 -0
- docker/start_memebot.sh +7 -0
- meme_generator/app.py +15 -15
- meme_generator/cli.py +6 -6
- meme_generator/config.py +4 -4
- meme_generator/dirs.py +2 -3
- meme_generator/download.py +4 -5
- meme_generator/manager.py +7 -7
- meme_generator/meme.py +17 -20
- meme_generator/memes/5000choyen/__init__.py +7 -9
- meme_generator/memes/ace_attorney_dialog/__init__.py +119 -0
- meme_generator/memes/ace_attorney_dialog/images/bubble.png +3 -0
- meme_generator/memes/ace_attorney_dialog/images/mark.png +3 -0
- meme_generator/memes/acg_entrance/__init__.py +1 -2
- meme_generator/memes/add_chaos/__init__.py +1 -2
- meme_generator/memes/addiction/__init__.py +1 -2
- meme_generator/memes/alike/__init__.py +1 -3
- meme_generator/memes/always/__init__.py +5 -3
- meme_generator/memes/always_like/__init__.py +1 -2
- meme_generator/memes/anti_kidnap/__init__.py +1 -2
- meme_generator/memes/anya_suki/__init__.py +1 -2
- meme_generator/memes/applaud/__init__.py +2 -3
- meme_generator/memes/ascension/__init__.py +1 -2
- meme_generator/memes/ask/__init__.py +1 -3
- meme_generator/memes/back_to_work/__init__.py +6 -3
- meme_generator/memes/bad_news/__init__.py +1 -2
- meme_generator/memes/beat_head/__init__.py +2 -3
- meme_generator/memes/beat_up/__init__.py +27 -0
- meme_generator/memes/beat_up/images/0.png +3 -0
- meme_generator/memes/beat_up/images/1.png +3 -0
- meme_generator/memes/beat_up/images/2.png +3 -0
- meme_generator/memes/bite/__init__.py +2 -3
- meme_generator/memes/blood_pressure/__init__.py +4 -3
- meme_generator/memes/bluearchive/__init__.py +1 -2
- meme_generator/memes/bocchi_draft/__init__.py +5 -4
- meme_generator/memes/bronya_holdsign/__init__.py +1 -2
- meme_generator/memes/bubble_tea/__init__.py +8 -4
- meme_generator/memes/call_110/__init__.py +1 -3
- meme_generator/memes/caoshen_bite/__init__.py +2 -3
- meme_generator/memes/capoo_draw/__init__.py +2 -3
- meme_generator/memes/capoo_rip/__init__.py +2 -3
- meme_generator/memes/capoo_rub/__init__.py +5 -4
- meme_generator/memes/capoo_say/__init__.py +3 -4
- meme_generator/memes/capoo_strike/__init__.py +1 -2
- meme_generator/memes/captain/__init__.py +1 -2
- meme_generator/memes/certificate/__init__.py +4 -3
- meme_generator/memes/charpic/__init__.py +1 -3
.gitignore
CHANGED
@@ -4,6 +4,7 @@ dist/
|
|
4 |
.idea/
|
5 |
venv/
|
6 |
.venv/
|
|
|
7 |
pdm.lock
|
8 |
|
9 |
result.png
|
|
|
4 |
.idea/
|
5 |
venv/
|
6 |
.venv/
|
7 |
+
node_modules/
|
8 |
pdm.lock
|
9 |
|
10 |
result.png
|
.pre-commit-config.yaml
CHANGED
@@ -6,23 +6,12 @@ ci:
|
|
6 |
autoupdate_commit_msg: "chore: auto update by pre-commit hooks"
|
7 |
repos:
|
8 |
- repo: https://github.com/astral-sh/ruff-pre-commit
|
9 |
-
rev: v0.
|
10 |
hooks:
|
11 |
- id: ruff
|
12 |
args: [--fix, --exit-non-zero-on-fix]
|
13 |
stages: [commit]
|
14 |
-
|
15 |
-
- repo: https://github.com/pycqa/isort
|
16 |
-
rev: 5.13.2
|
17 |
-
hooks:
|
18 |
-
- id: isort
|
19 |
-
stages: [commit]
|
20 |
-
|
21 |
-
- repo: https://github.com/psf/black
|
22 |
-
rev: 23.12.1
|
23 |
-
hooks:
|
24 |
-
- id: black
|
25 |
-
stages: [commit]
|
26 |
|
27 |
- repo: https://github.com/pre-commit/mirrors-prettier
|
28 |
rev: v4.0.0-alpha.8
|
|
|
6 |
autoupdate_commit_msg: "chore: auto update by pre-commit hooks"
|
7 |
repos:
|
8 |
- repo: https://github.com/astral-sh/ruff-pre-commit
|
9 |
+
rev: v0.3.5
|
10 |
hooks:
|
11 |
- id: ruff
|
12 |
args: [--fix, --exit-non-zero-on-fix]
|
13 |
stages: [commit]
|
14 |
+
- id: ruff-format
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
|
16 |
- repo: https://github.com/pre-commit/mirrors-prettier
|
17 |
rev: v4.0.0-alpha.8
|
Dockerfile
CHANGED
@@ -2,12 +2,13 @@ FROM python:3.10 as tmp
|
|
2 |
|
3 |
WORKDIR /tmp
|
4 |
|
|
|
|
|
5 |
ENV PATH="${PATH}:/root/.local/bin"
|
6 |
|
7 |
COPY ./pyproject.toml ./poetry.lock* /tmp/
|
8 |
-
|
9 |
-
|
10 |
-
&& poetry install --only main --no-interaction --no-ansi
|
11 |
|
12 |
FROM python:3.10-slim as app
|
13 |
|
@@ -17,20 +18,8 @@ EXPOSE 2233
|
|
17 |
|
18 |
VOLUME /data
|
19 |
|
20 |
-
COPY --from=tmp /tmp/.venv /app/.venv
|
21 |
-
|
22 |
-
COPY ./resources/fonts/* /usr/share/fonts/meme-fonts/
|
23 |
-
RUN apt-get update \
|
24 |
-
&& apt-get install -y --no-install-recommends locales fontconfig fonts-noto-cjk fonts-noto-color-emoji gettext \
|
25 |
-
&& localedef -i zh_CN -c -f UTF-8 -A /usr/share/locale/locale.alias zh_CN.UTF-8 \
|
26 |
-
&& fc-cache -fv \
|
27 |
-
&& apt-get purge -y --auto-remove \
|
28 |
-
&& rm -rf /var/lib/apt/lists/*
|
29 |
-
|
30 |
ENV TZ=Asia/Shanghai \
|
31 |
LC_ALL=zh_CN.UTF-8 \
|
32 |
-
PATH="/app/.venv/bin:${PATH}" \
|
33 |
-
VIRTUAL_ENV="/app/.venv" \
|
34 |
LOAD_BUILTIN_MEMES=true \
|
35 |
MEME_DIRS="[\"/data/memes\"]" \
|
36 |
MEME_DISABLED_LIST="[]" \
|
@@ -40,12 +29,23 @@ ENV TZ=Asia/Shanghai \
|
|
40 |
BAIDU_TRANS_APIKEY="" \
|
41 |
LOG_LEVEL="INFO"
|
42 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
COPY ./meme_generator /app/meme_generator
|
44 |
|
45 |
COPY ./docker/config.toml.template /app/config.toml.template
|
46 |
COPY ./docker/start.sh /app/start.sh
|
47 |
-
RUN mkdir -p /.config
|
48 |
-
RUN chmod -R 777 /.config
|
49 |
RUN chmod +x /app/start.sh
|
|
|
50 |
|
51 |
CMD ["/app/start.sh"]
|
|
|
2 |
|
3 |
WORKDIR /tmp
|
4 |
|
5 |
+
RUN curl -sSL https://install.python-poetry.org | python -
|
6 |
+
|
7 |
ENV PATH="${PATH}:/root/.local/bin"
|
8 |
|
9 |
COPY ./pyproject.toml ./poetry.lock* /tmp/
|
10 |
+
|
11 |
+
RUN poetry export -f requirements.txt --output requirements.txt --without-hashes
|
|
|
12 |
|
13 |
FROM python:3.10-slim as app
|
14 |
|
|
|
18 |
|
19 |
VOLUME /data
|
20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
ENV TZ=Asia/Shanghai \
|
22 |
LC_ALL=zh_CN.UTF-8 \
|
|
|
|
|
23 |
LOAD_BUILTIN_MEMES=true \
|
24 |
MEME_DIRS="[\"/data/memes\"]" \
|
25 |
MEME_DISABLED_LIST="[]" \
|
|
|
29 |
BAIDU_TRANS_APIKEY="" \
|
30 |
LOG_LEVEL="INFO"
|
31 |
|
32 |
+
COPY --from=tmp /tmp/requirements.txt /app/requirements.txt
|
33 |
+
|
34 |
+
COPY ./resources/fonts/* /usr/share/fonts/meme-fonts/
|
35 |
+
|
36 |
+
RUN apt-get update \
|
37 |
+
&& apt-get install -y --no-install-recommends locales fontconfig fonts-noto-color-emoji gettext \
|
38 |
+
&& localedef -i zh_CN -c -f UTF-8 -A /usr/share/locale/locale.alias zh_CN.UTF-8 \
|
39 |
+
&& fc-cache -fv \
|
40 |
+
&& apt-get purge -y --auto-remove \
|
41 |
+
&& rm -rf /var/lib/apt/lists/* \
|
42 |
+
&& pip install --no-cache-dir --upgrade -r /app/requirements.txt
|
43 |
+
|
44 |
COPY ./meme_generator /app/meme_generator
|
45 |
|
46 |
COPY ./docker/config.toml.template /app/config.toml.template
|
47 |
COPY ./docker/start.sh /app/start.sh
|
|
|
|
|
48 |
RUN chmod +x /app/start.sh
|
49 |
+
RUN python -m meme_generator.cli
|
50 |
|
51 |
CMD ["/app/start.sh"]
|
Dockerfile.memebot
ADDED
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.10 as tmp
|
2 |
+
|
3 |
+
WORKDIR /tmp
|
4 |
+
|
5 |
+
RUN curl -sSL https://install.python-poetry.org | python -
|
6 |
+
|
7 |
+
ENV PATH="${PATH}:/root/.local/bin"
|
8 |
+
|
9 |
+
COPY ./pyproject.toml ./poetry.lock* /tmp/
|
10 |
+
|
11 |
+
RUN apt-get update \
|
12 |
+
&& apt-get install -y --no-install-recommends git \
|
13 |
+
&& poetry export -f requirements.txt --output requirements.txt --without-hashes \
|
14 |
+
&& git clone --depth=1 https://github.com/MeetWq/github-meme-bot
|
15 |
+
|
16 |
+
FROM python:3.10-slim as app
|
17 |
+
|
18 |
+
WORKDIR /app
|
19 |
+
|
20 |
+
EXPOSE 2233
|
21 |
+
|
22 |
+
VOLUME /data
|
23 |
+
|
24 |
+
ENV TZ=Asia/Shanghai \
|
25 |
+
LC_ALL=zh_CN.UTF-8 \
|
26 |
+
LOAD_BUILTIN_MEMES=true \
|
27 |
+
MEME_DIRS="[\"/data/memes\"]" \
|
28 |
+
MEME_DISABLED_LIST="[]" \
|
29 |
+
GIF_MAX_SIZE=10.0 \
|
30 |
+
GIF_MAX_FRAMES=100 \
|
31 |
+
BAIDU_TRANS_APPID="" \
|
32 |
+
BAIDU_TRANS_APIKEY="" \
|
33 |
+
LOG_LEVEL="INFO"
|
34 |
+
|
35 |
+
COPY --from=tmp /tmp/requirements.txt /app/requirements.txt
|
36 |
+
COPY --from=tmp /tmp/github-meme-bot/src /app/src
|
37 |
+
COPY --from=tmp /tmp/github-meme-bot/bot.py /app/bot.py
|
38 |
+
COPY --from=tmp /tmp/github-meme-bot/.env /app/.env
|
39 |
+
|
40 |
+
COPY ./resources/fonts/* /usr/share/fonts/meme-fonts/
|
41 |
+
|
42 |
+
RUN apt-get update \
|
43 |
+
&& apt-get install -y --no-install-recommends locales fontconfig fonts-noto-color-emoji gettext \
|
44 |
+
&& localedef -i zh_CN -c -f UTF-8 -A /usr/share/locale/locale.alias zh_CN.UTF-8 \
|
45 |
+
&& fc-cache -fv \
|
46 |
+
&& apt-get purge -y --auto-remove \
|
47 |
+
&& rm -rf /var/lib/apt/lists/* \
|
48 |
+
&& pip install --no-cache-dir --upgrade -r /app/requirements.txt \
|
49 |
+
&& pip install --no-cache-dir --upgrade nonebot2 nonebot-adapter-github
|
50 |
+
|
51 |
+
COPY ./meme_generator /app/meme_generator
|
52 |
+
|
53 |
+
COPY ./docker/config.toml.template /app/config.toml.template
|
54 |
+
COPY ./docker/start_memebot.sh /app/start_memebot.sh
|
55 |
+
RUN chmod +x /app/start_memebot.sh
|
56 |
+
RUN python -m meme_generator.cli
|
57 |
+
|
58 |
+
CMD ["/app/start_memebot.sh"]
|
docker/start_memebot.sh
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#! /usr/bin/env bash
|
2 |
+
|
3 |
+
mkdir -p ~/.config/meme_generator
|
4 |
+
|
5 |
+
envsubst < /app/config.toml.template > ~/.config/meme_generator/config.toml
|
6 |
+
|
7 |
+
exec python /app/bot.py
|
meme_generator/app.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
from typing import Any,
|
2 |
|
3 |
import filetype
|
4 |
from fastapi import Depends, FastAPI, Form, HTTPException, Response, UploadFile
|
@@ -20,7 +20,7 @@ class MemeArgsResponse(BaseModel):
|
|
20 |
type: str
|
21 |
description: Optional[str] = None
|
22 |
default: Optional[Any] = None
|
23 |
-
enum: Optional[
|
24 |
|
25 |
|
26 |
class MemeParamsResponse(BaseModel):
|
@@ -28,14 +28,14 @@ class MemeParamsResponse(BaseModel):
|
|
28 |
max_images: int
|
29 |
min_texts: int
|
30 |
max_texts: int
|
31 |
-
default_texts:
|
32 |
-
args:
|
33 |
|
34 |
|
35 |
class MemeInfoResponse(BaseModel):
|
36 |
key: str
|
37 |
-
keywords:
|
38 |
-
patterns:
|
39 |
params: MemeParamsResponse
|
40 |
|
41 |
|
@@ -56,11 +56,11 @@ def register_router(meme: Meme):
|
|
56 |
|
57 |
@app.post(f"/memes/{meme.key}/")
|
58 |
async def _(
|
59 |
-
images:
|
60 |
-
texts:
|
61 |
args: args_model = Depends(args_checker), # type: ignore
|
62 |
):
|
63 |
-
imgs:
|
64 |
for image in images:
|
65 |
imgs.append(await image.read())
|
66 |
|
@@ -94,16 +94,16 @@ default_meme_list = [
|
|
94 |
|
95 |
|
96 |
class RenderMemeListRequest(BaseModel):
|
97 |
-
meme_list:
|
98 |
order_direction: Literal["row", "column"] = "column"
|
99 |
columns: int = 4
|
100 |
column_align: Literal["left", "center", "right"] = "left"
|
101 |
-
item_padding:
|
102 |
-
image_padding:
|
103 |
bg_color: ColorType = "white"
|
104 |
fontsize: int = 30
|
105 |
fontname: str = ""
|
106 |
-
fallback_fonts:
|
107 |
|
108 |
|
109 |
def register_routers():
|
@@ -158,7 +158,7 @@ def register_routers():
|
|
158 |
if meme.params_type.args_type
|
159 |
else MemeArgsModel
|
160 |
)
|
161 |
-
properties:
|
162 |
args_model.schema().get("properties", {}).copy()
|
163 |
)
|
164 |
properties.pop("user_infos")
|
@@ -198,7 +198,7 @@ def register_routers():
|
|
198 |
return Response(content=content, media_type=media_type)
|
199 |
|
200 |
@app.post("/memes/{key}/parse_args")
|
201 |
-
async def _(key: str, args:
|
202 |
try:
|
203 |
meme = get_meme(key)
|
204 |
return meme.parse_args(args)
|
|
|
1 |
+
from typing import Any, Literal, Optional
|
2 |
|
3 |
import filetype
|
4 |
from fastapi import Depends, FastAPI, Form, HTTPException, Response, UploadFile
|
|
|
20 |
type: str
|
21 |
description: Optional[str] = None
|
22 |
default: Optional[Any] = None
|
23 |
+
enum: Optional[list[Any]] = None
|
24 |
|
25 |
|
26 |
class MemeParamsResponse(BaseModel):
|
|
|
28 |
max_images: int
|
29 |
min_texts: int
|
30 |
max_texts: int
|
31 |
+
default_texts: list[str]
|
32 |
+
args: list[MemeArgsResponse]
|
33 |
|
34 |
|
35 |
class MemeInfoResponse(BaseModel):
|
36 |
key: str
|
37 |
+
keywords: list[str]
|
38 |
+
patterns: list[str]
|
39 |
params: MemeParamsResponse
|
40 |
|
41 |
|
|
|
56 |
|
57 |
@app.post(f"/memes/{meme.key}/")
|
58 |
async def _(
|
59 |
+
images: list[UploadFile] = [],
|
60 |
+
texts: list[str] = meme.params_type.default_texts,
|
61 |
args: args_model = Depends(args_checker), # type: ignore
|
62 |
):
|
63 |
+
imgs: list[bytes] = []
|
64 |
for image in images:
|
65 |
imgs.append(await image.read())
|
66 |
|
|
|
94 |
|
95 |
|
96 |
class RenderMemeListRequest(BaseModel):
|
97 |
+
meme_list: list[MemeKeyWithProperties] = default_meme_list
|
98 |
order_direction: Literal["row", "column"] = "column"
|
99 |
columns: int = 4
|
100 |
column_align: Literal["left", "center", "right"] = "left"
|
101 |
+
item_padding: tuple[int, int] = (15, 2)
|
102 |
+
image_padding: tuple[int, int] = (50, 50)
|
103 |
bg_color: ColorType = "white"
|
104 |
fontsize: int = 30
|
105 |
fontname: str = ""
|
106 |
+
fallback_fonts: list[str] = []
|
107 |
|
108 |
|
109 |
def register_routers():
|
|
|
158 |
if meme.params_type.args_type
|
159 |
else MemeArgsModel
|
160 |
)
|
161 |
+
properties: dict[str, dict[str, Any]] = (
|
162 |
args_model.schema().get("properties", {}).copy()
|
163 |
)
|
164 |
properties.pop("user_infos")
|
|
|
198 |
return Response(content=content, media_type=media_type)
|
199 |
|
200 |
@app.post("/memes/{key}/parse_args")
|
201 |
+
async def _(key: str, args: list[str] = []):
|
202 |
try:
|
203 |
meme = get_meme(key)
|
204 |
return meme.parse_args(args)
|
meme_generator/cli.py
CHANGED
@@ -2,7 +2,7 @@ import asyncio
|
|
2 |
import copy
|
3 |
from argparse import ArgumentParser
|
4 |
from pathlib import Path
|
5 |
-
from typing import Any
|
6 |
|
7 |
import filetype
|
8 |
|
@@ -80,7 +80,7 @@ def meme_info(key: str) -> str:
|
|
80 |
|
81 |
default_texts = ", ".join([f'"{text}"' for text in meme.params_type.default_texts])
|
82 |
|
83 |
-
def arg_info(name: str, info:
|
84 |
text = (
|
85 |
f' "{name}"\n'
|
86 |
f" 描述:{info.get('description', '')}\n"
|
@@ -94,7 +94,7 @@ def meme_info(key: str) -> str:
|
|
94 |
|
95 |
if args := meme.params_type.args_type:
|
96 |
model = args.model
|
97 |
-
properties:
|
98 |
properties.pop("user_infos")
|
99 |
args_info = "\n" + "\n".join(
|
100 |
[arg_info(name, info) for name, info in properties.items()]
|
@@ -134,7 +134,7 @@ def generate_meme_preview(key: str) -> str:
|
|
134 |
|
135 |
|
136 |
def generate_meme(
|
137 |
-
key: str, images:
|
138 |
) -> str:
|
139 |
try:
|
140 |
meme = get_meme(key)
|
@@ -180,8 +180,8 @@ def main():
|
|
180 |
kwargs = vars(args)
|
181 |
kwargs.pop("handle")
|
182 |
key: str = kwargs.pop("key")
|
183 |
-
images:
|
184 |
-
texts:
|
185 |
print(generate_meme(key, images, texts, kwargs)) # noqa: T201
|
186 |
|
187 |
elif handle in ["run", "start"]:
|
|
|
2 |
import copy
|
3 |
from argparse import ArgumentParser
|
4 |
from pathlib import Path
|
5 |
+
from typing import Any
|
6 |
|
7 |
import filetype
|
8 |
|
|
|
80 |
|
81 |
default_texts = ", ".join([f'"{text}"' for text in meme.params_type.default_texts])
|
82 |
|
83 |
+
def arg_info(name: str, info: dict[str, Any]) -> str:
|
84 |
text = (
|
85 |
f' "{name}"\n'
|
86 |
f" 描述:{info.get('description', '')}\n"
|
|
|
94 |
|
95 |
if args := meme.params_type.args_type:
|
96 |
model = args.model
|
97 |
+
properties: dict[str, dict[str, Any]] = model.schema().get("properties", {})
|
98 |
properties.pop("user_infos")
|
99 |
args_info = "\n" + "\n".join(
|
100 |
[arg_info(name, info) for name, info in properties.items()]
|
|
|
134 |
|
135 |
|
136 |
def generate_meme(
|
137 |
+
key: str, images: list[str], texts: list[str], args: dict[str, Any]
|
138 |
) -> str:
|
139 |
try:
|
140 |
meme = get_meme(key)
|
|
|
180 |
kwargs = vars(args)
|
181 |
kwargs.pop("handle")
|
182 |
key: str = kwargs.pop("key")
|
183 |
+
images: list[str] = kwargs.pop("images")
|
184 |
+
texts: list[str] = kwargs.pop("texts")
|
185 |
print(generate_meme(key, images, texts, kwargs)) # noqa: T201
|
186 |
|
187 |
elif handle in ["run", "start"]:
|
meme_generator/config.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
import json
|
2 |
from pathlib import Path
|
3 |
-
from typing import
|
4 |
|
5 |
import toml
|
6 |
from pydantic import BaseModel, Extra
|
@@ -12,13 +12,13 @@ config_file_path = get_config_file("config.toml")
|
|
12 |
|
13 |
class MemeConfig(BaseModel):
|
14 |
load_builtin_memes: bool = True
|
15 |
-
meme_dirs:
|
16 |
-
meme_disabled_list:
|
17 |
|
18 |
|
19 |
class ResourceConfig(BaseModel):
|
20 |
resource_url: Optional[str] = None
|
21 |
-
resource_urls:
|
22 |
"https://raw.githubusercontent.com/MeetWq/meme-generator/",
|
23 |
"https://ghproxy.com/https://raw.githubusercontent.com/MeetWq/meme-generator/",
|
24 |
"https://fastly.jsdelivr.net/gh/MeetWq/meme-generator@",
|
|
|
1 |
import json
|
2 |
from pathlib import Path
|
3 |
+
from typing import Optional, Union
|
4 |
|
5 |
import toml
|
6 |
from pydantic import BaseModel, Extra
|
|
|
12 |
|
13 |
class MemeConfig(BaseModel):
|
14 |
load_builtin_memes: bool = True
|
15 |
+
meme_dirs: list[Path] = []
|
16 |
+
meme_disabled_list: list[str] = []
|
17 |
|
18 |
|
19 |
class ResourceConfig(BaseModel):
|
20 |
resource_url: Optional[str] = None
|
21 |
+
resource_urls: list[str] = [
|
22 |
"https://raw.githubusercontent.com/MeetWq/meme-generator/",
|
23 |
"https://ghproxy.com/https://raw.githubusercontent.com/MeetWq/meme-generator/",
|
24 |
"https://fastly.jsdelivr.net/gh/MeetWq/meme-generator@",
|
meme_generator/dirs.py
CHANGED
@@ -23,7 +23,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23 |
SOFTWARE.
|
24 |
"""
|
25 |
|
26 |
-
|
27 |
import os
|
28 |
import sys
|
29 |
from pathlib import Path
|
@@ -119,7 +118,7 @@ def user_config_dir(appname: str, roaming: bool = True) -> Path:
|
|
119 |
|
120 |
# -- Windows support functions --
|
121 |
def _get_win_folder_from_registry(
|
122 |
-
csidl_name: Literal["CSIDL_APPDATA", "CSIDL_COMMON_APPDATA", "CSIDL_LOCAL_APPDATA"]
|
123 |
) -> Path:
|
124 |
"""
|
125 |
This is a fallback technique at best. I'm not sure if using the
|
@@ -143,7 +142,7 @@ def _get_win_folder_from_registry(
|
|
143 |
|
144 |
|
145 |
def _get_win_folder_with_ctypes(
|
146 |
-
csidl_name: Literal["CSIDL_APPDATA", "CSIDL_COMMON_APPDATA", "CSIDL_LOCAL_APPDATA"]
|
147 |
) -> Path:
|
148 |
csidl_const = {
|
149 |
"CSIDL_APPDATA": 26,
|
|
|
23 |
SOFTWARE.
|
24 |
"""
|
25 |
|
|
|
26 |
import os
|
27 |
import sys
|
28 |
from pathlib import Path
|
|
|
118 |
|
119 |
# -- Windows support functions --
|
120 |
def _get_win_folder_from_registry(
|
121 |
+
csidl_name: Literal["CSIDL_APPDATA", "CSIDL_COMMON_APPDATA", "CSIDL_LOCAL_APPDATA"],
|
122 |
) -> Path:
|
123 |
"""
|
124 |
This is a fallback technique at best. I'm not sure if using the
|
|
|
142 |
|
143 |
|
144 |
def _get_win_folder_with_ctypes(
|
145 |
+
csidl_name: Literal["CSIDL_APPDATA", "CSIDL_COMMON_APPDATA", "CSIDL_LOCAL_APPDATA"],
|
146 |
) -> Path:
|
147 |
csidl_const = {
|
148 |
"CSIDL_APPDATA": 26,
|
meme_generator/download.py
CHANGED
@@ -3,7 +3,6 @@ import hashlib
|
|
3 |
import json
|
4 |
import time
|
5 |
from pathlib import Path
|
6 |
-
from typing import List, Tuple
|
7 |
|
8 |
import httpx
|
9 |
from rich.progress import Progress
|
@@ -18,13 +17,13 @@ def _resource_url(base_url: str, name: str) -> str:
|
|
18 |
|
19 |
|
20 |
# https://github.com/mnixry/nonebot-plugin-gocqhttp/blob/main/nonebot_plugin_gocqhttp/process/download.py
|
21 |
-
async def get_fastest_mirror() ->
|
22 |
assert meme_config.resource.resource_urls, "No resource url specified."
|
23 |
|
24 |
async def head_mirror(client: httpx.AsyncClient, base_url: str):
|
25 |
begin_time = time.time()
|
26 |
response = await client.head(
|
27 |
-
_resource_url(base_url, "resources/fonts/NotoSansSC-Regular.
|
28 |
)
|
29 |
response.raise_for_status()
|
30 |
elapsed_time = (time.time() - begin_time) * 1000
|
@@ -39,7 +38,7 @@ async def get_fastest_mirror() -> List[str]:
|
|
39 |
return_exceptions=True,
|
40 |
)
|
41 |
results = sorted(
|
42 |
-
(result for result in results if not isinstance(result,
|
43 |
key=lambda r: r["elapsed_time"],
|
44 |
)
|
45 |
return [result["base_url"] for result in results]
|
@@ -76,7 +75,7 @@ async def check_resources():
|
|
76 |
else:
|
77 |
return
|
78 |
|
79 |
-
download_list:
|
80 |
for resource in resource_list:
|
81 |
file_name = str(resource["path"])
|
82 |
file_hash = str(resource["hash"])
|
|
|
3 |
import json
|
4 |
import time
|
5 |
from pathlib import Path
|
|
|
6 |
|
7 |
import httpx
|
8 |
from rich.progress import Progress
|
|
|
17 |
|
18 |
|
19 |
# https://github.com/mnixry/nonebot-plugin-gocqhttp/blob/main/nonebot_plugin_gocqhttp/process/download.py
|
20 |
+
async def get_fastest_mirror() -> list[str]:
|
21 |
assert meme_config.resource.resource_urls, "No resource url specified."
|
22 |
|
23 |
async def head_mirror(client: httpx.AsyncClient, base_url: str):
|
24 |
begin_time = time.time()
|
25 |
response = await client.head(
|
26 |
+
_resource_url(base_url, "resources/fonts/NotoSansSC-Regular.ttf"), timeout=5
|
27 |
)
|
28 |
response.raise_for_status()
|
29 |
elapsed_time = (time.time() - begin_time) * 1000
|
|
|
38 |
return_exceptions=True,
|
39 |
)
|
40 |
results = sorted(
|
41 |
+
(result for result in results if not isinstance(result, BaseException)),
|
42 |
key=lambda r: r["elapsed_time"],
|
43 |
)
|
44 |
return [result["base_url"] for result in results]
|
|
|
75 |
else:
|
76 |
return
|
77 |
|
78 |
+
download_list: list[tuple[Path, str]] = []
|
79 |
for resource in resource_list:
|
80 |
file_name = str(resource["path"])
|
81 |
file_hash = str(resource["hash"])
|
meme_generator/manager.py
CHANGED
@@ -2,14 +2,14 @@ import importlib
|
|
2 |
import importlib.util
|
3 |
import pkgutil
|
4 |
from pathlib import Path
|
5 |
-
from typing import
|
6 |
|
7 |
from .config import meme_config
|
8 |
from .exception import NoSuchMeme
|
9 |
from .log import logger
|
10 |
from .meme import Meme, MemeArgsType, MemeFunction, MemeParamsType
|
11 |
|
12 |
-
_memes:
|
13 |
|
14 |
|
15 |
def path_to_module_name(path: Path) -> str:
|
@@ -64,10 +64,10 @@ def add_meme(
|
|
64 |
max_images: int = 0,
|
65 |
min_texts: int = 0,
|
66 |
max_texts: int = 0,
|
67 |
-
default_texts:
|
68 |
args_type: Optional[MemeArgsType] = None,
|
69 |
-
keywords:
|
70 |
-
patterns:
|
71 |
):
|
72 |
if key in _memes:
|
73 |
logger.warning(f'Meme with key "{key}" already exists!')
|
@@ -96,9 +96,9 @@ def get_meme(key: str) -> Meme:
|
|
96 |
return _memes[key]
|
97 |
|
98 |
|
99 |
-
def get_memes() ->
|
100 |
return list(_memes.values())
|
101 |
|
102 |
|
103 |
-
def get_meme_keys() ->
|
104 |
return list(_memes.keys())
|
|
|
2 |
import importlib.util
|
3 |
import pkgutil
|
4 |
from pathlib import Path
|
5 |
+
from typing import Optional, Union
|
6 |
|
7 |
from .config import meme_config
|
8 |
from .exception import NoSuchMeme
|
9 |
from .log import logger
|
10 |
from .meme import Meme, MemeArgsType, MemeFunction, MemeParamsType
|
11 |
|
12 |
+
_memes: dict[str, Meme] = {}
|
13 |
|
14 |
|
15 |
def path_to_module_name(path: Path) -> str:
|
|
|
64 |
max_images: int = 0,
|
65 |
min_texts: int = 0,
|
66 |
max_texts: int = 0,
|
67 |
+
default_texts: list[str] = [],
|
68 |
args_type: Optional[MemeArgsType] = None,
|
69 |
+
keywords: list[str] = [],
|
70 |
+
patterns: list[str] = [],
|
71 |
):
|
72 |
if key in _memes:
|
73 |
logger.warning(f'Meme with key "{key}" already exists!')
|
|
|
96 |
return _memes[key]
|
97 |
|
98 |
|
99 |
+
def get_memes() -> list[Meme]:
|
100 |
return list(_memes.values())
|
101 |
|
102 |
|
103 |
+
def get_meme_keys() -> list[str]:
|
104 |
return list(_memes.keys())
|
meme_generator/meme.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1 |
import copy
|
2 |
from argparse import ArgumentError, ArgumentParser
|
|
|
3 |
from contextvars import ContextVar
|
4 |
from dataclasses import dataclass, field
|
5 |
from io import BytesIO
|
@@ -7,13 +8,9 @@ from pathlib import Path
|
|
7 |
from typing import (
|
8 |
IO,
|
9 |
Any,
|
10 |
-
Awaitable,
|
11 |
Callable,
|
12 |
-
Dict,
|
13 |
-
List,
|
14 |
Literal,
|
15 |
Optional,
|
16 |
-
Type,
|
17 |
TypeVar,
|
18 |
Union,
|
19 |
cast,
|
@@ -40,14 +37,14 @@ class UserInfo(BaseModel):
|
|
40 |
|
41 |
|
42 |
class MemeArgsModel(BaseModel):
|
43 |
-
user_infos:
|
44 |
|
45 |
|
46 |
ArgsModel = TypeVar("ArgsModel", bound=MemeArgsModel)
|
47 |
|
48 |
MemeFunction = Union[
|
49 |
-
Callable[[
|
50 |
-
Callable[[
|
51 |
]
|
52 |
|
53 |
|
@@ -77,8 +74,8 @@ class MemeArgsParser(ArgumentParser):
|
|
77 |
@dataclass
|
78 |
class MemeArgsType:
|
79 |
parser: MemeArgsParser
|
80 |
-
model:
|
81 |
-
instances:
|
82 |
|
83 |
|
84 |
@dataclass
|
@@ -87,7 +84,7 @@ class MemeParamsType:
|
|
87 |
max_images: int = 0
|
88 |
min_texts: int = 0
|
89 |
max_texts: int = 0
|
90 |
-
default_texts:
|
91 |
args_type: Optional[MemeArgsType] = None
|
92 |
|
93 |
|
@@ -96,15 +93,15 @@ class Meme:
|
|
96 |
key: str
|
97 |
function: MemeFunction
|
98 |
params_type: MemeParamsType
|
99 |
-
keywords:
|
100 |
-
patterns:
|
101 |
|
102 |
async def __call__(
|
103 |
self,
|
104 |
*,
|
105 |
-
images: Union[
|
106 |
-
texts:
|
107 |
-
args:
|
108 |
) -> BytesIO:
|
109 |
if not (
|
110 |
self.params_type.min_images <= len(images) <= self.params_type.max_images
|
@@ -128,12 +125,12 @@ class Meme:
|
|
128 |
except ValidationError as e:
|
129 |
raise ArgModelMismatch(self.key, str(e))
|
130 |
|
131 |
-
imgs:
|
132 |
try:
|
133 |
for image in images:
|
134 |
if isinstance(image, bytes):
|
135 |
image = BytesIO(image)
|
136 |
-
imgs.append(BuildImage.open(image))
|
137 |
except Exception as e:
|
138 |
raise OpenImageFailed(str(e))
|
139 |
|
@@ -146,7 +143,7 @@ class Meme:
|
|
146 |
else:
|
147 |
return await run_sync(cast(Callable[..., BytesIO], self.function))(**values)
|
148 |
|
149 |
-
def parse_args(self, args:
|
150 |
parser = (
|
151 |
copy.deepcopy(self.params_type.args_type.parser)
|
152 |
if self.params_type.args_type
|
@@ -163,7 +160,7 @@ class Meme:
|
|
163 |
finally:
|
164 |
parser_message.reset(t)
|
165 |
|
166 |
-
async def generate_preview(self, *, args:
|
167 |
default_images = [random_image() for _ in range(self.params_type.min_images)]
|
168 |
default_texts = (
|
169 |
self.params_type.default_texts.copy()
|
@@ -175,7 +172,7 @@ class Meme:
|
|
175 |
else [random_text() for _ in range(self.params_type.min_texts)]
|
176 |
)
|
177 |
|
178 |
-
async def _generate_preview(images:
|
179 |
try:
|
180 |
return await self.__call__(images=images, texts=texts, args=args)
|
181 |
except TextOrNameNotEnough:
|
|
|
1 |
import copy
|
2 |
from argparse import ArgumentError, ArgumentParser
|
3 |
+
from collections.abc import Awaitable
|
4 |
from contextvars import ContextVar
|
5 |
from dataclasses import dataclass, field
|
6 |
from io import BytesIO
|
|
|
8 |
from typing import (
|
9 |
IO,
|
10 |
Any,
|
|
|
11 |
Callable,
|
|
|
|
|
12 |
Literal,
|
13 |
Optional,
|
|
|
14 |
TypeVar,
|
15 |
Union,
|
16 |
cast,
|
|
|
37 |
|
38 |
|
39 |
class MemeArgsModel(BaseModel):
|
40 |
+
user_infos: list[UserInfo] = []
|
41 |
|
42 |
|
43 |
ArgsModel = TypeVar("ArgsModel", bound=MemeArgsModel)
|
44 |
|
45 |
MemeFunction = Union[
|
46 |
+
Callable[[list[BuildImage], list[str], ArgsModel], BytesIO],
|
47 |
+
Callable[[list[BuildImage], list[str], ArgsModel], Awaitable[BytesIO]],
|
48 |
]
|
49 |
|
50 |
|
|
|
74 |
@dataclass
|
75 |
class MemeArgsType:
|
76 |
parser: MemeArgsParser
|
77 |
+
model: type[MemeArgsModel]
|
78 |
+
instances: list[MemeArgsModel] = field(default_factory=list)
|
79 |
|
80 |
|
81 |
@dataclass
|
|
|
84 |
max_images: int = 0
|
85 |
min_texts: int = 0
|
86 |
max_texts: int = 0
|
87 |
+
default_texts: list[str] = field(default_factory=list)
|
88 |
args_type: Optional[MemeArgsType] = None
|
89 |
|
90 |
|
|
|
93 |
key: str
|
94 |
function: MemeFunction
|
95 |
params_type: MemeParamsType
|
96 |
+
keywords: list[str] = field(default_factory=list)
|
97 |
+
patterns: list[str] = field(default_factory=list)
|
98 |
|
99 |
async def __call__(
|
100 |
self,
|
101 |
*,
|
102 |
+
images: Union[list[str], list[Path], list[bytes], list[BytesIO]] = [],
|
103 |
+
texts: list[str] = [],
|
104 |
+
args: dict[str, Any] = {},
|
105 |
) -> BytesIO:
|
106 |
if not (
|
107 |
self.params_type.min_images <= len(images) <= self.params_type.max_images
|
|
|
125 |
except ValidationError as e:
|
126 |
raise ArgModelMismatch(self.key, str(e))
|
127 |
|
128 |
+
imgs: list[BuildImage] = []
|
129 |
try:
|
130 |
for image in images:
|
131 |
if isinstance(image, bytes):
|
132 |
image = BytesIO(image)
|
133 |
+
imgs.append(BuildImage.open(image)) # type: ignore
|
134 |
except Exception as e:
|
135 |
raise OpenImageFailed(str(e))
|
136 |
|
|
|
143 |
else:
|
144 |
return await run_sync(cast(Callable[..., BytesIO], self.function))(**values)
|
145 |
|
146 |
+
def parse_args(self, args: list[str] = []) -> dict[str, Any]:
|
147 |
parser = (
|
148 |
copy.deepcopy(self.params_type.args_type.parser)
|
149 |
if self.params_type.args_type
|
|
|
160 |
finally:
|
161 |
parser_message.reset(t)
|
162 |
|
163 |
+
async def generate_preview(self, *, args: dict[str, Any] = {}) -> BytesIO:
|
164 |
default_images = [random_image() for _ in range(self.params_type.min_images)]
|
165 |
default_texts = (
|
166 |
self.params_type.default_texts.copy()
|
|
|
172 |
else [random_text() for _ in range(self.params_type.min_texts)]
|
173 |
)
|
174 |
|
175 |
+
async def _generate_preview(images: list[BytesIO], texts: list[str]):
|
176 |
try:
|
177 |
return await self.__call__(images=images, texts=texts, args=args)
|
178 |
except TextOrNameNotEnough:
|
meme_generator/memes/5000choyen/__init__.py
CHANGED
@@ -1,5 +1,3 @@
|
|
1 |
-
from typing import List, Tuple
|
2 |
-
|
3 |
from PIL.Image import Image as IMG
|
4 |
from PIL.Image import Resampling, Transform
|
5 |
from pil_utils import BuildImage, Text2Image
|
@@ -8,13 +6,13 @@ from pil_utils.gradient import ColorStop, LinearGradient
|
|
8 |
from meme_generator import add_meme
|
9 |
|
10 |
|
11 |
-
def fivethousand_choyen(images, texts:
|
12 |
fontsize = 200
|
13 |
fontname = "Noto Sans SC"
|
14 |
text = texts[0]
|
15 |
pos_x = 40
|
16 |
pos_y = 220
|
17 |
-
imgs:
|
18 |
|
19 |
def transform(img: IMG) -> IMG:
|
20 |
skew = 0.45
|
@@ -26,7 +24,7 @@ def fivethousand_choyen(images, texts: List[str], args):
|
|
26 |
Resampling.BILINEAR,
|
27 |
)
|
28 |
|
29 |
-
def shift(t2m: Text2Image) ->
|
30 |
return (
|
31 |
pos_x
|
32 |
- t2m.lines[0].chars[0].stroke_width
|
@@ -34,7 +32,7 @@ def fivethousand_choyen(images, texts: List[str], args):
|
|
34 |
pos_y - t2m.lines[0].ascent,
|
35 |
)
|
36 |
|
37 |
-
def add_color_text(stroke_width: int, fill: str, pos:
|
38 |
t2m = Text2Image.from_text(
|
39 |
text, fontsize, fontname=fontname, stroke_width=stroke_width, fill=fill
|
40 |
)
|
@@ -43,9 +41,9 @@ def fivethousand_choyen(images, texts: List[str], args):
|
|
43 |
|
44 |
def add_gradient_text(
|
45 |
stroke_width: int,
|
46 |
-
dir:
|
47 |
-
color_stops:
|
48 |
-
pos:
|
49 |
):
|
50 |
t2m = Text2Image.from_text(
|
51 |
text, fontsize, fontname=fontname, stroke_width=stroke_width, fill="white"
|
|
|
|
|
|
|
1 |
from PIL.Image import Image as IMG
|
2 |
from PIL.Image import Resampling, Transform
|
3 |
from pil_utils import BuildImage, Text2Image
|
|
|
6 |
from meme_generator import add_meme
|
7 |
|
8 |
|
9 |
+
def fivethousand_choyen(images, texts: list[str], args):
|
10 |
fontsize = 200
|
11 |
fontname = "Noto Sans SC"
|
12 |
text = texts[0]
|
13 |
pos_x = 40
|
14 |
pos_y = 220
|
15 |
+
imgs: list[tuple[IMG, tuple[int, int]]] = []
|
16 |
|
17 |
def transform(img: IMG) -> IMG:
|
18 |
skew = 0.45
|
|
|
24 |
Resampling.BILINEAR,
|
25 |
)
|
26 |
|
27 |
+
def shift(t2m: Text2Image) -> tuple[int, int]:
|
28 |
return (
|
29 |
pos_x
|
30 |
- t2m.lines[0].chars[0].stroke_width
|
|
|
32 |
pos_y - t2m.lines[0].ascent,
|
33 |
)
|
34 |
|
35 |
+
def add_color_text(stroke_width: int, fill: str, pos: tuple[int, int]):
|
36 |
t2m = Text2Image.from_text(
|
37 |
text, fontsize, fontname=fontname, stroke_width=stroke_width, fill=fill
|
38 |
)
|
|
|
41 |
|
42 |
def add_gradient_text(
|
43 |
stroke_width: int,
|
44 |
+
dir: tuple[int, int, int, int],
|
45 |
+
color_stops: list[tuple[float, tuple[int, int, int]]],
|
46 |
+
pos: tuple[int, int],
|
47 |
):
|
48 |
t2m = Text2Image.from_text(
|
49 |
text, fontsize, fontname=fontname, stroke_width=stroke_width, fill="white"
|
meme_generator/memes/ace_attorney_dialog/__init__.py
ADDED
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import math
|
2 |
+
from pathlib import Path
|
3 |
+
|
4 |
+
from pil_utils import BuildImage, Text2Image
|
5 |
+
|
6 |
+
from meme_generator import add_meme
|
7 |
+
from meme_generator.exception import TextOverLength
|
8 |
+
|
9 |
+
img_dir = Path(__file__).parent / "images"
|
10 |
+
|
11 |
+
|
12 |
+
def ace_attorney_dialog(images, texts: list[str], args):
|
13 |
+
def shadow_text(text: str, fontsize: int) -> BuildImage:
|
14 |
+
fontname = "PangMenZhengDao-Cu"
|
15 |
+
inner = Text2Image.from_text(
|
16 |
+
text,
|
17 |
+
fontsize,
|
18 |
+
fill="#e60012",
|
19 |
+
fontname=fontname,
|
20 |
+
stroke_width=4,
|
21 |
+
stroke_fill="#500000",
|
22 |
+
).to_image()
|
23 |
+
shadow_width = 10
|
24 |
+
shadow = Text2Image.from_text(
|
25 |
+
text,
|
26 |
+
fontsize,
|
27 |
+
fill="#500000",
|
28 |
+
fontname=fontname,
|
29 |
+
stroke_width=shadow_width,
|
30 |
+
stroke_fill="#500000",
|
31 |
+
).to_image()
|
32 |
+
dy = 30
|
33 |
+
dx = 15
|
34 |
+
img = BuildImage.new(
|
35 |
+
"RGBA", (inner.width + dx + shadow_width, inner.height + dy + shadow_width)
|
36 |
+
)
|
37 |
+
img.paste(shadow, (dx - shadow_width, dy - shadow_width), alpha=True)
|
38 |
+
img.paste(inner, (0, 0), alpha=True)
|
39 |
+
return img
|
40 |
+
|
41 |
+
text = texts[0]
|
42 |
+
text_imgs: list[BuildImage] = []
|
43 |
+
for char in text:
|
44 |
+
text_imgs.append(shadow_text(char, 650))
|
45 |
+
|
46 |
+
total_width = sum(img.width for img in text_imgs)
|
47 |
+
if total_width > 4000:
|
48 |
+
raise TextOverLength(text)
|
49 |
+
|
50 |
+
def combine_text(text_imgs: list[BuildImage]) -> BuildImage:
|
51 |
+
ratio = 0.4
|
52 |
+
text_w = sum(img.width for img in text_imgs) - sum(
|
53 |
+
round(img.width * ratio) for img in text_imgs[1:]
|
54 |
+
)
|
55 |
+
text_h = max(img.height for img in text_imgs)
|
56 |
+
text_img = BuildImage.new("RGBA", (text_w, text_h))
|
57 |
+
x = 0
|
58 |
+
for img in text_imgs:
|
59 |
+
text_img.paste(img, (x, round((text_h - img.height) / 2)), alpha=True)
|
60 |
+
x += img.width - round(img.width * ratio)
|
61 |
+
return text_img
|
62 |
+
|
63 |
+
frame = BuildImage.open(img_dir / "bubble.png")
|
64 |
+
mark = BuildImage.open(img_dir / "mark.png")
|
65 |
+
|
66 |
+
if total_width <= 2000:
|
67 |
+
text_img = combine_text(text_imgs)
|
68 |
+
max_width = 900
|
69 |
+
if total_width > max_width:
|
70 |
+
text_img = text_img.resize(
|
71 |
+
(max_width, round(max_width / text_img.width * text_img.height))
|
72 |
+
)
|
73 |
+
text_img = text_img.rotate(10, expand=True)
|
74 |
+
frame.paste(
|
75 |
+
text_img,
|
76 |
+
(
|
77 |
+
round((frame.width - text_img.width) / 2),
|
78 |
+
round((frame.height - text_img.height) / 2),
|
79 |
+
),
|
80 |
+
alpha=True,
|
81 |
+
)
|
82 |
+
frame.paste(mark, (630, 230), alpha=True)
|
83 |
+
|
84 |
+
else:
|
85 |
+
index = math.ceil(len(text_imgs) / 2)
|
86 |
+
text_img1 = combine_text(text_imgs[:index])
|
87 |
+
text_img2 = combine_text(text_imgs[index:])
|
88 |
+
ratio = 0.6
|
89 |
+
text_img1 = text_img1.resize(
|
90 |
+
(round(text_img1.width * ratio), round(text_img1.height * ratio))
|
91 |
+
)
|
92 |
+
text_img2 = text_img2.resize(
|
93 |
+
(round(text_img2.width * ratio), round(text_img2.height * ratio))
|
94 |
+
)
|
95 |
+
text_img1 = text_img1.rotate(10, expand=True)
|
96 |
+
text_img2 = text_img2.rotate(10, expand=True)
|
97 |
+
frame.paste(
|
98 |
+
text_img1,
|
99 |
+
(round((frame.width - text_img1.width) / 2) - 50, 775 - text_img1.height),
|
100 |
+
alpha=True,
|
101 |
+
)
|
102 |
+
frame.paste(
|
103 |
+
text_img2,
|
104 |
+
(round((frame.width - text_img2.width) / 2) + 50, 325),
|
105 |
+
alpha=True,
|
106 |
+
)
|
107 |
+
frame.paste(mark, (680, 320), alpha=True)
|
108 |
+
|
109 |
+
return frame.save_png()
|
110 |
+
|
111 |
+
|
112 |
+
add_meme(
|
113 |
+
"ace_attorney_dialog",
|
114 |
+
ace_attorney_dialog,
|
115 |
+
min_texts=1,
|
116 |
+
max_texts=1,
|
117 |
+
default_texts=["表情包制作"],
|
118 |
+
keywords=["逆转裁判气泡"],
|
119 |
+
)
|
meme_generator/memes/ace_attorney_dialog/images/bubble.png
ADDED
Git LFS Details
|
meme_generator/memes/ace_attorney_dialog/images/mark.png
ADDED
Git LFS Details
|
meme_generator/memes/acg_entrance/__init__.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
from pathlib import Path
|
2 |
-
from typing import List
|
3 |
|
4 |
from pil_utils import BuildImage
|
5 |
|
@@ -10,7 +9,7 @@ from meme_generator.utils import make_jpg_or_gif
|
|
10 |
img_dir = Path(__file__).parent / "images"
|
11 |
|
12 |
|
13 |
-
def acg_entrance(images:
|
14 |
text = texts[0] if texts else "走,跟我去二次元吧"
|
15 |
frame = BuildImage.open(img_dir / "0.png")
|
16 |
try:
|
|
|
1 |
from pathlib import Path
|
|
|
2 |
|
3 |
from pil_utils import BuildImage
|
4 |
|
|
|
9 |
img_dir = Path(__file__).parent / "images"
|
10 |
|
11 |
|
12 |
+
def acg_entrance(images: list[BuildImage], texts: list[str], args):
|
13 |
text = texts[0] if texts else "走,跟我去二次元吧"
|
14 |
frame = BuildImage.open(img_dir / "0.png")
|
15 |
try:
|
meme_generator/memes/add_chaos/__init__.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
from pathlib import Path
|
2 |
-
from typing import List
|
3 |
|
4 |
from pil_utils import BuildImage
|
5 |
|
@@ -9,7 +8,7 @@ from meme_generator.utils import make_jpg_or_gif
|
|
9 |
img_dir = Path(__file__).parent / "images"
|
10 |
|
11 |
|
12 |
-
def add_chaos(images:
|
13 |
banner = BuildImage.open(img_dir / "0.png")
|
14 |
|
15 |
def make(img: BuildImage) -> BuildImage:
|
|
|
1 |
from pathlib import Path
|
|
|
2 |
|
3 |
from pil_utils import BuildImage
|
4 |
|
|
|
8 |
img_dir = Path(__file__).parent / "images"
|
9 |
|
10 |
|
11 |
+
def add_chaos(images: list[BuildImage], texts, args):
|
12 |
banner = BuildImage.open(img_dir / "0.png")
|
13 |
|
14 |
def make(img: BuildImage) -> BuildImage:
|
meme_generator/memes/addiction/__init__.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
from pathlib import Path
|
2 |
-
from typing import List
|
3 |
|
4 |
from pil_utils import BuildImage
|
5 |
|
@@ -10,7 +9,7 @@ from meme_generator.utils import make_jpg_or_gif
|
|
10 |
img_dir = Path(__file__).parent / "images"
|
11 |
|
12 |
|
13 |
-
def addiction(images:
|
14 |
frame = BuildImage.open(img_dir / "0.png")
|
15 |
|
16 |
if texts:
|
|
|
1 |
from pathlib import Path
|
|
|
2 |
|
3 |
from pil_utils import BuildImage
|
4 |
|
|
|
9 |
img_dir = Path(__file__).parent / "images"
|
10 |
|
11 |
|
12 |
+
def addiction(images: list[BuildImage], texts: list[str], args):
|
13 |
frame = BuildImage.open(img_dir / "0.png")
|
14 |
|
15 |
if texts:
|
meme_generator/memes/alike/__init__.py
CHANGED
@@ -1,12 +1,10 @@
|
|
1 |
-
from typing import List
|
2 |
-
|
3 |
from pil_utils import BuildImage
|
4 |
|
5 |
from meme_generator import add_meme
|
6 |
from meme_generator.utils import make_jpg_or_gif
|
7 |
|
8 |
|
9 |
-
def alike(images:
|
10 |
frame = BuildImage.new("RGBA", (470, 180), "white")
|
11 |
frame.draw_text(
|
12 |
(10, 10, 185, 140), "你怎么跟", max_fontsize=40, min_fontsize=30, halign="right"
|
|
|
|
|
|
|
1 |
from pil_utils import BuildImage
|
2 |
|
3 |
from meme_generator import add_meme
|
4 |
from meme_generator.utils import make_jpg_or_gif
|
5 |
|
6 |
|
7 |
+
def alike(images: list[BuildImage], texts, args):
|
8 |
frame = BuildImage.new("RGBA", (470, 180), "white")
|
9 |
frame.draw_text(
|
10 |
(10, 10, 185, 140), "你怎么跟", max_fontsize=40, min_fontsize=30, halign="right"
|
meme_generator/memes/always/__init__.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
from typing import
|
2 |
|
3 |
from pil_utils import BuildImage
|
4 |
from pydantic import Field
|
@@ -22,7 +22,9 @@ group.add_argument(
|
|
22 |
default="normal",
|
23 |
help=help,
|
24 |
)
|
25 |
-
group.add_argument(
|
|
|
|
|
26 |
group.add_argument("--loop", "/循环", action="store_const", const="loop", dest="mode")
|
27 |
|
28 |
|
@@ -90,7 +92,7 @@ def always_always(img: BuildImage, loop: bool = False):
|
|
90 |
)
|
91 |
|
92 |
|
93 |
-
def always(images:
|
94 |
img = images[0]
|
95 |
mode = args.mode
|
96 |
|
|
|
1 |
+
from typing import Literal
|
2 |
|
3 |
from pil_utils import BuildImage
|
4 |
from pydantic import Field
|
|
|
22 |
default="normal",
|
23 |
help=help,
|
24 |
)
|
25 |
+
group.add_argument(
|
26 |
+
"--circle", "/套娃", action="store_const", const="circle", dest="mode"
|
27 |
+
)
|
28 |
group.add_argument("--loop", "/循环", action="store_const", const="loop", dest="mode")
|
29 |
|
30 |
|
|
|
92 |
)
|
93 |
|
94 |
|
95 |
+
def always(images: list[BuildImage], texts, args: Model):
|
96 |
img = images[0]
|
97 |
mode = args.mode
|
98 |
|
meme_generator/memes/always_like/__init__.py
CHANGED
@@ -1,6 +1,5 @@
|
|
1 |
import random
|
2 |
from pathlib import Path
|
3 |
-
from typing import List
|
4 |
|
5 |
from pil_utils import BuildImage, Text2Image
|
6 |
|
@@ -10,7 +9,7 @@ from meme_generator.exception import TextOrNameNotEnough, TextOverLength
|
|
10 |
img_dir = Path(__file__).parent / "images"
|
11 |
|
12 |
|
13 |
-
def always_like(images:
|
14 |
names = [info.name for info in args.user_infos]
|
15 |
|
16 |
if len(images) > len(texts) + len(names):
|
|
|
1 |
import random
|
2 |
from pathlib import Path
|
|
|
3 |
|
4 |
from pil_utils import BuildImage, Text2Image
|
5 |
|
|
|
9 |
img_dir = Path(__file__).parent / "images"
|
10 |
|
11 |
|
12 |
+
def always_like(images: list[BuildImage], texts: list[str], args: MemeArgsModel):
|
13 |
names = [info.name for info in args.user_infos]
|
14 |
|
15 |
if len(images) > len(texts) + len(names):
|
meme_generator/memes/anti_kidnap/__init__.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
from pathlib import Path
|
2 |
-
from typing import List
|
3 |
|
4 |
from pil_utils import BuildImage
|
5 |
|
@@ -8,7 +7,7 @@ from meme_generator import add_meme
|
|
8 |
img_dir = Path(__file__).parent / "images"
|
9 |
|
10 |
|
11 |
-
def anti_kidnap(images:
|
12 |
img = images[0].convert("RGBA").resize((450, 450), keep_ratio=True)
|
13 |
frame = BuildImage.open(img_dir / "0.png")
|
14 |
frame.paste(img, (30, 78), below=True)
|
|
|
1 |
from pathlib import Path
|
|
|
2 |
|
3 |
from pil_utils import BuildImage
|
4 |
|
|
|
7 |
img_dir = Path(__file__).parent / "images"
|
8 |
|
9 |
|
10 |
+
def anti_kidnap(images: list[BuildImage], texts, args):
|
11 |
img = images[0].convert("RGBA").resize((450, 450), keep_ratio=True)
|
12 |
frame = BuildImage.open(img_dir / "0.png")
|
13 |
frame.paste(img, (30, 78), below=True)
|
meme_generator/memes/anya_suki/__init__.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
from pathlib import Path
|
2 |
-
from typing import List
|
3 |
|
4 |
from pil_utils import BuildImage
|
5 |
|
@@ -10,7 +9,7 @@ from meme_generator.utils import make_jpg_or_gif
|
|
10 |
img_dir = Path(__file__).parent / "images"
|
11 |
|
12 |
|
13 |
-
def anya_suki(images:
|
14 |
text = texts[0] if texts else "阿尼亚喜欢这个"
|
15 |
frame = BuildImage.open(img_dir / "0.png")
|
16 |
try:
|
|
|
1 |
from pathlib import Path
|
|
|
2 |
|
3 |
from pil_utils import BuildImage
|
4 |
|
|
|
9 |
img_dir = Path(__file__).parent / "images"
|
10 |
|
11 |
|
12 |
+
def anya_suki(images: list[BuildImage], texts: list[str], args):
|
13 |
text = texts[0] if texts else "阿尼亚喜欢这个"
|
14 |
frame = BuildImage.open(img_dir / "0.png")
|
15 |
try:
|
meme_generator/memes/applaud/__init__.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
from pathlib import Path
|
2 |
-
from typing import List
|
3 |
|
4 |
from PIL.Image import Image as IMG
|
5 |
from pil_utils import BuildImage
|
@@ -10,9 +9,9 @@ from meme_generator.utils import save_gif
|
|
10 |
img_dir = Path(__file__).parent / "images"
|
11 |
|
12 |
|
13 |
-
def applaud(images:
|
14 |
img = images[0].convert("RGBA").square().resize((110, 110))
|
15 |
-
frames:
|
16 |
locs = [
|
17 |
(109, 102, 27, 17),
|
18 |
(107, 105, 28, 15),
|
|
|
1 |
from pathlib import Path
|
|
|
2 |
|
3 |
from PIL.Image import Image as IMG
|
4 |
from pil_utils import BuildImage
|
|
|
9 |
img_dir = Path(__file__).parent / "images"
|
10 |
|
11 |
|
12 |
+
def applaud(images: list[BuildImage], texts, args):
|
13 |
img = images[0].convert("RGBA").square().resize((110, 110))
|
14 |
+
frames: list[IMG] = []
|
15 |
locs = [
|
16 |
(109, 102, 27, 17),
|
17 |
(107, 105, 28, 15),
|
meme_generator/memes/ascension/__init__.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
from pathlib import Path
|
2 |
-
from typing import List
|
3 |
|
4 |
from pil_utils import BuildImage
|
5 |
|
@@ -9,7 +8,7 @@ from meme_generator.exception import TextOverLength
|
|
9 |
img_dir = Path(__file__).parent / "images"
|
10 |
|
11 |
|
12 |
-
def ascension(images, texts:
|
13 |
frame = BuildImage.open(img_dir / "0.png")
|
14 |
text = f"你原本应该要去地狱的,但因为你生前{texts[0]},我们就当作你已经服完刑期了"
|
15 |
try:
|
|
|
1 |
from pathlib import Path
|
|
|
2 |
|
3 |
from pil_utils import BuildImage
|
4 |
|
|
|
8 |
img_dir = Path(__file__).parent / "images"
|
9 |
|
10 |
|
11 |
+
def ascension(images, texts: list[str], args):
|
12 |
frame = BuildImage.open(img_dir / "0.png")
|
13 |
text = f"你原本应该要去地狱的,但因为你生前{texts[0]},我们就当作你已经服完刑期了"
|
14 |
try:
|
meme_generator/memes/ask/__init__.py
CHANGED
@@ -1,5 +1,3 @@
|
|
1 |
-
from typing import List
|
2 |
-
|
3 |
from PIL import ImageFilter
|
4 |
from pil_utils import BuildImage, Text2Image
|
5 |
from pil_utils.gradient import ColorStop, LinearGradient
|
@@ -8,7 +6,7 @@ from meme_generator import MemeArgsModel, add_meme
|
|
8 |
from meme_generator.exception import TextOrNameNotEnough, TextOverLength
|
9 |
|
10 |
|
11 |
-
def ask(images:
|
12 |
if not texts and not args.user_infos:
|
13 |
raise TextOrNameNotEnough("ask")
|
14 |
|
|
|
|
|
|
|
1 |
from PIL import ImageFilter
|
2 |
from pil_utils import BuildImage, Text2Image
|
3 |
from pil_utils.gradient import ColorStop, LinearGradient
|
|
|
6 |
from meme_generator.exception import TextOrNameNotEnough, TextOverLength
|
7 |
|
8 |
|
9 |
+
def ask(images: list[BuildImage], texts: list[str], args: MemeArgsModel):
|
10 |
if not texts and not args.user_infos:
|
11 |
raise TextOrNameNotEnough("ask")
|
12 |
|
meme_generator/memes/back_to_work/__init__.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
from pathlib import Path
|
2 |
-
from typing import List
|
3 |
|
4 |
from pil_utils import BuildImage
|
5 |
|
@@ -8,7 +7,7 @@ from meme_generator import add_meme
|
|
8 |
img_dir = Path(__file__).parent / "images"
|
9 |
|
10 |
|
11 |
-
def back_to_work(images:
|
12 |
frame = BuildImage.open(img_dir / "0.png")
|
13 |
img = (
|
14 |
images[0].convert("RGBA").resize((220, 310), keep_ratio=True, direction="north")
|
@@ -18,5 +17,9 @@ def back_to_work(images: List[BuildImage], texts, args):
|
|
18 |
|
19 |
|
20 |
add_meme(
|
21 |
-
"back_to_work",
|
|
|
|
|
|
|
|
|
22 |
)
|
|
|
1 |
from pathlib import Path
|
|
|
2 |
|
3 |
from pil_utils import BuildImage
|
4 |
|
|
|
7 |
img_dir = Path(__file__).parent / "images"
|
8 |
|
9 |
|
10 |
+
def back_to_work(images: list[BuildImage], texts, args):
|
11 |
frame = BuildImage.open(img_dir / "0.png")
|
12 |
img = (
|
13 |
images[0].convert("RGBA").resize((220, 310), keep_ratio=True, direction="north")
|
|
|
17 |
|
18 |
|
19 |
add_meme(
|
20 |
+
"back_to_work",
|
21 |
+
back_to_work,
|
22 |
+
min_images=1,
|
23 |
+
max_images=1,
|
24 |
+
keywords=["继续干活", "打工人"],
|
25 |
)
|
meme_generator/memes/bad_news/__init__.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
from pathlib import Path
|
2 |
-
from typing import List
|
3 |
|
4 |
from pil_utils import BuildImage
|
5 |
|
@@ -9,7 +8,7 @@ from meme_generator.exception import TextOverLength
|
|
9 |
img_dir = Path(__file__).parent / "images"
|
10 |
|
11 |
|
12 |
-
def bad_news(images, texts:
|
13 |
text = texts[0]
|
14 |
frame = BuildImage.open(img_dir / "0.png")
|
15 |
try:
|
|
|
1 |
from pathlib import Path
|
|
|
2 |
|
3 |
from pil_utils import BuildImage
|
4 |
|
|
|
8 |
img_dir = Path(__file__).parent / "images"
|
9 |
|
10 |
|
11 |
+
def bad_news(images, texts: list[str], args):
|
12 |
text = texts[0]
|
13 |
frame = BuildImage.open(img_dir / "0.png")
|
14 |
try:
|
meme_generator/memes/beat_head/__init__.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
from pathlib import Path
|
2 |
-
from typing import List
|
3 |
|
4 |
from PIL.Image import Image as IMG
|
5 |
from pil_utils import BuildImage
|
@@ -11,11 +10,11 @@ from meme_generator.utils import save_gif
|
|
11 |
img_dir = Path(__file__).parent / "images"
|
12 |
|
13 |
|
14 |
-
def beat_head(images:
|
15 |
text = texts[0] if texts else "怎么说话的你"
|
16 |
img = images[0].convert("RGBA")
|
17 |
locs = [(160, 121, 76, 76), (172, 124, 69, 69), (208, 166, 52, 52)]
|
18 |
-
frames:
|
19 |
for i in range(3):
|
20 |
x, y, w, h = locs[i]
|
21 |
head = img.resize((w, h), keep_ratio=True).circle()
|
|
|
1 |
from pathlib import Path
|
|
|
2 |
|
3 |
from PIL.Image import Image as IMG
|
4 |
from pil_utils import BuildImage
|
|
|
10 |
img_dir = Path(__file__).parent / "images"
|
11 |
|
12 |
|
13 |
+
def beat_head(images: list[BuildImage], texts: list[str], args):
|
14 |
text = texts[0] if texts else "怎么说话的你"
|
15 |
img = images[0].convert("RGBA")
|
16 |
locs = [(160, 121, 76, 76), (172, 124, 69, 69), (208, 166, 52, 52)]
|
17 |
+
frames: list[IMG] = []
|
18 |
for i in range(3):
|
19 |
x, y, w, h = locs[i]
|
20 |
head = img.resize((w, h), keep_ratio=True).circle()
|
meme_generator/memes/beat_up/__init__.py
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pathlib import Path
|
2 |
+
|
3 |
+
from PIL.Image import Image as IMG
|
4 |
+
from pil_utils import BuildImage
|
5 |
+
|
6 |
+
from meme_generator import add_meme
|
7 |
+
from meme_generator.utils import save_gif
|
8 |
+
|
9 |
+
img_dir = Path(__file__).parent / "images"
|
10 |
+
|
11 |
+
|
12 |
+
def beat_up(images: list[BuildImage], texts, args):
|
13 |
+
self_head = images[0].convert("RGBA").circle().resize((55, 55))
|
14 |
+
user_head = images[1].convert("RGBA").circle().resize((45, 45))
|
15 |
+
self_locs = [(100, 43), (110, 46), (101, 40)]
|
16 |
+
user_locs = [(99, 136), (99, 136), (89, 140)]
|
17 |
+
frames: list[IMG] = []
|
18 |
+
for i in range(3):
|
19 |
+
frame = BuildImage.open(img_dir / f"{i}.png")
|
20 |
+
frame.paste(user_head, user_locs[i], alpha=True)
|
21 |
+
frame.paste(self_head, self_locs[i], alpha=True)
|
22 |
+
frames.append(frame.image)
|
23 |
+
|
24 |
+
return save_gif(frames, 0.1)
|
25 |
+
|
26 |
+
|
27 |
+
add_meme("beat_up", beat_up, min_images=2, max_images=2, keywords=["揍"])
|
meme_generator/memes/beat_up/images/0.png
ADDED
Git LFS Details
|
meme_generator/memes/beat_up/images/1.png
ADDED
Git LFS Details
|
meme_generator/memes/beat_up/images/2.png
ADDED
Git LFS Details
|
meme_generator/memes/bite/__init__.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
from pathlib import Path
|
2 |
-
from typing import List
|
3 |
|
4 |
from PIL.Image import Image as IMG
|
5 |
from pil_utils import BuildImage
|
@@ -10,9 +9,9 @@ from meme_generator.utils import save_gif
|
|
10 |
img_dir = Path(__file__).parent / "images"
|
11 |
|
12 |
|
13 |
-
def bite(images:
|
14 |
img = images[0].convert("RGBA").square()
|
15 |
-
frames:
|
16 |
# fmt: off
|
17 |
locs = [
|
18 |
(90, 90, 105, 150), (90, 83, 96, 172), (90, 90, 106, 148),
|
|
|
1 |
from pathlib import Path
|
|
|
2 |
|
3 |
from PIL.Image import Image as IMG
|
4 |
from pil_utils import BuildImage
|
|
|
9 |
img_dir = Path(__file__).parent / "images"
|
10 |
|
11 |
|
12 |
+
def bite(images: list[BuildImage], texts, args):
|
13 |
img = images[0].convert("RGBA").square()
|
14 |
+
frames: list[IMG] = []
|
15 |
# fmt: off
|
16 |
locs = [
|
17 |
(90, 90, 105, 150), (90, 83, 96, 172), (90, 90, 106, 148),
|
meme_generator/memes/blood_pressure/__init__.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
from pathlib import Path
|
2 |
-
from typing import List
|
3 |
|
4 |
from pil_utils import BuildImage
|
5 |
|
@@ -9,7 +8,7 @@ from meme_generator.utils import make_jpg_or_gif
|
|
9 |
img_dir = Path(__file__).parent / "images"
|
10 |
|
11 |
|
12 |
-
def blood_pressure(images:
|
13 |
frame = BuildImage.open(img_dir / "0.png")
|
14 |
|
15 |
def make(img: BuildImage) -> BuildImage:
|
@@ -19,4 +18,6 @@ def blood_pressure(images: List[BuildImage], texts, args):
|
|
19 |
return make_jpg_or_gif(images[0], make)
|
20 |
|
21 |
|
22 |
-
add_meme(
|
|
|
|
|
|
1 |
from pathlib import Path
|
|
|
2 |
|
3 |
from pil_utils import BuildImage
|
4 |
|
|
|
8 |
img_dir = Path(__file__).parent / "images"
|
9 |
|
10 |
|
11 |
+
def blood_pressure(images: list[BuildImage], texts, args):
|
12 |
frame = BuildImage.open(img_dir / "0.png")
|
13 |
|
14 |
def make(img: BuildImage) -> BuildImage:
|
|
|
18 |
return make_jpg_or_gif(images[0], make)
|
19 |
|
20 |
|
21 |
+
add_meme(
|
22 |
+
"blood_pressure", blood_pressure, min_images=1, max_images=1, keywords=["高血压"]
|
23 |
+
)
|
meme_generator/memes/bluearchive/__init__.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
from pathlib import Path
|
2 |
-
from typing import List
|
3 |
|
4 |
from PIL.Image import Image as IMG
|
5 |
from PIL.Image import Resampling, Transform
|
@@ -12,7 +11,7 @@ from meme_generator import add_meme
|
|
12 |
img_dir = Path(__file__).parent / "images"
|
13 |
|
14 |
|
15 |
-
def bluearchive(images, texts:
|
16 |
fontsize = 168
|
17 |
fontname = "Ro GSan Serif Std"
|
18 |
fallback_fonts = ["Glow Sans SC"] + DEFAULT_FALLBACK_FONTS
|
|
|
1 |
from pathlib import Path
|
|
|
2 |
|
3 |
from PIL.Image import Image as IMG
|
4 |
from PIL.Image import Resampling, Transform
|
|
|
11 |
img_dir = Path(__file__).parent / "images"
|
12 |
|
13 |
|
14 |
+
def bluearchive(images, texts: list[str], args):
|
15 |
fontsize = 168
|
16 |
fontname = "Ro GSan Serif Std"
|
17 |
fallback_fonts = ["Glow Sans SC"] + DEFAULT_FALLBACK_FONTS
|
meme_generator/memes/bocchi_draft/__init__.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
from pathlib import Path
|
2 |
-
from typing import List
|
3 |
|
4 |
from PIL.Image import Image as IMG
|
5 |
from pil_utils import BuildImage
|
@@ -10,7 +9,7 @@ from meme_generator.utils import save_gif
|
|
10 |
img_dir = Path(__file__).parent / "images"
|
11 |
|
12 |
|
13 |
-
def bocchi_draft(images:
|
14 |
img = images[0].convert("RGBA").resize((350, 400), keep_ratio=True)
|
15 |
params = [
|
16 |
(((54, 62), (353, 1), (379, 382), (1, 399)), (146, 173)),
|
@@ -30,7 +29,7 @@ def bocchi_draft(images: List[BuildImage], texts, args):
|
|
30 |
0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10,
|
31 |
]
|
32 |
# fmt: on
|
33 |
-
frames:
|
34 |
for i in range(23):
|
35 |
frame = BuildImage.open(img_dir / f"{i}.png")
|
36 |
points, pos = params[idx[i]]
|
@@ -39,4 +38,6 @@ def bocchi_draft(images: List[BuildImage], texts, args):
|
|
39 |
return save_gif(frames, 0.08)
|
40 |
|
41 |
|
42 |
-
add_meme(
|
|
|
|
|
|
1 |
from pathlib import Path
|
|
|
2 |
|
3 |
from PIL.Image import Image as IMG
|
4 |
from pil_utils import BuildImage
|
|
|
9 |
img_dir = Path(__file__).parent / "images"
|
10 |
|
11 |
|
12 |
+
def bocchi_draft(images: list[BuildImage], texts, args):
|
13 |
img = images[0].convert("RGBA").resize((350, 400), keep_ratio=True)
|
14 |
params = [
|
15 |
(((54, 62), (353, 1), (379, 382), (1, 399)), (146, 173)),
|
|
|
29 |
0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10,
|
30 |
]
|
31 |
# fmt: on
|
32 |
+
frames: list[IMG] = []
|
33 |
for i in range(23):
|
34 |
frame = BuildImage.open(img_dir / f"{i}.png")
|
35 |
points, pos = params[idx[i]]
|
|
|
38 |
return save_gif(frames, 0.08)
|
39 |
|
40 |
|
41 |
+
add_meme(
|
42 |
+
"bocchi_draft", bocchi_draft, min_images=1, max_images=1, keywords=["波奇手稿"]
|
43 |
+
)
|
meme_generator/memes/bronya_holdsign/__init__.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
from pathlib import Path
|
2 |
-
from typing import List
|
3 |
|
4 |
from pil_utils import BuildImage
|
5 |
|
@@ -9,7 +8,7 @@ from meme_generator.exception import TextOverLength
|
|
9 |
img_dir = Path(__file__).parent / "images"
|
10 |
|
11 |
|
12 |
-
def bronya_holdsign(images, texts:
|
13 |
text = texts[0]
|
14 |
frame = BuildImage.open(img_dir / "0.jpg")
|
15 |
try:
|
|
|
1 |
from pathlib import Path
|
|
|
2 |
|
3 |
from pil_utils import BuildImage
|
4 |
|
|
|
8 |
img_dir = Path(__file__).parent / "images"
|
9 |
|
10 |
|
11 |
+
def bronya_holdsign(images, texts: list[str], args):
|
12 |
text = texts[0]
|
13 |
frame = BuildImage.open(img_dir / "0.jpg")
|
14 |
try:
|
meme_generator/memes/bubble_tea/__init__.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
from pathlib import Path
|
2 |
-
from typing import
|
3 |
|
4 |
from PIL.Image import Transpose
|
5 |
from pil_utils import BuildImage
|
@@ -26,15 +26,19 @@ group.add_argument(
|
|
26 |
group.add_argument(
|
27 |
"--right", "/右手", action="store_const", const="right", dest="position"
|
28 |
)
|
29 |
-
group.add_argument(
|
30 |
-
|
|
|
|
|
|
|
|
|
31 |
|
32 |
|
33 |
class Model(MemeArgsModel):
|
34 |
position: Literal["right", "left", "both"] = Field("right", description=help)
|
35 |
|
36 |
|
37 |
-
def bubble_tea(images:
|
38 |
frame = images[0].convert("RGBA").resize((500, 500), keep_ratio=True)
|
39 |
bubble_tea = BuildImage.open(img_dir / "0.png")
|
40 |
position = args.position
|
|
|
1 |
from pathlib import Path
|
2 |
+
from typing import Literal
|
3 |
|
4 |
from PIL.Image import Transpose
|
5 |
from pil_utils import BuildImage
|
|
|
26 |
group.add_argument(
|
27 |
"--right", "/右手", action="store_const", const="right", dest="position"
|
28 |
)
|
29 |
+
group.add_argument(
|
30 |
+
"--left", "/左手", action="store_const", const="left", dest="position"
|
31 |
+
)
|
32 |
+
group.add_argument(
|
33 |
+
"--both", "/双手", action="store_const", const="both", dest="position"
|
34 |
+
)
|
35 |
|
36 |
|
37 |
class Model(MemeArgsModel):
|
38 |
position: Literal["right", "left", "both"] = Field("right", description=help)
|
39 |
|
40 |
|
41 |
+
def bubble_tea(images: list[BuildImage], texts, args: Model):
|
42 |
frame = images[0].convert("RGBA").resize((500, 500), keep_ratio=True)
|
43 |
bubble_tea = BuildImage.open(img_dir / "0.png")
|
44 |
position = args.position
|
meme_generator/memes/call_110/__init__.py
CHANGED
@@ -1,11 +1,9 @@
|
|
1 |
-
from typing import List
|
2 |
-
|
3 |
from pil_utils import BuildImage
|
4 |
|
5 |
from meme_generator import add_meme
|
6 |
|
7 |
|
8 |
-
def call_110(images:
|
9 |
img1 = images[0].convert("RGBA").square().resize((250, 250))
|
10 |
img0 = images[1].convert("RGBA").square().resize((250, 250))
|
11 |
|
|
|
|
|
|
|
1 |
from pil_utils import BuildImage
|
2 |
|
3 |
from meme_generator import add_meme
|
4 |
|
5 |
|
6 |
+
def call_110(images: list[BuildImage], texts, args):
|
7 |
img1 = images[0].convert("RGBA").square().resize((250, 250))
|
8 |
img0 = images[1].convert("RGBA").square().resize((250, 250))
|
9 |
|
meme_generator/memes/caoshen_bite/__init__.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
from pathlib import Path
|
2 |
-
from typing import List
|
3 |
|
4 |
from PIL.Image import Image as IMG
|
5 |
from pil_utils import BuildImage
|
@@ -10,7 +9,7 @@ from meme_generator.utils import save_gif
|
|
10 |
img_dir = Path(__file__).parent / "images"
|
11 |
|
12 |
|
13 |
-
def caoshen_bite(images:
|
14 |
img = images[0].convert("RGBA").resize((160, 140), keep_ratio=True)
|
15 |
# fmt: off
|
16 |
locs = [
|
@@ -22,7 +21,7 @@ def caoshen_bite(images: List[BuildImage], texts, args):
|
|
22 |
(122, 351, 159, 129), (122, 353, 159, 127), (123, 355, 158, 125),
|
23 |
]
|
24 |
# fmt: on
|
25 |
-
frames:
|
26 |
for i in range(38):
|
27 |
frame = BuildImage.open(img_dir / f"{i}.png")
|
28 |
x, y, w, h = locs[i % len(locs)]
|
|
|
1 |
from pathlib import Path
|
|
|
2 |
|
3 |
from PIL.Image import Image as IMG
|
4 |
from pil_utils import BuildImage
|
|
|
9 |
img_dir = Path(__file__).parent / "images"
|
10 |
|
11 |
|
12 |
+
def caoshen_bite(images: list[BuildImage], texts, args):
|
13 |
img = images[0].convert("RGBA").resize((160, 140), keep_ratio=True)
|
14 |
# fmt: off
|
15 |
locs = [
|
|
|
21 |
(122, 351, 159, 129), (122, 353, 159, 127), (123, 355, 158, 125),
|
22 |
]
|
23 |
# fmt: on
|
24 |
+
frames: list[IMG] = []
|
25 |
for i in range(38):
|
26 |
frame = BuildImage.open(img_dir / f"{i}.png")
|
27 |
x, y, w, h = locs[i % len(locs)]
|
meme_generator/memes/capoo_draw/__init__.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
from pathlib import Path
|
2 |
-
from typing import List
|
3 |
|
4 |
from PIL.Image import Image as IMG
|
5 |
from pil_utils import BuildImage
|
@@ -10,7 +9,7 @@ from meme_generator.utils import save_gif
|
|
10 |
img_dir = Path(__file__).parent / "images"
|
11 |
|
12 |
|
13 |
-
def capoo_draw(images:
|
14 |
img = images[0].convert("RGBA").resize((175, 120), keep_ratio=True)
|
15 |
params = (
|
16 |
(((27, 0), (207, 12), (179, 142), (0, 117)), (30, 16)),
|
@@ -21,7 +20,7 @@ def capoo_draw(images: List[BuildImage], texts, args):
|
|
21 |
points, pos = params[i]
|
22 |
raw_frames[4 + i].paste(img.perspective(points), pos, below=True)
|
23 |
|
24 |
-
frames:
|
25 |
frames.append(raw_frames[0].image)
|
26 |
for i in range(4):
|
27 |
frames.append(raw_frames[1].image)
|
|
|
1 |
from pathlib import Path
|
|
|
2 |
|
3 |
from PIL.Image import Image as IMG
|
4 |
from pil_utils import BuildImage
|
|
|
9 |
img_dir = Path(__file__).parent / "images"
|
10 |
|
11 |
|
12 |
+
def capoo_draw(images: list[BuildImage], texts, args):
|
13 |
img = images[0].convert("RGBA").resize((175, 120), keep_ratio=True)
|
14 |
params = (
|
15 |
(((27, 0), (207, 12), (179, 142), (0, 117)), (30, 16)),
|
|
|
20 |
points, pos = params[i]
|
21 |
raw_frames[4 + i].paste(img.perspective(points), pos, below=True)
|
22 |
|
23 |
+
frames: list[IMG] = []
|
24 |
frames.append(raw_frames[0].image)
|
25 |
for i in range(4):
|
26 |
frames.append(raw_frames[1].image)
|
meme_generator/memes/capoo_rip/__init__.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
from pathlib import Path
|
2 |
-
from typing import List
|
3 |
|
4 |
from pil_utils import BuildImage
|
5 |
|
@@ -9,7 +8,7 @@ from meme_generator.utils import save_gif
|
|
9 |
img_dir = Path(__file__).parent / "images"
|
10 |
|
11 |
|
12 |
-
def capoo_rip(images:
|
13 |
img = images[0].convert("RGBA").resize((150, 100), keep_ratio=True)
|
14 |
img_left = img.crop((0, 0, 75, 100))
|
15 |
img_right = img.crop((75, 0, 150, 100))
|
@@ -40,7 +39,7 @@ def capoo_rip(images: List[BuildImage], texts, args):
|
|
40 |
raw_frames[i + 6].paste(img_left.perspective(points1), pos1, below=True)
|
41 |
raw_frames[i + 6].paste(img_right.perspective(points2), pos2, below=True)
|
42 |
|
43 |
-
new_frames:
|
44 |
for i in range(3):
|
45 |
new_frames += raw_frames[0:3]
|
46 |
new_frames += raw_frames[3:]
|
|
|
1 |
from pathlib import Path
|
|
|
2 |
|
3 |
from pil_utils import BuildImage
|
4 |
|
|
|
8 |
img_dir = Path(__file__).parent / "images"
|
9 |
|
10 |
|
11 |
+
def capoo_rip(images: list[BuildImage], texts, args):
|
12 |
img = images[0].convert("RGBA").resize((150, 100), keep_ratio=True)
|
13 |
img_left = img.crop((0, 0, 75, 100))
|
14 |
img_right = img.crop((75, 0, 150, 100))
|
|
|
39 |
raw_frames[i + 6].paste(img_left.perspective(points1), pos1, below=True)
|
40 |
raw_frames[i + 6].paste(img_right.perspective(points2), pos2, below=True)
|
41 |
|
42 |
+
new_frames: list[BuildImage] = []
|
43 |
for i in range(3):
|
44 |
new_frames += raw_frames[0:3]
|
45 |
new_frames += raw_frames[3:]
|
meme_generator/memes/capoo_rub/__init__.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
from pathlib import Path
|
2 |
-
from typing import List
|
3 |
|
4 |
from PIL.Image import Image as IMG
|
5 |
from pil_utils import BuildImage
|
@@ -10,9 +9,9 @@ from meme_generator.utils import save_gif
|
|
10 |
img_dir = Path(__file__).parent / "images"
|
11 |
|
12 |
|
13 |
-
def capoo_rub(images:
|
14 |
img = images[0].convert("RGBA").square().resize((180, 180))
|
15 |
-
frames:
|
16 |
locs = [
|
17 |
(178, 184, 78, 260),
|
18 |
(178, 174, 84, 269),
|
@@ -27,4 +26,6 @@ def capoo_rub(images: List[BuildImage], texts, args):
|
|
27 |
return save_gif(frames, 0.1)
|
28 |
|
29 |
|
30 |
-
add_meme(
|
|
|
|
|
|
1 |
from pathlib import Path
|
|
|
2 |
|
3 |
from PIL.Image import Image as IMG
|
4 |
from pil_utils import BuildImage
|
|
|
9 |
img_dir = Path(__file__).parent / "images"
|
10 |
|
11 |
|
12 |
+
def capoo_rub(images: list[BuildImage], texts, args):
|
13 |
img = images[0].convert("RGBA").square().resize((180, 180))
|
14 |
+
frames: list[IMG] = []
|
15 |
locs = [
|
16 |
(178, 184, 78, 260),
|
17 |
(178, 174, 84, 269),
|
|
|
26 |
return save_gif(frames, 0.1)
|
27 |
|
28 |
|
29 |
+
add_meme(
|
30 |
+
"capoo_rub", capoo_rub, min_images=1, max_images=1, keywords=["咖波蹭", "咖波贴"]
|
31 |
+
)
|
meme_generator/memes/capoo_say/__init__.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
from pathlib import Path
|
2 |
-
from typing import List
|
3 |
|
4 |
from PIL.Image import Image as IMG
|
5 |
from pil_utils import BuildImage
|
@@ -11,7 +10,7 @@ from meme_generator.utils import save_gif
|
|
11 |
img_dir = Path(__file__).parent / "images"
|
12 |
|
13 |
|
14 |
-
def capoo_say_one_loop(text: str) ->
|
15 |
text_frame = BuildImage.new("RGBA", (80, 80))
|
16 |
try:
|
17 |
text_frame.draw_text(
|
@@ -39,7 +38,7 @@ def capoo_say_one_loop(text: str) -> List[IMG]:
|
|
39 |
None,
|
40 |
]
|
41 |
|
42 |
-
frames:
|
43 |
for i in range(10):
|
44 |
frame = BuildImage.open(img_dir / f"{i}.png")
|
45 |
param = params[i]
|
@@ -52,7 +51,7 @@ def capoo_say_one_loop(text: str) -> List[IMG]:
|
|
52 |
return frames
|
53 |
|
54 |
|
55 |
-
def capoo_say(images, texts:
|
56 |
frames = sum([capoo_say_one_loop(text) for text in texts], [])
|
57 |
return save_gif(frames, 0.1)
|
58 |
|
|
|
1 |
from pathlib import Path
|
|
|
2 |
|
3 |
from PIL.Image import Image as IMG
|
4 |
from pil_utils import BuildImage
|
|
|
10 |
img_dir = Path(__file__).parent / "images"
|
11 |
|
12 |
|
13 |
+
def capoo_say_one_loop(text: str) -> list[IMG]:
|
14 |
text_frame = BuildImage.new("RGBA", (80, 80))
|
15 |
try:
|
16 |
text_frame.draw_text(
|
|
|
38 |
None,
|
39 |
]
|
40 |
|
41 |
+
frames: list[IMG] = []
|
42 |
for i in range(10):
|
43 |
frame = BuildImage.open(img_dir / f"{i}.png")
|
44 |
param = params[i]
|
|
|
51 |
return frames
|
52 |
|
53 |
|
54 |
+
def capoo_say(images, texts: list[str], args):
|
55 |
frames = sum([capoo_say_one_loop(text) for text in texts], [])
|
56 |
return save_gif(frames, 0.1)
|
57 |
|
meme_generator/memes/capoo_strike/__init__.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
from pathlib import Path
|
2 |
-
from typing import List
|
3 |
|
4 |
from pil_utils import BuildImage
|
5 |
|
@@ -9,7 +8,7 @@ from meme_generator.utils import FrameAlignPolicy, Maker, make_gif_or_combined_g
|
|
9 |
img_dir = Path(__file__).parent / "images"
|
10 |
|
11 |
|
12 |
-
def capoo_strike(images:
|
13 |
params = (
|
14 |
(((0, 4), (153, 0), (138, 105), (0, 157)), (28, 47)),
|
15 |
(((1, 13), (151, 0), (130, 104), (0, 156)), (28, 48)),
|
|
|
1 |
from pathlib import Path
|
|
|
2 |
|
3 |
from pil_utils import BuildImage
|
4 |
|
|
|
8 |
img_dir = Path(__file__).parent / "images"
|
9 |
|
10 |
|
11 |
+
def capoo_strike(images: list[BuildImage], texts, args):
|
12 |
params = (
|
13 |
(((0, 4), (153, 0), (138, 105), (0, 157)), (28, 47)),
|
14 |
(((1, 13), (151, 0), (130, 104), (0, 156)), (28, 48)),
|
meme_generator/memes/captain/__init__.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
from pathlib import Path
|
2 |
-
from typing import List
|
3 |
|
4 |
from pil_utils import BuildImage
|
5 |
|
@@ -8,7 +7,7 @@ from meme_generator import add_meme
|
|
8 |
img_dir = Path(__file__).parent / "images"
|
9 |
|
10 |
|
11 |
-
def captain(images:
|
12 |
if len(images) == 2:
|
13 |
images.append(images[-1])
|
14 |
|
|
|
1 |
from pathlib import Path
|
|
|
2 |
|
3 |
from pil_utils import BuildImage
|
4 |
|
|
|
7 |
img_dir = Path(__file__).parent / "images"
|
8 |
|
9 |
|
10 |
+
def captain(images: list[BuildImage], texts, args):
|
11 |
if len(images) == 2:
|
12 |
images.append(images[-1])
|
13 |
|
meme_generator/memes/certificate/__init__.py
CHANGED
@@ -1,6 +1,5 @@
|
|
1 |
from datetime import datetime
|
2 |
from pathlib import Path
|
3 |
-
from typing import List
|
4 |
|
5 |
import dateparser
|
6 |
from pil_utils import BuildImage
|
@@ -20,7 +19,7 @@ class Model(MemeArgsModel):
|
|
20 |
img_dir = Path(__file__).parent / "images"
|
21 |
|
22 |
|
23 |
-
def certificate(images, texts:
|
24 |
time = datetime.now()
|
25 |
if args.time and (parsed_time := dateparser.parse(args.time)):
|
26 |
time = parsed_time
|
@@ -61,7 +60,9 @@ def certificate(images, texts: List[str], args: Model):
|
|
61 |
try:
|
62 |
frame.draw_text(
|
63 |
(450, 850, 2270, 1080),
|
64 |
-
texts[3]
|
|
|
|
|
65 |
allow_wrap=True,
|
66 |
max_fontsize=80,
|
67 |
min_fontsize=40,
|
|
|
1 |
from datetime import datetime
|
2 |
from pathlib import Path
|
|
|
3 |
|
4 |
import dateparser
|
5 |
from pil_utils import BuildImage
|
|
|
19 |
img_dir = Path(__file__).parent / "images"
|
20 |
|
21 |
|
22 |
+
def certificate(images, texts: list[str], args: Model):
|
23 |
time = datetime.now()
|
24 |
if args.time and (parsed_time := dateparser.parse(args.time)):
|
25 |
time = parsed_time
|
|
|
60 |
try:
|
61 |
frame.draw_text(
|
62 |
(450, 850, 2270, 1080),
|
63 |
+
texts[3]
|
64 |
+
if len(texts) >= 4
|
65 |
+
else " 在本学年第一学期中表现优秀,被我校决定评为",
|
66 |
allow_wrap=True,
|
67 |
max_fontsize=80,
|
68 |
min_fontsize=40,
|
meme_generator/memes/charpic/__init__.py
CHANGED
@@ -1,5 +1,3 @@
|
|
1 |
-
from typing import List
|
2 |
-
|
3 |
from PIL import Image, ImageDraw
|
4 |
from pil_utils import BuildImage
|
5 |
from pil_utils.fonts import Font
|
@@ -8,7 +6,7 @@ from meme_generator import add_meme
|
|
8 |
from meme_generator.utils import make_jpg_or_gif
|
9 |
|
10 |
|
11 |
-
def charpic(images:
|
12 |
img = images[0]
|
13 |
str_map = "@@$$&B88QMMGW##EE93SPPDOOU**==()+^,\"--''. "
|
14 |
num = len(str_map)
|
|
|
|
|
|
|
1 |
from PIL import Image, ImageDraw
|
2 |
from pil_utils import BuildImage
|
3 |
from pil_utils.fonts import Font
|
|
|
6 |
from meme_generator.utils import make_jpg_or_gif
|
7 |
|
8 |
|
9 |
+
def charpic(images: list[BuildImage], texts, args):
|
10 |
img = images[0]
|
11 |
str_map = "@@$$&B88QMMGW##EE93SPPDOOU**==()+^,\"--''. "
|
12 |
num = len(str_map)
|