aboutsummaryrefslogtreecommitdiff
path: root/deskutils/calibre/files/patch-tts-missing-fix
blob: a8ea80597c6aa12d8b48c1321136558943bea459 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
From ee2e5374cec0b5a313e943bdba6cf31f6f48b27f Mon Sep 17 00:00:00 2001
From: Eli Schwartz <eschwartz93@gmail.com>
Date: Fri, 8 Aug 2025 01:15:04 -0400
Subject: [PATCH] TTS: gracefully handle missing piper support

Third party redistributors might choose to skip distributing this for a
couple reasons:
- missing dependencies
- lack of interest in TTS as a feature

Lay some groundwork for handling this with fewer error message popups.
In particular note that speechd / flite depend on PyQt6 being built with
it, so support *may* appear dynamically after calibre is installed, and
available_engines queries Qt to see what is available. Piper is built
as part of calibre though, and if it has been patched out or skipped
via `setup.py build --only=xxx` we can at least avoid claiming it's
there.

Entrypoints into TTS eventually tend to consolidate into creating the
backend. This gives us one consistent place to raise errors for missing
backends... which however doesn't handle forcing a backend name. A
forced backend that is unavailable ended up hitting the "no prefs"
fallback code to use the default engine, which returned a different
backend than the one which is *forced*, and later a KeyError when
tweak_book attempted to access the backend name it forced but which
didn't exist.

Instead, raise an immediate "TTS engine piper is not available" error
dialog box, preventing any further confusing tracebacks.
---
 src/calibre/gui2/tts/types.py | 30 +++++++++++++++++++++---------
 1 file changed, 21 insertions(+), 9 deletions(-)

diff --git a/src/calibre/gui2/tts/types.py b/src/calibre/gui2/tts/types.py
index 40a2aad27e71..f509e8bddd16 100644
--- src/calibre/gui2/tts/types.py
+++ src/calibre/gui2/tts/types.py
@@ -234,11 +234,18 @@ def qt_engine_metadata(name: str, human_name: str, desc: str, allows_choosing_au
             ), True)
         elif x == 'speechd':
             continue
-    ans['piper'] = EngineMetadata('piper', _('The Piper Neural Engine'), _(
-        'The "piper" engine can track the currently spoken sentence on screen. It uses a neural network '
-        'for natural sounding voices. The neural network is run locally on your computer, it is fairly resource intensive to run.'
-    ), TrackingCapability.Sentence, can_change_pitch=False, voices_have_quality_metadata=True, has_managed_voices=True,
-    has_sentence_delay=True)
+
+    try:
+        import calibre_extensions.piper
+    except ImportError:
+        pass
+    else:
+        ans['piper'] = EngineMetadata('piper', _('The Piper Neural Engine'), _(
+            'The "piper" engine can track the currently spoken sentence on screen. It uses a neural network '
+            'for natural sounding voices. The neural network is run locally on your computer, it is fairly resource intensive to run.'
+        ), TrackingCapability.Sentence, can_change_pitch=False, voices_have_quality_metadata=True, has_managed_voices=True,
+        has_sentence_delay=True)
+
     if islinux:
         try:
             from speechd.paths import SPD_SPAWN_CMD
@@ -322,10 +329,15 @@ def create_tts_backend(force_engine: str | None = None, config_name: str = CONFI
     if not available_engines():
         raise OSError('There are no available TTS engines. Install a TTS engine before trying to use Read Aloud, such as flite or speech-dispatcher')
     prefs = load_config(config_name)
-    engine_name = prefs.get('engine', '') if force_engine is None else force_engine
-    engine_name = engine_name or default_engine_name()
-    if engine_name not in available_engines():
-        engine_name = default_engine_name()
+    if force_engine is not None:
+        engine_name = force_engine
+        if engine_name not in available_engines():
+            raise OSError(f'TTS engine {force_engine} is not available.')
+    else:
+        engine_name = prefs.get('engine', '')
+        engine_name = engine_name or default_engine_name()
+        if engine_name not in available_engines():
+            engine_name = default_engine_name()
     if engine_name == 'piper':
         if engine_name not in engine_instances:
             from calibre.gui2.tts.piper import Piper