Skip to content

Instantly share code, notes, and snippets.

@tasdemirbahadir
Last active February 14, 2026 09:11
Show Gist options
  • Select an option

  • Save tasdemirbahadir/e7b002096762d688252df7a6a828b296 to your computer and use it in GitHub Desktop.

Select an option

Save tasdemirbahadir/e7b002096762d688252df7a6a828b296 to your computer and use it in GitHub Desktop.
song instrument removed
#!/usr/bin/env python3
"""
Parametric Drum or Guitar Removal Tool using Demucs
Removes either drums or guitar from WAV files based on user input.
"""
import os
import argparse
import subprocess
import sys
import shutil
from pathlib import Path
def check_ffmpeg():
"""Check if FFmpeg is available in the system PATH."""
try:
subprocess.run(["ffmpeg", "-version"], capture_output=True, check=True, text=True)
return True
except (subprocess.CalledProcessError, FileNotFoundError):
return False
def check_demucs():
"""Check if Demucs is properly installed."""
try:
subprocess.run(["demucs", "--help"], capture_output=True, check=True, text=True)
return True
except (subprocess.CalledProcessError, FileNotFoundError):
return False
def install_demucs():
"""Install Demucs using pip."""
try:
subprocess.run([sys.executable, "-m", "pip", "install", "-U", "demucs"],
check=True, text=True)
return True
except subprocess.CalledProcessError:
return False
def get_model_for_stem(target_stems):
"""
Determine the appropriate Demucs model based on the target stem(s).
Args:
target_stems (list): List of stems to remove ('drums' and/or 'guitar')
Returns:
str: Appropriate Demucs model name
"""
# Convert single stem to list for backwards compatibility
if isinstance(target_stems, str):
target_stems = [target_stems]
# Use 6-stem model if guitar is in the list
if "guitar" in target_stems:
# Use the 6-stem model for guitar separation
return "htdemucs_6s"
else:
# Use standard model for drums only
return "htdemucs"
def separate_audio_demucs(input_wav, target_stems, output_dir=None, model_name=None):
"""
Use Demucs to separate audio into stems and remove the specified target stem(s).
Args:
input_wav (str): Path to input WAV file
target_stems (list or str): Stem(s) to remove ('drums' and/or 'guitar')
output_dir (str, optional): Output directory for results
model_name (str, optional): Specific Demucs model to use
Returns:
str: Path to the processed WAV file without the target stem(s)
"""
# Convert single stem to list for backwards compatibility
if isinstance(target_stems, str):
target_stems = [target_stems]
if output_dir is None:
output_dir = "./separated"
if model_name is None:
model_name = get_model_for_stem(target_stems)
# Create output directory if it doesn't exist
Path(output_dir).mkdir(exist_ok=True, parents=True)
# Build Demucs command based on target stems
# Only use --two-stems optimization if removing drums only
if len(target_stems) == 1 and target_stems[0] == "drums":
# Use --two-stems for drums-only removal (more efficient)
cmd = [
"demucs",
"--two-stems", "drums",
"-n", model_name,
"-o", output_dir,
input_wav
]
else:
# For guitar or multiple stems, we need to separate all stems and recombine
cmd = [
"demucs",
"-n", model_name,
"-o", output_dir,
input_wav
]
print(f"[+] Separating audio using Demucs model: {model_name}")
print(f"[+] Target stem(s) for removal: {', '.join(target_stems)}")
print(f"[+] Command: {' '.join(cmd)}")
try:
# Run Demucs
result = subprocess.run(cmd, check=True, text=True, capture_output=True)
print("[+] Demucs separation completed successfully")
# Find the output files
input_stem = Path(input_wav).stem
model_output_dir = Path(output_dir) / model_name / input_stem
if not model_output_dir.exists():
raise FileNotFoundError(f"Demucs output directory not found: {model_output_dir}")
# Handle different output structures based on target stems
if len(target_stems) == 1 and target_stems[0] == "drums":
# For drums-only removal with --two-stems, look for no_drums.wav
output_file = model_output_dir / "no_drums.wav"
if output_file.exists():
return str(output_file)
else:
raise FileNotFoundError("Could not find drumless output file")
else:
# For guitar or multiple stems removal, we need to combine all stems except targets
return combine_stems_except_target(model_output_dir, target_stems, output_dir)
except subprocess.CalledProcessError as e:
print(f"[-] Demucs failed with error: {e.stderr}")
raise
def combine_stems_except_target(stems_dir, target_stems, output_dir):
"""
Combine all audio stems except the target stem(s) into a single WAV file.
Args:
stems_dir (Path): Directory containing separated stems
target_stems (list or str): Stem(s) to exclude from combination
output_dir (str): Output directory for the combined file
Returns:
str: Path to the combined WAV file
"""
# Convert single stem to list for backwards compatibility
if isinstance(target_stems, str):
target_stems = [target_stems]
try:
import soundfile as sf
import numpy as np
# Map target stem to actual filenames
stem_mapping = {
"drums": "drums.wav",
"guitar": "guitar.wav",
"bass": "bass.wav",
"vocals": "vocals.wav",
"piano": "piano.wav",
"other": "other.wav"
}
# Validate all target stems
for target_stem in target_stems:
if target_stem not in stem_mapping:
raise ValueError(f"Unsupported target stem: {target_stem}")
# Find all stem files
stem_files = []
for stem_name, filename in stem_mapping.items():
file_path = stems_dir / filename
if file_path.exists():
stem_files.append((stem_name, file_path))
if not stem_files:
raise ValueError("No stem files found in the directory")
# Load all audio files except the target stems
audio_data = []
sample_rate = None
for stem_name, audio_file in stem_files:
if stem_name in target_stems:
print(f"[+] Excluding target stem: {stem_name}")
continue
data, sr = sf.read(audio_file)
if sample_rate is None:
sample_rate = sr
elif sample_rate != sr:
raise ValueError("Sample rate mismatch between files")
# Ensure proper shape for mono/stereo
if len(data.shape) == 1:
data = data.reshape(-1, 1)
elif data.shape[1] > 2: # Handle multi-channel if needed
data = data[:, :2] # Take first two channels
audio_data.append(data)
print(f"[+] Including stem: {stem_name}")
if not audio_data:
raise ValueError("No audio data to combine after exclusion")
# Mix all stems together
mixed_audio = sum(audio_data)
# Normalize to prevent clipping
max_val = np.max(np.abs(mixed_audio))
if max_val > 1.0:
mixed_audio = mixed_audio / max_val * 0.99 # Leave some headroom
# Create output filename
input_stem = stems_dir.parent.name
stems_suffix = "_".join(sorted(target_stems))
output_filename = Path(output_dir) / f"{input_stem}_no_{stems_suffix}.wav"
# Save the combined audio
sf.write(output_filename, mixed_audio, sample_rate)
print(f"[+] Combined audio saved to: {output_filename}")
return str(output_filename)
except ImportError:
print("[-] soundfile library required for audio combination")
print("[+] Install it with: pip install soundfile")
raise
def main():
parser = argparse.ArgumentParser(description='Remove drums and/or guitar from WAV files using Demucs')
parser.add_argument('input_file', help='Input WAV file path')
parser.add_argument('target_stem', nargs='+', choices=['drums', 'guitar'],
help='Stem(s) to remove from the audio (can specify one or both)')
parser.add_argument('-o', '--output', help='Output WAV file path', default=None)
parser.add_argument('-m', '--model', help='Demucs model name', default=None)
parser.add_argument('-d', '--output-dir', help='Temporary output directory', default='./separated')
args = parser.parse_args()
# Check if input file exists
if not os.path.exists(args.input_file):
print(f"Error: Input file {args.input_file} not found")
sys.exit(1)
# Validate target stems and remove duplicates
args.target_stem = list(set(args.target_stem))
for stem in args.target_stem:
if stem not in ['drums', 'guitar']:
print("Error: Target stems must be 'drums' and/or 'guitar'")
sys.exit(1)
# Check FFmpeg installation
if not check_ffmpeg():
print("Error: FFmpeg not found. Please install FFmpeg and add it to your system PATH.")
print("Download from: https://ffmpeg.org/download.html")
sys.exit(1)
# Check Demucs installation
if not check_demucs():
print("Demucs not found. Attempting to install...")
if not install_demucs():
print("Error: Failed to install Demucs. Please install manually:")
print("pip install demucs")
sys.exit(1)
print("Demucs installed successfully")
# Set output path
if args.output is None:
input_stem = Path(args.input_file).stem
stems_suffix = "_".join(sorted(args.target_stem))
args.output = f"{input_stem}_no_{stems_suffix}.wav"
try:
# Separate audio and remove target stem
processed_audio = separate_audio_demucs(
args.input_file,
args.target_stem,
args.output_dir,
args.model
)
# Copy the result to the desired output location
shutil.copy2(processed_audio, args.output)
print(f"[+] Processed audio saved to: {args.output}")
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
print("[+] Process completed successfully!")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment