mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-17 10:00:46 -04:00
Merge pull request #13 from legop3/fix-laptoprover-audio
Fix laptoprover tts and horn including gtts
This commit is contained in:
@@ -1,32 +1,36 @@
|
|||||||
# ALSA routing for the Debian laptop rover profile.
|
# Dedicated ALSA routing for the Debian laptop rover profile.
|
||||||
#
|
#
|
||||||
# This file intentionally mirrors the logical device names used by the Pi rover
|
# This intentionally mirrors pi/asound.conf as closely as a normal PC can:
|
||||||
# audio setup. roverd can keep sending horn audio to "horn", forwarded browser
|
# one fixed hardware card, one dmix playback engine, separate softvol controls
|
||||||
# audio to "forward", and TTS to ALSA's default playback path without caring
|
# for TTS/horn/forwarded audio, and a raw capture alias for the rover mic.
|
||||||
# which physical sound card is underneath the profile.
|
#
|
||||||
|
# This is NOT meant to preserve normal desktop audio behavior. The laptop rover
|
||||||
|
# installer disables PipeWire/PulseAudio so roverd owns the audio hardware like
|
||||||
|
# the Raspberry Pi rover does. If the laptop's real speaker/mic card is not ALSA
|
||||||
|
# card 0, change the hw:0,0/card 0 references below to the card shown by:
|
||||||
|
# aplay -l
|
||||||
|
# arecord -l
|
||||||
|
|
||||||
pcm.roverd_playback {
|
# Mix multiple playback clients in software with a fixed low-cost format.
|
||||||
type plug
|
pcm.dmixer {
|
||||||
|
type dmix
|
||||||
# Use the system's first normal ALSA playback device as the physical sink.
|
ipc_key 1024
|
||||||
# This avoids referencing "default" here, because this file replaces
|
ipc_perm 0666
|
||||||
# pcm.!default below and using it as a slave would recurse.
|
slave {
|
||||||
slave.pcm "sysdefault"
|
pcm "hw:0,0"
|
||||||
|
format S16_LE
|
||||||
|
rate 16000
|
||||||
|
channels 1
|
||||||
|
period_time 0
|
||||||
|
period_size 1024
|
||||||
|
buffer_size 4096
|
||||||
}
|
}
|
||||||
|
|
||||||
pcm.roverd_capture {
|
|
||||||
type plug
|
|
||||||
|
|
||||||
# The media publisher records from "default"; with pcm.!default below that
|
|
||||||
# capture side resolves here. Keeping capture separate from playback lets the
|
|
||||||
# asym default expose ordinary microphone input while playback goes through
|
|
||||||
# the TTS softvol path.
|
|
||||||
slave.pcm "sysdefault"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# TTS volume control (used by default playback path).
|
||||||
pcm.tts_softvol {
|
pcm.tts_softvol {
|
||||||
type softvol
|
type softvol
|
||||||
slave.pcm "roverd_playback"
|
slave.pcm "dmixer"
|
||||||
control {
|
control {
|
||||||
name "TTSMaster"
|
name "TTSMaster"
|
||||||
card 0
|
card 0
|
||||||
@@ -35,9 +39,10 @@ pcm.tts_softvol {
|
|||||||
max_dB 12.0
|
max_dB 12.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Horn volume control.
|
||||||
pcm.horn_softvol {
|
pcm.horn_softvol {
|
||||||
type softvol
|
type softvol
|
||||||
slave.pcm "roverd_playback"
|
slave.pcm "dmixer"
|
||||||
control {
|
control {
|
||||||
name "HornMaster"
|
name "HornMaster"
|
||||||
card 0
|
card 0
|
||||||
@@ -46,9 +51,10 @@ pcm.horn_softvol {
|
|||||||
max_dB 12.0
|
max_dB 12.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Forwarded audio volume control.
|
||||||
pcm.forward_softvol {
|
pcm.forward_softvol {
|
||||||
type softvol
|
type softvol
|
||||||
slave.pcm "roverd_playback"
|
slave.pcm "dmixer"
|
||||||
control {
|
control {
|
||||||
name "ForwardMaster"
|
name "ForwardMaster"
|
||||||
card 0
|
card 0
|
||||||
@@ -57,6 +63,7 @@ pcm.forward_softvol {
|
|||||||
max_dB 12.0
|
max_dB 12.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Per-source playback PCMs.
|
||||||
pcm.tts {
|
pcm.tts {
|
||||||
type plug
|
type plug
|
||||||
slave.pcm "tts_softvol"
|
slave.pcm "tts_softvol"
|
||||||
@@ -72,14 +79,17 @@ pcm.forward {
|
|||||||
slave.pcm "forward_softvol"
|
slave.pcm "forward_softvol"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Capture alias used by laptop rover config defaults.
|
||||||
|
pcm.rovermic {
|
||||||
|
type plug
|
||||||
|
slave.pcm "hw:0,0"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Defaults: TTS direct playback + raw capture on the dedicated laptop sound card.
|
||||||
pcm.!default {
|
pcm.!default {
|
||||||
type asym
|
type asym
|
||||||
|
|
||||||
# Existing TTS engines play to their default ALSA output, so default playback
|
|
||||||
# is intentionally the TTS path. This preserves the current TTS execution
|
|
||||||
# model while still making the TTS volume control meaningful on laptops.
|
|
||||||
playback.pcm "tts"
|
playback.pcm "tts"
|
||||||
capture.pcm "roverd_capture"
|
capture.pcm "rovermic"
|
||||||
}
|
}
|
||||||
|
|
||||||
ctl.!default {
|
ctl.!default {
|
||||||
|
|||||||
@@ -0,0 +1,202 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import ctypes
|
||||||
|
import ctypes.util
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import struct
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
ASSET_ROOT = "/opt/roverd/googletts"
|
||||||
|
LIB_PATH = os.path.join(ASSET_ROOT, "libchrometts.so")
|
||||||
|
VOICE_DIR = os.path.join(ASSET_ROOT, "en-us-x-multi-r30")
|
||||||
|
PIPELINE = "pipeline.pb"
|
||||||
|
PLAYBACK_DEVICE = "tts"
|
||||||
|
SAMPLE_RATE = "24000"
|
||||||
|
MAX_TEXT_CHARS = 512
|
||||||
|
|
||||||
|
VOICES = {
|
||||||
|
"sfg": "female",
|
||||||
|
"iob": "female",
|
||||||
|
"iog": "female",
|
||||||
|
"iol": "male",
|
||||||
|
"iom": "male",
|
||||||
|
"tpc": "female",
|
||||||
|
"tpd": "male",
|
||||||
|
"tpf": "female",
|
||||||
|
}
|
||||||
|
DEFAULT_VOICE = "tpf"
|
||||||
|
DEFAULT_PITCH = 1.0
|
||||||
|
DEFAULT_SPEED = 1.0
|
||||||
|
MIN_PITCH = 0.5
|
||||||
|
MAX_PITCH = 2.0
|
||||||
|
MIN_SPEED = 0.5
|
||||||
|
MAX_SPEED = 2.0
|
||||||
|
|
||||||
|
_runtime_handles = []
|
||||||
|
|
||||||
|
|
||||||
|
def load_shared_library(path):
|
||||||
|
mode = ctypes.RTLD_GLOBAL | getattr(os, "RTLD_NOW", 0)
|
||||||
|
return ctypes.CDLL(path, mode=mode)
|
||||||
|
|
||||||
|
|
||||||
|
def preload_runtime_libraries():
|
||||||
|
# Laptop-only workaround: some ChromeOS libchrometts builds reference
|
||||||
|
# compiler helper symbols such as __udivmodti4 without declaring the runtime
|
||||||
|
# library as an ELF dependency. Loading common compiler runtimes globally
|
||||||
|
# first makes those symbols visible before ctypes loads libchrometts.so.
|
||||||
|
for name in ("gcc_s", "atomic", "stdc++", "c++", "c++abi"):
|
||||||
|
lib = ctypes.util.find_library(name)
|
||||||
|
if not lib:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
_runtime_handles.append(load_shared_library(lib))
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
preload_runtime_libraries()
|
||||||
|
|
||||||
|
|
||||||
|
def varint(value):
|
||||||
|
out = bytearray()
|
||||||
|
while value >= 0x80:
|
||||||
|
out.append((value & 0x7F) | 0x80)
|
||||||
|
value >>= 7
|
||||||
|
out.append(value)
|
||||||
|
return bytes(out)
|
||||||
|
|
||||||
|
|
||||||
|
def field_bytes(number, payload):
|
||||||
|
return varint((number << 3) | 2) + varint(len(payload)) + payload
|
||||||
|
|
||||||
|
|
||||||
|
def field_float(number, value):
|
||||||
|
return varint((number << 3) | 5) + struct.pack("<f", float(value))
|
||||||
|
|
||||||
|
|
||||||
|
def build_utterance(text, pitch=1.0, speed=1.0):
|
||||||
|
params = field_float(2, pitch) + field_float(3, speed)
|
||||||
|
msg_b = field_bytes(1, text.encode("utf-8")) + field_bytes(20, params)
|
||||||
|
msg_a = field_bytes(1, msg_b)
|
||||||
|
return field_bytes(1, msg_a)
|
||||||
|
|
||||||
|
|
||||||
|
def build_speaker(name, gender):
|
||||||
|
return field_bytes(1, name.encode("utf-8")) + field_bytes(2, gender.encode("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
class ChromeTTS:
|
||||||
|
def __init__(self):
|
||||||
|
self.lib = load_shared_library(LIB_PATH)
|
||||||
|
self.lib.GoogleTtsInit.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
|
||||||
|
self.lib.GoogleTtsInit.restype = ctypes.c_bool
|
||||||
|
self.lib.GoogleTtsInitBuffered.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_int]
|
||||||
|
self.lib.GoogleTtsInitBuffered.restype = ctypes.c_bool
|
||||||
|
self.lib.GoogleTtsGetFramesInAudioBuffer.argtypes = []
|
||||||
|
self.lib.GoogleTtsGetFramesInAudioBuffer.restype = ctypes.c_size_t
|
||||||
|
self.lib.GoogleTtsReadBuffered.argtypes = [
|
||||||
|
ctypes.POINTER(ctypes.c_float),
|
||||||
|
ctypes.POINTER(ctypes.c_size_t),
|
||||||
|
]
|
||||||
|
self.lib.GoogleTtsReadBuffered.restype = ctypes.c_int
|
||||||
|
self.lib.GoogleTtsShutdown.argtypes = []
|
||||||
|
self.lib.GoogleTtsShutdown.restype = None
|
||||||
|
|
||||||
|
voice_dir = os.path.abspath(VOICE_DIR) + os.sep
|
||||||
|
pipeline = os.path.join(voice_dir, PIPELINE)
|
||||||
|
if not self.lib.GoogleTtsInit(pipeline.encode("utf-8"), voice_dir.encode("utf-8")):
|
||||||
|
raise RuntimeError("GoogleTtsInit failed")
|
||||||
|
self.frames = int(self.lib.GoogleTtsGetFramesInAudioBuffer())
|
||||||
|
if self.frames <= 0:
|
||||||
|
raise RuntimeError("invalid Google TTS audio buffer size")
|
||||||
|
self.buffer = (ctypes.c_float * self.frames)()
|
||||||
|
|
||||||
|
def speak_to_aplay(self, text, voice, pitch=DEFAULT_PITCH, speed=DEFAULT_SPEED):
|
||||||
|
voice = voice if voice in VOICES else DEFAULT_VOICE
|
||||||
|
pitch = clamp_float(pitch, MIN_PITCH, MAX_PITCH, DEFAULT_PITCH)
|
||||||
|
speed = clamp_float(speed, MIN_SPEED, MAX_SPEED, DEFAULT_SPEED)
|
||||||
|
text = text.strip()
|
||||||
|
if not text:
|
||||||
|
raise ValueError("text required")
|
||||||
|
text = text[:MAX_TEXT_CHARS]
|
||||||
|
utterance = build_utterance(text, pitch=pitch, speed=speed)
|
||||||
|
speaker = build_speaker(voice, VOICES[voice])
|
||||||
|
if not self.lib.GoogleTtsInitBuffered(utterance, speaker, len(utterance), len(speaker)):
|
||||||
|
raise RuntimeError("GoogleTtsInitBuffered failed")
|
||||||
|
|
||||||
|
player = subprocess.Popen(
|
||||||
|
["aplay", "-q", "-D", PLAYBACK_DEVICE, "-r", SAMPLE_RATE, "-f", "FLOAT_LE", "-c", "1"],
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
frames_written = ctypes.c_size_t(0)
|
||||||
|
while self.lib.GoogleTtsReadBuffered(self.buffer, ctypes.byref(frames_written)) > 0:
|
||||||
|
frames = int(frames_written.value)
|
||||||
|
if frames > 0:
|
||||||
|
player.stdin.write(ctypes.string_at(self.buffer, frames * ctypes.sizeof(ctypes.c_float)))
|
||||||
|
player.stdin.close()
|
||||||
|
rc = player.wait()
|
||||||
|
if rc != 0:
|
||||||
|
raise RuntimeError(f"aplay exited with {rc}")
|
||||||
|
finally:
|
||||||
|
if player.poll() is None:
|
||||||
|
player.kill()
|
||||||
|
player.wait()
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
self.lib.GoogleTtsShutdown()
|
||||||
|
|
||||||
|
|
||||||
|
def respond(payload):
|
||||||
|
sys.stdout.write(json.dumps(payload, separators=(",", ":")) + "\n")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
|
def clamp_float(value, minimum, maximum, fallback):
|
||||||
|
try:
|
||||||
|
value = float(value)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return fallback
|
||||||
|
if value <= 0:
|
||||||
|
return fallback
|
||||||
|
if value < minimum:
|
||||||
|
return minimum
|
||||||
|
if value > maximum:
|
||||||
|
return maximum
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
tts = ChromeTTS()
|
||||||
|
except Exception as exc:
|
||||||
|
respond({"ok": False, "error": str(exc)})
|
||||||
|
return 1
|
||||||
|
|
||||||
|
respond({"ok": True, "ready": True})
|
||||||
|
try:
|
||||||
|
for line in sys.stdin:
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
request = json.loads(line)
|
||||||
|
tts.speak_to_aplay(
|
||||||
|
str(request.get("text") or ""),
|
||||||
|
str(request.get("voice") or DEFAULT_VOICE),
|
||||||
|
request.get("pitch", DEFAULT_PITCH),
|
||||||
|
request.get("speed", DEFAULT_SPEED),
|
||||||
|
)
|
||||||
|
respond({"ok": True})
|
||||||
|
except Exception as exc:
|
||||||
|
respond({"ok": False, "error": str(exc)})
|
||||||
|
finally:
|
||||||
|
tts.shutdown()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -1,23 +1,63 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
install_debian_laptop_deps() {
|
install_debian_laptop_deps() {
|
||||||
# This profile is deliberately Debian-only. Using apt directly is simpler
|
# The laptop rover is a dedicated appliance, not a normal desktop/laptop audio
|
||||||
# than adding a fake cross-distro layer, and it keeps the installed package
|
# install. Keep this package set intentionally close to the Pi profile so the
|
||||||
# set easy to inspect on the actual rover laptop.
|
# same roverd TTS/playback/capture code paths are available on both targets.
|
||||||
if command -v ffmpeg >/dev/null 2>&1 \
|
if command -v ffmpeg >/dev/null 2>&1 \
|
||||||
&& command -v arecord >/dev/null 2>&1 \
|
&& command -v arecord >/dev/null 2>&1 \
|
||||||
&& command -v aplay >/dev/null 2>&1 \
|
&& command -v aplay >/dev/null 2>&1 \
|
||||||
&& command -v amixer >/dev/null 2>&1 \
|
&& command -v amixer >/dev/null 2>&1 \
|
||||||
&& command -v v4l2-ctl >/dev/null 2>&1 \
|
&& command -v v4l2-ctl >/dev/null 2>&1 \
|
||||||
&& command -v flite >/dev/null 2>&1 \
|
&& command -v flite >/dev/null 2>&1 \
|
||||||
&& command -v espeak >/dev/null 2>&1; then
|
&& command -v espeak >/dev/null 2>&1 \
|
||||||
log "Debian laptop media/audio dependencies already installed; skipping apt install"
|
&& command -v python3 >/dev/null 2>&1 \
|
||||||
|
&& command -v curl >/dev/null 2>&1 \
|
||||||
|
&& command -v xz >/dev/null 2>&1 \
|
||||||
|
&& command -v unzip >/dev/null 2>&1 \
|
||||||
|
&& ldconfig -p 2>/dev/null | grep -q 'libgcc_s\.so\.1' \
|
||||||
|
&& ldconfig -p 2>/dev/null | grep -q 'libstdc\+\+\.so\.6' \
|
||||||
|
&& ldconfig -p 2>/dev/null | grep -q 'libc++\.so\.1' \
|
||||||
|
&& ldconfig -p 2>/dev/null | grep -q 'libc++abi\.so\.1'; then
|
||||||
|
log "Debian laptop media/audio/TTS dependencies already installed; skipping apt install"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "Installing Debian laptop dependencies (ffmpeg, ALSA tools, V4L2 tools, flite, espeak)..."
|
log "Installing Debian laptop rover dependencies (ffmpeg, ALSA tools, V4L2 tools, flite/espeak, Chrome TTS runtime deps)..."
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y --no-install-recommends ffmpeg alsa-utils v4l-utils ca-certificates flite espeak
|
apt-get install -y --no-install-recommends \
|
||||||
|
ffmpeg alsa-utils v4l-utils ca-certificates flite espeak python3 curl xz-utils unzip libasound2-plugins libgcc-s1 libstdc++6 libc++1 libc++abi1 \
|
||||||
|
|| apt-get install -y --no-install-recommends \
|
||||||
|
ffmpeg alsa-utils v4l-utils ca-certificates flite espeak python3 curl xz-utils unzip libasound2-plugins libgcc-s1 libstdc++6 libc++1-14 libc++abi1-14
|
||||||
|
}
|
||||||
|
|
||||||
|
disable_debian_laptop_desktop_audio_stack() {
|
||||||
|
# This profile is for a dedicated rover laptop. PipeWire/PulseAudio are good
|
||||||
|
# desktop defaults, but they can grab the hardware device and make the rover's
|
||||||
|
# root/systemd ALSA services fail or route through a moving per-user graph.
|
||||||
|
# Mask them globally and kill already-running instances so ALSA owns the box,
|
||||||
|
# which is the closest behavior to the Pi rover appliance setup.
|
||||||
|
log "Disabling desktop audio daemons for dedicated laptop rover audio"
|
||||||
|
|
||||||
|
local -a user_units=(
|
||||||
|
pipewire.service
|
||||||
|
pipewire.socket
|
||||||
|
pipewire-pulse.service
|
||||||
|
pipewire-pulse.socket
|
||||||
|
wireplumber.service
|
||||||
|
pulseaudio.service
|
||||||
|
pulseaudio.socket
|
||||||
|
)
|
||||||
|
|
||||||
|
if command -v systemctl >/dev/null 2>&1; then
|
||||||
|
systemctl --global disable "${user_units[@]}" >/dev/null 2>&1 || true
|
||||||
|
systemctl --global mask "${user_units[@]}" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
pkill -x pipewire >/dev/null 2>&1 || true
|
||||||
|
pkill -x pipewire-pulse >/dev/null 2>&1 || true
|
||||||
|
pkill -x wireplumber >/dev/null 2>&1 || true
|
||||||
|
pkill -x pulseaudio >/dev/null 2>&1 || true
|
||||||
}
|
}
|
||||||
|
|
||||||
install_debian_laptop_audio_support() {
|
install_debian_laptop_audio_support() {
|
||||||
@@ -26,17 +66,92 @@ install_debian_laptop_audio_support() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# The laptop profile still uses roverd's existing audio contract: TTS plays
|
|
||||||
# to ALSA's default output, horn plays to the named "horn" device, and
|
|
||||||
# forwarded web audio plays to the named "forward" device. Installing one
|
|
||||||
# profile-specific asound.conf gives those paths independent softvol mixer
|
|
||||||
# controls without changing the TTS runtime code.
|
|
||||||
install -m 0644 pi/asound.debian-laptop.conf /etc/asound.conf
|
install -m 0644 pi/asound.debian-laptop.conf /etc/asound.conf
|
||||||
log "Installed Debian laptop ALSA config to /etc/asound.conf"
|
log "Installed dedicated Debian laptop ALSA config to /etc/asound.conf"
|
||||||
log "ALSA config updated; restarting audio clients or rebooting is recommended before testing laptop audio"
|
|
||||||
|
install -D -o root -g root -m 0755 pi/bin/chromegtts-daemon-laptop.py /usr/local/bin/chromegtts-daemon
|
||||||
|
log "Installed laptop chromegtts daemon"
|
||||||
|
|
||||||
|
install_google_tts_assets_laptop
|
||||||
|
|
||||||
|
log "ALSA config updated; reboot recommended before testing laptop rover audio"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_google_tts_assets_laptop() {
|
||||||
|
local asset_dir="/opt/roverd/googletts"
|
||||||
|
local voice_dir="${asset_dir}/en-us-x-multi-r30"
|
||||||
|
local dist_url="https://storage.googleapis.com/chromeos-localmirror/distfiles/googletts-26.5.tar.xz"
|
||||||
|
local tmp_dir
|
||||||
|
local lib_member=""
|
||||||
|
local member
|
||||||
|
|
||||||
|
if [[ -f "${asset_dir}/libchrometts.so" && -f "${voice_dir}/pipeline.pb" ]]; then
|
||||||
|
log "Google Chrome TTS assets already installed; skipping download"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmp_dir="$(mktemp -d)"
|
||||||
|
log "Downloading Google Chrome TTS assets for Debian laptop profile..."
|
||||||
|
if ! curl -L -o "${tmp_dir}/googletts-26.5.tar.xz" "$dist_url"; then
|
||||||
|
rm -rf "$tmp_dir"
|
||||||
|
log "WARNING: failed to download Google Chrome TTS assets; chromegtts will be unavailable"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local -a candidate_libs=()
|
||||||
|
case "$(uname -m)" in
|
||||||
|
aarch64|arm64)
|
||||||
|
candidate_libs=(libchrometts_arm64.so)
|
||||||
|
;;
|
||||||
|
armv7l|armhf)
|
||||||
|
candidate_libs=(libchrometts_armv7.so)
|
||||||
|
;;
|
||||||
|
x86_64|amd64)
|
||||||
|
candidate_libs=(libchrometts_x86_64.so libchrometts_amd64.so libchrometts_x64.so libchrometts.so)
|
||||||
|
;;
|
||||||
|
i386|i686)
|
||||||
|
candidate_libs=(libchrometts_x86.so libchrometts_i386.so libchrometts.so)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log "WARNING: unsupported Chrome TTS architecture $(uname -m); skipping Google TTS assets"
|
||||||
|
rm -rf "$tmp_dir"
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
for member in "${candidate_libs[@]}"; do
|
||||||
|
if tar -tf "${tmp_dir}/googletts-26.5.tar.xz" "$member" >/dev/null 2>&1; then
|
||||||
|
lib_member="$member"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$lib_member" ]]; then
|
||||||
|
log "WARNING: no libchrometts library matching $(uname -m) found in Google TTS archive; chromegtts will be unavailable"
|
||||||
|
rm -rf "$tmp_dir"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! tar -xf "${tmp_dir}/googletts-26.5.tar.xz" -C "$tmp_dir" en-us-x-multi.zvoice "$lib_member"; then
|
||||||
|
rm -rf "$tmp_dir"
|
||||||
|
log "WARNING: failed to unpack Google Chrome TTS assets; chromegtts will be unavailable"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
install -d -o root -g root -m 0755 "$asset_dir"
|
||||||
|
install -o root -g root -m 0644 "${tmp_dir}/${lib_member}" "${asset_dir}/libchrometts.so"
|
||||||
|
rm -rf "$voice_dir"
|
||||||
|
install -d -o root -g root -m 0755 "$voice_dir"
|
||||||
|
unzip -q "${tmp_dir}/en-us-x-multi.zvoice" -d "$voice_dir"
|
||||||
|
chown -R root:root "$asset_dir"
|
||||||
|
find "$asset_dir" -type d -exec chmod 0755 {} +
|
||||||
|
find "$asset_dir" -type f -exec chmod 0644 {} +
|
||||||
|
rm -rf "$tmp_dir"
|
||||||
|
log "Installed Google Chrome TTS assets to $asset_dir using $lib_member"
|
||||||
}
|
}
|
||||||
|
|
||||||
install_debian_laptop_profile() {
|
install_debian_laptop_profile() {
|
||||||
install_debian_laptop_deps
|
install_debian_laptop_deps
|
||||||
|
disable_debian_laptop_desktop_audio_stack
|
||||||
install_debian_laptop_audio_support
|
install_debian_laptop_audio_support
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user