Hug0endob commited on
Commit
da12238
·
verified ·
1 Parent(s): cf672fe

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +74 -92
app.py CHANGED
@@ -41,8 +41,8 @@ DEFAULT_HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Appl
41
 
42
  try:
43
  from mistralai import Mistral
44
- from mistralai.exceptions import MistralAPIException # Import for better error handling
45
- except ImportError: # Use ImportError for module import issues
46
  Mistral = None
47
  # Define a mock MistralAPIException for type hinting and to avoid NameError
48
  class MistralAPIException(Exception):
@@ -71,19 +71,51 @@ atexit.register(_cleanup_all_temp_preview_files)
71
 
72
  # --- Mistral Client and API Helpers ---
73
  def get_client(key: Optional[str] = None):
 
 
 
 
 
74
  api_key = (key or "").strip() or DEFAULT_KEY
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  if Mistral is None:
76
- class Dummy: # Mock client for no mistralai library or no key
77
  def __init__(self, k): self.api_key = k
78
  class Chat:
79
  def complete(self, **kwargs):
80
- raise RuntimeError("Mistral client library not installed or API key missing.")
 
 
 
81
  chat = Chat()
82
  class Files:
83
  def upload(self, **kwargs):
84
- raise RuntimeError("Mistral client library not installed or API key missing.")
 
 
 
85
  files = Files()
86
  return Dummy(api_key)
 
87
  return Mistral(api_key=api_key)
88
 
89
  def is_remote(src: str) -> bool:
@@ -243,7 +275,7 @@ def _get_video_info_and_timestamps(media_path: str, sample_count: int) -> Tuple[
243
 
244
  return info, timestamps
245
 
246
- def extract_frames_for_model_and_gallery(media_path: str, sample_count: int = 5, timeout_extract: int = 15, gallery_base_h: int = 256, progress=None) -> Tuple[List[bytes], List[str]]:
247
  """
248
  Extracts frames from a video, processes them for both model input (high-res JPEG bytes)
249
  and gallery display (smaller JPEG temp file paths), in a single pass.
@@ -288,6 +320,8 @@ def extract_frames_for_model_and_gallery(media_path: str, sample_count: int = 5,
288
  jpeg_model_bytes = convert_to_jpeg_bytes(raw_frame_bytes, base_h=720) # Keep higher res for model
289
  if jpeg_model_bytes: # Only append if conversion was successful
290
  frames_for_model.append(jpeg_model_bytes)
 
 
291
 
292
  # For gallery: convert to smaller JPEG bytes and save as new temp file
293
  jpeg_gallery_bytes = convert_to_jpeg_bytes(raw_frame_bytes, base_h=gallery_base_h) # Higher base_h
@@ -322,10 +356,10 @@ def chat_complete(client, model: str, messages, timeout: int = 120, progress=Non
322
  # Prefer using the Mistral client if available and functional
323
  if hasattr(client, "chat") and hasattr(client.chat, "complete"):
324
  res = client.chat.complete(model=model, messages=messages, stream=False, timeout_ms=timeout * 1000)
325
- else:
326
  api_key = getattr(client, "api_key", "") or DEFAULT_KEY
327
  if not api_key:
328
- return "Error: Mistral API key is not set."
329
  url = "https://api.mistral.ai/v1/chat/completions"
330
  headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
331
  r = requests.post(url, json={"model": model, "messages": messages}, headers=headers, timeout=timeout)
@@ -344,13 +378,13 @@ def chat_complete(client, model: str, messages, timeout: int = 120, progress=Non
344
  content = (msg.get("content") if isinstance(msg, dict) else getattr(msg, "content", None))
345
  return content.strip() if isinstance(content, str) else str(content)
346
 
347
- except MistralAPIException as e: # Catch Mistral client's specific API exceptions
348
  if e.status_code == 429 and attempt < max_retries - 1:
349
  delay = initial_delay * (2 ** attempt)
350
  print(f"MistralAPIException: Rate limit exceeded (429). Retrying in {delay:.2f}s...")
351
  time.sleep(delay)
352
  else:
353
- return f"Error: Mistral API error occurred with status {e.status_code}: {e.message}"
354
  except HTTPError as e: # For direct requests calls
355
  if e.response.status_code == 429 and attempt < max_retries - 1:
356
  delay = initial_delay * (2 ** attempt)
@@ -366,7 +400,7 @@ def chat_complete(client, model: str, messages, timeout: int = 120, progress=Non
366
  else:
367
  return f"Error: network/API request failed after {max_retries} attempts: {e}"
368
  except Exception as e:
369
- return f"Error during model call: {e}"
370
 
371
  return "Error: Maximum retries reached for API call."
372
 
@@ -390,7 +424,7 @@ def upload_file_to_mistral(client, path: str, filename: str | None = None, purpo
390
  if progress is not None:
391
  progress(0.6, desc="Upload complete")
392
  return fid
393
- else: # Fallback to manual requests if client.files.upload fails or Mistral is mocked
394
  api_key = getattr(client, "api_key", "") or DEFAULT_KEY
395
  if not api_key:
396
  raise RuntimeError("Mistral API key is not set for file upload.")
@@ -405,7 +439,7 @@ def upload_file_to_mistral(client, path: str, filename: str | None = None, purpo
405
  if progress is not None:
406
  progress(0.65, desc="Upload complete (REST)")
407
  return jr.get("id") or jr.get("data", [{}])[0].get("id")
408
- except MistralAPIException as e: # Catch Mistral client's specific API exceptions
409
  if e.status_code == 429 and attempt < max_retries - 1:
410
  delay = initial_delay * (2 ** attempt)
411
  print(f"MistralAPIException: Upload rate limit exceeded (429). Retrying in {delay:.2f}s...")
@@ -496,7 +530,7 @@ def analyze_video_cohesive(client, video_path: str, prompt: str, progress=None)
496
  # If successful upload, still extract frames for gallery display
497
  # Use the combined function for gallery frames
498
  _, gallery_frame_paths = extract_frames_for_model_and_gallery(
499
- video_path, sample_count=6, gallery_base_h=256, progress=progress # Increased gallery_base_h
500
  )
501
  return result, gallery_frame_paths
502
  except Exception as e:
@@ -505,7 +539,7 @@ def analyze_video_cohesive(client, video_path: str, prompt: str, progress=None)
505
 
506
  # Use the combined extraction function for both model input and gallery display
507
  frames_for_model_bytes, gallery_frame_paths = extract_frames_for_model_and_gallery(
508
- video_path, sample_count=6, gallery_base_h=256, progress=progress # Increased gallery_base_h
509
  )
510
 
511
  if not frames_for_model_bytes:
@@ -542,7 +576,9 @@ def _convert_video_for_preview_if_needed(path: str) -> str:
542
  return path # Cannot convert, return original
543
 
544
  # Check if the video is already likely browser-compatible (MP4 with H.264/H.265)
545
- if path.lower().endswith((".mp4", ".m4v", ".mov")):
 
 
546
  info = _ffprobe_streams(path)
547
  if info:
548
  video_streams = [s for s in info.get("streams", []) if s.get("codec_type") == "video"]
@@ -583,46 +619,29 @@ def _get_playable_preview_path_from_raw(src_url: str, raw_bytes: bytes, is_image
583
  print(f"Error: No raw bytes provided for preview generation of {src_url}.")
584
  return ""
585
 
586
- # Determine media type (prioritizing hints, then byte analysis)
587
- # This logic aims to be somewhat lenient for preview display
588
  is_actually_image = False
589
- is_actually_video = False
590
-
591
- # Try to determine from bytes first, if hints are ambiguous or absent
592
  try:
593
  Image.open(BytesIO(raw_bytes)).verify()
594
  is_actually_image = True
595
- except UnidentifiedImageError:
596
- # Not an identifiable image by PIL. Now consider hints or default to video.
597
- is_actually_image = False
598
- if is_video_hint:
599
- is_actually_video = True
600
- elif is_image_hint: # If hinted as image but PIL failed, still prefer image for error clarity
601
- print(f"Warning: Hinted as image but PIL failed for {src_url}. Assuming image for preview attempt.")
602
- is_actually_image = True
603
- else:
604
- is_actually_video = True # No strong hint, not an image, assume video
605
- except Exception as e:
606
- print(f"Warning: Generic error during image check for {src_url}: {e}. Falling back to video preview attempt.")
607
- is_actually_image = False # Clear image flag
608
- is_actually_video = True # Assume video as a fallback for preview
609
 
610
- # If still neither, use original hints as last resort
611
- if not is_actually_image and not is_actually_video:
 
612
  is_actually_image = is_image_hint
613
- is_actually_video = is_video_hint
614
-
615
 
616
  # --- Attempt Image Preview ---
617
  if is_actually_image:
618
- jpeg_bytes = convert_to_jpeg_bytes(raw_bytes, base_h=1024)
619
  if jpeg_bytes:
620
  return _temp_file(jpeg_bytes, suffix=".jpg")
621
  else:
622
- # If image conversion fails, even if it seemed like an image, fall back to video
623
  print(f"Warning: Could not convert image bytes for {src_url} to JPEG. Attempting video fallback.")
624
- is_actually_image = False # Clear image flag
625
- is_actually_video = True # Set video flag for next block
 
626
 
627
  # --- Attempt Video Preview ---
628
  if is_actually_video:
@@ -692,7 +711,6 @@ def create_demo():
692
  Cleans up all tracked temporary files and resets all relevant UI components.
693
  This function is meant to be called at the start of any new processing
694
  or when the user explicitly clicks "Clear".
695
- Returns values for all 11 output components.
696
  """
697
  for f_path in list(_temp_preview_files_to_delete):
698
  if os.path.exists(f_path):
@@ -702,7 +720,6 @@ def create_demo():
702
  print(f"Error during proactive cleanup of {f_path}: {e}")
703
  _temp_preview_files_to_delete.clear()
704
 
705
- # Return exactly 11 values to match the outputs list
706
  return "", \
707
  gr.update(value=None, visible=False), \
708
  gr.update(value=None, visible=False), \
@@ -730,7 +747,7 @@ def create_demo():
730
  main_preview_path_state,
731
  screenshot_paths_state,
732
  preview_status,
733
- raw_media_path_state # Clear the raw media path state as well
734
  ]
735
  )
736
 
@@ -780,31 +797,16 @@ def create_demo():
780
  gr.update(value="Preview load failed: could not make content playable.", visible=True), \
781
  main_path_clear, screenshot_paths_clear, raw_media_path_clear
782
 
783
- # 4. Re-evaluate actual type for Gradio display from the *playable path*
784
- # This is important as _get_playable_preview_path_from_raw might have converted the format
785
- is_img_preview = False
786
- is_vid_preview = False
787
-
788
  ext = ext_from_src(local_playable_path)
789
- if ext in IMAGE_EXTS:
790
- is_img_preview = True
791
- elif ext in VIDEO_EXTS:
792
- is_vid_preview = True
793
-
794
- # Fallback to PIL check if extension is ambiguous or unknown, and if it's an actual file
795
- if not is_img_preview and not is_vid_preview and os.path.exists(local_playable_path):
796
- try:
797
- Image.open(local_playable_path).verify()
798
- is_img_preview = True
799
- except Exception:
800
- # If not an image, assume it might be a video (or non-playable for Gradio)
801
- is_vid_preview = True # Flag as video for Gradio component decision
802
 
803
  if is_img_preview:
804
  return gr.update(value=local_playable_path, visible=True), gr.update(value=None, visible=False), \
805
  gr.update(value=[], visible=False), gr.update(value="Image preview loaded.", visible=True), \
806
  local_playable_path, [], temp_raw_path_for_analysis # Update raw_media_path_state
807
- elif is_vid_preview: # Assume video if not image
808
  return gr.update(value=None, visible=False), gr.update(value=local_playable_path, visible=True), \
809
  gr.update(value=[], visible=False), gr.update(value="Video preview loaded.", visible=True), \
810
  local_playable_path, [], temp_raw_path_for_analysis # Update raw_media_path_state
@@ -813,7 +815,6 @@ def create_demo():
813
  gr.update(value="Preview load failed: unknown playable format.", visible=True), \
814
  main_path_clear, screenshot_paths_clear, raw_media_path_clear
815
 
816
-
817
  except Exception as e:
818
  # Cleanup temp_raw_path_for_analysis on error
819
  if temp_raw_path_for_analysis in _temp_preview_files_to_delete:
@@ -857,7 +858,6 @@ def create_demo():
857
  is_actually_video_for_analysis = False
858
 
859
  try:
860
- # Try as image first, using actual bytes
861
  Image.open(BytesIO(raw_bytes_for_analysis)).verify()
862
  is_actually_image_for_analysis = True
863
  except UnidentifiedImageError:
@@ -918,31 +918,13 @@ def create_demo():
918
  video_update = gr.update(value=None, visible=False)
919
 
920
  if current_main_preview_path:
921
- try:
922
- is_img_preview = False
923
- is_vid_preview = False
924
-
925
- # Determine type based on file extension
926
- ext = ext_from_src(current_main_preview_path)
927
- if ext in IMAGE_EXTS:
928
- is_img_preview = True
929
- elif ext in VIDEO_EXTS:
930
- is_vid_preview = True
931
-
932
- # Fallback to PIL check if extension is ambiguous or unknown, and if it's an actual file
933
- if not is_img_preview and not is_vid_preview and os.path.exists(current_main_preview_path):
934
- try:
935
- Image.open(current_main_preview_path).verify()
936
- is_img_preview = True
937
- except Exception:
938
- is_vid_preview = True # If not image, assume video for display purposes
939
-
940
- if is_img_preview:
941
- img_update = gr.update(value=current_main_preview_path, visible=True)
942
- elif is_vid_preview:
943
- video_update = gr.update(value=current_main_preview_path, visible=True)
944
- except Exception as e:
945
- print(f"Error setting main preview from path {current_main_preview_path}: {e}")
946
 
947
  # Gallery is visible only if there are paths
948
  gallery_update = gr.update(value=current_screenshot_paths, visible=bool(current_screenshot_paths))
 
41
 
42
  try:
43
  from mistralai import Mistral
44
+ from mistralai.exceptions import MistralAPIException
45
+ except ImportError:
46
  Mistral = None
47
  # Define a mock MistralAPIException for type hinting and to avoid NameError
48
  class MistralAPIException(Exception):
 
71
 
72
  # --- Mistral Client and API Helpers ---
73
  def get_client(key: Optional[str] = None):
74
+ """
75
+ Returns a Mistral client instance. If the mistralai library is not installed
76
+ or the API key is missing, a mock client is returned which raises
77
+ MistralAPIException with specific messages.
78
+ """
79
  api_key = (key or "").strip() or DEFAULT_KEY
80
+
81
+ if not api_key:
82
+ class Dummy:
83
+ def __init__(self, k): self.api_key = k
84
+ class Chat:
85
+ def complete(self, **kwargs):
86
+ raise MistralAPIException(
87
+ "Mistral API key is not set. Please provide it in the UI or as MISTRAL_API_KEY environment variable.",
88
+ status_code=401
89
+ )
90
+ chat = Chat()
91
+ class Files:
92
+ def upload(self, **kwargs):
93
+ raise MistralAPIException(
94
+ "Mistral API key is not set. Please provide it in the UI or as MISTRAL_API_KEY environment variable.",
95
+ status_code=401
96
+ )
97
+ files = Files()
98
+ return Dummy(api_key)
99
+
100
  if Mistral is None:
101
+ class Dummy:
102
  def __init__(self, k): self.api_key = k
103
  class Chat:
104
  def complete(self, **kwargs):
105
+ raise MistralAPIException(
106
+ "Mistral client library is not installed. Please install it with 'pip install mistralai'.",
107
+ status_code=500
108
+ )
109
  chat = Chat()
110
  class Files:
111
  def upload(self, **kwargs):
112
+ raise MistralAPIException(
113
+ "Mistral client library is not installed. Please install it with 'pip install mistralai'.",
114
+ status_code=500
115
+ )
116
  files = Files()
117
  return Dummy(api_key)
118
+
119
  return Mistral(api_key=api_key)
120
 
121
  def is_remote(src: str) -> bool:
 
275
 
276
  return info, timestamps
277
 
278
+ def extract_frames_for_model_and_gallery(media_path: str, sample_count: int = 5, timeout_extract: int = 15, gallery_base_h: int = 720, progress=None) -> Tuple[List[bytes], List[str]]:
279
  """
280
  Extracts frames from a video, processes them for both model input (high-res JPEG bytes)
281
  and gallery display (smaller JPEG temp file paths), in a single pass.
 
320
  jpeg_model_bytes = convert_to_jpeg_bytes(raw_frame_bytes, base_h=720) # Keep higher res for model
321
  if jpeg_model_bytes: # Only append if conversion was successful
322
  frames_for_model.append(jpeg_model_bytes)
323
+ else:
324
+ print(f"Warning: Failed to convert extracted frame {i+1} to JPEG for model input.")
325
 
326
  # For gallery: convert to smaller JPEG bytes and save as new temp file
327
  jpeg_gallery_bytes = convert_to_jpeg_bytes(raw_frame_bytes, base_h=gallery_base_h) # Higher base_h
 
356
  # Prefer using the Mistral client if available and functional
357
  if hasattr(client, "chat") and hasattr(client.chat, "complete"):
358
  res = client.chat.complete(model=model, messages=messages, stream=False, timeout_ms=timeout * 1000)
359
+ else: # This path should ideally not be reached if get_client returns a dummy with exceptions
360
  api_key = getattr(client, "api_key", "") or DEFAULT_KEY
361
  if not api_key:
362
+ return "Error: Mistral API key is not set. Cannot make direct requests."
363
  url = "https://api.mistral.ai/v1/chat/completions"
364
  headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
365
  r = requests.post(url, json={"model": model, "messages": messages}, headers=headers, timeout=timeout)
 
378
  content = (msg.get("content") if isinstance(msg, dict) else getattr(msg, "content", None))
379
  return content.strip() if isinstance(content, str) else str(content)
380
 
381
+ except MistralAPIException as e: # Catch Mistral client's specific API exceptions (including from Dummy client)
382
  if e.status_code == 429 and attempt < max_retries - 1:
383
  delay = initial_delay * (2 ** attempt)
384
  print(f"MistralAPIException: Rate limit exceeded (429). Retrying in {delay:.2f}s...")
385
  time.sleep(delay)
386
  else:
387
+ return f"Error: Mistral API error occurred ({e.status_code if e.status_code else 'unknown'}): {e.message}"
388
  except HTTPError as e: # For direct requests calls
389
  if e.response.status_code == 429 and attempt < max_retries - 1:
390
  delay = initial_delay * (2 ** attempt)
 
400
  else:
401
  return f"Error: network/API request failed after {max_retries} attempts: {e}"
402
  except Exception as e:
403
+ return f"Error during model call: An unexpected error occurred: {e}"
404
 
405
  return "Error: Maximum retries reached for API call."
406
 
 
424
  if progress is not None:
425
  progress(0.6, desc="Upload complete")
426
  return fid
427
+ else: # This path should ideally not be reached if get_client returns a dummy with exceptions
428
  api_key = getattr(client, "api_key", "") or DEFAULT_KEY
429
  if not api_key:
430
  raise RuntimeError("Mistral API key is not set for file upload.")
 
439
  if progress is not None:
440
  progress(0.65, desc="Upload complete (REST)")
441
  return jr.get("id") or jr.get("data", [{}])[0].get("id")
442
+ except MistralAPIException as e: # Catch Mistral client's specific API exceptions (including from Dummy client)
443
  if e.status_code == 429 and attempt < max_retries - 1:
444
  delay = initial_delay * (2 ** attempt)
445
  print(f"MistralAPIException: Upload rate limit exceeded (429). Retrying in {delay:.2f}s...")
 
530
  # If successful upload, still extract frames for gallery display
531
  # Use the combined function for gallery frames
532
  _, gallery_frame_paths = extract_frames_for_model_and_gallery(
533
+ video_path, sample_count=6, gallery_base_h=720, progress=progress # Increased gallery_base_h
534
  )
535
  return result, gallery_frame_paths
536
  except Exception as e:
 
539
 
540
  # Use the combined extraction function for both model input and gallery display
541
  frames_for_model_bytes, gallery_frame_paths = extract_frames_for_model_and_gallery(
542
+ video_path, sample_count=6, gallery_base_h=720, progress=progress # Increased gallery_base_h
543
  )
544
 
545
  if not frames_for_model_bytes:
 
576
  return path # Cannot convert, return original
577
 
578
  # Check if the video is already likely browser-compatible (MP4 with H.264/H.265)
579
+ # This check is a heuristic; direct ffprobe is more reliable but adds overhead.
580
+ # For a preview, extension check is usually sufficient.
581
+ if path.lower().endswith((".mp4", ".m4v")): # .mov might still need conversion
582
  info = _ffprobe_streams(path)
583
  if info:
584
  video_streams = [s for s in info.get("streams", []) if s.get("codec_type") == "video"]
 
619
  print(f"Error: No raw bytes provided for preview generation of {src_url}.")
620
  return ""
621
 
622
+ # Attempt to determine media type from bytes for image
 
623
  is_actually_image = False
 
 
 
624
  try:
625
  Image.open(BytesIO(raw_bytes)).verify()
626
  is_actually_image = True
627
+ except (UnidentifiedImageError, Exception):
628
+ pass # Not an identifiable image, or some other PIL error
 
 
 
 
 
 
 
 
 
 
 
 
629
 
630
+ # Default to hints if byte check is inconclusive for images
631
+ is_actually_video = not is_actually_image and is_video_hint # Prefer video hint if not definitely an image
632
+ if not is_actually_image and not is_actually_video: # If still no clear type, use image hint as fallback
633
  is_actually_image = is_image_hint
 
 
634
 
635
  # --- Attempt Image Preview ---
636
  if is_actually_image:
637
+ jpeg_bytes = convert_to_jpeg_bytes(raw_bytes, base_h=1024) # High-res for main preview
638
  if jpeg_bytes:
639
  return _temp_file(jpeg_bytes, suffix=".jpg")
640
  else:
 
641
  print(f"Warning: Could not convert image bytes for {src_url} to JPEG. Attempting video fallback.")
642
+ # Fall through to video logic if image conversion fails
643
+ is_actually_image = False
644
+ is_actually_video = True # Assume video as a fallback for preview
645
 
646
  # --- Attempt Video Preview ---
647
  if is_actually_video:
 
711
  Cleans up all tracked temporary files and resets all relevant UI components.
712
  This function is meant to be called at the start of any new processing
713
  or when the user explicitly clicks "Clear".
 
714
  """
715
  for f_path in list(_temp_preview_files_to_delete):
716
  if os.path.exists(f_path):
 
720
  print(f"Error during proactive cleanup of {f_path}: {e}")
721
  _temp_preview_files_to_delete.clear()
722
 
 
723
  return "", \
724
  gr.update(value=None, visible=False), \
725
  gr.update(value=None, visible=False), \
 
747
  main_preview_path_state,
748
  screenshot_paths_state,
749
  preview_status,
750
+ raw_media_path_state
751
  ]
752
  )
753
 
 
797
  gr.update(value="Preview load failed: could not make content playable.", visible=True), \
798
  main_path_clear, screenshot_paths_clear, raw_media_path_clear
799
 
800
+ # 4. Determine actual type for Gradio display from the *playable path*
 
 
 
 
801
  ext = ext_from_src(local_playable_path)
802
+ is_img_preview = ext in IMAGE_EXTS
803
+ is_vid_preview = ext in VIDEO_EXTS
 
 
 
 
 
 
 
 
 
 
 
804
 
805
  if is_img_preview:
806
  return gr.update(value=local_playable_path, visible=True), gr.update(value=None, visible=False), \
807
  gr.update(value=[], visible=False), gr.update(value="Image preview loaded.", visible=True), \
808
  local_playable_path, [], temp_raw_path_for_analysis # Update raw_media_path_state
809
+ elif is_vid_preview:
810
  return gr.update(value=None, visible=False), gr.update(value=local_playable_path, visible=True), \
811
  gr.update(value=[], visible=False), gr.update(value="Video preview loaded.", visible=True), \
812
  local_playable_path, [], temp_raw_path_for_analysis # Update raw_media_path_state
 
815
  gr.update(value="Preview load failed: unknown playable format.", visible=True), \
816
  main_path_clear, screenshot_paths_clear, raw_media_path_clear
817
 
 
818
  except Exception as e:
819
  # Cleanup temp_raw_path_for_analysis on error
820
  if temp_raw_path_for_analysis in _temp_preview_files_to_delete:
 
858
  is_actually_video_for_analysis = False
859
 
860
  try:
 
861
  Image.open(BytesIO(raw_bytes_for_analysis)).verify()
862
  is_actually_image_for_analysis = True
863
  except UnidentifiedImageError:
 
918
  video_update = gr.update(value=None, visible=False)
919
 
920
  if current_main_preview_path:
921
+ ext = ext_from_src(current_main_preview_path)
922
+ if ext in IMAGE_EXTS:
923
+ img_update = gr.update(value=current_main_preview_path, visible=True)
924
+ elif ext in VIDEO_EXTS:
925
+ video_update = gr.update(value=current_main_preview_path, visible=True)
926
+ else:
927
+ print(f"Warning: Unknown media type for main preview path: {current_main_preview_path}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
928
 
929
  # Gallery is visible only if there are paths
930
  gallery_update = gr.update(value=current_screenshot_paths, visible=bool(current_screenshot_paths))