videosdk-plugins-rnnoise-dev 0.0.63.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- videosdk/plugins/rnnoise/__init__.py +3 -0
- videosdk/plugins/rnnoise/build_rnnoise.py +126 -0
- videosdk/plugins/rnnoise/denoise.py +62 -0
- videosdk/plugins/rnnoise/files/librnnoise.0.dylib +0 -0
- videosdk/plugins/rnnoise/files/librnnoise.dylib +0 -0
- videosdk/plugins/rnnoise/files/librnnoise.so +0 -0
- videosdk/plugins/rnnoise/rnnoise.py +77 -0
- videosdk/plugins/rnnoise/version.py +1 -0
- videosdk_plugins_rnnoise_dev-0.0.63.dev0.dist-info/METADATA +35 -0
- videosdk_plugins_rnnoise_dev-0.0.63.dev0.dist-info/RECORD +11 -0
- videosdk_plugins_rnnoise_dev-0.0.63.dev0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import platform
|
|
3
|
+
import subprocess
|
|
4
|
+
import shutil
|
|
5
|
+
import tempfile
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from shutil import which
|
|
9
|
+
|
|
10
|
+
def check_command(cmd, install_tip):
|
|
11
|
+
if not which(cmd):
|
|
12
|
+
raise EnvironmentError(f"{cmd} not found. Install it: {install_tip}")
|
|
13
|
+
|
|
14
|
+
def run_subprocess(cmd, cwd=None):
|
|
15
|
+
print(f"Running: {' '.join(cmd)}")
|
|
16
|
+
try:
|
|
17
|
+
result = subprocess.run(cmd, cwd=cwd, check=True, capture_output=True, text=True)
|
|
18
|
+
print(result.stdout)
|
|
19
|
+
if result.stderr:
|
|
20
|
+
print(f"Warnings/Errors: {result.stderr}")
|
|
21
|
+
return result
|
|
22
|
+
except subprocess.CalledProcessError as e:
|
|
23
|
+
print(f"Command failed with code {e.returncode}: {e.stderr}")
|
|
24
|
+
raise
|
|
25
|
+
|
|
26
|
+
def build_rnnoise():
|
|
27
|
+
# Force Linux detection when running in Docker
|
|
28
|
+
if os.path.exists("/.dockerenv") or os.environ.get("DOCKER_CONTAINER"):
|
|
29
|
+
os_sys = "Linux"
|
|
30
|
+
else:
|
|
31
|
+
os_sys = platform.system()
|
|
32
|
+
repo_url = "https://github.com/xiph/rnnoise.git"
|
|
33
|
+
|
|
34
|
+
git_tip = "Install git (e.g., apt install git on Debian/Ubuntu, brew install git on macOS, or Chocolatey on Windows)."
|
|
35
|
+
check_command("git", git_tip)
|
|
36
|
+
|
|
37
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
38
|
+
run_subprocess(["git", "clone", repo_url, tmpdir])
|
|
39
|
+
os.chdir(tmpdir)
|
|
40
|
+
if os_sys == "Darwin" or os_sys == "Linux":
|
|
41
|
+
# Check required Unix build tools
|
|
42
|
+
unix_tip = "Install build tools (e.g., apt install build-essential autoconf automake libtool on Debian/Ubuntu; brew install autoconf automake libtool on macOS)."
|
|
43
|
+
for tool in ["autoconf", "automake", "libtool", "make"]:
|
|
44
|
+
check_command(tool, unix_tip)
|
|
45
|
+
|
|
46
|
+
# Generate build files and compile
|
|
47
|
+
run_subprocess(["./autogen.sh"])
|
|
48
|
+
run_subprocess(["./configure"])
|
|
49
|
+
run_subprocess(["make"])
|
|
50
|
+
lib_dir = ".libs"
|
|
51
|
+
|
|
52
|
+
# Force Linux .so file when running in Docker container
|
|
53
|
+
if os.path.exists("/.dockerenv") or os.environ.get("DOCKER_CONTAINER"):
|
|
54
|
+
lib_file = "librnnoise.so"
|
|
55
|
+
print(f"🔧 Docker detected, forcing .so file: {lib_file}")
|
|
56
|
+
else:
|
|
57
|
+
lib_file = "librnnoise.dylib" if os_sys == "Darwin" else "librnnoise.so"
|
|
58
|
+
print(f"🔧 Host system detected: {os_sys}, using: {lib_file}")
|
|
59
|
+
|
|
60
|
+
built_lib = os.path.join(lib_dir, lib_file)
|
|
61
|
+
elif os_sys == "Windows":
|
|
62
|
+
# Check nmake for Visual Studio-based build
|
|
63
|
+
nmake_tip = "Install Visual Studio (community edition) and run this script from Developer Command Prompt. Alternatively, use MSYS2 (install via https://www.msys2.org/) and run make in the repo."
|
|
64
|
+
check_command("nmake", nmake_tip)
|
|
65
|
+
|
|
66
|
+
os.chdir("msvc")
|
|
67
|
+
# Assumes Visual Studio is installed; run from Developer Command Prompt if needed
|
|
68
|
+
run_subprocess(["nmake", "/f", "Makefile.ms"])
|
|
69
|
+
built_lib = "rnnoise.dll" # Adjust if name differs after build
|
|
70
|
+
else:
|
|
71
|
+
raise ValueError(f"Unsupported OS: {os_sys}. Manual build required (clone repo and follow README).")
|
|
72
|
+
|
|
73
|
+
if not os.path.exists(built_lib):
|
|
74
|
+
raise FileNotFoundError(f"Build failed: {built_lib} not found. Ensure build tools are installed and environment is set up (e.g., in containers, add tools via Dockerfile/package manager).")
|
|
75
|
+
|
|
76
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
77
|
+
target_dir = os.path.join(script_dir, "files")
|
|
78
|
+
os.makedirs(target_dir, exist_ok=True)
|
|
79
|
+
|
|
80
|
+
# Force .so files when running in Docker
|
|
81
|
+
if os.path.exists("/.dockerenv") or os.environ.get("DOCKER_CONTAINER"):
|
|
82
|
+
# Look for shared library specifically
|
|
83
|
+
shared_lib = os.path.join(lib_dir, "librnnoise.so")
|
|
84
|
+
static_lib = os.path.join(lib_dir, "librnnoise.a")
|
|
85
|
+
|
|
86
|
+
if os.path.exists(shared_lib):
|
|
87
|
+
source_lib = shared_lib
|
|
88
|
+
print(f"🔧 Found shared library: {source_lib}")
|
|
89
|
+
elif os.path.exists(static_lib):
|
|
90
|
+
# If no shared library, create one from static library
|
|
91
|
+
print(f"🔧 Creating shared library from static library")
|
|
92
|
+
# Extract object files from static library
|
|
93
|
+
run_subprocess(["ar", "-x", static_lib])
|
|
94
|
+
# Create shared library from object files
|
|
95
|
+
obj_files = [f for f in os.listdir(".") if f.endswith(".o")]
|
|
96
|
+
run_subprocess(["gcc", "-shared", "-o", shared_lib] + obj_files + ["-lm"])
|
|
97
|
+
source_lib = shared_lib
|
|
98
|
+
else:
|
|
99
|
+
raise FileNotFoundError(f"No librnnoise library found in {lib_dir}")
|
|
100
|
+
|
|
101
|
+
target_lib = os.path.join(target_dir, "librnnoise.so")
|
|
102
|
+
shutil.copy(source_lib, target_lib)
|
|
103
|
+
print(f"🔧 Copied {source_lib} to {target_lib}")
|
|
104
|
+
|
|
105
|
+
# Also create a symlink for compatibility
|
|
106
|
+
symlink_path = os.path.join(target_dir, "librnnoise.dylib")
|
|
107
|
+
if os.path.exists(symlink_path):
|
|
108
|
+
os.remove(symlink_path)
|
|
109
|
+
os.symlink("librnnoise.so", symlink_path)
|
|
110
|
+
print(f"🔧 Created symlink: librnnoise.dylib -> librnnoise.so")
|
|
111
|
+
|
|
112
|
+
# Copy to the mounted volume location
|
|
113
|
+
mounted_target = "/output/librnnoise.so"
|
|
114
|
+
shutil.copy(source_lib, mounted_target)
|
|
115
|
+
print(f"🔧 Copied to mounted volume: {mounted_target}")
|
|
116
|
+
else:
|
|
117
|
+
shutil.copy(built_lib, os.path.join(target_dir, os.path.basename(built_lib)))
|
|
118
|
+
|
|
119
|
+
if __name__ == "__main__":
|
|
120
|
+
try:
|
|
121
|
+
build_rnnoise()
|
|
122
|
+
print("RNNoise library built successfully for your OS.")
|
|
123
|
+
except Exception as e:
|
|
124
|
+
print(f"Build failed: {e}")
|
|
125
|
+
print("If in a container/cloud/terminal, ensure tools are installed (e.g., via apt/brew/Chocolatey) or build manually per RNNoise README: https://github.com/xiph/rnnoise")
|
|
126
|
+
sys.exit(1)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from videosdk.agents.denoise import Denoise
|
|
3
|
+
from .rnnoise import RNN
|
|
4
|
+
import numpy as np
|
|
5
|
+
import resampy
|
|
6
|
+
|
|
7
|
+
class RNNoise(Denoise):
|
|
8
|
+
def __init__(self):
|
|
9
|
+
super().__init__()
|
|
10
|
+
self.rnnoise = RNN()
|
|
11
|
+
self._target_sample_rate = 48000
|
|
12
|
+
self._frame_duration_ms = 20
|
|
13
|
+
self._rnnoise_frame_size = 480
|
|
14
|
+
|
|
15
|
+
async def denoise(self, audio_frames: bytes, **kwargs: Any) -> bytes:
|
|
16
|
+
if not audio_frames:
|
|
17
|
+
return b""
|
|
18
|
+
|
|
19
|
+
audio_np = np.frombuffer(audio_frames, dtype=np.int16)
|
|
20
|
+
num_samples = len(audio_np)
|
|
21
|
+
original_sample_rate = int(num_samples * 1000 / self._frame_duration_ms)
|
|
22
|
+
|
|
23
|
+
if original_sample_rate != self._target_sample_rate:
|
|
24
|
+
audio_float = audio_np.astype(np.float32) / 32767.0
|
|
25
|
+
resampled_audio_float = resampy.resample(audio_float, sr_orig=original_sample_rate, sr_new=self._target_sample_rate)
|
|
26
|
+
resampled_audio_np = (resampled_audio_float * 32767.0).astype(np.int16)
|
|
27
|
+
else:
|
|
28
|
+
resampled_audio_np = audio_np
|
|
29
|
+
|
|
30
|
+
num_rnnoise_frames = len(resampled_audio_np) // self._rnnoise_frame_size
|
|
31
|
+
denoised_chunks = []
|
|
32
|
+
|
|
33
|
+
for i in range(num_rnnoise_frames):
|
|
34
|
+
start = i * self._rnnoise_frame_size
|
|
35
|
+
end = start + self._rnnoise_frame_size
|
|
36
|
+
chunk = resampled_audio_np[start:end]
|
|
37
|
+
|
|
38
|
+
if len(chunk) != self._rnnoise_frame_size:
|
|
39
|
+
continue
|
|
40
|
+
|
|
41
|
+
chunk_bytes = chunk.tobytes()
|
|
42
|
+
_vod_prob, denoised_chunk_bytes = self.rnnoise.process_frame(chunk_bytes)
|
|
43
|
+
denoised_chunk_np = np.frombuffer(denoised_chunk_bytes, dtype=np.int16)
|
|
44
|
+
denoised_chunks.append(denoised_chunk_np)
|
|
45
|
+
|
|
46
|
+
if not denoised_chunks:
|
|
47
|
+
return b""
|
|
48
|
+
|
|
49
|
+
denoised_audio_np = np.concatenate(denoised_chunks)
|
|
50
|
+
|
|
51
|
+
if original_sample_rate != self._target_sample_rate:
|
|
52
|
+
denoised_float = denoised_audio_np.astype(np.float32) / 32767.0
|
|
53
|
+
original_format_float = resampy.resample(denoised_float, sr_orig=self._target_sample_rate, sr_new=original_sample_rate)
|
|
54
|
+
final_audio_np = (original_format_float * 32767.0).astype(np.int16)
|
|
55
|
+
else:
|
|
56
|
+
final_audio_np = denoised_audio_np
|
|
57
|
+
|
|
58
|
+
return final_audio_np.tobytes()
|
|
59
|
+
|
|
60
|
+
async def aclose(self) -> None:
|
|
61
|
+
self.rnnoise.destroy()
|
|
62
|
+
await super().aclose()
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import ctypes
|
|
2
|
+
import numpy as np
|
|
3
|
+
import os
|
|
4
|
+
import platform
|
|
5
|
+
|
|
6
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
sys_name = platform.system()
|
|
10
|
+
if sys_name == "Darwin":
|
|
11
|
+
lib_name = "librnnoise.dylib"
|
|
12
|
+
elif sys_name == "Linux":
|
|
13
|
+
lib_name = "librnnoise.so"
|
|
14
|
+
elif sys_name == "Windows":
|
|
15
|
+
lib_name = "rnnoise.dll"
|
|
16
|
+
else:
|
|
17
|
+
raise OSError(f"Unsupported OS: {sys_name}")
|
|
18
|
+
|
|
19
|
+
lib_path = os.path.join(script_dir, "files", lib_name)
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
lib = ctypes.cdll.LoadLibrary(lib_path)
|
|
23
|
+
except OSError as e:
|
|
24
|
+
raise OSError(
|
|
25
|
+
f"Error loading rnnoise library at {lib_path}. "
|
|
26
|
+
f"It may be corrupted or incompatible with your platform. "
|
|
27
|
+
f"Original error: {e}"
|
|
28
|
+
) from e
|
|
29
|
+
|
|
30
|
+
# Define the correct function signatures
|
|
31
|
+
lib.rnnoise_create.argtypes = [ctypes.c_void_p] # RNNModel *model (can be NULL)
|
|
32
|
+
lib.rnnoise_create.restype = ctypes.c_void_p
|
|
33
|
+
lib.rnnoise_destroy.argtypes = [ctypes.c_void_p]
|
|
34
|
+
lib.rnnoise_process_frame.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_float), ctypes.POINTER(ctypes.c_float)]
|
|
35
|
+
lib.rnnoise_process_frame.restype = ctypes.c_float
|
|
36
|
+
|
|
37
|
+
class RNN(object):
|
|
38
|
+
def __init__(self):
|
|
39
|
+
# Pass NULL (None) for the model parameter to use the default model
|
|
40
|
+
self.obj = lib.rnnoise_create(None)
|
|
41
|
+
if self.obj is None:
|
|
42
|
+
raise RuntimeError("Failed to create RNNoise instance")
|
|
43
|
+
|
|
44
|
+
def process_frame(self, inbuf):
|
|
45
|
+
# Create a copy of the input buffer to avoid modifying the original
|
|
46
|
+
if isinstance(inbuf, bytes):
|
|
47
|
+
# Convert bytes to numpy array
|
|
48
|
+
audio_data = np.frombuffer(inbuf, dtype=np.int16)
|
|
49
|
+
else:
|
|
50
|
+
audio_data = np.array(inbuf, dtype=np.int16)
|
|
51
|
+
|
|
52
|
+
# Ensure we have exactly 480 samples
|
|
53
|
+
if len(audio_data) != 480:
|
|
54
|
+
raise ValueError(f"Expected 480 samples, got {len(audio_data)}")
|
|
55
|
+
|
|
56
|
+
# Convert to float32 and normalize
|
|
57
|
+
audio_float = audio_data.astype(np.float32) / 32767.0
|
|
58
|
+
|
|
59
|
+
# Create output buffer
|
|
60
|
+
outbuf = np.zeros(480, dtype=np.float32)
|
|
61
|
+
|
|
62
|
+
# Get pointers
|
|
63
|
+
in_ptr = audio_float.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
|
|
64
|
+
out_ptr = outbuf.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
|
|
65
|
+
|
|
66
|
+
# Process the frame
|
|
67
|
+
VodProb = lib.rnnoise_process_frame(self.obj, out_ptr, in_ptr)
|
|
68
|
+
|
|
69
|
+
# Convert back to int16
|
|
70
|
+
outbuf_int16 = (outbuf * 32767.0).astype(np.int16)
|
|
71
|
+
|
|
72
|
+
return (VodProb, outbuf_int16.tobytes())
|
|
73
|
+
|
|
74
|
+
def destroy(self):
|
|
75
|
+
if self.obj is not None:
|
|
76
|
+
lib.rnnoise_destroy(self.obj)
|
|
77
|
+
self.obj = None
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.63.dev0"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: videosdk-plugins-rnnoise-dev
|
|
3
|
+
Version: 0.0.63.dev0
|
|
4
|
+
Summary: VideoSDK Agent Framework plugin for RNNoise.
|
|
5
|
+
Author: videosdk
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Keywords: ai,audio,lmnt,rime,tts,video,videosdk
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Topic :: Communications :: Conferencing
|
|
11
|
+
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
12
|
+
Classifier: Topic :: Multimedia :: Video
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
|
+
Requires-Dist: numpy>=1.26
|
|
16
|
+
Requires-Dist: resampy
|
|
17
|
+
Requires-Dist: videosdk-agents>=0.0.22
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# VideoSDK RNNoise Plugin
|
|
21
|
+
|
|
22
|
+
Agent Framework plugin for de-noising with RNNoise.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install videosdk-plugins-rnnoise
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Building RNNoise for your OS
|
|
31
|
+
|
|
32
|
+
To avoid OS security issues with prebuilt libraries, build RNNoise locally:
|
|
33
|
+
1. Ensure you have git and build tools (autoconf/make on Mac/Linux, Visual Studio with nmake on Windows).
|
|
34
|
+
2. Run `python build_rnnoise.py` in the project root.
|
|
35
|
+
3. This clones RNNoise, builds the library for your OS, and places it in videosdk/plugins/rnnoise/files/.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
videosdk/plugins/rnnoise/__init__.py,sha256=W1ioncb06WtjaxKXUuhQMsr6JHZmNTPeUOXqnRkQiuw,51
|
|
2
|
+
videosdk/plugins/rnnoise/build_rnnoise.py,sha256=zzyoYX04IzHs8TWq_cEwVAbW8www20oe8ldAtGtXPWU,6004
|
|
3
|
+
videosdk/plugins/rnnoise/denoise.py,sha256=t0Y8SjdRE2iVlrP3gfwmD_ArXwcBLBCi1V9-JryBJFI,2385
|
|
4
|
+
videosdk/plugins/rnnoise/rnnoise.py,sha256=tzANc9FmNYaalcnhYwaQGVZPKTc4xBfwDAtXkPD0Bu8,2366
|
|
5
|
+
videosdk/plugins/rnnoise/version.py,sha256=Q_1rx0vr-H8kMoyc5VNkl0Tdt8P1PyFB_NoS6uiUC9g,27
|
|
6
|
+
videosdk/plugins/rnnoise/files/librnnoise.0.dylib,sha256=dQMzNPgGtW4QRECenEZi6LKvFYTSzZlgNYivy1taNGI,1048576
|
|
7
|
+
videosdk/plugins/rnnoise/files/librnnoise.dylib,sha256=dQMzNPgGtW4QRECenEZi6LKvFYTSzZlgNYivy1taNGI,1048576
|
|
8
|
+
videosdk/plugins/rnnoise/files/librnnoise.so,sha256=dpC40omPtQ5kr8Q6eiif8OkWVaz7DEpE2jhsGv76L9U,1048576
|
|
9
|
+
videosdk_plugins_rnnoise_dev-0.0.63.dev0.dist-info/METADATA,sha256=RVFg8_MTthzbVvMZHjdbIsVIu2W32yAWuZ-C2xmhJ9o,1201
|
|
10
|
+
videosdk_plugins_rnnoise_dev-0.0.63.dev0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
11
|
+
videosdk_plugins_rnnoise_dev-0.0.63.dev0.dist-info/RECORD,,
|