duongthienz commited on
Commit
59d3520
·
verified ·
1 Parent(s): 7f16b4a

Update state.py

Browse files
Files changed (1) hide show
  1. state.py +75 -101
state.py CHANGED
@@ -55,7 +55,6 @@ def init_session_state():
55
  "speakerSegments": {}, # {filename: {speaker: [(start,end), ...]}}
56
  "speakerWaveforms": {}, # {filename: (waveform_tensor, sample_rate)}
57
  "globalRenames": [], # [{"name": str, "speakers": ["file: SPEAKER_##", ...]}]
58
- "globally_tracked_tokens": set(), # tokens ever assigned via globalRenames
59
  "analyzeAllToggle": False,
60
  }
61
  for key, value in defaults.items():
@@ -139,78 +138,34 @@ def updateCategoryOptions():
139
  # Global rename callbacks
140
  # ---------------------------------------------------------------------------
141
 
 
 
 
 
142
  def _global_rename_key(index):
143
  return f"grename_speakers_{index}"
144
 
145
 
146
- def applyGlobalRenames():
147
- """Sync speakerRenames (and inline tab widget keys) from globalRenames.
148
-
149
- Uses `globally_tracked_tokens` as a graveyard: any token ever assigned
150
- via globalRenames is recorded there. On each call:
151
- - token currently assigned → rename to the entry's name
152
- - token in graveyard but NOT currently assigned → revert to raw label
153
- - token never touched by globalRenames → leave speakerRenames untouched
154
- (inline-only rename)
155
  """
156
- # Build map of currently-assigned token -> name
157
- token_to_name: dict = {}
158
- for entry in st.session_state.globalRenames:
159
- for token in entry["speakers"]:
160
- token_to_name[token] = entry["name"]
161
-
162
- # Update the graveyard with any newly assigned tokens
163
- st.session_state.globally_tracked_tokens.update(token_to_name.keys())
164
-
165
- # 1. Update speakerRenames for tokens already in the graveyard
166
- for fname in list(st.session_state.speakerRenames.keys()):
167
- renames = st.session_state.speakerRenames[fname]
168
- for sp in list(renames.keys()):
169
- token = f"{fname}: {sp}"
170
- if token in token_to_name:
171
- # Currently assigned — apply the name
172
- renames[sp] = token_to_name[token]
173
- elif token in st.session_state.globally_tracked_tokens:
174
- # Was global but is now unassigned — revert to raw label
175
- del renames[sp]
176
- # else: purely inline rename — leave untouched
177
-
178
- # 2. Apply newly assigned tokens not yet in speakerRenames
179
- for token, name in token_to_name.items():
180
- if ": " not in token:
181
- continue
182
- fname, raw_sp = token.split(": ", 1)
183
- if fname in st.session_state.speakerRenames:
184
- st.session_state.speakerRenames[fname][raw_sp] = name
185
-
186
- # 3. Sync inline-tab widget keys so Rename Speaker tab reflects changes
187
- for fname, renames in st.session_state.speakerRenames.items():
188
- results = st.session_state.results.get(fname)
189
- if not results or len(results) != 2:
190
- continue
191
- for sp in results[0].labels():
192
- st.session_state[f"rename_{fname}_{sp}"] = renames.get(sp, "")
193
-
194
-
195
- def enforceGlobalRenameExclusivity(changed_idx):
196
- """Ensure a token assigned to entry[changed_idx] is removed from all others."""
197
- changed_tokens = set(st.session_state.globalRenames[changed_idx]["speakers"])
198
- for idx, entry in enumerate(st.session_state.globalRenames):
199
- if idx == changed_idx:
200
- continue
201
- new_speakers = [t for t in entry["speakers"] if t not in changed_tokens]
202
- if new_speakers != entry["speakers"]:
203
- entry["speakers"] = new_speakers
204
- # Clear widget key so it re-seeds from updated entry data on next render
205
- st.session_state.pop(_global_rename_key(idx), None)
206
- applyGlobalRenames()
207
 
208
 
209
  def addGlobalRename():
210
  new_name = st.session_state.globalRenameInput.strip()
211
  if not new_name:
212
  return
213
- # Don't create a duplicate entry — if the name already exists just clear the input
214
  for entry in st.session_state.globalRenames:
215
  if entry["name"] == new_name:
216
  st.toast(f"'{new_name}' already exists in the rename list")
@@ -218,69 +173,96 @@ def addGlobalRename():
218
  return
219
  st.toast(f"Adding rename '{new_name}'")
220
  st.session_state.globalRenames.append({"name": new_name, "speakers": []})
221
- # Don't pre-seed the widget key here — the render loop will seed it from
222
- # entry["speakers"] on the next pass, which avoids any widget-mutation error.
223
  st.session_state.globalRenameInput = ""
224
 
225
 
226
  def removeGlobalRename(index):
227
  entry = st.session_state.globalRenames[index]
228
  st.toast(f"Removing rename '{entry['name']}'")
229
- # Clear the widget key for the deleted entry so Streamlit doesn't try to
230
- # render it after it no longer exists in globalRenames.
 
231
  st.session_state.pop(_global_rename_key(index), None)
232
  del st.session_state.globalRenames[index]
233
- # Shift remaining widget keys down by one position
234
  for i in range(index, len(st.session_state.globalRenames)):
235
  st.session_state[_global_rename_key(i)] = list(
236
  st.session_state.globalRenames[i]["speakers"]
237
  )
238
- applyGlobalRenames()
239
 
240
 
241
  def apply_inline_rename(currFile, raw_sp, new_name):
242
- """Write a single inline rename into both speakerRenames and globalRenames.
243
 
244
- Keeps the sidebar global-rename list in sync so applyGlobalRenames()
245
- won't overwrite the change on the next rerun. Also syncs the inline-tab
246
- widget key so the Rename Speaker tab reflects the result immediately.
247
  """
248
  new_name = new_name.strip()
249
  token = f"{currFile}: {raw_sp}"
250
 
251
- # Remove this token from every existing globalRenames entry first — a token
252
- # must belong to at most one name. Clear the sidebar widget key so it
253
- # re-seeds from the updated entry data on the next render pass.
254
  for idx, entry in enumerate(st.session_state.globalRenames):
255
  if token in entry["speakers"]:
256
  entry["speakers"].remove(token)
257
  st.session_state.pop(_global_rename_key(idx), None)
258
 
259
  if new_name:
260
- # Write into speakerRenames immediately
261
- st.session_state.speakerRenames.setdefault(currFile, {})[raw_sp] = new_name
262
- # Sync the inline-tab text widget so the tab shows the confirmed name
263
- st.session_state[f"rename_{currFile}_{raw_sp}"] = new_name
264
- # Record this token as globally tracked so applyGlobalRenames can
265
- # revert it if it's ever removed from globalRenames.
266
- st.session_state.globally_tracked_tokens.add(token)
267
- # Find an existing globalRenames entry for this name, or create one
268
  for idx, entry in enumerate(st.session_state.globalRenames):
269
  if entry["name"] == new_name:
270
  entry["speakers"].append(token)
271
  st.session_state.pop(_global_rename_key(idx), None)
272
  return
273
- # No existing entry — create a new one; don't pre-set widget key,
274
- # the render loop will seed it from entry["speakers"] on the next pass.
275
  st.session_state.globalRenames.append({"name": new_name, "speakers": [token]})
276
  else:
277
- # Empty name — revert this speaker back to its raw label.
278
- st.session_state.speakerRenames.setdefault(currFile, {}).pop(raw_sp, None)
279
- # Clear the inline-tab text widget so the tab shows the raw SPEAKER_## label
280
- st.session_state[f"rename_{currFile}_{raw_sp}"] = ""
281
- # Remove from graveyard so a future inline rename on the same speaker
282
- # is treated as a fresh inline-only rename (not subject to global revert).
283
- st.session_state.globally_tracked_tokens.discard(token)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
 
285
 
286
  # ---------------------------------------------------------------------------
@@ -293,18 +275,10 @@ def updateMultiSelect():
293
  result = st.session_state.results.get(fileName)
294
  if not result:
295
  return
296
- currAnnotation, _ = result
297
- speakerNames = list(currAnnotation.labels())
298
- saved_renames = st.session_state.speakerRenames.get(fileName, {})
299
- for sp in speakerNames:
300
- saved = saved_renames.get(sp, "")
301
- st.session_state[f"rename_{fileName}_{sp}"] = saved
302
  # Pop category widget keys so they re-seed from categorySelect data
303
  for category in st.session_state.categories:
304
  st.session_state.pop(f"multiselect_{category}", None)
305
- # Pop globalRenames widget keys so they re-seed from entry["speakers"] data.
306
- # This prevents a file-switch from leaving stale selections in the sidebar
307
- # rename multiselects.
308
  for i in range(len(st.session_state.globalRenames)):
309
  st.session_state.pop(_global_rename_key(i), None)
310
 
 
55
  "speakerSegments": {}, # {filename: {speaker: [(start,end), ...]}}
56
  "speakerWaveforms": {}, # {filename: (waveform_tensor, sample_rate)}
57
  "globalRenames": [], # [{"name": str, "speakers": ["file: SPEAKER_##", ...]}]
 
58
  "analyzeAllToggle": False,
59
  }
60
  for key, value in defaults.items():
 
138
  # Global rename callbacks
139
  # ---------------------------------------------------------------------------
140
 
141
+ # ---------------------------------------------------------------------------
142
+ # Global rename helpers
143
+ # ---------------------------------------------------------------------------
144
+
145
  def _global_rename_key(index):
146
  return f"grename_speakers_{index}"
147
 
148
 
149
+ def _write_rename(token, name):
150
+ """Write name into speakerRenames for a single token (fname: SPEAKER_##).
151
+ If name is empty, clears the entry (revert to raw label).
152
+ Silently ignores tokens that don't match a known file.
 
 
 
 
 
153
  """
154
+ if ": " not in token:
155
+ return
156
+ fname, raw_sp = token.split(": ", 1)
157
+ if fname not in st.session_state.speakerRenames:
158
+ return # token references an unknown file — ignore
159
+ if name:
160
+ st.session_state.speakerRenames[fname][raw_sp] = name
161
+ else:
162
+ st.session_state.speakerRenames[fname].pop(raw_sp, None)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
 
164
 
165
  def addGlobalRename():
166
  new_name = st.session_state.globalRenameInput.strip()
167
  if not new_name:
168
  return
 
169
  for entry in st.session_state.globalRenames:
170
  if entry["name"] == new_name:
171
  st.toast(f"'{new_name}' already exists in the rename list")
 
173
  return
174
  st.toast(f"Adding rename '{new_name}'")
175
  st.session_state.globalRenames.append({"name": new_name, "speakers": []})
 
 
176
  st.session_state.globalRenameInput = ""
177
 
178
 
179
  def removeGlobalRename(index):
180
  entry = st.session_state.globalRenames[index]
181
  st.toast(f"Removing rename '{entry['name']}'")
182
+ # Revert every speaker that belonged to this entry
183
+ for token in entry["speakers"]:
184
+ _write_rename(token, "")
185
  st.session_state.pop(_global_rename_key(index), None)
186
  del st.session_state.globalRenames[index]
187
+ # Shift remaining widget keys down
188
  for i in range(index, len(st.session_state.globalRenames)):
189
  st.session_state[_global_rename_key(i)] = list(
190
  st.session_state.globalRenames[i]["speakers"]
191
  )
 
192
 
193
 
194
  def apply_inline_rename(currFile, raw_sp, new_name):
195
+ """Write a rename from the Rename Speaker tab into speakerRenames and globalRenames.
196
 
197
+ - Removes the token from any existing globalRenames entry first (exclusivity).
198
+ - If new_name is non-empty: writes the rename and upserts a globalRenames entry.
199
+ - If new_name is empty: clears the rename (revert to SPEAKER_##).
200
  """
201
  new_name = new_name.strip()
202
  token = f"{currFile}: {raw_sp}"
203
 
204
+ # Remove from any existing globalRenames entry
 
 
205
  for idx, entry in enumerate(st.session_state.globalRenames):
206
  if token in entry["speakers"]:
207
  entry["speakers"].remove(token)
208
  st.session_state.pop(_global_rename_key(idx), None)
209
 
210
  if new_name:
211
+ _write_rename(token, new_name)
212
+ # Upsert a globalRenames entry for this name
 
 
 
 
 
 
213
  for idx, entry in enumerate(st.session_state.globalRenames):
214
  if entry["name"] == new_name:
215
  entry["speakers"].append(token)
216
  st.session_state.pop(_global_rename_key(idx), None)
217
  return
 
 
218
  st.session_state.globalRenames.append({"name": new_name, "speakers": [token]})
219
  else:
220
+ _write_rename(token, "")
221
+ st.toast(f"Reverted {raw_sp} to original label")
222
+
223
+
224
+ def on_grename_change(idx):
225
+ """Callback for the sidebar rename multiselect at position idx.
226
+
227
+ Diffs old vs new speakers list:
228
+ - Newly added tokens → enforce exclusivity across other entries (evict + revert
229
+ their old name), then write this entry's name into speakerRenames.
230
+ - Removed tokens → clear speakerRenames for those tokens (revert to SPEAKER_##).
231
+ """
232
+ grkey = _global_rename_key(idx)
233
+ entry = st.session_state.globalRenames[idx]
234
+ name = entry["name"]
235
+
236
+ prev = set(entry["speakers"])
237
+ new = set(st.session_state.get(grkey, []))
238
+
239
+ added = new - prev
240
+ removed = prev - new
241
+
242
+ # Write the new speakers list to the data model (keep stable order: prev kept +
243
+ # new additions appended, so the multiselect doesn't jump around)
244
+ kept = [t for t in entry["speakers"] if t in new]
245
+ entry["speakers"] = kept + [t for t in added]
246
+ # Pop widget key so it re-seeds cleanly from entry["speakers"] on next render
247
+ st.session_state.pop(grkey, None)
248
+
249
+ # Enforce exclusivity and apply renames for newly added tokens
250
+ for token in added:
251
+ # Evict token from every other entry and revert its old name
252
+ for other_idx, other_entry in enumerate(st.session_state.globalRenames):
253
+ if other_idx == idx:
254
+ continue
255
+ if token in other_entry["speakers"]:
256
+ other_entry["speakers"].remove(token)
257
+ st.session_state.pop(_global_rename_key(other_idx), None)
258
+ # Revert the old name before we overwrite with the new one
259
+ _write_rename(token, "")
260
+ # Now write the new name
261
+ _write_rename(token, name)
262
+
263
+ # Revert speakerRenames for tokens that were removed from this entry
264
+ for token in removed:
265
+ _write_rename(token, "")
266
 
267
 
268
  # ---------------------------------------------------------------------------
 
275
  result = st.session_state.results.get(fileName)
276
  if not result:
277
  return
 
 
 
 
 
 
278
  # Pop category widget keys so they re-seed from categorySelect data
279
  for category in st.session_state.categories:
280
  st.session_state.pop(f"multiselect_{category}", None)
281
+ # Pop globalRenames widget keys so they re-seed from entry["speakers"] data
 
 
282
  for i in range(len(st.session_state.globalRenames)):
283
  st.session_state.pop(_global_rename_key(i), None)
284