duongthienz commited on
Commit
06ce411
·
verified ·
1 Parent(s): 3f04e6e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +123 -49
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 live rename widget keys: display name -> SPEAKER_##
 
265
  display_to_raw = {}
266
  for sp in speakerNames:
267
- wk = f"rename_{fileName}_{sp}"
268
- live = st.session_state.get(wk, "").strip()
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 live raw->display map from rename widget keys (always current)
757
- raw_to_display = {}
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("Replace SPEAKER_## labels with real names.")
 
 
 
784
 
785
- # --- Speaker clip preview ---
786
  file_clips = st.session_state.speakerClips.get(currFile, {})
787
  if file_clips:
788
- st.sidebar.caption(
789
- "Listen to a short clip (3–5 s) to help identify each speaker. "
790
- "If a clip sounds silent or unclear, press 🔀 to try a different one."
791
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
792
 
793
- current_renames = st.session_state.speakerRenames[currFile]
794
- for sp in speakerNames:
795
- widget_key = f"rename_{currFile}_{sp}"
796
- # Seed the widget state on first render only; updateMultiSelect handles
797
- # restoring it unconditionally whenever the user switches files.
798
- if widget_key not in st.session_state:
799
- st.session_state[widget_key] = current_renames.get(sp, "")
800
- live_name = st.session_state[widget_key].strip()
801
- display_label = live_name if live_name else sp
802
- st.sidebar.markdown(f"**{display_label}**")
803
- if sp in file_clips:
804
- st.sidebar.audio(file_clips[sp], format="audio/wav")
805
- # Only show randomize button if there are multiple segments to draw from
806
- sp_segs = st.session_state.speakerSegments.get(currFile, {}).get(sp, [])
807
- has_waveform = currFile in st.session_state.speakerWaveforms
808
- if has_waveform and len(sp_segs) >= 1:
809
- if st.sidebar.button(
810
- "🔀 Try Another Clip",
811
- key=f"randomize_{currFile}_{sp}",
812
- help="Pick a random clip from a different part of this speaker's audio",
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
- if new_name.strip():
824
- st.session_state.speakerRenames[currFile][sp] = new_name.strip()
825
- elif sp in st.session_state.speakerRenames[currFile]:
826
- del st.session_state.speakerRenames[currFile][sp]
 
 
 
 
 
 
 
 
 
 
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))