Spaces:
Sleeping
Sleeping
CB commited on
Update streamlit_app.py
Browse files- streamlit_app.py +104 -66
streamlit_app.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
#
|
| 2 |
import os
|
| 3 |
import time
|
| 4 |
import string
|
|
@@ -27,14 +27,19 @@ except Exception:
|
|
| 27 |
Agent = Gemini = DuckDuckGo = None
|
| 28 |
HAS_PHI = False
|
| 29 |
|
| 30 |
-
# google.generativeai SDK
|
| 31 |
try:
|
| 32 |
import google.generativeai as genai
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
| 34 |
HAS_GENAI = True
|
| 35 |
except Exception:
|
| 36 |
genai = None
|
| 37 |
-
|
|
|
|
|
|
|
| 38 |
HAS_GENAI = False
|
| 39 |
|
| 40 |
logging.basicConfig(level=logging.INFO)
|
|
@@ -44,7 +49,6 @@ st.set_page_config(page_title="Generate the story of videos", layout="wide")
|
|
| 44 |
DATA_DIR = Path("./data")
|
| 45 |
DATA_DIR.mkdir(exist_ok=True)
|
| 46 |
|
| 47 |
-
# Session defaults
|
| 48 |
st.session_state.setdefault("videos", "")
|
| 49 |
st.session_state.setdefault("loop_video", False)
|
| 50 |
st.session_state.setdefault("uploaded_file", None)
|
|
@@ -62,7 +66,6 @@ st.session_state.setdefault("processing_timeout", 900)
|
|
| 62 |
st.session_state.setdefault("generation_timeout", 300)
|
| 63 |
st.session_state.setdefault("preferred_model", "gemini-2.5-flash-lite")
|
| 64 |
|
| 65 |
-
# Model choices for dropdown
|
| 66 |
MODEL_OPTIONS = [
|
| 67 |
"gemini-2.5-flash",
|
| 68 |
"gemini-2.5-flash-lite",
|
|
@@ -71,7 +74,6 @@ MODEL_OPTIONS = [
|
|
| 71 |
"custom",
|
| 72 |
]
|
| 73 |
|
| 74 |
-
# Helpers
|
| 75 |
def sanitize_filename(path_str: str):
|
| 76 |
name = Path(path_str).name
|
| 77 |
return name.lower().translate(str.maketrans("", "", string.punctuation)).replace(" ", "_")
|
|
@@ -137,12 +139,12 @@ def configure_genai_if_needed():
|
|
| 137 |
if not key:
|
| 138 |
return False
|
| 139 |
try:
|
| 140 |
-
genai
|
|
|
|
| 141 |
except Exception:
|
| 142 |
pass
|
| 143 |
return True
|
| 144 |
|
| 145 |
-
# Agent management
|
| 146 |
_agent = None
|
| 147 |
def maybe_create_agent(model_id: str):
|
| 148 |
global _agent
|
|
@@ -153,7 +155,8 @@ def maybe_create_agent(model_id: str):
|
|
| 153 |
if _agent and st.session_state.get("last_model") == model_id:
|
| 154 |
return _agent
|
| 155 |
try:
|
| 156 |
-
genai
|
|
|
|
| 157 |
_agent = Agent(name="Video AI summarizer", model=Gemini(id=model_id), tools=[DuckDuckGo()], markdown=True)
|
| 158 |
st.session_state["last_model"] = model_id
|
| 159 |
except Exception:
|
|
@@ -174,13 +177,11 @@ def clear_all_video_state():
|
|
| 174 |
except Exception:
|
| 175 |
pass
|
| 176 |
|
| 177 |
-
# Reset on URL change
|
| 178 |
current_url = st.session_state.get("url", "")
|
| 179 |
if current_url != st.session_state.get("last_url_value"):
|
| 180 |
clear_all_video_state()
|
| 181 |
st.session_state["last_url_value"] = current_url
|
| 182 |
|
| 183 |
-
# Sidebar UI
|
| 184 |
st.sidebar.header("Video Input")
|
| 185 |
st.sidebar.text_input("Video URL", key="url", placeholder="https://")
|
| 186 |
|
|
@@ -220,14 +221,14 @@ safety_settings = [
|
|
| 220 |
{"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "OFF"},
|
| 221 |
]
|
| 222 |
|
| 223 |
-
# Upload & processing helpers
|
| 224 |
def upload_video_sdk(filepath: str):
|
| 225 |
key = get_effective_api_key()
|
| 226 |
if not key:
|
| 227 |
raise RuntimeError("No API key provided")
|
| 228 |
if not HAS_GENAI or upload_file is None:
|
| 229 |
raise RuntimeError("google.generativeai SDK not available; cannot upload")
|
| 230 |
-
genai
|
|
|
|
| 231 |
return upload_file(filepath)
|
| 232 |
|
| 233 |
def wait_for_processed(file_obj, timeout: int = None, progress_callback=None):
|
|
@@ -307,55 +308,6 @@ def compress_video_if_large(local_path: str, threshold_mb: int = 50):
|
|
| 307 |
st.session_state["last_error"] = f"Video compression failed: {e}\n{traceback.format_exc()}"
|
| 308 |
return local_path, False
|
| 309 |
|
| 310 |
-
# Responses API caller: robust, but NO automatic model-switching (user-controlled)
|
| 311 |
-
def generate_via_responses_api(prompt_text: str, processed, model_used: str, max_tokens: int = 1024, timeout: int = 300, progress_callback=None):
|
| 312 |
-
key = get_effective_api_key()
|
| 313 |
-
if not key:
|
| 314 |
-
raise RuntimeError("No API key provided")
|
| 315 |
-
if not HAS_GENAI or genai is None:
|
| 316 |
-
raise RuntimeError("Responses API not available; install google.generativeai SDK.")
|
| 317 |
-
genai.configure(api_key=key)
|
| 318 |
-
fname = file_name_or_id(processed)
|
| 319 |
-
if not fname:
|
| 320 |
-
raise RuntimeError("Uploaded file missing name/id")
|
| 321 |
-
|
| 322 |
-
system_msg = {"role": "system", "content": prompt_text}
|
| 323 |
-
user_msg = {"role": "user", "content": "Please summarize the attached video."}
|
| 324 |
-
call_variants = [
|
| 325 |
-
{"messages": [system_msg, user_msg], "files": [{"name": fname}], "safety_settings": safety_settings, "max_output_tokens": max_tokens},
|
| 326 |
-
{"input": [{"text": prompt_text, "files": [{"name": fname}]}], "safety_settings": safety_settings, "max_output_tokens": max_tokens},
|
| 327 |
-
]
|
| 328 |
-
|
| 329 |
-
def is_transient_error(e_text: str):
|
| 330 |
-
txt = str(e_text).lower()
|
| 331 |
-
return any(k in txt for k in ("internal", "unavailable", "deadlineexceeded", "deadline exceeded", "timeout", "rate limit", "503", "502", "500"))
|
| 332 |
-
|
| 333 |
-
start = time.time()
|
| 334 |
-
last_exc = None
|
| 335 |
-
backoff = 1.0
|
| 336 |
-
attempts = 0
|
| 337 |
-
while True:
|
| 338 |
-
for payload in call_variants:
|
| 339 |
-
attempts += 1
|
| 340 |
-
try:
|
| 341 |
-
if progress_callback:
|
| 342 |
-
progress_callback("starting", int(time.time() - start), {"model": model_used, "attempt": attempts})
|
| 343 |
-
response = genai.responses.generate(model=model_used, **payload)
|
| 344 |
-
text = _normalize_genai_response(response)
|
| 345 |
-
if progress_callback:
|
| 346 |
-
progress_callback("done", int(time.time() - start), {"model": model_used, "attempt": attempts})
|
| 347 |
-
return text
|
| 348 |
-
except Exception as e:
|
| 349 |
-
last_exc = e
|
| 350 |
-
msg = str(e)
|
| 351 |
-
logger.warning("Responses.generate error (model=%s attempt=%s): %s", model_used, attempts, msg)
|
| 352 |
-
if not is_transient_error(msg):
|
| 353 |
-
raise
|
| 354 |
-
if time.time() - start > timeout:
|
| 355 |
-
raise TimeoutError(f"Responses.generate timed out after {timeout}s: last error: {last_exc}")
|
| 356 |
-
time.sleep(backoff)
|
| 357 |
-
backoff = min(backoff * 2, 8.0)
|
| 358 |
-
|
| 359 |
def _normalize_genai_response(response):
|
| 360 |
if response is None:
|
| 361 |
return ""
|
|
@@ -420,7 +372,95 @@ def _normalize_genai_response(response):
|
|
| 420 |
seen.add(t)
|
| 421 |
return "\n\n".join(filtered).strip()
|
| 422 |
|
| 423 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 424 |
col1, col2 = st.columns([1, 3])
|
| 425 |
with col1:
|
| 426 |
generate_now = st.button("Generate the story", type="primary", disabled=not bool(get_effective_api_key()))
|
|
@@ -470,7 +510,6 @@ if st.session_state["videos"]:
|
|
| 470 |
except Exception:
|
| 471 |
pass
|
| 472 |
|
| 473 |
-
# Main generation flow
|
| 474 |
if generate_now and not st.session_state.get("busy"):
|
| 475 |
if not st.session_state.get("videos"):
|
| 476 |
st.error("No video loaded. Use 'Load Video' in the sidebar.")
|
|
@@ -487,7 +526,6 @@ if generate_now and not st.session_state.get("busy"):
|
|
| 487 |
except Exception:
|
| 488 |
pass
|
| 489 |
|
| 490 |
-
# chosen model
|
| 491 |
model_id = model_input_value or st.session_state.get("preferred_model") or "gemini-2.5-flash-lite"
|
| 492 |
if st.session_state.get("last_model") != model_id:
|
| 493 |
st.session_state["last_model"] = ""
|
|
|
|
| 1 |
+
# streamlit_app.py
|
| 2 |
import os
|
| 3 |
import time
|
| 4 |
import string
|
|
|
|
| 27 |
Agent = Gemini = DuckDuckGo = None
|
| 28 |
HAS_PHI = False
|
| 29 |
|
| 30 |
+
# google.generativeai SDK (try both legacy and newer patterns)
|
| 31 |
try:
|
| 32 |
import google.generativeai as genai
|
| 33 |
+
# some installs expose a top-level `responses` object, others require attribute access
|
| 34 |
+
genai_responses = getattr(genai, "responses", None) or getattr(genai, "Responses", None)
|
| 35 |
+
upload_file = getattr(genai, "upload_file", None)
|
| 36 |
+
get_file = getattr(genai, "get_file", None)
|
| 37 |
HAS_GENAI = True
|
| 38 |
except Exception:
|
| 39 |
genai = None
|
| 40 |
+
genai_responses = None
|
| 41 |
+
upload_file = None
|
| 42 |
+
get_file = None
|
| 43 |
HAS_GENAI = False
|
| 44 |
|
| 45 |
logging.basicConfig(level=logging.INFO)
|
|
|
|
| 49 |
DATA_DIR = Path("./data")
|
| 50 |
DATA_DIR.mkdir(exist_ok=True)
|
| 51 |
|
|
|
|
| 52 |
st.session_state.setdefault("videos", "")
|
| 53 |
st.session_state.setdefault("loop_video", False)
|
| 54 |
st.session_state.setdefault("uploaded_file", None)
|
|
|
|
| 66 |
st.session_state.setdefault("generation_timeout", 300)
|
| 67 |
st.session_state.setdefault("preferred_model", "gemini-2.5-flash-lite")
|
| 68 |
|
|
|
|
| 69 |
MODEL_OPTIONS = [
|
| 70 |
"gemini-2.5-flash",
|
| 71 |
"gemini-2.5-flash-lite",
|
|
|
|
| 74 |
"custom",
|
| 75 |
]
|
| 76 |
|
|
|
|
| 77 |
def sanitize_filename(path_str: str):
|
| 78 |
name = Path(path_str).name
|
| 79 |
return name.lower().translate(str.maketrans("", "", string.punctuation)).replace(" ", "_")
|
|
|
|
| 139 |
if not key:
|
| 140 |
return False
|
| 141 |
try:
|
| 142 |
+
if genai is not None and hasattr(genai, "configure"):
|
| 143 |
+
genai.configure(api_key=key)
|
| 144 |
except Exception:
|
| 145 |
pass
|
| 146 |
return True
|
| 147 |
|
|
|
|
| 148 |
_agent = None
|
| 149 |
def maybe_create_agent(model_id: str):
|
| 150 |
global _agent
|
|
|
|
| 155 |
if _agent and st.session_state.get("last_model") == model_id:
|
| 156 |
return _agent
|
| 157 |
try:
|
| 158 |
+
if genai is not None and hasattr(genai, "configure"):
|
| 159 |
+
genai.configure(api_key=key)
|
| 160 |
_agent = Agent(name="Video AI summarizer", model=Gemini(id=model_id), tools=[DuckDuckGo()], markdown=True)
|
| 161 |
st.session_state["last_model"] = model_id
|
| 162 |
except Exception:
|
|
|
|
| 177 |
except Exception:
|
| 178 |
pass
|
| 179 |
|
|
|
|
| 180 |
current_url = st.session_state.get("url", "")
|
| 181 |
if current_url != st.session_state.get("last_url_value"):
|
| 182 |
clear_all_video_state()
|
| 183 |
st.session_state["last_url_value"] = current_url
|
| 184 |
|
|
|
|
| 185 |
st.sidebar.header("Video Input")
|
| 186 |
st.sidebar.text_input("Video URL", key="url", placeholder="https://")
|
| 187 |
|
|
|
|
| 221 |
{"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "OFF"},
|
| 222 |
]
|
| 223 |
|
|
|
|
| 224 |
def upload_video_sdk(filepath: str):
|
| 225 |
key = get_effective_api_key()
|
| 226 |
if not key:
|
| 227 |
raise RuntimeError("No API key provided")
|
| 228 |
if not HAS_GENAI or upload_file is None:
|
| 229 |
raise RuntimeError("google.generativeai SDK not available; cannot upload")
|
| 230 |
+
if genai is not None and hasattr(genai, "configure"):
|
| 231 |
+
genai.configure(api_key=key)
|
| 232 |
return upload_file(filepath)
|
| 233 |
|
| 234 |
def wait_for_processed(file_obj, timeout: int = None, progress_callback=None):
|
|
|
|
| 308 |
st.session_state["last_error"] = f"Video compression failed: {e}\n{traceback.format_exc()}"
|
| 309 |
return local_path, False
|
| 310 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 311 |
def _normalize_genai_response(response):
|
| 312 |
if response is None:
|
| 313 |
return ""
|
|
|
|
| 372 |
seen.add(t)
|
| 373 |
return "\n\n".join(filtered).strip()
|
| 374 |
|
| 375 |
+
def generate_via_responses_api(prompt_text: str, processed, model_used: str, max_tokens: int = 1024, timeout: int = 300, progress_callback=None):
|
| 376 |
+
key = get_effective_api_key()
|
| 377 |
+
if not key:
|
| 378 |
+
raise RuntimeError("No API key provided")
|
| 379 |
+
if not HAS_GENAI or genai is None:
|
| 380 |
+
raise RuntimeError("Responses API not available; install google-generativeai SDK.")
|
| 381 |
+
if genai is not None and hasattr(genai, "configure"):
|
| 382 |
+
genai.configure(api_key=key)
|
| 383 |
+
fname = file_name_or_id(processed)
|
| 384 |
+
if not fname:
|
| 385 |
+
raise RuntimeError("Uploaded file missing name/id")
|
| 386 |
+
|
| 387 |
+
system_msg = {"role": "system", "content": prompt_text}
|
| 388 |
+
user_msg = {"role": "user", "content": "Please summarize the attached video."}
|
| 389 |
+
call_variants = []
|
| 390 |
+
|
| 391 |
+
# Two common payload shapes: `genai.responses.generate(model=..., **payload)`
|
| 392 |
+
# and legacy `genai.Responses.create(...)` or model/chat wrappers.
|
| 393 |
+
# Build payloads for both styles.
|
| 394 |
+
call_variants.append({"method": "responses.generate", "payload": {"model": model_used, "messages": [system_msg, user_msg], "files": [{"name": fname}], "safety_settings": safety_settings, "max_output_tokens": max_tokens}})
|
| 395 |
+
call_variants.append({"method": "responses.generate_alt", "payload": {"model": model_used, "input": [{"text": prompt_text, "files": [{"name": fname}]}], "safety_settings": safety_settings, "max_output_tokens": max_tokens}})
|
| 396 |
+
call_variants.append({"method": "legacy_responses_create", "payload": {"model": model_used, "input": prompt_text, "file": fname, "max_output_tokens": max_tokens}})
|
| 397 |
+
|
| 398 |
+
def is_transient_error(e_text: str):
|
| 399 |
+
txt = str(e_text).lower()
|
| 400 |
+
return any(k in txt for k in ("internal", "unavailable", "deadlineexceeded", "deadline exceeded", "timeout", "rate limit", "503", "502", "500"))
|
| 401 |
+
|
| 402 |
+
start = time.time()
|
| 403 |
+
last_exc = None
|
| 404 |
+
backoff = 1.0
|
| 405 |
+
attempts = 0
|
| 406 |
+
while True:
|
| 407 |
+
for attempt_payload in call_variants:
|
| 408 |
+
attempts += 1
|
| 409 |
+
method = attempt_payload["method"]
|
| 410 |
+
payload = attempt_payload["payload"]
|
| 411 |
+
try:
|
| 412 |
+
if progress_callback:
|
| 413 |
+
progress_callback("starting", int(time.time() - start), {"model": model_used, "attempt": attempts, "method": method})
|
| 414 |
+
# Preferred new API style if available
|
| 415 |
+
if genai_responses is not None and hasattr(genai_responses, "generate"):
|
| 416 |
+
response = genai_responses.generate(**payload) if method.startswith("responses.generate") else genai_responses.generate(**payload)
|
| 417 |
+
text = _normalize_genai_response(response)
|
| 418 |
+
if progress_callback:
|
| 419 |
+
progress_callback("done", int(time.time() - start), {"model": model_used, "attempt": attempts, "method": method})
|
| 420 |
+
return text
|
| 421 |
+
# Some versions expose a top-level Responses class/or function `genai.Responses.create`
|
| 422 |
+
if hasattr(genai, "Responses") and hasattr(genai.Responses, "create"):
|
| 423 |
+
response = genai.Responses.create(**payload) # type: ignore
|
| 424 |
+
text = _normalize_genai_response(response)
|
| 425 |
+
if progress_callback:
|
| 426 |
+
progress_callback("done", int(time.time() - start), {"model": model_used, "attempt": attempts, "method": method})
|
| 427 |
+
return text
|
| 428 |
+
# Legacy model object style (older gemini SDK wrappers)
|
| 429 |
+
if hasattr(genai, "GenerativeModel"):
|
| 430 |
+
try:
|
| 431 |
+
model_obj = genai.GenerativeModel(model_name=model_used)
|
| 432 |
+
# try chat pattern
|
| 433 |
+
if hasattr(model_obj, "start_chat"):
|
| 434 |
+
chat = model_obj.start_chat()
|
| 435 |
+
resp = chat.send_message(prompt_text, timeout=timeout)
|
| 436 |
+
text = getattr(resp, "text", None) or str(resp)
|
| 437 |
+
text = text if text else _normalize_genai_response(resp)
|
| 438 |
+
if progress_callback:
|
| 439 |
+
progress_callback("done", int(time.time() - start), {"model": model_used, "attempt": attempts, "method": "GenerativeModel.chat"})
|
| 440 |
+
return text
|
| 441 |
+
except Exception:
|
| 442 |
+
pass
|
| 443 |
+
# If none matched, raise to be caught below and trigger helpful error
|
| 444 |
+
raise RuntimeError("No supported response generation method available in installed google-generativeai package.")
|
| 445 |
+
except Exception as e:
|
| 446 |
+
last_exc = e
|
| 447 |
+
msg = str(e)
|
| 448 |
+
logger.warning("Responses.generate error (model=%s attempt=%s method=%s): %s", model_used, attempts, method, msg)
|
| 449 |
+
if not is_transient_error(msg):
|
| 450 |
+
# non-transient -> surface meaningful hint for common misconfig issues
|
| 451 |
+
if "No supported response generation method" in msg or "has no attribute" in msg or "module 'google.generativeai' has no attribute" in msg:
|
| 452 |
+
raise RuntimeError(
|
| 453 |
+
"Installed google-generativeai package does not expose a compatible Responses API. "
|
| 454 |
+
"Please upgrade to a recent release or install the Google GenAI SDK. "
|
| 455 |
+
"Run: pip install --upgrade google-generativeai"
|
| 456 |
+
) from e
|
| 457 |
+
raise
|
| 458 |
+
if time.time() - start > timeout:
|
| 459 |
+
raise TimeoutError(f"Responses.generate timed out after {timeout}s: last error: {last_exc}")
|
| 460 |
+
time.sleep(backoff)
|
| 461 |
+
backoff = min(backoff * 2, 8.0)
|
| 462 |
+
|
| 463 |
+
# UI layout
|
| 464 |
col1, col2 = st.columns([1, 3])
|
| 465 |
with col1:
|
| 466 |
generate_now = st.button("Generate the story", type="primary", disabled=not bool(get_effective_api_key()))
|
|
|
|
| 510 |
except Exception:
|
| 511 |
pass
|
| 512 |
|
|
|
|
| 513 |
if generate_now and not st.session_state.get("busy"):
|
| 514 |
if not st.session_state.get("videos"):
|
| 515 |
st.error("No video loaded. Use 'Load Video' in the sidebar.")
|
|
|
|
| 526 |
except Exception:
|
| 527 |
pass
|
| 528 |
|
|
|
|
| 529 |
model_id = model_input_value or st.session_state.get("preferred_model") or "gemini-2.5-flash-lite"
|
| 530 |
if st.session_state.get("last_model") != model_id:
|
| 531 |
st.session_state["last_model"] = ""
|