File size: 3,521 Bytes
cdc4405
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# Copyright (c) 2026 Scenema AI
# https://scenema.ai
# SPDX-License-Identifier: MIT

"""VoiceFixer audio post-processing for Scenema Audio.

Applies neural speech restoration to improve clarity, remove artifacts,
and bring speech to studio quality. Runs on GPU after SeedVC as the
final processing step.

Model is downloaded on first use and cached to disk for subsequent runs.
"""

import logging
import os
import subprocess
import sys
import tempfile

import numpy as np
import soundfile as sf
import torchaudio

logger = logging.getLogger(__name__)

_voicefixer = None


def _ensure_installed():
    """Install voicefixer if not available."""
    try:
        import voicefixer  # noqa: F401
    except ImportError:
        logger.info("Installing voicefixer...")
        try:
            subprocess.check_call(
                [sys.executable, "-m", "pip", "install", "voicefixer", "--quiet"],
            )
            logger.info("voicefixer installed")
        except subprocess.CalledProcessError:
            logger.warning("Failed to install voicefixer, enhancement will be skipped")
            raise ImportError("voicefixer not available")


def _get_voicefixer():
    """Get or initialize the VoiceFixer model.

    Downloaded on first use and cached by the library's default cache.
    """
    global _voicefixer

    if _voicefixer is not None:
        return _voicefixer

    _ensure_installed()

    from voicefixer import VoiceFixer  # noqa: E402

    _voicefixer = VoiceFixer()
    logger.info("VoiceFixer model loaded")
    return _voicefixer


def enhance_audio(audio_np: np.ndarray, sr: int) -> np.ndarray:
    """Apply VoiceFixer to audio for studio-quality output.

    VoiceFixer works on WAV files, so we write to temp, process, and read back.

    Args:
        audio_np: Audio array (mono or stereo), any sample rate.
        sr: Sample rate.

    Returns:
        Enhanced audio array at original sample rate.
    """
    try:
        vf = _get_voicefixer()
    except (ImportError, Exception) as e:
        logger.warning("VoiceFixer unavailable: %s, skipping", e)
        return audio_np

    is_stereo = audio_np.ndim == 2 and audio_np.shape[1] == 2

    with tempfile.TemporaryDirectory() as tmp:
        input_path = os.path.join(tmp, "input.wav")
        output_path = os.path.join(tmp, "output.wav")

        sf.write(input_path, audio_np, sr)

        try:
            vf.restore(
                input=input_path,
                output=output_path,
                cuda=True,
                mode=0,  # 0=general, 1=speech-specific
            )

            enhanced, enhanced_sr = sf.read(output_path)

            # Resample back to original sr if needed
            if enhanced_sr != sr:
                import torch

                t = torch.from_numpy(
                    enhanced.T if enhanced.ndim == 2 else enhanced
                ).float()
                if t.ndim == 1:
                    t = t.unsqueeze(0)
                t = torchaudio.functional.resample(t, enhanced_sr, sr)
                enhanced = t.squeeze(0).numpy()
                if enhanced.ndim == 1 and is_stereo:
                    enhanced = np.stack([enhanced, enhanced], axis=1)
                elif enhanced.ndim == 2:
                    enhanced = enhanced.T

            logger.info("Enhanced audio: %.1fs", len(enhanced) / sr)
            return enhanced

        except Exception as e:
            logger.warning("VoiceFixer failed: %s, returning original", e)
            return audio_np