175 lines
6.3 KiB
Python
175 lines
6.3 KiB
Python
|
#
|
||
|
# Copyright 2018-2023 Picovoice Inc.
|
||
|
#
|
||
|
# You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
|
||
|
# file accompanying this source.
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||
|
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||
|
# specific language governing permissions and limitations under the License.
|
||
|
#
|
||
|
|
||
|
import argparse
|
||
|
import os
|
||
|
import struct
|
||
|
import wave
|
||
|
from datetime import datetime
|
||
|
|
||
|
import pvporcupine
|
||
|
from pvrecorder import PvRecorder
|
||
|
|
||
|
ACCESS_KEY = 'hqNqw85hkJRXVjEevwpkreB8n8so3w9JPQ27qnCR5qTH8a3+XnkZTA=='
|
||
|
|
||
|
# pvporcupine.KEYWORDS
|
||
|
# print(f"Keywords: {pvporcupine.KEYWORDS}")
|
||
|
|
||
|
def main():
|
||
|
parser = argparse.ArgumentParser()
|
||
|
|
||
|
parser.add_argument(
|
||
|
'--access_key',
|
||
|
default=ACCESS_KEY,
|
||
|
help='AccessKey obtained from Picovoice Console (https://console.picovoice.ai/)')
|
||
|
|
||
|
parser.add_argument(
|
||
|
'--keywords',
|
||
|
nargs='+',
|
||
|
help='List of default keywords for detection. Available keywords: %s' % ', '.join(
|
||
|
'%s' % w for w in sorted(pvporcupine.KEYWORDS)),
|
||
|
# choices=sorted(pvporcupine.KEYWORDS),
|
||
|
# default=['pico clock', 'picovoice', 'ok google', 'americano', 'hey barista', 'alexa', 'grasshopper', 'blueberry', 'hey siri', 'jarvis', 'porcupine', 'terminator', 'grapefruit', 'computer', 'hey google', 'bumblebee'],
|
||
|
default=['可莉可莉'],
|
||
|
metavar='')
|
||
|
|
||
|
parser.add_argument(
|
||
|
'--keyword_paths',
|
||
|
default=[r"picovoice_models/可莉可莉_zh_raspberry-pi_v3_0_0.ppn"],
|
||
|
nargs='+',
|
||
|
help="Absolute paths to keyword model files. If not set it will be populated from `--keywords` argument")
|
||
|
|
||
|
parser.add_argument(
|
||
|
'--library_path',
|
||
|
help='Absolute path to dynamic library. Default: using the library provided by `pvporcupine`')
|
||
|
|
||
|
parser.add_argument(
|
||
|
'--model_path',
|
||
|
default=r"picovoice_models/porcupine_params_zh.pv",
|
||
|
help='Absolute path to the file containing model parameters. '
|
||
|
'Default: using the library provided by `pvporcupine`')
|
||
|
|
||
|
parser.add_argument(
|
||
|
'--sensitivities',
|
||
|
nargs='+',
|
||
|
help="Sensitivities for detecting keywords. Each value should be a number within [0, 1]. A higher "
|
||
|
"sensitivity results in fewer misses at the cost of increasing the false alarm rate. If not set 0.5 "
|
||
|
"will be used.",
|
||
|
type=float,
|
||
|
default=None)
|
||
|
|
||
|
parser.add_argument('--audio_device_index', help='Index of input audio device.', type=int, default=-1)
|
||
|
|
||
|
parser.add_argument('--output_path', help='Absolute path to recorded audio for debugging.', default=None)
|
||
|
|
||
|
parser.add_argument('--show_audio_devices', action='store_true')
|
||
|
|
||
|
args = parser.parse_args()
|
||
|
|
||
|
if args.show_audio_devices:
|
||
|
for i, device in enumerate(PvRecorder.get_available_devices()):
|
||
|
print('Device %d: %s' % (i, device))
|
||
|
return
|
||
|
|
||
|
if args.keyword_paths is None:
|
||
|
if args.keywords is None:
|
||
|
raise ValueError("Either `--keywords` or `--keyword_paths` must be set.")
|
||
|
|
||
|
keyword_paths = [pvporcupine.KEYWORD_PATHS[x] for x in args.keywords]
|
||
|
else:
|
||
|
keyword_paths = args.keyword_paths
|
||
|
# TODO
|
||
|
for i, kw_path in enumerate(keyword_paths):
|
||
|
if os.path.dirname(__file__) not in kw_path:
|
||
|
keyword_paths[i] = os.path.join(os.path.abspath(os.path.dirname(__file__)), kw_path)
|
||
|
args.model_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), args.model_path)
|
||
|
print(f"keyword_paths: {keyword_paths}")
|
||
|
print(f"model_path: {args.model_path}")
|
||
|
|
||
|
if args.sensitivities is None:
|
||
|
args.sensitivities = [0.9] * len(keyword_paths)
|
||
|
|
||
|
if len(keyword_paths) != len(args.sensitivities):
|
||
|
raise ValueError('Number of keywords does not match the number of sensitivities.')
|
||
|
|
||
|
try:
|
||
|
porcupine = pvporcupine.create(
|
||
|
access_key=args.access_key,
|
||
|
library_path=args.library_path,
|
||
|
model_path=args.model_path,
|
||
|
keyword_paths=keyword_paths,
|
||
|
sensitivities=args.sensitivities)
|
||
|
except pvporcupine.PorcupineInvalidArgumentError as e:
|
||
|
print("One or more arguments provided to Porcupine is invalid: ", args)
|
||
|
print(e)
|
||
|
raise e
|
||
|
except pvporcupine.PorcupineActivationError as e:
|
||
|
print("AccessKey activation error")
|
||
|
raise e
|
||
|
except pvporcupine.PorcupineActivationLimitError as e:
|
||
|
print("AccessKey '%s' has reached it's temporary device limit" % args.access_key)
|
||
|
raise e
|
||
|
except pvporcupine.PorcupineActivationRefusedError as e:
|
||
|
print("AccessKey '%s' refused" % args.access_key)
|
||
|
raise e
|
||
|
except pvporcupine.PorcupineActivationThrottledError as e:
|
||
|
print("AccessKey '%s' has been throttled" % args.access_key)
|
||
|
raise e
|
||
|
except pvporcupine.PorcupineError as e:
|
||
|
print("Failed to initialize Porcupine")
|
||
|
raise e
|
||
|
|
||
|
keywords = list()
|
||
|
for x in keyword_paths:
|
||
|
keyword_phrase_part = os.path.basename(x).replace('.ppn', '').split('_')
|
||
|
if len(keyword_phrase_part) > 6:
|
||
|
keywords.append(' '.join(keyword_phrase_part[0:-6]))
|
||
|
else:
|
||
|
keywords.append(keyword_phrase_part[0])
|
||
|
|
||
|
print('Porcupine version: %s' % porcupine.version)
|
||
|
|
||
|
recorder = PvRecorder(
|
||
|
frame_length=porcupine.frame_length,
|
||
|
device_index=args.audio_device_index)
|
||
|
recorder.start()
|
||
|
|
||
|
wav_file = None
|
||
|
if args.output_path is not None:
|
||
|
wav_file = wave.open(args.output_path, "w")
|
||
|
wav_file.setnchannels(1)
|
||
|
wav_file.setsampwidth(2)
|
||
|
wav_file.setframerate(16000)
|
||
|
|
||
|
print('Listening ... (press Ctrl+C to exit)')
|
||
|
|
||
|
try:
|
||
|
while True:
|
||
|
pcm = recorder.read()
|
||
|
result = porcupine.process(pcm)
|
||
|
|
||
|
if wav_file is not None:
|
||
|
wav_file.writeframes(struct.pack("h" * len(pcm), *pcm))
|
||
|
|
||
|
if result >= 0:
|
||
|
print('[%s] Detected %s' % (str(datetime.now()), keywords[result]))
|
||
|
except KeyboardInterrupt:
|
||
|
print('Stopping ...')
|
||
|
finally:
|
||
|
recorder.delete()
|
||
|
porcupine.delete()
|
||
|
if wav_file is not None:
|
||
|
wav_file.close()
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
main()
|