def regroup_words( words: list[dict], max_len: float = 15.0, gap: float = 0.50, ) -> list[dict]: """ Returns a list of segments with keys: 'start', 'end', 'text', 'words' """ if not words: return [] segs, seg_words = [], [] seg_start = words[0]["start"] last_end = seg_start for w in words: over_max = (w["end"] - seg_start) > max_len long_gap = (w["start"] - last_end) > gap if (seg_words and (over_max or long_gap)): segs.append({ "start": seg_start, "end": last_end, "segment": " ".join(x["word"] for x in seg_words), }) seg_words = [] seg_start = w["start"] seg_words.append(w) last_end = w["end"] # flush final segment segs.append({ "start": seg_start, "end": last_end, "segment": " ".join(x["word"] for x in seg_words), }) return segs def text_to_words(text: str) -> list[dict]: """ Convert text format like "word[start:end] word[start:end]..." to word list. Args: text: String in format "It's[4.96:5.52] a[5.52:5.84] long[5.84:6.16]..." Returns: List of word dictionaries with keys: 'word', 'start', 'end' """ import re if not text.strip(): return [] # Pattern to match word[start:end] format pattern = r'(\S+?)\[([^:]+):([^\]]+)\]' matches = re.findall(pattern, text) words = [] for word, start_str, end_str in matches: try: start = float(start_str) if start_str != 'xxx' else 0.0 end = float(end_str) if end_str != 'xxx' else 0.0 words.append({ 'word': word, 'start': start, 'end': end }) except ValueError: # Skip invalid entries continue return words def words_to_text(words: list[dict]) -> str: """ Convert word list to text format "word[start:end] word[start:end]...". Args: words: List of word dictionaries with keys: 'word', 'start', 'end' Returns: String in format "It's[4.96:5.52] a[5.52:5.84] long[5.84:6.16]..." """ if not words: return "" text_parts = [] for word in words: word_text = word.get('word', '') start = word.get('start', 0.0) end = word.get('end', 0.0) text_parts.append(f"{word_text}[{start}:{end}]") return " ".join(text_parts) def json_to_text(json_data: dict) -> str: """ Convert JSON lyrics data to text format for display. Only uses the 'word' layer from the JSON structure. Args: json_data: Dictionary with 'word' key containing list of word objects Returns: String in format "word[start:end] word[start:end]..." """ if not isinstance(json_data, dict) or 'word' not in json_data: return "" words = json_data['word'] return words_to_text(words) def text_to_json(text: str) -> dict: """ Convert text format to JSON structure expected by the model. Creates the 'word' layer that the model needs. Args: text: String in format "word[start:end] word[start:end]..." Returns: Dictionary with 'word' key containing list of word objects """ words = text_to_words(text) return {"word": words}