Threshold and min-interval (the librosa delta/wait knobs) were only ever tunable via the CLI's -t/-m flags. Added a Clap Detection Settings dialog (Tools menu) with both as spin boxes, an explicit Reset to Defaults button, and Cancel that leaves MainWindow's current settings untouched. Exposed DEFAULT_THRESHOLD=0.7/DEFAULT_MIN_INTERVAL=0.3 as named constants in clap_detection.py so the CLI script, DetectWorker's defaults, and the dialog's reset button all reference the same source of truth instead of three copies of the same magic numbers that could drift apart.
80 lines
2.6 KiB
Python
Executable File
80 lines
2.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Detect sharp audio onsets (claps, snaps) in audio/video files.
|
|
Outputs timestamps in hh:mm:ss format.
|
|
"""
|
|
|
|
import sys
|
|
import argparse
|
|
import os
|
|
from datetime import timedelta
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
from clap_detection import detect_claps, ClapDetectionUnavailable, DEFAULT_THRESHOLD, DEFAULT_MIN_INTERVAL
|
|
|
|
|
|
def format_timestamp(seconds):
|
|
"""Convert seconds to hh:mm:ss.mmm format."""
|
|
td = timedelta(seconds=seconds)
|
|
total_seconds = int(td.total_seconds())
|
|
hours = total_seconds // 3600
|
|
minutes = (total_seconds % 3600) // 60
|
|
secs = td.total_seconds() % 60
|
|
return f"{hours:02d}:{minutes:02d}:{secs:06.3f}"
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description='Detect claps/snaps in audio/video files',
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
epilog="""
|
|
Examples:
|
|
%(prog)s audio.wav
|
|
%(prog)s video.mp4 -t 0.7 -m 1.0
|
|
%(prog)s audio.wav -t 0.3 -s # Show onset strengths
|
|
|
|
Threshold guide:
|
|
0.3 = Very sensitive (detects most transients)
|
|
0.5 = Moderate (typical claps/snaps)
|
|
0.7 = Less sensitive (only loud, sharp claps)
|
|
0.9 = Very strict (only very loud events)
|
|
"""
|
|
)
|
|
|
|
parser.add_argument('input_file', help='Audio or video file')
|
|
parser.add_argument('-t', '--threshold', type=float, default=DEFAULT_THRESHOLD,
|
|
help=f'Detection threshold (0-1, default: {DEFAULT_THRESHOLD}, higher = less sensitive)')
|
|
parser.add_argument('-m', '--min-interval', type=float, default=DEFAULT_MIN_INTERVAL,
|
|
help=f'Minimum seconds between detections (default: {DEFAULT_MIN_INTERVAL})')
|
|
parser.add_argument('-s', '--show-strength', action='store_true',
|
|
help='Show onset strength values')
|
|
|
|
args = parser.parse_args()
|
|
|
|
if not os.path.exists(args.input_file):
|
|
print(f"Error: File not found: {args.input_file}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
try:
|
|
events = detect_claps(
|
|
args.input_file,
|
|
threshold=args.threshold,
|
|
min_interval=args.min_interval,
|
|
)
|
|
except ClapDetectionUnavailable as e:
|
|
print(f"Error: {e}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
print(f"\nFound {len(events)} onset(s):\n", file=sys.stderr)
|
|
for event in events:
|
|
timestamp = format_timestamp(event.time_sec)
|
|
if args.show_strength:
|
|
print(f"{timestamp} (strength: {event.strength:.2f})")
|
|
else:
|
|
print(timestamp)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|