""" SVS prompt reconstruction from preprocessed metadata. Provides :func:`rebuild_svs_prompt` for reconstructing the SVS text prompt from stored annotation fields (word, pitch, note, pitch2word, bpm). """ from typing import Dict from .text_tensor import build_text_tensor from .svs_preprocessor import create_lightweight_preprocessor def rebuild_svs_prompt( sample: Dict, tokenizer, preprocessor=None, force_lyrics_only: bool = False, ) -> str: """Rebuild SVS prompt string from metadata stored in preprocessed dataset. Works with both JSON label entries and preprocessed Arrow samples. Args: sample: Dict with word, pitch, note, pitch2word, bpm, has_score fields. tokenizer: LlamaTokenizerFast with SVS tokens added. preprocessor: Optional SVSPreprocessor (created if None). force_lyrics_only: If True, always use lyrics-only mode (```` for BPM/pitch/note), regardless of ``has_score`` in the sample. Returns: SVS prompt string (without trailing ````). """ from vocalrender.training.svs_data import convert_annotation_to_syllables words = list(sample.get("word", [])) bpm = int(sample.get("bpm", 120)) # Decide has_score: respect metadata, but override when force_lyrics_only has_score = bool( sample.get("pitch") and sample.get("note") and sample.get("has_score", True) ) and not force_lyrics_only if has_score: pitches = [int(p) for p in sample["pitch"]] notes = [str(n) for n in sample["note"]] pitch2word = [int(p) for p in sample.get("pitch2word", list(range(len(pitches))))] syllables = convert_annotation_to_syllables(words, pitches, notes, pitch2word) else: syllables = [{"char": w} for w in words] text_tensor = build_text_tensor(syllables, bpm, tokenizer, has_score=has_score) if text_tensor.shape[0] == 0: return "" if preprocessor is None: preprocessor = create_lightweight_preprocessor(tokenizer) svs_seq = preprocessor.build_svs_sequence( text_tensor, is_prompt=False, has_score=has_score, ) # Strip trailing audio_start token (model adds it during generation) seq_ids = svs_seq.tolist() if seq_ids: seq_ids = seq_ids[:-1] return tokenizer.decode(seq_ids, skip_special_tokens=False)