Package: command-lifecycle
Voice Command LifecyclePython library to manage the life-cycle of voice commands. Useful working with Alexa Voice Service.Installationpipinstallcommand_lifecycleWakeword detectorA wakeword is a specific word that triggers the code to spring into action. It allows your code to be idle until the specific word is uttered.The audio lifecycle usessnowboyto determine if the wakeword was uttered. The library will need to be installed first.Once you have compiled snowboy, copy the compiledsnowboyfolder to the top level of you project. By default, the folder structure should be:.
├── ...
├── snowboy
|   ├── snowboy-detect-swig.cc
|   ├── snowboydetect.py
|   └── resources
|       ├── alexa.umdl
|       └── common.res
└── ...If the default structure does not suit your needs can customize thewakeword detector.UsageYou should send a steady stream of audio to to the lifecycle by repetitively callinglifecycle.extend_audio(some_audio_bytes). If the wakeword such as "Alexa" (default), or "ok, Google" was uttered thenhandle_command_startedis called.handle_command_finisedis then called once the command audio that followed the wakeword has finished.Microphone audioimportpyaudioimportcommand_lifecycleclassAudioLifecycle(command_lifecycle.BaseAudioLifecycle):defhandle_command_started(self,wakeword_name):super().handle_command_started(wakeword_name)print(f'The audio contained{wakeword_name}!')defhandle_command_finised(self):super().handle_command_finised()print('The command has finished')lifecycle=AudioLifecycle()p=pyaudio.PyAudio()stream=p.open(format=pyaudio.paInt16,channels=1,rate=16000,input=True)try:print('listening. Start by saying "Alexa". Press CTRL + C to exit.')whileTrue:lifecycle.extend_audio(stream.read(1024))finally:stream.stop_stream()stream.close()p.terminate()File audioimportwaveimportcommand_lifecycleclassAudioLifecycle(command_lifecycle.BaseAudioLifecycle):defhandle_command_started(self,wakeword_name):super().handle_command_started(wakeword_name)print(f'The audio contained{wakeword_name}!')lifecycle=AudioLifecycle()withwave.open('./tests/resources/alexa_what_time_is_it.wav','rb')asf:whilef.tell()<f.getnframes():lifecycle.extend_audio(f.readframes(1024))print('The command has finished')Usage with Alexacommand_lifecycleis useful for interacting with voice services. The lifecycle waits until a wakeword was issued and then start streaming the audio command to the voice service (usingAlexa Voice Service Client), then do something useful with the response:fromavs_client.avs_client.clientimportAlexaVoiceServiceClientimportpyaudioimportcommand_lifecycleclassAudioLifecycle(command_lifecycle.BaseAudioLifecycle):alexa_client=AlexaVoiceServiceClient(client_id='my-client-id'secret='my-secret',refresh_token='my-refresh-token',)def__init__(self):self.alexa_client.connect()super().__init__()defhandle_command_started(self,wakeword_name):super().handle_command_started(wakeword_name)audio_file=command_lifecycle.to_audio_file()fordirectiveinself.alexa_client.send_audio_file(audio_file):# do something with the AVS audio response, e.g., play it.lifecycle=AudioLifecycle()p=pyaudio.PyAudio()stream=p.open(format=pyaudio.paInt16,channels=1,rate=16000,input=True)try:print('listening. Start by saying "Alexa". Press CTRL + C to exit.')whileTrue:lifecycle.extend_audio(stream.read(1024))finally:stream.stop_stream()stream.close()p.terminate()CustomizationWakewordThe default wakeword is "Alexa". This can be changed by sub-classingcommand_lifecycle.wakeword.SnowboyWakewordDetector:fromcommand_lifecycleimportwakewordclassMySnowboyWakewordDetector(wakeword.SnowboyWakewordDetector):decoder_models=[{'name':'CUSTOM','model':b'path/to/custom-wakeword-model.umdl''sensitivity':b'0.5',}]classAudioLifecycle(lifecycle.BaseAudioLifecycle):audio_detector_class=MySnowboyWakewordDetectordefhandle_command_started(self,wakeword_name):super().handle_command_started(wakeword_name)print(f'The audio contained the{wakeword_name}!')defhandle_command_finised(self):super().handle_command_finised()print('The command has finished')lifecycle=AudioLifecycle()# now load the audio into lifecycleSee theSnowboy docsfor steps on creating custom wakeword models.Multiple WakewordsTriggering different behaviour for different wakeword may be desirable. To do this use multiple items indecoder_models:fromcommand_lifecycleimportwakewordclassMyMultipleWakewordDetector(wakeword.SnowboyWakewordDetector):GOOGLE='GOOGLE'decoder_models=wakeword.SnowboyWakewordDetector.decoder_models+[{'name':GOOGLE,'model':b'path/to/okgoogle.umdl','sensitivity':b'0.5',}]classAudioLifecycle(lifecycle.BaseAudioLifecycle):audio_detector_class=MyMultipleWakewordDetectordefhandle_command_started(self,wakeword_name):ifwakeword_name==self.audio_detector.ALEXA:print('Alexa standing by')elifwakeword_name==self.audio_detector.GOOGLE:print('Google at your service')super().handle_command_started(wakeword_name)You can download wakewords fromhere.Wakeword detectorSnowboy is the default wakeword detector. Other wakeword detectors can be used by sub-classingcommand_lifecycle.wakeword.BaseWakewordDetectorand settingwakeword_detector_classto your custom class:importwavefromcommand_lifecycleimportlifecycle,wakewordclassMyCustomWakewordDetector(wakeword.BaseWakewordDetector):import_error_message='Cannot import wakeword library!'wakeword_library_import_path='path.to.wakeword.Library'defwas_wakeword_uttered(self,buffer):# use the library to check if the audio in the buffer has the wakeword.# not `buffer.get()` returns the audio inside the buffer....defis_talking(self,buffer):# use the library to check if the audio in the buffer has audible words# not `buffer.get()` returns the audio inside the buffer....classAudioLifecycle(lifecycle.BaseAudioLifecycle):audio_detector_class=MyCustomWakewordDetector...lifecycle=AudioLifecycle()# now load the audio into lifecycleHandling input dataThree input data formats are supported:ConverterNotesNoOperationConverterdefaultInput data is already wav bytes.WavIntSamplestoWavConverterInput data is list of integers.WebAudioToWavConverterInput data is list of floats generated by a web browser.Customize this by setting the lifecycle'saudio_converter_class:from command_lifecycle.helpers import WebAudioToWavConverter

class AudioLifecycle(lifecycle.BaseAudioLifecycle):
    audio_converter_class = WebAudioToWavConverterExpecting slower or faster commandsThe person giving the audio command might take a moment to collect their thoughts before finishing the command. This silence could be interpreted as the command ending, resulting inhandle_command_finisedbeing called prematurely.To avoid this the lifecycle tolerates some silence in the command before the lifecycle timesout the command. This silence can happen at the beginning or middle of the command. Note a side-effect of this is there will be a pause between when the person has stopped talking and whenhandle_command_finisedis called.To change this default behaviourtimeout_manager_classcan be changed. The available timeout managers are:Timeout managerNotesShortTimeoutManagerAllows one second of silence.MediumTimeoutManagerdefaultAllows 2 seconds of silence.LongTimeoutManagerAllows three seconds of silence.To make a custom timeout manager create a subclass ofcommand_lifecycle.timeout.BaseTimeoutManager:importwavefromcommand_lifecycleimporttimeout,wakewordclassMyCustomTimeoutManager(timeout.BaseTimeoutManager):allowed_silent_seconds=4classAudioLifecycle(lifecycle.BaseAudioLifecycle):timeout_manager_class=MyCustomTimeoutManagerUnit testTo run the unit tests, call the following commands:maketest_requirements
maketestVersioningWe useSemVerfor versioning. For the versions available, see thePyPI.Other projectsThis library is used byalexa-browser-client, which allows you to talk to Alexa from your browser.