Update app.py
Browse files
app.py
CHANGED
|
@@ -255,18 +255,64 @@ def removeCategory(index):
|
|
| 255 |
del st.session_state.categories[index]
|
| 256 |
for fname in st.session_state.categorySelect:
|
| 257 |
del st.session_state.categorySelect[fname][index]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 258 |
|
| 259 |
def updateCategoryOptions(fileName):
|
| 260 |
if st.session_state.resetResult:
|
| 261 |
return
|
| 262 |
currAnnotation, _ = st.session_state.results[fileName]
|
| 263 |
speakerNames = list(currAnnotation.labels())
|
| 264 |
-
# Build reverse map from
|
|
|
|
| 265 |
display_to_raw = {}
|
| 266 |
for sp in speakerNames:
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
display_to_raw[live if live else sp] = sp
|
| 270 |
unusedSpeakers = copy.deepcopy(speakerNames)
|
| 271 |
for i, category in enumerate(st.session_state['categories']):
|
| 272 |
display_choices = list(st.session_state[f'multiselect_{category}'])
|
|
@@ -511,6 +557,8 @@ if 'speakerSegments' not in st.session_state:
|
|
| 511 |
st.session_state.speakerSegments = {} # {filename: {speaker: [(start,end), ...]}}
|
| 512 |
if 'speakerWaveforms' not in st.session_state:
|
| 513 |
st.session_state.speakerWaveforms = {} # {filename: (waveform_tensor, sample_rate)}
|
|
|
|
|
|
|
| 514 |
if 'analyzeAllToggle' not in st.session_state:
|
| 515 |
st.session_state.analyzeAllToggle = False
|
| 516 |
|
|
@@ -753,12 +801,9 @@ try:
|
|
| 753 |
|
| 754 |
unusedSpeakers = st.session_state.unusedSpeakers[currFile]
|
| 755 |
categorySelections = st.session_state["categorySelect"][currFile]
|
| 756 |
-
# Build
|
| 757 |
-
|
| 758 |
-
for sp in speakerNames
|
| 759 |
-
wk = f"rename_{currFile}_{sp}"
|
| 760 |
-
live = st.session_state.get(wk, "").strip()
|
| 761 |
-
raw_to_display[sp] = live if live else sp
|
| 762 |
all_speakers_display = [raw_to_display[sp] for sp in speakerNames]
|
| 763 |
for i,category in enumerate(st.session_state.categories):
|
| 764 |
ms_key = f"multiselect_{category}"
|
|
@@ -780,50 +825,79 @@ try:
|
|
| 780 |
|
| 781 |
st.sidebar.divider()
|
| 782 |
st.sidebar.subheader("Rename Speakers")
|
| 783 |
-
st.sidebar.caption(
|
|
|
|
|
|
|
|
|
|
| 784 |
|
| 785 |
-
# --- Speaker clip preview ---
|
| 786 |
file_clips = st.session_state.speakerClips.get(currFile, {})
|
| 787 |
if file_clips:
|
| 788 |
-
st.sidebar.caption(
|
| 789 |
-
|
| 790 |
-
|
| 791 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 792 |
|
| 793 |
-
|
| 794 |
-
|
| 795 |
-
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
|
| 799 |
-
|
| 800 |
-
|
| 801 |
-
|
| 802 |
-
|
| 803 |
-
if
|
| 804 |
-
st.
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
| 811 |
-
|
| 812 |
-
|
| 813 |
-
):
|
| 814 |
-
randomize_speaker_clip(currFile, sp)
|
| 815 |
-
st.rerun()
|
| 816 |
-
# Label is always the fixed original sp so Streamlit never recreates the widget
|
| 817 |
-
new_name = st.sidebar.text_input(
|
| 818 |
-
sp,
|
| 819 |
-
placeholder="e.g. John",
|
| 820 |
-
key=widget_key,
|
| 821 |
-
label_visibility="collapsed"
|
| 822 |
)
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
|
| 826 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 827 |
|
| 828 |
catTypeColors = su.colorsCSS(3)
|
| 829 |
allColors = su.colorsCSS(len(speakerNames)+len(st.session_state.categories))
|
|
|
|
| 255 |
del st.session_state.categories[index]
|
| 256 |
for fname in st.session_state.categorySelect:
|
| 257 |
del st.session_state.categorySelect[fname][index]
|
| 258 |
+
|
| 259 |
+
def _global_rename_key(index):
|
| 260 |
+
return f"grename_speakers_{index}"
|
| 261 |
+
|
| 262 |
+
def applyGlobalRenames():
|
| 263 |
+
"""Write all globalRenames entries into speakerRenames and refresh widget keys."""
|
| 264 |
+
# Clear all existing renames first, then re-apply so removals take effect
|
| 265 |
+
for fname in st.session_state.speakerRenames:
|
| 266 |
+
st.session_state.speakerRenames[fname] = {}
|
| 267 |
+
for entry in st.session_state.globalRenames:
|
| 268 |
+
display_name = entry["name"]
|
| 269 |
+
for token in entry["speakers"]:
|
| 270 |
+
# token format: "filename: SPEAKER_##"
|
| 271 |
+
if ": " not in token:
|
| 272 |
+
continue
|
| 273 |
+
fname, raw_sp = token.split(": ", 1)
|
| 274 |
+
if fname in st.session_state.speakerRenames:
|
| 275 |
+
st.session_state.speakerRenames[fname][raw_sp] = display_name
|
| 276 |
+
# Refresh rename widget keys for the currently viewed file
|
| 277 |
+
curr = st.session_state.get("select_currFile")
|
| 278 |
+
if curr and curr in st.session_state.speakerRenames:
|
| 279 |
+
saved = st.session_state.speakerRenames[curr]
|
| 280 |
+
results = st.session_state.results.get(curr)
|
| 281 |
+
if results:
|
| 282 |
+
for sp in results[0].labels():
|
| 283 |
+
wk = f"rename_{curr}_{sp}"
|
| 284 |
+
st.session_state[wk] = saved.get(sp, "")
|
| 285 |
+
|
| 286 |
+
def addGlobalRename():
|
| 287 |
+
new_name = st.session_state.globalRenameInput.strip()
|
| 288 |
+
if not new_name:
|
| 289 |
+
return
|
| 290 |
+
st.toast(f"Adding rename '{new_name}'")
|
| 291 |
+
st.session_state.globalRenames.append({"name": new_name, "speakers": []})
|
| 292 |
+
st.session_state[_global_rename_key(len(st.session_state.globalRenames) - 1)] = []
|
| 293 |
+
st.session_state.globalRenameInput = ""
|
| 294 |
+
|
| 295 |
+
def removeGlobalRename(index):
|
| 296 |
+
entry = st.session_state.globalRenames[index]
|
| 297 |
+
st.toast(f"Removing rename '{entry['name']}'")
|
| 298 |
+
del st.session_state.globalRenames[index]
|
| 299 |
+
# Rebuild widget keys for remaining entries to stay in sync
|
| 300 |
+
for i in range(index, len(st.session_state.globalRenames)):
|
| 301 |
+
next_key = _global_rename_key(i)
|
| 302 |
+
st.session_state[next_key] = [s for s in st.session_state.globalRenames[i]["speakers"]]
|
| 303 |
+
applyGlobalRenames()
|
| 304 |
|
| 305 |
def updateCategoryOptions(fileName):
|
| 306 |
if st.session_state.resetResult:
|
| 307 |
return
|
| 308 |
currAnnotation, _ = st.session_state.results[fileName]
|
| 309 |
speakerNames = list(currAnnotation.labels())
|
| 310 |
+
# Build reverse map from speakerRenames (source of truth): display name -> SPEAKER_##
|
| 311 |
+
saved_renames = st.session_state.speakerRenames.get(fileName, {})
|
| 312 |
display_to_raw = {}
|
| 313 |
for sp in speakerNames:
|
| 314 |
+
display = saved_renames.get(sp, sp)
|
| 315 |
+
display_to_raw[display] = sp
|
|
|
|
| 316 |
unusedSpeakers = copy.deepcopy(speakerNames)
|
| 317 |
for i, category in enumerate(st.session_state['categories']):
|
| 318 |
display_choices = list(st.session_state[f'multiselect_{category}'])
|
|
|
|
| 557 |
st.session_state.speakerSegments = {} # {filename: {speaker: [(start,end), ...]}}
|
| 558 |
if 'speakerWaveforms' not in st.session_state:
|
| 559 |
st.session_state.speakerWaveforms = {} # {filename: (waveform_tensor, sample_rate)}
|
| 560 |
+
if 'globalRenames' not in st.session_state:
|
| 561 |
+
st.session_state.globalRenames = [] # [{"name": str, "speakers": ["file:SPEAKER_##", ...]}]
|
| 562 |
if 'analyzeAllToggle' not in st.session_state:
|
| 563 |
st.session_state.analyzeAllToggle = False
|
| 564 |
|
|
|
|
| 801 |
|
| 802 |
unusedSpeakers = st.session_state.unusedSpeakers[currFile]
|
| 803 |
categorySelections = st.session_state["categorySelect"][currFile]
|
| 804 |
+
# Build raw->display map from speakerRenames (source of truth, written by applyGlobalRenames)
|
| 805 |
+
_saved_renames = st.session_state.speakerRenames.get(currFile, {})
|
| 806 |
+
raw_to_display = {sp: (_saved_renames.get(sp, sp)) for sp in speakerNames}
|
|
|
|
|
|
|
|
|
|
| 807 |
all_speakers_display = [raw_to_display[sp] for sp in speakerNames]
|
| 808 |
for i,category in enumerate(st.session_state.categories):
|
| 809 |
ms_key = f"multiselect_{category}"
|
|
|
|
| 825 |
|
| 826 |
st.sidebar.divider()
|
| 827 |
st.sidebar.subheader("Rename Speakers")
|
| 828 |
+
st.sidebar.caption(
|
| 829 |
+
"Assign a name and select which speaker labels (across all files) it applies to. "
|
| 830 |
+
"Changes apply to all matched speakers instantly."
|
| 831 |
+
)
|
| 832 |
|
| 833 |
+
# --- Speaker clip preview (identification aid) ---
|
| 834 |
file_clips = st.session_state.speakerClips.get(currFile, {})
|
| 835 |
if file_clips:
|
| 836 |
+
st.sidebar.caption("🎧 Listen to clips to help identify speakers:")
|
| 837 |
+
current_renames = st.session_state.speakerRenames[currFile]
|
| 838 |
+
for sp in speakerNames:
|
| 839 |
+
widget_key = f"rename_{currFile}_{sp}"
|
| 840 |
+
if widget_key not in st.session_state:
|
| 841 |
+
st.session_state[widget_key] = current_renames.get(sp, "")
|
| 842 |
+
live_name = st.session_state[widget_key].strip()
|
| 843 |
+
display_label = live_name if live_name else sp
|
| 844 |
+
st.sidebar.markdown(f"**{display_label}**")
|
| 845 |
+
if sp in file_clips:
|
| 846 |
+
st.sidebar.audio(file_clips[sp], format="audio/wav")
|
| 847 |
+
sp_segs = st.session_state.speakerSegments.get(currFile, {}).get(sp, [])
|
| 848 |
+
has_waveform = currFile in st.session_state.speakerWaveforms
|
| 849 |
+
if has_waveform and len(sp_segs) >= 1:
|
| 850 |
+
if st.sidebar.button(
|
| 851 |
+
"🔀 Try Another Clip",
|
| 852 |
+
key=f"randomize_{currFile}_{sp}",
|
| 853 |
+
help="Pick a random clip from a different part of this speaker's audio",
|
| 854 |
+
):
|
| 855 |
+
randomize_speaker_clip(currFile, sp)
|
| 856 |
+
st.rerun()
|
| 857 |
+
|
| 858 |
+
# Build the full list of "filename: SPEAKER_##" tokens across all analyzed files
|
| 859 |
+
all_speaker_tokens = []
|
| 860 |
+
for fn in st.session_state.file_names:
|
| 861 |
+
if fn in st.session_state.results and len(st.session_state.results[fn]) == 2:
|
| 862 |
+
ann, _ = st.session_state.results[fn]
|
| 863 |
+
for sp in ann.labels():
|
| 864 |
+
all_speaker_tokens.append(f"{fn}: {sp}")
|
| 865 |
|
| 866 |
+
st.sidebar.divider()
|
| 867 |
+
|
| 868 |
+
# --- Render existing global rename entries ---
|
| 869 |
+
def _on_grename_change(idx):
|
| 870 |
+
key = _global_rename_key(idx)
|
| 871 |
+
st.session_state.globalRenames[idx]["speakers"] = list(st.session_state[key])
|
| 872 |
+
applyGlobalRenames()
|
| 873 |
+
|
| 874 |
+
for idx, entry in enumerate(st.session_state.globalRenames):
|
| 875 |
+
grkey = _global_rename_key(idx)
|
| 876 |
+
if grkey not in st.session_state:
|
| 877 |
+
st.session_state[grkey] = list(entry["speakers"])
|
| 878 |
+
st.sidebar.markdown(f"**{entry['name']}**")
|
| 879 |
+
st.sidebar.multiselect(
|
| 880 |
+
f"Speakers for {entry['name']}",
|
| 881 |
+
options=all_speaker_tokens,
|
| 882 |
+
key=grkey,
|
| 883 |
+
on_change=_on_grename_change,
|
| 884 |
+
args=(idx,),
|
| 885 |
+
label_visibility="collapsed",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 886 |
)
|
| 887 |
+
st.sidebar.button(
|
| 888 |
+
f"Remove '{entry['name']}'",
|
| 889 |
+
key=f"remove_grename_{idx}",
|
| 890 |
+
on_click=removeGlobalRename,
|
| 891 |
+
args=(idx,),
|
| 892 |
+
)
|
| 893 |
+
|
| 894 |
+
# --- Add new global rename ---
|
| 895 |
+
st.sidebar.text_input(
|
| 896 |
+
"Add rename",
|
| 897 |
+
placeholder="e.g. John",
|
| 898 |
+
key="globalRenameInput",
|
| 899 |
+
on_change=addGlobalRename,
|
| 900 |
+
)
|
| 901 |
|
| 902 |
catTypeColors = su.colorsCSS(3)
|
| 903 |
allColors = su.colorsCSS(len(speakerNames)+len(st.session_state.categories))
|