Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -581,111 +581,143 @@ def create_demo():
|
|
| 581 |
return False
|
| 582 |
|
| 583 |
# --- Convert only if not browser-playable
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 591 |
|
| 592 |
-
|
| 593 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 594 |
try:
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
|
| 608 |
-
|
| 609 |
-
|
| 610 |
-
|
| 611 |
-
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
elif is_img:
|
| 623 |
-
progress(0.08, desc="Fetching image bytes...")
|
| 624 |
-
raw = fetch_bytes(url, progress=progress)
|
| 625 |
-
|
| 626 |
-
try:
|
| 627 |
-
preview_fd, preview_path = tempfile.mkstemp(suffix=".jpg")
|
| 628 |
-
os.close(preview_fd)
|
| 629 |
-
with open(preview_path, "wb") as fh:
|
| 630 |
-
fh.write(convert_to_jpeg_bytes(raw, base_h=1024))
|
| 631 |
-
preview_local = preview_path
|
| 632 |
-
except Exception:
|
| 633 |
-
preview_local = None
|
| 634 |
-
|
| 635 |
-
progress(0.18, desc="Analyzing image...")
|
| 636 |
-
try:
|
| 637 |
-
res = analyze_image_structured(client, raw, prompt or "", progress=progress)
|
| 638 |
-
except UnidentifiedImageError:
|
| 639 |
-
return ("error", "Error: provided file is not a valid image.", preview_local or "")
|
| 640 |
-
|
| 641 |
-
progress(0.98, desc="Finalizing result...")
|
| 642 |
-
|
| 643 |
-
else:
|
| 644 |
-
progress(0.07, desc="Unknown media type — fetching bytes for heuristics...")
|
| 645 |
-
raw = fetch_bytes(url, timeout=120, progress=progress)
|
| 646 |
-
|
| 647 |
-
try:
|
| 648 |
-
progress(0.15, desc="Attempting to interpret as image...")
|
| 649 |
-
Image.open(BytesIO(raw))
|
| 650 |
-
progress(0.20, desc="Image detected — analyzing...")
|
| 651 |
-
res = analyze_image_structured(client, raw, prompt or "", progress=progress)
|
| 652 |
-
|
| 653 |
-
try:
|
| 654 |
-
preview_fd, preview_path = tempfile.mkstemp(suffix=".jpg")
|
| 655 |
-
os.close(preview_fd)
|
| 656 |
-
with open(preview_path, "wb") as fh:
|
| 657 |
-
fh.write(convert_to_jpeg_bytes(raw, base_h=1024))
|
| 658 |
-
preview_local = preview_path
|
| 659 |
-
except Exception:
|
| 660 |
-
preview_local = None
|
| 661 |
-
|
| 662 |
-
except Exception:
|
| 663 |
-
fd, tmp = tempfile.mkstemp(suffix=ext_from_src(url) or ".mp4")
|
| 664 |
-
os.close(fd)
|
| 665 |
-
with open(tmp, "wb") as fh:
|
| 666 |
-
fh.write(raw)
|
| 667 |
-
|
| 668 |
-
try:
|
| 669 |
-
progress(0.30, desc="Saved fallback video file; analyzing...")
|
| 670 |
-
preview_tmp = _convert_video_for_preview(tmp)
|
| 671 |
-
preview_local = preview_tmp if os.path.exists(preview_tmp) else tmp
|
| 672 |
-
res = analyze_video_cohesive(client, tmp, prompt or "", progress=progress)
|
| 673 |
-
finally:
|
| 674 |
-
try:
|
| 675 |
-
os.remove(tmp)
|
| 676 |
-
except Exception:
|
| 677 |
-
pass
|
| 678 |
-
|
| 679 |
-
# Determine final status and return
|
| 680 |
-
status = "done" if not (isinstance(res, str) and res.lower().startswith("error")) else "error"
|
| 681 |
-
return (status, res if isinstance(res, str) else str(res), preview_local or "")
|
| 682 |
-
|
| 683 |
-
except Exception as e:
|
| 684 |
-
return ("error", f"Unexpected worker error: {e}", "")
|
| 685 |
-
|
| 686 |
|
| 687 |
-
|
| 688 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 689 |
|
| 690 |
submit_btn.click(
|
| 691 |
fn=worker,
|
|
@@ -695,7 +727,7 @@ def create_demo():
|
|
| 695 |
show_progress="full",
|
| 696 |
show_progress_on=progress_md,
|
| 697 |
)
|
| 698 |
-
|
| 699 |
def _btn_label_for_status(s):
|
| 700 |
labels = {
|
| 701 |
"idle": "Submit",
|
|
|
|
| 581 |
return False
|
| 582 |
|
| 583 |
# --- Convert only if not browser-playable
|
| 584 |
+
def _convert_video_for_preview_if_needed(path: str) -> str:
|
| 585 |
+
"""
|
| 586 |
+
Return a path that the Gradio video component can play.
|
| 587 |
+
If the original file is already MP4 with H.264 (or another browser‑compatible codec),
|
| 588 |
+
the original path is returned unchanged.
|
| 589 |
+
Otherwise the file is re‑encoded to MP4 (H.264 + AAC) and the new path is returned.
|
| 590 |
+
"""
|
| 591 |
+
if not FFMPEG_BIN or not os.path.exists(path):
|
| 592 |
+
return path
|
| 593 |
+
|
| 594 |
+
# Quick check: extension + ffprobe for codecs
|
| 595 |
+
if path.lower().endswith((".mp4", ".m4v", ".mov")):
|
| 596 |
+
info = _ffprobe_streams(path)
|
| 597 |
+
if info:
|
| 598 |
+
codecs = {s.get("codec_name") for s in info.get("streams", []) if s.get("codec_type") == "video"}
|
| 599 |
+
if "h264" in codecs or "h265" in codecs:
|
| 600 |
+
return path # already playable
|
| 601 |
+
|
| 602 |
+
# Need conversion → write to a new temp MP4
|
| 603 |
+
out_fd, out_path = tempfile.mkstemp(suffix=".mp4")
|
| 604 |
+
os.close(out_fd)
|
| 605 |
+
cmd = [
|
| 606 |
+
FFMPEG_BIN, "-y", "-i", path,
|
| 607 |
+
"-c:v", "libx264", "-preset", "veryfast", "-crf", "28",
|
| 608 |
+
"-c:a", "aac", "-movflags", "+faststart", out_path,
|
| 609 |
+
]
|
| 610 |
+
try:
|
| 611 |
+
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=60)
|
| 612 |
+
return out_path
|
| 613 |
+
except Exception:
|
| 614 |
+
# If conversion fails, fall back to the original (Gradio will show its own warning)
|
| 615 |
+
try: os.remove(out_path)
|
| 616 |
+
except Exception: pass
|
| 617 |
+
return path
|
| 618 |
|
| 619 |
+
def worker(url: str, prompt: str, key: str, progress=gr.Progress()):
|
| 620 |
+
"""Return (status, markdown_output, preview_path)."""
|
| 621 |
+
try:
|
| 622 |
+
if not url:
|
| 623 |
+
return "error", "**Error:** No URL provided.", ""
|
| 624 |
+
|
| 625 |
+
# ------------------------------------------------------------------
|
| 626 |
+
# 1️⃣ Determine media type – use a *single* call to avoid duplicate work
|
| 627 |
+
# ------------------------------------------------------------------
|
| 628 |
+
progress(0.02, desc="Checking URL / content‑type")
|
| 629 |
+
is_img, is_vid = determine_media_type(url, progress=progress)
|
| 630 |
+
|
| 631 |
+
client = get_client(key)
|
| 632 |
+
preview_path = ""
|
| 633 |
+
|
| 634 |
+
# ------------------------------------------------------------------
|
| 635 |
+
# 2️⃣ Helper to write a temp file with a *correct* suffix
|
| 636 |
+
# ------------------------------------------------------------------
|
| 637 |
+
def _temp_file(data: bytes, suffix: str) -> str:
|
| 638 |
+
fd, p = tempfile.mkstemp(suffix=suffix)
|
| 639 |
+
os.close(fd)
|
| 640 |
+
with open(p, "wb") as f:
|
| 641 |
+
f.write(data)
|
| 642 |
+
return p
|
| 643 |
+
|
| 644 |
+
# ------------------------------------------------------------------
|
| 645 |
+
# 3️⃣ VIDEO PATH
|
| 646 |
+
# ------------------------------------------------------------------
|
| 647 |
+
if is_vid:
|
| 648 |
+
progress(0.05, desc="Downloading video")
|
| 649 |
+
raw = fetch_bytes(url, timeout=120, progress=progress)
|
| 650 |
+
if not raw:
|
| 651 |
+
return "error", "Failed to download video bytes.", ""
|
| 652 |
+
|
| 653 |
+
# write with a proper video extension
|
| 654 |
+
tmp_video = _temp_file(raw, suffix=ext_from_src(url) or ".mp4")
|
| 655 |
+
progress(0.15, desc="Preparing preview")
|
| 656 |
+
preview_path = _convert_video_for_preview_if_needed(tmp_video)
|
| 657 |
+
|
| 658 |
+
progress(0.25, desc="Running full‑video analysis")
|
| 659 |
+
result = analyze_video_cohesive(client, tmp_video, prompt, progress=progress)
|
| 660 |
+
|
| 661 |
+
# clean‑up the *raw* temp file (preview may be a different file)
|
| 662 |
+
try: os.remove(tmp_video)
|
| 663 |
+
except Exception: pass
|
| 664 |
+
|
| 665 |
+
# ------------------------------------------------------------------
|
| 666 |
+
# 4️⃣ IMAGE PATH
|
| 667 |
+
# ------------------------------------------------------------------
|
| 668 |
+
elif is_img:
|
| 669 |
+
progress(0.05, desc="Downloading image")
|
| 670 |
+
raw = fetch_bytes(url, progress=progress)
|
| 671 |
+
|
| 672 |
+
# preview image (always JPEG for consistency)
|
| 673 |
+
preview_path = _temp_file(convert_to_jpeg_bytes(raw, base_h=1024), suffix=".jpg")
|
| 674 |
+
|
| 675 |
+
progress(0.20, desc="Running image analysis")
|
| 676 |
+
result = analyze_image_structured(client, raw, prompt, progress=progress)
|
| 677 |
+
|
| 678 |
+
# ------------------------------------------------------------------
|
| 679 |
+
# 5️⃣ FALLBACK – try image first, then video
|
| 680 |
+
# ------------------------------------------------------------------
|
| 681 |
+
else:
|
| 682 |
+
progress(0.07, desc="Downloading unknown media")
|
| 683 |
+
raw = fetch_bytes(url, timeout=120, progress=progress)
|
| 684 |
+
|
| 685 |
+
# try to open as image
|
| 686 |
try:
|
| 687 |
+
Image.open(BytesIO(raw)).verify()
|
| 688 |
+
is_img = True
|
| 689 |
+
except Exception:
|
| 690 |
+
is_img = False
|
| 691 |
+
|
| 692 |
+
if is_img:
|
| 693 |
+
preview_path = _temp_file(convert_to_jpeg_bytes(raw, base_h=1024), suffix=".jpg")
|
| 694 |
+
result = analyze_image_structured(client, raw, prompt, progress=progress)
|
| 695 |
+
else:
|
| 696 |
+
tmp_vid = _temp_file(raw, suffix=ext_from_src(url) or ".mp4")
|
| 697 |
+
preview_path = _convert_video_for_preview_if_needed(tmp_vid)
|
| 698 |
+
result = analyze_video_cohesive(client, tmp_vid, prompt, progress=progress)
|
| 699 |
+
try: os.remove(tmp_vid)
|
| 700 |
+
except Exception: pass
|
| 701 |
+
|
| 702 |
+
# ------------------------------------------------------------------
|
| 703 |
+
# 6️⃣ Final status
|
| 704 |
+
# ------------------------------------------------------------------
|
| 705 |
+
status = "done" if not (isinstance(result, str) and result.lower().startswith("error")) else "error"
|
| 706 |
+
return status, result if isinstance(result, str) else str(result), preview_path or ""
|
| 707 |
+
|
| 708 |
+
except Exception as exc:
|
| 709 |
+
return "error", f"Unexpected worker error: {exc}", ""
|
| 710 |
+
|
| 711 |
+
def _start_processing(url, prompt, key):
|
| 712 |
+
# set state to busy and launch the worker in the same call
|
| 713 |
+
return "busy", None, None # temporary values; the real result will replace them
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 714 |
|
| 715 |
+
submit_btn.click(
|
| 716 |
+
fn=_start_processing,
|
| 717 |
+
inputs=[url_input, custom_prompt, api_key],
|
| 718 |
+
outputs=[status_state, output_md, preview_path_state],
|
| 719 |
+
queue=False, # this tiny wrapper runs instantly
|
| 720 |
+
)
|
| 721 |
|
| 722 |
submit_btn.click(
|
| 723 |
fn=worker,
|
|
|
|
| 727 |
show_progress="full",
|
| 728 |
show_progress_on=progress_md,
|
| 729 |
)
|
| 730 |
+
|
| 731 |
def _btn_label_for_status(s):
|
| 732 |
labels = {
|
| 733 |
"idle": "Submit",
|