Ejemplo n.º 1
0
async def main() -> None:
    global mic_stdin_running, mic_stdin_thread

    # Parse command-line arguments
    parser = argparse.ArgumentParser(description="Rhasspy")
    parser.add_argument("--profile",
                        "-p",
                        required=True,
                        type=str,
                        help="Name of profile to use")
    parser.add_argument(
        "--system-profiles",
        type=str,
        help="Directory with base profile files (read only)",
        default=os.path.join(os.getcwd(), "profiles"),
    )
    parser.add_argument(
        "--user-profiles",
        type=str,
        help="Directory with user profile files (read/write)",
        default=os.path.expanduser("~/.config/rhasspy/profiles"),
    )
    parser.add_argument(
        "--set",
        "-s",
        nargs=2,
        action="append",
        help="Set a profile setting value",
        default=[],
    )
    parser.add_argument("--debug",
                        action="store_true",
                        help="Print DEBUG log to console")
    parser.add_argument(
        "--no-check",
        action="store_true",
        help="Don't check profile for necessary files",
    )

    sub_parsers = parser.add_subparsers(dest="command")
    sub_parsers.required = True

    # info
    info_parser = sub_parsers.add_parser("info", help="Profile information")
    info_parser.add_argument("--defaults",
                             action="store_true",
                             help="Only print default settings")

    sentences_parser = sub_parsers.add_parser(
        "sentences", help="Print profile sentences.ini")

    # validate
    # validate_parser = sub_parsers.add_parser(
    #     "validate", help="Validate profile against schema"
    # )

    # wav2text
    wav2text_parser = sub_parsers.add_parser(
        "wav2text", help="WAV file to text transcription")
    wav2text_parser.add_argument("wav_files",
                                 nargs="*",
                                 help="Paths to WAV files")

    # text2intent
    text2intent_parser = sub_parsers.add_parser("text2intent",
                                                help="Text parsed to intent")
    text2intent_parser.add_argument("sentences",
                                    nargs="*",
                                    help="Sentences to parse")
    text2intent_parser.add_argument("--handle",
                                    action="store_true",
                                    help="Pass result to intent handler")

    # wav2intent
    wav2intent_parser = sub_parsers.add_parser(
        "wav2intent", help="WAV file to parsed intent")
    wav2intent_parser.add_argument("wav_files",
                                   nargs="*",
                                   help="Paths to WAV files")
    wav2intent_parser.add_argument("--handle",
                                   action="store_true",
                                   help="Pass result to intent handler")

    # train
    train_parser = sub_parsers.add_parser("train", help="Re-train profile")

    # record
    # record_parser = sub_parsers.add_parser('record', help='Record test phrases for profile')
    # record_parser.add_argument('--directory', help='Directory to write WAV files and intent JSON files')

    # record-wake
    # record_wake_parser = sub_parsers.add_parser('record-wake', help='Record wake word examples for profile')
    # record_wake_parser.add_argument('--directory', help='Directory to write WAV files')
    # record_wake_parser.add_argument('--negative', action='store_true', help='Record negative examples (not the wake word)')

    # tune
    # tune_parser = sub_parsers.add_parser('tune', help='Tune speech acoustic model for profile')
    # tune_parser.add_argument('--directory', help='Directory with WAV files and intent JSON files')

    # tune-wake
    # tune_wake_parser = sub_parsers.add_parser('tune-wake', help='Tune wake acoustic model for profile')
    # tune_wake_parser.add_argument('--directory', help='Directory with WAV files')

    # test
    # test_parser = sub_parsers.add_parser('test', help='Test speech/intent recognizers for profile')
    # test_parser.add_argument('directory', help='Directory with WAV files and intent JSON files')

    # test-wake
    # test_wake_parser = sub_parsers.add_parser(
    #     "test-wake", help="Test wake word examples for profile"
    # )
    # test_wake_parser.add_argument("directory", help="Directory with WAV files")
    # test_wake_parser.add_argument(
    #     "--threads", type=int, default=4, help="Number of threads to use"
    # )
    # test_wake_parser.add_argument(
    #     "--system", type=str, default=None, help="Override wake word system"
    # )

    # mic2wav
    mic2wav_parser = sub_parsers.add_parser("mic2wav",
                                            help="Voice command to WAV data")
    mic2wav_parser.add_argument(
        "--timeout",
        type=float,
        default=None,
        help="Maximum number of seconds to record (default=profile)",
    )

    # mic2text
    mic2text_parser = sub_parsers.add_parser(
        "mic2text", help="Voice command to text transcription")
    mic2text_parser.add_argument(
        "--timeout",
        type=float,
        default=None,
        help="Maximum number of seconds to record (default=profile)",
    )

    # mic2intent
    mic2intent_parser = sub_parsers.add_parser(
        "mic2intent", help="Voice command to parsed intent")
    mic2intent_parser.add_argument("--stdin",
                                   action="store_true",
                                   help="Read audio data from stdin")
    mic2intent_parser.add_argument("--handle",
                                   action="store_true",
                                   help="Pass result to intent handler")
    mic2intent_parser.add_argument(
        "--timeout",
        type=float,
        default=None,
        help="Maximum number of seconds to record (default=profile)",
    )

    # word2phonemes
    word2phonemes_parser = sub_parsers.add_parser(
        "word2phonemes", help="Get pronunciation(s) for word(s)")
    word2phonemes_parser.add_argument("words",
                                      nargs="*",
                                      help="Word(s) to pronounce")
    word2phonemes_parser.add_argument("-n",
                                      type=int,
                                      default=1,
                                      help="Maximum number of pronunciations")

    # word2wav
    word2wav_parser = sub_parsers.add_parser("word2wav", help="Pronounce word")
    word2wav_parser.add_argument("word", help="Word to pronounce")

    # wav2mqtt
    wav2mqtt_parser = sub_parsers.add_parser("wav2mqtt",
                                             help="Push WAV file(s) to MQTT")
    wav2mqtt_parser.add_argument("wav_files",
                                 nargs="*",
                                 help="Paths to WAV files")
    wav2mqtt_parser.add_argument(
        "--frames",
        type=int,
        default=480,
        help="WAV frames per MQTT message (default=0 for all)",
    )
    wav2mqtt_parser.add_argument("--site-id",
                                 type=str,
                                 default="default",
                                 help="Hermes siteId (default=default)")
    wav2mqtt_parser.add_argument(
        "--silence-before",
        type=float,
        default=0,
        help="Seconds of silence to add before each WAV",
    )
    wav2mqtt_parser.add_argument(
        "--silence-after",
        type=float,
        default=0,
        help="Seconds of silence to add after each WAV",
    )
    wav2mqtt_parser.add_argument(
        "--pause",
        type=float,
        default=0.01,
        help="Seconds to wait before sending next chunk (default=0.01)",
    )

    # text2wav
    text2wav_parser = sub_parsers.add_parser(
        "text2wav", help="Output WAV file using text to speech system")
    text2wav_parser.add_argument("sentence", help="Sentence to speak")

    # text2speech
    text2speech_parser = sub_parsers.add_parser(
        "text2speech", help="Speak sentences using text to speech system")
    text2speech_parser.add_argument("sentences",
                                    nargs="*",
                                    help="Sentences to speak")

    # sleep
    sleep_parser = sub_parsers.add_parser("sleep", help="Wait for wake word")

    # download
    download_parser = sub_parsers.add_parser("download",
                                             help="Download profile files")
    download_parser.add_argument(
        "--delete",
        action="store_true",
        help="Clear download cache before downloading")

    # check
    check_parser = sub_parsers.add_parser(
        "check", help="Check downloaded profile files")

    # -------------------------------------------------------------------------

    args = parser.parse_args()

    if args.debug:
        logging.root.setLevel(logging.DEBUG)

    profiles_dirs = [args.system_profiles, args.user_profiles]
    logger.debug(profiles_dirs)

    default_settings = Profile.load_defaults(args.system_profiles)

    # Create rhasspy core
    from rhasspy.core import RhasspyCore

    core = RhasspyCore(args.profile, args.system_profiles, args.user_profiles)

    # Add profile settings from the command line
    extra_settings = {}
    for key, value in args.set:
        try:
            value = json.loads(value)
        except:
            pass

        logger.debug("Profile: {0}={1}".format(key, value))
        extra_settings[key] = value
        core.profile.set(key, value)

    # Handle command
    if args.command == "info":
        if args.defaults:
            # Print default settings
            json.dump(core.defaults, sys.stdout, indent=4)
        else:
            # Print profile settings
            json.dump(core.profile.json, sys.stdout, indent=4)
    # elif args.command == "validate":
    #     from cerberus import Validator

    #     schema_path = os.path.join(os.path.dirname(__file__), "profile_schema.json")
    #     with open(schema_path, "r") as schema_file:
    #         v = Validator(json.load(schema_file))
    #         if v.validate(core.profile.json):
    #             print("VALID")
    #         else:
    #             print("INVALID")
    #             for err in v._errors:
    #                 print(err)
    elif args.command == "sentences":
        sentences_path = core.profile.read_path(
            core.profile.get("speech_to_text.sentences_ini", "sentences.ini"))

        with open(sentences_path, "r") as sentences_file:
            sys.stdout.write(sentences_file.read())
    else:
        # Patch profile
        profile = core.profile
        profile.set("rhasspy.listen_on_start", False)
        profile.set("rhasspy.preload_profile", False)

        if args.command == "wav2mqtt":
            profile.set("mqtt.enabled", True)
        elif args.command in ["mic2intent"] and args.stdin:
            profile.set("microphone.system", "stdin")
            profile.set("microphone.stdin.auto_start", False)
            mic_stdin_running = True
        elif args.command == "text2wav":
            profile.set("sounds.system", "dummy")

        # Set environment variables
        os.environ["RHASSPY_BASE_DIR"] = os.getcwd()
        os.environ["RHASSPY_PROFILE"] = core.profile.name
        os.environ["RHASSPY_PROFILE_DIR"] = core.profile.write_dir()

        # Execute command
        command_funcs = {
            "wav2text": wav2text,
            "text2intent": text2intent,
            "wav2intent": wav2intent,
            "train": train_profile,
            # 'record': record,
            # 'record-wake': record_wake,
            # 'tune': tune,
            # 'tune-wake': tune_wake,
            # 'test': test,
            # "test-wake": test_wake,
            "mic2text": mic2text,
            "mic2intent": mic2intent,
            "mic2wav": mic2wav,
            "word2phonemes": word2phonemes,
            "word2wav": word2wav,
            "wav2mqtt": wav2mqtt,
            "text2wav": text2wav,
            "text2speech": text2speech,
            "sleep": sleep,
            "download": download,
            "check": check,
        }

        if not args.command in ["test-wake"]:
            # Automatically start core
            await core.start()

        if not args.no_check and (args.command not in ["check", "download"]):
            # Verify that profile has necessary files
            missing_files = core.check_profile()
            if len(missing_files) > 0:
                logger.fatal(
                    f"Missing required files for {profile.name}: {missing_files.keys()}. Please run download command and try again."
                )
                sys.exit(1)

        if mic_stdin_running:
            logger.debug("Reading audio data from stdin")
            mic_stdin_thread = threading.Thread(target=read_audio_stdin,
                                                args=(core, ),
                                                daemon=True)
            mic_stdin_thread.start()

        # Run command
        try:
            await command_funcs[args.command](core, profile, args)

            if mic_stdin_thread is not None:
                mic_stdin_running = False
                mic_stdin_thread.join()
        finally:
            await core.shutdown()
Ejemplo n.º 2
0
async def check(core: RhasspyCore, profile: Profile, args: Any) -> None:
    """Verify that profile files are downloaded"""
    missing_files = core.check_profile()
    json.dump(missing_files, sys.stdout, indent=4)
Ejemplo n.º 3
0
async def check(core: RhasspyCore, profile: Profile, args: Any) -> None:
    missing_files = core.check_profile()
    json.dump(missing_files, sys.stdout, indent=4)
Ejemplo n.º 4
0
async def main() -> None:
    """Main method"""
    global mic_stdin_running, mic_stdin_thread

    # Parse command-line arguments
    parser = argparse.ArgumentParser(description="Rhasspy")
    parser.add_argument("--profile",
                        "-p",
                        required=True,
                        type=str,
                        help="Name of profile to use")
    parser.add_argument(
        "--system-profiles",
        type=str,
        help="Directory with base profile files (read only)",
        default=os.path.join(os.getcwd(), "profiles"),
    )
    parser.add_argument(
        "--user-profiles",
        type=str,
        help="Directory with user profile files (read/write)",
        default=os.path.expanduser("~/.config/rhasspy/profiles"),
    )
    parser.add_argument(
        "--set",
        "-s",
        nargs=2,
        action="append",
        help="Set a profile setting value",
        default=[],
    )
    parser.add_argument("--debug",
                        action="store_true",
                        help="Print DEBUG log to console")
    parser.add_argument(
        "--no-check",
        action="store_true",
        help="Don't check profile for necessary files",
    )

    sub_parsers = parser.add_subparsers(dest="command")
    sub_parsers.required = True

    # info
    info_parser = sub_parsers.add_parser("info", help="Profile information")
    info_parser.add_argument("--defaults",
                             action="store_true",
                             help="Only print default settings")

    # wav2text
    wav2text_parser = sub_parsers.add_parser(
        "wav2text", help="WAV file to text transcription")
    wav2text_parser.add_argument("wav_files",
                                 nargs="*",
                                 help="Paths to WAV files")

    # text2intent
    text2intent_parser = sub_parsers.add_parser("text2intent",
                                                help="Text parsed to intent")
    text2intent_parser.add_argument("sentences",
                                    nargs="*",
                                    help="Sentences to parse")
    text2intent_parser.add_argument("--handle",
                                    action="store_true",
                                    help="Pass result to intent handler")

    # wav2intent
    wav2intent_parser = sub_parsers.add_parser(
        "wav2intent", help="WAV file to parsed intent")
    wav2intent_parser.add_argument("wav_files",
                                   nargs="*",
                                   help="Paths to WAV files")
    wav2intent_parser.add_argument("--handle",
                                   action="store_true",
                                   help="Pass result to intent handler")

    # train
    train_parser = sub_parsers.add_parser("train", help="Re-train profile")
    train_parser.add_argument("--no-cache",
                              action="store_true",
                              help="Clear training cache")

    # mic2wav
    mic2wav_parser = sub_parsers.add_parser("mic2wav",
                                            help="Voice command to WAV data")
    mic2wav_parser.add_argument(
        "--timeout",
        type=float,
        default=None,
        help="Maximum number of seconds to record (default=profile)",
    )

    # mic2text
    mic2text_parser = sub_parsers.add_parser(
        "mic2text", help="Voice command to text transcription")
    mic2text_parser.add_argument(
        "--timeout",
        type=float,
        default=None,
        help="Maximum number of seconds to record (default=profile)",
    )

    # mic2intent
    mic2intent_parser = sub_parsers.add_parser(
        "mic2intent", help="Voice command to parsed intent")
    mic2intent_parser.add_argument("--stdin",
                                   action="store_true",
                                   help="Read audio data from stdin")
    mic2intent_parser.add_argument("--handle",
                                   action="store_true",
                                   help="Pass result to intent handler")
    mic2intent_parser.add_argument(
        "--timeout",
        type=float,
        default=None,
        help="Maximum number of seconds to record (default=profile)",
    )

    # word2phonemes
    word2phonemes_parser = sub_parsers.add_parser(
        "word2phonemes", help="Get pronunciation(s) for word(s)")
    word2phonemes_parser.add_argument("words",
                                      nargs="*",
                                      help="Word(s) to pronounce")
    word2phonemes_parser.add_argument("-n",
                                      type=int,
                                      default=1,
                                      help="Maximum number of pronunciations")

    # word2wav
    word2wav_parser = sub_parsers.add_parser("word2wav", help="Pronounce word")
    word2wav_parser.add_argument("word", help="Word to pronounce")

    # wav2mqtt
    wav2mqtt_parser = sub_parsers.add_parser("wav2mqtt",
                                             help="Push WAV file(s) to MQTT")
    wav2mqtt_parser.add_argument("wav_files",
                                 nargs="*",
                                 help="Paths to WAV files")
    wav2mqtt_parser.add_argument(
        "--frames",
        type=int,
        default=480,
        help="WAV frames per MQTT message (default=0 for all)",
    )
    wav2mqtt_parser.add_argument("--site-id",
                                 type=str,
                                 default="default",
                                 help="Hermes siteId (default=default)")
    wav2mqtt_parser.add_argument(
        "--silence-before",
        type=float,
        default=0,
        help="Seconds of silence to add before each WAV",
    )
    wav2mqtt_parser.add_argument(
        "--silence-after",
        type=float,
        default=0,
        help="Seconds of silence to add after each WAV",
    )
    wav2mqtt_parser.add_argument(
        "--pause",
        type=float,
        default=0.01,
        help="Seconds to wait before sending next chunk (default=0.01)",
    )

    # text2wav
    text2wav_parser = sub_parsers.add_parser(
        "text2wav", help="Output WAV file using text to speech system")
    text2wav_parser.add_argument("sentence", help="Sentence to speak")

    # text2speech
    text2speech_parser = sub_parsers.add_parser(
        "text2speech", help="Speak sentences using text to speech system")
    text2speech_parser.add_argument("sentences",
                                    nargs="*",
                                    help="Sentences to speak")

    # sleep
    sub_parsers.add_parser("sleep", help="Wait for wake word")

    # download
    download_parser = sub_parsers.add_parser("download",
                                             help="Download profile files")
    download_parser.add_argument(
        "--delete",
        action="store_true",
        help="Clear download cache before downloading")

    # check
    sub_parsers.add_parser("check", help="Check downloaded profile files")

    # -------------------------------------------------------------------------

    args = parser.parse_args()

    if args.debug:
        logging.basicConfig(level=logging.DEBUG)
    else:
        logging.basicConfig(level=logging.INFO)

    profiles_dirs = [args.system_profiles, args.user_profiles]
    logger.debug(profiles_dirs)

    # Create rhasspy core
    core = RhasspyCore(args.profile, args.system_profiles, args.user_profiles)

    # Add profile settings from the command line
    extra_settings = {}
    for key, value in args.set:
        try:
            value = json.loads(value)
        except Exception:
            pass

        logger.debug("Profile: %s=%s", key, value)
        extra_settings[key] = value
        core.profile.set(key, value)

    # Handle command
    if args.command == "info":
        if args.defaults:
            # Print default settings
            json.dump(core.defaults, sys.stdout, indent=4)
        else:
            # Print profile settings
            json.dump(core.profile.json, sys.stdout, indent=4)
    elif args.command == "sentences":
        sentences_path = core.profile.read_path(
            core.profile.get("speech_to_text.sentences_ini", "sentences.ini"))

        with open(sentences_path, "r") as sentences_file:
            sys.stdout.write(sentences_file.read())
    else:
        # Patch profile
        profile = core.profile
        profile.set("rhasspy.listen_on_start", False)
        profile.set("rhasspy.preload_profile", False)

        if args.command == "wav2mqtt":
            profile.set("mqtt.enabled", True)
        elif args.command in ["mic2intent"] and args.stdin:
            profile.set("microphone.system", "stdin")
            profile.set("microphone.stdin.auto_start", False)
            mic_stdin_running = True
        elif args.command == "text2wav":
            profile.set("sounds.system", "dummy")

        # Set environment variables
        os.environ["RHASSPY_BASE_DIR"] = os.getcwd()
        os.environ["RHASSPY_PROFILE"] = core.profile.name
        os.environ["RHASSPY_PROFILE_DIR"] = core.profile.write_dir()

        # Execute command
        command_funcs = {
            "wav2text": wav2text,
            "text2intent": text2intent,
            "wav2intent": wav2intent,
            "train": train_profile,
            "mic2text": mic2text,
            "mic2intent": mic2intent,
            "mic2wav": mic2wav,
            "word2phonemes": word2phonemes,
            "word2wav": word2wav,
            "wav2mqtt": wav2mqtt,
            "text2wav": text2wav,
            "text2speech": text2speech,
            "sleep": sleep,
            "download": download,
            "check": check,
        }

        # Automatically start core
        await core.start()

        if not args.no_check and (args.command not in ["check", "download"]):
            # Verify that profile has necessary files
            missing_files = core.check_profile()
            if missing_files:
                logger.fatal(
                    "Missing required files for %s: %s. Please run download command and try again.",
                    profile.name,
                    list(missing_files),
                )
                sys.exit(1)

        if mic_stdin_running:
            logger.debug("Reading audio data from stdin")
            mic_stdin_thread = threading.Thread(target=read_audio_stdin,
                                                args=(core, ),
                                                daemon=True)
            mic_stdin_thread.start()

        # Run command
        try:
            await command_funcs[args.command](core, profile, args)

            if mic_stdin_thread is not None:
                mic_stdin_running = False
                mic_stdin_thread.join()
        finally:
            await core.shutdown()