stream2sentence 0.2.3__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.3 → stream2sentence-0.2.4}/PKG-INFO +3 -1
- {stream2sentence-0.2.3 → stream2sentence-0.2.4}/README.md +2 -0
- {stream2sentence-0.2.3 → stream2sentence-0.2.4}/setup.py +1 -1
- stream2sentence-0.2.4/stream2sentence/stream2sentence - Kopie.py +427 -0
- {stream2sentence-0.2.3 → stream2sentence-0.2.4}/stream2sentence/stream2sentence.py +9 -8
- {stream2sentence-0.2.3 → stream2sentence-0.2.4}/stream2sentence.egg-info/PKG-INFO +3 -1
- {stream2sentence-0.2.3 → stream2sentence-0.2.4}/stream2sentence.egg-info/SOURCES.txt +1 -0
- {stream2sentence-0.2.3 → stream2sentence-0.2.4}/tests/test_stream2sentence.py +30 -2
- {stream2sentence-0.2.3 → stream2sentence-0.2.4}/setup.cfg +0 -0
- {stream2sentence-0.2.3 → stream2sentence-0.2.4}/stream2sentence/__init__.py +0 -0
- {stream2sentence-0.2.3 → stream2sentence-0.2.4}/stream2sentence.egg-info/dependency_links.txt +0 -0
- {stream2sentence-0.2.3 → stream2sentence-0.2.4}/stream2sentence.egg-info/requires.txt +0 -0
- {stream2sentence-0.2.3 → stream2sentence-0.2.4}/stream2sentence.egg-info/top_level.txt +0 -0
- {stream2sentence-0.2.3 → stream2sentence-0.2.4}/tests/test_stream_from_llm.py +0 -0
- {stream2sentence-0.2.3 → 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)
|
|
@@ -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)
|
|
@@ -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)
|
|
@@ -92,11 +92,15 @@ def _generate_characters(generator: Iterator[str],
|
|
|
92
92
|
Yields:
|
|
93
93
|
Individual characters from the generator
|
|
94
94
|
"""
|
|
95
|
+
if log_characters:
|
|
96
|
+
print("Stream: ", end="", flush=True)
|
|
95
97
|
for chunk in generator:
|
|
96
98
|
for char in chunk:
|
|
97
99
|
if log_characters:
|
|
98
100
|
print(char, end="", flush=True)
|
|
99
101
|
yield char
|
|
102
|
+
if log_characters:
|
|
103
|
+
print()
|
|
100
104
|
|
|
101
105
|
|
|
102
106
|
def _clean_text(text: str,
|
|
@@ -284,14 +288,11 @@ def generate_sentences(generator: Iterator[str],
|
|
|
284
288
|
if delimiter_char in sentence_fragment_delimiters:
|
|
285
289
|
sentences = _tokenize_sentences(buffer, tokenize_sentences)
|
|
286
290
|
if len(sentences) > 1:
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
yield yield_text
|
|
293
|
-
buffer = buffer[-context_size + 1:]
|
|
294
|
-
is_first_sentence = False
|
|
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]
|
|
295
296
|
|
|
296
297
|
# Yield remaining buffer
|
|
297
298
|
if buffer:
|
|
@@ -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)
|
|
@@ -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.3 → 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
|