Last active
February 14, 2026 09:11
-
-
Save tasdemirbahadir/e7b002096762d688252df7a6a828b296 to your computer and use it in GitHub Desktop.
song instrument removed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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