kacapower commited on
Commit
f0e1522
·
verified ·
1 Parent(s): 5732939

Upload 3 files

Browse files
Files changed (3) hide show
  1. app.py +1539 -0
  2. packages.txt +1 -0
  3. 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