The piper project list of voices has changed format and location

Need to look into updating the actual binary bundled with calibre as
well. Fixes #2117433 [download Neural network metadata with error](https://bugs.launchpad.net/calibre/+bug/2117433)
This commit is contained in:
Kovid Goyal 2025-07-22 07:41:29 +05:30
parent f25ef1ed2a
commit 0fc4516bc2
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C

View File

@ -3,7 +3,6 @@
import json import json
import os import os
import re
from contextlib import suppress from contextlib import suppress
from setup.revendor import ReVendor from setup.revendor import ReVendor
@ -14,8 +13,8 @@ class PiperVoices(ReVendor):
description = 'Download the list of Piper voices' description = 'Download the list of Piper voices'
NAME = 'piper_voices' NAME = 'piper_voices'
TAR_NAME = 'piper voice list' TAR_NAME = 'piper voice list'
VERSION = 'master' VERSION = 'main'
DOWNLOAD_URL = f'https://raw.githubusercontent.com/rhasspy/piper/{VERSION}/VOICES.md' DOWNLOAD_URL = f'https://huggingface.co/rhasspy/piper-voices/raw/{VERSION}/voices.json'
CAN_USE_SYSTEM_VERSION = False CAN_USE_SYSTEM_VERSION = False
@property @property
@ -30,36 +29,21 @@ class PiperVoices(ReVendor):
else: else:
url = opts.piper_voices_url url = opts.piper_voices_url
src = self.download_securely(url).decode('utf-8') src = self.download_securely(url).decode('utf-8')
data = json.loads(src)
lang_map = {} lang_map = {}
current_lang = current_voice = '' for voice in data.values():
lang_pat = re.compile(r'\((.+?)\)') language_code = voice['language']['code']
model_pat = re.compile(r'\[model\]\((.+?)\)') lang_entry = lang_map.setdefault(language_code, {})
config_pat = re.compile(r'\[config\]\((.+?)\)') voice_entry = lang_entry.setdefault(voice['name'], {})
for line in src.splitlines(): quality_entry = voice_entry.setdefault(voice['quality'], {})
if line.startswith('* '): for f, metadata in voice['files'].items():
if m := lang_pat.search(line): if f.endswith('.json'):
current_lang = m.group(1).partition(',')[0].replace('`', '') key = 'config'
lang_map[current_lang] = {} elif f.endswith('.onnx'):
current_voice = '' key = 'model'
else: else:
line = line.strip() key = 'card'
if not line.startswith('*'): quality_entry[key] = 'https://huggingface.co/rhasspy/piper-voices/resolve/main/' + f
continue
if '[model]' in line:
if current_lang and current_voice:
qual_map = lang_map[current_lang][current_voice]
quality = line.partition('-')[0].strip().lstrip('*').strip()
model = config = ''
if m := model_pat.search(line):
model = m.group(1)
if m := config_pat.search(line):
config = m.group(1)
if not quality or not model or not config:
raise SystemExit('Failed to parse piper voice model definition from:\n' + line)
qual_map[quality] = {'model': model, 'config': config}
else:
current_voice = line.partition(' ')[-1].strip()
lang_map[current_lang][current_voice] = {}
if not lang_map: if not lang_map:
raise SystemExit(f'Failed to read any piper voices from: {url}') raise SystemExit(f'Failed to read any piper voices from: {url}')
if 'en_US' not in lang_map: if 'en_US' not in lang_map: