soxogvv commited on
Commit
ff5aec9
Β·
verified Β·
1 Parent(s): 4f06ae2

Upload folder using huggingface_hub

Browse files
.gitattributes CHANGED
@@ -1,35 +1,3 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ laugh/Create[[:space:]]a[[:space:]]Python[[:space:]]virtual[[:space:]]environment[[:space:]]in[[:space:]]Windows[[:space:]]with[[:space:]]VS[[:space:]]Code.webm filter=lfs diff=lfs merge=lfs -text
2
+ laugh/laugh_one.mp4 filter=lfs diff=lfs merge=lfs -text
3
+ laugh/laugh_two.mp4 filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md CHANGED
@@ -1,10 +1,10 @@
1
  ---
2
- title: U
3
- emoji: πŸŒ–
4
  colorFrom: indigo
5
- colorTo: purple
6
  sdk: gradio
7
- sdk_version: 5.38.0
8
  app_file: app.py
9
  pinned: false
10
  ---
 
1
  ---
2
+ title: Hahahajah
3
+ emoji: 🐒
4
  colorFrom: indigo
5
+ colorTo: gray
6
  sdk: gradio
7
+ sdk_version: 5.36.2
8
  app_file: app.py
9
  pinned: false
10
  ---
app.py ADDED
@@ -0,0 +1,788 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ # ───────────── Standard Library
3
+ import os
4
+ import time
5
+ import uuid
6
+
7
+
8
+ import json
9
+ import base64
10
+ import asyncio
11
+ import nest_asyncio
12
+ import random
13
+ import logging
14
+ import atexit
15
+ import pathlib
16
+ from threading import Thread
17
+ from datetime import datetime, timedelta, timezone
18
+ from typing import Optional, Tuple, List
19
+
20
+ # ───────────── Flask
21
+ from flask import Flask, render_template, request, redirect, session
22
+
23
+ # ───────────── MoviePy
24
+ from moviepy.editor import (
25
+ VideoFileClip,
26
+ ImageClip,
27
+ ColorClip,
28
+ CompositeVideoClip,
29
+ concatenate_videoclips
30
+ )
31
+ from moviepy.video.fx import resize
32
+
33
+ # ───────────── Pillow (PIL)
34
+ from PIL import Image, ImageDraw, ImageFont
35
+
36
+ # ───────────── NumPy
37
+ import numpy as np
38
+
39
+ # ───────────── Requests
40
+ import requests
41
+
42
+ # ───────────── Emoji Handling
43
+ import emoji
44
+
45
+ # ───────────── MongoDB
46
+ from pymongo import MongoClient
47
+
48
+ # ───────────── YouTube API (Google)
49
+ from google.oauth2.credentials import Credentials
50
+ from google.auth.transport.requests import Request
51
+ from googleapiclient.discovery import build
52
+ from googleapiclient.http import MediaFileUpload
53
+
54
+ # ───────────── yt-dlp
55
+ from yt_dlp import YoutubeDL
56
+
57
+ # ───────────── Telegram Bot
58
+ from telegram import Update
59
+ from telegram.ext import (
60
+ Application,
61
+ CommandHandler,
62
+ MessageHandler,
63
+ filters,
64
+ ContextTypes,
65
+ JobQueue
66
+ )
67
+
68
+ UPLOAD_TIMES = []
69
+ NEXT_RESET = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1)
70
+
71
+
72
+ def patch_moviepy():
73
+ original_resizer = resize.resize
74
+
75
+ def patched_resizer(clip, *args, **kwargs):
76
+ newsize = kwargs.get("newsize", None)
77
+ if newsize:
78
+ newsize = tuple(map(int, newsize))
79
+ clip = clip.fl_image(lambda img: img.resize(newsize, Image.Resampling.LANCZOS))
80
+ else:
81
+ clip = original_resizer(clip, *args, **kwargs)
82
+ return clip
83
+
84
+ resize.resize = patched_resizer
85
+
86
+ patch_moviepy()
87
+
88
+ import emoji
89
+ from PIL import Image, ImageDraw, ImageFont
90
+ import emoji
91
+ import os
92
+ import requests
93
+ from PIL import Image, ImageDraw, ImageFont
94
+ import emoji
95
+ import os, requests
96
+
97
+ import os
98
+ import emoji
99
+ import requests
100
+ from PIL import Image, ImageDraw, ImageFont
101
+
102
+ def create_text_image_2(
103
+ text: str,
104
+ width: int,
105
+ height: int,
106
+ *,
107
+ font_size: int = 60,
108
+ align: str = "center", # "left" | "center" | "right"
109
+ bg_color=(255, 255, 255),
110
+ text_color=(0, 0, 0)
111
+ ):
112
+ img = Image.new("RGBA", (width, height), color=bg_color)
113
+ draw = ImageDraw.Draw(img)
114
+
115
+ # Load font
116
+ try:
117
+ font = ImageFont.truetype("DejaVuSans-Bold.ttf", size=font_size)
118
+ except OSError:
119
+ font = ImageFont.load_default()
120
+
121
+ # Extract emojis and plain text
122
+ all_emojis = emoji.emoji_list(text)
123
+ plain_text = emoji.replace_emoji(text, replace='')
124
+
125
+ # Measure text size
126
+ text_width, text_height = draw.textsize(plain_text, font=font)
127
+ total_emoji_width = len(all_emojis) * font_size
128
+ full_width = text_width + total_emoji_width + 5 * len(all_emojis)
129
+
130
+ # X‑offset based on alignment
131
+ if align == "left":
132
+ x_start = 20
133
+ elif align == "right":
134
+ x_start = max(20, width - full_width - 20)
135
+ else: # center
136
+ x_start = max(20, (width - full_width) // 2)
137
+
138
+ y_start = (height - text_height) // 2
139
+
140
+ # Draw the plain text
141
+ draw.text((x_start, y_start), plain_text, font=font, fill=text_color)
142
+
143
+ # Get emoji positions (in original string)
144
+ x = x_start + text_width + 5
145
+ for em in all_emojis:
146
+ char = em["emoji"]
147
+ hexcode = "-".join(f"{ord(c):x}" for c in char)
148
+
149
+ # Emoji image file
150
+ emoji_path = f"emoji_pngs/{hexcode}.png"
151
+ if not os.path.exists(emoji_path):
152
+ url = f"https://github.com/twitter/twemoji/raw/master/assets/72x72/{hexcode}.png"
153
+ os.makedirs("emoji_pngs", exist_ok=True)
154
+ try:
155
+ response = requests.get(url, timeout=5)
156
+ if response.ok:
157
+ with open(emoji_path, "wb") as f:
158
+ f.write(response.content)
159
+ except Exception:
160
+ continue
161
+
162
+ # Paste emoji
163
+ if os.path.exists(emoji_path):
164
+ em_img = Image.open(emoji_path).convert("RGBA").resize((font_size, font_size))
165
+ img.paste(em_img, (x, y_start), em_img)
166
+ x += font_size + 5
167
+
168
+ return img
169
+
170
+
171
+ def create_text_image(text, width, height):
172
+ img = Image.new("RGB", (width, height), color=(255, 255, 255))
173
+ draw = ImageDraw.Draw(img)
174
+
175
+ # Load font
176
+ try:
177
+ font = ImageFont.truetype("DejaVuSans-Bold.ttf", size=60)
178
+ except:
179
+ font = ImageFont.load_default()
180
+
181
+ # Extract emoji and clean text
182
+ emojis = emoji.emoji_list(text)
183
+ pure_text = emoji.replace_emoji(text, replace='')
184
+
185
+ # Adjust font size to fit
186
+ max_font_size = 70
187
+ while True:
188
+ font = ImageFont.truetype("DejaVuSans-Bold.ttf", size=max_font_size)
189
+ text_width, text_height = draw.textsize(pure_text, font=font)
190
+ total_width = text_width + (len(emojis) * 60) + 20
191
+ if total_width <= width - 40 or max_font_size <= 30:
192
+ break
193
+ max_font_size -= 2
194
+
195
+ # Starting X & Y for centered layout
196
+ start_x = (width - total_width) // 2
197
+ y = (height - text_height) // 2
198
+
199
+ # Draw text first
200
+ draw.text((start_x, y), pure_text, font=font, fill=(0, 0, 0))
201
+
202
+ # Then draw emojis to the right of the text
203
+ x = start_x + text_width + 10
204
+ for e in emojis:
205
+ hexcode = '-'.join(f"{ord(c):x}" for c in e['emoji'])
206
+ emoji_path = f"emoji_pngs/{hexcode}.png"
207
+ if not os.path.exists(emoji_path):
208
+ download_emoji_png(e['emoji'])
209
+
210
+ if os.path.exists(emoji_path):
211
+ emoji_img = Image.open(emoji_path).convert("RGBA")
212
+ emoji_img = emoji_img.resize((60, 60))
213
+ img.paste(emoji_img, (x, y), emoji_img)
214
+ x += 60 + 4
215
+
216
+ return img
217
+
218
+ from PIL import Image, ImageDraw, ImageFont
219
+ import numpy as np
220
+ from moviepy.editor import ImageClip
221
+
222
+ def generate_watermark_img(text, width, height=50):
223
+ img = Image.new("RGBA", (width, height), (0, 0, 0, 0))
224
+ draw = ImageDraw.Draw(img)
225
+
226
+ try:
227
+ font = ImageFont.truetype("DejaVuSans-Bold.ttf", size=35)
228
+ except:
229
+ font = ImageFont.load_default()
230
+
231
+ text_width, text_height = draw.textsize(text, font=font)
232
+ draw.text((5, height - text_height - 2), text, fill="white", font=font, stroke_width=1, stroke_fill="black")
233
+
234
+ return img
235
+
236
+ def download_emoji_png(emoji_char):
237
+ hexcode = '-'.join(f"{ord(c):x}" for c in emoji_char)
238
+ url = f"https://github.com/twitter/twemoji/raw/master/assets/72x72/{hexcode}.png"
239
+ os.makedirs("emoji_pngs", exist_ok=True)
240
+ path = f"emoji_pngs/{hexcode}.png"
241
+ try:
242
+ r = requests.get(url)
243
+ if r.status_code == 200:
244
+ with open(path, "wb") as f:
245
+ f.write(r.content)
246
+ print(f"βœ… Downloaded emoji: {emoji_char} β†’ {path}")
247
+ else:
248
+ print(f"❌ Failed to download emoji: {emoji_char}")
249
+ except Exception as e:
250
+ print(f"⚠️ Error downloading emoji {emoji_char}: {e}")
251
+
252
+
253
+ def edit_video(video_path):
254
+ clip = VideoFileClip(video_path)
255
+
256
+ video_width = clip.w
257
+ video_height = clip.h
258
+ bar_height = 120
259
+ total_height = video_height + bar_height
260
+
261
+ # === 1. Background Canvas
262
+ final_bg = ColorClip(size=(video_width, total_height), color=(255, 255, 255), duration=clip.duration)
263
+
264
+ # === 2. Caption Bar (Top)
265
+ caption = random.choice(CAPTIONS)
266
+ caption_img = create_text_image(caption, video_width, bar_height)
267
+ caption_clip = ImageClip(np.array(caption_img)).set_duration(clip.duration).set_position((0, 0))
268
+
269
+ # === 3. Eye Protection Overlay (6% White Transparent)
270
+ eye_protection = ColorClip(size=(video_width, video_height), color=(255, 255, 255), duration=clip.duration)
271
+ eye_protection = eye_protection.set_opacity(0.1).set_position((0, bar_height))
272
+
273
+ # === 4. Watermark (Bottom-left using Pillow + ImageClip)
274
+ watermark_img = generate_watermark_img("@fulltosscomedy4u", video_width, height=50)
275
+ watermark_clip = ImageClip(np.array(watermark_img)).set_duration(clip.duration).set_position(("left", bar_height + video_height - 50))
276
+
277
+ # === 5. Position original video below top bar
278
+ video_clip = clip.set_position((0, bar_height))
279
+
280
+ # === 6. Combine everything
281
+ final = CompositeVideoClip(
282
+ [final_bg, caption_clip, video_clip, eye_protection, watermark_clip],
283
+ size=(video_width, total_height)
284
+ )
285
+
286
+ os.makedirs("edited", exist_ok=True)
287
+ output_path = f"edited/{uuid.uuid4().hex}.mp4"
288
+ final.write_videofile(
289
+ output_path,
290
+ codec="libx264",
291
+ audio_codec="aac",
292
+ preset="veryslow",
293
+ bitrate="12000k",
294
+ verbose=False,
295
+ logger=None
296
+ )
297
+ return output_path
298
+
299
+ def edit_video_raw(video_path: str) -> str:
300
+ import os, uuid, random
301
+ import numpy as np
302
+ from moviepy.editor import (
303
+ VideoFileClip, ImageClip, ColorClip, TextClip,
304
+ CompositeVideoClip
305
+ )
306
+
307
+ # Load main video
308
+ clip = VideoFileClip(video_path).resize(width=1220)
309
+ vw, vh = 1220, 2460
310
+
311
+ # Pixel-perfect heights
312
+ CAPTION_H = 230
313
+ LAUGH_H = 508
314
+ MID_H = 210
315
+ MAIN_H = 1512
316
+
317
+ if clip.duration < 6:
318
+ raise ValueError("Main video must be at least 6 seconds.")
319
+ if clip.duration > 180:
320
+ clip = clip.subclip(0, 180)
321
+
322
+ # Top caption
323
+ caption = random.choice(CAPTIONS)
324
+ caption_img = create_text_image_2(
325
+ caption, vw, CAPTION_H,
326
+ font_size=72, align="center",
327
+ bg_color=(0, 0, 0), text_color=(255, 255, 255)
328
+ )
329
+ caption_clip = ImageClip(np.array(caption_img)) \
330
+ .set_duration(clip.duration) \
331
+ .set_position((0, 0))
332
+
333
+ # Laugh meme (2s + freeze + 2s)
334
+ laugh_files = ["laugh/laugh_one.mp4", "laugh/laugh_two.mp4"]
335
+ laugh_path = random.choice(laugh_files)
336
+ if not os.path.exists(laugh_path):
337
+ raise FileNotFoundError(f"❌ Laugh meme not found: {laugh_path}")
338
+
339
+ laugh_raw = VideoFileClip(laugh_path).resize(width=1220)
340
+ if laugh_raw.duration < 4:
341
+ raise ValueError("Laugh meme must be at least 4 seconds.")
342
+
343
+ laugh_start = (
344
+ laugh_raw.subclip(0, 2)
345
+ .resize(height=LAUGH_H)
346
+ .set_start(0)
347
+ .set_position((0, CAPTION_H))
348
+ )
349
+
350
+ freeze_frame = (
351
+ laugh_start.to_ImageClip()
352
+ .set_start(2)
353
+ .set_duration(max(0, clip.duration - 4))
354
+ .set_position((0, CAPTION_H))
355
+ )
356
+
357
+ laugh_end = (
358
+ laugh_raw.subclip(laugh_raw.duration - 2)
359
+ .resize(height=LAUGH_H)
360
+ .set_start(clip.duration - 2)
361
+ .set_position((0, CAPTION_H))
362
+ )
363
+
364
+ # Mid caption
365
+ mid_text = random.choice([
366
+ "Pura 1 din laga tab ye reel mili 🀣",
367
+ "Ye miss mat kr dena 😜",
368
+ "Kha thi ye reel ab tak πŸ€¨πŸ€—",
369
+ "Wait, ye dekh kr hi janna πŸ’₯πŸ’₯",
370
+ ])
371
+ mid_img = create_text_image_2(
372
+ mid_text, vw, MID_H,
373
+ font_size=64, align="center",
374
+ bg_color=(0, 0, 0), text_color=(255, 255, 255)
375
+ )
376
+ mid_caption_clip = ImageClip(np.array(mid_img)) \
377
+ .set_duration(clip.duration) \
378
+ .set_start(0) \
379
+ .set_position((0, CAPTION_H + LAUGH_H))
380
+
381
+ # Main video
382
+ main_video_y = CAPTION_H + LAUGH_H + MID_H
383
+ main_video = clip.resize(height=MAIN_H) \
384
+ .set_start(0) \
385
+ .set_position((0, main_video_y))
386
+
387
+ # Light filter over main video to reduce copyright detection
388
+ overlay = ColorClip(size=(vw, MAIN_H), color=(255, 255, 255), duration=clip.duration) \
389
+ .set_opacity(0.06) \
390
+ .set_position((0, main_video_y))
391
+
392
+ # Watermark: center-right inside main video
393
+ watermark_text = TextClip(
394
+ "@FullTossComedy4U",
395
+ fontsize=48,
396
+ font="Roboto-Bold", # Use a modern-looking font if available
397
+ color="white",
398
+ stroke_color="black",
399
+ stroke_width=2,
400
+ ).set_duration(clip.duration)
401
+
402
+ watermark_position = (
403
+ vw - watermark_text.w - 40,
404
+ main_video_y + (MAIN_H // 2) - (watermark_text.h // 2)
405
+ )
406
+
407
+ watermark = watermark_text.set_position(watermark_position)
408
+
409
+ # Final composition
410
+ final = CompositeVideoClip([
411
+ caption_clip,
412
+ laugh_start, freeze_frame, laugh_end,
413
+ mid_caption_clip,
414
+ main_video,
415
+ overlay,
416
+ watermark,
417
+ ], size=(vw, vh))
418
+
419
+ # Export
420
+ os.makedirs("edited", exist_ok=True)
421
+ out_path = f"edited/{uuid.uuid4().hex}.mp4"
422
+ final.write_videofile(
423
+ out_path,
424
+ codec="libx264",
425
+ audio_codec="aac",
426
+ preset="veryslow",
427
+ logger=None,
428
+ bitrate="15000k",
429
+ threads=4,
430
+ fps=clip.fps
431
+ )
432
+
433
+ clip.close(), laugh_raw.close(), final.close()
434
+ return out_path
435
+
436
+
437
+ # ───────────────────────────── LOGGING
438
+ logging.basicConfig(
439
+ level=logging.INFO,
440
+ format="%(asctime)s - %(levelname)s - %(message)s",
441
+ handlers=[
442
+ logging.FileHandler("app.log"),
443
+ logging.StreamHandler()
444
+ ]
445
+ )
446
+ logger = logging.getLogger(__name__)
447
+
448
+ # ───────────────────────────── CONSTANTS & GLOBALS
449
+ CAPTIONS = [
450
+ "Wait for it 😜", "Watch till end πŸ˜‚", "Try not to laugh 🀣",
451
+ "Don't skip this πŸ”₯", "You won't expect this! πŸ˜€", "Keep watching πŸ˜†",
452
+ "Stay till end! πŸ’₯", "Funniest one yet"
453
+ ]
454
+ BLOCKLIST = [
455
+ "nsfw", "18+", "xxx", "sexy", "adult", "porn", "onlyfans", "escort",
456
+ "betting", "gambling", "iplwin", "1xbet", "winzo", "my11circle", "dream11",
457
+ "rummy", "teenpatti", "fantasy", "casino", "promotion"
458
+ ]
459
+
460
+ UPLOAD_TIMES: List[datetime] = []
461
+ NEXT_RESET: datetime | None = None
462
+ first_run = True
463
+
464
+ # ───────────────────────────── DATABASE
465
+ client = MongoClient(os.getenv("MONGO_URI"))
466
+ db1 = client.shortttt # meta for YouTube uploads
467
+ meta = db1.meta
468
+
469
+ botdb = client.teleg4am_reelssss
470
+ a_raw = botdb.raw_links # {link:str, used:bool}
471
+ a_reacted = botdb.reacted_links
472
+
473
+ # ───────────────────────────── FLASK UI
474
+ from flask import Flask
475
+
476
+ app = Flask(__name__)
477
+
478
+ @app.route("/")
479
+ def home():
480
+ return "βœ… Code is running!"
481
+
482
+
483
+ # ───── Function 1: pick random link ──────────────────────────────────
484
+ def get_random_link() -> Tuple[Optional[str], Optional[str]]:
485
+ raw_left = list(a_raw.find({"used": False}))
486
+ reacted_left = list(a_reacted.find({"used": False}))
487
+ if not raw_left and not reacted_left:
488
+ return None, None
489
+
490
+ choice_pool = "raw" if random.random() < 0.7 else "reacted"
491
+ if choice_pool == "raw" and not raw_left:
492
+ choice_pool = "reacted"
493
+ if choice_pool == "reacted" and not reacted_left:
494
+ choice_pool = "raw"
495
+
496
+ col, pool_list = (a_raw, raw_left) if choice_pool == "raw" else (a_reacted, reacted_left)
497
+ doc = random.choice(pool_list)
498
+ col.update_one({"_id": doc["_id"]}, {"$set": {"used": True}})
499
+ return doc["link"], choice_pool
500
+ import os
501
+ import re
502
+ import uuid
503
+ import asyncio
504
+ import pathlib
505
+ import logging
506
+ from typing import Optional, Tuple
507
+
508
+ from telethon import TelegramClient
509
+ from telethon.sessions import StringSession
510
+ from telethon.tl.types import DocumentAttributeVideo
511
+ from moviepy.editor import VideoFileClip
512
+
513
+ API_ID = int(os.getenv("TG_API_ID", "3704772"))
514
+ API_HASH = os.getenv("TG_API_HASH", "b8e50a035abb851c0dd424e14cac4c06")
515
+ SESSION_STR = os.getenv("SESSION")
516
+ TARGET_BOT = "SaveMedia_bot"
517
+
518
+ logger = logging.getLogger(__name__)
519
+
520
+ def tg_duration_seconds(message) -> Optional[int]:
521
+ if not message or not message.media or not message.media.document:
522
+ return None
523
+ for attr in message.media.document.attributes:
524
+ if isinstance(attr, DocumentAttributeVideo):
525
+ return attr.duration
526
+ return None
527
+ import asyncio
528
+ import logging
529
+ import shutil
530
+
531
+ async def download_url_mp4(url: str, filename: str, timeout: int = 30) -> bool:
532
+ # 1. Ensure wget exists
533
+ if not shutil.which("wget"):
534
+ logger.error("❌ wget is not installed or not in PATH.")
535
+ return False
536
+
537
+ # 2. Build wget command
538
+ wget_cmd = [
539
+ "wget",
540
+ "--quiet", # minimal output # still show a progress bar
541
+ f"--timeout={timeout}", # seconds
542
+ "--header=User-Agent: Mozilla/5.0 (Linux; Android 10)",
543
+ "--header=Referer: https://www.instagram.com/",
544
+ "-O", filename, # output path
545
+ url
546
+ ]
547
+
548
+ try:
549
+ # 3. Launch wget as an async subprocess
550
+ proc = await asyncio.create_subprocess_exec(
551
+ *wget_cmd,
552
+ stdout=asyncio.subprocess.PIPE,
553
+ stderr=asyncio.subprocess.PIPE,
554
+ )
555
+
556
+ # 4. Wait for it to finish
557
+ stdout, stderr = await proc.communicate()
558
+
559
+ if proc.returncode == 0:
560
+ logger.info(f"πŸ“₯ Downloaded MP4 β†’ {filename}")
561
+ return True
562
+ else:
563
+ logger.error(
564
+ f"❌ wget exited with {proc.returncode}\nSTDERR: {stderr.decode().strip()}"
565
+ )
566
+ return False
567
+
568
+ except Exception as e:
569
+ logger.error(f"❌ wget launch failed: {e}")
570
+ return False
571
+
572
+
573
+ async def send_to_bot_and_get_video(link: str) -> Tuple[Optional[str], Optional[int]]:
574
+ async with TelegramClient(StringSession(SESSION_STR), API_ID, API_HASH) as client:
575
+ bot = await client.get_entity(TARGET_BOT)
576
+
577
+ async def attempt(send_link: str, depth=0) -> Tuple[Optional[str], Optional[int]]:
578
+ if depth > 1:
579
+ logger.warning("πŸ” Retry limit reached.")
580
+ return None, None
581
+
582
+ async with client.conversation(bot, timeout=30) as conv:
583
+ await conv.send_message(send_link)
584
+ logger.info(f"πŸ“€ Sent to {TARGET_BOT}: {send_link}")
585
+
586
+ try:
587
+ reply = await conv.get_response()
588
+ except asyncio.TimeoutError:
589
+ logger.warning("⏰ No reply from bot in 30 seconds.")
590
+ return None, None
591
+
592
+ msg = reply
593
+ text = msg.message or ""
594
+ logger.info(f"πŸ“¬ Bot replied: {text[:80]}")
595
+
596
+ # βœ… Case 1: Telegram video file
597
+ duration = tg_duration_seconds(msg)
598
+ if duration:
599
+ if 20 <= duration <= 180:
600
+ pathlib.Path("reels").mkdir(exist_ok=True)
601
+ file_path = await msg.download_media(file="reels/")
602
+ logger.info(f"βœ… Downloaded video β†’ {file_path}")
603
+ return file_path, duration
604
+ logger.info(f"⏩ Skipped due to invalid duration = {duration}s")
605
+ return None, None
606
+
607
+ # βœ… Case 2: CDN link in full text (even if surrounded by message)
608
+ urls = re.findall(r"https://[^\s]+", text)
609
+ if urls:
610
+ cdn_url = urls[0].strip()
611
+ pathlib.Path("reels").mkdir(exist_ok=True)
612
+ fname = f"reels/{uuid.uuid4().hex}.mp4"
613
+ success = await download_url_mp4(cdn_url, fname)
614
+ if not success:
615
+ return None, None
616
+
617
+ # Try to get duration using MoviePy
618
+ try:
619
+ clip = VideoFileClip(fname)
620
+ duration = int(clip.duration)
621
+ clip.close()
622
+ if 20 <= duration <= 180:
623
+ return fname, duration
624
+ else:
625
+ logger.info(f"⏩ CDN duration = {duration}s β†’ Skipped")
626
+ os.remove(fname)
627
+ return None, None
628
+ except Exception as e:
629
+ logger.warning(f"🎞️ Duration read failed: {e}")
630
+ return None, None
631
+
632
+ # ❌ Case 3: Ad or error
633
+ if "Request failed" in text:
634
+ logger.warning("πŸ” Bot said request failed, retrying once…")
635
+ return await attempt(send_link, depth + 1)
636
+
637
+ if "http" in text and (msg.photo or text.count(" ") > 0):
638
+ logger.info("❌ Detected ad/promo. Ignored.")
639
+ return None, None
640
+
641
+ logger.info("❌ No usable video received from bot.")
642
+ return None, None
643
+
644
+ return await attempt(link)
645
+ # ────────── Loop until we get a valid reel ────────
646
+ async def fetch_valid_reel() -> Tuple[Optional[str], Optional[str]]:
647
+ for _ in range(10):
648
+ link, pool = get_random_link()
649
+ if not link:
650
+ return None, None
651
+ logger.info(f"Trying {pool} link: {link}")
652
+ video_path, duration = await send_to_bot_and_get_video(link)
653
+ if video_path:
654
+ return video_path, pool
655
+ await asyncio.sleep(15)
656
+ return None, None
657
+
658
+
659
+ # ───────────────────────────── YOUTUBE UPLOAD & META
660
+ # (upload_to_youtube, save_to_db) β€” unchanged from original
661
+
662
+ def upload_to_youtube(video_path, title, desc):
663
+ creds = Credentials(
664
+ token=None,
665
+ refresh_token=os.getenv("YT_REFRESH_TOKEN", ),
666
+ token_uri="https://oauth2.googleapis.com/token",
667
+ client_id=os.getenv("YT_CLIENT_ID"),
668
+ client_secret=os.getenv("YT_CLIENT_SECRET"),
669
+ scopes=["https://www.googleapis.com/auth/youtube.upload"]
670
+ )
671
+ creds.refresh(Request())
672
+ youtube = build("youtube", "v3", credentials=creds)
673
+ request = youtube.videos().insert(
674
+ part="snippet,status",
675
+ body={
676
+ "snippet": {
677
+ "title": title,
678
+ "description": desc,
679
+ "tags": ["funny", "memes", "comedy", "shorts"],
680
+ "categoryId": "23"
681
+ },
682
+ "status": {
683
+ "privacyStatus": "public",
684
+ "madeForKids": False
685
+ }
686
+ },
687
+ media_body=MediaFileUpload(video_path)
688
+ )
689
+ res = request.execute()
690
+ logger.info(f"Uploaded: https://youtube.com/watch?v={res['id']}")
691
+ return f"https://youtube.com/watch?v={res['id']}"
692
+
693
+ def get_next_part():
694
+ last = meta.find_one(sort=[("part", -1)])
695
+ return 1 if not last else last["part"] + 1
696
+
697
+ def generate_description(title):
698
+ return f"Watch this hilarious clip: {title}"
699
+
700
+ def save_to_db(part, title, desc, link):
701
+ meta.insert_one({"part": part, "title": title, "description": desc, "link": link, "uploaded": time.time()})
702
+
703
+ # ───────────────────────────── MAIN AUTO LOOP
704
+
705
+ def auto_loop():
706
+ asyncio.set_event_loop(asyncio.new_event_loop())
707
+ global UPLOAD_TIMES, NEXT_RESET
708
+
709
+ ist = timezone(timedelta(hours=5, minutes=30))
710
+ daily_upload_count = random.randint(3, 5)
711
+ uploads_done_today = 0
712
+ NEXT_RESET = datetime.now(ist).replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1)
713
+
714
+ logger.info(f"[πŸ“…] Today's upload target: {daily_upload_count} reels.")
715
+
716
+ def wait_until(hour: int, minute: int = 0):
717
+ now = datetime.now(ist)
718
+ target = now.replace(hour=hour, minute=minute, second=0, microsecond=0)
719
+ if target < now:
720
+ return
721
+ logger.info(f"[πŸ•—] Waiting until {target.strftime('%H:%M')} IST...")
722
+ while datetime.now(ist) < target:
723
+ time.sleep(10)
724
+
725
+ wait_until(8)
726
+
727
+ while True:
728
+ try:
729
+ now = datetime.now(ist)
730
+ if now >= NEXT_RESET:
731
+ UPLOAD_TIMES.clear()
732
+ NEXT_RESET = now.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1)
733
+ daily_upload_count = random.randint(3, 5)
734
+ uploads_done_today = 0
735
+ logger.info(f"[πŸ”] Reset for new day. New target: {daily_upload_count} uploads.")
736
+
737
+ if uploads_done_today >= daily_upload_count:
738
+ logger.info("[βœ…] Daily upload target reached.")
739
+ time.sleep(60)
740
+ continue
741
+
742
+ if now.hour < 8 or now.hour >= 23:
743
+ time.sleep(60)
744
+ continue
745
+
746
+ if not UPLOAD_TIMES or (now - UPLOAD_TIMES[-1]).total_seconds() >= random.randint(7200, 14400):
747
+ video_path, reel_type = asyncio.run(fetch_valid_reel())
748
+ if not video_path:
749
+ logger.warning("[⚠️] No valid reel found. Retrying...")
750
+ time.sleep(60)
751
+ continue
752
+ edited = edit_video_raw(video_path) if reel_type == "raw" else edit_video(video_path)
753
+ part = get_next_part()
754
+ title = f"Try not to laugh || #{part} #funny #memes #comedy #shorts"
755
+ desc = generate_description(title)
756
+ link = upload_to_youtube(edited, title, desc)
757
+ save_to_db(part, title, desc, link)
758
+
759
+ logger.info(f"[πŸ“€] Uploaded #{part}: {link}")
760
+ UPLOAD_TIMES.append(now)
761
+ uploads_done_today += 1
762
+
763
+ os.remove(video_path)
764
+ os.remove(edited)
765
+
766
+ if uploads_done_today < daily_upload_count:
767
+ gap_seconds = random.randint(7200, 14400)
768
+ next_time = datetime.now(ist) + timedelta(seconds=gap_seconds)
769
+ if next_time.hour >= 20:
770
+ logger.info("[πŸŒ™] Next upload would exceed 8PM. Skipping.")
771
+ continue
772
+ logger.info(f"[⏳] Waiting ~{gap_seconds // 60} minutes before next upload.")
773
+ time.sleep(gap_seconds)
774
+ else:
775
+ time.sleep(60)
776
+
777
+ except Exception as e:
778
+ logger.error(f"Loop error: {e}")
779
+ time.sleep(60)
780
+
781
+ if __name__ == "__main__":
782
+ import asyncio
783
+ from threading import Thread
784
+
785
+ Thread(target=lambda: app.run(host="0.0.0.0", port=7860, debug=False, use_reloader=False)).start()
786
+
787
+ # βœ… Run uploader loop in background
788
+ Thread(target=auto_loop, daemon=True).start()
laugh/.keep ADDED
File without changes
laugh/Create a Python virtual environment in Windows with VS Code.webm ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7b2107089b67b775dedd2bfaaf922bd28ef0c094f3eabb6f8acbb3a82aa65ee5
3
+ size 2221195
laugh/laugh_one.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d212a88e44691712ef9b50e70d665443fb780a1158b2440a556e27e426133cb8
3
+ size 657611
laugh/laugh_two.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8824e08d1a2df2e2b25c2cd25d458bca203ee389cc89545119aac0af833812d2
3
+ size 643667
packages.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ ffmpeg
2
+ wget
3
+ imagemagick
requirements.txt ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ─── Core runtime ─────────────────────────────────────
2
+ flask>=3.0.0
3
+ requests>=2.32.0
4
+ pymongo>=4.7.0
5
+ numpy>=1.26.0
6
+ telethon
7
+ # ─── Google / YouTube API stack ───────────────────────
8
+ google-api-python-client>=2.126.0
9
+ google-auth>=2.29.0
10
+ google-auth-oauthlib>=1.2.0
11
+ google-auth-httplib2>=0.2.0
12
+ httplib2>=0.22.0 # pulled in, but list for clarity
13
+
14
+ # ─── Instagram + Selenium fetcher ─────────────────────
15
+ python-telegram-bot[job-queue]
16
+ httpx
17
+ yt-dlp
18
+ # ─── Video editing stack (moviepy + codecs) ───────────
19
+ moviepy==1.0.3
20
+ imageio[ffmpeg]>=2.34.0 # moviepy’s video I/O
21
+ moviepy==1.0.3
22
+ decorator==4.4.2 # moviepy dep
23
+ tqdm>=4.66.0 # moviepy progress bars
24
+ proglog>=0.1.10 # moviepy logging helper
25
+ pillow==9.4.0 # locked for emoji compositing
26
+ nest_asyncio
27
+ # ─── Extra utils ──────────────────────────────────────
28
+ emoji>=2.11.0
29
+
30
+ # (Optional) python-dotenv if you load .env vars elsewhere
31
+ # python-dotenv>=1.0.1