Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -53,7 +53,51 @@ def apply_speaker_renames_to_df(df, fileIndex, column="task"):
|
|
| 53 |
df = df.copy()
|
| 54 |
df[column] = df[column].apply(lambda s: get_display_name(s, fileIndex))
|
| 55 |
return df
|
| 56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
@st.cache_data
|
| 58 |
def convert_df(df):
|
| 59 |
return df.to_csv(index=False).encode('utf-8')
|
|
@@ -115,23 +159,7 @@ def processFile(filePath):
|
|
| 115 |
totalTimeInSeconds = int(waveform_gain_adjusted.shape[-1]/sampleRate)
|
| 116 |
print("Time in seconds calculated")
|
| 117 |
return annotations, totalTimeInSeconds
|
| 118 |
-
|
| 119 |
-
@st.cache_data
|
| 120 |
-
def extract_speaker_clip(file_path, speaker, _annotation, clip_duration=5.0):
|
| 121 |
-
waveform, sample_rate = torchaudio.load(file_path)
|
| 122 |
-
for segment, _, label in _annotation.itertracks(yield_label=True):
|
| 123 |
-
if label == speaker and segment.duration >= 1.0:
|
| 124 |
-
start_sample = int(segment.start * sample_rate)
|
| 125 |
-
end_sample = int(min(segment.start + clip_duration, segment.end) * sample_rate)
|
| 126 |
-
clip = waveform[:, start_sample:end_sample]
|
| 127 |
-
# Convert to wav bytes in memory
|
| 128 |
-
import io
|
| 129 |
-
buf = io.BytesIO()
|
| 130 |
-
torchaudio.save(buf, clip, sample_rate, format="wav")
|
| 131 |
-
buf.seek(0)
|
| 132 |
-
return buf.read()
|
| 133 |
-
return None
|
| 134 |
-
|
| 135 |
def addCategory():
|
| 136 |
newCategory = st.session_state.categoryInput
|
| 137 |
st.toast(f"Adding {newCategory}")
|
|
@@ -622,21 +650,39 @@ try:
|
|
| 622 |
|
| 623 |
newCategory = st.sidebar.text_input('Add category', key='categoryInput',on_change=addCategory)
|
| 624 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 625 |
st.sidebar.divider()
|
| 626 |
st.sidebar.subheader("Rename Speakers")
|
| 627 |
-
st.sidebar.caption("Replace SPEAKER_## labels with real names.
|
| 628 |
current_renames = st.session_state.speakerRenames[currFileIndex]
|
| 629 |
-
currFilePath = st.session_state.file_paths[currFileIndex]
|
| 630 |
for sp in speakerNames:
|
| 631 |
current_label = current_renames.get(sp, "")
|
| 632 |
-
st.sidebar.markdown(f"**{sp}**")
|
| 633 |
-
# Only show audio player for real audio files (not rttm/csv/txt)
|
| 634 |
-
if currFilePath.lower().endswith(('.wav', '.mp3', '.mp4')):
|
| 635 |
-
clip_bytes = extract_speaker_clip(currFilePath, sp, currAnnotation)
|
| 636 |
-
if clip_bytes:
|
| 637 |
-
st.sidebar.audio(clip_bytes, format="audio/wav")
|
| 638 |
new_name = st.sidebar.text_input(
|
| 639 |
-
f"
|
| 640 |
value=current_label,
|
| 641 |
placeholder=f"e.g. John",
|
| 642 |
key=f"rename_{currFileIndex}_{sp}"
|
|
|
|
| 53 |
df = df.copy()
|
| 54 |
df[column] = df[column].apply(lambda s: get_display_name(s, fileIndex))
|
| 55 |
return df
|
| 56 |
+
|
| 57 |
+
def extract_speaker_clip(audio_path, annotation, speaker, clip_duration=5):
|
| 58 |
+
"""
|
| 59 |
+
Extract a clip of up to clip_duration seconds for the given speaker.
|
| 60 |
+
Prefers the first segment >= clip_duration seconds; falls back to the longest segment.
|
| 61 |
+
Returns bytes (WAV) or None on failure.
|
| 62 |
+
"""
|
| 63 |
+
try:
|
| 64 |
+
import io
|
| 65 |
+
# Collect all segments for this speaker
|
| 66 |
+
speaker_segments = [
|
| 67 |
+
(seg.start, seg.end)
|
| 68 |
+
for seg, _, label in annotation.itertracks(yield_label=True)
|
| 69 |
+
if label == speaker
|
| 70 |
+
]
|
| 71 |
+
if not speaker_segments:
|
| 72 |
+
return None
|
| 73 |
+
|
| 74 |
+
# Prefer first segment long enough for a full clip
|
| 75 |
+
chosen_start, chosen_end = None, None
|
| 76 |
+
for start, end in speaker_segments:
|
| 77 |
+
if (end - start) >= clip_duration:
|
| 78 |
+
chosen_start, chosen_end = start, start + clip_duration
|
| 79 |
+
break
|
| 80 |
+
|
| 81 |
+
# Fall back: use the longest segment available
|
| 82 |
+
if chosen_start is None:
|
| 83 |
+
longest = max(speaker_segments, key=lambda s: s[1] - s[0])
|
| 84 |
+
chosen_start, chosen_end = longest
|
| 85 |
+
|
| 86 |
+
# Load audio and slice the clip
|
| 87 |
+
waveform, sample_rate = torchaudio.load(audio_path)
|
| 88 |
+
start_frame = int(chosen_start * sample_rate)
|
| 89 |
+
end_frame = min(int(chosen_end * sample_rate), waveform.shape[-1])
|
| 90 |
+
clip = waveform[:, start_frame:end_frame]
|
| 91 |
+
|
| 92 |
+
# Encode as WAV in memory
|
| 93 |
+
buffer = io.BytesIO()
|
| 94 |
+
torchaudio.save(buffer, clip, sample_rate, format="wav")
|
| 95 |
+
buffer.seek(0)
|
| 96 |
+
return buffer.read()
|
| 97 |
+
except Exception as e:
|
| 98 |
+
print(f"extract_speaker_clip error for {speaker}: {e}")
|
| 99 |
+
return None
|
| 100 |
+
|
| 101 |
@st.cache_data
|
| 102 |
def convert_df(df):
|
| 103 |
return df.to_csv(index=False).encode('utf-8')
|
|
|
|
| 159 |
totalTimeInSeconds = int(waveform_gain_adjusted.shape[-1]/sampleRate)
|
| 160 |
print("Time in seconds calculated")
|
| 161 |
return annotations, totalTimeInSeconds
|
| 162 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
def addCategory():
|
| 164 |
newCategory = st.session_state.categoryInput
|
| 165 |
st.toast(f"Adding {newCategory}")
|
|
|
|
| 650 |
|
| 651 |
newCategory = st.sidebar.text_input('Add category', key='categoryInput',on_change=addCategory)
|
| 652 |
|
| 653 |
+
# --- Speaker Sample Clips (audio files only) ---
|
| 654 |
+
_is_audio_file = currFile is not None and not any(
|
| 655 |
+
currFile.lower().endswith(ext) for ext in ('.rttm', '.txt', '.csv')
|
| 656 |
+
)
|
| 657 |
+
if _is_audio_file:
|
| 658 |
+
st.sidebar.divider()
|
| 659 |
+
st.sidebar.subheader("\U0001f3a7 Speaker Samples")
|
| 660 |
+
st.sidebar.caption(
|
| 661 |
+
"Listen to a 5-second clip for each speaker to help identify them."
|
| 662 |
+
)
|
| 663 |
+
_curr_audio_path = file_paths[currFileIndex] if currFileIndex < len(file_paths) else None
|
| 664 |
+
if _curr_audio_path and os.path.exists(_curr_audio_path):
|
| 665 |
+
for sp in speakerNames:
|
| 666 |
+
_display = get_display_name(sp, currFileIndex)
|
| 667 |
+
st.sidebar.markdown(f"**{_display}**")
|
| 668 |
+
_clip_bytes = extract_speaker_clip(
|
| 669 |
+
_curr_audio_path, currAnnotation, sp, clip_duration=5
|
| 670 |
+
)
|
| 671 |
+
if _clip_bytes:
|
| 672 |
+
st.sidebar.audio(_clip_bytes, format="audio/wav")
|
| 673 |
+
else:
|
| 674 |
+
st.sidebar.caption("_(No audio segment found)_")
|
| 675 |
+
else:
|
| 676 |
+
st.sidebar.caption("_(Audio file not available for preview)_")
|
| 677 |
+
|
| 678 |
st.sidebar.divider()
|
| 679 |
st.sidebar.subheader("Rename Speakers")
|
| 680 |
+
st.sidebar.caption("Replace SPEAKER_## labels with real names.")
|
| 681 |
current_renames = st.session_state.speakerRenames[currFileIndex]
|
|
|
|
| 682 |
for sp in speakerNames:
|
| 683 |
current_label = current_renames.get(sp, "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 684 |
new_name = st.sidebar.text_input(
|
| 685 |
+
f"{sp}",
|
| 686 |
value=current_label,
|
| 687 |
placeholder=f"e.g. John",
|
| 688 |
key=f"rename_{currFileIndex}_{sp}"
|