Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -185,7 +185,7 @@ def convert_to_jpeg_bytes(img_bytes: bytes, base_h: int = 480) -> bytes:
|
|
| 185 |
w = max(1, int(img.width * (base_h / img.height)))
|
| 186 |
img = img.resize((w, base_h), Image.LANCZOS)
|
| 187 |
buf = BytesIO()
|
| 188 |
-
img.save(buf, format="JPEG", quality=
|
| 189 |
return buf.getvalue()
|
| 190 |
|
| 191 |
def b64_bytes(b: bytes, mime: str = "image/jpeg") -> str:
|
|
@@ -238,7 +238,8 @@ def _get_video_info_and_timestamps(media_path: str, sample_count: int) -> Tuple[
|
|
| 238 |
timestamps = [step * (i + 1) for i in range(actual_sample_count)]
|
| 239 |
|
| 240 |
if not timestamps:
|
| 241 |
-
|
|
|
|
| 242 |
|
| 243 |
return info, timestamps
|
| 244 |
|
|
@@ -366,10 +367,9 @@ def upload_file_to_mistral(client: Mistral, path: str, purpose: str = "batch", t
|
|
| 366 |
if progress is not None:
|
| 367 |
progress(0.5 + 0.01 * attempt, desc=f"Uploading file to model service (attempt {attempt+1}/{max_retries})...")
|
| 368 |
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
res = client.files.upload(file=fh, purpose=purpose)
|
| 373 |
fid = getattr(res, "id", None)
|
| 374 |
if not fid:
|
| 375 |
raise RuntimeError(f"Mistral API upload response missing file ID: {res}")
|
|
@@ -464,6 +464,7 @@ def analyze_video_cohesive(client: Mistral, video_path: str, prompt: str, progre
|
|
| 464 |
]
|
| 465 |
result = chat_complete(client, VIDEO_MODEL, messages, progress=progress)
|
| 466 |
|
|
|
|
| 467 |
_, gallery_frame_paths = extract_frames_for_model_and_gallery(
|
| 468 |
video_path, sample_count=6, gallery_base_h=1080, model_base_h=1024, progress=progress
|
| 469 |
)
|
|
@@ -524,21 +525,18 @@ def _convert_video_for_preview_if_needed(path: str) -> str:
|
|
| 524 |
print(f"Error: Could not create temporary file for video conversion from {path}.")
|
| 525 |
return path
|
| 526 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 527 |
cmd = [
|
| 528 |
FFMPEG_BIN, "-y", "-i", path,
|
| 529 |
"-c:v", "libx264", "-preset", "veryfast", "-crf", "28",
|
| 530 |
-
|
| 531 |
-
"-b:a", "128k",
|
| 532 |
"-movflags", "+faststart", out_path,
|
| 533 |
"-map_metadata", "-1"
|
| 534 |
]
|
| 535 |
-
# Remove audio options if no audio stream exists to prevent ffmpeg errors
|
| 536 |
-
if "-c:a" in cmd and cmd[cmd.index("-c:a") + 1] == " ":
|
| 537 |
-
cmd.pop(cmd.index("-c:a") + 1)
|
| 538 |
-
cmd.pop(cmd.index("-c:a"))
|
| 539 |
-
cmd.pop(cmd.index("-b:a") + 1)
|
| 540 |
-
cmd.pop(cmd.index("-b:a"))
|
| 541 |
-
|
| 542 |
|
| 543 |
try:
|
| 544 |
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=60)
|
|
@@ -636,7 +634,8 @@ def create_demo():
|
|
| 636 |
with gr.Column(scale=1):
|
| 637 |
preview_image = gr.Image(label="Preview Image", type="filepath", elem_classes="preview_media", visible=False)
|
| 638 |
preview_video = gr.Video(label="Preview Video", elem_classes="preview_media", visible=False, format="mp4")
|
| 639 |
-
|
|
|
|
| 640 |
# Initially hidden, will become visible when a preview status is set
|
| 641 |
preview_status_text = gr.Textbox(label="Preview Status", interactive=False, lines=1, value="", visible=False)
|
| 642 |
with gr.Column(scale=2):
|
|
@@ -737,10 +736,13 @@ def create_demo():
|
|
| 737 |
main_path_clear = ""
|
| 738 |
screenshot_paths_clear = []
|
| 739 |
raw_media_path_clear = ""
|
|
|
|
|
|
|
| 740 |
|
| 741 |
if not url:
|
| 742 |
return img_update_clear, video_update_clear, gallery_update_clear, \
|
| 743 |
-
preview_status_clear, main_path_clear, raw_media_path_clear,
|
|
|
|
| 744 |
|
| 745 |
temp_raw_path_for_analysis = ""
|
| 746 |
try:
|
|
@@ -749,13 +751,15 @@ def create_demo():
|
|
| 749 |
if not raw_bytes_for_analysis:
|
| 750 |
return img_update_clear, video_update_clear, gallery_update_clear, \
|
| 751 |
gr.update(value="Preview load failed: No media bytes fetched.", visible=True), \
|
| 752 |
-
main_path_clear, raw_media_path_clear, screenshot_paths_clear
|
|
|
|
| 753 |
|
| 754 |
temp_raw_path_for_analysis = _temp_file(raw_bytes_for_analysis, suffix=ext_from_src(url) or ".tmp")
|
| 755 |
if not temp_raw_path_for_analysis:
|
| 756 |
return img_update_clear, video_update_clear, gallery_update_clear, \
|
| 757 |
gr.update(value="Preview load failed: Could not save raw media to temp file.", visible=True), \
|
| 758 |
-
main_path_clear, raw_media_path_clear, screenshot_paths_clear
|
|
|
|
| 759 |
|
| 760 |
progress(0.25, desc="Generating playable preview...")
|
| 761 |
is_img_initial, is_vid_initial = determine_media_type(url)
|
|
@@ -768,7 +772,8 @@ def create_demo():
|
|
| 768 |
|
| 769 |
return img_update_clear, video_update_clear, gallery_update_clear, \
|
| 770 |
gr.update(value="Preview load failed: could not make content playable.", visible=True), \
|
| 771 |
-
main_path_clear, raw_media_path_clear, screenshot_paths_clear
|
|
|
|
| 772 |
|
| 773 |
ext = ext_from_src(local_playable_path)
|
| 774 |
is_img_preview = ext in IMAGE_EXTENSIONS
|
|
@@ -777,11 +782,13 @@ def create_demo():
|
|
| 777 |
if is_img_preview:
|
| 778 |
return gr.update(value=local_playable_path, visible=True), gr.update(value=None, visible=False), \
|
| 779 |
gallery_update_clear, gr.update(value="Image preview loaded.", visible=True), \
|
| 780 |
-
local_playable_path, temp_raw_path_for_analysis, screenshot_paths_clear
|
|
|
|
| 781 |
elif is_vid_preview:
|
| 782 |
return gr.update(value=None, visible=False), gr.update(value=local_playable_path, visible=True), \
|
| 783 |
gallery_update_clear, gr.update(value="Video preview loaded.", visible=True), \
|
| 784 |
-
local_playable_path, temp_raw_path_for_analysis, screenshot_paths_clear
|
|
|
|
| 785 |
else:
|
| 786 |
_temp_files_to_delete.discard(local_playable_path)
|
| 787 |
try: os.remove(local_playable_path)
|
|
@@ -792,7 +799,8 @@ def create_demo():
|
|
| 792 |
|
| 793 |
return img_update_clear, video_update_clear, gallery_update_clear, \
|
| 794 |
gr.update(value="Preview load failed: unknown playable format.", visible=True), \
|
| 795 |
-
main_path_clear, raw_media_path_clear, screenshot_paths_clear
|
|
|
|
| 796 |
|
| 797 |
except Exception as e:
|
| 798 |
if os.path.exists(temp_raw_path_for_analysis):
|
|
@@ -802,12 +810,13 @@ def create_demo():
|
|
| 802 |
|
| 803 |
return img_update_clear, video_update_clear, gallery_update_clear, \
|
| 804 |
gr.update(value=f"Preview load failed: {type(e).__name__}: {e}", visible=True), \
|
| 805 |
-
main_path_clear, raw_media_path_clear, screenshot_paths_clear
|
|
|
|
| 806 |
|
| 807 |
url_input.change(
|
| 808 |
fn=load_main_preview_and_setup_for_analysis,
|
| 809 |
inputs=[url_input, main_preview_path_state, raw_media_path_state, screenshot_paths_state],
|
| 810 |
-
outputs=[preview_image, preview_video, screenshot_gallery, preview_status_text, main_preview_path_state, raw_media_path_state, screenshot_paths_state]
|
| 811 |
)
|
| 812 |
|
| 813 |
def worker(url: str, prompt: str, key: str, raw_media_path: str, progress=gr.Progress()):
|
|
|
|
| 185 |
w = max(1, int(img.width * (base_h / img.height)))
|
| 186 |
img = img.resize((w, base_h), Image.LANCZOS)
|
| 187 |
buf = BytesIO()
|
| 188 |
+
img.save(buf, format="JPEG", quality=90) # Increased quality from 85 to 90
|
| 189 |
return buf.getvalue()
|
| 190 |
|
| 191 |
def b64_bytes(b: bytes, mime: str = "image/jpeg") -> str:
|
|
|
|
| 238 |
timestamps = [step * (i + 1) for i in range(actual_sample_count)]
|
| 239 |
|
| 240 |
if not timestamps:
|
| 241 |
+
# Fallback for very short videos or if duration couldn't be determined
|
| 242 |
+
timestamps = [0.5, 1.0, 2.0, 3.0, 4.0, 5.0][:sample_count] # Ensure enough fallback timestamps
|
| 243 |
|
| 244 |
return info, timestamps
|
| 245 |
|
|
|
|
| 367 |
if progress is not None:
|
| 368 |
progress(0.5 + 0.01 * attempt, desc=f"Uploading file to model service (attempt {attempt+1}/{max_retries})...")
|
| 369 |
|
| 370 |
+
# CHANGE: Pass the file path (str) directly, allowing the mistralai client
|
| 371 |
+
# to handle opening the file and inferring filename/mimetype.
|
| 372 |
+
res = client.files.upload(file=path, purpose=purpose)
|
|
|
|
| 373 |
fid = getattr(res, "id", None)
|
| 374 |
if not fid:
|
| 375 |
raise RuntimeError(f"Mistral API upload response missing file ID: {res}")
|
|
|
|
| 464 |
]
|
| 465 |
result = chat_complete(client, VIDEO_MODEL, messages, progress=progress)
|
| 466 |
|
| 467 |
+
# Always extract frames for gallery, even if full analysis worked
|
| 468 |
_, gallery_frame_paths = extract_frames_for_model_and_gallery(
|
| 469 |
video_path, sample_count=6, gallery_base_h=1080, model_base_h=1024, progress=progress
|
| 470 |
)
|
|
|
|
| 525 |
print(f"Error: Could not create temporary file for video conversion from {path}.")
|
| 526 |
return path
|
| 527 |
|
| 528 |
+
audio_codec_args = []
|
| 529 |
+
video_info = _ffprobe_streams(path)
|
| 530 |
+
if video_info and any(s.get("codec_type") == "audio" for s in video_info.get("streams", [])):
|
| 531 |
+
audio_codec_args = ["-c:a", "aac", "-b:a", "128k"]
|
| 532 |
+
|
| 533 |
cmd = [
|
| 534 |
FFMPEG_BIN, "-y", "-i", path,
|
| 535 |
"-c:v", "libx264", "-preset", "veryfast", "-crf", "28",
|
| 536 |
+
*audio_codec_args, # Unpack the list
|
|
|
|
| 537 |
"-movflags", "+faststart", out_path,
|
| 538 |
"-map_metadata", "-1"
|
| 539 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 540 |
|
| 541 |
try:
|
| 542 |
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=60)
|
|
|
|
| 634 |
with gr.Column(scale=1):
|
| 635 |
preview_image = gr.Image(label="Preview Image", type="filepath", elem_classes="preview_media", visible=False)
|
| 636 |
preview_video = gr.Video(label="Preview Video", elem_classes="preview_media", visible=False, format="mp4")
|
| 637 |
+
# CHANGE: Set columns to 6 to display all 6 extracted frames without scrolling
|
| 638 |
+
screenshot_gallery = gr.Gallery(label="Extracted Screenshots", columns=6, rows=1, height="auto", object_fit="contain", visible=False)
|
| 639 |
# Initially hidden, will become visible when a preview status is set
|
| 640 |
preview_status_text = gr.Textbox(label="Preview Status", interactive=False, lines=1, value="", visible=False)
|
| 641 |
with gr.Column(scale=2):
|
|
|
|
| 736 |
main_path_clear = ""
|
| 737 |
screenshot_paths_clear = []
|
| 738 |
raw_media_path_clear = ""
|
| 739 |
+
progress_markdown_update_clear = gr.update(value="Idle")
|
| 740 |
+
|
| 741 |
|
| 742 |
if not url:
|
| 743 |
return img_update_clear, video_update_clear, gallery_update_clear, \
|
| 744 |
+
preview_status_clear, main_path_clear, raw_media_path_clear, \
|
| 745 |
+
screenshot_paths_clear, progress_markdown_update_clear
|
| 746 |
|
| 747 |
temp_raw_path_for_analysis = ""
|
| 748 |
try:
|
|
|
|
| 751 |
if not raw_bytes_for_analysis:
|
| 752 |
return img_update_clear, video_update_clear, gallery_update_clear, \
|
| 753 |
gr.update(value="Preview load failed: No media bytes fetched.", visible=True), \
|
| 754 |
+
main_path_clear, raw_media_path_clear, screenshot_paths_clear, \
|
| 755 |
+
gr.update(value="Preview load failed (Error)")
|
| 756 |
|
| 757 |
temp_raw_path_for_analysis = _temp_file(raw_bytes_for_analysis, suffix=ext_from_src(url) or ".tmp")
|
| 758 |
if not temp_raw_path_for_analysis:
|
| 759 |
return img_update_clear, video_update_clear, gallery_update_clear, \
|
| 760 |
gr.update(value="Preview load failed: Could not save raw media to temp file.", visible=True), \
|
| 761 |
+
main_path_clear, raw_media_path_clear, screenshot_paths_clear, \
|
| 762 |
+
gr.update(value="Preview load failed (Error)")
|
| 763 |
|
| 764 |
progress(0.25, desc="Generating playable preview...")
|
| 765 |
is_img_initial, is_vid_initial = determine_media_type(url)
|
|
|
|
| 772 |
|
| 773 |
return img_update_clear, video_update_clear, gallery_update_clear, \
|
| 774 |
gr.update(value="Preview load failed: could not make content playable.", visible=True), \
|
| 775 |
+
main_path_clear, raw_media_path_clear, screenshot_paths_clear, \
|
| 776 |
+
gr.update(value="Preview load failed (Error)")
|
| 777 |
|
| 778 |
ext = ext_from_src(local_playable_path)
|
| 779 |
is_img_preview = ext in IMAGE_EXTENSIONS
|
|
|
|
| 782 |
if is_img_preview:
|
| 783 |
return gr.update(value=local_playable_path, visible=True), gr.update(value=None, visible=False), \
|
| 784 |
gallery_update_clear, gr.update(value="Image preview loaded.", visible=True), \
|
| 785 |
+
local_playable_path, temp_raw_path_for_analysis, screenshot_paths_clear, \
|
| 786 |
+
gr.update(value="Preview ready")
|
| 787 |
elif is_vid_preview:
|
| 788 |
return gr.update(value=None, visible=False), gr.update(value=local_playable_path, visible=True), \
|
| 789 |
gallery_update_clear, gr.update(value="Video preview loaded.", visible=True), \
|
| 790 |
+
local_playable_path, temp_raw_path_for_analysis, screenshot_paths_clear, \
|
| 791 |
+
gr.update(value="Preview ready")
|
| 792 |
else:
|
| 793 |
_temp_files_to_delete.discard(local_playable_path)
|
| 794 |
try: os.remove(local_playable_path)
|
|
|
|
| 799 |
|
| 800 |
return img_update_clear, video_update_clear, gallery_update_clear, \
|
| 801 |
gr.update(value="Preview load failed: unknown playable format.", visible=True), \
|
| 802 |
+
main_path_clear, raw_media_path_clear, screenshot_paths_clear, \
|
| 803 |
+
gr.update(value="Preview load failed (Error)")
|
| 804 |
|
| 805 |
except Exception as e:
|
| 806 |
if os.path.exists(temp_raw_path_for_analysis):
|
|
|
|
| 810 |
|
| 811 |
return img_update_clear, video_update_clear, gallery_update_clear, \
|
| 812 |
gr.update(value=f"Preview load failed: {type(e).__name__}: {e}", visible=True), \
|
| 813 |
+
main_path_clear, raw_media_path_clear, screenshot_paths_clear, \
|
| 814 |
+
gr.update(value="Preview load failed (Error)")
|
| 815 |
|
| 816 |
url_input.change(
|
| 817 |
fn=load_main_preview_and_setup_for_analysis,
|
| 818 |
inputs=[url_input, main_preview_path_state, raw_media_path_state, screenshot_paths_state],
|
| 819 |
+
outputs=[preview_image, preview_video, screenshot_gallery, preview_status_text, main_preview_path_state, raw_media_path_state, screenshot_paths_state, progress_markdown] # Added progress_markdown to outputs
|
| 820 |
)
|
| 821 |
|
| 822 |
def worker(url: str, prompt: str, key: str, raw_media_path: str, progress=gr.Progress()):
|