v5: refactor into modular stream_tools desktop app
Same inaSpeechSegmenter engine split into reusable core/ (fftools, segmenter, exporters) + utils/ and a CustomTkinter multi-tab UI. Adds JSON and ffmpeg-script export. Extracted from the stream_tools app. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
118
core/segmenter.py
Normal file
118
core/segmenter.py
Normal file
@@ -0,0 +1,118 @@
|
||||
"""
|
||||
Singing segment detection via inaSpeechSegmenter.
|
||||
|
||||
Pipeline:
|
||||
1. Run inaSpeechSegmenter to classify speech/music/noise
|
||||
2. Filter for 'music' segments (singing voice is classified as music)
|
||||
3. Merge nearby segments, apply padding, filter by minimum duration
|
||||
"""
|
||||
|
||||
import time
|
||||
from utils.formats import format_timecode
|
||||
|
||||
|
||||
def check_segmenter_available() -> tuple[bool, str]:
|
||||
"""Check if inaSpeechSegmenter is installed."""
|
||||
try:
|
||||
from inaSpeechSegmenter import Segmenter # noqa: F401
|
||||
return True, ""
|
||||
except ImportError:
|
||||
return False, (
|
||||
"inaSpeechSegmenter is not installed.\n\n"
|
||||
"Install it with:\n"
|
||||
" pip install inaSpeechSegmenter\n\n"
|
||||
"You also need TensorFlow:\n"
|
||||
" pip install tensorflow[and-cuda] (GPU)\n"
|
||||
" pip install tensorflow (CPU)"
|
||||
)
|
||||
|
||||
|
||||
def run_segmentation(wav_path: str, progress_cb=None) -> list:
|
||||
"""
|
||||
Run inaSpeechSegmenter on audio file.
|
||||
Returns list of (label, start_sec, end_sec) tuples.
|
||||
Labels: 'music', 'speech', 'noise', 'noEnergy'
|
||||
"""
|
||||
from inaSpeechSegmenter import Segmenter
|
||||
|
||||
if progress_cb:
|
||||
progress_cb("Loading segmentation model…")
|
||||
|
||||
start_time = time.time()
|
||||
seg = Segmenter(vad_engine='smn', detect_gender=False)
|
||||
|
||||
if progress_cb:
|
||||
progress_cb("Running audio segmentation (this may take a while)…")
|
||||
|
||||
segments = seg(wav_path)
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
if progress_cb:
|
||||
progress_cb(f"Segmentation complete in {elapsed:.1f}s — {len(segments)} raw segments")
|
||||
|
||||
return segments
|
||||
|
||||
|
||||
def merge_singing_segments(
|
||||
segments: list,
|
||||
max_gap: float = 10.0,
|
||||
min_duration: float = 20.0,
|
||||
padding: float = 2.0,
|
||||
) -> list:
|
||||
"""
|
||||
Filter for 'music' segments and merge nearby ones.
|
||||
|
||||
Returns list of dicts:
|
||||
[{"index", "start", "end", "duration", "original_start", "original_end"}, ...]
|
||||
"""
|
||||
music_segs = [(start, end) for label, start, end in segments if label == "music"]
|
||||
|
||||
if not music_segs:
|
||||
return []
|
||||
|
||||
music_segs.sort(key=lambda x: x[0])
|
||||
|
||||
# Merge segments with gaps smaller than max_gap
|
||||
merged = []
|
||||
current_start, current_end = music_segs[0]
|
||||
|
||||
for start, end in music_segs[1:]:
|
||||
if start - current_end <= max_gap:
|
||||
current_end = max(current_end, end)
|
||||
else:
|
||||
merged.append((current_start, current_end))
|
||||
current_start, current_end = start, end
|
||||
merged.append((current_start, current_end))
|
||||
|
||||
# Apply padding and minimum duration filter
|
||||
results = []
|
||||
idx = 1
|
||||
for start, end in merged:
|
||||
duration = end - start
|
||||
if duration >= min_duration:
|
||||
padded_start = max(0, start - padding)
|
||||
padded_end = end + padding
|
||||
results.append({
|
||||
"index": idx,
|
||||
"start": padded_start,
|
||||
"end": padded_end,
|
||||
"duration": padded_end - padded_start,
|
||||
"original_start": start,
|
||||
"original_end": end,
|
||||
})
|
||||
idx += 1
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def get_segment_stats(segments: list) -> dict:
|
||||
"""Get summary statistics from raw segmentation output."""
|
||||
type_counts = {}
|
||||
type_durations = {}
|
||||
for label, start, end in segments:
|
||||
type_counts[label] = type_counts.get(label, 0) + 1
|
||||
type_durations[label] = type_durations.get(label, 0) + (end - start)
|
||||
return {
|
||||
"counts": type_counts,
|
||||
"durations": type_durations,
|
||||
}
|
||||
Reference in New Issue
Block a user