Badal commited on
Commit
a1954a9
Β·
1 Parent(s): 20eb503

Initial upload

Browse files
Files changed (4) hide show
  1. Dockerfile +6 -0
  2. README.md +10 -0
  3. app.py +328 -0
  4. requirements.txt +5 -0
Dockerfile ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ FROM python:3.10
2
+ WORKDIR /app
3
+ COPY requirements.txt .
4
+ RUN pip install -r requirements.txt
5
+ COPY . .
6
+ CMD ["gunicorn", "-b", "0.0.0.0:7860", "app:app"]
README.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Image Optimizer API
3
+ emoji: πŸ‘€
4
+ colorFrom: pink
5
+ colorTo: green
6
+ sdk: docker
7
+ pinned: false
8
+ ---
9
+
10
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,328 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ import time
4
+ import requests
5
+ import base64
6
+ import threading
7
+ from flask import Flask, request, jsonify, render_template_string
8
+ from PIL import Image, ImageOps
9
+ from huggingface_hub import HfApi, CommitOperationAdd
10
+
11
+ app = Flask(__name__)
12
+
13
+ # --- βš™οΈ SECRETS (Environment Variables) ---
14
+ HF_TOKEN = os.environ.get("HF_TOKEN")
15
+ HF_REPO = os.environ.get("HF_REPO")
16
+ WORKER_URL = os.environ.get("WORKER_URL", "https://your-worker.subdomain.workers.dev/") # CF Worker URL
17
+ XOR_KEY = os.environ.get("XOR_KEY", "badal_master_key") # Tumhari Secret Chabi πŸ”‘
18
+
19
+ Image.MAX_IMAGE_PIXELS = None
20
+
21
+ # --- πŸ“ LOCAL STORAGE SETUP (Queue ke liye) ---
22
+ PENDING_DIR = "pending_uploads"
23
+ os.makedirs(PENDING_DIR, exist_ok=True)
24
+
25
+ # --- πŸ”„ BACKGROUND BATCH UPLOADER (Har 30 seconds) ---
26
+ def background_uploader():
27
+ api = HfApi()
28
+ while True:
29
+ time.sleep(30) # ⏱️ 30 Second Timer (Tune bola tha)
30
+ try:
31
+ files_to_upload = os.listdir(PENDING_DIR)
32
+ if not files_to_upload:
33
+ continue
34
+
35
+ print(f"πŸš€ [BATCH UPLOAD] {len(files_to_upload)} files ko ek saath upload kar rahe hain...")
36
+
37
+ operations = []
38
+ uploaded_files = []
39
+
40
+ for filename in files_to_upload:
41
+ file_path = os.path.join(PENDING_DIR, filename)
42
+
43
+ # πŸ—‚οΈ 10k LIMIT BYPASS: Smart Folder System (Max ~9000 safe capacity)
44
+ # Naam (img_177237_3a7b9c.webp) me se '3a7' nikalega. Isse 4096 alag folders banenge!
45
+ try:
46
+ sub_folder = filename.split('_')[2][:3]
47
+ except:
48
+ sub_folder = "misc"
49
+
50
+ operations.append(
51
+ CommitOperationAdd(
52
+ # Ab file 'posters/3a7/img_xxx.webp' me jayegi
53
+ path_in_repo=f"posters/{sub_folder}/{filename}",
54
+ path_or_fileobj=file_path
55
+ )
56
+ )
57
+ uploaded_files.append(file_path)
58
+
59
+ # Single Commit on Hugging Face
60
+ api.create_commit(
61
+ repo_id=HF_REPO,
62
+ repo_type="dataset",
63
+ token=HF_TOKEN,
64
+ operations=operations,
65
+ commit_message=f"Batch upload of {len(operations)} images by OptiPix (Sharded)"
66
+ )
67
+ print("βœ… [BATCH UPLOAD] Success! Local files delete kar rahe hain...")
68
+
69
+ for f in uploaded_files:
70
+ try:
71
+ os.remove(f)
72
+ except:
73
+ pass
74
+ except Exception as e:
75
+ print(f"❌ [BATCH UPLOAD] Error: {e}")
76
+
77
+ # App start hote hi background thread on
78
+ threading.Thread(target=background_uploader, daemon=True).start()
79
+
80
+ # --- πŸ” XOR ENCRYPTION ENGINE ---
81
+ def xor_encrypt_decrypt_bytes(data: bytes, key: str) -> bytes:
82
+ """Image ke binary data ko corrupt/decrypt karta hai"""
83
+ key_bytes = key.encode()
84
+ key_len = len(key_bytes)
85
+ return bytes([b ^ key_bytes[i % key_len] for i, b in enumerate(data)])
86
+
87
+ def encrypt_text(text: str, key: str) -> str:
88
+ """Ab ye sidha target URL ko encrypt karega"""
89
+ xored = "".join(chr(ord(c) ^ ord(key[i % len(key)])) for i, c in enumerate(text))
90
+ return base64.urlsafe_b64encode(xored.encode()).decode().rstrip("=")
91
+
92
+ # --- 🧠 SUPER-OPTIMIZER LOGIC ---
93
+ def dynamic_optimize(image_content, level="extreme"):
94
+ """Image ko optimize karta hai"""
95
+ if level == "none":
96
+ return image_content
97
+
98
+ try:
99
+ img = Image.open(io.BytesIO(image_content))
100
+ img = ImageOps.exif_transpose(img)
101
+ if img.mode in ("RGBA", "P"): img = img.convert("RGB")
102
+
103
+ settings = {
104
+ "extreme": {"width": 550, "quality": 70, "method": 6, "target_kb": 60},
105
+ "high": {"width": 650, "quality": 80, "method": 5, "target_kb": 90},
106
+ "medium": {"width": 800, "quality": 85, "method": 4, "target_kb": 150},
107
+ "low": {"width": 1000, "quality": 95, "method": 3, "target_kb": 300}
108
+ }
109
+ conf = settings.get(level, settings["extreme"])
110
+
111
+ if img.width > conf["width"]:
112
+ ratio = conf["width"] / float(img.width)
113
+ img = img.resize((conf["width"], int(float(img.height) * ratio)), Image.Resampling.LANCZOS)
114
+
115
+ output = io.BytesIO()
116
+ curr_q = conf["quality"]
117
+
118
+ while curr_q > 10:
119
+ output.seek(0)
120
+ output.truncate(0)
121
+ img.save(output, format='WEBP', quality=curr_q, method=conf["method"], optimize=True)
122
+ if (output.tell() / 1024) <= conf["target_kb"]: break
123
+ curr_q -= 5
124
+
125
+ return output.getvalue()
126
+ except Exception as e:
127
+ print(f"❌ Optimization Error: {e}")
128
+ return image_content
129
+
130
+ # --- 🌐 API ROUTE (Handles BOTH URL and DIRECT FILE) ---
131
+ @app.route('/upload-poster', methods=['GET', 'POST'])
132
+ def handle_upload():
133
+ comp_level = request.args.get('level', request.form.get('level', 'extreme')).lower()
134
+ image_content = None
135
+
136
+ if not HF_TOKEN or not HF_REPO:
137
+ return jsonify({"success": False, "error": "HF Secrets missing!"}), 500
138
+
139
+ try:
140
+ if 'file' in request.files and request.files['file'].filename != '':
141
+ file = request.files['file']
142
+ image_content = file.read()
143
+
144
+ elif request.args.get('url') or request.form.get('url'):
145
+ img_url = request.args.get('url') or request.form.get('url')
146
+ resp = requests.get(img_url, timeout=20)
147
+ if resp.status_code != 200:
148
+ return jsonify({"success": False, "error": "URL Download failed"}), 400
149
+ image_content = resp.content
150
+
151
+ if not image_content:
152
+ return jsonify({"success": False, "error": "No file or URL provided"}), 400
153
+
154
+ # Optimize & Encrypt Data
155
+ optimized_data = dynamic_optimize(image_content, comp_level)
156
+ encrypted_image_data = xor_encrypt_decrypt_bytes(optimized_data, XOR_KEY)
157
+
158
+ # Generate Unique Filename
159
+ hex_random = os.urandom(3).hex()
160
+ real_filename = f"img_{int(time.time())}_{hex_random}.webp"
161
+
162
+ # πŸ—‚οΈ Calculate Sub-folder based on logic
163
+ sub_folder = hex_random[:3]
164
+
165
+ # 🌐 ASLI JADU: Poora Target URL banao
166
+ full_hf_url = f"https://huggingface.co/datasets/{HF_REPO}/resolve/main/posters/{sub_folder}/{real_filename}"
167
+
168
+ # πŸ” URL ko encrypt karo taaki worker ko de sake
169
+ encrypted_url_payload = encrypt_text(full_hf_url, XOR_KEY)
170
+
171
+ # Save to Local Queue
172
+ local_filepath = os.path.join(PENDING_DIR, real_filename)
173
+ with open(local_filepath, "wb") as f:
174
+ f.write(encrypted_image_data)
175
+
176
+ # Create Final Worker URL
177
+ worker_base = WORKER_URL if WORKER_URL.endswith('/') else WORKER_URL + '/'
178
+ final_cf_url = f"{worker_base}{encrypted_url_payload}"
179
+
180
+ return jsonify({
181
+ "success": True,
182
+ "url": final_cf_url,
183
+ "real_hf_filename": real_filename,
184
+ "level_used": comp_level,
185
+ "original_size_kb": round(len(image_content)/1024, 2),
186
+ "encrypted_size_kb": round(len(encrypted_image_data)/1024, 2)
187
+ })
188
+
189
+ except Exception as e:
190
+ return jsonify({"success": False, "error": str(e)}), 500
191
+
192
+ # --- πŸ–₯️ UI ROUTE (TESTING DASHBOARD) ---
193
+ HTML_UI = """
194
+ <!DOCTYPE html>
195
+ <html lang="en">
196
+ <head>
197
+ <meta charset="UTF-8">
198
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
199
+ <title>OptiPix Pro | Smart Image Optimizer</title>
200
+ <style>
201
+ :root { --primary: #3b82f6; --bg: #0f172a; --card: #1e293b; --text: #f1f5f9; }
202
+ body { font-family: 'Inter', system-ui, sans-serif; background: var(--bg); color: var(--text); padding: 20px; max-width: 700px; margin: auto; }
203
+ .card { background: var(--card); padding: 30px; border-radius: 16px; box-shadow: 0 10px 25px rgba(0,0,0,0.3); border: 1px solid #334155; }
204
+ h2 { color: #fff; margin-top: 0; font-size: 24px; text-align: center; }
205
+ p.sub { text-align: center; color: #94a3b8; font-size: 14px; margin-bottom: 25px; }
206
+ .input-group { background: #0f172a; padding: 15px; border-radius: 10px; margin-bottom: 20px; border: 1px solid #334155; }
207
+ label { display: block; font-weight: 600; margin-bottom: 8px; font-size: 14px; color: #3b82f6; }
208
+ input[type="text"], input[type="file"], select { width: 100%; padding: 12px; background: #1e293b; color: #fff; border: 1px solid #475569; border-radius: 8px; outline: none; box-sizing: border-box; font-size: 14px; }
209
+ input:focus { border-color: var(--primary); }
210
+ button { width: 100%; padding: 14px; background: var(--primary); color: white; font-weight: bold; border: none; border-radius: 8px; cursor: pointer; font-size: 16px; transition: 0.3s; margin-top: 10px; }
211
+ button:hover { background: #2563eb; transform: translateY(-1px); }
212
+ button:disabled { background: #475569; cursor: not-allowed; transform: none; }
213
+ #result { margin-top: 30px; padding: 20px; background: #064e3b; border-radius: 12px; display: none; animation: fadeIn 0.5s ease; }
214
+ .url-box { background: #0f172a; padding: 12px; border: 1px solid #059669; border-radius: 6px; margin: 10px 0; font-family: monospace; font-size: 13px; color: #34d399; overflow-x: auto; white-space: nowrap; }
215
+ .stats { display: flex; justify-content: space-between; font-size: 13px; margin-top: 15px; background: rgba(0,0,0,0.2); padding: 8px 12px; border-radius: 6px; }
216
+ .preview-img { max-width: 100%; border-radius: 8px; margin-top: 15px; border: 1px solid #334155; display: block; }
217
+ .notice-text { color: #fbbf24; font-size: 13.5px; margin-top: 15px; font-weight: 600; text-align: center; background: rgba(251, 191, 36, 0.1); padding: 10px; border-radius: 6px; border: 1px solid rgba(251, 191, 36, 0.3); }
218
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
219
+ .copy-btn { background: #334155; color: #fff; padding: 5px 10px; font-size: 11px; border-radius: 4px; border: none; cursor: pointer; float: right; margin-top: -35px; margin-right: 5px; }
220
+ </style>
221
+ </head>
222
+ <body>
223
+ <div class="card">
224
+ <h2>πŸš€ OptiPix Pro</h2>
225
+ <p class="sub">High-performance AI image compression and cloud hosting.</p>
226
+
227
+ <div class="input-group">
228
+ <label>Remote Image URL</label>
229
+ <input type="text" id="imgUrl" placeholder="https://example.com/image.jpg">
230
+ </div>
231
+
232
+ <div style="text-align: center; margin-bottom: 20px; color: #475569; font-size: 12px;">β€” OR β€”</div>
233
+
234
+ <div class="input-group">
235
+ <label>Upload Local Image</label>
236
+ <input type="file" id="imgFile" accept="image/*">
237
+ </div>
238
+
239
+ <label>Compression Level</label>
240
+ <select id="level">
241
+ <option value="none">Original (No Compression)</option>
242
+ <option value="extreme" selected>Web-Ready (Max Optimization)</option>
243
+ <option value="medium">Standard (Balanced)</option>
244
+ </select>
245
+
246
+ <button onclick="processImage()" id="btn">Optimize & Host Image</button>
247
+
248
+ <div id="result">
249
+ <h3 style="margin:0 0 10px 0; font-size: 18px; color: #34d399;">βœ… Processing Complete</h3>
250
+ <p style="margin: 0; color: #a7f3d0; font-size: 13px;">CDN Optimized Link:</p>
251
+ <div class="url-box" id="resLink"></div>
252
+ <button class="copy-btn" onclick="copyUrl()">Copy URL</button>
253
+
254
+ <div class="stats">
255
+ <span>Original: <span id="oldSize">--</span> KB</span>
256
+ <span style="color: #34d399;">Optimized: <span id="newSize">--</span> KB</span>
257
+ </div>
258
+
259
+ <div class="notice-text">
260
+ ⏳ Note: The image URL will be live in max 30 seconds.
261
+ </div>
262
+
263
+ <img id="resImg" class="preview-img" src="" alt="Preview will load soon...">
264
+ </div>
265
+ </div>
266
+
267
+ <script>
268
+ async function processImage() {
269
+ const btn = document.getElementById('btn');
270
+ const urlInput = document.getElementById('imgUrl').value;
271
+ const fileInput = document.getElementById('imgFile').files[0];
272
+ const level = document.getElementById('level').value;
273
+ const resDiv = document.getElementById('result');
274
+
275
+ if(!urlInput && !fileInput) { alert("Please provide an image source!"); return; }
276
+
277
+ btn.innerText = "⚑ Processing on Cloud...";
278
+ btn.disabled = true;
279
+ resDiv.style.display = "none";
280
+
281
+ let formData = new FormData();
282
+ formData.append('level', level);
283
+ if(fileInput) formData.append('file', fileInput);
284
+ else formData.append('url', urlInput);
285
+
286
+ try {
287
+ const response = await fetch('/upload-poster', { method: 'POST', body: formData });
288
+ const data = await response.json();
289
+
290
+ if(data.success) {
291
+ document.getElementById('resLink').innerText = data.url;
292
+ document.getElementById('oldSize').innerText = data.original_size_kb;
293
+ document.getElementById('newSize').innerText = data.encrypted_size_kb;
294
+
295
+ document.getElementById('resImg').src = "";
296
+
297
+ // 30 seconds wait timeout (plus 1s buffer)
298
+ setTimeout(() => {
299
+ document.getElementById('resImg').src = data.url;
300
+ }, 31000);
301
+
302
+ resDiv.style.display = "block";
303
+ } else {
304
+ alert("Processing Error: " + data.error);
305
+ }
306
+ } catch (err) {
307
+ alert("Cloud connection failed!");
308
+ }
309
+ btn.innerText = "Optimize & Host Image";
310
+ btn.disabled = false;
311
+ }
312
+
313
+ function copyUrl() {
314
+ const url = document.getElementById('resLink').innerText;
315
+ navigator.clipboard.writeText(url);
316
+ alert("Link copied to clipboard!");
317
+ }
318
+ </script>
319
+ </body>
320
+ </html>
321
+ """
322
+
323
+ @app.route('/')
324
+ def index():
325
+ return render_template_string(HTML_UI)
326
+
327
+ if __name__ == '__main__':
328
+ app.run(host='0.0.0.0', port=7860)
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Flask
2
+ requests
3
+ Pillow
4
+ huggingface_hub
5
+ gunicorn