def preInstalledFirstRun(): if not SystemQueries.isPreInstalled( ): # Do as little as possible if there is no pre-install if SystemQueries.wasPreInstalled(): module_logger.info('PRE INSTALL: REMOVED') # Set version to 0.0.0 so normal first run will execute and fix the # keymap Settings.setSetting('version', '0.0.0') enabler.markPreOrPost() # Update the install status return False lastVersion = Settings.getSetting('version') if not enabler.isPostInstalled() and SystemQueries.wasPostInstalled(): module_logger.info('POST INSTALL: UN-INSTALLED OR REMOVED') # Add-on was removed. Assume un-installed and treat this as a # pre-installed first run to disable the addon elif lastVersion: enabler.markPreOrPost() # Update the install status return False # Set version to 0.0.0 so normal first run will execute on first enable Settings.setSetting('version', '0.0.0') module_logger.info('PRE-INSTALLED FIRST RUN') module_logger.info('Installing basic keymap') # Install keymap with just F12 enabling included from utils import keymapeditor keymapeditor.installBasicKeymap() module_logger.info('Pre-installed - DISABLING') enabler.disableAddon() return True
def available(): try: subprocess.call(['flite', '--help'], stdout=(open(os.path.devnull, 'w')), universal_newlines=True, stderr=subprocess.STDOUT) except (OSError, IOError): return SystemQueries.isATV2() and SystemQueries.commandIsAvailable( 'flite') return True #class FliteTTSBackend(TTSBackendBase): # provider = 'Flite':q:q # def __init__(self): # import ctypes # self.flite = ctypes.CDLL('libflite.so.1',mode=ctypes.RTLD_GLOBAL) # flite_usenglish = ctypes.CDLL('libflite_usenglish.so.1',mode=ctypes.RTLD_GLOBAL) #analysis:ignore # flite_cmulex = ctypes.CDLL('libflite_cmulex.so.1',mode=ctypes.RTLD_GLOBAL) #analysis:ignore # flite_cmu_us_slt = ctypes.CDLL('libflite_cmu_us_slt.so.1') # self.flite.flite_init() # self.voice = flite_cmu_us_slt.register_cmu_us_slt() # # def say(self,text,interrupt=False): # if not text: return # self.flite.flite_text_to_speech(text,self.voice,'play') # # # @staticmethod # def available(): # try: # import ctypes # ctypes.CDLL('libflite.so.1') # except (OSError, IOError): # return False # return True #class FliteTTSBackend(TTSBackendBase): # provider = 'Flite' # # def say(self,text,interrupt=False): # if not text: return # voice = self.currentVoice() or 'kal16' # subprocess.call(['flite', '-voice', voice, '-t', text], universal_newlines=True) # # def voices(self): # return subprocess.check_output(['flite','-lv'], universal_newlines=True).split(': ',1)[-1].strip().split(' ') # # @staticmethod # def available(): # try: # subprocess.call(['flite', '--help'], stdout=(open(os.path.devnull, 'w')), # stderr=subprocess.STDOUT, universal_newlines=True) # except (OSError, IOError): # return False # return True
def getBackendFallback(): if SystemQueries.isATV2(): return FliteTTSBackend elif SystemQueries.isWindows(): return SAPITTSBackend elif SystemQueries.isOSX(): return OSXSayTTSBackend elif SystemQueries.isOpenElec(): return ESpeakTTSBackend for b in backendsByPriority: if b._available(): return b return None
def buildKeymap(defaults=False): # TODO: Build XML with ElementTree? xml = None with open(_keymapSource(), 'r') as f: xml = f.read() if not xml: return if defaults: defs = {} else: defs = loadCustomKeymapDefs() for action, default in ACTIONS: key = defs.get('key.{0}'.format(action)) if key: xml = xml.replace('<{0}>'.format(action), '<key id="{0}">'.format(key)).replace( '</{0}>'.format(action), '</key>') else: xml = xml.replace('<{0}>'.format(action), '<{0}>'.format(default)).replace( '</{0}>'.format(action), '</{0}>'.format(default.split(' ', 1)[0])) xml = xml.format( SPECIAL=SystemQueries.isPreInstalled() and 'xbmc' or 'home') saveKeymapXML(xml)
def available(): return sys.platform == 'darwin' and not SystemQueries.isATV2() #on isVoiceOverRunning() # set isRunning to false # tell application "System Events" # set isRunning to (name of processes) contains "VoiceOver" # end tell # return isRunning #end isVoiceOverRunning # #on isVoiceOverRunningWithAppleScript() # if isVoiceOverRunning() then # set isRunningWithAppleScript to true # # -- is AppleScript enabled on VoiceOver -- # tell application "VoiceOver" # try # set x to bounds of vo cursor # on error # set isRunningWithAppleScript to false # end try # end tell # return isRunningWithAppleScript # end if # return false #end isVoiceOverRunningWithAppleScript
def update(self): self.voice = self.setting('voice') self.rate = self.setting('speed') self.useAOSS = self.setting('use_aoss') if self.useAOSS and not SystemQueries.commandIsAvailable('aoss'): self._logger.info( 'Cepstral: Use aoss is enabled, but aoss is not found. Disabling.' ) self.useAOSS = False volume = self.setting('volume') self.volume = int(round( 100 * (10**(volume / 20.0)))) #convert from dB to percent pitch = self.setting('pitch') self.pitch = 0.4 + ( (pitch + 6) / 20.0) * 2 #Convert from (-6 to +14) value to (0.4 to 2.4)
def isSupportedOnPlatform(): return (SystemQueries.isLinux() or SystemQueries.isWindows() or SystemQueries.isOSX())
def available(): return SystemQueries.isWindows()
def isSupportedOnPlatform(): return SystemQueries.isWindows()
def isSupportedOnPlatform(): return SystemQueries.isLinux()
class FliteTTSBackend(base.SimpleTTSBackendBase): provider = Backends.FLITE_ID displayName = 'Flite' speedConstraints = (20, 100, 200, True) settings = { Settings.PIPE: False, Settings.PLAYER: Players.INTERNAL, Settings.SPEED: 100, Settings.VOICE: 'kal16', Settings.VOLUME: 0 } onATV2 = SystemQueries.isATV2() def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._logger = module_logger.getChild( self.__class__.__name__) # type: LazyLogger self.process = None def init(self): self.process = None self.update() @staticmethod def isSupportedOnPlatform(): return SystemQueries.isLinux() @staticmethod def isInstalled(): installed = False if FliteTTSBackend.isSupportedOnPlatform(): installed = True return installed def runCommand(self, text_to_voice, dummy): wave_file, exists = self.get_path_to_voice_file(text_to_voice, use_cache=False) if self.onATV2: os.system('flite -t "{0}" -o "{1}"'.format(text_to_voice, wave_file)) else: voice = type(self).getVoice() subprocess.call([ 'flite', '-voice', voice, '-t', text_to_voice, '-o', wave_file ], universal_newlines=True) return True def runCommandAndSpeak(self, text_to_voice): voice = type(self).getVoice() self.process = subprocess.Popen( ['flite', '-voice', voice, '-t', text_to_voice], universal_newlines=True) while self.process.poll() is None and self.active: xbmc.sleep(10) def update(self): pass def getMode(self): if not self.onATV2 and self.setting('output_via_flite'): return base.SimpleTTSBackendBase.ENGINESPEAK else: return base.SimpleTTSBackendBase.WAVOUT def stop(self): if not self.process: return try: self.process.terminate() except: pass @classmethod def settingList(cls, setting, *args): if cls.onATV2: return None elif setting == Settings.PLAYER: # Get list of player ids. Id is same as is stored in settings.xml players = cls.get_players(include_builtin=False) default_player = cls.get_setting_default(Settings.PLAYER) return players, default_player elif setting == 'voice': return [(v, v) for v in subprocess.check_output( ['flite', '-lv'], universal_newlines=True).split( ': ', 1)[-1].strip().split(' ')] @staticmethod def available(): try: subprocess.call(['flite', '--help'], stdout=(open(os.path.devnull, 'w')), universal_newlines=True, stderr=subprocess.STDOUT) except (OSError, IOError): return SystemQueries.isATV2() and SystemQueries.commandIsAvailable( 'flite') return True #class FliteTTSBackend(TTSBackendBase): # provider = 'Flite':q:q # def __init__(self): # import ctypes # self.flite = ctypes.CDLL('libflite.so.1',mode=ctypes.RTLD_GLOBAL) # flite_usenglish = ctypes.CDLL('libflite_usenglish.so.1',mode=ctypes.RTLD_GLOBAL) #analysis:ignore # flite_cmulex = ctypes.CDLL('libflite_cmulex.so.1',mode=ctypes.RTLD_GLOBAL) #analysis:ignore # flite_cmu_us_slt = ctypes.CDLL('libflite_cmu_us_slt.so.1') # self.flite.flite_init() # self.voice = flite_cmu_us_slt.register_cmu_us_slt() # # def say(self,text,interrupt=False): # if not text: return # self.flite.flite_text_to_speech(text,self.voice,'play') # # # @staticmethod # def available(): # try: # import ctypes # ctypes.CDLL('libflite.so.1') # except (OSError, IOError): # return False # return True #class FliteTTSBackend(TTSBackendBase): # provider = 'Flite' # # def say(self,text,interrupt=False): # if not text: return # voice = self.currentVoice() or 'kal16' # subprocess.call(['flite', '-voice', voice, '-t', text], universal_newlines=True) # # def voices(self): # return subprocess.check_output(['flite','-lv'], universal_newlines=True).split(': ',1)[-1].strip().split(' ') # # @staticmethod # def available(): # try: # subprocess.call(['flite', '--help'], stdout=(open(os.path.devnull, 'w')), # stderr=subprocess.STDOUT, universal_newlines=True) # except (OSError, IOError): # return False # return True
def available(): return sys.platform == 'darwin' and not SystemQueries.isATV2()
def isSupportedOnPlatform(): return SystemQueries.isOSX()
class FestivalTTSBackend(SimpleTTSBackendBase): provider = Backends.FESTIVAL_ID displayName = 'Festival' canStreamWav = SystemQueries.commandIsAvailable('mpg123') speedConstraints = (-16, 0, 12, True) pitchConstraints = (50, 105, 500, True) volumeConstraints = (-12, 0, 12, True) player_handler_class: Type[BasePlayerHandler] = WavAudioPlayerHandler settings = { Settings.PIPE: False, Settings.PITCH: 105, Settings.PLAYER: Players.MPLAYER, Settings.SPEED: 0, # Undoubtedly settable, also supported by some players Settings.VOICE: '', Settings.VOLUME: 0 } _logger = None def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if type(self)._logger is None: type(self)._logger = module_logger.getChild(self.__class__.__name__) # type: LazyLogger self.festivalProcess = None def init(self): self.festivalProcess = None self.update() @staticmethod def isSupportedOnPlatform(): return SystemQueries.isLinux() @staticmethod def isInstalled(): installed = False if FestivalTTSBackend.isSupportedOnPlatform(): installed = True return installed def getMode(self): player = type(self).getSetting(Settings.PLAYER) if type(self).getSetting(Settings.PIPE): return SimpleTTSBackendBase.PIPE else: return SimpleTTSBackendBase.WAVOUT def runCommand(self, text_to_voice, dummy): wave_file, exists = self.get_path_to_voice_file(text_to_voice, use_cache=False) wave_pipe = None return self.generate_speech(text_to_voice, wave_file) def runCommandAndPipe(self, text_to_voice): wave_file, exists = self.get_path_to_voice_file(text_to_voice, use_cache=False) wave_pipe = None if self.generate_speech(text_to_voice, wave_file): wave_pipe = io.BytesIO(wave_file) return wave_pipe def generate_speech(self, text_to_voice: str, wave_file: str): # In addition to festival, see the text2wave command if not text_to_voice: return None text_to_voice = text_to_voice.strip() if len(text_to_voice) == 0: return None volume = type(self).getVolume() volume = 1 * (10 ** (volume / 20.0)) # convert from dB to percent/100 voice = type(self).getVoice() voice = voice and '(voice_{0})'.format(voice) or '' speed = type(self).getSpeed() durationMultiplier = 1.8 - (((speed + 16) / 28.0) * 1.4) # # Convert from (-16 to +12) value to (1.8 to 0.4) durMult = durationMultiplier and "(Parameter.set 'Duration_Stretch {0})".format( durationMultiplier) or '' pitch = type(self).getPitch() pitch = pitch != 105 and "(require 'prosody-param)(set-pitch {0})".format( pitch) or '' # Assumption is to only adjust speech settings in engine, not player player_volume = 0.0 # '{:.2f}'.format(0.0) # Don't alter volume (db) player_speed = 100.0 # '{:.2f}'.format(100.0) # Don't alter speed/tempo (percent) # Changing pitch without impacting tempo (speed) is player_pitch = 100.0 # '{:.2f}'.format(100.0) # Percent # not easy. One suggestion is to use lib LADSPA self.setPlayer(type(self).getSetting(Settings.PLAYER)) self.player_handler.setVolume(player_volume) # In db -12 .. 12 self.player_handler.setSpeed(player_speed) self.player_handler.setPitch(player_pitch) self.festivalProcess = subprocess.Popen(['festival', '--pipe'], stdin=subprocess.PIPE, universal_newlines=True) text_to_voice = text_to_voice.replace('"', '\\"').strip() out = '(audio_mode \'async){0}{1}{2}(utt.save.wave (utt.wave.rescale (SynthText ' \ '' \ '"{3}") {4:.2f} nil)"{5}")\n'.format( voice, durMult, pitch, text_to_voice, volume, wave_file) self.festivalProcess.communicate(out) return True def stop(self): try: self.festivalProcess.terminate() except: return @classmethod def settingList(cls, setting, *args): if setting == Settings.LANGUAGE: return [], None elif setting == Settings.VOICE: p = subprocess.Popen(['festival', '-i'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) d = p.communicate('(voice.list)') voices = list( map(str.strip, d[0].rsplit('> (', 1)[-1].rsplit(')', 1)[0].split(' '))) if voices: return [(v, v) for v in voices] # name, id elif setting == Settings.PLAYER: # Get list of player ids. Id is same as is stored in settings.xml players = cls.get_players(include_builtin=False) default_player = cls.get_setting_default(Settings.PLAYER) return players, default_player return None @staticmethod def available(): try: subprocess.call(['festival', '--help'], stdout=(open(os.path.devnull, 'w')), stderr=subprocess.STDOUT, universal_newlines=True) except (OSError, IOError): return False return True
class GoogleTTSBackend(base.SimpleTTSBackendBase): provider = 'Google' displayName = 'Google' # ttsURL = 'http://translate.google.com/translate_tts?client=t&tl={0}&q={1}' ttsURL = 'https://translate.google.com/translate_tts?&q={1}&tl={0}&client=tw-ob' canStreamWav = SystemQueries.commandIsAvailable('mpg123') playerClass = audio.MP3AudioPlayerHandler settings = { 'language': 'en', 'pipe': False, 'player': 'mpg123', 'volume': 0 } def init(self): self.process = None self.update() @staticmethod def isSupportedOnPlatform(): return (SystemQueries.isLinux() or SystemQueries.isWindows() or SystemQueries.isOSX()) @staticmethod def isInstalled(): installed = False if GoogleTTSBackend.isSupportedOnPlatform(): installed = True return installed def threadedSay(self, text): if not text: return sections = textwrap.wrap(text, 100) if self.mode == self.PIPE: for text in sections: source = self.runCommandAndPipe(text) if not source: continue self.player_handler.pipeAudio(source) else: for text in sections: outFile = self.player_handler.getOutFile(text, use_cache=False) if not self.runCommand(text, outFile): return self.player_handler.play() def runCommand(self, text, outFile): url = self.ttsURL.format(self.language, urllib.parse.quote(text)) LazyLogger.debug_verbose('Google url: ' + url) # # local IFS = +; /usr/bin/mplayer -ao alsa -really -quiet -noconsolecontrols "http://translate.google.com/translate_tts?ie=UTF-8&client=tw-ob&q=$*&tl=en"; headers = { 'Referer': 'http://translate.google.com', 'User-Agent': 'stagefright/1.2 (Linux;Android 5.0)' } req = urllib.request.Request(url, headers=headers) try: resp = urllib.request.urlopen(req) except: OldLogger.ERROR('Failed to open Google TTS URL', hide_tb=True) return False with open(outFile, 'wb') as out: shutil.copyfileobj(resp, out) return True def runCommandAndPipe(self, text): url = self.ttsURL.format(self.language, urllib.parse.quote(text)) LazyLogger.debug_verbose('Google url: ' + url) #req = urllib.request.Request(url) #, headers={ 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36' }) headers = { 'Referer': 'http://translate.google.com/', 'User-Agent': 'stagefright/1.2 (Linux;Android 5.0)' } req = urllib.request.Request(url, headers=headers) try: resp = urllib.request.urlopen(req) LazyLogger.debug_verbose('url: ' + req.get_full_url()) LazyLogger.debug_verbose('headers: ' + str(req.header_items())) except: OldLogger.ERROR('Failed to open Google TTS URL', hide_tb=True) return None return resp def getWavStream(self, text): wav_path = os.path.join(utils.getTmpfs(), 'speech.wav') mp3_path = os.path.join(utils.getTmpfs(), 'speech.mp3') self.runCommand(text, mp3_path) self.process = subprocess.Popen(['mpg123', '-w', wav_path, mp3_path], stdout=(open(os.path.devnull, 'w')), stderr=subprocess.STDOUT, universal_newlines=True) while self.process.poll() is None and self.active: xbmc.sleep(10) os.remove(mp3_path) return open(wav_path, 'rb') def update(self): self.language = self.setting('language') self.setPlayer(self.setting('player')) self.setVolume(self.setting('volume')) self.setMode(self.getMode()) def getMode(self): if self.setting('pipe'): return base.SimpleTTSBackendBase.PIPE else: return base.SimpleTTSBackendBase.WAVOUT def stop(self): if not self.process: return try: self.process.terminate() except: pass @classmethod def settingList(cls, setting, *args): if setting == 'language': return LANGUAGES return None @staticmethod def available(): return audio.MP3AudioPlayerHandler.canPlay()
def isSupportedOnPlatform(cls): return SystemQueries.isLinux() or SystemQueries.isAndroid()