├── 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 | [![PyPI version](https://badge.fury.io/py/telegram-sticker-utils.svg)](https://badge.fury.io/py/telegram-sticker-utils) 4 | [![Downloads](https://pepy.tech/badge/telegram-sticker-utils)](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 | --------------------------------------------------------------------------------