Kanhshsh commited on
Commit
97e5c8a
Β·
verified Β·
1 Parent(s): 967e9a1

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +672 -0
app.py ADDED
@@ -0,0 +1,672 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import random
3
+ import uuid
4
+ import time
5
+ import json
6
+ import base64
7
+ import asyncio
8
+ import logging
9
+ import atexit
10
+ import numpy as np
11
+ import requests
12
+ from flask import Flask, render_template, request, redirect
13
+ from threading import Thread
14
+ from pymongo import MongoClient
15
+ from google.oauth2.credentials import Credentials
16
+ from google.auth.transport.requests import Request
17
+ from googleapiclient.discovery import build
18
+ from googleapiclient.http import MediaFileUpload
19
+ from moviepy.editor import VideoFileClip, ColorClip, CompositeVideoClip, ImageClip
20
+ from moviepy.video.fx import resize
21
+ from moviepy.editor import VideoFileClip, CompositeVideoClip, ColorClip, ImageClip, TextClip
22
+ from PIL import Image, ImageDraw, ImageFont
23
+ from datetime import datetime, timedelta, timezone
24
+ from moviepy.editor import *
25
+ import builtins
26
+ import logging
27
+ import re
28
+
29
+ import builtins
30
+ import re
31
+ import subprocess
32
+
33
+ # paste your full cookie JSON here
34
+ raw_cookies = [
35
+ {"name": "datr", "value": "ejpyaDZsc2mYbftSKZ3Sk2Lu"},
36
+ {"name": "ds_user_id", "value": "75543047114"},
37
+ {"name": "csrftoken", "value": "UCHoYMciHF3W2HynHCA3fBEZfJd1beB5"},
38
+ {"name": "ig_did", "value": "E6AD4B02-7FC5-4C36-9570-7AE89F1A85D3"},
39
+ {"name": "wd", "value": "468x935"},
40
+ {"name": "mid", "value": "aHI6egABAAEk7DQNY7Nj1qArGgnq"},
41
+ {"name": "sessionid", "value": "75543047114%3AmWa1OwxIcbgqMd%3A6%3AAYcaKZIghR6DJJsEZSZjsunYklzQ4jcTREcsdBkIrA"},
42
+ {"name": "dpr", "value": "2.608695652173913"},
43
+ {"name": "rur", "value": "\"NHA\\05475543047114\\0541783852609:01feb3e34e17329d37efbc1dfbc7d6c7cbdfd7496e0c5ea0f5e76a3125f0dda6e0b45e18\""}
44
+ ]
45
+
46
+ # Convert to requests cookie dict
47
+ cookies = {c['name']: c['value'] for c in raw_cookies}
48
+
49
+ #====== LOGGING SETUP ======
50
+ logging.basicConfig(
51
+ level=logging.INFO,
52
+ format="%(asctime)s - %(levelname)s - %(message)s",
53
+ handlers=[
54
+ logging.FileHandler("app.log"),
55
+ logging.StreamHandler()
56
+ ]
57
+ )
58
+ logger = logging.getLogger(__name__)
59
+
60
+ # ====== FLASK APP ======
61
+ app = Flask(__name__)
62
+
63
+ # ====== MONGO DB ======
64
+ client = MongoClient(os.getenv("MONGO_URI", "mongodb+srv://kanhagarg930123:[email protected]/?retryWrites=true&w=majority&appName=kanha"))
65
+ db1 = client.shortttt
66
+ meta = db1.meta
67
+ db2 = client["instagram_bot"]
68
+ reel_progress = db2["reel_progresss"]
69
+ # ====== CONSTANTS ======
70
+ CAPTIONS = [
71
+ "Wait for it 😜", "Watch till end πŸ˜‚", "Try not to laugh 🀣",
72
+ "Don't skip this πŸ”₯", "You won't expect this! πŸ˜€", "Keep watching πŸ˜†",
73
+ "Stay till end! πŸ’₯", "Funniest one yet"
74
+ ]
75
+
76
+ BLOCKLIST = [
77
+ "nsfw","18+", "xxx", "sexy", "adult", "porn", "onlyfans", "escort",
78
+ "betting", "gambling", "iplwin", "1xbet", "winzo", "my11circle", "dream11",
79
+ "rummy", "teenpatti", "fantasy", "casino", "promotion"
80
+ ]
81
+
82
+ UPLOAD_TIMES = []
83
+ NEXT_RESET = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1)
84
+
85
+ # ====== FUNCTIONS ======
86
+ def get_next_part():
87
+ last = meta.find_one(sort=[("part", -1)])
88
+ return 1 if not last else last["part"] + 1
89
+
90
+ def generate_description(title):
91
+ return f"Watch this hilarious clip: {title}"
92
+
93
+ # MongoDB integration assumed via reel_progress collection
94
+ def get_next_fetch_index(username):
95
+ entry = reel_progress.find_one({"username": username})
96
+ return entry["last_index"] if entry and "last_index" in entry else 0
97
+
98
+ def update_fetch_index(username, new_index):
99
+ reel_progress.update_one(
100
+ {"username": username},
101
+ {"$set": {"last_index": new_index}},
102
+ upsert=True
103
+ )
104
+
105
+ def is_reel_fetched_or_skipped(username, reel_id):
106
+ entry = reel_progress.find_one({"username": username})
107
+ if not entry:
108
+ return False
109
+ return reel_id in entry.get("fetched_ids", []) or reel_id in entry.get("skipped_ids", [])
110
+
111
+ def mark_reel_fetched(username, reel_id):
112
+ reel_progress.update_one(
113
+ {"username": username},
114
+ {"$addToSet": {"fetched_ids": reel_id}},
115
+ upsert=True
116
+ )
117
+
118
+ def mark_reel_skipped(username, reel_id):
119
+ reel_progress.update_one(
120
+ {"username": username},
121
+ {"$addToSet": {"skipped_ids": reel_id}},
122
+ upsert=True
123
+ )
124
+
125
+ import os
126
+ import base64
127
+ import tempfile
128
+ from telethon.sync import TelegramClient
129
+
130
+ import os
131
+ from telethon.sync import TelegramClient
132
+ from telethon.sessions import StringSession
133
+
134
+ TG_API_ID = int(os.getenv("API_ID", 3704772))
135
+ TG_API_HASH = os.getenv("API_HASH", "b8e50a035abb851c0dd424e14cac4c06")
136
+ SESSION_B64 = os.getenv("SESSION", "1BVtsOIEBu5jNa-E88vbC710wzyAKnteiQK7WQNvDK6wCeD3Q_c33uSaqvlvCUMglbhuVtugstOwIuE7WV2EuLGUEHH3AFJ84MZ93WVs010bx4N9nQOaQJNjIZDe4Nllq9r5PKRXxjgwuSqN-B7_TfpjQJT_ztOqQNZTQV3o9EqPBXfpiMzVF5U638wuRDVkInjbgAkI9ao36KDmcvBJzAW91l27loIsUL-Zst9H6XbAbVgqfs1fTOI6xQqPEyrFA-gB7lHbKrkqILwAmK88tTRQFHOvvXkGkJpDrXb1MZQssaVilGfXQ7sXWPltMpHklRy8HaPdfOUJ8t105DHXf-CN6SvzombM=")
137
+
138
+ if not SESSION_B64:
139
+ raise ValueError("SESSION is not set")
140
+
141
+ client = TelegramClient(StringSession(SESSION_B64), TG_API_ID, TG_API_HASH)
142
+
143
+ import json
144
+ import time
145
+ import random
146
+ import pathlib
147
+ import requests
148
+ from typing import Optional, Tuple
149
+ from telethon.sync import TelegramClient
150
+ from telethon.tl.functions.messages import GetHistoryRequest
151
+
152
+ # Assumes MongoDB functions are available:
153
+ # get_next_fetch_index, update_fetch_index, is_reel_fetched_or_skipped,
154
+ # mark_reel_fetched, mark_reel_skipped
155
+
156
+ RAW_USERNAMES = os.getenv("RAW_USERNAMES", "nickyteja06,videshi__indian,_notyourtype_yt_,sisterfunnyreel").split(",")
157
+ REACTED_USERNAMES = os.getenv("REACTED_USERNAMES", "biggestreact").split(",")
158
+
159
+
160
+ import os
161
+ import json
162
+ import time
163
+ import random
164
+ import pathlib
165
+ import requests
166
+ from typing import Optional, Tuple
167
+ from telethon.tl.functions.messages import GetHistoryRequest
168
+
169
+ # Constants
170
+ IG_HEADERS = {
171
+ "User-Agent": "Mozilla/5.0",
172
+ # Optional: Uncomment if using sessionid
173
+ # "Cookie": f"sessionid={os.getenv('IG_SESSION_ID')}"
174
+ }
175
+
176
+ import requests
177
+
178
+ def get_user_id(username: str) -> str | None:
179
+ url = f"https://www.instagram.com/api/v1/users/web_profile_info/?username={username}"
180
+ headers = {
181
+ "User-Agent": "Mozilla/5.0 (Linux; Android 10; Mobile)",
182
+ "X-Requested-With": "XMLHttpRequest",
183
+ "Referer": f"https://www.instagram.com/{username}/",
184
+ }
185
+ try:
186
+ res = requests.get(url, headers=headers, cookies=cookies, timeout=10)
187
+ if res.status_code == 200:
188
+ return res.json()["data"]["user"]["id"]
189
+ else:
190
+ print(f"⚠️ Failed to get user ID: {res.status_code}")
191
+ except Exception as e:
192
+ print(f"❌ Error fetching user ID: {e}")
193
+ return None
194
+
195
+
196
+ def get_links(user_id: str, limit: int = 5) -> list[Tuple[str, str]]:
197
+ links = []
198
+ query_hash = "58b6785bea111c67129decbe6a448951"
199
+ end_cursor = ""
200
+
201
+ while len(links) < limit:
202
+ variables = {
203
+ "id": user_id,
204
+ "first": 12,
205
+ "after": end_cursor
206
+ }
207
+ params = {
208
+ "query_hash": query_hash,
209
+ "variables": json.dumps(variables)
210
+ }
211
+
212
+ try:
213
+ res = requests.get("https://www.instagram.com/graphql/query/", params=params, headers=IG_HEADERS, timeout=10)
214
+ if res.status_code != 200:
215
+ logger.warning(f"[⚠️] Failed to get links for user {user_id}, status {res.status_code}")
216
+ break
217
+
218
+ media = res.json()["data"]["user"]["edge_owner_to_timeline_media"]
219
+ for edge in media["edges"]:
220
+ reel_id = edge["node"]["id"]
221
+ shortcode = edge["node"]["shortcode"]
222
+ link = f"https://www.instagram.com/p/{shortcode}/"
223
+ links.append((reel_id, link))
224
+ if len(links) >= limit:
225
+ break
226
+
227
+ if not media["page_info"]["has_next_page"]:
228
+ break
229
+ end_cursor = media["page_info"]["end_cursor"]
230
+ except Exception as e:
231
+ logger.warning(f"[❌] get_links() error: {e}")
232
+ break
233
+ logger.info(f"[πŸ”—] Found {len(links)} links for user ID {user_id}")
234
+ return links
235
+
236
+ def send_to_bot_and_get_video(link: str) -> Optional[str]:
237
+ try:
238
+ entity = client.loop.run_until_complete(client.get_entity("instasavegrambot"))
239
+ client.loop.run_until_complete(client.send_message(entity, link))
240
+
241
+ for _ in range(15): # Wait up to 30s
242
+ time.sleep(2)
243
+ history = client.loop.run_until_complete(
244
+ client(GetHistoryRequest(
245
+ peer=entity,
246
+ limit=2,
247
+ offset_id=0,
248
+ offset_date=None,
249
+ add_offset=0,
250
+ max_id=0,
251
+ min_id=0,
252
+ hash=0
253
+ ))
254
+ )
255
+ for msg in history.messages:
256
+ logger.info(f"[πŸ“¨] Bot message: {msg.message or '[media]'}")
257
+ if msg.video and 20 <= msg.video.duration <= 180:
258
+ path = f"reels/{msg.id}.mp4"
259
+ pathlib.Path("reels").mkdir(exist_ok=True)
260
+ client.loop.run_until_complete(msg.download_media(file=path))
261
+ logger.info(f"[βœ…] Downloaded reel to {path}")
262
+ return path
263
+ except Exception as e:
264
+ logger.warning(f"[❌] send_to_bot_and_get_video error: {e}")
265
+ return None
266
+
267
+ def fetch_valid_reel() -> Tuple[Optional[str], Optional[str]]:
268
+ with client:
269
+ pools = [(RAW_USERNAMES, "raw"), (REACTED_USERNAMES, "reacted")]
270
+ pools = [(p, t) for p, t in pools if p]
271
+ if not pools:
272
+ logger.warning("[⚠️] No usernames in either RAW or REACTED pools.")
273
+ return None, None
274
+
275
+ random.shuffle(pools)
276
+
277
+ for usernames, reel_type in pools:
278
+ random.shuffle(usernames)
279
+ for username in usernames:
280
+ logger.info(f"[πŸ”Ž] Trying @{username} ({reel_type})")
281
+ uid = get_user_id(username)
282
+ if not uid:
283
+ logger.warning(f"[🚫] Skipping @{username}, failed to get user ID.")
284
+ continue
285
+
286
+ fetch_idx = get_next_fetch_index(username)
287
+ links = get_links(uid, limit=5)
288
+
289
+ for idx, (reel_id, link) in enumerate(links[fetch_idx:], start=fetch_idx):
290
+ if is_reel_fetched_or_skipped(username, reel_id):
291
+ logger.info(f"[⏩] Already processed: @{username}/{reel_id}")
292
+ continue
293
+
294
+ logger.info(f"[πŸ“₯] Sending link to bot: {link}")
295
+ video_path = send_to_bot_and_get_video(link)
296
+ if not video_path:
297
+ logger.warning(f"[⚠️] Failed to fetch: {link}")
298
+ mark_reel_skipped(username, reel_id)
299
+ continue
300
+
301
+ mark_reel_fetched(username, reel_id)
302
+ update_fetch_index(username, idx + 1)
303
+ return video_path, reel_type
304
+
305
+ update_fetch_index(username, fetch_idx + len(links))
306
+
307
+ logger.warning("[🚫] No valid reels found after checking all usernames.")
308
+ return None, None
309
+
310
+ def patch_moviepy():
311
+ original_resizer = resize.resize
312
+
313
+ def patched_resizer(clip, *args, **kwargs):
314
+ newsize = kwargs.get("newsize", None)
315
+ if newsize:
316
+ newsize = tuple(map(int, newsize))
317
+ clip = clip.fl_image(lambda img: img.resize(newsize, Image.Resampling.LANCZOS))
318
+ else:
319
+ clip = original_resizer(clip, *args, **kwargs)
320
+ return clip
321
+
322
+ resize.resize = patched_resizer
323
+
324
+ patch_moviepy()
325
+
326
+ import emoji
327
+ from PIL import Image, ImageDraw, ImageFont
328
+ import emoji
329
+ import os
330
+ import requests
331
+
332
+
333
+ def create_text_image(text, width, height):
334
+ img = Image.new("RGB", (width, height), color=(255, 255, 255))
335
+ draw = ImageDraw.Draw(img)
336
+
337
+ # Load font
338
+ try:
339
+ font = ImageFont.truetype("DejaVuSans-Bold.ttf", size=60)
340
+ except:
341
+ font = ImageFont.load_default()
342
+
343
+ # Extract emoji and clean text
344
+ emojis = emoji.emoji_list(text)
345
+ pure_text = emoji.replace_emoji(text, replace='')
346
+
347
+ # Adjust font size to fit
348
+ max_font_size = 70
349
+ while True:
350
+ font = ImageFont.truetype("DejaVuSans-Bold.ttf", size=max_font_size)
351
+ text_width, text_height = draw.textsize(pure_text, font=font)
352
+ total_width = text_width + (len(emojis) * 60) + 20
353
+ if total_width <= width - 40 or max_font_size <= 30:
354
+ break
355
+ max_font_size -= 2
356
+
357
+ # Starting X & Y for centered layout
358
+ start_x = (width - total_width) // 2
359
+ y = (height - text_height) // 2
360
+
361
+ # Draw text first
362
+ draw.text((start_x, y), pure_text, font=font, fill=(0, 0, 0))
363
+
364
+ # Then draw emojis to the right of the text
365
+ x = start_x + text_width + 10
366
+ for e in emojis:
367
+ hexcode = '-'.join(f"{ord(c):x}" for c in e['emoji'])
368
+ emoji_path = f"emoji_pngs/{hexcode}.png"
369
+ if not os.path.exists(emoji_path):
370
+ download_emoji_png(e['emoji'])
371
+
372
+ if os.path.exists(emoji_path):
373
+ emoji_img = Image.open(emoji_path).convert("RGBA")
374
+ emoji_img = emoji_img.resize((60, 60))
375
+ img.paste(emoji_img, (x, y), emoji_img)
376
+ x += 60 + 4
377
+
378
+ return img
379
+
380
+ from PIL import Image, ImageDraw, ImageFont
381
+ import numpy as np
382
+ from moviepy.editor import ImageClip
383
+
384
+ def generate_watermark_img(text, width, height=50):
385
+ img = Image.new("RGBA", (width, height), (0, 0, 0, 0))
386
+ draw = ImageDraw.Draw(img)
387
+
388
+ try:
389
+ font = ImageFont.truetype("DejaVuSans-Bold.ttf", size=35)
390
+ except:
391
+ font = ImageFont.load_default()
392
+
393
+ text_width, text_height = draw.textsize(text, font=font)
394
+ draw.text((5, height - text_height - 2), text, fill="white", font=font, stroke_width=1, stroke_fill="black")
395
+
396
+ return img
397
+
398
+ def download_emoji_png(emoji_char):
399
+ hexcode = '-'.join(f"{ord(c):x}" for c in emoji_char)
400
+ url = f"https://github.com/twitter/twemoji/raw/master/assets/72x72/{hexcode}.png"
401
+ os.makedirs("emoji_pngs", exist_ok=True)
402
+ path = f"emoji_pngs/{hexcode}.png"
403
+ try:
404
+ r = requests.get(url)
405
+ if r.status_code == 200:
406
+ with open(path, "wb") as f:
407
+ f.write(r.content)
408
+ print(f"βœ… Downloaded emoji: {emoji_char} β†’ {path}")
409
+ else:
410
+ print(f"❌ Failed to download emoji: {emoji_char}")
411
+ except Exception as e:
412
+ print(f"⚠️ Error downloading emoji {emoji_char}: {e}")
413
+
414
+
415
+ def edit_video(video_path):
416
+ clip = VideoFileClip(video_path)
417
+
418
+ video_width = clip.w
419
+ video_height = clip.h
420
+ bar_height = 120
421
+ total_height = video_height + bar_height
422
+
423
+ # === 1. Background Canvas
424
+ final_bg = ColorClip(size=(video_width, total_height), color=(255, 255, 255), duration=clip.duration)
425
+
426
+ # === 2. Caption Bar (Top)
427
+ caption = random.choice(CAPTIONS)
428
+ caption_img = create_text_image(caption, video_width, bar_height)
429
+ caption_clip = ImageClip(np.array(caption_img)).set_duration(clip.duration).set_position((0, 0))
430
+
431
+ # === 3. Eye Protection Overlay (6% White Transparent)
432
+ eye_protection = ColorClip(size=(video_width, video_height), color=(255, 255, 255), duration=clip.duration)
433
+ eye_protection = eye_protection.set_opacity(0.1).set_position((0, bar_height))
434
+
435
+ # === 4. Watermark (Bottom-left using Pillow + ImageClip)
436
+ watermark_img = generate_watermark_img("@fulltosscomedy4u", video_width, height=50)
437
+ watermark_clip = ImageClip(np.array(watermark_img)).set_duration(clip.duration).set_position(("left", bar_height + video_height - 50))
438
+
439
+ # === 5. Position original video below top bar
440
+ video_clip = clip.set_position((0, bar_height))
441
+
442
+ # === 6. Combine everything
443
+ final = CompositeVideoClip(
444
+ [final_bg, caption_clip, video_clip, eye_protection, watermark_clip],
445
+ size=(video_width, total_height)
446
+ )
447
+
448
+ os.makedirs("edited", exist_ok=True)
449
+ output_path = f"edited/{uuid.uuid4().hex}.mp4"
450
+ final.write_videofile(
451
+ output_path,
452
+ codec="libx264",
453
+ audio_codec="aac",
454
+ preset="slow",
455
+ bitrate="12000k",
456
+ verbose=False,
457
+ logger=None
458
+ )
459
+ return output_path
460
+
461
+ def edit_video_raw(video_path):
462
+ clip = VideoFileClip(video_path)
463
+ video_width = clip.w
464
+ video_height = clip.h
465
+ top_bar_height = 120
466
+ mid_bar_height = 80
467
+ total_height = video_height + top_bar_height + mid_bar_height
468
+
469
+ # === 1. Background Canvas
470
+ final_bg = ColorClip(size=(video_width, total_height), color=(255, 255, 255), duration=clip.duration + 6)
471
+
472
+ # === 2. Top Caption (Black on White)
473
+ caption = random.choice(CAPTIONS)
474
+ caption_img = create_text_image(
475
+ caption,
476
+ width=video_width,
477
+ height=top_bar_height,
478
+ font_size=min(max(int(video_width * 0.08), 48), 80), # Auto-resize
479
+ align="center",
480
+ bg_color=(255, 255, 255),
481
+ text_color=(0, 0, 0)
482
+ )
483
+ caption_clip = ImageClip(np.array(caption_img)).set_duration(clip.duration + 6).set_position((0, 0))
484
+
485
+ # === 3. Middle Caption (White on Black)
486
+ mid_text = random.choice([
487
+ "Pura 1 din laga tab ye reel mili 🀣",
488
+ "Ye miss mat kr dena 😜",
489
+ "Kha thi ye reel ab tak πŸ€¨πŸ€—",
490
+ "Wait, ye dekh kr hi janna πŸ’₯πŸ’₯"
491
+ ])
492
+ mid_img = create_text_image(
493
+ mid_text,
494
+ width=video_width,
495
+ height=mid_bar_height,
496
+ font_size=min(max(int(video_width * 0.06), 40), 64),
497
+ align="center",
498
+ bg_color=(0, 0, 0),
499
+ text_color=(255, 255, 255)
500
+ )
501
+ mid_caption_clip = ImageClip(np.array(mid_img)).set_duration(clip.duration + 6).set_position((0, top_bar_height))
502
+
503
+ # === 4. Eye Protection Overlay
504
+ eye_protection = ColorClip(size=(video_width, video_height), color=(255, 255, 255), duration=clip.duration + 6)
505
+ eye_protection = eye_protection.set_opacity(0.1).set_position((0, top_bar_height + mid_bar_height))
506
+
507
+ # === 5. Watermark (Bottom Left)
508
+ watermark_img = generate_watermark_img("@fulltosscomedy4u", video_width, height=50)
509
+ watermark_clip = ImageClip(np.array(watermark_img)).set_duration(clip.duration + 6).set_position(("left", total_height - 50))
510
+
511
+ # === 6. Meme Section: Laugh -> Freeze -> Laugh
512
+ laugh_index = random.choice([1, 2])
513
+ laugh_clip = VideoFileClip(f"laugh/{laugh_index}.mp4").resize(width=video_width).set_duration(2)
514
+ freeze_frame = laugh_clip.to_ImageClip().set_duration(max(1, clip.duration - 4))
515
+ meme_part = concatenate_videoclips([laugh_clip, freeze_frame, laugh_clip])
516
+
517
+ # === 7. Combine All Video Parts
518
+ full_video = concatenate_videoclips([meme_part, clip])
519
+ full_video = full_video.set_position((0, top_bar_height + mid_bar_height))
520
+
521
+ # === 8. Composite Final Video
522
+ final = CompositeVideoClip(
523
+ [final_bg, caption_clip, mid_caption_clip, full_video, eye_protection, watermark_clip],
524
+ size=(video_width, total_height)
525
+ )
526
+
527
+ # === 9. Export
528
+ os.makedirs("edited", exist_ok=True)
529
+ output_path = f"edited/{uuid.uuid4().hex}.mp4"
530
+ final.write_videofile(
531
+ output_path,
532
+ codec="libx264",
533
+ audio_codec="aac",
534
+ preset="slow",
535
+ bitrate="12000k",
536
+ threads=4,
537
+ verbose=False,
538
+ logger=None
539
+ )
540
+ return output_path
541
+
542
+ def upload_to_youtube(video_path, title, desc):
543
+ creds = Credentials(
544
+ token=None,
545
+ refresh_token=os.getenv("YT_REFRESH_TOKEN"),
546
+ token_uri="https://oauth2.googleapis.com/token",
547
+ client_id=os.getenv("YT_CLIENT_ID", "387804137131-sjih05p3329n5n72gsgglv1tb9t62882.apps.googleusercontent.com"),
548
+ client_secret=os.getenv("YT_CLIENT_SECRET", "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"),
549
+ scopes=["https://www.googleapis.com/auth/youtube.upload"]
550
+ )
551
+ creds.refresh(Request())
552
+ youtube = build("youtube", "v3", credentials=creds)
553
+ request = youtube.videos().insert(
554
+ part="snippet,status",
555
+ body={
556
+ "snippet": {
557
+ "title": title,
558
+ "description": desc,
559
+ "tags": ["funny", "memes", "comedy", "shorts"],
560
+ "categoryId": "23"
561
+ },
562
+ "status": {
563
+ "privacyStatus": "public",
564
+ "madeForKids": False
565
+ }
566
+ },
567
+ media_body=MediaFileUpload(video_path)
568
+ )
569
+ res = request.execute()
570
+ logger.info(f"Uploaded: https://youtube.com/watch?v={res['id']}")
571
+ return f"https://youtube.com/watch?v={res['id']}"
572
+
573
+ first_run = True
574
+ def save_to_db(part, title, desc, link):
575
+ meta.insert_one({"part": part, "title": title, "description": desc, "link": link, "uploaded": time.time()})
576
+
577
+ def auto_loop():
578
+ asyncio.set_event_loop(asyncio.new_event_loop())
579
+ global UPLOAD_TIMES, NEXT_RESET, first_run
580
+
581
+ ist = timezone(timedelta(hours=5, minutes=30)) # IST timezone
582
+
583
+ daily_upload_count = random.randint(3, 5)
584
+ uploads_done_today = 0
585
+ NEXT_RESET = datetime.now(ist).replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1)
586
+
587
+ logger.info(f"[πŸ“…] Today's upload target: {daily_upload_count} reels.")
588
+
589
+ def wait_until(hour: int, minute: int = 0):
590
+ now = datetime.now(ist)
591
+ target = now.replace(hour=hour, minute=minute, second=0, microsecond=0)
592
+ if target < now:
593
+ return
594
+ logger.info(f"[πŸ•—] Waiting until {target.strftime('%H:%M')} IST...")
595
+ while datetime.now(ist) < target:
596
+ time.sleep(10)
597
+
598
+ wait_until(8)
599
+
600
+ while True:
601
+ try:
602
+ now = datetime.now(ist)
603
+
604
+ if now >= NEXT_RESET:
605
+ UPLOAD_TIMES.clear()
606
+ NEXT_RESET = now.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1)
607
+ daily_upload_count = random.randint(3, 5)
608
+ uploads_done_today = 0
609
+ logger.info(f"[πŸ”] Reset for new day. New target: {daily_upload_count} uploads.")
610
+
611
+ if uploads_done_today >= daily_upload_count:
612
+ logger.info("[βœ…] Daily upload target reached.")
613
+ time.sleep(60)
614
+ continue
615
+
616
+ if now.hour < 8 or now.hour >= 20:
617
+ time.sleep(60)
618
+ continue
619
+
620
+ if not UPLOAD_TIMES or (now - UPLOAD_TIMES[-1]).total_seconds() >= random.randint(7200, 14400):
621
+ video_path, reel_type = fetch_valid_reel()
622
+
623
+ if not video_path:
624
+ logger.warning("[⚠️] No valid reel found. Retrying...")
625
+ time.sleep(60)
626
+ continue
627
+ if reel_type == "reacted":
628
+ edited = edit_video(video_path)
629
+ else:
630
+ edited = edit_video_raw(video_path)
631
+ part = get_next_part()
632
+ title = f"Try not to laugh || #{part} #funny #memes #comedy #shorts"
633
+ desc = generate_description(title)
634
+ link = upload_to_youtube(edited, title, desc)
635
+ save_to_db(part, title, desc, link)
636
+
637
+ logger.info(f"[πŸ“€] Uploaded #{part}: {link}")
638
+ UPLOAD_TIMES.append(now)
639
+ uploads_done_today += 1
640
+
641
+ os.remove(video)
642
+ os.remove(edited)
643
+
644
+ if uploads_done_today < daily_upload_count:
645
+ gap_seconds = random.randint(7200, 14400)
646
+ next_time = datetime.now(ist) + timedelta(seconds=gap_seconds)
647
+ if next_time.hour >= 20:
648
+ logger.info("[πŸŒ™] Next upload would exceed 8PM. Skipping.")
649
+ continue
650
+ logger.info(f"[⏳] Waiting ~{gap_seconds // 60} minutes before next upload.")
651
+ time.sleep(gap_seconds)
652
+ else:
653
+ time.sleep(60)
654
+
655
+ except Exception as e:
656
+ logger.error(f"Loop error: {e}")
657
+ time.sleep(60)
658
+
659
+ @app.route('/')
660
+ def home():
661
+ last = meta.find_one(sort=[("uploaded", -1)])
662
+ video_id = last["link"].split("v=")[-1] if last and "link" in last else None
663
+ return render_template("index.html", time=time.ctime(), video_id=video_id)
664
+
665
+ @app.route('/run-now', methods=["POST"])
666
+ def run_now():
667
+ Thread(target=auto_loop, daemon=True).start()
668
+ return redirect("/")
669
+
670
+ if __name__ == "__main__":
671
+ Thread(target=auto_loop, daemon=True).start()
672
+ app.run(host="0.0.0.0", port=7860)