soxogvv commited on
Commit
4802ce5
Β·
verified Β·
1 Parent(s): 7808959

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: Bba
3
- emoji: πŸš€
4
- colorFrom: gray
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,830 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ from PIL import Image, ImageDraw, ImageFont
171
+
172
+ def create_text_image_with_shadow(
173
+ text: str,
174
+ width: int,
175
+ height: int,
176
+ *,
177
+ font_size: int = 60,
178
+ align: str = "center",
179
+ bg_color=(0, 0, 0, 0), # transparent
180
+ text_color=(255, 255, 255),
181
+ shadow_color=(0, 0, 0),
182
+ font_name="DejaVuSans-Bold.ttf" # Only font name, not full path
183
+ ):
184
+ # Create image
185
+ img = Image.new("RGBA", (width, height), bg_color)
186
+ draw = ImageDraw.Draw(img)
187
+
188
+ # Load font by name only (must be installed system-wide or available in fallback path)
189
+ try:
190
+ font = ImageFont.truetype(font_name, font_size)
191
+ except OSError:
192
+ raise ValueError(f"Font '{font_name}' not found. Ensure it’s installed system-wide or available.")
193
+
194
+ # Get size
195
+ text_bbox = draw.textbbox((0, 0), text, font=font)
196
+ text_width = text_bbox[2] - text_bbox[0]
197
+ text_height = text_bbox[3] - text_bbox[1]
198
+
199
+ # Alignment
200
+ if align == "left":
201
+ x = 0
202
+ elif align == "right":
203
+ x = width - text_width
204
+ else: # center
205
+ x = (width - text_width) // 2
206
+ y = (height - text_height) // 2
207
+
208
+ # Draw shadow (1px right)
209
+ draw.text((x + 1, y), text, font=font, fill=shadow_color)
210
+ # Draw actual text
211
+ draw.text((x, y), text, font=font, fill=text_color)
212
+
213
+ return img
214
+
215
+
216
+ def create_text_image(text, width, height):
217
+ img = Image.new("RGB", (width, height), color=(255, 255, 255))
218
+ draw = ImageDraw.Draw(img)
219
+
220
+ # Load font
221
+ try:
222
+ font = ImageFont.truetype("DejaVuSans-Bold.ttf", size=60)
223
+ except:
224
+ font = ImageFont.load_default()
225
+
226
+ # Extract emoji and clean text
227
+ emojis = emoji.emoji_list(text)
228
+ pure_text = emoji.replace_emoji(text, replace='')
229
+
230
+ # Adjust font size to fit
231
+ max_font_size = 70
232
+ while True:
233
+ font = ImageFont.truetype("DejaVuSans-Bold.ttf", size=max_font_size)
234
+ text_width, text_height = draw.textsize(pure_text, font=font)
235
+ total_width = text_width + (len(emojis) * 60) + 20
236
+ if total_width <= width - 40 or max_font_size <= 30:
237
+ break
238
+ max_font_size -= 2
239
+
240
+ # Starting X & Y for centered layout
241
+ start_x = (width - total_width) // 2
242
+ y = (height - text_height) // 2
243
+
244
+ # Draw text first
245
+ draw.text((start_x, y), pure_text, font=font, fill=(0, 0, 0))
246
+
247
+ # Then draw emojis to the right of the text
248
+ x = start_x + text_width + 10
249
+ for e in emojis:
250
+ hexcode = '-'.join(f"{ord(c):x}" for c in e['emoji'])
251
+ emoji_path = f"emoji_pngs/{hexcode}.png"
252
+ if not os.path.exists(emoji_path):
253
+ download_emoji_png(e['emoji'])
254
+
255
+ if os.path.exists(emoji_path):
256
+ emoji_img = Image.open(emoji_path).convert("RGBA")
257
+ emoji_img = emoji_img.resize((60, 60))
258
+ img.paste(emoji_img, (x, y), emoji_img)
259
+ x += 60 + 4
260
+
261
+ return img
262
+
263
+ from PIL import Image, ImageDraw, ImageFont
264
+ import numpy as np
265
+ from moviepy.editor import ImageClip
266
+
267
+ def generate_watermark_img(text, width, height=50):
268
+ img = Image.new("RGBA", (width, height), (0, 0, 0, 0))
269
+ draw = ImageDraw.Draw(img)
270
+
271
+ try:
272
+ font = ImageFont.truetype("DejaVuSans-Bold.ttf", size=35)
273
+ except:
274
+ font = ImageFont.load_default()
275
+
276
+ text_width, text_height = draw.textsize(text, font=font)
277
+ draw.text((5, height - text_height - 2), text, fill="white", font=font, stroke_width=1, stroke_fill="black")
278
+
279
+ return img
280
+
281
+ def download_emoji_png(emoji_char):
282
+ hexcode = '-'.join(f"{ord(c):x}" for c in emoji_char)
283
+ url = f"https://github.com/twitter/twemoji/raw/master/assets/72x72/{hexcode}.png"
284
+ os.makedirs("emoji_pngs", exist_ok=True)
285
+ path = f"emoji_pngs/{hexcode}.png"
286
+ try:
287
+ r = requests.get(url)
288
+ if r.status_code == 200:
289
+ with open(path, "wb") as f:
290
+ f.write(r.content)
291
+ print(f"βœ… Downloaded emoji: {emoji_char} β†’ {path}")
292
+ else:
293
+ print(f"❌ Failed to download emoji: {emoji_char}")
294
+ except Exception as e:
295
+ print(f"⚠️ Error downloading emoji {emoji_char}: {e}")
296
+
297
+
298
+ def edit_video(video_path):
299
+ clip = VideoFileClip(video_path)
300
+
301
+ video_width = clip.w
302
+ video_height = clip.h
303
+ bar_height = 120
304
+ total_height = video_height + bar_height
305
+
306
+ # === 1. Background Canvas
307
+ final_bg = ColorClip(size=(video_width, total_height), color=(255, 255, 255), duration=clip.duration)
308
+
309
+ # === 2. Caption Bar (Top)
310
+ caption = random.choice(CAPTIONS)
311
+ caption_img = create_text_image(caption, video_width, bar_height)
312
+ caption_clip = ImageClip(np.array(caption_img)).set_duration(clip.duration).set_position((0, 0))
313
+
314
+ # === 3. Eye Protection Overlay (6% White Transparent)
315
+ eye_protection = ColorClip(size=(video_width, video_height), color=(255, 255, 255), duration=clip.duration)
316
+ eye_protection = eye_protection.set_opacity(0.1).set_position((0, bar_height))
317
+
318
+ # === 4. Watermark (Bottom-left using Pillow + ImageClip)
319
+ watermark_img = generate_watermark_img("@fulltosscomedy4u", video_width, height=50)
320
+ watermark_clip = ImageClip(np.array(watermark_img)).set_duration(clip.duration).set_position(("left", bar_height + video_height - 50))
321
+
322
+ # === 5. Position original video below top bar
323
+ video_clip = clip.set_position((0, bar_height))
324
+
325
+ # === 6. Combine everything
326
+ final = CompositeVideoClip(
327
+ [final_bg, caption_clip, video_clip, eye_protection, watermark_clip],
328
+ size=(video_width, total_height)
329
+ )
330
+
331
+ os.makedirs("edited", exist_ok=True)
332
+ output_path = f"edited/{uuid.uuid4().hex}.mp4"
333
+ final.write_videofile(
334
+ output_path,
335
+ codec="libx264",
336
+ audio_codec="aac",
337
+ preset="veryslow",
338
+ bitrate="12000k",
339
+ verbose=False,
340
+ logger=None
341
+ )
342
+ return output_path
343
+
344
+ def edit_video_raw(video_path: str) -> str:
345
+ import os, uuid, random
346
+ import numpy as np
347
+ from moviepy.editor import (
348
+ VideoFileClip, ImageClip, ColorClip, TextClip,
349
+ CompositeVideoClip
350
+ )
351
+
352
+ # Load main video
353
+ clip = VideoFileClip(video_path).resize(width=1220)
354
+ vw, vh = 1220, 2460
355
+
356
+ # Pixel-perfect heights
357
+ CAPTION_H = 230
358
+ LAUGH_H = 508
359
+ MID_H = 210
360
+ MAIN_H = 1512
361
+
362
+ if clip.duration < 6:
363
+ raise ValueError("Main video must be at least 6 seconds.")
364
+ if clip.duration > 180:
365
+ clip = clip.subclip(0, 180)
366
+
367
+ # Top caption
368
+ caption = random.choice(CAPTIONS)
369
+ caption_img = create_text_image_2(
370
+ caption, vw, CAPTION_H,
371
+ font_size=72, align="center",
372
+ bg_color=(0, 0, 0), text_color=(255, 255, 255)
373
+ )
374
+ caption_clip = ImageClip(np.array(caption_img)) \
375
+ .set_duration(clip.duration) \
376
+ .set_position((0, 0))
377
+
378
+ # Laugh meme (2s + freeze + 2s)
379
+ laugh_files = ["laugh/laugh_one.mp4", "laugh/laugh_two.mp4"]
380
+ laugh_path = random.choice(laugh_files)
381
+ if not os.path.exists(laugh_path):
382
+ raise FileNotFoundError(f"❌ Laugh meme not found: {laugh_path}")
383
+
384
+ laugh_raw = VideoFileClip(laugh_path).resize(width=1220)
385
+ if laugh_raw.duration < 4:
386
+ raise ValueError("Laugh meme must be at least 4 seconds.")
387
+
388
+ laugh_start = (
389
+ laugh_raw.subclip(0, 2)
390
+ .resize(height=LAUGH_H)
391
+ .set_start(0)
392
+ .set_position((0, CAPTION_H))
393
+ )
394
+
395
+ freeze_frame = (
396
+ laugh_start.to_ImageClip()
397
+ .set_start(2)
398
+ .set_duration(max(0, clip.duration - 4))
399
+ .set_position((0, CAPTION_H))
400
+ )
401
+
402
+ laugh_end = (
403
+ laugh_raw.subclip(laugh_raw.duration - 2)
404
+ .resize(height=LAUGH_H)
405
+ .set_start(clip.duration - 2)
406
+ .set_position((0, CAPTION_H))
407
+ )
408
+
409
+ # Mid caption
410
+ mid_text = random.choice([
411
+ "Pura 1 din laga tab ye reel mili 🀣",
412
+ "Ye miss mat kr dena 😜",
413
+ "Kha thi ye reel ab tak πŸ€¨πŸ€—",
414
+ "Wait, ye dekh kr hi janna πŸ’₯πŸ’₯",
415
+ ])
416
+ mid_img = create_text_image_2(
417
+ mid_text, vw, MID_H,
418
+ font_size=64, align="center",
419
+ bg_color=(0, 0, 0), text_color=(255, 255, 255)
420
+ )
421
+ mid_caption_clip = ImageClip(np.array(mid_img)) \
422
+ .set_duration(clip.duration) \
423
+ .set_start(0) \
424
+ .set_position((0, CAPTION_H + LAUGH_H))
425
+
426
+ # Main video
427
+ main_video_y = CAPTION_H + LAUGH_H + MID_H
428
+ main_video = clip.resize(height=MAIN_H) \
429
+ .set_start(0) \
430
+ .set_position((0, main_video_y))
431
+
432
+ # Light filter over main video to reduce copyright detection
433
+ overlay = ColorClip(size=(vw, MAIN_H), color=(255, 255, 255), duration=clip.duration) \
434
+ .set_opacity(0.06) \
435
+ .set_position((0, main_video_y))
436
+
437
+ # Watermark: center-right inside main video
438
+ # Replace watermark_img creation
439
+ watermark_img = create_text_image_with_shadow(
440
+ "@FullTossComedy4U",
441
+ width=vw, height=100,
442
+ font_size=48,
443
+ align="right",
444
+ bg_color=(0, 0, 0, 0),
445
+ text_color=(255, 255, 255),
446
+ shadow_color=(0, 0, 0)
447
+ )
448
+
449
+ # Final composition
450
+ final = CompositeVideoClip([
451
+ caption_clip,
452
+ laugh_start, freeze_frame, laugh_end,
453
+ mid_caption_clip,
454
+ main_video,
455
+ overlay,
456
+ watermark,
457
+ ], size=(vw, vh))
458
+
459
+ # Export
460
+ os.makedirs("edited", exist_ok=True)
461
+ out_path = f"edited/{uuid.uuid4().hex}.mp4"
462
+ final.write_videofile(
463
+ out_path,
464
+ codec="libx264",
465
+ audio_codec="aac",
466
+ preset="veryslow",
467
+ logger=None,
468
+ bitrate="15000k",
469
+ threads=4,
470
+ fps=clip.fps
471
+ )
472
+
473
+ clip.close(), laugh_raw.close(), final.close()
474
+ return out_path
475
+
476
+
477
+ # ───────────────────────────── LOGGING
478
+ logging.basicConfig(
479
+ level=logging.INFO,
480
+ format="%(asctime)s - %(levelname)s - %(message)s",
481
+ handlers=[
482
+ logging.FileHandler("app.log"),
483
+ logging.StreamHandler()
484
+ ]
485
+ )
486
+ logger = logging.getLogger(__name__)
487
+
488
+ # ───────────────────────────── CONSTANTS & GLOBALS
489
+ CAPTIONS = [
490
+ "Wait for it 😜", "Watch till end πŸ˜‚", "Try not to laugh 🀣",
491
+ "Don't skip this πŸ”₯", "You won't expect this! πŸ˜€", "Keep watching πŸ˜†",
492
+ "Stay till end! πŸ’₯", "Funniest one yet"
493
+ ]
494
+ BLOCKLIST = [
495
+ "nsfw", "18+", "xxx", "sexy", "adult", "porn", "onlyfans", "escort",
496
+ "betting", "gambling", "iplwin", "1xbet", "winzo", "my11circle", "dream11",
497
+ "rummy", "teenpatti", "fantasy", "casino", "promotion"
498
+ ]
499
+
500
+ UPLOAD_TIMES: List[datetime] = []
501
+ NEXT_RESET: datetime | None = None
502
+ first_run = True
503
+
504
+ # ───────────────────────────── DATABASE
505
+ client = MongoClient(os.getenv("MONGO_URI"))
506
+ db1 = client.shortttt # meta for YouTube uploads
507
+ meta = db1.meta
508
+
509
+ botdb = client.teleg4am_reelssss
510
+ a_raw = botdb.raw_links # {link:str, used:bool}
511
+ a_reacted = botdb.reacted_links
512
+
513
+ # ───────────────────────────── FLASK UI
514
+ from flask import Flask
515
+
516
+ app = Flask(__name__)
517
+
518
+ @app.route("/")
519
+ def home():
520
+ return "βœ… Code is running!"
521
+
522
+
523
+ # ───── Function 1: pick random link ──────────────────────────────────
524
+ def get_random_link() -> Tuple[Optional[str], Optional[str]]:
525
+ raw_left = list(a_raw.find({"used": False}))
526
+ reacted_left = list(a_reacted.find({"used": False}))
527
+ if not raw_left and not reacted_left:
528
+ return None, None
529
+
530
+ choice_pool = "raw" if random.random() < 0.7 else "reacted"
531
+ if choice_pool == "raw" and not raw_left:
532
+ choice_pool = "reacted"
533
+ if choice_pool == "reacted" and not reacted_left:
534
+ choice_pool = "raw"
535
+
536
+ col, pool_list = (a_raw, raw_left) if choice_pool == "raw" else (a_reacted, reacted_left)
537
+ doc = random.choice(pool_list)
538
+ col.update_one({"_id": doc["_id"]}, {"$set": {"used": True}})
539
+ return doc["link"], choice_pool
540
+ import os
541
+ import re
542
+ import uuid
543
+ import asyncio
544
+ import pathlib
545
+ import logging
546
+ from typing import Optional, Tuple
547
+
548
+ from telethon import TelegramClient
549
+ from telethon.sessions import StringSession
550
+ from telethon.tl.types import DocumentAttributeVideo
551
+ from moviepy.editor import VideoFileClip
552
+
553
+ API_ID = int(os.getenv("TG_API_ID", "3704772"))
554
+ API_HASH = os.getenv("TG_API_HASH", "b8e50a035abb851c0dd424e14cac4c06")
555
+ SESSION_STR = os.getenv("SESSION")
556
+ TARGET_BOT = "instasavegrambot"
557
+
558
+ logger = logging.getLogger(__name__)
559
+
560
+ def tg_duration_seconds(message) -> Optional[int]:
561
+ if not message or not message.media or not message.media.document:
562
+ return None
563
+ for attr in message.media.document.attributes:
564
+ if isinstance(attr, DocumentAttributeVideo):
565
+ return attr.duration
566
+ return None
567
+ import asyncio
568
+ import logging
569
+ import shutil
570
+
571
+ async def download_url_mp4(url: str, filename: str, timeout: int = 30) -> bool:
572
+ # 1. Ensure wget exists
573
+ if not shutil.which("wget"):
574
+ logger.error("❌ wget is not installed or not in PATH.")
575
+ return False
576
+
577
+ # 2. Build wget command
578
+ wget_cmd = [
579
+ "wget",
580
+ "--quiet", # minimal output # still show a progress bar
581
+ f"--timeout={timeout}", # seconds
582
+ "--header=User-Agent: Mozilla/5.0 (Linux; Android 10)",
583
+ "--header=Referer: https://www.instagram.com/",
584
+ "-O", filename, # output path
585
+ url
586
+ ]
587
+
588
+ try:
589
+ # 3. Launch wget as an async subprocess
590
+ proc = await asyncio.create_subprocess_exec(
591
+ *wget_cmd,
592
+ stdout=asyncio.subprocess.PIPE,
593
+ stderr=asyncio.subprocess.PIPE,
594
+ )
595
+
596
+ # 4. Wait for it to finish
597
+ stdout, stderr = await proc.communicate()
598
+
599
+ if proc.returncode == 0:
600
+ logger.info(f"πŸ“₯ Downloaded MP4 β†’ {filename}")
601
+ return True
602
+ else:
603
+ logger.error(
604
+ f"❌ wget exited with {proc.returncode}\nSTDERR: {stderr.decode().strip()}"
605
+ )
606
+ return False
607
+
608
+ except Exception as e:
609
+ logger.error(f"❌ wget launch failed: {e}")
610
+ return False
611
+
612
+
613
+ async def send_to_bot_and_get_video(link: str) -> Tuple[Optional[str], Optional[int]]:
614
+ async with TelegramClient(StringSession(SESSION_STR), API_ID, API_HASH) as client:
615
+ bot = await client.get_entity(TARGET_BOT)
616
+
617
+ async def attempt(send_link: str, depth=0) -> Tuple[Optional[str], Optional[int]]:
618
+ if depth > 2:
619
+ logger.warning("πŸ” Retry limit reached.")
620
+ return None, None
621
+
622
+ async with client.conversation(bot, timeout=30) as conv:
623
+ await conv.send_message(send_link)
624
+ logger.info(f"πŸ“€ Sent to {TARGET_BOT}: {send_link}")
625
+
626
+ try:
627
+ reply = await conv.get_response()
628
+ except asyncio.TimeoutError:
629
+ logger.warning("⏰ No reply from bot in 30 seconds.")
630
+ return None, None
631
+
632
+ msg = reply
633
+ text = msg.message or ""
634
+ logger.info(f"πŸ“¬ Bot replied: {text[:80]}")
635
+
636
+ # βœ… Case 1: Telegram video file
637
+ duration = tg_duration_seconds(msg)
638
+ if duration:
639
+ if 20 <= duration <= 180:
640
+ pathlib.Path("reels").mkdir(exist_ok=True)
641
+ file_path = await msg.download_media(file="reels/")
642
+ logger.info(f"βœ… Downloaded video β†’ {file_path}")
643
+ return file_path, duration
644
+ logger.info(f"⏩ Skipped due to invalid duration = {duration}s")
645
+ return None, None
646
+
647
+ # βœ… Case 2: CDN link in full text (even if surrounded by message)
648
+ urls = re.findall(r"https://[^\s]+", text)
649
+ if urls:
650
+ cdn_url = urls[0].strip()
651
+ pathlib.Path("reels").mkdir(exist_ok=True)
652
+ fname = f"reels/{uuid.uuid4().hex}.mp4"
653
+ success = await download_url_mp4(cdn_url, fname)
654
+ if not success:
655
+ return None, None
656
+
657
+ # Try to get duration using MoviePy
658
+ try:
659
+ clip = VideoFileClip(fname)
660
+ duration = int(clip.duration)
661
+ clip.close()
662
+ if 20 <= duration <= 180:
663
+ return fname, duration
664
+ else:
665
+ logger.info(f"⏩ CDN duration = {duration}s β†’ Skipped")
666
+ os.remove(fname)
667
+ return None, None
668
+ except Exception as e:
669
+ logger.warning(f"🎞️ Duration read failed: {e}")
670
+ return None, None
671
+
672
+ # ❌ Case 3: Ad or error
673
+ if "Request failed" in text:
674
+ logger.warning("πŸ” Bot said request failed, retrying once…")
675
+ return await attempt(send_link, depth + 1)
676
+ if "We are experiencing high" in text:
677
+ return await asyncio.sleep(3600)
678
+
679
+ if "http" in text and (msg.photo or text.count(" ") > 0):
680
+ logger.info("❌ Detected ad/promo. Ignored.")
681
+ return None, None
682
+
683
+ logger.info("❌ No usable video received from bot.")
684
+ return None, None
685
+
686
+ return await attempt(link)
687
+ # ────────── Loop until we get a valid reel ────────
688
+ async def fetch_valid_reel() -> Tuple[Optional[str], Optional[str]]:
689
+ for _ in range(10):
690
+ link, pool = get_random_link()
691
+ if not link:
692
+ return None, None
693
+ logger.info(f"Trying {pool} link: {link}")
694
+ video_path, duration = await send_to_bot_and_get_video(link)
695
+ if video_path:
696
+ return video_path, pool
697
+ await asyncio.sleep(15)
698
+ return None, None
699
+
700
+
701
+ # ───────────────────────────── YOUTUBE UPLOAD & META
702
+ # (upload_to_youtube, save_to_db) β€” unchanged from original
703
+
704
+ def upload_to_youtube(video_path, title, desc):
705
+ creds = Credentials(
706
+ token=None,
707
+ refresh_token=os.getenv("YT_REFRESH_TOKEN", ),
708
+ token_uri="https://oauth2.googleapis.com/token",
709
+ client_id=os.getenv("YT_CLIENT_ID"),
710
+ client_secret=os.getenv("YT_CLIENT_SECRET"),
711
+ scopes=["https://www.googleapis.com/auth/youtube.upload"]
712
+ )
713
+ creds.refresh(Request())
714
+ youtube = build("youtube", "v3", credentials=creds)
715
+ request = youtube.videos().insert(
716
+ part="snippet,status",
717
+ body={
718
+ "snippet": {
719
+ "title": title,
720
+ "description": desc,
721
+ "tags": ["funny", "memes", "comedy", "shorts"],
722
+ "categoryId": "23"
723
+ },
724
+ "status": {
725
+ "privacyStatus": "public",
726
+ "madeForKids": False
727
+ }
728
+ },
729
+ media_body=MediaFileUpload(video_path)
730
+ )
731
+ res = request.execute()
732
+ logger.info(f"Uploaded: https://youtube.com/watch?v={res['id']}")
733
+ return f"https://youtube.com/watch?v={res['id']}"
734
+
735
+ def get_next_part():
736
+ last = meta.find_one(sort=[("part", -1)])
737
+ return 1 if not last else last["part"] + 1
738
+
739
+ def generate_description(title):
740
+ return f"Watch this hilarious clip: {title}"
741
+
742
+ def save_to_db(part, title, desc, link):
743
+ meta.insert_one({"part": part, "title": title, "description": desc, "link": link, "uploaded": time.time()})
744
+
745
+ # ───────────────────────────── MAIN AUTO LOOP
746
+
747
+ def auto_loop():
748
+ asyncio.set_event_loop(asyncio.new_event_loop())
749
+ global UPLOAD_TIMES, NEXT_RESET
750
+
751
+ ist = timezone(timedelta(hours=5, minutes=30))
752
+ daily_upload_count = random.randint(3, 5)
753
+ uploads_done_today = 0
754
+ NEXT_RESET = datetime.now(ist).replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1)
755
+
756
+ logger.info(f"[πŸ“…] Today's upload target: {daily_upload_count} reels.")
757
+
758
+ def wait_until(hour: int, minute: int = 0):
759
+ now = datetime.now(ist)
760
+ target = now.replace(hour=hour, minute=minute, second=0, microsecond=0)
761
+ if target < now:
762
+ return
763
+ logger.info(f"[πŸ•—] Waiting until {target.strftime('%H:%M')} IST...")
764
+ while datetime.now(ist) < target:
765
+ time.sleep(10)
766
+
767
+ wait_until(8)
768
+
769
+ while True:
770
+ try:
771
+ now = datetime.now(ist)
772
+ if now >= NEXT_RESET:
773
+ UPLOAD_TIMES.clear()
774
+ NEXT_RESET = now.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1)
775
+ daily_upload_count = random.randint(3, 5)
776
+ uploads_done_today = 0
777
+ logger.info(f"[πŸ”] Reset for new day. New target: {daily_upload_count} uploads.")
778
+
779
+ if uploads_done_today >= daily_upload_count:
780
+ logger.info("[βœ…] Daily upload target reached.")
781
+ time.sleep(60)
782
+ continue
783
+
784
+ if now.hour < 8 or now.hour >= 23:
785
+ time.sleep(60)
786
+ continue
787
+
788
+ if not UPLOAD_TIMES or (now - UPLOAD_TIMES[-1]).total_seconds() >= random.randint(7200, 14400):
789
+ video_path, reel_type = asyncio.run(fetch_valid_reel())
790
+ if not video_path:
791
+ logger.warning("[⚠️] No valid reel found. Retrying...")
792
+ time.sleep(60)
793
+ continue
794
+ edited = edit_video_raw(video_path) if reel_type == "raw" else edit_video(video_path)
795
+ part = get_next_part()
796
+ title = f"Try not to laugh || #{part} #funny #memes #comedy #shorts"
797
+ desc = generate_description(title)
798
+ link = upload_to_youtube(edited, title, desc)
799
+ save_to_db(part, title, desc, link)
800
+
801
+ logger.info(f"[πŸ“€] Uploaded #{part}: {link}")
802
+ UPLOAD_TIMES.append(now)
803
+ uploads_done_today += 1
804
+
805
+ os.remove(video_path)
806
+ os.remove(edited)
807
+
808
+ if uploads_done_today < daily_upload_count:
809
+ gap_seconds = random.randint(7200, 14400)
810
+ next_time = datetime.now(ist) + timedelta(seconds=gap_seconds)
811
+ if next_time.hour >= 20:
812
+ logger.info("[πŸŒ™] Next upload would exceed 8PM. Skipping.")
813
+ continue
814
+ logger.info(f"[⏳] Waiting ~{gap_seconds // 60} minutes before next upload.")
815
+ time.sleep(gap_seconds)
816
+ else:
817
+ time.sleep(60)
818
+
819
+ except Exception as e:
820
+ logger.error(f"Loop error: {e}")
821
+ time.sleep(60)
822
+
823
+ if __name__ == "__main__":
824
+ import asyncio
825
+ from threading import Thread
826
+
827
+ Thread(target=lambda: app.run(host="0.0.0.0", port=7860, debug=False, use_reloader=False)).start()
828
+
829
+ # βœ… Run uploader loop in background
830
+ 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,2 @@
 
 
 
1
+ ffmpeg
2
+ wget
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