Spaces:
Running on Zero
Running on Zero
File size: 6,768 Bytes
b3b65a0 2be1765 b3b65a0 2be1765 b3b65a0 2bb52a1 b3b65a0 2bb52a1 2b54be1 2bb52a1 b3b65a0 f3d9515 0220423 f3d9515 554e2b1 c266981 b3b65a0 02915a5 0220423 2b54be1 3153851 2b54be1 b3b65a0 2b54be1 8ae09f3 3153851 2b54be1 3153851 2b54be1 b3b65a0 2b54be1 0220423 2b54be1 2be1765 0c7b128 2be1765 b3b65a0 4b53fbf 28eed4a | 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 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | from __future__ import annotations
import spaces # imported first, as ZeroGPU requires
import gradio as gr
from pyharp import *
import contextlib
import tempfile
from pathlib import Path
import soundfile as sf
import torch
from huggingface_hub import hf_hub_download
model_card = ModelCard(
name="VoiceFixer",
description=(
"VoiceFixer aims to restore human speech regardless how serious its "
"degraded. It can handle noise, reverberation, low resolution "
"(2kHz~44.1kHz) and clipping (0.1-1.0 threshold) effect within one "
"model. The model is fixed-rate and mono, so the result is resampled "
"back to the input's rate and copied across its channels: the output "
"has the format it came in with, but no stereo image. Repository: "
"https://github.com/haoheliu/voicefixer Paper: "
"https://arxiv.org/abs/2109.13731"
),
author="Haohe Liu, Qiuqiang Kong, Qiao Tian, Yan Zhao, DeLiang Wang, Chuanzeng Huang, Yuxuan Wang",
tags=[Subcategory.RESTORATION, SampleRate(44100), Channels(1), 'speech', 'domain:speech'],
)
# pyharp runs process_fn in a worker, which imports this file as __mp_main__ and starts with the
# server, so the model is fetched and built there alone. voicefixer downloads its checkpoints from
# Zenodo when it is imported, at about 1 MB/s, so the same files, mirrored on the Hub, are linked
# where it looks before downloading, ahead of the import. It picks CUDA itself where there is one.
if __name__ != "__main__":
_VOICEFIXER_CACHE = Path.home() / ".cache" / "voicefixer"
for _name, _path in (
("vf.ckpt", "analysis_module/checkpoints/vf.ckpt"),
("model.ckpt-1490000_trimed.pt", "synthesis_module/44100/model.ckpt-1490000_trimed.pt"),
):
_link = _VOICEFIXER_CACHE / _path
if not _link.exists():
_link.parent.mkdir(parents=True, exist_ok=True)
with contextlib.suppress(FileExistsError):
_link.symlink_to(hf_hub_download(
"teamup-tech/VoiceFixer-weights", _name,
revision="11ca799c59df55bbbc438f223bf3b55a64ebced6",
))
from voicefixer import VoiceFixer
voicefixer_model = VoiceFixer()
def match_input_format(output_path: str, input_path: str) -> str:
"""
Rewrite an output file at the sample rate and channel count it came in with.
The models here are fixed-rate and mono, and hand the writing to vendored
upstream code, so the format is corrected afterwards rather than threaded
through inference. Duplicated channels are copies, not a recovered stereo
image; the model card says so.
Args:
output_path (str): File the model wrote. Overwritten in place.
input_path (str): File the request supplied.
Returns:
output_path (str): The same path, for use in a return statement.
"""
import librosa
import numpy as np
import soundfile
want = soundfile.info(input_path)
have = soundfile.info(output_path)
if have.samplerate == want.samplerate and have.channels == want.channels:
return output_path
data, rate = soundfile.read(output_path, always_2d=True)
if rate != want.samplerate:
data = librosa.resample(
data.T, orig_sr=rate, target_sr=want.samplerate
).T
if data.shape[1] == 1 and want.channels > 1:
data = np.tile(data, (1, want.channels))
elif data.shape[1] > 1 and want.channels == 1:
data = data.mean(axis=1, keepdims=True)
soundfile.write(output_path, data, want.samplerate)
return output_path
# The recording is restored in 30-second segments, so the cost grows with its length
# PyHARP stops a job that runs past its timeout_s, 15 minutes by default, so a request whose
# reservation would exceed that is refused rather than started. A shorter one is left to
# ZeroGPU, which admits it when the caller's own allowance covers it.
MAX_GPU_SECONDS = 900
def gpu_seconds(input_audio, output_file, mode):
return 15 + 0.1 * sf.info(input_audio).duration
@spaces.GPU(duration=gpu_seconds)
def restore(input_audio, output_file, mode):
voicefixer_model.restore(input_audio, output_file, cuda=torch.cuda.is_available(),
mode=int(mode))
def process_fn(input_audio, mode):
# Checked before the GPU is reserved, so a request the model cannot take spends none of the
# caller's ZeroGPU allowance
if not input_audio:
raise gr.Error("Provide the speech to restore.")
try:
duration = sf.info(input_audio).duration
except Exception as exc:
raise gr.Error(f"Could not read the audio file: {exc}") from exc
if duration == 0:
raise gr.Error("The audio file is empty.")
if gpu_seconds(input_audio, None, mode) > MAX_GPU_SECONDS:
raise gr.Error(
f"The recording is {duration:.0f} s long, more than one request can restore in the "
f"{MAX_GPU_SECONDS // 60} minutes it may run. Trim it."
)
output_file = tempfile.NamedTemporaryFile(suffix=".wav", delete=False).name
restore(input_audio, output_file, mode)
# The format is matched outside the GPU call, which it does not need
output_file = match_input_format(output_file, input_audio)
# A lossless input keeps its container. A lossy one comes back as WAV, since encoding the
# result lossily again would expose the artifacts of the first encode and add a second set.
input_ext = Path(input_audio).suffix.lower()
if input_ext in {".flac"}:
data, rate = sf.read(output_file, always_2d=True)
lossless_file = str(Path(output_file).with_suffix(input_ext))
sf.write(lossless_file, data, rate)
output_file = lossless_file
return output_file
if __name__ == "__main__":
with gr.Blocks() as demo:
input_components = [
gr.Audio(type="filepath", label="Input Audio").harp_required(True).set_info("Upload an audio file to be processed by VoiceFixer."),
gr.Dropdown(choices=["0", "1", "2"], value="0", label="Processing Mode", info="Select the VoiceFixer processing mode:\n0: Original Model (suggested by default)\n1: Add preprocessing module (remove higher frequency)\n2: Train mode (might work sometimes on seriously degraded real speech)"),
]
output_components = [
gr.Audio(type="filepath", label="Fixed Audio").set_info("The enhanced audio output from VoiceFixer."),
]
build_endpoint(
model_card=model_card,
input_components=input_components,
output_components=output_components,
process_fn=process_fn,
)
demo.queue().launch(show_error=True, pwa=True)
|