401 lines
13 KiB
Python
401 lines
13 KiB
Python
"""
|
|
Android Speech/TTS 组件 — TextToSpeech, Voice, SynthesisCallback 等
|
|
方法签名完全对齐 Android SDK
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import locale as _locale
|
|
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
|
|
|
from .._core import (
|
|
Stub, AndroidOnlyError, android_context,
|
|
resolve_android_path, to_android_path,
|
|
_sdcard_root, _app_data_dir, _package_name,
|
|
)
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# TextToSpeech.OnInitListener
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class TextToSpeechOnInitListener(Stub):
|
|
"""OnInitListener — 初始化监听器"""
|
|
|
|
def onInit(self, status: int):
|
|
pass
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# UtteranceProgressListener
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class UtteranceProgressListener(Stub):
|
|
"""UtteranceProgressListener — 发音进度监听器"""
|
|
|
|
def onStart(self, utterance_id: str):
|
|
pass
|
|
|
|
def onDone(self, utterance_id: str):
|
|
pass
|
|
|
|
def onError(self, utterance_id: str, error_code: int = -1):
|
|
pass
|
|
|
|
def onStop(self, utterance_id: str, interrupted: bool = False):
|
|
pass
|
|
|
|
def onBeginSynthesis(self, utterance_id: str, sample_rate_in_hz: int,
|
|
audio_format: int, channel_count: int):
|
|
pass
|
|
|
|
def onRangeStart(self, utterance_id: str, start: int, end: int, frame: int):
|
|
pass
|
|
|
|
|
|
class TextToSpeechOnUtteranceCompletedListener(Stub):
|
|
"""OnUtteranceCompletedListener — 旧版发音完成监听器"""
|
|
|
|
def onUtteranceCompleted(self, utterance_id: str):
|
|
pass
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# SynthesisRequest
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class SynthesisRequest(Stub):
|
|
"""SynthesisRequest — 合成请求"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._text: str = ""
|
|
self._speech_rate: float = 1.0
|
|
self._voice: Optional["Voice"] = None
|
|
self._calling_uid: int = 0
|
|
self._country: str = ""
|
|
self._language: str = ""
|
|
self._variant: str = ""
|
|
self._params: Dict[str, str] = {}
|
|
|
|
def getText(self) -> str:
|
|
return self._text
|
|
|
|
def getSpeechRate(self) -> float:
|
|
return self._speech_rate
|
|
|
|
def getVoice(self) -> Optional["Voice"]:
|
|
return self._voice
|
|
|
|
def getCallingUid(self) -> int:
|
|
return self._calling_uid
|
|
|
|
def getCountry(self) -> str:
|
|
return self._country
|
|
|
|
def getLanguage(self) -> str:
|
|
return self._language
|
|
|
|
def getVariant(self) -> str:
|
|
return self._variant
|
|
|
|
def getParams(self) -> Dict[str, str]:
|
|
return dict(self._params)
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# SynthesisCallback
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class SynthesisCallback(Stub):
|
|
"""SynthesisCallback — 合成回调"""
|
|
|
|
def start(self, sample_rate_in_hz: int, audio_format: int, channel_count: int) -> int:
|
|
return 0
|
|
|
|
def getMaxBufferSize(self) -> int:
|
|
return 8192
|
|
|
|
def getChannelCount(self) -> int:
|
|
return 1
|
|
|
|
def getAudioFormat(self) -> int:
|
|
return 1
|
|
|
|
def getSampleRate(self) -> int:
|
|
return 22050
|
|
|
|
def availableBuffer(self, buffer_size: int) -> int:
|
|
return buffer_size
|
|
|
|
def audioAvailable(self, audio: bytes) -> int:
|
|
return len(audio)
|
|
|
|
def done(self) -> int:
|
|
return 0
|
|
|
|
def error(self) -> int:
|
|
return -1
|
|
|
|
def hasStarted(self) -> bool:
|
|
return False
|
|
|
|
def hasFinished(self) -> bool:
|
|
return False
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# Voice
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class Voice(Stub):
|
|
"""Voice — 语音"""
|
|
|
|
QUALITY_DEFAULT = -1
|
|
QUALITY_VERY_LOW = 100
|
|
QUALITY_LOW = 200
|
|
QUALITY_NORMAL = 300
|
|
QUALITY_HIGH = 400
|
|
QUALITY_VERY_HIGH = 500
|
|
|
|
LATENCY_DEFAULT = -1
|
|
LATENCY_VERY_LOW = 100
|
|
LATENCY_LOW = 200
|
|
LATENCY_NORMAL = 300
|
|
LATENCY_HIGH = 400
|
|
LATENCY_VERY_HIGH = 500
|
|
|
|
def __init__(self, name: str = "", locale: Any = None,
|
|
quality: int = QUALITY_NORMAL, latency: int = LATENCY_NORMAL,
|
|
requires_network: bool = False,
|
|
features: Optional[List[str]] = None):
|
|
super().__init__()
|
|
self._name = name
|
|
self._locale = locale or _locale.getdefaultlocale()[0]
|
|
self._quality = quality
|
|
self._latency = latency
|
|
self._requires_network = requires_network
|
|
self._features = features or []
|
|
|
|
def getName(self) -> str:
|
|
return self._name
|
|
|
|
def getLocale(self) -> Any:
|
|
return self._locale
|
|
|
|
def getQuality(self) -> int:
|
|
return self._quality
|
|
|
|
def getLatency(self) -> int:
|
|
return self._latency
|
|
|
|
def isNetworkConnectionRequired(self) -> bool:
|
|
return self._requires_network
|
|
|
|
def getFeatures(self) -> List[str]:
|
|
return list(self._features)
|
|
|
|
def toString(self) -> str:
|
|
return f"Voice(name={self._name}, locale={self._locale})"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# TextToSpeech.Engine
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class TextToSpeechEngine(Stub):
|
|
"""TextToSpeech.Engine — TTS 引擎元数据"""
|
|
|
|
ACTION_CHECK_TTS_DATA = "android.speech.tts.engine.CHECK_TTS_DATA"
|
|
ACTION_INSTALL_TTS_DATA = "android.speech.tts.engine.INSTALL_TTS_DATA"
|
|
ACTION_GET_SAMPLE_TEXT = "android.speech.tts.engine.GET_SAMPLE_TEXT"
|
|
|
|
KEY_PARAM_STREAM = "streamType"
|
|
KEY_PARAM_VOLUME = "volume"
|
|
KEY_PARAM_PAN = "pan"
|
|
KEY_PARAM_UTTERANCE_ID = "utteranceId"
|
|
KEY_PARAM_SESSION_ID = "sessionId"
|
|
|
|
KEY_FEATURE_NETWORK_SYNTHESIS = "networkSynthesis"
|
|
KEY_FEATURE_EMBEDDED_SYNTHESIS = "embeddedSynthesis"
|
|
KEY_FEATURE_NOT_INSTALLED = "notInstalled"
|
|
|
|
DEFAULT_STREAM = 3
|
|
DEFAULT_LANG = "eng"
|
|
DEFAULT_COUNTRY = "USA"
|
|
DEFAULT_VARIANT = ""
|
|
|
|
@staticmethod
|
|
def getLocaleLang(lang: str) -> str:
|
|
return lang[:3] if len(lang) >= 3 else lang
|
|
|
|
@staticmethod
|
|
def getLocaleCountry(lang: str) -> str:
|
|
return lang[3:6] if len(lang) >= 6 else ""
|
|
|
|
@staticmethod
|
|
def getLocaleVariant(lang: str) -> str:
|
|
return lang[6:] if len(lang) > 6 else ""
|
|
|
|
|
|
Engine = TextToSpeechEngine
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# TextToSpeech
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class TextToSpeech(Stub):
|
|
"""TextToSpeech — 文字转语音"""
|
|
|
|
SUCCESS = 0
|
|
ERROR = -1
|
|
STOPPED = -2
|
|
|
|
QUEUE_FLUSH = 0
|
|
QUEUE_ADD = 1
|
|
|
|
LANG_COUNTRY_AVAILABLE = 1
|
|
LANG_AVAILABLE = 0
|
|
LANG_COUNTRY_VAR_AVAILABLE = 2
|
|
LANG_MISSING_DATA = -1
|
|
LANG_NOT_SUPPORTED = -2
|
|
|
|
Engine = TextToSpeechEngine
|
|
|
|
def __init__(self, context: Any = None, listener: Optional[TextToSpeechOnInitListener] = None):
|
|
super().__init__()
|
|
self._context = context
|
|
self._listener = listener
|
|
self._utterance_progress_listener: Optional[UtteranceProgressListener] = None
|
|
self._language: str = ""
|
|
self._country: str = ""
|
|
self._pitch: float = 1.0
|
|
self._speech_rate: float = 1.0
|
|
self._voice: Optional[Voice] = None
|
|
self._voices: List[Voice] = [
|
|
Voice("en-us-1", "en_US", Voice.QUALITY_HIGH, Voice.LATENCY_NORMAL),
|
|
Voice("en-gb-1", "en_GB", Voice.QUALITY_NORMAL, Voice.LATENCY_NORMAL),
|
|
Voice("zh-cn-1", "zh_CN", Voice.QUALITY_HIGH, Voice.LATENCY_NORMAL),
|
|
]
|
|
self._engine_package: str = ""
|
|
self._initialized = False
|
|
if listener:
|
|
try:
|
|
listener.onInit(self.SUCCESS)
|
|
except Exception:
|
|
pass
|
|
self._initialized = True
|
|
|
|
def speak(self, text: str, queue_mode: int = QUEUE_FLUSH,
|
|
params: Any = None, utterance_id: str = "") -> int:
|
|
return self.SUCCESS
|
|
|
|
def stop(self) -> int:
|
|
return self.SUCCESS
|
|
|
|
def shutdown(self):
|
|
self._initialized = False
|
|
|
|
def setLanguage(self, language: str, country: str = "", variant: str = "") -> int:
|
|
self._language = language
|
|
self._country = country
|
|
return self.LANG_AVAILABLE
|
|
|
|
def getLanguage(self) -> Tuple[str, str, str]:
|
|
return (self._language, self._country, "")
|
|
|
|
def isLanguageAvailable(self, language: str, country: str = "",
|
|
variant: str = "") -> int:
|
|
return self.LANG_AVAILABLE
|
|
|
|
def setOnUtteranceProgressListener(self, listener: UtteranceProgressListener):
|
|
self._utterance_progress_listener = listener
|
|
|
|
def setOnUtteranceCompletedListener(self, listener: TextToSpeechOnUtteranceCompletedListener):
|
|
pass
|
|
|
|
def setPitch(self, pitch: float) -> int:
|
|
self._pitch = pitch
|
|
return self.SUCCESS
|
|
|
|
def setSpeechRate(self, rate: float) -> int:
|
|
self._speech_rate = rate
|
|
return self.SUCCESS
|
|
|
|
def getVoice(self) -> Optional[Voice]:
|
|
return self._voice
|
|
|
|
def setVoice(self, voice: Voice) -> int:
|
|
self._voice = voice
|
|
return self.SUCCESS
|
|
|
|
def getVoices(self) -> Set[Voice]:
|
|
return set(self._voices)
|
|
|
|
def addSpeech(self, text: str, filename: str) -> int:
|
|
return self.SUCCESS
|
|
|
|
def addEyes(self, text: str, filename: str) -> int:
|
|
return self.SUCCESS
|
|
|
|
def synthesizeToFile(self, text: str, params: Any, filename: str) -> int:
|
|
return self.SUCCESS
|
|
|
|
def setEngineByPackageName(self, package_name: str):
|
|
self._engine_package = package_name
|
|
|
|
def getDefaultEngine(self) -> Optional[str]:
|
|
return "com.android.tts"
|
|
|
|
def getEngines(self) -> List["EngineInfo"]:
|
|
return []
|
|
|
|
def getFeatures(self, locale: Any) -> Set[str]:
|
|
return set()
|
|
|
|
def areDefaultsEnforced(self) -> bool:
|
|
return False
|
|
|
|
def setQueueMode(self, queue_mode: int):
|
|
pass
|
|
|
|
def isSpeaking(self) -> bool:
|
|
return False
|
|
|
|
def toString(self) -> str:
|
|
return f"TextToSpeech(lang={self._language})"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# EngineInfo
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class EngineInfo(Stub):
|
|
"""EngineInfo — 引擎信息"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.name: str = ""
|
|
self.label: str = ""
|
|
self.icon: int = 0
|
|
self.priority: int = 0
|
|
self.system: bool = False
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# __all__
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
__all__ = [
|
|
"TextToSpeech",
|
|
"TextToSpeechEngine",
|
|
"TextToSpeechOnInitListener",
|
|
"TextToSpeechOnUtteranceCompletedListener",
|
|
"SynthesisCallback",
|
|
"SynthesisRequest",
|
|
"Voice",
|
|
"UtteranceProgressListener",
|
|
"Engine",
|
|
"EngineInfo",
|
|
] |