Fix Seed-VC integration: use SeedVCWrapper instead of non-existent load_model

- Replace broken import from inference import load_model with SeedVCWrapper
- SeedVCWrapper properly loads all models (DiT, Whisper, BigVGAN, CAMPPlus, RMVPE)
- Add support for F0 conditioning and voice conversion parameters
- Implement proper voice conversion using wrapper.convert_voice()
- Keep fallback to passthrough mode if wrapper fails to load

Fixes: Could not import Seed-VC modules: cannot import name 'load_model' from 'inference'
This commit is contained in:
Azimkin
2026-04-08 13:52:05 +02:00
parent 69cb910eb9
commit 7ecb9941e9

View File

@@ -13,16 +13,16 @@ logger = logging.getLogger(__name__)
class SeedVCService: class SeedVCService:
"""Service for voice conversion using Seed-VC.""" """Service for voice conversion using Seed-VC."""
def __init__(self, model_name: str = "seed-uvit-whisper-small-wavenet", repo_path: str = "./Seed-VC"): def __init__(self, model_name: str = "seed-uvit-whisper-small-wavenet", repo_path: str = "./Seed-VC"):
self.model_name = model_name self.model_name = model_name
self.repo_path = repo_path self.repo_path = repo_path
self.model = None self.wrapper = None
self.device = None self.device = None
self.initialized = False self.initialized = False
logger.info(f"SeedVC Service initialized with model: {model_name}") logger.info(f"SeedVC Service initialized with model: {model_name}")
def _ensure_repo_exists(self): def _ensure_repo_exists(self):
"""Ensure Seed-VC repository is cloned.""" """Ensure Seed-VC repository is cloned."""
if not os.path.exists(self.repo_path): if not os.path.exists(self.repo_path):
@@ -32,171 +32,189 @@ class SeedVCService:
"git", "clone", "https://github.com/Plachtaa/Seed-VC.git", self.repo_path "git", "clone", "https://github.com/Plachtaa/Seed-VC.git", self.repo_path
], check=True) ], check=True)
logger.info("Seed-VC repository cloned") logger.info("Seed-VC repository cloned")
def initialize(self): def initialize(self):
"""Initialize the model (lazy loading).""" """Initialize the model (lazy loading)."""
if self.initialized: if self.initialized:
return return
try: try:
self._ensure_repo_exists() self._ensure_repo_exists()
# Add Seed-VC to path # Add Seed-VC to path
if self.repo_path not in sys.path: if self.repo_path not in sys.path:
sys.path.insert(0, self.repo_path) sys.path.insert(0, self.repo_path)
import torch import torch
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
logger.info(f"Using device: {self.device}") logger.info(f"Using device: {self.device}")
# Import Seed-VC modules # Import Seed-VC wrapper
try: try:
from inference import load_model, inference from seed_vc_wrapper import SeedVCWrapper
self.inference_fn = inference self.wrapper = SeedVCWrapper(device=self.device)
self.model = load_model(self.model_name, self.device) logger.info("Seed-VC wrapper loaded successfully")
logger.info(f"Model {self.model_name} loaded successfully")
except ImportError as e: except ImportError as e:
logger.warning(f"Could not import Seed-VC modules: {e}") logger.warning(f"Could not import SeedVCWrapper: {e}")
logger.warning("Voice conversion will use passthrough mode") logger.warning("Voice conversion will use passthrough mode")
self.model = None self.wrapper = None
self.initialized = True self.initialized = True
except Exception as e: except Exception as e:
logger.error(f"Failed to initialize Seed-VC: {e}") logger.error(f"Failed to initialize Seed-VC: {e}")
self.initialized = True # Mark as initialized to avoid retry self.initialized = True # Mark as initialized to avoid retry
self.model = None self.wrapper = None
def convert_voice( def convert_voice(
self, self,
source_audio_path: str, source_audio_path: str,
reference_audio_path: str, reference_audio_path: str,
output_path: str output_path: str,
diffusion_steps: int = 10,
length_adjust: float = 1.0,
inference_cfg_rate: float = 0.7,
f0_condition: bool = False,
auto_f0_adjust: bool = True,
pitch_shift: int = 0
) -> str: ) -> str:
""" """
Convert voice from source to match reference voice. Convert voice from source to match reference voice.
Args: Args:
source_audio_path: Path to source audio (TTS output) source_audio_path: Path to source audio (TTS output)
reference_audio_path: Path to reference voice audio reference_audio_path: Path to reference voice audio
output_path: Path to save converted audio output_path: Path to save converted audio
diffusion_steps: Number of diffusion steps (default: 10)
length_adjust: Length adjustment factor (default: 1.0)
inference_cfg_rate: Inference CFG rate (default: 0.7)
f0_condition: Whether to use F0 conditioning (default: False)
auto_f0_adjust: Whether to automatically adjust F0 (default: True)
pitch_shift: Pitch shift in semitones (default: 0)
Returns: Returns:
Path to converted audio file Path to converted audio file
""" """
self.initialize() self.initialize()
logger.info(f"Converting voice: {source_audio_path} -> {output_path}") logger.info(f"Converting voice: {source_audio_path} -> {output_path}")
logger.info(f"Using reference: {reference_audio_path}") logger.info(f"Using reference: {reference_audio_path}")
if self.model is None: if self.wrapper is None:
# Fallback: convert MP3 to WAV # Fallback: convert MP3 to WAV
logger.warning("Seed-VC not available, using passthrough mode") logger.warning("Seed-VC not available, using passthrough mode")
return self._passthrough_convert(source_audio_path, output_path) return self._passthrough_convert(source_audio_path, output_path)
try: try:
# Load audio files # Perform voice conversion using the wrapper
source_audio, sr = librosa.load(source_audio_path, sr=22050) # Returns numpy array of audio samples
reference_audio, _ = librosa.load(reference_audio_path, sr=22050) audio_array = self.wrapper.convert_voice(
source=source_audio_path,
# Perform voice conversion target=reference_audio_path,
converted_audio = self.inference_fn( diffusion_steps=diffusion_steps,
self.model, length_adjust=length_adjust,
source_audio, inference_cfg_rate=inference_cfg_rate,
reference_audio, f0_condition=f0_condition,
sr=22050 auto_f0_adjust=auto_f0_adjust,
pitch_shift=pitch_shift,
stream_output=False # We want the full audio array, not streaming
) )
# Determine sample rate based on f0_condition
sr = 44100 if f0_condition else 22050
# Save output # Save output
sf.write(output_path, converted_audio, 22050) sf.write(output_path, audio_array, sr)
logger.info(f"Voice conversion complete: {output_path}") logger.info(f"Voice conversion complete: {output_path}")
return output_path return output_path
except Exception as e: except Exception as e:
logger.error(f"Voice conversion failed: {e}") logger.error(f"Voice conversion failed: {e}")
import traceback
logger.error(traceback.format_exc())
# Fallback: convert MP3 to WAV # Fallback: convert MP3 to WAV
logger.warning("Fallback to MP3-to-WAV conversion") logger.warning("Fallback to MP3-to-WAV conversion")
return self._passthrough_convert(source_audio_path, output_path) return self._passthrough_convert(source_audio_path, output_path)
def _passthrough_convert(self, source_audio_path: str, output_path: str) -> str: def _passthrough_convert(self, source_audio_path: str, output_path: str) -> str:
""" """
Convert MP3 to WAV as passthrough when Seed-VC is not available. Convert MP3 to WAV as passthrough when Seed-VC is not available.
Args: Args:
source_audio_path: Path to source MP3 audio source_audio_path: Path to source MP3 audio
output_path: Path to save converted WAV audio output_path: Path to save converted WAV audio
Returns: Returns:
Path to the output file (may differ from output_path if MP3 is used) Path to the output file (may differ from output_path if MP3 is used)
""" """
# Use absolute paths # Use absolute paths
source_audio_path = os.path.abspath(source_audio_path) source_audio_path = os.path.abspath(source_audio_path)
output_path = os.path.abspath(output_path) output_path = os.path.abspath(output_path)
logger.info(f"Passthrough convert: {source_audio_path} -> {output_path}") logger.info(f"Passthrough convert: {source_audio_path} -> {output_path}")
# Verify source exists # Verify source exists
if not os.path.exists(source_audio_path): if not os.path.exists(source_audio_path):
raise FileNotFoundError(f"Source audio file not found: {source_audio_path}") raise FileNotFoundError(f"Source audio file not found: {source_audio_path}")
# Ensure output directory exists # Ensure output directory exists
output_dir = os.path.dirname(output_path) output_dir = os.path.dirname(output_path)
if output_dir: if output_dir:
os.makedirs(output_dir, exist_ok=True) os.makedirs(output_dir, exist_ok=True)
logger.debug(f"Ensured output directory exists: {output_dir}") logger.debug(f"Ensured output directory exists: {output_dir}")
try: try:
from pydub import AudioSegment from pydub import AudioSegment
# Load MP3 # Load MP3
logger.debug(f"Loading MP3 from: {source_audio_path}") logger.debug(f"Loading MP3 from: {source_audio_path}")
audio = AudioSegment.from_mp3(source_audio_path) audio = AudioSegment.from_mp3(source_audio_path)
logger.debug(f"Audio loaded: {len(audio)}ms, {audio.channels} channels") logger.debug(f"Audio loaded: {len(audio)}ms, {audio.channels} channels")
# Export as WAV # Export as WAV
logger.debug(f"Exporting WAV to: {output_path}") logger.debug(f"Exporting WAV to: {output_path}")
audio.export(output_path, format="wav") audio.export(output_path, format="wav")
# Verify file was created # Verify file was created
if not os.path.exists(output_path): if not os.path.exists(output_path):
raise RuntimeError(f"WAV file was not created at {output_path}") raise RuntimeError(f"WAV file was not created at {output_path}")
file_size = os.path.getsize(output_path) file_size = os.path.getsize(output_path)
if file_size == 0: if file_size == 0:
raise RuntimeError(f"WAV file is empty: {output_path}") raise RuntimeError(f"WAV file is empty: {output_path}")
logger.info(f"MP3 to WAV conversion complete: {output_path} ({file_size} bytes)") logger.info(f"MP3 to WAV conversion complete: {output_path} ({file_size} bytes)")
return output_path return output_path
except Exception as e: except Exception as e:
logger.error(f"MP3 to WAV conversion failed: {e}") logger.error(f"MP3 to WAV conversion failed: {e}")
# Fallback: return MP3 file directly (rename extension) # Fallback: return MP3 file directly (rename extension)
mp3_output = output_path.replace('.wav', '.mp3') mp3_output = output_path.replace('.wav', '.mp3')
if source_audio_path != mp3_output: if source_audio_path != mp3_output:
import shutil import shutil
logger.warning(f"Using MP3 fallback: copying to {mp3_output}") logger.warning(f"Using MP3 fallback: copying to {mp3_output}")
shutil.copy2(source_audio_path, mp3_output) shutil.copy2(source_audio_path, mp3_output)
if not os.path.exists(mp3_output): if not os.path.exists(mp3_output):
raise RuntimeError(f"Failed to create fallback MP3 file: {mp3_output}") raise RuntimeError(f"Failed to create fallback MP3 file: {mp3_output}")
logger.info(f"Fallback MP3 created: {mp3_output}") logger.info(f"Fallback MP3 created: {mp3_output}")
return mp3_output return mp3_output
else: else:
# Source is already the target # Source is already the target
return source_audio_path return source_audio_path
def preprocess_audio(self, input_path: str, output_path: str, target_sr: int = 22050) -> str: def preprocess_audio(self, input_path: str, output_path: str, target_sr: int = 22050) -> str:
""" """
Preprocess audio to match Seed-VC requirements. Preprocess audio to match Seed-VC requirements.
Args: Args:
input_path: Input audio path input_path: Input audio path
output_path: Output audio path output_path: Output audio path
target_sr: Target sample rate target_sr: Target sample rate
Returns: Returns:
Path to preprocessed audio Path to preprocessed audio
""" """
@@ -206,4 +224,4 @@ class SeedVCService:
return output_path return output_path
except Exception as e: except Exception as e:
logger.error(f"Audio preprocessing failed: {e}") logger.error(f"Audio preprocessing failed: {e}")
raise raise