You've already forked OpenShot-ComfyUI
mirror of
https://github.com/OpenShot/OpenShot-ComfyUI.git
synced 2026-06-08 22:18:13 -07:00
Switching audio upscaler to LavaSR
This commit is contained in:
@@ -28,7 +28,7 @@ This project addresses that gap with chunk-oriented processing designed specific
|
||||
- `OpenShotGroundingDinoDetect` (text-prompted object detection -> mask + JSON)
|
||||
- `OpenShotTransNetSceneDetect` (direct TransNetV2 inference -> IN/OUT JSON ranges)
|
||||
- `OpenShotDeepFilterNetDenoiseAudio` (file-path based audio denoise -> FLAC path)
|
||||
- `OpenShotAudioSRClarity` (isolated AudioSR runner -> FLAC path)
|
||||
- `OpenShotLavaSRSpeechClarity` (isolated LavaSR speech runner -> FLAC path)
|
||||
|
||||
## Attribution
|
||||
|
||||
@@ -44,6 +44,7 @@ Please see upstream projects for full original implementations and credits.
|
||||
- ComfyUI
|
||||
- PyTorch (as used by your Comfy install)
|
||||
- `ffmpeg` and `ffprobe` available on your `PATH`
|
||||
- `git` available on your `PATH` for LavaSR's first-use isolated install
|
||||
|
||||
Install this node pack into `ComfyUI/custom_nodes/OpenShot-ComfyUI` and restart ComfyUI.
|
||||
|
||||
@@ -83,7 +84,7 @@ SAM2 is installed separately on purpose. Keeping it out of `requirements.txt` ma
|
||||
- `OpenShotDownloadAndLoadSAM2Model` downloads supported SAM2 checkpoints into `ComfyUI/models/sam2` on first use.
|
||||
- `OpenShotGroundingDinoDetect` downloads model weights from Hugging Face on first use and uses the normal HF cache.
|
||||
- `OpenShotDeepFilterNetDenoiseAudio` downloads the default `DeepFilterNet3` model on first use using DeepFilterNet's cache directory.
|
||||
- `OpenShotAudioSRClarity` creates an isolated local venv on first use, installs AudioSR there, and then downloads the selected AudioSR checkpoint from Hugging Face on first run.
|
||||
- `OpenShotLavaSRSpeechClarity` creates an isolated local venv on first use, installs LavaSR there, and then downloads the `YatharthS/LavaSR` model snapshot from Hugging Face on first run.
|
||||
- `OpenShotTransNetSceneDetect` does not require a separate manual weight download from this node pack.
|
||||
|
||||
## Audio denoise node
|
||||
@@ -98,17 +99,17 @@ SAM2 is installed separately on purpose. Keeping it out of `requirements.txt` ma
|
||||
|
||||
The node accepts typical audio formats that `ffmpeg` can decode and always writes a new `.flac` file into ComfyUI's output folder under `openshot_audio/`.
|
||||
|
||||
## Audio clarity node
|
||||
## Speech clarity node
|
||||
|
||||
`OpenShotAudioSRClarity` is intended for low-fidelity or bandwidth-limited audio:
|
||||
`OpenShotLavaSRSpeechClarity` is intended for low-quality speech recordings:
|
||||
|
||||
- Input: `source_audio_path` or `audio`, `model_name`, `keep_model_loaded`
|
||||
- Input: `source_audio_path` or `audio`, `keep_model_loaded`
|
||||
- Output: a new FLAC file path
|
||||
- `model_name=speech` is intended for spoken voice
|
||||
- `model_name=basic` is intended for music and sound effects
|
||||
- Uses LavaSR v2 speech enhancement
|
||||
- Intended for speech/dialogue clarity, not general music restoration
|
||||
|
||||
AudioSR is intentionally not installed into the main Comfy environment because its package pins older `numpy`, `librosa`, and `transformers` versions. Instead, the node creates and uses an isolated runner environment on first use so the main Comfy environment stays stable.
|
||||
That means there is no extra install command for AudioSR, but the first `Clarity` run will take longer while the isolated environment and model checkpoint are prepared.
|
||||
LavaSR is installed into an isolated runner environment on first use so the main Comfy environment stays stable.
|
||||
That means there is no extra install command for LavaSR, but the first `Clarity -> Speech` run will take longer while the isolated environment and model snapshot are prepared.
|
||||
|
||||
## Validation script
|
||||
|
||||
@@ -131,7 +132,7 @@ It checks:
|
||||
- `OpenShotSam2VideoSegmentationChunked` returns only the requested chunk range (bounded memory) instead of collecting whole-video masks.
|
||||
- For very long videos, pair chunked outputs with batch-safe downstream nodes (VHS meta-batch, staged processing, or on-disk intermediates).
|
||||
- `torchaudio` is listed explicitly because DeepFilterNet imports it internally and some environments do not pull it in automatically.
|
||||
- AudioSR uses a separate on-demand runner environment to avoid downgrading shared Comfy dependencies.
|
||||
- LavaSR uses a separate on-demand runner environment and preserves the original channel count by processing each channel independently before recombining the output.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -4,28 +4,28 @@ import time
|
||||
import venv
|
||||
|
||||
|
||||
AUDIOSR_ENV_VERSION = "6"
|
||||
LAVASR_ENV_VERSION = "1"
|
||||
|
||||
|
||||
def _log(message):
|
||||
print("[OpenShot-ComfyUI:AudioSR] {}".format(message), flush=True)
|
||||
print("[OpenShot-ComfyUI:LavaSR] {}".format(message), flush=True)
|
||||
|
||||
|
||||
def audiosr_env_dir(base_dir):
|
||||
path = os.path.join(base_dir, ".openshot_envs", "audiosr")
|
||||
def lavasr_env_dir(base_dir):
|
||||
path = os.path.join(base_dir, ".openshot_envs", "lavasr")
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
return path
|
||||
|
||||
|
||||
def audiosr_python_path(base_dir):
|
||||
env_dir = audiosr_env_dir(base_dir)
|
||||
def lavasr_python_path(base_dir):
|
||||
env_dir = lavasr_env_dir(base_dir)
|
||||
if os.name == "nt":
|
||||
return os.path.join(env_dir, "Scripts", "python.exe")
|
||||
return os.path.join(env_dir, "bin", "python")
|
||||
|
||||
|
||||
def audiosr_runner_path(base_dir):
|
||||
return os.path.join(base_dir, "audiosr_runner.py")
|
||||
def lavasr_runner_path(base_dir):
|
||||
return os.path.join(base_dir, "lavasr_runner.py")
|
||||
|
||||
|
||||
def run_checked(cmd, error_prefix):
|
||||
@@ -55,7 +55,7 @@ def run_checked(cmd, error_prefix):
|
||||
raise RuntimeError("{}: {}".format(error_prefix, err))
|
||||
|
||||
|
||||
def audiosr_env_needs_refresh(marker_path, python_path):
|
||||
def lavasr_env_needs_refresh(marker_path, python_path):
|
||||
if not os.path.isfile(marker_path) or not os.path.isfile(python_path):
|
||||
return True
|
||||
try:
|
||||
@@ -63,16 +63,16 @@ def audiosr_env_needs_refresh(marker_path, python_path):
|
||||
lines = [line.strip() for line in handle.readlines() if line.strip()]
|
||||
except Exception:
|
||||
return True
|
||||
return (not lines) or lines[0] != AUDIOSR_ENV_VERSION
|
||||
return (not lines) or lines[0] != LAVASR_ENV_VERSION
|
||||
|
||||
|
||||
def ensure_audiosr_environment(base_dir):
|
||||
env_dir = audiosr_env_dir(base_dir)
|
||||
python_path = audiosr_python_path(base_dir)
|
||||
def ensure_lavasr_environment(base_dir):
|
||||
env_dir = lavasr_env_dir(base_dir)
|
||||
python_path = lavasr_python_path(base_dir)
|
||||
marker_path = os.path.join(env_dir, ".ready")
|
||||
runner_path = audiosr_runner_path(base_dir)
|
||||
runner_path = lavasr_runner_path(base_dir)
|
||||
|
||||
if not audiosr_env_needs_refresh(marker_path, python_path):
|
||||
if not lavasr_env_needs_refresh(marker_path, python_path):
|
||||
_log("Using existing isolated environment: {}".format(env_dir))
|
||||
return python_path
|
||||
|
||||
@@ -86,8 +86,8 @@ def ensure_audiosr_environment(base_dir):
|
||||
builder.create(env_dir)
|
||||
|
||||
_log("Bootstrapping pip/setuptools/wheel")
|
||||
run_checked([python_path, "-m", "pip", "install", "--upgrade", "pip", "setuptools", "wheel"], "AudioSR pip bootstrap failed")
|
||||
_log("Installing AudioSR core package")
|
||||
run_checked([python_path, "-m", "pip", "install", "--upgrade", "pip", "setuptools", "wheel"], "LavaSR pip bootstrap failed")
|
||||
_log("Installing LavaSR")
|
||||
run_checked(
|
||||
[
|
||||
python_path,
|
||||
@@ -95,50 +95,17 @@ def ensure_audiosr_environment(base_dir):
|
||||
"pip",
|
||||
"install",
|
||||
"--upgrade",
|
||||
"--no-deps",
|
||||
"audiosr==0.0.7",
|
||||
],
|
||||
"AudioSR core package install failed",
|
||||
)
|
||||
_log("Installing AudioSR dependency stack")
|
||||
run_checked(
|
||||
[
|
||||
python_path,
|
||||
"-m",
|
||||
"pip",
|
||||
"install",
|
||||
"--upgrade",
|
||||
"numpy<=1.23.5",
|
||||
"librosa==0.9.2",
|
||||
"transformers==4.30.2",
|
||||
"soundfile",
|
||||
"phonemizer",
|
||||
"torchlibrosa>=0.0.9",
|
||||
"tqdm",
|
||||
"progressbar",
|
||||
"ipdb",
|
||||
"dlinfo",
|
||||
"segments",
|
||||
"csvw",
|
||||
"language-tags",
|
||||
"ftfy",
|
||||
"einops",
|
||||
"pandas",
|
||||
"unidecode",
|
||||
"chardet",
|
||||
"pyyaml",
|
||||
"gradio",
|
||||
"git+https://github.com/ysharma3501/LavaSR.git",
|
||||
"huggingface-hub",
|
||||
"scipy",
|
||||
"timm",
|
||||
],
|
||||
"AudioSR dependency install failed",
|
||||
"LavaSR dependency install failed",
|
||||
)
|
||||
|
||||
with open(marker_path, "w", encoding="utf-8") as handle:
|
||||
handle.write("{}\n".format(AUDIOSR_ENV_VERSION))
|
||||
handle.write("{}\n".format(LAVASR_ENV_VERSION))
|
||||
handle.write("{}\n".format(time.time()))
|
||||
if not os.path.isfile(runner_path):
|
||||
raise RuntimeError("AudioSR runner script not found: {}".format(runner_path))
|
||||
raise RuntimeError("LavaSR runner script not found: {}".format(runner_path))
|
||||
_log("Isolated environment ready")
|
||||
return python_path
|
||||
|
||||
@@ -2,7 +2,6 @@ import argparse
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import wave
|
||||
|
||||
@@ -10,7 +9,7 @@ import numpy as np
|
||||
|
||||
|
||||
def log(message):
|
||||
print("[OpenShot-ComfyUI:AudioSR] {}".format(message), flush=True)
|
||||
print("[OpenShot-ComfyUI:LavaSR] {}".format(message), flush=True)
|
||||
|
||||
|
||||
def run_ffmpeg(args, error_prefix):
|
||||
@@ -77,6 +76,8 @@ def load_pcm16_wav(path):
|
||||
raise RuntimeError("Expected 16-bit PCM WAV, got sample width {}".format(sample_width))
|
||||
raw = wav_file.readframes(frame_count)
|
||||
audio = np.frombuffer(raw, dtype="<i2").astype(np.float32)
|
||||
if channels <= 0:
|
||||
raise RuntimeError("Invalid WAV channel count: {}".format(channels))
|
||||
audio = audio.reshape(-1, channels).T
|
||||
audio /= 32768.0
|
||||
return audio, sample_rate
|
||||
@@ -96,30 +97,7 @@ def save_pcm16_wav(path, audio, sample_rate):
|
||||
|
||||
|
||||
def save_mono_pcm16_wav(path, audio, sample_rate):
|
||||
audio = np.asarray(audio, dtype=np.float32).reshape(1, -1)
|
||||
save_pcm16_wav(path, audio, sample_rate)
|
||||
|
||||
|
||||
def patch_torchaudio_load():
|
||||
import torch
|
||||
import torchaudio
|
||||
|
||||
def _safe_load(path, *args, **kwargs):
|
||||
audio_np, sample_rate = load_pcm16_wav(path)
|
||||
waveform = torch.from_numpy(audio_np)
|
||||
return waveform, sample_rate
|
||||
|
||||
torchaudio.load = _safe_load
|
||||
|
||||
|
||||
def patch_strip_silence():
|
||||
import audiosr.utils as audiosr_utils
|
||||
|
||||
def _no_strip(input_path, temp_path, save_path):
|
||||
del input_path
|
||||
os.replace(temp_path, save_path)
|
||||
|
||||
audiosr_utils.strip_silence = _no_strip
|
||||
save_pcm16_wav(path, np.asarray(audio, dtype=np.float32).reshape(1, -1), sample_rate)
|
||||
|
||||
|
||||
def is_cuda_oom(ex):
|
||||
@@ -127,83 +105,53 @@ def is_cuda_oom(ex):
|
||||
return "outofmemoryerror" in text or "out of memory" in text or "would exceed allowed memory" in text
|
||||
|
||||
|
||||
def run_audiosr(build_model, super_resolution, source_wav, model_name, device_name):
|
||||
log("Loading AudioSR model '{}' on {}".format(model_name, device_name))
|
||||
model = build_model(model_name=model_name, device=device_name)
|
||||
try:
|
||||
log("Running AudioSR super resolution on {}".format(device_name))
|
||||
waveform = super_resolution(
|
||||
model,
|
||||
source_wav,
|
||||
seed=42,
|
||||
guidance_scale=3.5,
|
||||
ddim_steps=50,
|
||||
latent_t_per_second=12.8,
|
||||
)
|
||||
return waveform
|
||||
finally:
|
||||
def safe_input_sr(source_sr):
|
||||
source_sr = int(source_sr or 16000)
|
||||
return max(8000, min(48000, source_sr))
|
||||
|
||||
|
||||
def run_lavasr_channels(source_audio_np, source_sr, device_name, tmp_dir):
|
||||
from LavaSR.model import LavaEnhance2
|
||||
|
||||
input_sr = safe_input_sr(source_sr)
|
||||
log("Loading LavaSR speech model on {}".format(device_name))
|
||||
lava_model = LavaEnhance2("YatharthS/LavaSR", device_name)
|
||||
channel_outputs = []
|
||||
for channel_index in range(int(source_audio_np.shape[0])):
|
||||
log("Enhancing channel {}/{} on {}".format(channel_index + 1, int(source_audio_np.shape[0]), device_name))
|
||||
channel_path = os.path.join(tmp_dir, "channel_{}.wav".format(channel_index))
|
||||
save_mono_pcm16_wav(channel_path, source_audio_np[channel_index], int(source_sr))
|
||||
channel_audio, _input_sr = lava_model.load_audio(channel_path, input_sr=input_sr)
|
||||
output_audio = lava_model.enhance(channel_audio, denoise=False, batch=False).cpu().numpy().reshape(-1)
|
||||
channel_outputs.append(output_audio.astype(np.float32))
|
||||
max_len = max(int(ch.shape[0]) for ch in channel_outputs)
|
||||
padded = []
|
||||
for ch in channel_outputs:
|
||||
if int(ch.shape[0]) < max_len:
|
||||
ch = np.pad(ch, (0, max_len - int(ch.shape[0])), mode="edge")
|
||||
padded.append(ch)
|
||||
if device_name != "cpu":
|
||||
try:
|
||||
model.cpu()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def normalize_audiosr_output(waveform):
|
||||
out_np = np.asarray(waveform, dtype=np.float32)
|
||||
if out_np.ndim == 3:
|
||||
out_np = out_np[0]
|
||||
if out_np.ndim == 1:
|
||||
out_np = out_np.reshape(1, -1)
|
||||
return out_np
|
||||
|
||||
|
||||
def run_audiosr_with_model(model, super_resolution, source_wav, device_name):
|
||||
log("Running AudioSR super resolution on {}".format(device_name))
|
||||
return super_resolution(
|
||||
model,
|
||||
source_wav,
|
||||
seed=42,
|
||||
guidance_scale=3.5,
|
||||
ddim_steps=50,
|
||||
latent_t_per_second=12.8,
|
||||
)
|
||||
|
||||
|
||||
def run_audiosr_channels(build_model, super_resolution, source_audio_np, source_sr, model_name, device_name, tmp_dir):
|
||||
log("Loading AudioSR model '{}' on {}".format(model_name, device_name))
|
||||
model = build_model(model_name=model_name, device=device_name)
|
||||
try:
|
||||
channel_outputs = []
|
||||
for channel_index in range(int(source_audio_np.shape[0])):
|
||||
log("Enhancing channel {}/{} on {}".format(channel_index + 1, int(source_audio_np.shape[0]), device_name))
|
||||
channel_path = os.path.join(tmp_dir, "channel_{}.wav".format(channel_index))
|
||||
save_mono_pcm16_wav(channel_path, source_audio_np[channel_index], int(source_sr))
|
||||
waveform = run_audiosr_with_model(model, super_resolution, channel_path, device_name)
|
||||
channel_outputs.append(normalize_audiosr_output(waveform)[0])
|
||||
return np.stack(channel_outputs, axis=0)
|
||||
finally:
|
||||
try:
|
||||
model.cpu()
|
||||
torch.cuda.empty_cache()
|
||||
except Exception:
|
||||
pass
|
||||
return np.stack(padded, axis=0)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--input", required=True)
|
||||
parser.add_argument("--output", required=True)
|
||||
parser.add_argument("--model-name", required=True, choices=["basic", "speech"])
|
||||
parser.add_argument("--release-model", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
import torch
|
||||
|
||||
input_path = os.path.abspath(args.input)
|
||||
output_path = os.path.abspath(args.output)
|
||||
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||
source_info = probe_audio_info(input_path)
|
||||
|
||||
tmp_dir = tempfile.mkdtemp(prefix="openshot_audiosr_")
|
||||
tmp_dir = tempfile.mkdtemp(prefix="openshot_lavasr_")
|
||||
try:
|
||||
source_wav = os.path.join(tmp_dir, "source.wav")
|
||||
enhanced_wav = os.path.join(tmp_dir, "enhanced.wav")
|
||||
@@ -211,31 +159,20 @@ def main():
|
||||
decode_audio_to_wav(input_path, source_wav)
|
||||
source_audio_np, source_sr = load_pcm16_wav(source_wav)
|
||||
|
||||
patch_torchaudio_load()
|
||||
log("Importing AudioSR")
|
||||
from audiosr import build_model, super_resolution
|
||||
|
||||
patch_strip_silence()
|
||||
|
||||
waveform = None
|
||||
try:
|
||||
waveform = run_audiosr_channels(build_model, super_resolution, source_audio_np, source_sr, args.model_name, "auto", tmp_dir)
|
||||
preferred_device = "cuda" if torch.cuda.is_available() else "cpu"
|
||||
waveform = run_lavasr_channels(source_audio_np, source_sr, preferred_device, tmp_dir)
|
||||
except Exception as ex:
|
||||
if not is_cuda_oom(ex):
|
||||
if preferred_device != "cuda" or not is_cuda_oom(ex):
|
||||
raise
|
||||
log("CUDA OOM during AudioSR; retrying on CPU")
|
||||
try:
|
||||
if torch.cuda.is_available():
|
||||
torch.cuda.empty_cache()
|
||||
except Exception:
|
||||
pass
|
||||
waveform = run_audiosr_channels(build_model, super_resolution, source_audio_np, source_sr, args.model_name, "cpu", tmp_dir)
|
||||
log("CUDA OOM during LavaSR; retrying on CPU")
|
||||
waveform = run_lavasr_channels(source_audio_np, source_sr, "cpu", tmp_dir)
|
||||
|
||||
out_np = normalize_audiosr_output(waveform)
|
||||
log("Encoding enhanced audio to FLAC")
|
||||
save_pcm16_wav(enhanced_wav, out_np, 48000)
|
||||
save_pcm16_wav(enhanced_wav, waveform, 48000)
|
||||
encode_audio_to_flac(enhanced_wav, output_path, source_info.get("channel_layout"))
|
||||
log("AudioSR output ready: {}".format(output_path))
|
||||
log("LavaSR output ready: {}".format(output_path))
|
||||
return 0
|
||||
finally:
|
||||
import shutil
|
||||
@@ -6,7 +6,6 @@ import shutil
|
||||
import sys
|
||||
import time
|
||||
import tempfile
|
||||
import venv
|
||||
import wave
|
||||
from contextlib import nullcontext
|
||||
from urllib.parse import urlparse
|
||||
@@ -18,9 +17,9 @@ import torch.nn.functional as F
|
||||
from torch.hub import download_url_to_file
|
||||
from PIL import Image
|
||||
try:
|
||||
from .audiosr_bootstrap import audiosr_runner_path, ensure_audiosr_environment, run_checked
|
||||
from .lavasr_bootstrap import lavasr_runner_path, ensure_lavasr_environment, run_checked
|
||||
except Exception:
|
||||
from audiosr_bootstrap import audiosr_runner_path, ensure_audiosr_environment, run_checked
|
||||
from lavasr_bootstrap import lavasr_runner_path, ensure_lavasr_environment, run_checked
|
||||
|
||||
import comfy.model_management as mm
|
||||
from comfy.utils import ProgressBar, common_upscale
|
||||
@@ -65,12 +64,6 @@ except Exception as ex: # pragma: no cover - runtime env specific
|
||||
else:
|
||||
_deepfilternet_import_error = None
|
||||
|
||||
try:
|
||||
import torchvision
|
||||
except Exception:
|
||||
torchvision = None
|
||||
|
||||
|
||||
SAM2_MODEL_DIR = "sam2"
|
||||
OPENSHOT_NODEPACK_VERSION = "v1.1.2-track-object-keyframes"
|
||||
GROUNDING_DINO_MODEL_IDS = (
|
||||
@@ -149,11 +142,9 @@ def _require_deepfilternet():
|
||||
)
|
||||
|
||||
|
||||
def _require_audiosr_bootstrap():
|
||||
def _require_lavasr_bootstrap():
|
||||
if torchaudio is None:
|
||||
raise RuntimeError("AudioSR bootstrap requires torchaudio in the main Comfy environment")
|
||||
if torchvision is None:
|
||||
raise RuntimeError("AudioSR bootstrap requires torchvision in the main Comfy environment")
|
||||
raise RuntimeError("LavaSR bootstrap requires torchaudio in the main Comfy environment")
|
||||
|
||||
|
||||
def _model_storage_dir():
|
||||
@@ -1174,13 +1165,13 @@ def _deepfilternet_runner_path():
|
||||
return os.path.join(os.path.dirname(os.path.abspath(__file__)), "deepfilternet_runner.py")
|
||||
|
||||
|
||||
def _audiosr_runner_path():
|
||||
return audiosr_runner_path(os.path.dirname(os.path.abspath(__file__)))
|
||||
def _lavasr_runner_path():
|
||||
return lavasr_runner_path(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
|
||||
def _ensure_audiosr_environment():
|
||||
_require_audiosr_bootstrap()
|
||||
return ensure_audiosr_environment(os.path.dirname(os.path.abspath(__file__)))
|
||||
def _ensure_lavasr_environment():
|
||||
_require_lavasr_bootstrap()
|
||||
return ensure_lavasr_environment(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
|
||||
class OpenShotSceneRangesFromSegments:
|
||||
@@ -2949,7 +2940,7 @@ class OpenShotDeepFilterNetDenoiseAudio:
|
||||
return (output_path,)
|
||||
|
||||
|
||||
class OpenShotAudioSRClarity:
|
||||
class OpenShotLavaSRSpeechClarity:
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, **kwargs):
|
||||
return ""
|
||||
@@ -2959,7 +2950,6 @@ class OpenShotAudioSRClarity:
|
||||
return {
|
||||
"required": {
|
||||
"source_audio_path": ("STRING", {"default": ""}),
|
||||
"model_name": (["speech", "basic"], {"default": "speech"}),
|
||||
"keep_model_loaded": ("BOOLEAN", {"default": True}),
|
||||
},
|
||||
"optional": {
|
||||
@@ -2968,7 +2958,7 @@ class OpenShotAudioSRClarity:
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("clarified_audio_path",)
|
||||
RETURN_NAMES = ("clarified_speech_audio_path",)
|
||||
FUNCTION = "enhance"
|
||||
CATEGORY = "OpenShot/Audio"
|
||||
|
||||
@@ -2979,21 +2969,20 @@ class OpenShotAudioSRClarity:
|
||||
raise ValueError("Audio path not found: {}".format(source_audio_path))
|
||||
return source_path
|
||||
|
||||
def _build_output_path(self, source_path, model_name):
|
||||
def _build_output_path(self, source_path):
|
||||
output_dir = os.path.join(_safe_output_directory(), "openshot_audio")
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
stem = _sanitize_filename_part(os.path.splitext(os.path.basename(source_path))[0], default="audio")
|
||||
stat = os.stat(source_path)
|
||||
key = "{}|{}|{}|{}".format(
|
||||
key = "{}|{}|{}".format(
|
||||
source_path,
|
||||
int(stat.st_mtime_ns),
|
||||
int(stat.st_size),
|
||||
str(model_name or "basic"),
|
||||
)
|
||||
digest = hashlib.sha256(key.encode("utf-8")).hexdigest()[:10]
|
||||
return os.path.join(output_dir, "{}_clarity_{}_{}.flac".format(stem, str(model_name), digest))
|
||||
return os.path.join(output_dir, "{}_clarity_speech_{}.flac".format(stem, digest))
|
||||
|
||||
def enhance(self, source_audio_path, model_name, keep_model_loaded, audio=None):
|
||||
def enhance(self, source_audio_path, keep_model_loaded, audio=None):
|
||||
temp_source_dir = None
|
||||
if audio is not None:
|
||||
temp_source_dir = tempfile.mkdtemp(prefix="openshot_audio_input_", dir=folder_paths.get_temp_directory())
|
||||
@@ -3002,16 +2991,16 @@ class OpenShotAudioSRClarity:
|
||||
else:
|
||||
source_path = self._resolve_source_path(source_audio_path)
|
||||
|
||||
output_path = self._build_output_path(source_path, model_name)
|
||||
output_path = self._build_output_path(source_path)
|
||||
if os.path.isfile(output_path):
|
||||
if temp_source_dir:
|
||||
shutil.rmtree(temp_source_dir, ignore_errors=True)
|
||||
return (output_path,)
|
||||
|
||||
runner = _audiosr_runner_path()
|
||||
runner = _lavasr_runner_path()
|
||||
if not os.path.isfile(runner):
|
||||
raise RuntimeError("AudioSR runner script not found: {}".format(runner))
|
||||
python_path = _ensure_audiosr_environment()
|
||||
raise RuntimeError("LavaSR runner script not found: {}".format(runner))
|
||||
python_path = _ensure_lavasr_environment()
|
||||
|
||||
cmd = [
|
||||
python_path,
|
||||
@@ -3020,20 +3009,18 @@ class OpenShotAudioSRClarity:
|
||||
source_path,
|
||||
"--output",
|
||||
output_path,
|
||||
"--model-name",
|
||||
str(model_name or "basic"),
|
||||
]
|
||||
if not bool(keep_model_loaded):
|
||||
cmd.append("--release-model")
|
||||
|
||||
try:
|
||||
run_checked(cmd, "AudioSR enhancement failed")
|
||||
run_checked(cmd, "LavaSR enhancement failed")
|
||||
finally:
|
||||
if temp_source_dir:
|
||||
shutil.rmtree(temp_source_dir, ignore_errors=True)
|
||||
|
||||
if not os.path.isfile(output_path):
|
||||
raise RuntimeError("AudioSR enhancement did not produce output: {}".format(output_path))
|
||||
raise RuntimeError("LavaSR enhancement did not produce output: {}".format(output_path))
|
||||
return (output_path,)
|
||||
|
||||
|
||||
@@ -3193,7 +3180,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"OpenShotImageBlurMasked": OpenShotImageBlurMasked,
|
||||
"OpenShotImageHighlightMasked": OpenShotImageHighlightMasked,
|
||||
"OpenShotDeepFilterNetDenoiseAudio": OpenShotDeepFilterNetDenoiseAudio,
|
||||
"OpenShotAudioSRClarity": OpenShotAudioSRClarity,
|
||||
"OpenShotLavaSRSpeechClarity": OpenShotLavaSRSpeechClarity,
|
||||
"OpenShotGroundingDinoDetect": OpenShotGroundingDinoDetect,
|
||||
"OpenShotSceneRangesFromSegments": OpenShotSceneRangesFromSegments,
|
||||
}
|
||||
@@ -3207,7 +3194,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"OpenShotImageBlurMasked": "OpenShot Blur Masked (Skip Empty)",
|
||||
"OpenShotImageHighlightMasked": "OpenShot Highlight Masked",
|
||||
"OpenShotDeepFilterNetDenoiseAudio": "OpenShot DeepFilterNet Audio Denoise",
|
||||
"OpenShotAudioSRClarity": "OpenShot AudioSR Clarity",
|
||||
"OpenShotLavaSRSpeechClarity": "OpenShot LavaSR Speech Clarity",
|
||||
"OpenShotGroundingDinoDetect": "OpenShot GroundingDINO Detect",
|
||||
"OpenShotSceneRangesFromSegments": "OpenShot Scene Ranges From Segments",
|
||||
}
|
||||
|
||||
+14
-14
@@ -4,9 +4,9 @@ import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
try:
|
||||
from .audiosr_bootstrap import audiosr_runner_path, ensure_audiosr_environment
|
||||
from .lavasr_bootstrap import lavasr_runner_path, ensure_lavasr_environment
|
||||
except Exception:
|
||||
from audiosr_bootstrap import audiosr_runner_path, ensure_audiosr_environment
|
||||
from lavasr_bootstrap import lavasr_runner_path, ensure_lavasr_environment
|
||||
|
||||
|
||||
BASE_REQUIRED_MODULES = [
|
||||
@@ -36,7 +36,7 @@ EXPECTED_NODES = [
|
||||
"OpenShotImageBlurMasked",
|
||||
"OpenShotImageHighlightMasked",
|
||||
"OpenShotDeepFilterNetDenoiseAudio",
|
||||
"OpenShotAudioSRClarity",
|
||||
"OpenShotLavaSRSpeechClarity",
|
||||
"OpenShotGroundingDinoDetect",
|
||||
"OpenShotSceneRangesFromSegments",
|
||||
]
|
||||
@@ -114,24 +114,24 @@ def check_deepfilternet_runner():
|
||||
return False
|
||||
|
||||
|
||||
def check_audiosr_runner():
|
||||
def check_lavasr_runner():
|
||||
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
runner = audiosr_runner_path(base_dir)
|
||||
runner = lavasr_runner_path(base_dir)
|
||||
if not os.path.isfile(runner):
|
||||
print("[FAIL] audiosr runner missing: {}".format(runner))
|
||||
print("[FAIL] lavasr runner missing: {}".format(runner))
|
||||
return False
|
||||
if not module_available("torch") or not module_available("torchaudio") or not module_available("torchvision"):
|
||||
print("[OK] audiosr runner present (skipping isolated env probe; main torch/torchaudio/torchvision not available)")
|
||||
if not module_available("torch") or not module_available("torchaudio"):
|
||||
print("[OK] lavasr runner present (skipping isolated env probe; main torch/torchaudio not available)")
|
||||
return True
|
||||
try:
|
||||
python_path = ensure_audiosr_environment(base_dir)
|
||||
python_path = ensure_lavasr_environment(base_dir)
|
||||
except Exception as ex:
|
||||
print("[FAIL] audiosr isolated env bootstrap: {}".format(ex))
|
||||
print("[FAIL] lavasr isolated env bootstrap: {}".format(ex))
|
||||
return False
|
||||
|
||||
code = (
|
||||
"import warnings; warnings.filterwarnings('ignore');"
|
||||
"from audiosr import build_model, super_resolution;"
|
||||
"from LavaSR.model import LavaEnhance2;"
|
||||
"print('ok')"
|
||||
)
|
||||
try:
|
||||
@@ -142,11 +142,11 @@ def check_audiosr_runner():
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
print("[OK] audiosr isolated env import compatibility")
|
||||
print("[OK] lavasr isolated env import compatibility")
|
||||
return True
|
||||
except subprocess.CalledProcessError as ex:
|
||||
err = "\n".join(part.strip() for part in ((ex.stdout or ""), (ex.stderr or "")) if part.strip())
|
||||
print("[FAIL] audiosr isolated env import compatibility: {}".format(err or "unknown error"))
|
||||
print("[FAIL] lavasr isolated env import compatibility: {}".format(err or "unknown error"))
|
||||
return False
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ def main():
|
||||
ok = check_module(import_name, label) and ok
|
||||
|
||||
ok = check_deepfilternet_runner() and ok
|
||||
ok = check_audiosr_runner() and ok
|
||||
ok = check_lavasr_runner() and ok
|
||||
|
||||
for binary in ("ffmpeg", "ffprobe"):
|
||||
ok = check_binary(binary) and ok
|
||||
|
||||
Reference in New Issue
Block a user