Beepeen24 commited on
Commit
af4ca05
·
verified ·
1 Parent(s): b18069f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +94 -1239
app.py CHANGED
@@ -1,1268 +1,123 @@
1
  import os
2
- os.environ['HF_HUB_DISABLE_SYMLINKS_WARNING'] = '1'
3
- os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
4
- os.environ['CUDA_MODULE_LOADING'] = 'LAZY'
5
-
6
- import cv2
7
- import torch
8
- import random
9
- import numpy as np
10
- import time
11
- import secrets
12
- import json
13
- import hashlib
14
- from datetime import datetime, timedelta
15
-
16
- #import spaces
17
-
18
- import PIL
19
- from PIL import Image, ImageDraw, ImageFont
20
- from typing import Tuple
21
-
22
- import diffusers
23
- from diffusers.utils import load_image
24
- from diffusers.models import ControlNetModel
25
- from diffusers.pipelines.controlnet.multicontrolnet import MultiControlNetModel
26
-
27
- from huggingface_hub import hf_hub_download
28
-
29
- from insightface.app import FaceAnalysis
30
-
31
- # Optional imports with fallbacks
32
- try:
33
- from style_template import styles
34
- STYLE_NAMES = list(styles.keys())
35
- except:
36
- styles = {}
37
- STYLE_NAMES = []
38
-
39
- try:
40
- from pipeline_stable_diffusion_xl_instantid_full import StableDiffusionXLInstantIDPipeline, draw_kps
41
- except:
42
- # Create dummy functions if imports fail
43
- class StableDiffusionXLInstantIDPipeline:
44
- def __init__(self, *args, **kwargs):
45
- pass
46
- def draw_kps(*args, **kwargs):
47
- return None
48
 
49
  import gradio as gr
 
 
 
50
 
51
- try:
52
- from depth_anything.dpt import DepthAnything
53
- from depth_anything.util.transform import Resize, NormalizeImage, PrepareForNet
54
- import torch.nn.functional as F
55
- from torchvision.transforms import Compose
56
- DEPTH_AVAILABLE = True
57
- except:
58
- DEPTH_AVAILABLE = False
59
-
60
- # GPU optimization
61
- torch.backends.cudnn.benchmark = True
62
- torch.backends.cuda.matmul.allow_tf32 = True
63
-
64
- # global variable
65
- MAX_SEED = np.iinfo(np.int32).max
66
- device = "cuda" if torch.cuda.is_available() else "cpu"
67
- dtype = torch.float16 if str(device).__contains__("cuda") else torch.float32
68
- enable_lcm_arg = False
69
-
70
- print(f"🚀 Starting AI Headshot Generator on {device}...")
71
 
72
  # ===== LICENSE SYSTEM =====
73
- LICENSE_FILE = "valid_licenses.json"
74
-
75
- # PERMANENT ADMIN/TEST KEYS (Always work)
76
- ADMIN_KEYS = {
77
- "HEADSHOT-TEST123456",
78
- "HEADSHOT-OWNERACCESS",
79
- "HEADSHOT-DEVELOPER123",
80
- "HEADSHOT-ADMIN789012"
81
- }
82
-
83
- def load_licenses():
84
- """Load valid licenses from file"""
85
- try:
86
- with open(LICENSE_FILE, 'r') as f:
87
- return set(json.load(f))
88
- except FileNotFoundError:
89
- # Initialize with admin keys on first run
90
- initial_licenses = ADMIN_KEYS.copy()
91
- save_licenses(initial_licenses)
92
- return initial_licenses
93
-
94
- def save_licenses(licenses):
95
- """Save licenses to file"""
96
- with open(LICENSE_FILE, 'w') as f:
97
- json.dump(list(licenses), f)
98
-
99
- def generate_license_key():
100
- """Generate a unique license key"""
101
- license_key = f"HEADSHOT-{secrets.token_hex(6).upper()}"
102
- valid_licenses = load_licenses()
103
- valid_licenses.add(license_key)
104
- save_licenses(valid_licenses)
105
- return license_key
106
-
107
- def verify_license(license_key):
108
- """Check if license key is valid"""
109
- if not license_key or not license_key.strip():
110
- return False
111
-
112
- license_upper = license_key.strip().upper()
113
-
114
- # Check admin keys first (always work)
115
- if license_upper in ADMIN_KEYS:
116
- print(f"✅ Admin access granted with: {license_upper}")
117
- return True
118
-
119
- # Check purchased licenses
120
- valid_licenses = load_licenses()
121
- return license_upper in valid_licenses
122
-
123
- # ===== TRIAL SYSTEM =====
124
- TRIAL_FILE = "user_trials.json"
125
- MAX_FREE_TRIALS = 3
126
-
127
- def load_trials():
128
- """Load user trial data"""
129
- try:
130
- with open(TRIAL_FILE, 'r') as f:
131
- return json.load(f)
132
- except FileNotFoundError:
133
- return {}
134
-
135
- def save_trials(trials_data):
136
- """Save user trial data"""
137
- with open(TRIAL_FILE, 'w') as f:
138
- json.dump(trials_data, f)
139
-
140
- def get_user_identifier():
141
- """Create a unique but anonymous user identifier"""
142
- # Simple identifier for Spaces
143
- return "gradio_user"
144
-
145
- def can_use_free_trial(user_id):
146
- """Check if user can use free trial"""
147
- trials_data = load_trials()
148
-
149
- if user_id not in trials_data:
150
- return True, MAX_FREE_TRIALS
151
-
152
- user_data = trials_data[user_id]
153
- trials_used = user_data.get('trials_used', 0)
154
- first_trial_date = user_data.get('first_trial_date')
155
-
156
- # Reset trials after 30 days
157
- if first_trial_date:
158
- first_date = datetime.fromisoformat(first_trial_date)
159
- if datetime.now() - first_date > timedelta(days=30):
160
- trials_used = 0
161
- user_data['trials_used'] = 0
162
- user_data['first_trial_date'] = datetime.now().isoformat()
163
- save_trials(trials_data)
164
-
165
- trials_left = MAX_FREE_TRIALS - trials_used
166
- return trials_left > 0, trials_left
167
-
168
- def record_trial_usage(user_id):
169
- """Record that user used a trial"""
170
- trials_data = load_trials()
171
-
172
- if user_id not in trials_data:
173
- trials_data[user_id] = {
174
- 'trials_used': 1,
175
- 'first_trial_date': datetime.now().isoformat(),
176
- 'last_used': datetime.now().isoformat()
177
- }
178
- else:
179
- trials_data[user_id]['trials_used'] += 1
180
- trials_data[user_id]['last_used'] = datetime.now().isoformat()
181
-
182
- save_trials(trials_data)
183
-
184
- def apply_watermark(image):
185
- """Apply watermark to free trial images"""
186
- # Convert to PIL if needed
187
- if hasattr(image, 'mode'):
188
- pil_image = image
189
- else:
190
- pil_image = Image.fromarray(image)
191
-
192
- draw = ImageDraw.Draw(pil_image, 'RGBA')
193
- width, height = pil_image.size
194
-
195
- # Watermark text
196
- watermark_text = "PREVIEW - UPGRADE TO DOWNLOAD"
197
-
198
- # Use default font
199
- try:
200
- font = ImageFont.truetype("arial.ttf", min(width, height) // 20)
201
- except:
202
- try:
203
- font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", min(width, height) // 20)
204
- except:
205
- font = ImageFont.load_default()
206
-
207
- # Get text size
208
- bbox = draw.textbbox((0, 0), watermark_text, font=font)
209
- text_width = bbox[2] - bbox[0]
210
- text_height = bbox[3] - bbox[1]
211
-
212
- # Position watermark (center bottom)
213
- x = (width - text_width) // 2
214
- y = height - text_height - 50
215
-
216
- # Draw semi-transparent background
217
- draw.rectangle([x-10, y-10, x+text_width+10, y+text_height+10],
218
- fill=(0, 0, 0, 128))
219
-
220
- # Draw text
221
- draw.text((x, y), watermark_text, fill=(255, 255, 255, 255), font=font)
222
-
223
- return pil_image
224
-
225
- # Initialize licenses
226
- VALID_LICENSES = load_licenses()
227
- print(f"✅ License system initialized. Admin keys available.")
228
-
229
- # ===== LAZY LOAD MODELS =====
230
- print("🔄 Loading AI models (this may take a few minutes)...")
231
-
232
- # Create temp directory if needed
233
- os.makedirs("./checkpoints", exist_ok=True)
234
- os.makedirs("temp_downloads", exist_ok=True)
235
-
236
- # Load models with error handling
237
- try:
238
- # Download checkpoints with timeout
239
- print("📥 Downloading InstantID checkpoints...")
240
- hf_hub_download(repo_id="InstantX/InstantID", filename="ControlNetModel/config.json", local_dir="./checkpoints")
241
- hf_hub_download(
242
- repo_id="InstantX/InstantID",
243
- filename="ControlNetModel/diffusion_pytorch_model.safetensors",
244
- local_dir="./checkpoints",
245
- )
246
- hf_hub_download(repo_id="InstantX/InstantID", filename="ip-adapter.bin", local_dir="./checkpoints")
247
- print("✅ Checkpoints downloaded")
248
- except Exception as e:
249
- print(f"⚠️ Could not download checkpoints: {e}")
250
- print("⚠️ App will use fallback mode")
251
-
252
- # Load face encoder
253
- try:
254
- app = FaceAnalysis(
255
- name="antelopev2",
256
- root="./",
257
- providers=["CPUExecutionProvider"],
258
- )
259
- app.prepare(ctx_id=0, det_size=(640, 640))
260
- print("✅ Face encoder loaded")
261
- except Exception as e:
262
- print(f"⚠️ Could not load face encoder: {e}")
263
- app = None
264
 
265
- # Initialize depth model if available
266
- if DEPTH_AVAILABLE:
267
- try:
268
- depth_anything = DepthAnything.from_pretrained('LiheYoung/depth_anything_vitl14').to(device).eval()
269
- transform = Compose([
270
- Resize(
271
- width=518,
272
- height=518,
273
- resize_target=False,
274
- keep_aspect_ratio=True,
275
- ensure_multiple_of=14,
276
- resize_method='lower_bound',
277
- image_interpolation_method=cv2.INTER_CUBIC,
278
- ),
279
- NormalizeImage(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
280
- PrepareForNet(),
281
- ])
282
- except:
283
- DEPTH_AVAILABLE = False
284
- print("⚠️ Depth model not available")
285
 
286
- # Path to InstantID models
287
- face_adapter = f"./checkpoints/ip-adapter.bin"
288
- controlnet_path = f"./checkpoints/ControlNetModel"
289
-
290
- # Initialize pipeline with error handling
291
- pipe = None
292
- controlnet_identitynet = None
293
- controlnet_canny = None
294
- controlnet_depth = None
295
-
296
- try:
297
- print("🔄 Loading ControlNet models...")
298
- controlnet_identitynet = ControlNetModel.from_pretrained(
299
- controlnet_path, torch_dtype=dtype
300
- )
301
-
302
- controlnet_canny_model = "diffusers/controlnet-canny-sdxl-1.0"
303
- controlnet_depth_model = "diffusers/controlnet-depth-sdxl-1.0-small"
304
-
305
- controlnet_canny = ControlNetModel.from_pretrained(
306
- controlnet_canny_model, torch_dtype=dtype
307
- ).to(device)
308
 
309
- if DEPTH_AVAILABLE:
310
- controlnet_depth = ControlNetModel.from_pretrained(
311
- controlnet_depth_model, torch_dtype=dtype
312
- ).to(device)
313
-
314
- print("🔄 Loading Stable Diffusion pipeline...")
315
- pretrained_model_name_or_path = "wangqixun/YamerMIX_v8"
316
-
317
- pipe = StableDiffusionXLInstantIDPipeline.from_pretrained(
318
- pretrained_model_name_or_path,
319
- controlnet=[controlnet_identitynet],
320
- torch_dtype=dtype,
321
- safety_checker=None,
322
- feature_extractor=None,
323
- ).to(device)
324
-
325
- pipe.scheduler = diffusers.EulerDiscreteScheduler.from_config(
326
- pipe.scheduler.config
327
- )
328
 
329
- # Load and disable LCM
330
- pipe.load_lora_weights("latent-consistency/lcm-lora-sdxl")
331
- pipe.disable_lora()
332
-
333
- pipe.cuda()
334
- pipe.load_ip_adapter_instantid(face_adapter)
335
-
336
- if hasattr(pipe, 'image_proj_model'):
337
- pipe.image_proj_model.to("cuda")
338
- if hasattr(pipe, 'unet'):
339
- pipe.unet.to("cuda")
340
-
341
- print("✅ AI pipeline loaded successfully!")
342
-
343
- except Exception as e:
344
- print(f"⚠️ Could not load AI pipeline: {e}")
345
- print("⚠️ App will run in demo mode")
346
-
347
- # ControlNet functions with fallbacks
348
- if DEPTH_AVAILABLE:
349
- def get_depth_map(image):
350
- image = np.array(image) / 255.0
351
  h, w = image.shape[:2]
352
- image = transform({'image': image})['image']
353
- image = torch.from_numpy(image).unsqueeze(0).to("cuda")
354
- with torch.no_grad():
355
- depth = depth_anything(image)
356
- depth = F.interpolate(depth[None], (h, w), mode='bilinear', align_corners=False)[0, 0]
357
- depth = (depth - depth.min()) / (depth.max() - depth.min()) * 255.0
358
- depth = depth.cpu().numpy().astype(np.uint8)
359
- depth_image = Image.fromarray(depth)
360
- return depth_image
361
- else:
362
- def get_depth_map(image):
363
- return Image.fromarray(np.zeros((512, 512), dtype=np.uint8))
364
-
365
- def get_canny_image(image, t1=100, t2=200):
366
- try:
367
- image_np = np.array(image)
368
- if len(image_np.shape) == 3:
369
- image_np = cv2.cvtColor(image_np, cv2.COLOR_RGB2BGR)
370
- edges = cv2.Canny(image_np, t1, t2)
371
- return Image.fromarray(edges, "L")
372
- return Image.fromarray(np.zeros((512, 512), dtype=np.uint8))
373
- except:
374
- return Image.fromarray(np.zeros((512, 512), dtype=np.uint8))
375
-
376
- controlnet_map = {
377
- "canny": controlnet_canny,
378
- "depth": controlnet_depth,
379
- }
380
- controlnet_map_fn = {
381
- "canny": get_canny_image,
382
- "depth": get_depth_map,
383
- }
384
-
385
- def toggle_lcm_ui(value):
386
- if value:
387
- return (
388
- gr.update(minimum=0, maximum=100, step=1, value=5),
389
- gr.update(minimum=0.1, maximum=20.0, step=0.1, value=1.5),
390
- )
391
  else:
392
- return (
393
- gr.update(minimum=5, maximum=100, step=1, value=30),
394
- gr.update(minimum=0.1, maximum=20.0, step=0.1, value=5),
395
- )
396
-
397
- def randomize_seed_fn(seed: int, randomize_seed: bool) -> int:
398
- if randomize_seed:
399
- seed = random.randint(0, MAX_SEED)
400
- return seed
401
-
402
- def convert_from_cv2_to_image(img: np.ndarray) -> Image:
403
- return Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
404
-
405
- def convert_from_image_to_cv2(img: Image) -> np.ndarray:
406
- return cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
407
-
408
- def resize_img(
409
- input_image,
410
- max_side=1280,
411
- min_side=1024,
412
- size=None,
413
- pad_to_max_side=False,
414
- mode=PIL.Image.BILINEAR,
415
- base_pixel_number=64,
416
- ):
417
- w, h = input_image.size
418
- if size is not None:
419
- w_resize_new, h_resize_new = size
420
- else:
421
- ratio = min_side / min(h, w)
422
- w, h = round(ratio * w), round(ratio * h)
423
- ratio = max_side / max(h, w)
424
- input_image = input_image.resize([round(ratio * w), round(ratio * h)], mode)
425
- w_resize_new = (round(ratio * w) // base_pixel_number) * base_pixel_number
426
- h_resize_new = (round(ratio * h) // base_pixel_number) * base_pixel_number
427
- input_image = input_image.resize([w_resize_new, h_resize_new], mode)
428
-
429
- if pad_to_max_side:
430
- res = np.ones([max_side, max_side, 3], dtype=np.uint8) * 255
431
- offset_x = (max_side - w_resize_new) // 2
432
- offset_y = (max_side - h_resize_new) // 2
433
- res[
434
- offset_y : offset_y + h_resize_new, offset_x : offset_x + w_resize_new
435
- ] = np.array(input_image)
436
- input_image = Image.fromarray(res)
437
- return input_image
438
-
439
- def apply_style(style_name: str, positive: str, negative: str = "") -> Tuple[str, str]:
440
- if style_name == "No Style" or style_name not in styles:
441
- return positive, negative
442
- p, n = styles.get(style_name, ("{prompt}", ""))
443
- return p.replace("{prompt}", positive), n + " " + negative
444
-
445
- def save_as_png(image, filename="professional_headshot"):
446
- """Save image as PNG and return file path for download"""
447
- temp_dir = "temp_downloads"
448
- os.makedirs(temp_dir, exist_ok=True)
449
 
450
- timestamp = int(time.time())
451
- filepath = os.path.join(temp_dir, f"{filename}_{timestamp}.png")
 
452
 
453
- if image.mode in ('RGBA', 'LA'):
454
- background = Image.new('RGB', image.size, (255, 255, 255))
455
- background.paste(image, mask=image.split()[-1])
456
- image = background
457
- elif image.mode != 'RGB':
458
- image = image.convert('RGB')
459
 
460
- image.save(filepath, "PNG", optimize=True)
461
- return filepath
462
 
463
- @spaces.GPU
464
- def generate_image(
465
- face_image_path,
466
- license_key,
467
- prompt,
468
- negative_prompt,
469
- style_name,
470
- num_steps,
471
- identitynet_strength_ratio,
472
- adapter_strength_ratio,
473
- canny_strength,
474
- depth_strength,
475
- controlnet_selection,
476
- guidance_scale,
477
- seed,
478
- scheduler,
479
- enable_LCM,
480
- enhance_face_region,
481
- progress=gr.Progress(track_tqdm=True),
482
- ):
483
- """Generate AI headshot with license and trial system"""
484
-
485
- # ===== LICENSE & TRIAL VERIFICATION =====
486
- user_id = get_user_identifier()
487
- has_valid_license = verify_license(license_key)
488
-
489
- # If no valid license, check free trials
490
- if not has_valid_license:
491
- can_use_trial, trials_left = can_use_free_trial(user_id)
492
-
493
- if not can_use_trial:
494
- raise gr.Error(f"""
495
- ❌ No free trials remaining!
496
-
497
- You've used all {MAX_FREE_TRIALS} free generations.
498
 
499
- 🔑 **Options:**
500
- 1. **Purchase a license** for unlimited HD downloads
501
- 2. **Enter your existing license key** if you already purchased
 
 
 
502
 
503
- 💡 Visit: https://canadianheadshotpro.carrd.co to purchase
504
- 📧 Support: bee.tools@zohomailcloud.ca
505
- """)
506
-
507
- # ===== CHECK IF MODELS ARE LOADED =====
508
- if pipe is None or app is None:
509
- raise gr.Error("""
510
- ⚠️ AI models are still loading...
511
-
512
- Please wait a moment and try again.
513
- The models take a few minutes to load on first use.
514
-
515
- If this persists, refresh the page.
516
- """)
517
-
518
- # ===== AI GENERATION =====
519
- if enable_LCM:
520
- pipe.scheduler = diffusers.LCMScheduler.from_config(pipe.scheduler.config)
521
- pipe.enable_lora()
522
- else:
523
- pipe.disable_lora()
524
- scheduler_class_name = scheduler.split("-")[0]
525
- add_kwargs = {}
526
- if len(scheduler.split("-")) > 1:
527
- add_kwargs["use_karras_sigmas"] = True
528
- if len(scheduler.split("-")) > 2:
529
- add_kwargs["algorithm_type"] = "sde-dpmsolver++"
530
- scheduler = getattr(diffusers, scheduler_class_name)
531
- pipe.scheduler = scheduler.from_config(pipe.scheduler.config, **add_kwargs)
532
-
533
- if face_image_path is None:
534
- raise gr.Error("Please upload a face image")
535
-
536
- if prompt is None:
537
- prompt = "a person"
538
-
539
- prompt, negative_prompt = apply_style(style_name, prompt, negative_prompt)
540
-
541
- try:
542
- face_image = load_image(face_image_path)
543
- face_image = resize_img(face_image, max_side=1024)
544
- face_image_cv2 = convert_from_image_to_cv2(face_image)
545
- height, width, _ = face_image_cv2.shape
546
-
547
- face_info = app.get(face_image_cv2)
548
-
549
- if len(face_info) == 0:
550
- raise gr.Error("Unable to detect a face in the image. Please upload a different photo with a clear face.")
551
-
552
- face_info = sorted(
553
- face_info,
554
- key=lambda x: (x["bbox"][2] - x["bbox"][0]) * x["bbox"][3] - x["bbox"][1],
555
- )[-1]
556
- face_emb = face_info["embedding"]
557
- face_kps = draw_kps(convert_from_cv2_to_image(face_image_cv2), face_info["kps"])
558
- img_controlnet = face_image
559
-
560
- if enhance_face_region:
561
- control_mask = np.zeros([height, width, 3])
562
- x1, y1, x2, y2 = face_info["bbox"]
563
- x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
564
- control_mask[y1:y2, x1:x2] = 255
565
- control_mask = Image.fromarray(control_mask.astype(np.uint8))
566
- else:
567
- control_mask = None
568
-
569
- if len(controlnet_selection) > 0 and all(s in controlnet_map and controlnet_map[s] is not None for s in controlnet_selection):
570
- controlnet_scales = {
571
- "canny": canny_strength,
572
- "depth": depth_strength,
573
- }
574
- pipe.controlnet = MultiControlNetModel(
575
- [controlnet_identitynet]
576
- + [controlnet_map[s] for s in controlnet_selection]
577
  )
578
- control_scales = [float(identitynet_strength_ratio)] + [
579
- controlnet_scales[s] for s in controlnet_selection
580
- ]
581
- control_images = [face_kps] + [
582
- controlnet_map_fn[s](img_controlnet).resize((width, height))
583
- for s in controlnet_selection
584
- ]
585
- else:
586
- pipe.controlnet = controlnet_identitynet
587
- control_scales = float(identitynet_strength_ratio)
588
- control_images = face_kps
589
-
590
- generator = torch.Generator(device=device).manual_seed(seed)
591
-
592
- pipe.set_ip_adapter_scale(adapter_strength_ratio)
593
- images = pipe(
594
- prompt=prompt,
595
- negative_prompt=negative_prompt,
596
- image_embeds=face_emb,
597
- image=control_images,
598
- control_mask=control_mask,
599
- controlnet_conditioning_scale=control_scales,
600
- num_inference_steps=num_steps,
601
- guidance_scale=guidance_scale,
602
- height=height,
603
- width=width,
604
- generator=generator,
605
- ).images
606
-
607
- # ===== APPLY WATERMARK IF USING FREE TRIAL =====
608
- final_image = images[0]
609
- if not has_valid_license:
610
- # Record trial usage
611
- record_trial_usage(user_id)
612
- # Apply watermark
613
- final_image = apply_watermark(final_image)
614
 
615
- # Update trials left
616
- _, trials_left = can_use_free_trial(user_id)
 
 
 
 
617
 
618
- # Show trial usage message
619
- gr.Info(f"Free trial used! {trials_left} generations remaining. Upgrade for watermark-free HD downloads.")
620
-
621
- png_filepath = save_as_png(final_image)
622
- return png_filepath, gr.update(visible=True)
623
-
624
- except Exception as e:
625
- print(f"Error during generation: {e}")
626
- raise gr.Error(f"Error generating image: {str(e)}")
627
-
628
- # ===== GRADIO UI =====
629
- print("🎨 Building Gradio interface...")
630
-
631
- css = """
632
- :root {
633
- --primary: #2563eb;
634
- --primary-dark: #1d4ed8;
635
- --secondary: #7c3aed;
636
- --accent: #06b6d4;
637
- --success: #10b981;
638
- --warning: #f59e0b;
639
- --error: #ef4444;
640
- --surface: #ffffff;
641
- --surface-alt: #f8fafc;
642
- --text-primary: #1e293b;
643
- --text-secondary: #64748b;
644
- --border: #e2e8f0;
645
- --shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
646
- }
647
- .gradio-container {
648
- max-width: 1400px !important;
649
- font-family: 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif;
650
- background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%) !important;
651
- }
652
- .main-container {
653
- background: white;
654
- border-radius: 24px;
655
- box-shadow: var(--shadow);
656
- margin: 20px auto;
657
- overflow: hidden;
658
- border: 1px solid var(--border);
659
- }
660
- .hero-section {
661
- background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
662
- color: white;
663
- padding: 40px 0;
664
- text-align: center;
665
- position: relative;
666
- overflow: hidden;
667
- }
668
- .hero-section::before {
669
- content: '';
670
- position: absolute;
671
- top: 0;
672
- left: 0;
673
- right: 0;
674
- bottom: 0;
675
- background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 100" fill="rgba(255,255,255,0.1)"><polygon points="0,0 1000,100 0,100"/></svg>');
676
- background-size: cover;
677
- }
678
- .hero-title {
679
- font-size: 3em;
680
- font-weight: 800;
681
- margin-bottom: 16px;
682
- background: linear-gradient(135deg, #ffffff 0%, #e2e8f0 100%);
683
- -webkit-background-clip: text;
684
- -webkit-text-fill-color: transparent;
685
- background-clip: text;
686
- }
687
- .hero-subtitle {
688
- font-size: 1.3em;
689
- font-weight: 400;
690
- opacity: 0.9;
691
- max-width: 600px;
692
- margin: 0 auto;
693
- }
694
- .upload-area {
695
- border: 3px dashed var(--border);
696
- border-radius: 20px;
697
- padding: 40px 20px;
698
- text-align: center;
699
- background: var(--surface-alt);
700
- transition: all 0.3s ease;
701
- cursor: pointer;
702
- }
703
- .upload-area:hover {
704
- border-color: var(--primary);
705
- background: #f0f9ff;
706
- transform: translateY(-2px);
707
- }
708
- .control-card {
709
- background: var(--surface);
710
- border-radius: 16px;
711
- padding: 24px;
712
- margin-bottom: 20px;
713
- border: 1px solid var(--border);
714
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
715
- transition: all 0.3s ease;
716
- }
717
- .control-card:hover {
718
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
719
- transform: translateY(-1px);
720
- }
721
- .control-header {
722
- display: flex;
723
- align-items: center;
724
- margin-bottom: 16px;
725
- }
726
- .control-icon {
727
- width: 40px;
728
- height: 40px;
729
- border-radius: 12px;
730
- background: linear-gradient(135deg, var(--primary), var(--secondary));
731
- display: flex;
732
- align-items: center;
733
- justify-content: center;
734
- margin-right: 12px;
735
- color: white;
736
- font-weight: 600;
737
- }
738
- .control-title {
739
- font-size: 1.2em;
740
- font-weight: 600;
741
- color: var(--text-primary);
742
- margin: 0;
743
- }
744
- .result-card {
745
- background: var(--surface);
746
- border-radius: 20px;
747
- padding: 30px;
748
- border: 1px solid var(--border);
749
- box-shadow: var(--shadow);
750
- height: 100%;
751
- }
752
- .result-header {
753
- text-align: center;
754
- margin-bottom: 24px;
755
- }
756
- .result-title {
757
- font-size: 1.5em;
758
- font-weight: 700;
759
- color: var(--text-primary);
760
- margin-bottom: 8px;
761
- }
762
- .result-subtitle {
763
- color: var(--text-secondary);
764
- font-size: 0.95em;
765
- }
766
- .image-container {
767
- border-radius: 16px;
768
- overflow: hidden;
769
- background: var(--surface-alt);
770
- border: 1px solid var(--border);
771
- margin-bottom: 20px;
772
- }
773
- .success-banner {
774
- background: linear-gradient(135deg, var(--success), #059669);
775
- color: white;
776
- padding: 20px;
777
- border-radius: 16px;
778
- margin-top: 20px;
779
- text-align: center;
780
- }
781
- .btn-primary {
782
- background: linear-gradient(135deg, var(--primary), var(--primary-dark)) !important;
783
- color: white !important;
784
- border: none !important;
785
- border-radius: 12px !important;
786
- padding: 16px 32px !important;
787
- font-weight: 600 !important;
788
- font-size: 1.1em !important;
789
- transition: all 0.3s ease !important;
790
- box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3) !important;
791
- }
792
- .btn-primary:hover {
793
- transform: translateY(-2px) !important;
794
- box-shadow: 0 6px 20px rgba(37, 99, 235, 0.4) !important;
795
- }
796
- .slider-container {
797
- padding: 8px 0;
798
- }
799
- .slider-label {
800
- display: flex;
801
- justify-content: space-between;
802
- align-items: center;
803
- margin-bottom: 8px;
804
- }
805
- .slider-value {
806
- background: var(--primary);
807
- color: white;
808
- padding: 4px 12px;
809
- border-radius: 20px;
810
- font-size: 0.85em;
811
- font-weight: 600;
812
- }
813
- .tips-card {
814
- background: linear-gradient(135deg, #fef3c7, #f59e0b);
815
- border: none;
816
- border-radius: 16px;
817
- padding: 20px;
818
- margin-bottom: 20px;
819
- }
820
- .tips-header {
821
- display: flex;
822
- align-items: center;
823
- margin-bottom: 12px;
824
- }
825
- .tips-icon {
826
- font-size: 1.5em;
827
- margin-right: 12px;
828
- }
829
- .progress-container {
830
- margin: 20px 0;
831
- text-align: center;
832
- }
833
- .progress-text {
834
- font-size: 0.9em;
835
- color: var(--text-secondary);
836
- margin-top: 8px;
837
- }
838
- .trial-banner {
839
- background: linear-gradient(135deg, #10b981, #059669);
840
- color: white;
841
- padding: 15px;
842
- border-radius: 12px;
843
- text-align: center;
844
- margin-bottom: 15px;
845
- }
846
- .trial-banner-warning {
847
- background: linear-gradient(135deg, #f59e0b, #d97706);
848
- color: white;
849
- padding: 15px;
850
- border-radius: 12px;
851
- text-align: center;
852
- margin-bottom: 15px;
853
- }
854
- .trial-banner-error {
855
- background: linear-gradient(135deg, #ef4444, #dc2626);
856
- color: white;
857
- padding: 15px;
858
- border-radius: 12px;
859
- text-align: center;
860
- margin-bottom: 15px;
861
- }
862
- /* Responsive design */
863
- @media (max-width: 768px) {
864
- .hero-title {
865
- font-size: 2em;
866
- }
867
- .hero-subtitle {
868
- font-size: 1.1em;
869
- }
870
- .control-card {
871
- padding: 16px;
872
- }
873
- }
874
- .loading-overlay {
875
- position: fixed;
876
- top: 0;
877
- left: 0;
878
- right: 0;
879
- bottom: 0;
880
- background: rgba(255, 255, 255, 0.9);
881
- display: flex;
882
- align-items: center;
883
- justify-content: center;
884
- z-index: 1000;
885
- }
886
- """
887
-
888
- with gr.Blocks(css=css, theme=gr.themes.Soft(), title="Pro AI Headshot Generator") as demo:
889
-
890
- # Loading indicator
891
- loading_html = gr.HTML("""
892
- <div class="loading-overlay">
893
- <div style="text-align: center;">
894
- <h2>🚀 Loading AI Headshot Generator...</h2>
895
- <p>This may take 2-3 minutes on first load. Please be patient.</p>
896
- <p>Models are downloading and initializing...</p>
897
- </div>
898
- </div>
899
- """, visible=True)
900
-
901
- # Main Container
902
- with gr.Column(elem_classes="main-container") as main_container:
903
-
904
- # Hero Section
905
- with gr.Column(elem_classes="hero-section"):
906
- gr.HTML("""
907
- <div style="position: relative; z-index: 2;">
908
- <h1 class="hero-title">🎯 Pro AI Headshot Generator</h1>
909
- <p class="hero-subtitle">Transform any selfie into professional headshots in seconds. Perfect for LinkedIn, corporate profiles, and professional portfolios.</p>
910
- </div>
911
- """)
912
 
913
- # Main Content
914
- with gr.Row(equal_height=True):
 
 
 
 
915
 
916
- # Left Column - Controls
917
- with gr.Column(scale=1, min_width=400):
918
-
919
- # Upload Section
920
- with gr.Column(elem_classes="control-card"):
921
- gr.HTML("""
922
- <div class="control-header">
923
- <div class="control-icon">📸</div>
924
- <h3 class="control-title">Upload Your Photo</h3>
925
- </div>
926
- """)
927
- gr.HTML("""
928
- <p style="color: var(--text-secondary); margin-bottom: 20px; font-size: 0.95em;">
929
- For best results, use a clear, well-lit photo where your face is clearly visible.
930
- </p>
931
- """)
932
- face_file = gr.Image(
933
- label="",
934
- type="filepath",
935
- height=200,
936
- show_label=False,
937
- elem_classes="upload-area"
938
- )
939
-
940
- # Access Options Section
941
- with gr.Column(elem_classes="control-card"):
942
- gr.HTML("""
943
- <div class="control-header">
944
- <div class="control-icon">🔑</div>
945
- <h3 class="control-title">Access Options</h3>
946
- </div>
947
- """)
948
-
949
- # Trial Status Display
950
- trial_status = gr.HTML(f"""
951
- <div class="trial-banner">
952
- <h4 style="margin: 0 0 8px 0;">🎉 {MAX_FREE_TRIALS} FREE Trials Available!</h4>
953
- <p style="margin: 0; font-size: 0.9em;">Try our AI headshot generator - no credit card required</p>
954
- </div>
955
- """)
956
-
957
- license_input = gr.Textbox(
958
- label="",
959
- placeholder="Enter license key (or leave blank for free trial)",
960
- show_label=False,
961
- value="HEADSHOT-TEST123456",
962
- info=f"💡 You get {MAX_FREE_TRIALS} free generations. Purchase license for HD downloads without watermark."
963
- )
964
-
965
- gr.HTML(f"""
966
- <div style="font-size: 0.85em; color: var(--text-secondary); margin-top: 8px;">
967
- <strong>Free Trial:</strong> {MAX_FREE_TRIALS} watermarked previews<br>
968
- <strong>Premium License:</strong> Unlimited HD downloads, no watermark<br>
969
- <strong>Test Key:</strong> HEADSHOT-TEST123456<br>
970
- <a href="https://canadianheadshotpro.carrd.co" target="_blank" style="color: var(--primary); font-weight: 600;">👉 Click here to purchase license</a>
971
- </div>
972
- """)
973
-
974
- # Description Section
975
- with gr.Column(elem_classes="control-card"):
976
- gr.HTML("""
977
- <div class="control-header">
978
- <div class="control-icon">✍️</div>
979
- <h3 class="control-title">Describe Your Look</h3>
980
- </div>
981
- """)
982
- prompt = gr.Textbox(
983
- label="",
984
- placeholder="Describe how you want to appear...",
985
- value="modern professional headshot, creative director style, soft natural lighting, authentic expression, contemporary business portrait, premium quality photo",
986
- show_label=False,
987
- lines=3
988
- )
989
- gr.HTML("""
990
- <div style="font-size: 0.85em; color: var(--text-secondary); margin-top: 8px;">
991
- 💡 Examples: "professional business headshot", "friendly corporate portrait", "creative director style"
992
- </div>
993
- """)
994
-
995
- # Style Selection
996
- with gr.Column(elem_classes="control-card"):
997
- gr.HTML("""
998
- <div class="control-header">
999
- <div class="control-icon">🎨</div>
1000
- <h3 class="control-title">Style Options</h3>
1001
- </div>
1002
- """)
1003
- style = gr.Dropdown(
1004
- label="Style Theme",
1005
- choices=["No Style"] + STYLE_NAMES,
1006
- value="No Style",
1007
- info="'No Style' recommended for natural professional results"
1008
- )
1009
-
1010
- # Quality Settings
1011
- with gr.Column(elem_classes="control-card"):
1012
- gr.HTML("""
1013
- <div class="control-header">
1014
- <div class="control-icon">⚙️</div>
1015
- <h3 class="control-title">Quality Settings</h3>
1016
- </div>
1017
- """)
1018
-
1019
- with gr.Row():
1020
- identitynet_strength_ratio = gr.Slider(
1021
- label="Face Similarity",
1022
- minimum=0.5,
1023
- maximum=1.2,
1024
- step=0.05,
1025
- value=0.80,
1026
- info="How closely the headshot resembles your photo"
1027
- )
1028
-
1029
- with gr.Row():
1030
- adapter_strength_ratio = gr.Slider(
1031
- label="Detail Quality",
1032
- minimum=0.3,
1033
- maximum=1.2,
1034
- step=0.05,
1035
- value=0.55,
1036
- info="Level of detail in the final image"
1037
- )
1038
-
1039
- enable_LCM = gr.Checkbox(
1040
- label="Enable Fast Generation Mode",
1041
- value=False,
1042
- info="Faster results with slightly lower quality"
1043
- )
1044
-
1045
- # Tips Card
1046
- with gr.Column(elem_classes="tips-card"):
1047
- gr.HTML("""
1048
- <div class="tips-header">
1049
- <div class="tips-icon">💡</div>
1050
- <h4 style="margin: 0; color: #92400e;">Pro Tips for Best Results</h4>
1051
- </div>
1052
- <ul style="margin: 0; color: #92400e; font-size: 0.9em;">
1053
- <li>Use clear, well-lit face photos</li>
1054
- <li>Face should be visible and not too small</li>
1055
- <li>Avoid blurry or dark images</li>
1056
- <li>Single person in photo works best</li>
1057
- </ul>
1058
- """)
1059
-
1060
- # Generate Button
1061
- submit = gr.Button(
1062
- "✨ Generate Professional Headshot",
1063
- variant="primary",
1064
- size="lg",
1065
- elem_classes="btn-primary",
1066
- scale=1
1067
- )
1068
 
1069
- # Right Column - Results
1070
- with gr.Column(scale=1, min_width=500):
1071
- with gr.Column(elem_classes="result-card"):
1072
- gr.HTML("""
1073
- <div class="result-header">
1074
- <h2 class="result-title">Your Professional Headshot</h2>
1075
- <p class="result-subtitle">Your AI-generated headshot will appear here. Download as high-quality PNG for professional use.</p>
1076
- </div>
1077
- """)
1078
-
1079
- # Image Display
1080
- gallery = gr.Image(
1081
- label="",
1082
- height=400,
1083
- show_download_button=True,
1084
- show_label=False,
1085
- type="filepath",
1086
- elem_classes="image-container"
1087
- )
1088
-
1089
- # Success Message
1090
- success_msg = gr.HTML("""
1091
- <div class="success-banner" style="display: none;">
1092
- <h4 style="margin: 0 0 8px 0;">✅ Success! Your Professional Headshot is Ready</h4>
1093
- <p style="margin: 0; opacity: 0.9;">Download your high-quality PNG file for LinkedIn, professional profiles, or portfolios.</p>
1094
- </div>
1095
- """)
1096
-
1097
- # Progress Information
1098
- progress_info = gr.HTML("""
1099
- <div class="progress-container">
1100
- <div style="font-size: 0.9em; color: var(--text-secondary);">
1101
- ⏱️ Generation takes 20-30 seconds
1102
- </div>
1103
- </div>
1104
- """)
1105
-
1106
- # Hidden technical parameters
1107
- negative_prompt = gr.Textbox(
1108
- value="(lowres, low quality, worst quality:1.2), (text:1.2), watermark, (frame:1.2), deformed, ugly, deformed eyes, blur, out of focus, blurry, deformed cat, deformed, photo, anthropomorphic cat, monochrome, pet collar, gun, weapon, blue, 3d, drones, drone, buildings in background, green",
1109
- visible=False
1110
- )
1111
- num_steps = gr.Slider(
1112
- minimum=5,
1113
- maximum=100,
1114
- step=1,
1115
- value=30,
1116
- label="Number of steps",
1117
- visible=False
1118
- )
1119
- guidance_scale = gr.Slider(
1120
- minimum=0.1,
1121
- maximum=20.0,
1122
- step=0.1,
1123
- value=5.0,
1124
- label="Guidance scale",
1125
- visible=False
1126
- )
1127
- seed = gr.Slider(
1128
- minimum=0,
1129
- maximum=MAX_SEED,
1130
- step=1,
1131
- value=42,
1132
- label="Seed",
1133
- visible=False
1134
- )
1135
- scheduler = gr.Dropdown(
1136
- value="EulerDiscreteScheduler",
1137
- choices=["EulerDiscreteScheduler", "EulerAncestralDiscreteScheduler", "DPMSolverMultistepScheduler"],
1138
- visible=False
1139
- )
1140
- randomize_seed = gr.Checkbox(value=True, visible=False)
1141
- enhance_face_region = gr.Checkbox(value=True, visible=False)
1142
- controlnet_selection = gr.CheckboxGroup(
1143
- choices=["canny", "depth"],
1144
- value=["depth"],
1145
- label="Controlnet",
1146
- visible=False
1147
- )
1148
- canny_strength = gr.Slider(
1149
- minimum=0,
1150
- maximum=1.5,
1151
- step=0.01,
1152
- value=0.4,
1153
- label="Canny strength",
1154
- visible=False
1155
- )
1156
- depth_strength = gr.Slider(
1157
- minimum=0,
1158
- maximum=1.5,
1159
- step=0.01,
1160
- value=0.4,
1161
- label="Depth strength",
1162
- visible=False
1163
- )
1164
-
1165
- def show_success():
1166
- return """
1167
- <div class="success-banner">
1168
- <h4 style="margin: 0 0 8px 0;">✅ Success! Your Professional Headshot is Ready</h4>
1169
- <p style="margin: 0; opacity: 0.9;">Download your high-quality PNG file for LinkedIn, professional profiles, or portfolios.</p>
1170
- </div>
1171
- """
1172
-
1173
- def update_trial_display(license_key):
1174
- """Update trial counter based on license status"""
1175
- if verify_license(license_key):
1176
- return """
1177
- <div class="trial-banner">
1178
- <h4 style="margin: 0 0 8px 0;">✅ Premium License Active</h4>
1179
- <p style="margin: 0; font-size: 0.9em;">Unlimited HD downloads - no watermark</p>
1180
- </div>
1181
- """
1182
-
1183
- user_id = get_user_identifier()
1184
- can_use, trials_left = can_use_free_trial(user_id)
1185
-
1186
- if not can_use:
1187
- return f"""
1188
- <div class="trial-banner-error">
1189
- <h4 style="margin: 0 0 8px 0;">❌ No Free Trials Left</h4>
1190
- <p style="margin: 0; font-size: 0.9em;">Please purchase a license to continue</p>
1191
- </div>
1192
- """
1193
-
1194
- return f"""
1195
- <div class="trial-banner">
1196
- <h4 style="margin: 0 0 8px 0;">🎉 {trials_left} Free Generations Left!</h4>
1197
- <p style="margin: 0; font-size: 0.9em;">Watermarked previews - upgrade for HD downloads</p>
1198
- </div>
1199
- """
1200
-
1201
- def hide_loading():
1202
- """Hide loading overlay once app is ready"""
1203
- return gr.update(visible=False)
1204
-
1205
- # Hide loading overlay after 5 seconds
1206
- import threading
1207
- def delayed_hide():
1208
- time.sleep(5)
1209
- loading_html.value = ""
1210
 
1211
- hide_thread = threading.Thread(target=delayed_hide)
1212
- hide_thread.daemon = True
1213
- hide_thread.start()
1214
-
1215
- submit.click(
1216
- fn=generate_image,
1217
- inputs=[
1218
- face_file,
1219
- license_input,
1220
- prompt,
1221
- negative_prompt,
1222
- style,
1223
- num_steps,
1224
- identitynet_strength_ratio,
1225
- adapter_strength_ratio,
1226
- canny_strength,
1227
- depth_strength,
1228
- controlnet_selection,
1229
- guidance_scale,
1230
- seed,
1231
- scheduler,
1232
- enable_LCM,
1233
- enhance_face_region,
1234
- ],
1235
- outputs=[gallery, success_msg]
1236
- ).then(
1237
- fn=show_success,
1238
- outputs=success_msg
1239
- )
1240
-
1241
- # Update trial display when license input changes
1242
- license_input.change(
1243
- fn=update_trial_display,
1244
- inputs=[license_input],
1245
- outputs=[trial_status]
1246
  )
1247
-
1248
- enable_LCM.input(
1249
- fn=toggle_lcm_ui,
1250
- inputs=[enable_LCM],
1251
- outputs=[num_steps, guidance_scale],
1252
- queue=False,
1253
  )
1254
 
1255
- print("✅ App initialization complete!")
1256
- print(f"🔑 Test license key: HEADSHOT-TEST123456")
1257
- print(f"🎮 Free trials: {MAX_FREE_TRIALS} per user")
1258
- print("🚀 Ready to launch!")
1259
-
1260
  if __name__ == "__main__":
1261
- demo.queue(max_size=3)
1262
- demo.launch(
1263
- share=True,
1264
- server_name="0.0.0.0",
1265
- server_port=7860,
1266
- show_error=True,
1267
- debug=True
1268
- )
 
1
  import os
2
+ os.environ['CUDA_VISIBLE_DEVICES'] = '0'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
  import gradio as gr
5
+ import numpy as np
6
+ from PIL import Image, ImageDraw
7
+ import torch
8
 
9
+ print(f"🚀 Starting on ZeroGPU with PyTorch {torch.__version__}")
10
+ print(f"✅ CUDA available: {torch.cuda.is_available()}")
11
+ print(f"✅ GPU: {torch.cuda.get_device_name(0)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  # ===== LICENSE SYSTEM =====
14
+ ADMIN_KEYS = {"HEADSHOT-TEST123456", "HEADSHOT-OWNERACCESS"}
15
+ MAX_TRIALS = 3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
+ def verify_license(key):
18
+ return key.strip().upper() in ADMIN_KEYS if key else False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
+ # ===== SIMPLE GENERATOR =====
21
+ def generate_headshot(image, license_key):
22
+ """Placeholder that shows GPU is working"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
+ # Verify license
25
+ if not verify_license(license_key):
26
+ return np.zeros((512, 512, 3), dtype=np.uint8), "❌ Invalid license"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
+ # Create result image
29
+ if image is not None:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  h, w = image.shape[:2]
31
+ result = np.ones((h, w, 3), dtype=np.uint8) * 240
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  else:
33
+ result = np.ones((512, 512, 3), dtype=np.uint8) * 240
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
+ # Add GPU status
36
+ img = Image.fromarray(result)
37
+ draw = ImageDraw.Draw(img)
38
 
39
+ draw.text((30, 30), "✅ ZEROGPU WORKING", fill=(0, 150, 0), size=20)
40
+ draw.text((30, 70), f"PyTorch {torch.__version__}", fill=(0, 0, 0))
41
+ draw.text((30, 100), f"GPU: {torch.cuda.get_device_name(0)}", fill=(0, 0, 0))
42
+ draw.text((30, 130), f"Memory: {torch.cuda.get_device_properties(0).total_memory/1e9:.1f}GB", fill=(0, 0, 0))
43
+ draw.text((30, 160), "Test Key: HEADSHOT-TEST123456", fill=(50, 50, 150))
44
+ draw.text((30, 190), "Next: Add AI models", fill=(150, 50, 0))
45
 
46
+ return np.array(img), "✅ Success! GPU is working."
 
47
 
48
+ # ===== GRADIO UI =====
49
+ with gr.Blocks(title="AI Headshot Generator", theme=gr.themes.Soft()) as demo:
50
+
51
+ gr.Markdown("""
52
+ # 🎯 AI Headshot Generator
53
+ *Powered by ZeroGPU - PyTorch ready*
54
+ """)
55
+
56
+ with gr.Row():
57
+ with gr.Column(scale=1):
58
+ # GPU Status
59
+ gr.Markdown(f"""
60
+ ### 🚀 ZeroGPU Status
61
+ - **PyTorch**: {torch.__version__}
62
+ - **CUDA**: {'✅ Available' if torch.cuda.is_available() else '❌ Not available'}
63
+ - **GPU**: {torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'None'}
64
+ """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
+ # Upload
67
+ image_input = gr.Image(
68
+ label="📸 Upload Photo",
69
+ type="numpy",
70
+ height=250
71
+ )
72
 
73
+ # License
74
+ license_input = gr.Textbox(
75
+ label="🔑 License Key",
76
+ value="HEADSHOT-TEST123456",
77
+ placeholder="Enter HEADSHOT-TEST123456"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
+ # Generate Button
81
+ generate_btn = gr.Button(
82
+ "✨ Test GPU & Generate",
83
+ variant="primary",
84
+ size="lg"
85
+ )
86
 
87
+ # Status
88
+ status_output = gr.Textbox(
89
+ label="Status",
90
+ interactive=False
91
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
+ with gr.Column(scale=1):
94
+ # Result
95
+ result_image = gr.Image(
96
+ label="🖼️ Result",
97
+ height=400
98
+ )
99
 
100
+ gr.Markdown("""
101
+ ### 🎯 Next Steps
102
+ 1. ✅ This confirms ZeroGPU works
103
+ 2. 🔧 Add AI models gradually
104
+ 3. 🚀 Deploy full headshot generator
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
 
106
+ [🔗 Purchase License](https://canadianheadshotpro.carrd.co)
107
+ """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
 
109
+ # Connect
110
+ generate_btn.click(
111
+ fn=generate_headshot,
112
+ inputs=[image_input, license_input],
113
+ outputs=[result_image, status_output]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  )
115
+
116
+ # Auto-test on load
117
+ demo.load(
118
+ fn=lambda: f"��� ZeroGPU ready with {torch.cuda.get_device_name(0)}",
119
+ outputs=[status_output]
 
120
  )
121
 
 
 
 
 
 
122
  if __name__ == "__main__":
123
+ demo.launch(share=True)