VaniConnect Pipeline Bot commited on
Commit
ba4ad1b
Β·
0 Parent(s):

Auto-deployed fresh snapshot from GitHub

Browse files
Files changed (20) hide show
  1. .gitattributes +1 -0
  2. .github/workflows/deploy-hf.yml +36 -0
  3. .gitignore +8 -0
  4. Dockerfile +23 -0
  5. EDSR_x4.pb +3 -0
  6. FSRCNN_x4.pb +3 -0
  7. README.md +10 -0
  8. add_logo.py +43 -0
  9. app.py +781 -0
  10. auto_detect.py +61 -0
  11. bg_remove.py +66 -0
  12. db.py +37 -0
  13. enhance_photo.py +58 -0
  14. enhance_video.py +64 -0
  15. main_photo.py +58 -0
  16. main_video.py +216 -0
  17. process_test.py +22 -0
  18. requirements.txt +64 -0
  19. trim_video.py +136 -0
  20. yt_down.py +58 -0
.gitattributes ADDED
@@ -0,0 +1 @@
 
 
1
+ *.pb filter=lfs diff=lfs merge=lfs -text
.github/workflows/deploy-hf.yml ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Sync to Hugging Face Space
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ - master
8
+
9
+ jobs:
10
+ sync-to-hub:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - name: Checkout Code
14
+ uses: actions/checkout@v3
15
+ with:
16
+ lfs: true
17
+
18
+ - name: Push to Hugging Face
19
+ env:
20
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
21
+ run: |
22
+ # 1. Delete the old history inside the temporary computer
23
+ rm -rf .git
24
+
25
+ # 2. Start a fresh, clean shipping box
26
+ git init
27
+ git lfs install
28
+ git config user.email "bot@vaniconnect.com"
29
+ git config user.name "VaniConnect Pipeline Bot"
30
+
31
+ # 3. Pack up the current files (which are now safely configured for LFS)
32
+ git add .
33
+ git commit -m "Auto-deployed fresh snapshot from GitHub"
34
+
35
+ # 4. Force overwrite Hugging Face with this clean box
36
+ git push --force https://vaniconnect:$HF_TOKEN@huggingface.co/spaces/vaniconnect/vaniconnect-api master:main
.gitignore ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ venv/
2
+ .env
3
+ __pycache__/
4
+ firebase_key.json
5
+ downloads/
6
+ vaniconnect.db
7
+ *.mp4
8
+ big-lama.onnx
Dockerfile ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 1. Start with a reliable, specific Python server version
2
+ FROM python:3.10-slim-bullseye
3
+
4
+ # 2. Install the heavy system tools for OpenCV and Video Processing
5
+ RUN apt-get update && apt-get install -y \
6
+ ffmpeg \
7
+ libsm6 \
8
+ libxext6 \
9
+ libgl1 \
10
+ && rm -rf /var/lib/apt/lists/*
11
+
12
+ # 3. Set up our working folder
13
+ WORKDIR /app
14
+
15
+ # 4. Copy our shopping list and install the Python tools
16
+ COPY requirements.txt .
17
+ RUN pip install --no-cache-dir -r requirements.txt
18
+
19
+ # 5. Copy all our python scripts into the container
20
+ COPY . .
21
+
22
+ # 6. Turn on the FastAPI engine
23
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
EDSR_x4.pb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:dd35ce3cae53ecee2d16045e08a932c3e7242d641bb65cb971d123e06904347f
3
+ size 38573255
FSRCNN_x4.pb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5c68d18db561aed8ead4ffedf1b897ea615baaf60ebf6c35f8e641f8fa4a21bf
3
+ size 41661
README.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Vaniconnect Api
3
+ emoji: πŸš€
4
+ colorFrom: blue
5
+ colorTo: green
6
+ sdk: docker
7
+ pinned: false
8
+ ---
9
+
10
+ # VaniConnect Studio AI Backend
add_logo.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import os
3
+ from moviepy.editor import VideoFileClip, ImageClip, CompositeVideoClip
4
+
5
+ def add_user_controlled_logo(video_path, logo_path, output_path, x, y, logo_w, logo_h):
6
+ if not os.path.exists(video_path) or not os.path.exists(logo_path):
7
+ return False
8
+
9
+ try:
10
+ video = VideoFileClip(video_path)
11
+
12
+ # Calculate proper height to maintain aspect ratio
13
+ logo_img = cv2.imread(logo_path, cv2.IMREAD_UNCHANGED)
14
+ if logo_img is None:
15
+ return False
16
+
17
+ aspect_ratio = logo_img.shape[0] / logo_img.shape[1]
18
+ actual_height = int(logo_w * aspect_ratio)
19
+
20
+ logo = (ImageClip(logo_path)
21
+ .set_duration(video.duration)
22
+ .resize(width=logo_w, height=actual_height)
23
+ .set_position((x, y)))
24
+
25
+ final_video = CompositeVideoClip([video, logo])
26
+
27
+ final_video.write_videofile(
28
+ output_path,
29
+ codec="libx264",
30
+ audio_codec="aac",
31
+ preset="ultrafast",
32
+ threads=4,
33
+ fps=video.fps,
34
+ logger=None
35
+ )
36
+
37
+ video.close()
38
+ final_video.close()
39
+ return True
40
+
41
+ except Exception as e:
42
+ print(f"Python Error: {e}")
43
+ return False
app.py ADDED
@@ -0,0 +1,781 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import cv2
4
+ import boto3
5
+ import shutil
6
+ import razorpay
7
+ from fastapi import FastAPI, Request, BackgroundTasks, UploadFile, File, Form, HTTPException, Response
8
+ from fastapi.middleware.cors import CORSMiddleware
9
+ from fastapi.staticfiles import StaticFiles
10
+ from slowapi import Limiter, _rate_limit_exceeded_handler
11
+ from slowapi.util import get_remote_address
12
+ from slowapi.errors import RateLimitExceeded
13
+ from pydantic import BaseModel
14
+ from dotenv import load_dotenv
15
+ from typing import Optional
16
+ from fastapi import Response
17
+ import firebase_admin
18
+ from firebase_admin import credentials, firestore
19
+
20
+ # πŸ€– YOUR AI TOOLS
21
+
22
+ import main_photo
23
+ import main_video
24
+ import bg_remove
25
+ import add_logo
26
+ import enhance_photo
27
+ import enhance_video
28
+ import auto_detect
29
+ import db
30
+
31
+ # 1️⃣ LOAD SECRETS FIRST
32
+ load_dotenv()
33
+
34
+ # 2️⃣ INITIALIZE THE APP (ONLY ONCE!)
35
+ app = FastAPI(title="VaniConnect AI Engine")
36
+
37
+ # 3️⃣ OPEN THE SECURITY GATES (CORS) - THIS FIXES YOUR BUTTON!
38
+ app.add_middleware(
39
+ CORSMiddleware,
40
+ allow_origins=[
41
+ "http://localhost:5173",
42
+ "https://vaniconnect-studio.vercel.app",
43
+ "https://clipeto.com",
44
+ "https://www.clipeto.com"
45
+
46
+ ],
47
+ allow_credentials=True,
48
+ allow_methods=["*"],
49
+ allow_headers=["*"],
50
+ )
51
+
52
+ # 4️⃣ SET UP RATE LIMITER
53
+ limiter = Limiter(key_func=get_remote_address)
54
+ app.state.limiter = limiter
55
+ app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
56
+
57
+
58
+ # 5️⃣ SET UP DOWNLOADS FOLDER
59
+ # 1. Define the custom class FIRST so Python knows what it is
60
+ class CORStaticFiles(StaticFiles):
61
+ async def item_response(self, *args, **kwargs) -> Response:
62
+ response = await super().item_response(*args, **kwargs)
63
+ response.headers["Access-Control-Allow-Origin"] = "*"
64
+ response.headers["Access-Control-Allow-Methods"] = "GET, OPTIONS"
65
+ response.headers["Access-Control-Allow-Headers"] = "*"
66
+ return response
67
+
68
+ # 2. Make the directory physical structure
69
+ os.makedirs("downloads", exist_ok=True)
70
+
71
+ # 3. Mount the folder using your class LAST
72
+ app.mount("/downloads", CORStaticFiles(directory="downloads"), name="downloads")
73
+
74
+ # 6️⃣ CONNECT TO CLOUDFLARE R2
75
+ r2_access_key = os.getenv('R2_ACCESS_KEY_ID')
76
+ r2_secret_key = os.getenv('R2_SECRET_ACCESS_KEY')
77
+ r2_endpoint = os.getenv('R2_ENDPOINT_URL')
78
+ bucket_name = os.getenv('R2_BUCKET_NAME')
79
+
80
+ s3 = boto3.client(
81
+ 's3',
82
+ endpoint_url=r2_endpoint,
83
+ aws_access_key_id=r2_access_key,
84
+ aws_secret_access_key=r2_secret_key,
85
+ region_name='auto'
86
+ )
87
+
88
+ # 7️⃣ RAZORPAY SETUP
89
+ RAZORPAY_KEY_ID = os.getenv("RAZORPAY_KEY_ID")
90
+ RAZORPAY_KEY_SECRET = os.getenv("RAZORPAY_KEY_SECRET")
91
+
92
+ rzp_client = razorpay.Client(auth=(RAZORPAY_KEY_ID, RAZORPAY_KEY_SECRET))
93
+
94
+ class OrderRequest(BaseModel):
95
+ user_id: str
96
+
97
+ class VerifyRequest(BaseModel):
98
+ razorpay_order_id: str
99
+ razorpay_payment_id: str
100
+ razorpay_signature: str
101
+ user_id: str
102
+
103
+ # 8️⃣ FIREBASE ADMIN SETUP
104
+ firebase_secret = os.environ.get("FIREBASE_KEY")
105
+ cred = credentials.Certificate(json.loads(firebase_secret))
106
+
107
+ # πŸ”₯ FIX: Check if Firebase is already initialized to prevent reload crashes
108
+ if not firebase_admin._apps:
109
+ firebase_admin.initialize_app(cred)
110
+
111
+ firestore_db = firestore.client()
112
+ # ---------------------------------------------------------
113
+ # πŸš€ ROUTES
114
+ # ---------------------------------------------------------
115
+
116
+ @app.head("/")
117
+ @app.get("/")
118
+ def read_root():
119
+ return {"message": "βœ… VaniConnect AI Engine is Live and Running!"}
120
+
121
+ # 1️⃣ CREATE THE ORDER
122
+ @app.post("/api/create-order")
123
+ async def create_order(req: OrderRequest):
124
+ try:
125
+ # β‚Ή299 is 99900 paise in Razorpay
126
+ order_amount = 29900
127
+
128
+ razorpay_order = rzp_client.order.create({
129
+ "amount": order_amount,
130
+ "currency": "INR",
131
+ "receipt": f"receipt_{req.user_id}",
132
+ "payment_capture": "1" # Auto-capture the payment
133
+ })
134
+
135
+ return {
136
+ "order_id": razorpay_order['id'],
137
+ "amount": order_amount,
138
+ "currency": "INR"
139
+ }
140
+ except Exception as e:
141
+ raise HTTPException(status_code=500, detail=str(e))
142
+
143
+ # 2️⃣ VERIFY PAYMENT & UNLOCK PRO IN FIREBASE
144
+ @app.post("/api/verify-payment")
145
+ async def verify_payment(req: VerifyRequest):
146
+ try:
147
+ # 1. Razorpay securely checks if the payment is legitimate
148
+ rzp_client.utility.verify_payment_signature({
149
+ 'razorpay_order_id': req.razorpay_order_id,
150
+ 'razorpay_payment_id': req.razorpay_payment_id,
151
+ 'razorpay_signature': req.razorpay_signature
152
+ })
153
+
154
+ # 2. 🚨 UNLOCK THE FIREBASE USER
155
+ try:
156
+ user_ref = firestore_db.collection('users').document(req.user_id)
157
+
158
+ # πŸ”₯ We use .set() with merge=True so it creates the user if they don't exist yet!
159
+ user_ref.set({"isProUser": True}, merge=True)
160
+
161
+ print(f"βœ… Successfully upgraded user {req.user_id} to PRO in Firestore!")
162
+ except Exception as e:
163
+ print(f"πŸ”₯ FIREBASE ERROR: {e}")
164
+ # We just print the error to the terminal, but we don't crash the app
165
+ # because the Razorpay payment was actually successful!
166
+
167
+ return {"status": "success", "message": "Payment verified. Pro Unlocked!"}
168
+
169
+ except razorpay.errors.SignatureVerificationError:
170
+ raise HTTPException(status_code=400, detail="Payment verification failed!")
171
+ # πŸ‘‡ (Paste the rest of your AI tool routes down here like /api/enhance, etc.) πŸ‘‡
172
+ @app.get("/")
173
+ def read_root():
174
+ return {"message": "βœ… VaniConnect AI Engine is Live and Running!"}
175
+
176
+ @app.post("/api/remove-photo-watermark")
177
+ @limiter.limit("5/minute")
178
+ async def process_photo(
179
+ request: Request,
180
+ file: UploadFile = File(...),
181
+ x: int = Form(0),
182
+ y: int = Form(0),
183
+ w: int = Form(0),
184
+ h: int = Form(0),
185
+ style: str = Form("Standard AI Inpaint"),
186
+ mode: str = Form("manual"),
187
+ user_id: str = Form(...) # 🌟 1. Added the Catching Mitt!
188
+ ):
189
+ # ❌ 2. Deleted the hardcoded admin_user_1!
190
+
191
+ user_data = db.get_or_create_user(user_id)
192
+
193
+ # 2. Safety Check: If Firebase returns NOTHING, stop gracefully!
194
+ if not user_data:
195
+ print(f"🚨 Blocked: User {user_id} not found in Firebase!")
196
+ raise HTTPException(status_code=401, detail="User profile not found. Please log in securely.")
197
+
198
+ # 3. Use the NEW Firebase vocabulary
199
+ is_pro = user_data.get("isProUser", False)
200
+ credits_left = user_data.get("free_credits", 0)
201
+
202
+ # 4. The Paywall Logic
203
+ if not is_pro and credits_left <= 0:
204
+ print(f"🚨 Blocked: User {user_id} is out of credits!")
205
+ raise HTTPException(status_code=402, detail="PaywallTrigger: Daily limit reached. Upgrade to Pro.")
206
+
207
+ input_filename = f"downloads/temp_{file.filename}"
208
+ output_filename = f"downloads/clean_{file.filename}"
209
+
210
+ with open(input_filename, "wb") as buffer:
211
+ buffer.write(await file.read())
212
+
213
+ print(f"βœ‚οΈ Removing photo watermark from {input_filename} in {mode.upper()} mode...")
214
+
215
+ # 🌟 THE AI AUTO LOGIC FOR PHOTOS!
216
+ if mode == "auto":
217
+ print("πŸ€– Handing photo to EasyOCR to find the watermark...")
218
+ box = auto_detect.find_text_watermark(input_filename)
219
+
220
+ if box:
221
+ x, y, w, h = box['x'], box['y'], box['w'], box['h']
222
+ print(f"βœ… AI found the text at: x={x}, y={y}, w={w}, h={h}")
223
+ else:
224
+ return {"error": "AI Auto could not find clear text in the image. Please use Manual Select."}
225
+
226
+ success = main_photo.remove_photo_watermark_web(
227
+ input_path=input_filename,
228
+ output_path=output_filename,
229
+ x=x, y=y, w=w, h=h, style=style
230
+ )
231
+
232
+ if not success:
233
+ return {"error": "Failed to process photo"}
234
+
235
+ try:
236
+ with open(output_filename, 'rb') as clean_file:
237
+ s3.put_object(
238
+ Bucket=bucket_name,
239
+ Key=f'processed_photos/clean_{file.filename}',
240
+ Body=clean_file
241
+ )
242
+ except Exception as e:
243
+ print(f"Cloudflare upload skipped: {e}")
244
+
245
+ try:
246
+ os.remove(input_filename)
247
+ except Exception as e:
248
+ print(f"Cleanup issue: {e}")
249
+
250
+ # 🌟 3. FIXED INDENTATION: Now credit is deducted safely!
251
+ if not is_pro:
252
+ db.deduct_credit(user_id)
253
+ print(f"πŸ’Έ Credit deducted! Remaining: {credits_left - 1}")
254
+
255
+ return {"message": "Success!", "file_name": f"clean_{file.filename}"}
256
+
257
+ @app.post("/api/remove-video-watermark")
258
+ @limiter.limit("5/minute")
259
+ async def process_video(
260
+ request: Request,
261
+ file: UploadFile = File(...),
262
+ x: int = Form(0),
263
+ y: int = Form(0),
264
+ w: int = Form(0),
265
+ h: int = Form(0),
266
+ mode: str = Form("manual"),
267
+ user_id: str = Form(...)
268
+ ):
269
+ # 1. User Validation & Profile Synchronization
270
+ user_data = db.get_or_create_user(user_id)
271
+
272
+ if not user_data:
273
+ print(f"🚨 Blocked: User {user_id} not found in Firebase!")
274
+ raise HTTPException(status_code=401, detail="User profile not found. Please log in securely.")
275
+
276
+ is_pro = user_data.get("isProUser", False)
277
+ credits_left = user_data.get("free_credits", 0)
278
+
279
+ if not is_pro and credits_left <= 0:
280
+ print(f"🚨 Blocked: User {user_id} is out of credits!")
281
+ raise HTTPException(status_code=402, detail="PaywallTrigger: Daily limit reached. Upgrade to Pro.")
282
+
283
+ # 2. Disk Staging Setup
284
+ input_filename = f"downloads/temp_vid_{file.filename}"
285
+ output_filename = f"downloads/clean_vid_{file.filename}"
286
+
287
+ with open(input_filename, "wb") as buffer:
288
+ buffer.write(await file.read())
289
+
290
+ print(f"🎬 Incoming Frontend Request Mode: {mode.upper()}")
291
+
292
+ # Both modes will share your amazing fast dominant color patcher engine!
293
+ backend_execution_mode = "fast"
294
+
295
+ # 🌟 ISOLATED AUTO MODE LOGIC CORE (Does not interfere with Manual mode parameters)
296
+ if mode == "auto":
297
+ print("πŸ€– Handing video to EasyOCR to parse text layouts...")
298
+ box = auto_detect.find_text_watermark(input_filename)
299
+
300
+ if box:
301
+ # Open video metadata stream to calculate strict layout boundaries
302
+ vid = cv2.VideoCapture(input_filename)
303
+ vid_w = int(vid.get(cv2.CAP_PROP_FRAME_WIDTH))
304
+ vid_h = int(vid.get(cv2.CAP_PROP_FRAME_HEIGHT))
305
+ vid.release()
306
+
307
+ temp_x, temp_y = box['x'], box['y']
308
+ temp_w, temp_h = box['w'], box['h']
309
+ print(f"🎯 Raw AI Detection Coordinates -> x: {temp_x}, y: {temp_y}, w: {temp_w}, h: {temp_h} (Canvas Size: {vid_w}x{vid_h})")
310
+
311
+ # πŸ›‘οΈ SHIELD 1: Headline Banner Detection (Rejects long horizontal bars)
312
+ if temp_w > (vid_w * 0.40) or temp_h > (vid_h * 0.30):
313
+ print("⚠️ Intercepted: AI detected a structural headline banner instead of a corner logo.")
314
+ try: os.remove(input_filename)
315
+ except: pass
316
+ return {"error": "AI Auto targeted the large news headline banner. To keep the banner text intact, please use 'Manual' mode and circle just the corner logo directly!"}
317
+
318
+ # πŸ›‘οΈ SHIELD 2: Central Video Content Guard (Rejects center subtitles/captions)
319
+ if (vid_h * 0.25) < temp_y < (vid_h * 0.75) and (vid_w * 0.20) < temp_x < (vid_w * 0.80):
320
+ print("⚠️ Intercepted: AI detected text within the primary central video tracking zone.")
321
+ try: os.remove(input_filename)
322
+ except: pass
323
+ return {"error": "AI Auto detected text elements in the center of the screen. Please switch to 'Manual' mode to specify your watermark area!"}
324
+
325
+ # If the coordinates pass both safety filters, assign them smoothly!
326
+ x, y, w, h = temp_x, temp_y, temp_w, temp_h
327
+ print(f"βœ… Auto coordinates cleared security check: x={x}, y={y}, w={w}, h={h}")
328
+ else:
329
+ print("❌ EasyOCR scan returned empty array fields.")
330
+ try: os.remove(input_filename)
331
+ except: pass
332
+ return {"error": "AI Auto could not detect any explicit text watermarks in the corners. Please use Manual Mode to draw your selection box."}
333
+
334
+ # 3. Core Engine Execution (Safe, flawless rendering path)
335
+ success = main_video.remove_watermark_pro(
336
+ input_path=input_filename,
337
+ output_path=output_filename,
338
+ x=x, y=y, w=w, h=h,
339
+ mode=backend_execution_mode
340
+ )
341
+
342
+ if not success:
343
+ return {"error": "Video processing execution encountered a system error."}
344
+
345
+ # 4. Production Cloud Delivery & Cleanup
346
+ print("☁️ Uploading clean asset matrix to Cloudflare R2...")
347
+ try:
348
+ with open(output_filename, 'rb') as clean_file:
349
+ s3.put_object(
350
+ Bucket=bucket_name,
351
+ Key=f'processed_videos/clean_vid_{file.filename}',
352
+ Body=clean_file
353
+ )
354
+ except Exception as e:
355
+ print(f"Cloudflare infrastructure bypass (Local Fallback Triggered): {e}")
356
+
357
+ try:
358
+ os.remove(input_filename)
359
+ except Exception as e:
360
+ print(f"File retention warning: {e}")
361
+
362
+ if not is_pro:
363
+ db.deduct_credit(user_id)
364
+
365
+ return {"message": "Success!", "file_name": f"clean_vid_{file.filename}"}
366
+
367
+ @app.post("/api/remove-bg")
368
+ @limiter.limit("5/minute")
369
+ async def process_background(
370
+ request: Request,
371
+ file: UploadFile = File(...),
372
+ bg_color: Optional[str] = Form(None),
373
+ bg_image: Optional[UploadFile] = File(None) ,
374
+ user_id: str = Form(...)
375
+ ):
376
+ # 1. Grab the REAL user_id from the frontend (Replace hardcoded string later!)
377
+
378
+ user_data = db.get_or_create_user(user_id)
379
+
380
+ # 2. Safety Check: If Firebase returns NOTHING, stop gracefully!
381
+ if not user_data:
382
+ print(f"🚨 Blocked: User {user_id} not found in Firebase!")
383
+ raise HTTPException(status_code=401, detail="User profile not found. Please log in securely.")
384
+
385
+ # 3. Use the NEW Firebase vocabulary
386
+ is_pro = user_data.get("isProUser", False)
387
+ credits_left = user_data.get("free_credits", 0)
388
+
389
+ # 4. The Paywall Logic
390
+ if not is_pro and credits_left <= 0:
391
+ print(f"🚨 Blocked: User {user_id} is out of credits!")
392
+ # 402 Payment Required is the perfect status code here!
393
+ raise HTTPException(status_code=402, detail="PaywallTrigger: Daily limit reached. Upgrade to Pro.")
394
+
395
+ # --- YOUR EXISTING LOGIC STARTS HERE ---
396
+ input_filename = f"temp_bg_{file.filename}"
397
+ base_name = os.path.splitext(file.filename)[0]
398
+
399
+ transparent_filename = f"nobg_{base_name}.png"
400
+ pro_output_filename = f"pro_bg_{base_name}.jpg"
401
+
402
+ with open(input_filename, "wb") as buffer:
403
+ buffer.write(await file.read())
404
+
405
+ print(f"🧽 Removing background from {input_filename}...")
406
+
407
+ success = bg_remove.remove_background_web(input_path=input_filename, output_path=transparent_filename)
408
+ if not success:
409
+ return {"error": "Background removal failed"}
410
+
411
+ file_to_upload = transparent_filename
412
+ content_type = 'image/png'
413
+
414
+ if bg_color or bg_image:
415
+ custom_bg_path = None
416
+ if bg_image:
417
+ custom_bg_path = f"temp_custom_bg_{bg_image.filename}"
418
+ with open(custom_bg_path, "wb") as buffer:
419
+ buffer.write(await bg_image.read())
420
+
421
+ print("🎨 Applying Pro Background...")
422
+ pro_success = bg_remove.apply_pro_background(
423
+ transparent_filename, pro_output_filename,
424
+ bg_color_hex=bg_color, bg_image_path=custom_bg_path
425
+ )
426
+ if pro_success:
427
+ file_to_upload = pro_output_filename
428
+ content_type = 'image/jpeg'
429
+
430
+ if custom_bg_path and os.path.exists(custom_bg_path):
431
+ os.remove(custom_bg_path)
432
+
433
+ shutil.copy(file_to_upload, f"downloads/{file_to_upload}")
434
+
435
+ print(f"☁️ Uploading {file_to_upload} to Cloudflare...")
436
+ with open(file_to_upload, 'rb') as final_file:
437
+ s3.put_object(
438
+ Bucket=bucket_name,
439
+ Key=f'processed_backgrounds/{file_to_upload}',
440
+ Body=final_file,
441
+ ContentType=content_type
442
+ )
443
+
444
+ if os.path.exists(input_filename): os.remove(input_filename)
445
+ if os.path.exists(transparent_filename): os.remove(transparent_filename)
446
+ if os.path.exists(pro_output_filename): os.remove(pro_output_filename)
447
+
448
+
449
+ # πŸ’° 5. DEDUCT CREDIT: The job was successful, subtract 1 credit!
450
+ # If they are NOT a pro user, deduct a credit. (Pro users don't lose credits)
451
+ if not is_pro:
452
+ db.deduct_credit(user_id)
453
+ print(f"πŸ’Έ Credit deducted! Remaining: {credits_left - 1}")
454
+
455
+ return {"message": "Success!", "file_name": file_to_upload}
456
+
457
+ @app.post("/api/add-custom-logo")
458
+ @limiter.limit("5/minute")
459
+ async def process_add_logo(
460
+ request: Request,
461
+ video_file: UploadFile = File(...),
462
+ logo_file: UploadFile = File(...),
463
+ x: int = Form(...),
464
+ y: int = Form(...),
465
+ logo_w: int = Form(...),
466
+ logo_h: int = Form(100),
467
+ user_id: str = Form(...) # 🌟 1. Added the Catching Mitt!
468
+ ):
469
+ # ❌ 2. Deleted the hardcoded admin_user_1!
470
+
471
+ user_data = db.get_or_create_user(user_id)
472
+
473
+ # 2. Safety Check: If Firebase returns NOTHING, stop gracefully!
474
+ if not user_data:
475
+ print(f"🚨 Blocked: User {user_id} not found in Firebase!")
476
+ raise HTTPException(status_code=401, detail="User profile not found. Please log in securely.")
477
+
478
+ # 3. Use the NEW Firebase vocabulary
479
+ is_pro = user_data.get("isProUser", False)
480
+ credits_left = user_data.get("free_credits", 0)
481
+
482
+ # 4. The Paywall Logic
483
+ if not is_pro and credits_left <= 0:
484
+ print(f"🚨 Blocked: User {user_id} is out of credits!")
485
+ # 402 Payment Required is the perfect status code here!
486
+ raise HTTPException(status_code=402, detail="PaywallTrigger: Daily limit reached. Upgrade to Pro.")
487
+
488
+ # 1. πŸ“‚ THE FOLDER FIX: Save everything directly into the 'downloads' folder
489
+ input_video = f"downloads/temp_vid_{video_file.filename}"
490
+ input_logo = f"downloads/temp_logo_{logo_file.filename}"
491
+ output_video = f"downloads/watermarked_{video_file.filename}"
492
+
493
+ # Save both uploaded files directly to the safe folder
494
+ with open(input_video, "wb") as buffer:
495
+ buffer.write(await video_file.read())
496
+
497
+ with open(input_logo, "wb") as buffer:
498
+ buffer.write(await logo_file.read())
499
+
500
+ print(f"πŸ“Œ Stamping logo onto {input_video}...")
501
+
502
+ # 2. Run your logo logic
503
+ success = add_logo.add_user_controlled_logo(
504
+ video_path=input_video,
505
+ logo_path=input_logo,
506
+ output_path=output_video,
507
+ x=x, y=y, logo_w=logo_w, logo_h=logo_h
508
+ )
509
+
510
+ if not success:
511
+ return {"error": "Failed to add logo to video"}
512
+
513
+ # 3. THE CLOUD FIX: Wrap in a try/except so local tests don't crash without Wi-Fi
514
+ print("☁️ Uploading watermarked video to Cloudflare...")
515
+ try:
516
+ with open(output_video, 'rb') as clean_file:
517
+ s3.put_object(
518
+ Bucket=bucket_name,
519
+ Key=f'processed_videos/watermarked_{video_file.filename}',
520
+ Body=clean_file
521
+ )
522
+ except Exception as e:
523
+ print(f"Cloudflare upload skipped (Local Mode): {e}")
524
+
525
+ # 4. πŸ›‘ THE DELETE FIX: Clean up inputs, but KEEP the output video!
526
+ try:
527
+ os.remove(input_video)
528
+ os.remove(input_logo)
529
+ # os.remove(output_video) <-- Commented out so the frontend can play it!
530
+ except Exception as e:
531
+ print(f"Cleanup issue: {e}")
532
+
533
+ # Send JUST the final file name back to React
534
+ if not is_pro:
535
+ db.deduct_credit(user_id)
536
+ print(f"πŸ’Έ Credit deducted! Remaining: {credits_left - 1}")
537
+
538
+ return {"message": "Success!", "file_name": f"watermarked_{video_file.filename}"}
539
+
540
+ @app.post("/api/enhance-photo")
541
+ @limiter.limit("5/minute")
542
+ async def process_enhance_photo(
543
+ request: Request,
544
+ file: UploadFile = File(...),
545
+ factor: int = Form(4),
546
+ face_restoration: str = Form("true"),
547
+ color_correction: str = Form("false"),
548
+ user_id: str = Form(...) # 🌟 1. Added the Catching Mitt!
549
+ ):
550
+ # ❌ 2. Deleted the hardcoded admin_user_1!
551
+
552
+ user_data = db.get_or_create_user(user_id)
553
+
554
+ # 2. Safety Check: If Firebase returns NOTHING, stop gracefully!
555
+ if not user_data:
556
+ print(f"🚨 Blocked: User {user_id} not found in Firebase!")
557
+ raise HTTPException(status_code=401, detail="User profile not found. Please log in securely.")
558
+
559
+ # 3. Use the NEW Firebase vocabulary
560
+ is_pro = user_data.get("isProUser", False)
561
+ credits_left = user_data.get("free_credits", 0)
562
+
563
+ # 4. The Paywall Logic
564
+ if not is_pro and credits_left <= 0:
565
+ print(f"🚨 Blocked: User {user_id} is out of credits!")
566
+ raise HTTPException(status_code=402, detail="PaywallTrigger: Daily limit reached. Upgrade to Pro.")
567
+
568
+ # 1. πŸ“‚ THE FOLDER FIX: Save directly into the safe 'downloads' folder
569
+ input_filename = f"downloads/temp_enhance_{file.filename}"
570
+ output_filename = f"downloads/enhanced_{file.filename}"
571
+
572
+ # Save uploaded photo locally
573
+ with open(input_filename, "wb") as buffer:
574
+ buffer.write(await file.read())
575
+
576
+ print(f"✨ Enhancing photo {input_filename} | Factor: {factor}x | Face: {face_restoration} | Color: {color_correction}")
577
+
578
+ # 2. Run your OpenCV enhancement logic
579
+ # We pass all the React UI settings directly to your engine!
580
+ success = enhance_photo.enhance_photo_web(
581
+ input_path=input_filename,
582
+ output_path=output_filename,
583
+ factor=factor,
584
+ face_restoration=(face_restoration == "true"),
585
+ color_correction=(color_correction == "false")
586
+ )
587
+
588
+ if not success:
589
+ return {"error": "Photo enhancement failed"}
590
+
591
+ # 3. ☁️ THE CLOUD FIX: Wrap in try/except so local tests don't crash
592
+ print("☁️ Uploading enhanced photo to Cloudflare...")
593
+ try:
594
+ with open(output_filename, 'rb') as clean_file:
595
+ s3.put_object(
596
+ Bucket=bucket_name,
597
+ Key=f'processed_enhancements/enhanced_{file.filename}',
598
+ Body=clean_file
599
+ )
600
+ except Exception as e:
601
+ print(f"Cloudflare upload skipped (Local Mode): {e}")
602
+
603
+ # 4. πŸ›‘ THE DELETE FIX: Keep the output file alive!
604
+ try:
605
+ os.remove(input_filename)
606
+ # os.remove(output_filename) <-- Commented out so frontend can show it!
607
+ except Exception as e:
608
+ print(f"Cleanup issue: {e}")
609
+
610
+ # 5. THE DOUBLE-DOWNLOAD FIX: Send ONLY the file name back to React
611
+ if not is_pro:
612
+ db.deduct_credit(user_id)
613
+ print(f"πŸ’Έ Credit deducted! Remaining: {credits_left - 1}")
614
+
615
+ return {"message": "Success!", "file_name": f"enhanced_{file.filename}"}
616
+
617
+ @app.post("/api/enhance-video")
618
+ @limiter.limit("5/minute") # <-- 1. The Bouncer: Only 5 downloads per minute!
619
+ async def process_enhance_video(
620
+ request: Request,
621
+ file: UploadFile = File(...),
622
+ # πŸ›‘οΈ THE SHIELD: Catch the UI settings so React doesn't crash!
623
+ resolution: str = Form("1080p FHD"),
624
+ fps_60: str = Form("true"),
625
+ denoise: str = Form("true"),
626
+ user_id: str = Form(...) # 🌟 1. Added the Catching Mitt!
627
+ ):
628
+ # ❌ 2. Deleted the hardcoded admin_user_1!
629
+
630
+ user_data = db.get_or_create_user(user_id)
631
+
632
+ # 2. Safety Check: If Firebase returns NOTHING, stop gracefully!
633
+ if not user_data:
634
+ print(f"🚨 Blocked: User {user_id} not found in Firebase!")
635
+ raise HTTPException(status_code=401, detail="User profile not found. Please log in securely.")
636
+
637
+ # 3. Use the NEW Firebase vocabulary
638
+ is_pro = user_data.get("isProUser", False)
639
+ credits_left = user_data.get("free_credits", 0)
640
+
641
+ # 4. The Paywall Logic
642
+ if not is_pro and credits_left <= 0:
643
+ print(f"🚨 Blocked: User {user_id} is out of credits!")
644
+ raise HTTPException(status_code=402, detail="PaywallTrigger: Daily limit reached. Upgrade to Pro.")
645
+
646
+ # 1. πŸ“‚ THE FOLDER FIX: Force the engine to save inside the 'downloads' folder
647
+ input_filename = f"downloads/temp_enhvid_{file.filename}"
648
+ output_filename = f"downloads/enhanced_{file.filename}"
649
+
650
+ # Save uploaded video locally
651
+ with open(input_filename, "wb") as buffer:
652
+ buffer.write(await file.read())
653
+
654
+ print(f"⚑ Enhancing video {input_filename} in Turbo Mode... (UI Settings Caught but using Turbo Override)")
655
+
656
+ # Run your smart MoviePy logic (Turbo Mode)
657
+ success = enhance_video.enhance_video_smartly(
658
+ input_path=input_filename,
659
+ output_path=output_filename
660
+ )
661
+
662
+ if not success:
663
+ return {"error": "Video enhancement failed"}
664
+
665
+ # Upload the brightened video to Cloudflare
666
+ print("☁️ Uploading enhanced video to Cloudflare...")
667
+ try:
668
+ with open(output_filename, 'rb') as clean_file:
669
+ s3.put_object(
670
+ Bucket=bucket_name,
671
+ Key=f'processed_videos/enhanced_{file.filename}',
672
+ Body=clean_file
673
+ )
674
+ except Exception as e:
675
+ print(f"Cloudflare upload skipped: {e}")
676
+
677
+ # 2. πŸ›‘ THE DELETE FIX: Keep the output file alive!
678
+ try:
679
+ os.remove(input_filename) # We can safely delete the temporary input video
680
+ except Exception:
681
+ pass
682
+
683
+ # Send JUST the file name back to the website so it knows what to look for
684
+ if not is_pro:
685
+ db.deduct_credit(user_id)
686
+ print(f"πŸ’Έ Credit deducted! Remaining: {credits_left - 1}")
687
+
688
+ return {"message": "Success!", "file_name": f"enhanced_{file.filename}"}
689
+
690
+ @app.post("/api/clipcut-pro")
691
+ @limiter.limit("5/minute") # <-- 1. The Bouncer: Only 5 downloads per minute!
692
+ async def process_clipcut(
693
+ request: Request,
694
+ file: UploadFile = File(...),
695
+ start_time: str = Form("00:00:00"),
696
+ end_time: str = Form("00:00:10"),
697
+ text: str = Form("VaniConnect AI"),
698
+ user_id: str = Form(...) # 🌟 1. Added the Catching Mitt!
699
+ ):
700
+ # ❌ 2. Deleted the hardcoded admin_user_1!
701
+
702
+ user_data = db.get_or_create_user(user_id)
703
+
704
+ # 2. Safety Check: If Firebase returns NOTHING, stop gracefully!
705
+ if not user_data:
706
+ print(f"🚨 Blocked: User {user_id} not found in Firebase!")
707
+ raise HTTPException(status_code=401, detail="User profile not found. Please log in securely.")
708
+
709
+ # 3. Use the NEW Firebase vocabulary
710
+ is_pro = user_data.get("isProUser", False)
711
+ credits_left = user_data.get("free_credits", 0)
712
+
713
+ # 4. The Paywall Logic
714
+ if not is_pro and credits_left <= 0:
715
+ print(f"🚨 Blocked: User {user_id} is out of credits!")
716
+ raise HTTPException(status_code=402, detail="PaywallTrigger: Daily limit reached. Upgrade to Pro.")
717
+
718
+ input_filename = f"downloads/temp_clip_{file.filename}"
719
+ trimmed_filename = f"downloads/trimmed_{file.filename}"
720
+ final_filename = f"downloads/final_clipcut_{file.filename}"
721
+
722
+ # 1. Save uploaded video locally
723
+ with open(input_filename, "wb") as buffer:
724
+ buffer.write(await file.read())
725
+
726
+ print(f"βœ‚οΈ 1/2: Trimming video from {start_time} to {end_time}...")
727
+
728
+ # 2. Run trimming function (MoviePy understands HH:MM:SS perfectly!)
729
+ trim_success = trim_video.trim_video(
730
+ input_path=input_filename,
731
+ output_path=trimmed_filename,
732
+ start_sec=start_time,
733
+ end_sec=end_time
734
+ )
735
+
736
+ if not trim_success:
737
+ return {"error": "Video trimming failed. Make sure Start Time is before End Time!"}
738
+
739
+ print(f"✍️ 2/2: Adding professional text overlay: '{text}'...")
740
+
741
+ # 3. Add text overlay
742
+ text_success = trim_video.add_professional_text(
743
+ input_path=trimmed_filename,
744
+ output_path=final_filename,
745
+ text=text
746
+ )
747
+
748
+ if not text_success:
749
+ return {"error": "Text overlay failed"}
750
+
751
+ # 4. Upload to Cloudflare (optional cloud step)
752
+ print("☁️ Uploading ClipCut Pro video to Cloudflare...")
753
+ try:
754
+ with open(final_filename, 'rb') as clean_file:
755
+ s3.put_object(
756
+ Bucket=bucket_name,
757
+ Key=f'processed_videos/{final_filename}',
758
+ Body=clean_file
759
+ )
760
+ except Exception as e:
761
+ print(f"Cloud upload skipped: {e}")
762
+
763
+ # 5. Clean up local files
764
+ try:
765
+ os.remove(input_filename)
766
+ os.remove(trimmed_filename)
767
+ except Exception:
768
+ pass
769
+
770
+ # 🌟 3. Fixed the indentation here!
771
+ if not is_pro:
772
+ db.deduct_credit(user_id)
773
+ print(f"πŸ’Έ Credit deducted! Remaining: {credits_left - 1}")
774
+
775
+ return {"message": "Success!", "file_name": f"final_clipcut_{file.filename}"}
776
+
777
+
778
+ @app.get("/test-db")
779
+ def test_database():
780
+ user_data = db.get_or_create_user("ceo@vaniconnect.com")
781
+ return {"message": "Database is working perfectly!", "user_data": user_data}
auto_detect.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import easyocr
3
+ import os
4
+
5
+ # Initialize the EasyOCR reader
6
+ reader = easyocr.Reader(['mr', 'hi', 'en'], gpu=False)
7
+
8
+ def find_text_watermark(file_path):
9
+ """
10
+ Smartly reads an image OR grabs the first frame of a video,
11
+ finds text, and returns x, y, w, h.
12
+ """
13
+ print(f"πŸ€– AI Auto: Scanning {file_path} for text watermarks...")
14
+
15
+ # 1. SMART CHECK: Is this a video or a photo?
16
+ is_video = file_path.lower().endswith(('.mp4', '.mov', '.avi'))
17
+
18
+ if is_video:
19
+ print("🎬 Video detected! Extracting first frame...")
20
+ cap = cv2.VideoCapture(file_path)
21
+ success, img = cap.read()
22
+ cap.release()
23
+ if not success:
24
+ print("❌ Could not read video frame.")
25
+ return None
26
+ else:
27
+ print("πŸ“Έ Photo detected! Reading image...")
28
+ img = cv2.imread(file_path)
29
+ if img is None:
30
+ print("❌ Could not read the image file.")
31
+ return None
32
+
33
+ # 2. Ask EasyOCR to find text in the frame/image
34
+ results = reader.readtext(img, detail=1)
35
+
36
+ if not results:
37
+ print("❌ AI Auto could not find any text.")
38
+ return None
39
+
40
+ # 3. Find the biggest/most confident text box
41
+ best_box = results[0][0]
42
+
43
+ x1 = int(best_box[0][0])
44
+ y1 = int(best_box[0][1])
45
+ x2 = int(best_box[2][0])
46
+ y2 = int(best_box[2][1])
47
+
48
+ # 4. Calculate x, y, w, h with padding
49
+ x = x1
50
+ y = y1
51
+ w = x2 - x1
52
+ h = y2 - y1
53
+
54
+ padding = 50
55
+ x = max(0, x - padding)
56
+ y = max(0, y - padding)
57
+ w = w + (padding * 2)
58
+ h = h + (padding * 2)
59
+
60
+ print(f"🎯 AI Auto Found Target! Coordinates: x={x}, y={y}, w={w}, h={h}")
61
+ return {"x": x, "y": y, "w": w, "h": h}
bg_remove.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from rembg import remove, new_session
2
+ from PIL import Image
3
+ import os
4
+
5
+ def remove_background_web(input_path, output_path):
6
+ try:
7
+ # 1. Open the image and force it into RGBA (Red, Green, Blue, Alpha) mode
8
+ img = Image.open(input_path).convert("RGBA")
9
+
10
+ # 🌟 THE COLOR FIX: Save the original vibrant color profile!
11
+ icc_profile = img.info.get('icc_profile')
12
+
13
+ # --- THE BULLETPROOF RAM FIX ---
14
+ img.thumbnail((2048, 2048), Image.Resampling.LANCZOS)
15
+
16
+ my_session = new_session("isnet-general-use")
17
+
18
+ # 2. Ask the AI to ONLY generate the cut-out mask (Black & White silhouette)
19
+ print("🧠 AI generating precision mask...")
20
+ mask = remove(
21
+ img,
22
+ session=my_session,
23
+ only_mask=True, # 🌟 THE SECRET SAUCE: Do not let AI touch the colors!
24
+ alpha_matting=True,
25
+ alpha_matting_foreground_threshold=200,
26
+ alpha_matting_background_threshold=15,
27
+ alpha_matting_erode_size=5
28
+ )
29
+
30
+ # 3. Stamp the AI's mask onto our untouched, beautifully colored original image!
31
+ img.putalpha(mask)
32
+
33
+ # 4. Save the final PNG, injecting the original color profile back inside
34
+ print("πŸ’Ύ Saving with 100% original color accuracy...")
35
+ img.save(output_path, format="PNG", icc_profile=icc_profile)
36
+
37
+ return True
38
+ except Exception as e:
39
+ print(f"🚨 Background Removal Failed: {e}")
40
+ return False
41
+
42
+ # ==========================================
43
+ # 🌟 PRO TIER: BACKGROUND REPLACER
44
+ # ==========================================
45
+ def apply_pro_background(transparent_img_path, output_path, bg_color_hex=None, bg_image_path=None):
46
+ """ Takes a transparent PNG and adds a solid color or custom image behind it. """
47
+ try:
48
+ fg = Image.open(transparent_img_path).convert("RGBA")
49
+
50
+ if bg_image_path:
51
+ bg = Image.open(bg_image_path).convert("RGBA")
52
+ bg = bg.resize(fg.size, Image.Resampling.LANCZOS)
53
+ bg.paste(fg, (0, 0), fg)
54
+ bg.convert("RGB").save(output_path, format="JPEG")
55
+ return True
56
+
57
+ elif bg_color_hex:
58
+ bg = Image.new("RGBA", fg.size, bg_color_hex)
59
+ bg.paste(fg, (0, 0), fg)
60
+ bg.convert("RGB").save(output_path, format="JPEG")
61
+ return True
62
+
63
+ return False
64
+ except Exception as e:
65
+ print(f"🚨 Pro Background Error: {e}")
66
+ return False
db.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import firebase_admin
2
+ import os
3
+ import json
4
+ from firebase_admin import credentials, firestore
5
+
6
+ # 1. Connect to the Firebase Vault using your Master Key
7
+ # (Ensure your 'firebase_key.json' file is inside the same folder as this file!)
8
+ firebase_secret = os.environ.get("FIREBASE_KEY")
9
+ cred = credentials.Certificate(json.loads(firebase_secret))
10
+
11
+ # Prevent the server from crashing if it reloads and tries to initialize twice
12
+ if not firebase_admin._apps:
13
+ firebase_admin.initialize_app(cred)
14
+
15
+ # 2. Get the database instance
16
+ db = firestore.client()
17
+
18
+ def get_user(user_uid):
19
+ """Fetches the user from Firebase to check their credits"""
20
+ user_ref = db.collection('users').document(user_uid)
21
+ doc = user_ref.get()
22
+
23
+ if doc.exists:
24
+ return doc.to_dict()
25
+ else:
26
+ return None
27
+
28
+ # We keep this old function name so your app.py doesn't immediately crash!
29
+ def get_or_create_user(user_uid):
30
+ return get_user(user_uid)
31
+
32
+ def deduct_credit(user_uid):
33
+ """Subtracts 1 credit from the user after a successful AI generation"""
34
+ user_ref = db.collection('users').document(user_uid)
35
+ user_ref.update({
36
+ "free_credits": firestore.Increment(-1)
37
+ })
enhance_photo.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ from cv2 import dnn_superres
3
+ import numpy as np
4
+
5
+ def enhance_photo_web(input_path, output_path, factor=4, face_restoration=True, color_correction=True):
6
+ try:
7
+ print(f"🧠 AI Engine starting: Reading {input_path}")
8
+ img = cv2.imread(input_path)
9
+
10
+ if img is None:
11
+ return False
12
+
13
+ # 1. THE RELAXED MEMORY SHIELD
14
+ max_dim = 1600
15
+ height, width = img.shape[:2]
16
+ if max(height, width) > max_dim:
17
+ scale = max_dim / max(height, width)
18
+ img = cv2.resize(img, (int(width * scale), int(height * scale)), interpolation=cv2.INTER_LANCZOS4)
19
+
20
+ # 🌟 2. SAFE COLOR CORRECTION
21
+ if color_correction:
22
+ # We gently lift shadows without crushing the blacks or muddying the image
23
+ lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
24
+ l, a, b = cv2.split(lab)
25
+
26
+ # Lowered the clipLimit to 1.2. This prevents the "dirty" look on selfies!
27
+ clahe = cv2.createCLAHE(clipLimit=1.2, tileGridSize=(8,8))
28
+ cl = clahe.apply(l)
29
+
30
+ limg = cv2.merge((cl,a,b))
31
+ img = cv2.cvtColor(limg, cv2.COLOR_LAB2BGR)
32
+
33
+ # (Notice I completely removed the cv2.detailEnhance line that was ruining it!)
34
+
35
+ # 3. FAST AI UPSCALING (FSRCNN)
36
+ print("⚑ Booting up FSRCNN Neural Network...")
37
+ sr = dnn_superres.DnnSuperResImpl_create()
38
+ model_path = "FSRCNN_x4.pb"
39
+ sr.readModel(model_path)
40
+ sr.setModel("fsrcnn", 4)
41
+
42
+ result = sr.upsample(img)
43
+
44
+ # ✨ 4. CLEAN SHARPENING
45
+ if face_restoration:
46
+ print("✨ Applying clean unsharp mask...")
47
+ # The aggressive matrix made noisy photos look terrible.
48
+ # This Gaussian approach is much safer for skin and faces.
49
+ gaussian_blur = cv2.GaussianBlur(result, (5, 5), 0)
50
+ result = cv2.addWeighted(result, 1.5, gaussian_blur, -0.5, 0)
51
+
52
+ cv2.imwrite(output_path, result)
53
+ print(f"βœ… SUCCESS: Photo enhanced cleanly!")
54
+ return True
55
+
56
+ except Exception as e:
57
+ print(f"❌ PYTHON AI ERROR: {str(e)}")
58
+ return False
enhance_video.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from moviepy.editor import VideoFileClip
2
+ import cv2
3
+ import numpy as np
4
+
5
+ def enhance_video_smartly(input_path, output_path, resolution="1080p FHD", fps_60=True, denoise=True):
6
+ """Turbo Mode: Video Enhancement matched to your React UI!"""
7
+ try:
8
+ print(f"🎬 Starting Video Engine | Res: {resolution} | 60FPS: {fps_60} | Denoise: {denoise}")
9
+
10
+ # 1. Load the video
11
+ clip = VideoFileClip(input_path)
12
+
13
+ # 2. RESOLUTION UPGRADE
14
+ # Warning: True 4K on a CPU takes hours. For MVP, we use MoviePy's fast resize.
15
+ if resolution == "4K UHD":
16
+ print("πŸ“Ί Scaling to 4K UHD...")
17
+ clip = clip.resize(height=2160)
18
+ else:
19
+ print("πŸ“Ί Scaling to 1080p FHD...")
20
+ # If the video is already 1080p, we skip resizing to save massive processing time!
21
+ if clip.h < 1080:
22
+ clip = clip.resize(height=1080)
23
+
24
+ # 3. FPS INTERPOLATION
25
+ target_fps = clip.fps
26
+ if fps_60:
27
+ print("🎞️ Upconverting to 60 FPS...")
28
+ target_fps = 60
29
+ # MoviePy will automatically duplicate/blend frames to hit 60fps!
30
+
31
+ # 4. FRAME-BY-FRAME AI MATH
32
+ def process_frame(frame):
33
+ # Boost contrast slightly so it pops
34
+ enhanced = cv2.convertScaleAbs(frame, alpha=1.1, beta=5)
35
+
36
+ # If Denoise is ON, apply a Bilateral Filter (Removes grain, keeps edges sharp)
37
+ if denoise:
38
+ enhanced = cv2.bilateralFilter(enhanced, d=5, sigmaColor=25, sigmaSpace=25)
39
+
40
+ return enhanced
41
+
42
+ # Apply the visual filters
43
+ print("βš™οΈ Processing frames... (This will take some time on a CPU!)")
44
+ final_clip = clip.fl_image(process_frame)
45
+
46
+ # 5. Export
47
+ final_clip.write_videofile(
48
+ output_path,
49
+ codec='libx264',
50
+ audio_codec='aac',
51
+ fps=target_fps, # Force the new FPS here!
52
+ logger=None
53
+ )
54
+
55
+ # Free up computer RAM
56
+ clip.close()
57
+ final_clip.close()
58
+
59
+ print("βœ… SUCCESS: Video Enhanced!")
60
+ return True
61
+
62
+ except Exception as e:
63
+ print(f"🚨 Enhancement Error: {e}")
64
+ return False
main_photo.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ import os
4
+
5
+ def remove_photo_watermark_web(input_path, output_path, x, y, w, h, style="Standard AI Inpaint"):
6
+ if not os.path.exists(input_path):
7
+ return False
8
+
9
+ img = cv2.imread(input_path)
10
+ if img is None:
11
+ return False
12
+
13
+ img_h, img_w = img.shape[:2]
14
+
15
+ # Safety check: Keep the coordinates inside the image
16
+ x = max(0, min(x, img_w - 1))
17
+ y = max(0, min(y, img_h - 1))
18
+ w = max(1, min(w, img_w - x))
19
+ h = max(1, min(h, img_h - y))
20
+
21
+ if "Heavy Blur" in style:
22
+ roi = img[y:y+h, x:x+w]
23
+ k_size = max(15, min(w, h) // 2)
24
+ if k_size % 2 == 0: k_size += 1
25
+ img[y:y+h, x:x+w] = cv2.GaussianBlur(roi, (k_size, k_size), 0)
26
+
27
+ else:
28
+ # THE MARGIN TRICK: We grab an extra 30 pixels of background around the logo
29
+ margin = 30
30
+ y1 = max(0, y - margin)
31
+ y2 = min(img_h, y + h + margin)
32
+ x1 = max(0, x - margin)
33
+ x2 = min(img_w, x + w + margin)
34
+
35
+ roi = img[y1:y2, x1:x2]
36
+ mask = np.zeros(roi.shape[:2], dtype=np.uint8)
37
+
38
+ local_y = y - y1
39
+ local_x = x - x1
40
+
41
+ # We tell the AI EXACTLY where the logo is inside this bordered box
42
+ cv2.rectangle(mask, (local_x, local_y), (local_x + w, local_y + h), 255, -1)
43
+
44
+ if "Smart Text Eraser" in style:
45
+ gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
46
+ _, text_mask = cv2.threshold(gray, 180, 255, cv2.THRESH_BINARY)
47
+ final_mask = cv2.bitwise_and(text_mask, mask)
48
+ kernel = np.ones((3,3), np.uint8)
49
+ final_mask = cv2.dilate(final_mask, kernel, iterations=1)
50
+ cleaned_roi = cv2.inpaint(roi, final_mask, 3, cv2.INPAINT_TELEA)
51
+ else:
52
+ cleaned_roi = cv2.inpaint(roi, mask, 3, cv2.INPAINT_TELEA)
53
+
54
+ # Paste the cleaned area perfectly back into the image
55
+ img[y1:y2, x1:x2] = cleaned_roi
56
+
57
+ cv2.imwrite(output_path, img)
58
+ return True
main_video.py ADDED
@@ -0,0 +1,216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ import os
4
+ import shutil
5
+ import subprocess
6
+ import urllib.request
7
+ import onnxruntime as ort
8
+ from moviepy.editor import VideoFileClip
9
+
10
+ # πŸš€ INITIALIZE THE LAMA AI ENGINE GLOBALLY
11
+ MODEL_PATH = os.path.join(os.path.dirname(__file__), "models", "big-lama.onnx")
12
+
13
+ if not os.path.exists(MODEL_PATH):
14
+ print("πŸ“₯ AI Model file not found. Auto-downloading from production mirror...")
15
+ os.makedirs(os.path.dirname(MODEL_PATH), exist_ok=True)
16
+ url = "https://huggingface.co/Carve/LaMa-ONNX/resolve/main/lama_fp32.onnx"
17
+ try:
18
+ req = urllib.request.Request(
19
+ url,
20
+ headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'}
21
+ )
22
+ with urllib.request.urlopen(req) as response, open(MODEL_PATH, 'wb') as out_file:
23
+ shutil.copyfileobj(response, out_file)
24
+ print("βœ… AI Model downloaded successfully!")
25
+ except Exception as download_err:
26
+ print(f"🚨 Mirror download failed ({download_err}).")
27
+
28
+ try:
29
+ if os.path.exists(MODEL_PATH):
30
+ opts = ort.SessionOptions()
31
+ opts.intra_op_num_threads = 1
32
+ opts.inter_op_num_threads = 1
33
+ opts.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL
34
+
35
+ providers = ['CUDAExecutionProvider', 'CPUExecutionProvider']
36
+ ort_session = ort.InferenceSession(MODEL_PATH, sess_options=opts, providers=providers)
37
+ print("πŸš€ LaMa AI Inpainting Engine Initialized Safely!")
38
+ else:
39
+ ort_session = None
40
+ except Exception as e:
41
+ print(f"⚠️ ONNX Session initialization failed ({e}). Falling back to classical.")
42
+ ort_session = None
43
+
44
+
45
+ def remove_watermark_pro(input_path, output_path, x, y, w, h, mode="fast"):
46
+ """
47
+ Video Watermark Removal with adaptive dominant-color patching for seamless graphics removal.
48
+ """
49
+ global ort_session
50
+
51
+ base_name = os.path.basename(input_path)
52
+ normalized_input = f"normalized_{base_name}.mp4"
53
+
54
+ try:
55
+ conversion_cmd = [
56
+ "ffmpeg", "-y", "-i", input_path,
57
+ "-vcodec", "libx264", "-acodec", "aac",
58
+ "-pix_fmt", "yuv420p", "-preset", "ultrafast",
59
+ normalized_input
60
+ ]
61
+ subprocess.run(conversion_cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
62
+ processing_source = normalized_input
63
+ except Exception:
64
+ processing_source = input_path
65
+
66
+ cap = cv2.VideoCapture(processing_source)
67
+ fps = cap.get(cv2.CAP_PROP_FPS)
68
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
69
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
70
+
71
+ width = width if width % 2 == 0 else width - 1
72
+ height = height if height % 2 == 0 else height - 1
73
+
74
+ temp_output = f"temp_pro_{base_name}"
75
+ fourcc = cv2.VideoWriter_fourcc(*'mp4v')
76
+ out = cv2.VideoWriter(temp_output, fourcc, fps, (width, height))
77
+
78
+ # Calculate bounded crop area coordinates for AI mode
79
+ padding = 16
80
+ x1 = max(0, int(x) - padding)
81
+ y1 = max(0, int(y) - padding)
82
+ x2 = min(width, int(x + w) + padding)
83
+ y2 = min(height, int(y + h) + padding)
84
+
85
+ crop_w = x2 - x1
86
+ crop_h = y2 - y1
87
+ if crop_w % 8 != 0: x2 = min(width, x2 + (8 - (crop_w % 8)))
88
+ if crop_h % 8 != 0: y2 = min(height, y2 + (8 - (crop_h % 8)))
89
+
90
+ img_key, mask_key = None, None
91
+ if ort_session is not None and mode == "ai":
92
+ try:
93
+ inputs = ort_session.get_inputs()
94
+ for node in inputs:
95
+ if "mask" in node.name.lower():
96
+ mask_key = node.name
97
+ else:
98
+ img_key = node.name
99
+ except Exception:
100
+ ort_session = None
101
+
102
+ frames_processed = 0
103
+ while True:
104
+ ret, frame = cap.read()
105
+ if not ret:
106
+ break
107
+
108
+ if frame.shape[1] != width or frame.shape[0] != height:
109
+ frame = cv2.resize(frame, (width, height))
110
+
111
+ mask = np.zeros(frame.shape[:2], dtype=np.uint8)
112
+ cv2.rectangle(mask, (int(x), int(y)), (int(x + w), int(y + h)), 255, -1)
113
+
114
+ if mode == "ai" and ort_session is not None and (y2 > y1 and x2 > x1):
115
+ try:
116
+ crop_frame = frame[y1:y2, x1:x2]
117
+ crop_mask = mask[y1:y2, x1:x2]
118
+ orig_h, orig_w = crop_frame.shape[:2]
119
+
120
+ crop_frame_resized = cv2.resize(crop_frame, (512, 512))
121
+ crop_mask_resized = cv2.resize(crop_mask, (512, 512))
122
+ crop_rgb = cv2.cvtColor(crop_frame_resized, cv2.COLOR_BGR2RGB)
123
+
124
+ img_tensor = crop_rgb.astype(np.float32) / 255.0
125
+ img_tensor = np.transpose(img_tensor, (2, 0, 1))[np.newaxis, ...]
126
+
127
+ mask_tensor = crop_mask_resized.astype(np.float32)
128
+ mask_tensor = np.where(mask_tensor > 0, 1.0, 0.0).astype(np.float32)
129
+ mask_tensor = mask_tensor[np.newaxis, np.newaxis, ...]
130
+
131
+ ai_outputs = ort_session.run(None, {img_key: img_tensor, mask_key: mask_tensor})
132
+
133
+ ai_output = ai_outputs[0][0]
134
+ ai_output = np.transpose(ai_output, (1, 2, 0))
135
+ ai_output = np.clip(ai_output * 255.0, 0, 255).astype(np.uint8)
136
+ clean_crop_bgr = cv2.cvtColor(ai_output, cv2.COLOR_RGB2BGR)
137
+
138
+ frame[y1:y2, x1:x2] = cv2.resize(clean_crop_bgr, (orig_w, orig_h))
139
+ except Exception:
140
+ frame = cv2.inpaint(frame, mask, 1, cv2.INPAINT_TELEA)
141
+ else:
142
+ # ⚑ HIGH-FIDELITY FAST MODE: Content-Aware Dominant Color Patching
143
+ ymin, ymax = max(0, int(y)), min(height, int(y + h))
144
+ xmin, xmax = max(0, int(x)), min(width, int(x + w))
145
+
146
+ if (ymax > ymin) and (xmax > xmin):
147
+ # Sample 4 pixels wide around the boundary of the selection box
148
+ sample_top = frame[max(0, ymin-4):ymin, xmin:xmax]
149
+ sample_bottom = frame[ymax:min(height, ymax+4), xmin:xmax]
150
+ sample_left = frame[ymin:ymax, max(0, xmin-4):xmin]
151
+ sample_right = frame[ymin:ymax, xmax:min(width, xmax+4)]
152
+
153
+ samples = []
154
+ for s in [sample_top, sample_bottom, sample_left, sample_right]:
155
+ if s.size > 0:
156
+ samples.append(s.reshape(-1, 3))
157
+
158
+ if samples:
159
+ # Use median value to completely disregard text letter colors and extract pure background tint
160
+ all_pixels = np.vstack(samples)
161
+ bg_color = np.median(all_pixels, axis=0).astype(int)
162
+
163
+ # Paint a clean, solid patch over the watermark zone matching the background perfectly
164
+ cv2.rectangle(frame, (xmin, ymin), (xmax, ymax), (int(bg_color[0]), int(bg_color[1]), int(bg_color[2])), -1)
165
+
166
+ # Smooth the local border bounds slightly so it blends organically
167
+ if xmin > 2 and xmax < width - 2 and ymin > 2 and ymax < height - 2:
168
+ roi = frame[ymin-2:ymax+2, xmin-2:xmax+2]
169
+ frame[ymin-2:ymax+2, xmin-2:xmax+2] = cv2.GaussianBlur(roi, (3, 3), 0)
170
+ else:
171
+ frame = cv2.inpaint(frame, mask, 1, cv2.INPAINT_TELEA)
172
+ else:
173
+ frame = cv2.inpaint(frame, mask, 1, cv2.INPAINT_TELEA)
174
+
175
+ out.write(frame)
176
+ frames_processed += 1
177
+
178
+ cap.release()
179
+ out.release()
180
+
181
+ if frames_processed == 0:
182
+ if os.path.exists(temp_output): os.remove(temp_output)
183
+ if os.path.exists(normalized_input): os.remove(normalized_input)
184
+ return False
185
+
186
+ try:
187
+ original_clip = VideoFileClip(processing_source)
188
+ processed_clip = VideoFileClip(temp_output)
189
+
190
+ if original_clip.audio is not None:
191
+ final_video = processed_clip.set_audio(original_clip.audio)
192
+ else:
193
+ final_video = processed_clip
194
+
195
+ final_video.write_videofile(
196
+ output_path, codec="libx264", audio_codec="aac",
197
+ preset="ultrafast", threads=1,
198
+ ffmpeg_params=["-movflags", "+faststart"], logger=None
199
+ )
200
+ original_clip.close()
201
+ processed_clip.close()
202
+ except Exception:
203
+ try:
204
+ conversion_command = [
205
+ "ffmpeg", "-y", "-i", temp_output,
206
+ "-vcodec", "libx264", "-pix_fmt", "yuv420p",
207
+ "-movflags", "+faststart", "-an", output_path
208
+ ]
209
+ subprocess.run(conversion_command, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
210
+ except Exception:
211
+ shutil.copy(temp_output, output_path)
212
+ finally:
213
+ if os.path.exists(temp_output): os.remove(temp_output)
214
+ if os.path.exists(normalized_input): os.remove(normalized_input)
215
+
216
+ return True
process_test.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from moviepy.editor import VideoFileClip
2
+
3
+ print("🎬 Starting Phase 2: Video Processing Test...")
4
+
5
+ # 1. Load the 18-second video we downloaded in Phase 1
6
+ try:
7
+ video = VideoFileClip("test_video.mp4")
8
+ except Exception as e:
9
+ print("❌ Error: Make sure test_video.mp4 is in your folder!")
10
+ exit()
11
+
12
+ # 2. Simulate "Watermark Removal" by cropping the bottom 10%
13
+ # (We will plug your exact old logic in here later!)
14
+ print("βœ‚οΈ Cropping the bottom of the video to hide watermarks...")
15
+ width, height = video.size
16
+ clean_video = video.crop(y1=0, y2=height * 0.9)
17
+
18
+ # 3. Save the new video
19
+ print("⏳ Processing... (This might take a minute, computers have to work hard for video!)")
20
+ clean_video.write_videofile("clean_video.mp4", codec="libx264", audio_codec="aac")
21
+
22
+ print("βœ… SUCCESS! Look in your folder for the new clean_video.mp4!")
requirements.txt ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ annotated-doc
2
+ annotated-types
3
+ anyio
4
+ attrs
5
+ boto3
6
+ botocore
7
+ certifi
8
+ charset-normalizer
9
+ click
10
+ colorama
11
+ decorator
12
+ fastapi
13
+ flatbuffers
14
+ h11
15
+ idna
16
+ ImageIO
17
+ imageio-ffmpeg
18
+ jmespath
19
+ jsonschema
20
+ jsonschema-specifications
21
+ lazy-loader
22
+ llvmlite
23
+ mpmath
24
+ networkx
25
+ numba
26
+ numpy
27
+ onnxruntime
28
+ opencv-contrib-python-headless
29
+ packaging
30
+ pillow
31
+ platformdirs
32
+ pooch
33
+ proglog
34
+ protobuf
35
+ pydantic
36
+ pydantic_core
37
+ PyMatting
38
+ python-dateutil
39
+ python-dotenv
40
+ python-multipart
41
+ referencing
42
+ rembg
43
+ requests
44
+ rpds-py
45
+ s3transfer
46
+ scikit-image
47
+ scipy
48
+ six
49
+ starlette
50
+ sympy
51
+ tifffile
52
+ tqdm
53
+ typing-inspection
54
+ typing_extensions
55
+ urllib3
56
+ uvicorn
57
+ slowapi
58
+ razorpay
59
+ firebase-admin
60
+ moviepy==1.0.3
61
+ easyocr
62
+ onnxruntime==1.18.0
63
+ numpy==1.26.4
64
+ opencv-python-headless==4.9.0.80
trim_video.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from moviepy.editor import VideoFileClip
2
+ import cv2
3
+ import os
4
+ import zipfile # 🌟 NEW: Needed for packing the batch clips
5
+ import math # 🌟 NEW: Needed to calculate how many clips to make
6
+
7
+ def trim_video(input_path, output_path, start_sec, end_sec):
8
+ """ Cuts the video to the specified time range. """
9
+ try:
10
+ # Load the video and explicitly grab the audio
11
+ video = VideoFileClip(input_path)
12
+ new_video = video.subclip(start_sec, end_sec)
13
+
14
+ new_video.write_videofile(
15
+ output_path,
16
+ codec="libx264",
17
+ audio_codec="aac", # 🌐 Force Web-Safe Audio
18
+ preset="ultrafast",
19
+ threads=8,
20
+ logger=None
21
+ )
22
+
23
+ # πŸ›‘ WINDOWS FIX: Explicitly close everything to free RAM!
24
+ video.close()
25
+ new_video.close()
26
+ return True
27
+ except Exception as e:
28
+ print(f"Trim Error: {e}")
29
+ return False
30
+
31
+ def add_professional_text(input_path, output_path, text="VaniConnect AI"):
32
+ """ Adds a professional text overlay using OpenCV. """
33
+ try:
34
+ cap = cv2.VideoCapture(input_path)
35
+ fps = cap.get(cv2.CAP_PROP_FPS)
36
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
37
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
38
+
39
+ temp_v = "downloads/temp_text.mp4"
40
+ out = cv2.VideoWriter(temp_v, cv2.VideoWriter_fourcc(*'mp4v'), fps, (width, height))
41
+
42
+ while True:
43
+ ret, frame = cap.read()
44
+ if not ret: break
45
+
46
+ # Add text with a subtle shadow for readability
47
+ cv2.putText(frame, text, (52, height - 48), cv2.FONT_HERSHEY_DUPLEX, 1.5, (0, 0, 0), 3)
48
+ cv2.putText(frame, text, (50, height - 50), cv2.FONT_HERSHEY_DUPLEX, 1.5, (255, 255, 255), 2)
49
+ out.write(frame)
50
+
51
+ cap.release()
52
+ out.release()
53
+
54
+ # πŸ”Š THE AUDIO FIX: Explicitly rip the audio and force it onto the new video
55
+ original = VideoFileClip(input_path)
56
+ processed = VideoFileClip(temp_v)
57
+
58
+ # Only set audio if the original video actually had sound!
59
+ if original.audio is not None:
60
+ final = processed.set_audio(original.audio)
61
+ else:
62
+ final = processed
63
+
64
+ final.write_videofile(
65
+ output_path,
66
+ codec="libx264",
67
+ audio_codec="aac",
68
+ preset="ultrafast",
69
+ threads=8,
70
+ logger=None
71
+ )
72
+
73
+ # πŸ›‘ WINDOWS FIX: Explicitly close every single file!
74
+ original.close()
75
+ processed.close()
76
+ final.close()
77
+
78
+ os.remove(temp_v)
79
+ return True
80
+ except Exception as e:
81
+ print(f"Text Overlay Error: {e}")
82
+ return False
83
+
84
+ # ==========================================
85
+ # 🌟 PRO TIER FEATURE: THE BATCH SPLITTER
86
+ # ==========================================
87
+ def split_video_into_parts(video_path, clip_duration_sec, output_folder):
88
+ """ Slices a long video into multiple equal parts and zips them up. """
89
+ try:
90
+ video = VideoFileClip(video_path)
91
+ total_duration = video.duration
92
+
93
+ # Calculate how many full clips we get
94
+ number_of_clips = math.ceil(total_duration / clip_duration_sec)
95
+
96
+ saved_files = []
97
+ base_name = os.path.basename(video_path).rsplit('.', 1)[0]
98
+
99
+ for i in range(number_of_clips):
100
+ start_time = i * clip_duration_sec
101
+ end_time = min((i + 1) * clip_duration_sec, total_duration)
102
+
103
+ # Skip tiny clips at the very end (less than 3 seconds)
104
+ if end_time - start_time < 3:
105
+ continue
106
+
107
+ out_name = os.path.join(output_folder, f"{base_name}_part{i+1}.mp4")
108
+
109
+ # Slice and save with turbo settings
110
+ clip = video.subclip(start_time, end_time)
111
+ print(f"βœ‚οΈ Cutting Part {i+1}: {start_time}s to {end_time}s")
112
+
113
+ clip.write_videofile(
114
+ out_name,
115
+ codec="libx264",
116
+ audio_codec="aac",
117
+ preset="ultrafast",
118
+ threads=4,
119
+ logger=None
120
+ )
121
+ saved_files.append(out_name)
122
+
123
+ video.close()
124
+
125
+ # πŸ“¦ Zip them all up!
126
+ zip_filename = os.path.join(output_folder, f"{base_name}_Batch.zip")
127
+ print("πŸ“¦ Zipping files together...")
128
+ with zipfile.ZipFile(zip_filename, 'w') as zipf:
129
+ for file in saved_files:
130
+ zipf.write(file, os.path.basename(file))
131
+ os.remove(file) # Delete the individual MP4s to save space
132
+
133
+ return True, zip_filename
134
+ except Exception as e:
135
+ print(f"🚨 Batch Split Error: {e}")
136
+ return False, None
yt_down.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import yt_dlp
2
+ import os
3
+ import uuid
4
+
5
+ def download_youtube_video(video_url, output_folder="downloads", quality="720p"):
6
+ try:
7
+ os.makedirs(output_folder, exist_ok=True)
8
+
9
+ random_id = uuid.uuid4().hex[:8]
10
+ is_audio = (quality == "audio")
11
+ ext = "mp3" if is_audio else "mp4"
12
+
13
+ safe_filename = f"yt_{random_id}.{ext}"
14
+ final_path = os.path.join(output_folder, safe_filename)
15
+
16
+ # Quality Mapping
17
+ if quality == "best":
18
+ format_str = 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best'
19
+ elif quality == "720p":
20
+ format_str = 'bestvideo[height<=720][ext=mp4]+bestaudio[ext=m4a]/best[height<=720][ext=mp4]/best'
21
+ elif quality == "480p":
22
+ format_str = 'bestvideo[height<=480][ext=mp4]+bestaudio[ext=m4a]/best[height<=480][ext=mp4]/best'
23
+ elif quality == "audio":
24
+ format_str = 'bestaudio/best'
25
+ else:
26
+ format_str = 'best[ext=mp4]/best'
27
+
28
+ ydl_opts = {
29
+ 'format': format_str,
30
+ 'outtmpl': final_path,
31
+ 'quiet': False,
32
+ 'no_warnings': True,
33
+ 'source_address': '0.0.0.0'
34
+ }
35
+
36
+ if is_audio:
37
+ ydl_opts['postprocessors'] = [{
38
+ 'key': 'FFmpegExtractAudio',
39
+ 'preferredcodec': 'mp3',
40
+ 'preferredquality': '192',
41
+ }]
42
+ else:
43
+ ydl_opts['merge_output_format'] = 'mp4'
44
+
45
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
46
+ # 🌟 THE UPGRADE: We extract info AND download at the same time!
47
+ info_dict = ydl.extract_info(video_url, download=True)
48
+
49
+ # Grab the metadata to send to React
50
+ title = info_dict.get('title', 'Unknown Media')
51
+ thumbnail = info_dict.get('thumbnail', '')
52
+
53
+ # 🌟 Return all 3 pieces of data!
54
+ return safe_filename, title, thumbnail
55
+
56
+ except Exception as e:
57
+ print(f"🚨 Download Error: {e}")
58
+ return None, None, None