abstractvoice 0.6.1__tar.gz → 0.6.2__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.
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/PKG-INFO +1 -1
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/__init__.py +1 -1
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/examples/cli_repl.py +6 -1
- abstractvoice-0.6.2/abstractvoice/text_sanitize.py +33 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice.egg-info/PKG-INFO +1 -1
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice.egg-info/SOURCES.txt +2 -0
- abstractvoice-0.6.2/tests/test_text_sanitize_markdown.py +29 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/LICENSE +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/README.md +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/__main__.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/adapters/__init__.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/adapters/base.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/adapters/stt_faster_whisper.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/adapters/tts_piper.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/aec/__init__.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/aec/webrtc_apm.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/artifacts.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/audio/__init__.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/audio/recorder.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/audio/resample.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/cloning/__init__.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/cloning/engine_chroma.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/cloning/engine_f5.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/cloning/manager.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/cloning/store.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/compute/__init__.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/compute/device.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/config/__init__.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/config/voice_catalog.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/dependency_check.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/examples/__init__.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/examples/voice_cli.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/examples/web_api.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/integrations/__init__.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/integrations/abstractcore.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/integrations/abstractcore_plugin.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/prefetch.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/recognition.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/stop_phrase.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/stt/__init__.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/stt/transcriber.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/tts/__init__.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/tts/adapter_tts_engine.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/tts/tts_engine.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/vad/__init__.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/vad/voice_detector.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/vm/__init__.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/vm/common.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/vm/core.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/vm/manager.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/vm/stt_mixin.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/vm/tts_mixin.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/voice_manager.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice.egg-info/dependency_links.txt +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice.egg-info/entry_points.txt +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice.egg-info/requires.txt +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice.egg-info/top_level.txt +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/pyproject.toml +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/setup.cfg +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_abstractcore_plugin.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_adr0002_phase1.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_adr0002_phase2_optional_aec.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_artifact_tools.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_audio_player_resample.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_callbacks.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_chroma_cloning_integration.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_cloned_tts_cancellation.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_cloning_reference_text_autofallback.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_faster_whisper_adapter.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_fresh_install.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_full_mode_echo_gate.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_piper_adapter.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_reference_text_autofallback_offline_cached_model.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_repl_interrupt_cloned_voice_does_not_resume_old_audio.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_stop_phrase_continuous_detector.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_stop_phrase_matching_is_tolerant.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_stop_speaking_restores_recognizer_state.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_voice_clone_store_delete_rename.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_voice_cloner_engine_dispatch.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_voice_cloning_integration_hal9000.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_voice_cloning_store.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_voice_mode_wait_pauses_listening_during_tts.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_voice_recognizer_ptt_profile.py +0 -0
- {abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_voice_switching.py +0 -0
|
@@ -19,6 +19,7 @@ import threading
|
|
|
19
19
|
import time
|
|
20
20
|
import requests
|
|
21
21
|
from abstractvoice import VoiceManager
|
|
22
|
+
from abstractvoice.text_sanitize import sanitize_markdown_for_speech
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
# ANSI color codes
|
|
@@ -1563,6 +1564,10 @@ class VoiceREPL(cmd.Cmd):
|
|
|
1563
1564
|
if not self.voice_manager:
|
|
1564
1565
|
return
|
|
1565
1566
|
|
|
1567
|
+
# LLM output often contains Markdown. Strip the most common formatting
|
|
1568
|
+
# tokens so TTS stays natural (do not change what is printed).
|
|
1569
|
+
speak_text = sanitize_markdown_for_speech(text)
|
|
1570
|
+
|
|
1566
1571
|
is_clone = bool(self.current_tts_voice)
|
|
1567
1572
|
if not is_clone:
|
|
1568
1573
|
# Offline-first: Piper voices must be explicitly cached. Provide a clear
|
|
@@ -1583,7 +1588,7 @@ class VoiceREPL(cmd.Cmd):
|
|
|
1583
1588
|
try:
|
|
1584
1589
|
if is_clone:
|
|
1585
1590
|
ind.start()
|
|
1586
|
-
self.voice_manager.speak(
|
|
1591
|
+
self.voice_manager.speak(speak_text, voice=self.current_tts_voice)
|
|
1587
1592
|
|
|
1588
1593
|
if not is_clone:
|
|
1589
1594
|
return
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Lightweight text sanitizers for better speech UX.
|
|
2
|
+
|
|
3
|
+
Design goals:
|
|
4
|
+
- No heavy dependencies (regex only).
|
|
5
|
+
- Deterministic and conservative (only strip the most common Markdown syntax).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_MD_HEADER_RE = re.compile(r"(?m)^[ \t]*#{1,5}(?!#)[ \t]*(\S.*)$")
|
|
14
|
+
_MD_BOLD_RE = re.compile(r"\*\*([^*\n]+?)\*\*")
|
|
15
|
+
# Avoid matching "**bold**" as italic. The bold pass runs first, but keep this safe.
|
|
16
|
+
_MD_ITALIC_RE = re.compile(r"(?<!\*)\*([^*\n]+?)\*(?!\*)")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def sanitize_markdown_for_speech(text: str) -> str:
|
|
20
|
+
"""Remove common Markdown syntax that sounds bad in TTS.
|
|
21
|
+
|
|
22
|
+
Intentionally minimal:
|
|
23
|
+
- Strip ATX headers at the start of a line: "#", "##", ... "#####"
|
|
24
|
+
- Strip emphasis markers: "**bold**" and "*italic*"
|
|
25
|
+
"""
|
|
26
|
+
if not text:
|
|
27
|
+
return ""
|
|
28
|
+
|
|
29
|
+
out = str(text)
|
|
30
|
+
out = _MD_HEADER_RE.sub(r"\1", out)
|
|
31
|
+
out = _MD_BOLD_RE.sub(r"\1", out)
|
|
32
|
+
out = _MD_ITALIC_RE.sub(r"\1", out)
|
|
33
|
+
return out
|
|
@@ -8,6 +8,7 @@ abstractvoice/dependency_check.py
|
|
|
8
8
|
abstractvoice/prefetch.py
|
|
9
9
|
abstractvoice/recognition.py
|
|
10
10
|
abstractvoice/stop_phrase.py
|
|
11
|
+
abstractvoice/text_sanitize.py
|
|
11
12
|
abstractvoice/voice_manager.py
|
|
12
13
|
abstractvoice.egg-info/PKG-INFO
|
|
13
14
|
abstractvoice.egg-info/SOURCES.txt
|
|
@@ -71,6 +72,7 @@ tests/test_repl_interrupt_cloned_voice_does_not_resume_old_audio.py
|
|
|
71
72
|
tests/test_stop_phrase_continuous_detector.py
|
|
72
73
|
tests/test_stop_phrase_matching_is_tolerant.py
|
|
73
74
|
tests/test_stop_speaking_restores_recognizer_state.py
|
|
75
|
+
tests/test_text_sanitize_markdown.py
|
|
74
76
|
tests/test_voice_clone_store_delete_rename.py
|
|
75
77
|
tests/test_voice_cloner_engine_dispatch.py
|
|
76
78
|
tests/test_voice_cloning_integration_hal9000.py
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abstractvoice.text_sanitize import sanitize_markdown_for_speech
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_sanitize_removes_bold_single_word() -> None:
|
|
7
|
+
assert sanitize_markdown_for_speech("Hello **world**!") == "Hello world!"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_sanitize_removes_bold_multiple_words() -> None:
|
|
11
|
+
assert sanitize_markdown_for_speech("**sentence with multiple words**") == "sentence with multiple words"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_sanitize_removes_italic_single_word() -> None:
|
|
15
|
+
assert sanitize_markdown_for_speech("Use *italics* here.") == "Use italics here."
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_sanitize_removes_italic_multiple_words() -> None:
|
|
19
|
+
assert sanitize_markdown_for_speech("Use *a sentence with words* here.") == "Use a sentence with words here."
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_sanitize_strips_markdown_headers_keep_text() -> None:
|
|
23
|
+
txt = "# Title\n## Subtitle\n##### Small header\nNot a header\n###"
|
|
24
|
+
assert sanitize_markdown_for_speech(txt) == "Title\nSubtitle\nSmall header\nNot a header\n###"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_sanitize_does_not_touch_bullet_marker_without_closing_star() -> None:
|
|
28
|
+
assert sanitize_markdown_for_speech("* item") == "* item"
|
|
29
|
+
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{abstractvoice-0.6.1 → abstractvoice-0.6.2}/abstractvoice/integrations/abstractcore_plugin.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_cloning_reference_text_autofallback.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{abstractvoice-0.6.1 → abstractvoice-0.6.2}/tests/test_stop_speaking_restores_recognizer_state.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|