mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Read Aloud config: Show already downloaded voices in bold
This commit is contained in:
parent
065bafd0c5
commit
4ae5fe59c0
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
from qt.core import (
|
from qt.core import (
|
||||||
QCheckBox,
|
QCheckBox,
|
||||||
|
QFont,
|
||||||
QFormLayout,
|
QFormLayout,
|
||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
QIcon,
|
QIcon,
|
||||||
@ -136,20 +137,30 @@ class Voices(QTreeWidget):
|
|||||||
self.setHeaderHidden(True)
|
self.setHeaderHidden(True)
|
||||||
self.system_default_voice = Voice()
|
self.system_default_voice = Voice()
|
||||||
self.currentItemChanged.connect(self.voice_changed)
|
self.currentItemChanged.connect(self.voice_changed)
|
||||||
|
self.normal_font = f = self.font()
|
||||||
|
self.highlight_font = f = QFont(f)
|
||||||
|
f.setBold(True), f.setItalic(True)
|
||||||
|
|
||||||
def sizeHint(self) -> QSize:
|
def sizeHint(self) -> QSize:
|
||||||
return QSize(400, 500)
|
return QSize(400, 500)
|
||||||
|
|
||||||
|
def set_item_downloaded_state(self, ans: QTreeWidgetItem) -> None:
|
||||||
|
voice = ans.data(0, Qt.ItemDataRole.UserRole)
|
||||||
|
is_downloaded = bool(voice.engine_data and voice.engine_data.get('is_downloaded'))
|
||||||
|
ans.setFont(0, self.highlight_font if is_downloaded else self.normal_font)
|
||||||
|
|
||||||
def set_voices(self, all_voices: tuple[Voice, ...], current_voice: str, engine_metadata: EngineMetadata) -> None:
|
def set_voices(self, all_voices: tuple[Voice, ...], current_voice: str, engine_metadata: EngineMetadata) -> None:
|
||||||
self.clear()
|
self.clear()
|
||||||
current_item = None
|
current_item = None
|
||||||
def qv(parent, voice):
|
def qv(parent, voice):
|
||||||
nonlocal current_item
|
nonlocal current_item
|
||||||
ans = QTreeWidgetItem(parent, [voice.short_text(engine_metadata)])
|
text = voice.short_text(engine_metadata)
|
||||||
|
ans = QTreeWidgetItem(parent, [text])
|
||||||
ans.setData(0, Qt.ItemDataRole.UserRole, voice)
|
ans.setData(0, Qt.ItemDataRole.UserRole, voice)
|
||||||
ans.setToolTip(0, voice.tooltip(engine_metadata))
|
ans.setToolTip(0, voice.tooltip(engine_metadata))
|
||||||
if current_voice == voice.name:
|
if current_voice == voice.name:
|
||||||
current_item = ans
|
current_item = ans
|
||||||
|
self.set_item_downloaded_state(ans)
|
||||||
return ans
|
return ans
|
||||||
qv(self.invisibleRootItem(), self.system_default_voice)
|
qv(self.invisibleRootItem(), self.system_default_voice)
|
||||||
vmap = {}
|
vmap = {}
|
||||||
@ -182,6 +193,11 @@ class Voices(QTreeWidget):
|
|||||||
if ci is not None:
|
if ci is not None:
|
||||||
return ci.data(0, Qt.ItemDataRole.UserRole)
|
return ci.data(0, Qt.ItemDataRole.UserRole)
|
||||||
|
|
||||||
|
def refresh_current_item(self) -> None:
|
||||||
|
ci = self.currentItem()
|
||||||
|
if ci is not None:
|
||||||
|
self.set_item_downloaded_state(ci)
|
||||||
|
|
||||||
|
|
||||||
class EngineSpecificConfig(QWidget):
|
class EngineSpecificConfig(QWidget):
|
||||||
|
|
||||||
@ -357,6 +373,7 @@ class ConfigDialog(Dialog):
|
|||||||
else:
|
else:
|
||||||
b.setIcon(QIcon.ic('download-metadata.png'))
|
b.setIcon(QIcon.ic('download-metadata.png'))
|
||||||
b.setText(_('Download voice'))
|
b.setText(_('Download voice'))
|
||||||
|
self.engine_specific_config.voices.refresh_current_item()
|
||||||
|
|
||||||
def voice_action(self):
|
def voice_action(self):
|
||||||
self.engine_specific_config.voice_action()
|
self.engine_specific_config.voice_action()
|
||||||
|
@ -406,6 +406,9 @@ class Piper(TTSBackend):
|
|||||||
ans = []
|
ans = []
|
||||||
lang_voices_map = {}
|
lang_voices_map = {}
|
||||||
self._voice_name_map = {}
|
self._voice_name_map = {}
|
||||||
|
downloaded = set()
|
||||||
|
with suppress(OSError):
|
||||||
|
downloaded = set(os.listdir(self.cache_dir))
|
||||||
for bcp_code, voice_map in d['lang_map'].items():
|
for bcp_code, voice_map in d['lang_map'].items():
|
||||||
lang, sep, country = bcp_code.partition('_')
|
lang, sep, country = bcp_code.partition('_')
|
||||||
lang = canonicalize_lang(lang) or lang
|
lang = canonicalize_lang(lang) or lang
|
||||||
@ -416,9 +419,10 @@ class Piper(TTSBackend):
|
|||||||
q = Quality.from_piper_quality(qual)
|
q = Quality.from_piper_quality(qual)
|
||||||
if best_qual is None or q.value < best_qual.value:
|
if best_qual is None or q.value < best_qual.value:
|
||||||
best_qual = q
|
best_qual = q
|
||||||
|
mf = f'{bcp_code}-{voice_name}-{qual}.onnx'
|
||||||
voice = Voice(bcp_code + ':' + voice_name, lang, country, human_name=voice_name, quality=q, engine_data={
|
voice = Voice(bcp_code + ':' + voice_name, lang, country, human_name=voice_name, quality=q, engine_data={
|
||||||
'model_url': e['model'], 'config_url': e['config'],
|
'model_url': e['model'], 'config_url': e['config'],
|
||||||
'model_filename': f'{bcp_code}-{voice_name}-{qual}.onnx',
|
'model_filename': mf, 'is_downloaded': mf in downloaded,
|
||||||
})
|
})
|
||||||
if voice:
|
if voice:
|
||||||
ans.append(voice)
|
ans.append(voice)
|
||||||
@ -442,9 +446,13 @@ class Piper(TTSBackend):
|
|||||||
lang = canonicalize_lang(lang) or lang
|
lang = canonicalize_lang(lang) or lang
|
||||||
return self._voice_for_lang.get(lang) or self._voice_for_lang['eng']
|
return self._voice_for_lang.get(lang) or self._voice_for_lang['eng']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cache_dir(self) -> str:
|
||||||
|
return os.path.join(cache_dir(), 'piper-voices')
|
||||||
|
|
||||||
def _paths_for_voice(self, voice: Voice) -> tuple[str, str]:
|
def _paths_for_voice(self, voice: Voice) -> tuple[str, str]:
|
||||||
fname = voice.engine_data['model_filename']
|
fname = voice.engine_data['model_filename']
|
||||||
model_path = os.path.join(cache_dir(), 'piper-voices', fname)
|
model_path = os.path.join(self.cache_dir, fname)
|
||||||
config_path = os.path.join(os.path.dirname(model_path), fname + '.json')
|
config_path = os.path.join(os.path.dirname(model_path), fname + '.json')
|
||||||
return model_path, config_path
|
return model_path, config_path
|
||||||
|
|
||||||
@ -462,6 +470,7 @@ class Piper(TTSBackend):
|
|||||||
for path in self._paths_for_voice(v):
|
for path in self._paths_for_voice(v):
|
||||||
with suppress(FileNotFoundError):
|
with suppress(FileNotFoundError):
|
||||||
os.remove(path)
|
os.remove(path)
|
||||||
|
v.engine_data['is_downloaded'] = False
|
||||||
|
|
||||||
def _download_voice(self, voice: Voice, download_even_if_exists: bool = False) -> tuple[str, str]:
|
def _download_voice(self, voice: Voice, download_even_if_exists: bool = False) -> tuple[str, str]:
|
||||||
model_path, config_path = self._paths_for_voice(voice)
|
model_path, config_path = self._paths_for_voice(voice)
|
||||||
@ -475,6 +484,7 @@ class Piper(TTSBackend):
|
|||||||
voice.engine_data['config_url']: (config_path, _('Neural network metadata')),
|
voice.engine_data['config_url']: (config_path, _('Neural network metadata')),
|
||||||
}, parent=widget_parent(self), headless=getattr(QApplication.instance(), 'headless', False)
|
}, parent=widget_parent(self), headless=getattr(QApplication.instance(), 'headless', False)
|
||||||
)
|
)
|
||||||
|
voice.engine_data['is_downloaded'] = bool(ok)
|
||||||
return (model_path, config_path) if ok else ('', '')
|
return (model_path, config_path) if ok else ('', '')
|
||||||
|
|
||||||
def download_voice(self, v: Voice) -> None:
|
def download_voice(self, v: Voice) -> None:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user