Foydalanuvchi commited on
Commit
c1d5bd6
ยท
0 Parent(s):

Initial Space deployment

Browse files
Files changed (6) hide show
  1. .gitignore +7 -0
  2. Dockerfile +23 -0
  3. database.py +92 -0
  4. filters.py +763 -0
  5. main.py +407 -0
  6. requirements.txt +9 -0
.gitignore ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ .env
2
+ __pycache__/
3
+ *.db
4
+ in_*.jpg
5
+ out_*.jpg
6
+ in_*.mp4
7
+ out_*.mp4
Dockerfile ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hugging Face Space (Docker) muhiti uchun
2
+ # Asosiy Python imiji
3
+ FROM python:3.10-slim
4
+
5
+ # Tizim paketlarini yangilash va OpenCV hamda MoviePy uchun kerakli kutubxonalarni o'rnatish
6
+ RUN apt-get update && apt-get install -y \
7
+ libgl1-mesa-glx \
8
+ libglib2.0-0 \
9
+ ffmpeg \
10
+ && rm -rf /var/lib/apt/lists/*
11
+
12
+ # Ishchi katalogini yaratish
13
+ WORKDIR /app
14
+
15
+ # Talab qilinadigan paketlarni ko'chirib olish va o'rnatish
16
+ COPY requirements.txt .
17
+ RUN pip install --no-cache-dir -r requirements.txt
18
+
19
+ # Barcha bot fayllarini ko'chirib olish
20
+ COPY . .
21
+
22
+ # Botni ishga tushirish (main ismli faylingiz)
23
+ CMD ["python", "main.py"]
database.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sqlite3
2
+ import os
3
+ from datetime import datetime
4
+
5
+ class Database:
6
+ def __init__(self, db_name="bot_database.db"):
7
+ self.db_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), db_name)
8
+ self.init_db()
9
+
10
+ def get_connection(self):
11
+ return sqlite3.connect(self.db_path)
12
+
13
+ def init_db(self):
14
+ """Ma'lumotlar bazasi jadvallarini yaratish."""
15
+ with self.get_connection() as conn:
16
+ cursor = conn.cursor()
17
+
18
+ # Foydalanuvchilar jadvali
19
+ cursor.execute('''
20
+ CREATE TABLE IF NOT EXISTS users (
21
+ user_id INTEGER PRIMARY KEY,
22
+ username TEXT,
23
+ first_name TEXT,
24
+ joined_at DATETIME,
25
+ default_filter TEXT DEFAULT 'retro'
26
+ )
27
+ ''')
28
+
29
+ # Amallar tarixi jadvali
30
+ cursor.execute('''
31
+ CREATE TABLE IF NOT EXISTS history (
32
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
33
+ user_id INTEGER,
34
+ media_type TEXT,
35
+ filter_type TEXT,
36
+ processed_at DATETIME,
37
+ file_id TEXT,
38
+ FOREIGN KEY (user_id) REFERENCES users (user_id)
39
+ )
40
+ ''')
41
+ conn.commit()
42
+
43
+ def add_user(self, user_id, username, first_name):
44
+ """Yangi foydalanuvchini ro'yxatga olish yoki yangilash."""
45
+ with self.get_connection() as conn:
46
+ cursor = conn.cursor()
47
+ cursor.execute('''
48
+ INSERT OR REPLACE INTO users (user_id, username, first_name, joined_at)
49
+ VALUES (?, ?, ?, COALESCE((SELECT joined_at FROM users WHERE user_id = ?), ?))
50
+ ''', (user_id, username, first_name, user_id, datetime.now().isoformat()))
51
+ conn.commit()
52
+
53
+ def log_history(self, user_id, media_type, filter_type, file_id):
54
+ """Ishlov berish tarixini saqlash."""
55
+ with self.get_connection() as conn:
56
+ cursor = conn.cursor()
57
+ cursor.execute('''
58
+ INSERT INTO history (user_id, media_type, filter_type, processed_at, file_id)
59
+ VALUES (?, ?, ?, ?, ?)
60
+ ''', (user_id, media_type, filter_type, datetime.now().isoformat(), file_id))
61
+ conn.commit()
62
+
63
+ def get_user_history(self, user_id, limit=5):
64
+ """Foydalanuvchining oxirgi amallarini olish."""
65
+ with self.get_connection() as conn:
66
+ cursor = conn.cursor()
67
+ cursor.execute('''
68
+ SELECT media_type, filter_type, processed_at
69
+ FROM history
70
+ WHERE user_id = ?
71
+ ORDER BY processed_at DESC
72
+ LIMIT ?
73
+ ''', (user_id, limit))
74
+ return cursor.fetchall()
75
+
76
+ def set_default_filter(self, user_id, filter_type):
77
+ """Foydalanuvchi uchun standart filtrni o'rnatish."""
78
+ with self.get_connection() as conn:
79
+ cursor = conn.cursor()
80
+ cursor.execute('UPDATE users SET default_filter = ? WHERE user_id = ?', (filter_type, user_id))
81
+ conn.commit()
82
+
83
+ def get_user_settings(self, user_id):
84
+ """Foydalanuvchi sozlamalarini olish."""
85
+ with self.get_connection() as conn:
86
+ cursor = conn.cursor()
87
+ cursor.execute('SELECT default_filter FROM users WHERE user_id = ?', (user_id,))
88
+ result = cursor.fetchone()
89
+ return result[0] if result else 'retro'
90
+
91
+ # Global DB instance
92
+ db = Database()
filters.py ADDED
@@ -0,0 +1,763 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ from PIL import Image, ImageEnhance
4
+ import os
5
+ import logging
6
+
7
+ # Loglarni filters.py uchun ham alohida sozlash
8
+ logger = logging.getLogger(__name__)
9
+
10
+ def apply_retro_filter(image_path, output_path):
11
+ """Rasmni retro uslubiga o'tkazadi (donadorlik, rang siljishi, vinyetka)."""
12
+ try:
13
+ image_path = os.path.abspath(image_path)
14
+ output_path = os.path.abspath(output_path)
15
+
16
+ img = cv2.imread(image_path)
17
+ if img is None:
18
+ logger.error(f"Rasm o'qilmadi: {image_path}")
19
+ return None
20
+
21
+ # Formatlarni BGR ga o'tkazish
22
+ if len(img.shape) == 2:
23
+ img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
24
+ elif img.shape[2] == 4:
25
+ img = cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)
26
+
27
+ # 1. Sepia
28
+ sepia_filter = np.array([[0.272, 0.534, 0.131],
29
+ [0.349, 0.686, 0.168],
30
+ [0.393, 0.769, 0.189]])
31
+ img = cv2.transform(img, sepia_filter)
32
+ img = np.clip(img, 0, 255).astype(np.uint8)
33
+
34
+ # 2. Grain
35
+ noise = np.random.randint(0, 30, img.shape, dtype='uint8')
36
+ img = cv2.add(img, noise)
37
+
38
+ # 3. Vignette
39
+ rows, cols = img.shape[:2]
40
+ kernel_x = cv2.getGaussianKernel(cols, int(cols/1.5))
41
+ kernel_y = cv2.getGaussianKernel(rows, int(rows/1.5))
42
+ kernel = kernel_y * kernel_x.T
43
+ mask = 255 * kernel / np.max(kernel)
44
+
45
+ vignette = img.astype(float)
46
+ for i in range(3):
47
+ vignette[:,:,i] = vignette[:,:,i] * mask / 255
48
+
49
+ cv2.imwrite(output_path, vignette.astype(np.uint8))
50
+ if os.path.exists(output_path):
51
+ return output_path
52
+ return None
53
+ except Exception as e:
54
+ logger.error(f"Retro rasm xatosi: {e}")
55
+ return None
56
+
57
+ def upscale_image(image_path, output_path, scale=2):
58
+ """Rasmni sifatini oshirish."""
59
+ try:
60
+ image_path = os.path.abspath(image_path)
61
+ output_path = os.path.abspath(output_path)
62
+
63
+ img = cv2.imread(image_path)
64
+ if img is None: return None
65
+
66
+ if len(img.shape) == 2:
67
+ img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
68
+ elif img.shape[2] == 4:
69
+ img = cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)
70
+
71
+ height, width = img.shape[:2]
72
+ new_size = (width * scale, height * scale)
73
+ upscaled = cv2.resize(img, new_size, interpolation=cv2.INTER_LANCZOS4)
74
+
75
+ pill_img = Image.fromarray(cv2.cvtColor(upscaled, cv2.COLOR_BGR2RGB))
76
+
77
+ # O'tkirlikni ko'proq oshirish (2.5)
78
+ enhancer = ImageEnhance.Sharpness(pill_img)
79
+ pill_img = enhancer.enhance(2.5)
80
+
81
+ # Kontrastni ham biroz oshirish
82
+ enhancer_c = ImageEnhance.Contrast(pill_img)
83
+ pill_img = enhancer_c.enhance(1.15)
84
+
85
+ upscaled_final = cv2.cvtColor(np.array(pill_img), cv2.COLOR_RGB2BGR)
86
+ cv2.imwrite(output_path, upscaled_final)
87
+ return output_path
88
+ except Exception as e:
89
+ logger.error(f"Upscale rasm xatosi: {e}")
90
+ return None
91
+
92
+ def process_video_retro(video_path, output_path, progress_callback=None):
93
+ """Videoga retro filtrini qo'llaydi."""
94
+ import moviepy.video.VideoClip as mp_video
95
+ from moviepy.video.io.VideoFileClip import VideoFileClip
96
+
97
+ try:
98
+ video_path = os.path.abspath(video_path)
99
+ output_path = os.path.abspath(output_path)
100
+
101
+ video = VideoFileClip(video_path)
102
+ total_frames = int(video.fps * video.duration)
103
+ current_frame = [0]
104
+
105
+ def filter_frame(frame):
106
+ current_frame[0] += 1
107
+ if progress_callback and current_frame[0] % 15 == 0:
108
+ percent = min(99, int((current_frame[0] / total_frames) * 100))
109
+ progress_callback(percent)
110
+
111
+ # Input frame is RGB
112
+ if frame.shape[2] == 4:
113
+ frame = frame[:, :, :3]
114
+ elif len(frame.shape) == 2:
115
+ frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2RGB)
116
+
117
+ bgr = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
118
+
119
+ sepia_filter = np.array([[0.272, 0.534, 0.131],
120
+ [0.349, 0.686, 0.168],
121
+ [0.393, 0.769, 0.189]])
122
+ bgr = cv2.transform(bgr, sepia_filter)
123
+ bgr = np.clip(bgr, 0, 255).astype(np.uint8)
124
+
125
+ noise = np.random.randint(0, 15, bgr.shape, dtype='uint8')
126
+ bgr = cv2.add(bgr, noise)
127
+
128
+ return cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)
129
+
130
+ processed_video = video.image_transform(filter_frame)
131
+
132
+ processed_video.write_videofile(
133
+ output_path,
134
+ codec="libx264",
135
+ audio_codec="aac",
136
+ fps=video.fps or 24,
137
+ preset="ultrafast", # Tezroq bo'lishi uchun
138
+ threads=4,
139
+ logger=None
140
+ )
141
+
142
+ video.close()
143
+ processed_video.close()
144
+
145
+ if os.path.exists(output_path):
146
+ return output_path
147
+ return None
148
+ except Exception as e:
149
+ logger.error(f"Video process xatosi: {e}")
150
+ return None
151
+
152
+ def process_video_upscale(video_path, output_path, progress_callback=None):
153
+ """Videoning sifatini (o'lcham va o'tkirlik) oshiradi."""
154
+ from moviepy.video.io.VideoFileClip import VideoFileClip
155
+
156
+ try:
157
+ video_path = os.path.abspath(video_path)
158
+ output_path = os.path.abspath(output_path)
159
+
160
+ video = VideoFileClip(video_path)
161
+ total_frames = int(video.fps * video.duration)
162
+ current_frame = [0]
163
+
164
+ def upscale_frame(frame):
165
+ current_frame[0] += 1
166
+ if progress_callback and current_frame[0] % 15 == 0:
167
+ percent = min(99, int((current_frame[0] / total_frames) * 100))
168
+ progress_callback(percent)
169
+
170
+ # RGB -> BGR
171
+ bgr = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
172
+
173
+ # 1.5x kattalashtirish
174
+ h, w = bgr.shape[:2]
175
+ upscaled = cv2.resize(bgr, (int(w*1.5), int(h*1.5)), interpolation=cv2.INTER_LANCZOS4)
176
+
177
+ # Sharpness
178
+ gaussian_3 = cv2.GaussianBlur(upscaled, (0, 0), 2.0)
179
+ unsharp_image = cv2.addWeighted(upscaled, 1.5, gaussian_3, -0.5, 0)
180
+
181
+ return cv2.cvtColor(unsharp_image, cv2.COLOR_BGR2RGB)
182
+
183
+ processed_video = video.image_transform(upscale_frame)
184
+
185
+ processed_video.write_videofile(
186
+ output_path,
187
+ codec="libx264",
188
+ audio_codec="aac",
189
+ fps=video.fps or 24,
190
+ preset="ultrafast",
191
+ threads=4,
192
+ logger=None
193
+ )
194
+
195
+ video.close()
196
+ processed_video.close()
197
+ return output_path if os.path.exists(output_path) else None
198
+ except Exception as e:
199
+ logger.error(f"Video upscale xatosi: {e}")
200
+ return None
201
+
202
+ def apply_face_restore(image_path, output_path):
203
+ """Yuzlarni aniqlaydi va ularni tiniqlashtiradi (Face Fix)."""
204
+ try:
205
+ image_path = os.path.abspath(image_path)
206
+ output_path = os.path.abspath(output_path)
207
+
208
+ img = cv2.imread(image_path)
209
+ if img is None: return None
210
+
211
+ # Yuzni aniqlash uchun kaskad yuklanadi (yanada qat'iy parametrlar)
212
+ face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
213
+ gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
214
+
215
+ # HistEq orqali qorong'u yuzlarni ham topish
216
+ gray = cv2.equalizeHist(gray)
217
+ faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(40, 40))
218
+
219
+ for (x, y, w, h) in faces:
220
+ # Yuz qismini biroz kattaroq qilib ajratib olish (padding)
221
+ p = int(w * 0.15)
222
+ x1, y1 = max(0, x - p), max(0, y - p)
223
+ x2, y2 = min(img.shape[1], x + w + p), min(img.shape[0], y + h + p)
224
+ face_roi = img[y1:y2, x1:x2]
225
+
226
+ # 1. Yuzni tekislash (skin smoothing - Bilateral + Median)
227
+ smoothed = cv2.bilateralFilter(face_roi, 9, 75, 75)
228
+ smoothed = cv2.medianBlur(smoothed, 3)
229
+
230
+ # 2. Detallarni kuchli qaytarish (Unsharp Mask)
231
+ gaussian = cv2.GaussianBlur(smoothed, (0, 0), 2.5)
232
+ sharpened = cv2.addWeighted(smoothed, 2.0, gaussian, -1.0, 0)
233
+
234
+ # 3. Sifatini oshirish (CLAHE orqali yorug'lik)
235
+ lab = cv2.cvtColor(sharpened, cv2.COLOR_BGR2LAB)
236
+ l, a, b = cv2.split(lab)
237
+ clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
238
+ cl = clahe.apply(l)
239
+ limg = cv2.merge((cl,a,b))
240
+ face_roi = cv2.cvtColor(limg, cv2.COLOR_LAB2BGR)
241
+
242
+ # Asl rasmga qaytarish
243
+ img[y1:y2, x1:x2] = face_roi
244
+
245
+ cv2.imwrite(output_path, img)
246
+ return output_path
247
+ except Exception as e:
248
+ logger.error(f"Face Restore xatosi: {e}")
249
+ return None
250
+
251
+ def apply_auto_enhance(image_path, output_path):
252
+ """Ranglarni va yorug'likni avtomatik balanslaydi."""
253
+ try:
254
+ image_path = os.path.abspath(image_path)
255
+ output_path = os.path.abspath(output_path)
256
+
257
+ img = cv2.imread(image_path)
258
+ if img is None: return None
259
+
260
+ # CLAHE - Yorug'likni kuchliroq taqsimlash
261
+ lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
262
+ l, a, b = cv2.split(lab)
263
+ clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8))
264
+ cl = clahe.apply(l)
265
+ limg = cv2.merge((cl,a,b))
266
+ enhanced = cv2.cvtColor(limg, cv2.COLOR_LAB2BGR)
267
+
268
+ # Detallarni kuchaytirish (Unsharp Mask)
269
+ gaussian = cv2.GaussianBlur(enhanced, (0, 0), 2.0)
270
+ unsharp = cv2.addWeighted(enhanced, 1.5, gaussian, -0.5, 0)
271
+
272
+ # Saturation (to'yinganlik) ni sezilarli oshirish
273
+ hsv = cv2.cvtColor(unsharp, cv2.COLOR_BGR2HSV)
274
+ h, s, v = cv2.split(hsv)
275
+ s = cv2.add(s, 25)
276
+ hsv = cv2.merge((h, s, v))
277
+ final = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
278
+
279
+ cv2.imwrite(output_path, final)
280
+ return output_path
281
+ except Exception as e:
282
+ logger.error(f"Auto Enhance xatosi: {e}")
283
+ return None
284
+
285
+ # ============================================================
286
+ # YANGI VIDEO FILTRLAR (Phase 6)
287
+ # ============================================================
288
+
289
+ def process_video_slowmo(video_path, output_path, progress_callback=None):
290
+ """Videoni 2x sekinlashtiradi (Slow Motion)."""
291
+ from moviepy.video.io.VideoFileClip import VideoFileClip
292
+
293
+ try:
294
+ video_path = os.path.abspath(video_path)
295
+ output_path = os.path.abspath(output_path)
296
+
297
+ video = VideoFileClip(video_path)
298
+
299
+ # 2x sekinlashtirish (tezlikni 0.5 ga kamaytirish)
300
+ slow_video = video.with_speed_scaled(0.5)
301
+
302
+ audio_params = {"audio_codec": "aac"} if video.audio else {"audio": False}
303
+
304
+ slow_video.write_videofile(
305
+ output_path,
306
+ codec="libx264",
307
+ fps=video.fps or 24,
308
+ preset="ultrafast",
309
+ threads=4,
310
+ logger=None,
311
+ **audio_params
312
+ )
313
+
314
+ video.close()
315
+ slow_video.close()
316
+ return output_path if os.path.exists(output_path) else None
317
+ except Exception as e:
318
+ logger.error(f"Slow Motion xatosi: {e}")
319
+ return None
320
+
321
+ def process_video_bw(video_path, output_path, progress_callback=None):
322
+ """Videoni oq-qora (B&W) holatga o'tkazadi."""
323
+ from moviepy.video.io.VideoFileClip import VideoFileClip
324
+
325
+ try:
326
+ video_path = os.path.abspath(video_path)
327
+ output_path = os.path.abspath(output_path)
328
+
329
+ video = VideoFileClip(video_path)
330
+ total_frames = int(video.fps * video.duration)
331
+ current_frame = [0]
332
+
333
+ def bw_frame(frame):
334
+ current_frame[0] += 1
335
+ if progress_callback and current_frame[0] % 15 == 0:
336
+ percent = min(99, int((current_frame[0] / total_frames) * 100))
337
+ progress_callback(percent)
338
+
339
+ gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
340
+ # Kontrastni biroz oshirish
341
+ clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
342
+ gray = clahe.apply(gray)
343
+ return cv2.cvtColor(gray, cv2.COLOR_GRAY2RGB)
344
+
345
+ processed = video.image_transform(bw_frame)
346
+ audio_params = {"audio_codec": "aac"} if video.audio else {"audio": False}
347
+ processed.write_videofile(
348
+ output_path,
349
+ codec="libx264",
350
+ fps=video.fps or 24,
351
+ preset="ultrafast",
352
+ threads=4,
353
+ logger=None,
354
+ **audio_params
355
+ )
356
+
357
+ video.close()
358
+ processed.close()
359
+ return output_path if os.path.exists(output_path) else None
360
+ except Exception as e:
361
+ logger.error(f"B&W video xatosi: {e}")
362
+ return None
363
+
364
+ def process_video_color_correct(video_path, output_path, progress_callback=None):
365
+ """Video ranglarini avtomatik korreksiya qiladi."""
366
+ from moviepy.video.io.VideoFileClip import VideoFileClip
367
+
368
+ try:
369
+ video_path = os.path.abspath(video_path)
370
+ output_path = os.path.abspath(output_path)
371
+
372
+ video = VideoFileClip(video_path)
373
+ total_frames = int(video.fps * video.duration)
374
+ current_frame = [0]
375
+
376
+ def color_frame(frame):
377
+ current_frame[0] += 1
378
+ if progress_callback and current_frame[0] % 15 == 0:
379
+ percent = min(99, int((current_frame[0] / total_frames) * 100))
380
+ progress_callback(percent)
381
+
382
+ bgr = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
383
+
384
+ # CLAHE yorug'lik
385
+ lab = cv2.cvtColor(bgr, cv2.COLOR_BGR2LAB)
386
+ l, a, b = cv2.split(lab)
387
+ clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8))
388
+ cl = clahe.apply(l)
389
+ lab = cv2.merge((cl, a, b))
390
+ bgr = cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)
391
+
392
+ # Unsharp mask
393
+ gaussian = cv2.GaussianBlur(bgr, (0, 0), 2.0)
394
+ bgr = cv2.addWeighted(bgr, 1.4, gaussian, -0.4, 0)
395
+
396
+ # Saturation oshirish
397
+ hsv = cv2.cvtColor(bgr, cv2.COLOR_BGR2HSV)
398
+ h, s, v = cv2.split(hsv)
399
+ s = cv2.add(s, 20)
400
+ hsv = cv2.merge((h, s, v))
401
+ bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
402
+
403
+ return cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)
404
+
405
+ processed = video.image_transform(color_frame)
406
+ audio_params = {"audio_codec": "aac"} if video.audio else {"audio": False}
407
+ processed.write_videofile(
408
+ output_path,
409
+ codec="libx264",
410
+ fps=video.fps or 24,
411
+ preset="ultrafast",
412
+ threads=4,
413
+ logger=None,
414
+ **audio_params
415
+ )
416
+
417
+ video.close()
418
+ processed.close()
419
+ return output_path if os.path.exists(output_path) else None
420
+ except Exception as e:
421
+ logger.error(f"Color Correct xatosi: {e}")
422
+ return None
423
+
424
+ def process_video_remove_audio(video_path, output_path, progress_callback=None):
425
+ """Videodan ovozni olib tashlaydi."""
426
+ from moviepy.video.io.VideoFileClip import VideoFileClip
427
+
428
+ try:
429
+ video_path = os.path.abspath(video_path)
430
+ output_path = os.path.abspath(output_path)
431
+
432
+ video = VideoFileClip(video_path)
433
+ muted = video.without_audio()
434
+
435
+ muted.write_videofile(
436
+ output_path,
437
+ codec="libx264",
438
+ fps=video.fps or 24,
439
+ preset="ultrafast",
440
+ threads=4,
441
+ logger=None
442
+ )
443
+
444
+ video.close()
445
+ muted.close()
446
+ return output_path if os.path.exists(output_path) else None
447
+ except Exception as e:
448
+ logger.error(f"Remove audio xatosi: {e}")
449
+ return None
450
+
451
+ def process_video_trim(video_path, output_path, progress_callback=None, max_duration=15):
452
+ """Videoning birinchi N soniyasini kesib oladi."""
453
+ from moviepy.video.io.VideoFileClip import VideoFileClip
454
+
455
+ try:
456
+ video_path = os.path.abspath(video_path)
457
+ output_path = os.path.abspath(output_path)
458
+
459
+ video = VideoFileClip(video_path)
460
+
461
+ # Agar video allaqachon qisqa bo'lsa, barini olish
462
+ end_time = min(max_duration, video.duration)
463
+ trimmed = video.subclipped(0, end_time)
464
+
465
+ audio_params = {"audio_codec": "aac"} if video.audio else {"audio": False}
466
+ trimmed.write_videofile(
467
+ output_path,
468
+ codec="libx264",
469
+ fps=video.fps or 24,
470
+ preset="ultrafast",
471
+ threads=4,
472
+ logger=None,
473
+ **audio_params
474
+ )
475
+
476
+ video.close()
477
+ trimmed.close()
478
+ return output_path if os.path.exists(output_path) else None
479
+ except Exception as e:
480
+ logger.error(f"Video trim xatosi: {e}")
481
+ return None
482
+
483
+ def process_video_face_fix(video_path, output_path, progress_callback=None):
484
+ """Video kadrlardagi yuzlarni aniqlaydi va tiniqlashtiradi."""
485
+ from moviepy.video.io.VideoFileClip import VideoFileClip
486
+
487
+ try:
488
+ video_path = os.path.abspath(video_path)
489
+ output_path = os.path.abspath(output_path)
490
+
491
+ video = VideoFileClip(video_path)
492
+ total_frames = int(video.fps * video.duration)
493
+ current_frame = [0]
494
+ face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
495
+
496
+ def face_frame(frame):
497
+ current_frame[0] += 1
498
+ if progress_callback and current_frame[0] % 15 == 0:
499
+ percent = min(99, int((current_frame[0] / total_frames) * 100))
500
+ progress_callback(percent)
501
+
502
+ bgr = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
503
+ gray = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)
504
+ # HistEq
505
+ gray = cv2.equalizeHist(gray)
506
+ faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(40, 40))
507
+
508
+ for (x, y, w, h) in faces:
509
+ p = int(w * 0.1)
510
+ x1, y1 = max(0, x - p), max(0, y - p)
511
+ x2, y2 = min(bgr.shape[1], x + w + p), min(bgr.shape[0], y + h + p)
512
+ face_roi = bgr[y1:y2, x1:x2]
513
+
514
+ # Bilateral + Median + Sharpen
515
+ smoothed = cv2.bilateralFilter(face_roi, 7, 50, 50)
516
+ smoothed = cv2.medianBlur(smoothed, 3)
517
+ gaussian = cv2.GaussianBlur(smoothed, (0, 0), 2.0)
518
+ sharpened = cv2.addWeighted(smoothed, 1.8, gaussian, -0.8, 0)
519
+
520
+ # CLAHE
521
+ lab = cv2.cvtColor(sharpened, cv2.COLOR_BGR2LAB)
522
+ l, a, b = cv2.split(lab)
523
+ clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
524
+ cl = clahe.apply(l)
525
+ lab = cv2.merge((cl, a, b))
526
+ face_roi = cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)
527
+
528
+ bgr[y1:y2, x1:x2] = face_roi
529
+
530
+ return cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)
531
+
532
+ processed = video.image_transform(face_frame)
533
+ audio_params = {"audio_codec": "aac"} if video.audio else {"audio": False}
534
+
535
+ processed.write_videofile(
536
+ output_path,
537
+ codec="libx264",
538
+ fps=video.fps or 24,
539
+ preset="ultrafast",
540
+ threads=4,
541
+ logger=None,
542
+ **audio_params
543
+ )
544
+
545
+ video.close()
546
+ processed.close()
547
+ return output_path if os.path.exists(output_path) else None
548
+ except Exception as e:
549
+ logger.error(f"Video Face Fix xatosi: {e}")
550
+ return None
551
+
552
+ def process_video_auto_enhance(video_path, output_path, progress_callback=None):
553
+ """Video ranglarini va yorug'ligini avtomatik yaxshilaydi."""
554
+ from moviepy.video.io.VideoFileClip import VideoFileClip
555
+
556
+ try:
557
+ video_path = os.path.abspath(video_path)
558
+ output_path = os.path.abspath(output_path)
559
+
560
+ video = VideoFileClip(video_path)
561
+ total_frames = int(video.fps * video.duration)
562
+ current_frame = [0]
563
+
564
+ def enhance_frame(frame):
565
+ current_frame[0] += 1
566
+ if progress_callback and current_frame[0] % 15 == 0:
567
+ percent = min(99, int((current_frame[0] / total_frames) * 100))
568
+ progress_callback(percent)
569
+
570
+ bgr = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
571
+
572
+ # CLAHE
573
+ lab = cv2.cvtColor(bgr, cv2.COLOR_BGR2LAB)
574
+ l, a, b = cv2.split(lab)
575
+ clahe = cv2.createCLAHE(clipLimit=2.5, tileGridSize=(8,8))
576
+ cl = clahe.apply(l)
577
+ lab = cv2.merge((cl, a, b))
578
+ enhanced = cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)
579
+
580
+ # Unsharp
581
+ gaussian = cv2.GaussianBlur(enhanced, (0, 0), 1.5)
582
+ enhanced = cv2.addWeighted(enhanced, 1.3, gaussian, -0.3, 0)
583
+
584
+ # Saturation
585
+ hsv = cv2.cvtColor(enhanced, cv2.COLOR_BGR2HSV)
586
+ h, s, v = cv2.split(hsv)
587
+ s = cv2.add(s, 15)
588
+ hsv = cv2.merge((h, s, v))
589
+ enhanced = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
590
+
591
+ return cv2.cvtColor(enhanced, cv2.COLOR_BGR2RGB)
592
+
593
+ processed = video.image_transform(enhance_frame)
594
+ audio_params = {"audio_codec": "aac"} if video.audio else {"audio": False}
595
+
596
+ processed.write_videofile(
597
+ output_path,
598
+ codec="libx264",
599
+ fps=video.fps or 24,
600
+ preset="ultrafast",
601
+ threads=4,
602
+ logger=None,
603
+ **audio_params
604
+ )
605
+
606
+ video.close()
607
+ processed.close()
608
+ return output_path if os.path.exists(output_path) else None
609
+ except Exception as e:
610
+ logger.error(f"Video Auto Enhance xatosi: {e}")
611
+ return None
612
+
613
+ def process_video_fps_boost(video_path, output_path, target_fps=60, progress_callback=None):
614
+ """Videoni Optical Flow orqali silliq FPS ga ko'taradi (frame interpolation).
615
+
616
+ Kuchli bidirectional flow + sharpen + yuqori sifat codec.
617
+ """
618
+ try:
619
+ video_path = os.path.abspath(video_path)
620
+ output_path = os.path.abspath(output_path)
621
+
622
+ cap = cv2.VideoCapture(video_path)
623
+ if not cap.isOpened():
624
+ logger.error(f"Video ochilmadi: {video_path}")
625
+ return None
626
+
627
+ orig_fps = cap.get(cv2.CAP_PROP_FPS) or 24
628
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
629
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
630
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
631
+
632
+ # Agar video allaqachon target_fps dan yuqori bo'lsa
633
+ if orig_fps >= target_fps:
634
+ cap.release()
635
+ from moviepy.video.io.VideoFileClip import VideoFileClip
636
+ video = VideoFileClip(video_path)
637
+ video.write_videofile(output_path, fps=target_fps, codec="libx264",
638
+ audio_codec="aac", preset="medium",
639
+ bitrate="8000k", threads=4, logger=None)
640
+ video.close()
641
+ return output_path if os.path.exists(output_path) else None
642
+
643
+ # Nechta oraliq kadr qo'shish kerak
644
+ multiplier = max(2, round(target_fps / orig_fps))
645
+ actual_target_fps = orig_fps * multiplier
646
+
647
+ logger.info(f"FPS Boost: {orig_fps} -> {actual_target_fps} (x{multiplier})")
648
+
649
+ # Vaqtincha faylga yozish (audio'siz)
650
+ temp_video_path = output_path + "_temp.avi"
651
+ fourcc = cv2.VideoWriter_fourcc(*'MJPG') # Yuqori sifat
652
+ writer = cv2.VideoWriter(temp_video_path, fourcc, actual_target_fps, (width, height))
653
+
654
+ ret, prev_frame = cap.read()
655
+ if not ret:
656
+ cap.release()
657
+ return None
658
+
659
+ frame_count = 0
660
+
661
+ # Koordinata gridi (bir marta hisoblash)
662
+ h, w = prev_frame.shape[:2]
663
+ y_coords, x_coords = np.mgrid[0:h, 0:w].astype(np.float32)
664
+
665
+ while True:
666
+ ret, next_frame = cap.read()
667
+ if not ret:
668
+ writer.write(prev_frame)
669
+ break
670
+
671
+ frame_count += 1
672
+ if progress_callback and frame_count % 3 == 0:
673
+ percent = min(99, int((frame_count / total_frames) * 100))
674
+ progress_callback(percent)
675
+
676
+ # Asl kadrni yozish
677
+ writer.write(prev_frame)
678
+
679
+ # Ikki tomonlama Optical Flow (Bidirectional)
680
+ prev_gray = cv2.cvtColor(prev_frame, cv2.COLOR_BGR2GRAY)
681
+ next_gray = cv2.cvtColor(next_frame, cv2.COLOR_BGR2GRAY)
682
+
683
+ # Oldinga oqim (prev -> next)
684
+ flow_forward = cv2.calcOpticalFlowFarneback(
685
+ prev_gray, next_gray, None,
686
+ pyr_scale=0.5, levels=5, winsize=21,
687
+ iterations=5, poly_n=7, poly_sigma=1.5,
688
+ flags=cv2.OPTFLOW_FARNEBACK_GAUSSIAN
689
+ )
690
+
691
+ # Orqaga oqim (next -> prev)
692
+ flow_backward = cv2.calcOpticalFlowFarneback(
693
+ next_gray, prev_gray, None,
694
+ pyr_scale=0.5, levels=5, winsize=21,
695
+ iterations=5, poly_n=7, poly_sigma=1.5,
696
+ flags=cv2.OPTFLOW_FARNEBACK_GAUSSIAN
697
+ )
698
+
699
+ # Oraliq kadrlarni yaratish
700
+ for i in range(1, multiplier):
701
+ alpha = i / multiplier
702
+
703
+ # Prev -> oraliq pozitsiya (oldinga warp)
704
+ flow_f = flow_forward * alpha
705
+ map_x_f = (x_coords + flow_f[..., 0]).astype(np.float32)
706
+ map_y_f = (y_coords + flow_f[..., 1]).astype(np.float32)
707
+ warped_prev = cv2.remap(prev_frame, map_x_f, map_y_f,
708
+ cv2.INTER_CUBIC, borderMode=cv2.BORDER_REFLECT_101)
709
+
710
+ # Next -> oraliq pozitsiya (orqaga warp)
711
+ flow_b = flow_backward * (1 - alpha)
712
+ map_x_b = (x_coords + flow_b[..., 0]).astype(np.float32)
713
+ map_y_b = (y_coords + flow_b[..., 1]).astype(np.float32)
714
+ warped_next = cv2.remap(next_frame, map_x_b, map_y_b,
715
+ cv2.INTER_CUBIC, borderMode=cv2.BORDER_REFLECT_101)
716
+
717
+ # Ikki warp natijasini birlashtirish
718
+ interpolated = cv2.addWeighted(warped_prev, 1 - alpha, warped_next, alpha, 0)
719
+
720
+ # Xiralashishga qarshi o'tkirlashtirish (Unsharp Mask)
721
+ gaussian = cv2.GaussianBlur(interpolated, (0, 0), 1.0)
722
+ interpolated = cv2.addWeighted(interpolated, 1.3, gaussian, -0.3, 0)
723
+
724
+ writer.write(interpolated)
725
+
726
+ prev_frame = next_frame
727
+
728
+ cap.release()
729
+ writer.release()
730
+
731
+ # Audio'ni biriktirish va yuqori sifatda saqlash
732
+ try:
733
+ from moviepy.video.io.VideoFileClip import VideoFileClip
734
+ original = VideoFileClip(video_path)
735
+ processed = VideoFileClip(temp_video_path)
736
+
737
+ if original.audio is not None:
738
+ final = processed.with_audio(original.audio)
739
+ final.write_videofile(output_path, codec="libx264", audio_codec="aac",
740
+ preset="medium", bitrate="8000k",
741
+ threads=4, logger=None)
742
+ final.close()
743
+ else:
744
+ processed.write_videofile(output_path, codec="libx264",
745
+ preset="medium", bitrate="8000k",
746
+ threads=4, logger=None)
747
+
748
+ original.close()
749
+ processed.close()
750
+ except Exception as audio_err:
751
+ logger.warning(f"Audio biriktirish xatosi: {audio_err}")
752
+ import shutil
753
+ shutil.move(temp_video_path, output_path)
754
+
755
+ # Tozalash
756
+ if os.path.exists(temp_video_path):
757
+ try: os.remove(temp_video_path)
758
+ except: pass
759
+
760
+ return output_path if os.path.exists(output_path) else None
761
+ except Exception as e:
762
+ logger.error(f"FPS Boost xatosi: {e}")
763
+ return None
main.py ADDED
@@ -0,0 +1,407 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ import asyncio
4
+ import collections
5
+ import gc
6
+ from datetime import datetime, timedelta
7
+ from dotenv import load_dotenv
8
+
9
+ from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, constants
10
+ from telegram.ext import (
11
+ ApplicationBuilder,
12
+ CommandHandler,
13
+ MessageHandler,
14
+ filters,
15
+ ContextTypes,
16
+ CallbackQueryHandler
17
+ )
18
+
19
+ from filters import (
20
+ apply_retro_filter,
21
+ upscale_image,
22
+ process_video_retro,
23
+ process_video_upscale,
24
+ apply_face_restore,
25
+ apply_auto_enhance,
26
+ process_video_slowmo,
27
+ process_video_bw,
28
+ process_video_color_correct,
29
+ process_video_remove_audio,
30
+ process_video_trim,
31
+ process_video_face_fix,
32
+ process_video_auto_enhance,
33
+ process_video_fps_boost
34
+ )
35
+ from database import db
36
+ from concurrent.futures import ThreadPoolExecutor
37
+ from apscheduler.schedulers.asyncio import AsyncIOScheduler
38
+
39
+ # Muhit o'zgaruvchilarini yuklash
40
+ load_dotenv()
41
+ TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
42
+
43
+ # Loglarni sozlash
44
+ logging.basicConfig(
45
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
46
+ level=logging.INFO
47
+ )
48
+ logger = logging.getLogger(__name__)
49
+
50
+ # Global media storage
51
+ media_storage = {}
52
+ processing_semaphore = asyncio.Semaphore(2) # Maksimal 2 ta parallel jarayon
53
+ executor = ThreadPoolExecutor(max_workers=4)
54
+ base_dir = os.path.dirname(os.path.abspath(__file__))
55
+
56
+ # Scheduler for cleanup
57
+ scheduler = AsyncIOScheduler()
58
+
59
+ async def post_init(application):
60
+ """Bot ishga tushgandan so'ng bajariladigan amallar."""
61
+ scheduler.add_job(cleanup_old_files, 'interval', minutes=30)
62
+ scheduler.start()
63
+ logger.info("Scheduler va tozalash tizimi ishga tushdi.")
64
+
65
+ async def cleanup_old_files():
66
+ """Eski fayllarni va xotirani tozalash."""
67
+ now = datetime.now()
68
+ to_delete = [k for k, v in media_storage.items() if (now - v.get('timestamp', now)) > timedelta(hours=1)]
69
+ for k in to_delete:
70
+ del media_storage[k]
71
+
72
+ for f in os.listdir(base_dir):
73
+ if (f.startswith("in_") or f.startswith("out_")) and (now - datetime.fromtimestamp(os.path.getmtime(os.path.join(base_dir, f)))) > timedelta(hours=1):
74
+ try:
75
+ os.remove(os.path.join(base_dir, f))
76
+ except: pass
77
+
78
+ # --- UI Helpers ---
79
+
80
+ def get_main_menu_keyboard():
81
+ """Asosiy menyu tugmalari."""
82
+ keyboard = [
83
+ [
84
+ InlineKeyboardButton("๐Ÿ“œ Tarix", callback_data="nav|history"),
85
+ InlineKeyboardButton("โš™๏ธ Sozlamalar", callback_data="nav|settings")
86
+ ],
87
+ [
88
+ InlineKeyboardButton("โ„น๏ธ Yordam", callback_data="nav|help")
89
+ ]
90
+ ]
91
+ return InlineKeyboardMarkup(keyboard)
92
+
93
+ async def show_main_menu(update_or_query, context):
94
+ """Asosiy menyuni ko'rsatish."""
95
+ text = (
96
+ "โœจ **Mukammal Filtr Botga xush kelibsiz!**\n\n"
97
+ "Menga rasm yoki video yuboring, so'ngra mo''jizani ko'ring. ๐ŸŽจโœจ\n\n"
98
+ "๐Ÿ’ก *Pastdagi tugmalar orqali botni boshqarishingiz mumkin:*"
99
+ )
100
+
101
+ if isinstance(update_or_query, Update):
102
+ await update_or_query.message.reply_text(text, reply_markup=get_main_menu_keyboard(), parse_mode=constants.ParseMode.MARKDOWN)
103
+ else:
104
+ await update_or_query.edit_message_text(text, reply_markup=get_main_menu_keyboard(), parse_mode=constants.ParseMode.MARKDOWN)
105
+
106
+ # --- Handlers ---
107
+
108
+ async def settings_command(update_or_query, context: ContextTypes.DEFAULT_TYPE):
109
+ """Sozlamalar menyusi."""
110
+ if isinstance(update_or_query, Update):
111
+ user_id = update_or_query.effective_user.id
112
+ else:
113
+ user_id = update_or_query.from_user.id
114
+
115
+ current_filter = db.get_user_settings(user_id)
116
+
117
+ text = (
118
+ "\u2699\ufe0f **Sozlamalar**\n\n"
119
+ f"Joriy standart filtr: **{current_filter.upper()}**\n"
120
+ "Yangi standart filtrni tanlang:"
121
+ )
122
+
123
+ keyboard = [
124
+ [
125
+ InlineKeyboardButton("\ud83c\udf9e Retro", callback_data="set|retro"),
126
+ InlineKeyboardButton("\ud83d\udc8e Ultra HD", callback_data="set|upscale")
127
+ ],
128
+ [
129
+ InlineKeyboardButton("\ud83e\udd16 Face Fix", callback_data="set|face_fix"),
130
+ InlineKeyboardButton("\u2728 Auto-Enhance", callback_data="set|auto_enhance")
131
+ ],
132
+ [InlineKeyboardButton("\ud83d\udd19 Orqaga", callback_data="nav|main")]
133
+ ]
134
+
135
+ if isinstance(update_or_query, Update):
136
+ await update_or_query.message.reply_text(text, reply_markup=InlineKeyboardMarkup(keyboard), parse_mode=constants.ParseMode.MARKDOWN)
137
+ else:
138
+ await update_or_query.edit_message_text(text, reply_markup=InlineKeyboardMarkup(keyboard), parse_mode=constants.ParseMode.MARKDOWN)
139
+
140
+ async def settings_callback_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
141
+ """Filtrni tanlash callbacki."""
142
+ query = update.callback_query
143
+ await query.answer()
144
+
145
+ filter_type = query.data.split("|")[1]
146
+ db.set_default_filter(query.from_user.id, filter_type)
147
+
148
+ name_map = {"retro": "RETRO", "upscale": "ULTRA HD", "face_fix": "FACE FIX", "auto_enhance": "AUTO-ENHANCE"}
149
+ display_name = name_map.get(filter_type, filter_type.upper())
150
+
151
+ await query.edit_message_text(
152
+ f"\u2705 Standart filtr **{display_name}** ga o'zgartirildi!",
153
+ reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("\ud83d\udd19 Orqaga", callback_data="nav|settings")]]),
154
+ parse_mode=constants.ParseMode.MARKDOWN
155
+ )
156
+
157
+ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
158
+ """/start buyrug'i."""
159
+ user = update.effective_user
160
+ db.add_user(user.id, user.username, user.first_name)
161
+ await show_main_menu(update, context)
162
+
163
+ async def handle_photo(update: Update, context: ContextTypes.DEFAULT_TYPE):
164
+ """Rasmlarni qabul qilish."""
165
+ try:
166
+ photo = update.message.photo[-1]
167
+ file_id = photo.file_id
168
+ short_id = str(update.message.message_id)
169
+ media_storage[short_id] = {"file_id": file_id, "type": "photo", "timestamp": datetime.now()}
170
+
171
+ text = "๐Ÿ–ผ **Rasm qabul qilindi!**\nQanday mo''jiza yaratamiz?"
172
+ if photo.file_size < 150000:
173
+ text = "๐Ÿ” **Tahlil:** Rasm biroz xira. 'Ultra HD' yoki 'Face Fix' tavsiya etiladi!\n\n" + text
174
+
175
+ keyboard = [
176
+ [
177
+ InlineKeyboardButton("๐ŸŽž Retro", callback_data=f"r|p|{short_id}"),
178
+ InlineKeyboardButton("๐Ÿ’Ž Ultra HD", callback_data=f"u|p|{short_id}")
179
+ ],
180
+ [
181
+ InlineKeyboardButton("๐Ÿค– Face Fix", callback_data=f"f|p|{short_id}"),
182
+ InlineKeyboardButton("โœจ Auto-Enhance", callback_data=f"a|p|{short_id}")
183
+ ],
184
+ [
185
+ InlineKeyboardButton("๐Ÿ”™ Bekor qilish", callback_data="nav|main")
186
+ ]
187
+ ]
188
+ await update.message.reply_text(text, reply_markup=InlineKeyboardMarkup(keyboard), parse_mode=constants.ParseMode.MARKDOWN)
189
+ except Exception as e:
190
+ logger.error(f"Error in handle_photo: {e}")
191
+ await update.message.reply_text("โŒ Xatolik yuz berdi.")
192
+
193
+ async def handle_video(update: Update, context: ContextTypes.DEFAULT_TYPE):
194
+ """Videolarni qabul qilish."""
195
+ try:
196
+ message = update.message
197
+ video = message.video or message.animation or message.document
198
+
199
+ if message.document and not (message.document.mime_type or "").startswith('video/'):
200
+ return
201
+
202
+ file_id = getattr(video, 'file_id', None)
203
+ if not file_id: return
204
+
205
+ if getattr(video, 'file_size', 0) > 50 * 1024 * 1024:
206
+ await message.reply_text("โš ๏ธ Video juda katta (max 50MB).")
207
+ return
208
+
209
+ short_id = str(message.message_id)
210
+ media_storage[short_id] = {"file_id": file_id, "type": "video", "timestamp": datetime.now()}
211
+
212
+ keyboard = [
213
+ [
214
+ InlineKeyboardButton("๐ŸŽž Retro", callback_data=f"r|v|{short_id}"),
215
+ InlineKeyboardButton("๐Ÿ’Ž Ultra HD", callback_data=f"u|v|{short_id}")
216
+ ],
217
+ [
218
+ InlineKeyboardButton("๐Ÿข Slow-Mo", callback_data=f"s|v|{short_id}"),
219
+ InlineKeyboardButton("โฌ› Oq-Qora", callback_data=f"bw|v|{short_id}")
220
+ ],
221
+ [
222
+ InlineKeyboardButton("๐ŸŒˆ Rang Fix", callback_data=f"cc|v|{short_id}"),
223
+ InlineKeyboardButton("โœจ Auto-Enhance", callback_data=f"va|v|{short_id}")
224
+ ],
225
+ [
226
+ InlineKeyboardButton("๐Ÿค– Face Fix", callback_data=f"vf|v|{short_id}"),
227
+ InlineKeyboardButton("๐Ÿ”‡ Ovoz o'chirish", callback_data=f"ra|v|{short_id}")
228
+ ],
229
+ [
230
+ InlineKeyboardButton("โœ‚๏ธ Kesish (15s)", callback_data=f"t|v|{short_id}")
231
+ ],
232
+ [
233
+ InlineKeyboardButton("30 FPS", callback_data=f"fps30|v|{short_id}"),
234
+ InlineKeyboardButton("60 FPS", callback_data=f"fps60|v|{short_id}"),
235
+ InlineKeyboardButton("120 FPS", callback_data=f"fps120|v|{short_id}")
236
+ ],
237
+ [
238
+ InlineKeyboardButton("๐Ÿ”™ Bekor qilish", callback_data="nav|main")
239
+ ]
240
+ ]
241
+ await update.message.reply_text("๐ŸŽฅ **Video qabul qilindi!**\nQanday effekt beramiz?", reply_markup=InlineKeyboardMarkup(keyboard), parse_mode=constants.ParseMode.MARKDOWN)
242
+ except Exception as e:
243
+ logger.error(f"Error in handle_video: {e}")
244
+ await update.message.reply_text("โŒ Xatolik yuz berdi.")
245
+
246
+ async def navigation_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
247
+ """Navigatsiya tugmalari (Menu, History, Settings)."""
248
+ query = update.callback_query
249
+ await query.answer()
250
+
251
+ data = query.data.split("|")[1]
252
+
253
+ if data == "main":
254
+ await show_main_menu(query, context)
255
+
256
+ elif data == "history":
257
+ history = db.get_user_history(query.from_user.id)
258
+ if not history:
259
+ text = "๐Ÿ“ญ Tarix bo'sh."
260
+ else:
261
+ text = "๐Ÿ“œ **Sizning oxirgi 5 ta amalingiz:**\n\n"
262
+ for i, (m_type, f_type, dt) in enumerate(history, 1):
263
+ dt_obj = datetime.fromisoformat(dt).strftime("%H:%M %d.%m")
264
+ text += f"{i}. {'๐Ÿ–ผ' if m_type == 'photo' else '๐ŸŽฅ'} {f_type.upper()} - {dt_obj}\n"
265
+
266
+ keyboard = [[InlineKeyboardButton("๐Ÿ”™ Orqaga", callback_data="nav|main")]]
267
+ await query.edit_message_text(text, reply_markup=InlineKeyboardMarkup(keyboard), parse_mode=constants.ParseMode.MARKDOWN)
268
+
269
+ elif data == "settings":
270
+ await settings_command(query, context)
271
+
272
+ elif data == "help":
273
+ text = (
274
+ "โ„น๏ธ **Yordam**\n\n"
275
+ "1. Rasm yoki Video yuboring.\n"
276
+ "2. Tugmalar orqali filtrni tanlang.\n"
277
+ "3. Progress bar orqali jarayonni kuzating.\n"
278
+ "4. Natijani yuklab oling!"
279
+ )
280
+ keyboard = [[InlineKeyboardButton("๐Ÿ”™ Orqaga", callback_data="nav|main")]]
281
+ await query.edit_message_text(text, reply_markup=InlineKeyboardMarkup(keyboard), parse_mode=constants.ParseMode.MARKDOWN)
282
+
283
+ async def button_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
284
+ """Filtr tugmalari."""
285
+ query = update.callback_query
286
+ await query.answer()
287
+
288
+ try:
289
+ data = query.data.split("|")
290
+ action, m_type, short_id = data[0], data[1], data[2]
291
+
292
+ if short_id not in media_storage:
293
+ await query.edit_message_text("โš ๏ธ Media ma'lumotlari topilmadi. Iltimos, qayta yuboring.")
294
+ return
295
+
296
+ async with processing_semaphore:
297
+ media_info = media_storage[short_id]
298
+ file_id = media_info["file_id"]
299
+
300
+ msg = await query.edit_message_text("โณ Jarayon boshlanmoqda...")
301
+
302
+ file = await context.bot.get_file(file_id, read_timeout=60)
303
+ ext = "mp4" if m_type == "v" else "jpg"
304
+ input_path = os.path.join(base_dir, f"in_{short_id}.{ext}")
305
+ output_path = os.path.join(base_dir, f"out_{short_id}.{ext}")
306
+ await file.download_to_drive(input_path, read_timeout=60)
307
+
308
+ last_percent = [0]
309
+ async def progress(p):
310
+ # Faqat 10% dan oshganda xabarni yangilash (Telegram API ni qiynamaslik uchun)
311
+ if p - last_percent[0] >= 10 or p == 99:
312
+ last_percent[0] = p
313
+ bar = "โ–ˆ" * (p // 10) + "โ–‘" * (10 - p // 10)
314
+ try: await query.edit_message_text(f"โš™๏ธ Qayta ishlanmoqda...\n[{bar}] {p}%")
315
+ except: pass
316
+
317
+ loop = asyncio.get_event_loop()
318
+ callback = lambda p: asyncio.run_coroutine_threadsafe(progress(p), loop)
319
+ success_path = None
320
+
321
+ logger.info(f"Processing started: {action} on {m_type} for {short_id}")
322
+
323
+ if m_type == "p":
324
+ func = {
325
+ "r": apply_retro_filter, "u": upscale_image,
326
+ "f": apply_face_restore, "a": apply_auto_enhance
327
+ }.get(action)
328
+ if not func:
329
+ logger.error(f"Unknown action: {action}")
330
+ await context.bot.send_message(query.message.chat_id, "โŒ Noma'lum amal.")
331
+ return
332
+ success_path = await loop.run_in_executor(executor, func, input_path, output_path)
333
+ else:
334
+ # FPS Boost uchun alohida mantiq
335
+ if action in ("fps30", "fps60", "fps120"):
336
+ fps_map = {"fps30": 30, "fps60": 60, "fps120": 120}
337
+ target = fps_map[action]
338
+ success_path = await loop.run_in_executor(
339
+ executor, process_video_fps_boost, input_path, output_path, target, callback
340
+ )
341
+ else:
342
+ video_funcs = {
343
+ "r": process_video_retro,
344
+ "u": process_video_upscale,
345
+ "s": process_video_slowmo,
346
+ "bw": process_video_bw,
347
+ "cc": process_video_color_correct,
348
+ "ra": process_video_remove_audio,
349
+ "t": process_video_trim,
350
+ "vf": process_video_face_fix,
351
+ "va": process_video_auto_enhance
352
+ }
353
+ func = video_funcs.get(action)
354
+ if not func:
355
+ logger.error(f"Unknown video action: {action}")
356
+ await context.bot.send_message(query.message.chat_id, "โŒ Noma'lum video amali.")
357
+ return
358
+ success_path = await loop.run_in_executor(executor, func, input_path, output_path, callback)
359
+
360
+ logger.info(f"Processing finished. Success path: {success_path}")
361
+
362
+ if success_path and os.path.exists(output_path):
363
+ # Save to history
364
+ f_name = {
365
+ "r": "retro", "u": "4k", "f": "face fix", "a": "auto enhance",
366
+ "s": "slow motion", "bw": "oq-qora", "cc": "rang fix",
367
+ "ra": "ovoz olib tashlash", "t": "kesish", "vf": "video face fix",
368
+ "va": "video auto enhance", "fps30": "30 FPS boost",
369
+ "fps60": "60 FPS boost", "fps120": "120 FPS boost"
370
+ }.get(action, "filter")
371
+ db.log_history(query.from_user.id, "photo" if m_type == "p" else "video", f_name, file_id)
372
+
373
+ with open(output_path, 'rb') as f:
374
+ caption = f"โœ… <b>{f_name.upper()}</b> muvaffaqiyatli qo'llanildi!"
375
+ if m_type == "p":
376
+ await context.bot.send_photo(query.message.chat_id, f, caption=caption, parse_mode=constants.ParseMode.HTML, read_timeout=60, write_timeout=60)
377
+ else:
378
+ await context.bot.send_video(query.message.chat_id, f, caption=caption, parse_mode=constants.ParseMode.HTML, read_timeout=120, write_timeout=120)
379
+ else:
380
+ logger.error(f"Processing failed for {short_id}. Action: {action}")
381
+ await context.bot.send_message(query.message.chat_id, "โŒ Afsuski, ishlov berishda xatolik yuz berdi yoki natija topilmadi.")
382
+
383
+ await query.delete_message()
384
+ except Exception as e:
385
+ logger.error(f"Global button_handler error: {e}")
386
+ try: await context.bot.send_message(query.message.chat_id, f"โŒ Tizimli xatolik: {e}")
387
+ except: pass
388
+ finally:
389
+ try:
390
+ if os.path.exists(input_path): os.remove(input_path)
391
+ if os.path.exists(output_path): os.remove(output_path)
392
+ except: pass
393
+
394
+ # Xotirani majburiy tozalash (RAM to'lishini oldini oladi)
395
+ gc.collect()
396
+
397
+ if __name__ == '__main__':
398
+ if TOKEN:
399
+ app = ApplicationBuilder().token(TOKEN).post_init(post_init).build()
400
+ app.add_handler(CommandHandler("start", start))
401
+ app.add_handler(CallbackQueryHandler(navigation_handler, pattern=r"^nav\|"))
402
+ app.add_handler(CallbackQueryHandler(settings_callback_handler, pattern=r"^set\|"))
403
+ app.add_handler(CallbackQueryHandler(button_handler))
404
+ app.add_handler(MessageHandler(filters.PHOTO, handle_photo))
405
+ app.add_handler(MessageHandler(filters.VIDEO | filters.ANIMATION | filters.Document.ALL, handle_video))
406
+ print("Bot ishga tushdi...")
407
+ app.run_polling()
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ python-telegram-bot[job-queue]
2
+ opencv-python
3
+ Pillow
4
+ moviepy
5
+ torch
6
+ torchvision
7
+ numpy
8
+ python-dotenv
9
+ apscheduler