Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -415,8 +415,8 @@ def create_demo():
|
|
| 415 |
with gr.Blocks(title="Flux Multimodal", css=css) as demo:
|
| 416 |
with gr.Row():
|
| 417 |
with gr.Column(scale=1):
|
| 418 |
-
preview_image = gr.Image(label="Preview Image", type="
|
| 419 |
-
preview_video = gr.Video(label="Preview Video", elem_classes="preview_media", visible=False, format="mp4")
|
| 420 |
preview_status = gr.Textbox(label="Preview status", interactive=False, lines=2, value="", visible=True)
|
| 421 |
with gr.Column(scale=2):
|
| 422 |
url_input = gr.Textbox(label="Image / Video URL", placeholder="https://...", lines=1)
|
|
@@ -430,42 +430,106 @@ def create_demo():
|
|
| 430 |
progress_md = gr.Markdown("Idle")
|
| 431 |
output_md = gr.Markdown("")
|
| 432 |
status_state = gr.State("idle")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 433 |
|
| 434 |
def load_preview(url: str):
|
|
|
|
| 435 |
if not url:
|
| 436 |
return gr.update(value=None, visible=False), gr.update(value=None, visible=False), gr.update(value="")
|
| 437 |
-
#
|
| 438 |
try:
|
| 439 |
if is_remote(url):
|
| 440 |
head = safe_head(url)
|
| 441 |
if head:
|
| 442 |
ctype = (head.headers.get("content-type") or "").lower()
|
| 443 |
if ctype.startswith("video/") or any(url.lower().endswith(ext) for ext in VIDEO_EXTS):
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 456 |
except Exception as e:
|
| 457 |
-
|
| 458 |
-
return gr.update(value=None, visible=False), gr.update(value=None, visible=False), gr.update(value=msg)
|
| 459 |
|
| 460 |
url_input.change(fn=load_preview, inputs=[url_input], outputs=[preview_image, preview_video, preview_status])
|
| 461 |
|
| 462 |
def clear_all():
|
| 463 |
-
return "", None, None, "idle", "Idle", ""
|
| 464 |
|
| 465 |
-
clear_btn.click(fn=clear_all, inputs=[], outputs=[url_input, preview_image, preview_video, status_state, progress_md, output_md])
|
| 466 |
|
| 467 |
def _convert_video_for_preview(path: str) -> str:
|
| 468 |
-
|
|
|
|
| 469 |
return path
|
| 470 |
out_fd, out_path = tempfile.mkstemp(suffix=".mp4")
|
| 471 |
os.close(out_fd)
|
|
@@ -482,45 +546,57 @@ def create_demo():
|
|
| 482 |
except Exception: pass
|
| 483 |
return path
|
| 484 |
|
|
|
|
| 485 |
def worker(url: str, prompt: str, key: str, progress=gr.Progress()):
|
| 486 |
try:
|
| 487 |
if not url:
|
| 488 |
-
return ("error", "**Error:** No URL provided.")
|
| 489 |
progress(0.01, desc="Starting processing...")
|
| 490 |
progress(0.03, desc="Checking URL / content-type...")
|
| 491 |
is_img, is_vid = determine_media_type(url, progress=progress)
|
| 492 |
progress(0.06, desc=f"Determined media type: image={is_img}, video={is_vid}")
|
| 493 |
client = get_client(key)
|
|
|
|
| 494 |
if is_vid:
|
| 495 |
progress(0.08, desc="Fetching video bytes (may take a while)...")
|
| 496 |
raw = fetch_bytes(url, timeout=120, progress=progress)
|
| 497 |
tmp = save_bytes_to_temp(raw, suffix=ext_from_src(url) or ".mp4")
|
| 498 |
progress(0.18, desc="Saved video to temp; converting for preview if needed...")
|
| 499 |
preview_tmp = _convert_video_for_preview(tmp)
|
| 500 |
-
#
|
|
|
|
| 501 |
progress(0.25, desc="Starting video analysis...")
|
| 502 |
res = analyze_video_cohesive(client, tmp, prompt or "", progress=progress)
|
| 503 |
progress(0.98, desc="Finalizing result...")
|
| 504 |
try:
|
| 505 |
-
if preview_tmp != tmp:
|
| 506 |
-
|
| 507 |
-
|
| 508 |
finally:
|
| 509 |
try: os.remove(tmp)
|
| 510 |
except Exception: pass
|
| 511 |
status = "done" if not (isinstance(res, str) and res.lower().startswith("error")) else "error"
|
| 512 |
-
return (status, res if isinstance(res, str) else str(res))
|
| 513 |
elif is_img:
|
| 514 |
progress(0.08, desc="Fetching image bytes...")
|
| 515 |
raw = fetch_bytes(url, progress=progress)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 516 |
progress(0.18, desc="Analyzing image...")
|
| 517 |
try:
|
| 518 |
res = analyze_image_structured(client, raw, prompt or "", progress=progress)
|
| 519 |
except UnidentifiedImageError:
|
| 520 |
-
return ("error", "Error: provided file is not a valid image.")
|
| 521 |
progress(0.98, desc="Finalizing result...")
|
| 522 |
status = "done" if not (isinstance(res, str) and res.lower().startswith("error")) else "error"
|
| 523 |
-
return (status, res if isinstance(res, str) else str(res))
|
| 524 |
else:
|
| 525 |
progress(0.07, desc="Unknown media type — fetching bytes for heuristics...")
|
| 526 |
raw = fetch_bytes(url, timeout=120, progress=progress)
|
|
@@ -530,7 +606,16 @@ def create_demo():
|
|
| 530 |
progress(0.2, desc="Image detected — analyzing...")
|
| 531 |
res = analyze_image_structured(client, raw, prompt or "", progress=progress)
|
| 532 |
status = "done" if not (isinstance(res, str) and res.lower().startswith("error")) else "error"
|
| 533 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 534 |
except Exception:
|
| 535 |
fd, tmp = tempfile.mkstemp(suffix=ext_from_src(url) or ".mp4")
|
| 536 |
os.close(fd)
|
|
@@ -538,28 +623,54 @@ def create_demo():
|
|
| 538 |
fh.write(raw)
|
| 539 |
try:
|
| 540 |
progress(0.3, desc="Saved fallback video file; analyzing...")
|
|
|
|
|
|
|
| 541 |
res = analyze_video_cohesive(client, tmp, prompt or "", progress=progress)
|
| 542 |
status = "done" if not (isinstance(res, str) and res.lower().startswith("error")) else "error"
|
| 543 |
-
return (status, res if isinstance(res, str) else str(res))
|
| 544 |
finally:
|
| 545 |
try: os.remove(tmp)
|
| 546 |
except Exception: pass
|
| 547 |
except Exception as e:
|
| 548 |
-
return ("error", f"Unexpected worker error: {e}")
|
| 549 |
|
| 550 |
-
# submit returns (status_state, output_md)
|
| 551 |
-
submit_btn.click(fn=worker, inputs=[url_input, custom_prompt, api_key], outputs=[status_state, output_md], queue=True)
|
| 552 |
|
|
|
|
| 553 |
def btn_label_from_state(s):
|
| 554 |
return _btn_label_for_status(s)
|
| 555 |
|
| 556 |
status_state.change(fn=btn_label_from_state, inputs=[status_state], outputs=[submit_btn])
|
| 557 |
|
|
|
|
| 558 |
def status_to_progress_text(s):
|
| 559 |
return {"idle":"Idle","busy":"Processing…","done":"Completed","error":"Error — see output"}.get(s, s)
|
| 560 |
status_state.change(fn=status_to_progress_text, inputs=[status_state], outputs=[progress_md])
|
| 561 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 562 |
return demo
|
| 563 |
|
|
|
|
| 564 |
if __name__ == "__main__":
|
| 565 |
create_demo().launch(share=False, server_name="0.0.0.0", server_port=7860, max_threads=8)
|
|
|
|
| 415 |
with gr.Blocks(title="Flux Multimodal", css=css) as demo:
|
| 416 |
with gr.Row():
|
| 417 |
with gr.Column(scale=1):
|
| 418 |
+
preview_image = gr.Image(label="Preview Image", type="filepath", elem_classes="preview_media", visible=False)
|
| 419 |
+
preview_video = gr.Video(label="Preview Video", elem_classes="preview_media", visible=False, format="mp4", type="filepath")
|
| 420 |
preview_status = gr.Textbox(label="Preview status", interactive=False, lines=2, value="", visible=True)
|
| 421 |
with gr.Column(scale=2):
|
| 422 |
url_input = gr.Textbox(label="Image / Video URL", placeholder="https://...", lines=1)
|
|
|
|
| 430 |
progress_md = gr.Markdown("Idle")
|
| 431 |
output_md = gr.Markdown("")
|
| 432 |
status_state = gr.State("idle")
|
| 433 |
+
# hidden state to pass preview path from worker to frontend
|
| 434 |
+
preview_path_state = gr.State("")
|
| 435 |
+
|
| 436 |
+
# small helper: fetch URL into bytes with retries and respect Retry-After
|
| 437 |
+
def _fetch_with_retries_bytes(src: str, timeout: int = 15, max_retries: int = 3):
|
| 438 |
+
attempt = 0
|
| 439 |
+
delay = 1.0
|
| 440 |
+
while True:
|
| 441 |
+
attempt += 1
|
| 442 |
+
try:
|
| 443 |
+
if is_remote(src):
|
| 444 |
+
r = requests.get(src, timeout=timeout, stream=True)
|
| 445 |
+
if r.status_code == 200:
|
| 446 |
+
return r.content
|
| 447 |
+
if r.status_code == 429:
|
| 448 |
+
ra = r.headers.get("Retry-After")
|
| 449 |
+
try:
|
| 450 |
+
delay = float(ra) if ra is not None else delay
|
| 451 |
+
except Exception:
|
| 452 |
+
pass
|
| 453 |
+
# for other 4xx/5xx raise to outer except to allow retry logic
|
| 454 |
+
r.raise_for_status()
|
| 455 |
+
else:
|
| 456 |
+
with open(src, "rb") as fh:
|
| 457 |
+
return fh.read()
|
| 458 |
+
except requests.exceptions.RequestException as e:
|
| 459 |
+
if attempt >= max_retries:
|
| 460 |
+
raise
|
| 461 |
+
time.sleep(delay)
|
| 462 |
+
delay *= 2
|
| 463 |
+
except FileNotFoundError:
|
| 464 |
+
raise
|
| 465 |
+
except Exception:
|
| 466 |
+
if attempt >= max_retries:
|
| 467 |
+
raise
|
| 468 |
+
time.sleep(delay)
|
| 469 |
+
delay *= 2
|
| 470 |
+
|
| 471 |
+
# create a local temp file for a remote URL and return local path (or None)
|
| 472 |
+
def _save_preview_local(src: str) -> Optional[str]:
|
| 473 |
+
if not src:
|
| 474 |
+
return None
|
| 475 |
+
if not is_remote(src):
|
| 476 |
+
return src if os.path.exists(src) else None
|
| 477 |
+
try:
|
| 478 |
+
b = _fetch_with_retries_bytes(src, timeout=15, max_retries=3)
|
| 479 |
+
ext = ext_from_src(src) or ".bin"
|
| 480 |
+
fd, tmp = tempfile.mkstemp(suffix=ext)
|
| 481 |
+
os.close(fd)
|
| 482 |
+
with open(tmp, "wb") as fh:
|
| 483 |
+
fh.write(b)
|
| 484 |
+
return tmp
|
| 485 |
+
except Exception:
|
| 486 |
+
return None
|
| 487 |
|
| 488 |
def load_preview(url: str):
|
| 489 |
+
# returns (preview_image_path, preview_video_path, status_msg)
|
| 490 |
if not url:
|
| 491 |
return gr.update(value=None, visible=False), gr.update(value=None, visible=False), gr.update(value="")
|
| 492 |
+
# If remote, check head to avoid unnecessary download when it's clearly video
|
| 493 |
try:
|
| 494 |
if is_remote(url):
|
| 495 |
head = safe_head(url)
|
| 496 |
if head:
|
| 497 |
ctype = (head.headers.get("content-type") or "").lower()
|
| 498 |
if ctype.startswith("video/") or any(url.lower().endswith(ext) for ext in VIDEO_EXTS):
|
| 499 |
+
# save remote to temp to avoid Gradio re-download later
|
| 500 |
+
local = _save_preview_local(url)
|
| 501 |
+
if local:
|
| 502 |
+
return gr.update(value=None, visible=False), gr.update(value=local, visible=True), gr.update(value=f"Remote video detected (content-type={ctype}). Showing preview if browser-playable.")
|
| 503 |
+
else:
|
| 504 |
+
return gr.update(value=None, visible=False), gr.update(value=None, visible=False), gr.update(value=f"Remote video detected but preview download failed (content-type={ctype}).")
|
| 505 |
+
# Try image fetch and load
|
| 506 |
+
local = _save_preview_local(url)
|
| 507 |
+
if not local:
|
| 508 |
+
return gr.update(value=None, visible=False), gr.update(value=None, visible=False), gr.update(value="Preview load failed: could not fetch resource.")
|
| 509 |
+
try:
|
| 510 |
+
img = Image.open(local)
|
| 511 |
+
if getattr(img, "is_animated", False):
|
| 512 |
+
img.seek(0)
|
| 513 |
+
# return local path for gr.Image (type=filepath)
|
| 514 |
+
return gr.update(value=local, visible=True), gr.update(value=None, visible=False), gr.update(value="Image preview loaded.")
|
| 515 |
+
except UnidentifiedImageError:
|
| 516 |
+
# Not an image — maybe a video; return video preview if available
|
| 517 |
+
if any(local.lower().endswith(ext) for ext in VIDEO_EXTS) or True:
|
| 518 |
+
return gr.update(value=None, visible=False), gr.update(value=local, visible=True), gr.update(value="Non-image file — showing as video preview if playable.")
|
| 519 |
+
return gr.update(value=None, visible=False), gr.update(value=None, visible=False), gr.update(value="Preview load failed: file is not a valid image.")
|
| 520 |
except Exception as e:
|
| 521 |
+
return gr.update(value=None, visible=False), gr.update(value=None, visible=False), gr.update(value=f"Preview load failed: {e}")
|
|
|
|
| 522 |
|
| 523 |
url_input.change(fn=load_preview, inputs=[url_input], outputs=[preview_image, preview_video, preview_status])
|
| 524 |
|
| 525 |
def clear_all():
|
| 526 |
+
return "", None, None, "idle", "Idle", "", ""
|
| 527 |
|
| 528 |
+
clear_btn.click(fn=clear_all, inputs=[], outputs=[url_input, preview_image, preview_video, status_state, progress_md, output_md, preview_path_state])
|
| 529 |
|
| 530 |
def _convert_video_for_preview(path: str) -> str:
|
| 531 |
+
# fix: check FFMPEG_BIN path, not 'path'
|
| 532 |
+
if not FFMPEG_BIN or not os.path.exists(FFMPEG_BIN):
|
| 533 |
return path
|
| 534 |
out_fd, out_path = tempfile.mkstemp(suffix=".mp4")
|
| 535 |
os.close(out_fd)
|
|
|
|
| 546 |
except Exception: pass
|
| 547 |
return path
|
| 548 |
|
| 549 |
+
# Worker now returns (status_state, output_md, preview_path)
|
| 550 |
def worker(url: str, prompt: str, key: str, progress=gr.Progress()):
|
| 551 |
try:
|
| 552 |
if not url:
|
| 553 |
+
return ("error", "**Error:** No URL provided.", "")
|
| 554 |
progress(0.01, desc="Starting processing...")
|
| 555 |
progress(0.03, desc="Checking URL / content-type...")
|
| 556 |
is_img, is_vid = determine_media_type(url, progress=progress)
|
| 557 |
progress(0.06, desc=f"Determined media type: image={is_img}, video={is_vid}")
|
| 558 |
client = get_client(key)
|
| 559 |
+
preview_local = None
|
| 560 |
if is_vid:
|
| 561 |
progress(0.08, desc="Fetching video bytes (may take a while)...")
|
| 562 |
raw = fetch_bytes(url, timeout=120, progress=progress)
|
| 563 |
tmp = save_bytes_to_temp(raw, suffix=ext_from_src(url) or ".mp4")
|
| 564 |
progress(0.18, desc="Saved video to temp; converting for preview if needed...")
|
| 565 |
preview_tmp = _convert_video_for_preview(tmp)
|
| 566 |
+
# ensure preview_local is a local path to avoid Gradio re-download
|
| 567 |
+
preview_local = preview_tmp if os.path.exists(preview_tmp) else tmp
|
| 568 |
progress(0.25, desc="Starting video analysis...")
|
| 569 |
res = analyze_video_cohesive(client, tmp, prompt or "", progress=progress)
|
| 570 |
progress(0.98, desc="Finalizing result...")
|
| 571 |
try:
|
| 572 |
+
if preview_tmp != tmp and os.path.exists(preview_tmp):
|
| 573 |
+
# leave preview_tmp for frontend; worker will clean tmp
|
| 574 |
+
pass
|
| 575 |
finally:
|
| 576 |
try: os.remove(tmp)
|
| 577 |
except Exception: pass
|
| 578 |
status = "done" if not (isinstance(res, str) and res.lower().startswith("error")) else "error"
|
| 579 |
+
return (status, res if isinstance(res, str) else str(res), preview_local or "")
|
| 580 |
elif is_img:
|
| 581 |
progress(0.08, desc="Fetching image bytes...")
|
| 582 |
raw = fetch_bytes(url, progress=progress)
|
| 583 |
+
# save preview locally so Gradio won't re-download later
|
| 584 |
+
try:
|
| 585 |
+
preview_fd, preview_path = tempfile.mkstemp(suffix=".jpg")
|
| 586 |
+
os.close(preview_fd)
|
| 587 |
+
with open(preview_path, "wb") as fh:
|
| 588 |
+
fh.write(convert_to_jpeg_bytes(raw, base_h=1024))
|
| 589 |
+
preview_local = preview_path
|
| 590 |
+
except Exception:
|
| 591 |
+
preview_local = None
|
| 592 |
progress(0.18, desc="Analyzing image...")
|
| 593 |
try:
|
| 594 |
res = analyze_image_structured(client, raw, prompt or "", progress=progress)
|
| 595 |
except UnidentifiedImageError:
|
| 596 |
+
return ("error", "Error: provided file is not a valid image.", preview_local or "")
|
| 597 |
progress(0.98, desc="Finalizing result...")
|
| 598 |
status = "done" if not (isinstance(res, str) and res.lower().startswith("error")) else "error"
|
| 599 |
+
return (status, res if isinstance(res, str) else str(res), preview_local or "")
|
| 600 |
else:
|
| 601 |
progress(0.07, desc="Unknown media type — fetching bytes for heuristics...")
|
| 602 |
raw = fetch_bytes(url, timeout=120, progress=progress)
|
|
|
|
| 606 |
progress(0.2, desc="Image detected — analyzing...")
|
| 607 |
res = analyze_image_structured(client, raw, prompt or "", progress=progress)
|
| 608 |
status = "done" if not (isinstance(res, str) and res.lower().startswith("error")) else "error"
|
| 609 |
+
# save preview as image
|
| 610 |
+
try:
|
| 611 |
+
preview_fd, preview_path = tempfile.mkstemp(suffix=".jpg")
|
| 612 |
+
os.close(preview_fd)
|
| 613 |
+
with open(preview_path, "wb") as fh:
|
| 614 |
+
fh.write(convert_to_jpeg_bytes(raw, base_h=1024))
|
| 615 |
+
preview_local = preview_path
|
| 616 |
+
except Exception:
|
| 617 |
+
preview_local = None
|
| 618 |
+
return (status, res if isinstance(res, str) else str(res), preview_local or "")
|
| 619 |
except Exception:
|
| 620 |
fd, tmp = tempfile.mkstemp(suffix=ext_from_src(url) or ".mp4")
|
| 621 |
os.close(fd)
|
|
|
|
| 623 |
fh.write(raw)
|
| 624 |
try:
|
| 625 |
progress(0.3, desc="Saved fallback video file; analyzing...")
|
| 626 |
+
preview_tmp = _convert_video_for_preview(tmp)
|
| 627 |
+
preview_local = preview_tmp if os.path.exists(preview_tmp) else tmp
|
| 628 |
res = analyze_video_cohesive(client, tmp, prompt or "", progress=progress)
|
| 629 |
status = "done" if not (isinstance(res, str) and res.lower().startswith("error")) else "error"
|
| 630 |
+
return (status, res if isinstance(res, str) else str(res), preview_local or "")
|
| 631 |
finally:
|
| 632 |
try: os.remove(tmp)
|
| 633 |
except Exception: pass
|
| 634 |
except Exception as e:
|
| 635 |
+
return ("error", f"Unexpected worker error: {e}", "")
|
| 636 |
|
| 637 |
+
# submit returns (status_state, output_md, preview_path_state)
|
| 638 |
+
submit_btn.click(fn=worker, inputs=[url_input, custom_prompt, api_key], outputs=[status_state, output_md, preview_path_state], queue=True)
|
| 639 |
|
| 640 |
+
# update submit button label from status
|
| 641 |
def btn_label_from_state(s):
|
| 642 |
return _btn_label_for_status(s)
|
| 643 |
|
| 644 |
status_state.change(fn=btn_label_from_state, inputs=[status_state], outputs=[submit_btn])
|
| 645 |
|
| 646 |
+
# map status to progress text
|
| 647 |
def status_to_progress_text(s):
|
| 648 |
return {"idle":"Idle","busy":"Processing…","done":"Completed","error":"Error — see output"}.get(s, s)
|
| 649 |
status_state.change(fn=status_to_progress_text, inputs=[status_state], outputs=[progress_md])
|
| 650 |
|
| 651 |
+
# when preview_path_state changes, update preview components appropriately
|
| 652 |
+
def apply_preview(path: str):
|
| 653 |
+
if not path:
|
| 654 |
+
return gr.update(value=None, visible=False), gr.update(value=None, visible=False), ""
|
| 655 |
+
# choose whether it's image or video by extension/content
|
| 656 |
+
try:
|
| 657 |
+
if any(path.lower().endswith(ext) for ext in IMAGE_EXTS):
|
| 658 |
+
return gr.update(value=path, visible=True), gr.update(value=None, visible=False), "Preview updated."
|
| 659 |
+
if any(path.lower().endswith(ext) for ext in VIDEO_EXTS):
|
| 660 |
+
return gr.update(value=None, visible=False), gr.update(value=path, visible=True), "Preview updated."
|
| 661 |
+
# try load as image
|
| 662 |
+
try:
|
| 663 |
+
Image.open(path)
|
| 664 |
+
return gr.update(value=path, visible=True), gr.update(value=None, visible=False), "Preview updated."
|
| 665 |
+
except Exception:
|
| 666 |
+
return gr.update(value=None, visible=False), gr.update(value=path, visible=True), "Preview updated."
|
| 667 |
+
except Exception:
|
| 668 |
+
return gr.update(value=None, visible=False), gr.update(value=None, visible=False), ""
|
| 669 |
+
|
| 670 |
+
preview_path_state.change(fn=apply_preview, inputs=[preview_path_state], outputs=[preview_image, preview_video, preview_status])
|
| 671 |
+
|
| 672 |
return demo
|
| 673 |
|
| 674 |
+
|
| 675 |
if __name__ == "__main__":
|
| 676 |
create_demo().launch(share=False, server_name="0.0.0.0", server_port=7860, max_threads=8)
|