| | import re |
| | import xml.etree.ElementTree as ET |
| | from xml.sax import saxutils |
| | |
| |
|
| | |
| | def split_and_recombine_text(text, desired_length=100, max_length=150): |
| | |
| |
|
| | |
| | """Split text it into chunks of a desired length trying to keep sentences intact.""" |
| | |
| | text = re.sub(r"\n\n+", "\n", text) |
| | text = re.sub(r"\s+", " ", text) |
| | text = re.sub(r"[“”]", '"', text) |
| |
|
| | rv = [] |
| | in_quote = False |
| | current = "" |
| | split_pos = [] |
| | pos = -1 |
| | end_pos = len(text) - 1 |
| |
|
| | def seek(delta): |
| | nonlocal pos, in_quote, current |
| | is_neg = delta < 0 |
| | for _ in range(abs(delta)): |
| | if is_neg: |
| | pos -= 1 |
| | current = current[:-1] |
| | else: |
| | pos += 1 |
| | current += text[pos] |
| | if text[pos] == '"': |
| | in_quote = not in_quote |
| | return text[pos] |
| |
|
| | def peek(delta): |
| | p = pos + delta |
| | return text[p] if p < end_pos and p >= 0 else "" |
| |
|
| | def commit(): |
| | nonlocal rv, current, split_pos |
| | rv.append(current) |
| | current = "" |
| | split_pos = [] |
| |
|
| | while pos < end_pos: |
| | c = seek(1) |
| | |
| | if len(current) >= max_length: |
| | if len(split_pos) > 0 and len(current) > (desired_length / 2): |
| | |
| | d = pos - split_pos[-1] |
| | seek(-d) |
| | else: |
| | |
| | while c not in "!?.,\n " and pos > 0 and len(current) > desired_length: |
| | c = seek(-1) |
| | commit() |
| | |
| | elif not in_quote and (c in "!?]\n" or (c == "." and peek(1) in "\n ")): |
| | |
| | while ( |
| | pos < len(text) - 1 and len(current) < max_length and peek(1) in "!?.]" |
| | ): |
| | c = seek(1) |
| | split_pos.append(pos) |
| | if len(current) >= desired_length: |
| | commit() |
| | |
| | elif in_quote and peek(1) == '"' and peek(2) in "\n ": |
| | seek(2) |
| | split_pos.append(pos) |
| | rv.append(current) |
| |
|
| | |
| | rv = [s.strip() for s in rv] |
| | rv = [s for s in rv if len(s) > 0 and not re.match(r"^[\s\.,;:!?]*$", s)] |
| |
|
| | return rv |
| |
|
| | def is_ssml(value): |
| | try: |
| | ET.fromstring(value) |
| | except ET.ParseError: |
| | return False |
| | return True |
| |
|
| | def build_ssml(rawtext, selected_voice): |
| | texts = rawtext.split("\n") |
| | joinedparts = "" |
| | for textpart in texts: |
| | textpart = textpart.strip() |
| | if len(textpart) < 1: |
| | continue |
| | joinedparts = joinedparts + f"\n<voice name=\"{selected_voice}\">{saxutils.escape(textpart)}</voice>" |
| | ssml = f"""<?xml version="1.0"?> |
| | <speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" |
| | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| | xsi:schemaLocation="http://www.w3.org/2001/10/synthesis |
| | http://www.w3.org/TR/speech-synthesis/synthesis.xsd" |
| | xml:lang="en-US"> |
| | {joinedparts} |
| | </speak> |
| | """ |
| | return ssml |
| |
|
| | def create_clips_from_ssml(ssmlinput): |
| | |
| | tree = ET.ElementTree(ET.fromstring(ssmlinput)) |
| | root = tree.getroot() |
| |
|
| | |
| | voice_list = [] |
| |
|
| | |
| | for voice in root.iter('{http://www.w3.org/2001/10/synthesis}voice'): |
| | |
| | voice_name = voice.attrib['name'] |
| | voice_content = voice.text.strip() if voice.text else '' |
| | if(len(voice_content) > 0): |
| | parts = split_and_recombine_text(voice_content) |
| | for p in parts: |
| | if(len(p) > 1): |
| | |
| | voice_list.append((voice_name, p)) |
| | return voice_list |
| |
|
| |
|