stream2sentence 0.2.2__tar.gz → 0.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {stream2sentence-0.2.2 → stream2sentence-0.2.4}/PKG-INFO +15 -4
- {stream2sentence-0.2.2 → stream2sentence-0.2.4}/README.md +14 -3
- {stream2sentence-0.2.2 → stream2sentence-0.2.4}/setup.py +1 -1
- stream2sentence-0.2.4/stream2sentence/stream2sentence - Kopie.py +427 -0
- {stream2sentence-0.2.2 → stream2sentence-0.2.4}/stream2sentence/stream2sentence.py +131 -53
- {stream2sentence-0.2.2 → stream2sentence-0.2.4}/stream2sentence.egg-info/PKG-INFO +15 -4
- {stream2sentence-0.2.2 → stream2sentence-0.2.4}/stream2sentence.egg-info/SOURCES.txt +1 -0
- {stream2sentence-0.2.2 → stream2sentence-0.2.4}/tests/test_stream2sentence.py +30 -2
- {stream2sentence-0.2.2 → stream2sentence-0.2.4}/setup.cfg +0 -0
- {stream2sentence-0.2.2 → stream2sentence-0.2.4}/stream2sentence/__init__.py +0 -0
- {stream2sentence-0.2.2 → stream2sentence-0.2.4}/stream2sentence.egg-info/dependency_links.txt +0 -0
- {stream2sentence-0.2.2 → stream2sentence-0.2.4}/stream2sentence.egg-info/requires.txt +0 -0
- {stream2sentence-0.2.2 → stream2sentence-0.2.4}/stream2sentence.egg-info/top_level.txt +0 -0
- {stream2sentence-0.2.2 → stream2sentence-0.2.4}/tests/test_stream_from_llm.py +0 -0
- {stream2sentence-0.2.2 → stream2sentence-0.2.4}/tests/test_stream_from_llm_old_api.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: stream2sentence
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Real-time processing and delivery of sentences from a continuous stream of characters or text chunks.
|
|
5
5
|
Home-page: https://github.com/KoljaB/stream2sentence
|
|
6
6
|
Author: Kolja Beigel
|
|
@@ -19,6 +19,8 @@ Requires-Dist: stanza==1.6.1
|
|
|
19
19
|
|
|
20
20
|
Real-time processing and delivery of sentences from a continuous stream of characters or text chunks.
|
|
21
21
|
|
|
22
|
+
> **Hint:** *If you're interested in state-of-the-art voice solutions you might also want to <strong>have a look at [Linguflex](https://github.com/KoljaB/Linguflex)</strong>, the original project from which stream2sentence is spun off. It lets you control your environment by speaking and is one of the most capable and sophisticated open-source assistants currently available.*
|
|
23
|
+
|
|
22
24
|
## Table of Contents
|
|
23
25
|
|
|
24
26
|
- [Features](#features)
|
|
@@ -76,9 +78,6 @@ The `generate_sentences()` function has the following parameters:
|
|
|
76
78
|
- `generator`: Input character generator.
|
|
77
79
|
Iterator that emits chunks of text. These chunks can be of any size, and they'll be processed one by one to extract sentences from them. It forms the primary source from which the function reads and generates sentences.
|
|
78
80
|
|
|
79
|
-
- `quick_yield_single_sentence_fragment`: Whether to return a sentence fragment as fast as possible.
|
|
80
|
-
This is a feature for realtime speech synthesis. In some use cases you want to audio stream a minimal chunk of text as fast as possible, even when it means to synthesize mid-sentence. In this case you set this flag to True which will yield a synthesizable sentence fragment as early as possible.
|
|
81
|
-
|
|
82
81
|
- `context_size`: Context size for sentence detection.
|
|
83
82
|
This controls how much context is looked at to detect sentence boundaries. It determines the number of characters around a potential delimiter (like a period) that are considered when detecting sentence boundaries. A larger context size allows more reliable sentence boundary detection, but requires buffering more characters before emitting a sentence.
|
|
84
83
|
Default is 12 characters. Increasing this can help detect sentences more accurately, at the cost of added latency.
|
|
@@ -103,6 +102,18 @@ The `generate_sentences()` function has the following parameters:
|
|
|
103
102
|
If True, any Unicode emoji characters are identified and removed from the emitted output sentences. This can help to clean up the output.
|
|
104
103
|
Default is False. Set to True if emojis are not required in the output.
|
|
105
104
|
|
|
105
|
+
- `tokenize_sentences`: (Optional) Function for sentence tokenization. Default is None.
|
|
106
|
+
|
|
107
|
+
- `tokenizer`: Specifies the tokenizer to use ('nltk' or 'stanza'). Default is 'nltk'.
|
|
108
|
+
|
|
109
|
+
- `language`: Language setting for the tokenizer ('en' or 'multilingual' for stanza). Default is 'en'.
|
|
110
|
+
|
|
111
|
+
- `log_characters`: Logs each processed character to the console for debugging. Default is False.
|
|
112
|
+
|
|
113
|
+
- `sentence_fragment_delimiters`: Characters considered as sentence delimiters for yielding quick fragment.
|
|
114
|
+
|
|
115
|
+
- `force_first_fragment_after_words`: Forces the first sentence fragment to yield after a specified number of words. Default is 10 words.
|
|
116
|
+
|
|
106
117
|
- `log_characters`: Option to log characters to the console.
|
|
107
118
|
When enabled, each character processed by the function is printed to the console. This is mainly for debugging purposes to observe the flow of characters through the function.
|
|
108
119
|
Default is False. Set to True for a visual representation of characters being processed. Example: allows printing llm output to console when using stream2sentence to prepare input generation for text to speech synthesis.
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Real-time processing and delivery of sentences from a continuous stream of characters or text chunks.
|
|
4
4
|
|
|
5
|
+
> **Hint:** *If you're interested in state-of-the-art voice solutions you might also want to <strong>have a look at [Linguflex](https://github.com/KoljaB/Linguflex)</strong>, the original project from which stream2sentence is spun off. It lets you control your environment by speaking and is one of the most capable and sophisticated open-source assistants currently available.*
|
|
6
|
+
|
|
5
7
|
## Table of Contents
|
|
6
8
|
|
|
7
9
|
- [Features](#features)
|
|
@@ -59,9 +61,6 @@ The `generate_sentences()` function has the following parameters:
|
|
|
59
61
|
- `generator`: Input character generator.
|
|
60
62
|
Iterator that emits chunks of text. These chunks can be of any size, and they'll be processed one by one to extract sentences from them. It forms the primary source from which the function reads and generates sentences.
|
|
61
63
|
|
|
62
|
-
- `quick_yield_single_sentence_fragment`: Whether to return a sentence fragment as fast as possible.
|
|
63
|
-
This is a feature for realtime speech synthesis. In some use cases you want to audio stream a minimal chunk of text as fast as possible, even when it means to synthesize mid-sentence. In this case you set this flag to True which will yield a synthesizable sentence fragment as early as possible.
|
|
64
|
-
|
|
65
64
|
- `context_size`: Context size for sentence detection.
|
|
66
65
|
This controls how much context is looked at to detect sentence boundaries. It determines the number of characters around a potential delimiter (like a period) that are considered when detecting sentence boundaries. A larger context size allows more reliable sentence boundary detection, but requires buffering more characters before emitting a sentence.
|
|
67
66
|
Default is 12 characters. Increasing this can help detect sentences more accurately, at the cost of added latency.
|
|
@@ -86,6 +85,18 @@ The `generate_sentences()` function has the following parameters:
|
|
|
86
85
|
If True, any Unicode emoji characters are identified and removed from the emitted output sentences. This can help to clean up the output.
|
|
87
86
|
Default is False. Set to True if emojis are not required in the output.
|
|
88
87
|
|
|
88
|
+
- `tokenize_sentences`: (Optional) Function for sentence tokenization. Default is None.
|
|
89
|
+
|
|
90
|
+
- `tokenizer`: Specifies the tokenizer to use ('nltk' or 'stanza'). Default is 'nltk'.
|
|
91
|
+
|
|
92
|
+
- `language`: Language setting for the tokenizer ('en' or 'multilingual' for stanza). Default is 'en'.
|
|
93
|
+
|
|
94
|
+
- `log_characters`: Logs each processed character to the console for debugging. Default is False.
|
|
95
|
+
|
|
96
|
+
- `sentence_fragment_delimiters`: Characters considered as sentence delimiters for yielding quick fragment.
|
|
97
|
+
|
|
98
|
+
- `force_first_fragment_after_words`: Forces the first sentence fragment to yield after a specified number of words. Default is 10 words.
|
|
99
|
+
|
|
89
100
|
- `log_characters`: Option to log characters to the console.
|
|
90
101
|
When enabled, each character processed by the function is printed to the console. This is mainly for debugging purposes to observe the flow of characters through the function.
|
|
91
102
|
Default is False. Set to True for a visual representation of characters being processed. Example: allows printing llm output to console when using stream2sentence to prepare input generation for text to speech synthesis.
|
|
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
|
|
|
5
5
|
|
|
6
6
|
setuptools.setup(
|
|
7
7
|
name="stream2sentence",
|
|
8
|
-
version="0.2.
|
|
8
|
+
version="0.2.4",
|
|
9
9
|
author="Kolja Beigel",
|
|
10
10
|
author_email="kolja.beigel@web.de",
|
|
11
11
|
description="Real-time processing and delivery of sentences from a continuous stream of characters or text chunks.",
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Real-time processing and delivery of sentences
|
|
3
|
+
from a continuous stream of characters or text chunks
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Iterator
|
|
7
|
+
import logging
|
|
8
|
+
import emoji
|
|
9
|
+
import time
|
|
10
|
+
import re
|
|
11
|
+
|
|
12
|
+
current_tokenizer = "nltk"
|
|
13
|
+
stanza_initialized = False
|
|
14
|
+
nltk_initialized = False
|
|
15
|
+
nlp = None
|
|
16
|
+
debug_logging = True
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def initialize_nltk():
|
|
20
|
+
"""
|
|
21
|
+
Initializes NLTK by downloading required data for sentence tokenization.
|
|
22
|
+
"""
|
|
23
|
+
global nltk_initialized
|
|
24
|
+
if nltk_initialized:
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
logging.info("Initializing NLTK")
|
|
28
|
+
|
|
29
|
+
import nltk
|
|
30
|
+
try:
|
|
31
|
+
_ = nltk.data.find('tokenizers/punkt')
|
|
32
|
+
except LookupError:
|
|
33
|
+
nltk.download('punkt')
|
|
34
|
+
nltk_initialized = True
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def initialize_stanza(language: str = "en"):
|
|
38
|
+
"""
|
|
39
|
+
Initializes Stanza by downloading required data for sentence tokenization.
|
|
40
|
+
"""
|
|
41
|
+
global nlp, stanza_initialized
|
|
42
|
+
if stanza_initialized:
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
logging.info("Initializing Stanza")
|
|
46
|
+
|
|
47
|
+
import stanza
|
|
48
|
+
stanza.download(language)
|
|
49
|
+
nlp = stanza.Pipeline(language)
|
|
50
|
+
stanza_initialized = True
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _remove_links(text: str) -> str:
|
|
54
|
+
"""
|
|
55
|
+
Removes any links from the input text.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
text (str): Input text
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
str: Text with links removed
|
|
62
|
+
"""
|
|
63
|
+
pattern = (
|
|
64
|
+
r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|'
|
|
65
|
+
r'[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
return re.sub(pattern, '', text)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _remove_emojis(text: str) -> str:
|
|
72
|
+
"""
|
|
73
|
+
Removes emojis from the input text.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
text (str): Input text
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
str: Text with emojis removed
|
|
80
|
+
"""
|
|
81
|
+
return emoji.replace_emoji(text, u'')
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _generate_characters(generator: Iterator[str],
|
|
85
|
+
log_characters: bool = False) -> Iterator[str]:
|
|
86
|
+
"""
|
|
87
|
+
Generates individual characters from a text generator.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
generator (Iterator[str]): Input text generator
|
|
91
|
+
log_characters (bool): Whether to log the characters to the console
|
|
92
|
+
|
|
93
|
+
Yields:
|
|
94
|
+
Individual characters from the generator
|
|
95
|
+
"""
|
|
96
|
+
if log_characters:
|
|
97
|
+
print("Stream: ", end="", flush=True)
|
|
98
|
+
for chunk in generator:
|
|
99
|
+
for char in chunk:
|
|
100
|
+
if log_characters:
|
|
101
|
+
print(char, end="", flush=True)
|
|
102
|
+
yield char
|
|
103
|
+
if log_characters:
|
|
104
|
+
print()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _clean_text(text: str,
|
|
108
|
+
cleanup_text_links: bool = False,
|
|
109
|
+
cleanup_text_emojis: bool = False,
|
|
110
|
+
strip_text: bool = True) -> str:
|
|
111
|
+
"""
|
|
112
|
+
Cleans the text by removing links and emojis.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
text (str): Input text
|
|
116
|
+
cleanup_text_links (boolean, optional): Remove non-desired links from
|
|
117
|
+
the stream.
|
|
118
|
+
cleanup_text_emojis (boolean, optional): Remove non-desired emojis
|
|
119
|
+
from the stream.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
str: Cleaned text
|
|
123
|
+
"""
|
|
124
|
+
if cleanup_text_links:
|
|
125
|
+
text = _remove_links(text)
|
|
126
|
+
if cleanup_text_emojis:
|
|
127
|
+
text = _remove_emojis(text)
|
|
128
|
+
if strip_text:
|
|
129
|
+
text = text.strip()
|
|
130
|
+
return text
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _tokenize_sentences(text: str, tokenize_sentences=None) -> Iterator[str]:
|
|
134
|
+
"""
|
|
135
|
+
Tokenizes sentences from the input text.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
text (str): Input text
|
|
139
|
+
tokenize_sentences (Callable, optional): A function that tokenizes
|
|
140
|
+
sentences from the input text. Defaults to None.
|
|
141
|
+
|
|
142
|
+
Yields:
|
|
143
|
+
Iterator[str]: An iterator of sentences
|
|
144
|
+
"""
|
|
145
|
+
if tokenize_sentences:
|
|
146
|
+
sentences = tokenize_sentences(text)
|
|
147
|
+
else:
|
|
148
|
+
nlp_start_time = time.time()
|
|
149
|
+
if current_tokenizer == "nltk":
|
|
150
|
+
import nltk
|
|
151
|
+
sentences = nltk.tokenize.sent_tokenize(text)
|
|
152
|
+
elif current_tokenizer == "stanza":
|
|
153
|
+
import stanza
|
|
154
|
+
global nlp
|
|
155
|
+
doc = nlp(text)
|
|
156
|
+
sentences = [sentence.text for sentence in doc.sentences]
|
|
157
|
+
else:
|
|
158
|
+
raise ValueError(f"Unknown tokenizer: {current_tokenizer}")
|
|
159
|
+
nlp_end_time = time.time()
|
|
160
|
+
logging.info("Time to split sentences: "
|
|
161
|
+
f"{nlp_end_time - nlp_start_time}")
|
|
162
|
+
return sentences
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def init_tokenizer(tokenizer: str, language: str = "en"):
|
|
166
|
+
"""
|
|
167
|
+
Initializes the sentence tokenizer.
|
|
168
|
+
"""
|
|
169
|
+
if tokenizer == "nltk":
|
|
170
|
+
initialize_nltk()
|
|
171
|
+
elif tokenizer == "stanza":
|
|
172
|
+
initialize_stanza(language)
|
|
173
|
+
else:
|
|
174
|
+
logging.warning(f"Unknown tokenizer: {tokenizer}")
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def generate_sentences(generator: Iterator[str],
|
|
178
|
+
context_size: int = 12,
|
|
179
|
+
minimum_sentence_length: int = 10,
|
|
180
|
+
minimum_first_fragment_length=10,
|
|
181
|
+
quick_yield_single_sentence_fragment: bool = False,
|
|
182
|
+
cleanup_text_links: bool = False,
|
|
183
|
+
cleanup_text_emojis: bool = False,
|
|
184
|
+
tokenize_sentences=None,
|
|
185
|
+
tokenizer: str = "nltk",
|
|
186
|
+
language: str = "en",
|
|
187
|
+
log_characters: bool = False,
|
|
188
|
+
sentence_fragment_delimiters: str = ".?!;:,\n…)]}。-",
|
|
189
|
+
force_first_fragment_after_words=15,
|
|
190
|
+
) -> Iterator[str]:
|
|
191
|
+
"""
|
|
192
|
+
Generates well-formed sentences from a stream of characters or text chunks
|
|
193
|
+
provided by an input generator.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
generator (Iterator[str]): A generator that yields chunks of text as a
|
|
197
|
+
stream of characters.
|
|
198
|
+
context_size (int): The number of characters used to establish context
|
|
199
|
+
for sentence boundary detection. A larger context improves the
|
|
200
|
+
accuracy of detecting sentence boundaries.
|
|
201
|
+
Default is 12 characters.
|
|
202
|
+
minimum_sentence_length (int): The minimum number of characters a
|
|
203
|
+
sentence must have. If a sentence is shorter, it will be
|
|
204
|
+
concatenated with the following one, improving the overall
|
|
205
|
+
readability. This parameter does not apply to the first sentence
|
|
206
|
+
fragment, which is governed by `minimum_first_fragment_length`.
|
|
207
|
+
Default is 10 characters.
|
|
208
|
+
minimum_first_fragment_length (int): The minimum number of characters
|
|
209
|
+
required for the first sentence fragment before yielding.
|
|
210
|
+
Default is 10 characters.
|
|
211
|
+
quick_yield_single_sentence_fragment (bool): If set to True, the
|
|
212
|
+
generator will yield the first sentence fragment as quickly as
|
|
213
|
+
possible. This is particularly useful for real-time applications
|
|
214
|
+
such as speech synthesis.
|
|
215
|
+
cleanup_text_links (bool): If True, removes hyperlinks from the text
|
|
216
|
+
stream to ensure clean output.
|
|
217
|
+
cleanup_text_emojis (bool): If True, filters out emojis from the text
|
|
218
|
+
stream for clear textual content.
|
|
219
|
+
tokenize_sentences (Callable): A function that tokenizes sentences
|
|
220
|
+
from the input text. Defaults to None.
|
|
221
|
+
tokenizer (str): The tokenizer to use for sentence tokenization.
|
|
222
|
+
Default is "nltk". Can be "nltk" or "stanza".
|
|
223
|
+
language (str): The language to use for sentence tokenization.
|
|
224
|
+
Default is "en". Can be "multilingual" for stanze tokenizer.
|
|
225
|
+
log_characters (bool): If True, logs each character to the console as
|
|
226
|
+
they are processed.
|
|
227
|
+
sentence_fragment_delimiters (str): A string of characters that are
|
|
228
|
+
considered sentence delimiters. Default is ".?!;:,\n…)]}。-".
|
|
229
|
+
force_first_fragment_after_words (int): The number of words after
|
|
230
|
+
which the first sentence fragment is forced to be yielded.
|
|
231
|
+
Default is 15 words.
|
|
232
|
+
|
|
233
|
+
Yields:
|
|
234
|
+
Iterator[str]: An iterator of complete sentences constructed from the
|
|
235
|
+
input text stream. Each yielded sentence meets the specified minimum
|
|
236
|
+
length requirements and is cleaned up if specified.
|
|
237
|
+
|
|
238
|
+
The function maintains a buffer to accumulate text chunks and applies
|
|
239
|
+
natural language processing to detect sentence boundaries.
|
|
240
|
+
It employs various heuristics, such as minimum sentence length and
|
|
241
|
+
sentence delimiters, to ensure the quality of the output sentences.
|
|
242
|
+
The function also provides options to clean up the text stream,
|
|
243
|
+
making it versatile for different types of text processing applications.
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
global current_tokenizer
|
|
247
|
+
current_tokenizer = tokenizer
|
|
248
|
+
init_tokenizer(current_tokenizer, language)
|
|
249
|
+
|
|
250
|
+
buffer = ''
|
|
251
|
+
is_first_sentence = True
|
|
252
|
+
word_count = 0 # Initialize word count
|
|
253
|
+
|
|
254
|
+
for char in _generate_characters(generator, log_characters):
|
|
255
|
+
|
|
256
|
+
if char:
|
|
257
|
+
buffer += char
|
|
258
|
+
buffer = buffer.lstrip()
|
|
259
|
+
|
|
260
|
+
# Update word count
|
|
261
|
+
if char.isspace() or char in sentence_fragment_delimiters:
|
|
262
|
+
word_count += 1
|
|
263
|
+
|
|
264
|
+
print(f"DBG # buffer: {buffer}")
|
|
265
|
+
|
|
266
|
+
if (is_first_sentence
|
|
267
|
+
and len(buffer) > minimum_first_fragment_length
|
|
268
|
+
and quick_yield_single_sentence_fragment):
|
|
269
|
+
|
|
270
|
+
if (buffer[-1] in sentence_fragment_delimiters
|
|
271
|
+
or word_count >= force_first_fragment_after_words):
|
|
272
|
+
|
|
273
|
+
yield_text = _clean_text(
|
|
274
|
+
buffer,
|
|
275
|
+
cleanup_text_links,
|
|
276
|
+
cleanup_text_emojis)
|
|
277
|
+
if debug_logging:
|
|
278
|
+
print(f"s2s: yield 1st {yield_text}, set buffer='', is_first_sentence = False")
|
|
279
|
+
yield yield_text
|
|
280
|
+
buffer = ""
|
|
281
|
+
is_first_sentence = False
|
|
282
|
+
continue
|
|
283
|
+
|
|
284
|
+
# Check if minimum length reached
|
|
285
|
+
if len(buffer) <= minimum_sentence_length + context_size:
|
|
286
|
+
continue
|
|
287
|
+
|
|
288
|
+
# Potential delimiter character has to be a bit away from the end
|
|
289
|
+
# of the buffer. For reliable sentence detection the engine needs
|
|
290
|
+
# enough context to work with
|
|
291
|
+
delimiter_char = buffer[-context_size]
|
|
292
|
+
|
|
293
|
+
if delimiter_char in sentence_fragment_delimiters:
|
|
294
|
+
sentences = _tokenize_sentences(buffer, tokenize_sentences)
|
|
295
|
+
if debug_logging:
|
|
296
|
+
print(f"s2s: sentences tokenizer {sentences}")
|
|
297
|
+
if len(sentences) > 1:
|
|
298
|
+
total_length_except_first = sum(len(sentence) for sentence in sentences[1:])
|
|
299
|
+
if total_length_except_first >= context_size:
|
|
300
|
+
for sentence in sentences[:-1]:
|
|
301
|
+
yield sentence
|
|
302
|
+
#last_sentence = sentences[-1]
|
|
303
|
+
buffer = sentences[-1]
|
|
304
|
+
|
|
305
|
+
# total_length_except_last = sum(len(sentence) for sentence in sentences[:-1])
|
|
306
|
+
# print(f"DBG # total_length_except_last: {total_length_except_last}, len(buffer): {len(buffer)}, context_size: {context_size}")
|
|
307
|
+
# print(f"DBG # total_length_except_last: {total_length_except_last}, len(buffer) - context_size + 1: {len(buffer) - context_size + 1}")
|
|
308
|
+
|
|
309
|
+
# # print(f"DBG # len(sentences[0]): {len(sentences[0])}, len(buffer): {len(buffer)}, context_size: {context_size}")
|
|
310
|
+
# # print(f"DBG # len(sentences[0]): {len(sentences[0])}, len(buffer) - context_size + 1: {len(buffer) - context_size + 1}")
|
|
311
|
+
# if total_length_except_last == len(buffer) - context_size + 1:
|
|
312
|
+
# if debug_logging:
|
|
313
|
+
# print(f"s2s: yield tokenizer {buffer[:-context_size + 1]}")
|
|
314
|
+
# yield_text = _clean_text(
|
|
315
|
+
# buffer[:-context_size + 1],
|
|
316
|
+
# cleanup_text_links,
|
|
317
|
+
# cleanup_text_emojis)
|
|
318
|
+
# yield yield_text
|
|
319
|
+
# buffer = buffer[-context_size + 1:]
|
|
320
|
+
# if debug_logging:
|
|
321
|
+
# print(f"s2s: AFTER yield tokenizer buffer {buffer}")
|
|
322
|
+
# is_first_sentence = False
|
|
323
|
+
|
|
324
|
+
# Yield remaining buffer
|
|
325
|
+
if buffer:
|
|
326
|
+
sentences = _tokenize_sentences(buffer, tokenize_sentences)
|
|
327
|
+
sentence_buffer = ""
|
|
328
|
+
|
|
329
|
+
for sentence in sentences:
|
|
330
|
+
sentence_buffer += sentence
|
|
331
|
+
if len(sentence_buffer) < minimum_sentence_length:
|
|
332
|
+
sentence_buffer += " "
|
|
333
|
+
continue
|
|
334
|
+
yield_text = _clean_text(
|
|
335
|
+
sentence_buffer,
|
|
336
|
+
cleanup_text_links,
|
|
337
|
+
cleanup_text_emojis)
|
|
338
|
+
if debug_logging:
|
|
339
|
+
print(f"s2s: yield remaining {yield_text}, set sentence_buffer=''")
|
|
340
|
+
yield yield_text
|
|
341
|
+
|
|
342
|
+
sentence_buffer = ""
|
|
343
|
+
|
|
344
|
+
if sentence_buffer:
|
|
345
|
+
yield_text = _clean_text(
|
|
346
|
+
sentence_buffer,
|
|
347
|
+
cleanup_text_links,
|
|
348
|
+
cleanup_text_emojis)
|
|
349
|
+
if debug_logging:
|
|
350
|
+
print(f"s2s: yield last sentence_buffer {yield_text}")
|
|
351
|
+
yield yield_text
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
# def generate_sentences(generator: Iterator[str],
|
|
355
|
+
# context_size: int = 12,
|
|
356
|
+
# minimum_sentence_length: int = 10,
|
|
357
|
+
# minimum_first_fragment_length=10,
|
|
358
|
+
# quick_yield_single_sentence_fragment: bool = False,
|
|
359
|
+
# cleanup_text_links: bool = False,
|
|
360
|
+
# cleanup_text_emojis: bool = False,
|
|
361
|
+
# tokenize_sentences=None,
|
|
362
|
+
# tokenizer: str = "nltk",
|
|
363
|
+
# language: str = "en",
|
|
364
|
+
# log_characters: bool = False,
|
|
365
|
+
# sentence_fragment_delimiters: str = ".?!;:,\n…)]}。-",
|
|
366
|
+
# force_first_fragment_after_words=15,
|
|
367
|
+
# ) -> Iterator[str]:
|
|
368
|
+
# global current_tokenizer
|
|
369
|
+
# current_tokenizer = tokenizer
|
|
370
|
+
# init_tokenizer(current_tokenizer, language)
|
|
371
|
+
|
|
372
|
+
# buffer = ''
|
|
373
|
+
# is_first_sentence = True
|
|
374
|
+
# word_count = 0
|
|
375
|
+
|
|
376
|
+
# def yield_sentence(text):
|
|
377
|
+
# return _clean_text(text, cleanup_text_links, cleanup_text_emojis)
|
|
378
|
+
|
|
379
|
+
# for char in _generate_characters(generator, log_characters):
|
|
380
|
+
# if char:
|
|
381
|
+
# buffer += char
|
|
382
|
+
# buffer = buffer.lstrip()
|
|
383
|
+
|
|
384
|
+
# if char.isspace() or char in sentence_fragment_delimiters:
|
|
385
|
+
# word_count += 1
|
|
386
|
+
|
|
387
|
+
# # Handle first sentence fragment
|
|
388
|
+
# if is_first_sentence and len(buffer) >= minimum_first_fragment_length:
|
|
389
|
+
# if quick_yield_single_sentence_fragment:
|
|
390
|
+
# if (buffer[-1] in sentence_fragment_delimiters
|
|
391
|
+
# or word_count >= force_first_fragment_after_words):
|
|
392
|
+
# yield yield_sentence(buffer)
|
|
393
|
+
# buffer = ""
|
|
394
|
+
# is_first_sentence = False
|
|
395
|
+
# word_count = 0
|
|
396
|
+
# continue
|
|
397
|
+
# elif len(buffer) >= minimum_sentence_length:
|
|
398
|
+
# sentences = _tokenize_sentences(buffer, tokenize_sentences)
|
|
399
|
+
# if sentences:
|
|
400
|
+
# yield yield_sentence(sentences[0])
|
|
401
|
+
# buffer = buffer[len(sentences[0]):].lstrip()
|
|
402
|
+
# is_first_sentence = False
|
|
403
|
+
# word_count = 0
|
|
404
|
+
# continue
|
|
405
|
+
|
|
406
|
+
# # Process remaining text
|
|
407
|
+
# if len(buffer) > context_size:
|
|
408
|
+
# sentences = _tokenize_sentences(buffer[:-context_size], tokenize_sentences)
|
|
409
|
+
# if sentences:
|
|
410
|
+
# complete_sentences = sentences[:-1]
|
|
411
|
+
# for sentence in complete_sentences:
|
|
412
|
+
# if len(sentence) >= minimum_sentence_length:
|
|
413
|
+
# yield yield_sentence(sentence)
|
|
414
|
+
# else:
|
|
415
|
+
# buffer = sentence + ' ' + buffer[len(sentence):].lstrip()
|
|
416
|
+
# break
|
|
417
|
+
# else:
|
|
418
|
+
# buffer = sentences[-1] + buffer[-context_size:]
|
|
419
|
+
|
|
420
|
+
# # Handle remaining buffer
|
|
421
|
+
# if buffer:
|
|
422
|
+
# sentences = _tokenize_sentences(buffer, tokenize_sentences)
|
|
423
|
+
# for sentence in sentences:
|
|
424
|
+
# if len(sentence) >= minimum_sentence_length or sentence == buffer:
|
|
425
|
+
# yield yield_sentence(sentence)
|
|
426
|
+
# elif sentences[-1] == sentence:
|
|
427
|
+
# yield yield_sentence(sentence)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Real-time processing and delivery of sentences
|
|
2
|
+
Real-time processing and delivery of sentences
|
|
3
|
+
from a continuous stream of characters or text chunks
|
|
3
4
|
"""
|
|
4
5
|
|
|
5
6
|
from typing import Iterator
|
|
@@ -13,6 +14,7 @@ stanza_initialized = False
|
|
|
13
14
|
nltk_initialized = False
|
|
14
15
|
nlp = None
|
|
15
16
|
|
|
17
|
+
|
|
16
18
|
def initialize_nltk():
|
|
17
19
|
"""
|
|
18
20
|
Initializes NLTK by downloading required data for sentence tokenization.
|
|
@@ -20,16 +22,17 @@ def initialize_nltk():
|
|
|
20
22
|
global nltk_initialized
|
|
21
23
|
if nltk_initialized:
|
|
22
24
|
return
|
|
23
|
-
|
|
25
|
+
|
|
24
26
|
logging.info("Initializing NLTK")
|
|
25
27
|
|
|
26
28
|
import nltk
|
|
27
29
|
try:
|
|
28
|
-
_ = nltk.data.find('tokenizers/punkt')
|
|
30
|
+
_ = nltk.data.find('tokenizers/punkt')
|
|
29
31
|
except LookupError:
|
|
30
32
|
nltk.download('punkt')
|
|
31
33
|
nltk_initialized = True
|
|
32
34
|
|
|
35
|
+
|
|
33
36
|
def initialize_stanza(language: str = "en"):
|
|
34
37
|
"""
|
|
35
38
|
Initializes Stanza by downloading required data for sentence tokenization.
|
|
@@ -37,7 +40,7 @@ def initialize_stanza(language: str = "en"):
|
|
|
37
40
|
global nlp, stanza_initialized
|
|
38
41
|
if stanza_initialized:
|
|
39
42
|
return
|
|
40
|
-
|
|
43
|
+
|
|
41
44
|
logging.info("Initializing Stanza")
|
|
42
45
|
|
|
43
46
|
import stanza
|
|
@@ -45,6 +48,7 @@ def initialize_stanza(language: str = "en"):
|
|
|
45
48
|
nlp = stanza.Pipeline(language)
|
|
46
49
|
stanza_initialized = True
|
|
47
50
|
|
|
51
|
+
|
|
48
52
|
def _remove_links(text: str) -> str:
|
|
49
53
|
"""
|
|
50
54
|
Removes any links from the input text.
|
|
@@ -55,22 +59,28 @@ def _remove_links(text: str) -> str:
|
|
|
55
59
|
Returns:
|
|
56
60
|
str: Text with links removed
|
|
57
61
|
"""
|
|
58
|
-
pattern =
|
|
62
|
+
pattern = (
|
|
63
|
+
r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|'
|
|
64
|
+
r'[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'
|
|
65
|
+
)
|
|
66
|
+
|
|
59
67
|
return re.sub(pattern, '', text)
|
|
60
68
|
|
|
69
|
+
|
|
61
70
|
def _remove_emojis(text: str) -> str:
|
|
62
71
|
"""
|
|
63
72
|
Removes emojis from the input text.
|
|
64
73
|
|
|
65
74
|
Args:
|
|
66
75
|
text (str): Input text
|
|
67
|
-
|
|
76
|
+
|
|
68
77
|
Returns:
|
|
69
78
|
str: Text with emojis removed
|
|
70
79
|
"""
|
|
71
80
|
return emoji.replace_emoji(text, u'')
|
|
72
81
|
|
|
73
|
-
|
|
82
|
+
|
|
83
|
+
def _generate_characters(generator: Iterator[str],
|
|
74
84
|
log_characters: bool = False) -> Iterator[str]:
|
|
75
85
|
"""
|
|
76
86
|
Generates individual characters from a text generator.
|
|
@@ -82,14 +92,19 @@ def _generate_characters(generator: Iterator[str],
|
|
|
82
92
|
Yields:
|
|
83
93
|
Individual characters from the generator
|
|
84
94
|
"""
|
|
95
|
+
if log_characters:
|
|
96
|
+
print("Stream: ", end="", flush=True)
|
|
85
97
|
for chunk in generator:
|
|
86
98
|
for char in chunk:
|
|
87
99
|
if log_characters:
|
|
88
100
|
print(char, end="", flush=True)
|
|
89
101
|
yield char
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
102
|
+
if log_characters:
|
|
103
|
+
print()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _clean_text(text: str,
|
|
107
|
+
cleanup_text_links: bool = False,
|
|
93
108
|
cleanup_text_emojis: bool = False,
|
|
94
109
|
strip_text: bool = True) -> str:
|
|
95
110
|
"""
|
|
@@ -97,9 +112,11 @@ def _clean_text(text: str,
|
|
|
97
112
|
|
|
98
113
|
Args:
|
|
99
114
|
text (str): Input text
|
|
100
|
-
cleanup_text_links (boolean, optional): Remove non-desired links from
|
|
101
|
-
|
|
102
|
-
|
|
115
|
+
cleanup_text_links (boolean, optional): Remove non-desired links from
|
|
116
|
+
the stream.
|
|
117
|
+
cleanup_text_emojis (boolean, optional): Remove non-desired emojis
|
|
118
|
+
from the stream.
|
|
119
|
+
|
|
103
120
|
Returns:
|
|
104
121
|
str: Cleaned text
|
|
105
122
|
"""
|
|
@@ -108,19 +125,21 @@ def _clean_text(text: str,
|
|
|
108
125
|
if cleanup_text_emojis:
|
|
109
126
|
text = _remove_emojis(text)
|
|
110
127
|
if strip_text:
|
|
111
|
-
text = text.strip()
|
|
128
|
+
text = text.strip()
|
|
112
129
|
return text
|
|
113
130
|
|
|
114
|
-
|
|
131
|
+
|
|
132
|
+
def _tokenize_sentences(text: str, tokenize_sentences=None) -> Iterator[str]:
|
|
115
133
|
"""
|
|
116
134
|
Tokenizes sentences from the input text.
|
|
117
135
|
|
|
118
136
|
Args:
|
|
119
137
|
text (str): Input text
|
|
120
|
-
tokenize_sentences (Callable, optional): A function that tokenizes
|
|
138
|
+
tokenize_sentences (Callable, optional): A function that tokenizes
|
|
139
|
+
sentences from the input text. Defaults to None.
|
|
121
140
|
|
|
122
141
|
Yields:
|
|
123
|
-
Iterator[str]: An iterator of sentences
|
|
142
|
+
Iterator[str]: An iterator of sentences
|
|
124
143
|
"""
|
|
125
144
|
if tokenize_sentences:
|
|
126
145
|
sentences = tokenize_sentences(text)
|
|
@@ -136,10 +155,12 @@ def _tokenize_sentences(text: str, tokenize_sentences = None) -> Iterator[str]:
|
|
|
136
155
|
sentences = [sentence.text for sentence in doc.sentences]
|
|
137
156
|
else:
|
|
138
157
|
raise ValueError(f"Unknown tokenizer: {current_tokenizer}")
|
|
139
|
-
nlp_end_time = time.time()
|
|
140
|
-
logging.info(
|
|
158
|
+
nlp_end_time = time.time()
|
|
159
|
+
logging.info("Time to split sentences: "
|
|
160
|
+
f"{nlp_end_time - nlp_start_time}")
|
|
141
161
|
return sentences
|
|
142
162
|
|
|
163
|
+
|
|
143
164
|
def init_tokenizer(tokenizer: str, language: str = "en"):
|
|
144
165
|
"""
|
|
145
166
|
Initializes the sentence tokenizer.
|
|
@@ -151,34 +172,74 @@ def init_tokenizer(tokenizer: str, language: str = "en"):
|
|
|
151
172
|
else:
|
|
152
173
|
logging.warning(f"Unknown tokenizer: {tokenizer}")
|
|
153
174
|
|
|
154
|
-
|
|
175
|
+
|
|
176
|
+
def generate_sentences(generator: Iterator[str],
|
|
155
177
|
context_size: int = 12,
|
|
156
178
|
minimum_sentence_length: int = 10,
|
|
157
|
-
minimum_first_fragment_length
|
|
179
|
+
minimum_first_fragment_length=10,
|
|
158
180
|
quick_yield_single_sentence_fragment: bool = False,
|
|
159
181
|
cleanup_text_links: bool = False,
|
|
160
|
-
cleanup_text_emojis: bool = False,
|
|
161
|
-
tokenize_sentences
|
|
182
|
+
cleanup_text_emojis: bool = False,
|
|
183
|
+
tokenize_sentences=None,
|
|
162
184
|
tokenizer: str = "nltk",
|
|
163
185
|
language: str = "en",
|
|
164
|
-
log_characters: bool = False
|
|
186
|
+
log_characters: bool = False,
|
|
187
|
+
sentence_fragment_delimiters: str = ".?!;:,\n…)]}。-",
|
|
188
|
+
force_first_fragment_after_words=15,
|
|
189
|
+
) -> Iterator[str]:
|
|
165
190
|
"""
|
|
166
|
-
Generates well-formed sentences from a stream of characters or text chunks
|
|
191
|
+
Generates well-formed sentences from a stream of characters or text chunks
|
|
192
|
+
provided by an input generator.
|
|
167
193
|
|
|
168
194
|
Args:
|
|
169
|
-
generator (Iterator[str]): A generator that yields chunks of text as a
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
195
|
+
generator (Iterator[str]): A generator that yields chunks of text as a
|
|
196
|
+
stream of characters.
|
|
197
|
+
context_size (int): The number of characters used to establish context
|
|
198
|
+
for sentence boundary detection. A larger context improves the
|
|
199
|
+
accuracy of detecting sentence boundaries.
|
|
200
|
+
Default is 12 characters.
|
|
201
|
+
minimum_sentence_length (int): The minimum number of characters a
|
|
202
|
+
sentence must have. If a sentence is shorter, it will be
|
|
203
|
+
concatenated with the following one, improving the overall
|
|
204
|
+
readability. This parameter does not apply to the first sentence
|
|
205
|
+
fragment, which is governed by `minimum_first_fragment_length`.
|
|
206
|
+
Default is 10 characters.
|
|
207
|
+
minimum_first_fragment_length (int): The minimum number of characters
|
|
208
|
+
required for the first sentence fragment before yielding.
|
|
209
|
+
Default is 10 characters.
|
|
210
|
+
quick_yield_single_sentence_fragment (bool): If set to True, the
|
|
211
|
+
generator will yield the first sentence fragment as quickly as
|
|
212
|
+
possible. This is particularly useful for real-time applications
|
|
213
|
+
such as speech synthesis.
|
|
214
|
+
cleanup_text_links (bool): If True, removes hyperlinks from the text
|
|
215
|
+
stream to ensure clean output.
|
|
216
|
+
cleanup_text_emojis (bool): If True, filters out emojis from the text
|
|
217
|
+
stream for clear textual content.
|
|
218
|
+
tokenize_sentences (Callable): A function that tokenizes sentences
|
|
219
|
+
from the input text. Defaults to None.
|
|
220
|
+
tokenizer (str): The tokenizer to use for sentence tokenization.
|
|
221
|
+
Default is "nltk". Can be "nltk" or "stanza".
|
|
222
|
+
language (str): The language to use for sentence tokenization.
|
|
223
|
+
Default is "en". Can be "multilingual" for stanze tokenizer.
|
|
224
|
+
log_characters (bool): If True, logs each character to the console as
|
|
225
|
+
they are processed.
|
|
226
|
+
sentence_fragment_delimiters (str): A string of characters that are
|
|
227
|
+
considered sentence delimiters. Default is ".?!;:,\n…)]}。-".
|
|
228
|
+
force_first_fragment_after_words (int): The number of words after
|
|
229
|
+
which the first sentence fragment is forced to be yielded.
|
|
230
|
+
Default is 15 words.
|
|
177
231
|
|
|
178
232
|
Yields:
|
|
179
|
-
Iterator[str]: An iterator of complete sentences constructed from the
|
|
180
|
-
|
|
181
|
-
|
|
233
|
+
Iterator[str]: An iterator of complete sentences constructed from the
|
|
234
|
+
input text stream. Each yielded sentence meets the specified minimum
|
|
235
|
+
length requirements and is cleaned up if specified.
|
|
236
|
+
|
|
237
|
+
The function maintains a buffer to accumulate text chunks and applies
|
|
238
|
+
natural language processing to detect sentence boundaries.
|
|
239
|
+
It employs various heuristics, such as minimum sentence length and
|
|
240
|
+
sentence delimiters, to ensure the quality of the output sentences.
|
|
241
|
+
The function also provides options to clean up the text stream,
|
|
242
|
+
making it versatile for different types of text processing applications.
|
|
182
243
|
"""
|
|
183
244
|
|
|
184
245
|
global current_tokenizer
|
|
@@ -187,8 +248,7 @@ def generate_sentences(generator: Iterator[str],
|
|
|
187
248
|
|
|
188
249
|
buffer = ''
|
|
189
250
|
is_first_sentence = True
|
|
190
|
-
|
|
191
|
-
sentence_delimiters = '.?!;:,\n…)]}。'
|
|
251
|
+
word_count = 0 # Initialize word count
|
|
192
252
|
|
|
193
253
|
for char in _generate_characters(generator, log_characters):
|
|
194
254
|
|
|
@@ -196,10 +256,21 @@ def generate_sentences(generator: Iterator[str],
|
|
|
196
256
|
buffer += char
|
|
197
257
|
buffer = buffer.lstrip()
|
|
198
258
|
|
|
199
|
-
|
|
259
|
+
# Update word count
|
|
260
|
+
if char.isspace() or char in sentence_fragment_delimiters:
|
|
261
|
+
word_count += 1
|
|
262
|
+
|
|
263
|
+
if (is_first_sentence
|
|
264
|
+
and len(buffer) > minimum_first_fragment_length
|
|
265
|
+
and quick_yield_single_sentence_fragment):
|
|
266
|
+
|
|
267
|
+
if (buffer[-1] in sentence_fragment_delimiters
|
|
268
|
+
or word_count >= force_first_fragment_after_words):
|
|
200
269
|
|
|
201
|
-
|
|
202
|
-
|
|
270
|
+
yield_text = _clean_text(
|
|
271
|
+
buffer,
|
|
272
|
+
cleanup_text_links,
|
|
273
|
+
cleanup_text_emojis)
|
|
203
274
|
yield yield_text
|
|
204
275
|
buffer = ""
|
|
205
276
|
is_first_sentence = False
|
|
@@ -209,18 +280,19 @@ def generate_sentences(generator: Iterator[str],
|
|
|
209
280
|
if len(buffer) <= minimum_sentence_length + context_size:
|
|
210
281
|
continue
|
|
211
282
|
|
|
212
|
-
# Potential delimiter character has to be a bit away from the end
|
|
213
|
-
# For reliable sentence detection the engine needs
|
|
283
|
+
# Potential delimiter character has to be a bit away from the end
|
|
284
|
+
# of the buffer. For reliable sentence detection the engine needs
|
|
285
|
+
# enough context to work with
|
|
214
286
|
delimiter_char = buffer[-context_size]
|
|
215
287
|
|
|
216
|
-
if delimiter_char in
|
|
288
|
+
if delimiter_char in sentence_fragment_delimiters:
|
|
217
289
|
sentences = _tokenize_sentences(buffer, tokenize_sentences)
|
|
218
290
|
if len(sentences) > 1:
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
291
|
+
total_length_except_first = sum(len(sentence) for sentence in sentences[1:])
|
|
292
|
+
if total_length_except_first >= context_size:
|
|
293
|
+
for sentence in sentences[:-1]:
|
|
294
|
+
yield sentence
|
|
295
|
+
buffer = sentences[-1]
|
|
224
296
|
|
|
225
297
|
# Yield remaining buffer
|
|
226
298
|
if buffer:
|
|
@@ -232,11 +304,17 @@ def generate_sentences(generator: Iterator[str],
|
|
|
232
304
|
if len(sentence_buffer) < minimum_sentence_length:
|
|
233
305
|
sentence_buffer += " "
|
|
234
306
|
continue
|
|
235
|
-
yield_text = _clean_text(
|
|
307
|
+
yield_text = _clean_text(
|
|
308
|
+
sentence_buffer,
|
|
309
|
+
cleanup_text_links,
|
|
310
|
+
cleanup_text_emojis)
|
|
236
311
|
yield yield_text
|
|
237
312
|
|
|
238
313
|
sentence_buffer = ""
|
|
239
|
-
|
|
314
|
+
|
|
240
315
|
if sentence_buffer:
|
|
241
|
-
yield_text = _clean_text(
|
|
242
|
-
|
|
316
|
+
yield_text = _clean_text(
|
|
317
|
+
sentence_buffer,
|
|
318
|
+
cleanup_text_links,
|
|
319
|
+
cleanup_text_emojis)
|
|
320
|
+
yield yield_text
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: stream2sentence
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Real-time processing and delivery of sentences from a continuous stream of characters or text chunks.
|
|
5
5
|
Home-page: https://github.com/KoljaB/stream2sentence
|
|
6
6
|
Author: Kolja Beigel
|
|
@@ -19,6 +19,8 @@ Requires-Dist: stanza==1.6.1
|
|
|
19
19
|
|
|
20
20
|
Real-time processing and delivery of sentences from a continuous stream of characters or text chunks.
|
|
21
21
|
|
|
22
|
+
> **Hint:** *If you're interested in state-of-the-art voice solutions you might also want to <strong>have a look at [Linguflex](https://github.com/KoljaB/Linguflex)</strong>, the original project from which stream2sentence is spun off. It lets you control your environment by speaking and is one of the most capable and sophisticated open-source assistants currently available.*
|
|
23
|
+
|
|
22
24
|
## Table of Contents
|
|
23
25
|
|
|
24
26
|
- [Features](#features)
|
|
@@ -76,9 +78,6 @@ The `generate_sentences()` function has the following parameters:
|
|
|
76
78
|
- `generator`: Input character generator.
|
|
77
79
|
Iterator that emits chunks of text. These chunks can be of any size, and they'll be processed one by one to extract sentences from them. It forms the primary source from which the function reads and generates sentences.
|
|
78
80
|
|
|
79
|
-
- `quick_yield_single_sentence_fragment`: Whether to return a sentence fragment as fast as possible.
|
|
80
|
-
This is a feature for realtime speech synthesis. In some use cases you want to audio stream a minimal chunk of text as fast as possible, even when it means to synthesize mid-sentence. In this case you set this flag to True which will yield a synthesizable sentence fragment as early as possible.
|
|
81
|
-
|
|
82
81
|
- `context_size`: Context size for sentence detection.
|
|
83
82
|
This controls how much context is looked at to detect sentence boundaries. It determines the number of characters around a potential delimiter (like a period) that are considered when detecting sentence boundaries. A larger context size allows more reliable sentence boundary detection, but requires buffering more characters before emitting a sentence.
|
|
84
83
|
Default is 12 characters. Increasing this can help detect sentences more accurately, at the cost of added latency.
|
|
@@ -103,6 +102,18 @@ The `generate_sentences()` function has the following parameters:
|
|
|
103
102
|
If True, any Unicode emoji characters are identified and removed from the emitted output sentences. This can help to clean up the output.
|
|
104
103
|
Default is False. Set to True if emojis are not required in the output.
|
|
105
104
|
|
|
105
|
+
- `tokenize_sentences`: (Optional) Function for sentence tokenization. Default is None.
|
|
106
|
+
|
|
107
|
+
- `tokenizer`: Specifies the tokenizer to use ('nltk' or 'stanza'). Default is 'nltk'.
|
|
108
|
+
|
|
109
|
+
- `language`: Language setting for the tokenizer ('en' or 'multilingual' for stanza). Default is 'en'.
|
|
110
|
+
|
|
111
|
+
- `log_characters`: Logs each processed character to the console for debugging. Default is False.
|
|
112
|
+
|
|
113
|
+
- `sentence_fragment_delimiters`: Characters considered as sentence delimiters for yielding quick fragment.
|
|
114
|
+
|
|
115
|
+
- `force_first_fragment_after_words`: Forces the first sentence fragment to yield after a specified number of words. Default is 10 words.
|
|
116
|
+
|
|
106
117
|
- `log_characters`: Option to log characters to the console.
|
|
107
118
|
When enabled, each character processed by the function is printed to the console. This is mainly for debugging purposes to observe the flow of characters through the function.
|
|
108
119
|
Default is False. Set to True for a visual representation of characters being processed. Example: allows printing llm output to console when using stream2sentence to prepare input generation for text to speech synthesis.
|
|
@@ -5,8 +5,36 @@ class TestSentenceGenerator(unittest.TestCase):
|
|
|
5
5
|
|
|
6
6
|
def test_chinese(self):
|
|
7
7
|
text = "我喜欢读书。天气很好。我们去公园吧。今天是星期五。早上好。这是我的朋友。请帮我。吃饭了吗?我在学中文。晚安。"
|
|
8
|
-
expected = ["我喜欢读书。", "天气很好。", "我们去公园吧。", "今天是星期五。", "早上好。", "这是我的朋友。", "
|
|
9
|
-
sentences = list(generate_sentences(text, minimum_sentence_length
|
|
8
|
+
expected = ["我喜欢读书。", "天气很好。", "我们去公园吧。", "今天是星期五。", "早上好。", "这是我的朋友。", "请帮我。吃饭了吗?", "我在学中文。", "晚安。"]
|
|
9
|
+
sentences = list(generate_sentences(text, minimum_sentence_length=2, context_size=2, tokenizer="stanza", language="zh"))
|
|
10
|
+
self.assertEqual(sentences, expected)
|
|
11
|
+
|
|
12
|
+
def test_chinese2(self):
|
|
13
|
+
text = """
|
|
14
|
+
胡/爷/爷,我/来/给/您/讲/一下/下/周/每/天/的/安/排。
|
|
15
|
+
周/一/:/9:00-10:00:晨/练/太/极/拳/,/地点/:/活/动/室/。
|
|
16
|
+
10:30-11:30:园/艺/活/动/菠菜/种/植/,/地点/:/花/园/。
|
|
17
|
+
14:00-15:00:手/工/制/作/睡/眠/香/囊/,/地点/:/手/工/室/。
|
|
18
|
+
15:30-16:30:观/看/老/电/影/,/地点/:/影/音/室/。
|
|
19
|
+
|
|
20
|
+
周/二/:/9:00-10:00:八/段/锦/简/化/版/,/地点/:/大/厅/。
|
|
21
|
+
10:30-11:30:书/法/练/习/,/地点/:/书/画/室/。
|
|
22
|
+
14:00-15:00:棋/牌/娱/乐/象/棋/、/围/棋/等/,/地点/:/棋/牌/室/。
|
|
23
|
+
15:30-16:30:养/生/讲/座/春/天/养/生/1/,/地点/:/会/议/室/。
|
|
24
|
+
大/厅/"""
|
|
25
|
+
expected = [
|
|
26
|
+
"胡/爷/爷,我/来/给/您/讲/一下/下/周/每/天/的/安/排。",
|
|
27
|
+
"周/一/:/9:00-10:00:晨/练/太/极/拳/,/地点/:/活/动/室/。",
|
|
28
|
+
"10:30-11:30:园/艺/活/动/菠菜/种/植/,/地点/:/花/园/。",
|
|
29
|
+
"14:00-15:00:手/工/制/作/睡/眠/香/囊/,/地点/:/手/工/室/。",
|
|
30
|
+
"15:30-16:30:观/看/老/电/影/,/地点/:/影/音/室/。",
|
|
31
|
+
"周/二/:/9:00-10:00:八/段/锦/简/化/版/,/地点/:/大/厅/。",
|
|
32
|
+
"10:30-11:30:书/法/练/习/,/地点/:/书/画/室/。",
|
|
33
|
+
"14:00-15:00:棋/牌/娱/乐/象/棋/、/围/棋/等/,/地点/:/棋/牌/室/。",
|
|
34
|
+
"15:30-16:30:养/生/讲/座/春/天/养/生/1/,/地点/:/会/议/室/。",
|
|
35
|
+
"大/厅/",
|
|
36
|
+
]
|
|
37
|
+
sentences = list(generate_sentences(text, minimum_sentence_length=2, context_size=2, tokenizer="stanza", language="zh"))
|
|
10
38
|
self.assertEqual(sentences, expected)
|
|
11
39
|
|
|
12
40
|
def test_generator(self):
|
|
File without changes
|
|
File without changes
|
{stream2sentence-0.2.2 → stream2sentence-0.2.4}/stream2sentence.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|