Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
File size: 12,550 Bytes
c07f4bc 5da9657 c07f4bc ec2153e c07f4bc bfa5937 c07f4bc bfa5937 c07f4bc bfa5937 c9a70a9 5da9657 bfa5937 5da9657 2902b0f ad7eece 5da9657 2902b0f 5da9657 bfa5937 5da9657 c9a70a9 2902b0f 5da9657 5b6254c c18c8f8 5b6254c c9a70a9 c18c8f8 c9a70a9 c18c8f8 c9a70a9 5da9657 ad7eece c18c8f8 ad7eece 5da9657 2902b0f 5da9657 bfa5937 2902b0f bfa5937 c07f4bc bfa5937 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 | """
ui.py β Reusable Streamlit UI rendering helpers.
Keeps all layout/widget code that isn't tied to a single page out of app.py,
while keeping utils.py pure-Python and state.py focused on callbacks.
"""
import os
import streamlit as st
import plotly.express as px
import utils
from state import (
updateCategoryOptions, removeCategory, addCategory,
applyGlobalRenames, addGlobalRename, removeGlobalRename,
_global_rename_key, randomize_speaker_clip, apply_inline_rename,
)
# ---------------------------------------------------------------------------
# Chart rendering
# ---------------------------------------------------------------------------
def render_chart(fig, tab, pdf_path, svg_path, pdf_name, svg_name, pdf_key, svg_key,
plotly_config=None):
"""Render a Plotly figure inside a tab with PDF/SVG download buttons."""
cfg = plotly_config or {"displayModeBar": True, "modeBarButtonsToRemove": []}
with tab:
st.plotly_chart(fig, use_container_width=True, config=cfg)
col_l, col_r = st.columns(2)
try:
fig.write_image(pdf_path)
fig.write_image(svg_path)
except Exception:
pass
with col_l:
if os.path.exists(pdf_path):
with open(pdf_path, "rb") as f:
st.download_button("Save As PDF", f, pdf_name, "application/pdf",
key=pdf_key, on_click="ignore")
with col_r:
if os.path.exists(svg_path):
with open(svg_path, "rb") as f:
st.download_button("Save As SVG", f, svg_name, "image/svg+xml",
key=svg_key, on_click="ignore")
# ---------------------------------------------------------------------------
# Sidebar β categories section
# ---------------------------------------------------------------------------
def render_categories_sidebar(currFile, categorySelections, all_speakers_display,
raw_to_display):
"""Render category multiselects, remove buttons, and Add category input."""
for i, category in enumerate(st.session_state.categories):
ms_key = f"multiselect_{category}"
speakerSet = categorySelections[i]
default_disp = [raw_to_display.get(sp, sp) for sp in speakerSet]
if ms_key not in st.session_state:
st.session_state[ms_key] = default_disp
st.sidebar.multiselect(
category, all_speakers_display,
key=ms_key, on_change=updateCategoryOptions, args=(currFile,),
)
st.sidebar.button(
f"Remove {category}", key=f"remove_{category}",
on_click=removeCategory, args=(i,),
)
st.sidebar.text_input("Add Category", key="categoryInput", on_change=addCategory)
# ---------------------------------------------------------------------------
# Sidebar β rename speakers section
# ---------------------------------------------------------------------------
def render_rename_sidebar(currFile, speakerNames, all_speaker_tokens):
"""Render the full Rename Speakers sidebar section."""
st.sidebar.divider()
st.sidebar.subheader("Rename Speakers")
st.sidebar.markdown(
"<p style='font-size:0.85rem; color:gray; margin-bottom:2px;'>"
"Assign a name and select which speaker labels (across all files) it applies to. "
"Changes apply to all matched speakers instantly.</p>",
unsafe_allow_html=True,
)
st.sidebar.divider()
def _on_grename_change(idx):
st.session_state.globalRenames[idx]["speakers"] = list(
st.session_state[_global_rename_key(idx)]
)
applyGlobalRenames()
for idx, entry in enumerate(st.session_state.globalRenames):
grkey = _global_rename_key(idx)
if grkey not in st.session_state:
st.session_state[grkey] = list(entry["speakers"])
st.sidebar.markdown(f"**{entry['name']}**")
st.sidebar.multiselect(
f"Speakers for {entry['name']}", options=all_speaker_tokens,
key=grkey, on_change=_on_grename_change, args=(idx,),
label_visibility="collapsed",
)
st.sidebar.button(
f"Remove '{entry['name']}'", key=f"remove_grename_{idx}",
on_click=removeGlobalRename, args=(idx,),
)
st.sidebar.text_input(
"Add rename", placeholder="e.g. John",
key="globalRenameInput", on_change=addGlobalRename,
)
# ---------------------------------------------------------------------------
# Data tab β custom scrollable table
# ---------------------------------------------------------------------------
def render_data_table(tableDF, speakerNames, raw_to_display, currFile):
"""Render the custom column-based scrollable table (Speaker, Start, Finish)."""
other_cols = [c for c in tableDF.columns if c != "Speaker"]
col_widths = [2] + [1] * len(other_cols)
# Header
header_cols = st.columns(col_widths)
header_cols[0].markdown("**Speaker**")
for i, col_name in enumerate(other_cols):
header_cols[i + 1].markdown(f"**{col_name}**")
st.markdown("<hr style='margin-top:2px; margin-bottom:4px;'>", unsafe_allow_html=True)
with st.container(height=480, border=False):
for _, row in tableDF.iterrows():
row_cols = st.columns(col_widths)
display_sp = row["Speaker"]
row_cols[0].write(display_sp)
for i, col_name in enumerate(other_cols):
row_cols[i + 1].write(row[col_name])
# ---------------------------------------------------------------------------
# Rename Speaker tab β speaker / audio sample table
# ---------------------------------------------------------------------------
def render_speaker_samples_tab(speakerNames, raw_to_display, currFile):
"""Render a table: Speaker (with inline β rename + history dropdown) | Audio Sample | βΊ button."""
file_samples = st.session_state.speakerClips.get(currFile, {})
has_waveform = currFile in st.session_state.speakerWaveforms
has_samples = bool(file_samples)
if "inline_rename_active" not in st.session_state:
st.session_state.inline_rename_active = {}
if "inline_rename_history" not in st.session_state:
st.session_state.inline_rename_history = []
# Header
header_cols = st.columns([3, 3, 1])
header_cols[0].markdown("**Speaker**")
header_cols[1].markdown("**Audio Sample**")
header_cols[2].markdown("** **", unsafe_allow_html=True)
st.markdown("<hr style='margin-top:2px; margin-bottom:4px;'>", unsafe_allow_html=True)
if not has_samples:
st.info("Audio samples are only available for files analyzed from audio (not RTTM/CSV/TXT).")
for sp in speakerNames:
display_name = raw_to_display.get(sp, sp)
edit_key = (currFile, sp)
is_editing = st.session_state.inline_rename_active.get(edit_key, False)
row_cols = st.columns([3, 3, 1])
# --- Speaker cell ---
if is_editing:
select_key = f"inline_rename_select_{currFile}_{sp}"
input_key = f"inline_rename_input_{currFile}_{sp}"
confirm_key = f"inline_rename_confirm_{currFile}_{sp}"
cancel_key = f"inline_rename_cancel_{currFile}_{sp}"
history = st.session_state.inline_rename_history
current_val = display_name if display_name != sp else ""
with row_cols[0]:
if history:
# Selectbox with index=None shows placeholder and lets user
# type to filter β single combined widget, no separate input.
options = history
default_idx = history.index(current_val) if current_val in history else None
chosen = st.selectbox(
"Rename",
options=options,
index=default_idx,
key=select_key,
label_visibility="collapsed",
placeholder="Type or select a nameβ¦",
)
new_name = chosen or ""
else:
# No history yet β plain text input
new_name = st.text_input(
"Rename",
value=current_val,
key=input_key,
label_visibility="collapsed",
placeholder=f"Rename {sp}β¦",
)
btn_col1, btn_col2 = st.columns(2)
if btn_col1.button("β", key=confirm_key, help="Confirm rename"):
confirmed = new_name.strip()
if confirmed:
apply_inline_rename(currFile, sp, confirmed)
if confirmed not in st.session_state.inline_rename_history:
st.session_state.inline_rename_history.append(confirmed)
st.session_state.inline_rename_active[edit_key] = False
st.rerun()
if btn_col2.button("β", key=cancel_key, help="Cancel"):
st.session_state.inline_rename_active[edit_key] = False
st.rerun()
else:
with row_cols[0]:
name_col, pencil_col = st.columns([4, 1])
name_col.write(display_name)
if pencil_col.button(
"β", key=f"inline_rename_edit_{currFile}_{sp}",
help=f"Rename {sp}",
):
st.session_state.inline_rename_active[edit_key] = True
st.rerun()
# --- Audio sample cell ---
if sp in file_samples:
row_cols[1].audio(file_samples[sp], format="audio/wav")
sp_segs = st.session_state.speakerSegments.get(currFile, {}).get(sp, [])
if has_waveform and sp_segs:
if row_cols[2].button(
"βΊ", key=f"sample_randomize_{currFile}_{sp}",
help="Try a different audio sample for this speaker",
):
randomize_speaker_clip(currFile, sp)
st.rerun()
else:
row_cols[1].write("β")
# ---------------------------------------------------------------------------
# Multi-file summary expander
# ---------------------------------------------------------------------------
def render_multifile_summary(plotly_config=None):
"""Render the Multi-file Summary Data expander if enough files are analyzed."""
cfg = plotly_config or {"displayModeBar": True, "modeBarButtonsToRemove": []}
if not st.session_state.results:
return
with st.expander("Multi-file Summary Data"):
st.header("Multi-file Summary Data")
with st.spinner("Processing summary results..."):
validNames = [
fn for fn in st.session_state.file_names
if fn in st.session_state.results
and len(st.session_state.results[fn]) == 2
]
if len(validNames) <= 1:
return
df6, allCategories = utils.build_multifile_category_df(
validNames, st.session_state.results, st.session_state.summaries,
st.session_state.categories, st.session_state.categorySelect,
)
st.plotly_chart(
px.bar(df6, x="files", y=allCategories,
title="Time Spoken by Each Speaker in Each File"),
use_container_width=True, config=cfg,
)
df7, _ = utils.build_multifile_voice_df(validNames, st.session_state.summaries)
for sort_cols, ascending, title in [
(["One Voice", "Multi Voice"], True, "Cross-file Voice Categories sorted for One Voice"),
(["Multi Voice","One Voice"], True, "Cross-file Voice Categories sorted for Multi Voice"),
(["No Voice", "Multi Voice"], False, "Cross-file Voice Categories sorted for Any Voice"),
]:
st.plotly_chart(
px.bar(df7.sort_values(by=sort_cols, ascending=ascending),
x="files", y=["One Voice", "Multi Voice", "No Voice"],
title=title),
use_container_width=True, config=cfg,
) |