sh4lu-z commited on
Commit
f5b45db
·
1 Parent(s): 1a34eba

Fixed missing folders issue

Browse files
audio_server DELETED
@@ -1 +0,0 @@
1
- Subproject commit 87d300defcef4c2b8e8b55db7135aefff9e84f31
 
 
audio_server/README.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Audio Server
3
+ emoji: 🚀
4
+ colorFrom: yellow
5
+ colorTo: pink
6
+ sdk: docker
7
+ pinned: false
8
+ ---
9
+
10
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
audio_server/app.py ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Install requirements:
2
+ # pip install fastapi uvicorn python-multipart ffmpeg-python
3
+
4
+ import os
5
+ import subprocess
6
+ import math
7
+ from fastapi import FastAPI, File, UploadFile, Form
8
+ from fastapi.responses import FileResponse
9
+ import shutil
10
+ import time
11
+
12
+ app = FastAPI()
13
+
14
+ # 📂 ෆයිල් ටික තියාගන්න තැන්j
15
+ UPLOAD_FOLDER = "uploads"
16
+ PROCESSED_FOLDER = "processed"
17
+ ASSETS_FOLDER = "assets" # මෙතන intro.mp3 සහ outro.mp3 තියෙන්න ඕනේ
18
+
19
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
20
+ os.makedirs(PROCESSED_FOLDER, exist_ok=True)
21
+ os.makedirs(ASSETS_FOLDER, exist_ok=True)
22
+
23
+ # 🛠️ 1. DURATION FINDER (සින්දුවේ දිග හොයාගන්න - Outro එකට ඕන වෙනවා)
24
+ def get_audio_duration(file_path):
25
+ cmd = [
26
+ "ffprobe", "-v", "error", "-show_entries", "format=duration",
27
+ "-of", "default=noprint_wrappers=1:nokey=1", file_path
28
+ ]
29
+ try:
30
+ output = subprocess.check_output(cmd).decode("utf-8").strip()
31
+ return float(output)
32
+ except:
33
+ return 0
34
+
35
+ # 🎛️ 2. THE ULTIMATE FILTER GENERATOR (මෙන්න මැජික් එක)
36
+ def get_filter_complex(effect_type, level, duration):
37
+
38
+ # DJ Drop Logic (Intro & Outro Mixing)
39
+ # Intro එක පටන් ගද්දිම (delay 0), Outro එක සින්දුව ඉවර වෙන්න තත්පර 5කට කලින්
40
+ outro_start = max(0, (duration * 1000) - 5000) # milliseconds
41
+
42
+ # මූලික මික්සින් කෝඩ් එක (Watermark)
43
+ # [0] = Song, [1] = Intro, [2] = Outro
44
+ # amix පාවිච්චි කරලා සින්දුවයි වොයිස් කට් එකයි ලස්සනට අමුනනවා
45
+ dj_mix_code = (
46
+ f"[0:a]volume=1.0[main];"
47
+ f"[1:a]volume=0.8,adelay=0|0[in_v];"
48
+ f"[2:a]volume=0.8,adelay={outro_start}|{outro_start}[out_v];"
49
+ f"[main][in_v][out_v]amix=inputs=3:duration=first:dropout_transition=3[mixed];"
50
+ )
51
+
52
+ # දැන් අපි Effect එක තෝරමු (16GB RAM Optimized Filters)
53
+ effects = {
54
+ # --- BASS SERIES ---
55
+ 'bass_studio': f"asplit=2[b][t];[b]lowpass=f=100,asubboost=dry=0.6:wet=0.8,bass=g={level}:f=60:w=0.5[bass_out];[t]highpass=f=100[treble_out];[bass_out][treble_out]amix=inputs=2:duration=first,volume=2dB",
56
+ 'bass_edm': f"asplit=2[b][t];[b]lowpass=f=120,asubboost=dry=0.5:wet=1.0,bass=g={level}:f=80:w=0.6,equalizer=f=60:width_type=h:width=50:g=5[bass_out];[t]highpass=f=120[treble_out];[bass_out][treble_out]amix=inputs=2:duration=first,volume=3dB",
57
+ 'bass_car': f"asplit=2[b][t];[b]lowpass=f=80,asubboost=dry=0.5:wet=1.0,bass=g={level}:f=50:w=1.0[bass_out];[t]highpass=f=80,volume=0.8[treble_out];[bass_out][treble_out]amix=inputs=2:duration=first,volume=4dB",
58
+ 'bass_slap': f"bass=g={level}:f=150:w=1.0,treble=g=3", # House Music වලට
59
+
60
+ # --- SPATIAL & 8D ---
61
+ '8d_audio': "apulsator=hz=0.125", # ඔලුව වටේ කැරකෙනවා (8s per cycle)
62
+ '3d_surround': "apulsator=hz=0.5,stereowiden=level=0.4",
63
+ 'wide_stereo': "stereowiden=delay=20:feedback=0.3:crossfeed=0.3:drymix=0.8",
64
+ 'hall_reverb': "aecho=0.8:0.9:1000:0.3", # ලොකු හෝල් එකක වගේ
65
+
66
+ # --- VOCAL & EQ ---
67
+ 'vocal_boost': "treble=g=5,equalizer=f=1000:width_type=h:width=200:g=4", # කටහඩ මතු කරන්න
68
+ 'crystal_clear': "treble=g=4,bass=g=-2,equalizer=f=3000:width_type=h:width=1000:g=2",
69
+ 'lofi_chill': "aresample=22050,asetrate=22050,lowpass=f=3000,noise=alls=20:allf=t+u", # පරණ රේඩියෝ වගේ
70
+
71
+ # --- FUN & STYLE ---
72
+ 'nightcore': "asetrate=44100*1.25,aresample=44100,equalizer=f=60:width_type=h:width=100:g=5", # වේගෙන් සහ හීන් හඩින්
73
+ 'slow_reverb': "asetrate=44100*0.85,aresample=44100,aecho=0.8:0.9:1000:0.3",
74
+ 'vaporwave': "asetrate=44100*0.8,aresample=44100,lowpass=f=2000",
75
+ 'reverse': "areverse",
76
+ 'underwater': "lowpass=f=300,volume=2dB",
77
+
78
+ # --- MASTERING TOOLS ---
79
+ 'compressor': "acompressor=threshold=-20dB:ratio=4:attack=5:release=50", # Professional Compression
80
+ 'limiter': "alimiter=limit=0.95:level=1.0", # සද්දෙ පැලෙන්නේ නැති වෙන්න හදනවා
81
+ 'loudness': "loudnorm=I=-16:TP=-1.5:LRA=11", # TV/Radio Quality Volume
82
+ 'concert': "aecho=0.8:0.88:60:0.4,stereowiden=level=0.4"
83
+ }
84
+
85
+ selected_effect = effects.get(effect_type, effects['bass_studio']) # Default to studio
86
+
87
+ # Final Chain: Mixed Audio -> Effect -> Limiter (Safety)
88
+ return f"{dj_mix_code}[mixed]{selected_effect},alimiter=limit=0.99[out]"
89
+
90
+ # 🚀 3. API ENDPOINT
91
+ @app.post("/process")
92
+ async def process_audio(
93
+ file: UploadFile = File(...),
94
+ effect: str = Form("bass_studio"),
95
+ level: int = Form(10)
96
+ ):
97
+ timestamp = int(time.time())
98
+ input_path = os.path.join(UPLOAD_FOLDER, f"in_{timestamp}_{file.filename}")
99
+ output_path = os.path.join(PROCESSED_FOLDER, f"out_{timestamp}.mp3")
100
+
101
+ # 1. සින්දුව සේව් කරගන්නවා
102
+ with open(input_path, "wb") as buffer:
103
+ shutil.copyfileobj(file.file, buffer)
104
+
105
+ # 2. අවශ්‍ය ෆයිල් තියෙනවද බලනවා (Intro/Outro)
106
+ intro_path = os.path.join(ASSETS_FOLDER, "intro.mp3")
107
+ outro_path = os.path.join(ASSETS_FOLDER, "outro.mp3")
108
+
109
+ if not os.path.exists(intro_path) or not os.path.exists(outro_path):
110
+ return {"error": "Missing intro.mp3 or outro.mp3 in assets folder!"}
111
+
112
+ # 3. සින්දුවේ දිග හොයනවා (Outro එක හරියට තියන්න)
113
+ duration = get_audio_duration(input_path)
114
+
115
+ # 4. Filter Chain එක හදාගන්නවා
116
+ filter_complex = get_filter_complex(effect, level, duration)
117
+
118
+ # 5. FFmpeg Command එක ගහනවා (16GB RAM Power 🔥)
119
+ # -threads 0 : CPU එකේ ඔක්කොම කෝර් ටික පාවිච්චි කරනවා
120
+ # -preset ultrafast : කොලිටි එක අඩුවෙන්නේ නෑ Audio වලට, හැබැයි වේගවත්
121
+ cmd = [
122
+ "ffmpeg", "-y",
123
+ "-i", input_path, # [0] Main Song
124
+ "-i", intro_path, # [1] Intro
125
+ "-i", outro_path, # [2] Outro
126
+ "-filter_complex", filter_complex,
127
+ "-map", "[out]",
128
+ "-b:a", "320k", # 320kbps High Quality Audio
129
+ "-threads", "0",
130
+ output_path
131
+ ]
132
+
133
+ print(f"🎛️ Processing: {effect} | Level: {level}")
134
+
135
+ process = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
136
+
137
+ if process.returncode != 0:
138
+ print("Error:", process.stderr.decode())
139
+ return {"error": "FFmpeg processing failed"}
140
+
141
+ # 6. හදපු ෆයිල් එක යවනවා
142
+ return FileResponse(output_path, media_type="audio/mpeg", filename=f"Cipher_{effect}.mp3")
143
+
144
+ if __name__ == "__main__":
145
+ # Server එක රන් කරන්න
146
+ import uvicorn
147
+ uvicorn.run(app, host="0.0.0.0", port=7862)
audio_server/assets/intro.mp3 ADDED
Binary file (42.6 kB). View file
 
audio_server/assets/outro.mp3 ADDED
Binary file (67.2 kB). View file
 
biologic DELETED
@@ -1 +0,0 @@
1
- Subproject commit 412049530afc5413f7adf70348c7094fd496338e
 
 
biologic/README.md ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Market Chart Gen
3
+ emoji: 📈
4
+ colorFrom: blue
5
+ colorTo: green
6
+ sdk: docker
7
+ pinned: false
8
+ app_port: 7860
9
+ ---
10
+
11
+ # Market Chart Generator
biologic/index.js ADDED
@@ -0,0 +1,551 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const { Canvas, loadImage } = require('skia-canvas');
3
+ const app = express();
4
+ const multer = require('multer'); // ෆයිල් ගන්න
5
+ const { Sticker, StickerTypes } = require('wa-sticker-formatter');
6
+ const upload = multer({ storage: multer.memoryStorage() });
7
+ app.use(express.json({ limit: '50mb' }));
8
+
9
+ app.get('/', (req, res) => res.send("🚀 Cipher-MD ULTRA-SHARP ENGINE"));
10
+
11
+ // ============================================================
12
+ // 🎨 1. WELCOME IMAGE GENERATOR (Exact Custom Design)
13
+ // ============================================================
14
+ app.post('/welcome', upload.single('image'), async (req, res) => {
15
+ try {
16
+ // ෆොටෝ එකක් නැත්නම් එරර් එකක්
17
+ if (!req.file) return res.status(400).send("No Profile Pic Uploaded");
18
+
19
+ // බොට්ගෙන් එන ඩේටා (gName = title විදිහට ගන්නවා)
20
+ const { title, count } = req.body;
21
+ const gName = title || "Group Name"; // නම හිස් නම් default එකක්
22
+
23
+ const size = 1024;
24
+ const canvas = new Canvas(size, size);
25
+ const ctx = canvas.getContext('2d');
26
+ const watermark = "CIPHER-MD VISUAL ENGINE v2.0";
27
+
28
+ // Style 1 හෝ 3 විතරයි පාවිච්චි කරන්නේ
29
+ const styleId = Math.random() > 0.5 ? 1 : 3;
30
+
31
+ // 1. Image Load (Buffer එකෙන් කෙලින්ම ගන්නවා)
32
+ const img = await loadImage(req.file.buffer);
33
+
34
+ // 2. Background Blur
35
+ ctx.filter = 'blur(50px) brightness(0.5)';
36
+ ctx.drawImage(img, -50, -50, size + 100, size + 100);
37
+ ctx.filter = 'none';
38
+
39
+ // 3. Styles Logic (1 & 3)
40
+ if (styleId === 1) {
41
+ ctx.strokeStyle = 'rgba(0, 242, 255, 0.1)';
42
+ for (let i = 0; i < size; i += 60) {
43
+ ctx.beginPath(); ctx.moveTo(i, 0); ctx.lineTo(i, size); ctx.stroke();
44
+ ctx.beginPath(); ctx.moveTo(0, i); ctx.lineTo(size, i); ctx.stroke();
45
+ }
46
+ } else {
47
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
48
+ ctx.lineWidth = 1;
49
+ for (let i = 0; i < 20; i++) {
50
+ ctx.beginPath(); ctx.moveTo(Math.random() * size, Math.random() * size);
51
+ ctx.lineTo(Math.random() * size, Math.random() * size); ctx.stroke();
52
+ }
53
+ }
54
+
55
+ // 4. Highlight Beam (උඹේ කෝඩ් එකේ තිබ්බ අලුත් කෑල්ල)
56
+ ctx.save();
57
+ const beamGrad = ctx.createLinearGradient(0, 0, size, size);
58
+ beamGrad.addColorStop(0, 'rgba(0, 242, 255, 0)');
59
+ beamGrad.addColorStop(0.5, 'rgba(0, 242, 255, 0.15)');
60
+ beamGrad.addColorStop(1, 'rgba(0, 242, 255, 0)');
61
+ ctx.fillStyle = beamGrad; ctx.rotate(-15 * Math.PI / 180);
62
+ ctx.fillRect(-size, size / 3, size * 2, 150); ctx.restore();
63
+
64
+ // 5. ✅ AVATAR CENTERED (පොටෝ එක හරි මැදට)
65
+ const centerX = size / 2;
66
+ const centerY = size / 2;
67
+ const radius = 250;
68
+
69
+ ctx.save();
70
+ ctx.strokeStyle = '#00f2ff'; ctx.lineWidth = 12; ctx.shadowBlur = 40; ctx.shadowColor = '#00f2ff';
71
+ ctx.beginPath(); ctx.arc(centerX, centerY, radius + 15, 0, Math.PI * 2); ctx.stroke(); ctx.restore();
72
+
73
+ ctx.save();
74
+ ctx.beginPath(); ctx.arc(centerX, centerY, radius, 0, Math.PI * 2); ctx.clip();
75
+ ctx.drawImage(img, centerX - radius, centerY - radius, radius * 2, radius * 2); ctx.restore();
76
+
77
+ // 6. ✅ TEXT LAYOUT
78
+ ctx.textAlign = 'center';
79
+
80
+ // "W E L C O M E" Label
81
+ ctx.font = 'bold 60px sans-serif';
82
+ ctx.strokeStyle = 'rgba(0, 242, 255, 0.8)';
83
+ ctx.lineWidth = 3;
84
+ ctx.strokeText("W E L C O M E", centerX, centerY + radius + 120);
85
+
86
+ // Group Name & Count
87
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
88
+ ctx.font = '35px sans-serif';
89
+ // Emoji Safe Regex එකත් දාලා තියෙන්නේ ආරක්ෂාවට
90
+ const safeTitle = gName.replace(/([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g, '');
91
+
92
+ ctx.fillText(`${safeTitle} • MEMBER #${count}`, centerX, centerY + radius + 190);
93
+
94
+ // Watermark
95
+ ctx.globalAlpha = 0.3;
96
+ ctx.font = 'bold 25px sans-serif';
97
+ ctx.fillStyle = '#ffffff';
98
+ ctx.fillText(watermark, centerX, size - 30);
99
+
100
+ // යැවීම
101
+ const buffer = await canvas.toBuffer('png');
102
+ res.set('Content-Type', 'image/png');
103
+ res.send(buffer);
104
+
105
+ } catch (e) {
106
+ console.error(e);
107
+ res.status(500).send("Welcome Gen Error: " + e.message);
108
+ }
109
+ });
110
+
111
+ // ============================================================
112
+ // 🖼️ 2. WATERMARK (Only for .img gen)
113
+ // ============================================================
114
+ app.post('/watermark', upload.single('image'), async (req, res) => {
115
+ try {
116
+ if (!req.file) return res.status(400).send("No image");
117
+ const img = await loadImage(req.file.buffer);
118
+ const canvas = new Canvas(img.width, img.height);
119
+ const ctx = canvas.getContext('2d');
120
+
121
+ ctx.drawImage(img, 0, 0);
122
+
123
+ // Add Watermark
124
+ const fontSize = Math.floor(img.width * 0.02);
125
+ ctx.font = `bold ${fontSize}px sans-serif`;
126
+ ctx.textAlign = 'right';
127
+ ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
128
+ ctx.shadowBlur = 5; ctx.shadowOffsetX = 3; ctx.shadowOffsetY = 3;
129
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
130
+ ctx.fillText('CIPHER-MD GEN ENGINE', img.width - 20, img.height - 20);
131
+
132
+ res.set('Content-Type', 'image/png');
133
+ res.send(await canvas.toBuffer('png'));
134
+ } catch (e) { res.status(500).send("WM Error"); }
135
+ });
136
+
137
+ // ============================================================
138
+ // 🃏 3. STICKER MAKER
139
+ // ============================================================
140
+ app.post('/sticker', upload.single('media'), async (req, res) => {
141
+ try {
142
+ if (!req.file) return res.status(400).send("No media");
143
+ const { pack, author } = req.body;
144
+
145
+ const sticker = new Sticker(req.file.buffer, {
146
+ pack: pack || 'Cipher-MD',
147
+ author: author || 'Bot',
148
+ type: StickerTypes.FULL,
149
+ categories: ['🤩', '🎉'],
150
+ quality: 40,
151
+ background: 'transparent'
152
+ });
153
+
154
+ res.set('Content-Type', 'image/webp');
155
+ res.send(await sticker.toBuffer());
156
+ } catch (e) { res.status(500).send("Sticker Error"); }
157
+ });
158
+
159
+
160
+ // ============================================================
161
+ // ✨ 4. ENHANCE ROUTE (Server 2 -> Python Server 3)
162
+ // ============================================================
163
+ app.post('/enhance', upload.single('image'), async (req, res) => {
164
+ try {
165
+ if (!req.file) return res.status(400).send("No image");
166
+
167
+ // 1. Python Server එකට කනෙක්ට් වෙනවා
168
+ const { Client } = await import("@gradio/client");
169
+ const client = await Client.connect("https://sh4lu-z-image-generate.hf.space");
170
+
171
+ // 2. හරියටම පරාමිතීන් (Settings) යවනවා
172
+ // (උඹ එවපු Python Code එකේ පිළිවෙළටම)
173
+ const result = await client.predict("/inference", [
174
+ new Blob([req.file.buffer]), // 1. ෆොටෝ එක
175
+ "GFPGANv1.4.pth", // 2. Face Model
176
+ "SRVGG, realesr-general-x4v3.pth", // 3. Upscale Model (මේක තමයි කලින් මිස් වුණේ)
177
+ 2, // 4. Scale (2x හෝ 4x දාන්න පුළුවන්)
178
+ "retinaface_resnet50", // 5. Face Detection
179
+ 10, // 6. Threshold
180
+ false, // 7. Only Center Face?
181
+ true, // 8. Output Name?
182
+ true // 9. Save as PNG?
183
+ ]);
184
+
185
+ // 3. හරියටම 4 වෙනි ෆොටෝ එක (Final Result) තෝරාගැනීම
186
+ // Python Server එකෙන් එන result එකේ [0] වෙනි array එකේ [3] වෙනි අයිතමය
187
+ const target = result.data[0][3] || result.data[0];
188
+ const enhancedUrl = target.image ? target.image.url : target.url;
189
+
190
+ // 4. ෆොටෝ එක බාගෙන Bot ට යවනවා
191
+ const imgBuffer = await axios.get(enhancedUrl, { responseType: 'arraybuffer' }).then(r => r.data);
192
+ res.set('Content-Type', 'image/png');
193
+ res.send(imgBuffer);
194
+
195
+ } catch (e) {
196
+ console.error("Enhance Error:", e);
197
+ res.status(500).send("Enhance Failed: " + e.message);
198
+ }
199
+ });
200
+
201
+ // ============================================================
202
+ // 🛠️ HELPER: ROUNDED RECTANGLE
203
+ // ============================================================
204
+ function roundedRect(ctx, x, y, width, height, radius) {
205
+ ctx.beginPath();
206
+ ctx.moveTo(x + radius, y);
207
+ ctx.lineTo(x + width - radius, y);
208
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
209
+ ctx.lineTo(x + width, y + height - radius);
210
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
211
+ ctx.lineTo(x + radius, y + height);
212
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
213
+ ctx.lineTo(x, y + radius);
214
+ ctx.quadraticCurveTo(x, y, x + radius, y);
215
+ ctx.closePath();
216
+ }
217
+
218
+ // ============================================================
219
+ // 📈 ENGINE 1: ULTRA-SHARP LINE CHART (Gold, P2P, CSE)
220
+ // ============================================================
221
+ async function drawLineChart(title, subTitle, history, colorTheme) {
222
+ const width = 2400;
223
+ const height = 1500;
224
+ const canvas = new Canvas(width, height);
225
+ const ctx = canvas.getContext('2d');
226
+
227
+ ctx.scale(2, 2);
228
+ // 1. Background (Solid Dark)
229
+ ctx.fillStyle = '#131722';
230
+ ctx.fillRect(0, 0, width, height);
231
+
232
+ // 📐 Precise Layout Calculation
233
+ // යට ඉඩ (Bottom Padding) වැඩි කළා අකුරු ගෑවෙන්නෙ නැති වෙන්න
234
+ const chartX = 20;
235
+ const chartY = 120; // Title area height
236
+ const chartW = 1050;
237
+ const chartH = 530; // Chart height reduced slightly to keep bottom clear
238
+
239
+ // Data Min/Max
240
+ const values = history.map(h => (h.price || h.rate || h.value));
241
+ const minVal = Math.min(...values) * 0.9995;
242
+ const maxVal = Math.max(...values) * 1.0005;
243
+ const range = maxVal - minVal;
244
+
245
+ const getY = (val) => chartY + chartH - ((val - minVal) / range) * chartH;
246
+ const getX = (i) => chartX + (i / (values.length - 1)) * chartW;
247
+
248
+ // 2. GRID LINES (Very Subtle)
249
+ ctx.lineWidth = 1;
250
+ ctx.strokeStyle = '#2a2e39';
251
+
252
+ // Horizontal Grids
253
+ const rows = 6;
254
+ for (let i = 0; i <= rows; i++) {
255
+ const y = chartY + (chartH / rows) * i;
256
+ ctx.beginPath(); ctx.moveTo(chartX, y); ctx.lineTo(chartX + chartW, y); ctx.stroke();
257
+
258
+ // Price Label (Sharp Font)
259
+ const price = maxVal - (range / rows) * i;
260
+ ctx.fillStyle = '#787b86';
261
+ ctx.font = '13px Consolas, monospace'; // Monospace for alignment
262
+ ctx.textAlign = 'left';
263
+ ctx.fillText(price.toLocaleString(undefined, {minimumFractionDigits: 2}), chartX + chartW + 12, y + 8);
264
+ }
265
+
266
+ // Vertical Grids
267
+ const cols = 8;
268
+ for (let i = 0; i <= cols; i++) {
269
+ const x = chartX + (chartW / cols) * i;
270
+ ctx.beginPath(); ctx.moveTo(x, chartY); ctx.lineTo(x, chartY + chartH + 60); ctx.stroke();
271
+ }
272
+
273
+ // 3. THE CHART AREA (Clean Gradient)
274
+ ctx.save();
275
+ ctx.beginPath();
276
+ ctx.moveTo(getX(0), getY(values[0]));
277
+ for (let i = 0; i < values.length; i++) ctx.lineTo(getX(i), getY(values[i]));
278
+
279
+ // Close path to bottom of chart area
280
+ ctx.lineTo(getX(values.length - 1), chartY + chartH + 60);
281
+ ctx.lineTo(getX(0), chartY + chartH + 60);
282
+ ctx.closePath();
283
+
284
+ // Sharp Gradient (No blurring at bottom)
285
+ const gradient = ctx.createLinearGradient(0, chartY, 0, chartY + chartH + 60);
286
+ gradient.addColorStop(0, `${colorTheme}55`); // Top: Semi-transparent
287
+ gradient.addColorStop(1, `${colorTheme}00`); // Bottom: Completely transparent
288
+ ctx.fillStyle = gradient;
289
+ ctx.fill();
290
+ ctx.restore();
291
+
292
+ // 4. THE MAIN LINE (Thin & Sharp)
293
+ ctx.beginPath();
294
+ ctx.lineWidth = 3; // Thinner line for "Pro" look (was 5)
295
+ ctx.strokeStyle = colorTheme;
296
+ ctx.lineJoin = 'round';
297
+ // Removed shadowBlur here to avoid "smudge" look
298
+ for (let i = 0; i < values.length; i++) {
299
+ if(i===0) ctx.moveTo(getX(i), getY(values[i]));
300
+ else ctx.lineTo(getX(i), getY(values[i]));
301
+ }
302
+ ctx.stroke();
303
+
304
+ // 5. CURRENT PRICE BADGE (Right Axis)
305
+ const lastVal = values[values.length - 1];
306
+ const lastY = getY(lastVal);
307
+
308
+ // Dotted line to axis
309
+ ctx.setLineDash([2, 2]);
310
+ ctx.lineWidth = 1;
311
+ ctx.beginPath(); ctx.moveTo(getX(values.length-1), lastY); ctx.lineTo(chartX + chartW, lastY); ctx.stroke();
312
+ ctx.setLineDash([]);
313
+
314
+ // Badge
315
+ ctx.fillStyle = colorTheme;
316
+ roundedRect(ctx, chartX + chartW + 5, lastY - 11, 115, 22, 3);
317
+ ctx.fill();
318
+
319
+ ctx.fillStyle = '#ffffff';
320
+ ctx.font = 'bold 13px Consolas';
321
+ ctx.textAlign = 'left';
322
+ ctx.fillText(lastVal.toLocaleString(undefined, {minimumFractionDigits: 2}), chartX + chartW + 10, lastY + 5);
323
+
324
+ // Pulsing Dot (Sharp)
325
+ const lastX = getX(values.length - 1);
326
+ ctx.beginPath(); ctx.arc(lastX, lastY, 5, 0, Math.PI * 2); ctx.fillStyle = '#ffffff'; ctx.fill();
327
+ ctx.beginPath(); ctx.arc(lastX, lastY, 12, 0, Math.PI * 2); ctx.strokeStyle = `${colorTheme}66`; ctx.lineWidth = 2; ctx.stroke();
328
+
329
+ // 6. HEADER (Aligned)
330
+ ctx.fillStyle = '#ffffff'; ctx.font = 'bold 42px sans-serif'; ctx.textAlign = 'left';
331
+ ctx.fillText(title, 30, 60);
332
+
333
+ ctx.fillStyle = colorTheme; ctx.font = 'bold 32px sans-serif';
334
+ ctx.fillText(subTitle, 30, 100);
335
+
336
+ // 7. WATERMARK (Safe Distance)
337
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.1)'; ctx.font = '18px sans-serif'; ctx.textAlign = 'right';
338
+ ctx.fillText('⚡ Cipher-MD Analytics (4K)', (width / 2) - 20, (height / 2) - 20);
339
+
340
+ return canvas.toBuffer('png');
341
+ }
342
+
343
+ // ============================================================
344
+ // 🕯️ ENGINE 2: CANDLESTICK CHART (Fixed Sharpness)
345
+ // ============================================================
346
+ app.post('/chart/candle', async (req, res) => {
347
+ try {
348
+ const { candles, symbol, change, currentPrice } = req.body;
349
+ const width = 2400;
350
+ const height = 1500;
351
+ const canvas = new Canvas(width, height);
352
+ const ctx = canvas.getContext('2d');
353
+
354
+ ctx.scale(2, 2);
355
+ ctx.fillStyle = '#131722'; ctx.fillRect(0, 0, width, height);
356
+
357
+ const chartX = 20;
358
+ const chartY = 60;
359
+ const chartW = 1050;
360
+ const chartH = 530;
361
+
362
+ const minPrice = Math.min(...candles.map(c => c.low));
363
+ const maxPrice = Math.max(...candles.map(c => c.high));
364
+ const range = maxPrice - minPrice;
365
+ const maxVol = Math.max(...candles.map(c => c.volume));
366
+ const getY = (p) => chartY + chartH - ((p - minPrice) / range) * chartH;
367
+
368
+ // Grid
369
+ ctx.lineWidth = 1; ctx.strokeStyle = '#2a2e39';
370
+ const rows = 8;
371
+ for(let i=0; i<=rows; i++) {
372
+ const y = chartY + (chartH/rows)*i;
373
+ ctx.beginPath(); ctx.moveTo(chartX, y); ctx.lineTo(chartX+chartW, y); ctx.stroke();
374
+ const p = maxPrice - (range/rows)*i;
375
+ ctx.fillStyle = '#787b86'; ctx.textAlign = 'left'; ctx.font = '13px Consolas';
376
+ ctx.fillText(p.toFixed(2), chartX+chartW+10, y+4);
377
+ }
378
+
379
+ const candleW = (chartW / candles.length) * 0.7;
380
+ const spacing = chartW / candles.length;
381
+
382
+ candles.forEach((c, i) => {
383
+ const x = chartX + (i * spacing) + (spacing/2);
384
+ const isUp = c.close >= c.open;
385
+ const color = isUp ? '#089981' : '#f23645';
386
+
387
+ // Volume (Sharp, low opacity)
388
+ const volH = (c.volume / maxVol) * 120;
389
+ ctx.fillStyle = isUp ? 'rgba(8, 153, 129, 0.2)' : 'rgba(242, 54, 69, 0.2)';
390
+ ctx.fillRect(x - candleW/2, chartY + chartH - volH + 150, candleW, volH);
391
+
392
+ // Wick (Thin line)
393
+ ctx.strokeStyle = color; ctx.lineWidth = 1.5;
394
+ ctx.beginPath(); ctx.moveTo(x, getY(c.high)); ctx.lineTo(x, getY(c.low)); ctx.stroke();
395
+
396
+ // Body
397
+ ctx.fillStyle = color;
398
+ const yOpen = getY(c.open);
399
+ const yClose = getY(c.close);
400
+ ctx.fillRect(x - candleW/2, Math.min(yOpen, yClose), candleW, Math.max(Math.abs(yOpen - yClose), 1));
401
+ });
402
+
403
+ // Price Badge
404
+ const lastC = candles[candles.length-1];
405
+ const lastY = getY(lastC.close);
406
+ const lastColor = lastC.close >= lastC.open ? '#089981' : '#f23645';
407
+
408
+ ctx.strokeStyle = '#ffffff'; ctx.setLineDash([2,2]); ctx.lineWidth = 1;
409
+ ctx.beginPath(); ctx.moveTo(0, lastY); ctx.lineTo(chartX+chartW, lastY); ctx.stroke();
410
+ ctx.setLineDash([]);
411
+
412
+ ctx.fillStyle = lastColor;
413
+ roundedRect(ctx, chartX+chartW+5, lastY-12, 115, 24, 3); ctx.fill();
414
+ ctx.fillStyle = '#ffffff'; ctx.font = 'bold 13px Consolas';
415
+ ctx.fillText(parseFloat(currentPrice).toFixed(2), chartX+chartW+10, lastY+5);
416
+
417
+ // Header
418
+ ctx.fillStyle = '#ffffff'; ctx.font = 'bold 42px sans-serif'; ctx.textAlign = 'left';
419
+ ctx.fillText(`${symbol} / USDT`, 30, 60);
420
+ ctx.fillStyle = parseFloat(change) >= 0 ? '#089981' : '#f23645'; ctx.font = 'bold 32px sans-serif';
421
+ ctx.fillText(`${change}%`, 30, 100);
422
+
423
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.1)'; ctx.font = '18px sans-serif'; ctx.textAlign = 'right';
424
+ ctx.fillText('⚡ Cipher-MD Analytics', (width / 2) - 20, (height / 2) - 20);
425
+
426
+ res.set('Content-Type', 'image/png'); res.send(await canvas.toBuffer('png'));
427
+ } catch (e) { res.status(500).send(e.message); }
428
+ });
429
+
430
+ // ============================================================
431
+ // 💎 ENGINE 3: NFT & ARB (Retained Fixed Version)
432
+ // ============================================================
433
+ app.post('/chart/nft', async (req, res) => {
434
+ try {
435
+ const { data } = req.body;
436
+ const canvas = new Canvas(800, 400); const ctx = canvas.getContext('2d');
437
+ ctx.fillStyle = '#0a0b0d'; roundedRect(ctx, 0, 0, 800, 400, 25); ctx.fill();
438
+
439
+ if (data.image_url) {
440
+ try {
441
+ const img = await loadImage(data.image_url);
442
+ ctx.save(); ctx.beginPath(); roundedRect(ctx, 40, 40, 200, 200, 20); ctx.clip();
443
+ ctx.drawImage(img, 40, 40, 200, 200); ctx.restore();
444
+ } catch (e) {}
445
+ }
446
+
447
+ ctx.fillStyle = '#ffffff'; ctx.font = 'bold 36px sans-serif';
448
+ ctx.fillText(data.name.substring(0, 18), 270, 70);
449
+
450
+ ctx.fillStyle = '#8a939b'; ctx.font = '24px sans-serif';
451
+ ctx.fillText(`Chain: ${data.chain.toUpperCase()}`, 270, 110);
452
+
453
+ ctx.fillStyle = '#2081e2'; ctx.font = 'bold 55px sans-serif';
454
+ ctx.fillText(`${data.floor_price} ${data.floor_price_symbol}`, 270, 190);
455
+
456
+ ctx.fillStyle = '#ffffff'; ctx.font = '18px sans-serif';
457
+ ctx.fillText('FLOOR PRICE', 270, 220);
458
+
459
+ ctx.fillStyle = '#161b22'; roundedRect(ctx, 270, 260, 480, 80, 15); ctx.fill();
460
+
461
+ ctx.fillStyle = '#ffffff'; ctx.font = 'bold 20px sans-serif';
462
+ ctx.textAlign = 'left'; ctx.fillText(`Vol: ${Math.round(data.total_volume)}`, 290, 308);
463
+ ctx.textAlign = 'right'; ctx.fillText(`Owners: ${data.owner_count}`, 730, 308);
464
+
465
+ ctx.textAlign = 'right'; ctx.fillStyle = 'rgba(255,255,255,0.2)';
466
+ ctx.fillText('Cipher-MD NFT', 750, 380);
467
+
468
+ res.set('Content-Type', 'image/png'); res.send(await canvas.toBuffer('png'));
469
+ } catch (e) { res.status(500).send(e.message); }
470
+ });
471
+
472
+ app.post('/chart/arb', async (req, res) => {
473
+ try {
474
+ const { coin, exData } = req.body;
475
+ const canvas = new Canvas(900, 600); const ctx = canvas.getContext('2d');
476
+ ctx.fillStyle = '#131722'; ctx.fillRect(0,0,900,600);
477
+
478
+ ctx.fillStyle = '#ffffff'; ctx.font = 'bold 40px sans-serif';
479
+ ctx.fillText(`${coin} Arbitrage Monitor`, 40, 60);
480
+
481
+ let y = 120;
482
+ const avg = exData.reduce((a, b) => a + b.price, 0) / exData.length;
483
+
484
+ exData.forEach(ex => {
485
+ const diff = (((ex.price - avg) / avg) * 100);
486
+ const color = diff >= 0 ? '#089981' : '#f23645';
487
+
488
+ ctx.fillStyle = '#1e222d'; roundedRect(ctx, 40, y, 820, 70, 10); ctx.fill();
489
+
490
+ ctx.fillStyle = '#b2b5be'; ctx.textAlign = 'left'; ctx.font = 'bold 24px sans-serif';
491
+ ctx.fillText(ex.name, 70, y + 45);
492
+
493
+ ctx.fillStyle = '#ffffff'; ctx.textAlign = 'right';
494
+ ctx.fillText(`$${ex.price.toLocaleString()}`, 600, y + 45);
495
+
496
+ ctx.fillStyle = color;
497
+ ctx.fillText(`${diff > 0 ? '+' : ''}${diff.toFixed(2)}%`, 830, y + 45);
498
+ y += 85;
499
+ });
500
+
501
+ res.set('Content-Type', 'image/png'); res.send(await canvas.toBuffer('png'));
502
+ } catch (e) { res.status(500).send(e.message); }
503
+ });
504
+
505
+ // ============================================================
506
+ // ⚡ ROUTERS
507
+ // ============================================================
508
+ app.post('/chart/p2p', async (req, res) => {
509
+ try {
510
+ const { history, currentRate } = req.body;
511
+ const buf = await drawLineChart("USDT / LKR (P2P)", `Rs. ${currentRate}`, history, '#2962ff');
512
+ res.set('Content-Type', 'image/png'); res.send(buf);
513
+ } catch (e) { res.status(500).send(e.message); }
514
+ });
515
+
516
+ app.post('/chart/forex', async (req, res) => {
517
+ try {
518
+ const { history, currentRate } = req.body;
519
+ const buf = await drawLineChart("USD / LKR (Official)", `Rs. ${currentRate}`, history, '#089981');
520
+ res.set('Content-Type', 'image/png'); res.send(buf);
521
+ } catch (e) { res.status(500).send(e.message); }
522
+ });
523
+
524
+ app.post('/chart/gold', async (req, res) => {
525
+ try {
526
+ const { history, currentRate } = req.body;
527
+ const buf = await drawLineChart("Gold (24K)", `Rs. ${currentRate}`, history, '#f2a900');
528
+ res.set('Content-Type', 'image/png'); res.send(buf);
529
+ } catch (e) { res.status(500).send(e.message); }
530
+ });
531
+
532
+ app.post('/chart/cse', async (req, res) => {
533
+ try {
534
+ const { history, currentRate } = req.body;
535
+ const buf = await drawLineChart("CSE ASPI", `${currentRate}`, history, '#a300cc');
536
+ res.set('Content-Type', 'image/png'); res.send(buf);
537
+ } catch (e) { res.status(500).send(e.message); }
538
+ });
539
+
540
+ app.post('/chart/fear', async (req, res) => {
541
+ try {
542
+ const { history } = req.body;
543
+ const last = history[history.length-1].value;
544
+ const color = last > 50 ? '#089981' : '#f23645';
545
+ const buf = await drawLineChart("Fear & Greed", `${last}/100`, history, color);
546
+ res.set('Content-Type', 'image/png'); res.send(buf);
547
+ } catch (e) { res.status(500).send(e.message); }
548
+ });
549
+
550
+ const port = 7861;
551
+ app.listen(port, () => console.log(`🚀 ULTRA-SHARP ENGINE running on port ${port}`));
cipher_sticker DELETED
@@ -1 +0,0 @@
1
- Subproject commit bccc38b266995e4478b5f1380e0e16384366a6eb
 
 
cipher_sticker/README.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Cipher Sticker Api
3
+ emoji: 🏢
4
+ colorFrom: purple
5
+ colorTo: pink
6
+ sdk: docker
7
+ pinned: false
8
+ ---
9
+
10
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
cipher_sticker/app.py ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, send_file, jsonify
2
+ import os
3
+ import subprocess
4
+ import uuid
5
+ import shutil
6
+ import zipfile
7
+ import math
8
+
9
+ app = Flask(__name__)
10
+ UPLOAD_FOLDER = "uploads"
11
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
12
+
13
+ # වීඩියෝ එකේ කාලය (Duration) ගන්න ෆන්ෂන් එක
14
+ def get_video_duration(input_path):
15
+ try:
16
+ result = subprocess.run(
17
+ ["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", input_path],
18
+ stdout=subprocess.PIPE, stderr=subprocess.STDOUT
19
+ )
20
+ return float(result.stdout)
21
+ except:
22
+ return 0.0
23
+
24
+ # 🎨 UNIVERSAL CONVERTER ENGINE (මේක තමයි හැම වැඩේම කරන්නේ)
25
+ def convert_segment(input_path, output_path, conf, start_time=0):
26
+ duration = conf['t']
27
+ fps = conf['fps']
28
+ quality = conf['q']
29
+ scale_size = conf['s']
30
+
31
+ # 🌟 Blur Filter Logic (Smart Resize එක්ක)
32
+ # 512px, 384px, 320px ඕන එකකට හරියන්න මේක හැදෙනවා
33
+ filter_complex = (
34
+ f"split[bg][fg];"
35
+ f"[bg]scale={scale_size}:{scale_size}:force_original_aspect_ratio=increase,crop={scale_size}:{scale_size},boxblur=20:10[bg_blurred];"
36
+ f"[fg]scale={scale_size}:{scale_size}:force_original_aspect_ratio=decrease[fg_scaled];"
37
+ f"[bg_blurred][fg_scaled]overlay=(W-w)/2:(H-h)/2"
38
+ )
39
+
40
+ command = [
41
+ "ffmpeg", "-y",
42
+ "-ss", str(start_time), # පටන් ගන්න තැන (Pack එකට ඕන වෙනවා)
43
+ "-i", input_path,
44
+ "-vf", f"{filter_complex},fps={fps}",
45
+ "-vcodec", "libwebp",
46
+ "-lossless", "0",
47
+ "-compression_level", "6", # Ultra Compression
48
+ "-preset", "default",
49
+ "-q:v", str(quality),
50
+ "-loop", "0",
51
+ "-an",
52
+ "-t", str(duration), # කොච්චර වෙලා කපනවද
53
+ output_path
54
+ ]
55
+
56
+ try:
57
+ subprocess.run(command, check=True)
58
+ return True
59
+ except subprocess.CalledProcessError as e:
60
+ print(f"FFmpeg Error: {e}")
61
+ return False
62
+
63
+ @app.route("/")
64
+ def home():
65
+ return "🔥 Cipher_MD Smart Engine (Guaranteed < 1MB) Running!"
66
+
67
+ # ================================================================
68
+ # 1. SINGLE STICKER ROUTE (SMART LOOP ENABLED ✅)
69
+ # ================================================================
70
+ @app.route("/sticker", methods=["POST"])
71
+ def make_single_sticker():
72
+ if 'file' not in request.files:
73
+ return jsonify({"error": "No file uploaded"}), 400
74
+
75
+ file = request.files['file']
76
+ ext = file.filename.split('.')[-1]
77
+ filename = f"{uuid.uuid4()}.{ext}"
78
+ input_path = os.path.join(UPLOAD_FOLDER, filename)
79
+ output_path = os.path.join(UPLOAD_FOLDER, f"{uuid.uuid4()}.webp")
80
+
81
+ file.save(input_path)
82
+
83
+ is_animated = ext.lower() in ['mp4', 'gif', 'mov', 'avi', 'mkv', 'webm']
84
+ success = False
85
+
86
+ if is_animated:
87
+ original_duration = get_video_duration(input_path)
88
+ target_duration = min(original_duration, 10.0)
89
+
90
+ # 🧠 SMART LOOP (Priority List)
91
+ # එකින් එක ට්‍රයි කරලා 1MB ට අඩු එකක් හම්බෙනකම් දුවනවා
92
+ attempts = [
93
+ {"s": 512, "q": 50, "fps": 15, "t": target_duration}, # Attempt 1
94
+ {"s": 384, "q": 45, "fps": 12, "t": target_duration}, # Attempt 2 (Resize)
95
+ {"s": 320, "q": 40, "fps": 12, "t": min(target_duration, 7.0)}, # Attempt 3 (Time Cut)
96
+ {"s": 256, "q": 30, "fps": 10, "t": min(target_duration, 5.0)} # Emergency (Low Quality)
97
+ ]
98
+
99
+ for i, conf in enumerate(attempts):
100
+ print(f"🔄 Single Attempt {i+1}: Size={conf['s']}px...")
101
+ if convert_segment(input_path, output_path, conf, start_time=0):
102
+ size = os.path.getsize(output_path)
103
+ print(f"📦 Size: {size/1024:.2f} KB")
104
+
105
+ if size < 980000: # 980KB Safe Limit
106
+ success = True
107
+ break
108
+ else:
109
+ # Static Image Logic
110
+ cmd = ["ffmpeg", "-y", "-i", input_path, "-vf", "scale='if(gt(iw,ih),512,-1)':'if(gt(iw,ih),-1,512)',scale=512:512:force_original_aspect_ratio=decrease,pad=512:512:(ow-iw)/2:(oh-ih)/2:color=#00000000", "-vcodec", "libwebp", "-lossless", "0", "-q:v", "80", output_path]
111
+ try:
112
+ subprocess.run(cmd, check=True)
113
+ success = True
114
+ except:
115
+ success = False
116
+
117
+ if os.path.exists(input_path): os.remove(input_path)
118
+
119
+ if success and os.path.exists(output_path):
120
+ return send_file(output_path, mimetype='image/webp')
121
+ else:
122
+ return jsonify({"error": "Failed to generate valid sticker"}), 500
123
+
124
+ # ================================================================
125
+ # 2. HEAVY PACK ROUTE (SMART LOOP FOR EVERY SEGMENT ✅)
126
+ # ================================================================
127
+ @app.route("/pack", methods=["POST"])
128
+ def make_pack():
129
+ if 'file' not in request.files:
130
+ return jsonify({"error": "No file uploaded"}), 400
131
+
132
+ file = request.files['file']
133
+ request_id = str(uuid.uuid4())
134
+
135
+ pack_folder = os.path.join(UPLOAD_FOLDER, request_id)
136
+ os.makedirs(pack_folder, exist_ok=True)
137
+
138
+ input_path = os.path.join(pack_folder, "video.mp4")
139
+ file.save(input_path)
140
+
141
+ duration = get_video_duration(input_path)
142
+ if duration > 130:
143
+ shutil.rmtree(pack_folder)
144
+ return jsonify({"error": "Video too long (Max 2 mins)"}), 400
145
+
146
+ sticker_files = []
147
+ num_segments = math.ceil(duration / 10)
148
+
149
+ # 🔄 හැම කෑල්ලක්ම Loop එකක් ඇතුලේ Process කරනවා
150
+ for i in range(num_segments):
151
+ start_time = i * 10
152
+ output_name = f"{i+1}.webp"
153
+ output_full_path = os.path.join(pack_folder, output_name)
154
+
155
+ segment_duration = min(10, duration - start_time)
156
+ if segment_duration < 1: continue
157
+
158
+ # 🧠 PACK SMART LOOP (මේක තමයි කලින් මිස් වුනේ!)
159
+ # හැම Sticker කෑල්ලක්ම 1MB ට අඩු වෙනකම් Resize කරනවා
160
+ attempts = [
161
+ {"s": 512, "q": 50, "fps": 15, "t": segment_duration},
162
+ {"s": 384, "q": 45, "fps": 12, "t": segment_duration},
163
+ {"s": 320, "q": 40, "fps": 10, "t": segment_duration},
164
+ {"s": 256, "q": 30, "fps": 10, "t": segment_duration}
165
+ ]
166
+
167
+ converted = False
168
+ for conf in attempts:
169
+ print(f"📦 Pack Segment {i+1}: Trying Size={conf['s']}...")
170
+ if convert_segment(input_path, output_full_path, conf, start_time=start_time):
171
+ if os.path.getsize(output_full_path) < 980000:
172
+ sticker_files.append(output_full_path)
173
+ converted = True
174
+ break # හරි ගියොත් ඊළඟ කෑල්ලට යනවා
175
+
176
+ # කොහොමවත් බැරි වුනොත් අන්තිම Output එක හරි ගන්නවා (Fail නොවෙන්න)
177
+ if not converted and os.path.exists(output_full_path):
178
+ sticker_files.append(output_full_path)
179
+
180
+ # ZIP File එක හදනවා
181
+ zip_path = os.path.join(UPLOAD_FOLDER, f"{request_id}.zip")
182
+ with zipfile.ZipFile(zip_path, 'w') as zipf:
183
+ for sticker in sticker_files:
184
+ zipf.write(sticker, arcname=os.path.basename(sticker))
185
+
186
+ # Cleanup
187
+ shutil.rmtree(pack_folder)
188
+
189
+ return send_file(zip_path, mimetype='application/zip', as_attachment=True, download_name=f"{request_id}.zip")
190
+
191
+ if __name__ == "__main__":
192
+ app.run(host="0.0.0.0", port=7863)
math_scan DELETED
@@ -1 +0,0 @@
1
- Subproject commit 89245243e6391011953a5a4db92287f51a37ef7b
 
 
math_scan/README.md ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Math Scan Processor
3
+ emoji: 🏃
4
+ colorFrom: pink
5
+ colorTo: yellow
6
+ sdk: gradio
7
+ sdk_version: 6.5.1
8
+ app_file: app.py
9
+ pinned: false
10
+ ---
11
+
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
math_scan/app.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, File, UploadFile, Form
2
+ from fastapi.responses import FileResponse, JSONResponse
3
+ import shutil
4
+ import os
5
+ import sys
6
+ from io import StringIO
7
+ import contextlib
8
+
9
+ # ඔයාගේ original ෆයිල් දෙක මෙතනට import කරනවා
10
+ import math_solver
11
+ import scan # අපි ඔයාගේ app.py එක scan.py කියලා rename කළානේ
12
+
13
+ app = FastAPI()
14
+
15
+ @app.get("/")
16
+ def home():
17
+ return {"message": "Cipher-MD Python Engine is Running! 🚀"}
18
+
19
+ # =================================================================
20
+ # 1️⃣ MATH SOLVER ENDPOINT
21
+ # =================================================================
22
+ @app.post("/math")
23
+ async def solve_math(image: UploadFile = File(...), prompt: str = Form("")):
24
+ temp_filename = f"temp_math_{image.filename}"
25
+
26
+ # 1. Image එක Save කරගන්නවා
27
+ with open(temp_filename, "wb") as buffer:
28
+ shutil.copyfileobj(image.file, buffer)
29
+
30
+ # 2. ඔයාගේ math_solver එකේ Output එක (Print වෙන ඒවා) අල්ලාගන්නවා
31
+ output_capture = StringIO()
32
+ try:
33
+ # Stdout එක capture කරනවා (Console එකට print වෙන දේ variable එකකට ගන්නවා)
34
+ with contextlib.redirect_stdout(output_capture):
35
+ # ඔයාගේ original function එක call කරනවා
36
+ math_solver.solve_math_problem(temp_filename, prompt)
37
+
38
+ result = output_capture.getvalue()
39
+ except Exception as e:
40
+ result = f"Error: {str(e)}"
41
+ finally:
42
+ # කුණු ෆයිල් අස් කරනවා
43
+ if os.path.exists(temp_filename):
44
+ os.remove(temp_filename)
45
+
46
+ # 3. JSON විදිහට උත්තරේ යවනවා
47
+ return {"result": result}
48
+
49
+ # =================================================================
50
+ # 2️⃣ SCAN / REVEAL ENDPOINT
51
+ # =================================================================
52
+ @app.post("/scan")
53
+ async def scan_watermark(image: UploadFile = File(...)):
54
+ in_file = f"in_{image.filename}"
55
+ out_file = f"out_{image.filename}"
56
+
57
+ # 1. Image එක Save කරගන්නවා
58
+ with open(in_file, "wb") as buffer:
59
+ shutil.copyfileobj(image.file, buffer)
60
+
61
+ try:
62
+ # 2. ඔයාගේ scan script (reveal_watermark) එක run කරනවා
63
+ scan.reveal_watermark(in_file, out_file)
64
+
65
+ # 3. හැදුනු ෆොටෝ එක ආපහු යවනවා
66
+ if os.path.exists(out_file):
67
+ return FileResponse(out_file)
68
+ else:
69
+ return {"error": "Processing failed"}
70
+
71
+ except Exception as e:
72
+ return {"error": str(e)}
73
+
74
+ finally:
75
+ # Input ෆයිල් එක විතරක් මකනවා (Output එක යැව්වට පස්සේ මැකෙන විදිහට HuggingFace එකේ auto clean වෙනවා)
76
+ if os.path.exists(in_file):
77
+ os.remove(in_file)
78
+
79
+ # Docker එකේ Run වෙන්න ඕන නිසා
80
+ if __name__ == "__main__":
81
+ import uvicorn
82
+ uvicorn.run(app, host="0.0.0.0", port=7864)
math_scan/math_solver.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import base64
3
+ import requests
4
+ import json
5
+ import sys
6
+ import io
7
+
8
+ # UTF-8 Encoding අනිවාර්යයි (නැත්නම් x² අකුර Print වෙද්දි Error එනවා)
9
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
10
+
11
+ def solve_math_problem(image_path, user_prompt=""):
12
+ img = cv2.imread(image_path)
13
+ if img is None:
14
+ print("❌ Error: Image not found!")
15
+ return
16
+
17
+ # Image Resize Logic
18
+ height, width = img.shape[:2]
19
+ max_width = 768
20
+ if width > max_width:
21
+ ratio = max_width / width
22
+ new_height = int(height * ratio)
23
+ img = cv2.resize(img, (max_width, new_height), interpolation=cv2.INTER_AREA)
24
+
25
+ _, buffer = cv2.imencode('.jpg', img)
26
+ base64_image = base64.b64encode(buffer).decode('utf-8')
27
+ data_url = f"data:image/jpeg;base64,{base64_image}"
28
+
29
+ if not user_prompt.strip():
30
+ final_instruction = "Solve this accurately."
31
+ else:
32
+ final_instruction = f"User Request: {user_prompt}"
33
+
34
+ # 🔥 විශේෂ Prompt එක (Formatting Rules)
35
+ prompt_text = f"""You are a math tutor on WhatsApp.
36
+
37
+ CRITICAL FORMATTING RULES (Follow these strictly):
38
+ 1. 🚫 NEVER use the caret symbol (^).
39
+ - CORRECT: x², y³, 10⁵
40
+ - WRONG: x^2, y^3
41
+ 2. 🚫 NEVER use asterisk (*) for multiplication.
42
+ - CORRECT: 3x × 2y
43
+ - WRONG: 3x * 2y
44
+ 3. 🚫 NEVER use standard slash (/) for division steps if confusing.
45
+ - Use '÷' or write clearly.
46
+ 4. Write algebraic terms naturally (e.g., 3x² + 2x + 5).
47
+
48
+ LAYOUT:
49
+ 🎯 **Answer:** [Final Result in Bold]
50
+ ──────────────────
51
+ 📝 **Steps:**
52
+ [Use Emojis 🔹 for bullet points. Keep sentences short.]
53
+ ──────────────────
54
+
55
+ INSTRUCTION: {final_instruction}"""
56
+
57
+ url = "https://text.pollinations.ai/"
58
+ payload = {
59
+ "messages": [
60
+ {
61
+ "role": "user",
62
+ "content": [
63
+ {"type": "text", "text": prompt_text},
64
+ {"type": "image_url", "image_url": {"url": data_url}}
65
+ ]
66
+ }
67
+ ],
68
+ "model": "gemini",
69
+ "jsonMode": False
70
+ }
71
+
72
+ try:
73
+ response = requests.post(url, json=payload, headers={"Content-Type": "application/json"})
74
+ response.encoding = 'utf-8'
75
+
76
+ if response.status_code == 200:
77
+ print(response.text)
78
+ else:
79
+ print("❌ AI Error.")
80
+
81
+ except Exception as e:
82
+ print(f"❌ Connection Error: {str(e)}")
83
+
84
+ if __name__ == "__main__":
85
+ img_file = sys.argv[1] if len(sys.argv) > 1 else "math.png"
86
+ u_prompt = sys.argv[2] if len(sys.argv) > 2 else ""
87
+ solve_math_problem(img_file, u_prompt)
math_scan/scan.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import sys
3
+ import os
4
+ #sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss
5
+ def reveal_watermark(input_path, output_path):
6
+ try:
7
+ # 1. පින්තූරය කියවගන්නවා
8
+ img = cv2.imread(input_path)
9
+ if img is None:
10
+ print("Error: Image not found.")
11
+ return
12
+
13
+ # 2. රහස් හොයන ෆිල්ටර් එක (Laplacian)
14
+ laplacian = cv2.Laplacian(img, cv2.CV_64F)
15
+
16
+ # 3. අහුවුණු දේවල් තදින් පෙන්නනවා (Amplify x20)
17
+ revealed = cv2.convertScaleAbs(laplacian, alpha=20)
18
+
19
+ # 4. Save කරනවා (Window එකේ පෙන්නන්නේ නැතුව)
20
+ cv2.imwrite(output_path, revealed)
21
+ print("Success")
22
+
23
+ except Exception as e:
24
+ print(f"Error: {e}")
25
+
26
+ if __name__ == "__main__":
27
+ # බොට් එකෙන් එවන නම් දෙක මෙතනින් ගන්නවා (Input & Output)
28
+ if len(sys.argv) > 2:
29
+ input_file = sys.argv[1]
30
+ output_file = sys.argv[2]
31
+ reveal_watermark(input_file, output_file)
32
+ else:
33
+
34
+ print("Error: Arguments missing")