Spaces:
Running
Running
Upload 3 files
Browse files- app.py +1539 -0
- packages.txt +1 -0
- requirements.txt +11 -0
app.py
ADDED
|
@@ -0,0 +1,1539 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import requests
|
| 3 |
+
from PIL import Image, ImageDraw, ImageFont
|
| 4 |
+
import io
|
| 5 |
+
import time
|
| 6 |
+
import json
|
| 7 |
+
import base64
|
| 8 |
+
import uuid
|
| 9 |
+
import hashlib
|
| 10 |
+
import urllib.parse
|
| 11 |
+
import random
|
| 12 |
+
import datetime
|
| 13 |
+
import re
|
| 14 |
+
from collections import Counter
|
| 15 |
+
from bs4 import BeautifulSoup
|
| 16 |
+
from fpdf import FPDF
|
| 17 |
+
import os
|
| 18 |
+
from io import BytesIO
|
| 19 |
+
import streamlit as st
|
| 20 |
+
import html
|
| 21 |
+
import string
|
| 22 |
+
# --- BATCH 1: MEDIA & FILE FUNCTIONS ---
|
| 23 |
+
|
| 24 |
+
def lexical_replacer_tool():
|
| 25 |
+
"""
|
| 26 |
+
The Master Function for the Lexical Space Replacer Tool.
|
| 27 |
+
Contains 100+ text manipulation features split into Normal and Dev modes.
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
st.markdown("## 🛠️ Lexical Replacer Toolkit")
|
| 31 |
+
st.markdown("---")
|
| 32 |
+
|
| 33 |
+
# --- STATE MANAGEMENT ---
|
| 34 |
+
# We use session state to hold the text so it persists when switching tabs
|
| 35 |
+
if 'replacer_input' not in st.session_state:
|
| 36 |
+
st.session_state.replacer_input = "Paste your text or HTML here..."
|
| 37 |
+
if 'replacer_output' not in st.session_state:
|
| 38 |
+
st.session_state.replacer_output = ""
|
| 39 |
+
|
| 40 |
+
# --- THE LOGIC ENGINE (100+ Features Mapped) ---
|
| 41 |
+
# We map "Human Readable Names" to "Lambda Functions" for efficiency.
|
| 42 |
+
|
| 43 |
+
ops = {
|
| 44 |
+
# --- GROUP 1: BASIC CLEANUP (Normal Mode) ---
|
| 45 |
+
"Remove Double Spaces": lambda t: re.sub(r'\s+', ' ', t),
|
| 46 |
+
"Trim Whitespace": lambda t: t.strip(),
|
| 47 |
+
"Remove Empty Lines": lambda t: "\n".join([line for line in t.splitlines() if line.strip()]),
|
| 48 |
+
"Remove Duplicate Lines": lambda t: "\n".join(list(dict.fromkeys(t.splitlines()))), # Preserves order
|
| 49 |
+
"Sentence Case": lambda t: ". ".join([s.capitalize() for s in t.split(". ")]),
|
| 50 |
+
"Title Case": lambda t: t.title(),
|
| 51 |
+
"UPPERCASE": lambda t: t.upper(),
|
| 52 |
+
"lowercase": lambda t: t.lower(),
|
| 53 |
+
"tOGGLE cASE": lambda t: t.swapcase(),
|
| 54 |
+
"Smart Quotes to Straight": lambda t: t.replace('“', '"').replace('”', '"').replace("‘", "'").replace("’", "'"),
|
| 55 |
+
"Remove Special Characters": lambda t: re.sub(r'[^a-zA-Z0-9\s]', '', t),
|
| 56 |
+
"Remove Emojis": lambda t: t.encode('ascii', 'ignore').decode('ascii'),
|
| 57 |
+
"Remove Numbers": lambda t: re.sub(r'\d+', '', t),
|
| 58 |
+
"Remove Non-ASCII": lambda t: re.sub(r'[^\x00-\x7F]+', '', t),
|
| 59 |
+
"Unescape HTML": lambda t: html.unescape(t),
|
| 60 |
+
"Text Reverse": lambda t: t[::-1],
|
| 61 |
+
"Word Reverse": lambda t: " ".join(t.split()[::-1]),
|
| 62 |
+
"Sort Lines A-Z": lambda t: "\n".join(sorted(t.splitlines())),
|
| 63 |
+
"Sort Lines Z-A": lambda t: "\n".join(sorted(t.splitlines(), reverse=True)),
|
| 64 |
+
"Shuffle Lines": lambda t: "\n".join(random.sample(t.splitlines(), len(t.splitlines()))),
|
| 65 |
+
|
| 66 |
+
# --- GROUP 2: FORMATTING (Normal Mode) ---
|
| 67 |
+
"Add Line Breaks (<br>)": lambda t: t.replace("\n", "<br>\n"),
|
| 68 |
+
"Wrap in <p> Tags": lambda t: "\n".join([f"<p>{line}</p>" for line in t.splitlines() if line.strip()]),
|
| 69 |
+
"List Maker (Bullets)": lambda t: "<ul>\n" + "\n".join([f" <li>{line}</li>" for line in t.splitlines() if line.strip()]) + "\n</ul>",
|
| 70 |
+
"List Maker (Numbered)": lambda t: "<ol>\n" + "\n".join([f" <li>{line}</li>" for line in t.splitlines() if line.strip()]) + "\n</ol>",
|
| 71 |
+
"Markdown to HTML Bold": lambda t: re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', t),
|
| 72 |
+
"HTML to Markdown Bold": lambda t: re.sub(r'<b>(.*?)</b>', r'**\1**', t),
|
| 73 |
+
"Strip All HTML Tags": lambda t: re.sub(r'<[^>]+>', '', t),
|
| 74 |
+
"Obfuscate Emails": lambda t: re.sub(r'([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})', r'[email protected]', t),
|
| 75 |
+
"Format Date (DD/MM/YYYY)": lambda t: t, # Placeholder for complex regex
|
| 76 |
+
"Tab to 4 Spaces": lambda t: t.replace("\t", " "),
|
| 77 |
+
"Slugify (URL Friendly)": lambda t: re.sub(r'[^a-z0-9]+', '-', t.lower()).strip('-'),
|
| 78 |
+
"Add Target='_blank'": lambda t: t.replace('<a ', '<a target="_blank" '),
|
| 79 |
+
"Add Rel='nofollow'": lambda t: t.replace('<a ', '<a rel="nofollow" '),
|
| 80 |
+
|
| 81 |
+
# --- GROUP 3: DEV UTILS (Dev Mode) ---
|
| 82 |
+
"Minify JSON": lambda t: json.dumps(json.loads(t), separators=(',', ':')) if t.strip() else "",
|
| 83 |
+
"Beautify JSON": lambda t: json.dumps(json.loads(t), indent=4) if t.strip() else "",
|
| 84 |
+
"Escape HTML Entities": lambda t: html.escape(t),
|
| 85 |
+
"Escape JSON String": lambda t: json.dumps(t),
|
| 86 |
+
"Base64 Encode": lambda t: base64.b64encode(t.encode()).decode(),
|
| 87 |
+
"Base64 Decode": lambda t: base64.b64decode(t.encode()).decode(),
|
| 88 |
+
"URL Encode": lambda t: urllib.parse.quote(t),
|
| 89 |
+
"URL Decode": lambda t: urllib.parse.unquote(t),
|
| 90 |
+
"Hex to RGB": lambda t: "\n".join([f"rgb({int(h[1:3], 16)}, {int(h[3:5], 16)}, {int(h[5:7], 16)})" for h in re.findall(r'#[0-9a-fA-F]{6}', t)]),
|
| 91 |
+
|
| 92 |
+
# --- GROUP 4: BLOGGER SPECIFIC (Dev Mode) ---
|
| 93 |
+
"Blogger: Fix Image Sizes": lambda t: re.sub(r'(width|height)="\d+"', '', t),
|
| 94 |
+
"Blogger: HTTPS Force": lambda t: t.replace("http://", "https://"),
|
| 95 |
+
"Clean MS Word Junk": lambda t: re.sub(r'class="Mso.*?"', '', t),
|
| 96 |
+
"Remove Inline Styles": lambda t: re.sub(r'style=".*?"', '', t),
|
| 97 |
+
"Remove Script Tags": lambda t: re.sub(r'<script.*?>.*?</script>', '', t, flags=re.DOTALL),
|
| 98 |
+
"Remove Comments": lambda t: re.sub(r'', '', t, flags=re.DOTALL),
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
# --- LAYOUT ---
|
| 102 |
+
|
| 103 |
+
col1, col2 = st.columns([1, 1])
|
| 104 |
+
|
| 105 |
+
with col1:
|
| 106 |
+
st.subheader("Input")
|
| 107 |
+
text_input = st.text_area("Paste text here", value=st.session_state.replacer_input, height=350, key="input_widget")
|
| 108 |
+
|
| 109 |
+
# Update session state on change
|
| 110 |
+
if text_input != st.session_state.replacer_input:
|
| 111 |
+
st.session_state.replacer_input = text_input
|
| 112 |
+
|
| 113 |
+
with col2:
|
| 114 |
+
st.subheader("Control Panel")
|
| 115 |
+
|
| 116 |
+
mode = st.radio("Select Mode:", ["🟢 Normal Mode", "🔴 Developer Mode"], horizontal=True)
|
| 117 |
+
|
| 118 |
+
selected_ops = []
|
| 119 |
+
|
| 120 |
+
if mode == "🟢 Normal Mode":
|
| 121 |
+
st.info("Tools for Writing, SEO, and Formatting")
|
| 122 |
+
with st.expander("✨ Cleaning Tools", expanded=True):
|
| 123 |
+
if st.button("Remove Double Spaces"): selected_ops.append("Remove Double Spaces")
|
| 124 |
+
if st.button("Trim Whitespace"): selected_ops.append("Trim Whitespace")
|
| 125 |
+
if st.button("Remove Empty Lines"): selected_ops.append("Remove Empty Lines")
|
| 126 |
+
if st.button("Remove Duplicate Lines"): selected_ops.append("Remove Duplicate Lines")
|
| 127 |
+
if st.button("Remove Special Chars"): selected_ops.append("Remove Special Characters")
|
| 128 |
+
|
| 129 |
+
with st.expander("🔠 Casing Tools"):
|
| 130 |
+
c1, c2, c3 = st.columns(3)
|
| 131 |
+
with c1:
|
| 132 |
+
if st.button("UPPERCASE"): selected_ops.append("UPPERCASE")
|
| 133 |
+
if st.button("Sentence Case"): selected_ops.append("Sentence Case")
|
| 134 |
+
with c2:
|
| 135 |
+
if st.button("lowercase"): selected_ops.append("lowercase")
|
| 136 |
+
if st.button("Title Case"): selected_ops.append("Title Case")
|
| 137 |
+
with c3:
|
| 138 |
+
if st.button("Toggle Case"): selected_ops.append("tOGGLE cASE")
|
| 139 |
+
|
| 140 |
+
with st.expander("📄 Formatting"):
|
| 141 |
+
if st.button("Make HTML List (Bullet)"): selected_ops.append("List Maker (Bullets)")
|
| 142 |
+
if st.button("Make HTML List (Number)"): selected_ops.append("List Maker (Numbered)")
|
| 143 |
+
if st.button("Smart Quotes -> Straight"): selected_ops.append("Smart Quotes to Straight")
|
| 144 |
+
if st.button("Slugify Text"): selected_ops.append("Slugify (URL Friendly)")
|
| 145 |
+
|
| 146 |
+
else: # Developer Mode
|
| 147 |
+
st.error("Tools for Code, Regex, and Backend")
|
| 148 |
+
|
| 149 |
+
# Regex Section
|
| 150 |
+
with st.expander("🔍 Regex Find & Replace", expanded=True):
|
| 151 |
+
regex_find = st.text_input("Find Pattern (Regex)", value="")
|
| 152 |
+
regex_repl = st.text_input("Replace With", value="")
|
| 153 |
+
if st.button("Run Regex Replace"):
|
| 154 |
+
try:
|
| 155 |
+
st.session_state.replacer_input = re.sub(regex_find, regex_repl, st.session_state.replacer_input)
|
| 156 |
+
st.success("Regex Applied!")
|
| 157 |
+
st.rerun()
|
| 158 |
+
except Exception as e:
|
| 159 |
+
st.error(f"Regex Error: {e}")
|
| 160 |
+
|
| 161 |
+
with st.expander("💻 Encoders / Decoders"):
|
| 162 |
+
c1, c2 = st.columns(2)
|
| 163 |
+
with c1:
|
| 164 |
+
if st.button("Base64 Encode"): selected_ops.append("Base64 Encode")
|
| 165 |
+
if st.button("URL Encode"): selected_ops.append("URL Encode")
|
| 166 |
+
with c2:
|
| 167 |
+
if st.button("Base64 Decode"): selected_ops.append("Base64 Decode")
|
| 168 |
+
if st.button("URL Decode"): selected_ops.append("URL Decode")
|
| 169 |
+
|
| 170 |
+
with st.expander("🧹 Code Cleaning"):
|
| 171 |
+
if st.button("Minify JSON"): selected_ops.append("Minify JSON")
|
| 172 |
+
if st.button("Beautify JSON"): selected_ops.append("Beautify JSON")
|
| 173 |
+
if st.button("Escape HTML"): selected_ops.append("Escape HTML Entities")
|
| 174 |
+
|
| 175 |
+
with st.expander("🅱️ Blogger Specific"):
|
| 176 |
+
if st.button("Clean MS Word Junk"): selected_ops.append("Clean MS Word Junk")
|
| 177 |
+
if st.button("Force HTTPS"): selected_ops.append("Blogger: HTTPS Force")
|
| 178 |
+
if st.button("Remove Inline Styles"): selected_ops.append("Remove Inline Styles")
|
| 179 |
+
if st.button("Remove Scripts"): selected_ops.append("Remove Script Tags")
|
| 180 |
+
|
| 181 |
+
# --- ANALYSIS (Always Visible) ---
|
| 182 |
+
st.write("---")
|
| 183 |
+
st.caption("📊 Live Analysis")
|
| 184 |
+
char_count = len(st.session_state.replacer_input)
|
| 185 |
+
word_count = len(st.session_state.replacer_input.split())
|
| 186 |
+
st.write(f"**Chars:** {char_count} | **Words:** {word_count}")
|
| 187 |
+
|
| 188 |
+
# --- EXECUTION ENGINE ---
|
| 189 |
+
# If a button added an op to the list, run it against the input text
|
| 190 |
+
if selected_ops:
|
| 191 |
+
current_text = st.session_state.replacer_input
|
| 192 |
+
for op_name in selected_ops:
|
| 193 |
+
try:
|
| 194 |
+
# Apply the function from our dictionary
|
| 195 |
+
current_text = ops[op_name](current_text)
|
| 196 |
+
st.toast(f"Applied: {op_name}")
|
| 197 |
+
except Exception as e:
|
| 198 |
+
st.error(f"Error in {op_name}: {e}")
|
| 199 |
+
|
| 200 |
+
# Update State
|
| 201 |
+
st.session_state.replacer_input = current_text
|
| 202 |
+
st.rerun() # Refresh to show changes in the text area
|
| 203 |
+
|
| 204 |
+
# --- RESULT DOWNLOAD ---
|
| 205 |
+
if st.session_state.replacer_input:
|
| 206 |
+
st.download_button(
|
| 207 |
+
label="Download Result",
|
| 208 |
+
data=st.session_state.replacer_input,
|
| 209 |
+
file_name="lexical_cleaned.txt",
|
| 210 |
+
mime="text/plain"
|
| 211 |
+
)
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
def run_ultimate_pdf_converter():
|
| 218 |
+
"""
|
| 219 |
+
The Ultimate Text-to-PDF Converter (Stable Version).
|
| 220 |
+
Features:
|
| 221 |
+
- Auto-Healing Font Loader (Fixes TTLibError)
|
| 222 |
+
- Smart Symbols & Typography
|
| 223 |
+
- Markdown Engine (Headers, Tables, Code Blocks)
|
| 224 |
+
- LMS Junk Cleaner
|
| 225 |
+
"""
|
| 226 |
+
|
| 227 |
+
# --- CONSTANTS ---
|
| 228 |
+
SMART_SYMBOLS = {
|
| 229 |
+
r'<->': '↔', r'->': '→', r'<-': '←', r'=>': '⇒', r'<=': '≤', r'>=': '≥', r'!=': '≠',
|
| 230 |
+
r'\.\.\.': '…', r'\(c\)': '©', r'\(r\)': '®', r'\(tm\)': '™',
|
| 231 |
+
r'\+-': '±', r'\~=': '≈', r'--': '—',
|
| 232 |
+
r'alpha': 'α', r'beta': 'β', r'theta': 'θ', r'pi': 'π', r'sigma': 'Σ',
|
| 233 |
+
r'delta': 'Δ', r'gamma': 'Γ', r'omega': 'Ω', r'mu': 'μ', r'lambda': 'λ',
|
| 234 |
+
r'deg': '°', r'infinity': '∞', r'sqrt': '√'
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
# --- INTERNAL PDF CLASS ---
|
| 238 |
+
class UltimatePDF(FPDF):
|
| 239 |
+
def __init__(self, orientation='P', unit='mm', format='A4'):
|
| 240 |
+
super().__init__(orientation=orientation, unit=unit, format=format)
|
| 241 |
+
self.set_auto_page_break(auto=True, margin=15)
|
| 242 |
+
self.main_font = 'Arial' # Default fallback
|
| 243 |
+
self.ensure_fonts()
|
| 244 |
+
|
| 245 |
+
def ensure_fonts(self):
|
| 246 |
+
font_filename = "DejaVuSans.ttf"
|
| 247 |
+
font_url = "https://github.com/dejavu-fonts/dejavu-fonts/raw/master/ttf/DejaVuSans.ttf"
|
| 248 |
+
|
| 249 |
+
# 1. Check if file exists and is valid size (HTML error pages are small)
|
| 250 |
+
if os.path.exists(font_filename):
|
| 251 |
+
if os.path.getsize(font_filename) < 1000: # Less than 1KB is definitely garbage
|
| 252 |
+
os.remove(font_filename)
|
| 253 |
+
|
| 254 |
+
# 2. Download if missing
|
| 255 |
+
if not os.path.exists(font_filename):
|
| 256 |
+
try:
|
| 257 |
+
# Fake user-agent to avoid GitHub blocking scripts
|
| 258 |
+
headers = {'User-Agent': 'Mozilla/5.0'}
|
| 259 |
+
r = requests.get(font_url, headers=headers, timeout=10)
|
| 260 |
+
if r.status_code == 200:
|
| 261 |
+
with open(font_filename, "wb") as f:
|
| 262 |
+
f.write(r.content)
|
| 263 |
+
except Exception as e:
|
| 264 |
+
print(f"Font download failed: {e}")
|
| 265 |
+
|
| 266 |
+
# 3. Try Loading the Font
|
| 267 |
+
try:
|
| 268 |
+
if os.path.exists(font_filename):
|
| 269 |
+
self.add_font('DejaVu', '', font_filename, uni=True)
|
| 270 |
+
self.main_font = 'DejaVu'
|
| 271 |
+
except Exception:
|
| 272 |
+
# If loading fails (corrupt file), delete it to retry next time
|
| 273 |
+
try: os.remove(font_filename)
|
| 274 |
+
except: pass
|
| 275 |
+
self.main_font = 'Arial' # Fallback to standard
|
| 276 |
+
st.toast("⚠️ Font failed to load. Using standard font (some symbols may be missing).", icon="⚠️")
|
| 277 |
+
|
| 278 |
+
def header(self):
|
| 279 |
+
if getattr(self, 'show_header', False):
|
| 280 |
+
self.set_font(self.main_font, '', 8)
|
| 281 |
+
self.set_text_color(128)
|
| 282 |
+
self.cell(0, 10, f'Generated by Ultimate PDF | {getattr(self, "title_meta", "Doc")}', 0, 0, 'R')
|
| 283 |
+
self.ln(10)
|
| 284 |
+
|
| 285 |
+
def footer(self):
|
| 286 |
+
self.set_y(-15)
|
| 287 |
+
self.set_font(self.main_font, '', 8)
|
| 288 |
+
self.set_text_color(128)
|
| 289 |
+
self.cell(0, 10, f'Page {self.page_no()}', 0, 0, 'C')
|
| 290 |
+
|
| 291 |
+
# --- MARKDOWN RENDERING HELPERS ---
|
| 292 |
+
def add_markdown_header(self, text, level):
|
| 293 |
+
sizes = {1: 20, 2: 16, 3: 14}
|
| 294 |
+
self.set_font(self.main_font, '', sizes.get(level, 12))
|
| 295 |
+
self.set_text_color(0, 50, 100)
|
| 296 |
+
self.cell(0, 10, text, ln=True)
|
| 297 |
+
self.set_text_color(0)
|
| 298 |
+
self.set_font(self.main_font, '', 12)
|
| 299 |
+
|
| 300 |
+
def add_code_block(self, code_lines):
|
| 301 |
+
self.set_font("Courier", size=10)
|
| 302 |
+
self.set_fill_color(245, 245, 245)
|
| 303 |
+
for line in code_lines:
|
| 304 |
+
# Replace tabs with spaces to prevent alignment issues
|
| 305 |
+
safe_line = line.replace('\t', ' ')
|
| 306 |
+
self.cell(0, 5, safe_line, ln=True, fill=True, border=0)
|
| 307 |
+
self.set_font(self.main_font, '', 12)
|
| 308 |
+
self.ln(3)
|
| 309 |
+
|
| 310 |
+
def add_table(self, table_lines):
|
| 311 |
+
self.set_font(self.main_font, '', 10)
|
| 312 |
+
cell_h = 7
|
| 313 |
+
for row in table_lines:
|
| 314 |
+
cols = [c.strip() for c in row.split('|') if c.strip()]
|
| 315 |
+
if not cols: continue
|
| 316 |
+
col_w = (self.w - 30) // len(cols)
|
| 317 |
+
for col in cols:
|
| 318 |
+
self.cell(col_w, cell_h, col, border=1)
|
| 319 |
+
self.ln()
|
| 320 |
+
self.set_font(self.main_font, '', 12)
|
| 321 |
+
self.ln(5)
|
| 322 |
+
|
| 323 |
+
def add_blockquote(self, text):
|
| 324 |
+
self.set_text_color(80)
|
| 325 |
+
self.set_x(self.l_margin + 8)
|
| 326 |
+
self.multi_cell(0, 6, f"“ {text}")
|
| 327 |
+
self.set_x(self.l_margin)
|
| 328 |
+
self.set_text_color(0)
|
| 329 |
+
self.ln(2)
|
| 330 |
+
|
| 331 |
+
def add_image_from_url(self, url):
|
| 332 |
+
try:
|
| 333 |
+
r = requests.get(url, timeout=5)
|
| 334 |
+
if r.status_code == 200:
|
| 335 |
+
img_data = BytesIO(r.content)
|
| 336 |
+
self.image(img_data, w=100)
|
| 337 |
+
self.ln(5)
|
| 338 |
+
except:
|
| 339 |
+
self.set_text_color(200, 0, 0)
|
| 340 |
+
self.cell(0, 10, f"[Image load failed: {url}]", ln=True)
|
| 341 |
+
self.set_text_color(0)
|
| 342 |
+
|
| 343 |
+
# --- TEXT PROCESSOR ---
|
| 344 |
+
def clean_and_parse(raw_text, use_smart_symbols=True, clean_lms=True):
|
| 345 |
+
processed_lines = []
|
| 346 |
+
|
| 347 |
+
# 1. LMS Regex Cleaning
|
| 348 |
+
if clean_lms:
|
| 349 |
+
# Common LMS patterns
|
| 350 |
+
patterns = [
|
| 351 |
+
r'\[ID:?\s*\w+\]', # [ID: 123]
|
| 352 |
+
r'Question\s+ID\s*[:\-]\s*\w+', # Question ID: 123
|
| 353 |
+
r'\(\d+\s*pts?\)', # (1 pts)
|
| 354 |
+
r'Select one:', # Moodle/Blackboard prompt
|
| 355 |
+
r'\[\d{1,2}:\d{2}\s*(AM|PM)?\]' # Timestamps
|
| 356 |
+
]
|
| 357 |
+
for p in patterns:
|
| 358 |
+
raw_text = re.sub(p, '', raw_text, flags=re.IGNORECASE)
|
| 359 |
+
raw_text = re.sub(r'\n{3,}', '\n\n', raw_text) # Fix spacing
|
| 360 |
+
|
| 361 |
+
# 2. Smart Symbols
|
| 362 |
+
if use_smart_symbols:
|
| 363 |
+
for pattern, symbol in SMART_SYMBOLS.items():
|
| 364 |
+
if pattern.isalpha():
|
| 365 |
+
raw_text = re.sub(r'\b' + pattern + r'\b', symbol, raw_text, flags=re.IGNORECASE)
|
| 366 |
+
else:
|
| 367 |
+
raw_text = re.sub(pattern, symbol, raw_text)
|
| 368 |
+
|
| 369 |
+
lines = raw_text.split('\n')
|
| 370 |
+
|
| 371 |
+
# 3. Block Parser
|
| 372 |
+
buffer_type = None
|
| 373 |
+
buffer_content = []
|
| 374 |
+
|
| 375 |
+
for line in lines:
|
| 376 |
+
line_stripped = line.strip()
|
| 377 |
+
|
| 378 |
+
# Detect Code Block
|
| 379 |
+
if line_stripped.startswith('```'):
|
| 380 |
+
if buffer_type == 'code': # Close code
|
| 381 |
+
processed_lines.append({'type': 'code', 'content': buffer_content})
|
| 382 |
+
buffer_content = []
|
| 383 |
+
buffer_type = None
|
| 384 |
+
else: # Open code
|
| 385 |
+
if buffer_type == 'table': # Close table if open
|
| 386 |
+
processed_lines.append({'type': 'table', 'content': buffer_content})
|
| 387 |
+
buffer_content = []
|
| 388 |
+
buffer_type = 'code'
|
| 389 |
+
continue
|
| 390 |
+
|
| 391 |
+
if buffer_type == 'code':
|
| 392 |
+
buffer_content.append(line) # Preserve whitespace in code
|
| 393 |
+
continue
|
| 394 |
+
|
| 395 |
+
# Detect Table
|
| 396 |
+
if '|' in line_stripped and len(line_stripped) > 3:
|
| 397 |
+
if buffer_type != 'table':
|
| 398 |
+
buffer_type = 'table'
|
| 399 |
+
buffer_content.append(line_stripped)
|
| 400 |
+
continue
|
| 401 |
+
elif buffer_type == 'table': # Close table
|
| 402 |
+
processed_lines.append({'type': 'table', 'content': buffer_content})
|
| 403 |
+
buffer_content = []
|
| 404 |
+
buffer_type = None
|
| 405 |
+
|
| 406 |
+
# Detect Headers
|
| 407 |
+
if line_stripped.startswith('#'):
|
| 408 |
+
level = line_stripped.count('#')
|
| 409 |
+
text = line_stripped.replace('#', '').strip()
|
| 410 |
+
processed_lines.append({'type': 'header', 'level': min(level, 3), 'content': text})
|
| 411 |
+
continue
|
| 412 |
+
|
| 413 |
+
# Detect Quotes
|
| 414 |
+
if line_stripped.startswith('> '):
|
| 415 |
+
processed_lines.append({'type': 'quote', 'content': line_stripped[2:]})
|
| 416 |
+
continue
|
| 417 |
+
|
| 418 |
+
# Detect Images
|
| 419 |
+
if line_stripped.startswith('http') and line_stripped.lower().endswith(('.png', '.jpg', '.jpeg', '.webp')):
|
| 420 |
+
processed_lines.append({'type': 'image', 'url': line_stripped})
|
| 421 |
+
continue
|
| 422 |
+
|
| 423 |
+
# Detect Lists
|
| 424 |
+
if line_stripped.startswith(('* ', '- ')):
|
| 425 |
+
processed_lines.append({'type': 'list', 'content': line_stripped[2:]})
|
| 426 |
+
continue
|
| 427 |
+
|
| 428 |
+
# Detect HR
|
| 429 |
+
if line_stripped == '---':
|
| 430 |
+
processed_lines.append({'type': 'hr'})
|
| 431 |
+
continue
|
| 432 |
+
|
| 433 |
+
if line_stripped:
|
| 434 |
+
processed_lines.append({'type': 'text', 'content': line_stripped})
|
| 435 |
+
else:
|
| 436 |
+
processed_lines.append({'type': 'empty'})
|
| 437 |
+
|
| 438 |
+
if buffer_type == 'table':
|
| 439 |
+
processed_lines.append({'type': 'table', 'content': buffer_content})
|
| 440 |
+
|
| 441 |
+
return processed_lines
|
| 442 |
+
|
| 443 |
+
# --- UI RENDER ---
|
| 444 |
+
st.title("⚡ Ultimate PDF Engine")
|
| 445 |
+
|
| 446 |
+
with st.expander("ℹ️ Help & Features", expanded=False):
|
| 447 |
+
st.write("- **Smart Symbols:** Writes 'alpha' as α, '->' as →")
|
| 448 |
+
st.write("- **Tables:** Use `| Name | Score |` format")
|
| 449 |
+
st.write("- **Code:** Use ` ``` ` for code blocks")
|
| 450 |
+
st.write("- **Images:** Paste URL on new line")
|
| 451 |
+
|
| 452 |
+
# Settings Sidebar
|
| 453 |
+
with st.sidebar:
|
| 454 |
+
st.header("⚙️ PDF Config")
|
| 455 |
+
filename = st.text_input("Filename", "My_Notes.pdf")
|
| 456 |
+
orientation = st.radio("Orientation", ["Portrait", "Landscape"])
|
| 457 |
+
st.subheader("Filters")
|
| 458 |
+
enable_lms = st.checkbox("Clean LMS Junk", True)
|
| 459 |
+
enable_smart = st.checkbox("Smart Symbols", True)
|
| 460 |
+
enable_header = st.checkbox("Show Header", True)
|
| 461 |
+
font_size = st.slider("Font Size", 8, 24, 12)
|
| 462 |
+
|
| 463 |
+
# Input
|
| 464 |
+
raw_input = st.text_area("Paste text here...", height=350)
|
| 465 |
+
|
| 466 |
+
# Action
|
| 467 |
+
if st.button("🚀 Generate PDF", type="primary"):
|
| 468 |
+
if not raw_input.strip():
|
| 469 |
+
st.warning("Input is empty.")
|
| 470 |
+
return
|
| 471 |
+
|
| 472 |
+
with st.spinner("Processing..."):
|
| 473 |
+
# Setup PDF
|
| 474 |
+
orient_code = 'P' if orientation == "Portrait" else 'L'
|
| 475 |
+
pdf = UltimatePDF(orientation=orient_code)
|
| 476 |
+
pdf.title_meta = filename.replace('.pdf', '')
|
| 477 |
+
pdf.show_header = enable_header
|
| 478 |
+
|
| 479 |
+
pdf.add_page()
|
| 480 |
+
pdf.set_font(pdf.main_font, '', font_size)
|
| 481 |
+
|
| 482 |
+
# Process
|
| 483 |
+
blocks = clean_and_parse(raw_input, use_smart_symbols=enable_smart, clean_lms=enable_lms)
|
| 484 |
+
|
| 485 |
+
# Render
|
| 486 |
+
for block in blocks:
|
| 487 |
+
if block['type'] == 'header':
|
| 488 |
+
pdf.add_markdown_header(block['content'], block['level'])
|
| 489 |
+
elif block['type'] == 'code':
|
| 490 |
+
pdf.add_code_block(block['content'])
|
| 491 |
+
elif block['type'] == 'table':
|
| 492 |
+
pdf.add_table(block['content'])
|
| 493 |
+
elif block['type'] == 'quote':
|
| 494 |
+
pdf.add_blockquote(block['content'])
|
| 495 |
+
elif block['type'] == 'image':
|
| 496 |
+
pdf.add_image_from_url(block['url'])
|
| 497 |
+
elif block['type'] == 'list':
|
| 498 |
+
pdf.set_x(pdf.l_margin + 5)
|
| 499 |
+
pdf.write(8, f"• {block['content']}")
|
| 500 |
+
pdf.ln()
|
| 501 |
+
pdf.set_x(pdf.l_margin)
|
| 502 |
+
elif block['type'] == 'hr':
|
| 503 |
+
pdf.ln(2)
|
| 504 |
+
pdf.line(pdf.l_margin, pdf.get_y(), pdf.w - pdf.r_margin, pdf.get_y())
|
| 505 |
+
pdf.ln(5)
|
| 506 |
+
elif block['type'] == 'text':
|
| 507 |
+
pdf.write(8, block['content'])
|
| 508 |
+
pdf.ln()
|
| 509 |
+
elif block['type'] == 'empty':
|
| 510 |
+
pdf.ln(4)
|
| 511 |
+
|
| 512 |
+
# Download
|
| 513 |
+
try:
|
| 514 |
+
pdf_bytes = pdf.output(dest='S').encode('latin-1', 'replace')
|
| 515 |
+
st.success("PDF Generated Successfully!")
|
| 516 |
+
st.download_button(
|
| 517 |
+
"⬇️ Download PDF",
|
| 518 |
+
data=pdf_bytes,
|
| 519 |
+
file_name=filename if filename.endswith('.pdf') else f"{filename}.pdf",
|
| 520 |
+
mime="application/pdf"
|
| 521 |
+
)
|
| 522 |
+
except Exception as e:
|
| 523 |
+
st.error(f"Error creating PDF file: {e}")
|
| 524 |
+
|
| 525 |
+
|
| 526 |
+
|
| 527 |
+
|
| 528 |
+
|
| 529 |
+
|
| 530 |
+
import streamlit as st
|
| 531 |
+
from huggingface_hub import InferenceClient
|
| 532 |
+
import os
|
| 533 |
+
|
| 534 |
+
# --- 1. The Logic Function (Backend) ---
|
| 535 |
+
# We use @st.cache_data so if the user clicks other buttons, we don't re-run the expensive API call.
|
| 536 |
+
@st.cache_data(show_spinner="Analyzing code with Qwen AI...")
|
| 537 |
+
def get_seo_data(code_snippet, file_type, api_key):
|
| 538 |
+
"""
|
| 539 |
+
Sends code to Hugging Face Inference API and returns SEO/JSON-LD strategy.
|
| 540 |
+
"""
|
| 541 |
+
if not code_snippet:
|
| 542 |
+
return None, "⚠️ Please paste some code first."
|
| 543 |
+
|
| 544 |
+
if not api_key:
|
| 545 |
+
return None, "❌ Error: HF_TOKEN not found in secrets."
|
| 546 |
+
|
| 547 |
+
try:
|
| 548 |
+
client = InferenceClient(api_key=api_key)
|
| 549 |
+
|
| 550 |
+
# Strict Prompt for Qwen 2.5 Coder
|
| 551 |
+
system_instruction = f"""
|
| 552 |
+
You are an expert Technical SEO Specialist. Analyze the user's {file_type} code.
|
| 553 |
+
|
| 554 |
+
Task: Generate Google-compliant JSON-LD structured data and SEO meta tags.
|
| 555 |
+
|
| 556 |
+
Output Format (Strict Markdown):
|
| 557 |
+
## SEO Metadata
|
| 558 |
+
**Title:** [Engaging Title, max 60 chars]
|
| 559 |
+
**Description:** [Summary including keywords, max 160 chars]
|
| 560 |
+
**Keywords:** [5-8 comma-separated keywords]
|
| 561 |
+
|
| 562 |
+
## JSON-LD Structured Data
|
| 563 |
+
```json
|
| 564 |
+
[Insert VALID JSON-LD here.
|
| 565 |
+
- If Python: Use schema.org/SoftwareSourceCode
|
| 566 |
+
- If HTML: Use schema.org/WebPage or schema.org/TechArticle]
|
| 567 |
+
```
|
| 568 |
+
"""
|
| 569 |
+
|
| 570 |
+
user_message = f"Analyze this {file_type} code:\n\n{code_snippet}"
|
| 571 |
+
|
| 572 |
+
response = client.chat_completion(
|
| 573 |
+
model="Qwen/Qwen2.5-Coder-32B-Instruct",
|
| 574 |
+
messages=[
|
| 575 |
+
{"role": "system", "content": system_instruction},
|
| 576 |
+
{"role": "user", "content": user_message}
|
| 577 |
+
],
|
| 578 |
+
max_tokens=1500,
|
| 579 |
+
temperature=0.2
|
| 580 |
+
)
|
| 581 |
+
return response.choices[0].message.content, None
|
| 582 |
+
|
| 583 |
+
except Exception as e:
|
| 584 |
+
return None, f"Error: {str(e)}"
|
| 585 |
+
|
| 586 |
+
# --- 2. The UI Function (Frontend) ---
|
| 587 |
+
def render_seo_ui():
|
| 588 |
+
"""
|
| 589 |
+
Call this function in your main app.py where you want the SEO tool to show up.
|
| 590 |
+
"""
|
| 591 |
+
st.header("🚀 AI Code-to-SEO Generator")
|
| 592 |
+
st.markdown("Generate **JSON-LD** and **Meta Tags** for your Python/HTML files using Qwen 2.5 Coder.")
|
| 593 |
+
|
| 594 |
+
# Get Token (Try secrets first, then env var)
|
| 595 |
+
try:
|
| 596 |
+
hf_token = st.secrets["HF_TOKEN"]
|
| 597 |
+
except:
|
| 598 |
+
hf_token = os.getenv("HF_TOKEN")
|
| 599 |
+
|
| 600 |
+
with st.form("seo_form"):
|
| 601 |
+
col1, col2 = st.columns([1, 3])
|
| 602 |
+
|
| 603 |
+
with col1:
|
| 604 |
+
file_type = st.radio("File Type", ["Python", "HTML"])
|
| 605 |
+
|
| 606 |
+
with col2:
|
| 607 |
+
code_input = st.text_area("Paste Code Here", height=300, placeholder="import os...")
|
| 608 |
+
|
| 609 |
+
submitted = st.form_submit_button("✨ Generate SEO Data")
|
| 610 |
+
|
| 611 |
+
if submitted:
|
| 612 |
+
if not hf_token:
|
| 613 |
+
st.error("Authentication Error: Please add `HF_TOKEN` to your Streamlit secrets.")
|
| 614 |
+
else:
|
| 615 |
+
result, error = get_seo_data(code_input, file_type, hf_token)
|
| 616 |
+
|
| 617 |
+
if error:
|
| 618 |
+
st.error(error)
|
| 619 |
+
else:
|
| 620 |
+
st.success("SEO Data Generated Successfully!")
|
| 621 |
+
st.markdown("---")
|
| 622 |
+
st.markdown(result)
|
| 623 |
+
|
| 624 |
+
# Optional: Add a copy button for the raw result
|
| 625 |
+
st.download_button(
|
| 626 |
+
label="📥 Download Result",
|
| 627 |
+
data=result,
|
| 628 |
+
file_name="seo_strategy.md",
|
| 629 |
+
mime="text/markdown"
|
| 630 |
+
)
|
| 631 |
+
|
| 632 |
+
|
| 633 |
+
|
| 634 |
+
|
| 635 |
+
def tool_youtube_downloader():
|
| 636 |
+
st.header("🎥 YouTube Media Extractor")
|
| 637 |
+
url = st.text_input("Paste YouTube URL", placeholder="https://youtube.com/...")
|
| 638 |
+
format_type = st.radio("Format", ["Video (MP4)", "Audio Only (MP3)"], horizontal=True)
|
| 639 |
+
|
| 640 |
+
if url and st.button("🚀 Process Media"):
|
| 641 |
+
with st.spinner("Contacting server..."):
|
| 642 |
+
try:
|
| 643 |
+
headers = {"Accept": "application/json", "Content-Type": "application/json"}
|
| 644 |
+
payload = {
|
| 645 |
+
"url": url,
|
| 646 |
+
"vQuality": "1080",
|
| 647 |
+
"isAudioOnly": True if "Audio" in format_type else False
|
| 648 |
+
}
|
| 649 |
+
# Using Cobalt API
|
| 650 |
+
response = requests.post("https://api.cobalt.tools/api/json", headers=headers, json=payload)
|
| 651 |
+
data = response.json()
|
| 652 |
+
|
| 653 |
+
if "url" in data:
|
| 654 |
+
st.success("✅ Ready!")
|
| 655 |
+
st.link_button(f"⬇️ Download {format_type}", data["url"])
|
| 656 |
+
if "Audio" not in format_type:
|
| 657 |
+
st.video(data["url"])
|
| 658 |
+
else:
|
| 659 |
+
st.audio(data["url"])
|
| 660 |
+
else:
|
| 661 |
+
st.error(f"Error: {data.get('text', 'Unknown error')}")
|
| 662 |
+
except Exception as e:
|
| 663 |
+
st.error(f"Connection failed: {str(e)}")
|
| 664 |
+
|
| 665 |
+
def tool_smart_converter():
|
| 666 |
+
with st.spinner("Starting File Engine..."):
|
| 667 |
+
|
| 668 |
+
time.sleep(0.3)
|
| 669 |
+
|
| 670 |
+
|
| 671 |
+
import pandas as pd
|
| 672 |
+
st.header("🔄 Smart File Converter")
|
| 673 |
+
st.info("Supports: Images (PNG/JPG/WEBP) and Data (CSV/JSON/Excel)")
|
| 674 |
+
|
| 675 |
+
uploaded_file = st.file_uploader("Upload File", type=['png', 'jpg', 'jpeg', 'webp', 'csv', 'json', 'xlsx'],key="smart_conv_upload")
|
| 676 |
+
|
| 677 |
+
if uploaded_file:
|
| 678 |
+
file_type = uploaded_file.name.split('.')[-1].lower()
|
| 679 |
+
|
| 680 |
+
# LOGIC: IMAGE CONVERSION
|
| 681 |
+
if file_type in ['png', 'jpg', 'jpeg', 'webp']:
|
| 682 |
+
image = Image.open(uploaded_file)
|
| 683 |
+
st.image(image, caption="Preview", width=300)
|
| 684 |
+
target_format = st.selectbox("Convert to:", ["PNG", "JPEG", "WEBP", "PDF"])
|
| 685 |
+
|
| 686 |
+
if st.button("Convert Image"):
|
| 687 |
+
buf = io.BytesIO()
|
| 688 |
+
# RGB required for JPEG/PDF
|
| 689 |
+
if image.mode in ("RGBA", "P") and target_format in ["JPEG", "PDF"]:
|
| 690 |
+
image = image.convert("RGB")
|
| 691 |
+
|
| 692 |
+
image.save(buf, format=target_format)
|
| 693 |
+
st.download_button(f"Download {target_format}", data=buf.getvalue(), file_name=f"converted.{target_format.lower()}")
|
| 694 |
+
|
| 695 |
+
# LOGIC: DATA CONVERSION
|
| 696 |
+
elif file_type in ['csv', 'json', 'xlsx']:
|
| 697 |
+
df = None
|
| 698 |
+
try:
|
| 699 |
+
if file_type == 'csv': df = pd.read_csv(uploaded_file)
|
| 700 |
+
elif file_type == 'json': df = pd.read_json(uploaded_file)
|
| 701 |
+
elif file_type == 'xlsx': df = pd.read_excel(uploaded_file)
|
| 702 |
+
|
| 703 |
+
st.write("Data Preview:", df.head())
|
| 704 |
+
target_data = st.selectbox("Convert to:", ["CSV", "JSON", "Excel"])
|
| 705 |
+
|
| 706 |
+
if st.button("Convert Data"):
|
| 707 |
+
buf = io.BytesIO()
|
| 708 |
+
if target_data == "CSV":
|
| 709 |
+
df.to_csv(buf, index=False)
|
| 710 |
+
ext = "csv"
|
| 711 |
+
elif target_data == "JSON":
|
| 712 |
+
df.to_json(buf, orient='records')
|
| 713 |
+
ext = "json"
|
| 714 |
+
elif target_data == "Excel":
|
| 715 |
+
df.to_excel(buf, index=False)
|
| 716 |
+
ext = "xlsx"
|
| 717 |
+
|
| 718 |
+
st.download_button(f"Download {target_data}", data=buf.getvalue(), file_name=f"converted.{ext}")
|
| 719 |
+
except Exception as e:
|
| 720 |
+
st.error(f"Error reading file: {e}")
|
| 721 |
+
|
| 722 |
+
def tool_image_compressor():
|
| 723 |
+
st.header("📉 Image Compressor")
|
| 724 |
+
# ADD key="compressor"
|
| 725 |
+
uploaded_file = st.file_uploader("Upload Image", type=['png', 'jpg', 'jpeg'], key="compressor")
|
| 726 |
+
|
| 727 |
+
|
| 728 |
+
|
| 729 |
+
if uploaded_file:
|
| 730 |
+
image = Image.open(uploaded_file)
|
| 731 |
+
st.write(f"Original Size: {uploaded_file.size / 1024:.2f} KB")
|
| 732 |
+
quality = st.slider("Quality (Lower = Smaller file)", 10, 95, 60)
|
| 733 |
+
|
| 734 |
+
if st.button("Compress"):
|
| 735 |
+
buf = io.BytesIO()
|
| 736 |
+
if image.mode in ("RGBA", "P"): image = image.convert("RGB")
|
| 737 |
+
image.save(buf, format="JPEG", quality=quality, optimize=True)
|
| 738 |
+
|
| 739 |
+
size_kb = len(buf.getvalue()) / 1024
|
| 740 |
+
st.success(f"Compressed Size: {size_kb:.2f} KB")
|
| 741 |
+
st.download_button("Download Compressed Image", data=buf.getvalue(), file_name="compressed.jpg")
|
| 742 |
+
|
| 743 |
+
def tool_image_resizer():
|
| 744 |
+
st.header("📐 Image Resizer")
|
| 745 |
+
# ADD key="resizer"
|
| 746 |
+
uploaded_file = st.file_uploader("Upload Image", type=['png', 'jpg', 'jpeg', 'webp'], key="resizer")
|
| 747 |
+
|
| 748 |
+
|
| 749 |
+
|
| 750 |
+
if uploaded_file:
|
| 751 |
+
image = Image.open(uploaded_file)
|
| 752 |
+
st.write(f"Current Dimensions: {image.size}")
|
| 753 |
+
|
| 754 |
+
col1, col2 = st.columns(2)
|
| 755 |
+
w = col1.number_input("Width", value=image.width)
|
| 756 |
+
h = col2.number_input("Height", value=image.height)
|
| 757 |
+
|
| 758 |
+
if st.button("Resize"):
|
| 759 |
+
new_img = image.resize((int(w), int(h)))
|
| 760 |
+
buf = io.BytesIO()
|
| 761 |
+
new_img.save(buf, format=image.format)
|
| 762 |
+
st.image(new_img, caption="Resized Preview")
|
| 763 |
+
st.download_button("Download Resized Image", data=buf.getvalue(), file_name=f"resized.{image.format.lower()}")
|
| 764 |
+
|
| 765 |
+
def tool_thumbnail_generator():
|
| 766 |
+
st.header("🚀 Ultimate Thumbnail Studio")
|
| 767 |
+
|
| 768 |
+
# --- DEPENDENCIES ---
|
| 769 |
+
from PIL import Image, ImageDraw, ImageFont, ImageFilter, ImageEnhance, ImageOps, ImageColor
|
| 770 |
+
import io
|
| 771 |
+
|
| 772 |
+
# --- LAYOUT FIX FOR PC/MOBILE ---
|
| 773 |
+
# We use a container for the controls to keep them tidy on PC
|
| 774 |
+
# and "use_column_width" to fix the crash.
|
| 775 |
+
|
| 776 |
+
# Top row: All Controls (Split into 2 columns for better PC spacing)
|
| 777 |
+
with st.expander("🎨 Thumbnail Settings & Controls", expanded=True):
|
| 778 |
+
col_c1, col_c2 = st.columns(2)
|
| 779 |
+
|
| 780 |
+
# --- LEFT CONTROL COLUMN ---
|
| 781 |
+
with col_c1:
|
| 782 |
+
st.subheader("1. Background & Filters")
|
| 783 |
+
bg_mode = st.radio("Background Type", ["Upload Image", "Solid Color", "Gradient"], horizontal=True)
|
| 784 |
+
|
| 785 |
+
bg_image = None
|
| 786 |
+
if bg_mode == "Upload Image":
|
| 787 |
+
bg_file = st.file_uploader("Upload BG", type=['png', 'jpg', 'jpeg', 'webp'], key="max_bg")
|
| 788 |
+
if bg_file: bg_image = Image.open(bg_file).convert("RGBA")
|
| 789 |
+
elif bg_mode == "Solid Color":
|
| 790 |
+
hex_bg = st.color_picker("Pick Color", "#1E1E1E")
|
| 791 |
+
st.session_state.thumb_bg_color = hex_bg
|
| 792 |
+
else: # Gradient
|
| 793 |
+
c1_col, c2_col = st.columns(2)
|
| 794 |
+
grad_c1 = c1_col.color_picker("Start", "#12c2e9")
|
| 795 |
+
grad_c2 = c2_col.color_picker("End", "#c471ed")
|
| 796 |
+
grad_dir = st.selectbox("Direction", ["Horizontal", "Vertical"])
|
| 797 |
+
|
| 798 |
+
st.markdown("---")
|
| 799 |
+
st.write("**Filters**")
|
| 800 |
+
f1, f2 = st.columns(2)
|
| 801 |
+
blur_amt = f1.slider("Blur", 0, 20, 0)
|
| 802 |
+
brightness = f2.slider("Brightness", 0.5, 1.5, 1.0)
|
| 803 |
+
|
| 804 |
+
# --- RIGHT CONTROL COLUMN ---
|
| 805 |
+
with col_c2:
|
| 806 |
+
st.subheader("2. Text & Branding")
|
| 807 |
+
main_text = st.text_area("Main Title", "THE ULTIMATE\nGUIDE TO PYTHON", height=100)
|
| 808 |
+
|
| 809 |
+
t1, t2 = st.columns(2)
|
| 810 |
+
font_size = t1.slider("Size", 20, 200, 80)
|
| 811 |
+
font_color = t2.color_picker("Text Color", "#FFFFFF")
|
| 812 |
+
|
| 813 |
+
st.write("**Styling**")
|
| 814 |
+
s1, s2 = st.columns(2)
|
| 815 |
+
stroke_width = s1.slider("Outline", 0, 10, 2)
|
| 816 |
+
stroke_color = s2.color_picker("Outline Color", "#000000")
|
| 817 |
+
|
| 818 |
+
st.markdown("---")
|
| 819 |
+
logo_file = st.file_uploader("Upload Logo (PNG)", type=['png', 'webp'], key="max_logo")
|
| 820 |
+
if logo_file:
|
| 821 |
+
l1, l2 = st.columns(2)
|
| 822 |
+
logo_size = l1.slider("Logo Size", 50, 300, 150)
|
| 823 |
+
logo_pos = l2.selectbox("Position", ["Top-Right", "Top-Left", "Bottom-Right", "Bottom-Left"])
|
| 824 |
+
else:
|
| 825 |
+
logo_size = 150
|
| 826 |
+
logo_pos = "Top-Right"
|
| 827 |
+
|
| 828 |
+
# --- PREVIEW SECTION (Full Width for PC clarity) ---
|
| 829 |
+
st.subheader("👁️ Preview & Download")
|
| 830 |
+
|
| 831 |
+
# GENERATION LOGIC
|
| 832 |
+
CANVAS_W, CANVAS_H = 1280, 720
|
| 833 |
+
canvas = Image.new("RGBA", (CANVAS_W, CANVAS_H), (0,0,0,0))
|
| 834 |
+
|
| 835 |
+
# 1. Background
|
| 836 |
+
if bg_mode == "Upload Image" and bg_image:
|
| 837 |
+
bg_ratio = bg_image.width / bg_image.height
|
| 838 |
+
target_ratio = CANVAS_W / CANVAS_H
|
| 839 |
+
if bg_ratio > target_ratio:
|
| 840 |
+
new_h = CANVAS_H
|
| 841 |
+
new_w = int(new_h * bg_ratio)
|
| 842 |
+
else:
|
| 843 |
+
new_w = CANVAS_W
|
| 844 |
+
new_h = int(new_w / bg_ratio)
|
| 845 |
+
bg_image = bg_image.resize((new_w, new_h), Image.Resampling.LANCZOS)
|
| 846 |
+
left = (new_w - CANVAS_W)/2
|
| 847 |
+
top = (new_h - CANVAS_H)/2
|
| 848 |
+
bg_image = bg_image.crop((left, top, left+CANVAS_W, top+CANVAS_H))
|
| 849 |
+
canvas.paste(bg_image, (0,0))
|
| 850 |
+
elif bg_mode == "Gradient":
|
| 851 |
+
base = Image.new('RGB', (CANVAS_W, CANVAS_H), grad_c1)
|
| 852 |
+
top_img = Image.new('RGB', (CANVAS_W, CANVAS_H), grad_c2)
|
| 853 |
+
mask = Image.new("L", (CANVAS_W, CANVAS_H))
|
| 854 |
+
mask_data = []
|
| 855 |
+
for y in range(CANVAS_H):
|
| 856 |
+
for x in range(CANVAS_W):
|
| 857 |
+
if grad_dir == "Vertical": mask_data.append(int(255 * (y / CANVAS_H)))
|
| 858 |
+
else: mask_data.append(int(255 * (x / CANVAS_W)))
|
| 859 |
+
mask.putdata(mask_data)
|
| 860 |
+
canvas = Image.composite(top_img, base, mask).convert("RGBA")
|
| 861 |
+
else:
|
| 862 |
+
if 'thumb_bg_color' not in st.session_state: st.session_state.thumb_bg_color = "#1E1E1E"
|
| 863 |
+
canvas = Image.new("RGBA", (CANVAS_W, CANVAS_H), st.session_state.thumb_bg_color)
|
| 864 |
+
|
| 865 |
+
# 2. Filters
|
| 866 |
+
if blur_amt > 0: canvas = canvas.filter(ImageFilter.GaussianBlur(blur_amt))
|
| 867 |
+
if brightness != 1.0:
|
| 868 |
+
enhancer = ImageEnhance.Brightness(canvas)
|
| 869 |
+
canvas = enhancer.enhance(brightness)
|
| 870 |
+
|
| 871 |
+
# 3. Text
|
| 872 |
+
txt_layer = Image.new("RGBA", canvas.size, (255,255,255,0))
|
| 873 |
+
draw = ImageDraw.Draw(txt_layer)
|
| 874 |
+
|
| 875 |
+
try:
|
| 876 |
+
font = ImageFont.truetype("arialbd.ttf", font_size)
|
| 877 |
+
except:
|
| 878 |
+
try:
|
| 879 |
+
font = ImageFont.truetype("arial.ttf", font_size)
|
| 880 |
+
except:
|
| 881 |
+
font = ImageFont.load_default()
|
| 882 |
+
|
| 883 |
+
# Center Text Calculation
|
| 884 |
+
# Using basic textsize for compatibility if textbbox fails in older Pillow
|
| 885 |
+
try:
|
| 886 |
+
bbox = draw.multiline_textbbox((0,0), main_text, font=font, align="center")
|
| 887 |
+
text_w = bbox[2] - bbox[0]
|
| 888 |
+
text_h = bbox[3] - bbox[1]
|
| 889 |
+
except:
|
| 890 |
+
# Fallback for very old Pillow versions
|
| 891 |
+
text_w, text_h = draw.textsize(main_text, font=font)
|
| 892 |
+
|
| 893 |
+
cx, cy = CANVAS_W // 2, CANVAS_H // 2
|
| 894 |
+
text_x = cx - (text_w // 2)
|
| 895 |
+
text_y = cy - (text_h // 2)
|
| 896 |
+
|
| 897 |
+
# Shadow
|
| 898 |
+
draw.multiline_text((text_x + 8, text_y + 8), main_text, font=font, align="center", fill="black")
|
| 899 |
+
# Main Text
|
| 900 |
+
draw.multiline_text((text_x, text_y), main_text, font=font, align="center", fill=font_color, stroke_width=stroke_width, stroke_fill=stroke_color)
|
| 901 |
+
|
| 902 |
+
# 4. Logo
|
| 903 |
+
if logo_file:
|
| 904 |
+
logo = Image.open(logo_file).convert("RGBA")
|
| 905 |
+
logo.thumbnail((logo_size, logo_size), Image.Resampling.LANCZOS)
|
| 906 |
+
pad = 30
|
| 907 |
+
if "Top" in logo_pos: ly = pad
|
| 908 |
+
elif "Bottom" in logo_pos: ly = CANVAS_H - logo.height - pad
|
| 909 |
+
if "Left" in logo_pos: lx = pad
|
| 910 |
+
elif "Right" in logo_pos: lx = CANVAS_W - logo.width - pad
|
| 911 |
+
txt_layer.paste(logo, (lx, ly), logo)
|
| 912 |
+
|
| 913 |
+
# Final Composite
|
| 914 |
+
final_comp = Image.alpha_composite(canvas, txt_layer)
|
| 915 |
+
|
| 916 |
+
# --- DISPLAY & DOWNLOAD ---
|
| 917 |
+
# Centered layout for PC look
|
| 918 |
+
col_show, col_down = st.columns([3, 1])
|
| 919 |
+
|
| 920 |
+
with col_show:
|
| 921 |
+
# FIX: Changed use_container_width to use_column_width
|
| 922 |
+
st.image(final_comp, caption="Result", use_column_width=True)
|
| 923 |
+
|
| 924 |
+
with col_down:
|
| 925 |
+
st.write("### Ready?")
|
| 926 |
+
buf = io.BytesIO()
|
| 927 |
+
final_comp.convert("RGB").save(buf, format="PNG")
|
| 928 |
+
st.download_button("💾 Download PNG", data=buf.getvalue(), file_name="thumbnail.png", mime="image/png")
|
| 929 |
+
# --- MAIN ROUTER (Paste this at the VERY END of app.py) ---
|
| 930 |
+
# This checks the URL params and decides which function to run
|
| 931 |
+
if __name__ == "__main__":
|
| 932 |
+
st.set_page_config(page_title="Lexical Space Tools", layout="centered")
|
| 933 |
+
|
| 934 |
+
# Get the 'mode' from the URL (e.g. ?mode=youtube)
|
| 935 |
+
params = st.query_params
|
| 936 |
+
mode = params.get("mode", "home")
|
| 937 |
+
|
| 938 |
+
|
| 939 |
+
|
| 940 |
+
|
| 941 |
+
|
| 942 |
+
# --- BATCH 2: WEBMASTER & SEO FUNCTIONS ---
|
| 943 |
+
|
| 944 |
+
def tool_meta_tag_generator():
|
| 945 |
+
st.header("🏷️ Meta Tag Generator")
|
| 946 |
+
title = st.text_input("Site Title", "My Awesome Blog")
|
| 947 |
+
desc = st.text_area("Description", "A blog about technology and coding.")
|
| 948 |
+
keywords = st.text_input("Keywords (comma separated)", "tech, coding, python")
|
| 949 |
+
author = st.text_input("Author", "Lexical Space")
|
| 950 |
+
|
| 951 |
+
if st.button("Generate Tags"):
|
| 952 |
+
code = f"""
|
| 953 |
+
<title>{title}</title>
|
| 954 |
+
<meta name="description" content="{desc}">
|
| 955 |
+
<meta name="keywords" content="{keywords}">
|
| 956 |
+
<meta name="author" content="{author}">
|
| 957 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 958 |
+
<meta property="og:type" content="website">
|
| 959 |
+
<meta property="og:title" content="{title}">
|
| 960 |
+
<meta property="og:description" content="{desc}">
|
| 961 |
+
"""
|
| 962 |
+
st.code(code, language="html")
|
| 963 |
+
|
| 964 |
+
def tool_slug_generator():
|
| 965 |
+
st.header("🐌 URL Slug Generator")
|
| 966 |
+
text = st.text_input("Enter Post Title", "How to Install Python on Windows 10!")
|
| 967 |
+
|
| 968 |
+
if text:
|
| 969 |
+
# Lowercase, strip whitespace, replace spaces with dashes, remove non-alphanumeric
|
| 970 |
+
slug = text.lower().strip()
|
| 971 |
+
slug = re.sub(r'[^a-z0-9\s-]', '', slug)
|
| 972 |
+
slug = re.sub(r'[\s-]+', '-', slug)
|
| 973 |
+
st.success(f"Slug: {slug}")
|
| 974 |
+
st.code(slug, language="text")
|
| 975 |
+
|
| 976 |
+
def tool_robots_generator():
|
| 977 |
+
st.header("🤖 Robots.txt Generator")
|
| 978 |
+
st.write("Control which crawlers can access your site.")
|
| 979 |
+
|
| 980 |
+
all_agents = st.checkbox("Apply to all robots (*)", value=True)
|
| 981 |
+
disallow_admin = st.checkbox("Disallow /admin", value=True)
|
| 982 |
+
disallow_private = st.checkbox("Disallow /private", value=False)
|
| 983 |
+
sitemap_url = st.text_input("Sitemap URL (Optional)", "https://yoursite.com/sitemap.xml")
|
| 984 |
+
|
| 985 |
+
if st.button("Generate Robots.txt"):
|
| 986 |
+
agent = "*" if all_agents else "Googlebot"
|
| 987 |
+
txt = f"User-agent: {agent}\n"
|
| 988 |
+
if disallow_admin: txt += "Disallow: /admin/\n"
|
| 989 |
+
if disallow_private: txt += "Disallow: /private/\n"
|
| 990 |
+
if sitemap_url: txt += f"\nSitemap: {sitemap_url}"
|
| 991 |
+
|
| 992 |
+
st.text_area("Result", txt, height=150)
|
| 993 |
+
|
| 994 |
+
def tool_sitemap_builder():
|
| 995 |
+
st.header("🗺️ XML Sitemap Builder")
|
| 996 |
+
urls = st.text_area("Paste URLs (one per line)", "https://site.com\nhttps://site.com/about")
|
| 997 |
+
|
| 998 |
+
if st.button("Build XML"):
|
| 999 |
+
xml = '<?xml version="1.0" encoding="UTF-8"?>\n'
|
| 1000 |
+
xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
|
| 1001 |
+
|
| 1002 |
+
for url in urls.split('\n'):
|
| 1003 |
+
if url.strip():
|
| 1004 |
+
xml += f' <url>\n <loc>{url.strip()}</loc>\n <changefreq>monthly</changefreq>\n </url>\n'
|
| 1005 |
+
|
| 1006 |
+
xml += '</urlset>'
|
| 1007 |
+
st.text_area("Sitemap.xml", xml, height=200)
|
| 1008 |
+
|
| 1009 |
+
def tool_keyword_density():
|
| 1010 |
+
with st.spinner("Loading Analytics..."):
|
| 1011 |
+
import pandas as pd
|
| 1012 |
+
from collections import Counter
|
| 1013 |
+
|
| 1014 |
+
|
| 1015 |
+
st.header("📊 Keyword Density Checker")
|
| 1016 |
+
text = st.text_area("Paste Article Text", height=200,key="density_text")
|
| 1017 |
+
|
| 1018 |
+
if st.button("Analyze"):
|
| 1019 |
+
# Simple stopword list to ignore
|
| 1020 |
+
stopwords = set(['the', 'and', 'is', 'in', 'it', 'of', 'to', 'a', 'for', 'on', 'that', 'with', 'as'])
|
| 1021 |
+
|
| 1022 |
+
words = re.findall(r'\w+', text.lower())
|
| 1023 |
+
filtered = [w for w in words if w not in stopwords and len(w) > 2]
|
| 1024 |
+
|
| 1025 |
+
counts = Counter(filtered).most_common(10)
|
| 1026 |
+
|
| 1027 |
+
df = pd.DataFrame(counts, columns=["Keyword", "Count"])
|
| 1028 |
+
df['Density %'] = (df['Count'] / len(words) * 100).round(2)
|
| 1029 |
+
st.table(df)
|
| 1030 |
+
|
| 1031 |
+
def tool_plagiarism_check():
|
| 1032 |
+
st.header("🕵️ Plagiarism Scanner (Google Check)")
|
| 1033 |
+
st.info("Splits text into sentences and searches Google for exact matches.")
|
| 1034 |
+
text = st.text_area("Paste Text to Check", height=150,key="plag_text")
|
| 1035 |
+
|
| 1036 |
+
if st.button("Check Text"):
|
| 1037 |
+
sentences = re.split(r'[.!?]', text)
|
| 1038 |
+
clean_sentences = [s.strip() for s in sentences if len(s.strip()) > 20]
|
| 1039 |
+
|
| 1040 |
+
for i, s in enumerate(clean_sentences[:5]): # Limit to first 5 for demo
|
| 1041 |
+
query = f'"{s}"'
|
| 1042 |
+
url = f"https://www.google.com/search?q={query}"
|
| 1043 |
+
st.markdown(f"**Sentence {i+1}:** {s[:50]}...")
|
| 1044 |
+
st.link_button(f"🔍 Check Google for Match", url)
|
| 1045 |
+
|
| 1046 |
+
def tool_code_minifier():
|
| 1047 |
+
st.header("🧹 Code Minifier")
|
| 1048 |
+
mode = st.radio("Type", ["HTML", "CSS"])
|
| 1049 |
+
raw_code = st.text_area("Input Code", height=200)
|
| 1050 |
+
|
| 1051 |
+
if st.button("Minify"):
|
| 1052 |
+
minified = ""
|
| 1053 |
+
if mode == "HTML":
|
| 1054 |
+
# Basic whitespace removal between tags
|
| 1055 |
+
lines = raw_code.split('\n')
|
| 1056 |
+
minified = "".join([line.strip() for line in lines])
|
| 1057 |
+
elif mode == "CSS":
|
| 1058 |
+
# Remove comments and whitespace
|
| 1059 |
+
# 1. Remove comments
|
| 1060 |
+
minified = re.sub(r'/\*[\s\S]*?\*/', '', raw_code)
|
| 1061 |
+
# 2. Remove whitespace around braces/colons
|
| 1062 |
+
minified = re.sub(r'\s*([{:;,])\s*', r'\1', minified)
|
| 1063 |
+
# 3. Remove newlines
|
| 1064 |
+
minified = minified.replace('\n', '').replace('\r', '')
|
| 1065 |
+
|
| 1066 |
+
st.text_area("Minified Output", minified, height=200)
|
| 1067 |
+
# --- BATCH 3: DEVELOPER TOOLS ---
|
| 1068 |
+
|
| 1069 |
+
def tool_qr_generator():
|
| 1070 |
+
with st.spinner("Initializing QR Tool..."):
|
| 1071 |
+
import qrcode
|
| 1072 |
+
import io
|
| 1073 |
+
|
| 1074 |
+
st.header("🏁 QR Code Generator")
|
| 1075 |
+
data = st.text_input("Enter Link or Text", "https://lexicalspace.blogspot.com")
|
| 1076 |
+
|
| 1077 |
+
if data:
|
| 1078 |
+
qr = qrcode.QRCode(version=1, box_size=10, border=5)
|
| 1079 |
+
qr.add_data(data)
|
| 1080 |
+
qr.make(fit=True)
|
| 1081 |
+
img = qr.make_image(fill='black', back_color='white')
|
| 1082 |
+
|
| 1083 |
+
buf = io.BytesIO()
|
| 1084 |
+
img.save(buf)
|
| 1085 |
+
st.image(img.get_image(), width=300)
|
| 1086 |
+
st.download_button("Download QR", data=buf.getvalue(), file_name="qrcode.png")
|
| 1087 |
+
|
| 1088 |
+
def tool_json_formatter():
|
| 1089 |
+
st.header("✨ JSON Prettifier")
|
| 1090 |
+
raw = st.text_area("Paste Messy JSON", '{"id":1,"name":"Lexical","roles":["admin","dev"]}', height=150)
|
| 1091 |
+
|
| 1092 |
+
col1, col2 = st.columns(2)
|
| 1093 |
+
if col1.button("Format (Pretty)"):
|
| 1094 |
+
try:
|
| 1095 |
+
parsed = json.loads(raw)
|
| 1096 |
+
st.code(json.dumps(parsed, indent=4), language="json")
|
| 1097 |
+
except Exception as e:
|
| 1098 |
+
st.error(f"Invalid JSON: {e}")
|
| 1099 |
+
|
| 1100 |
+
if col2.button("Minify (Compact)"):
|
| 1101 |
+
try:
|
| 1102 |
+
parsed = json.loads(raw)
|
| 1103 |
+
st.code(json.dumps(parsed, separators=(',', ':')), language="json")
|
| 1104 |
+
except Exception as e:
|
| 1105 |
+
st.error(f"Invalid JSON: {e}")
|
| 1106 |
+
|
| 1107 |
+
def tool_base64():
|
| 1108 |
+
st.header("🔐 Base64 Converter")
|
| 1109 |
+
mode = st.radio("Action", ["Encode", "Decode"], horizontal=True)
|
| 1110 |
+
text = st.text_area("Input Text")
|
| 1111 |
+
|
| 1112 |
+
if st.button("Process"):
|
| 1113 |
+
try:
|
| 1114 |
+
if mode == "Encode":
|
| 1115 |
+
res = base64.b64encode(text.encode()).decode()
|
| 1116 |
+
else:
|
| 1117 |
+
res = base64.b64decode(text).decode()
|
| 1118 |
+
st.code(res)
|
| 1119 |
+
except Exception as e:
|
| 1120 |
+
st.error(f"Error: {e}")
|
| 1121 |
+
|
| 1122 |
+
def tool_url_encoder():
|
| 1123 |
+
st.header("🔗 URL Encoder/Decoder")
|
| 1124 |
+
text = st.text_input("Input URL", "https://example.com/search?q=hello world")
|
| 1125 |
+
|
| 1126 |
+
c1, c2 = st.columns(2)
|
| 1127 |
+
with c1:
|
| 1128 |
+
if st.button("Encode"):
|
| 1129 |
+
st.code(urllib.parse.quote(text))
|
| 1130 |
+
with c2:
|
| 1131 |
+
if st.button("Decode"):
|
| 1132 |
+
st.code(urllib.parse.unquote(text))
|
| 1133 |
+
|
| 1134 |
+
def tool_markdown_editor():
|
| 1135 |
+
st.header("📝 Markdown Editor")
|
| 1136 |
+
|
| 1137 |
+
col1, col2 = st.columns(2)
|
| 1138 |
+
with col1:
|
| 1139 |
+
md_text = st.text_area("Write Markdown", "# Hello\n* Item 1\n* Item 2", height=400)
|
| 1140 |
+
|
| 1141 |
+
with col2:
|
| 1142 |
+
st.markdown("### Preview")
|
| 1143 |
+
st.markdown(md_text)
|
| 1144 |
+
|
| 1145 |
+
def tool_regex_tester():
|
| 1146 |
+
st.header("🧪 Regex Tester")
|
| 1147 |
+
pattern = st.text_input("Regex Pattern", r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b")
|
| 1148 |
+
text = st.text_area("Test String", "Contact us at support@lexical.com or admin@site.org")
|
| 1149 |
+
|
| 1150 |
+
if pattern and text:
|
| 1151 |
+
try:
|
| 1152 |
+
matches = re.findall(pattern, text)
|
| 1153 |
+
st.write(f"Found {len(matches)} matches:")
|
| 1154 |
+
st.json(matches)
|
| 1155 |
+
except Exception as e:
|
| 1156 |
+
st.error(f"Regex Error: {e}")
|
| 1157 |
+
|
| 1158 |
+
def tool_uuid_gen():
|
| 1159 |
+
st.header("🆔 UUID/GUID Generator")
|
| 1160 |
+
count = st.number_input("How many?", 1, 100, 5)
|
| 1161 |
+
|
| 1162 |
+
if st.button("Generate"):
|
| 1163 |
+
uuids = [str(uuid.uuid4()) for _ in range(count)]
|
| 1164 |
+
st.code("\n".join(uuids), language="text")
|
| 1165 |
+
|
| 1166 |
+
def tool_hash_gen():
|
| 1167 |
+
st.header("🔑 Hash Generator")
|
| 1168 |
+
text = st.text_input("Input String", "mypassword")
|
| 1169 |
+
|
| 1170 |
+
if text:
|
| 1171 |
+
st.write("**MD5:**")
|
| 1172 |
+
st.code(hashlib.md5(text.encode()).hexdigest())
|
| 1173 |
+
st.write("**SHA256:**")
|
| 1174 |
+
st.code(hashlib.sha256(text.encode()).hexdigest())
|
| 1175 |
+
# ... (Batch 1 & 2 Routing above) ...
|
| 1176 |
+
|
| 1177 |
+
# BATCH 3 ROUTING
|
| 1178 |
+
elif mode == "qrcode": tool_qr_generator()
|
| 1179 |
+
elif mode == "json": tool_json_formatter()
|
| 1180 |
+
elif mode == "base64": tool_base64()
|
| 1181 |
+
elif mode == "url": tool_url_encoder()
|
| 1182 |
+
elif mode == "markdown": tool_markdown_editor()
|
| 1183 |
+
elif mode == "regex": tool_regex_tester()
|
| 1184 |
+
elif mode == "uuid": tool_uuid_gen()
|
| 1185 |
+
elif mode == "hash": tool_hash_gen()
|
| 1186 |
+
|
| 1187 |
+
# ... (Home Dashboard below) ...
|
| 1188 |
+
|
| 1189 |
+
st.write("### 🛠️ Developer Tools")
|
| 1190 |
+
st.markdown("""
|
| 1191 |
+
* [🏁 QR Code Gen](?mode=qrcode)
|
| 1192 |
+
* [✨ JSON Prettifier](?mode=json)
|
| 1193 |
+
* [🔐 Base64 Converter](?mode=base64)
|
| 1194 |
+
* [🔗 URL Encode/Decode](?mode=url)
|
| 1195 |
+
* [📝 Markdown Editor](?mode=markdown)
|
| 1196 |
+
* [🧪 Regex Tester](?mode=regex)
|
| 1197 |
+
* [🆔 UUID Generator](?mode=uuid)
|
| 1198 |
+
* [🔑 Hash Generator](?mode=hash)
|
| 1199 |
+
""")
|
| 1200 |
+
# --- BATCH 4: TEXT, UTILITIES & EXTRAS ---
|
| 1201 |
+
|
| 1202 |
+
def tool_case_converter():
|
| 1203 |
+
st.header("🔠 Case Converter")
|
| 1204 |
+
text = st.text_area("Input Text", "hello world")
|
| 1205 |
+
|
| 1206 |
+
c1, c2, c3, c4 = st.columns(4)
|
| 1207 |
+
if c1.button("UPPERCASE"): st.code(text.upper(), language="text")
|
| 1208 |
+
if c2.button("lowercase"): st.code(text.lower(), language="text")
|
| 1209 |
+
if c3.button("Title Case"): st.code(text.title(), language="text")
|
| 1210 |
+
if c4.button("aLtErNaTiNg"):
|
| 1211 |
+
res = "".join([c.upper() if i%2==0 else c.lower() for i, c in enumerate(text)])
|
| 1212 |
+
st.code(res, language="text")
|
| 1213 |
+
|
| 1214 |
+
def tool_lorem_ipsum():
|
| 1215 |
+
st.header("📜 Lorem Ipsum Generator")
|
| 1216 |
+
paras = st.slider("Paragraphs", 1, 10, 3)
|
| 1217 |
+
|
| 1218 |
+
dummy_text = [
|
| 1219 |
+
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
| 1220 |
+
"Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
| 1221 |
+
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.",
|
| 1222 |
+
"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum.",
|
| 1223 |
+
"Excepteur sint occaecat cupidatat non proident, sunt in culpa."
|
| 1224 |
+
]
|
| 1225 |
+
|
| 1226 |
+
if st.button("Generate"):
|
| 1227 |
+
result = "\n\n".join([random.choice(dummy_text) * 3 for _ in range(paras)])
|
| 1228 |
+
st.text_area("Result", result, height=200)
|
| 1229 |
+
|
| 1230 |
+
def tool_word_counter():
|
| 1231 |
+
st.header("🧮 Word & Character Counter")
|
| 1232 |
+
text = st.text_area("Paste Text Here", height=200,key="counter_text")
|
| 1233 |
+
|
| 1234 |
+
if text:
|
| 1235 |
+
words = len(text.split())
|
| 1236 |
+
chars = len(text)
|
| 1237 |
+
no_space = len(text.replace(" ", ""))
|
| 1238 |
+
read_time = round(words / 200, 2)
|
| 1239 |
+
|
| 1240 |
+
c1, c2, c3, c4 = st.columns(4)
|
| 1241 |
+
c1.metric("Words", words)
|
| 1242 |
+
c2.metric("Chars", chars)
|
| 1243 |
+
c3.metric("No Spaces", no_space)
|
| 1244 |
+
c4.metric("Read Time", f"{read_time} min")
|
| 1245 |
+
|
| 1246 |
+
def tool_remove_duplicates():
|
| 1247 |
+
st.header("🗑️ Remove Duplicate Lines")
|
| 1248 |
+
text = st.text_area("Paste List (One per line)", "Apple\nBanana\nApple\nOrange")
|
| 1249 |
+
|
| 1250 |
+
if st.button("Clean"):
|
| 1251 |
+
lines = text.split('\n')
|
| 1252 |
+
seen = set()
|
| 1253 |
+
clean = []
|
| 1254 |
+
for line in lines:
|
| 1255 |
+
if line not in seen and line.strip():
|
| 1256 |
+
clean.append(line)
|
| 1257 |
+
seen.add(line)
|
| 1258 |
+
st.text_area("Cleaned List", "\n".join(clean), height=200)
|
| 1259 |
+
|
| 1260 |
+
def tool_text_to_speech():
|
| 1261 |
+
with st.spinner("Loading Audio Engine..."):
|
| 1262 |
+
from gtts import gTTS
|
| 1263 |
+
import io
|
| 1264 |
+
|
| 1265 |
+
st.header("🗣️ Text to Speech")
|
| 1266 |
+
text = st.text_area("Enter Text", "Hello, welcome to Lexical Space.")
|
| 1267 |
+
lang = st.selectbox("Language", ["en", "es", "fr", "de", "hi"],key="tts_text")
|
| 1268 |
+
|
| 1269 |
+
if st.button("Speak"):
|
| 1270 |
+
try:
|
| 1271 |
+
tts = gTTS(text=text, lang=lang, slow=False)
|
| 1272 |
+
buf = io.BytesIO()
|
| 1273 |
+
tts.write_to_fp(buf)
|
| 1274 |
+
st.audio(buf, format='audio/mp3')
|
| 1275 |
+
except Exception as e:
|
| 1276 |
+
st.error(f"Error: {e}")
|
| 1277 |
+
|
| 1278 |
+
def tool_timestamp():
|
| 1279 |
+
st.header("⏰ Unix Timestamp Converter")
|
| 1280 |
+
now = int(time.time())
|
| 1281 |
+
st.write(f"Current Timestamp: `{now}`")
|
| 1282 |
+
|
| 1283 |
+
col1, col2 = st.columns(2)
|
| 1284 |
+
with col1:
|
| 1285 |
+
ts_input = st.number_input("Timestamp to Date", value=now)
|
| 1286 |
+
if st.button("Convert to Date"):
|
| 1287 |
+
st.success(datetime.datetime.fromtimestamp(ts_input))
|
| 1288 |
+
|
| 1289 |
+
with col2:
|
| 1290 |
+
d_input = st.date_input("Date to Timestamp")
|
| 1291 |
+
if st.button("Convert to Timestamp"):
|
| 1292 |
+
ts = int(time.mktime(d_input.timetuple()))
|
| 1293 |
+
st.success(ts)
|
| 1294 |
+
|
| 1295 |
+
def tool_color_palette():
|
| 1296 |
+
st.header("🎨 Image Color Palette")
|
| 1297 |
+
# ADD key="palette"
|
| 1298 |
+
uploaded_file = st.file_uploader("Upload Image", type=['jpg', 'png'], key="palette")
|
| 1299 |
+
|
| 1300 |
+
|
| 1301 |
+
|
| 1302 |
+
if uploaded_file:
|
| 1303 |
+
img = Image.open(uploaded_file).convert("RGB")
|
| 1304 |
+
st.image(img, width=200)
|
| 1305 |
+
|
| 1306 |
+
# Simple extraction by resizing to 5 pixels
|
| 1307 |
+
small = img.resize((5, 1))
|
| 1308 |
+
colors = small.getdata()
|
| 1309 |
+
|
| 1310 |
+
st.write("Dominant Colors:")
|
| 1311 |
+
cols = st.columns(5)
|
| 1312 |
+
for i, color in enumerate(colors):
|
| 1313 |
+
hex_code = '#{:02x}{:02x}{:02x}'.format(*color)
|
| 1314 |
+
cols[i].color_picker(f"Color {i+1}", hex_code, disabled=True)
|
| 1315 |
+
cols[i].code(hex_code)
|
| 1316 |
+
|
| 1317 |
+
def tool_password_strength():
|
| 1318 |
+
st.header("💪 Password Strength")
|
| 1319 |
+
pwd = st.text_input("Test Password", type="password")
|
| 1320 |
+
|
| 1321 |
+
if pwd:
|
| 1322 |
+
score = 0
|
| 1323 |
+
if len(pwd) >= 8: score += 1
|
| 1324 |
+
if re.search(r"[A-Z]", pwd): score += 1
|
| 1325 |
+
if re.search(r"[a-z]", pwd): score += 1
|
| 1326 |
+
if re.search(r"\d", pwd): score += 1
|
| 1327 |
+
if re.search(r"[!@#$%^&*]", pwd): score += 1
|
| 1328 |
+
|
| 1329 |
+
st.progress(score / 5)
|
| 1330 |
+
if score < 3: st.warning("Weak")
|
| 1331 |
+
elif score < 5: st.info("Moderate")
|
| 1332 |
+
else: st.success("Strong!")
|
| 1333 |
+
|
| 1334 |
+
def tool_aspect_ratio():
|
| 1335 |
+
st.header("🖥️ Aspect Ratio Calculator")
|
| 1336 |
+
w = st.number_input("Width", 1920)
|
| 1337 |
+
h = st.number_input("Height", 1080)
|
| 1338 |
+
|
| 1339 |
+
if w and h:
|
| 1340 |
+
def gcd(a, b):
|
| 1341 |
+
while b: a, b = b, a % b
|
| 1342 |
+
return a
|
| 1343 |
+
divisor = gcd(int(w), int(h))
|
| 1344 |
+
st.metric("Aspect Ratio", f"{int(w/divisor)}:{int(h/divisor)}")
|
| 1345 |
+
|
| 1346 |
+
def tool_stopwatch():
|
| 1347 |
+
st.header("⏱️ Stopwatch")
|
| 1348 |
+
if 'start_time' not in st.session_state: st.session_state.start_time = None
|
| 1349 |
+
|
| 1350 |
+
if st.button("Start/Reset"):
|
| 1351 |
+
st.session_state.start_time = time.time()
|
| 1352 |
+
|
| 1353 |
+
if st.session_state.start_time:
|
| 1354 |
+
elapsed = time.time() - st.session_state.start_time
|
| 1355 |
+
st.metric("Time Elapsed", f"{elapsed:.2f}s")
|
| 1356 |
+
if st.button("Stop"):
|
| 1357 |
+
st.session_state.start_time = None
|
| 1358 |
+
|
| 1359 |
+
|
| 1360 |
+
def tool_python_checker():
|
| 1361 |
+
st.header("🐍 Python Syntax & Error Checker")
|
| 1362 |
+
st.markdown("Paste your Python code or upload a `.py` file to check for syntax errors.")
|
| 1363 |
+
|
| 1364 |
+
# Import dependencies inside the function to avoid global scope clutter
|
| 1365 |
+
import tempfile
|
| 1366 |
+
import os
|
| 1367 |
+
import io
|
| 1368 |
+
try:
|
| 1369 |
+
from pylint.lint import Run
|
| 1370 |
+
from pylint.reporters.text import TextReporter
|
| 1371 |
+
except ImportError:
|
| 1372 |
+
st.error("⚠️ Pylint is not installed. Please add `pylint` to your requirements.txt")
|
| 1373 |
+
return
|
| 1374 |
+
|
| 1375 |
+
# --- INPUTS ---
|
| 1376 |
+
col1, col2 = st.columns(2)
|
| 1377 |
+
with col1:
|
| 1378 |
+
paste_code = st.text_area("Paste Code Here", height=300)
|
| 1379 |
+
with col2:
|
| 1380 |
+
file_obj = st.file_uploader("Or Upload .py File", type=[".py"])
|
| 1381 |
+
|
| 1382 |
+
# --- PROCESS BUTTON ---
|
| 1383 |
+
if st.button("Check Syntax 🚀", type="primary"):
|
| 1384 |
+
code_to_check = ""
|
| 1385 |
+
|
| 1386 |
+
# Logic: Prefer File > Paste
|
| 1387 |
+
if file_obj is not None:
|
| 1388 |
+
try:
|
| 1389 |
+
code_to_check = file_obj.getvalue().decode("utf-8")
|
| 1390 |
+
except Exception as e:
|
| 1391 |
+
st.error(f"❌ Error reading file: {str(e)}")
|
| 1392 |
+
return
|
| 1393 |
+
elif paste_code.strip() != "":
|
| 1394 |
+
code_to_check = paste_code
|
| 1395 |
+
else:
|
| 1396 |
+
st.warning("⚠️ Please either paste code or upload a file.")
|
| 1397 |
+
return
|
| 1398 |
+
|
| 1399 |
+
# --- LINTING LOGIC ---
|
| 1400 |
+
# 1. Create Temp File (Pylint needs a file on disk)
|
| 1401 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".py", mode='w', encoding='utf-8') as temp:
|
| 1402 |
+
temp.write(code_to_check)
|
| 1403 |
+
temp_path = temp.name
|
| 1404 |
+
|
| 1405 |
+
# 2. Run Pylint
|
| 1406 |
+
pylint_output = io.StringIO()
|
| 1407 |
+
reporter = TextReporter(pylint_output)
|
| 1408 |
+
|
| 1409 |
+
with st.spinner("Analyzing syntax..."):
|
| 1410 |
+
try:
|
| 1411 |
+
# --errors-only hides warnings, showing only code-breaking errors
|
| 1412 |
+
Run([temp_path, "--errors-only"], reporter=reporter, exit=False)
|
| 1413 |
+
except Exception as e:
|
| 1414 |
+
st.error(f"System Error: {e}")
|
| 1415 |
+
|
| 1416 |
+
# 3. Cleanup & Display
|
| 1417 |
+
os.unlink(temp_path) # Delete temp file
|
| 1418 |
+
result = pylint_output.getvalue()
|
| 1419 |
+
|
| 1420 |
+
st.markdown("---")
|
| 1421 |
+
if not result:
|
| 1422 |
+
st.success("✅ No Syntax Errors Found! (Code looks valid)")
|
| 1423 |
+
st.balloons()
|
| 1424 |
+
else:
|
| 1425 |
+
st.error("❌ Errors Found:")
|
| 1426 |
+
# Hide the messy temp file path from the user
|
| 1427 |
+
clean_report = result.replace(temp_path, "Your_Script.py")
|
| 1428 |
+
st.code(clean_report, language="text")
|
| 1429 |
+
|
| 1430 |
+
|
| 1431 |
+
# --- FINAL MAIN ROUTER (SPA VERSION) ---
|
| 1432 |
+
if __name__ == "__main__":
|
| 1433 |
+
# 1. INITIALIZE SESSION STATE (This replaces the URL logic)
|
| 1434 |
+
if 'mode' not in st.session_state:
|
| 1435 |
+
st.session_state.mode = 'home'
|
| 1436 |
+
|
| 1437 |
+
|
| 1438 |
+
# 3. NAVIGATION HANDLING
|
| 1439 |
+
# Function to change mode without URL reload
|
| 1440 |
+
def set_mode(new_mode):
|
| 1441 |
+
st.session_state.mode = new_mode
|
| 1442 |
+
|
| 1443 |
+
# Show "Back to Dashboard" button if not home
|
| 1444 |
+
if st.session_state.mode != 'home':
|
| 1445 |
+
if st.button("⬅️ Back to Grid"):
|
| 1446 |
+
set_mode('home')
|
| 1447 |
+
st.rerun()
|
| 1448 |
+
|
| 1449 |
+
# 4. TOOL ROUTING (Checks session_state instead of URL)
|
| 1450 |
+
mode = st.session_state.mode
|
| 1451 |
+
|
| 1452 |
+
if mode == "youtube": tool_youtube_downloader()
|
| 1453 |
+
elif mode == "smart_converter": tool_smart_converter()
|
| 1454 |
+
elif mode == "compressor": tool_image_compressor()
|
| 1455 |
+
elif mode == "resizer": tool_image_resizer()
|
| 1456 |
+
elif mode == "thumbnail": tool_thumbnail_generator()
|
| 1457 |
+
elif mode == "metatags": tool_meta_tag_generator()
|
| 1458 |
+
elif mode == "slug": tool_slug_generator()
|
| 1459 |
+
elif mode == "robots": tool_robots_generator()
|
| 1460 |
+
elif mode == "sitemap": tool_sitemap_builder()
|
| 1461 |
+
elif mode == "density": tool_keyword_density()
|
| 1462 |
+
elif mode == "plagiarism": tool_plagiarism_check()
|
| 1463 |
+
elif mode == "minify": tool_code_minifier()
|
| 1464 |
+
elif mode == "qrcode": tool_qr_generator()
|
| 1465 |
+
elif mode == "json": tool_json_formatter()
|
| 1466 |
+
elif mode == "base64": tool_base64()
|
| 1467 |
+
elif mode == "url": tool_url_encoder()
|
| 1468 |
+
elif mode == "markdown": tool_markdown_editor()
|
| 1469 |
+
elif mode == "regex": tool_regex_tester()
|
| 1470 |
+
elif mode == "uuid": tool_uuid_gen()
|
| 1471 |
+
elif mode == "hash": tool_hash_gen()
|
| 1472 |
+
elif mode == "case": tool_case_converter()
|
| 1473 |
+
elif mode == "lorem": tool_lorem_ipsum()
|
| 1474 |
+
elif mode == "counter": tool_word_counter()
|
| 1475 |
+
elif mode == "dedupe": tool_remove_duplicates()
|
| 1476 |
+
elif mode == "tts": tool_text_to_speech()
|
| 1477 |
+
elif mode == "timestamp": tool_timestamp()
|
| 1478 |
+
elif mode == "palette": tool_color_palette()
|
| 1479 |
+
elif mode == "password": tool_password_strength()
|
| 1480 |
+
elif mode == "ratio": tool_aspect_ratio()
|
| 1481 |
+
elif mode == "stopwatch": tool_stopwatch()
|
| 1482 |
+
elif mode == "python": tool_python_checker()
|
| 1483 |
+
elif mode == "seo": render_seo_ui()
|
| 1484 |
+
elif mode == "Pdf Converter": run_ultimate_pdf_converter()
|
| 1485 |
+
elif mode == "Replacer Tool": lexical_replacer_tool()
|
| 1486 |
+
|
| 1487 |
+
# 5. HOME DASHBOARD (Button Grid)
|
| 1488 |
+
else:
|
| 1489 |
+
st.write("### ⚡ Select a tool to get started:")
|
| 1490 |
+
|
| 1491 |
+
# We use standard Streamlit columns to create a grid layout
|
| 1492 |
+
# This replaces the Markdown links with actual Buttons
|
| 1493 |
+
|
| 1494 |
+
c1, c2 = st.columns(2)
|
| 1495 |
+
|
| 1496 |
+
with c1:
|
| 1497 |
+
st.info("**📂 Media & Files**")
|
| 1498 |
+
if st.button("Converter"): set_mode("Replacer Tool"); st.rerun()
|
| 1499 |
+
if st.button("🎥 Pdf"): set_mode("Pdf Converter"); st.rerun()
|
| 1500 |
+
if st.button("🎥 YouTube Downloader"): set_mode("youtube"); st.rerun()
|
| 1501 |
+
if st.button("🎥 Seo Generator"): set_mode("seo"); st.rerun()
|
| 1502 |
+
|
| 1503 |
+
if st.button("🔄 Smart File Converter"): set_mode("smart_converter"); st.rerun()
|
| 1504 |
+
if st.button("📉 Image Compressor"): set_mode("compressor"); st.rerun()
|
| 1505 |
+
if st.button("📐 Image Resizer"): set_mode("resizer"); st.rerun()
|
| 1506 |
+
if st.button("🖼️ Thumbnail Gen"): set_mode("thumbnail"); st.rerun()
|
| 1507 |
+
|
| 1508 |
+
st.info("**🛠️ Developer Tools**")
|
| 1509 |
+
if st.button("🏁 QR Code Gen"): set_mode("qrcode"); st.rerun()
|
| 1510 |
+
if st.button("✨ JSON Prettifier"): set_mode("json"); st.rerun()
|
| 1511 |
+
if st.button("🔐 Base64 Converter"): set_mode("base64"); st.rerun()
|
| 1512 |
+
if st.button("🔗 URL Encoder"): set_mode("url"); st.rerun()
|
| 1513 |
+
if st.button("📝 Markdown Editor"): set_mode("markdown"); st.rerun()
|
| 1514 |
+
if st.button("🧪 Regex Tester"): set_mode("regex"); st.rerun()
|
| 1515 |
+
if st.button("🆔 UUID Gen"): set_mode("uuid"); st.rerun()
|
| 1516 |
+
if st.button("🔑 Hash Gen"): set_mode("hash"); st.rerun()
|
| 1517 |
+
|
| 1518 |
+
with c2:
|
| 1519 |
+
st.info("**🕸️ SEO & Webmaster**")
|
| 1520 |
+
if st.button("🏷️ Meta Tag Gen"): set_mode("metatags"); st.rerun()
|
| 1521 |
+
if st.button("🐌 Slug Generator"): set_mode("slug"); st.rerun()
|
| 1522 |
+
if st.button("🤖 Robots.txt Gen"): set_mode("robots"); st.rerun()
|
| 1523 |
+
if st.button("🗺️ Sitemap Builder"): set_mode("sitemap"); st.rerun()
|
| 1524 |
+
if st.button("📊 Density Checker"): set_mode("density"); st.rerun()
|
| 1525 |
+
if st.button("🕵️ Plagiarism Check"): set_mode("plagiarism"); st.rerun()
|
| 1526 |
+
if st.button("🧹 Code Minifier"): set_mode("minify"); st.rerun()
|
| 1527 |
+
|
| 1528 |
+
st.info("**📝 Text & Utilities**")
|
| 1529 |
+
if st.button("🔠 Case Converter"): set_mode("case"); st.rerun()
|
| 1530 |
+
if st.button("📜 Lorem Ipsum"): set_mode("lorem"); st.rerun()
|
| 1531 |
+
if st.button("🧮 Word Counter"): set_mode("counter"); st.rerun()
|
| 1532 |
+
if st.button("🗑️ Dedupe Lines"): set_mode("dedupe"); st.rerun()
|
| 1533 |
+
if st.button("🗣️ Text to Speech"): set_mode("tts"); st.rerun()
|
| 1534 |
+
if st.button("⏰ Unix Timestamp"): set_mode("timestamp"); st.rerun()
|
| 1535 |
+
if st.button("🎨 Color Palette"): set_mode("palette"); st.rerun()
|
| 1536 |
+
if st.button("💪 Password Strength"): set_mode("password"); st.rerun()
|
| 1537 |
+
if st.button("🖥️ Aspect Ratio"): set_mode("ratio"); st.rerun()
|
| 1538 |
+
if st.button("⏱️ Stopwatch"): set_mode("stopwatch"); st.rerun()
|
| 1539 |
+
if st.button("Python Checker"): set_mode("python"); st.rerun()
|
packages.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
ffmpeg
|
requirements.txt
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit
|
| 2 |
+
requests
|
| 3 |
+
Pillow
|
| 4 |
+
fpdf2
|
| 5 |
+
pandas
|
| 6 |
+
openpyxl
|
| 7 |
+
beautifulsoup4
|
| 8 |
+
huggingface_hub
|
| 9 |
+
qrcode
|
| 10 |
+
gTTS
|
| 11 |
+
pylint
|