meme-generator-next 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (320) hide show
  1. meme_generator/__init__.py +59 -0
  2. meme_generator/app.py +322 -0
  3. meme_generator/cli.py +238 -0
  4. meme_generator/compat.py +96 -0
  5. meme_generator/config.py +98 -0
  6. meme_generator/dirs.py +251 -0
  7. meme_generator/download.py +116 -0
  8. meme_generator/exception.py +120 -0
  9. meme_generator/log.py +87 -0
  10. meme_generator/manager.py +111 -0
  11. meme_generator/meme.py +167 -0
  12. meme_generator/memes/5000choyen/__init__.py +202 -0
  13. meme_generator/memes/abstinence/__init__.py +93 -0
  14. meme_generator/memes/ace_attorney_dialog/__init__.py +122 -0
  15. meme_generator/memes/acg_entrance/__init__.py +47 -0
  16. meme_generator/memes/add_chaos/__init__.py +29 -0
  17. meme_generator/memes/addiction/__init__.py +41 -0
  18. meme_generator/memes/alike/__init__.py +32 -0
  19. meme_generator/memes/alipay/__init__.py +89 -0
  20. meme_generator/memes/always/__init__.py +138 -0
  21. meme_generator/memes/always_like/__init__.py +96 -0
  22. meme_generator/memes/anti_kidnap/__init__.py +26 -0
  23. meme_generator/memes/anya_suki/__init__.py +50 -0
  24. meme_generator/memes/applaud/__init__.py +39 -0
  25. meme_generator/memes/arona_throw/__init__.py +64 -0
  26. meme_generator/memes/ascension/__init__.py +37 -0
  27. meme_generator/memes/ask/__init__.py +94 -0
  28. meme_generator/memes/atri_pillow/__init__.py +75 -0
  29. meme_generator/memes/ayachi_holdsign/__init__.py +49 -0
  30. meme_generator/memes/ba_say/__init__.py +144 -0
  31. meme_generator/memes/back_to_work/__init__.py +28 -0
  32. meme_generator/memes/backflip/__init__.py +81 -0
  33. meme_generator/memes/bad_news/__init__.py +41 -0
  34. meme_generator/memes/beat_head/__init__.py +52 -0
  35. meme_generator/memes/beat_up/__init__.py +38 -0
  36. meme_generator/memes/bite/__init__.py +41 -0
  37. meme_generator/memes/blamed_mahiro/__init__.py +78 -0
  38. meme_generator/memes/blood_pressure/__init__.py +30 -0
  39. meme_generator/memes/bluearchive/__init__.py +67 -0
  40. meme_generator/memes/bocchi_draft/__init__.py +52 -0
  41. meme_generator/memes/bronya_holdsign/__init__.py +41 -0
  42. meme_generator/memes/bubble_tea/__init__.py +76 -0
  43. meme_generator/memes/call_110/__init__.py +28 -0
  44. meme_generator/memes/capoo_draw/__init__.py +47 -0
  45. meme_generator/memes/capoo_point/__init__.py +40 -0
  46. meme_generator/memes/capoo_rip/__init__.py +63 -0
  47. meme_generator/memes/capoo_rub/__init__.py +40 -0
  48. meme_generator/memes/capoo_say/__init__.py +71 -0
  49. meme_generator/memes/capoo_stew/__init__.py +40 -0
  50. meme_generator/memes/capoo_strike/__init__.py +48 -0
  51. meme_generator/memes/captain/__init__.py +39 -0
  52. meme_generator/memes/caused_by_this/__init__.py +66 -0
  53. meme_generator/memes/certificate/__init__.py +125 -0
  54. meme_generator/memes/charpic/__init__.py +43 -0
  55. meme_generator/memes/chase_train/__init__.py +66 -0
  56. meme_generator/memes/china_flag/__init__.py +26 -0
  57. meme_generator/memes/chino_throw/__init__.py +60 -0
  58. meme_generator/memes/clauvio_twist/__init__.py +58 -0
  59. meme_generator/memes/clown/__init__.py +91 -0
  60. meme_generator/memes/clown_mask/__init__.py +86 -0
  61. meme_generator/memes/confuse/__init__.py +40 -0
  62. meme_generator/memes/coupon/__init__.py +45 -0
  63. meme_generator/memes/cover_face/__init__.py +27 -0
  64. meme_generator/memes/crawl/__init__.py +63 -0
  65. meme_generator/memes/cyan/__init__.py +39 -0
  66. meme_generator/memes/daynight/__init__.py +28 -0
  67. meme_generator/memes/decent_kiss/__init__.py +26 -0
  68. meme_generator/memes/dianzhongdian/__init__.py +62 -0
  69. meme_generator/memes/dinosaur/__init__.py +30 -0
  70. meme_generator/memes/distracted/__init__.py +33 -0
  71. meme_generator/memes/divorce/__init__.py +26 -0
  72. meme_generator/memes/dog_dislike/__init__.py +94 -0
  73. meme_generator/memes/dog_of_vtb/__init__.py +31 -0
  74. meme_generator/memes/dont_go_near/__init__.py +30 -0
  75. meme_generator/memes/dont_press/__init__.py +38 -0
  76. meme_generator/memes/dont_touch/__init__.py +66 -0
  77. meme_generator/memes/douyin/__init__.py +80 -0
  78. meme_generator/memes/eat/__init__.py +30 -0
  79. meme_generator/memes/emperor_dragon/__init__.py +40 -0
  80. meme_generator/memes/erised_mirror/__init__.py +39 -0
  81. meme_generator/memes/fade_away/__init__.py +134 -0
  82. meme_generator/memes/fanatic/__init__.py +38 -0
  83. meme_generator/memes/father_work/__init__.py +49 -0
  84. meme_generator/memes/fencing/__init__.py +45 -0
  85. meme_generator/memes/fight_with_sunuo/__init__.py +32 -0
  86. meme_generator/memes/fill_head/__init__.py +48 -0
  87. meme_generator/memes/find_chips/__init__.py +44 -0
  88. meme_generator/memes/firefly_holdsign/__init__.py +105 -0
  89. meme_generator/memes/flash_blind/__init__.py +65 -0
  90. meme_generator/memes/flick/__init__.py +109 -0
  91. meme_generator/memes/flush/__init__.py +55 -0
  92. meme_generator/memes/fogging/__init__.py +51 -0
  93. meme_generator/memes/follow/__init__.py +46 -0
  94. meme_generator/memes/frieren_take/__init__.py +50 -0
  95. meme_generator/memes/funny_mirror/__init__.py +31 -0
  96. meme_generator/memes/garbage/__init__.py +38 -0
  97. meme_generator/memes/genshin_eat/__init__.py +99 -0
  98. meme_generator/memes/genshin_start/__init__.py +61 -0
  99. meme_generator/memes/gif_subtitle/__init__.py +197 -0
  100. meme_generator/memes/good_news/__init__.py +41 -0
  101. meme_generator/memes/google/__init__.py +29 -0
  102. meme_generator/memes/google_captcha/__init__.py +78 -0
  103. meme_generator/memes/gorilla_throw/__init__.py +58 -0
  104. meme_generator/memes/guichu/__init__.py +129 -0
  105. meme_generator/memes/gun/__init__.py +76 -0
  106. meme_generator/memes/hammer/__init__.py +38 -0
  107. meme_generator/memes/haruhi_raise/__init__.py +36 -0
  108. meme_generator/memes/high_EQ/__init__.py +56 -0
  109. meme_generator/memes/hit_screen/__init__.py +56 -0
  110. meme_generator/memes/hold_grudge/__init__.py +37 -0
  111. meme_generator/memes/hold_tight/__init__.py +26 -0
  112. meme_generator/memes/hug/__init__.py +45 -0
  113. meme_generator/memes/hug_leg/__init__.py +40 -0
  114. meme_generator/memes/hutao_bite/__init__.py +35 -0
  115. meme_generator/memes/imprison/__init__.py +37 -0
  116. meme_generator/memes/incivilization/__init__.py +47 -0
  117. meme_generator/memes/intel_inside/__init__.py +47 -0
  118. meme_generator/memes/interview/__init__.py +49 -0
  119. meme_generator/memes/jerry_stare/__init__.py +32 -0
  120. meme_generator/memes/jiji_king/__init__.py +117 -0
  121. meme_generator/memes/jinhsi/__init__.py +81 -0
  122. meme_generator/memes/jiubingfufa/__init__.py +53 -0
  123. meme_generator/memes/jiujiu/__init__.py +31 -0
  124. meme_generator/memes/jump/__init__.py +41 -0
  125. meme_generator/memes/kaleidoscope/__init__.py +73 -0
  126. meme_generator/memes/karyl_point/__init__.py +28 -0
  127. meme_generator/memes/keep_away/__init__.py +51 -0
  128. meme_generator/memes/keep_your_money/__init__.py +70 -0
  129. meme_generator/memes/kick_ball/__init__.py +38 -0
  130. meme_generator/memes/kirby_hammer/__init__.py +80 -0
  131. meme_generator/memes/kiss/__init__.py +43 -0
  132. meme_generator/memes/klee_eat/__init__.py +43 -0
  133. meme_generator/memes/knock/__init__.py +38 -0
  134. meme_generator/memes/kokona_seal/__init__.py +81 -0
  135. meme_generator/memes/konata_watch/__init__.py +36 -0
  136. meme_generator/memes/learn/__init__.py +47 -0
  137. meme_generator/memes/left_right_jump/__init__.py +105 -0
  138. meme_generator/memes/let_me_in/__init__.py +30 -0
  139. meme_generator/memes/lick_candy/__init__.py +131 -0
  140. meme_generator/memes/lim_x_0/__init__.py +43 -0
  141. meme_generator/memes/listen_music/__init__.py +34 -0
  142. meme_generator/memes/little_angel/__init__.py +57 -0
  143. meme_generator/memes/loading/__init__.py +44 -0
  144. meme_generator/memes/look_flat/__init__.py +79 -0
  145. meme_generator/memes/look_this_icon/__init__.py +48 -0
  146. meme_generator/memes/loop/__init__.py +108 -0
  147. meme_generator/memes/lost_dog/__init__.py +43 -0
  148. meme_generator/memes/love_you/__init__.py +34 -0
  149. meme_generator/memes/luotianyi_need/__init__.py +34 -0
  150. meme_generator/memes/luotianyi_say/__init__.py +44 -0
  151. meme_generator/memes/luoyonghao_say/__init__.py +44 -0
  152. meme_generator/memes/luxun_say/__init__.py +39 -0
  153. meme_generator/memes/mahiro_readbook/__init__.py +45 -0
  154. meme_generator/memes/maimai_awaken/__init__.py +34 -0
  155. meme_generator/memes/maimai_join/__init__.py +32 -0
  156. meme_generator/memes/make_friend/__init__.py +52 -0
  157. meme_generator/memes/marriage/__init__.py +35 -0
  158. meme_generator/memes/meteor/__init__.py +38 -0
  159. meme_generator/memes/mihoyo/__init__.py +33 -0
  160. meme_generator/memes/mourning/__init__.py +55 -0
  161. meme_generator/memes/murmur/__init__.py +36 -0
  162. meme_generator/memes/my_friend/__init__.py +96 -0
  163. meme_generator/memes/my_opinion/__init__.py +38 -0
  164. meme_generator/memes/my_wife/__init__.py +102 -0
  165. meme_generator/memes/nahida_bite/__init__.py +44 -0
  166. meme_generator/memes/name_generator/__init__.py +48 -0
  167. meme_generator/memes/need/__init__.py +30 -0
  168. meme_generator/memes/nekoha_holdsign/__init__.py +42 -0
  169. meme_generator/memes/nijika_holdsign/__init__.py +40 -0
  170. meme_generator/memes/no_response/__init__.py +26 -0
  171. meme_generator/memes/nokia/__init__.py +44 -0
  172. meme_generator/memes/not_call_me/__init__.py +37 -0
  173. meme_generator/memes/note_for_leave/__init__.py +107 -0
  174. meme_generator/memes/oshi_no_ko/__init__.py +75 -0
  175. meme_generator/memes/osu/__init__.py +42 -0
  176. meme_generator/memes/out/__init__.py +38 -0
  177. meme_generator/memes/overtime/__init__.py +26 -0
  178. meme_generator/memes/p5letter/__init__.py +158 -0
  179. meme_generator/memes/paint/__init__.py +26 -0
  180. meme_generator/memes/painter/__init__.py +30 -0
  181. meme_generator/memes/panda_dragon_figure/__init__.py +88 -0
  182. meme_generator/memes/pass_the_buck/__init__.py +44 -0
  183. meme_generator/memes/pat/__init__.py +37 -0
  184. meme_generator/memes/pepe_raise/__init__.py +51 -0
  185. meme_generator/memes/perfect/__init__.py +26 -0
  186. meme_generator/memes/petpet/__init__.py +67 -0
  187. meme_generator/memes/pinch/__init__.py +33 -0
  188. meme_generator/memes/pixelate/__init__.py +59 -0
  189. meme_generator/memes/pjsk/__init__.py +154 -0
  190. meme_generator/memes/plana_eat/__init__.py +32 -0
  191. meme_generator/memes/play/__init__.py +54 -0
  192. meme_generator/memes/play_baseball/__init__.py +53 -0
  193. meme_generator/memes/play_basketball/__init__.py +84 -0
  194. meme_generator/memes/play_game/__init__.py +54 -0
  195. meme_generator/memes/play_together/__init__.py +61 -0
  196. meme_generator/memes/police/__init__.py +68 -0
  197. meme_generator/memes/pornhub/__init__.py +41 -0
  198. meme_generator/memes/potato/__init__.py +26 -0
  199. meme_generator/memes/pound/__init__.py +38 -0
  200. meme_generator/memes/printing/__init__.py +39 -0
  201. meme_generator/memes/prpr/__init__.py +36 -0
  202. meme_generator/memes/psyduck/__init__.py +78 -0
  203. meme_generator/memes/punch/__init__.py +39 -0
  204. meme_generator/memes/pyramid/__init__.py +99 -0
  205. meme_generator/memes/raise_image/__init__.py +38 -0
  206. meme_generator/memes/raise_sign/__init__.py +43 -0
  207. meme_generator/memes/read_book/__init__.py +70 -0
  208. meme_generator/memes/remote_control/__init__.py +52 -0
  209. meme_generator/memes/repeat/__init__.py +70 -0
  210. meme_generator/memes/rip/__init__.py +38 -0
  211. meme_generator/memes/rip_angrily/__init__.py +27 -0
  212. meme_generator/memes/rip_clothes/__init__.py +38 -0
  213. meme_generator/memes/rise_dead/__init__.py +44 -0
  214. meme_generator/memes/roll/__init__.py +38 -0
  215. meme_generator/memes/rotate_3d/__init__.py +79 -0
  216. meme_generator/memes/rub/__init__.py +47 -0
  217. meme_generator/memes/run/__init__.py +40 -0
  218. meme_generator/memes/run_away/__init__.py +47 -0
  219. meme_generator/memes/safe_sense/__init__.py +48 -0
  220. meme_generator/memes/saimin_app/__init__.py +45 -0
  221. meme_generator/memes/scratch_head/__init__.py +40 -0
  222. meme_generator/memes/scratchcard/__init__.py +41 -0
  223. meme_generator/memes/scroll/__init__.py +61 -0
  224. meme_generator/memes/seal/__init__.py +38 -0
  225. meme_generator/memes/sekaiichi_kawaii/__init__.py +45 -0
  226. meme_generator/memes/shake_head/__init__.py +56 -0
  227. meme_generator/memes/shiroko_pero/__init__.py +41 -0
  228. meme_generator/memes/shock/__init__.py +31 -0
  229. meme_generator/memes/shutup/__init__.py +37 -0
  230. meme_generator/memes/sit_still/__init__.py +41 -0
  231. meme_generator/memes/slap/__init__.py +36 -0
  232. meme_generator/memes/slogan/__init__.py +49 -0
  233. meme_generator/memes/smash/__init__.py +36 -0
  234. meme_generator/memes/sold_out/__init__.py +40 -0
  235. meme_generator/memes/speechless/__init__.py +52 -0
  236. meme_generator/memes/sphere_rotate/__init__.py +166 -0
  237. meme_generator/memes/spider/__init__.py +50 -0
  238. meme_generator/memes/stare_at_you/__init__.py +49 -0
  239. meme_generator/memes/steam_message/__init__.py +101 -0
  240. meme_generator/memes/step_on/__init__.py +39 -0
  241. meme_generator/memes/stew/__init__.py +46 -0
  242. meme_generator/memes/stickman_dancing/__init__.py +62 -0
  243. meme_generator/memes/subject3/__init__.py +53 -0
  244. meme_generator/memes/suck/__init__.py +38 -0
  245. meme_generator/memes/support/__init__.py +26 -0
  246. meme_generator/memes/swirl_turn/__init__.py +41 -0
  247. meme_generator/memes/symmetric/__init__.py +134 -0
  248. meme_generator/memes/tankuku_raisesign/__init__.py +51 -0
  249. meme_generator/memes/taunt/__init__.py +30 -0
  250. meme_generator/memes/teach/__init__.py +50 -0
  251. meme_generator/memes/tease/__init__.py +61 -0
  252. meme_generator/memes/telescope/__init__.py +52 -0
  253. meme_generator/memes/thermometer_gun/__init__.py +61 -0
  254. meme_generator/memes/think_what/__init__.py +30 -0
  255. meme_generator/memes/this_chicken/__init__.py +49 -0
  256. meme_generator/memes/throw/__init__.py +35 -0
  257. meme_generator/memes/throw_gif/__init__.py +42 -0
  258. meme_generator/memes/thump/__init__.py +35 -0
  259. meme_generator/memes/thump_wildly/__init__.py +34 -0
  260. meme_generator/memes/tightly/__init__.py +38 -0
  261. meme_generator/memes/time_to_go/__init__.py +43 -0
  262. meme_generator/memes/together/__init__.py +49 -0
  263. meme_generator/memes/tom_tease/__init__.py +39 -0
  264. meme_generator/memes/tomb_yeah/__init__.py +33 -0
  265. meme_generator/memes/top_notch/__init__.py +37 -0
  266. meme_generator/memes/trance/__init__.py +31 -0
  267. meme_generator/memes/trolley/__init__.py +59 -0
  268. meme_generator/memes/turn/__init__.py +31 -0
  269. meme_generator/memes/twist/__init__.py +38 -0
  270. meme_generator/memes/universal/__init__.py +44 -0
  271. meme_generator/memes/upside_down/__init__.py +48 -0
  272. meme_generator/memes/vibrate/__init__.py +44 -0
  273. meme_generator/memes/wakeup/__init__.py +41 -0
  274. meme_generator/memes/wallpaper/__init__.py +35 -0
  275. meme_generator/memes/walnut_pad/__init__.py +32 -0
  276. meme_generator/memes/walnut_zoom/__init__.py +46 -0
  277. meme_generator/memes/washer/__init__.py +34 -0
  278. meme_generator/memes/wave/__init__.py +54 -0
  279. meme_generator/memes/wechat_pay/__init__.py +92 -0
  280. meme_generator/memes/what_I_want_to_do/__init__.py +30 -0
  281. meme_generator/memes/what_he_wants/__init__.py +52 -0
  282. meme_generator/memes/why_at_me/__init__.py +28 -0
  283. meme_generator/memes/why_have_hands/__init__.py +79 -0
  284. meme_generator/memes/windmill_turn/__init__.py +35 -0
  285. meme_generator/memes/wish_fail/__init__.py +37 -0
  286. meme_generator/memes/wooden_fish/__init__.py +29 -0
  287. meme_generator/memes/worship/__init__.py +33 -0
  288. meme_generator/memes/wujing/__init__.py +65 -0
  289. meme_generator/memes/yeshu/__init__.py +227 -0
  290. meme_generator/memes/you_dont_get/__init__.py +28 -0
  291. meme_generator/memes/you_should_call/__init__.py +54 -0
  292. meme_generator/memes/your_new_years_eve/__init__.py +34 -0
  293. meme_generator/memes/youtube/__init__.py +65 -0
  294. meme_generator/redis_cache.py +37 -0
  295. meme_generator/resources/images/emojis/emoji_u1f602.png +0 -0
  296. meme_generator/resources/images/emojis/emoji_u1f605.png +0 -0
  297. meme_generator/resources/images/emojis/emoji_u1f60f.png +0 -0
  298. meme_generator/resources/images/emojis/emoji_u1f62d.png +0 -0
  299. meme_generator/resources/images/emojis/emoji_u1f642.png +0 -0
  300. meme_generator/resources/images/emojis/emoji_u1f913.png +0 -0
  301. meme_generator/resources/images/emojis/emoji_u1f917.png +0 -0
  302. meme_generator/resources/images/emojis/emoji_u1f924.png +0 -0
  303. meme_generator/resources/images/emojis/emoji_u1f970.png +0 -0
  304. meme_generator/resources/images/emojis/emoji_u1f975.png +0 -0
  305. meme_generator/resources/images/icons/hot.png +0 -0
  306. meme_generator/resources/images/icons/image.png +0 -0
  307. meme_generator/resources/images/icons/image_disabled.png +0 -0
  308. meme_generator/resources/images/icons/new.png +0 -0
  309. meme_generator/resources/images/icons/text.png +0 -0
  310. meme_generator/resources/images/icons/text_disabled.png +0 -0
  311. meme_generator/storage.py +110 -0
  312. meme_generator/tags.py +146 -0
  313. meme_generator/utils.py +517 -0
  314. meme_generator/version.py +1 -0
  315. meme_generator_cli.py +36 -0
  316. meme_generator_next-0.2.0.dist-info/METADATA +169 -0
  317. meme_generator_next-0.2.0.dist-info/RECORD +320 -0
  318. meme_generator_next-0.2.0.dist-info/WHEEL +4 -0
  319. meme_generator_next-0.2.0.dist-info/entry_points.txt +3 -0
  320. meme_generator_next-0.2.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,59 @@
1
+ import argparse
2
+ import os
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ CONFIG_FILE_ENV = "MEME_GENERATOR_CONFIG_FILE"
7
+
8
+
9
+ def _is_module_invocation(module_name: str) -> bool:
10
+ orig_argv = getattr(sys, "orig_argv", [])
11
+ try:
12
+ option_index = orig_argv.index("-m")
13
+ except ValueError:
14
+ return False
15
+ return (
16
+ option_index + 1 < len(orig_argv) and orig_argv[option_index + 1] == module_name
17
+ )
18
+
19
+
20
+ def _apply_config_file_option() -> None:
21
+ if not _is_module_invocation("meme_generator.app"):
22
+ return
23
+
24
+ parser = argparse.ArgumentParser(add_help=False, allow_abbrev=False)
25
+ parser.add_argument("--config-file", dest="config_file")
26
+ args, remaining = parser.parse_known_args(sys.argv[1:])
27
+
28
+ if args.config_file is None:
29
+ return
30
+ if not args.config_file:
31
+ parser.error("--config-file must not be empty")
32
+
33
+ os.environ[CONFIG_FILE_ENV] = str(Path(args.config_file).expanduser())
34
+ sys.argv[:] = [sys.argv[0], *remaining]
35
+
36
+
37
+ _apply_config_file_option()
38
+
39
+ from meme_generator.config import meme_config as config
40
+ from meme_generator.manager import add_meme as add_meme
41
+ from meme_generator.manager import get_meme as get_meme
42
+ from meme_generator.manager import get_meme_keys as get_meme_keys
43
+ from meme_generator.manager import get_memes as get_memes
44
+ from meme_generator.manager import load_meme as load_meme
45
+ from meme_generator.manager import load_memes as load_memes
46
+ from meme_generator.meme import CommandShortcut as CommandShortcut
47
+ from meme_generator.meme import Meme as Meme
48
+ from meme_generator.meme import MemeArgsModel as MemeArgsModel
49
+ from meme_generator.meme import MemeArgsType as MemeArgsType
50
+ from meme_generator.meme import MemeParamsType as MemeParamsType
51
+ from meme_generator.meme import ParserArg as ParserArg
52
+ from meme_generator.meme import ParserOption as ParserOption
53
+ from meme_generator.version import __version__ as __version__
54
+
55
+ if config.meme.load_builtin_memes:
56
+ for path in (Path(__file__).parent / "memes").iterdir():
57
+ load_meme(f"meme_generator.memes.{path.name}")
58
+ for meme_dir in config.meme.meme_dirs:
59
+ load_memes(meme_dir)
meme_generator/app.py ADDED
@@ -0,0 +1,322 @@
1
+ import json
2
+ from datetime import datetime
3
+ from typing import Any, Literal, Optional
4
+
5
+ import filetype
6
+ from fastapi import Depends, FastAPI, Form, HTTPException, Response, UploadFile
7
+ from pydantic import BaseModel, ValidationError
8
+ from redis.exceptions import RedisError
9
+
10
+ from meme_generator.compat import model_dump, model_json_schema, type_validate_python
11
+ from meme_generator.config import meme_config
12
+ from meme_generator.exception import (
13
+ ArgModelMismatch,
14
+ MemeGeneratorException,
15
+ NoSuchMeme,
16
+ RedisCacheError,
17
+ S3StorageError,
18
+ )
19
+ from meme_generator.log import LOGGING_CONFIG, setup_logger
20
+ from meme_generator.manager import get_meme, get_meme_keys, get_memes
21
+ from meme_generator.meme import CommandShortcut, Meme, MemeArgsModel, ParserOption
22
+ from meme_generator.redis_cache import (
23
+ cache_preview_url,
24
+ create_redis_client,
25
+ get_cached_preview_url,
26
+ )
27
+ from meme_generator.storage import upload_to_s3
28
+ from meme_generator.utils import MemeProperties, render_meme_list, run_sync
29
+ from meme_generator.version import __version__
30
+
31
+ app = FastAPI()
32
+
33
+
34
+ class MemeArgsResponse(BaseModel):
35
+ args_model: dict[str, Any]
36
+ args_examples: list[dict[str, Any]]
37
+ parser_options: list[ParserOption]
38
+
39
+
40
+ class MemeParamsResponse(BaseModel):
41
+ min_images: int
42
+ max_images: int
43
+ min_texts: int
44
+ max_texts: int
45
+ default_texts: list[str]
46
+ args_type: Optional[MemeArgsResponse] = None
47
+
48
+
49
+ class MemeUrlResponse(BaseModel):
50
+ url: str
51
+ meme_key: str
52
+ object_key: str
53
+
54
+
55
+ class MemePreviewUrlResponse(BaseModel):
56
+ url: str
57
+ meme_key: str
58
+
59
+
60
+ class MemeInfoResponse(BaseModel):
61
+ key: str
62
+ params_type: MemeParamsResponse
63
+ keywords: list[str]
64
+ shortcuts: list[CommandShortcut]
65
+ tags: set[str]
66
+ date_created: datetime
67
+ date_modified: datetime
68
+
69
+
70
+ async def generate_meme(
71
+ meme: Meme, images: list[bytes], texts: list[str], args: Any
72
+ ) -> tuple[bytes, str]:
73
+ try:
74
+ result = await run_sync(meme)(images=images, texts=texts, args=model_dump(args))
75
+ except MemeGeneratorException as e:
76
+ raise HTTPException(status_code=e.status_code, detail=e.message)
77
+
78
+ content = result.getvalue()
79
+ media_type = str(filetype.guess_mime(content)) or "text/plain"
80
+ return content, media_type
81
+
82
+
83
+ def register_router(meme: Meme):
84
+ if args_type := meme.params_type.args_type:
85
+ args_model = args_type.args_model
86
+ else:
87
+ args_model = MemeArgsModel
88
+
89
+ def args_checker(
90
+ args: Optional[str] = Form(default=json.dumps(model_dump(args_model()))),
91
+ ):
92
+ if not args:
93
+ return MemeArgsModel()
94
+ try:
95
+ model = type_validate_python(args_model, json.loads(args))
96
+ except ValidationError as e:
97
+ e = ArgModelMismatch(str(e))
98
+ raise HTTPException(status_code=552, detail=e.message)
99
+ return model
100
+
101
+ @app.post(f"/memes/{meme.key}/")
102
+ async def _(
103
+ images: list[UploadFile] = [],
104
+ texts: list[str] = meme.params_type.default_texts,
105
+ args: args_model = Depends(args_checker), # type: ignore
106
+ ):
107
+ imgs: list[bytes] = []
108
+ for image in images:
109
+ imgs.append(await image.read())
110
+
111
+ texts = [text for text in texts if text]
112
+
113
+ assert isinstance(args, args_model)
114
+
115
+ content, media_type = await generate_meme(meme, imgs, texts, args)
116
+
117
+ return Response(content=content, media_type=media_type)
118
+
119
+ @app.post(f"/memes/{meme.key}/url/")
120
+ async def _(
121
+ images: list[UploadFile] = [],
122
+ texts: list[str] = meme.params_type.default_texts,
123
+ args: args_model = Depends(args_checker), # type: ignore
124
+ ):
125
+ imgs: list[bytes] = []
126
+ for image in images:
127
+ imgs.append(await image.read())
128
+
129
+ texts = [text for text in texts if text]
130
+
131
+ assert isinstance(args, args_model)
132
+
133
+ content, media_type = await generate_meme(meme, imgs, texts, args)
134
+
135
+ try:
136
+ upload_result = await run_sync(upload_to_s3)(
137
+ content=content,
138
+ media_type=media_type,
139
+ meme_key=meme.key,
140
+ config=meme_config.storage.s3,
141
+ upload_kind="result",
142
+ )
143
+ except S3StorageError as e:
144
+ raise HTTPException(status_code=e.status_code, detail=e.message)
145
+
146
+ return MemeUrlResponse(
147
+ url=upload_result.url,
148
+ meme_key=meme.key,
149
+ object_key=upload_result.object_key,
150
+ )
151
+
152
+ @app.get(f"/memes/{meme.key}/preview/url/")
153
+ async def _():
154
+ redis_config = meme_config.storage.redis
155
+ if not redis_config.enabled:
156
+ error = RedisCacheError(
157
+ "Redis 缓存未启用,请在 config.toml 中配置 storage.redis"
158
+ )
159
+ raise HTTPException(status_code=error.status_code, detail=error.message)
160
+
161
+ try:
162
+ redis_client = create_redis_client(redis_config)
163
+ except RedisCacheError as e:
164
+ raise HTTPException(status_code=e.status_code, detail=e.message)
165
+
166
+ try:
167
+ cached_url = await get_cached_preview_url(
168
+ redis_client, redis_config, meme.key
169
+ )
170
+ if cached_url:
171
+ return MemePreviewUrlResponse(url=cached_url, meme_key=meme.key)
172
+
173
+ try:
174
+ result = await run_sync(meme.generate_preview)()
175
+ except MemeGeneratorException as e:
176
+ raise HTTPException(status_code=e.status_code, detail=e.message)
177
+
178
+ content = result.getvalue()
179
+ media_type = str(filetype.guess_mime(content)) or "text/plain"
180
+
181
+ try:
182
+ upload_result = await run_sync(upload_to_s3)(
183
+ content=content,
184
+ media_type=media_type,
185
+ meme_key=meme.key,
186
+ config=meme_config.storage.s3,
187
+ upload_kind="preview",
188
+ )
189
+ except S3StorageError as e:
190
+ raise HTTPException(status_code=e.status_code, detail=e.message)
191
+
192
+ try:
193
+ await cache_preview_url(
194
+ redis_client, redis_config, meme.key, upload_result.url
195
+ )
196
+ except RedisError as e:
197
+ error = RedisCacheError(f"Redis 缓存写入失败:{e}")
198
+ raise HTTPException(status_code=error.status_code, detail=error.message)
199
+
200
+ return MemePreviewUrlResponse(url=upload_result.url, meme_key=meme.key)
201
+ except RedisError as e:
202
+ error = RedisCacheError(f"Redis 缓存读取失败:{e}")
203
+ raise HTTPException(status_code=error.status_code, detail=error.message)
204
+ finally:
205
+ await redis_client.aclose()
206
+
207
+
208
+ class MemeKeyWithProperties(BaseModel):
209
+ meme_key: str
210
+ disabled: bool = False
211
+ labels: list[Literal["new", "hot"]] = []
212
+
213
+
214
+ default_meme_list = [
215
+ MemeKeyWithProperties(meme_key=meme.key)
216
+ for meme in sorted(get_memes(), key=lambda meme: meme.key)
217
+ ]
218
+
219
+
220
+ class RenderMemeListRequest(BaseModel):
221
+ meme_list: list[MemeKeyWithProperties] = default_meme_list
222
+ text_template: str = "{keywords}"
223
+ add_category_icon: bool = True
224
+
225
+
226
+ def register_routers():
227
+ @app.post("/memes/render_list")
228
+ def _(params: RenderMemeListRequest = RenderMemeListRequest()):
229
+ try:
230
+ meme_list = [
231
+ (
232
+ get_meme(p.meme_key),
233
+ MemeProperties(disabled=p.disabled, labels=p.labels),
234
+ )
235
+ for p in params.meme_list
236
+ ]
237
+ except NoSuchMeme as e:
238
+ raise HTTPException(status_code=e.status_code, detail=e.message)
239
+
240
+ result = render_meme_list(
241
+ meme_list,
242
+ text_template=params.text_template,
243
+ add_category_icon=params.add_category_icon,
244
+ )
245
+ content = result.getvalue()
246
+ media_type = str(filetype.guess_mime(content)) or "text/plain"
247
+ return Response(content=content, media_type=media_type)
248
+
249
+ @app.get("/meme/version")
250
+ def _():
251
+ return __version__
252
+
253
+ @app.get("/memes/keys")
254
+ def _():
255
+ return get_meme_keys()
256
+
257
+ @app.get("/memes/{key}/info")
258
+ def _(key: str):
259
+ try:
260
+ meme = get_meme(key)
261
+ except NoSuchMeme as e:
262
+ raise HTTPException(status_code=e.status_code, detail=e.message)
263
+
264
+ args_type_response = None
265
+ if args_type := meme.params_type.args_type:
266
+ args_model = args_type.args_model
267
+ args_type_response = MemeArgsResponse(
268
+ args_model=model_json_schema(args_model),
269
+ args_examples=[
270
+ model_dump(example) for example in args_type.args_examples
271
+ ],
272
+ parser_options=args_type.parser_options,
273
+ )
274
+
275
+ return MemeInfoResponse(
276
+ key=meme.key,
277
+ params_type=MemeParamsResponse(
278
+ min_images=meme.params_type.min_images,
279
+ max_images=meme.params_type.max_images,
280
+ min_texts=meme.params_type.min_texts,
281
+ max_texts=meme.params_type.max_texts,
282
+ default_texts=meme.params_type.default_texts,
283
+ args_type=args_type_response,
284
+ ),
285
+ keywords=meme.keywords,
286
+ shortcuts=meme.shortcuts,
287
+ tags=meme.tags,
288
+ date_created=meme.date_created,
289
+ date_modified=meme.date_modified,
290
+ )
291
+
292
+ @app.get("/memes/{key}/preview")
293
+ async def _(key: str):
294
+ try:
295
+ meme = get_meme(key)
296
+ result = await run_sync(meme.generate_preview)()
297
+ except MemeGeneratorException as e:
298
+ raise HTTPException(status_code=e.status_code, detail=e.message)
299
+
300
+ content = result.getvalue()
301
+ media_type = str(filetype.guess_mime(content)) or "text/plain"
302
+ return Response(content=content, media_type=media_type)
303
+
304
+ for meme in sorted(get_memes(), key=lambda meme: meme.key):
305
+ register_router(meme)
306
+
307
+
308
+ def run_server():
309
+ import uvicorn
310
+
311
+ register_routers()
312
+ uvicorn.run(
313
+ app,
314
+ host=meme_config.server.host,
315
+ port=meme_config.server.port,
316
+ log_config=LOGGING_CONFIG,
317
+ )
318
+
319
+
320
+ if __name__ == "__main__":
321
+ setup_logger()
322
+ run_server()
meme_generator/cli.py ADDED
@@ -0,0 +1,238 @@
1
+ import asyncio
2
+ from pathlib import Path
3
+ from typing import Any
4
+
5
+ import filetype
6
+ from arclet.alconna import (
7
+ Alconna,
8
+ Args,
9
+ CommandMeta,
10
+ MultiVar,
11
+ Option,
12
+ Subcommand,
13
+ TextFormatter,
14
+ )
15
+ from arclet.alconna.exceptions import SpecialOptionTriggered
16
+ from arclet.alconna.tools import RichConsoleFormatter
17
+
18
+ from meme_generator.app import run_server
19
+ from meme_generator.config import meme_config
20
+ from meme_generator.download import check_resources
21
+ from meme_generator.exception import MemeGeneratorException, NoSuchMeme
22
+ from meme_generator.log import setup_logger
23
+ from meme_generator.manager import get_meme, get_memes
24
+
25
+
26
+ def construct_parser() -> Alconna:
27
+ sub_commands: list[Subcommand] = []
28
+ for meme in get_memes():
29
+ options: list[Option] = []
30
+ if args_type := meme.params_type.args_type:
31
+ for option in args_type.parser_options:
32
+ options.append(option.option())
33
+ sub_command = Subcommand(
34
+ meme.key,
35
+ *options,
36
+ Option(
37
+ "--images",
38
+ Args["images", MultiVar(str, "+")],
39
+ help_text="输入图片路径",
40
+ ),
41
+ Option("--texts", Args["texts", MultiVar(str, "+")], help_text="输入文字"),
42
+ help_text="/".join(meme.keywords),
43
+ )
44
+ sub_commands.append(sub_command)
45
+
46
+ parser = Alconna(
47
+ "meme",
48
+ Option(
49
+ "--config-file",
50
+ Args["config_file", str],
51
+ help_text="指定 config.toml 文件路径",
52
+ ),
53
+ Subcommand("list", alias=["ls"], help_text="查看表情列表"),
54
+ Subcommand(
55
+ "info", Args["key#表情名", str], alias=["show"], help_text="查看表情详情"
56
+ ),
57
+ Subcommand("preview", Args["key#表情名", str], help_text="生成表情预览"),
58
+ Subcommand("generate", *sub_commands, alias=["make"], help_text="制作表情"),
59
+ Subcommand(
60
+ "run",
61
+ Option(
62
+ "--config-file",
63
+ Args["config_file", str],
64
+ help_text="指定 config.toml 文件路径",
65
+ ),
66
+ alias=["start"],
67
+ help_text="启动 web server",
68
+ ),
69
+ Subcommand(
70
+ "download",
71
+ Option("--url", Args["url", str], help_text="指定资源链接"),
72
+ help_text="下载内置表情图片",
73
+ ),
74
+ meta=CommandMeta(
75
+ description="表情包生成器",
76
+ example="meme generate petpet --images /path/to/image/file",
77
+ ),
78
+ formatter_type=RichConsoleFormatter,
79
+ )
80
+ return parser
81
+
82
+
83
+ def list_memes() -> str:
84
+ memes = sorted(get_memes(), key=lambda meme: meme.key)
85
+ return "\n".join(
86
+ f"{i}. {meme.key} ({'/'.join(meme.keywords)})"
87
+ for i, meme in enumerate(memes, start=1)
88
+ )
89
+
90
+
91
+ def meme_info(key: str) -> str:
92
+ try:
93
+ meme = get_meme(key)
94
+ except NoSuchMeme:
95
+ return f'表情 "{key}" 不存在!'
96
+
97
+ keywords = "、".join([f'"{keyword}"' for keyword in meme.keywords])
98
+ shortcuts = "、".join(
99
+ [f'"{shortcut.humanized or shortcut.key}"' for shortcut in meme.shortcuts]
100
+ )
101
+ tags = "、".join([f'"{tag}"' for tag in sorted(meme.tags)])
102
+
103
+ image_num = f"{meme.params_type.min_images}"
104
+ if meme.params_type.max_images > meme.params_type.min_images:
105
+ image_num += f" ~ {meme.params_type.max_images}"
106
+
107
+ text_num = f"{meme.params_type.min_texts}"
108
+ if meme.params_type.max_texts > meme.params_type.min_texts:
109
+ text_num += f" ~ {meme.params_type.max_texts}"
110
+
111
+ default_texts = ", ".join([f'"{text}"' for text in meme.params_type.default_texts])
112
+
113
+ args_info = ""
114
+ if args_type := meme.params_type.args_type:
115
+ formater = TextFormatter()
116
+ for option in args_type.parser_options:
117
+ opt = option.option()
118
+ alias_text = (
119
+ " ".join(opt.requires)
120
+ + (" " if opt.requires else "")
121
+ + "│".join(sorted(opt.aliases, key=len))
122
+ )
123
+ args_info += (
124
+ f"\n * {alias_text}{opt.separators[0]}"
125
+ f"{formater.parameters(opt.args)} {opt.help_text}"
126
+ )
127
+
128
+ return (
129
+ f"表情名:{meme.key}\n"
130
+ + f"关键词:{keywords}\n"
131
+ + (f"快捷指令:{shortcuts}\n" if shortcuts else "")
132
+ + (f"标签:{tags}\n" if tags else "")
133
+ + f"需要图片数目:{image_num}\n"
134
+ + f"需要文字数目:{text_num}\n"
135
+ + (f"默认文字:[{default_texts}]\n" if default_texts else "")
136
+ + (f"其他参数:{args_info}" if args_info else "")
137
+ )
138
+
139
+
140
+ def generate_meme_preview(key: str) -> str:
141
+ try:
142
+ meme = get_meme(key)
143
+ except NoSuchMeme:
144
+ return f'表情 "{key}" 不存在!'
145
+
146
+ try:
147
+ result = meme.generate_preview()
148
+ content = result.getvalue()
149
+ ext = filetype.guess_extension(content)
150
+ filename = f"result.{ext}"
151
+ with open(filename, "wb") as f:
152
+ f.write(content)
153
+ return f'表情制作成功!生成的表情文件为 "{filename}"'
154
+ except MemeGeneratorException as e:
155
+ return str(e)
156
+
157
+
158
+ def generate_meme(
159
+ key: str, images: list[str], texts: list[str], args: dict[str, Any]
160
+ ) -> str:
161
+ try:
162
+ meme = get_meme(key)
163
+ except NoSuchMeme:
164
+ return f'表情 "{key}" 不存在!'
165
+
166
+ for image in images:
167
+ if not Path(image).exists():
168
+ return f'图片路径 "{image}" 不存在!'
169
+
170
+ try:
171
+ result = meme(images=images, texts=texts, args=args)
172
+ content = result.getvalue()
173
+ ext = filetype.guess_extension(content)
174
+ filename = f"result.{ext}"
175
+ with open(filename, "wb") as f:
176
+ f.write(content)
177
+ return f'表情制作成功!生成的表情文件为 "{filename}"'
178
+ except MemeGeneratorException as e:
179
+ return str(e)
180
+
181
+
182
+ def main():
183
+ setup_logger()
184
+ parser = construct_parser()
185
+ result = parser()
186
+
187
+ if not result.matched:
188
+ if not isinstance(result.error_info, SpecialOptionTriggered):
189
+ print(result.error_info) # noqa: T201
190
+ return
191
+
192
+ if not result.subcommands:
193
+ print(parser.get_help()) # noqa: T201
194
+
195
+ for subcommand, sub_result in result.subcommands.items():
196
+ if subcommand == "list":
197
+ print(list_memes()) # noqa: T201
198
+
199
+ elif subcommand == "info":
200
+ key = str(sub_result.args["key"])
201
+ print(meme_info(key)) # noqa: T201
202
+
203
+ elif subcommand == "preview":
204
+ key = str(sub_result.args["key"])
205
+ print(generate_meme_preview(key)) # noqa: T201
206
+
207
+ elif subcommand == "generate":
208
+ for key, subsub_result in sub_result.subcommands.items():
209
+ images = (
210
+ list(subsub_result.options["images"].args["images"])
211
+ if "images" in subsub_result.options
212
+ else []
213
+ )
214
+ texts = (
215
+ list(subsub_result.options["texts"].args["texts"])
216
+ if "texts" in subsub_result.options
217
+ else []
218
+ )
219
+ options = subsub_result.options
220
+ options.pop("images", None)
221
+ options.pop("texts", None)
222
+ args = {}
223
+ for option, option_result in options.items():
224
+ if option_result.value is None:
225
+ args.update(option_result.args)
226
+ else:
227
+ args[option] = option_result.value
228
+ print(generate_meme(key, images, texts, args)) # noqa: T201
229
+
230
+ elif subcommand == "run":
231
+ run_server()
232
+
233
+ elif subcommand == "download":
234
+ if "url" in sub_result.options:
235
+ url = sub_result.options["url"].args["url"]
236
+ meme_config.resource.resource_url = url
237
+ loop = asyncio.new_event_loop()
238
+ loop.run_until_complete(check_resources())