├── tests
└── __init__.py
├── src
└── telegram_sticker_utils
│ ├── core
│ ├── __init__.py
│ ├── _sort.py
│ ├── const.py
│ └── rules.json
│ └── __init__.py
├── playground
├── 300_ori.gif
├── 300_ori.png
├── 512_ori.gif
├── 512_ori.png
├── output
│ ├── 300_ori.png
│ ├── 300_ori.webm
│ ├── 512_ori.png
│ └── 512_ori.webm
└── resize.py
├── .github
└── workflows
│ └── publish.yml
├── pyproject.toml
├── README.md
├── .gitignore
├── LICENSE
└── pdm.lock
/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/telegram_sticker_utils/core/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/playground/300_ori.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudoskys/telegram-sticker-utils/HEAD/playground/300_ori.gif
--------------------------------------------------------------------------------
/playground/300_ori.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudoskys/telegram-sticker-utils/HEAD/playground/300_ori.png
--------------------------------------------------------------------------------
/playground/512_ori.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudoskys/telegram-sticker-utils/HEAD/playground/512_ori.gif
--------------------------------------------------------------------------------
/playground/512_ori.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudoskys/telegram-sticker-utils/HEAD/playground/512_ori.png
--------------------------------------------------------------------------------
/playground/output/300_ori.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudoskys/telegram-sticker-utils/HEAD/playground/output/300_ori.png
--------------------------------------------------------------------------------
/playground/output/300_ori.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudoskys/telegram-sticker-utils/HEAD/playground/output/300_ori.webm
--------------------------------------------------------------------------------
/playground/output/512_ori.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudoskys/telegram-sticker-utils/HEAD/playground/output/512_ori.png
--------------------------------------------------------------------------------
/playground/output/512_ori.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sudoskys/telegram-sticker-utils/HEAD/playground/output/512_ori.webm
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: publish
2 | on:
3 | workflow_dispatch:
4 | push:
5 | tags:
6 | - pypi*
7 |
8 | permissions:
9 | contents: read
10 |
11 | jobs:
12 | pypi-publish:
13 | name: upload release to PyPI
14 | runs-on: ubuntu-latest
15 | permissions:
16 | # IMPORTANT: this permission is mandatory for trusted publishing
17 | id-token: write
18 | steps:
19 | - uses: actions/checkout@v3
20 |
21 | - uses: pdm-project/setup-pdm@v3
22 |
23 | - name: Publish package distributions to PyPI
24 | run: pdm publish
25 |
--------------------------------------------------------------------------------
/src/telegram_sticker_utils/core/_sort.py:
--------------------------------------------------------------------------------
1 | if __name__ == "__main__":
2 | import json
3 |
4 | json_file_path = "rules.json"
5 | # 存储键值对的 JSON 文件
6 |
7 | with open(json_file_path, "r", encoding="utf-8") as json_file:
8 | data = json.load(json_file)
9 |
10 | sorted_data = {k: data[k] for k in sorted(data.keys(), key=lambda k: len(k), reverse=True)}
11 |
12 | print("按照键长度降序排序后的结果:")
13 | print(json.dumps(sorted_data, ensure_ascii=False, indent=4))
14 |
15 | output_file_path = "rules_sorted.json"
16 | with open(output_file_path, "w", encoding="utf-8") as output_file:
17 | json.dump(sorted_data, output_file, ensure_ascii=False, indent=4)
18 | print(f"排序后的 JSON 文件已保存到: {output_file_path}")
19 |
20 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "telegram-sticker-utils"
3 | version = "0.2.24"
4 | description = "easy and fast processing of telegram stickers"
5 | authors = [
6 | { name = "sudoskys", email = "coldlando@hotmail.com" },
7 | ]
8 | dependencies = [
9 | "loguru>=0.7.2",
10 | "Wand>=0.6.13",
11 | "Pillow>=6.2.0",
12 | "ffmpy>=0.3.2",
13 | "emoji>=2.12.1",
14 | "setuptools>=60.0.0",
15 | "magika>=0.5.1",
16 | "moviepy>=1.0.3",
17 | ]
18 | requires-python = ">=3.9"
19 | readme = "README.md"
20 | license = { text = "Apache-2.0" }
21 | keywords = ["telegram", "sticker", "image", "video", "processing", "ffmpeg", "pillow"]
22 |
23 | [project.urls]
24 | Repository = "https://github.com/sudoskys/telegram-sticker-utils"
25 |
26 |
27 | [build-system]
28 | requires = ["pdm-backend"]
29 | build-backend = "pdm.backend"
30 |
31 |
32 | [tool.pdm]
33 | distribution = true
34 |
--------------------------------------------------------------------------------
/playground/resize.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | from loguru import logger
4 |
5 | from telegram_sticker_utils import ImageProcessor
6 | from telegram_sticker_utils.core.const import add_emoji_rule
7 |
8 | logger.remove(0)
9 | handler_id = logger.add(
10 | sys.stderr,
11 | format="[{level}] | {message} | "
12 | "{name}:{function}:{line} @{time}",
13 | colorize=True,
14 | backtrace=True,
15 | enqueue=True,
16 | level="TRACE",
17 | )
18 | add_emoji_rule("sad", "😢")
19 | sticker = ImageProcessor.make_sticker(
20 | input_name='私密马赛',
21 | input_data=open("酷酷.gif", 'rb').read(),
22 | scale=512,
23 | master_edge='width',
24 | )
25 | print(sticker.sticker_type)
26 | print(sticker.emojis)
27 | with open(f"output.{sticker.file_extension}", 'wb') as f:
28 | f.write(sticker.data)
29 |
30 | import mimetypes
31 |
32 |
33 | def validate_webm(file_path):
34 | mime_type, _ = mimetypes.guess_type(file_path)
35 | if mime_type != 'video/webm':
36 | raise ValueError(f"Expected 'video/webm', but got '{mime_type}'")
37 | print("The file is a valid WebM video.")
38 |
39 |
40 | # Example usage
41 | validate_webm('output.webm')
42 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 📦 Telegram Sticker Utils SDK
2 |
3 | [](https://badge.fury.io/py/telegram-sticker-utils)
4 | [](https://pepy.tech/project/telegram-sticker-utils)
5 |
6 | If you are not a developer, you can use the Telegram Sticker [CLI](https://github.com/sudoskys/tsticker) (developed by
7 | this SDK) for
8 | user-friendly operations.
9 |
10 | This SDK provides a set of utilities for working with Telegram stickers.
11 |
12 | - Convert image formats without losing transparency.
13 | - Auto optimize output size for sticker, make it valid for Telegram.
14 | - Auto-detect sticker type and emojis.
15 |
16 | ## 🛠 Supported Pack Types
17 |
18 | - [x] Video Sticker
19 | - [x] Static Sticker
20 | - [ ] Animated Sticker(Tgs)
21 |
22 | ## 🚀 Installation
23 |
24 | You need install **[ImageMagick](https://github.com/imagemagick/imagemagick)** and
25 | **[ffmpeg](https://www.ffmpeg.org/download.html)** before using this SDK.
26 |
27 | Install Guide: https://docs.wand-py.org/en/0.6.12/guide/install.html
28 |
29 | ```shell
30 | apt install ffmpeg
31 | pip3 install telegram-sticker-utils
32 | ```
33 |
34 | ## 📖 Usage
35 |
36 | ```python
37 | import emoji
38 |
39 | from telegram_sticker_utils import ImageProcessor
40 | from telegram_sticker_utils import is_animated_gif
41 | from telegram_sticker_utils.core.const import add_emoji_rule
42 |
43 | try:
44 | add_emoji_rule("sad", "😢")
45 | add_emoji_rule("happy", emoji.emojize(":smile:"))
46 | except ValueError as e:
47 | print("NOT emoji")
48 |
49 | print(is_animated_gif('test.gif')) # Path to the image file or a file-like object.
50 |
51 | for sticker_file in ["happy.webp", "sad.png", "高兴.jpg", "悲伤.gif"]:
52 | sticker = ImageProcessor.make_sticker(
53 | input_name=sticker_file,
54 | input_data=open(sticker_file, 'rb').read(),
55 | scale=512
56 | )
57 | print(sticker.sticker_type)
58 | print(sticker.emojis)
59 | with open(f"{sticker_file}.{sticker.file_extension}", 'wb') as f:
60 | f.write(sticker.data)
61 | ```
--------------------------------------------------------------------------------
/src/telegram_sticker_utils/core/const.py:
--------------------------------------------------------------------------------
1 | import json
2 | import random
3 | from pathlib import Path
4 | from typing import Optional
5 |
6 | import emoji
7 | from loguru import logger
8 |
9 | SERVICE_NAME = "TStickerService"
10 | USERNAME = "telegram"
11 |
12 | # 读取规则,本文件目录下的rules.json
13 | rule_file = Path(__file__).parent / "rules.json"
14 | EMOJI_RULES = json.loads(rule_file.read_text(encoding='utf-8'))
15 |
16 |
17 | def add_emoji_rule(rule: str, emoji_char: str):
18 | """
19 | 添加一个emoji规则
20 | :param rule: 规则
21 | :param emoji_char: emoji字符
22 | :raises ValueError: 如果生成的emoji不受支持
23 | """
24 | # 判断是否是 emoji
25 | if not emoji.is_emoji(emoji_char):
26 | raise ValueError(f"Emoji {emoji_char} is not supported")
27 | EMOJI_RULES[rule] = emoji_char
28 |
29 |
30 | def target_emoji(text: str) -> Optional[str]:
31 | """
32 | 判断是否是目标emoji
33 | :param text: 输入的字符串
34 | :return: 生成的emoji字符
35 | """
36 | # 遍历规则,查找键是否在文本中
37 | for rule, emj in EMOJI_RULES.items():
38 | if rule.lower() in text.lower():
39 | return emj
40 | return None
41 |
42 |
43 | def get_random_emoji_from_text(
44 | text: str,
45 | fallback_emoji: str = None
46 | ) -> str:
47 | """
48 | 从给定的文本中提取字母并根据映射规则生成随机emoji。
49 | 如果文本中没有匹配的字符,则返回默认emoji(❤️)。如果生成的emoji不受支持,则引发ValueError。
50 | :param text: 输入的字符串
51 | :param fallback_emoji: 默认的emoji字符
52 | :return: 生成的emoji字符
53 | """
54 | if fallback_emoji is None:
55 | fallback_emoji = '\N{HEAVY BLACK HEART}'
56 | emoji_candidates = []
57 | # 仅处理文本中下划线后的部分
58 | if "_" in text:
59 | text = text.split("_")[-1]
60 |
61 | # 遍历规则,查找键是否在文本中
62 | find_emoji = target_emoji(text)
63 | if find_emoji:
64 | emoji_candidates.append(find_emoji)
65 |
66 | # 未找到匹配字符使用默认emoji
67 | if not emoji_candidates:
68 | selected_emoji = fallback_emoji
69 | else:
70 | selected_emoji = random.choice(emoji_candidates)
71 |
72 | # 处理和确认emoji是有效的
73 | selected_emoji = emoji.emojize(emoji.demojize(selected_emoji.strip()))
74 | if not emoji.is_emoji(selected_emoji):
75 | logger.warning(f"Emoji {selected_emoji} is not supported")
76 | selected_emoji = fallback_emoji
77 | return selected_emoji
78 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # poetry
98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99 | # This is especially recommended for binary packages to ensure reproducibility, and is more
100 | # commonly ignored for libraries.
101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102 | #poetry.lock
103 |
104 | # pdm
105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106 | #pdm.lock
107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108 | # in version control.
109 | # https://pdm.fming.dev/#use-with-ide
110 | .pdm.toml
111 |
112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113 | __pypackages__/
114 |
115 | # Celery stuff
116 | celerybeat-schedule
117 | celerybeat.pid
118 |
119 | # SageMath parsed files
120 | *.sage.py
121 |
122 | # Environments
123 | .env
124 | .venv
125 | env/
126 | venv/
127 | ENV/
128 | env.bak/
129 | venv.bak/
130 |
131 | # Spyder project settings
132 | .spyderproject
133 | .spyproject
134 |
135 | # Rope project settings
136 | .ropeproject
137 |
138 | # mkdocs documentation
139 | /site
140 |
141 | # mypy
142 | .mypy_cache/
143 | .dmypy.json
144 | dmypy.json
145 |
146 | # Pyre type checker
147 | .pyre/
148 |
149 | # pytype static type analyzer
150 | .pytype/
151 |
152 | # Cython debug symbols
153 | cython_debug/
154 |
155 | # PyCharm
156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158 | # and can be added to the global gitignore or merged into this file. For a more nuclear
159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160 | #.idea/
161 | /.pdm-python
162 | /src/telegram_sticker_utils/core/rules_sorted.json
163 |
164 | .idea/
165 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/src/telegram_sticker_utils/core/rules.json:
--------------------------------------------------------------------------------
1 | {
2 | "disappointed": "😔",
3 | "embarrassed": "😳",
4 | "confident": "😎",
5 | "Fighting": "💪",
6 | "surprise": "😲",
7 | "confused": "🤔",
8 | "relieved": "😌",
9 | "prefect": "👍",
10 | "disgust": "🤢",
11 | "excited": "😆",
12 | "relaxed": "😌",
13 | "ciallo": "👋",
14 | "imfine": "😄",
15 | "haqian": "🤔",
16 | "sensei": "💖",
17 | "guilty": "😳",
18 | "GREAT": "👍",
19 | "jesus": "🙏",
20 | "happy": "😄",
21 | "angry": "😡",
22 | "sleep": "😴",
23 | "smile": "😄",
24 | "laugh": "😄",
25 | "proud": "😏",
26 | "shame": "😳",
27 | "bored": "😐",
28 | "tired": "😴",
29 | "哔哩哔哩": "📺",
30 | "suki": "❤️",
31 | "wink": "😉",
32 | "rich": "💰",
33 | "like": "👍",
34 | "help": "🆘",
35 | "nice": "👍",
36 | "yyds": "👍",
37 | "rush": "🏃",
38 | "prpr": "👅",
39 | "fxxk": "🤬",
40 | "奇迹行者": "👨",
41 | "KING": "👑",
42 | "kiss": "💋",
43 | "fear": "😱",
44 | "sick": "🤢",
45 | "pain": "🤕",
46 | "owo": "😳",
47 | "uwu": "🥺",
48 | "QAQ": "😭",
49 | "QwQ": "😭",
50 | "TAT": "😭",
51 | "T^T": "😭",
52 | "T.T": "😭",
53 | "剩条命": "❤️",
54 | "吉瑟斯": "🙏",
55 | "三原色": "🎨",
56 | "斯ki": "❤️",
57 | "Hum": "🤔",
58 | "哥布林": "👹",
59 | "服务器": "🖥️",
60 | "biu": "💨",
61 | "薛定谔": "🐱",
62 | "私密马": "🙇",
63 | "私密吗": "🙇",
64 | "8扯住": "🔚",
65 | "csp": "🎨",
66 | "独角兽": "🦄",
67 | "过过过": "🔄",
68 | "cpu": "🖥️",
69 | "886": "👋",
70 | "发际线": "👴",
71 | "一边去": "👉",
72 | "icu": "🏥",
73 | "林黛玉": "👩",
74 | "run": "🏃",
75 | "麦当劳": "🍔",
76 | "方便面": "🍜",
77 | "蜀道山": "🔢",
78 | "妖妖灵": "👮",
79 | "幺幺零": "👮",
80 | "妖妖零": "👮",
81 | "sad": "😭",
82 | "cry": "😭",
83 | "shy": "😳",
84 | "投降": "🏳️",
85 | "抱歉": "🙇",
86 | "鼓掌": "👏",
87 | "加油": "💪",
88 | "鼓励": "👍",
89 | "拜托": "🙏",
90 | "谢谢": "🙏",
91 | "再见": "👋",
92 | "挠头": "🤔",
93 | "红温": "🌡️",
94 | "看书": "📚",
95 | "爆炸": "💥",
96 | "可乐": "🥤",
97 | "文盲": "📚",
98 | "乱叫": "🤬",
99 | "萝卜": "🥕",
100 | "早早": "🌞",
101 | "黑线": "😅",
102 | "命运": "🎲",
103 | "狗头": "🐶",
104 | "照相": "📸",
105 | "道歉": "🙇",
106 | "周末": "🎉",
107 | "团建": "🎉",
108 | "命苦": "😭",
109 | "装逼": "🤥",
110 | "梦想": "💭",
111 | "实际": "🤔",
112 | "法棍": "🥖",
113 | "少用": "🚫",
114 | "泥嚎": "👋",
115 | "社恐": "🫣",
116 | "减肥": "🏋️",
117 | "审判": "⚖️",
118 | "磨叽": "😅",
119 | "香蕉": "🍌",
120 | "卧槽": "😱",
121 | "狗屎": "💩",
122 | "卧巢": "😱",
123 | "被窝": "🛌",
124 | "颤抖": "😨",
125 | "懒猫": "🐱",
126 | "阴性": "♀️",
127 | "突突": "🔫",
128 | "揶揄": "😏",
129 | "呦呦": "🦌",
130 | "补钙": "🥛",
131 | "猛男": "👨",
132 | "分裂": "🔪",
133 | "虔诚": "🙏",
134 | "一半": "🔚",
135 | "伤害": "🔪",
136 | "大招": "🔥",
137 | "狼人": "🐺",
138 | "设计": "🎨",
139 | "拍照": "📸",
140 | "正义": "⚖️",
141 | "上号": "👋",
142 | "沧桑": "🌊",
143 | "错过": "🔄",
144 | "掉线": "📶",
145 | "端午": "🍙",
146 | "太阳": "🌞",
147 | "信息": "📩",
148 | "消息": "📩",
149 | "清明": "🌸",
150 | "主意": "💡",
151 | "喜欢": "❤️",
152 | "胖次": "🩲",
153 | "学校": "🏫",
154 | "黑化": "🖤",
155 | "细节": "🔍",
156 | "从众": "🐑",
157 | "维护": "🔧",
158 | "灵感": "💡",
159 | "情绪": "😭",
160 | "游戏": "🎮",
161 | "总裁": "👔",
162 | "游过": "🏊",
163 | "折磨": "😭",
164 | "画画": "🎨",
165 | "流浪": "🐕",
166 | "矜持": "😏",
167 | "知道": "✅",
168 | "完美": "👌",
169 | "发四": "🎉",
170 | "绘画": "🎨",
171 | "运动": "🏃",
172 | "阴暗": "🌚",
173 | "鼻涕": "🤧",
174 | "公益": "🌍",
175 | "保证": "🤝",
176 | "熄灭": "💡",
177 | "登录": "🔑",
178 | "接受": "🤝",
179 | "文明": "🏛️",
180 | "做掉": "🔪",
181 | "大人": "👨",
182 | "乖乖": "👶",
183 | "rd": "🔴",
184 | "沙发": "🛋️",
185 | "年货": "🧧",
186 | "喷水": "💦",
187 | "上线": "📶",
188 | "南通": "👬",
189 | "男同": "👬",
190 | "签到": "📝",
191 | "借一": "👉",
192 | "功课": "📚",
193 | "咖啡": "☕",
194 | "大便": "💩",
195 | "突破": "🔨",
196 | "目标": "🎯",
197 | "上床": "🛏️",
198 | "露馅": "🤥",
199 | "艾伦": "👨",
200 | "炒股": "💰",
201 | "一定": "👍",
202 | "记得": "📝",
203 | "浑身": "👤",
204 | "智慧": "🧠",
205 | "借过": "👉",
206 | "摄入": "🍽️",
207 | "传奇": "📖",
208 | "大方": "👍",
209 | "回见": "👋",
210 | "紫砂": "💀",
211 | "楼上": "👆",
212 | "楼下": "👇",
213 | "荒谬": "🤪",
214 | "热狗": "🌭",
215 | "姿势": "🤔",
216 | "萧条": "📉",
217 | "注意": "⚠️",
218 | "小心": "⚠️",
219 | "胸脯": "👚",
220 | "崩铁": "🎮",
221 | "原神": "🎮",
222 | "哥哥": "👨",
223 | "上吊": "🔗",
224 | "检查": "🔍",
225 | "注销": "🔒",
226 | "调试": "🔧",
227 | "拜拜": "👋",
228 | "白白": "👋",
229 | "泡汤": "🍜",
230 | "奈斯": "👍",
231 | "依托": "💩",
232 | "太多": "🔚",
233 | "诱惑": "🩷",
234 | "世界": "🌍",
235 | "长大": "👨",
236 | "瑜伽": "🧘",
237 | "申请": "📝",
238 | "学霸": "📚",
239 | "纸鹤": "🕊️",
240 | "崆峒": "👬",
241 | "战术": "🔪",
242 | "聪明": "🧠",
243 | "不行": "🙅",
244 | "拉拉": "👭",
245 | "同意": "👍",
246 | "姐姐": "👩",
247 | "浇浇": "📚",
248 | "病毒": "🦠",
249 | "变装": "👗",
250 | "勿扰": "🚫",
251 | "去世": "💀",
252 | "趋势": "💀",
253 | "单身": "👤",
254 | "紧张": "😰",
255 | "差评": "👎",
256 | "热热": "🔥",
257 | "考古": "🔍",
258 | "考试": "📝",
259 | "蔚蓝": "🌊",
260 | "弟弟": "👦",
261 | "上吧": "👆",
262 | "黄油": "🧈",
263 | "色情": "🔞",
264 | "艺术": "🎨",
265 | "项圈": "🔗",
266 | "高能": "💥",
267 | "遥控": "📺",
268 | "划水": "🏊",
269 | "遨油": "🚗",
270 | "服从": "👍",
271 | "轮椅": "♿",
272 | "破防": "🔨",
273 | "大叔": "👨",
274 | "上学": "🏫",
275 | "烦恼": "😤",
276 | "诅咒": "🔮",
277 | "先锋": "👨",
278 | "垃圾": "🚮",
279 | "共识": "🤝",
280 | "舔狗": "🐶",
281 | "水产": "🐟",
282 | "输入": "🔍",
283 | "公交": "🚌",
284 | "等等": "🔄",
285 | "清理": "🧹",
286 | "联系": "📞",
287 | "弱智": "🤪",
288 | "若至": "🤪",
289 | "赋能": "🔋",
290 | "啤酒": "🍺",
291 | "偶像": "💖",
292 | "报废": "🚮",
293 | "行李": "🛄",
294 | "饶命": "🙏",
295 | "分享": "🔄",
296 | "过年": "🎉",
297 | "蛄蛹": "🐛",
298 | "儿童": "👶",
299 | "国一": "👍",
300 | "马桶": "🚽",
301 | "吉他": "🎸",
302 | "狗粮": "🐶",
303 | "杂耍": "🤹",
304 | "调解": "🤝",
305 | "森林": "🌲",
306 | "亲亲": "💋",
307 | "十字": "✝️",
308 | "阴影": "🌚",
309 | "证据": "🔍",
310 | "杂技": "🤹",
311 | "巡逻": "👮",
312 | "伟大": "👍",
313 | "布丁": "🍮",
314 | "钻石": "💎",
315 | "宝宝": "👶",
316 | "和解": "🤝",
317 | "路过": "🚶",
318 | "死啊": "💀",
319 | "西内": "💀",
320 | "装弹": "🔫",
321 | "像素": "🔍",
322 | "瑟瑟": "🔞",
323 | "色色": "🔞",
324 | "衣服": "👕",
325 | "便便": "💩",
326 | "知识": "📚",
327 | "支付": "💳",
328 | "缠绕": "🔄",
329 | "字典": "📚",
330 | "上帝": "🙏",
331 | "团圆": "🎉",
332 | "鸿图": "🎨",
333 | "理解": "🤔",
334 | "啥": "🤔",
335 | "叼": "🥺",
336 | "急": "😰",
337 | "怒": "😡",
338 | "怨": "😤",
339 | "怪": "🤪",
340 | "怯": "😨",
341 | "怵": "😰",
342 | "怼": "🤬",
343 | "恁": "🤨",
344 | "恶": "😈",
345 | "恸": "😭",
346 | "恹": "😴",
347 | "恺": "😄",
348 | "恻": "😢",
349 | "恼": "😡",
350 | "恽": "😔",
351 | "悄": "🤫",
352 | "悚": "😱",
353 | "爱": "❤️",
354 | "爷": "👴",
355 | "睡": "😴",
356 | "瞅": "👀",
357 | "瞎": "🙈",
358 | "瞒": "🙈",
359 | "爬": "🐍",
360 | "爸": "👨",
361 | "疯": "🤪",
362 | "疼": "🤕",
363 | "啊": "😮",
364 | "嗯": "🤨",
365 | "温": "🌡️",
366 | "渴": "🥤",
367 | "笑": "😄",
368 | "迎": "🤗",
369 | "贺": "🎉",
370 | "福": "🎉",
371 | "哼": "😏",
372 | "唉": "😔",
373 | "走": "🚶",
374 | "跑": "🏃",
375 | "斗": "🥊",
376 | "跳": "🤸",
377 | "好": "👍",
378 | "哇": "😲",
379 | "坏": "👎",
380 | "摔": "🤕",
381 | "望": "👀",
382 | "不": "🚫",
383 | "瞄": "👀",
384 | "嘤": "😭",
385 | "喜": "😄",
386 | "嘛": "😏",
387 | "惊": "😱",
388 | "惜": "😢",
389 | "惭": "😳",
390 | "惨": "😢",
391 | "惯": "🤪",
392 | "惩": "👿",
393 | "惫": "😴",
394 | "惮": "😔",
395 | "尾": "🐾",
396 | "尿": "🚽",
397 | "释": "🔓",
398 | "我": "🤗",
399 | "来": "👧",
400 | "啦": "👧",
401 | "慌": "😰",
402 | "逊": "😔",
403 | "逗": "😄",
404 | "警": "🚨",
405 | "讨": "🤔",
406 | "困": "😴",
407 | "围": "🤗",
408 | "抱": "🤗",
409 | "拥": "🤗",
410 | "择": "🤔",
411 | "括": "🤔",
412 | "没": "🚫",
413 | "什": "🤔",
414 | "锤": "🔨",
415 | "捶": "🔨",
416 | "栗": "🌰",
417 | "压": "🫠",
418 | "厨": "🍳",
419 | "工": "🔨",
420 | "迷": "😵",
421 | "声": "📢",
422 | "逢": "😆",
423 | "肉": "🍖",
424 | "肚": "🤰",
425 | "翅": "🦅",
426 | "柳": "🌿",
427 | "霉": "🍞",
428 | "针": "💉",
429 | "耳": "👂",
430 | "刀": "🔪",
431 | "晒": "🌞",
432 | "踩": "👟",
433 | "狠": "👿",
434 | "箭": "🏹",
435 | "偷": "👀",
436 | "门": "🚪",
437 | "猪": "🐷",
438 | "猴": "🐒",
439 | "羊": "🐑",
440 | "胜": "🏆",
441 | "蝶": "🦋",
442 | "险": "⚠️",
443 | "A": "🅰️",
444 | "饱": "🍔",
445 | "桔": "🍊",
446 | "六": "6️⃣",
447 | "良": "👍",
448 | "冤": "😭",
449 | "吗": "❔",
450 | "王": "👑",
451 | "车": "🚗",
452 | "扫": "🧹",
453 | "召": "📢",
454 | "魂": "👻",
455 | "岗": "👷",
456 | "政": "🏛️",
457 | "坑": "🕳️",
458 | "常": "🔄",
459 | "臭": "🤢",
460 | "尬": "😳",
461 | "伤": "🤕",
462 | "休": "😴",
463 | "壁": "🧱",
464 | "兄": "👬",
465 | "优": "👍",
466 | "卡": "💳",
467 | "嚎": "🐺",
468 | "咆": "🐯",
469 | "壮": "🏋️",
470 | "龙": "🐉",
471 | "春": "🌸",
472 | "洗": "🧼",
473 | "撒": "🤚",
474 | "窝": "🏠",
475 | "独": "🕴️",
476 | "泳": "🏊",
477 | "遇": "🤝",
478 | "都": "👍",
479 | "舐": "👅",
480 | "铃": "🔔",
481 | "葱": "🧅",
482 | "盯": "👀",
483 | "呔": "🚫",
484 | "伞": "☔",
485 | "浪": "🌊",
486 | "券": "💳",
487 | "鼻": "👃",
488 | "菇": "🍄",
489 | "月": "🌙",
490 | "牵": "🤝",
491 | "激": "🔥",
492 | "热": "🔥",
493 | "晕": "😵",
494 | "掐": "👌",
495 | "畏": "😨",
496 | "毒": "🐍",
497 | "闲": "😴",
498 | "袖": "👕",
499 | "尸": "💀",
500 | "卦": "🔮",
501 | "扔": "⚡",
502 | "改": "🔄",
503 | "药": "💊",
504 | "酱": "🍯",
505 | "毛": "🐱",
506 | "罪": "👿",
507 | "退": "🚪",
508 | "姦": "👉",
509 | "沙": "🏖️",
510 | "否": "🚫",
511 | "痛": "😭",
512 | "橙": "🍊",
513 | "情": "🩷",
514 | "唔": "🤔",
515 | "兔": "🐰",
516 | "反": "🔄",
517 | "唠": "🗣️",
518 | "德": "🇩",
519 | "舔": "👅",
520 | "有": "👍",
521 | "帽": "👒",
522 | "觉": "😴",
523 | "楼": "🏢",
524 | "补": "🔋",
525 | "凉": "🌬️",
526 | "摁": "🔒",
527 | "蔽": "🚫",
528 | "投": "🎯",
529 | "球": "🏀",
530 | "溃": "😭",
531 | "乱": "🔄",
532 | "颈": "🦢",
533 | "虾": "🦐",
534 | "魅": "😏",
535 | "忏": "🙏",
536 | "论": "📜",
537 | "河": "🏞️",
538 | "是": "👍",
539 | "给": "🤝",
540 | "讲": "🗣️",
541 | "想": "💡",
542 | "追": "🏃",
543 | "塔": "🗼",
544 | "三": "3️⃣",
545 | "撼": "😱",
546 | "报": "📰",
547 | "鉴": "🔍",
548 | "泣": "😭",
549 | "泡": "🛁",
550 | "屎": "💩",
551 | "汉": "👨",
552 | "洁": "🧼",
553 | "奏": "🎵",
554 | "犸": "🐘",
555 | "奸": "👉",
556 | "似": "💀",
557 | "兽": "🐯",
558 | "捅": "🔪",
559 | "皮": "👞",
560 | "败": "👎",
561 | "圆": "🔵",
562 | "厕": "🚽",
563 | "纸": "🧻",
564 | "鹿": "🦌",
565 | "食": "🍔",
566 | "立": "🚶",
567 | "污": "🚮",
568 | "灯": "💡",
569 | "选": "🔍",
570 | "肥": "🐷",
571 | "船": "🚢",
572 | "人": "👤",
573 | "咚": "🚪",
574 | "顺": "👍",
575 | "钩": "🎣",
576 | "测": "🔍",
577 | "鄙": "😒",
578 | "暖": "🌞",
579 | "盾": "🛡️",
580 | "怂": "😨",
581 | "煤": "🏭",
582 | "仙": "🧙",
583 | "官": "👮",
584 | "光": "💡",
585 | "修": "🔧",
586 | "画": "🎨",
587 | "萌": "😊",
588 | "争": "⚔️",
589 | "遨": "🚗",
590 | "聋": "👂",
591 | "瘦": "👖",
592 | "学": "📚",
593 | "爽": "😄",
594 | "演": "🎭",
595 | "戏": "🎭",
596 | "蠢": "🤪",
597 | "闷": "😔",
598 | "漏": "👚",
599 | "胁": "🔪",
600 | "档": "📁",
601 | "踢": "👟",
602 | "踹": "👟",
603 | "白": "⚪",
604 | "记": "📝",
605 | "佛": "🙏",
606 | "智": "🧠",
607 | "钞": "💰",
608 | "啤": "🍺",
609 | "锅": "🍳",
610 | "稿": "📝",
611 | "卜": "🔮",
612 | "皇": "👑",
613 | "祟": "👻",
614 | "砍": "🔪",
615 | "棍": "🥖",
616 | "键": "⌨️",
617 | "雨": "🌧️",
618 | "汤": "🍲",
619 | "马": "🐴",
620 | "宅": "🏠",
621 | "叶": "🍃",
622 | "椰": "🥥",
623 | "桶": "🛢️",
624 | "瓶": "🍾",
625 | "蛇": "🐍",
626 | "揍": "👊",
627 | "亲": "🤗",
628 | "存": "💾",
629 | "吼": "📢",
630 | "丑": "👹",
631 | "肝": "🍖",
632 | "甜": "🍬",
633 | "童": "👶",
634 | "探": "👀",
635 | "跪": "🙏",
636 | "漂": "🏊",
637 | "麦": "🌾",
638 | "雷": "💣",
639 | "袜": "🧦",
640 | "鸥": "🕊️",
641 | "牌": "🃏",
642 | "救": "🆘",
643 | "善": "👍",
644 | "美": "🌸",
645 | "悲": "😭",
646 | "床": "🛏️",
647 | "鸿": "🦢",
648 | "病": "🤒",
649 | "酒": "🍺",
650 | "喝": "🍺",
651 | "醉": "🍺",
652 | "吃": "🍔",
653 | "饭": "🍔",
654 | "饿": "🍔",
655 | "吐": "🤮",
656 | "吸": "🚬",
657 | "吹": "🎺",
658 | "吻": "💋",
659 | "嗨": "👋",
660 | "嗦": "🤔",
661 | "鼓": "👍",
662 | "鼠": "🐭",
663 | "哭": "😭",
664 | "哄": "🤔",
665 | "哗": "🤔",
666 | "响": "🤔",
667 | "哑": "🤔",
668 | "哟": "🤔",
669 | "空": "🤔",
670 | "无": "🚫",
671 | "暴": "🤬",
672 | "呆": "😐",
673 | "呀": "😮",
674 | "呃": "😮",
675 | "呗": "😮",
676 | "拜": "🙏",
677 | "呜": "😭",
678 | "可": "👌",
679 | "加": "➕",
680 | "又": "🔁",
681 | "叉": "🔁",
682 | "力": "💪",
683 | "羞": "😳",
684 | "翻": "🔄",
685 | "嫌": "😒",
686 | "嫁": "👰",
687 | "嫉": "😒",
688 | "嫂": "👰",
689 | "嫩": "👶",
690 | "问": "🤔",
691 | "赞": "👍",
692 | "赢": "🏆",
693 | "赌": "🎲",
694 | "赏": "👏",
695 | "懒": "😴",
696 | "懂": "🤔",
697 | "懈": "😴",
698 | "懊": "😔",
699 | "别": "🚫",
700 | "强": "💪",
701 | "喵": "🐱",
702 | "气": "😤",
703 | "犯": "🚔",
704 | "犬": "🐶",
705 | "汪": "🐶",
706 | "汁": "🥤",
707 | "汇": "🤝",
708 | "丢": "🚮",
709 | "看": "👀",
710 | "关": "🔒",
711 | "闭": "🔒",
712 | "开": "🔓",
713 | "奋": "💪",
714 | "送": "🎁",
715 | "礼": "🎁",
716 | "祝": "🎉",
717 | "禁": "🚫",
718 | "囊": "👜",
719 | "思": "🤔",
720 | "恩": "🙏",
721 | "恭": "🙏",
722 | "心": "❤️",
723 | "忧": "😔",
724 | "快": "😄",
725 | "念": "🤔",
726 | "忽": "🤔",
727 | "棒": "👍",
728 | "梦": "💤",
729 | "收": "📦",
730 | "放": "📦",
731 | "散": "📦",
732 | "厌": "😒",
733 | "厉": "👿",
734 | "傲": "😏",
735 | "傻": "🤪",
736 | "烧": "🔥",
737 | "烤": "🔥",
738 | "奶": "🍼",
739 | "奴": "🍼",
740 | "花": "🌸",
741 | "芽": "🌸",
742 | "芬": "🌸",
743 | "冷": "🥶",
744 | "冻": "🥶",
745 | "冰": "🥶",
746 | "冲": "🥶",
747 | "奔": "🏃",
748 | "点": "🔴",
749 | "炸": "💥",
750 | "炮": "💥",
751 | "抢": "💥",
752 | "抗": "💪",
753 | "枪": "🔫",
754 | "蹦": "🏃",
755 | "脸": "😊",
756 | "脱": "👕",
757 | "脚": "👟",
758 | "脏": "🚮",
759 | "娇": "😊",
760 | "女": "👩",
761 | "打": "👊",
762 | "乐": "😄",
763 | "朋": "👫",
764 | "友": "👫",
765 | "倒": "🔄",
766 | "粽": "🍙",
767 | "子": "👶",
768 | "场": "🏟️",
769 | "块": "💰",
770 | "坚": "💪",
771 | "坛": "🍶",
772 | "均": "🍶",
773 | "钱": "💰",
774 | "坐": "🛋️",
775 | "买": "💰",
776 | "红": "❤️",
777 | "闪": "💥",
778 | "擦": "🧼",
779 | "操": "🤬",
780 | "土": "🌍",
781 | "手": "🤚",
782 | "碗": "🍚",
783 | "谢": "🙏",
784 | "谷": "🌾",
785 | "谋": "🤔",
786 | "谍": "🕵️",
787 | "谎": "🤥",
788 | "老": "👴",
789 | "犹": "🤔",
790 | "猜": "🤔",
791 | "杯": "🍺",
792 | "切": "🔪",
793 | "血": "🩸",
794 | "歉": "🙇",
795 | "表": "👗",
796 | "妈": "👩",
797 | "烦": "😤",
798 | "累": "😴",
799 | "了": "🚫",
800 | "疲": "😴",
801 | "滑": "🛷",
802 | "酷": "😎",
803 | "哦": "😮",
804 | "屈": "😔",
805 | "屁": "💨",
806 | "?": "❓",
807 | "!": "❗",
808 | "。": "🔚",
809 | ",": "🔚",
810 | "、": "🔚",
811 | "生": "👶",
812 | "恋": "💑",
813 | "哈": "😄",
814 | "玩": "🎮",
815 | "难": "😰",
816 | "随": "🔄",
817 | "死": "💀",
818 | "万": "🎃",
819 | "骷": "💀",
820 | "鬼": "👻",
821 | "爪": "🐾",
822 | "鬃": "🐾",
823 | "干": "🍺",
824 | "希": "🤞",
825 | "帅": "😎",
826 | "停": "🛑",
827 | "猫": "🐱",
828 | "弱": "😔",
829 | "额": "😔",
830 | "察": "🕵️",
831 | "寡": "🕵️",
832 | "口": "👄",
833 | "欺": "🤥",
834 | "歌": "🎤",
835 | "趴": "🤔",
836 | "足": "👟",
837 | "拳": "🥊",
838 | "拼": "🧩",
839 | "丸": "🍬",
840 | "治": "🏥",
841 | "疗": "🏥",
842 | "金": "💰",
843 | "银": "💰",
844 | "资": "💰",
845 | "财": "💰",
846 | "贵": "💰",
847 | "贱": "💰",
848 | "指": "👉",
849 | "摆": "🤔",
850 | "爆": "💥",
851 | "躺": "🛌",
852 | "康": "👀",
853 | "安": "🏥",
854 | "庆": "🎉",
855 | "嘿": "😏",
856 | "0": "0️⃣",
857 | "1": "1️⃣",
858 | "2": "2️⃣",
859 | "涩": "❤️",
860 | "呕": "🤮",
861 | "味": "🤔",
862 | "呼": "🤔",
863 | "呢": "🤔",
864 | "呵": "🤔",
865 | "呸": "🤔",
866 | "呻": "🤔",
867 | "噗": "🎉",
868 | "噢": "😮",
869 | "噜": "😏",
870 | "噌": "🎉",
871 | "噎": "🤔",
872 | "汗": "😰",
873 | "酸": "🤢",
874 | "呱": "🐸",
875 | "寝": "😴",
876 | "噩": "😱",
877 | "噪": "🤔",
878 | "绝": "😰",
879 | "隐": "😷",
880 | "墨": "🖊️",
881 | "骂": "🤬",
882 | "包": "🎁",
883 | "堡": "🍔",
884 | "骑": "🏇",
885 | "头": "👤",
886 | "骗": "🤥",
887 | "滚": "🤬",
888 | "满": "🤗",
889 | "贴": "📌",
890 | "杀": "🔪",
891 | "骨": "💀",
892 | "鲨": "🦈",
893 | "话": "🗣️",
894 | "书": "📚",
895 | "啧": "🤔",
896 | "啪": "🎉",
897 | "茶": "🍵",
898 | "啵": "🎉",
899 | "脑": "🧠",
900 | "晚": "🌙",
901 | "摸": "🤔",
902 | "胡": "🤔",
903 | "噔": "🎉",
904 | "亮": "💡",
905 | "听": "👂",
906 | "吵": "🤬",
907 | "鹅": "🦢",
908 | "网": "🕸️",
909 | "牡": "🐂",
910 | "牛": "🐂",
911 | "瓜": "🍈",
912 | "疑": "🤔",
913 | "仇": "🤔",
914 | "鱼": "🐟",
915 | "鱿": "🦑",
916 | "鲸": "🐋",
917 | "菜": "🥦",
918 | "逮": "👮",
919 | "鲜": "🥦",
920 | "捕": "👮",
921 | "捉": "👮",
922 | "捞": "🎣",
923 | "击": "👮",
924 | "排": "🍆",
925 | "组": "🍆",
926 | "真": "👍",
927 | "假": "👎",
928 | "得": "👍",
929 | "失": "👎",
930 | "忘": "🤔",
931 | "忙": "😰",
932 | "忠": "👍",
933 | "飞": "✈️",
934 | "吨": "🚚",
935 | "鸡": "🐔",
936 | "掌": "👋",
937 | "握": "🤝",
938 | "橘": "🍊",
939 | "阿": "🤤",
940 | "巴": "🤤",
941 | "鸭": "🦆",
942 | "鸽": "🕊️",
943 | "O": "👌",
944 | "果": "🍎",
945 | "鸣": "🐦",
946 | "期": "📅",
947 | "欢": "🎉",
948 | "欠": "😔",
949 | "电": "🔌",
950 | "起": "🔌",
951 | "鸦": "🦜",
952 | "小": "👶",
953 | "木": "🌲",
954 | "牙": "🦷",
955 | "宣": "📢",
956 | "捧": "🤲",
957 | "瘫": "🤕",
958 | "触": "👉",
959 | "蜷": "🤔",
960 | "蜜": "🍯",
961 | "蜂": "🐝",
962 | "虫": "🐛",
963 | "蜘": "🕷️",
964 | "出": "🚪",
965 | "蜡": "🕯️",
966 | "蜻": "🦗",
967 | "就": "👍",
968 | "逆": "🔄",
969 | "透": "🔄",
970 | "天": "🌞",
971 | "火": "🔥",
972 | "烈": "🔥",
973 | "烟": "🚬",
974 | "机": "🛩️",
975 | "音": "🎵",
976 | "鸟": "🐦",
977 | "下": "👇",
978 | "刺": "🔪",
979 | "叭": "📢",
980 | "叮": "📢",
981 | "家": "🏠",
982 | "眼": "👀",
983 | "峻": "😰",
984 | "速": "🏃",
985 | "猛": "🐯",
986 | "飙": "🏎️",
987 | "风": "🌬️",
988 | "应": "👍",
989 | "碎": "🔨",
990 | "菠": "🍍",
991 | "说": "🗣️",
992 | "怎": "🤔",
993 | "陨": "☄️",
994 | "秋": "🍂",
995 | "遗": "📜",
996 | "遣": "📜",
997 | "言": "📜",
998 | "蛋": "🥚",
999 | "嘻": "😄",
1000 | "斩": "🔪",
1001 | "宴": "🍽️",
1002 | "英": "🦸",
1003 | "钟": "🕰️",
1004 | "魔": "🧙",
1005 | "嘴": "👄",
1006 | "神": "🧙",
1007 | "骰": "🎲",
1008 | "朦": "🌫️",
1009 | "融": "🌞",
1010 | "螺": "🐚",
1011 | "敲": "🔨",
1012 | "螃": "🦀",
1013 | "螂": "🐜",
1014 | "泪": "😭",
1015 | "墙": "🧱",
1016 | "寄": "📬",
1017 | "寂": "🤫",
1018 | "槟": "🍾",
1019 | "抽": "🚬",
1020 | "观": "👀",
1021 | "班": "👔",
1022 | "奖": "🏆",
1023 | "猎": "🏹",
1024 | "士": "🎓",
1025 | "剑": "⚔️",
1026 | "视": "👀",
1027 | "糊": "🥞",
1028 | "咬": "🦷",
1029 | "咳": "🤢",
1030 | "咸": "🥨",
1031 | "咽": "🥤",
1032 | "咪": "🎤",
1033 | "再": "👋",
1034 | "卫": "🚽",
1035 | "导": "🚀",
1036 | "柠": "🍋",
1037 | "怕": "😨",
1038 | "因": "🤔",
1039 | "闹": "🤬",
1040 | "愣": "😐",
1041 | "愈": "🏥",
1042 | "結": "🎀",
1043 | "兵": "🎖️",
1044 | "启": "🚀",
1045 | "雾": "🌫️",
1046 | "狼": "🐺",
1047 | "雪": "❄️",
1048 | "卖": "💰",
1049 | "狂": "🤪",
1050 | "狗": "🐶",
1051 | "狐": "🦊",
1052 | "袋": "👜",
1053 | "宙": "🌌",
1054 | "角": "🦌",
1055 | "蔼": "🌿",
1056 | "终": "🔚",
1057 | "咦": "🤔",
1058 | "眯": "😴",
1059 | "昏": "🌚",
1060 | "餐": "🍽️",
1061 | "时": "🕰️",
1062 | "笨": "🤪",
1063 | "求": "🙏",
1064 | "超": "🥵",
1065 | "你": "👉",
1066 | "捏": "🤔",
1067 | "损": "🤔",
1068 | "咩": "🚫",
1069 | "草": "🌿",
1070 | "狸": "🦝",
1071 | "耶": "🎉",
1072 | "祷": "🙏",
1073 | "星": "🌟",
1074 | "憋": "🤐",
1075 | "瞌": "😴"
1076 | }
--------------------------------------------------------------------------------
/src/telegram_sticker_utils/__init__.py:
--------------------------------------------------------------------------------
1 | import os
2 | import pathlib
3 | import tempfile
4 | from dataclasses import dataclass
5 | from enum import Enum
6 | from io import BytesIO
7 | from typing import Literal
8 | from typing import Union, IO
9 | import json
10 | import subprocess
11 |
12 | import wand.image as w_image
13 | from PIL import Image as PilImage
14 | from ffmpy import FFmpeg
15 | from loguru import logger
16 | from magika import Magika
17 |
18 | from telegram_sticker_utils.core.const import get_random_emoji_from_text
19 |
20 | mimetype_detector = Magika()
21 |
22 |
23 | class BadInput(Exception):
24 | pass
25 |
26 |
27 | class StickerType(Enum):
28 | STATIC = "static"
29 | VIDEO = "video"
30 |
31 |
32 | @dataclass
33 | class Sticker:
34 | data: bytes
35 | file_extension: str
36 | emojis: list[str]
37 | sticker_type: Union[Literal["static", "video"], str]
38 |
39 |
40 | def is_animated_gif(
41 | image: Union[str, bytes, os.PathLike, IO[bytes]]
42 | ) -> bool:
43 | """
44 | Check if an image is an animated GIF.
45 | :param image: Path to the image file or a file-like object.
46 | :return: True if the image is an animated GIF, False otherwise.
47 | :raises ValueError: If the image is not a valid GIF file.
48 | """
49 | # Load the image data
50 | if isinstance(image, (str, os.PathLike)):
51 | image_path = pathlib.Path(image)
52 | if not image_path.exists():
53 | raise FileNotFoundError(f"Input file {image_path} does not exist")
54 | with open(image_path, 'rb') as f:
55 | image_data = f.read()
56 | elif isinstance(image, IO):
57 | image_data = image.read()
58 | elif isinstance(image, bytes):
59 | image_data = image
60 | else:
61 | raise TypeError("image_path must be a string, bytes, os.PathLike, or file-like object")
62 |
63 | # Check animation using Wand
64 | try:
65 | from wand.image import Image as WImage # noqa
66 | with WImage(blob=image_data) as img:
67 | return img.animation
68 | except ImportError:
69 | pass # Wand is not available
70 |
71 | # Check animation using PIL
72 | try:
73 | from PIL import Image # noqa
74 | with Image.open(BytesIO(image_data)) as img:
75 | try:
76 | img.seek(1) # Try to move to the second frame
77 | return True
78 | except EOFError:
79 | return False
80 | except ImportError:
81 | pass # PIL is not available
82 |
83 | raise ValueError("Unable to process the image file. Ensure the file is a valid GIF.")
84 |
85 |
86 | class ImageProcessor(object):
87 | MAX_STATIC_IMAGE_SIZE_BYTES = 500 * 1024 # 500 KB
88 |
89 | @staticmethod
90 | def _read_input_data(input_data: Union[str, bytes, os.PathLike, IO[bytes]]) -> bytes:
91 | """Helper function to read input data from different formats."""
92 | if isinstance(input_data, (str, os.PathLike)):
93 | input_path = pathlib.Path(input_data)
94 | if not input_path.exists():
95 | raise FileNotFoundError(f"Input file {input_path} does not exist")
96 | with open(input_path, 'rb') as f:
97 | return f.read()
98 | elif isinstance(input_data, IO):
99 | return input_data.read()
100 | if not isinstance(input_data, bytes):
101 | raise TypeError(f"Invalid input_data type: {type(input_data)}")
102 | return input_data
103 |
104 | @staticmethod
105 | def _resize_image(
106 | input_data: Union[str, bytes, os.PathLike, IO[bytes]],
107 | target_size: int,
108 | output_format: str = 'png'
109 | ) -> bytes:
110 | """针对二次元风格图片的高质量缩放"""
111 | # 提前导入,避免重复导入
112 | from PIL.ImageEnhance import Sharpness
113 |
114 | input_buffer = BytesIO(input_data)
115 | with PilImage.open(input_buffer) as img:
116 | # 优化透明度处理
117 | if img.mode not in ('RGBA', 'LA'):
118 | img = img.convert('RGBA')
119 |
120 | # 计算新尺寸,确保最长边等于target_size
121 | current_w, current_h = img.size
122 | if current_w >= current_h:
123 | new_width = target_size
124 | new_height = int(round((target_size / current_w) * current_h))
125 | else:
126 | new_height = target_size
127 | new_width = int(round((target_size / current_h) * current_w))
128 |
129 | # 优化重采样策略
130 | if target_size <= 100:
131 | # 小图优化:直接使用高质量重采样
132 | resized = img.resize(
133 | (new_width, new_height),
134 | PilImage.Resampling.LANCZOS,
135 | reducing_gap=2.0,
136 | box=None # 显式指定完整区域
137 | )
138 | # 更温和的锐化
139 | resized = Sharpness(resized).enhance(1.2)
140 | else:
141 | # 大图优化:渐进式重采样
142 | current_size = img.size
143 | steps = []
144 |
145 | # 计算渐进式缩放步骤
146 | while (current_size[0] / 1.5 > new_width or
147 | current_size[1] / 1.5 > new_height):
148 | current_size = (
149 | max(int(current_size[0] / 1.5), new_width),
150 | max(int(current_size[1] / 1.5), new_height)
151 | )
152 | steps.append(current_size)
153 |
154 | # 渐进式重采样
155 | current = img
156 | for size in steps:
157 | current = current.resize(
158 | size,
159 | PilImage.Resampling.BICUBIC,
160 | reducing_gap=3.0
161 | )
162 |
163 | # 最终重采样
164 | resized = current.resize(
165 | (new_width, new_height),
166 | PilImage.Resampling.LANCZOS,
167 | reducing_gap=2.0
168 | )
169 |
170 | # 根据缩放比例调整锐化程度
171 | scale_ratio = min(new_width/img.size[0], new_height/img.size[1])
172 | sharpen_amount = 1.1 if scale_ratio < 0.5 else 1.05
173 | resized = Sharpness(resized).enhance(sharpen_amount)
174 |
175 | # 优化输出质量
176 | output = BytesIO()
177 | save_params = {
178 | 'format': 'PNG',
179 | 'optimize': True,
180 | 'compress_level': 9,
181 | 'bits': 8,
182 | }
183 |
184 | # 根据是否有透明通道优化保存参数
185 | if resized.mode == 'RGBA' and not any(resized.getchannel('A').getdata()):
186 | resized = resized.convert('RGB')
187 |
188 | resized.save(output, **save_params)
189 | initial_output_data = output.getvalue()
190 | if len(initial_output_data) > ImageProcessor.MAX_STATIC_IMAGE_SIZE_BYTES:
191 | optimized_data = ImageProcessor._optimize_png(initial_output_data)
192 |
193 | if len(optimized_data) > ImageProcessor.MAX_STATIC_IMAGE_SIZE_BYTES:
194 | logger.warning(
195 | f"Optimized image size is still too large: {len(optimized_data) / 1024:.2f}KB "
196 | f"(limit: {ImageProcessor.MAX_STATIC_IMAGE_SIZE_BYTES / 1024:.2f}KB)."
197 | )
198 | return optimized_data
199 |
200 | # 如果初始大小在限制内,直接返回
201 | return initial_output_data
202 |
203 | @staticmethod
204 | def _optimize_png(png_data: bytes) -> bytes:
205 | """优化PNG输出质量,主要用于后处理"""
206 | output = BytesIO()
207 |
208 | with PilImage.open(BytesIO(png_data)) as img:
209 | # 确保 RGBA 模式
210 | if img.mode != 'RGBA':
211 | img = img.convert('RGBA')
212 |
213 | # 应用颜色量化,保持透明度
214 | if img.mode == 'RGBA':
215 | # 分离 alpha 通道
216 | rgb = img.convert('RGB')
217 | alpha = img.split()[3]
218 |
219 | # 对 RGB 通道进行量化
220 | quantized = rgb.quantize(colors=256, method=2) # method=2 使用中位切分算法
221 |
222 | # 重新组合 alpha 通道
223 | quantized = quantized.convert('RGBA')
224 | quantized.putalpha(alpha)
225 |
226 | img = quantized
227 |
228 | img.save(
229 | output,
230 | format='PNG',
231 | optimize=True,
232 | compress_level=9,
233 | quality=95,
234 | bits=8
235 | )
236 |
237 | output.seek(0)
238 | return output.read()
239 |
240 | @staticmethod
241 | def resize_image_with_scale(
242 | input_data: Union[str, bytes, os.PathLike, IO[bytes]],
243 | scale: int,
244 | output_format: str = 'png'
245 | ) -> bytes:
246 | """
247 | Resize an image file using wand and ensure the longest side does not exceed the given scale.
248 |
249 | :param input_data: Path to the input image file or binary data.
250 | :param scale: Maximum length of the longest side of the image file.
251 | :param output_format: Output image format. Supported formats: 'gif', 'png'.
252 | :return: Resized image as binary data.
253 | """
254 | input_data = ImageProcessor._read_input_data(input_data)
255 | return ImageProcessor._resize_image(input_data, scale, output_format)
256 |
257 | @staticmethod
258 | def _process_animated_image(input_data: bytes, scale: int) -> tuple[bytes, StickerType]:
259 | """Helper function to process animated images."""
260 | try:
261 | return WebmHelper.convert_to_webm_ffmpeg(input_data=input_data, scale=scale), StickerType.VIDEO
262 | except Exception as exc:
263 | logger.error(f"ffmpeg report error {exc}\ntry to using wand instead")
264 | return WebmHelper.convert_to_webm_wand(input_data, scale=scale), StickerType.VIDEO
265 |
266 | @staticmethod
267 | def _resize_static_image(input_data: bytes, scale: int) -> tuple[bytes, StickerType]:
268 | """Helper function to resize static images."""
269 | return ImageProcessor.resize_image_with_scale(
270 | input_data,
271 | scale=scale,
272 | output_format='png'
273 | ), StickerType.STATIC
274 |
275 | @staticmethod
276 | def make_raw_sticker(
277 | input_data: Union[str, bytes, os.PathLike, IO[bytes]],
278 | *,
279 | scale: int = 512
280 | ) -> tuple[bytes, StickerType]:
281 | """
282 | Process the image. If the image is animated, convert it to WebM.
283 | If the image is static, resize it to the specified dimensions.
284 |
285 | :param input_data: Path to the input image file or binary data.
286 | :param scale: New size of the image file.
287 | :return: Processed image as binary data.
288 | """
289 | input_data = ImageProcessor._read_input_data(input_data)
290 | file_type = mimetype_detector.identify_bytes(input_data).output.ct_label
291 | if file_type in ["webm", "mp4", "mov", "avi"]:
292 | return ImageProcessor._process_animated_image(input_data, scale)
293 |
294 | if file_type in ["gif"]:
295 | with w_image.Image(blob=input_data) as img:
296 | if img.animation:
297 | return ImageProcessor._process_animated_image(input_data, scale)
298 | return ImageProcessor._resize_static_image(input_data, scale)
299 |
300 | if file_type in ["png", "jpeg", "jpg"]:
301 | return ImageProcessor._resize_static_image(input_data, scale)
302 |
303 | try:
304 | with w_image.Image(blob=input_data) as img:
305 | if img.animation:
306 | return ImageProcessor._process_animated_image(input_data, scale)
307 | return ImageProcessor._resize_static_image(input_data, scale)
308 | except Exception as exc:
309 | logger.warning(f"Unsupported file type: {file_type}")
310 | raise BadInput(
311 | f"An Error happened!Unsupported file type @{file_type}."
312 | f"If you believe this is an error, please report it at "
313 | f"https://github.com/sudoskys/telegram-sticker-utils/issues/new"
314 | ) from exc
315 |
316 | @staticmethod
317 | def make_sticker(
318 | input_name: str,
319 | input_data: Union[str, bytes, os.PathLike, IO[bytes]],
320 | *,
321 | scale: int = 512,
322 | **kwargs
323 | ) -> Sticker:
324 | """
325 | Process the image. If the image is animated, convert it to WebM.
326 | If the image is static, resize it to the specified dimensions.
327 |
328 | :param input_name: Name of the input image file.
329 | :param input_data: Path to the input image file or binary data.
330 | :param scale: New size of the image file.
331 | :return: Processed image as binary data.
332 | """
333 | if isinstance(input_data, (str, os.PathLike)):
334 | input_path = pathlib.Path(input_data)
335 | if not input_path.exists():
336 | raise FileNotFoundError(f"Input file {input_path} does not exist")
337 | with open(input_path, 'rb') as f:
338 | input_data = f.read()
339 | elif isinstance(input_data, IO):
340 | input_data = input_data.read()
341 |
342 | # Process the image
343 | sticker_data, sticker_type = ImageProcessor.make_raw_sticker(
344 | input_data,
345 | scale=scale
346 | )
347 | # Get random emoji from the input name
348 | emoji_item = [get_random_emoji_from_text(input_name)]
349 | # Output file extension
350 | file_extension = "png" if sticker_type == StickerType.STATIC else "webm"
351 | return Sticker(
352 | data=sticker_data,
353 | file_extension=file_extension,
354 | emojis=emoji_item,
355 | sticker_type=sticker_type.value
356 | )
357 |
358 |
359 | class WebmHelper(object):
360 | MAX_SIZE = 256 * 1024 # 256 KB
361 |
362 | @staticmethod
363 | def convert_to_webm_ffmpeg(
364 | input_data: Union[str, bytes, os.PathLike, IO[bytes]],
365 | scale: int,
366 | *,
367 | frame_rate: Union[int, None] = None,
368 | duration: Union[float, None] = None
369 | ) -> bytes:
370 | """
371 | 将输入数据转换为优化的WebM格式动态贴纸。
372 |
373 | 参数:
374 | input_data: 输入文件路径或二进制数据
375 | scale: 输出视频最长边的目标尺寸
376 | frame_rate: 可选的目标帧率,默认为原始帧率(最高30fps)
377 | duration: 可选的目标时长,默认为原始时长(最长2.9秒)
378 |
379 | 返回:
380 | bytes: 优化后的WebM格式数据
381 |
382 | 异常:
383 | BadInput: 当文件类型推断失败或视频处理失败时
384 | """
385 | try:
386 | file_type = mimetype_detector.identify_bytes(input_data).output.ct_label
387 | except Exception as exc:
388 | raise BadInput("Failed to infer file type") from exc
389 |
390 | with tempfile.TemporaryDirectory() as temp_dir:
391 | # 准备输入文件
392 | input_path = os.path.join(temp_dir, f"input.{file_type}")
393 | with open(input_path, 'wb') as f:
394 | f.write(input_data)
395 |
396 | # 获取输入视频信息
397 | probe_cmd = [
398 | 'ffprobe', '-v', 'error',
399 | '-select_streams', 'v:0',
400 | '-show_entries', 'stream=width,height,r_frame_rate,duration',
401 | '-of', 'json',
402 | input_path
403 | ]
404 | try:
405 | probe = subprocess.run(probe_cmd, capture_output=True, text=True, check=True)
406 | video_info = json.loads(probe.stdout)
407 |
408 | # 确保streams存在且不为空
409 | if 'streams' not in video_info or not video_info['streams']:
410 | raise ValueError("No video streams found in the input file")
411 |
412 | stream_info = video_info['streams'][0]
413 |
414 | # 计算原始帧率
415 | fps_num, fps_den = map(int, stream_info.get('r_frame_rate', '24/1').split('/'))
416 | original_fps = fps_num / fps_den
417 |
418 | # 智能帧率控制
419 | target_fps = min(original_fps, 30)
420 | if frame_rate:
421 | target_fps = min(frame_rate, 30)
422 |
423 | # 智能时长控制
424 | orig_duration = float(stream_info.get('duration', '3'))
425 | target_duration = min(orig_duration, 2.9)
426 | if duration:
427 | target_duration = min(duration, 2.9)
428 |
429 | # 获取原始尺寸
430 | width = int(stream_info.get('width', 512))
431 | height = int(stream_info.get('height', 512))
432 | except (subprocess.SubprocessError, json.JSONDecodeError, ValueError, KeyError) as e:
433 | logger.warning(f"Failed to get video info: {e}, using default values")
434 | target_fps = 24
435 | target_duration = 2.9
436 | width = height = 512
437 |
438 | output_path = os.path.join(temp_dir, "output.webm")
439 |
440 | # 计算缩放参数,确保最长边等于scale
441 | if width >= height:
442 | scale_filter = f"scale={scale}:-2:flags=lanczos"
443 | else:
444 | scale_filter = f"scale=-2:{scale}:flags=lanczos"
445 |
446 | # 基础编码参数
447 | base_options = [
448 | # 视频编码器设置
449 | '-c:v', 'libvpx-vp9',
450 | '-pix_fmt', 'yuva420p',
451 | # 尺寸控制 - 修正为确保最长边是scale
452 | '-vf', f"{scale_filter},setsar=1:1",
453 | # 移除音频
454 | '-an',
455 | # 循环设置
456 | '-loop', '0',
457 | # 质量控制
458 | '-deadline', 'good',
459 | '-cpu-used', '2',
460 | # 并行处理
461 | '-tile-columns', '2',
462 | '-frame-parallel', '1',
463 | '-auto-alt-ref', '1',
464 | '-lag-in-frames', '16',
465 | # 比特率控制
466 | '-b:v', '0',
467 | '-crf', '30',
468 | # 速度控制
469 | '-speed', '2',
470 | # 帧率控制
471 | '-r', f'{target_fps}',
472 | # 时长控制
473 | '-t', f'{target_duration}',
474 | # 元数据(用于欺骗服务器)
475 | '-metadata:s:v:0', 'alpha_mode="1"',
476 | '-metadata', f'duration="{target_duration}"',
477 | '-metadata', 'encoder="VP9 HW Encoder"',
478 | # 错误处理
479 | '-v', 'error'
480 | ]
481 |
482 | try:
483 | # 第一次编码尝试
484 | ff = FFmpeg(
485 | global_options=['-y', '-hide_banner'],
486 | inputs={input_path: None},
487 | outputs={output_path: base_options}
488 | )
489 | ff.run()
490 |
491 | # 检查文件大小并优化
492 | if os.path.getsize(output_path) > WebmHelper.MAX_SIZE:
493 | # 压缩配置序列
494 | compression_configs = [
495 | {'crf': 35, 'cpu-used': 2, 'speed': 2},
496 | {'crf': 40, 'cpu-used': 3, 'speed': 3},
497 | {'crf': 45, 'cpu-used': 4, 'speed': 4},
498 | {'crf': 50, 'deadline': 'realtime', 'cpu-used': 4, 'speed': 4}
499 | ]
500 |
501 | for i, config in enumerate(compression_configs):
502 | try:
503 | options = base_options.copy()
504 | # 更新压缩参数
505 | for param, value in config.items():
506 | if f'-{param}' in options:
507 | param_index = options.index(f'-{param}') + 1
508 | options[param_index] = str(value)
509 | else:
510 | # 如果参数不存在,添加它
511 | options.extend([f'-{param}', str(value)])
512 |
513 | temp_output = os.path.join(temp_dir, f"output_compressed_{i}.webm")
514 | ff = FFmpeg(
515 | global_options=['-y', '-hide_banner'],
516 | inputs={input_path: None},
517 | outputs={temp_output: options}
518 | )
519 | ff.run()
520 |
521 | if os.path.getsize(temp_output) <= WebmHelper.MAX_SIZE:
522 | with open(temp_output, 'rb') as f:
523 | return f.read()
524 | except Exception as e:
525 | logger.warning(f"Compression config {config} failed: {e}")
526 | continue
527 |
528 | # 如果所有压缩配置都失败,但原始输出文件存在,返回它
529 | if os.path.exists(output_path) and os.path.getsize(output_path) <= WebmHelper.MAX_SIZE:
530 | logger.warning("Using original output as all compression attempts failed")
531 | with open(output_path, 'rb') as f:
532 | return f.read()
533 |
534 | raise BadInput("Failed to compress animated sticker within size limit")
535 |
536 | with open(output_path, 'rb') as f:
537 | return f.read()
538 |
539 | except Exception as e:
540 | logger.error(f"FFmpeg processing failed: {e}")
541 | raise BadInput(f"Video processing failed: {e}") from e
542 |
543 | @staticmethod
544 | def convert_to_webm_wand(
545 | input_data: Union[str, bytes, os.PathLike, IO[bytes]],
546 | scale: int,
547 | *,
548 | strict: bool = True
549 | ) -> bytes:
550 | """
551 | Convert image or video data to optimized WEBM format, resizing as necessary.
552 |
553 | :param input_data: Path to the input file or the input file data.
554 | :param scale: Desired maximum size for the longest side of the output video.
555 | :param strict: Some images may have wrong metadata, set this to True to fall back to ffmpeg.
556 | :return: Bytes of the optimized WEBM file.
557 | :raises FileNotFoundError: If the input file does not exist.
558 | :raises ValueError: If the image dimensions change after optimization.
559 | """
560 | # Load input data
561 | if isinstance(input_data, (str, os.PathLike)):
562 | input_path = pathlib.Path(input_data)
563 | if not input_path.exists():
564 | raise FileNotFoundError(f"Input file {input_path} does not exist")
565 | with open(input_path, 'rb') as file:
566 | input_data = file.read()
567 |
568 | with w_image.Image(blob=input_data) as img:
569 | # 计算新尺寸,确保最长边等于scale
570 | if img.width > img.height:
571 | new_width = scale
572 | new_height = int(img.height * (scale / img.width))
573 | else:
574 | new_height = scale
575 | new_width = int(img.width * (scale / img.height))
576 |
577 | if img.format == "GIF":
578 | # Use Pillow to get the image dimensions
579 | with BytesIO(input_data) as img_byte_io:
580 | pil_image = PilImage.open(img_byte_io)
581 | pil_width, pil_height = pil_image.size
582 | pil_image.close()
583 | # Check if dimensions match between wand and Pillow
584 | if (img.width, img.height) != (pil_width, pil_height):
585 | if strict:
586 | # Use ffmpeg for conversion if dimensions do not match
587 | return WebmHelper.convert_to_webm_ffmpeg(input_data, scale)
588 | raise ValueError("Image dimensions unknown error occurred")
589 |
590 | # 使用高质量重采样方法
591 | img.resize(new_width, new_height, filter='lanczos')
592 |
593 | # 应用轻微锐化以提高清晰度
594 | img.sharpen(radius=0, sigma=0.8)
595 |
596 | # 优化透明度
597 | img.alpha_channel = True
598 |
599 | # 转换为WebM并设置高质量参数
600 | img.options['webm:method'] = '6' # 高质量压缩方法
601 | img.options['webm:thread-level'] = '2' # 并行处理
602 |
603 | if img.width != new_width or img.height != new_height:
604 | raise ValueError(f"Sticker Dimensions changed after optimization {img.width}x{img.height}")
605 |
606 | img.format = 'webm'
607 | optimized_blob = BytesIO()
608 | img.save(file=optimized_blob)
609 | optimized_blob.seek(0)
610 | sticker_data = optimized_blob.read()
611 | if len(sticker_data) > 256 * 1024:
612 | raise BadInput(
613 | "Encoded video exceeds 256 KB size limit when using wind, "
614 | "but Telegram thinks it's too big! "
615 | "Please check this file"
616 | )
617 | return sticker_data
618 |
--------------------------------------------------------------------------------
/pdm.lock:
--------------------------------------------------------------------------------
1 | # This file is @generated by PDM.
2 | # It is not intended for manual editing.
3 |
4 | [metadata]
5 | groups = ["default"]
6 | strategy = ["inherit_metadata"]
7 | lock_version = "4.5.0"
8 | content_hash = "sha256:f1f188099ef74ec35582ed39ffe32ba27447ab34d734437c41328f6f2933018b"
9 |
10 | [[metadata.targets]]
11 | requires_python = ">=3.9,<3.13"
12 |
13 | [[package]]
14 | name = "click"
15 | version = "8.1.8"
16 | requires_python = ">=3.7"
17 | summary = "Composable command line interface toolkit"
18 | groups = ["default"]
19 | dependencies = [
20 | "colorama; platform_system == \"Windows\"",
21 | "importlib-metadata; python_version < \"3.8\"",
22 | ]
23 | files = [
24 | {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"},
25 | {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"},
26 | ]
27 |
28 | [[package]]
29 | name = "colorama"
30 | version = "0.4.6"
31 | requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
32 | summary = "Cross-platform colored terminal text."
33 | groups = ["default"]
34 | marker = "sys_platform == \"win32\" or platform_system == \"Windows\""
35 | files = [
36 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
37 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
38 | ]
39 |
40 | [[package]]
41 | name = "coloredlogs"
42 | version = "15.0.1"
43 | requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
44 | summary = "Colored terminal output for Python's logging module"
45 | groups = ["default"]
46 | dependencies = [
47 | "humanfriendly>=9.1",
48 | ]
49 | files = [
50 | {file = "coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934"},
51 | {file = "coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0"},
52 | ]
53 |
54 | [[package]]
55 | name = "decorator"
56 | version = "4.4.2"
57 | requires_python = ">=2.6, !=3.0.*, !=3.1.*"
58 | summary = "Decorators for Humans"
59 | groups = ["default"]
60 | files = [
61 | {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"},
62 | {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"},
63 | ]
64 |
65 | [[package]]
66 | name = "emoji"
67 | version = "2.14.1"
68 | requires_python = ">=3.7"
69 | summary = "Emoji for Python"
70 | groups = ["default"]
71 | dependencies = [
72 | "typing-extensions>=4.7.0; python_version < \"3.9\"",
73 | ]
74 | files = [
75 | {file = "emoji-2.14.1-py3-none-any.whl", hash = "sha256:35a8a486c1460addb1499e3bf7929d3889b2e2841a57401903699fef595e942b"},
76 | {file = "emoji-2.14.1.tar.gz", hash = "sha256:f8c50043d79a2c1410ebfae833ae1868d5941a67a6cd4d18377e2eb0bd79346b"},
77 | ]
78 |
79 | [[package]]
80 | name = "ffmpy"
81 | version = "0.5.0"
82 | requires_python = "<4.0,>=3.8"
83 | summary = "A simple Python wrapper for FFmpeg"
84 | groups = ["default"]
85 | files = [
86 | {file = "ffmpy-0.5.0-py3-none-any.whl", hash = "sha256:df3799cf5816daa56d4959a023630ee53c6768b66009dae6d131519ba4b80233"},
87 | {file = "ffmpy-0.5.0.tar.gz", hash = "sha256:277e131f246d18e9dcfee9bb514c50749031c43582ce5ef82c57b51e3d3955c3"},
88 | ]
89 |
90 | [[package]]
91 | name = "flatbuffers"
92 | version = "25.1.24"
93 | summary = "The FlatBuffers serialization format for Python"
94 | groups = ["default"]
95 | files = [
96 | {file = "flatbuffers-25.1.24-py2.py3-none-any.whl", hash = "sha256:1abfebaf4083117225d0723087ea909896a34e3fec933beedb490d595ba24145"},
97 | {file = "flatbuffers-25.1.24.tar.gz", hash = "sha256:e0f7b7d806c0abdf166275492663130af40c11f89445045fbef0aa3c9a8643ad"},
98 | ]
99 |
100 | [[package]]
101 | name = "humanfriendly"
102 | version = "10.0"
103 | requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
104 | summary = "Human friendly output for text interfaces using Python"
105 | groups = ["default"]
106 | dependencies = [
107 | "monotonic; python_version == \"2.7\"",
108 | "pyreadline3; sys_platform == \"win32\" and python_version >= \"3.8\"",
109 | "pyreadline; sys_platform == \"win32\" and python_version < \"3.8\"",
110 | ]
111 | files = [
112 | {file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"},
113 | {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"},
114 | ]
115 |
116 | [[package]]
117 | name = "imageio"
118 | version = "2.35.0"
119 | requires_python = ">=3.8"
120 | summary = "Library for reading and writing a wide range of image, video, scientific, and volumetric data formats."
121 | groups = ["default"]
122 | dependencies = [
123 | "numpy<2.0.0",
124 | "pillow>=8.3.2",
125 | ]
126 | files = [
127 | {file = "imageio-2.35.0-py3-none-any.whl", hash = "sha256:ebfb39c0ad7acf93750d7ee151664238aaa9208f202288ea6f3ca7cb0ed719c3"},
128 | {file = "imageio-2.35.0.tar.gz", hash = "sha256:e88f759803d2968e19a55fa8372e9fbe12967a8598fe042d8719ca46d03f4256"},
129 | ]
130 |
131 | [[package]]
132 | name = "imageio-ffmpeg"
133 | version = "0.5.1"
134 | requires_python = ">=3.5"
135 | summary = "FFMPEG wrapper for Python"
136 | groups = ["default"]
137 | dependencies = [
138 | "setuptools",
139 | ]
140 | files = [
141 | {file = "imageio-ffmpeg-0.5.1.tar.gz", hash = "sha256:0ed7a9b31f560b0c9d929c5291cd430edeb9bed3ce9a497480e536dd4326484c"},
142 | {file = "imageio_ffmpeg-0.5.1-py3-none-macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:1460e84712b9d06910c1f7bb524096b0341d4b7844cea6c20e099d0a24e795b1"},
143 | {file = "imageio_ffmpeg-0.5.1-py3-none-manylinux2010_x86_64.whl", hash = "sha256:5289f75c7f755b499653f3209fea4efd1430cba0e39831c381aad2d458f7a316"},
144 | {file = "imageio_ffmpeg-0.5.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7fa9132a291d5eb28c44553550deb40cbdab831f2a614e55360301a6582eb205"},
145 | {file = "imageio_ffmpeg-0.5.1-py3-none-win32.whl", hash = "sha256:89efe2c79979d8174ba8476deb7f74d74c331caee3fb2b65ba2883bec0737625"},
146 | {file = "imageio_ffmpeg-0.5.1-py3-none-win_amd64.whl", hash = "sha256:1521e79e253bedbdd36a547e0cbd94a025ba0b558e17f08fea687d805a0e4698"},
147 | ]
148 |
149 | [[package]]
150 | name = "loguru"
151 | version = "0.7.3"
152 | requires_python = "<4.0,>=3.5"
153 | summary = "Python logging made (stupidly) simple"
154 | groups = ["default"]
155 | dependencies = [
156 | "aiocontextvars>=0.2.0; python_version < \"3.7\"",
157 | "colorama>=0.3.4; sys_platform == \"win32\"",
158 | "win32-setctime>=1.0.0; sys_platform == \"win32\"",
159 | ]
160 | files = [
161 | {file = "loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c"},
162 | {file = "loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6"},
163 | ]
164 |
165 | [[package]]
166 | name = "magika"
167 | version = "0.5.1"
168 | requires_python = ">=3.8,<3.13"
169 | summary = "A tool to determine the content type of a file with deep-learning"
170 | groups = ["default"]
171 | dependencies = [
172 | "click<9.0.0,>=8.1.3",
173 | "numpy<2.0,>=1.24; python_version >= \"3.8\" and python_version < \"3.9\"",
174 | "numpy<2.0,>=1.26; python_version >= \"3.9\" and python_version < \"3.13\"",
175 | "onnxruntime<2.0.0,>=1.17.0",
176 | "python-dotenv<2.0.0,>=1.0.1",
177 | "tabulate<0.10.0,>=0.9.0",
178 | "tqdm<5.0.0,>=4.66.2",
179 | ]
180 | files = [
181 | {file = "magika-0.5.1-py3-none-any.whl", hash = "sha256:a4d1f64f71460f335841c13c3d16cfc2cb21e839c1898a1ae9bd5adc8d66cb2b"},
182 | {file = "magika-0.5.1.tar.gz", hash = "sha256:43dc1153a1637327225a626a1550c0a395a1d45ea33ec1f5d46b9b080238bee0"},
183 | ]
184 |
185 | [[package]]
186 | name = "moviepy"
187 | version = "2.1.2"
188 | summary = "Video editing with Python"
189 | groups = ["default"]
190 | dependencies = [
191 | "decorator<6.0,>=4.0.2",
192 | "imageio-ffmpeg>=0.2.0",
193 | "imageio<3.0,>=2.5",
194 | "numpy>=1.25.0",
195 | "pillow<11.0,>=9.2.0",
196 | "proglog<=1.0.0",
197 | "python-dotenv>=0.10",
198 | ]
199 | files = [
200 | {file = "moviepy-2.1.2-py3-none-any.whl", hash = "sha256:6cdc0d739110c8f347a224d72bd59eebaec010720d01eff290d37111bf545a73"},
201 | {file = "moviepy-2.1.2.tar.gz", hash = "sha256:22c57a7472f607eaad9fe80791df67c05082e1060fb74817c4eaac68e138ee77"},
202 | ]
203 |
204 | [[package]]
205 | name = "mpmath"
206 | version = "1.3.0"
207 | summary = "Python library for arbitrary-precision floating-point arithmetic"
208 | groups = ["default"]
209 | files = [
210 | {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"},
211 | {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"},
212 | ]
213 |
214 | [[package]]
215 | name = "numpy"
216 | version = "1.26.4"
217 | requires_python = ">=3.9"
218 | summary = "Fundamental package for array computing in Python"
219 | groups = ["default"]
220 | files = [
221 | {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"},
222 | {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"},
223 | {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"},
224 | {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"},
225 | {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"},
226 | {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"},
227 | {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"},
228 | {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"},
229 | {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"},
230 | {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"},
231 | {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"},
232 | {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"},
233 | {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"},
234 | {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"},
235 | {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"},
236 | {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"},
237 | {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"},
238 | {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"},
239 | {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"},
240 | {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"},
241 | {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"},
242 | {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"},
243 | {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"},
244 | {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"},
245 | {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"},
246 | {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"},
247 | {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"},
248 | {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"},
249 | {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"},
250 | {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"},
251 | {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"},
252 | {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"},
253 | {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"},
254 | {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"},
255 | {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"},
256 | {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"},
257 | ]
258 |
259 | [[package]]
260 | name = "onnxruntime"
261 | version = "1.20.1"
262 | summary = "ONNX Runtime is a runtime accelerator for Machine Learning models"
263 | groups = ["default"]
264 | dependencies = [
265 | "coloredlogs",
266 | "flatbuffers",
267 | "numpy>=1.21.6",
268 | "packaging",
269 | "protobuf",
270 | "sympy",
271 | ]
272 | files = [
273 | {file = "onnxruntime-1.20.1-cp310-cp310-macosx_13_0_universal2.whl", hash = "sha256:e50ba5ff7fed4f7d9253a6baf801ca2883cc08491f9d32d78a80da57256a5439"},
274 | {file = "onnxruntime-1.20.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b2908b50101a19e99c4d4e97ebb9905561daf61829403061c1adc1b588bc0de"},
275 | {file = "onnxruntime-1.20.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d82daaec24045a2e87598b8ac2b417b1cce623244e80e663882e9fe1aae86410"},
276 | {file = "onnxruntime-1.20.1-cp310-cp310-win32.whl", hash = "sha256:4c4b251a725a3b8cf2aab284f7d940c26094ecd9d442f07dd81ab5470e99b83f"},
277 | {file = "onnxruntime-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:d3b616bb53a77a9463707bb313637223380fc327f5064c9a782e8ec69c22e6a2"},
278 | {file = "onnxruntime-1.20.1-cp311-cp311-macosx_13_0_universal2.whl", hash = "sha256:06bfbf02ca9ab5f28946e0f912a562a5f005301d0c419283dc57b3ed7969bb7b"},
279 | {file = "onnxruntime-1.20.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6243e34d74423bdd1edf0ae9596dd61023b260f546ee17d701723915f06a9f7"},
280 | {file = "onnxruntime-1.20.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5eec64c0269dcdb8d9a9a53dc4d64f87b9e0c19801d9321246a53b7eb5a7d1bc"},
281 | {file = "onnxruntime-1.20.1-cp311-cp311-win32.whl", hash = "sha256:a19bc6e8c70e2485a1725b3d517a2319603acc14c1f1a017dda0afe6d4665b41"},
282 | {file = "onnxruntime-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:8508887eb1c5f9537a4071768723ec7c30c28eb2518a00d0adcd32c89dea3221"},
283 | {file = "onnxruntime-1.20.1-cp312-cp312-macosx_13_0_universal2.whl", hash = "sha256:22b0655e2bf4f2161d52706e31f517a0e54939dc393e92577df51808a7edc8c9"},
284 | {file = "onnxruntime-1.20.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f56e898815963d6dc4ee1c35fc6c36506466eff6d16f3cb9848cea4e8c8172"},
285 | {file = "onnxruntime-1.20.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb71a814f66517a65628c9e4a2bb530a6edd2cd5d87ffa0af0f6f773a027d99e"},
286 | {file = "onnxruntime-1.20.1-cp312-cp312-win32.whl", hash = "sha256:bd386cc9ee5f686ee8a75ba74037750aca55183085bf1941da8efcfe12d5b120"},
287 | {file = "onnxruntime-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:19c2d843eb074f385e8bbb753a40df780511061a63f9def1b216bf53860223fb"},
288 | ]
289 |
290 | [[package]]
291 | name = "packaging"
292 | version = "24.2"
293 | requires_python = ">=3.8"
294 | summary = "Core utilities for Python packages"
295 | groups = ["default"]
296 | files = [
297 | {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
298 | {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
299 | ]
300 |
301 | [[package]]
302 | name = "pillow"
303 | version = "10.4.0"
304 | requires_python = ">=3.8"
305 | summary = "Python Imaging Library (Fork)"
306 | groups = ["default"]
307 | files = [
308 | {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"},
309 | {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"},
310 | {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"},
311 | {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"},
312 | {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"},
313 | {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"},
314 | {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"},
315 | {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"},
316 | {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"},
317 | {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"},
318 | {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"},
319 | {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"},
320 | {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"},
321 | {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"},
322 | {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"},
323 | {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"},
324 | {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"},
325 | {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"},
326 | {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"},
327 | {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"},
328 | {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"},
329 | {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"},
330 | {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"},
331 | {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"},
332 | {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"},
333 | {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"},
334 | {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"},
335 | {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"},
336 | {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"},
337 | {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"},
338 | {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"},
339 | {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"},
340 | {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"},
341 | {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"},
342 | {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"},
343 | {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"},
344 | {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"},
345 | {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"},
346 | {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"},
347 | {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"},
348 | {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"},
349 | {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"},
350 | {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"},
351 | {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"},
352 | {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"},
353 | {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"},
354 | {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"},
355 | {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"},
356 | {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"},
357 | {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"},
358 | {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"},
359 | {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"},
360 | {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"},
361 | {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"},
362 | {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"},
363 | {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"},
364 | {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"},
365 | {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"},
366 | {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"},
367 | ]
368 |
369 | [[package]]
370 | name = "proglog"
371 | version = "0.1.10"
372 | summary = "Log and progress bar manager for console, notebooks, web..."
373 | groups = ["default"]
374 | dependencies = [
375 | "tqdm",
376 | ]
377 | files = [
378 | {file = "proglog-0.1.10-py3-none-any.whl", hash = "sha256:19d5da037e8c813da480b741e3fa71fb1ac0a5b02bf21c41577c7f327485ec50"},
379 | {file = "proglog-0.1.10.tar.gz", hash = "sha256:658c28c9c82e4caeb2f25f488fff9ceace22f8d69b15d0c1c86d64275e4ddab4"},
380 | ]
381 |
382 | [[package]]
383 | name = "protobuf"
384 | version = "5.29.3"
385 | requires_python = ">=3.8"
386 | summary = ""
387 | groups = ["default"]
388 | files = [
389 | {file = "protobuf-5.29.3-cp310-abi3-win32.whl", hash = "sha256:3ea51771449e1035f26069c4c7fd51fba990d07bc55ba80701c78f886bf9c888"},
390 | {file = "protobuf-5.29.3-cp310-abi3-win_amd64.whl", hash = "sha256:a4fa6f80816a9a0678429e84973f2f98cbc218cca434abe8db2ad0bffc98503a"},
391 | {file = "protobuf-5.29.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a8434404bbf139aa9e1300dbf989667a83d42ddda9153d8ab76e0d5dcaca484e"},
392 | {file = "protobuf-5.29.3-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:daaf63f70f25e8689c072cfad4334ca0ac1d1e05a92fc15c54eb9cf23c3efd84"},
393 | {file = "protobuf-5.29.3-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:c027e08a08be10b67c06bf2370b99c811c466398c357e615ca88c91c07f0910f"},
394 | {file = "protobuf-5.29.3-cp39-cp39-win32.whl", hash = "sha256:0eb32bfa5219fc8d4111803e9a690658aa2e6366384fd0851064b963b6d1f2a7"},
395 | {file = "protobuf-5.29.3-cp39-cp39-win_amd64.whl", hash = "sha256:6ce8cc3389a20693bfde6c6562e03474c40851b44975c9b2bf6df7d8c4f864da"},
396 | {file = "protobuf-5.29.3-py3-none-any.whl", hash = "sha256:0a18ed4a24198528f2333802eb075e59dea9d679ab7a6c5efb017a59004d849f"},
397 | {file = "protobuf-5.29.3.tar.gz", hash = "sha256:5da0f41edaf117bde316404bad1a486cb4ededf8e4a54891296f648e8e076620"},
398 | ]
399 |
400 | [[package]]
401 | name = "pyreadline3"
402 | version = "3.5.4"
403 | requires_python = ">=3.8"
404 | summary = "A python implementation of GNU readline."
405 | groups = ["default"]
406 | marker = "sys_platform == \"win32\" and python_version >= \"3.8\""
407 | files = [
408 | {file = "pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6"},
409 | {file = "pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7"},
410 | ]
411 |
412 | [[package]]
413 | name = "python-dotenv"
414 | version = "1.0.1"
415 | requires_python = ">=3.8"
416 | summary = "Read key-value pairs from a .env file and set them as environment variables"
417 | groups = ["default"]
418 | files = [
419 | {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"},
420 | {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"},
421 | ]
422 |
423 | [[package]]
424 | name = "setuptools"
425 | version = "75.8.0"
426 | requires_python = ">=3.9"
427 | summary = "Easily download, build, install, upgrade, and uninstall Python packages"
428 | groups = ["default"]
429 | files = [
430 | {file = "setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3"},
431 | {file = "setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6"},
432 | ]
433 |
434 | [[package]]
435 | name = "sympy"
436 | version = "1.13.3"
437 | requires_python = ">=3.8"
438 | summary = "Computer algebra system (CAS) in Python"
439 | groups = ["default"]
440 | dependencies = [
441 | "mpmath<1.4,>=1.1.0",
442 | ]
443 | files = [
444 | {file = "sympy-1.13.3-py3-none-any.whl", hash = "sha256:54612cf55a62755ee71824ce692986f23c88ffa77207b30c1368eda4a7060f73"},
445 | {file = "sympy-1.13.3.tar.gz", hash = "sha256:b27fd2c6530e0ab39e275fc9b683895367e51d5da91baa8d3d64db2565fec4d9"},
446 | ]
447 |
448 | [[package]]
449 | name = "tabulate"
450 | version = "0.9.0"
451 | requires_python = ">=3.7"
452 | summary = "Pretty-print tabular data"
453 | groups = ["default"]
454 | files = [
455 | {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"},
456 | {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"},
457 | ]
458 |
459 | [[package]]
460 | name = "tqdm"
461 | version = "4.66.5"
462 | requires_python = ">=3.7"
463 | summary = "Fast, Extensible Progress Meter"
464 | groups = ["default"]
465 | dependencies = [
466 | "colorama; platform_system == \"Windows\"",
467 | ]
468 | files = [
469 | {file = "tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd"},
470 | {file = "tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad"},
471 | ]
472 |
473 | [[package]]
474 | name = "wand"
475 | version = "0.6.13"
476 | summary = "Ctypes-based simple MagickWand API binding for Python"
477 | groups = ["default"]
478 | files = [
479 | {file = "Wand-0.6.13-py2.py3-none-any.whl", hash = "sha256:e5dda0ac2204a40c29ef5c4cb310770c95d3d05c37b1379e69c94ea79d7d19c0"},
480 | {file = "Wand-0.6.13.tar.gz", hash = "sha256:f5013484eaf7a20eb22d1821aaefe60b50cc329722372b5f8565d46d4aaafcca"},
481 | ]
482 |
483 | [[package]]
484 | name = "win32-setctime"
485 | version = "1.1.0"
486 | requires_python = ">=3.5"
487 | summary = "A small Python utility to set file creation time on Windows"
488 | groups = ["default"]
489 | marker = "sys_platform == \"win32\""
490 | files = [
491 | {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"},
492 | {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"},
493 | ]
494 |
--------------------------------------------------------------------------------