aboutsummaryrefslogtreecommitdiff
path: root/deskutils/calibre/files/patch-tts-missing-fix
diff options
context:
space:
mode:
Diffstat (limited to 'deskutils/calibre/files/patch-tts-missing-fix')
-rw-r--r--deskutils/calibre/files/patch-tts-missing-fix81
1 files changed, 81 insertions, 0 deletions
diff --git a/deskutils/calibre/files/patch-tts-missing-fix b/deskutils/calibre/files/patch-tts-missing-fix
new file mode 100644
index 000000000000..a8ea80597c6a
--- /dev/null
+++ b/deskutils/calibre/files/patch-tts-missing-fix
@@ -0,0 +1,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