renderfy commited on
Commit
2e0e104
·
verified ·
1 Parent(s): da2c648

Upload streamlit_app.py

Browse files
Files changed (1) hide show
  1. streamlit_app.py +195 -281
streamlit_app.py CHANGED
@@ -1,18 +1,16 @@
1
- # streamlit_app.py — AI LightBox · Jewelry (uniform one-row previews + unique keys)
2
- import os, io, zipfile, requests, json, streamlit as st
3
  from PIL import Image
4
 
5
- # ================ Env & API ================
6
  API_BASE = (os.getenv("AI_LIGHTBOX_API", "http://127.0.0.1:8000") or "http://127.0.0.1:8000").strip().strip("'\"").rstrip("/")
7
  HF_TOKEN = (os.getenv("AI_LIGHTBOX_TOKEN", "") or "").strip().strip("'\"")
8
  HEADERS = {"Authorization": f"Bearer {HF_TOKEN}"} if HF_TOKEN else {}
 
9
 
10
- # Önizleme sabitleri
11
- PREVIEW_SIZE = int(os.getenv("PREVIEW_SIZE", "384")) # tüm thumbs sabit
12
- PAIRS_PER_ROW = 2 # sadece legacy eşleşmeli görünüm istersek kullanılır
13
-
14
- # ================ Helpers ================
15
  def _needs_auth(url: str) -> bool:
 
16
  return url.startswith(API_BASE) or url.startswith("outputs/")
17
 
18
  def backend_ok() -> bool:
@@ -24,32 +22,20 @@ def backend_ok() -> bool:
24
  return False
25
 
26
  def post_chain(data: dict, files_payload):
 
27
  data = {**data, "to_video": "false"}
28
  r = requests.post(f"{API_BASE}/v1/tryon/chain", data=data, files=files_payload or None,
29
  headers=HEADERS, timeout=600)
30
  r.raise_for_status()
31
  return r.json()
32
 
33
- def post_attach_direct(data: dict, files_payload):
34
- data = {**data, "to_video": "false"}
35
- r = requests.post(f"{API_BASE}/v1/attach/direct", data=data, files=files_payload or None,
36
- headers=HEADERS, timeout=600)
37
- r.raise_for_status()
38
- return r.json()
39
-
40
- def post_packshot(data: dict, files_payload):
41
- data = {**data, "to_video": "false"}
42
- r = requests.post(f"{API_BASE}/v1/packshot", data=data, files=files_payload or None,
43
- headers=HEADERS, timeout=600)
44
- r.raise_for_status()
45
- return r.json()
46
-
47
  def post_video(image_url: str, duration="6", resolution="768P", prompt_optimizer=False):
48
  payload = {
49
  "image_url": image_url,
50
  "duration": duration,
51
  "resolution": resolution,
52
- "prompt_optimizer": "true" if prompt_optimizer else "false",
 
53
  }
54
  r = requests.post(f"{API_BASE}/v1/video/from-image", data=payload, headers=HEADERS, timeout=600)
55
  r.raise_for_status()
@@ -60,7 +46,10 @@ def fetch_bytes(url_or_path: str):
60
  if not url_or_path:
61
  return None
62
  try:
63
- url = f"{API_BASE}/{url_or_path}" if url_or_path.startswith("outputs/") else url_or_path
 
 
 
64
  headers = HEADERS if _needs_auth(url) else None
65
  r = requests.get(url, headers=headers, timeout=180)
66
  r.raise_for_status()
@@ -72,7 +61,7 @@ def fetch_bytes(url_or_path: str):
72
  def image_size_from_bytes(b: bytes):
73
  try:
74
  im = Image.open(io.BytesIO(b))
75
- return im.size
76
  except Exception:
77
  return None
78
 
@@ -110,329 +99,254 @@ def first_present(d: dict, keys: list, default=None):
110
  return v
111
  return default
112
 
113
- def parse_urls_field_all(s: str | None) -> list[str]:
114
- if not s:
115
- return []
116
- s = s.strip()
117
- if s.startswith("["):
118
- try:
119
- arr = json.loads(s)
120
- if isinstance(arr, list):
121
- return [str(x).strip() for x in arr if str(x).strip()]
122
- except Exception:
123
- return []
124
- if "," in s:
125
- return [x.strip() for x in s.split(",") if x.strip()]
126
- return [s]
127
-
128
- # ---- Tiles (sabit boyut + unique key) ----
129
- def show_tile(col, title, url: str | None, section: str, idx: int, filename_stub="image"):
130
  col.subheader(title)
131
  if not url:
132
- ph = Image.new("RGBA", (PREVIEW_SIZE, PREVIEW_SIZE), (0, 0, 0, 0))
133
- col.image(ph, width=PREVIEW_SIZE)
134
  col.caption("—")
135
  return None, None
 
136
  b = fetch_bytes(url)
137
  if not b:
138
- col.warning("Görsel yüklenemedi"); return None, None
139
- pv = square_preview_bytes(b, size=PREVIEW_SIZE)
140
- col.image(pv, width=PREVIEW_SIZE) # sabit boy
141
- sz = image_size_from_bytes(b)
142
- if sz: col.caption(f"Gerçek çözünürlük: {sz[0]}×{sz[1]} px")
143
- ext = infer_ext(b)
144
- mime = "image/jpeg" if ext.lower() in [".jpg", ".jpeg"] else ("image/png" if ext.lower()==".png" else "image/webp")
145
- col.download_button("İndir (tam kalite)", data=b, file_name=f"{filename_stub}{ext}", mime=mime,
146
- key=f"dl_{section}_{idx}")
147
- return url, b
148
 
149
- def show_bytes_tile(col, title, b: bytes | None, section: str, idx: int, filename_stub="image"):
150
- col.subheader(title)
151
- if not b:
152
- ph = Image.new("RGBA", (PREVIEW_SIZE, PREVIEW_SIZE), (0, 0, 0, 0))
153
- col.image(ph, width=PREVIEW_SIZE); col.caption("—"); return None
154
- pv = square_preview_bytes(b, size=PREVIEW_SIZE)
155
- col.image(pv, width=PREVIEW_SIZE)
156
  sz = image_size_from_bytes(b)
157
- if sz: col.caption(f"Girdi çözünürlüğü: {sz[0]}×{sz[1]} px")
 
 
158
  ext = infer_ext(b)
 
159
  mime = "image/jpeg" if ext.lower() in [".jpg", ".jpeg"] else ("image/png" if ext.lower()==".png" else "image/webp")
160
- col.download_button("İndir (tam kalite)", data=b, file_name=f"{filename_stub}{ext}", mime=mime,
161
- key=f"dl_{section}_{idx}")
162
- return b
163
 
164
  def make_zip(named_bytes: list[tuple[str, bytes]]) -> bytes:
165
  buf = io.BytesIO()
166
  with zipfile.ZipFile(buf, "w", compression=zipfile.ZIP_DEFLATED) as z:
167
  for fname, b in named_bytes:
168
- if b: z.writestr(fname, b)
169
- buf.seek(0); return buf.read()
170
-
171
- # ---- Gallery (tek satır, aynı boyut) ----
172
- def show_gallery_bytes_one_row(title: str, items: list[bytes], section: str, prefix="item"):
173
- st.markdown(f"### {title}")
174
- if not items:
175
- st.caption("—"); return []
176
- cols = st.columns(len(items), gap="small") # tek satır, kaç öğe varsa o kadar kolon
177
- named = []
178
- for i, b in enumerate(items):
179
- got = show_bytes_tile(cols[i], f"{prefix} {i+1}", b, section, i, filename_stub=f"{section}_{prefix}_{i+1}")
180
- if got: named.append((f"{section}_{prefix}_{i+1}{infer_ext(got)}", got))
181
- return named
182
-
183
- def show_gallery_urls_one_row(title: str, urls: list[str], section: str, prefix="item"):
184
- st.markdown(f"### {title}")
185
- if not urls:
186
- st.caption("—"); return []
187
- cols = st.columns(len(urls), gap="small")
188
- named = []
189
- for i, u in enumerate(urls):
190
- _, b = show_tile(cols[i], f"{prefix} {i+1}", u, section, i, filename_stub=f"{section}_{prefix}_{i+1}")
191
- if b: named.append((f"{section}_{prefix}_{i+1}{infer_ext(b)}", b))
192
- return named
193
-
194
- # ================ Page ================
195
  st.set_page_config(page_title="AI LightBox · Jewelry", layout="wide", page_icon="💎")
196
  st.title("💎 AI LightBox · Jewelry")
 
197
 
198
- # Mod seçimi
199
- mode = st.radio("Mod", ["Direct attach", "Packshot", "Legacy chain"], index=0, horizontal=True)
200
- st.caption(
201
- "Direct attach: her ürün tek tek padding (içerik ölçeği) alır → tek seferde mankene takılır → Final SR → (opsiyonel) Video. \n"
202
- "Packshot: ayrı endpoint; padding yok; tek/çoklu girdiden packshot + SR → (opsiyonel) Video. \n"
203
- "Legacy chain: eski akış (packshot→padding→model)."
204
- )
205
-
206
- # ================ Üst Kontrol Şeridi ================
207
  ok = backend_ok()
208
- col1, col2, col3, col4, col5, col6, col7 = st.columns([1.8, 1.8, 1.2, 1.35, 1.0, 1.1, 1.1])
209
 
210
  with col1:
211
- file_list = st.file_uploader("Ürün görselleri (çoklu)", type=["jpg","jpeg","png","webp"], accept_multiple_files=True)
212
  with col2:
213
- image_urls_field = st.text_input("veya URL(ler) — tek / CSV / JSON", value="")
214
  with col3:
215
- jewel_type = st.selectbox("Tür (Legacy)", ["auto","earring","ring","necklace","bracelet","pendant","brooch","set"],
216
- index=0, disabled=(mode!="Legacy chain"))
217
  with col4:
218
  padding_ratio_val = st.number_input(
219
- "İçerik ölçeği (Direct) 0.30–0.95",
220
  min_value=0.30, max_value=0.95, value=0.50, step=0.01,
221
- help="Direct attach varsayılan içerik ölçeği.", disabled=(mode!="Direct attach")
222
  )
223
  with col5:
224
  upscale = st.checkbox("Super Resolution", True)
225
  with col6:
226
- upscale_stage = st.selectbox("SR aşaması (Legacy)", ["both","final","packshot"], index=0,
227
- help="Legacy chain için.", disabled=(mode!="Legacy chain"))
228
  with col7:
229
  upscale_factor = st.selectbox("SR katsayı", ["2","4"], index=0)
230
 
231
- colA, colB, colC, colD, colE = st.columns([1.0, 1.6, 1.6, 1.0, 1.0])
232
  with colA:
233
  st.info(f"Bağlantı: {'Online' if ok else 'Offline'}", icon="🔌")
234
  with colB:
235
- identity_lock = st.checkbox("Identity lock", True)
236
  with colC:
237
- mannequin_url = st.text_input("Manken URL (opsiyonel)", value="")
238
  with colD:
239
- duration = st.selectbox("Video (sn)", ["6","10"], index=0)
240
  with colE:
241
- to_video = st.checkbox("Video üret (SR kaynaklı)", False)
242
 
243
- # Packshot özel
244
- merge_into_single = st.checkbox("Packshot: Çoklu girdiyi tek kare birleştir", True, disabled=(mode!="Packshot"))
245
 
246
- run = st.button("Çalıştır", type="primary", use_container_width=True)
 
 
247
 
248
- # ================ Input önizleme (tek satır) ================
249
- ordered_input_bytes = []
250
- # URL'ler önce (backend input sırası ile uyumlu olsun)
251
- for u in parse_urls_field_all(image_urls_field):
252
- b = fetch_bytes(u)
253
- if b: ordered_input_bytes.append(b)
254
- # Dosyalar sonra
255
  if file_list:
256
- for f in file_list:
257
- try: ordered_input_bytes.append(f.getvalue())
258
- except Exception: pass
 
 
 
259
 
260
- _ = show_gallery_bytes_one_row("Input(lar)", ordered_input_bytes, section="inputs", prefix="input")
 
 
 
 
 
 
 
 
 
261
 
262
  # ================ Koş & Göster ================
263
  if run:
264
  if not ok:
265
  st.error("Backend erişilemiyor. API_BASE / TOKEN kontrol edin.")
266
- elif not (file_list or image_urls_field.strip()):
267
- st.error("En az bir görsel girişi yapın (dosya veya URL).")
268
  else:
269
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  files_payload = []
271
  if file_list:
272
  for f in file_list:
273
  files_payload.append(("files", (f.name, f.getvalue(), f.type or "image/jpeg")))
274
 
275
- all_named = [] # zip için
276
-
277
- # -------- Direct attach --------
278
- if mode == "Direct attach":
279
- data = {
280
- "product_image_urls": image_urls_field.strip(),
281
- "categories": "", # opsiyonel kategori listesi girmiyorsak boş kalsın
282
- "padding_ratio": str(clamp(padding_ratio_val, 0.30, 0.95)),
283
- "padding_ratios": "",
284
- "mannequin_image_url": mannequin_url.strip(),
285
- "identity_lock": "true" if identity_lock else "false",
286
- "upscale": "true" if upscale else "false",
287
- "upscale_factor": upscale_factor,
288
- }
289
- with st.spinner("Direct attach çalışıyor…"):
290
- out = post_attach_direct(data, files_payload)
291
-
292
- # Padded ürünler TEK SATIR
293
- padded_list = (out.get("attach_input_debug") or {}).get("padded_product_urls") or []
294
- all_named += show_gallery_urls_one_row("Padded (1200×1200)", padded_list, section="padded", prefix="padded")
295
-
296
- # Nihai model (SR varsa öncelik)
297
- attach = out.get("attach_step", {}) or {}
298
- imgs = (attach.get("result") or {}).get("images") or []
299
- attached_url = imgs[0].get("url") if imgs else None
300
- attach_sr = attach.get("upscaled_final_urls") or []
301
- attached_sr_url = attach_sr[0] if attach_sr else None
302
- final_img = attached_sr_url or attached_url
303
-
304
- st.markdown("### Model Üzerinde")
305
- _, bfinal = show_tile(st, "Sonuç", final_img, section="final_direct", idx=0, filename_stub="model_direct")
306
- if bfinal: all_named.append((f"model_direct{infer_ext(bfinal)}", bfinal))
307
-
308
- # Video (SR kaynak)
309
- if to_video and final_img:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
  with st.spinner("Video üretiliyor (SR kaynak)…"):
311
- v = post_video(final_img, duration=duration, resolution="768P", prompt_optimizer=False)
312
  vurl = (v.get("result") or {}).get("video", {}).get("url")
313
  if vurl:
314
- st.subheader("Video")
315
  st.video(vurl, format="video/mp4")
316
  vb = fetch_bytes(vurl)
317
  if vb:
318
- all_named.append(("video.mp4", vb))
319
- st.download_button("Videoyu indir (MP4)", data=vb, file_name="ai_lightbox_video.mp4",
320
- mime="video/mp4", key="dl_video_direct")
321
-
322
- # -------- Packshot --------
323
- elif mode == "Packshot":
324
- data = {
325
- "product_image_urls": image_urls_field.strip(),
326
- "merge_into_single": "true" if merge_into_single else "false",
327
- "upscale": "true" if upscale else "false",
328
- "upscale_factor": upscale_factor,
329
- }
330
- with st.spinner("Packshot çalışıyor…"):
331
- out = post_packshot(data, files_payload)
332
-
333
- ps = out.get("packshot_step", {}) or {}
334
- imgs = (ps.get("result") or {}).get("images") or []
335
- packshot_url = imgs[0].get("url") if imgs else None
336
- packshot_sr = ps.get("upscaled_packshot_urls") or []
337
- packshot_sr_url = packshot_sr[0] if packshot_sr else None
338
- final_img = packshot_sr_url or packshot_url
339
-
340
- st.markdown("### Packshot")
341
- _, bps = show_tile(st, "Packshot (SR öncelikli)", final_img, section="packshot", idx=0, filename_stub="packshot")
342
- if bps: all_named.append((f"packshot{infer_ext(bps)}", bps))
343
-
344
- if to_video and final_img:
345
- with st.spinner("Video üretiliyor (SR kaynak)…"):
346
- v = post_video(final_img, duration=duration, resolution="768P", prompt_optimizer=False)
347
- vurl = (v.get("result") or {}).get("video", {}).get("url")
348
- if vurl:
349
- st.subheader("Video")
350
- st.video(vurl, format="video/mp4")
351
- vb = fetch_bytes(vurl)
352
- if vb:
353
- all_named.append(("video.mp4", vb))
354
- st.download_button("Videoyu indir (MP4)", data=vb, file_name="ai_lightbox_video.mp4",
355
- mime="video/mp4", key="dl_video_packshot")
356
-
357
- # -------- Legacy chain (eski akış) --------
358
- else:
359
- data = {
360
- "category": jewel_type,
361
- "edit_prompt": "",
362
- "num_images": "1",
363
- "image_urls": image_urls_field.strip(),
364
- "mannequin_image_url": mannequin_url.strip(),
365
- "padding_ratio": str(clamp(padding_ratio_val, 0.30, 0.95)),
366
- "identity_lock": "true" if identity_lock else "false",
367
- "upscale": "true" if upscale else "false",
368
- "upscale_factor": upscale_factor,
369
- "upscale_stage": upscale_stage,
370
- }
371
- with st.spinner("Legacy chain çalışıyor…"):
372
- out = post_chain(data, files_payload)
373
-
374
- packshot_url = None
375
- try:
376
- imgs = out["packshot_step"]["result"]["images"]
377
- if imgs: packshot_url = imgs[0].get("url")
378
- except Exception:
379
- pass
380
- packshot_sr_list = first_present(out.get("packshot_step", {}), [
381
- "upscaled_packshot_urls", "upscaled_packshot_files"
382
- ], default=[]) or []
383
- packshot_sr_url = packshot_sr_list[0] if packshot_sr_list else None
384
-
385
- padded_url = out.get("padding_step", {}).get("saved_file") or \
386
- (out.get("packshot_step", {}).get("padded_urls") or [None])[0]
387
-
388
- placed_url = None
389
- try:
390
- imgs = out["image_step"]["result"]["images"]
391
- if imgs: placed_url = imgs[0].get("url")
392
- except Exception:
393
- pass
394
- final_sr_list = first_present(out.get("image_step", {}), [
395
- "upscaled_final_urls", "upscaled_final_files"
396
- ], default=[]) or []
397
- placed_sr_url = final_sr_list[0] if final_sr_list else None
398
- shown_final_url = placed_sr_url or placed_url
399
-
400
- # Üçlü satır (tek satırda üç karo)
401
- st.markdown("### Çıktılar")
402
- cols = st.columns(3, gap="small")
403
- _, b1 = show_tile(cols[0], "Packshot", packshot_sr_url or packshot_url, section="legacy_pack", idx=0, filename_stub="packshot")
404
- _, b2 = show_tile(cols[1], "Padded", padded_url, section="legacy_pad", idx=0, filename_stub="padded")
405
- _, b3 = show_tile(cols[2], "Model", shown_final_url, section="legacy_model", idx=0, filename_stub="model")
406
- for b, nm in [(b1,"packshot"),(b2,"padded"),(b3,"model")]:
407
- if b: all_named.append((f"{nm}{infer_ext(b)}", b))
408
-
409
- if to_video and shown_final_url:
410
- with st.spinner("Video üretiliyor (SR kaynak)…"):
411
- v = post_video(shown_final_url, duration=duration, resolution="768P", prompt_optimizer=False)
412
- vurl = (v.get("result") or {}).get("video", {}).get("url")
413
- if vurl:
414
- st.subheader("Video")
415
- st.video(vurl, format="video/mp4")
416
- vb = fetch_bytes(vurl)
417
- if vb:
418
- all_named.append(("video.mp4", vb))
419
- st.download_button("Videoyu indir (MP4)", data=vb, file_name="ai_lightbox_video.mp4",
420
- mime="video/mp4", key="dl_video_legacy")
421
-
422
- # ZIP
423
- if all_named:
424
- zip_bytes = make_zip(all_named)
425
- st.download_button("Hepsini ZIP indir", data=zip_bytes, file_name="ai_lightbox_outputs.zip",
426
- mime="application/zip", key=f"dl_zip_{mode.replace(' ','_')}")
427
 
428
  except requests.HTTPError as e:
429
  st.error(f"HTTP {e.response.status_code}")
430
  except Exception as e:
431
  st.error(f"Hata: {e}")
432
 
433
- # Sidebar
434
  with st.sidebar:
435
- if st.button("Önbelleği temizle", key="clear_cache"):
436
- st.cache_data.clear(); st.success("Önbellek temizlendi.")
 
437
  st.caption(f"API_BASE: {API_BASE}")
438
- if HF_TOKEN: st.caption("Auth: Bearer (aktif)")
 
 
1
+ # streamlit_app.py — AI LightBox · Jewelry (gerçek çözünürlük + SR both + video SR)
2
+ import os, io, zipfile, requests, streamlit as st
3
  from PIL import Image
4
 
5
+ # ================= Env & API =================
6
  API_BASE = (os.getenv("AI_LIGHTBOX_API", "http://127.0.0.1:8000") or "http://127.0.0.1:8000").strip().strip("'\"").rstrip("/")
7
  HF_TOKEN = (os.getenv("AI_LIGHTBOX_TOKEN", "") or "").strip().strip("'\"")
8
  HEADERS = {"Authorization": f"Bearer {HF_TOKEN}"} if HF_TOKEN else {}
9
+ PREVIEW_SIZE = int(os.getenv("PREVIEW_SIZE", "512")) # sadece UI kare önizleme
10
 
11
+ # ================= Yardımcılar =================
 
 
 
 
12
  def _needs_auth(url: str) -> bool:
13
+ # Sadece kendi backend çağrılarında token gönder
14
  return url.startswith(API_BASE) or url.startswith("outputs/")
15
 
16
  def backend_ok() -> bool:
 
22
  return False
23
 
24
  def post_chain(data: dict, files_payload):
25
+ # Videoyu backend'e bırakmıyoruz (SR final ile biz tetikleriz)
26
  data = {**data, "to_video": "false"}
27
  r = requests.post(f"{API_BASE}/v1/tryon/chain", data=data, files=files_payload or None,
28
  headers=HEADERS, timeout=600)
29
  r.raise_for_status()
30
  return r.json()
31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  def post_video(image_url: str, duration="6", resolution="768P", prompt_optimizer=False):
33
  payload = {
34
  "image_url": image_url,
35
  "duration": duration,
36
  "resolution": resolution,
37
+ "prompt_optimizer": "true" if prompt_optimizer else "false", # backend default False
38
+ # prompt vermiyoruz -> backend JEWELRY_VIDEO_PROMPT (zoom yok)
39
  }
40
  r = requests.post(f"{API_BASE}/v1/video/from-image", data=payload, headers=HEADERS, timeout=600)
41
  r.raise_for_status()
 
46
  if not url_or_path:
47
  return None
48
  try:
49
+ if url_or_path.startswith("outputs/"):
50
+ url = f"{API_BASE}/{url_or_path}"
51
+ else:
52
+ url = url_or_path
53
  headers = HEADERS if _needs_auth(url) else None
54
  r = requests.get(url, headers=headers, timeout=180)
55
  r.raise_for_status()
 
61
  def image_size_from_bytes(b: bytes):
62
  try:
63
  im = Image.open(io.BytesIO(b))
64
+ return im.size # (w, h)
65
  except Exception:
66
  return None
67
 
 
99
  return v
100
  return default
101
 
102
+ def show_tile(col, title, url: str | None, bg_rgb=(255,255,255), filename_stub="image"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  col.subheader(title)
104
  if not url:
105
+ placeholder = Image.new("RGBA", (PREVIEW_SIZE, PREVIEW_SIZE), (0, 0, 0, 0))
106
+ col.image(placeholder, use_container_width=True)
107
  col.caption("—")
108
  return None, None
109
+
110
  b = fetch_bytes(url)
111
  if not b:
112
+ col.warning("Görsel yüklenemedi")
113
+ return None, None
 
 
 
 
 
 
 
 
114
 
115
+ pv = square_preview_bytes(b, size=PREVIEW_SIZE, bg_rgb=bg_rgb)
116
+ col.image(pv, use_container_width=True)
 
 
 
 
 
117
  sz = image_size_from_bytes(b)
118
+ if sz:
119
+ col.caption(f"Gerçek çözünürlük: {sz[0]}×{sz[1]} px")
120
+
121
  ext = infer_ext(b)
122
+ # MIME'ı görüntünün gerçek tipinden bağımsız sabit jpeg vermeyelim:
123
  mime = "image/jpeg" if ext.lower() in [".jpg", ".jpeg"] else ("image/png" if ext.lower()==".png" else "image/webp")
124
+ col.download_button("İndir (tam kalite)", data=b, file_name=f"{filename_stub}{ext}", mime=mime)
125
+ return url, b
 
126
 
127
  def make_zip(named_bytes: list[tuple[str, bytes]]) -> bytes:
128
  buf = io.BytesIO()
129
  with zipfile.ZipFile(buf, "w", compression=zipfile.ZIP_DEFLATED) as z:
130
  for fname, b in named_bytes:
131
+ if b:
132
+ z.writestr(fname, b)
133
+ buf.seek(0)
134
+ return buf.read()
135
+
136
+ # ================= Sayfa =================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  st.set_page_config(page_title="AI LightBox · Jewelry", layout="wide", page_icon="💎")
138
  st.title("💎 AI LightBox · Jewelry")
139
+ st.caption("Packshot → Padding 1200×1200 (içerik ölçeği) → Model · Super Resolution (both) · (opsiyonel) Video (SR kaynaklı)")
140
 
141
+ # ================= Üst Kontrol Şeridi =================
 
 
 
 
 
 
 
 
142
  ok = backend_ok()
143
+ col1, col2, col3, col4, col5, col6, col7 = st.columns([1.6, 1.6, 1.0, 1.3, 0.9, 1.1, 0.9])
144
 
145
  with col1:
146
+ file_list = st.file_uploader("Takı görseli (yükle)", type=["jpg","jpeg","png","webp"], accept_multiple_files=True)
147
  with col2:
148
+ image_url = st.text_input("veya URL")
149
  with col3:
150
+ jewel_type = st.selectbox("Tür", ["auto","earring","ring","necklace","bracelet","pendant","brooch","set"], index=0)
 
151
  with col4:
152
  padding_ratio_val = st.number_input(
153
+ "İçerik ölçeği (0.30–0.95)",
154
  min_value=0.30, max_value=0.95, value=0.50, step=0.01,
155
+ help="1200×1200 kare tuvalde ürünün uzun kenarı = 1200 × bu oran. 0.50 önerilir."
156
  )
157
  with col5:
158
  upscale = st.checkbox("Super Resolution", True)
159
  with col6:
160
+ upscale_stage = st.selectbox("SR aşaması", ["both","final","packshot"], index=0, help="Varsayılan: both")
 
161
  with col7:
162
  upscale_factor = st.selectbox("SR katsayı", ["2","4"], index=0)
163
 
164
+ colA, colB, colC, colD, colE = st.columns([1.0, 1.2, 1.2, 0.9, 1.0])
165
  with colA:
166
  st.info(f"Bağlantı: {'Online' if ok else 'Offline'}", icon="🔌")
167
  with colB:
168
+ identity_lock = st.checkbox("Identity lock (önerilir)", True)
169
  with colC:
170
+ to_video = st.checkbox("Video üret (SR kaynaklı)", False)
171
  with colD:
172
+ duration = st.selectbox("Süre (sn)", ["6","10"], index=0)
173
  with colE:
174
+ run = st.button("Çalıştır", type="primary", use_container_width=True)
175
 
176
+ mannequin_url = st.text_input("Manken URL (opsiyonel)")
 
177
 
178
+ # ================= Girdi Önizleme =================
179
+ c_in, c_pack, c_pad, c_model = st.columns(4)
180
+ white_bg = (255, 255, 255)
181
 
182
+ input_preview_bytes = None
 
 
 
 
 
 
183
  if file_list:
184
+ try:
185
+ input_preview_bytes = file_list[0].getvalue()
186
+ except Exception:
187
+ input_preview_bytes = None
188
+ elif image_url:
189
+ input_preview_bytes = fetch_bytes(image_url)
190
 
191
+ if input_preview_bytes:
192
+ c_in.subheader("Input")
193
+ c_in.image(square_preview_bytes(input_preview_bytes, size=PREVIEW_SIZE, bg_rgb=white_bg), use_container_width=True)
194
+ try:
195
+ sz = Image.open(io.BytesIO(input_preview_bytes)).size
196
+ c_in.caption(f"Girdi çözünürlüğü: {sz[0]}×{sz[1]} px")
197
+ except Exception:
198
+ c_in.caption("—")
199
+ else:
200
+ show_tile(c_in, "Input", None)
201
 
202
  # ================ Koş & Göster ================
203
  if run:
204
  if not ok:
205
  st.error("Backend erişilemiyor. API_BASE / TOKEN kontrol edin.")
206
+ elif not (file_list or image_url):
207
+ st.error("En az bir görsel girişi yapın (dosya yükle veya URL).")
208
  else:
209
  try:
210
+ padding_ratio = clamp(padding_ratio_val, 0.30, 0.95)
211
+ data = {
212
+ "category": jewel_type,
213
+ "edit_prompt": "",
214
+ "num_images": "1",
215
+ "image_urls": (image_url or ""),
216
+ "mannequin_image_url": (mannequin_url or ""),
217
+ "padding_ratio": str(padding_ratio),
218
+ "identity_lock": "true" if identity_lock else "false",
219
+ # SR
220
+ "upscale": "true" if upscale else "false",
221
+ "upscale_factor": upscale_factor,
222
+ "upscale_stage": upscale_stage, # varsayılan both
223
+ }
224
  files_payload = []
225
  if file_list:
226
  for f in file_list:
227
  files_payload.append(("files", (f.name, f.getvalue(), f.type or "image/jpeg")))
228
 
229
+ with st.spinner("Zincir çalışıyor…"):
230
+ out = post_chain(data, files_payload)
231
+
232
+ # ---- Çıktıları topla ----
233
+ packshot_url = None
234
+ try:
235
+ imgs = out["packshot_step"]["result"]["images"]
236
+ if imgs:
237
+ packshot_url = imgs[0].get("url")
238
+ except Exception:
239
+ pass
240
+
241
+ padded_url = None
242
+ try:
243
+ # backward compat
244
+ padded_url = out.get("padding_step", {}).get("saved_file")
245
+ if not padded_url:
246
+ pads = out.get("packshot_step", {}).get("padded_urls") or out.get("padded_urls") or []
247
+ if pads:
248
+ padded_url = pads[0]
249
+ except Exception:
250
+ pass
251
+
252
+ packshot_sr_list = first_present(out.get("packshot_step", {}), [
253
+ "upscaled_packshot_urls", "upscaled_packshot_files"
254
+ ], default=[]) or []
255
+ packshot_sr_url = packshot_sr_list[0] if packshot_sr_list else None
256
+
257
+ placed_url = None
258
+ try:
259
+ imgs = out["image_step"]["result"]["images"]
260
+ if imgs:
261
+ placed_url = imgs[0].get("url")
262
+ except Exception:
263
+ pass
264
+
265
+ final_sr_list = first_present(out.get("image_step", {}), [
266
+ "upscaled_final_urls", "upscaled_final_files"
267
+ ], default=[]) or []
268
+ placed_sr_url = final_sr_list[0] if final_sr_list else None
269
+
270
+ # ---- Görselleri göster + indir (tam kalite) ----
271
+ p_url, p_bytes = show_tile(
272
+ c_pack,
273
+ "Packshot (SR)" if packshot_sr_url else "Packshot",
274
+ packshot_sr_url or packshot_url,
275
+ bg_rgb=white_bg,
276
+ filename_stub="packshot"
277
+ )
278
+
279
+ pad_url, pad_bytes = show_tile(c_pad, "Padded", padded_url, bg_rgb=white_bg, filename_stub="padded")
280
+
281
+ shown_final_url = placed_sr_url or placed_url
282
+ m_url, m_bytes = show_tile(
283
+ c_model,
284
+ "Model Üzerinde (SR)" if placed_sr_url else "Model Üzerinde",
285
+ shown_final_url,
286
+ bg_rgb=white_bg,
287
+ filename_stub="model"
288
+ )
289
+
290
+ # ---- Video (daima SR edilmiş final görseliyle) ----
291
+ vurl, vbytes = None, None
292
+ if to_video:
293
+ src_for_video = placed_sr_url or placed_url
294
+ if not src_for_video:
295
+ st.warning("Video için final görsel bulunamadı.")
296
+ else:
297
  with st.spinner("Video üretiliyor (SR kaynak)…"):
298
+ v = post_video(src_for_video, duration=duration, resolution="768P", prompt_optimizer=False)
299
  vurl = (v.get("result") or {}).get("video", {}).get("url")
300
  if vurl:
301
+ st.subheader("Video (SR kaynak)")
302
  st.video(vurl, format="video/mp4")
303
  vb = fetch_bytes(vurl)
304
  if vb:
305
+ vbytes = vb
306
+ st.download_button("Videoyu indir (MP4)", data=vb, file_name="ai_lightbox_video.mp4", mime="video/mp4")
307
+ else:
308
+ st.info("Video URL gelmedi.")
309
+
310
+ # ---- Hepsini ZIP indir ----
311
+ named = []
312
+ if p_bytes:
313
+ ext = infer_ext(p_bytes)
314
+ named.append((f"packshot{ext}", p_bytes))
315
+ if pad_bytes:
316
+ ext = infer_ext(pad_bytes)
317
+ named.append((f"padded{ext}", pad_bytes))
318
+ if m_bytes:
319
+ ext = infer_ext(m_bytes)
320
+ named.append((f"model{ext}", m_bytes))
321
+ if vbytes:
322
+ named.append(("video.mp4", vbytes))
323
+ if named:
324
+ zip_bytes = make_zip(named)
325
+ st.download_button("Hepsini ZIP indir", data=zip_bytes, file_name="ai_lightbox_outputs.zip", mime="application/zip")
326
+
327
+ # ---- Debug ----
328
+ with st.expander("Debug / Kaynak Alanlar"):
329
+ st.json({
330
+ "api_base": API_BASE,
331
+ "has_auth_header": bool(HEADERS),
332
+ "packshot_url": packshot_url,
333
+ "packshot_sr_url": packshot_sr_url,
334
+ "padded_url": padded_url,
335
+ "placed_url": placed_url,
336
+ "placed_sr_url": placed_sr_url,
337
+ "video_url": vurl,
338
+ })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
339
 
340
  except requests.HTTPError as e:
341
  st.error(f"HTTP {e.response.status_code}")
342
  except Exception as e:
343
  st.error(f"Hata: {e}")
344
 
345
+ # Küçük yardımcı: cache temizleme
346
  with st.sidebar:
347
+ if st.button("Önbelleği temizle"):
348
+ st.cache_data.clear()
349
+ st.success("Önbellek temizlendi.")
350
  st.caption(f"API_BASE: {API_BASE}")
351
+ if HF_TOKEN:
352
+ st.caption("Auth: Bearer (aktif)")