Hug0endob commited on
Commit
b5699ae
·
verified ·
1 Parent(s): cdeafb3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +144 -33
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="pil", elem_classes="preview_media", visible=False)
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
- # Quick content-type/extension heuristic first
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
- return gr.update(value=None, visible=False), gr.update(value=url, visible=True), gr.update(value=f"Remote video detected (content-type={ctype}). Showing preview if browser-playable; converting if necessary.")
445
- # Try image fetch (fast)
446
- if is_remote(url):
447
- r = safe_get(url, timeout=10)
448
- img_bytes = r.content
449
- else:
450
- with open(url, "rb") as f:
451
- img_bytes = f.read()
452
- img = Image.open(BytesIO(img_bytes))
453
- if getattr(img, "is_animated", False):
454
- img.seek(0)
455
- return gr.update(value=img.convert("RGB"), visible=True), gr.update(value=None, visible=False), gr.update(value="Image preview loaded.")
 
 
 
 
 
 
 
 
 
456
  except Exception as e:
457
- msg = f"Preview load failed: {e}. If this is a video, ensure URL ends with .mp4/.webm or upload a playable file."
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
- if not FFMPEG_BIN or not os.path.exists(path):
 
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
- # Return preview path to frontend by placing it into preview_video (separate event)
 
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
- try: os.remove(preview_tmp)
507
- except Exception: pass
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
- return (status, res if isinstance(res, str) else str(res))
 
 
 
 
 
 
 
 
 
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)