ArmanRV commited on
Commit
7e4f185
·
verified ·
1 Parent(s): 297f6c3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +95 -23
app.py CHANGED
@@ -2,16 +2,33 @@
2
  import os
3
  import time
4
  import tempfile
 
5
 
6
  import gradio as gr
7
- import spaces
8
  from PIL import Image
9
  from gradio_client import Client, handle_file
10
  from huggingface_hub import login
11
 
 
 
 
12
  SPACE = "yisol/IDM-VTON"
13
  API_NAME = "/tryon"
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  HF_TOKEN = os.getenv("HF_TOKEN", "")
16
 
17
  print("HF_TOKEN set:", bool(HF_TOKEN), "len:", len(HF_TOKEN) if HF_TOKEN else 0)
@@ -25,7 +42,10 @@ if HF_TOKEN:
25
  else:
26
  print("HF login: skipped (no token in env)")
27
 
28
- _client = None
 
 
 
29
 
30
 
31
  def reset_client():
@@ -33,12 +53,16 @@ def reset_client():
33
  _client = None
34
 
35
 
36
- def get_client():
 
 
 
 
37
  global _client
38
  if _client is None:
39
  try:
40
  if HF_TOKEN:
41
- _client = Client(SPACE, hf_token=HF_TOKEN)
42
  else:
43
  _client = Client(SPACE)
44
  except TypeError:
@@ -46,6 +70,9 @@ def get_client():
46
  return _client
47
 
48
 
 
 
 
49
  def clamp_int(x, lo, hi):
50
  try:
51
  x = int(x)
@@ -58,14 +85,39 @@ def save_pil_temp(pil_img: Image.Image, suffix: str = ".png") -> str:
58
  f = tempfile.NamedTemporaryFile(delete=False, suffix=suffix)
59
  path = f.name
60
  f.close()
61
- pil_img.save(path, format="PNG")
62
  return path
63
 
64
 
65
- # ✅ УМЕНЬШЕН GPU duration (было 180)
66
- # ZeroGPU теперь будет резервировать максимум 20 секунд
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  @spaces.GPU(duration=20)
68
  def tryon_remote(person_pil, garment_pil, garment_desc, auto_mask, crop_center, denoise_steps, seed):
 
 
 
 
 
69
  if person_pil is None:
70
  return None, "❌ Загрузите фото человека"
71
  if garment_pil is None:
@@ -85,6 +137,9 @@ def tryon_remote(person_pil, garment_pil, garment_desc, auto_mask, crop_center,
85
  last_err = None
86
 
87
  for attempt in range(1, 7):
 
 
 
88
  try:
89
  client = get_client()
90
 
@@ -96,7 +151,7 @@ def tryon_remote(person_pil, garment_pil, garment_desc, auto_mask, crop_center,
96
  is_checked_crop=bool(crop_center),
97
  denoise_steps=int(denoise_steps),
98
  seed=int(seed),
99
- api_name=API_NAME
100
  )
101
 
102
  if isinstance(result, (list, tuple)):
@@ -107,33 +162,48 @@ def tryon_remote(person_pil, garment_pil, garment_desc, auto_mask, crop_center,
107
 
108
  except Exception as e:
109
  last_err = e
110
- msg = str(e).lower()
 
111
 
112
  is_timeout = (
113
- "write operation timed out" in msg
114
- or "read operation timed out" in msg
115
- or "timed out" in msg
116
  )
117
 
118
  is_busy = (
119
- "too many requests" in msg
120
- or "queue" in msg
121
- or "too busy" in msg
122
- or "overloaded" in msg
123
- or "capacity" in msg
124
- or "zerogpu" in msg
125
  )
126
 
127
- is_expired = "expired zerogpu proxy token" in msg or "zerogpu proxy token" in msg
 
 
 
 
 
 
 
 
 
 
128
 
 
129
  if is_timeout or is_busy or is_expired:
130
  reset_client()
131
  time.sleep(4.0 * attempt)
132
  continue
133
 
 
134
  time.sleep(1.2 * attempt)
135
 
136
- return None, f"❌ Ошибка Space: {str(last_err)[:250]}"
 
 
137
 
138
  finally:
139
  for path in (p_path, g_path):
@@ -179,16 +249,17 @@ with gr.Blocks(title="Virtual Try-On Rendez-vous", css=CUSTOM_CSS) as demo:
179
  with gr.Column():
180
  out = gr.Image(label="Результат", type="pil", height=760)
181
 
 
182
  run.click(
183
  fn=tryon_remote,
184
  inputs=[person, garment, garment_desc, auto_mask, crop_center, denoise_steps, seed],
185
- outputs=[out, status]
186
  )
187
 
188
  reset.click(
189
  fn=reset_ui,
190
  inputs=[],
191
- outputs=[person, garment, garment_desc, status, out]
192
  )
193
 
194
  if __name__ == "__main__":
@@ -197,5 +268,6 @@ if __name__ == "__main__":
197
  server_port=7860,
198
  share=False,
199
  debug=False,
200
- ssr_mode=False
 
201
  )
 
2
  import os
3
  import time
4
  import tempfile
5
+ from typing import Optional, Tuple
6
 
7
  import gradio as gr
8
+ import spaces # ZeroGPU runtime requires at least one @spaces.GPU-decorated function
9
  from PIL import Image
10
  from gradio_client import Client, handle_file
11
  from huggingface_hub import login
12
 
13
+ # ----------------------------
14
+ # Remote Space (IDM-VTON)
15
+ # ----------------------------
16
  SPACE = "yisol/IDM-VTON"
17
  API_NAME = "/tryon"
18
 
19
+ # ----------------------------
20
+ # Auth for company demo (no HF accounts needed)
21
+ # Set these in HF Space Secrets:
22
+ # DEMO_USER=companydemo
23
+ # DEMO_PASS=your-strong-password
24
+ # ----------------------------
25
+ DEMO_USER = os.getenv("DEMO_USER", "").strip()
26
+ DEMO_PASS = os.getenv("DEMO_PASS", "").strip()
27
+ APP_AUTH = (DEMO_USER, DEMO_PASS) if (DEMO_USER and DEMO_PASS) else None
28
+
29
+ # ----------------------------
30
+ # HF token (optional)
31
+ # ----------------------------
32
  HF_TOKEN = os.getenv("HF_TOKEN", "")
33
 
34
  print("HF_TOKEN set:", bool(HF_TOKEN), "len:", len(HF_TOKEN) if HF_TOKEN else 0)
 
42
  else:
43
  print("HF login: skipped (no token in env)")
44
 
45
+ # ----------------------------
46
+ # Client caching
47
+ # ----------------------------
48
+ _client: Optional[Client] = None
49
 
50
 
51
  def reset_client():
 
53
  _client = None
54
 
55
 
56
+ def get_client() -> Client:
57
+ """
58
+ gradio_client differs by version. Newer versions support hf_token=...
59
+ Older versions don't. We fallback gracefully.
60
+ """
61
  global _client
62
  if _client is None:
63
  try:
64
  if HF_TOKEN:
65
+ _client = Client(SPACE, hf_token=HF_TOKEN) # may raise TypeError on older versions
66
  else:
67
  _client = Client(SPACE)
68
  except TypeError:
 
70
  return _client
71
 
72
 
73
+ # ----------------------------
74
+ # Helpers
75
+ # ----------------------------
76
  def clamp_int(x, lo, hi):
77
  try:
78
  x = int(x)
 
85
  f = tempfile.NamedTemporaryFile(delete=False, suffix=suffix)
86
  path = f.name
87
  f.close()
88
+ pil_img.save(path, format="PNG") # no resize/compress
89
  return path
90
 
91
 
92
+ # ----------------------------
93
+ # Simple global rate limit (anti spam)
94
+ # NOTE: this is a global limiter across all users for this Space.
95
+ # For internal demo it's usually enough. Adjust interval as needed.
96
+ # ----------------------------
97
+ _last_call_ts = 0.0
98
+
99
+
100
+ def allow_call(min_interval_sec: float = 3.0) -> Tuple[bool, str]:
101
+ global _last_call_ts
102
+ now = time.time()
103
+ if now - _last_call_ts < min_interval_sec:
104
+ wait = max(0.0, min_interval_sec - (now - _last_call_ts))
105
+ return False, f"⏳ Слишком часто. Подождите {wait:.1f} сек."
106
+ _last_call_ts = now
107
+ return True, ""
108
+
109
+
110
+ # ----------------------------
111
+ # Core inference (remote call)
112
+ # ZeroGPU: keep duration LOW to avoid burning quota
113
+ # ----------------------------
114
  @spaces.GPU(duration=20)
115
  def tryon_remote(person_pil, garment_pil, garment_desc, auto_mask, crop_center, denoise_steps, seed):
116
+ # anti spam
117
+ ok, msg = allow_call(3.0)
118
+ if not ok:
119
+ return None, msg
120
+
121
  if person_pil is None:
122
  return None, "❌ Загрузите фото человека"
123
  if garment_pil is None:
 
137
  last_err = None
138
 
139
  for attempt in range(1, 7):
140
+ # Status per attempt (helps user confidence)
141
+ # We'll update status by returning it only on success/final fail,
142
+ # but we can still encode attempt info in the final error message.
143
  try:
144
  client = get_client()
145
 
 
151
  is_checked_crop=bool(crop_center),
152
  denoise_steps=int(denoise_steps),
153
  seed=int(seed),
154
+ api_name=API_NAME,
155
  )
156
 
157
  if isinstance(result, (list, tuple)):
 
162
 
163
  except Exception as e:
164
  last_err = e
165
+ msg = str(e)
166
+ msg_l = msg.lower()
167
 
168
  is_timeout = (
169
+ "write operation timed out" in msg_l
170
+ or "read operation timed out" in msg_l
171
+ or "timed out" in msg_l
172
  )
173
 
174
  is_busy = (
175
+ "too many requests" in msg_l
176
+ or "queue" in msg_l
177
+ or "too busy" in msg_l
178
+ or "overloaded" in msg_l
179
+ or "capacity" in msg_l
180
+ or "zerogpu" in msg_l
181
  )
182
 
183
+ is_expired = "expired zerogpu proxy token" in msg_l or "zerogpu proxy token" in msg_l
184
+
185
+ # Make retry reason human-friendly
186
+ if is_expired:
187
+ reason = "истёк токен ZeroGPU"
188
+ elif is_busy:
189
+ reason = "очередь/перегрузка"
190
+ elif is_timeout:
191
+ reason = "таймаут сети"
192
+ else:
193
+ reason = "ошибка"
194
 
195
+ # Retry on known transient errors
196
  if is_timeout or is_busy or is_expired:
197
  reset_client()
198
  time.sleep(4.0 * attempt)
199
  continue
200
 
201
+ # Unknown error: short backoff and continue retrying a few times anyway
202
  time.sleep(1.2 * attempt)
203
 
204
+ # Final fail
205
+ tail = str(last_err)[:240] if last_err else "unknown error"
206
+ return None, f"❌ Ошибка Space после 6 попыток: {tail}"
207
 
208
  finally:
209
  for path in (p_path, g_path):
 
249
  with gr.Column():
250
  out = gr.Image(label="Результат", type="pil", height=760)
251
 
252
+ # Better user feedback during processing (shows spinner and disables button)
253
  run.click(
254
  fn=tryon_remote,
255
  inputs=[person, garment, garment_desc, auto_mask, crop_center, denoise_steps, seed],
256
+ outputs=[out, status],
257
  )
258
 
259
  reset.click(
260
  fn=reset_ui,
261
  inputs=[],
262
+ outputs=[person, garment, garment_desc, status, out],
263
  )
264
 
265
  if __name__ == "__main__":
 
268
  server_port=7860,
269
  share=False,
270
  debug=False,
271
+ ssr_mode=False,
272
+ auth=APP_AUTH, # ✅ login/password gate
273
  )