Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
Update app.py
Browse files
app.py
CHANGED
|
@@ -39,19 +39,17 @@ def printV(message,verbosityLevel):
|
|
| 39 |
if verbosity>=verbosityLevel:
|
| 40 |
print(message)
|
| 41 |
|
| 42 |
-
def get_display_name(speaker,
|
| 43 |
"""Return the user-assigned display name for a speaker, or the original label."""
|
| 44 |
renames = st.session_state.speakerRenames
|
| 45 |
-
|
| 46 |
-
return renames[fileIndex][speaker]
|
| 47 |
-
return speaker
|
| 48 |
|
| 49 |
-
def apply_speaker_renames_to_df(df,
|
| 50 |
"""Replace speaker_## labels in a DataFrame column with display names."""
|
| 51 |
if column not in df.columns:
|
| 52 |
return df
|
| 53 |
df = df.copy()
|
| 54 |
-
df[column] = df[column].apply(lambda s: get_display_name(s,
|
| 55 |
return df
|
| 56 |
|
| 57 |
@st.cache_data
|
|
@@ -127,10 +125,7 @@ def generate_speaker_clips(annotations, waveform, sample_rate, file_index):
|
|
| 127 |
"""
|
| 128 |
import io
|
| 129 |
|
| 130 |
-
#
|
| 131 |
-
while len(st.session_state.speakerClips) <= file_index:
|
| 132 |
-
st.session_state.speakerClips.append({})
|
| 133 |
-
|
| 134 |
clips = {}
|
| 135 |
for speaker in annotations.labels():
|
| 136 |
speaker_segments = [
|
|
@@ -159,7 +154,7 @@ def generate_speaker_clips(annotations, waveform, sample_rate, file_index):
|
|
| 159 |
clips[speaker] = buffer.read()
|
| 160 |
|
| 161 |
st.session_state.speakerClips[file_index] = clips
|
| 162 |
-
print(f"Generated {len(clips)} speaker clips for
|
| 163 |
|
| 164 |
def addCategory():
|
| 165 |
newCategory = st.session_state.categoryInput
|
|
@@ -167,8 +162,8 @@ def addCategory():
|
|
| 167 |
st.session_state[f'multiselect_{newCategory}'] = []
|
| 168 |
st.session_state.categories.append(newCategory)
|
| 169 |
st.session_state.categoryInput = ''
|
| 170 |
-
for
|
| 171 |
-
|
| 172 |
|
| 173 |
def removeCategory(index):
|
| 174 |
categoryName = st.session_state.categories[index]
|
|
@@ -176,55 +171,43 @@ def removeCategory(index):
|
|
| 176 |
del st.session_state[f'multiselect_{categoryName}']
|
| 177 |
del st.session_state[f'remove_{categoryName}']
|
| 178 |
del st.session_state.categories[index]
|
| 179 |
-
for
|
| 180 |
-
del
|
| 181 |
|
| 182 |
-
def updateCategoryOptions(
|
| 183 |
if st.session_state.resetResult:
|
| 184 |
-
#st.info(f"Skipping update of {resultIndex}")
|
| 185 |
return
|
| 186 |
-
|
| 187 |
-
#st.info(f"In update: {st.session_state.categorySelect}")
|
| 188 |
-
# Handle
|
| 189 |
-
_, currAnnotation, _ = st.session_state.results[currFileIndex]
|
| 190 |
speakerNames = currAnnotation.labels()
|
| 191 |
-
|
| 192 |
-
# Handle speaker category sidebars
|
| 193 |
unusedSpeakers = copy.deepcopy(speakerNames)
|
| 194 |
-
# Remove used speakers
|
| 195 |
for i, category in enumerate(st.session_state['categories']):
|
| 196 |
category_choices = copy.deepcopy(st.session_state[f'multiselect_{category}'])
|
| 197 |
-
st.session_state["categorySelect"][
|
| 198 |
for sp in category_choices:
|
| 199 |
try:
|
| 200 |
unusedSpeakers.remove(sp)
|
| 201 |
except:
|
| 202 |
continue
|
| 203 |
-
st.session_state.unusedSpeakers[
|
| 204 |
-
#st.info(f"After update: {st.session_state.categorySelect}")
|
| 205 |
|
| 206 |
def updateMultiSelect():
|
| 207 |
-
|
| 208 |
st.session_state.resetResult = True
|
| 209 |
for i, category in enumerate(st.session_state['categories']):
|
| 210 |
-
st.session_state[f'multiselect_{category}'] = st.session_state['categorySelect'][
|
| 211 |
|
| 212 |
def analyze(inFileName):
|
| 213 |
try:
|
| 214 |
print(f"Start analyzing {inFileName}")
|
| 215 |
st.session_state.resetResult = False
|
| 216 |
-
|
| 217 |
-
print(f"Found at index {currFileIndex}")
|
| 218 |
-
if len(st.session_state.results) > currFileIndex and len(st.session_state.summaries) > currFileIndex and len(st.session_state.results[currFileIndex]) > 0:
|
| 219 |
|
| 220 |
printV(f'In if',4)
|
| 221 |
-
|
| 222 |
-
currAnnotation, currTotalTime = st.session_state.results[currFileIndex]
|
| 223 |
speakerNames = currAnnotation.labels()
|
| 224 |
printV(f'Loaded results',4)
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
categorySelections = st.session_state["categorySelect"][currFileIndex]
|
| 228 |
printV(f'Loaded speaker selections',4)
|
| 229 |
noVoice, oneVoice, multiVoice = su.calcSpeakingTypes(currAnnotation,currTotalTime)
|
| 230 |
sumNoVoice = su.sumTimes(noVoice)
|
|
@@ -241,7 +224,7 @@ def analyze(inFileName):
|
|
| 241 |
}
|
| 242 |
)
|
| 243 |
df3.name = "df3"
|
| 244 |
-
st.session_state.summaries[
|
| 245 |
printV(f'Set df3',4)
|
| 246 |
|
| 247 |
# --- Build df4 ---
|
|
@@ -275,7 +258,7 @@ def analyze(inFileName):
|
|
| 275 |
}
|
| 276 |
df4 = pd.DataFrame(data=df4_dict)
|
| 277 |
df4.name = "df4"
|
| 278 |
-
st.session_state.summaries[
|
| 279 |
printV(f'Set df4', 4)
|
| 280 |
|
| 281 |
# --- Build df5 ---
|
|
@@ -331,20 +314,20 @@ def analyze(inFileName):
|
|
| 331 |
+ [(t * 100) / summativeMultiSpeaker for t in multiTimeList],
|
| 332 |
})
|
| 333 |
df5.name = "df5"
|
| 334 |
-
st.session_state.summaries[
|
| 335 |
printV(f'Set df5', 4)
|
| 336 |
|
| 337 |
# --- Build speakers_dataFrame, df2 ---
|
| 338 |
speakers_dataFrame, speakers_times = su.annotationToDataFrame(currAnnotation)
|
| 339 |
-
st.session_state.summaries[
|
| 340 |
-
st.session_state.summaries[
|
| 341 |
|
| 342 |
df2_dict = {
|
| 343 |
"values": [100 * t / currTotalTime for t in df4_dict["values"]],
|
| 344 |
"names": df4_dict["names"],
|
| 345 |
}
|
| 346 |
df2 = pd.DataFrame(df2_dict)
|
| 347 |
-
st.session_state.summaries[
|
| 348 |
printV(f'Set df2', 4)
|
| 349 |
except Exception as e:
|
| 350 |
import traceback
|
|
@@ -393,34 +376,32 @@ pipeline = Pipeline.from_pretrained("pyannote/speaker-diarization-3.1")
|
|
| 393 |
pipeline.to(device)#torch.device("cuda"))
|
| 394 |
|
| 395 |
# Store results for viewing and further processing
|
| 396 |
-
#
|
| 397 |
if 'results' not in st.session_state:
|
| 398 |
-
st.session_state.results =
|
| 399 |
if 'speakerRenames' not in st.session_state:
|
| 400 |
-
st.session_state.speakerRenames =
|
| 401 |
if 'summaries' not in st.session_state:
|
| 402 |
-
st.session_state.summaries =
|
| 403 |
if 'categories' not in st.session_state:
|
| 404 |
st.session_state.categories = []
|
| 405 |
-
st.session_state.categorySelect = []
|
| 406 |
-
# Single Use
|
| 407 |
if 'removeCategory' not in st.session_state:
|
| 408 |
st.session_state.removeCategory = None
|
| 409 |
if 'resetResult' not in st.session_state:
|
| 410 |
st.session_state.resetResult = False
|
| 411 |
-
# Specific to target file
|
| 412 |
if 'unusedSpeakers' not in st.session_state:
|
| 413 |
-
st.session_state.unusedSpeakers = []
|
| 414 |
if 'file_names' not in st.session_state:
|
| 415 |
st.session_state.file_names = []
|
| 416 |
if 'valid_files' not in st.session_state:
|
| 417 |
st.session_state.valid_files = []
|
| 418 |
if 'file_paths' not in st.session_state:
|
| 419 |
-
st.session_state.file_paths =
|
| 420 |
if 'showSummary' not in st.session_state:
|
| 421 |
st.session_state.showSummary = 'No'
|
| 422 |
if 'speakerClips' not in st.session_state:
|
| 423 |
-
st.session_state.speakerClips =
|
| 424 |
|
| 425 |
|
| 426 |
|
|
@@ -447,127 +428,97 @@ temp_dir = tempfile.mkdtemp()
|
|
| 447 |
|
| 448 |
if uploaded_file_paths is not None and len(uploaded_file_paths) > 0:
|
| 449 |
print("Found file paths")
|
| 450 |
-
valid_files = []
|
| 451 |
-
file_paths = []
|
| 452 |
-
file_names = []
|
| 453 |
for uploaded_file in uploaded_file_paths:
|
| 454 |
if not uploaded_file.name.lower().endswith(supported_file_types):
|
| 455 |
st.error('File must be of type: {}'.format(supported_file_types))
|
| 456 |
-
uploaded_file = None
|
| 457 |
else:
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
new_list.append(old_list[idx] if idx < len(old_list) else empty_val_fn())
|
| 479 |
-
else:
|
| 480 |
-
new_list.append(empty_val_fn())
|
| 481 |
-
return new_list
|
| 482 |
-
|
| 483 |
-
st.session_state.results = _carry(st.session_state.results, lambda: [])
|
| 484 |
-
st.session_state.summaries = _carry(st.session_state.summaries, lambda: [])
|
| 485 |
-
st.session_state.unusedSpeakers= _carry(st.session_state.unusedSpeakers, lambda: [])
|
| 486 |
-
st.session_state.categorySelect= _carry(st.session_state.categorySelect,
|
| 487 |
-
lambda: [[] for _ in st.session_state.categories])
|
| 488 |
-
st.session_state.speakerRenames= _carry(st.session_state.speakerRenames, lambda: {})
|
| 489 |
-
st.session_state.speakerClips = _carry(st.session_state.speakerClips, lambda: {})
|
| 490 |
-
|
| 491 |
-
st.session_state.file_names = file_names
|
| 492 |
st.session_state.valid_files = valid_files
|
| 493 |
-
st.session_state.file_paths = file_paths
|
| 494 |
|
| 495 |
file_names = st.session_state.file_names
|
| 496 |
-
|
|
|
|
| 497 |
if len(file_names) == 0:
|
| 498 |
st.text("Upload file(s) to enable analysis")
|
| 499 |
else:
|
| 500 |
if st.button("Analyze All New Audio",key=f"button_all"):
|
| 501 |
-
if len(
|
| 502 |
st.error('Upload file(s) first!')
|
| 503 |
else:
|
| 504 |
print("Start analyzing")
|
| 505 |
start_time = time.time()
|
| 506 |
-
totalFiles = len(
|
| 507 |
-
for i in
|
| 508 |
-
|
|
|
|
| 509 |
continue
|
| 510 |
-
|
| 511 |
-
if file_paths[i].lower().endswith('.txt'):
|
| 512 |
with st.spinner(text=f'Loading Demo File {i+1} of {totalFiles}'):
|
| 513 |
-
|
| 514 |
-
speakerList, annotations = su.loadAudioTXT(file_paths[i])
|
| 515 |
printV(annotations,4)
|
| 516 |
-
# Approximate total seconds
|
| 517 |
totalSeconds = 0
|
| 518 |
for segment in annotations.itersegments():
|
| 519 |
if segment.end > totalSeconds:
|
| 520 |
totalSeconds = segment.end
|
| 521 |
-
st.session_state.results[
|
| 522 |
-
st.session_state.summaries[
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
elif file_paths[i].lower().endswith('.rttm'):
|
| 526 |
with st.spinner(text=f'Loading File {i+1} of {totalFiles}'):
|
| 527 |
-
|
| 528 |
-
speakerList, annotations = su.loadAudioRTTM(file_paths[i])
|
| 529 |
printV(annotations,4)
|
| 530 |
-
# Approximate total seconds
|
| 531 |
totalSeconds = 0
|
| 532 |
for segment in annotations.itersegments():
|
| 533 |
if segment.end > totalSeconds:
|
| 534 |
totalSeconds = segment.end
|
| 535 |
-
st.session_state.results[
|
| 536 |
-
st.session_state.summaries[
|
| 537 |
-
|
| 538 |
-
|
| 539 |
-
elif file_paths[i].lower().endswith('.csv'):
|
| 540 |
with st.spinner(text=f'Loading File {i+1} of {totalFiles}'):
|
| 541 |
-
|
| 542 |
-
speakerList, annotations = su.loadAudioCSV(file_paths[i])
|
| 543 |
printV(annotations,4)
|
| 544 |
-
# Approximate total seconds
|
| 545 |
totalSeconds = 0
|
| 546 |
for segment in annotations.itersegments():
|
| 547 |
if segment.end > totalSeconds:
|
| 548 |
totalSeconds = segment.end
|
| 549 |
-
st.session_state.results[
|
| 550 |
-
st.session_state.summaries[
|
| 551 |
-
|
| 552 |
-
st.session_state.unusedSpeakers[i] = speakerNames
|
| 553 |
else:
|
| 554 |
with st.spinner(text=f'Processing File {i+1} of {totalFiles}'):
|
| 555 |
-
annotations, totalSeconds, waveform, sample_rate = processFile(
|
| 556 |
-
print(f"Finished processing {
|
| 557 |
-
st.session_state.results[
|
| 558 |
-
|
| 559 |
-
st.session_state.
|
| 560 |
-
print("Summaries saved")
|
| 561 |
-
speakerNames = annotations.labels()
|
| 562 |
-
st.session_state.unusedSpeakers[i] = speakerNames
|
| 563 |
-
print("Speakers saved")
|
| 564 |
with st.spinner(text=f'Generating speaker clips for File {i+1} of {totalFiles}'):
|
| 565 |
-
generate_speaker_clips(annotations, waveform, sample_rate,
|
| 566 |
-
del waveform
|
| 567 |
-
print(f"Speaker clips generated for {
|
| 568 |
with st.spinner(text=f'Analyzing File {i+1} of {totalFiles}'):
|
| 569 |
-
analyze(
|
| 570 |
-
print(f"Finished analyzing {
|
| 571 |
print(f"Took {time.time() - start_time} seconds to analyze {totalFiles} files!")
|
| 572 |
st.success(f"Took {time.time() - start_time} seconds to analyze {totalFiles} files!")
|
| 573 |
|
|
@@ -579,80 +530,58 @@ class FakeUpload:
|
|
| 579 |
demoPath = "sample.rttm"
|
| 580 |
isDemo = False
|
| 581 |
if st.sidebar.button("Load Demo Example"):
|
| 582 |
-
|
| 583 |
-
valid_files=[sampleUpload]
|
| 584 |
-
file_paths=[sampleUpload.path]
|
| 585 |
-
file_names=[sampleUpload.name]
|
| 586 |
-
st.session_state.valid_files = valid_files
|
| 587 |
-
st.session_state.file_paths = file_paths
|
| 588 |
start_time = time.time()
|
| 589 |
-
st.session_state.file_names
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
tempCategories = [[] for cat in st.session_state.categories]
|
| 601 |
-
st.session_state.categorySelect.append(tempCategories)
|
| 602 |
-
while (len(st.session_state.summaries) < len(valid_files)):
|
| 603 |
-
st.session_state.summaries.append([])
|
| 604 |
-
while (len(st.session_state.speakerRenames) < len(valid_files)):
|
| 605 |
-
st.session_state.speakerRenames.append({})
|
| 606 |
-
while (len(st.session_state.speakerClips) < len(valid_files)):
|
| 607 |
-
st.session_state.speakerClips.append({})
|
| 608 |
-
|
| 609 |
with st.spinner(text=f'Loading Demo Sample'):
|
| 610 |
-
|
| 611 |
-
speakerList, annotations = su.loadAudioRTTM(file_paths[0])
|
| 612 |
-
# Approximate total seconds
|
| 613 |
totalSeconds = 0
|
| 614 |
for segment in annotations.itersegments():
|
| 615 |
if segment.end > totalSeconds:
|
| 616 |
totalSeconds = segment.end
|
| 617 |
-
st.session_state.results =
|
| 618 |
-
st.session_state.summaries =
|
| 619 |
-
|
| 620 |
-
st.session_state.speakerRenames.append({})
|
| 621 |
-
speakerNames = annotations.labels()
|
| 622 |
-
st.session_state.unusedSpeakers = [speakerNames]
|
| 623 |
with st.spinner(text=f'Analyzing Demo Data'):
|
| 624 |
-
|
| 625 |
st.success(f"Took {time.time() - start_time} seconds to analyze the demo file!")
|
| 626 |
-
st.session_state.select_currFile=
|
| 627 |
isDemo = True
|
| 628 |
|
| 629 |
-
currFile = st.sidebar.selectbox('Current File', file_names,on_change=updateMultiSelect,key="select_currFile")
|
| 630 |
|
| 631 |
if isDemo:
|
| 632 |
-
currFile=file_names[0]
|
| 633 |
isDemo = False
|
| 634 |
-
|
| 635 |
-
if currFile is None and
|
| 636 |
st.write("Select a file to view from the sidebar")
|
| 637 |
try:
|
| 638 |
st.session_state.resetResult = False
|
| 639 |
-
currFileIndex = file_names.index(currFile)
|
| 640 |
currPlainName = currFile.split('.')[0]
|
| 641 |
-
if
|
| 642 |
st.header(f"Analysis of file {currFile}")
|
| 643 |
graphNames = ["Data","Voice Categories","Speaker Percentage","Speakers with Categories","Treemap","Timeline","Time Spoken"]
|
| 644 |
dataTab, pie1, pie2, sunburst1, treemap1, timeline, bar1 = st.tabs(graphNames)
|
| 645 |
-
|
| 646 |
-
currAnnotation, currTotalTime = st.session_state.results[currFileIndex]
|
| 647 |
speakerNames = currAnnotation.labels()
|
| 648 |
-
|
| 649 |
-
speakers_dataFrame = st.session_state.summaries[
|
| 650 |
currDF, _ = su.annotationToSimpleDataFrame(currAnnotation)
|
| 651 |
-
speakers_times = st.session_state.summaries[
|
| 652 |
-
|
| 653 |
-
|
| 654 |
-
|
| 655 |
-
categorySelections = st.session_state["categorySelect"][currFileIndex]
|
| 656 |
for i,category in enumerate(st.session_state.categories):
|
| 657 |
speakerSet = categorySelections[i]
|
| 658 |
st.sidebar.multiselect(category,
|
|
@@ -660,7 +589,7 @@ try:
|
|
| 660 |
default=speakerSet,
|
| 661 |
key=f"multiselect_{category}",
|
| 662 |
on_change=updateCategoryOptions,
|
| 663 |
-
args=(
|
| 664 |
st.sidebar.button(f"Remove {category}",key=f"remove_{category}",on_click=removeCategory,args=(i,))
|
| 665 |
|
| 666 |
|
|
@@ -672,38 +601,34 @@ try:
|
|
| 672 |
st.sidebar.caption("Replace SPEAKER_## labels with real names.")
|
| 673 |
|
| 674 |
# --- Speaker clip preview ---
|
| 675 |
-
file_clips = (
|
| 676 |
-
st.session_state.speakerClips[currFileIndex]
|
| 677 |
-
if currFileIndex < len(st.session_state.speakerClips)
|
| 678 |
-
else {}
|
| 679 |
-
)
|
| 680 |
if file_clips:
|
| 681 |
st.sidebar.caption(
|
| 682 |
"Listen to each speaker's longest clip (up to 5 s) to help identify them."
|
| 683 |
)
|
| 684 |
|
| 685 |
-
current_renames = st.session_state.speakerRenames[
|
| 686 |
for sp in speakerNames:
|
| 687 |
-
widget_key = f"rename_{
|
| 688 |
-
# Seed the widget state once
|
| 689 |
-
# After that, never pass value= so Streamlit never overwrites what the user typed.
|
| 690 |
if widget_key not in st.session_state:
|
| 691 |
st.session_state[widget_key] = current_renames.get(sp, "")
|
| 692 |
-
# Use live widget value for the display label so it updates immediately
|
| 693 |
live_name = st.session_state[widget_key].strip()
|
| 694 |
display_label = live_name if live_name else sp
|
| 695 |
st.sidebar.markdown(f"**{display_label}**")
|
| 696 |
if sp in file_clips:
|
| 697 |
st.sidebar.audio(file_clips[sp], format="audio/wav")
|
|
|
|
| 698 |
new_name = st.sidebar.text_input(
|
| 699 |
-
|
| 700 |
placeholder="e.g. John",
|
| 701 |
-
key=widget_key
|
|
|
|
| 702 |
)
|
| 703 |
if new_name.strip():
|
| 704 |
-
st.session_state.speakerRenames[
|
| 705 |
-
elif sp in st.session_state.speakerRenames[
|
| 706 |
-
del st.session_state.speakerRenames[
|
| 707 |
|
| 708 |
catTypeColors = su.colorsCSS(3)
|
| 709 |
allColors = su.colorsCSS(len(speakerNames)+len(st.session_state.categories))
|
|
@@ -730,10 +655,10 @@ try:
|
|
| 730 |
}
|
| 731 |
df4 = pd.DataFrame(data=df4_dict)
|
| 732 |
df4.name = "df4"
|
| 733 |
-
st.session_state.summaries[
|
| 734 |
|
| 735 |
with dataTab:
|
| 736 |
-
displayDF = apply_speaker_renames_to_df(currDF,
|
| 737 |
csv = convert_df(displayDF)
|
| 738 |
|
| 739 |
st.download_button(
|
|
@@ -747,7 +672,7 @@ try:
|
|
| 747 |
st.dataframe(displayDF)
|
| 748 |
with pie1:
|
| 749 |
printV("In Pie1",4)
|
| 750 |
-
df3 = st.session_state.summaries[
|
| 751 |
fig1 = go.Figure()
|
| 752 |
fig1.update_layout(
|
| 753 |
title_text="Percentage of each Voice Category",
|
|
@@ -796,14 +721,14 @@ try:
|
|
| 796 |
|
| 797 |
with pie2:
|
| 798 |
printV("In Pie2",4)
|
| 799 |
-
df4 = st.session_state.summaries[
|
| 800 |
|
| 801 |
# Some speakers may be missing, so fix colors
|
| 802 |
figColors = []
|
| 803 |
for n in df4["names"]:
|
| 804 |
if n in speakerNames:
|
| 805 |
figColors.append(speakerColors[speakerNames.index(n)])
|
| 806 |
-
df4["names"] = df4["names"].apply(lambda s: get_display_name(s,
|
| 807 |
fig2 = go.Figure()
|
| 808 |
fig2.update_layout(
|
| 809 |
title_text="Percentage of Speakers and Custom Categories",
|
|
@@ -845,9 +770,9 @@ try:
|
|
| 845 |
)
|
| 846 |
|
| 847 |
with sunburst1:
|
| 848 |
-
df5 = st.session_state.summaries[
|
| 849 |
-
df5["labels"] = df5["labels"].apply(lambda s: get_display_name(s,
|
| 850 |
-
df5["parentNames"] = df5["parentNames"].apply(lambda s: get_display_name(s,
|
| 851 |
fig3_1 = px.sunburst(df5,
|
| 852 |
branchvalues = 'total',
|
| 853 |
names = "labels",
|
|
@@ -903,9 +828,9 @@ try:
|
|
| 903 |
)
|
| 904 |
|
| 905 |
with treemap1:
|
| 906 |
-
df5 = st.session_state.summaries[
|
| 907 |
-
df5["labels"] = df5["labels"].apply(lambda s: get_display_name(s,
|
| 908 |
-
df5["parentNames"] = df5["parentNames"].apply(lambda s: get_display_name(s,
|
| 909 |
fig3 = px.treemap(df5,
|
| 910 |
branchvalues = "total",
|
| 911 |
names = "labels",
|
|
@@ -965,7 +890,7 @@ try:
|
|
| 965 |
|
| 966 |
with timeline:
|
| 967 |
timeline_df = speakers_dataFrame.copy()
|
| 968 |
-
timeline_df["Resource"] = timeline_df["Resource"].apply(lambda s: get_display_name(s,
|
| 969 |
base = dt.datetime.combine(dt.date.today(), dt.time.min)
|
| 970 |
def to_audio_datetime(s):
|
| 971 |
# If already a datetime/Timestamp, extract seconds since midnight of that date
|
|
@@ -1034,8 +959,8 @@ try:
|
|
| 1034 |
)
|
| 1035 |
|
| 1036 |
with bar1:
|
| 1037 |
-
df2 = st.session_state.summaries[
|
| 1038 |
-
df2["names"] = df2["names"].apply(lambda s: get_display_name(s,
|
| 1039 |
fig2_la = px.bar(df2, x="values", y="names", color="names", orientation='h',
|
| 1040 |
custom_data=["names","values"],title="Time Spoken by each Speaker",
|
| 1041 |
color_discrete_sequence=catColors+speakerColors)
|
|
@@ -1094,36 +1019,28 @@ if len(st.session_state.results) > 0:
|
|
| 1094 |
st.header("Multi-file Summary Data")
|
| 1095 |
with st.spinner(text='Processing summary results...'):
|
| 1096 |
fileNames = st.session_state.file_names
|
| 1097 |
-
|
| 1098 |
-
|
| 1099 |
-
|
| 1100 |
-
|
| 1101 |
-
results.append(resultTuple)
|
| 1102 |
-
indices.append(i)
|
| 1103 |
-
if len(indices) > 1:
|
| 1104 |
-
|
| 1105 |
-
df6_dict = {
|
| 1106 |
-
"files":fileNames,
|
| 1107 |
-
}
|
| 1108 |
allCategories = copy.deepcopy(st.session_state.categories)
|
| 1109 |
-
for
|
| 1110 |
-
currAnnotation, currTotalTime = st.session_state.results[
|
| 1111 |
-
categorySelections = st.session_state["categorySelect"][
|
| 1112 |
-
catSummary,extraCats = su.calcCategories(currAnnotation,categorySelections)
|
| 1113 |
-
st.session_state.summaries[
|
| 1114 |
for extra in extraCats:
|
| 1115 |
df6_dict[extra] = []
|
| 1116 |
if extra not in allCategories:
|
| 1117 |
allCategories.append(extra)
|
| 1118 |
-
|
| 1119 |
-
|
| 1120 |
for category in st.session_state.categories:
|
| 1121 |
df6_dict[category] = []
|
| 1122 |
-
for
|
| 1123 |
-
summary, extras = st.session_state.summaries[
|
| 1124 |
theseCategories = st.session_state.categories + extras
|
| 1125 |
for j, timeSlots in enumerate(summary):
|
| 1126 |
-
df6_dict[theseCategories[j]].append(sum([t.duration for _,t in timeSlots])/st.session_state.results[
|
| 1127 |
for category in allCategories:
|
| 1128 |
if category not in theseCategories:
|
| 1129 |
df6_dict[category].append(0)
|
|
@@ -1138,8 +1055,8 @@ if len(st.session_state.results) > 0:
|
|
| 1138 |
}
|
| 1139 |
for category in voiceNames:
|
| 1140 |
df7_dict[category] = []
|
| 1141 |
-
for
|
| 1142 |
-
partialDf =
|
| 1143 |
for i in range(len(voiceNames)):
|
| 1144 |
df7_dict[voiceNames[i]].append(partialDf["percentiles"][i])
|
| 1145 |
df7 = pd.DataFrame(df7_dict)
|
|
|
|
| 39 |
if verbosity>=verbosityLevel:
|
| 40 |
print(message)
|
| 41 |
|
| 42 |
+
def get_display_name(speaker, fileName):
|
| 43 |
"""Return the user-assigned display name for a speaker, or the original label."""
|
| 44 |
renames = st.session_state.speakerRenames
|
| 45 |
+
return renames.get(fileName, {}).get(speaker, speaker)
|
|
|
|
|
|
|
| 46 |
|
| 47 |
+
def apply_speaker_renames_to_df(df, fileName, column="task"):
|
| 48 |
"""Replace speaker_## labels in a DataFrame column with display names."""
|
| 49 |
if column not in df.columns:
|
| 50 |
return df
|
| 51 |
df = df.copy()
|
| 52 |
+
df[column] = df[column].apply(lambda s: get_display_name(s, fileName))
|
| 53 |
return df
|
| 54 |
|
| 55 |
@st.cache_data
|
|
|
|
| 125 |
"""
|
| 126 |
import io
|
| 127 |
|
| 128 |
+
# file_index is now a filename string
|
|
|
|
|
|
|
|
|
|
| 129 |
clips = {}
|
| 130 |
for speaker in annotations.labels():
|
| 131 |
speaker_segments = [
|
|
|
|
| 154 |
clips[speaker] = buffer.read()
|
| 155 |
|
| 156 |
st.session_state.speakerClips[file_index] = clips
|
| 157 |
+
print(f"Generated {len(clips)} speaker clips for {file_index}")
|
| 158 |
|
| 159 |
def addCategory():
|
| 160 |
newCategory = st.session_state.categoryInput
|
|
|
|
| 162 |
st.session_state[f'multiselect_{newCategory}'] = []
|
| 163 |
st.session_state.categories.append(newCategory)
|
| 164 |
st.session_state.categoryInput = ''
|
| 165 |
+
for fname in st.session_state.categorySelect:
|
| 166 |
+
st.session_state.categorySelect[fname].append([])
|
| 167 |
|
| 168 |
def removeCategory(index):
|
| 169 |
categoryName = st.session_state.categories[index]
|
|
|
|
| 171 |
del st.session_state[f'multiselect_{categoryName}']
|
| 172 |
del st.session_state[f'remove_{categoryName}']
|
| 173 |
del st.session_state.categories[index]
|
| 174 |
+
for fname in st.session_state.categorySelect:
|
| 175 |
+
del st.session_state.categorySelect[fname][index]
|
| 176 |
|
| 177 |
+
def updateCategoryOptions(fileName):
|
| 178 |
if st.session_state.resetResult:
|
|
|
|
| 179 |
return
|
| 180 |
+
currAnnotation, _ = st.session_state.results[fileName]
|
|
|
|
|
|
|
|
|
|
| 181 |
speakerNames = currAnnotation.labels()
|
|
|
|
|
|
|
| 182 |
unusedSpeakers = copy.deepcopy(speakerNames)
|
|
|
|
| 183 |
for i, category in enumerate(st.session_state['categories']):
|
| 184 |
category_choices = copy.deepcopy(st.session_state[f'multiselect_{category}'])
|
| 185 |
+
st.session_state["categorySelect"][fileName][i] = category_choices
|
| 186 |
for sp in category_choices:
|
| 187 |
try:
|
| 188 |
unusedSpeakers.remove(sp)
|
| 189 |
except:
|
| 190 |
continue
|
| 191 |
+
st.session_state.unusedSpeakers[fileName] = unusedSpeakers
|
|
|
|
| 192 |
|
| 193 |
def updateMultiSelect():
|
| 194 |
+
fileName = st.session_state["select_currFile"]
|
| 195 |
st.session_state.resetResult = True
|
| 196 |
for i, category in enumerate(st.session_state['categories']):
|
| 197 |
+
st.session_state[f'multiselect_{category}'] = st.session_state['categorySelect'][fileName][i]
|
| 198 |
|
| 199 |
def analyze(inFileName):
|
| 200 |
try:
|
| 201 |
print(f"Start analyzing {inFileName}")
|
| 202 |
st.session_state.resetResult = False
|
| 203 |
+
if inFileName in st.session_state.results and inFileName in st.session_state.summaries and len(st.session_state.results[inFileName]) > 0:
|
|
|
|
|
|
|
| 204 |
|
| 205 |
printV(f'In if',4)
|
| 206 |
+
currAnnotation, currTotalTime = st.session_state.results[inFileName]
|
|
|
|
| 207 |
speakerNames = currAnnotation.labels()
|
| 208 |
printV(f'Loaded results',4)
|
| 209 |
+
unusedSpeakers = st.session_state.unusedSpeakers[inFileName]
|
| 210 |
+
categorySelections = st.session_state["categorySelect"][inFileName]
|
|
|
|
| 211 |
printV(f'Loaded speaker selections',4)
|
| 212 |
noVoice, oneVoice, multiVoice = su.calcSpeakingTypes(currAnnotation,currTotalTime)
|
| 213 |
sumNoVoice = su.sumTimes(noVoice)
|
|
|
|
| 224 |
}
|
| 225 |
)
|
| 226 |
df3.name = "df3"
|
| 227 |
+
st.session_state.summaries[inFileName]["df3"] = df3
|
| 228 |
printV(f'Set df3',4)
|
| 229 |
|
| 230 |
# --- Build df4 ---
|
|
|
|
| 258 |
}
|
| 259 |
df4 = pd.DataFrame(data=df4_dict)
|
| 260 |
df4.name = "df4"
|
| 261 |
+
st.session_state.summaries[inFileName]["df4"] = df4
|
| 262 |
printV(f'Set df4', 4)
|
| 263 |
|
| 264 |
# --- Build df5 ---
|
|
|
|
| 314 |
+ [(t * 100) / summativeMultiSpeaker for t in multiTimeList],
|
| 315 |
})
|
| 316 |
df5.name = "df5"
|
| 317 |
+
st.session_state.summaries[inFileName]["df5"] = df5
|
| 318 |
printV(f'Set df5', 4)
|
| 319 |
|
| 320 |
# --- Build speakers_dataFrame, df2 ---
|
| 321 |
speakers_dataFrame, speakers_times = su.annotationToDataFrame(currAnnotation)
|
| 322 |
+
st.session_state.summaries[inFileName]["speakers_dataFrame"] = speakers_dataFrame
|
| 323 |
+
st.session_state.summaries[inFileName]["speakers_times"] = speakers_times
|
| 324 |
|
| 325 |
df2_dict = {
|
| 326 |
"values": [100 * t / currTotalTime for t in df4_dict["values"]],
|
| 327 |
"names": df4_dict["names"],
|
| 328 |
}
|
| 329 |
df2 = pd.DataFrame(df2_dict)
|
| 330 |
+
st.session_state.summaries[inFileName]["df2"] = df2
|
| 331 |
printV(f'Set df2', 4)
|
| 332 |
except Exception as e:
|
| 333 |
import traceback
|
|
|
|
| 376 |
pipeline.to(device)#torch.device("cuda"))
|
| 377 |
|
| 378 |
# Store results for viewing and further processing
|
| 379 |
+
# All per-file state is keyed by filename (str) so it survives upload order changes.
|
| 380 |
if 'results' not in st.session_state:
|
| 381 |
+
st.session_state.results = {} # {filename: (annotations, totalSeconds)}
|
| 382 |
if 'speakerRenames' not in st.session_state:
|
| 383 |
+
st.session_state.speakerRenames = {} # {filename: {speaker: name}}
|
| 384 |
if 'summaries' not in st.session_state:
|
| 385 |
+
st.session_state.summaries = {} # {filename: {df2, df3, ...}}
|
| 386 |
if 'categories' not in st.session_state:
|
| 387 |
st.session_state.categories = []
|
| 388 |
+
st.session_state.categorySelect = {} # {filename: [[], [], ...]}
|
|
|
|
| 389 |
if 'removeCategory' not in st.session_state:
|
| 390 |
st.session_state.removeCategory = None
|
| 391 |
if 'resetResult' not in st.session_state:
|
| 392 |
st.session_state.resetResult = False
|
|
|
|
| 393 |
if 'unusedSpeakers' not in st.session_state:
|
| 394 |
+
st.session_state.unusedSpeakers = {} # {filename: [speaker, ...]}
|
| 395 |
if 'file_names' not in st.session_state:
|
| 396 |
st.session_state.file_names = []
|
| 397 |
if 'valid_files' not in st.session_state:
|
| 398 |
st.session_state.valid_files = []
|
| 399 |
if 'file_paths' not in st.session_state:
|
| 400 |
+
st.session_state.file_paths = {} # {filename: path}
|
| 401 |
if 'showSummary' not in st.session_state:
|
| 402 |
st.session_state.showSummary = 'No'
|
| 403 |
if 'speakerClips' not in st.session_state:
|
| 404 |
+
st.session_state.speakerClips = {} # {filename: {speaker: wav_bytes}}
|
| 405 |
|
| 406 |
|
| 407 |
|
|
|
|
| 428 |
|
| 429 |
if uploaded_file_paths is not None and len(uploaded_file_paths) > 0:
|
| 430 |
print("Found file paths")
|
|
|
|
|
|
|
|
|
|
| 431 |
for uploaded_file in uploaded_file_paths:
|
| 432 |
if not uploaded_file.name.lower().endswith(supported_file_types):
|
| 433 |
st.error('File must be of type: {}'.format(supported_file_types))
|
|
|
|
| 434 |
else:
|
| 435 |
+
fname = uploaded_file.name
|
| 436 |
+
print(f"Valid file: {fname}")
|
| 437 |
+
# Write to disk (always refresh so file bytes are current)
|
| 438 |
+
path = os.path.join(temp_dir, fname)
|
| 439 |
+
with open(path, "wb") as f:
|
| 440 |
+
f.write(uploaded_file.getvalue())
|
| 441 |
+
# Add to master lists only if not already tracked
|
| 442 |
+
if fname not in st.session_state.file_names:
|
| 443 |
+
st.session_state.file_names.append(fname)
|
| 444 |
+
st.session_state.results.setdefault(fname, [])
|
| 445 |
+
st.session_state.summaries.setdefault(fname, [])
|
| 446 |
+
st.session_state.unusedSpeakers.setdefault(fname, [])
|
| 447 |
+
st.session_state.categorySelect.setdefault(fname, [[] for _ in st.session_state.categories])
|
| 448 |
+
st.session_state.speakerRenames.setdefault(fname, {})
|
| 449 |
+
st.session_state.speakerClips.setdefault(fname, {})
|
| 450 |
+
st.session_state.file_paths[fname] = path
|
| 451 |
+
# Rebuild valid_files / file_paths lists from tracked state
|
| 452 |
+
valid_files = [f for f in st.session_state.file_names]
|
| 453 |
+
file_paths = [st.session_state.file_paths[f] for f in valid_files]
|
| 454 |
+
file_names = valid_files
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 455 |
st.session_state.valid_files = valid_files
|
| 456 |
+
st.session_state.file_paths = {f: st.session_state.file_paths[f] for f in valid_files}
|
| 457 |
|
| 458 |
file_names = st.session_state.file_names
|
| 459 |
+
file_paths_dict = st.session_state.file_paths # dict {fname: path}
|
| 460 |
+
|
| 461 |
if len(file_names) == 0:
|
| 462 |
st.text("Upload file(s) to enable analysis")
|
| 463 |
else:
|
| 464 |
if st.button("Analyze All New Audio",key=f"button_all"):
|
| 465 |
+
if len(file_names) == 0:
|
| 466 |
st.error('Upload file(s) first!')
|
| 467 |
else:
|
| 468 |
print("Start analyzing")
|
| 469 |
start_time = time.time()
|
| 470 |
+
totalFiles = len(file_names)
|
| 471 |
+
for i, fname in enumerate(file_names):
|
| 472 |
+
fpath = file_paths_dict.get(fname, "")
|
| 473 |
+
if st.session_state.results.get(fname):
|
| 474 |
continue
|
| 475 |
+
if fpath.lower().endswith('.txt'):
|
|
|
|
| 476 |
with st.spinner(text=f'Loading Demo File {i+1} of {totalFiles}'):
|
| 477 |
+
speakerList, annotations = su.loadAudioTXT(fpath)
|
|
|
|
| 478 |
printV(annotations,4)
|
|
|
|
| 479 |
totalSeconds = 0
|
| 480 |
for segment in annotations.itersegments():
|
| 481 |
if segment.end > totalSeconds:
|
| 482 |
totalSeconds = segment.end
|
| 483 |
+
st.session_state.results[fname] = (annotations, totalSeconds)
|
| 484 |
+
st.session_state.summaries[fname] = {}
|
| 485 |
+
st.session_state.unusedSpeakers[fname] = list(annotations.labels())
|
| 486 |
+
elif fpath.lower().endswith('.rttm'):
|
|
|
|
| 487 |
with st.spinner(text=f'Loading File {i+1} of {totalFiles}'):
|
| 488 |
+
speakerList, annotations = su.loadAudioRTTM(fpath)
|
|
|
|
| 489 |
printV(annotations,4)
|
|
|
|
| 490 |
totalSeconds = 0
|
| 491 |
for segment in annotations.itersegments():
|
| 492 |
if segment.end > totalSeconds:
|
| 493 |
totalSeconds = segment.end
|
| 494 |
+
st.session_state.results[fname] = (annotations, totalSeconds)
|
| 495 |
+
st.session_state.summaries[fname] = {}
|
| 496 |
+
st.session_state.unusedSpeakers[fname] = list(annotations.labels())
|
| 497 |
+
elif fpath.lower().endswith('.csv'):
|
|
|
|
| 498 |
with st.spinner(text=f'Loading File {i+1} of {totalFiles}'):
|
| 499 |
+
speakerList, annotations = su.loadAudioCSV(fpath)
|
|
|
|
| 500 |
printV(annotations,4)
|
|
|
|
| 501 |
totalSeconds = 0
|
| 502 |
for segment in annotations.itersegments():
|
| 503 |
if segment.end > totalSeconds:
|
| 504 |
totalSeconds = segment.end
|
| 505 |
+
st.session_state.results[fname] = (annotations, totalSeconds)
|
| 506 |
+
st.session_state.summaries[fname] = {}
|
| 507 |
+
st.session_state.unusedSpeakers[fname] = list(annotations.labels())
|
|
|
|
| 508 |
else:
|
| 509 |
with st.spinner(text=f'Processing File {i+1} of {totalFiles}'):
|
| 510 |
+
annotations, totalSeconds, waveform, sample_rate = processFile(fpath)
|
| 511 |
+
print(f"Finished processing {fpath}")
|
| 512 |
+
st.session_state.results[fname] = (annotations, totalSeconds)
|
| 513 |
+
st.session_state.summaries[fname] = {}
|
| 514 |
+
st.session_state.unusedSpeakers[fname] = list(annotations.labels())
|
|
|
|
|
|
|
|
|
|
|
|
|
| 515 |
with st.spinner(text=f'Generating speaker clips for File {i+1} of {totalFiles}'):
|
| 516 |
+
generate_speaker_clips(annotations, waveform, sample_rate, fname)
|
| 517 |
+
del waveform
|
| 518 |
+
print(f"Speaker clips generated for {fpath}")
|
| 519 |
with st.spinner(text=f'Analyzing File {i+1} of {totalFiles}'):
|
| 520 |
+
analyze(fname)
|
| 521 |
+
print(f"Finished analyzing {fpath}")
|
| 522 |
print(f"Took {time.time() - start_time} seconds to analyze {totalFiles} files!")
|
| 523 |
st.success(f"Took {time.time() - start_time} seconds to analyze {totalFiles} files!")
|
| 524 |
|
|
|
|
| 530 |
demoPath = "sample.rttm"
|
| 531 |
isDemo = False
|
| 532 |
if st.sidebar.button("Load Demo Example"):
|
| 533 |
+
demoName = demoPath.split('/')[-1]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 534 |
start_time = time.time()
|
| 535 |
+
if demoName not in st.session_state.file_names:
|
| 536 |
+
st.session_state.file_names.append(demoName)
|
| 537 |
+
st.session_state.file_paths[demoName] = demoPath
|
| 538 |
+
st.session_state.results.setdefault(demoName, [])
|
| 539 |
+
st.session_state.summaries.setdefault(demoName, {})
|
| 540 |
+
st.session_state.unusedSpeakers.setdefault(demoName, [])
|
| 541 |
+
st.session_state.categorySelect.setdefault(demoName, [[] for _ in st.session_state.categories])
|
| 542 |
+
st.session_state.speakerRenames.setdefault(demoName, {})
|
| 543 |
+
st.session_state.speakerClips.setdefault(demoName, {})
|
| 544 |
+
file_names = st.session_state.file_names
|
| 545 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 546 |
with st.spinner(text=f'Loading Demo Sample'):
|
| 547 |
+
speakerList, annotations = su.loadAudioRTTM(demoPath)
|
|
|
|
|
|
|
| 548 |
totalSeconds = 0
|
| 549 |
for segment in annotations.itersegments():
|
| 550 |
if segment.end > totalSeconds:
|
| 551 |
totalSeconds = segment.end
|
| 552 |
+
st.session_state.results[demoName] = (annotations, totalSeconds)
|
| 553 |
+
st.session_state.summaries[demoName] = {}
|
| 554 |
+
st.session_state.unusedSpeakers[demoName] = list(annotations.labels())
|
|
|
|
|
|
|
|
|
|
| 555 |
with st.spinner(text=f'Analyzing Demo Data'):
|
| 556 |
+
analyze(demoName)
|
| 557 |
st.success(f"Took {time.time() - start_time} seconds to analyze the demo file!")
|
| 558 |
+
st.session_state.select_currFile = demoName
|
| 559 |
isDemo = True
|
| 560 |
|
| 561 |
+
currFile = st.sidebar.selectbox('Current File', file_names, on_change=updateMultiSelect, key="select_currFile")
|
| 562 |
|
| 563 |
if isDemo:
|
| 564 |
+
currFile = file_names[0]
|
| 565 |
isDemo = False
|
| 566 |
+
|
| 567 |
+
if currFile is None and st.session_state.results:
|
| 568 |
st.write("Select a file to view from the sidebar")
|
| 569 |
try:
|
| 570 |
st.session_state.resetResult = False
|
|
|
|
| 571 |
currPlainName = currFile.split('.')[0]
|
| 572 |
+
if currFile in st.session_state.results and currFile in st.session_state.summaries and len(st.session_state.results[currFile]) > 0:
|
| 573 |
st.header(f"Analysis of file {currFile}")
|
| 574 |
graphNames = ["Data","Voice Categories","Speaker Percentage","Speakers with Categories","Treemap","Timeline","Time Spoken"]
|
| 575 |
dataTab, pie1, pie2, sunburst1, treemap1, timeline, bar1 = st.tabs(graphNames)
|
| 576 |
+
currAnnotation, currTotalTime = st.session_state.results[currFile]
|
|
|
|
| 577 |
speakerNames = currAnnotation.labels()
|
| 578 |
+
|
| 579 |
+
speakers_dataFrame = st.session_state.summaries[currFile]["speakers_dataFrame"]
|
| 580 |
currDF, _ = su.annotationToSimpleDataFrame(currAnnotation)
|
| 581 |
+
speakers_times = st.session_state.summaries[currFile]["speakers_times"]
|
| 582 |
+
|
| 583 |
+
unusedSpeakers = st.session_state.unusedSpeakers[currFile]
|
| 584 |
+
categorySelections = st.session_state["categorySelect"][currFile]
|
|
|
|
| 585 |
for i,category in enumerate(st.session_state.categories):
|
| 586 |
speakerSet = categorySelections[i]
|
| 587 |
st.sidebar.multiselect(category,
|
|
|
|
| 589 |
default=speakerSet,
|
| 590 |
key=f"multiselect_{category}",
|
| 591 |
on_change=updateCategoryOptions,
|
| 592 |
+
args=(currFile,))
|
| 593 |
st.sidebar.button(f"Remove {category}",key=f"remove_{category}",on_click=removeCategory,args=(i,))
|
| 594 |
|
| 595 |
|
|
|
|
| 601 |
st.sidebar.caption("Replace SPEAKER_## labels with real names.")
|
| 602 |
|
| 603 |
# --- Speaker clip preview ---
|
| 604 |
+
file_clips = st.session_state.speakerClips.get(currFile, {})
|
|
|
|
|
|
|
|
|
|
|
|
|
| 605 |
if file_clips:
|
| 606 |
st.sidebar.caption(
|
| 607 |
"Listen to each speaker's longest clip (up to 5 s) to help identify them."
|
| 608 |
)
|
| 609 |
|
| 610 |
+
current_renames = st.session_state.speakerRenames[currFile]
|
| 611 |
for sp in speakerNames:
|
| 612 |
+
widget_key = f"rename_{currFile}_{sp}"
|
| 613 |
+
# Seed the widget state once; never pass value= so reruns don't overwrite it
|
|
|
|
| 614 |
if widget_key not in st.session_state:
|
| 615 |
st.session_state[widget_key] = current_renames.get(sp, "")
|
|
|
|
| 616 |
live_name = st.session_state[widget_key].strip()
|
| 617 |
display_label = live_name if live_name else sp
|
| 618 |
st.sidebar.markdown(f"**{display_label}**")
|
| 619 |
if sp in file_clips:
|
| 620 |
st.sidebar.audio(file_clips[sp], format="audio/wav")
|
| 621 |
+
# Label is always the fixed original sp so Streamlit never recreates the widget
|
| 622 |
new_name = st.sidebar.text_input(
|
| 623 |
+
sp,
|
| 624 |
placeholder="e.g. John",
|
| 625 |
+
key=widget_key,
|
| 626 |
+
label_visibility="collapsed"
|
| 627 |
)
|
| 628 |
if new_name.strip():
|
| 629 |
+
st.session_state.speakerRenames[currFile][sp] = new_name.strip()
|
| 630 |
+
elif sp in st.session_state.speakerRenames[currFile]:
|
| 631 |
+
del st.session_state.speakerRenames[currFile][sp]
|
| 632 |
|
| 633 |
catTypeColors = su.colorsCSS(3)
|
| 634 |
allColors = su.colorsCSS(len(speakerNames)+len(st.session_state.categories))
|
|
|
|
| 655 |
}
|
| 656 |
df4 = pd.DataFrame(data=df4_dict)
|
| 657 |
df4.name = "df4"
|
| 658 |
+
st.session_state.summaries[currFile]["df4"] = df4
|
| 659 |
|
| 660 |
with dataTab:
|
| 661 |
+
displayDF = apply_speaker_renames_to_df(currDF, currFile, column="Resource")
|
| 662 |
csv = convert_df(displayDF)
|
| 663 |
|
| 664 |
st.download_button(
|
|
|
|
| 672 |
st.dataframe(displayDF)
|
| 673 |
with pie1:
|
| 674 |
printV("In Pie1",4)
|
| 675 |
+
df3 = st.session_state.summaries[currFile]["df3"]
|
| 676 |
fig1 = go.Figure()
|
| 677 |
fig1.update_layout(
|
| 678 |
title_text="Percentage of each Voice Category",
|
|
|
|
| 721 |
|
| 722 |
with pie2:
|
| 723 |
printV("In Pie2",4)
|
| 724 |
+
df4 = st.session_state.summaries[currFile]["df4"].copy()
|
| 725 |
|
| 726 |
# Some speakers may be missing, so fix colors
|
| 727 |
figColors = []
|
| 728 |
for n in df4["names"]:
|
| 729 |
if n in speakerNames:
|
| 730 |
figColors.append(speakerColors[speakerNames.index(n)])
|
| 731 |
+
df4["names"] = df4["names"].apply(lambda s: get_display_name(s, currFile))
|
| 732 |
fig2 = go.Figure()
|
| 733 |
fig2.update_layout(
|
| 734 |
title_text="Percentage of Speakers and Custom Categories",
|
|
|
|
| 770 |
)
|
| 771 |
|
| 772 |
with sunburst1:
|
| 773 |
+
df5 = st.session_state.summaries[currFile]["df5"].copy()
|
| 774 |
+
df5["labels"] = df5["labels"].apply(lambda s: get_display_name(s, currFile))
|
| 775 |
+
df5["parentNames"] = df5["parentNames"].apply(lambda s: get_display_name(s, currFile))
|
| 776 |
fig3_1 = px.sunburst(df5,
|
| 777 |
branchvalues = 'total',
|
| 778 |
names = "labels",
|
|
|
|
| 828 |
)
|
| 829 |
|
| 830 |
with treemap1:
|
| 831 |
+
df5 = st.session_state.summaries[currFile]["df5"].copy()
|
| 832 |
+
df5["labels"] = df5["labels"].apply(lambda s: get_display_name(s, currFile))
|
| 833 |
+
df5["parentNames"] = df5["parentNames"].apply(lambda s: get_display_name(s, currFile))
|
| 834 |
fig3 = px.treemap(df5,
|
| 835 |
branchvalues = "total",
|
| 836 |
names = "labels",
|
|
|
|
| 890 |
|
| 891 |
with timeline:
|
| 892 |
timeline_df = speakers_dataFrame.copy()
|
| 893 |
+
timeline_df["Resource"] = timeline_df["Resource"].apply(lambda s: get_display_name(s, currFile))
|
| 894 |
base = dt.datetime.combine(dt.date.today(), dt.time.min)
|
| 895 |
def to_audio_datetime(s):
|
| 896 |
# If already a datetime/Timestamp, extract seconds since midnight of that date
|
|
|
|
| 959 |
)
|
| 960 |
|
| 961 |
with bar1:
|
| 962 |
+
df2 = st.session_state.summaries[currFile]["df2"].copy()
|
| 963 |
+
df2["names"] = df2["names"].apply(lambda s: get_display_name(s, currFile))
|
| 964 |
fig2_la = px.bar(df2, x="values", y="names", color="names", orientation='h',
|
| 965 |
custom_data=["names","values"],title="Time Spoken by each Speaker",
|
| 966 |
color_discrete_sequence=catColors+speakerColors)
|
|
|
|
| 1019 |
st.header("Multi-file Summary Data")
|
| 1020 |
with st.spinner(text='Processing summary results...'):
|
| 1021 |
fileNames = st.session_state.file_names
|
| 1022 |
+
validNames = [fn for fn in fileNames if fn in st.session_state.results and len(st.session_state.results[fn]) == 2]
|
| 1023 |
+
if len(validNames) > 1:
|
| 1024 |
+
|
| 1025 |
+
df6_dict = {"files": validNames}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1026 |
allCategories = copy.deepcopy(st.session_state.categories)
|
| 1027 |
+
for fn in validNames:
|
| 1028 |
+
currAnnotation, currTotalTime = st.session_state.results[fn]
|
| 1029 |
+
categorySelections = st.session_state["categorySelect"][fn]
|
| 1030 |
+
catSummary, extraCats = su.calcCategories(currAnnotation, categorySelections)
|
| 1031 |
+
st.session_state.summaries[fn]["categories"] = (catSummary, extraCats)
|
| 1032 |
for extra in extraCats:
|
| 1033 |
df6_dict[extra] = []
|
| 1034 |
if extra not in allCategories:
|
| 1035 |
allCategories.append(extra)
|
| 1036 |
+
|
|
|
|
| 1037 |
for category in st.session_state.categories:
|
| 1038 |
df6_dict[category] = []
|
| 1039 |
+
for fn in validNames:
|
| 1040 |
+
summary, extras = st.session_state.summaries[fn]["categories"]
|
| 1041 |
theseCategories = st.session_state.categories + extras
|
| 1042 |
for j, timeSlots in enumerate(summary):
|
| 1043 |
+
df6_dict[theseCategories[j]].append(sum([t.duration for _,t in timeSlots])/st.session_state.results[fn][1])
|
| 1044 |
for category in allCategories:
|
| 1045 |
if category not in theseCategories:
|
| 1046 |
df6_dict[category].append(0)
|
|
|
|
| 1055 |
}
|
| 1056 |
for category in voiceNames:
|
| 1057 |
df7_dict[category] = []
|
| 1058 |
+
for fn in validNames:
|
| 1059 |
+
partialDf = st.session_state.summaries[fn]["df5"]
|
| 1060 |
for i in range(len(voiceNames)):
|
| 1061 |
df7_dict[voiceNames[i]].append(partialDf["percentiles"][i])
|
| 1062 |
df7 = pd.DataFrame(df7_dict)
|