manim-captcha 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- manim_captcha-1.0.0/LICENSE +21 -0
- manim_captcha-1.0.0/PKG-INFO +177 -0
- manim_captcha-1.0.0/README.md +161 -0
- manim_captcha-1.0.0/pyproject.toml +29 -0
- manim_captcha-1.0.0/setup.cfg +4 -0
- manim_captcha-1.0.0/src/manim_captcha/__init__.py +0 -0
- manim_captcha-1.0.0/src/manim_captcha/auto_generator.py +234 -0
- manim_captcha-1.0.0/src/manim_captcha/colors.py +107 -0
- manim_captcha-1.0.0/src/manim_captcha/data.py +46 -0
- manim_captcha-1.0.0/src/manim_captcha/generator.py +212 -0
- manim_captcha-1.0.0/src/manim_captcha/scene/__init__.py +0 -0
- manim_captcha-1.0.0/src/manim_captcha/scene/circle_nums.py +227 -0
- manim_captcha-1.0.0/src/manim_captcha/scene/matrix_nums.py +229 -0
- manim_captcha-1.0.0/src/manim_captcha/scene/piramid_nums.py +240 -0
- manim_captcha-1.0.0/src/manim_captcha/scenes.py +56 -0
- manim_captcha-1.0.0/src/manim_captcha.egg-info/PKG-INFO +177 -0
- manim_captcha-1.0.0/src/manim_captcha.egg-info/SOURCES.txt +20 -0
- manim_captcha-1.0.0/src/manim_captcha.egg-info/dependency_links.txt +1 -0
- manim_captcha-1.0.0/src/manim_captcha.egg-info/requires.txt +1 -0
- manim_captcha-1.0.0/src/manim_captcha.egg-info/top_level.txt +1 -0
- manim_captcha-1.0.0/tests/test_captcha_auto_generator.py +117 -0
- manim_captcha-1.0.0/tests/test_captcha_creation.py +231 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jose
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: manim-captcha
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Animated visual captcha generation library using Manim.
|
|
5
|
+
Author: Jose Miguel Rios Rubio
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: captcha,manim,animation,video,gif,mp4
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.12
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: manim==0.19.2
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# Manim-Captcha
|
|
18
|
+
|
|
19
|
+
<div style="display: flex;">
|
|
20
|
+
<img src="https://github.com/user-attachments/assets/62677f79-c7a3-49ad-b01a-0143e48cb7ae" alt="captcha_circle" width="270" />
|
|
21
|
+
<img src="https://github.com/user-attachments/assets/223b7e2d-99c4-410e-ab6e-4966ad47d2e8" alt="captcha_matrix" width="270" />
|
|
22
|
+
<img src="https://github.com/user-attachments/assets/3c606acd-24bf-45bf-8a45-6127f392ea73" alt="captcha_piramid" width="270" />
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
## Overview
|
|
26
|
+
|
|
27
|
+
Animated visual captcha generation library using [Manim Framework](https://github.com/ManimCommunity/manim).
|
|
28
|
+
|
|
29
|
+
Traditional CAPTCHAs are static images. Manim-Captcha generates animated visual challenges (e.g., moving selectors, distributed numbers, dynamic noise).
|
|
30
|
+
|
|
31
|
+
This library offers:
|
|
32
|
+
|
|
33
|
+
- A "Captcha Generator" that allows to request generation of captcha files.
|
|
34
|
+
- A "Captcha Automatic Generator" for async operation that acts as a background process to automatically generate captcha files in the filesystem for a custom interval and keeping a maximum number of captchas files (file rotation).
|
|
35
|
+
- Some builtin captchas animations that you can select to use.
|
|
36
|
+
- Kind of "plugin" based system were you can provide any external custom Manim captcha animation script (scene) and make the library use it.
|
|
37
|
+
- Easy way to pass custom properties to the captcha scenes for customization (like scenes).
|
|
38
|
+
|
|
39
|
+
## Table of Contents
|
|
40
|
+
|
|
41
|
+
- [Requirements](#requirements)
|
|
42
|
+
- [Installation](#installation)
|
|
43
|
+
- [Usage](#usage)
|
|
44
|
+
- [Library Development](#library-development)
|
|
45
|
+
|
|
46
|
+
## Requirements
|
|
47
|
+
|
|
48
|
+
The manim-captcha library requires python 3 (tested with v3.12), Manim Community framework, and the corresponding requirements for Manim (like Cairo and Pango libraries).
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
**Note:** The next installation instructions are for Debian based Linux systems.
|
|
53
|
+
|
|
54
|
+
From a command line, install the different requirements:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
sudo apt-get update
|
|
58
|
+
sudo apt-get install build-essential make python3 python3-dev python3-pip
|
|
59
|
+
sudo apt-get install libcairo2-dev libpango1.0-dev
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The library has been published to pypi so can be easily installed via pip:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install manim-captcha
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Usage
|
|
69
|
+
|
|
70
|
+
Basic captcha generator:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from manim_captcha.generator import CaptchaGenerator
|
|
74
|
+
from manim_captcha.scenes import CaptchaScene
|
|
75
|
+
from pathlib import Path
|
|
76
|
+
|
|
77
|
+
generator = CaptchaGenerator()
|
|
78
|
+
|
|
79
|
+
captcha = generator.generate(
|
|
80
|
+
code="1234",
|
|
81
|
+
scene=CaptchaScene.CIRCLE_NUMS,
|
|
82
|
+
out_dir=Path("./captchas"),
|
|
83
|
+
properties={
|
|
84
|
+
"theme": "dark",
|
|
85
|
+
"noise": True
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if captcha.error:
|
|
90
|
+
print("Fail to create the captcha:")
|
|
91
|
+
print(captcha.error_info)
|
|
92
|
+
else:
|
|
93
|
+
print("Captcha successfully created")
|
|
94
|
+
print(f" Code: \"{captcha.code}\"")
|
|
95
|
+
print(f" File: {captcha.file}")
|
|
96
|
+
print("")
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Async and non-blocking automatic captcha generator:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
import asyncio
|
|
103
|
+
import logging
|
|
104
|
+
from manim_captcha.auto_generator import CaptchaAutoGenerator
|
|
105
|
+
from pathlib import Path
|
|
106
|
+
|
|
107
|
+
logging.basicConfig(
|
|
108
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
109
|
+
level=logging.INFO
|
|
110
|
+
)
|
|
111
|
+
logger = logging.getLogger(__name__)
|
|
112
|
+
|
|
113
|
+
async def main():
|
|
114
|
+
# Setup the CaptchaAutoGenerator
|
|
115
|
+
MAX_NUM_CAPTCHAS = 5
|
|
116
|
+
TIME_GEN_INTERVAL_S = 10
|
|
117
|
+
OUT_DIR = Path("./captchas")
|
|
118
|
+
auto_generator = CaptchaAutoGenerator(
|
|
119
|
+
OUT_DIR, TIME_GEN_INTERVAL_S, MAX_NUM_CAPTCHAS)
|
|
120
|
+
# Start the generator process
|
|
121
|
+
start_success = await auto_generator.start()
|
|
122
|
+
if not start_success:
|
|
123
|
+
logger.error("Fail to Start CaptchaAutoGenerator")
|
|
124
|
+
return
|
|
125
|
+
# Wait and get some captchas during 1 minute
|
|
126
|
+
TIME_CHECK = 60
|
|
127
|
+
time_pass_s = 0
|
|
128
|
+
run = True
|
|
129
|
+
while run:
|
|
130
|
+
# Show number of captchas availables
|
|
131
|
+
num_captchas = auto_generator.num_captchas()
|
|
132
|
+
logger.info("Num Captchas Availables: %d", num_captchas)
|
|
133
|
+
# Try to get a captcha
|
|
134
|
+
captcha = auto_generator.get_captcha()
|
|
135
|
+
if not captcha.error:
|
|
136
|
+
logger.info("Captcha retrieved:")
|
|
137
|
+
logger.info(" Code: \"%s\"", captcha.code)
|
|
138
|
+
logger.info(" File: %s", str(captcha.file))
|
|
139
|
+
await asyncio.sleep(TIME_GEN_INTERVAL_S)
|
|
140
|
+
# Check if check end time has arrive to exit the loop
|
|
141
|
+
time_pass_s = time_pass_s + TIME_GEN_INTERVAL_S
|
|
142
|
+
if time_pass_s >= TIME_CHECK:
|
|
143
|
+
run = False
|
|
144
|
+
# Stop the generator process
|
|
145
|
+
await auto_generator.stop()
|
|
146
|
+
|
|
147
|
+
if __name__ == '__main__':
|
|
148
|
+
asyncio.run(main())
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Note:** You can find some extra usages in the *examples/* directory.
|
|
152
|
+
|
|
153
|
+
## Library Development
|
|
154
|
+
|
|
155
|
+
For library development, to ease project setup and installation of all the requirements, a Makefile and some bash scripts are provided for Linux systems based on Debian.
|
|
156
|
+
|
|
157
|
+
Setup the project (this will install all system requirements and creates a python virtual environment):
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
make setup
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Extra actions can be performed through make, just run make without argments to see the usage help:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
make
|
|
167
|
+
|
|
168
|
+
Usage:
|
|
169
|
+
setup: Setup Project and install requirements
|
|
170
|
+
test: Run project tests
|
|
171
|
+
check_code_style: Run Code Style Checks
|
|
172
|
+
check_static_types: Run Static Types Checks
|
|
173
|
+
install: Install local package in edit mode
|
|
174
|
+
publish: Publish the library to pypi
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Now open any code editor (i.e. vscode) withing the python environment and start coding!
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Manim-Captcha
|
|
2
|
+
|
|
3
|
+
<div style="display: flex;">
|
|
4
|
+
<img src="https://github.com/user-attachments/assets/62677f79-c7a3-49ad-b01a-0143e48cb7ae" alt="captcha_circle" width="270" />
|
|
5
|
+
<img src="https://github.com/user-attachments/assets/223b7e2d-99c4-410e-ab6e-4966ad47d2e8" alt="captcha_matrix" width="270" />
|
|
6
|
+
<img src="https://github.com/user-attachments/assets/3c606acd-24bf-45bf-8a45-6127f392ea73" alt="captcha_piramid" width="270" />
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
Animated visual captcha generation library using [Manim Framework](https://github.com/ManimCommunity/manim).
|
|
12
|
+
|
|
13
|
+
Traditional CAPTCHAs are static images. Manim-Captcha generates animated visual challenges (e.g., moving selectors, distributed numbers, dynamic noise).
|
|
14
|
+
|
|
15
|
+
This library offers:
|
|
16
|
+
|
|
17
|
+
- A "Captcha Generator" that allows to request generation of captcha files.
|
|
18
|
+
- A "Captcha Automatic Generator" for async operation that acts as a background process to automatically generate captcha files in the filesystem for a custom interval and keeping a maximum number of captchas files (file rotation).
|
|
19
|
+
- Some builtin captchas animations that you can select to use.
|
|
20
|
+
- Kind of "plugin" based system were you can provide any external custom Manim captcha animation script (scene) and make the library use it.
|
|
21
|
+
- Easy way to pass custom properties to the captcha scenes for customization (like scenes).
|
|
22
|
+
|
|
23
|
+
## Table of Contents
|
|
24
|
+
|
|
25
|
+
- [Requirements](#requirements)
|
|
26
|
+
- [Installation](#installation)
|
|
27
|
+
- [Usage](#usage)
|
|
28
|
+
- [Library Development](#library-development)
|
|
29
|
+
|
|
30
|
+
## Requirements
|
|
31
|
+
|
|
32
|
+
The manim-captcha library requires python 3 (tested with v3.12), Manim Community framework, and the corresponding requirements for Manim (like Cairo and Pango libraries).
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
**Note:** The next installation instructions are for Debian based Linux systems.
|
|
37
|
+
|
|
38
|
+
From a command line, install the different requirements:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
sudo apt-get update
|
|
42
|
+
sudo apt-get install build-essential make python3 python3-dev python3-pip
|
|
43
|
+
sudo apt-get install libcairo2-dev libpango1.0-dev
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The library has been published to pypi so can be easily installed via pip:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install manim-captcha
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Usage
|
|
53
|
+
|
|
54
|
+
Basic captcha generator:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from manim_captcha.generator import CaptchaGenerator
|
|
58
|
+
from manim_captcha.scenes import CaptchaScene
|
|
59
|
+
from pathlib import Path
|
|
60
|
+
|
|
61
|
+
generator = CaptchaGenerator()
|
|
62
|
+
|
|
63
|
+
captcha = generator.generate(
|
|
64
|
+
code="1234",
|
|
65
|
+
scene=CaptchaScene.CIRCLE_NUMS,
|
|
66
|
+
out_dir=Path("./captchas"),
|
|
67
|
+
properties={
|
|
68
|
+
"theme": "dark",
|
|
69
|
+
"noise": True
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
if captcha.error:
|
|
74
|
+
print("Fail to create the captcha:")
|
|
75
|
+
print(captcha.error_info)
|
|
76
|
+
else:
|
|
77
|
+
print("Captcha successfully created")
|
|
78
|
+
print(f" Code: \"{captcha.code}\"")
|
|
79
|
+
print(f" File: {captcha.file}")
|
|
80
|
+
print("")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Async and non-blocking automatic captcha generator:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
import asyncio
|
|
87
|
+
import logging
|
|
88
|
+
from manim_captcha.auto_generator import CaptchaAutoGenerator
|
|
89
|
+
from pathlib import Path
|
|
90
|
+
|
|
91
|
+
logging.basicConfig(
|
|
92
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
93
|
+
level=logging.INFO
|
|
94
|
+
)
|
|
95
|
+
logger = logging.getLogger(__name__)
|
|
96
|
+
|
|
97
|
+
async def main():
|
|
98
|
+
# Setup the CaptchaAutoGenerator
|
|
99
|
+
MAX_NUM_CAPTCHAS = 5
|
|
100
|
+
TIME_GEN_INTERVAL_S = 10
|
|
101
|
+
OUT_DIR = Path("./captchas")
|
|
102
|
+
auto_generator = CaptchaAutoGenerator(
|
|
103
|
+
OUT_DIR, TIME_GEN_INTERVAL_S, MAX_NUM_CAPTCHAS)
|
|
104
|
+
# Start the generator process
|
|
105
|
+
start_success = await auto_generator.start()
|
|
106
|
+
if not start_success:
|
|
107
|
+
logger.error("Fail to Start CaptchaAutoGenerator")
|
|
108
|
+
return
|
|
109
|
+
# Wait and get some captchas during 1 minute
|
|
110
|
+
TIME_CHECK = 60
|
|
111
|
+
time_pass_s = 0
|
|
112
|
+
run = True
|
|
113
|
+
while run:
|
|
114
|
+
# Show number of captchas availables
|
|
115
|
+
num_captchas = auto_generator.num_captchas()
|
|
116
|
+
logger.info("Num Captchas Availables: %d", num_captchas)
|
|
117
|
+
# Try to get a captcha
|
|
118
|
+
captcha = auto_generator.get_captcha()
|
|
119
|
+
if not captcha.error:
|
|
120
|
+
logger.info("Captcha retrieved:")
|
|
121
|
+
logger.info(" Code: \"%s\"", captcha.code)
|
|
122
|
+
logger.info(" File: %s", str(captcha.file))
|
|
123
|
+
await asyncio.sleep(TIME_GEN_INTERVAL_S)
|
|
124
|
+
# Check if check end time has arrive to exit the loop
|
|
125
|
+
time_pass_s = time_pass_s + TIME_GEN_INTERVAL_S
|
|
126
|
+
if time_pass_s >= TIME_CHECK:
|
|
127
|
+
run = False
|
|
128
|
+
# Stop the generator process
|
|
129
|
+
await auto_generator.stop()
|
|
130
|
+
|
|
131
|
+
if __name__ == '__main__':
|
|
132
|
+
asyncio.run(main())
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Note:** You can find some extra usages in the *examples/* directory.
|
|
136
|
+
|
|
137
|
+
## Library Development
|
|
138
|
+
|
|
139
|
+
For library development, to ease project setup and installation of all the requirements, a Makefile and some bash scripts are provided for Linux systems based on Debian.
|
|
140
|
+
|
|
141
|
+
Setup the project (this will install all system requirements and creates a python virtual environment):
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
make setup
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Extra actions can be performed through make, just run make without argments to see the usage help:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
make
|
|
151
|
+
|
|
152
|
+
Usage:
|
|
153
|
+
setup: Setup Project and install requirements
|
|
154
|
+
test: Run project tests
|
|
155
|
+
check_code_style: Run Code Style Checks
|
|
156
|
+
check_static_types: Run Static Types Checks
|
|
157
|
+
install: Install local package in edit mode
|
|
158
|
+
publish: Publish the library to pypi
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Now open any code editor (i.e. vscode) withing the python environment and start coding!
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "manim-captcha"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Animated visual captcha generation library using Manim."
|
|
9
|
+
authors = [
|
|
10
|
+
{ name="Jose Miguel Rios Rubio" }
|
|
11
|
+
]
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
license = { text = "MIT" }
|
|
14
|
+
keywords = ["captcha", "manim", "animation", "video", "gif", "mp4"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent"
|
|
19
|
+
]
|
|
20
|
+
requires-python = ">=3.12"
|
|
21
|
+
dependencies = [
|
|
22
|
+
"manim==0.19.2"
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[tool.setuptools]
|
|
26
|
+
package-dir = {"" = "src"}
|
|
27
|
+
|
|
28
|
+
[tool.setuptools.packages.find]
|
|
29
|
+
where = ["src"]
|
|
File without changes
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Script:
|
|
6
|
+
auto_generator.py
|
|
7
|
+
Description:
|
|
8
|
+
Automatic captcha generation manager.
|
|
9
|
+
This component act as a producer that periodically creates and store
|
|
10
|
+
random Manim Captchas into a directory of the filesystem, allowing
|
|
11
|
+
external consumers (users of the library) to request any of these
|
|
12
|
+
captchas. It keeps control of the number of generated captchas, and
|
|
13
|
+
apply some kind of "files rotation" of the storage, removing and
|
|
14
|
+
replacing older captchas with newer ones to control and limit the
|
|
15
|
+
maximum number of captchas in the filesytem.
|
|
16
|
+
Author:
|
|
17
|
+
Jose Miguel Rios Rubio
|
|
18
|
+
Creation date:
|
|
19
|
+
15/02/2026
|
|
20
|
+
Last modified date:
|
|
21
|
+
16/02/2026
|
|
22
|
+
Version:
|
|
23
|
+
1.0.0
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
###############################################################################
|
|
27
|
+
# Standard Libraries
|
|
28
|
+
###############################################################################
|
|
29
|
+
|
|
30
|
+
import asyncio
|
|
31
|
+
import logging
|
|
32
|
+
import secrets
|
|
33
|
+
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
from traceback import format_exc
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
###############################################################################
|
|
39
|
+
# Third-Party Libraries
|
|
40
|
+
###############################################################################
|
|
41
|
+
|
|
42
|
+
# None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
###############################################################################
|
|
46
|
+
# Local Libraries
|
|
47
|
+
###############################################################################
|
|
48
|
+
|
|
49
|
+
from .data import CaptchaData
|
|
50
|
+
from .generator import CaptchaGenerator
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
###############################################################################
|
|
54
|
+
# Logger Setup
|
|
55
|
+
###############################################################################
|
|
56
|
+
|
|
57
|
+
logger = logging.getLogger(__name__)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
###############################################################################
|
|
61
|
+
# Class ManimCaptchaGenerator
|
|
62
|
+
###############################################################################
|
|
63
|
+
|
|
64
|
+
class CaptchaAutoGenerator:
|
|
65
|
+
"""Automatic captcha generation manager."""
|
|
66
|
+
|
|
67
|
+
###########################################################################
|
|
68
|
+
|
|
69
|
+
### Constants ###
|
|
70
|
+
|
|
71
|
+
# None
|
|
72
|
+
|
|
73
|
+
###########################################################################
|
|
74
|
+
|
|
75
|
+
### Data Types ###
|
|
76
|
+
|
|
77
|
+
# None
|
|
78
|
+
|
|
79
|
+
###########################################################################
|
|
80
|
+
|
|
81
|
+
### Constructor ###
|
|
82
|
+
|
|
83
|
+
def __init__(self,
|
|
84
|
+
out_dir: Path = Path("./captchas"),
|
|
85
|
+
interval_s: int = 60,
|
|
86
|
+
max_items: int = 100):
|
|
87
|
+
'''
|
|
88
|
+
Automatic captcha generation manager construction.
|
|
89
|
+
Arguments:
|
|
90
|
+
- out_dir: Output directory to place the generated captchas.
|
|
91
|
+
- interval: Time for creation of each new Captcha (in seconds).
|
|
92
|
+
- max_items: Maximum number of Captchas allowed in the
|
|
93
|
+
filesystem (once this number of captchas are generated, the file
|
|
94
|
+
rotate mechanism will be replacing older captchas with newer
|
|
95
|
+
ones).
|
|
96
|
+
'''
|
|
97
|
+
self.out_dir = out_dir
|
|
98
|
+
self.interval_s = interval_s
|
|
99
|
+
self.max_items = max_items
|
|
100
|
+
self.format: str = "mp4"
|
|
101
|
+
self._task_create: asyncio.Task | None = None
|
|
102
|
+
self._task_remove: asyncio.Task | None = None
|
|
103
|
+
self._running: bool = False
|
|
104
|
+
self._lock = asyncio.Lock()
|
|
105
|
+
self._generator = CaptchaGenerator()
|
|
106
|
+
self.available: bool = self._generator.is_available()
|
|
107
|
+
|
|
108
|
+
###########################################################################
|
|
109
|
+
|
|
110
|
+
### Public Methods ###
|
|
111
|
+
|
|
112
|
+
async def start(self) -> bool:
|
|
113
|
+
'''Launch the manager to start creating captchas.'''
|
|
114
|
+
if not self.available:
|
|
115
|
+
return False
|
|
116
|
+
if self._running:
|
|
117
|
+
return False
|
|
118
|
+
self._running = True
|
|
119
|
+
self._task_create = asyncio.create_task(self._producer_loop())
|
|
120
|
+
self._task_remove = asyncio.create_task(self._remove_loop())
|
|
121
|
+
return True
|
|
122
|
+
|
|
123
|
+
async def stop(self):
|
|
124
|
+
'''Stop the manager and the captcha creation.'''
|
|
125
|
+
self._running = False
|
|
126
|
+
if self._task_create:
|
|
127
|
+
await self._task_create
|
|
128
|
+
self._task_create = None
|
|
129
|
+
if self._task_remove:
|
|
130
|
+
await self._task_remove
|
|
131
|
+
self._task_remove = None
|
|
132
|
+
|
|
133
|
+
def is_running(self) -> bool:
|
|
134
|
+
'''Get Captcha Generator manager running status.'''
|
|
135
|
+
return self._running
|
|
136
|
+
|
|
137
|
+
def num_captchas(self) -> int:
|
|
138
|
+
'''
|
|
139
|
+
Get the number of generated captcha files availables in the
|
|
140
|
+
file system.
|
|
141
|
+
'''
|
|
142
|
+
try:
|
|
143
|
+
return sum(1 for _ in self.out_dir.glob(f"*.{self.format}"))
|
|
144
|
+
except Exception:
|
|
145
|
+
logger.exception("Fail to get number of captchas files available")
|
|
146
|
+
return 0
|
|
147
|
+
|
|
148
|
+
def get_captcha(self) -> CaptchaData:
|
|
149
|
+
'''Return a random captcha file from storage.'''
|
|
150
|
+
captcha_result = CaptchaData()
|
|
151
|
+
if not self.available:
|
|
152
|
+
captcha_result.error = True
|
|
153
|
+
captcha_result.error_info = "Manim not found in the system"
|
|
154
|
+
return captcha_result
|
|
155
|
+
try:
|
|
156
|
+
files = list(self.out_dir.glob(f"*.{self.format}"))
|
|
157
|
+
if not files:
|
|
158
|
+
captcha_result.error = True
|
|
159
|
+
captcha_result.error_info = "None captcha files generated"
|
|
160
|
+
return captcha_result
|
|
161
|
+
captcha_result.file = secrets.choice(files)
|
|
162
|
+
captcha_result.code = captcha_result.file.stem
|
|
163
|
+
except Exception:
|
|
164
|
+
logger.error(format_exc())
|
|
165
|
+
captcha_result.error = True
|
|
166
|
+
captcha_result.error_info = "Fail to get captcha from filesystem"
|
|
167
|
+
return captcha_result
|
|
168
|
+
|
|
169
|
+
###########################################################################
|
|
170
|
+
|
|
171
|
+
### Private Methods - Create Captchas ###
|
|
172
|
+
|
|
173
|
+
async def _producer_loop(self):
|
|
174
|
+
"""Internal async captcha creation producer loop."""
|
|
175
|
+
while self._running:
|
|
176
|
+
try:
|
|
177
|
+
async with self._lock:
|
|
178
|
+
await self._generate_captcha()
|
|
179
|
+
except Exception:
|
|
180
|
+
logger.error(format_exc())
|
|
181
|
+
await asyncio.sleep(self.interval_s)
|
|
182
|
+
|
|
183
|
+
async def _generate_captcha(self):
|
|
184
|
+
"""Generate a captcha."""
|
|
185
|
+
loop = asyncio.get_running_loop()
|
|
186
|
+
result = await loop.run_in_executor(
|
|
187
|
+
None,
|
|
188
|
+
self._generator.generate,
|
|
189
|
+
None, # code
|
|
190
|
+
None, # scene
|
|
191
|
+
self.out_dir
|
|
192
|
+
)
|
|
193
|
+
if result.error:
|
|
194
|
+
logger.error("Captcha generation failed: %s", result.error_info)
|
|
195
|
+
else:
|
|
196
|
+
logger.debug("Generated captcha: %s", result.file)
|
|
197
|
+
|
|
198
|
+
###########################################################################
|
|
199
|
+
|
|
200
|
+
### Private Methods - Remove Captchas ###
|
|
201
|
+
|
|
202
|
+
async def _remove_loop(self):
|
|
203
|
+
"""Internal async captcha remove/rotate loop."""
|
|
204
|
+
while self._running:
|
|
205
|
+
try:
|
|
206
|
+
async with self._lock:
|
|
207
|
+
self._process_remove()
|
|
208
|
+
except Exception:
|
|
209
|
+
logger.error(format_exc())
|
|
210
|
+
await asyncio.sleep(self.interval_s)
|
|
211
|
+
|
|
212
|
+
def _process_remove(self):
|
|
213
|
+
"""Remove oldest file if limit exceeded."""
|
|
214
|
+
# Dont remove if number of captchas is less than max_items limit
|
|
215
|
+
files = list(self.out_dir.glob(f"*.{self.format}"))
|
|
216
|
+
if len(files) < self.max_items:
|
|
217
|
+
return
|
|
218
|
+
# Remove oldest captcha file
|
|
219
|
+
files.sort(key=lambda p: p.stat().st_mtime)
|
|
220
|
+
while len(files) > self.max_items:
|
|
221
|
+
oldest = files.pop(0)
|
|
222
|
+
try:
|
|
223
|
+
# Atomic rename (protect race condition from consumers)
|
|
224
|
+
# and delete the file
|
|
225
|
+
deleting = oldest.with_suffix(oldest.suffix + ".deleting")
|
|
226
|
+
oldest.rename(deleting)
|
|
227
|
+
deleting.unlink(missing_ok=True)
|
|
228
|
+
logger.info("Removed old captcha: %s", oldest)
|
|
229
|
+
except PermissionError:
|
|
230
|
+
logger.warning("File in use, skipping removal: %s", oldest)
|
|
231
|
+
except Exception:
|
|
232
|
+
logger.error(format_exc())
|
|
233
|
+
|
|
234
|
+
###############################################################################
|