Jacky2305 commited on
Commit
69692b7
·
1 Parent(s): 3a1e2f1

Replace RVC WebUI with Matcha TTS API - faster TTS solution

Browse files
Files changed (5) hide show
  1. Dockerfile +17 -26
  2. README.md +56 -32
  3. app.py +0 -192
  4. main.py +84 -0
  5. requirements.txt +6 -53
Dockerfile CHANGED
@@ -1,35 +1,26 @@
1
- FROM python:3.12
2
 
3
  WORKDIR /app
4
 
5
  # Install system dependencies
6
  RUN apt-get update && apt-get install -y \
7
- git git-lfs ffmpeg libsm6 libxext6 cmake rsync libgl1 curl \
8
- build-essential pkg-config \
9
- && rm -rf /var/lib/apt/lists/* \
10
- && git lfs install
11
 
12
- # IMPORTANT: pin to a specific commit instead of always pulling latest main.
13
- # The repo has been substantially rewritten (Python 3.12 branch, new HuBERT
14
- # format, pymss vocal separation, split requirments_*.txt files). Pulling
15
- # "latest" on every rebuild silently breaks the Space whenever upstream
16
- # pushes a change — which is exactly what happened here.
17
- #
18
- # To pick a commit: browse https://github.com/RVC-Project/Retrieval-based-Voice-Conversion-WebUI/commits/main
19
- # test it locally/in a scratch Space, then hardcode the hash below.
20
- ARG RVC_COMMIT=main
21
- RUN git clone https://github.com/RVC-Project/Retrieval-based-Voice-Conversion-WebUI.git /app/rvc \
22
- && cd /app/rvc \
23
- && git checkout ${RVC_COMMIT}
24
 
25
- # Install the repo's OWN requirements file for CPU, not a hand-maintained
26
- # one — this guarantees dependency versions always match the checked-out
27
- # webui.py code. (The old requirements.txt pinned gradio 3.14 + fairseq,
28
- # which is the pre-rewrite stack and no longer matches current webui.py.)
29
- RUN pip install --no-cache-dir pip -U \
30
- && pip install --no-cache-dir -r /app/rvc/requirments_cpu_py312.txt
31
 
32
- # Copy local application launcher
33
- COPY app.py /app/app.py
34
 
35
- CMD ["python", "app.py"]
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
 
3
  WORKDIR /app
4
 
5
  # Install system dependencies
6
  RUN apt-get update && apt-get install -y \
7
+ libsndfile1 \
8
+ ffmpeg \
9
+ && rm -rf /var/lib/apt/lists/*
 
10
 
11
+ # Install Python dependencies
12
+ COPY requirements.txt .
13
+ RUN pip install --no-cache-dir -r requirements.txt
 
 
 
 
 
 
 
 
 
14
 
15
+ # Copy application code
16
+ COPY main.py .
 
 
 
 
17
 
18
+ # Expose port
19
+ EXPOSE 7860
20
 
21
+ # Health check
22
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
23
+ CMD curl -f http://localhost:7860/health || exit 1
24
+
25
+ # Run the application
26
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1,53 +1,77 @@
1
  ---
2
- title: RVC WebUI
3
- emoji: 🎤
4
  colorFrom: blue
5
- colorTo: purple
6
  sdk: docker
7
  app_port: 7860
 
8
  ---
9
 
10
- # RVC Voice Conversion — Hugging Face CPU Basic
11
 
12
- CPU-only deployment of the official RVC WebUI.
13
 
14
- ## What this Space does
15
 
16
- - Upload an input voice/song
17
- - Select an RVC `.pth` voice model
18
- - Optional `.index` retrieval index
19
- - Convert voice with RVC
20
- - Supports CPU inference
21
 
22
- ## Important
23
 
24
- This Space is intended for **inference**, not RVC model training.
 
25
 
26
- You still need an RVC voice model (`.pth`). You can upload/select your own model through the RVC interface.
 
 
 
 
 
 
 
 
 
 
27
 
28
- The first startup downloads the RVC inference assets. This can take several minutes.
29
 
30
- ## CPU performance
 
31
 
32
- CPU Basic can run RVC, but it is considerably slower than GPU inference. For long songs, expect substantial processing time.
 
33
 
34
- For best CPU performance:
35
 
36
- - Use RMVPE only when needed.
37
- - Keep audio reasonably short while testing.
38
- - Avoid running several conversions simultaneously.
39
- - Use a smaller/shorter input when troubleshooting.
 
 
40
 
41
- ## Deployment
 
 
 
 
42
 
43
- 1. Create a new Hugging Face Space.
44
- 2. Choose **Gradio**.
45
- 3. Choose **CPU Basic**.
46
- 4. Upload `app.py`, `requirements.txt`, and this `README.md`.
47
- 5. Wait for the Space to build.
48
- 6. Open the Space after it becomes `Running`.
 
 
49
 
50
- ## Official project
 
 
 
51
 
52
- RVC-Project / Retrieval-based-Voice-Conversion-WebUI:
53
- https://github.com/RVC-Project/Retrieval-based-Voice-Conversion-WebUI
 
1
  ---
2
+ title: Matcha TTS API
3
+ emoji: 🗣️
4
  colorFrom: blue
5
+ colorTo: red
6
  sdk: docker
7
  app_port: 7860
8
+ pinned: false
9
  ---
10
 
11
+ # Matcha TTS Space
12
 
13
+ A Hugging Face Space deployment of **Matcha-TTS** for fast, high-quality text-to-speech synthesis on CPU Basic.
14
 
15
+ ## Features
16
 
17
+ - **Fast**: Real-time factor (RTF) ~0.1-0.15 on CPU Basic
18
+ - **High Quality**: Natural-sounding speech with good prosody
19
+ - **Simple API**: REST endpoint for TTS synthesis
20
+ - **CPU Optimized**: Uses ONNX runtime for efficient inference
21
+ - **Configurable**: Supports different Matcha-TTS models via environment variable
22
 
23
+ ## API Endpoints
24
 
25
+ ### POST `/tts`
26
+ Synthesize speech from text.
27
 
28
+ **Request Body:**
29
+ ```json
30
+ {
31
+ "text": "Hello world",
32
+ "language": "en",
33
+ "speaker_id": 0,
34
+ "noise_scale": 0.667,
35
+ "noise_scale_w": 0.8,
36
+ "length_scale": 1.0
37
+ }
38
+ ```
39
 
40
+ **Returns:** WAV audio file (24kHz, 16-bit PCM)
41
 
42
+ ### GET `/health`
43
+ Health check endpoint.
44
 
45
+ ### GET `/`
46
+ API information and usage example.
47
 
48
+ ## Usage Example (curl)
49
 
50
+ ```bash
51
+ curl -X POST "https://your-username-your-space.hf.space/tts" \
52
+ -H "Content-Type: application/json" \
53
+ -d '{"text":"Hello, this is Matcha TTS!"}' \
54
+ -o output.wav
55
+ ```
56
 
57
+ ## Model
58
+
59
+ Default: `matcha-tts/matcha-v1.0` (English)
60
+
61
+ To use a different model, set the `MATCHA_MODEL` environment variable in Space Settings.
62
 
63
+ ## Performance
64
+
65
+ On Hugging Face CPU Basic:
66
+ - Startup time: ~10-15 seconds (model loading)
67
+ - Synthesis RTF: ~0.1-0.15 (1 second of audio in 100-150ms)
68
+ - Concurrent requests: Limited by CPU, recommend sequential processing
69
+
70
+ ## Deployment
71
 
72
+ This Space is built from:
73
+ - `Dockerfile`: Container definition
74
+ - `requirements.txt`: Python dependencies
75
+ - `main.py`: FastAPI application
76
 
77
+ Built and deployed automatically on pushes to the main branch.
 
app.py DELETED
@@ -1,192 +0,0 @@
1
- import os
2
- import sys
3
- import shutil
4
- import subprocess
5
- import time
6
- from pathlib import Path
7
-
8
- # Hugging Face Spaces / CPU-only environment setup
9
- os.environ["CUDA_VISIBLE_DEVICES"] = ""
10
- os.environ["GRADIO_SERVER_NAME"] = "0.0.0.0"
11
- os.environ["GRADIO_SERVER_PORT"] = "7860"
12
- os.environ["HF_HUB_DISABLE_TELEMETRY"] = "1"
13
-
14
- ROOT = Path(__file__).resolve().parent
15
- RVC_DIR = ROOT / "rvc"
16
-
17
- def run(cmd, cwd=None):
18
- print(">>>", " ".join(map(str, cmd)), flush=True)
19
- subprocess.check_call(cmd, cwd=cwd)
20
-
21
- def download_with_retry(repo_id, filename, local_dir, max_retries=3):
22
- """Download a single file with retry logic."""
23
- from huggingface_hub import hf_hub_download
24
- for attempt in range(max_retries):
25
- try:
26
- print(f"Downloading {filename} from {repo_id} (attempt {attempt+1}/{max_retries})...", flush=True)
27
- hf_hub_download(
28
- repo_id=repo_id,
29
- filename=filename,
30
- local_dir=local_dir,
31
- )
32
- target = Path(local_dir) / filename
33
- if target.exists() and target.stat().st_size > 1000:
34
- print(f"✓ {filename} downloaded successfully ({target.stat().st_size} bytes)", flush=True)
35
- return True
36
- else:
37
- print(f"✗ {filename} download failed: file too small or missing", flush=True)
38
- except Exception as e:
39
- print(f"✗ Attempt {attempt+1} failed: {e}", flush=True)
40
- if attempt < max_retries - 1:
41
- time.sleep(5)
42
- return False
43
-
44
- def snapshot_with_retry(repo_id, allow_patterns, local_dir, max_retries=3):
45
- """Download a folder/pattern set from the HF repo with retry logic."""
46
- from huggingface_hub import snapshot_download
47
- for attempt in range(max_retries):
48
- try:
49
- print(f"Downloading {allow_patterns} from {repo_id} (attempt {attempt+1}/{max_retries})...", flush=True)
50
- snapshot_download(
51
- repo_id=repo_id,
52
- allow_patterns=allow_patterns,
53
- local_dir=local_dir,
54
- )
55
- print(f"✓ {allow_patterns} downloaded", flush=True)
56
- return True
57
- except Exception as e:
58
- print(f"✗ Attempt {attempt+1} failed: {e}", flush=True)
59
- if attempt < max_retries - 1:
60
- time.sleep(5)
61
- return False
62
-
63
- # 1. Guarantee RVC existence regardless of build SDK
64
- # NOTE: the Dockerfile now pins RVC to a specific commit at build time via
65
- # RVC_COMMIT. This fallback clone (used if the build step didn't run, e.g.
66
- # local testing) intentionally does NOT pin a commit, so if you hit weird
67
- # errors when running app.py standalone, check you're on the same commit
68
- # as the Docker image.
69
- if not (RVC_DIR / "webui.py").exists():
70
- print("RVC missing or incomplete. Performing clean clone...", flush=True)
71
- if RVC_DIR.exists():
72
- shutil.rmtree(RVC_DIR, ignore_errors=True)
73
-
74
- run([
75
- "git", "clone", "--depth", "1",
76
- "https://github.com/RVC-Project/Retrieval-based-Voice-Conversion-WebUI.git",
77
- str(RVC_DIR)
78
- ])
79
-
80
- # 2. Set execution context
81
- sys.path.insert(0, str(RVC_DIR))
82
- os.chdir(RVC_DIR)
83
-
84
- # 3. Ensure asset directories exist (current layout, per upstream README)
85
- for asset_dir in [
86
- RVC_DIR / "assets" / "hubert_base",
87
- RVC_DIR / "assets" / "rmvpe",
88
- RVC_DIR / "assets" / "weights",
89
- RVC_DIR / "assets" / "indices",
90
- RVC_DIR / "assets" / "pymss_weights",
91
- RVC_DIR / "logs",
92
- ]:
93
- asset_dir.mkdir(parents=True, exist_ok=True)
94
-
95
- BASE_MODEL_REPO = "lj1995/VoiceConversionWebUI"
96
-
97
- # 4. Download base inference assets (this is what the previous app.py was
98
- # missing entirely — it only ever downloaded the custom voice model).
99
- print("=== Downloading base RVC inference assets ===", flush=True)
100
- base_ok = True
101
-
102
- # HuBERT is now a Transformers-format folder (config.json,
103
- # preprocessor_config.json, pytorch_model.bin) — NOT the old single
104
- # fairseq hubert_base.pt file.
105
- hubert_dir = RVC_DIR / "assets" / "hubert_base"
106
- if not (hubert_dir / "pytorch_model.bin").exists():
107
- base_ok &= snapshot_with_retry(
108
- BASE_MODEL_REPO,
109
- allow_patterns=["hubert_base/*"],
110
- local_dir=RVC_DIR / "assets",
111
- )
112
- else:
113
- print("✓ hubert_base already present", flush=True)
114
-
115
- # RMVPE pitch extraction model
116
- rmvpe_path = RVC_DIR / "assets" / "rmvpe" / "rmvpe.pt"
117
- if not (rmvpe_path.exists() and rmvpe_path.stat().st_size > 1000):
118
- base_ok &= download_with_retry(BASE_MODEL_REPO, "rmvpe.pt", RVC_DIR / "assets" / "rmvpe")
119
- else:
120
- print("✓ rmvpe.pt already present", flush=True)
121
-
122
- # pymss weights — only needed if you use the built-in vocal separation tab.
123
- # Comment this block out if you don't use that feature, to save build time.
124
- pymss_dir = RVC_DIR / "assets" / "pymss_weights"
125
- if not any(pymss_dir.iterdir()) if pymss_dir.exists() else True:
126
- base_ok &= snapshot_with_retry(
127
- BASE_MODEL_REPO,
128
- allow_patterns=["pymss_weights/*"],
129
- local_dir=RVC_DIR / "assets",
130
- )
131
- else:
132
- print("✓ pymss_weights already present", flush=True)
133
-
134
- if not base_ok:
135
- print("⚠ Some base assets failed to download. Inference will likely fail.", flush=True)
136
- else:
137
- print("=== Base assets ready ===", flush=True)
138
-
139
- # 5. Download OldChineseMan RVC model from your model repo
140
- model_repo = "jacky3102/OldChineseMan_RVC"
141
- weights_dir = RVC_DIR / "assets" / "weights"
142
- indices_dir = RVC_DIR / "assets" / "indices"
143
-
144
- files_to_download = [
145
- ("OldChineseMan.pth", weights_dir),
146
- ("added_IVF24_Flat_nprobe_1_OldChineseMan_v2.index", indices_dir),
147
- ]
148
-
149
- print("=== Downloading RVC model files ===", flush=True)
150
- all_ok = True
151
- for fname, target_dir in files_to_download:
152
- target = target_dir / fname
153
- if target.exists() and target.stat().st_size > 1000:
154
- print(f"✓ {fname} already exists ({target.stat().st_size} bytes)", flush=True)
155
- else:
156
- if not download_with_retry(model_repo, fname, target_dir):
157
- print(f"✗ FAILED to download {fname}", flush=True)
158
- all_ok = False
159
-
160
- # Verify model file can be loaded
161
- if all_ok:
162
- print("=== Verifying model file ===", flush=True)
163
- try:
164
- import torch
165
- pth_path = weights_dir / "OldChineseMan.pth"
166
- print(f"Loading {pth_path}...", flush=True)
167
- state = torch.load(pth_path, map_location="cpu", weights_only=False)
168
- print(f"✓ Model loaded. Keys: {list(state.keys())[:10]}...", flush=True)
169
- if "config" in state:
170
- print(f" Config: {state['config']}", flush=True)
171
- except Exception as e:
172
- print(f"✗ Model verification failed: {e}", flush=True)
173
- all_ok = False
174
-
175
- if not (all_ok and base_ok):
176
- print("⚠ Some model/asset files failed verification. Inference may fail.", flush=True)
177
- else:
178
- print("=== All model files ready and verified ===", flush=True)
179
-
180
- # 6. Launch RVC WebUI
181
- # The current webui.py reads host/port from GRADIO_SERVER_NAME /
182
- # GRADIO_SERVER_PORT (already set above) rather than a --port flag in the
183
- # rewritten branch. Run `python webui.py --help` in the build logs once to
184
- # confirm which flags your pinned commit actually supports, then adjust
185
- # the args below accordingly. --noautoopen avoids it trying to open a
186
- # local browser on a headless server.
187
- print("=== Starting RVC WebUI ===", flush=True)
188
- run([
189
- sys.executable,
190
- "webui.py",
191
- "--noautoopen",
192
- ], cwd=RVC_DIR)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
main.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException
2
+ from pydantic import BaseModel
3
+ from matcha_tts import MatchaTTS
4
+ import soundfile as sf
5
+ import torch
6
+ import numpy as np
7
+ import os
8
+ import uuid
9
+ from fastapi.responses import Response
10
+ from io import BytesIO
11
+
12
+ app = FastAPI(title="Matcha TTS Space")
13
+
14
+ # Load model on startup
15
+ MODEL_NAME = os.getenv("MATCHA_MODEL", "matcha-tts/matcha-v1.0")
16
+ print(f"Loading Matcha-TTS model: {MODEL_NAME}")
17
+ try:
18
+ model = MatchaTTS.from_pretrained(MODEL_NAME)
19
+ print("Model loaded successfully")
20
+ except Exception as e:
21
+ print(f"Failed to load model: {e}")
22
+ model = None
23
+
24
+ class TTSRequest(BaseModel):
25
+ text: str
26
+ language: str = "en" # ISO 639-1 code
27
+ speaker_id: int = 0
28
+ noise_scale: float = 0.667
29
+ noise_scale_w: float = 0.8
30
+ length_scale: float = 1.0
31
+
32
+ @app.post("/tts")
33
+ async def tts(request: TTSRequest):
34
+ if model is None:
35
+ raise HTTPException(status_code=503, detail="Model not loaded")
36
+
37
+ try:
38
+ # Synthesize audio
39
+ audio = model.synthesise(
40
+ request.text,
41
+ speaker_id=request.speaker_id,
42
+ noise_scale=request.noise_scale,
43
+ noise_scale_w=request.noise_scale_w,
44
+ length_scale=request.length_scale,
45
+ )
46
+
47
+ # Convert to numpy array if needed
48
+ if torch.is_tensor(audio):
49
+ audio = audio.cpu().numpy()
50
+
51
+ # Ensure it's 1D array
52
+ if audio.ndim > 1:
53
+ audio = audio.squeeze()
54
+
55
+ # Create WAV in memory
56
+ buffer = BytesIO()
57
+ sf.write(buffer, audio, 24000, format='WAV') # Matcha outputs 24kHz
58
+ buffer.seek(0)
59
+
60
+ return Response(
61
+ content=buffer.read(),
62
+ media_type="audio/wav",
63
+ headers={
64
+ "Content-Disposition": f"attachment; filename=tts_{uuid.uuid4().hex}.wav"
65
+ }
66
+ )
67
+ except Exception as e:
68
+ raise HTTPException(status_code=500, detail=f"Synthesis failed: {str(e)}")
69
+
70
+ @app.get("/health")
71
+ async def health():
72
+ return {"status": "healthy" if model is not None else "model_not_loaded", "model": MODEL_NAME}
73
+
74
+ @app.get("/")
75
+ async def root():
76
+ return {
77
+ "message": "Matcha TTS API",
78
+ "endpoint": "/tts (POST)",
79
+ "model": MODEL_NAME,
80
+ "example": {
81
+ "text": "Hello, this is a test of the Matcha TTS system.",
82
+ "language": "en"
83
+ }
84
+ }
requirements.txt CHANGED
@@ -1,53 +1,6 @@
1
- # RVC official CPU stack for Python 3.12 (Linux)
2
- --extra-index-url https://download.pytorch.org/whl/cpu
3
-
4
- torch==2.4.1+cpu
5
- torchaudio==2.4.1+cpu
6
- torchvision==0.19.1+cpu
7
-
8
- setuptools>=75.0,<81
9
- wheel>=0.45,<1
10
- packaging>=24.0
11
-
12
- av>=15.1.0,<16
13
- einops>=0.8.0,<1
14
- faiss-cpu>=1.13.0,<2
15
- ffmpeg-python>=0.2.0,<1
16
- FreeSimpleGUI>=5.1.0,<5.2
17
- librosa>=0.10.2,<0.11
18
- local-attention>=1.11.0,<2
19
- hyper-connections>=0.1.8,<0.4.3
20
- matplotlib>=3.8.2,<4
21
- networkx>=3.2.0,<4
22
- numpy>=1.26.4,<2
23
- onnxruntime>=1.24.4,<2
24
- coloredlogs>=15.0,<16
25
- opencv-python-headless>=4.10.0,<5
26
- praat-parselmouth>=0.4.5,<1
27
- PyYAML>=6.0
28
- scikit-learn>=1.6.0,<2
29
- scipy>=1.13.1,<2
30
- sounddevice>=0.5.0,<1
31
- soundfile>=0.13.0,<1
32
- tensorboard>=2.19.0
33
- torchfcpe>=0.0.4,<0.1
34
- tqdm>=4.67.0,<5
35
- transformers>=4.49.0,<4.50
36
-
37
- # Gradio 3.14 API compatibility (RVC WebUI requires Gradio 3.14.x)
38
- gradio>=3.14.0,<3.15
39
- huggingface_hub>=0.24.0
40
- altair>=4.2.0,<5
41
- anyio>=3.6.2,<4
42
- fastapi>=0.88.0,<0.100
43
- httpx>=0.23.0,<0.24
44
- markdown-it-py>=2.2.0,<3
45
- mdit-py-plugins>=0.3.3,<0.4
46
- pydantic>=1.10.13,<2
47
- pyparsing>=3.0.9,<3.1
48
- starlette>=0.22.0,<0.28
49
- uvicorn>=0.20.0,<0.23
50
- websockets>=10.4,<11
51
-
52
- # RVC imports fairseq directly
53
- fairseq @ git+https://github.com/One-sixth/fairseq.git
 
1
+ fastapi==0.111.0
2
+ uvicorn[standard]==0.30.1
3
+ matcha-tts==0.1.0
4
+ onnxruntime==1.19.2
5
+ soundfile==0.12.1
6
+ pydantic==2.8.2