rlackey Claude Opus 4.5 commited on
Commit
f8bc6a3
·
1 Parent(s): ed6615e

Use ZeroGPU for stem separation in song package

Browse files

- Update separate_stems to support two_stem parameter for 2-pass separation
- Song package now uses GPU-accelerated Demucs for both passes
- Pass 1: vocals/instrumental (2-stem mode)
- Pass 2: drums, bass, guitar, keys, other, vocals (6-stem mode)
- Proper quota error handling for both passes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Files changed (1) hide show
  1. app.py +53 -38
app.py CHANGED
@@ -400,18 +400,26 @@ def download_youtube(url: str) -> tuple:
400
  return None, str(e)
401
 
402
  @spaces.GPU(duration=120)
403
- def _separate_stems_gpu(audio_path: str, progress=None) -> dict:
404
  """Separate audio into stems using Demucs (GPU accelerated) - internal"""
405
  try:
406
  import subprocess
407
- stems_dir = OUTPUT_DIR / f"stems_{datetime.now().strftime('%H%M%S')}"
 
408
  stems_dir.mkdir(exist_ok=True)
409
 
410
  if progress:
411
- progress(0.3, "Running Demucs stem separation...")
 
 
 
 
 
 
 
 
 
412
 
413
- # Use GPU if available
414
- cmd = ['python', '-m', 'demucs', '-o', str(stems_dir), audio_path]
415
  subprocess.run(cmd, capture_output=True, check=True)
416
 
417
  # Find output stems
@@ -419,18 +427,24 @@ def _separate_stems_gpu(audio_path: str, progress=None) -> dict:
419
  song_dir = list(model_dir.glob('*'))[0]
420
 
421
  stems = {}
422
- for stem_file in song_dir.glob('*.wav'):
423
  stem_name = stem_file.stem
424
  stems[stem_name] = str(stem_file)
425
 
426
- return stems
 
 
 
 
 
 
427
  except Exception as e:
428
  return {'error': str(e)}
429
 
430
- def separate_stems(audio_path: str, progress=None) -> dict:
431
  """Separate stems - wrapper with quota error handling"""
432
  try:
433
- return _separate_stems_gpu(audio_path, progress)
434
  except Exception as e:
435
  return {'error': handle_gpu_error(e)}
436
 
@@ -619,39 +633,45 @@ def create_song_package(audio, yt_url, name, lyrics, user_email, progress=gr.Pro
619
  key = analysis.get('key', 'C')
620
  log.append(f"Key: {key} | BPM: {tempo}")
621
 
622
- # === PASS 1: 2-STEM SEPARATION (vocals/instrumental) ===
623
- stems_pass1_dir = package_dir / "stems_pass1"
624
  stems_pass1 = {}
 
625
 
626
- progress(0.15, "Pass 1: Separating vocals/instrumental...")
627
  try:
628
- if HAS_STEM_MODULE:
629
- stems_pass1_output = module_separate_stems(audio_path, str(stems_pass1_dir), two_stem=True)
630
- if stems_pass1_output:
631
- for stem_file in Path(stems_pass1_output).glob('*.mp3'):
632
- stems_pass1[stem_file.stem] = str(stem_file)
633
- log.append(f"Pass 1 stems: {', '.join(stems_pass1.keys())}")
 
634
  else:
635
- log.append("Pass 1: Stem module not available")
 
 
636
  except Exception as e:
637
- log.append(f"Pass 1 error: {str(e)}")
638
 
639
- # === PASS 2: 6-STEM SEPARATION (detailed) ===
640
- stems_pass2_dir = package_dir / "stems_pass2"
641
  stems_pass2 = {}
 
642
 
643
- progress(0.35, "Pass 2: Separating detailed stems (drums, bass, guitar, keys, other)...")
644
  try:
645
- if HAS_STEM_MODULE:
646
- stems_pass2_output = module_separate_stems(audio_path, str(stems_pass2_dir), two_stem=False)
647
- if stems_pass2_output:
648
- for stem_file in Path(stems_pass2_output).glob('*.mp3'):
649
- stems_pass2[stem_file.stem] = str(stem_file)
650
- log.append(f"Pass 2 stems: {', '.join(stems_pass2.keys())}")
 
651
  else:
652
- log.append("Pass 2: Stem module not available")
 
 
653
  except Exception as e:
654
- log.append(f"Pass 2 error: {str(e)}")
655
 
656
  # === MASTER ORIGINAL (Warm preset) ===
657
  mastered_path = None
@@ -711,14 +731,9 @@ def create_song_package(audio, yt_url, name, lyrics, user_email, progress=gr.Pro
711
  progress(0.80, "Creating Reaper project...")
712
  try:
713
  if HAS_REAPER_MODULE:
714
- # Use Pass 2 stems for Reaper (more detailed)
715
- stems_for_reaper = stems_pass2_dir if stems_pass2 else (stems_pass1_dir if stems_pass1 else None)
716
  if stems_for_reaper and Path(stems_for_reaper).exists():
717
- # Find the actual stems directory (Demucs creates nested dirs)
718
- possible_dirs = list(Path(stems_for_reaper).glob('*/*'))
719
- if possible_dirs:
720
- stems_for_reaper = possible_dirs[0]
721
-
722
  rpp_content = create_reaper_project(
723
  song_name=song_name,
724
  stems_dir=stems_for_reaper,
 
400
  return None, str(e)
401
 
402
  @spaces.GPU(duration=120)
403
+ def _separate_stems_gpu(audio_path: str, progress=None, two_stem: bool = False) -> dict:
404
  """Separate audio into stems using Demucs (GPU accelerated) - internal"""
405
  try:
406
  import subprocess
407
+ mode = "2stem" if two_stem else "6stem"
408
+ stems_dir = OUTPUT_DIR / f"stems_{mode}_{datetime.now().strftime('%H%M%S')}"
409
  stems_dir.mkdir(exist_ok=True)
410
 
411
  if progress:
412
+ if two_stem:
413
+ progress(0.3, "Running Demucs 2-stem separation (vocals/instrumental)...")
414
+ else:
415
+ progress(0.3, "Running Demucs 6-stem separation (drums, bass, guitar, keys, other, vocals)...")
416
+
417
+ # Build command based on mode
418
+ if two_stem:
419
+ cmd = ['python', '-m', 'demucs', '--two-stems=vocals', '-o', str(stems_dir), '--mp3', '--mp3-bitrate=320', audio_path]
420
+ else:
421
+ cmd = ['python', '-m', 'demucs', '-n', 'htdemucs_6s', '-o', str(stems_dir), '--mp3', '--mp3-bitrate=320', audio_path]
422
 
 
 
423
  subprocess.run(cmd, capture_output=True, check=True)
424
 
425
  # Find output stems
 
427
  song_dir = list(model_dir.glob('*'))[0]
428
 
429
  stems = {}
430
+ for stem_file in song_dir.glob('*.mp3'):
431
  stem_name = stem_file.stem
432
  stems[stem_name] = str(stem_file)
433
 
434
+ # Also check for wav files
435
+ for stem_file in song_dir.glob('*.wav'):
436
+ stem_name = stem_file.stem
437
+ if stem_name not in stems:
438
+ stems[stem_name] = str(stem_file)
439
+
440
+ return {'stems': stems, 'stems_dir': str(song_dir)}
441
  except Exception as e:
442
  return {'error': str(e)}
443
 
444
+ def separate_stems(audio_path: str, progress=None, two_stem: bool = False) -> dict:
445
  """Separate stems - wrapper with quota error handling"""
446
  try:
447
+ return _separate_stems_gpu(audio_path, progress, two_stem)
448
  except Exception as e:
449
  return {'error': handle_gpu_error(e)}
450
 
 
633
  key = analysis.get('key', 'C')
634
  log.append(f"Key: {key} | BPM: {tempo}")
635
 
636
+ # === PASS 1: 2-STEM SEPARATION (vocals/instrumental) - GPU ACCELERATED ===
 
637
  stems_pass1 = {}
638
+ stems_pass1_dir = None
639
 
640
+ progress(0.15, "Pass 1: Separating vocals/instrumental (GPU)...")
641
  try:
642
+ result = separate_stems(audio_path, progress, two_stem=True)
643
+ if 'error' in result:
644
+ error_msg = result['error']
645
+ if is_quota_error(str(error_msg)):
646
+ log.append(QUOTA_ERROR_MSG)
647
+ else:
648
+ log.append(f"Pass 1 error: {error_msg}")
649
  else:
650
+ stems_pass1 = result.get('stems', {})
651
+ stems_pass1_dir = result.get('stems_dir')
652
+ log.append(f"Pass 1 stems: {', '.join(stems_pass1.keys())}")
653
  except Exception as e:
654
+ log.append(f"Pass 1 error: {handle_gpu_error(e)}")
655
 
656
+ # === PASS 2: 6-STEM SEPARATION (detailed) - GPU ACCELERATED ===
 
657
  stems_pass2 = {}
658
+ stems_pass2_dir = None
659
 
660
+ progress(0.35, "Pass 2: Separating detailed stems (GPU)...")
661
  try:
662
+ result = separate_stems(audio_path, progress, two_stem=False)
663
+ if 'error' in result:
664
+ error_msg = result['error']
665
+ if is_quota_error(str(error_msg)):
666
+ log.append(QUOTA_ERROR_MSG)
667
+ else:
668
+ log.append(f"Pass 2 error: {error_msg}")
669
  else:
670
+ stems_pass2 = result.get('stems', {})
671
+ stems_pass2_dir = result.get('stems_dir')
672
+ log.append(f"Pass 2 stems: {', '.join(stems_pass2.keys())}")
673
  except Exception as e:
674
+ log.append(f"Pass 2 error: {handle_gpu_error(e)}")
675
 
676
  # === MASTER ORIGINAL (Warm preset) ===
677
  mastered_path = None
 
731
  progress(0.80, "Creating Reaper project...")
732
  try:
733
  if HAS_REAPER_MODULE:
734
+ # Use Pass 2 stems for Reaper (more detailed), fall back to Pass 1
735
+ stems_for_reaper = stems_pass2_dir if stems_pass2 else stems_pass1_dir
736
  if stems_for_reaper and Path(stems_for_reaper).exists():
 
 
 
 
 
737
  rpp_content = create_reaper_project(
738
  song_name=song_name,
739
  stems_dir=stems_for_reaper,