From 27b4424e1a452492c1f2e78cf5c7933b2a80ced1 Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Sat, 4 Apr 2026 16:22:59 +0200 Subject: [PATCH] feat: prompt entered once in SelvaFeatureExtractor, reused by SelvaSampler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SelvaFeatureExtractor now stores the prompt in SELVA_FEATURES (both in the returned dict and the .npz cache). SelvaSampler's prompt is now optional — when left empty it falls back to the prompt stored in features. A non-empty override can still be passed when CLIP text should differ from the sync text. Co-Authored-By: Claude Sonnet 4.6 --- nodes/selva_feature_extractor.py | 7 ++++++- nodes/selva_sampler.py | 20 +++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/nodes/selva_feature_extractor.py b/nodes/selva_feature_extractor.py index 2db1526..c802026 100644 --- a/nodes/selva_feature_extractor.py +++ b/nodes/selva_feature_extractor.py @@ -150,6 +150,7 @@ class SelvaFeatureExtractor: clip_features=clip_features.cpu().float().numpy(), sync_features=sync_features.cpu().float().numpy(), duration=float(duration), + prompt=np.array(prompt), ) print(f"[SelVA] Features cached: {cached_path}", flush=True) @@ -157,13 +158,17 @@ class SelvaFeatureExtractor: "clip_features": clip_features.cpu(), "sync_features": sync_features.cpu(), "duration": float(duration), + "prompt": prompt, }, float(fps)) def _load_cached(path): data = np.load(path, allow_pickle=False) - return { + features = { "clip_features": torch.from_numpy(data["clip_features"]), "sync_features": torch.from_numpy(data["sync_features"]), "duration": float(data["duration"]), } + if "prompt" in data: + features["prompt"] = str(data["prompt"]) + return features diff --git a/nodes/selva_sampler.py b/nodes/selva_sampler.py index 3681a1d..21c5802 100644 --- a/nodes/selva_sampler.py +++ b/nodes/selva_sampler.py @@ -11,10 +11,6 @@ class SelvaSampler: "required": { "model": ("SELVA_MODEL",), "features": ("SELVA_FEATURES",), - "prompt": ("STRING", { - "default": "", "multiline": True, - "tooltip": "Should match the prompt used in SelvaFeatureExtractor.", - }), "negative_prompt": ("STRING", { "default": "", "multiline": True, "tooltip": "Sounds to steer away from, e.g. 'wind noise, background music'.", @@ -29,6 +25,12 @@ class SelvaSampler: "tooltip": "CFG scale (SelVA default is 4.5)."}), "seed": ("INT", {"default": 0, "min": 0, "max": 0xFFFFFFFF}), }, + "optional": { + "prompt": ("STRING", { + "default": "", "multiline": True, + "tooltip": "CLIP text for audio generation. Leave empty to reuse the prompt from SelvaFeatureExtractor.", + }), + }, } RETURN_TYPES = ("AUDIO",) @@ -36,7 +38,7 @@ class SelvaSampler: FUNCTION = "generate" CATEGORY = PRISMAUDIO_CATEGORY - def generate(self, model, features, prompt, negative_prompt, duration, steps, cfg_strength, seed): + def generate(self, model, features, negative_prompt, duration, steps, cfg_strength, seed, prompt=None): from selva_core.model.flow_matching import FlowMatching from selva_core.model.sequence_config import SequenceConfig @@ -47,6 +49,14 @@ class SelvaSampler: feature_utils = model["feature_utils"] mode = model["mode"] + # Resolve prompt: use override if given, otherwise fall back to features prompt + if not prompt or not prompt.strip(): + prompt = features.get("prompt", "") + if prompt: + print(f"[SelVA] Using prompt from features: '{prompt[:60]}'", flush=True) + else: + print("[SelVA] Warning: no prompt in features or sampler — CLIP text conditioning will be empty.", flush=True) + # Resolve duration if duration <= 0: if "duration" not in features: