Update app.py
Browse files
app.py
CHANGED
|
@@ -10,10 +10,7 @@ import uuid
|
|
| 10 |
from object_detection import detectObjects, detectVideo
|
| 11 |
from object_detection_count import detectObjectsAndCount
|
| 12 |
from pose_analysis import process_gif
|
| 13 |
-
|
| 14 |
-
# Traffic sign detection (image + video)
|
| 15 |
-
from traffic_sign_detection import detectTrafficObjects, detectTrafficVideo
|
| 16 |
-
|
| 17 |
|
| 18 |
# -----------------------------
|
| 19 |
# Constants
|
|
@@ -33,23 +30,6 @@ TASK_TO_ASSET_SUBDIR = {
|
|
| 33 |
IMAGE_EXTS = {".jpg", ".jpeg", ".png"}
|
| 34 |
VIDEO_EXTS = {".mp4", ".mov", ".avi", ".gif"}
|
| 35 |
|
| 36 |
-
# Per-tab allowed types for uploader + validation
|
| 37 |
-
TAB_ALLOWED_EXTS = {
|
| 38 |
-
"Object Detection": IMAGE_EXTS | VIDEO_EXTS, # image + video
|
| 39 |
-
"Traffic Sign Detection": IMAGE_EXTS | VIDEO_EXTS, # image + video
|
| 40 |
-
"Pose Analysis": VIDEO_EXTS, # video only
|
| 41 |
-
"Object Counting": IMAGE_EXTS, # image only
|
| 42 |
-
}
|
| 43 |
-
|
| 44 |
-
# Streamlit uploader "type" expects extensions WITHOUT dots
|
| 45 |
-
TAB_UPLOADER_TYPES = {
|
| 46 |
-
"Object Detection": ["jpg", "jpeg", "png", "gif", "mp4", "avi", "mov"],
|
| 47 |
-
"Traffic Sign Detection": ["jpg", "jpeg", "png", "gif", "mp4", "avi", "mov"],
|
| 48 |
-
"Pose Analysis": ["gif", "mp4", "avi", "mov"],
|
| 49 |
-
"Object Counting": ["jpg", "jpeg", "png"],
|
| 50 |
-
}
|
| 51 |
-
|
| 52 |
-
|
| 53 |
# -----------------------------
|
| 54 |
# Helpers
|
| 55 |
# -----------------------------
|
|
@@ -61,19 +41,15 @@ def check_file_size(file):
|
|
| 61 |
|
| 62 |
|
| 63 |
def save_uploaded_file_to_temp(uploaded_file) -> str:
|
| 64 |
-
|
| 65 |
-
Save upload to a temp folder to avoid cluttering project root
|
| 66 |
-
and reduce filename collisions.
|
| 67 |
-
"""
|
| 68 |
safe_name = uploaded_file.name.replace("/", "_").replace("\\", "_")
|
| 69 |
-
|
| 70 |
-
temp_dir.mkdir(parents=True, exist_ok=True)
|
| 71 |
|
| 72 |
-
|
| 73 |
-
with open(out_path, "wb") as f:
|
| 74 |
f.write(uploaded_file.getbuffer())
|
| 75 |
|
| 76 |
-
|
|
|
|
| 77 |
|
| 78 |
|
| 79 |
def list_demo_files(task_name: str, limit: int = 6):
|
|
@@ -111,10 +87,14 @@ def is_h264(path: str) -> bool:
|
|
| 111 |
out = subprocess.check_output(
|
| 112 |
[
|
| 113 |
"ffprobe",
|
| 114 |
-
"-v",
|
| 115 |
-
"
|
| 116 |
-
"-
|
| 117 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
path,
|
| 119 |
],
|
| 120 |
text=True,
|
|
@@ -131,23 +111,30 @@ def to_h264_mp4(input_path: str) -> str:
|
|
| 131 |
- Uses a unique temp output so we don't overwrite assets or collide across reruns.
|
| 132 |
"""
|
| 133 |
if shutil.which("ffmpeg") is None:
|
| 134 |
-
st.warning("ffmpeg not found. Video may
|
| 135 |
return input_path
|
| 136 |
|
| 137 |
out_path = str(Path(tempfile.gettempdir()) / f"{uuid.uuid4().hex}.mp4")
|
| 138 |
cmd = [
|
| 139 |
"ffmpeg",
|
| 140 |
"-y",
|
| 141 |
-
"-i",
|
| 142 |
-
|
| 143 |
-
"-
|
| 144 |
-
"
|
| 145 |
-
"-
|
| 146 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
out_path,
|
| 148 |
]
|
| 149 |
try:
|
| 150 |
subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
|
|
|
| 151 |
if not Path(out_path).exists() or Path(out_path).stat().st_size < 1024:
|
| 152 |
st.warning("Video conversion produced an empty file; showing original.")
|
| 153 |
return input_path
|
|
@@ -162,6 +149,8 @@ def render_media(path: Path):
|
|
| 162 |
if ext in IMAGE_EXTS:
|
| 163 |
st.image(str(path), caption=path.name, use_container_width=True)
|
| 164 |
elif ext in VIDEO_EXTS:
|
|
|
|
|
|
|
| 165 |
playable = str(path)
|
| 166 |
if not is_h264(playable):
|
| 167 |
playable = to_h264_mp4(playable)
|
|
@@ -171,7 +160,10 @@ def render_media(path: Path):
|
|
| 171 |
|
| 172 |
def render_showcase(tasks, per_task_limit=6):
|
| 173 |
st.subheader("Example outputs (what to expect)")
|
| 174 |
-
st.write(
|
|
|
|
|
|
|
|
|
|
| 175 |
|
| 176 |
for task in tasks:
|
| 177 |
st.markdown(f"### {task}")
|
|
@@ -179,8 +171,8 @@ def render_showcase(tasks, per_task_limit=6):
|
|
| 179 |
|
| 180 |
if not demo_files:
|
| 181 |
st.info(
|
| 182 |
-
f"No demo files found for **{task}**. Add
|
| 183 |
-
f"
|
| 184 |
)
|
| 185 |
continue
|
| 186 |
|
|
@@ -192,60 +184,55 @@ def render_showcase(tasks, per_task_limit=6):
|
|
| 192 |
st.divider()
|
| 193 |
|
| 194 |
|
| 195 |
-
def validate_file_for_tab(file_path: str, tab_name: str) -> bool:
|
| 196 |
-
ext = Path(file_path).suffix.lower()
|
| 197 |
-
allowed = TAB_ALLOWED_EXTS.get(tab_name, set())
|
| 198 |
-
return ext in allowed
|
| 199 |
-
|
| 200 |
-
|
| 201 |
def process_file(file_path, tab_name, confidence_score, progress_placeholder, class_type):
|
| 202 |
-
"""
|
| 203 |
-
Enforces per-tab constraints:
|
| 204 |
-
- Object Detection: image + video
|
| 205 |
-
- Traffic Sign Detection: image + video
|
| 206 |
-
- Pose Analysis: video only
|
| 207 |
-
- Object Counting: image only
|
| 208 |
-
"""
|
| 209 |
-
if not validate_file_for_tab(file_path, tab_name):
|
| 210 |
-
allowed = ", ".join(sorted(TAB_ALLOWED_EXTS.get(tab_name, [])))
|
| 211 |
-
st.error(f"Unsupported file for **{tab_name}**. Allowed: {allowed}")
|
| 212 |
-
return None, None
|
| 213 |
-
|
| 214 |
progress_placeholder.info(f"Processing... Please wait. (Confidence Score: {confidence_score})")
|
| 215 |
-
time.sleep(
|
| 216 |
-
|
| 217 |
-
ext = Path(file_path).suffix.lower()
|
| 218 |
|
| 219 |
if tab_name == "Object Detection":
|
| 220 |
-
|
| 221 |
-
|
| 222 |
img = detectObjects(file_path, confidence_score)
|
| 223 |
return img, "image"
|
| 224 |
-
|
|
|
|
|
|
|
| 225 |
out_video_path = detectVideo(file_path, confidence_score)
|
| 226 |
return out_video_path, "video"
|
| 227 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
elif tab_name == "Object Counting":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
progress_placeholder.empty()
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
st.info(f"Count for class '{class_type}': {count}")
|
| 233 |
-
return img, "image"
|
| 234 |
|
| 235 |
elif tab_name == "Pose Analysis":
|
| 236 |
progress_placeholder.empty()
|
| 237 |
-
# video only
|
| 238 |
out_video_path = process_gif(file_path, confidence_score)
|
| 239 |
return out_video_path, "video"
|
| 240 |
|
| 241 |
elif tab_name == "Traffic Sign Detection":
|
| 242 |
-
|
| 243 |
-
|
| 244 |
img = detectTrafficObjects(file_path, confidence_score)
|
| 245 |
return img, "image"
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
|
|
|
| 249 |
|
| 250 |
st.error("Unknown tab selection.")
|
| 251 |
return None, None
|
|
@@ -257,23 +244,30 @@ def process_file(file_path, tab_name, confidence_score, progress_placeholder, cl
|
|
| 257 |
st.set_page_config(page_title="AI Video/Image Analysis Platform", layout="wide")
|
| 258 |
|
| 259 |
st.title("AI Video/Image Analysis Platform")
|
| 260 |
-
st.write("Upload
|
| 261 |
|
|
|
|
| 262 |
tabs = st.tabs(TABS)
|
| 263 |
|
| 264 |
for i, tab_name in enumerate(TABS):
|
| 265 |
with tabs[i]:
|
| 266 |
st.header(tab_name)
|
| 267 |
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
elif tab_name == "Object Counting":
|
| 272 |
-
|
|
|
|
|
|
|
| 273 |
|
| 274 |
uploaded_file = st.file_uploader(
|
| 275 |
-
|
| 276 |
-
type=
|
| 277 |
key=f"uploader_{tab_name}",
|
| 278 |
)
|
| 279 |
|
|
@@ -285,14 +279,9 @@ for i, tab_name in enumerate(TABS):
|
|
| 285 |
|
| 286 |
st.success(f"Uploaded file: {uploaded_file.name} ({file_size:.2f} MB)")
|
| 287 |
|
|
|
|
| 288 |
file_path = save_uploaded_file_to_temp(uploaded_file)
|
| 289 |
|
| 290 |
-
# Validate again (hard guard)
|
| 291 |
-
if not validate_file_for_tab(file_path, tab_name):
|
| 292 |
-
allowed = ", ".join(sorted(TAB_ALLOWED_EXTS.get(tab_name, [])))
|
| 293 |
-
st.error(f"Unsupported file for **{tab_name}**. Allowed: {allowed}")
|
| 294 |
-
continue
|
| 295 |
-
|
| 296 |
confidence_score = st.number_input(
|
| 297 |
"Adjust Confidence Score",
|
| 298 |
min_value=0.0,
|
|
@@ -312,11 +301,25 @@ for i, tab_name in enumerate(TABS):
|
|
| 312 |
key=f"class_type_{tab_name}",
|
| 313 |
)
|
| 314 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 315 |
if st.button(f"Process {tab_name}", key=f"process_{tab_name}"):
|
| 316 |
progress_placeholder = st.empty()
|
| 317 |
with st.spinner("Processing... Please wait."):
|
| 318 |
result, result_type = process_file(
|
| 319 |
-
|
| 320 |
tab_name,
|
| 321 |
confidence_score,
|
| 322 |
progress_placeholder,
|
|
@@ -327,9 +330,11 @@ for i, tab_name in enumerate(TABS):
|
|
| 327 |
st.success(f"{tab_name} completed successfully!")
|
| 328 |
|
| 329 |
playable = result
|
|
|
|
| 330 |
if not is_h264(playable):
|
| 331 |
playable = to_h264_mp4(playable)
|
| 332 |
|
|
|
|
| 333 |
st_video_file(playable, fmt="video/mp4")
|
| 334 |
|
| 335 |
if result_type == "image" and result is not None:
|
|
|
|
| 10 |
from object_detection import detectObjects, detectVideo
|
| 11 |
from object_detection_count import detectObjectsAndCount
|
| 12 |
from pose_analysis import process_gif
|
| 13 |
+
from traffic_sign_detection import detectTrafficObjects
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
# -----------------------------
|
| 16 |
# Constants
|
|
|
|
| 30 |
IMAGE_EXTS = {".jpg", ".jpeg", ".png"}
|
| 31 |
VIDEO_EXTS = {".mp4", ".mov", ".avi", ".gif"}
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
# -----------------------------
|
| 34 |
# Helpers
|
| 35 |
# -----------------------------
|
|
|
|
| 41 |
|
| 42 |
|
| 43 |
def save_uploaded_file_to_temp(uploaded_file) -> str:
|
| 44 |
+
# NOTE: left exactly as you had it (do not change)
|
|
|
|
|
|
|
|
|
|
| 45 |
safe_name = uploaded_file.name.replace("/", "_").replace("\\", "_")
|
| 46 |
+
save_path = "./"
|
|
|
|
| 47 |
|
| 48 |
+
with open(safe_name, "wb") as f:
|
|
|
|
| 49 |
f.write(uploaded_file.getbuffer())
|
| 50 |
|
| 51 |
+
print("Saved uploaded file to:", safe_name)
|
| 52 |
+
return str(safe_name)
|
| 53 |
|
| 54 |
|
| 55 |
def list_demo_files(task_name: str, limit: int = 6):
|
|
|
|
| 87 |
out = subprocess.check_output(
|
| 88 |
[
|
| 89 |
"ffprobe",
|
| 90 |
+
"-v",
|
| 91 |
+
"error",
|
| 92 |
+
"-select_streams",
|
| 93 |
+
"v:0",
|
| 94 |
+
"-show_entries",
|
| 95 |
+
"stream=codec_name",
|
| 96 |
+
"-of",
|
| 97 |
+
"default=nk=1:nw=1",
|
| 98 |
path,
|
| 99 |
],
|
| 100 |
text=True,
|
|
|
|
| 111 |
- Uses a unique temp output so we don't overwrite assets or collide across reruns.
|
| 112 |
"""
|
| 113 |
if shutil.which("ffmpeg") is None:
|
| 114 |
+
st.warning("ffmpeg not found in this environment. Video may show black screen if not H.264.")
|
| 115 |
return input_path
|
| 116 |
|
| 117 |
out_path = str(Path(tempfile.gettempdir()) / f"{uuid.uuid4().hex}.mp4")
|
| 118 |
cmd = [
|
| 119 |
"ffmpeg",
|
| 120 |
"-y",
|
| 121 |
+
"-i",
|
| 122 |
+
input_path,
|
| 123 |
+
"-c:v",
|
| 124 |
+
"libx264",
|
| 125 |
+
"-pix_fmt",
|
| 126 |
+
"yuv420p",
|
| 127 |
+
"-movflags",
|
| 128 |
+
"+faststart",
|
| 129 |
+
"-c:a",
|
| 130 |
+
"aac",
|
| 131 |
+
"-b:a",
|
| 132 |
+
"128k",
|
| 133 |
out_path,
|
| 134 |
]
|
| 135 |
try:
|
| 136 |
subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
| 137 |
+
# Sanity check: ensure output exists and is not tiny/empty
|
| 138 |
if not Path(out_path).exists() or Path(out_path).stat().st_size < 1024:
|
| 139 |
st.warning("Video conversion produced an empty file; showing original.")
|
| 140 |
return input_path
|
|
|
|
| 149 |
if ext in IMAGE_EXTS:
|
| 150 |
st.image(str(path), caption=path.name, use_container_width=True)
|
| 151 |
elif ext in VIDEO_EXTS:
|
| 152 |
+
# IMPORTANT: do NOT try to "convert-and-overwrite" showcase assets.
|
| 153 |
+
# Only convert to a temp H.264 file if needed, then pass BYTES to Streamlit.
|
| 154 |
playable = str(path)
|
| 155 |
if not is_h264(playable):
|
| 156 |
playable = to_h264_mp4(playable)
|
|
|
|
| 160 |
|
| 161 |
def render_showcase(tasks, per_task_limit=6):
|
| 162 |
st.subheader("Example outputs (what to expect)")
|
| 163 |
+
st.write(
|
| 164 |
+
"These are pre-generated results (detected/segmented/pose analyzed/traffic signs) "
|
| 165 |
+
"so you can see the expected output before uploading."
|
| 166 |
+
)
|
| 167 |
|
| 168 |
for task in tasks:
|
| 169 |
st.markdown(f"### {task}")
|
|
|
|
| 171 |
|
| 172 |
if not demo_files:
|
| 173 |
st.info(
|
| 174 |
+
f"No demo files found for **{task}**. Add images/videos under: "
|
| 175 |
+
f"{ASSETS_DIR / TASK_TO_ASSET_SUBDIR[task]}"
|
| 176 |
)
|
| 177 |
continue
|
| 178 |
|
|
|
|
| 184 |
st.divider()
|
| 185 |
|
| 186 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
def process_file(file_path, tab_name, confidence_score, progress_placeholder, class_type):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
progress_placeholder.info(f"Processing... Please wait. (Confidence Score: {confidence_score})")
|
| 189 |
+
time.sleep(1)
|
|
|
|
|
|
|
| 190 |
|
| 191 |
if tab_name == "Object Detection":
|
| 192 |
+
if file_path.lower().endswith((".jpg", ".png", ".jpeg")):
|
| 193 |
+
progress_placeholder.empty()
|
| 194 |
img = detectObjects(file_path, confidence_score)
|
| 195 |
return img, "image"
|
| 196 |
+
|
| 197 |
+
elif file_path.lower().endswith((".mp4", ".avi", ".mov", ".gif")):
|
| 198 |
+
progress_placeholder.empty()
|
| 199 |
out_video_path = detectVideo(file_path, confidence_score)
|
| 200 |
return out_video_path, "video"
|
| 201 |
|
| 202 |
+
progress_placeholder.empty()
|
| 203 |
+
st.error("Unsupported file format! Please upload an image or video.")
|
| 204 |
+
return None, None
|
| 205 |
+
|
| 206 |
elif tab_name == "Object Counting":
|
| 207 |
+
if file_path.lower().endswith((".jpg", ".png", ".jpeg")):
|
| 208 |
+
progress_placeholder.empty()
|
| 209 |
+
img, count = detectObjectsAndCount(file_path, confidence_score, class_type)
|
| 210 |
+
st.info(f"Count for class '{class_type}': {count}")
|
| 211 |
+
return img, "image"
|
| 212 |
+
|
| 213 |
+
elif file_path.lower().endswith((".mp4", ".avi", ".mov", ".gif")):
|
| 214 |
+
progress_placeholder.empty()
|
| 215 |
+
out_video_path = detectVideo(file_path, confidence_score)
|
| 216 |
+
return out_video_path, "video"
|
| 217 |
+
|
| 218 |
progress_placeholder.empty()
|
| 219 |
+
st.error("Unsupported file format! Please upload an image or video.")
|
| 220 |
+
return None, None
|
|
|
|
|
|
|
| 221 |
|
| 222 |
elif tab_name == "Pose Analysis":
|
| 223 |
progress_placeholder.empty()
|
|
|
|
| 224 |
out_video_path = process_gif(file_path, confidence_score)
|
| 225 |
return out_video_path, "video"
|
| 226 |
|
| 227 |
elif tab_name == "Traffic Sign Detection":
|
| 228 |
+
if file_path.lower().endswith((".jpg", ".png", ".jpeg")):
|
| 229 |
+
progress_placeholder.empty()
|
| 230 |
img = detectTrafficObjects(file_path, confidence_score)
|
| 231 |
return img, "image"
|
| 232 |
+
|
| 233 |
+
progress_placeholder.empty()
|
| 234 |
+
st.error("Unsupported file format! Please upload an image.")
|
| 235 |
+
return None, None
|
| 236 |
|
| 237 |
st.error("Unknown tab selection.")
|
| 238 |
return None, None
|
|
|
|
| 244 |
st.set_page_config(page_title="AI Video/Image Analysis Platform", layout="wide")
|
| 245 |
|
| 246 |
st.title("AI Video/Image Analysis Platform")
|
| 247 |
+
st.write("Upload an image or video and choose a tab for analysis.")
|
| 248 |
|
| 249 |
+
# Tabs for different functionalities
|
| 250 |
tabs = st.tabs(TABS)
|
| 251 |
|
| 252 |
for i, tab_name in enumerate(TABS):
|
| 253 |
with tabs[i]:
|
| 254 |
st.header(tab_name)
|
| 255 |
|
| 256 |
+
# -----------------------------
|
| 257 |
+
# MINIMAL CHANGE: per-tab uploader constraints
|
| 258 |
+
# -----------------------------
|
| 259 |
+
if tab_name in ["Object Detection", "Traffic Sign Detection"]:
|
| 260 |
+
allowed_types = ["jpg", "jpeg", "png", "gif", "mp4", "avi", "mov"] # image + video
|
| 261 |
+
elif tab_name == "Pose Analysis":
|
| 262 |
+
allowed_types = ["gif", "mp4", "avi", "mov"] # video only
|
| 263 |
elif tab_name == "Object Counting":
|
| 264 |
+
allowed_types = ["jpg", "jpeg", "png"] # image only
|
| 265 |
+
else:
|
| 266 |
+
allowed_types = ["jpg", "jpeg", "png", "gif", "mp4", "avi", "mov"]
|
| 267 |
|
| 268 |
uploaded_file = st.file_uploader(
|
| 269 |
+
"Upload an Image/Video",
|
| 270 |
+
type=allowed_types,
|
| 271 |
key=f"uploader_{tab_name}",
|
| 272 |
)
|
| 273 |
|
|
|
|
| 279 |
|
| 280 |
st.success(f"Uploaded file: {uploaded_file.name} ({file_size:.2f} MB)")
|
| 281 |
|
| 282 |
+
# Save to temp and use the full path for downstream processing
|
| 283 |
file_path = save_uploaded_file_to_temp(uploaded_file)
|
| 284 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
confidence_score = st.number_input(
|
| 286 |
"Adjust Confidence Score",
|
| 287 |
min_value=0.0,
|
|
|
|
| 301 |
key=f"class_type_{tab_name}",
|
| 302 |
)
|
| 303 |
|
| 304 |
+
safe_name = uploaded_file.name.replace("/", "_").replace("\\", "_")
|
| 305 |
+
print("Process file called with", safe_name)
|
| 306 |
+
|
| 307 |
+
# -----------------------------
|
| 308 |
+
# MINIMAL CHANGE: hard guard (enforce per-tab constraints)
|
| 309 |
+
# -----------------------------
|
| 310 |
+
ext = os.path.splitext(safe_name)[1].lower()
|
| 311 |
+
if tab_name == "Pose Analysis" and ext in (".jpg", ".jpeg", ".png"):
|
| 312 |
+
st.error("Pose Analysis supports video only. Please upload mp4/mov/avi/gif.")
|
| 313 |
+
continue
|
| 314 |
+
if tab_name == "Object Counting" and ext in (".mp4", ".mov", ".avi", ".gif"):
|
| 315 |
+
st.error("Object Counting supports images only. Please upload jpg/jpeg/png.")
|
| 316 |
+
continue
|
| 317 |
+
|
| 318 |
if st.button(f"Process {tab_name}", key=f"process_{tab_name}"):
|
| 319 |
progress_placeholder = st.empty()
|
| 320 |
with st.spinner("Processing... Please wait."):
|
| 321 |
result, result_type = process_file(
|
| 322 |
+
safe_name,
|
| 323 |
tab_name,
|
| 324 |
confidence_score,
|
| 325 |
progress_placeholder,
|
|
|
|
| 330 |
st.success(f"{tab_name} completed successfully!")
|
| 331 |
|
| 332 |
playable = result
|
| 333 |
+
# Convert ONLY if not already H.264 (or if ffprobe missing, this will attempt conversion)
|
| 334 |
if not is_h264(playable):
|
| 335 |
playable = to_h264_mp4(playable)
|
| 336 |
|
| 337 |
+
# Use bytes for reliability on Spaces
|
| 338 |
st_video_file(playable, fmt="video/mp4")
|
| 339 |
|
| 340 |
if result_type == "image" and result is not None:
|