From 678d6d15683d80f91dba60900c156a7ea9d31341 Mon Sep 17 00:00:00 2001 From: youfufan Date: Thu, 1 Jan 2026 10:00:00 +0000 Subject: [PATCH] =?UTF-8?q?v1:=20svd=20prototype=20=E2=80=94=20single-file?= =?UTF-8?q?=20singing=20detector?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HuggingFace matthijs/svd (torch.jit) model + ffmpeg audio extract + tkinter file picker. Proves the concept in one script. Co-Authored-By: Claude Opus 4.8 --- .gitignore | 7 ++++ sing_detect.py | 108 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 .gitignore create mode 100644 sing_detect.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c76f48e --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +__pycache__/ +*.pyc +*.wav +*.mp4 +.DS_Store +venv/ +.venv/ diff --git a/sing_detect.py b/sing_detect.py new file mode 100644 index 0000000..5504563 --- /dev/null +++ b/sing_detect.py @@ -0,0 +1,108 @@ +import subprocess +import os +import torch +import librosa +import numpy as np +from huggingface_hub import hf_hub_download +import tkinter as tk +from tkinter import filedialog + +# ------------------------ +# 1. Extract audio from video +# ------------------------ +def extract_audio(input_video, output_wav): + cmd = [ + "ffmpeg", "-y", + "-i", input_video, + "-vn", # no video + "-ac", "1", # mono + "-ar", "16000", # 16kHz + output_wav + ] + subprocess.run(cmd, check=True) + +# ------------------------ +# 2. Load pretrained model +# ------------------------ +def load_model(): + repo_id = "matthijs/svd" # HuggingFace singing voice detection model + filename = hf_hub_download(repo_id=repo_id, filename="model.pt") + model = torch.jit.load(filename) + model.eval() + return model + +# ------------------------ +# 3. Run inference on audio +# ------------------------ +def detect_singing(audio_path, model, hop_length=512, threshold=0.5, min_duration=10.0): + y, sr = librosa.load(audio_path, sr=16000) + x = torch.tensor(y).float().unsqueeze(0) + + with torch.no_grad(): + pred = model(x).squeeze().numpy() + + times = librosa.frames_to_time(np.arange(len(pred)), sr=sr, hop_length=hop_length) + + segments = [] + in_segment = False + seg_start = None + + for t, p in zip(times, pred): + if p > threshold and not in_segment: + in_segment = True + seg_start = t + elif p <= threshold and in_segment: + in_segment = False + seg_end = t + if seg_end - seg_start >= min_duration: + segments.append((seg_start, seg_end)) + + if in_segment: + seg_end = times[-1] + if seg_end - seg_start >= min_duration: + segments.append((seg_start, seg_end)) + + return segments + +# ------------------------ +# 4. Export CSV +# ------------------------ +def export_csv(segments, out_file="segments.csv"): + with open(out_file, "w", encoding="utf-8") as f: + f.write("start_time,end_time\n") + for s, e in segments: + f.write(f"{s:.2f},{e:.2f}\n") + print(f"[+] Saved: {out_file}") + +# ------------------------ +# MAIN +# ------------------------ +if __name__ == "__main__": + # Tkinter GUI to choose file + root = tk.Tk() + root.withdraw() + input_video = filedialog.askopenfilename( + title="Choose your video file", + filetypes=[("Video files", "*.mp4 *.mkv *.flv *.mov *.avi"), ("All files", "*.*")] + ) + if not input_video: + print("No file selected. Exiting.") + exit() + + audio_file = "audio.wav" + + print(f"[+] Processing: {input_video}") + + # Step 1: extract audio + extract_audio(input_video, audio_file) + + # Step 2: load model + model = load_model() + + # Step 3: run detection + segments = detect_singing(audio_file, model) + + # Step 4: save results + export_csv(segments, "segments.csv") + + print("[+] Done! Detected singing segments have been saved to segments.csv")