diff --git a/tortoise/read.py b/tortoise/read.py
index e89bbc9..e81bd71 100644
--- a/tortoise/read.py
+++ b/tortoise/read.py
@@ -5,25 +5,8 @@ import torch
 import torchaudio
 
 from api import TextToSpeech
-from tortoise.utils.audio import load_audio, get_voices, load_voices
-
-
-def split_and_recombine_text(texts, desired_length=200, max_len=300):
-    # TODO: also split across '!' and '?'. Attempt to keep quotations together.
-    texts = [s.strip() + "." for s in texts.split('.')]
-
-    i = 0
-    while i < len(texts):
-        ltxt = texts[i]
-        if len(ltxt) >= desired_length or i == len(texts)-1:
-            i += 1
-            continue
-        if len(ltxt) + len(texts[i+1]) > max_len:
-            i += 1
-            continue
-        texts[i] = f'{ltxt} {texts[i+1]}'
-        texts.pop(i+1)
-    return texts
+from utils.audio import load_audio, get_voices, load_voices
+from utils.text import split_and_recombine_text
 
 
 if __name__ == '__main__':
diff --git a/tortoise/utils/text.py b/tortoise/utils/text.py
new file mode 100644
index 0000000..18bcebb
--- /dev/null
+++ b/tortoise/utils/text.py
@@ -0,0 +1,84 @@
+import re
+
+
+def split_and_recombine_text(text, desired_length=200, max_length=300):
+    """Split text it into chunks of a desired length trying to keep sentences intact."""
+    # normalize text, remove redundant whitespace and convert non-ascii quotes to ascii
+    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
+
+    def seek(delta):
+        nonlocal pos, in_quote, text
+        is_neg = delta < 0
+        for _ in range(abs(delta)):
+            if is_neg:
+                pos -= 1
+            else:
+                pos += 1
+            if text[pos] == '"':
+                in_quote = not in_quote
+        return text[pos], text[pos+1] if pos < len(text)-1 else ""
+
+    def commit():
+        nonlocal rv, current, split_pos
+        rv.append(current)
+        current = ""
+        split_pos = []
+
+    while pos < len(text) - 1:
+        c, next_c = seek(1)
+        current += c
+        # do we need to force a split?
+        if len(current) >= max_length:
+            if len(split_pos) > 0 and len(current) > (desired_length / 2):
+                # we have at least one sentence and we are over half the desired length, seek back to the last split
+                d = pos - split_pos[-1]
+                seek(-d)
+                current = current[:-d]
+            else:
+                # no full sentences, seek back until we are not in the middle of a word and split there
+                while c not in '!?.\n ' and pos > 0 and len(current) > desired_length:
+                    c, _ = seek(-1)
+                    current = current[:-1]
+            commit()
+        # check for sentence boundaries
+        elif not in_quote and (c in '!?\n' or (c == '.' and next_c in '\n ')):
+            split_pos.append(pos)
+            if len(current) >= desired_length:
+                commit()
+    rv.append(current)
+
+    # clean up
+    rv = [s.strip() for s in rv]
+    rv = [s for s in rv if len(s) > 0]
+
+    return rv
+
+
+if __name__ == '__main__':
+    import unittest
+
+    class Test(unittest.TestCase):
+        def test_split_and_recombine_text(self):
+            text = """
+            This is a sample sentence.
+            This is another sample sentence.
+            This is a longer sample sentence that should force a split inthemiddlebutinotinthislongword.
+            "Don't split my quote... please"
+            """
+            self.assertEqual(split_and_recombine_text(text, desired_length=20, max_length=40),
+                             ['This is a sample sentence.',
+                              'This is another sample sentence.',
+                              'This is a longer sample sentence that',
+                              'should force a split',
+                              'inthemiddlebutinotinthislongword.',
+                              '"Don\'t split my quote... please"'])
+
+    unittest.main()