├── .github ├── dependabot.yml └── workflows │ ├── build_tests.yml │ ├── conventional-label.yaml │ ├── downstream.yml │ ├── license_tests.yml │ ├── pipaudit.yml │ ├── publish_stable.yml │ ├── release_workflow.yml │ └── unit_tests.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── downstream_report.txt ├── examples ├── entries.py ├── find_installed.py ├── loading.py └── searching.py ├── ovos_plugin_manager ├── __init__.py ├── audio.py ├── audio2ipa.py ├── audio_transformers.py ├── coreference.py ├── dialog_transformers.py ├── embeddings.py ├── exceptions.py ├── g2p.py ├── gui.py ├── hardware │ ├── __init__.py │ ├── fan.py │ ├── led │ │ ├── __init__.py │ │ └── animations.py │ └── switches.py ├── installation.py ├── intent_transformers.py ├── keywords.py ├── language.py ├── metadata_transformers.py ├── microphone.py ├── ocp.py ├── persona.py ├── phal.py ├── pipeline.py ├── plugin_entry.py ├── postag.py ├── segmentation.py ├── skills.py ├── solvers.py ├── stt.py ├── templates │ ├── __init__.py │ ├── audio.py │ ├── audio2ipa.py │ ├── coreference.py │ ├── embeddings.py │ ├── g2p.py │ ├── gui.py │ ├── hotwords.py │ ├── keywords.py │ ├── language.py │ ├── media.py │ ├── microphone.py │ ├── ocp.py │ ├── phal.py │ ├── pipeline.py │ ├── postag.py │ ├── segmentation.py │ ├── solvers.py │ ├── stt.py │ ├── tokenization.py │ ├── transformers.py │ ├── triples.py │ ├── tts.py │ └── vad.py ├── text_transformers.py ├── thirdparty │ ├── __init__.py │ └── solvers.py ├── tokenization.py ├── triples.py ├── tts.py ├── utils │ ├── __init__.py │ ├── config.py │ ├── tts_cache.py │ └── ui.py ├── vad.py ├── version.py └── wakewords.py ├── requirements ├── requirements.txt └── test.txt ├── setup.py └── test └── unittests ├── test_audio.py ├── test_audio2ipa.py ├── test_audio_transformers.py ├── test_coref.py ├── test_g2p.py ├── test_gui.py ├── test_hardware.py ├── test_installation.py ├── test_keywords.py ├── test_language.py ├── test_metadata_transformers.py ├── test_microphone.py ├── test_ocp.py ├── test_persona.py ├── test_phal.py ├── test_plugin_entry.py ├── test_postag.py ├── test_segmentation.py ├── test_skills.py ├── test_solver.py ├── test_stt.py ├── test_text_transformers.py ├── test_tokenization.py ├── test_tts.py ├── test_utils.py ├── test_vad.py └── test_wakewords.py /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "pip" # See documentation for possible values 9 | directory: "/requirements" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/build_tests.yml: -------------------------------------------------------------------------------- 1 | name: Run Build Tests 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - dev 9 | paths-ignore: 10 | - 'ovos_plugin_manager/version.py' 11 | - 'test/**' 12 | - 'examples/**' 13 | - '.github/**' 14 | - '.gitignore' 15 | - 'LICENSE' 16 | - 'CHANGELOG.md' 17 | - 'MANIFEST.in' 18 | - 'README.md' 19 | - 'scripts/**' 20 | workflow_dispatch: 21 | 22 | jobs: 23 | build_tests: 24 | strategy: 25 | max-parallel: 2 26 | matrix: 27 | python-version: ["3.10", "3.11"] 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v4 31 | - name: Setup Python 32 | uses: actions/setup-python@v5 33 | with: 34 | python-version: ${{ matrix.python-version }} 35 | - name: Install Build Tools 36 | run: | 37 | python -m pip install build wheel 38 | - name: Install System Dependencies 39 | run: | 40 | sudo apt-get update 41 | sudo apt install python3-dev swig libssl-dev 42 | - name: Build Source Packages 43 | run: | 44 | python setup.py sdist 45 | - name: Build Distribution Packages 46 | run: | 47 | python setup.py bdist_wheel 48 | - name: Install package 49 | run: | 50 | pip install . -------------------------------------------------------------------------------- /.github/workflows/conventional-label.yaml: -------------------------------------------------------------------------------- 1 | # auto add labels to PRs 2 | on: 3 | pull_request_target: 4 | types: [ opened, edited ] 5 | name: conventional-release-labels 6 | jobs: 7 | label: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: bcoe/conventional-release-labels@v1 -------------------------------------------------------------------------------- /.github/workflows/downstream.yml: -------------------------------------------------------------------------------- 1 | name: Track Downstream Dependencies 2 | 3 | on: 4 | push: 5 | branches: [dev] 6 | schedule: 7 | - cron: "0 0 * * *" # Runs daily at midnight UTC 8 | workflow_dispatch: # Allows manual triggering 9 | 10 | env: 11 | TARGET_PACKAGE: "ovos-plugin-manager" # Set the package to track here 12 | 13 | jobs: 14 | check-dependencies: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout Repository 18 | uses: actions/checkout@v4 19 | 20 | - name: Download requirements file 21 | run: | 22 | curl -o constraints-alpha.txt https://raw.githubusercontent.com/OpenVoiceOS/ovos-releases/refs/heads/main/constraints-alpha.txt 23 | 24 | - name: Set up Python 25 | uses: actions/setup-python@v5 26 | with: 27 | python-version: '3.11' 28 | 29 | - name: Install Dependencies 30 | run: | 31 | sudo apt-get update 32 | sudo apt install python3-dev swig libssl-dev libfann-dev portaudio19-dev libpulse-dev python3-fann2 33 | python -m venv venv 34 | source venv/bin/activate 35 | pip install build wheel 36 | pip install -r constraints-alpha.txt 37 | pip install pipdeptree 38 | 39 | - name: Find downstream dependencies 40 | run: | 41 | source venv/bin/activate 42 | pipdeptree -r -p "$TARGET_PACKAGE" > downstream_report.txt || echo "No dependencies found" 43 | 44 | - name: Commit and push changes 45 | run: | 46 | git config --global user.name "github-actions[bot]" 47 | git config --global user.email "github-actions[bot]@users.noreply.github.com" 48 | git checkout dev || git checkout -b dev 49 | git add downstream_report.txt 50 | git commit -m "Update downstream dependencies for $TARGET_PACKAGE" || echo "No changes to commit" 51 | git push origin dev 52 | -------------------------------------------------------------------------------- /.github/workflows/license_tests.yml: -------------------------------------------------------------------------------- 1 | name: Run License Tests 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - dev 9 | workflow_dispatch: 10 | 11 | jobs: 12 | license_tests: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Setup Python 17 | uses: actions/setup-python@v5 18 | with: 19 | python-version: '3.11' 20 | - name: Install Build Tools 21 | run: | 22 | python -m pip install build wheel 23 | - name: Install System Dependencies 24 | run: | 25 | sudo apt-get update 26 | sudo apt install python3-dev swig libssl-dev 27 | - name: Install core repo 28 | run: | 29 | pip install . 30 | - name: Get explicit and transitive dependencies 31 | run: | 32 | pip freeze > requirements-all.txt 33 | - name: Check python 34 | id: license_check_report 35 | uses: pilosus/action-pip-license-checker@v0.5.0 36 | with: 37 | requirements: 'requirements-all.txt' 38 | fail: 'Copyleft,Other,Error' 39 | fails-only: true 40 | exclude: '^(tqdm).*' 41 | exclude-license: '^(Mozilla).*$' 42 | - name: Print report 43 | if: ${{ always() }} 44 | run: echo "${{ steps.license_check_report.outputs.report }}" -------------------------------------------------------------------------------- /.github/workflows/pipaudit.yml: -------------------------------------------------------------------------------- 1 | name: Run PipAudit 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - dev 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build_tests: 11 | strategy: 12 | max-parallel: 2 13 | matrix: 14 | python-version: ["3.10", "3.11"] 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Setup Python 19 | uses: actions/setup-python@v5 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | - name: Install Build Tools 23 | run: | 24 | python -m pip install build wheel 25 | - name: Install System Dependencies 26 | run: | 27 | sudo apt-get update 28 | sudo apt install python3-dev swig libssl-dev 29 | - name: Install package 30 | run: | 31 | pip install . 32 | - uses: pypa/gh-action-pip-audit@v1.0.0 33 | with: 34 | # Ignore setuptools vulnerability we can't do much about 35 | ignore-vulns: | 36 | GHSA-r9hx-vwmv-q579 -------------------------------------------------------------------------------- /.github/workflows/publish_stable.yml: -------------------------------------------------------------------------------- 1 | name: Stable Release 2 | on: 3 | push: 4 | branches: [master] 5 | workflow_dispatch: 6 | 7 | jobs: 8 | publish_stable: 9 | uses: TigreGotico/gh-automations/.github/workflows/publish-stable.yml@master 10 | secrets: inherit 11 | with: 12 | branch: 'master' 13 | version_file: 'ovos_plugin_manager/version.py' 14 | setup_py: 'setup.py' 15 | publish_release: true 16 | 17 | publish_pypi: 18 | needs: publish_stable 19 | if: success() # Ensure this job only runs if the previous job succeeds 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v4 23 | with: 24 | ref: master 25 | fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. 26 | - name: Setup Python 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version: '3.11' 30 | - name: Install Build Tools 31 | run: | 32 | python -m pip install build wheel 33 | - name: version 34 | run: echo "::set-output name=version::$(python setup.py --version)" 35 | id: version 36 | - name: Build Distribution Packages 37 | run: | 38 | python setup.py sdist bdist_wheel 39 | - name: Publish to PyPI 40 | uses: pypa/gh-action-pypi-publish@master 41 | with: 42 | password: ${{secrets.PYPI_TOKEN}} 43 | 44 | 45 | sync_dev: 46 | needs: publish_stable 47 | if: success() # Ensure this job only runs if the previous job succeeds 48 | runs-on: ubuntu-latest 49 | steps: 50 | - uses: actions/checkout@v4 51 | with: 52 | fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. 53 | ref: master 54 | - name: Push master -> dev 55 | uses: ad-m/github-push-action@master 56 | with: 57 | github_token: ${{ secrets.GITHUB_TOKEN }} 58 | branch: dev 59 | -------------------------------------------------------------------------------- /.github/workflows/release_workflow.yml: -------------------------------------------------------------------------------- 1 | name: Release Alpha and Propose Stable 2 | 3 | on: 4 | pull_request: 5 | types: [closed] 6 | branches: [dev] 7 | 8 | jobs: 9 | publish_alpha: 10 | if: github.event.pull_request.merged == true 11 | uses: TigreGotico/gh-automations/.github/workflows/publish-alpha.yml@master 12 | secrets: inherit 13 | with: 14 | branch: 'dev' 15 | version_file: 'ovos_plugin_manager/version.py' 16 | setup_py: 'setup.py' 17 | update_changelog: true 18 | publish_prerelease: true 19 | changelog_max_issues: 100 20 | 21 | notify: 22 | if: github.event.pull_request.merged == true 23 | needs: publish_alpha 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v4 27 | - name: Send message to Matrix bots channel 28 | id: matrix-chat-message 29 | uses: fadenb/matrix-chat-message@v0.0.6 30 | with: 31 | homeserver: 'matrix.org' 32 | token: ${{ secrets.MATRIX_TOKEN }} 33 | channel: '!WjxEKjjINpyBRPFgxl:krbel.duckdns.org' 34 | message: | 35 | new ${{ github.event.repository.name }} PR merged! https://github.com/${{ github.repository }}/pull/${{ github.event.number }} 36 | 37 | publish_pypi: 38 | needs: publish_alpha 39 | if: success() # Ensure this job only runs if the previous job succeeds 40 | runs-on: ubuntu-latest 41 | steps: 42 | - uses: actions/checkout@v4 43 | with: 44 | ref: dev 45 | fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. 46 | - name: Setup Python 47 | uses: actions/setup-python@v5 48 | with: 49 | python-version: '3.11' 50 | - name: Install Build Tools 51 | run: | 52 | python -m pip install build wheel 53 | - name: version 54 | run: echo "::set-output name=version::$(python setup.py --version)" 55 | id: version 56 | - name: Build Distribution Packages 57 | run: | 58 | python setup.py sdist bdist_wheel 59 | - name: Publish to PyPI 60 | uses: pypa/gh-action-pypi-publish@master 61 | with: 62 | password: ${{secrets.PYPI_TOKEN}} 63 | 64 | 65 | propose_release: 66 | needs: publish_alpha 67 | if: success() # Ensure this job only runs if the previous job succeeds 68 | runs-on: ubuntu-latest 69 | steps: 70 | - name: Checkout dev branch 71 | uses: actions/checkout@v4 72 | with: 73 | ref: dev 74 | 75 | - name: Setup Python 76 | uses: actions/setup-python@v5 77 | with: 78 | python-version: '3.10' 79 | 80 | - name: Get version from setup.py 81 | id: get_version 82 | run: | 83 | VERSION=$(python setup.py --version) 84 | echo "VERSION=$VERSION" >> $GITHUB_ENV 85 | 86 | - name: Create and push new branch 87 | run: | 88 | git checkout -b release-${{ env.VERSION }} 89 | git push origin release-${{ env.VERSION }} 90 | 91 | - name: Open Pull Request from dev to master 92 | env: 93 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 94 | run: | 95 | # Variables 96 | BRANCH_NAME="release-${{ env.VERSION }}" 97 | BASE_BRANCH="master" 98 | HEAD_BRANCH="release-${{ env.VERSION }}" 99 | PR_TITLE="Release ${{ env.VERSION }}" 100 | PR_BODY="Human review requested!" 101 | 102 | # Create a PR using GitHub API 103 | curl -X POST \ 104 | -H "Accept: application/vnd.github+json" \ 105 | -H "Authorization: token $GITHUB_TOKEN" \ 106 | -d "{\"title\":\"$PR_TITLE\",\"body\":\"$PR_BODY\",\"head\":\"$HEAD_BRANCH\",\"base\":\"$BASE_BRANCH\"}" \ 107 | https://api.github.com/repos/${{ github.repository }}/pulls 108 | 109 | -------------------------------------------------------------------------------- /.github/workflows/unit_tests.yml: -------------------------------------------------------------------------------- 1 | name: Run UnitTests 2 | on: 3 | pull_request: 4 | branches: 5 | - dev 6 | paths-ignore: 7 | - 'ovos_plugin_manager/version.py' 8 | - 'examples/**' 9 | - '.github/**' 10 | - '.gitignore' 11 | - 'LICENSE' 12 | - 'CHANGELOG.md' 13 | - 'MANIFEST.in' 14 | - 'README.md' 15 | - 'scripts/**' 16 | push: 17 | branches: 18 | - master 19 | paths-ignore: 20 | - 'ovos_plugin_manager/version.py' 21 | - 'examples/**' 22 | - '.github/**' 23 | - '.gitignore' 24 | - 'LICENSE' 25 | - 'CHANGELOG.md' 26 | - 'MANIFEST.in' 27 | - 'README.md' 28 | - 'scripts/**' 29 | workflow_dispatch: 30 | 31 | jobs: 32 | unit_tests: 33 | strategy: 34 | max-parallel: 2 35 | matrix: 36 | python-version: ["3.10", "3.11"] 37 | runs-on: ubuntu-latest 38 | steps: 39 | - uses: actions/checkout@v4 40 | - name: Set up python ${{ matrix.python-version }} 41 | uses: actions/setup-python@v5 42 | with: 43 | python-version: ${{ matrix.python-version }} 44 | - name: Install System Dependencies 45 | run: | 46 | sudo apt-get update 47 | sudo apt install python3-dev 48 | python -m pip install build wheel 49 | - name: Install test dependencies 50 | run: | 51 | pip install -r requirements/test.txt 52 | - name: Install core repo 53 | run: | 54 | pip install . 55 | - name: Run unittests 56 | run: | 57 | pytest --cov=ovos_plugin_manager --cov-report xml test/unittests 58 | # NOTE: additional pytest invocations should also add the --cov-append flag 59 | # or they will overwrite previous invocations' coverage reports 60 | # (for an example, see OVOS Skill Manager's workflow) 61 | - name: Upload coverage 62 | env: 63 | CODECOV_TOKEN: ${{secrets.CODECOV_TOKEN}} 64 | uses: codecov/codecov-action@v2 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dev.env 2 | .dev_opts.json 3 | .idea 4 | *.code-workspace 5 | *.pyc 6 | *.swp 7 | *~ 8 | *.egg-info/ 9 | build 10 | dist 11 | .coverage 12 | /htmlcov 13 | .installed 14 | .mypy_cache 15 | .vscode 16 | .theia 17 | .venv/ 18 | 19 | # Created by unit tests 20 | .pytest_cache/ 21 | /.gtm/ 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.0.3a1](https://github.com/OpenVoiceOS/ovos-plugin-manager/tree/1.0.3a1) (2025-06-10) 4 | 5 | [Full Changelog](https://github.com/OpenVoiceOS/ovos-plugin-manager/compare/1.0.2...1.0.3a1) 6 | 7 | **Merged pull requests:** 8 | 9 | - refactor: simplify pipeline factory, missed in previous PR [\#327](https://github.com/OpenVoiceOS/ovos-plugin-manager/pull/327) ([JarbasAl](https://github.com/JarbasAl)) 10 | 11 | 12 | 13 | \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* 14 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include ovos_plugin_manager * 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OVOS plugin manager 2 | 3 | OPM can be used to load and create plugins for the OpenVoiceOS ecosystem! 4 | 5 | ![image](https://github.com/OpenVoiceOS/ovos-plugin-manager/assets/33701864/8c939267-42fc-4377-bcdb-f7df65e73252) 6 | 7 | Documentation can be found in the [ovos-technical-manual](https://openvoiceos.github.io/ovos-technical-manual/OPM) 8 | -------------------------------------------------------------------------------- /examples/entries.py: -------------------------------------------------------------------------------- 1 | from ovos_plugin_manager.plugin_entry import OpenVoiceOSPlugin 2 | from ovos_plugin_manager.installation import search_pip 3 | 4 | # installed 5 | p = OpenVoiceOSPlugin.from_name("cotovia_tts_plug") 6 | print(p.json) 7 | """ 8 | {'class': 'CotoviaTTSPlugin', 9 | 'description': 'Interface to cotovia TTS.', 10 | 'human_name': 'Cotovia TTS Plugin', 11 | 'is_installed': True, 12 | 'module_name': 'ovos_tts_plugin_cotovia', 13 | 'name': 'cotovia_tts_plug', 14 | 'package_name': None, 15 | 'plugin_type': , 16 | 'url': None} 17 | """ 18 | 19 | for pkg in search_pip("mycroft-tts-plugin"): 20 | data = {"description": pkg[1], "package_name": pkg[0]} 21 | p = OpenVoiceOSPlugin(data) 22 | print(p.json) 23 | """ 24 | {'class': None, 25 | 'description': 'A tts plugin for mycroft, using Azure Cognitive Services', 26 | 'human_name': 'Mycroft TTS Plugin Azure', 27 | 'is_installed': False, 28 | 'module_name': None, 29 | 'name': None, 30 | 'package_name': 'mycroft-tts-plugin-azure', 31 | 'plugin_type': , 32 | 'url': None} 33 | """ -------------------------------------------------------------------------------- /examples/find_installed.py: -------------------------------------------------------------------------------- 1 | from ovos_plugin_manager.tts import find_tts_plugins 2 | from ovos_plugin_manager.stt import find_stt_plugins 3 | from ovos_plugin_manager import find_plugins, PluginTypes 4 | 5 | for p in find_plugins(): 6 | print("PLUGIN:", p) 7 | 8 | for p in find_plugins(plug_type=PluginTypes.WAKEWORD): 9 | print("WAKE WORD PLUGIN:", p) 10 | 11 | for p in find_tts_plugins(): 12 | print("TTS PLUGIN:", p) 13 | 14 | for p in find_stt_plugins(): 15 | print("STT PLUGIN:", p) 16 | -------------------------------------------------------------------------------- /examples/loading.py: -------------------------------------------------------------------------------- 1 | from ovos_plugin_manager import load_plugin 2 | from ovos_plugin_manager.tts import load_tts_plugin 3 | from ovos_plugin_manager.stt import load_stt_plugin 4 | 5 | engine = load_tts_plugin("cotovia_tts_plug") 6 | print(engine.__name__) # CotoviaTTSPlugin 7 | 8 | engine = load_stt_plugin("chromium_stt_plug") 9 | print(engine.__name__) # ChromiumSTT 10 | 11 | engine = load_plugin("jarbas_precise_ww_plug") 12 | print(engine.__name__) # PreciseHotwordPlugin 13 | 14 | # using a plugin 15 | 16 | engine = load_tts_plugin("google_tts_plug") 17 | print(engine.__name__) # gTTSPlugin 18 | 19 | tts = engine(lang="en-us", config={}) 20 | tts.get_tts("hello world", "tts.mp3") 21 | # if you dont call this it will hang here forever 22 | tts.playback.stop() 23 | -------------------------------------------------------------------------------- /examples/searching.py: -------------------------------------------------------------------------------- 1 | from ovos_plugin_manager.installation import search_pip 2 | 3 | packages = search_pip("mycroft-tts-plugin") 4 | """ 5 | [('mycroft-tts-plugin-azure', 6 | 'A tts plugin for mycroft, using Azure Cognitive Services')] 7 | """ 8 | 9 | packages = search_pip("jarbas-tts-plugin") 10 | """ 11 | [('jarbas-tts-plugin-cotovia', 'A galician/spanish tts plugin for mycroft'), 12 | ('jarbas-tts-plugin-voicerss', 'A catalan tts plugin for mycroft'), 13 | ('jarbas-tts-plugin-softcatala', 'A catalan tts plugin for mycroft'), 14 | ('jarbas-tts-plugin-responsivevoice', 'ResponsiveVoice tts plugin for mycroft'), 15 | ('jarbas-tts-plugin-catotron', 'A catalan tacotron based tts plugin for mycroft')] 16 | """ 17 | 18 | packages = search_pip("mycroft") 19 | """ 20 | [('mycroft-precise', 'Mycroft Precise Wake Word Listener'), 21 | ('mycroft-messagebus-client', 'Mycroft Messagebus Client'), 22 | ('mycroft-tts-plugin-azure', 'A tts plugin for mycroft, using Azure Cognitive Services'), 23 | ('mycroft-ekylibre-utils', 'Ekylibre set of tools for MycroftAI skills'), 24 | ('mycroftapi', 'a library to communicate with Mycroft API')] 25 | """ 26 | 27 | packages = search_pip("mycroft", strict=False) 28 | """ 29 | ('mycroft-precise', 'Mycroft Precise Wake Word Listener') 30 | ('msk', 'Mycroft Skills Kit') 31 | ('mycroft-messagebus-client', 'Mycroft Messagebus Client') 32 | ('majel', 'A front-end for Mycroft that allows you to do cool things like stream video or surf the web.') 33 | ('mycroft-tts-plugin-azure', 'A tts plugin for mycroft, using Azure Cognitive Services') 34 | ('mycroft-ekylibre-utils', 'Ekylibre set of tools for MycroftAI skills') 35 | ('rhasspy-wake-precise-hermes', '') 36 | ('lingua-franca', 'Mycroft's multilingual text parsing and formatting library') 37 | ('adapt-parser', 'A text-to-intent parsing framework.') 38 | ('aklogger', 'A generic logging package for python projects') 39 | ('HiveMind-chatroom', 'Mycroft Chatroom') 40 | ('jarbas-hive-mind-red', 'Mycroft Node Red') 41 | ('HiveMind-cli', 'Mycroft Remote Cli') 42 | ('msm', 'Mycroft Skills Manager') 43 | ('ovos-local-backend', 'mock mycroft backend') 44 | ('HiveMind-voice-sat', 'Mycroft Voice Satellite') 45 | ('mycroftapi', 'a library to communicate with Mycroft API') 46 | ('HiveMind-PtT', 'Mycroft Push to Talk Satellite') 47 | ('jarbas-tts-plugin-softcatala', 'A catalan tts plugin for mycroft') 48 | ('jarbas-tts-plugin-responsivevoice', 'ResponsiveVoice tts plugin for mycroft') 49 | ('speech2text', 'Mycroft STT engine wrappers') 50 | ('jarbas-wake-word-plugin-precise', 'A wake word plugin for mycroft') 51 | ('chatterbox-wake-word-plugin-dummy', 'A wake word plugin for mycroft') 52 | ('jarbas-wake-word-plugin-pocketsphinx', 'A wake word plugin for mycroft') 53 | ('jarbas-core', 'Jarbas fork of Mycroft Core') 54 | ('jarbas-stt-plugin-vosk', 'A vosk stt plugin for mycroft') 55 | ('jarbas-tts-plugin-cotovia', 'A galician/spanish tts plugin for mycroft') 56 | ('jarbas-tts-plugin-catotron', 'A catalan tacotron based tts plugin for mycroft') 57 | ('jarbas-stt-plugin-chromium', 'A stt plugin for mycroft using the google chrome browser api') 58 | ('jarbas-wake-word-plugin-nyumaya', 'Nyumaya wake word plugin for mycroft') 59 | ('jarbas-hive-mind', 'Mesh Networking utilities for mycroft core') 60 | ('jarbas-wake-word-plugin-snowboy', 'Snowboy wake word plugin for mycroft') 61 | ('jarbas-wake-word-plugin-vosk', 'Kaldi wake word plugin for mycroft') 62 | ('ovos-utils', 'collection of simple utilities for use across the mycroft ecosystem') 63 | ('precise-runner', 'Wrapper to use Mycroft Precise Wake Word Listener') 64 | ('ovos-skill-installer', 'Mycroft skill installer from .zip or .tar.gz urls') 65 | """ 66 | -------------------------------------------------------------------------------- /ovos_plugin_manager/__init__.py: -------------------------------------------------------------------------------- 1 | from ovos_plugin_manager.utils import PluginTypes 2 | from ovos_plugin_manager.plugin_entry import OpenVoiceOSPlugin 3 | -------------------------------------------------------------------------------- /ovos_plugin_manager/audio.py: -------------------------------------------------------------------------------- 1 | from ovos_plugin_manager.utils import PluginConfigTypes, PluginTypes 2 | from ovos_utils.log import LOG 3 | from ovos_bus_client.util import get_mycroft_bus 4 | from ovos_config import Configuration 5 | 6 | 7 | # TODO - restore this log in next release with updated version string 8 | #log_deprecation("ovos_plugin_manager.audio has been deprecated on ovos-audio, " 9 | # "move to ovos_plugin_manager.media", "1.0.0") 10 | 11 | 12 | def find_audio_service_plugins() -> dict: 13 | """ 14 | Find all installed plugins 15 | @return: dict plugin names to entrypoints 16 | """ 17 | from ovos_plugin_manager.utils import find_plugins 18 | return find_plugins(PluginTypes.AUDIO) 19 | 20 | 21 | def get_audio_service_configs() -> dict: 22 | """ 23 | Get valid plugin configurations by plugin name 24 | @return: dict plugin names to list of dict configurations 25 | """ 26 | from ovos_plugin_manager.utils.config import load_configs_for_plugin_type 27 | return load_configs_for_plugin_type(PluginTypes.AUDIO) 28 | 29 | 30 | def get_audio_service_module_configs(module_name: str) -> dict: 31 | """ 32 | Get valid configuration for the specified plugin 33 | @param module_name: plugin to get configuration for 34 | @return: dict configuration (if provided) 35 | """ 36 | from ovos_plugin_manager.utils.config import load_plugin_configs 37 | return load_plugin_configs(module_name, PluginConfigTypes.AUDIO) 38 | 39 | 40 | def setup_audio_service(service_module, config=None, bus=None): 41 | """Run the appropriate setup function and return created service objects. 42 | 43 | Arguments: 44 | service_module: Python module to run 45 | config (dict): OpenVoiceOS configuration dict 46 | bus (MessageBusClient): Messagebus interface 47 | Returns: 48 | (list) List of created services. 49 | """ 50 | config = config or Configuration().get("Audio", {}) 51 | bus = bus or get_mycroft_bus() 52 | 53 | if (hasattr(service_module, 'autodetect') and 54 | callable(service_module.autodetect)): 55 | try: 56 | return service_module.autodetect(config, bus) 57 | except Exception as e: 58 | LOG.error('Failed to autodetect audio service. ' + repr(e)) 59 | elif hasattr(service_module, 'load_service'): 60 | try: 61 | return service_module.load_service(config, bus) 62 | except Exception as e: 63 | LOG.error('Failed to load audio service. ' + repr(e)) 64 | else: 65 | return None 66 | 67 | 68 | def load_audio_service_plugins(config=None, bus=None): 69 | """Load installed audioservice plugins. 70 | 71 | Arguments: 72 | config: OpenVoiceOS core configuration 73 | bus: OpenVoiceOS messagebus 74 | 75 | Returns: 76 | List of started services 77 | """ 78 | bus = bus or get_mycroft_bus() 79 | plugin_services = [] 80 | found_plugins = find_audio_service_plugins() 81 | for plugin_name, plugin_module in found_plugins.items(): 82 | LOG.info(f'Loading audio service plugin: {plugin_name}') 83 | service = setup_audio_service(plugin_module, config, bus) 84 | if service: 85 | plugin_services += service 86 | return plugin_services 87 | -------------------------------------------------------------------------------- /ovos_plugin_manager/audio2ipa.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from ovos_plugin_manager.utils import normalize_lang, PluginTypes, PluginConfigTypes 3 | from ovos_plugin_manager.templates.audio2ipa import Audio2IPA 4 | from ovos_utils.log import LOG 5 | 6 | 7 | def find_audio2ipa_plugins() -> dict: 8 | """ 9 | Find all installed plugins 10 | @return: dict plugin names to entrypoints 11 | """ 12 | from ovos_plugin_manager.utils import find_plugins 13 | return find_plugins(PluginTypes.AUDIO2IPA) 14 | 15 | 16 | def load_audio2ipa_plugin(module_name: str) -> type(Audio2IPA): 17 | """ 18 | Get an uninstantiated class for the requested module_name 19 | @param module_name: Plugin entrypoint name to load 20 | @return: Uninstantiated class 21 | """ 22 | from ovos_plugin_manager.utils import load_plugin 23 | return load_plugin(module_name, PluginTypes.AUDIO2IPA) 24 | 25 | 26 | def get_audio2ipa_config(config: Optional[dict] = None) -> dict: 27 | """ 28 | Get relevant configuration for factory methods 29 | @param config: global Configuration OR plugin class-specific configuration 30 | @return: plugin class-specific configuration 31 | """ 32 | from ovos_plugin_manager.utils.config import get_plugin_config 33 | return get_plugin_config(config, "audio2ipa") 34 | 35 | 36 | class OVOSAudio2IPAFactory: 37 | @staticmethod 38 | def get_class(config=None): 39 | """Factory method to get a Audio2IPA engine class based on configuration. 40 | 41 | The configuration file ``mycroft.conf`` contains a ``audio2ipa`` section with 42 | the name of a Audio2IPA module to be read by this method. 43 | 44 | "audio2ipa": { 45 | "module": 46 | } 47 | """ 48 | config = get_audio2ipa_config(config) 49 | audio2ipa_module = config.get("module") 50 | return load_audio2ipa_plugin(audio2ipa_module) 51 | 52 | @staticmethod 53 | def create(config=None): 54 | """Factory method to create a Audio2IPA engine based on configuration. 55 | 56 | The configuration file ``mycroft.conf`` contains a ``audio2ipa`` section with 57 | the name of a Audio2IPA module to be read by this method. 58 | 59 | "audio2ipa": { 60 | "module": 61 | } 62 | """ 63 | audio2ipa_config = get_audio2ipa_config(config) 64 | audio2ipa_module = audio2ipa_config.get('module', 'dummy') 65 | try: 66 | clazz = OVOSAudio2IPAFactory.get_class(audio2ipa_config) 67 | audio2ipa = clazz(audio2ipa_config) 68 | LOG.debug(f'Loaded plugin {audio2ipa_module}') 69 | except Exception: 70 | LOG.debug('The selected Audio2IPA plugin could not be loaded.') 71 | raise 72 | return audio2ipa 73 | -------------------------------------------------------------------------------- /ovos_plugin_manager/audio_transformers.py: -------------------------------------------------------------------------------- 1 | from ovos_plugin_manager.templates.transformers import AudioTransformer, AudioLanguageDetector 2 | from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes 3 | 4 | 5 | def find_audio_transformer_plugins() -> dict: 6 | """ 7 | Find all installed plugins 8 | @return: dict plugin names to entrypoints 9 | """ 10 | from ovos_plugin_manager.utils import find_plugins 11 | return find_plugins(PluginTypes.AUDIO_TRANSFORMER) 12 | 13 | 14 | def load_audio_transformer_plugin(module_name: str) -> type(AudioTransformer): 15 | """Wrapper function for loading audio_transformer plugin. 16 | 17 | Arguments: 18 | (str) OpenVoiceOS audio_transformer module name from config 19 | Returns: 20 | class: found audio_transformer plugin class 21 | """ 22 | from ovos_plugin_manager.utils import load_plugin 23 | return load_plugin(module_name, PluginTypes.AUDIO_TRANSFORMER) 24 | 25 | 26 | def get_audio_transformer_configs() -> dict: 27 | """ 28 | Get valid plugin configurations by plugin name 29 | @return: dict plugin names to list of dict configurations 30 | """ 31 | from ovos_plugin_manager.utils.config import load_configs_for_plugin_type 32 | return load_configs_for_plugin_type(PluginTypes.AUDIO_TRANSFORMER) 33 | 34 | 35 | def get_audio_transformer_module_configs(module_name: str): 36 | """ 37 | Get valid configuration for the specified plugin 38 | @param module_name: plugin to get configuration for 39 | @return: dict configuration (if provided) (TODO: Validate return type) 40 | """ 41 | from ovos_plugin_manager.utils.config import load_plugin_configs 42 | return load_plugin_configs(module_name, PluginConfigTypes.AUDIO_TRANSFORMER) 43 | 44 | 45 | def find_audio_lang_detector_plugins() -> dict: 46 | """ 47 | Find all installed audio language detector plugins 48 | @return: dict plugin names to entrypoints 49 | """ 50 | from ovos_plugin_manager.utils import find_plugins 51 | return {k: p for k, p in find_plugins(PluginTypes.AUDIO_TRANSFORMER).items() 52 | if issubclass(p, AudioLanguageDetector)} 53 | -------------------------------------------------------------------------------- /ovos_plugin_manager/coreference.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from ovos_plugin_manager.utils import normalize_lang, PluginTypes, \ 4 | PluginConfigTypes 5 | from ovos_config import Configuration 6 | from ovos_utils.log import LOG 7 | from ovos_plugin_manager.templates.coreference import CoreferenceSolverEngine, \ 8 | replace_coreferences 9 | 10 | 11 | def find_coref_plugins() -> dict: 12 | """ 13 | Find all installed plugins 14 | @return: dict plugin names to entrypoints 15 | """ 16 | from ovos_plugin_manager.utils import find_plugins 17 | return find_plugins(PluginTypes.COREFERENCE_SOLVER) 18 | 19 | 20 | def load_coref_plugin(module_name: str) -> type(CoreferenceSolverEngine): 21 | """ 22 | Get an uninstantiated class for the requested module_name 23 | @param module_name: Plugin entrypoint name to load 24 | @return: Uninstantiated class 25 | """ 26 | from ovos_plugin_manager.utils import load_plugin 27 | return load_plugin(module_name, PluginTypes.COREFERENCE_SOLVER) 28 | 29 | 30 | def get_coref_configs() -> dict: 31 | """ 32 | Get valid plugin configurations by plugin name 33 | @return: dict plugin names to list of dict configurations 34 | """ 35 | from ovos_plugin_manager.utils.config import load_configs_for_plugin_type 36 | return load_configs_for_plugin_type(PluginTypes.COREFERENCE_SOLVER) 37 | 38 | 39 | def get_coref_module_configs(module_name: str) -> dict: 40 | """ 41 | Get valid configuration for the specified plugin 42 | @param module_name: plugin to get configuration for 43 | @return: dict configuration (if provided) 44 | """ 45 | from ovos_plugin_manager.utils.config import load_plugin_configs 46 | return load_plugin_configs(module_name, 47 | PluginConfigTypes.COREFERENCE_SOLVER, True) 48 | 49 | 50 | def get_coref_lang_configs(lang: str, include_dialects: bool = False) -> dict: 51 | """ 52 | Get a dict of plugin names to list valid configurations for the requested 53 | lang. 54 | @param lang: Language to get configurations for 55 | @param include_dialects: consider configurations in different locales 56 | @return: dict {`plugin_name`: [`valid_configs`]} 57 | """ 58 | from ovos_plugin_manager.utils.config import get_plugin_language_configs 59 | return get_plugin_language_configs(PluginTypes.COREFERENCE_SOLVER, lang, 60 | include_dialects) 61 | 62 | 63 | def get_coref_supported_langs() -> dict: 64 | """ 65 | Return a dict of plugin names to list supported languages 66 | @return: dict plugin names to list supported languages 67 | """ 68 | from ovos_plugin_manager.utils.config import get_plugin_supported_languages 69 | return get_plugin_supported_languages(PluginTypes.COREFERENCE_SOLVER) 70 | 71 | 72 | def get_coref_config(config: Optional[dict] = None) -> dict: 73 | """ 74 | Get relevant configuration for factory methods 75 | @param config: global Configuration OR plugin class-specific configuration 76 | @return: plugin class-specific configuration 77 | """ 78 | from ovos_plugin_manager.utils.config import get_plugin_config 79 | config = config or Configuration() 80 | return get_plugin_config(config, "coref") 81 | 82 | 83 | class OVOSCoreferenceSolverFactory: 84 | """ replicates the base mycroft class, but uses only OPM enabled plugins""" 85 | MAPPINGS = { 86 | "pronomial": "ovos-coref-plugin-pronomial" 87 | } 88 | 89 | @staticmethod 90 | def get_class(config=None): 91 | """Factory method to get a CoreferenceSolver engine class based on configuration. 92 | 93 | The configuration file ``mycroft.conf`` contains a ``coref`` section with 94 | the name of a CoreferenceSolver module to be read by this method. 95 | 96 | "coref": { 97 | "module": 98 | } 99 | """ 100 | config = get_coref_config(config) 101 | coref_module = config.get("module", "dummy") 102 | if coref_module == "dummy": 103 | return CoreferenceSolverEngine 104 | if coref_module in OVOSCoreferenceSolverFactory.MAPPINGS: 105 | coref_module = OVOSCoreferenceSolverFactory.MAPPINGS[coref_module] 106 | return load_coref_plugin(coref_module) 107 | 108 | @staticmethod 109 | def create(config=None): 110 | """Factory method to create a CoreferenceSolver engine based on configuration. 111 | 112 | The configuration file ``mycroft.conf`` contains a ``coref`` section with 113 | the name of a CoreferenceSolver module to be read by this method. 114 | 115 | "coref": { 116 | "module": 117 | } 118 | """ 119 | config = config or get_coref_config() 120 | plugin = config.get("module") or "dummy" 121 | plugin_config = config.get(plugin) or {} 122 | try: 123 | clazz = OVOSCoreferenceSolverFactory.get_class(config) 124 | return clazz(plugin_config) 125 | except Exception: 126 | LOG.exception(f'CoreferenceSolver plugin {plugin} ' 127 | f'could not be loaded!') 128 | raise 129 | -------------------------------------------------------------------------------- /ovos_plugin_manager/dialog_transformers.py: -------------------------------------------------------------------------------- 1 | from ovos_plugin_manager.templates.transformers import DialogTransformer, TTSTransformer 2 | from ovos_plugin_manager.utils import PluginTypes 3 | from ovos_plugin_manager.utils import load_plugin, find_plugins 4 | 5 | 6 | def find_dialog_transformer_plugins() -> dict: 7 | """ 8 | Find all installed plugins 9 | @return: dict plugin names to entrypoints 10 | """ 11 | return find_plugins(PluginTypes.DIALOG_TRANSFORMER) 12 | 13 | 14 | def load_dialog_transformer_plugin(module_name: str) -> type(DialogTransformer): 15 | """Wrapper function for loading dialog_transformer plugin. 16 | 17 | Arguments: 18 | (str) OpenVoiceOS dialog_transformer module name from config 19 | Returns: 20 | class: found dialog_transformer plugin class 21 | """ 22 | return load_plugin(module_name, PluginTypes.DIALOG_TRANSFORMER) 23 | 24 | 25 | def find_tts_transformer_plugins() -> dict: 26 | """ 27 | Find all installed plugins 28 | @return: dict plugin names to entrypoints 29 | """ 30 | return find_plugins(PluginTypes.TTS_TRANSFORMER) 31 | 32 | 33 | def load_tts_transformer_plugin(module_name: str) -> type(TTSTransformer): 34 | """Wrapper function for loading dialog_transformer plugin. 35 | 36 | Arguments: 37 | (str) OpenVoiceOS dialog_transformer module name from config 38 | Returns: 39 | class: found dialog_transformer plugin class 40 | """ 41 | return load_plugin(module_name, PluginTypes.TTS_TRANSFORMER) 42 | -------------------------------------------------------------------------------- /ovos_plugin_manager/embeddings.py: -------------------------------------------------------------------------------- 1 | from ovos_plugin_manager.templates.embeddings import EmbeddingsDB, TextEmbeddingsStore, FaceEmbeddingsStore, VoiceEmbeddingsStore 2 | from ovos_plugin_manager.utils import PluginTypes 3 | 4 | 5 | def find_embeddings_plugins() -> dict: 6 | """ 7 | Find all installed plugins 8 | @return: dict plugin names to entrypoints 9 | """ 10 | from ovos_plugin_manager.utils import find_plugins 11 | return find_plugins(PluginTypes.EMBEDDINGS) 12 | 13 | 14 | def load_embeddings_plugin(module_name: str) -> type(EmbeddingsDB): 15 | """ 16 | Get an uninstantiated class for the requested module_name 17 | @param module_name: Plugin entrypoint name to load 18 | @return: Uninstantiated class 19 | """ 20 | from ovos_plugin_manager.utils import load_plugin 21 | return load_plugin(module_name, PluginTypes.EMBEDDINGS) 22 | 23 | 24 | def find_voice_embeddings_plugins() -> dict: 25 | """ 26 | Find all installed plugins 27 | @return: dict plugin names to entrypoints 28 | """ 29 | from ovos_plugin_manager.utils import find_plugins 30 | return find_plugins(PluginTypes.VOICE_EMBEDDINGS) 31 | 32 | 33 | def load_voice_embeddings_plugin(module_name: str) -> type(VoiceEmbeddingsStore): 34 | """ 35 | Get an uninstantiated class for the requested module_name 36 | @param module_name: Plugin entrypoint name to load 37 | @return: Uninstantiated class 38 | """ 39 | from ovos_plugin_manager.utils import load_plugin 40 | return load_plugin(module_name, PluginTypes.VOICE_EMBEDDINGS) 41 | 42 | 43 | def find_face_embeddings_plugins() -> dict: 44 | """ 45 | Find all installed plugins 46 | @return: dict plugin names to entrypoints 47 | """ 48 | from ovos_plugin_manager.utils import find_plugins 49 | return find_plugins(PluginTypes.FACE_EMBEDDINGS) 50 | 51 | 52 | def load_face_embeddings_plugin(module_name: str) -> type(FaceEmbeddingsStore): 53 | """ 54 | Get an uninstantiated class for the requested module_name 55 | @param module_name: Plugin entrypoint name to load 56 | @return: Uninstantiated class 57 | """ 58 | from ovos_plugin_manager.utils import load_plugin 59 | return load_plugin(module_name, PluginTypes.FACE_EMBEDDINGS) 60 | 61 | 62 | def find_text_embeddings_plugins() -> dict: 63 | """ 64 | Find all installed plugins 65 | @return: dict plugin names to entrypoints 66 | """ 67 | from ovos_plugin_manager.utils import find_plugins 68 | return find_plugins(PluginTypes.TEXT_EMBEDDINGS) 69 | 70 | 71 | def load_text_embeddings_plugin(module_name: str) -> type(TextEmbeddingsStore): 72 | """ 73 | Get an uninstantiated class for the requested module_name 74 | @param module_name: Plugin entrypoint name to load 75 | @return: Uninstantiated class 76 | """ 77 | from ovos_plugin_manager.utils import load_plugin 78 | return load_plugin(module_name, PluginTypes.TEXT_EMBEDDINGS) 79 | -------------------------------------------------------------------------------- /ovos_plugin_manager/exceptions.py: -------------------------------------------------------------------------------- 1 | class PipException(RuntimeError): 2 | """ failed to run pip """ 3 | -------------------------------------------------------------------------------- /ovos_plugin_manager/g2p.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from ovos_config import Configuration 3 | from ovos_plugin_manager.utils import normalize_lang, PluginTypes, PluginConfigTypes 4 | from ovos_plugin_manager.templates.g2p import Grapheme2PhonemePlugin, PhonemeAlphabet 5 | from ovos_utils.log import LOG 6 | 7 | 8 | def find_g2p_plugins() -> dict: 9 | """ 10 | Find all installed plugins 11 | @return: dict plugin names to entrypoints 12 | """ 13 | from ovos_plugin_manager.utils import find_plugins 14 | return find_plugins(PluginTypes.PHONEME) 15 | 16 | 17 | def load_g2p_plugin(module_name: str) -> Grapheme2PhonemePlugin: 18 | """ 19 | Get an uninstantiated class for the requested module_name 20 | @param module_name: Plugin entrypoint name to load 21 | @return: Uninstantiated class 22 | """ 23 | from ovos_plugin_manager.utils import load_plugin 24 | return load_plugin(module_name, PluginTypes.PHONEME) 25 | 26 | 27 | def get_g2p_configs() -> dict: 28 | """ 29 | Get valid plugin configurations by plugin name 30 | @return: dict plugin names to list of dict configurations 31 | """ 32 | from ovos_plugin_manager.utils.config import load_configs_for_plugin_type 33 | return load_configs_for_plugin_type(PluginTypes.PHONEME) 34 | 35 | 36 | def get_g2p_module_configs(module_name: str): 37 | """ 38 | Get valid configurations for the specified plugin 39 | @param module_name: plugin to get configuration for 40 | @return: dict configuration (if provided) (TODO: Validate return type) 41 | """ 42 | from ovos_plugin_manager.utils.config import load_plugin_configs 43 | return load_plugin_configs(module_name, 44 | PluginConfigTypes.PHONEME, True) 45 | 46 | 47 | def get_g2p_lang_configs(lang: str, include_dialects: bool = False) -> dict: 48 | """ 49 | Get a dict of plugin names to list valid configurations for the requested 50 | lang. 51 | @param lang: Language to get configurations for 52 | @param include_dialects: consider configurations in different locales 53 | @return: dict {`plugin_name`: [`valid_configs`]} 54 | """ 55 | from ovos_plugin_manager.utils.config import get_plugin_language_configs 56 | return get_plugin_language_configs(PluginTypes.PHONEME, lang, 57 | include_dialects) 58 | 59 | 60 | def get_g2p_supported_langs() -> dict: 61 | """ 62 | Return a dict of plugin names to list supported languages 63 | @return: dict plugin names to list supported languages 64 | """ 65 | from ovos_plugin_manager.utils.config import get_plugin_supported_languages 66 | return get_plugin_supported_languages(PluginTypes.PHONEME) 67 | 68 | 69 | def get_g2p_config(config: Optional[dict] = None) -> dict: 70 | """ 71 | Get relevant configuration for factory methods 72 | @param config: global Configuration OR plugin class-specific configuration 73 | @return: plugin class-specific configuration 74 | """ 75 | from ovos_plugin_manager.utils.config import get_plugin_config 76 | return get_plugin_config(config, "g2p") 77 | 78 | 79 | class OVOSG2PFactory: 80 | 81 | @staticmethod 82 | def get_class(config=None): 83 | """Factory method to get a G2P engine class based on configuration. 84 | 85 | The configuration file ``mycroft.conf`` contains a ``g2p`` section with 86 | the name of a G2P module to be read by this method. 87 | 88 | "g2p": { 89 | "module": 90 | } 91 | """ 92 | config = get_g2p_config(config) 93 | g2p_module = config.get("module") or 'dummy' 94 | if g2p_module == 'dummy': 95 | return Grapheme2PhonemePlugin 96 | 97 | return load_g2p_plugin(g2p_module) 98 | 99 | @classmethod 100 | def create(cls, config=None): 101 | """Factory method to create a G2P engine based on configuration. 102 | 103 | The configuration file ``mycroft.conf`` contains a ``g2p`` section with 104 | the name of a G2P module to be read by this method. 105 | 106 | "g2p": { 107 | "module": 108 | } 109 | """ 110 | config = config or Configuration() 111 | if "g2p" in config: 112 | config = config["g2p"] 113 | g2p_config = get_g2p_config(config) 114 | g2p_module = g2p_config.get('module', 'dummy') 115 | fallback = g2p_config.get("fallback_module") 116 | try: 117 | clazz = OVOSG2PFactory.get_class(g2p_config) 118 | g2p = clazz(g2p_config) 119 | LOG.debug(f'Loaded plugin {g2p_module}') 120 | except Exception: 121 | LOG.exception('The selected G2P plugin could not be loaded.') 122 | if fallback in config and fallback != g2p_module: 123 | LOG.info(f"Attempting to load fallback plugin instead: {fallback}") 124 | config["module"] = fallback 125 | return cls.create(config) 126 | raise 127 | return g2p 128 | -------------------------------------------------------------------------------- /ovos_plugin_manager/gui.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes 4 | from ovos_plugin_manager.templates.gui import GUIExtension 5 | from ovos_utils.log import LOG 6 | 7 | 8 | def find_gui_plugins() -> dict: 9 | """ 10 | Find all installed plugins 11 | @return: dict plugin names to entrypoints 12 | """ 13 | from ovos_plugin_manager.utils import find_plugins 14 | return find_plugins(PluginTypes.GUI) 15 | 16 | 17 | def load_gui_plugin(module_name: str) -> type(GUIExtension): 18 | """ 19 | Get an uninstantiated class for the requested module_name 20 | @param module_name: Plugin entrypoint name to load 21 | @return: Uninstantiated class 22 | """ 23 | from ovos_plugin_manager.utils import load_plugin 24 | return load_plugin(module_name, PluginTypes.GUI) 25 | 26 | 27 | def get_gui_configs() -> dict: 28 | """ 29 | Get valid plugin configurations by plugin name 30 | @return: dict plugin names to list of dict configurations 31 | """ 32 | from ovos_plugin_manager.utils.config import load_configs_for_plugin_type 33 | return load_configs_for_plugin_type(PluginTypes.GUI) 34 | 35 | 36 | def get_gui_module_configs(module_name: str) -> List[dict]: 37 | """ 38 | Get valid configurations for the specified plugin 39 | @param module_name: plugin to get configuration for 40 | @return: list of dict configurations (if provided) 41 | """ 42 | from ovos_plugin_manager.utils.config import load_plugin_configs 43 | cfgs = load_plugin_configs(module_name, PluginConfigTypes.GUI) 44 | return {module_name: cfgs} if isinstance(cfgs, list) else cfgs 45 | 46 | 47 | def get_gui_config(config: Optional[dict] = None) -> dict: 48 | """ 49 | Get relevant configuration for factory methods 50 | @param config: global Configuration OR plugin class-specific configuration 51 | @return: plugin class-specific configuration 52 | """ 53 | from ovos_plugin_manager.utils.config import get_plugin_config 54 | return get_plugin_config(config, "gui") 55 | 56 | 57 | class OVOSGuiFactory: 58 | @staticmethod 59 | def get_class(config=None): 60 | """Factory method to get a gui engine class based on configuration. 61 | 62 | The configuration file ``mycroft.conf`` contains a ``gui`` section with 63 | the name of a gui module to be read by this method. 64 | 65 | "gui": { 66 | "extension": 67 | } 68 | """ 69 | config = get_gui_config(config) 70 | gui_module = config.get("module") or 'generic' 71 | if gui_module == 'generic': 72 | return GUIExtension 73 | return load_gui_plugin(gui_module) 74 | 75 | @staticmethod 76 | def create(config=None, bus=None, gui=None): 77 | """Factory method to create a gui engine based on configuration. 78 | 79 | The configuration file ``mycroft.conf`` contains a ``gui`` section with 80 | the name of a gui module to be read by this method. 81 | 82 | "gui": { 83 | "extension": 84 | } 85 | """ 86 | gui_config = get_gui_config(config) 87 | gui_module = gui_config.get('module', 'generic') 88 | try: 89 | clazz = OVOSGuiFactory.get_class(gui_config) 90 | gui = clazz(gui_config, bus=bus, gui=gui) 91 | LOG.debug(f'Loaded plugin {gui_module}') 92 | except Exception: 93 | LOG.exception('The selected gui plugin could not be loaded.') 94 | raise 95 | return gui 96 | -------------------------------------------------------------------------------- /ovos_plugin_manager/hardware/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenVoiceOS/ovos-plugin-manager/cd2930fbef92882ecbd680c82bc2dea74785169b/ovos_plugin_manager/hardware/__init__.py -------------------------------------------------------------------------------- /ovos_plugin_manager/hardware/fan.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod 2 | 3 | 4 | class AbstractFan: 5 | @abstractmethod 6 | def set_fan_speed(self, percent: int): 7 | """ 8 | Set the fan speed to the specified percentage value. 9 | :param percent: 0-100 fan speed value 10 | """ 11 | 12 | @abstractmethod 13 | def get_fan_speed(self) -> int: 14 | """ 15 | Get the current fan speed as a 0-100 percentage value. 16 | """ 17 | # TODO: Consider an equivalent property for this 18 | 19 | @abstractmethod 20 | def get_cpu_temp(self) -> float: 21 | """ 22 | Get the current CPU temp in celsius (-1.0 if not available) 23 | """ 24 | # TODO: Consider an equivalent property for this 25 | return -1.0 26 | 27 | @abstractmethod 28 | def shutdown(self): 29 | """ 30 | Perform any cleanup and set the fan to a reasonable speed. 31 | """ 32 | -------------------------------------------------------------------------------- /ovos_plugin_manager/hardware/led/__init__.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod 2 | from enum import Enum 3 | from typing import Union 4 | 5 | 6 | class Color(Enum): 7 | """ 8 | Enum class for colors. For theme support, call Color.set_theme() with a 9 | valid hex value. 10 | """ 11 | BLACK = (0, 0, 0) 12 | WHITE = (255, 255, 255) 13 | 14 | RED = (255, 0, 0) 15 | GREEN = (0, 255, 0) 16 | BLUE = (0, 0, 255) 17 | 18 | YELLOW = (255, 255, 0) 19 | MAGENTA = (255, 0, 255) 20 | CYAN = (0, 255, 255) 21 | 22 | BURNT_ORANGE = (173, 64, 0) 23 | 24 | MYCROFT_BLUE = (34, 167, 240) 25 | NEON_ORANGE = (255, 134, 0) 26 | OVOS_RED = (255, 26, 26) 27 | 28 | THEME = () 29 | 30 | def as_rgb_tuple(self) -> tuple: 31 | """ 32 | Get an RGB tuple representation of the color. 33 | """ 34 | if self.name == Color.THEME.name: 35 | if not hasattr(self, '_THEME'): 36 | return Color.WHITE.as_rgb_tuple() 37 | return self._THEME 38 | assert isinstance(self.value, tuple) 39 | return self.value 40 | 41 | @staticmethod 42 | def from_hex(hex_code: str) -> tuple: 43 | """ 44 | Get a color RGB tuple from a hex code 45 | @param hex_code: RGB hex code, optionally starting with '#' 46 | @return: tuple RGB values 47 | """ 48 | hex_code = hex_code.lstrip('#').strip().lower() 49 | if hex_code.startswith("ff") and len(hex_code) == 8: 50 | hex_code = hex_code[2:] 51 | if len(hex_code) != 6: 52 | raise ValueError(f"Expected 6-character hex code, got: {hex_code}") 53 | return tuple(int(hex_code[i:i + 2], 16) for i in (0, 2, 4)) 54 | 55 | @classmethod 56 | def from_name(cls, color: str): 57 | """ 58 | Get a Color object by name. 59 | :param color: string color corresponding on a name in the Color enum 60 | :returns: Color enum object for the requested string color 61 | """ 62 | for c in cls: 63 | if c.name.lower() == color.lower(): 64 | return c 65 | raise ValueError(f'{color} is not a valid Color') 66 | 67 | @classmethod 68 | def set_theme(cls, color: str): 69 | try: 70 | cls._THEME = Color.from_hex(color) 71 | except ValueError: 72 | cls._THEME = Color.WHITE.as_rgb_tuple() 73 | 74 | 75 | class AbstractLed: 76 | @property 77 | @abstractmethod 78 | def num_leds(self) -> int: 79 | """ 80 | Return the logical number of addressable LEDs. 81 | """ 82 | 83 | @property 84 | @abstractmethod 85 | def capabilities(self) -> dict: 86 | """ 87 | Return a dict of capabilities this object supports 88 | """ 89 | 90 | @abstractmethod 91 | def set_led(self, led_idx: int, color: tuple, immediate: bool = True): 92 | """ 93 | Set a specific LED to a particular color. 94 | :param led_idx: index of LED to modify 95 | :param color: RGB color value as ints 96 | :param immediate: If true, update LED immediately, else wait for `show` 97 | """ 98 | 99 | # TODO: get_led? 100 | 101 | @abstractmethod 102 | def fill(self, color: tuple): 103 | """ 104 | Set all LEDs to a particular color. 105 | :param color: RGB color value as a tuple of ints 106 | """ 107 | 108 | @abstractmethod 109 | def show(self): 110 | """ 111 | Update LEDs to match values set in this class. 112 | """ 113 | 114 | @abstractmethod 115 | def shutdown(self): 116 | """ 117 | Perform any cleanup and turn off LEDs. 118 | """ 119 | 120 | @staticmethod 121 | def scale_brightness(color_val: int, bright_val: float) -> float: 122 | """ 123 | Scale an individual color value by a specified brightness. 124 | :param color_val: 0-255 R, G, or B value 125 | :param bright_val: 0.0-1.0 brightness scalar value 126 | :returns: Float modified color value to account for brightness 127 | """ 128 | return min(255.0, color_val * bright_val) 129 | 130 | def get_capabilities(self) -> dict: 131 | """ 132 | Backwards-compatible method to return `self.capabilities` 133 | """ 134 | return self.capabilities 135 | -------------------------------------------------------------------------------- /ovos_plugin_manager/hardware/switches.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod 2 | 3 | 4 | class AbstractSwitches: 5 | @property 6 | @abstractmethod 7 | def capabilities(self) -> dict: 8 | """ 9 | Return a dict of capabilities this object supports 10 | """ 11 | 12 | @abstractmethod 13 | def on_action(self): 14 | """ 15 | Override to do something when the `action` button is pressed. 16 | """ 17 | pass 18 | 19 | @abstractmethod 20 | def on_vol_up(self): 21 | """ 22 | Override to do something when the `volume up` button is pressed. 23 | """ 24 | pass 25 | 26 | @abstractmethod 27 | def on_vol_down(self): 28 | """ 29 | Override to do something when the `volume down` button is pressed. 30 | """ 31 | pass 32 | 33 | @abstractmethod 34 | def on_mute(self): 35 | """ 36 | Override to do something when `mute` switch is activated. 37 | """ 38 | pass 39 | 40 | @abstractmethod 41 | def on_unmute(self): 42 | """ 43 | Override to do something when `mute` switch is deactivated. 44 | """ 45 | pass 46 | 47 | @abstractmethod 48 | def shutdown(self): 49 | """ 50 | Perform any cleanup. 51 | """ 52 | 53 | def get_capabilities(self) -> dict: 54 | """ 55 | Backwards-compatible method to return `self.capabilities` 56 | """ 57 | return self.capabilities 58 | -------------------------------------------------------------------------------- /ovos_plugin_manager/installation.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from os.path import exists, dirname 4 | from subprocess import PIPE, Popen 5 | 6 | import requests 7 | from combo_lock import NamedLock 8 | from ovos_utils.log import LOG 9 | 10 | from ovos_plugin_manager.exceptions import PipException 11 | 12 | # default constraints to use if none are given 13 | DEFAULT_CONSTRAINTS = '/etc/mycroft/constraints.txt' 14 | PIP_LOCK = NamedLock("ovos_pip.lock") 15 | 16 | 17 | def search_pip(query, strict=True, page=1, max_results=10): 18 | raw_text = requests.get(f'https://pypi.org/search/?q={query}&page=' 19 | f'{page}').text 20 | raw_names = raw_text.split('')[1:-2] 21 | names = [] 22 | for name in raw_names: 23 | names.append(name.split('')[0]) 24 | 25 | raw_desc = raw_text.split('

')[1:-2] 26 | descs = [] 27 | for desc in raw_desc: 28 | descs.append(desc.split('

')[0]) 29 | 30 | n_results = 0 31 | if strict: 32 | pkgs = [(names[i], descs[i]) for i in range(len(names)) if 33 | query in names[i]] 34 | else: 35 | pkgs = [(names[i], descs[i]) for i in range(len(names))] 36 | for p in pkgs[:max_results]: 37 | yield p 38 | if len(pkgs) > max_results or not len(pkgs): 39 | return 40 | 41 | raw_pages = raw_text.split(f'')[-1].split('')[0] 45 | raw_pages[idx] = int(p) 46 | except: 47 | raw_pages[idx] = 0 48 | next_page = bool(len([p for p in raw_pages if p > page])) 49 | 50 | if next_page: 51 | for pkg in search_pip(query, strict, page + 1): 52 | n_results += 1 53 | yield pkg 54 | if n_results >= max_results: 55 | return 56 | 57 | 58 | def pip_install(packages, constraints=None, print_logs=False): 59 | if not len(packages): 60 | return False 61 | # Use constraints to limit the installed versions 62 | if constraints and not exists(constraints): 63 | LOG.error('Couldn\'t find the constraints file') 64 | return False 65 | elif exists(DEFAULT_CONSTRAINTS): 66 | constraints = DEFAULT_CONSTRAINTS 67 | 68 | can_pip = os.access(dirname(sys.executable), os.W_OK | os.X_OK) 69 | pip_args = [sys.executable, '-m', 'pip', 'install'] 70 | if constraints: 71 | pip_args += ['-c', constraints] 72 | 73 | if not can_pip: 74 | pip_args = ['sudo', '-n'] + pip_args 75 | 76 | with PIP_LOCK: 77 | """ 78 | Iterate over the individual Python packages and 79 | install them one by one to enforce the order specified 80 | in the manifest. 81 | """ 82 | for dependent_python_package in packages: 83 | LOG.info("(pip) Installing " + dependent_python_package) 84 | pip_command = pip_args + [dependent_python_package] 85 | if print_logs: 86 | proc = Popen(pip_command) 87 | else: 88 | proc = Popen(pip_command, stdout=PIPE, stderr=PIPE) 89 | pip_code = proc.wait() 90 | if pip_code != 0: 91 | stderr = proc.stderr.read().decode() 92 | raise PipException( 93 | pip_code, proc.stdout.read().decode(), stderr 94 | ) 95 | 96 | return True 97 | -------------------------------------------------------------------------------- /ovos_plugin_manager/intent_transformers.py: -------------------------------------------------------------------------------- 1 | from ovos_plugin_manager.templates.transformers import IntentTransformer 2 | from ovos_plugin_manager.utils import PluginTypes 3 | 4 | 5 | def find_intent_transformer_plugins() -> dict: 6 | """ 7 | Find all installed plugins 8 | @return: dict plugin names to entrypoints 9 | """ 10 | from ovos_plugin_manager.utils import find_plugins 11 | return find_plugins(PluginTypes.INTENT_TRANSFORMER) 12 | 13 | 14 | def load_intent_transformer_plugin(module_name: str) -> \ 15 | type(IntentTransformer): 16 | """ 17 | Get an uninstantiated class for the requested module_name 18 | @param module_name: Plugin entrypoint name to load 19 | @return: Uninstantiated class 20 | """ 21 | from ovos_plugin_manager.utils import load_plugin 22 | return load_plugin(module_name, PluginTypes.INTENT_TRANSFORMER) 23 | -------------------------------------------------------------------------------- /ovos_plugin_manager/keywords.py: -------------------------------------------------------------------------------- 1 | from ovos_plugin_manager.utils import normalize_lang, PluginTypes, PluginConfigTypes 2 | from ovos_config import Configuration 3 | from ovos_utils.log import LOG 4 | from ovos_plugin_manager.templates.keywords import KeywordExtractor 5 | 6 | 7 | def find_keyword_extract_plugins() -> dict: 8 | """ 9 | Find all installed plugins 10 | @return: dict plugin names to entrypoints 11 | """ 12 | from ovos_plugin_manager.utils import find_plugins 13 | return find_plugins(PluginTypes.KEYWORD_EXTRACTION) 14 | 15 | 16 | def load_keyword_extract_plugin(module_name: str) -> type(KeywordExtractor): 17 | """ 18 | Get an uninstantiated class for the requested module_name 19 | @param module_name: Plugin entrypoint name to load 20 | @return: Uninstantiated class 21 | """ 22 | from ovos_plugin_manager.utils import load_plugin 23 | return load_plugin(module_name, PluginTypes.KEYWORD_EXTRACTION) 24 | 25 | 26 | def get_keyword_extract_configs() -> dict: 27 | """ 28 | Get valid plugin configurations by plugin name 29 | @return: dict plugin names to list of dict configurations 30 | """ 31 | from ovos_plugin_manager.utils.config import load_configs_for_plugin_type 32 | return load_configs_for_plugin_type(PluginTypes.KEYWORD_EXTRACTION) 33 | 34 | 35 | def get_keyword_extract_module_configs(module_name: str) -> dict: 36 | """ 37 | Get valid configurations for the specified plugin 38 | @param module_name: plugin to get configuration for 39 | @return: dict configurations by language (if provided) 40 | """ 41 | from ovos_plugin_manager.utils.config import load_plugin_configs 42 | return load_plugin_configs(module_name, 43 | PluginConfigTypes.KEYWORD_EXTRACTION, True) 44 | 45 | 46 | def get_keyword_extract_lang_configs(lang: str, 47 | include_dialects: bool = False) -> dict: 48 | """ 49 | Get a dict of plugin names to list valid configurations for the requested 50 | lang. 51 | @param lang: Language to get configurations for 52 | @param include_dialects: consider configurations in different locales 53 | @return: dict {`plugin_name`: `valid_configs`]} 54 | """ 55 | from ovos_plugin_manager.utils.config import get_plugin_language_configs 56 | return get_plugin_language_configs(PluginTypes.KEYWORD_EXTRACTION, lang, 57 | include_dialects) 58 | 59 | 60 | def get_keyword_extract_supported_langs() -> dict: 61 | """ 62 | Return a dict of plugin names to list supported languages 63 | @return: dict plugin names to list supported languages 64 | """ 65 | from ovos_plugin_manager.utils.config import get_plugin_supported_languages 66 | return get_plugin_supported_languages(PluginTypes.KEYWORD_EXTRACTION) 67 | 68 | 69 | def get_keyword_extract_config(config: dict = None) -> dict: 70 | """ 71 | Get relevant configuration for factory methods 72 | @param config: global Configuration OR plugin class-specific configuration 73 | @return: plugin class-specific configuration 74 | """ 75 | from ovos_plugin_manager.utils.config import get_plugin_config 76 | config = config or Configuration() 77 | return get_plugin_config(config, "keyword_extract") 78 | 79 | 80 | class OVOSKeywordExtractorFactory: 81 | """ reads mycroft.conf and returns the globally configured plugin """ 82 | MAPPINGS = { 83 | # default split at sentence boundaries 84 | # usually helpful in other plugins and included in base class 85 | "dummy": "ovos-keyword-plugin-dummy" 86 | } 87 | 88 | @staticmethod 89 | def get_class(config=None): 90 | """Factory method to get a KeywordExtractor engine class based on configuration. 91 | 92 | The configuration file ``mycroft.conf`` contains a ``keyword_extract`` section with 93 | the name of a KeywordExtractor module to be read by this method. 94 | 95 | "keyword_extract": { 96 | "module": 97 | } 98 | """ 99 | config = get_keyword_extract_config(config) 100 | keyword_extract_module = config.get("module", "ovos-keyword-plugin-dummy") 101 | if keyword_extract_module in OVOSKeywordExtractorFactory.MAPPINGS: 102 | keyword_extract_module = OVOSKeywordExtractorFactory.MAPPINGS[keyword_extract_module] 103 | return load_keyword_extract_plugin(keyword_extract_module) 104 | 105 | @staticmethod 106 | def create(config=None): 107 | """Factory method to create a KeywordExtractor engine based on configuration. 108 | 109 | The configuration file ``mycroft.conf`` contains a ``keyword_extract`` section with 110 | the name of a KeywordExtractor module to be read by this method. 111 | 112 | "keyword_extract": { 113 | "module": 114 | } 115 | """ 116 | config = config or get_keyword_extract_config() 117 | plugin = config.get("module") or "ovos-keyword-plugin-dummy" 118 | plugin_config = config.get(plugin) or {} 119 | try: 120 | clazz = OVOSKeywordExtractorFactory.get_class(config) 121 | return clazz(plugin_config) 122 | except Exception: 123 | LOG.exception(f'Keyword extraction plugin {plugin} ' 124 | f'could not be loaded!') 125 | return KeywordExtractor() 126 | -------------------------------------------------------------------------------- /ovos_plugin_manager/metadata_transformers.py: -------------------------------------------------------------------------------- 1 | from ovos_plugin_manager.utils import normalize_lang, PluginTypes, \ 2 | PluginConfigTypes 3 | from ovos_plugin_manager.templates.transformers import MetadataTransformer 4 | 5 | 6 | def find_metadata_transformer_plugins() -> dict: 7 | """ 8 | Find all installed plugins 9 | @return: dict plugin names to entrypoints 10 | """ 11 | from ovos_plugin_manager.utils import find_plugins 12 | return find_plugins(PluginTypes.METADATA_TRANSFORMER) 13 | 14 | 15 | def load_metadata_transformer_plugin(module_name: str) -> \ 16 | type(MetadataTransformer): 17 | """ 18 | Get an uninstantiated class for the requested module_name 19 | @param module_name: Plugin entrypoint name to load 20 | @return: Uninstantiated class 21 | """ 22 | from ovos_plugin_manager.utils import load_plugin 23 | return load_plugin(module_name, PluginTypes.METADATA_TRANSFORMER) 24 | 25 | 26 | def get_metadata_transformer_configs() -> dict: 27 | """ 28 | Get valid plugin configurations by plugin name 29 | @return: dict plugin names to list of dict configurations 30 | """ 31 | from ovos_plugin_manager.utils.config import load_configs_for_plugin_type 32 | return load_configs_for_plugin_type(PluginTypes.METADATA_TRANSFORMER) 33 | 34 | 35 | def get_metadata_transformer_module_configs(module_name) -> dict: 36 | """ 37 | Get valid configurations for the specified plugin 38 | @param module_name: plugin to get configuration for 39 | @return: dict configurations by language (if provided) 40 | """ 41 | from ovos_plugin_manager.utils.config import load_plugin_configs 42 | return load_plugin_configs(module_name, 43 | PluginConfigTypes.METADATA_TRANSFORMER, True) 44 | 45 | 46 | def get_metadata_transformer_lang_configs(lang: str, 47 | include_dialects: bool = False) -> dict: 48 | """ 49 | Get a dict of plugin names to list valid configurations for the requested 50 | lang. 51 | @param lang: Language to get configurations for 52 | @param include_dialects: consider configurations in different locales 53 | @return: dict {`plugin_name`: `valid_configs`]} 54 | """ 55 | from ovos_plugin_manager.utils.config import get_plugin_language_configs 56 | return get_plugin_language_configs(PluginTypes.METADATA_TRANSFORMER, lang, 57 | include_dialects) 58 | 59 | 60 | def get_metadata_transformer_supported_langs() -> dict: 61 | """ 62 | Return a dict of plugin names to list supported languages 63 | @return: dict plugin names to list supported languages 64 | """ 65 | from ovos_plugin_manager.utils.config import get_plugin_supported_languages 66 | return get_plugin_supported_languages(PluginTypes.METADATA_TRANSFORMER) 67 | -------------------------------------------------------------------------------- /ovos_plugin_manager/microphone.py: -------------------------------------------------------------------------------- 1 | from ovos_config import Configuration 2 | from ovos_utils.log import LOG, deprecated 3 | import warnings 4 | from ovos_plugin_manager.templates.microphone import Microphone 5 | from ovos_plugin_manager.utils import PluginTypes 6 | 7 | 8 | def find_microphone_plugins() -> dict: 9 | """ 10 | Find all installed plugins 11 | @return: dict plugin names to entrypoints 12 | """ 13 | from ovos_plugin_manager.utils import find_plugins 14 | return find_plugins(PluginTypes.MIC) 15 | 16 | 17 | def load_microphone_plugin(module_name: str) -> type(Microphone): 18 | """ 19 | Get an uninstantiated class for the requested module_name 20 | @param module_name: Plugin entrypoint name to load 21 | @return: Uninstantiated class 22 | """ 23 | from ovos_plugin_manager.utils import load_plugin 24 | return load_plugin(module_name, PluginTypes.MIC) 25 | 26 | 27 | @deprecated("get_microphone_config is deprecated, use Configuration() directly", "1.0.0") 28 | def get_microphone_config(config=None): 29 | """ 30 | Get relevant configuration for factory methods 31 | @param config: global Configuration OR plugin class-specific configuration 32 | @return: plugin class-specific configuration 33 | """ 34 | warnings.warn( 35 | "get_microphone_config is deprecated, use Configuration() directly", 36 | DeprecationWarning, 37 | stacklevel=2, 38 | ) 39 | from ovos_plugin_manager.utils.config import get_plugin_config 40 | return get_plugin_config(config, "microphone") 41 | 42 | 43 | class OVOSMicrophoneFactory: 44 | 45 | @staticmethod 46 | def get_class(config=None): 47 | """Factory method to get a microphone engine class based on configuration. 48 | 49 | The configuration file ``mycroft.conf`` contains a ``microphone`` section with 50 | the name of a microphone module to be read by this method. 51 | 52 | "microphone": { 53 | "module": 54 | } 55 | """ 56 | config = config or Configuration().get("listener", {}).get("microphone", {}) 57 | microphone_module = config.get("module") 58 | return load_microphone_plugin(microphone_module) 59 | 60 | @classmethod 61 | def create(cls, config=None): 62 | """Factory method to create a microphone engine based on configuration. 63 | 64 | The configuration file ``mycroft.conf`` contains a ``microphone`` section with 65 | the name of a microphone module to be read by this method. 66 | 67 | "microphone": { 68 | "module": 69 | } 70 | """ 71 | config = config or Configuration().get("listener", {}).get("microphone", {}) 72 | microphone_module = config.get('module') 73 | microphone_config = config.get(microphone_module, {}) 74 | fallback = microphone_config.get("fallback_module") 75 | try: 76 | clazz = OVOSMicrophoneFactory.get_class(config) 77 | if fallback: 78 | microphone_config.pop('fallback_module') 79 | microphone = clazz(**microphone_config) 80 | LOG.debug(f'Loaded microphone plugin {microphone_module}') 81 | except Exception: 82 | LOG.exception('The selected microphone plugin could not be loaded.') 83 | if fallback in config and fallback != microphone_module: 84 | LOG.info(f"Attempting to load fallback plugin instead: {fallback}") 85 | config["module"] = fallback 86 | return cls.create(config) 87 | raise 88 | return microphone 89 | -------------------------------------------------------------------------------- /ovos_plugin_manager/ocp.py: -------------------------------------------------------------------------------- 1 | from ovos_plugin_manager.utils import PluginTypes 2 | from ovos_plugin_manager.templates.ocp import OCPStreamExtractor 3 | from ovos_utils.log import LOG 4 | from functools import lru_cache 5 | 6 | from ovos_plugin_manager.utils import find_plugins 7 | 8 | try: 9 | from ovos_plugin_manager.templates.media import AudioPlayerBackend, VideoPlayerBackend, WebPlayerBackend 10 | except ImportError: 11 | LOG.warning("Please install ovos-utils~=0.1 for `AudioPlayerBackend`, " 12 | "`VideoPlayerBackend`, and `WebPlayerBackend` imports.") 13 | 14 | 15 | def find_ocp_plugins() -> dict: 16 | """ 17 | Find all installed plugins 18 | @return: dict plugin names to entrypoints 19 | """ 20 | return find_plugins(PluginTypes.STREAM_EXTRACTOR) 21 | 22 | 23 | def find_ocp_audio_plugins() -> dict: 24 | """ 25 | Find all installed plugins 26 | @return: dict plugin names to entrypoints 27 | """ 28 | return find_plugins(PluginTypes.AUDIO_PLAYER) 29 | 30 | 31 | def find_ocp_video_plugins() -> dict: 32 | """ 33 | Find all installed plugins 34 | @return: dict plugin names to entrypoints 35 | """ 36 | return find_plugins(PluginTypes.VIDEO_PLAYER) 37 | 38 | 39 | def find_ocp_web_plugins() -> dict: 40 | """ 41 | Find all installed plugins 42 | @return: dict plugin names to entrypoints 43 | """ 44 | return find_plugins(PluginTypes.WEB_PLAYER) 45 | 46 | 47 | class StreamHandler: 48 | def __init__(self): 49 | self.extractors = {} 50 | self.load() 51 | 52 | @property 53 | def supported_seis(self): 54 | """ 55 | skills may return results requesting a specific extractor to be used 56 | 57 | plugins should report a StreamExtractorIds (sei) that identifies it can handle certain kinds of requests 58 | 59 | any streams of the format "{sei}//{uri}" can be handled by this plugin 60 | """ 61 | seis = [] 62 | for extractor in self.extractors.values(): 63 | seis += extractor.supported_seis 64 | return seis 65 | 66 | def load(self): 67 | for plugin, clazz in find_ocp_plugins().items(): 68 | try: 69 | self.extractors[plugin] = clazz() 70 | LOG.info(f"Loaded OCP plugin: {plugin}") 71 | except: 72 | LOG.error(f"Failed to load {plugin}") 73 | continue 74 | 75 | def _get_sei_plugs(self, uri): 76 | return [plug for plug in self.extractors.values() 77 | if any((uri.startswith(f"{sei}//") for sei in plug.supported_seis))] 78 | 79 | def _extract_from_sei(self, uri, video=True): 80 | # attempt to use a dedicated stream extractor if requested 81 | for plug in self._get_sei_plugs(uri): 82 | try: 83 | return plug.extract_stream(uri, video) 84 | except Exception as e: 85 | LOG.exception(f"error extracting stream with {plug}") 86 | 87 | def _extract_from_url(self, uri, video=True): 88 | for plug in self.extractors.values(): 89 | try: 90 | if plug.validate_uri(uri): 91 | return plug.extract_stream(uri, video) 92 | except Exception as e: 93 | LOG.exception(f"error extracting stream with {plug}") 94 | 95 | def extract_stream(self, uri, video=True): 96 | meta = {} 97 | 98 | # attempt to use a dedicated stream extractor if requested 99 | while len(self._get_sei_plugs(uri)): # support chained extractions, where one plugin calls another 100 | meta = self._extract_from_sei(uri, video) or {} 101 | if meta.get("uri"): 102 | uri = meta["uri"] 103 | else: 104 | break 105 | 106 | # let plugins parse the raw url and see if they want to handle it 107 | meta = self._extract_from_url(uri, video) or meta 108 | 109 | # no extractor available, return raw url 110 | return meta or {"uri": uri} 111 | 112 | 113 | @lru_cache() # to avoid loading StreamHandler more than once 114 | def load_stream_extractors(): 115 | return StreamHandler() 116 | 117 | 118 | def available_extractors(): 119 | xtract = load_stream_extractors() 120 | return ["/", "http:", "https:", "file:"] + \ 121 | [f"{sei}//" for sei in xtract.supported_seis] 122 | -------------------------------------------------------------------------------- /ovos_plugin_manager/persona.py: -------------------------------------------------------------------------------- 1 | from ovos_plugin_manager.utils import PluginTypes 2 | 3 | 4 | def find_persona_plugins() -> dict: 5 | """ 6 | Find all installed plugins 7 | @return: dict plugin names to entrypoints (persona entrypoint are just dicts) 8 | """ 9 | from ovos_plugin_manager.utils import find_plugins 10 | return find_plugins(PluginTypes.PERSONA) 11 | 12 | 13 | -------------------------------------------------------------------------------- /ovos_plugin_manager/phal.py: -------------------------------------------------------------------------------- 1 | from ovos_plugin_manager.utils import PluginTypes, \ 2 | PluginConfigTypes 3 | from ovos_plugin_manager.templates.phal import PHALPlugin, AdminPlugin 4 | from ovos_utils.log import LOG 5 | 6 | 7 | def find_phal_plugins() -> dict: 8 | """ 9 | Find all installed plugins 10 | @return: dict plugin names to entrypoints 11 | """ 12 | from ovos_plugin_manager.utils import find_plugins 13 | return find_plugins(PluginTypes.PHAL) 14 | 15 | 16 | def get_phal_configs() -> dict: 17 | """ 18 | Get valid plugin configurations by plugin name 19 | @return: dict plugin names to list of dict configurations 20 | """ 21 | from ovos_plugin_manager.utils.config import load_configs_for_plugin_type 22 | return load_configs_for_plugin_type(PluginTypes.PHAL) 23 | 24 | 25 | def get_phal_module_configs(module_name: str) -> dict: 26 | """ 27 | Get valid configurations for the specified plugin 28 | @param module_name: plugin to get configuration for 29 | @return: dict configurations (if provided) 30 | """ 31 | from ovos_plugin_manager.utils.config import load_plugin_configs 32 | # PHAL plugins return [list of config dicts] or {module_name: [list of config dicts]} 33 | cfgs = load_plugin_configs(module_name, PluginConfigTypes.PHAL) 34 | return {module_name: cfgs} if isinstance(cfgs, list) else cfgs 35 | 36 | 37 | def find_admin_plugins(): 38 | """ 39 | Find all installed plugins 40 | @return: dict plugin names to entrypoints 41 | """ 42 | from ovos_plugin_manager.utils import find_plugins 43 | return find_plugins(PluginTypes.ADMIN) 44 | 45 | 46 | def get_admin_configs() -> dict: 47 | """ 48 | Get valid plugin configurations by plugin name 49 | @return: dict plugin names to list of dict configurations 50 | """ 51 | from ovos_plugin_manager.utils.config import load_configs_for_plugin_type 52 | return load_configs_for_plugin_type(PluginTypes.ADMIN) 53 | 54 | 55 | def get_admin_module_configs(module_name: str) -> dict: 56 | """ 57 | Get valid configurations for the specified plugin 58 | @param module_name: plugin to get configuration for 59 | @return: dict configurations (if provided) 60 | """ 61 | from ovos_plugin_manager.utils.config import load_plugin_configs 62 | # admin plugins return [list of config dicts] or {module_name: [list of config dicts]} 63 | cfgs = load_plugin_configs(module_name, PluginConfigTypes.ADMIN) 64 | return {module_name: cfgs} if isinstance(cfgs, list) else cfgs 65 | -------------------------------------------------------------------------------- /ovos_plugin_manager/pipeline.py: -------------------------------------------------------------------------------- 1 | from typing import Any, List, Optional, Union, Dict, Type 2 | 3 | from ovos_bus_client.client import MessageBusClient 4 | from ovos_config import Configuration 5 | from ovos_utils.fakebus import FakeBus 6 | 7 | from ovos_plugin_manager.templates.pipeline import ConfidenceMatcherPipeline, PipelinePlugin 8 | from ovos_plugin_manager.utils import PluginTypes, find_plugins, load_plugin 9 | 10 | # Typing aliases 11 | PipelineID = str 12 | PipelineMatcherID = str 13 | 14 | 15 | def find_pipeline_plugins() -> Dict[PipelineID, Type[PipelinePlugin]]: 16 | """ 17 | Discover and return all installed pipeline plugins. 18 | 19 | Returns: 20 | A dictionary mapping pipeline plugin IDs to their classes. 21 | """ 22 | return find_plugins(PluginTypes.PIPELINE) 23 | 24 | 25 | def load_pipeline_plugin(module_name: str) -> Type[PipelinePlugin]: 26 | """ 27 | Load a pipeline plugin class by name. 28 | 29 | Args: 30 | module_name: The name of the plugin to load. 31 | 32 | Returns: 33 | The uninstantiated plugin class. 34 | """ 35 | return load_plugin(module_name, PluginTypes.PIPELINE) 36 | 37 | 38 | class OVOSPipelineFactory: 39 | """ 40 | Factory class for discovering, loading, and managing OVOS pipeline plugins. 41 | """ 42 | 43 | @staticmethod 44 | def get_installed_pipeline_ids() -> List[PipelineID]: 45 | """ 46 | List all installed pipeline plugin identifiers. 47 | 48 | Returns: 49 | A list of installed pipeline plugin IDs. 50 | """ 51 | return list(find_pipeline_plugins().keys()) 52 | 53 | @staticmethod 54 | def get_installed_pipeline_matcher_ids() -> List[PipelineMatcherID]: 55 | """ 56 | List all available pipeline matcher identifiers, including confidence levels. 57 | 58 | Returns: 59 | A list of matcher IDs. 60 | """ 61 | pipelines: List[PipelineMatcherID] = [] 62 | for plug_id, clazz in find_pipeline_plugins().items(): 63 | if issubclass(clazz, ConfidenceMatcherPipeline): 64 | pipelines.extend([ 65 | f"{plug_id}-low", 66 | f"{plug_id}-medium", 67 | f"{plug_id}-high" 68 | ]) 69 | else: 70 | pipelines.append(plug_id) 71 | return pipelines 72 | 73 | @classmethod 74 | def load_plugin( 75 | cls, 76 | pipe_id: PipelineID, 77 | bus: Optional[Union[MessageBusClient, FakeBus]] = None, 78 | config: Optional[Dict[str, Any]] = None 79 | ) -> PipelinePlugin: 80 | """ 81 | Load a pipeline plugin instance. 82 | 83 | Args: 84 | pipe_id: The pipeline plugin ID. 85 | bus: Optional message bus client. 86 | config: Optional configuration for the plugin. 87 | 88 | Returns: 89 | An instance of the loaded pipeline plugin. 90 | """ 91 | config = config or Configuration().get("intents", {}).get(pipe_id, {}) 92 | clazz = find_pipeline_plugins().get(pipe_id) 93 | if not clazz: 94 | raise ValueError(f"Unknown pipeline plugin: {pipe_id}") 95 | plugin_instance = clazz(bus, config) 96 | return plugin_instance 97 | -------------------------------------------------------------------------------- /ovos_plugin_manager/postag.py: -------------------------------------------------------------------------------- 1 | from ovos_plugin_manager.utils import normalize_lang, PluginTypes, \ 2 | PluginConfigTypes 3 | from ovos_config import Configuration 4 | from ovos_utils.log import LOG 5 | from ovos_plugin_manager.templates.postag import PosTagger 6 | 7 | 8 | def find_postag_plugins() -> dict: 9 | """ 10 | Find all installed plugins 11 | @return: dict plugin names to entrypoints 12 | """ 13 | from ovos_plugin_manager.utils import find_plugins 14 | return find_plugins(PluginTypes.POSTAG) 15 | 16 | 17 | def load_postag_plugin(module_name: str) -> type(PosTagger): 18 | """ 19 | Get an uninstantiated class for the requested module_name 20 | @param module_name: Plugin entrypoint name to load 21 | @return: Uninstantiated class 22 | """ 23 | from ovos_plugin_manager.utils import load_plugin 24 | return load_plugin(module_name, PluginTypes.POSTAG) 25 | 26 | 27 | def get_postag_configs() -> dict: 28 | """ 29 | Get valid plugin configurations by plugin name 30 | @return: dict plugin names to list of dict configurations 31 | """ 32 | from ovos_plugin_manager.utils.config import load_configs_for_plugin_type 33 | return load_configs_for_plugin_type(PluginTypes.POSTAG) 34 | 35 | 36 | def get_postag_module_configs(module_name: str) -> dict: 37 | """ 38 | Get valid configurations for the specified plugin 39 | @param module_name: plugin to get configuration for 40 | @return: dict configurations by language (if provided) 41 | """ 42 | from ovos_plugin_manager.utils.config import load_plugin_configs 43 | return load_plugin_configs(module_name, PluginConfigTypes.POSTAG, True) 44 | 45 | 46 | def get_postag_lang_configs(lang: str, include_dialects: bool = False) -> dict: 47 | """ 48 | Get a dict of plugin names to list valid configurations for the requested 49 | lang. 50 | @param lang: Language to get configurations for 51 | @param include_dialects: consider configurations in different locales 52 | @return: dict {`plugin_name`: `valid_configs`]} 53 | """ 54 | from ovos_plugin_manager.utils.config import get_plugin_language_configs 55 | return get_plugin_language_configs(PluginTypes.POSTAG, lang, 56 | include_dialects) 57 | 58 | 59 | def get_postag_supported_langs() -> dict: 60 | """ 61 | Return a dict of plugin names to list supported languages 62 | @return: dict plugin names to list supported languages 63 | """ 64 | from ovos_plugin_manager.utils.config import get_plugin_supported_languages 65 | return get_plugin_supported_languages(PluginTypes.POSTAG) 66 | 67 | 68 | def get_postag_config(config: dict = None) -> dict: 69 | """ 70 | Get relevant configuration for factory methods 71 | @param config: global Configuration OR plugin class-specific configuration 72 | @return: plugin class-specific configuration 73 | """ 74 | from ovos_plugin_manager.utils.config import get_plugin_config 75 | config = config or Configuration() 76 | return get_plugin_config(config, "postag") 77 | 78 | 79 | class OVOSPosTaggerFactory: 80 | """ reads mycroft.conf and returns the globally configured plugin """ 81 | MAPPINGS = { 82 | # default split at sentence boundaries 83 | # usually helpful in other plugins and included in base class 84 | "dummy": "ovos-postag-plugin-dummy" 85 | } 86 | 87 | @staticmethod 88 | def get_class(config=None): 89 | """Factory method to get a PosTagger engine class based on configuration. 90 | 91 | The configuration file ``mycroft.conf`` contains a ``postag`` section with 92 | the name of a PosTagger module to be read by this method. 93 | 94 | "postag": { 95 | "module": 96 | } 97 | """ 98 | config = get_postag_config(config) 99 | postag_module = config.get("module", "ovos-postag-plugin-dummy") 100 | if postag_module in OVOSPosTaggerFactory.MAPPINGS: 101 | postag_module = OVOSPosTaggerFactory.MAPPINGS[postag_module] 102 | return load_postag_plugin(postag_module) 103 | 104 | @staticmethod 105 | def create(config=None): 106 | """Factory method to create a PosTagger engine based on configuration. 107 | 108 | The configuration file ``mycroft.conf`` contains a ``postag`` section with 109 | the name of a PosTagger module to be read by this method. 110 | 111 | "postag": { 112 | "module": 113 | } 114 | """ 115 | config = config or get_postag_config() 116 | plugin = config.get("module") or "ovos-postag-plugin-dummy" 117 | plugin_config = config.get(plugin) or {} 118 | try: 119 | clazz = OVOSPosTaggerFactory.get_class(config) 120 | return clazz(plugin_config) 121 | except Exception: 122 | LOG.exception(f'Postag plugin {plugin} could not be loaded!') 123 | return PosTagger() 124 | -------------------------------------------------------------------------------- /ovos_plugin_manager/segmentation.py: -------------------------------------------------------------------------------- 1 | from ovos_plugin_manager.utils import normalize_lang, \ 2 | PluginTypes, PluginConfigTypes 3 | from ovos_config import Configuration 4 | from ovos_utils.log import LOG 5 | from ovos_plugin_manager.templates.segmentation import Segmenter 6 | 7 | 8 | def find_segmentation_plugins() -> dict: 9 | """ 10 | Find all installed plugins 11 | @return: dict plugin names to entrypoints 12 | """ 13 | from ovos_plugin_manager.utils import find_plugins 14 | return find_plugins(PluginTypes.UTTERANCE_SEGMENTATION) 15 | 16 | 17 | def load_segmentation_plugin(module_name: str) -> type(Segmenter): 18 | """ 19 | Get an uninstantiated class for the requested module_name 20 | @param module_name: Plugin entrypoint name to load 21 | @return: Uninstantiated class 22 | """ 23 | from ovos_plugin_manager.utils import load_plugin 24 | return load_plugin(module_name, PluginTypes.UTTERANCE_SEGMENTATION) 25 | 26 | 27 | def get_segmentation_configs() -> dict: 28 | """ 29 | Get valid plugin configurations by plugin name 30 | @return: dict plugin names to list of dict configurations 31 | """ 32 | from ovos_plugin_manager.utils.config import load_configs_for_plugin_type 33 | return load_configs_for_plugin_type(PluginTypes.UTTERANCE_SEGMENTATION) 34 | 35 | 36 | def get_segmentation_module_configs(module_name: str) -> dict: 37 | """ 38 | Get valid configurations for the specified plugin 39 | @param module_name: plugin to get configuration for 40 | @return: dict configurations by language (if provided) 41 | """ 42 | from ovos_plugin_manager.utils.config import load_plugin_configs 43 | return load_plugin_configs(module_name, 44 | PluginConfigTypes.UTTERANCE_SEGMENTATION, True) 45 | 46 | 47 | def get_segmentation_lang_configs(lang: str, 48 | include_dialects: bool = False) -> dict: 49 | """ 50 | Get a dict of plugin names to list valid configurations for the requested 51 | lang. 52 | @param lang: Language to get configurations for 53 | @param include_dialects: consider configurations in different locales 54 | @return: dict {`plugin_name`: `valid_configs`]} 55 | """ 56 | from ovos_plugin_manager.utils.config import get_plugin_language_configs 57 | return get_plugin_language_configs(PluginTypes.UTTERANCE_SEGMENTATION, lang, 58 | include_dialects) 59 | 60 | 61 | def get_segmentation_supported_langs(): 62 | """ 63 | Return a dict of plugin names to list supported languages 64 | @return: dict plugin names to list supported languages 65 | """ 66 | from ovos_plugin_manager.utils.config import get_plugin_supported_languages 67 | return get_plugin_supported_languages(PluginTypes.UTTERANCE_SEGMENTATION) 68 | 69 | 70 | def get_segmentation_config(config: dict = None) -> dict: 71 | """ 72 | Get relevant configuration for factory methods 73 | @param config: global Configuration OR plugin class-specific configuration 74 | @return: plugin class-specific configuration 75 | """ 76 | from ovos_plugin_manager.utils.config import get_plugin_config 77 | config = config or Configuration() 78 | return get_plugin_config(config, "segmentation") 79 | 80 | 81 | class OVOSUtteranceSegmenterFactory: 82 | """ reads mycroft.conf and returns the globally configured plugin """ 83 | MAPPINGS = { 84 | # default split at sentence boundaries 85 | # usually helpful in other plugins and included in base class 86 | "dummy": "ovos-segmentation-plugin-quebrafrases" 87 | } 88 | 89 | @staticmethod 90 | def get_class(config=None): 91 | """Factory method to get a Segmenter engine class based on configuration. 92 | 93 | The configuration file ``mycroft.conf`` contains a ``segmentation`` section with 94 | the name of a Segmenter module to be read by this method. 95 | 96 | "segmentation": { 97 | "module": 98 | } 99 | """ 100 | config = get_segmentation_config(config) 101 | segmentation_module = config.get("module", "ovos-segmentation-plugin-quebrafrases") 102 | if segmentation_module in OVOSUtteranceSegmenterFactory.MAPPINGS: 103 | segmentation_module = OVOSUtteranceSegmenterFactory.MAPPINGS[segmentation_module] 104 | return load_segmentation_plugin(segmentation_module) 105 | 106 | @staticmethod 107 | def create(config=None): 108 | """Factory method to create a Segmenter engine based on configuration. 109 | 110 | The configuration file ``mycroft.conf`` contains a ``segmentation`` section with 111 | the name of a Segmenter module to be read by this method. 112 | 113 | "segmentation": { 114 | "module": 115 | } 116 | """ 117 | config = config or get_segmentation_config() 118 | plugin = config.get("module") or "ovos-segmentation-plugin-quebrafrases" 119 | plugin_config = config.get(plugin) or {} 120 | try: 121 | clazz = OVOSUtteranceSegmenterFactory.get_class(config) 122 | return clazz(plugin_config) 123 | except Exception: 124 | LOG.exception(f'Utterance Segmentation plugin {plugin} ' 125 | f'could not be loaded!') 126 | return Segmenter() 127 | -------------------------------------------------------------------------------- /ovos_plugin_manager/stt.py: -------------------------------------------------------------------------------- 1 | from ovos_plugin_manager.utils import normalize_lang, \ 2 | PluginTypes, PluginConfigTypes 3 | from ovos_config import Configuration 4 | from ovos_plugin_manager.utils.config import get_valid_plugin_configs, \ 5 | sort_plugin_configs, get_plugin_config 6 | from ovos_utils.log import LOG 7 | from ovos_plugin_manager.templates.stt import STT, StreamingSTT, StreamThread 8 | 9 | 10 | def find_stt_plugins() -> dict: 11 | """ 12 | Find all installed plugins 13 | @return: dict plugin names to entrypoints 14 | """ 15 | from ovos_plugin_manager.utils import find_plugins 16 | return find_plugins(PluginTypes.STT) 17 | 18 | 19 | def load_stt_plugin(module_name: str) -> type(STT): 20 | """ 21 | Get an uninstantiated class for the requested module_name 22 | @param module_name: Plugin entrypoint name to load 23 | @return: Uninstantiated class 24 | """ 25 | from ovos_plugin_manager.utils import load_plugin 26 | return load_plugin(module_name, PluginTypes.STT) 27 | 28 | 29 | def get_stt_configs() -> dict: 30 | """ 31 | Get a dict of plugin names to valid STT configuration 32 | @return: dict plugin name to dict of str lang to list of dict valid configs 33 | """ 34 | from ovos_plugin_manager.utils.config import load_configs_for_plugin_type 35 | return load_configs_for_plugin_type(PluginTypes.STT) 36 | 37 | 38 | def get_stt_module_configs(module_name: str) -> dict: 39 | """ 40 | Get a dict of lang to list of valid config dicts for a specific plugin 41 | @param module_name: name of plugin to get configurations for 42 | @return: {lang: [list of config dicts]} 43 | """ 44 | from ovos_plugin_manager.utils.config import load_plugin_configs 45 | configs = load_plugin_configs(module_name, PluginConfigTypes.STT, True) 46 | # let's sort by priority key 47 | for k, v in configs.items(): 48 | configs[k] = sorted(v, key=lambda c: c.get("priority", 60)) 49 | return configs 50 | 51 | 52 | def get_stt_lang_configs(lang: str, include_dialects: bool = False) -> dict: 53 | """ 54 | Get a dict of plugins names to sorted list of valid configurations 55 | @param lang: language to get configurations for (i.e. en, en-US) 56 | @param include_dialects: If true, include configs for other locales 57 | (i.e. include en-GB configs for lang=en-US) 58 | @return: dict plugin name to list of valid configs sorted by priority 59 | """ 60 | from ovos_plugin_manager.utils.config import get_plugin_language_configs 61 | matched_configs = get_plugin_language_configs(PluginTypes.STT, lang, 62 | include_dialects) 63 | return sort_plugin_configs(matched_configs) 64 | 65 | 66 | def get_stt_supported_langs() -> dict: 67 | """ 68 | Get a dict of languages to valid configuration options 69 | @return: dict lang to list of plugins that support that lang 70 | """ 71 | from ovos_plugin_manager.utils.config import get_plugin_supported_languages 72 | return get_plugin_supported_languages(PluginTypes.STT) 73 | 74 | 75 | def get_stt_config(config: dict = None, module: str = None) -> dict: 76 | """ 77 | Get relevant configuration for factory methods 78 | @param config: global Configuration OR plugin class-specific configuration 79 | @param module: STT module to get configuration for 80 | @return: plugin class-specific configuration 81 | """ 82 | from ovos_plugin_manager.utils.config import get_plugin_config 83 | stt_config = get_plugin_config(config, "stt", module) 84 | assert stt_config.get('lang') is not None, "expected lang but got None" 85 | return stt_config 86 | 87 | 88 | class OVOSSTTFactory: 89 | """ replicates the base mycroft class, but uses only OPM enabled plugins""" 90 | 91 | @staticmethod 92 | def get_class(config=None): 93 | """Factory method to get a STT engine class based on configuration. 94 | 95 | The configuration file ``mycroft.conf`` contains a ``stt`` section with 96 | the name of a STT module to be read by this method. 97 | 98 | "stt": { 99 | "module": 100 | } 101 | """ 102 | config = get_stt_config(config) 103 | stt_module = config["module"] 104 | return load_stt_plugin(stt_module) 105 | 106 | @staticmethod 107 | def create(config=None): 108 | """Factory method to create a STT engine based on configuration. 109 | 110 | The configuration file ``mycroft.conf`` contains a ``stt`` section with 111 | the name of a STT module to be read by this method. 112 | 113 | "stt": { 114 | "module": 115 | } 116 | """ 117 | stt_config = get_stt_config(config) 118 | try: 119 | clazz = OVOSSTTFactory.get_class(stt_config) 120 | return clazz(stt_config) 121 | except Exception: 122 | LOG.exception('The selected STT plugin could not be loaded!') 123 | raise 124 | -------------------------------------------------------------------------------- /ovos_plugin_manager/templates/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | this module is meant to enable usage of mycroft plugins inside and outside 3 | mycroft, importing from here will make things work as planned in mycroft, 4 | but if outside mycroft things will still work 5 | 6 | The main use case is for plugins to be used across different projects 7 | """ 8 | from ovos_plugin_manager.templates.audio import AudioBackend, RemoteAudioBackend 9 | from ovos_plugin_manager.templates.tts import TTS, TTSValidator 10 | from ovos_plugin_manager.templates.stt import STT, StreamingSTT, StreamThread 11 | from ovos_plugin_manager.templates.hotwords import HotWordEngine 12 | -------------------------------------------------------------------------------- /ovos_plugin_manager/templates/audio2ipa.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | from ovos_utils import classproperty 4 | from ovos_utils.process_utils import RuntimeRequirements 5 | 6 | 7 | class Audio2IPA: 8 | 9 | def __init__(self, config=None): 10 | self.config = config or {} 11 | 12 | @classproperty 13 | def runtime_requirements(cls): 14 | """ skill developers should override this if they do not require connectivity 15 | some examples: 16 | IOT plugin that controls devices via LAN could return: 17 | scans_on_init = True 18 | RuntimeRequirements(internet_before_load=False, 19 | network_before_load=scans_on_init, 20 | requires_internet=False, 21 | requires_network=True, 22 | no_internet_fallback=True, 23 | no_network_fallback=False) 24 | online search plugin with a local cache: 25 | has_cache = False 26 | RuntimeRequirements(internet_before_load=not has_cache, 27 | network_before_load=not has_cache, 28 | requires_internet=True, 29 | requires_network=True, 30 | no_internet_fallback=True, 31 | no_network_fallback=True) 32 | a fully offline plugin: 33 | RuntimeRequirements(internet_before_load=False, 34 | network_before_load=False, 35 | requires_internet=False, 36 | requires_network=False, 37 | no_internet_fallback=True, 38 | no_network_fallback=True) 39 | """ 40 | return RuntimeRequirements(internet_before_load=False, 41 | network_before_load=False, 42 | requires_internet=False, 43 | requires_network=False, 44 | no_internet_fallback=True, 45 | no_network_fallback=True) 46 | 47 | @abc.abstractmethod 48 | def get_ipa(self, audio_data): 49 | raise NotImplementedError 50 | -------------------------------------------------------------------------------- /ovos_plugin_manager/templates/g2p.py: -------------------------------------------------------------------------------- 1 | import enum 2 | import abc 3 | from typing import Set 4 | 5 | from ovos_utils import classproperty 6 | from ovos_utils.lang.phonemes import arpabet2ipa, ipa2arpabet 7 | from ovos_utils.lang.visimes import VISIMES 8 | from ovos_utils.process_utils import RuntimeRequirements 9 | 10 | 11 | class PhonemeAlphabet(str, enum.Enum): 12 | ARPA = "arpa" 13 | IPA = "ipa" 14 | 15 | 16 | class OutOfVocabulary(ValueError): 17 | """ could not get phonemes for word """ 18 | 19 | 20 | class Grapheme2PhonemePlugin: 21 | def __init__(self, config=None): 22 | self.config = config or {} 23 | 24 | @classproperty 25 | def runtime_requirements(self): 26 | """ skill developers should override this if they do not require connectivity 27 | some examples: 28 | IOT plugin that controls devices via LAN could return: 29 | scans_on_init = True 30 | RuntimeRequirements(internet_before_load=False, 31 | network_before_load=scans_on_init, 32 | requires_internet=False, 33 | requires_network=True, 34 | no_internet_fallback=True, 35 | no_network_fallback=False) 36 | online search plugin with a local cache: 37 | has_cache = False 38 | RuntimeRequirements(internet_before_load=not has_cache, 39 | network_before_load=not has_cache, 40 | requires_internet=True, 41 | requires_network=True, 42 | no_internet_fallback=True, 43 | no_network_fallback=True) 44 | a fully offline plugin: 45 | RuntimeRequirements(internet_before_load=False, 46 | network_before_load=False, 47 | requires_internet=False, 48 | requires_network=False, 49 | no_internet_fallback=True, 50 | no_network_fallback=True) 51 | """ 52 | return RuntimeRequirements(internet_before_load=False, 53 | network_before_load=False, 54 | requires_internet=False, 55 | requires_network=False, 56 | no_internet_fallback=True, 57 | no_network_fallback=True) 58 | 59 | @property 60 | def arpa_is_implemented(self): 61 | return self.__class__.get_arpa is not Grapheme2PhonemePlugin.get_arpa 62 | 63 | @property 64 | def ipa_is_implemented(self): 65 | return self.__class__.get_ipa is not Grapheme2PhonemePlugin.get_ipa 66 | 67 | def get_arpa(self, word, lang, ignore_oov=False): 68 | # if ipa is implemented, use it and convert 69 | if self.ipa_is_implemented: 70 | ipa = self.get_ipa(word, lang) 71 | norm = lambda k: k.replace('ˈ', "") 72 | return [ipa2arpabet[norm(p)] for p in ipa 73 | if norm(p) in ipa2arpabet] 74 | if ignore_oov: 75 | return None 76 | raise OutOfVocabulary 77 | 78 | def get_ipa(self, word, lang, ignore_oov=False): 79 | # if arpa is implemented, use it and convert 80 | if self.arpa_is_implemented: 81 | arpa = self.get_arpa(word, lang) 82 | norm = lambda k: k.replace("9", "") \ 83 | .replace("8", "") \ 84 | .replace("7", "") \ 85 | .replace("6", "") \ 86 | .replace("5", "") \ 87 | .replace("4", "") \ 88 | .replace("3", "") \ 89 | .replace("2", "") \ 90 | .replace("1", "") \ 91 | .replace("0", "") 92 | return [arpabet2ipa[norm(p)] for p in arpa 93 | if norm(p) in arpabet2ipa] 94 | if ignore_oov: 95 | return None 96 | raise OutOfVocabulary 97 | 98 | def utterance2arpa(self, utterance, lang, ignore_oov=False): 99 | arpa = [] 100 | for w in utterance.split(): 101 | phones = self.get_arpa(w, lang, ignore_oov) or [] 102 | if not phones and not ignore_oov: 103 | raise OutOfVocabulary(f"unknown word: {w}") 104 | arpa += phones + ["."] 105 | if arpa: 106 | return arpa[:-1] 107 | if ignore_oov: 108 | return None 109 | raise OutOfVocabulary 110 | 111 | def utterance2ipa(self, utterance, lang, ignore_oov=False): 112 | ipa = [] 113 | for w in utterance.split(): 114 | phones = self.get_ipa(w, lang, ignore_oov) or [] 115 | if not phones and not ignore_oov: 116 | raise OutOfVocabulary(f"unknown word: {w}") 117 | ipa += phones + ["."] 118 | if ipa: 119 | return ipa[:-1] 120 | if ignore_oov: 121 | return None 122 | raise OutOfVocabulary 123 | 124 | def utterance2visemes(self, utterance, lang, default_dur=0.4): 125 | arpa = [] 126 | for w in utterance.split(): 127 | phones = self.get_arpa(w, lang) or \ 128 | ['B', 'L', 'AE', '.', 'B', 'L', 'AE'] 129 | arpa += phones + ["."] 130 | return [(VISIMES.get(pho.lower(), '4'), default_dur) for pho in arpa] 131 | 132 | @classproperty 133 | @abc.abstractmethod 134 | def available_languages(cls) -> Set[str]: 135 | """Return languages supported by this G2P implementation in this state 136 | This property should be overridden by the derived class to advertise 137 | what languages that engine supports. 138 | Returns: 139 | set: supported languages 140 | """ 141 | -------------------------------------------------------------------------------- /ovos_plugin_manager/templates/gui.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Dict, Any 2 | 3 | from ovos_bus_client import Message 4 | from ovos_bus_client import MessageBusClient 5 | from ovos_bus_client.apis.gui import GUIInterface 6 | from ovos_config import Configuration 7 | from ovos_utils.log import LOG 8 | 9 | try: 10 | from ovos_gui.homescreen import HomescreenManager 11 | except ImportError as _exc: 12 | 13 | class HomescreenManager: 14 | """Fallback class when ovos-gui is not installed. 15 | 16 | Raises the original ImportError when instantiated to 17 | provide clear error messaging while still allowing type hints to work. 18 | """ 19 | 20 | def __init__(self, *args, **kwargs): 21 | LOG.error("you seem to be running GUIExtensions without ovos-gui installed...") 22 | # raise the original ImportError 23 | raise _exc 24 | 25 | 26 | class GUIExtension: 27 | """ GUI Extension base class 28 | 29 | These plugins are responsible for managing the GUI behaviours 30 | for specific platforms such as homescreen handling 31 | 32 | only 1 GUIExtension is loaded at any time by ovos-gui service 33 | 34 | Args: 35 | bus: MessageBus instance 36 | gui: GUI instance 37 | preload_gui (bool): load GUI skills even if gui client not connected 38 | permanent (bool): disable unloading of GUI skills on gui client disconnections 39 | """ 40 | 41 | def __init__(self, config: Dict[str, Any], 42 | bus: Optional[MessageBusClient] = None, 43 | gui: Optional[GUIInterface] = None, 44 | preload_gui: bool = False, 45 | permanent: bool = False): 46 | 47 | if not bus: 48 | bus = MessageBusClient() 49 | bus.run_in_thread() 50 | bus.connected_event.wait() 51 | self.bus: MessageBusClient = bus 52 | self.gui: GUIInterface = gui or GUIInterface("ovos.shell", bus=self.bus, 53 | config=Configuration().get("gui", {})) 54 | self.preload_gui = preload_gui 55 | self.permanent = permanent 56 | self.config = config 57 | self.homescreen_manager: Optional[HomescreenManager] = None 58 | self.register_bus_events() 59 | 60 | def register_bus_events(self): 61 | self.bus.on("mycroft.gui.screen.close", self.handle_remove_namespace) 62 | 63 | def bind_homescreen(self, homescreen: Optional[HomescreenManager] = None): 64 | if self.config.get("homescreen_supported", False): 65 | if not homescreen: 66 | LOG.debug("Loading HomescreenManager") 67 | homescreen = HomescreenManager(self.bus) 68 | homescreen.daemon = True 69 | homescreen.start() 70 | self.homescreen_manager = homescreen 71 | else: 72 | LOG.info("Homescreen support not configured") 73 | 74 | def handle_remove_namespace(self, message: Message): 75 | get_skill_namespace = message.data.get("skill_id", "") 76 | if get_skill_namespace: 77 | self.bus.emit(Message("gui.clear.namespace", 78 | {"__from": get_skill_namespace})) 79 | -------------------------------------------------------------------------------- /ovos_plugin_manager/templates/hotwords.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from typing import Optional, Dict, Any 3 | 4 | from ovos_utils import classproperty 5 | from ovos_utils.process_utils import RuntimeRequirements 6 | 7 | from ovos_config import Configuration 8 | 9 | 10 | def msec_to_sec(msecs): 11 | """Convert milliseconds to seconds. 12 | 13 | Arguments: 14 | msecs: milliseconds 15 | 16 | Returns: 17 | int: input converted from milliseconds to seconds 18 | """ 19 | return msecs / 1000 20 | 21 | 22 | class HotWordEngine: 23 | """Hotword/Wakeword base class to be implemented by all wake word plugins. 24 | 25 | Arguments: 26 | key_phrase (str): string representation of the wake word 27 | config (dict): Configuration block for the specific wake word 28 | lang (str): language code (BCP-47) 29 | """ 30 | 31 | def __init__(self, key_phrase: str, config: Optional[Dict[str, Any]] = None): 32 | self.key_phrase = str(key_phrase).lower() 33 | self.config = config or Configuration().get("hotwords", {}).get(self.key_phrase, {}) 34 | 35 | @classproperty 36 | def runtime_requirements(cls): 37 | """ skill developers should override this if they do not require connectivity 38 | some examples: 39 | IOT plugin that controls devices via LAN could return: 40 | scans_on_init = True 41 | RuntimeRequirements(internet_before_load=False, 42 | network_before_load=scans_on_init, 43 | requires_internet=False, 44 | requires_network=True, 45 | no_internet_fallback=True, 46 | no_network_fallback=False) 47 | online search plugin with a local cache: 48 | has_cache = False 49 | RuntimeRequirements(internet_before_load=not has_cache, 50 | network_before_load=not has_cache, 51 | requires_internet=True, 52 | requires_network=True, 53 | no_internet_fallback=True, 54 | no_network_fallback=True) 55 | a fully offline plugin: 56 | RuntimeRequirements(internet_before_load=False, 57 | network_before_load=False, 58 | requires_internet=False, 59 | requires_network=False, 60 | no_internet_fallback=True, 61 | no_network_fallback=True) 62 | """ 63 | return RuntimeRequirements(internet_before_load=False, 64 | network_before_load=False, 65 | requires_internet=False, 66 | requires_network=False, 67 | no_internet_fallback=True, 68 | no_network_fallback=True) 69 | 70 | @abc.abstractmethod 71 | def found_wake_word(self) -> bool: 72 | """Check if wake word has been found. 73 | 74 | Checks if the wake word has been found. Should reset any internal 75 | tracking of the wake word state. 76 | 77 | Returns: 78 | bool: True if a wake word was detected, else False 79 | """ 80 | raise NotImplementedError() 81 | 82 | def reset(self): 83 | """ 84 | Reset the WW engine to prepare for a new detection 85 | """ 86 | pass 87 | 88 | @abc.abstractmethod 89 | def update(self, chunk): 90 | """Updates the hotword engine with new audio data. 91 | 92 | The engine should process the data and update internal trigger state. 93 | 94 | Arguments: 95 | chunk (bytes): Chunk of audio data to process 96 | """ 97 | raise NotImplementedError() 98 | 99 | def stop(self): 100 | """ 101 | Perform any actions needed to shut down the wake word engine. 102 | This may include things such as unloading data or shutdown 103 | external processes. 104 | """ 105 | 106 | def shutdown(self): 107 | """ 108 | Compatibility wrapper for `self.stop` 109 | """ 110 | self.stop() 111 | -------------------------------------------------------------------------------- /ovos_plugin_manager/templates/keywords.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from typing import List, Dict, Optional 3 | from ovos_utils.process_utils import RuntimeRequirements 4 | 5 | from ovos_bus_client.session import SessionManager 6 | from ovos_utils import classproperty 7 | from ovos_utils.lang import standardize_lang_tag 8 | 9 | 10 | class KeywordExtractor: 11 | def __init__(self, config=None): 12 | self.config = config or {} 13 | 14 | @classproperty 15 | def runtime_requirements(cls): 16 | """ skill developers should override this if they do not require connectivity 17 | some examples: 18 | IOT plugin that controls devices via LAN could return: 19 | scans_on_init = True 20 | RuntimeRequirements(internet_before_load=False, 21 | network_before_load=scans_on_init, 22 | requires_internet=False, 23 | requires_network=True, 24 | no_internet_fallback=True, 25 | no_network_fallback=False) 26 | online search plugin with a local cache: 27 | has_cache = False 28 | RuntimeRequirements(internet_before_load=not has_cache, 29 | network_before_load=not has_cache, 30 | requires_internet=True, 31 | requires_network=True, 32 | no_internet_fallback=True, 33 | no_network_fallback=True) 34 | a fully offline plugin: 35 | RuntimeRequirements(internet_before_load=False, 36 | network_before_load=False, 37 | requires_internet=False, 38 | requires_network=False, 39 | no_internet_fallback=True, 40 | no_network_fallback=True) 41 | """ 42 | return RuntimeRequirements(internet_before_load=False, 43 | network_before_load=False, 44 | requires_internet=False, 45 | requires_network=False, 46 | no_internet_fallback=True, 47 | no_network_fallback=True) 48 | 49 | @property 50 | def lang(self) -> str: 51 | lang = self.config.get("lang") or SessionManager.get().lang 52 | return standardize_lang_tag(lang) 53 | 54 | @abc.abstractmethod 55 | def extract(self, text: str, lang: Optional[str] = None) -> Dict[str, float]: 56 | raise NotImplementedError() 57 | -------------------------------------------------------------------------------- /ovos_plugin_manager/templates/microphone.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | from dataclasses import dataclass 4 | from typing import Optional 5 | 6 | 7 | @dataclass 8 | class Microphone: 9 | sample_rate: int = 16000 10 | sample_width: int = 2 11 | sample_channels: int = 1 12 | chunk_size: int = 4096 13 | 14 | @property 15 | def frames_per_chunk(self) -> int: 16 | return self.chunk_size // (self.sample_width * self.sample_channels) 17 | 18 | @property 19 | def seconds_per_chunk(self) -> float: 20 | return self.frames_per_chunk / self.sample_rate 21 | 22 | @abc.abstractmethod 23 | def start(self): 24 | raise NotImplementedError() 25 | 26 | @abc.abstractmethod 27 | def read_chunk(self) -> Optional[bytes]: 28 | raise NotImplementedError() 29 | 30 | @abc.abstractmethod 31 | def stop(self): 32 | raise NotImplementedError() 33 | 34 | -------------------------------------------------------------------------------- /ovos_plugin_manager/templates/ocp.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from typing import Optional, List 3 | from ovos_utils import classproperty 4 | from ovos_utils.process_utils import RuntimeRequirements 5 | 6 | 7 | class OCPStreamExtractor: 8 | 9 | def __init__(self, ocp_settings=None): 10 | self.ocp_settings = ocp_settings or {} 11 | 12 | @classproperty 13 | def runtime_requirements(cls): 14 | """ skill developers should override this if they do not require connectivity 15 | some examples: 16 | IOT plugin that controls devices via LAN could return: 17 | scans_on_init = True 18 | RuntimeRequirements(internet_before_load=False, 19 | network_before_load=scans_on_init, 20 | requires_internet=False, 21 | requires_network=True, 22 | no_internet_fallback=True, 23 | no_network_fallback=False) 24 | online search plugin with a local cache: 25 | has_cache = False 26 | RuntimeRequirements(internet_before_load=not has_cache, 27 | network_before_load=not has_cache, 28 | requires_internet=True, 29 | requires_network=True, 30 | no_internet_fallback=True, 31 | no_network_fallback=True) 32 | a fully offline plugin: 33 | RuntimeRequirements(internet_before_load=False, 34 | network_before_load=False, 35 | requires_internet=False, 36 | requires_network=False, 37 | no_internet_fallback=True, 38 | no_network_fallback=True) 39 | """ 40 | return RuntimeRequirements(internet_before_load=False, 41 | network_before_load=False, 42 | requires_internet=True, 43 | requires_network=True, 44 | no_internet_fallback=False, 45 | no_network_fallback=False) 46 | 47 | @classproperty 48 | def supported_seis(cls) -> List[str]: 49 | """ 50 | skills may return results requesting a specific extractor to be used 51 | 52 | plugins should report a StreamExtractorIds (sei) that identifies it can handle certain kinds of requests 53 | 54 | any streams of the format "{sei}//{uri}" can be handled by this plugin 55 | """ 56 | return [] 57 | 58 | def validate_uri(self, uri) -> bool: 59 | """ return True if uri can be handled by this extractor, False otherwise""" 60 | return any([uri.startswith(f"{sei}//") for sei in self.supported_seis]) 61 | 62 | @abc.abstractmethod 63 | def extract_stream(self, uri, video=True) -> Optional[str]: 64 | """ return the real uri that can be played by OCP """ 65 | raise NotImplementedError() 66 | -------------------------------------------------------------------------------- /ovos_plugin_manager/templates/pipeline.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from dataclasses import dataclass 3 | from typing import Optional, Dict, List, Union 4 | 5 | from ovos_bus_client.client import MessageBusClient 6 | from ovos_bus_client.message import Message 7 | from ovos_bus_client.session import Session 8 | from ovos_utils.fakebus import FakeBus 9 | 10 | 11 | @dataclass 12 | class IntentHandlerMatch: 13 | """ 14 | Represents an intent handler match result, expected by ovos-core plugins. 15 | 16 | Attributes: 17 | match_type (str): Name of the service that matched the intent. 18 | match_data (Optional[Dict]): Additional data provided by the intent match. 19 | skill_id (Optional[str]): The skill this handler belongs to. 20 | utterance (Optional[str]): The original utterance triggering the intent. 21 | """ 22 | match_type: str 23 | match_data: Optional[Dict] = None 24 | skill_id: Optional[str] = None 25 | utterance: Optional[str] = None 26 | updated_session: Optional[Session] = None 27 | 28 | 29 | class PipelinePlugin: 30 | """ 31 | Base class for intent matching pipeline plugins. Mainly useful for typing 32 | 33 | Attributes: 34 | config (Dict): Configuration for the plugin. 35 | """ 36 | 37 | def __init__(self, bus: Optional[Union[MessageBusClient, FakeBus]] = None, 38 | config: Optional[Dict] = None): 39 | self.bus = bus or FakeBus() 40 | self.config = config or {} 41 | 42 | @abc.abstractmethod 43 | def match(self, utterances: List[str], lang: str, message: Message) -> Optional[IntentHandlerMatch]: 44 | """ 45 | Match an utterance 46 | 47 | Args: 48 | utterances (List[str]): List of utterances to match. 49 | lang (str): The language of the utterances. 50 | message (Message): The message containing the utterance. 51 | 52 | Returns: 53 | Optional[IntentHandlerMatch]: The match result or None if no match is found. 54 | """ 55 | 56 | 57 | class ConfidenceMatcherPipeline(PipelinePlugin): 58 | """ 59 | Base class for plugins that match utterances with confidence levels, 60 | but do not directly trigger actions. 61 | 62 | Example plugins: adapt, padatious. 63 | 64 | Attributes: 65 | bus (Union[MessageBusClient, FakeBus]): The message bus client for communication. 66 | """ 67 | 68 | def match(self, utterances: List[str], lang: str, message: Message) -> Optional[IntentHandlerMatch]: 69 | return (self.match_high(utterances, lang, message) or 70 | self.match_medium(utterances, lang, message) or 71 | self.match_low(utterances, lang, message)) 72 | 73 | @abc.abstractmethod 74 | def match_high(self, utterances: List[str], lang: str, message: Message) -> Optional[IntentHandlerMatch]: 75 | """ 76 | Match an utterance with high confidence. 77 | 78 | Args: 79 | utterances (List[str]): List of utterances to match. 80 | lang (str): The language of the utterances. 81 | message (Message): The message containing the utterance. 82 | 83 | Returns: 84 | Optional[IntentHandlerMatch]: The match result or None if no match is found. 85 | """ 86 | 87 | @abc.abstractmethod 88 | def match_medium(self, utterances: List[str], lang: str, message: Message) -> Optional[IntentHandlerMatch]: 89 | """ 90 | Match an utterance with medium confidence. 91 | 92 | Args: 93 | utterances (List[str]): List of utterances to match. 94 | lang (str): The language of the utterances. 95 | message (Message): The message containing the utterance. 96 | 97 | Returns: 98 | Optional[IntentHandlerMatch]: The match result or None if no match is found. 99 | """ 100 | 101 | @abc.abstractmethod 102 | def match_low(self, utterances: List[str], lang: str, message: Message) -> Optional[IntentHandlerMatch]: 103 | """ 104 | Match an utterance with low confidence. 105 | 106 | Args: 107 | utterances (List[str]): List of utterances to match. 108 | lang (str): The language of the utterances. 109 | message (Message): The message containing the utterance. 110 | 111 | Returns: 112 | Optional[IntentHandlerMatch]: The match result or None if no match is found. 113 | """ 114 | -------------------------------------------------------------------------------- /ovos_plugin_manager/templates/postag.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from typing import Tuple, List 3 | from ovos_bus_client.session import SessionManager 4 | from ovos_utils import classproperty 5 | from ovos_utils.lang import standardize_lang_tag 6 | from ovos_utils.process_utils import RuntimeRequirements 7 | 8 | 9 | Tag = Tuple[int, int, str, str] # start_idx, end_idx, word, tag 10 | 11 | 12 | class PosTagger: 13 | def __init__(self, config=None): 14 | self.config = config or {} 15 | 16 | @classproperty 17 | def runtime_requirements(cls): 18 | """ skill developers should override this if they do not require connectivity 19 | some examples: 20 | IOT plugin that controls devices via LAN could return: 21 | scans_on_init = True 22 | RuntimeRequirements(internet_before_load=False, 23 | network_before_load=scans_on_init, 24 | requires_internet=False, 25 | requires_network=True, 26 | no_internet_fallback=True, 27 | no_network_fallback=False) 28 | online search plugin with a local cache: 29 | has_cache = False 30 | RuntimeRequirements(internet_before_load=not has_cache, 31 | network_before_load=not has_cache, 32 | requires_internet=True, 33 | requires_network=True, 34 | no_internet_fallback=True, 35 | no_network_fallback=True) 36 | a fully offline plugin: 37 | RuntimeRequirements(internet_before_load=False, 38 | network_before_load=False, 39 | requires_internet=False, 40 | requires_network=False, 41 | no_internet_fallback=True, 42 | no_network_fallback=True) 43 | """ 44 | return RuntimeRequirements(internet_before_load=False, 45 | network_before_load=False, 46 | requires_internet=False, 47 | requires_network=False, 48 | no_internet_fallback=True, 49 | no_network_fallback=True) 50 | 51 | @property 52 | def lang(self) -> str: 53 | lang = self.config.get("lang") or SessionManager.get().lang 54 | return standardize_lang_tag(lang) 55 | 56 | @abc.abstractmethod 57 | def postag(self, spans, lang=None) -> List[Tag]: 58 | raise NotImplementedError() 59 | -------------------------------------------------------------------------------- /ovos_plugin_manager/templates/segmentation.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from typing import Optional, List 3 | 4 | from ovos_bus_client.session import SessionManager 5 | from ovos_utils import classproperty 6 | from ovos_utils.lang import standardize_lang_tag 7 | from ovos_utils.process_utils import RuntimeRequirements 8 | 9 | 10 | class Segmenter: 11 | def __init__(self, config=None): 12 | self.config = config or {} 13 | 14 | @classproperty 15 | def runtime_requirements(cls): 16 | """ skill developers should override this if they do not require connectivity 17 | some examples: 18 | IOT plugin that controls devices via LAN could return: 19 | scans_on_init = True 20 | RuntimeRequirements(internet_before_load=False, 21 | network_before_load=scans_on_init, 22 | requires_internet=False, 23 | requires_network=True, 24 | no_internet_fallback=True, 25 | no_network_fallback=False) 26 | online search plugin with a local cache: 27 | has_cache = False 28 | RuntimeRequirements(internet_before_load=not has_cache, 29 | network_before_load=not has_cache, 30 | requires_internet=True, 31 | requires_network=True, 32 | no_internet_fallback=True, 33 | no_network_fallback=True) 34 | a fully offline plugin: 35 | RuntimeRequirements(internet_before_load=False, 36 | network_before_load=False, 37 | requires_internet=False, 38 | requires_network=False, 39 | no_internet_fallback=True, 40 | no_network_fallback=True) 41 | """ 42 | return RuntimeRequirements(internet_before_load=False, 43 | network_before_load=False, 44 | requires_internet=False, 45 | requires_network=False, 46 | no_internet_fallback=True, 47 | no_network_fallback=True) 48 | 49 | @property 50 | def lang(self) -> str: 51 | lang = self.config.get("lang") or SessionManager.get().lang 52 | return standardize_lang_tag(lang) 53 | 54 | @abc.abstractmethod 55 | def segment(self, text: str, lang: Optional[str] = None) -> List[str]: 56 | raise NotImplementedError() 57 | -------------------------------------------------------------------------------- /ovos_plugin_manager/templates/tokenization.py: -------------------------------------------------------------------------------- 1 | from ovos_bus_client.session import SessionManager 2 | from ovos_utils import classproperty 3 | from ovos_utils.lang import standardize_lang_tag 4 | from ovos_utils.process_utils import RuntimeRequirements 5 | import abc 6 | 7 | 8 | class Tokenizer: 9 | def __init__(self, config=None): 10 | self.config = config or {} 11 | 12 | @classproperty 13 | def runtime_requirements(cls): 14 | """ skill developers should override this if they do not require connectivity 15 | some examples: 16 | IOT plugin that controls devices via LAN could return: 17 | scans_on_init = True 18 | RuntimeRequirements(internet_before_load=False, 19 | network_before_load=scans_on_init, 20 | requires_internet=False, 21 | requires_network=True, 22 | no_internet_fallback=True, 23 | no_network_fallback=False) 24 | online search plugin with a local cache: 25 | has_cache = False 26 | RuntimeRequirements(internet_before_load=not has_cache, 27 | network_before_load=not has_cache, 28 | requires_internet=True, 29 | requires_network=True, 30 | no_internet_fallback=True, 31 | no_network_fallback=True) 32 | a fully offline plugin: 33 | RuntimeRequirements(internet_before_load=False, 34 | network_before_load=False, 35 | requires_internet=False, 36 | requires_network=False, 37 | no_internet_fallback=True, 38 | no_network_fallback=True) 39 | """ 40 | return RuntimeRequirements(internet_before_load=False, 41 | network_before_load=False, 42 | requires_internet=False, 43 | requires_network=False, 44 | no_internet_fallback=True, 45 | no_network_fallback=True) 46 | 47 | @property 48 | def lang(self) -> str: 49 | lang = self.config.get("lang") or SessionManager.get().lang 50 | return standardize_lang_tag(lang) 51 | 52 | @abc.abstractmethod 53 | def span_tokenize(self, text, lang=None) -> list[tuple[int, int, str]]: 54 | raise NotImplementedError 55 | 56 | def tokenize(self, text, lang=None) -> list[str]: 57 | return [s[-1] for s in self.span_tokenize(text, lang or self.lang)] 58 | 59 | @staticmethod 60 | def restore_spans(spans): 61 | # restore sentence from spans 62 | sentence = "" 63 | for start, end, token in spans: 64 | if start > len(sentence): 65 | sentence += " " 66 | sentence += token 67 | return sentence 68 | -------------------------------------------------------------------------------- /ovos_plugin_manager/templates/triples.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | from typing import Tuple, List, Iterable 4 | 5 | 6 | class TriplesExtractor: 7 | 8 | def __init__(self, config=None): 9 | self.config = config or {} 10 | self.first_person_token = self.config.get("first_person_token", "USER") 11 | 12 | @abc.abstractmethod 13 | def extract_triples(self, documents: List[str]) -> Iterable[Tuple[str, str, str]]: 14 | """Extract semantic triples from a list of documents.""" 15 | -------------------------------------------------------------------------------- /ovos_plugin_manager/templates/vad.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import collections 3 | from typing import Iterable 4 | 5 | from ovos_utils import classproperty 6 | from ovos_utils.process_utils import RuntimeRequirements 7 | 8 | from ovos_config import Configuration 9 | 10 | 11 | class AudioFrame: 12 | """Represents a "frame" of audio data.""" 13 | 14 | def __init__(self, audio: bytes, timestamp: float, duration: int): 15 | self.bytes = audio 16 | self.timestamp = timestamp 17 | self.duration = duration 18 | 19 | 20 | class VADEngine: 21 | def __init__(self, config=None, sample_rate=None): 22 | self.config_core = Configuration() 23 | self.config = config or {} 24 | self.sample_rate = sample_rate or \ 25 | self.config_core.get("listener", {}).get("sample_rate", 16000) 26 | 27 | self.padding_duration_ms = self.config.get("padding_duration_ms", 300) 28 | self.frame_duration_ms = self.config.get("frame_duration_ms", 30) 29 | self.thresh = self.config.get("thresh", 0.8) 30 | self.num_padding_frames = int(self.padding_duration_ms / self.frame_duration_ms) 31 | 32 | @classproperty 33 | def runtime_requirements(cls): 34 | """ skill developers should override this if they do not require connectivity 35 | some examples: 36 | IOT plugin that controls devices via LAN could return: 37 | scans_on_init = True 38 | RuntimeRequirements(internet_before_load=False, 39 | network_before_load=scans_on_init, 40 | requires_internet=False, 41 | requires_network=True, 42 | no_internet_fallback=True, 43 | no_network_fallback=False) 44 | online search plugin with a local cache: 45 | has_cache = False 46 | RuntimeRequirements(internet_before_load=not has_cache, 47 | network_before_load=not has_cache, 48 | requires_internet=True, 49 | requires_network=True, 50 | no_internet_fallback=True, 51 | no_network_fallback=True) 52 | a fully offline plugin: 53 | RuntimeRequirements(internet_before_load=False, 54 | network_before_load=False, 55 | requires_internet=False, 56 | requires_network=False, 57 | no_internet_fallback=True, 58 | no_network_fallback=True) 59 | """ 60 | return RuntimeRequirements(internet_before_load=False, 61 | network_before_load=False, 62 | requires_internet=False, 63 | requires_network=False, 64 | no_internet_fallback=True, 65 | no_network_fallback=True) 66 | 67 | def _frame_generator(self, audio: bytes) -> Iterable[AudioFrame]: 68 | """Generates audio frames from PCM audio data. 69 | Takes the desired frame duration in milliseconds, the PCM data, and 70 | the sample rate. 71 | Yields Frames of the requested duration. 72 | """ 73 | n = int(self.sample_rate * (self.frame_duration_ms / 1000.0) * 2) 74 | offset = 0 75 | timestamp = 0.0 76 | duration = (float(n) / self.sample_rate) / 2.0 77 | 78 | while offset + n <= len(audio): 79 | yield AudioFrame(audio[offset:offset + n], timestamp, duration) 80 | timestamp += duration 81 | offset += n 82 | 83 | def extract_speech(self, audio: bytes) -> bytes: 84 | """returns the audio data with speech only, removing all noise before and after speech""" 85 | # We use a deque for our sliding window/ring buffer. 86 | ring_buffer = collections.deque(maxlen=self.num_padding_frames) 87 | triggered = False 88 | is_speech = False 89 | voiced_frames = [] 90 | 91 | for frame in self._frame_generator(audio): 92 | 93 | is_speech = not self.is_silence(frame.bytes) 94 | 95 | if not triggered: 96 | ring_buffer.append((frame, is_speech)) 97 | num_voiced = len([f for f, speech in ring_buffer if speech]) 98 | # If we're NOTTRIGGERED and more than 90% of the frames in 99 | # the ring buffer are voiced frames, then enter the 100 | # TRIGGERED state. 101 | if num_voiced > self.thresh * ring_buffer.maxlen: 102 | triggered = True 103 | # We want to yield all the audio we see from now until 104 | # we are NOTTRIGGERED, but we have to start with the 105 | # audio that's already in the ring buffer. 106 | for f, s in ring_buffer: 107 | voiced_frames.append(f) 108 | ring_buffer.clear() 109 | else: 110 | # We're in the TRIGGERED state, so collect the audio data 111 | # and add it to the ring buffer. 112 | voiced_frames.append(frame) 113 | ring_buffer.append((frame, is_speech)) 114 | num_unvoiced = len([f for f, speech in ring_buffer if not speech]) 115 | 116 | # If more than 90% of the frames in the ring buffer are 117 | # unvoiced, then enter NOTTRIGGERED and yield whatever 118 | # audio we've collected. 119 | if num_unvoiced > self.thresh * ring_buffer.maxlen: 120 | return b''.join([f.bytes for f in voiced_frames]) 121 | 122 | @abc.abstractmethod 123 | def is_silence(self, chunk) -> bool: 124 | # return True or False 125 | return False 126 | 127 | def reset(self): 128 | pass 129 | -------------------------------------------------------------------------------- /ovos_plugin_manager/text_transformers.py: -------------------------------------------------------------------------------- 1 | from ovos_plugin_manager.utils import normalize_lang, \ 2 | PluginTypes, PluginConfigTypes 3 | from ovos_plugin_manager.templates.transformers import UtteranceTransformer 4 | 5 | 6 | def find_utterance_transformer_plugins() -> dict: 7 | """ 8 | Find all installed plugins 9 | @return: dict plugin names to entrypoints 10 | """ 11 | from ovos_plugin_manager.utils import find_plugins 12 | return find_plugins(PluginTypes.UTTERANCE_TRANSFORMER) 13 | 14 | 15 | def load_utterance_transformer_plugin(module_name: str) -> \ 16 | type(UtteranceTransformer): 17 | """ 18 | Get an uninstantiated class for the requested module_name 19 | @param module_name: Plugin entrypoint name to load 20 | @return: Uninstantiated class 21 | """ 22 | from ovos_plugin_manager.utils import load_plugin 23 | return load_plugin(module_name, PluginTypes.UTTERANCE_TRANSFORMER) 24 | 25 | 26 | def get_utterance_transformer_configs() -> dict: 27 | """ 28 | Get valid plugin configurations by plugin name 29 | @return: dict plugin names to list of dict configurations 30 | """ 31 | from ovos_plugin_manager.utils.config import load_configs_for_plugin_type 32 | return load_configs_for_plugin_type(PluginTypes.UTTERANCE_TRANSFORMER) 33 | 34 | 35 | def get_utterance_transformer_module_configs(module_name: str) -> dict: 36 | """ 37 | Get valid configurations for the specified plugin 38 | @param module_name: plugin to get configuration for 39 | @return: dict configurations by language (if provided) 40 | """ 41 | from ovos_plugin_manager.utils.config import load_plugin_configs 42 | # utterance plugins return {lang: [list of config dicts]} 43 | return load_plugin_configs(module_name, 44 | PluginConfigTypes.UTTERANCE_TRANSFORMER, True) 45 | 46 | 47 | def get_utterance_transformer_lang_configs(lang: str, 48 | include_dialects: bool = False) -> \ 49 | dict: 50 | """ 51 | Get a dict of plugin names to list valid configurations for the requested 52 | lang. 53 | @param lang: Language to get configurations for 54 | @param include_dialects: consider configurations in different locales 55 | @return: dict {`plugin_name`: `valid_configs`]} 56 | """ 57 | from ovos_plugin_manager.utils.config import get_plugin_language_configs 58 | return get_plugin_language_configs(PluginTypes.UTTERANCE_TRANSFORMER, lang, 59 | include_dialects) 60 | 61 | 62 | def get_utterance_transformer_supported_langs() -> dict: 63 | """ 64 | Return a dict of plugin names to list supported languages 65 | @return: dict plugin names to list supported languages 66 | """ 67 | from ovos_plugin_manager.utils.config import get_plugin_supported_languages 68 | return get_plugin_supported_languages(PluginTypes.UTTERANCE_TRANSFORMER) 69 | 70 | -------------------------------------------------------------------------------- /ovos_plugin_manager/thirdparty/__init__.py: -------------------------------------------------------------------------------- 1 | # any code that isnt OVOS original should live in this submodule to ensure proper attribution 2 | -------------------------------------------------------------------------------- /ovos_plugin_manager/tokenization.py: -------------------------------------------------------------------------------- 1 | from ovos_plugin_manager.utils import normalize_lang, \ 2 | PluginTypes, PluginConfigTypes 3 | from ovos_config import Configuration 4 | from ovos_utils.log import LOG 5 | from ovos_plugin_manager.templates.tokenization import Tokenizer 6 | 7 | 8 | def find_tokenization_plugins() -> dict: 9 | """ 10 | Find all installed plugins 11 | @return: dict plugin names to entrypoints 12 | """ 13 | from ovos_plugin_manager.utils import find_plugins 14 | return find_plugins(PluginTypes.TOKENIZATION) 15 | 16 | 17 | def load_tokenization_plugin(module_name: str) -> type(Tokenizer): 18 | """ 19 | Get an uninstantiated class for the requested module_name 20 | @param module_name: Plugin entrypoint name to load 21 | @return: Uninstantiated class 22 | """ 23 | from ovos_plugin_manager.utils import load_plugin 24 | return load_plugin(module_name, PluginTypes.TOKENIZATION) 25 | 26 | 27 | def get_tokenization_configs() -> dict: 28 | """ 29 | Get valid plugin configurations by plugin name 30 | @return: dict plugin names to list of dict configurations 31 | """ 32 | from ovos_plugin_manager.utils.config import load_configs_for_plugin_type 33 | return load_configs_for_plugin_type(PluginTypes.TOKENIZATION) 34 | 35 | 36 | def get_tokenization_module_configs(module_name: str) -> dict: 37 | """ 38 | Get valid configurations for the specified plugin 39 | @param module_name: plugin to get configuration for 40 | @return: dict configurations by language (if provided) 41 | """ 42 | # TOKENIZATION plugins return {lang: [list of config dicts]} 43 | from ovos_plugin_manager.utils.config import load_plugin_configs 44 | return load_plugin_configs(module_name, 45 | PluginConfigTypes.TOKENIZATION, True) 46 | 47 | 48 | def get_tokenization_lang_configs(lang: str, 49 | include_dialects: bool = False) -> dict: 50 | """ 51 | Get a dict of plugin names to list valid configurations for the requested 52 | lang. 53 | @param lang: Language to get configurations for 54 | @param include_dialects: consider configurations in different locales 55 | @return: dict {`plugin_name`: `valid_configs`]} 56 | """ 57 | from ovos_plugin_manager.utils.config import get_plugin_language_configs 58 | return get_plugin_language_configs(PluginTypes.TOKENIZATION, lang, 59 | include_dialects) 60 | 61 | 62 | def get_tokenization_supported_langs() -> dict: 63 | """ 64 | Return a dict of plugin names to list supported languages 65 | @return: dict plugin names to list supported languages 66 | """ 67 | from ovos_plugin_manager.utils.config import get_plugin_supported_languages 68 | return get_plugin_supported_languages(PluginTypes.TOKENIZATION) 69 | 70 | 71 | def get_tokenization_config(config: dict = None) -> dict: 72 | """ 73 | Get relevant configuration for factory methods 74 | @param config: global Configuration OR plugin class-specific configuration 75 | @return: plugin class-specific configuration 76 | """ 77 | from ovos_plugin_manager.utils.config import get_plugin_config 78 | config = config or Configuration() 79 | return get_plugin_config(config, "tokenization") 80 | 81 | 82 | class OVOSTokenizerFactory: 83 | """ reads mycroft.conf and returns the globally configured plugin """ 84 | MAPPINGS = { 85 | # default split at sentence boundaries 86 | # usually helpful in other plugins and included in base class 87 | "dummy": "ovos-tokenization-plugin-quebrafrases" 88 | } 89 | 90 | @staticmethod 91 | def get_class(config=None): 92 | """Factory method to get a Tokenizer engine class based on configuration. 93 | 94 | The configuration file ``mycroft.conf`` contains a ``tokenization`` section with 95 | the name of a Tokenizer module to be read by this method. 96 | 97 | "tokenization": { 98 | "module": 99 | } 100 | """ 101 | config = get_tokenization_config(config) 102 | tokenization_module = config.get("module", "ovos-tokenization-plugin-quebrafrases") 103 | if tokenization_module in OVOSTokenizerFactory.MAPPINGS: 104 | tokenization_module = OVOSTokenizerFactory.MAPPINGS[tokenization_module] 105 | return load_tokenization_plugin(tokenization_module) 106 | 107 | @staticmethod 108 | def create(config=None): 109 | """Factory method to create a Tokenizer engine based on configuration. 110 | 111 | The configuration file ``mycroft.conf`` contains a ``tokenization`` section with 112 | the name of a Tokenizer module to be read by this method. 113 | 114 | "tokenization": { 115 | "module": 116 | } 117 | """ 118 | config = config or get_tokenization_config() 119 | plugin = config.get("module") or "ovos-tokenization-plugin-quebrafrases" 120 | plugin_config = config.get(plugin) or {} 121 | try: 122 | clazz = OVOSTokenizerFactory.get_class(config) 123 | return clazz(plugin_config) 124 | except Exception: 125 | LOG.exception(f'Tokenizer plugin {plugin} could not be loaded!') 126 | return Tokenizer() 127 | -------------------------------------------------------------------------------- /ovos_plugin_manager/triples.py: -------------------------------------------------------------------------------- 1 | from ovos_plugin_manager.templates.triples import TriplesExtractor 2 | from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes 3 | 4 | 5 | def find_triples_plugins() -> dict: 6 | """ 7 | Find all installed plugins 8 | @return: dict plugin names to entrypoints 9 | """ 10 | from ovos_plugin_manager.utils import find_plugins 11 | return find_plugins(PluginTypes.COREFERENCE_SOLVER) 12 | 13 | 14 | def load_triples_plugin(module_name: str) -> type(TriplesExtractor): 15 | """ 16 | Get an uninstantiated class for the requested module_name 17 | @param module_name: Plugin entrypoint name to load 18 | @return: Uninstantiated class 19 | """ 20 | from ovos_plugin_manager.utils import load_plugin 21 | return load_plugin(module_name, PluginTypes.COREFERENCE_SOLVER) 22 | 23 | 24 | def get_triples_configs() -> dict: 25 | """ 26 | Get valid plugin configurations by plugin name 27 | @return: dict plugin names to list of dict configurations 28 | """ 29 | from ovos_plugin_manager.utils.config import load_configs_for_plugin_type 30 | return load_configs_for_plugin_type(PluginTypes.TRIPLES) 31 | 32 | 33 | def get_triples_module_configs(module_name: str) -> dict: 34 | """ 35 | Get valid configuration for the specified plugin 36 | @param module_name: plugin to get configuration for 37 | @return: dict configuration (if provided) 38 | """ 39 | from ovos_plugin_manager.utils.config import load_plugin_configs 40 | return load_plugin_configs(module_name, PluginConfigTypes.TRIPLES, True) 41 | -------------------------------------------------------------------------------- /ovos_plugin_manager/vad.py: -------------------------------------------------------------------------------- 1 | from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes 2 | from ovos_config import Configuration 3 | from ovos_utils.log import LOG 4 | from ovos_plugin_manager.templates.vad import VADEngine 5 | 6 | 7 | def find_vad_plugins() -> dict: 8 | """ 9 | Find all installed plugins 10 | @return: dict plugin names to entrypoints 11 | """ 12 | from ovos_plugin_manager.utils import find_plugins 13 | return find_plugins(PluginTypes.VAD) 14 | 15 | 16 | def load_vad_plugin(module_name: str) -> type(VADEngine): 17 | """ 18 | Get an uninstantiated class for the requested module_name 19 | @param module_name: Plugin entrypoint name to load 20 | @return: Uninstantiated class 21 | """ 22 | from ovos_plugin_manager.utils import load_plugin 23 | return load_plugin(module_name, PluginTypes.VAD) 24 | 25 | 26 | def get_vad_configs() -> dict: 27 | """ 28 | Get valid plugin configurations by plugin name 29 | @return: dict plugin names to list of dict configurations 30 | """ 31 | from ovos_plugin_manager.utils.config import load_configs_for_plugin_type 32 | return load_configs_for_plugin_type(PluginTypes.VAD) 33 | 34 | 35 | def get_vad_module_configs(module_name: str) -> dict: 36 | """ 37 | Get valid configurations for the specified plugin 38 | @param module_name: plugin to get configuration for 39 | @return: dict configurations by language (if provided) 40 | """ 41 | # VAD plugins return [list of config dicts] or {module_name: [list of config dicts]} 42 | from ovos_plugin_manager.utils.config import load_plugin_configs 43 | cfgs = load_plugin_configs(module_name, 44 | PluginConfigTypes.VAD) 45 | return {module_name: cfgs} if isinstance(cfgs, list) else cfgs 46 | 47 | 48 | def get_vad_config(config: dict = None) -> dict: 49 | """ 50 | Get relevant configuration for factory methods 51 | @param config: global Configuration OR plugin class-specific configuration 52 | @return: plugin class-specific configuration 53 | """ 54 | from ovos_plugin_manager.utils.config import get_plugin_config 55 | config = config or Configuration() 56 | if "listener" in config and "VAD" not in config: 57 | config = get_plugin_config(config, "listener") 58 | if "VAD" in config: 59 | config = get_plugin_config(config, "VAD") 60 | return config 61 | 62 | 63 | class OVOSVADFactory: 64 | 65 | @staticmethod 66 | def get_class(config=None): 67 | """Factory method to get a VAD engine class based on configuration. 68 | 69 | The configuration file ``mycroft.conf`` contains a ``vad`` section with 70 | the name of a VAD module to be read by this method. 71 | 72 | "VAD": { 73 | "module": 74 | } 75 | """ 76 | config = get_vad_config(config) 77 | vad_module = config.get("module") 78 | if not vad_module: 79 | raise ValueError(f"VAD Plugin not configured in: {config}") 80 | if vad_module == "dummy": 81 | return VADEngine 82 | return load_vad_plugin(vad_module) 83 | 84 | @classmethod 85 | def create(cls, config=None): 86 | """Factory method to create a VAD engine based on configuration. 87 | 88 | The configuration file ``mycroft.conf`` contains a ``VAD`` section with 89 | the name of a VAD module to be read by this method. 90 | 91 | "VAD": { 92 | "module": 93 | } 94 | """ 95 | config = config or Configuration() 96 | if "listener" in config: 97 | config = config["listener"] 98 | if "VAD" in config: 99 | config = config["VAD"] 100 | plugin = config.get("module") 101 | if not plugin: 102 | raise ValueError(f"VAD Plugin not configured in: {config}") 103 | 104 | plugin_config = config.get(plugin, {}) 105 | fallback = plugin_config.get("fallback_module") 106 | 107 | try: 108 | clazz = OVOSVADFactory.get_class(config) 109 | return clazz(plugin_config) 110 | except Exception: 111 | LOG.exception(f'VAD plugin {plugin} could not be loaded!') 112 | if fallback in config and fallback != plugin: 113 | LOG.info(f"Attempting to load fallback plugin instead: {fallback}") 114 | config["module"] = fallback 115 | return cls.create(config) 116 | raise 117 | -------------------------------------------------------------------------------- /ovos_plugin_manager/version.py: -------------------------------------------------------------------------------- 1 | # START_VERSION_BLOCK 2 | VERSION_MAJOR = 1 3 | VERSION_MINOR = 0 4 | VERSION_BUILD = 3 5 | VERSION_ALPHA = 0 6 | # END_VERSION_BLOCK 7 | -------------------------------------------------------------------------------- /requirements/requirements.txt: -------------------------------------------------------------------------------- 1 | ovos-utils>=0.2.1,<1.0.0 2 | ovos_bus_client>=0.0.8,<2.0.0 3 | ovos-config>=0.0.12,<2.0.0 4 | combo_lock~=0.2 5 | requests~=2.26 6 | quebra_frases 7 | langcodes~=3.3.0 8 | 9 | # see https://github.com/pypa/setuptools/issues/1471 10 | importlib_metadata 11 | 12 | # needed explicitly since python 3.12 13 | setuptools 14 | -------------------------------------------------------------------------------- /requirements/test.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | pytest-timeout 3 | pytest-cov 4 | ovos-translate-server-plugin 5 | ovos-classifiers 6 | ovos-utils>=0.1.0a8 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup 3 | 4 | BASEDIR = os.path.abspath(os.path.dirname(__file__)) 5 | 6 | 7 | def get_version(): 8 | """ Find the version of the package""" 9 | version_file = os.path.join(BASEDIR, 'ovos_plugin_manager', 'version.py') 10 | major, minor, build, alpha = (None, None, None, None) 11 | with open(version_file) as f: 12 | for line in f: 13 | if 'VERSION_MAJOR' in line: 14 | major = line.split('=')[1].strip() 15 | elif 'VERSION_MINOR' in line: 16 | minor = line.split('=')[1].strip() 17 | elif 'VERSION_BUILD' in line: 18 | build = line.split('=')[1].strip() 19 | elif 'VERSION_ALPHA' in line: 20 | alpha = line.split('=')[1].strip() 21 | 22 | if ((major and minor and build and alpha) or 23 | '# END_VERSION_BLOCK' in line): 24 | break 25 | version = f"{major}.{minor}.{build}" 26 | if alpha and int(alpha) > 0: 27 | version += f"a{alpha}" 28 | return version 29 | 30 | 31 | def package_files(directory): 32 | paths = [] 33 | for (path, directories, filenames) in os.walk(directory): 34 | for filename in filenames: 35 | paths.append(os.path.join('..', path, filename)) 36 | return paths 37 | 38 | 39 | def required(requirements_file): 40 | """ Read requirements file and remove comments and empty lines. """ 41 | with open(os.path.join(BASEDIR, requirements_file), 'r') as f: 42 | requirements = f.read().splitlines() 43 | if 'MYCROFT_LOOSE_REQUIREMENTS' in os.environ: 44 | print('USING LOOSE REQUIREMENTS!') 45 | requirements = [r.replace('==', '>=').replace('~=', '>=') for r in requirements] 46 | return [pkg for pkg in requirements 47 | if pkg.strip() and not pkg.startswith("#")] 48 | 49 | 50 | with open(os.path.join(BASEDIR, "README.md"), "r") as f: 51 | long_description = f.read() 52 | 53 | setup( 54 | name='ovos-plugin-manager', 55 | version=get_version(), 56 | packages=['ovos_plugin_manager', 57 | 'ovos_plugin_manager.templates', 58 | 'ovos_plugin_manager.utils', 59 | 'ovos_plugin_manager.thirdparty', 60 | 'ovos_plugin_manager.hardware', 61 | 'ovos_plugin_manager.hardware.led'], 62 | url='https://github.com/OpenVoiceOS/OVOS-plugin-manager', 63 | license='Apache-2.0', 64 | author='jarbasAi', 65 | install_requires=required("requirements/requirements.txt"), 66 | package_data={'': package_files('ovos-plugin-manager')}, 67 | author_email='jarbas@openvoiceos.com', 68 | description='OpenVoiceOS plugin manager', 69 | long_description=long_description, 70 | long_description_content_type="text/markdown" 71 | ) 72 | -------------------------------------------------------------------------------- /test/unittests/test_audio.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import patch 3 | 4 | from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes 5 | 6 | 7 | class TestAudioTemplate(unittest.TestCase): 8 | def test_audio_backend(self): 9 | from ovos_plugin_manager.templates.media import AudioPlayerBackend 10 | # TODO 11 | 12 | def test_remote_audio_backend(self): 13 | from ovos_plugin_manager.templates.media import RemoteAudioPlayerBackend 14 | 15 | 16 | class TestAudio(unittest.TestCase): 17 | PLUGIN_TYPE = PluginTypes.AUDIO 18 | CONFIG_TYPE = PluginConfigTypes.AUDIO 19 | TEST_CONFIG = {"test": True} 20 | CONFIG_SECTION = "" 21 | 22 | @patch("ovos_plugin_manager.utils.find_plugins") 23 | def test_find_plugins(self, find_plugins): 24 | from ovos_plugin_manager.audio import find_audio_service_plugins 25 | find_audio_service_plugins() 26 | find_plugins.assert_called_once_with(self.PLUGIN_TYPE) 27 | 28 | @patch("ovos_plugin_manager.utils.config.load_configs_for_plugin_type") 29 | def test_get_configs(self, load_configs): 30 | from ovos_plugin_manager.audio import get_audio_service_configs 31 | get_audio_service_configs() 32 | load_configs.assert_called_once_with(self.PLUGIN_TYPE) 33 | 34 | @patch("ovos_plugin_manager.utils.config.load_plugin_configs") 35 | def test_get_module_configs(self, load_plugin_configs): 36 | from ovos_plugin_manager.audio import get_audio_service_module_configs 37 | get_audio_service_module_configs("test_mod") 38 | load_plugin_configs.assert_called_once_with("test_mod", 39 | self.CONFIG_TYPE) 40 | 41 | def test_setup_audio_service(self): 42 | from ovos_plugin_manager.audio import setup_audio_service 43 | # TODO 44 | 45 | def test_load_audio_service_plugins(self): 46 | from ovos_plugin_manager.audio import load_audio_service_plugins 47 | # TODO 48 | -------------------------------------------------------------------------------- /test/unittests/test_audio2ipa.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import patch 3 | 4 | from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes 5 | 6 | 7 | class TestAudio2IPATemplate(unittest.TestCase): 8 | def test_audio2ipa(self): 9 | from ovos_plugin_manager.templates.audio2ipa import Audio2IPA 10 | # TODO 11 | 12 | 13 | class TestAudio2IPA(unittest.TestCase): 14 | PLUGIN_TYPE = PluginTypes.AUDIO2IPA 15 | CONFIG_TYPE = PluginConfigTypes.AUDIO2IPA 16 | TEST_CONFIG = {"test": True} 17 | CONFIG_SECTION = "audio2ipa" 18 | 19 | @patch("ovos_plugin_manager.utils.find_plugins") 20 | def test_find_plugins(self, find_plugins): 21 | from ovos_plugin_manager.audio2ipa import find_audio2ipa_plugins 22 | find_audio2ipa_plugins() 23 | find_plugins.assert_called_once_with(self.PLUGIN_TYPE) 24 | 25 | @patch("ovos_plugin_manager.utils.load_plugin") 26 | def test_load_plugin(self, load_plugin): 27 | from ovos_plugin_manager.audio2ipa import load_audio2ipa_plugin 28 | load_audio2ipa_plugin("test_mod") 29 | load_plugin.assert_called_once_with("test_mod", self.PLUGIN_TYPE) 30 | 31 | @patch("ovos_plugin_manager.utils.config.get_plugin_config") 32 | def test_get_config(self, get_config): 33 | from ovos_plugin_manager.audio2ipa import get_audio2ipa_config 34 | get_audio2ipa_config(self.TEST_CONFIG) 35 | get_config.assert_called_once_with(self.TEST_CONFIG, 36 | self.CONFIG_SECTION) 37 | -------------------------------------------------------------------------------- /test/unittests/test_audio_transformers.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import time 3 | from unittest.mock import patch 4 | 5 | from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes, ReadWriteStream 6 | 7 | 8 | class TestReadWriteStream(unittest.TestCase): 9 | def test_write_and_read(self): 10 | # Initialize the stream 11 | stream = ReadWriteStream() 12 | 13 | # Write some data to the stream 14 | stream.write(b'1234567890abcdefghijklmnopqrstuvwxyz') 15 | 16 | # Read some data from the stream 17 | self.assertEqual(stream.read(10), b'1234567890') 18 | 19 | # Read more data with a timeout 20 | self.assertEqual(stream.read(5, timeout=1), b'abcde') 21 | 22 | def test_clear_buffer(self): 23 | # Initialize the stream 24 | stream = ReadWriteStream() 25 | 26 | # Write some data to the stream 27 | stream.write(b'1234567890abcdefghijklmnopqrstuvwxyz') 28 | 29 | # Clear the buffer 30 | stream.clear() 31 | self.assertEqual(len(stream), 0) 32 | 33 | def test_write_with_max_size(self): 34 | # Initialize the stream with a max size of 20 bytes 35 | stream = ReadWriteStream(max_size=20) 36 | 37 | # Write some data to the stream 38 | stream.write(b'1234567890abcdefghijklmnopqrstuvwxyz') 39 | 40 | # The buffer should have been trimmed to the last 20 bytes 41 | self.assertEqual(stream.read(20), b'ghijklmnopqrstuvwxyz') 42 | 43 | def test_clear_buffer_with_max_size(self): 44 | # Initialize the stream with a max size of 20 bytes 45 | stream = ReadWriteStream(max_size=20) 46 | 47 | # Write some data to the stream 48 | stream.write(b'1234567890abcdefghijklmnopqrstuvwxyz') 49 | 50 | # Clear the buffer 51 | stream.clear() 52 | self.assertEqual(len(stream), 0) 53 | 54 | 55 | class TestAudioTransformersTemplate(unittest.TestCase): 56 | def test_audio_transformer(self): 57 | pass 58 | # TODO 59 | 60 | 61 | class TestAudioTransformers(unittest.TestCase): 62 | PLUGIN_TYPE = PluginTypes.AUDIO_TRANSFORMER 63 | CONFIG_TYPE = PluginConfigTypes.AUDIO_TRANSFORMER 64 | TEST_CONFIG = {"test": True} 65 | CONFIG_SECTION = "" 66 | 67 | @patch("ovos_plugin_manager.utils.find_plugins") 68 | def test_find_plugins(self, find_plugins): 69 | from ovos_plugin_manager.audio_transformers import \ 70 | find_audio_transformer_plugins 71 | find_audio_transformer_plugins() 72 | find_plugins.assert_called_once_with(self.PLUGIN_TYPE) 73 | 74 | @patch("ovos_plugin_manager.utils.load_plugin") 75 | def test_load_plugin(self, load_plugin): 76 | from ovos_plugin_manager.audio_transformers import \ 77 | load_audio_transformer_plugin 78 | load_audio_transformer_plugin("test_mod") 79 | load_plugin.assert_called_once_with("test_mod", self.PLUGIN_TYPE) 80 | 81 | @patch("ovos_plugin_manager.utils.config.load_configs_for_plugin_type") 82 | def test_get_configs(self, load_configs): 83 | from ovos_plugin_manager.audio_transformers import \ 84 | get_audio_transformer_configs 85 | get_audio_transformer_configs() 86 | load_configs.assert_called_once_with(self.PLUGIN_TYPE) 87 | 88 | @patch("ovos_plugin_manager.utils.config.load_plugin_configs") 89 | def test_get_module_configs(self, load_plugin_configs): 90 | from ovos_plugin_manager.audio_transformers import \ 91 | get_audio_transformer_module_configs 92 | get_audio_transformer_module_configs("test_mod") 93 | load_plugin_configs.assert_called_once_with("test_mod", 94 | self.CONFIG_TYPE) 95 | -------------------------------------------------------------------------------- /test/unittests/test_coref.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import patch 3 | 4 | from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes 5 | 6 | 7 | class TestCoref(unittest.TestCase): 8 | PLUGIN_TYPE = PluginTypes.COREFERENCE_SOLVER 9 | CONFIG_TYPE = PluginConfigTypes.COREFERENCE_SOLVER 10 | TEST_CONFIG = {"test": True} 11 | CONFIG_SECTION = "coref" 12 | TEST_LANG = "en-US" 13 | 14 | @patch("ovos_plugin_manager.utils.find_plugins") 15 | def test_find_plugins(self, find_plugins): 16 | from ovos_plugin_manager.coreference import find_coref_plugins 17 | find_coref_plugins() 18 | find_plugins.assert_called_once_with(self.PLUGIN_TYPE) 19 | 20 | @patch("ovos_plugin_manager.utils.load_plugin") 21 | def test_load_plugin(self, load_plugin): 22 | from ovos_plugin_manager.coreference import load_coref_plugin 23 | load_coref_plugin("test_mod") 24 | load_plugin.assert_called_once_with("test_mod", self.PLUGIN_TYPE) 25 | 26 | @patch("ovos_plugin_manager.utils.config.load_configs_for_plugin_type") 27 | def test_get_configs(self, load_configs): 28 | from ovos_plugin_manager.coreference import get_coref_configs 29 | get_coref_configs() 30 | load_configs.assert_called_once_with(self.PLUGIN_TYPE) 31 | 32 | @patch("ovos_plugin_manager.utils.config.load_plugin_configs") 33 | def test_get_module_configs(self, load_plugin_configs): 34 | from ovos_plugin_manager.coreference import get_coref_module_configs 35 | get_coref_module_configs("test_mod") 36 | load_plugin_configs.assert_called_once_with("test_mod", 37 | self.CONFIG_TYPE, True) 38 | 39 | @patch("ovos_plugin_manager.utils.config.get_plugin_language_configs") 40 | def test_get_lang_configs(self, get_language_configs): 41 | from ovos_plugin_manager.coreference import get_coref_lang_configs 42 | get_coref_lang_configs(self.TEST_LANG) 43 | get_language_configs.assert_called_once_with(self.PLUGIN_TYPE, 44 | self.TEST_LANG, False) 45 | 46 | @patch("ovos_plugin_manager.utils.config.get_plugin_supported_languages") 47 | def test_get_supported_langs(self, get_supported_languages): 48 | from ovos_plugin_manager.coreference import get_coref_supported_langs 49 | get_coref_supported_langs() 50 | get_supported_languages.assert_called_once_with(self.PLUGIN_TYPE) 51 | 52 | @patch("ovos_plugin_manager.utils.config.get_plugin_config") 53 | def test_get_config(self, get_config): 54 | from ovos_plugin_manager.coreference import get_coref_config 55 | get_coref_config(self.TEST_CONFIG) 56 | get_config.assert_called_once_with(self.TEST_CONFIG, 57 | self.CONFIG_SECTION) 58 | 59 | 60 | class TestCorefSolverFactory(unittest.TestCase): 61 | from ovos_plugin_manager.coreference import OVOSCoreferenceSolverFactory 62 | # TODO 63 | -------------------------------------------------------------------------------- /test/unittests/test_g2p.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from copy import deepcopy 3 | from enum import Enum 4 | from unittest.mock import patch, Mock 5 | 6 | from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes 7 | 8 | _TEST_CONFIG = { 9 | "g2p": { 10 | "module": "good", 11 | "good": {"a": "b"} 12 | } 13 | } 14 | _FALLBACK_CONFIG = { 15 | "g2p": { 16 | "module": "bad", 17 | "bad": {"fallback_module": "good"}, 18 | "good": {"a": "b"} 19 | } 20 | } 21 | 22 | 23 | class TestG2PTemplate(unittest.TestCase): 24 | def test_phoneme_alphabet(self): 25 | from ovos_plugin_manager.templates.g2p import PhonemeAlphabet 26 | for alpha in (PhonemeAlphabet.ARPA, PhonemeAlphabet.IPA): 27 | self.assertIsInstance(alpha, Enum) 28 | self.assertIsInstance(alpha, str) 29 | self.assertIsInstance(alpha.value, str) 30 | 31 | def test_grapheme_to_phoneme(self): 32 | from ovos_plugin_manager.templates.g2p import Grapheme2PhonemePlugin 33 | # TODO 34 | 35 | 36 | class TestG2P(unittest.TestCase): 37 | PLUGIN_TYPE = PluginTypes.PHONEME 38 | CONFIG_TYPE = PluginConfigTypes.PHONEME 39 | TEST_CONFIG = {"test": True} 40 | CONFIG_SECTION = "g2p" 41 | TEST_LANG = "en-US" 42 | 43 | @patch("ovos_plugin_manager.utils.find_plugins") 44 | def test_find_plugins(self, find_plugins): 45 | from ovos_plugin_manager.g2p import find_g2p_plugins 46 | find_g2p_plugins() 47 | find_plugins.assert_called_once_with(self.PLUGIN_TYPE) 48 | 49 | @patch("ovos_plugin_manager.utils.load_plugin") 50 | def test_load_plugin(self, load_plugin): 51 | from ovos_plugin_manager.g2p import load_g2p_plugin 52 | load_g2p_plugin("test_mod") 53 | load_plugin.assert_called_once_with("test_mod", self.PLUGIN_TYPE) 54 | 55 | @patch("ovos_plugin_manager.utils.config.load_configs_for_plugin_type") 56 | def test_get_configs(self, load_configs): 57 | from ovos_plugin_manager.g2p import get_g2p_configs 58 | get_g2p_configs() 59 | load_configs.assert_called_once_with(self.PLUGIN_TYPE) 60 | 61 | @patch("ovos_plugin_manager.utils.config.load_plugin_configs") 62 | def test_get_module_configs(self, load_plugin_configs): 63 | from ovos_plugin_manager.g2p import get_g2p_module_configs 64 | get_g2p_module_configs("test_mod") 65 | load_plugin_configs.assert_called_once_with("test_mod", 66 | self.CONFIG_TYPE, True) 67 | 68 | @patch("ovos_plugin_manager.utils.config.get_plugin_language_configs") 69 | def test_get_lang_configs(self, get_language_configs): 70 | from ovos_plugin_manager.g2p import get_g2p_lang_configs 71 | get_g2p_lang_configs(self.TEST_LANG) 72 | get_language_configs.assert_called_once_with(self.PLUGIN_TYPE, 73 | self.TEST_LANG, False) 74 | 75 | @patch("ovos_plugin_manager.utils.config.get_plugin_supported_languages") 76 | def test_get_supported_langs(self, get_supported_languages): 77 | from ovos_plugin_manager.g2p import get_g2p_supported_langs 78 | get_g2p_supported_langs() 79 | get_supported_languages.assert_called_once_with(self.PLUGIN_TYPE) 80 | 81 | @patch("ovos_plugin_manager.utils.config.get_plugin_config") 82 | def test_get_config(self, get_config): 83 | from ovos_plugin_manager.g2p import get_g2p_config 84 | get_g2p_config(self.TEST_CONFIG) 85 | get_config.assert_called_once_with(self.TEST_CONFIG, 86 | self.CONFIG_SECTION) 87 | 88 | 89 | class TestG2PFactory(unittest.TestCase): 90 | def test_create_g2p(self): 91 | from ovos_plugin_manager.g2p import OVOSG2PFactory 92 | real_get_class = OVOSG2PFactory.get_class 93 | mock_class = Mock() 94 | call_args = None 95 | 96 | def _copy_args(*args): 97 | nonlocal call_args 98 | call_args = deepcopy(args) 99 | return mock_class 100 | 101 | mock_get_class = Mock(side_effect=_copy_args) 102 | OVOSG2PFactory.get_class = mock_get_class 103 | 104 | OVOSG2PFactory.create(config=_TEST_CONFIG) 105 | mock_get_class.assert_called_once() 106 | self.assertEqual(call_args, ({**_TEST_CONFIG['g2p']['good'], 107 | **{"module": "good", 108 | "lang": "en-US"}},)) 109 | mock_class.assert_called_once_with({**_TEST_CONFIG['g2p']['good'], 110 | **{"module": "good", 111 | "lang": "en-US"}}) 112 | OVOSG2PFactory.get_class = real_get_class 113 | 114 | def test_create_fallback(self): 115 | from ovos_plugin_manager.g2p import OVOSG2PFactory 116 | real_get_class = OVOSG2PFactory.get_class 117 | mock_class = Mock() 118 | call_args = None 119 | bad_call_args = None 120 | 121 | def _copy_args(*args): 122 | nonlocal call_args, bad_call_args 123 | if args[0]["module"] == "bad": 124 | bad_call_args = deepcopy(args) 125 | return None 126 | call_args = deepcopy(args) 127 | return mock_class 128 | 129 | mock_get_class = Mock(side_effect=_copy_args) 130 | OVOSG2PFactory.get_class = mock_get_class 131 | 132 | OVOSG2PFactory.create(config=_FALLBACK_CONFIG) 133 | mock_get_class.assert_called() 134 | self.assertEqual(call_args[0]["module"], 'good') 135 | self.assertEqual(bad_call_args[0]["module"], 'bad') 136 | mock_class.assert_called_once_with({**_FALLBACK_CONFIG['g2p']['good'], 137 | **{"module": "good", 138 | "lang": "en-US"}}) 139 | OVOSG2PFactory.get_class = real_get_class 140 | -------------------------------------------------------------------------------- /test/unittests/test_gui.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from unittest.mock import patch 4 | from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes 5 | 6 | 7 | class TestGuiTemplate(unittest.TestCase): 8 | def test_gui_extension(self): 9 | from ovos_plugin_manager.templates.gui import GUIExtension 10 | # TODO 11 | 12 | 13 | class TestGui(unittest.TestCase): 14 | PLUGIN_TYPE = PluginTypes.GUI 15 | CONFIG_TYPE = PluginConfigTypes.GUI 16 | TEST_CONFIG = {"test": True} 17 | CONFIG_SECTION = "gui" 18 | TEST_LANG = "en-US" 19 | 20 | @patch("ovos_plugin_manager.utils.find_plugins") 21 | def test_find_plugins(self, find_plugins): 22 | from ovos_plugin_manager.gui import find_gui_plugins 23 | find_gui_plugins() 24 | find_plugins.assert_called_once_with(self.PLUGIN_TYPE) 25 | 26 | @patch("ovos_plugin_manager.utils.load_plugin") 27 | def test_load_plugin(self, load_plugin): 28 | from ovos_plugin_manager.gui import load_gui_plugin 29 | load_gui_plugin("test_mod") 30 | load_plugin.assert_called_once_with("test_mod", self.PLUGIN_TYPE) 31 | 32 | @patch("ovos_plugin_manager.utils.config.load_configs_for_plugin_type") 33 | def test_get_configs(self, load_configs): 34 | from ovos_plugin_manager.gui import get_gui_configs 35 | get_gui_configs() 36 | load_configs.assert_called_once_with(self.PLUGIN_TYPE) 37 | 38 | @patch("ovos_plugin_manager.utils.config.load_plugin_configs") 39 | def test_get_module_configs(self, load_plugin_configs): 40 | from ovos_plugin_manager.gui import get_gui_module_configs 41 | get_gui_module_configs("test_mod") 42 | load_plugin_configs.assert_called_once_with("test_mod", 43 | self.CONFIG_TYPE) 44 | 45 | @patch("ovos_plugin_manager.utils.config.get_plugin_config") 46 | def test_get_config(self, get_config): 47 | from ovos_plugin_manager.gui import get_gui_config 48 | get_gui_config(self.TEST_CONFIG) 49 | get_config.assert_called_once_with(self.TEST_CONFIG, 50 | self.CONFIG_SECTION) 51 | 52 | 53 | class TestGuiFactory(unittest.TestCase): 54 | from ovos_plugin_manager.gui import OVOSGuiFactory 55 | # TODO 56 | -------------------------------------------------------------------------------- /test/unittests/test_hardware.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class TestLed(unittest.TestCase): 5 | def test_color(self): 6 | from ovos_plugin_manager.hardware.led import Color 7 | 8 | # Check types 9 | self.assertIsInstance(Color.BLACK, Color) 10 | self.assertIsInstance(Color.BLACK.value, tuple) 11 | 12 | # from_name 13 | self.assertEqual(Color.WHITE, Color.from_name('white')) 14 | with self.assertRaises(ValueError): 15 | Color.from_name('not a color') 16 | 17 | # as_rgb_tuple 18 | self.assertIsInstance(Color.RED.as_rgb_tuple(), tuple) 19 | 20 | # from_hex 21 | self.assertEqual(Color.from_hex('#ffffff'), Color.from_hex("#FFFFFF")) 22 | self.assertEqual(Color.from_hex('000000'), Color.BLACK.as_rgb_tuple()) 23 | self.assertEqual(Color.from_hex('ff0000'), Color.RED.as_rgb_tuple()) 24 | self.assertEqual(Color.from_hex('00FF00'), Color.GREEN.as_rgb_tuple()) 25 | self.assertEqual(Color.from_hex('#0000fF'), Color.BLUE.as_rgb_tuple()) 26 | 27 | # theme 28 | self.assertFalse(hasattr(Color, '_THEME')) 29 | self.assertEqual(Color.THEME.as_rgb_tuple(), 30 | Color.WHITE.as_rgb_tuple()) 31 | 32 | Color.set_theme('#fafafa') 33 | self.assertTrue(hasattr(Color, '_THEME')) 34 | self.assertEqual(Color.THEME.as_rgb_tuple(), Color.from_hex('#fafafa')) 35 | 36 | Color.set_theme('#aaaaaa') 37 | self.assertEqual(Color.THEME.as_rgb_tuple(), Color.from_hex('#aaaaaa')) 38 | 39 | Color.set_theme('#FFFF0000') 40 | self.assertEqual(Color.THEME.as_rgb_tuple(), (255, 0, 0)) 41 | 42 | def test_abstract_led(self): 43 | from ovos_plugin_manager.hardware.led import AbstractLed 44 | # TODO 45 | 46 | def test_led_animation(self): 47 | from ovos_plugin_manager.hardware.led.animations import LedAnimation 48 | # TODO 49 | 50 | def test_breathe_led_animation(self): 51 | from ovos_plugin_manager.hardware.led.animations import \ 52 | BreatheLedAnimation 53 | # TODO 54 | 55 | def test_chase_led_animation(self): 56 | from ovos_plugin_manager.hardware.led.animations import \ 57 | ChaseLedAnimation 58 | # TODO 59 | 60 | def test_fill_led_animation(self): 61 | from ovos_plugin_manager.hardware.led.animations import FillLedAnimation 62 | # TODO 63 | 64 | def test_refill_led_animation(self): 65 | from ovos_plugin_manager.hardware.led.animations import \ 66 | RefillLedAnimation 67 | # TODO 68 | 69 | def test_bounce_led_animation(self): 70 | from ovos_plugin_manager.hardware.led.animations import \ 71 | BounceLedAnimation 72 | # TODO 73 | 74 | def test_Blink_led_animation(self): 75 | from ovos_plugin_manager.hardware.led.animations import \ 76 | BlinkLedAnimation 77 | # TODO 78 | 79 | def test_alternating_led_animation(self): 80 | from ovos_plugin_manager.hardware.led.animations import \ 81 | AlternatingLedAnimation 82 | # TODO 83 | 84 | def test_animations(self): 85 | from ovos_plugin_manager.hardware.led.animations import animations, \ 86 | LedAnimation 87 | for key, val in animations.items(): 88 | self.assertIsInstance(key, str) 89 | self.assertTrue(issubclass(val, LedAnimation)) 90 | 91 | 92 | class TestFan(unittest.TestCase): 93 | from ovos_plugin_manager.hardware.fan import AbstractFan 94 | # TODO 95 | 96 | 97 | class TestSwitches(unittest.TestCase): 98 | from ovos_plugin_manager.hardware.switches import AbstractSwitches 99 | # TODO 100 | -------------------------------------------------------------------------------- /test/unittests/test_installation.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class TestInstallation(unittest.TestCase): 5 | def test_search_pip(self): 6 | from ovos_plugin_manager.installation import search_pip 7 | # TODO 8 | 9 | def test_pip_install(self): 10 | from ovos_plugin_manager.installation import pip_install 11 | # TODO 12 | -------------------------------------------------------------------------------- /test/unittests/test_keywords.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from unittest.mock import patch 4 | from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes 5 | 6 | 7 | class TestKeywordTemplate(unittest.TestCase): 8 | def test_keyword_extractor(self): 9 | from ovos_plugin_manager.templates.keywords import KeywordExtractor 10 | # TODO 11 | 12 | 13 | class TestKeywords(unittest.TestCase): 14 | PLUGIN_TYPE = PluginTypes.KEYWORD_EXTRACTION 15 | CONFIG_TYPE = PluginConfigTypes.KEYWORD_EXTRACTION 16 | TEST_CONFIG = {"test": True} 17 | CONFIG_SECTION = "keyword_extract" 18 | TEST_LANG = "en-US" 19 | 20 | @patch("ovos_plugin_manager.utils.find_plugins") 21 | def test_find_plugins(self, find_plugins): 22 | from ovos_plugin_manager.keywords import find_keyword_extract_plugins 23 | find_keyword_extract_plugins() 24 | find_plugins.assert_called_once_with(self.PLUGIN_TYPE) 25 | 26 | @patch("ovos_plugin_manager.utils.load_plugin") 27 | def test_load_plugin(self, load_plugin): 28 | from ovos_plugin_manager.keywords import load_keyword_extract_plugin 29 | load_keyword_extract_plugin("test_mod") 30 | load_plugin.assert_called_once_with("test_mod", self.PLUGIN_TYPE) 31 | 32 | @patch("ovos_plugin_manager.utils.config.load_configs_for_plugin_type") 33 | def test_get_configs(self, load_configs): 34 | from ovos_plugin_manager.keywords import get_keyword_extract_configs 35 | get_keyword_extract_configs() 36 | load_configs.assert_called_once_with(self.PLUGIN_TYPE) 37 | 38 | @patch("ovos_plugin_manager.utils.config.load_plugin_configs") 39 | def test_get_module_configs(self, load_plugin_configs): 40 | from ovos_plugin_manager.keywords import \ 41 | get_keyword_extract_module_configs 42 | get_keyword_extract_module_configs("test_mod") 43 | load_plugin_configs.assert_called_once_with("test_mod", 44 | self.CONFIG_TYPE, True) 45 | 46 | @patch("ovos_plugin_manager.utils.config.get_plugin_language_configs") 47 | def test_get_lang_configs(self, get_language_configs): 48 | from ovos_plugin_manager.keywords import \ 49 | get_keyword_extract_lang_configs 50 | get_keyword_extract_lang_configs(self.TEST_LANG) 51 | get_language_configs.assert_called_once_with(self.PLUGIN_TYPE, 52 | self.TEST_LANG, False) 53 | 54 | @patch("ovos_plugin_manager.utils.config.get_plugin_supported_languages") 55 | def test_get_supported_langs(self, get_supported_languages): 56 | from ovos_plugin_manager.keywords import \ 57 | get_keyword_extract_supported_langs 58 | get_keyword_extract_supported_langs() 59 | get_supported_languages.assert_called_once_with(self.PLUGIN_TYPE) 60 | 61 | @patch("ovos_plugin_manager.utils.config.get_plugin_config") 62 | def test_get_config(self, get_config): 63 | from ovos_plugin_manager.keywords import get_keyword_extract_config 64 | get_keyword_extract_config(self.TEST_CONFIG) 65 | get_config.assert_called_once_with(self.TEST_CONFIG, 66 | self.CONFIG_SECTION) 67 | 68 | 69 | class TestKeywordsFactory(unittest.TestCase): 70 | from ovos_plugin_manager.keywords import OVOSKeywordExtractorFactory 71 | # TODO 72 | -------------------------------------------------------------------------------- /test/unittests/test_metadata_transformers.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from unittest.mock import patch 4 | from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes 5 | 6 | 7 | class TestMetadataTransformerTemplate(unittest.TestCase): 8 | def test_metadata_transformer(self): 9 | from ovos_plugin_manager.templates.transformers import MetadataTransformer 10 | # TODO 11 | 12 | 13 | class TestMedatadataTransformer(unittest.TestCase): 14 | PLUGIN_TYPE = PluginTypes.METADATA_TRANSFORMER 15 | CONFIG_TYPE = PluginConfigTypes.METADATA_TRANSFORMER 16 | TEST_CONFIG = {"test": True} 17 | CONFIG_SECTION = "" 18 | TEST_LANG = "en-US" 19 | 20 | @patch("ovos_plugin_manager.utils.find_plugins") 21 | def test_find_plugins(self, find_plugins): 22 | from ovos_plugin_manager.metadata_transformers import \ 23 | find_metadata_transformer_plugins 24 | find_metadata_transformer_plugins() 25 | find_plugins.assert_called_once_with(self.PLUGIN_TYPE) 26 | 27 | @patch("ovos_plugin_manager.utils.load_plugin") 28 | def test_load_plugin(self, load_plugin): 29 | from ovos_plugin_manager.metadata_transformers import \ 30 | load_metadata_transformer_plugin 31 | load_metadata_transformer_plugin("test_mod") 32 | load_plugin.assert_called_once_with("test_mod", self.PLUGIN_TYPE) 33 | 34 | @patch("ovos_plugin_manager.utils.config.load_configs_for_plugin_type") 35 | def test_get_configs(self, load_configs): 36 | from ovos_plugin_manager.metadata_transformers import \ 37 | get_metadata_transformer_configs 38 | get_metadata_transformer_configs() 39 | load_configs.assert_called_once_with(self.PLUGIN_TYPE) 40 | 41 | @patch("ovos_plugin_manager.utils.config.load_plugin_configs") 42 | def test_get_module_configs(self, load_plugin_configs): 43 | from ovos_plugin_manager.metadata_transformers import \ 44 | get_metadata_transformer_module_configs 45 | get_metadata_transformer_module_configs("test_mod") 46 | load_plugin_configs.assert_called_once_with("test_mod", 47 | self.CONFIG_TYPE, True) 48 | 49 | @patch("ovos_plugin_manager.utils.config.get_plugin_language_configs") 50 | def test_get_lang_configs(self, get_language_configs): 51 | from ovos_plugin_manager.metadata_transformers import \ 52 | get_metadata_transformer_lang_configs 53 | get_metadata_transformer_lang_configs(self.TEST_LANG) 54 | get_language_configs.assert_called_once_with(self.PLUGIN_TYPE, 55 | self.TEST_LANG, False) 56 | 57 | @patch("ovos_plugin_manager.utils.config.get_plugin_supported_languages") 58 | def test_get_supported_langs(self, get_supported_languages): 59 | from ovos_plugin_manager.metadata_transformers import \ 60 | get_metadata_transformer_supported_langs 61 | get_metadata_transformer_supported_langs() 62 | get_supported_languages.assert_called_once_with(self.PLUGIN_TYPE) 63 | -------------------------------------------------------------------------------- /test/unittests/test_ocp.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import patch 3 | 4 | from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes 5 | 6 | 7 | class TestOCPTemplate(unittest.TestCase): 8 | def test_ocp_stream_extractor(self): 9 | from ovos_plugin_manager.templates.ocp import OCPStreamExtractor 10 | # TODO 11 | 12 | 13 | class TestOCP(unittest.TestCase): 14 | PLUGIN_TYPE = PluginTypes.STREAM_EXTRACTOR 15 | CONFIG_TYPE = PluginConfigTypes.STREAM_EXTRACTOR 16 | TEST_CONFIG = {"test": True} 17 | CONFIG_SECTION = "" 18 | 19 | @patch("ovos_plugin_manager.utils.find_plugins") 20 | def test_find_plugins(self, find_plugins): 21 | from ovos_plugin_manager.ocp import find_ocp_plugins 22 | find_ocp_plugins() 23 | find_plugins.assert_called_once_with(self.PLUGIN_TYPE) 24 | 25 | def test_stream_handler(self): 26 | from ovos_plugin_manager.ocp import StreamHandler 27 | handler = StreamHandler() 28 | self.assertIsInstance(handler.extractors, dict) 29 | -------------------------------------------------------------------------------- /test/unittests/test_persona.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import patch 3 | 4 | from ovos_plugin_manager.utils import PluginTypes 5 | 6 | 7 | class TestPersona(unittest.TestCase): 8 | PLUGIN_TYPE = PluginTypes.PERSONA 9 | TEST_CONFIG = {"test": True} 10 | CONFIG_SECTION = "persona" 11 | TEST_LANG = "en-US" 12 | 13 | @patch("ovos_plugin_manager.utils.find_plugins") 14 | def test_find_plugins(self, find_plugins): 15 | from ovos_plugin_manager.persona import find_persona_plugins 16 | find_persona_plugins() 17 | find_plugins.assert_called_once_with(self.PLUGIN_TYPE) 18 | -------------------------------------------------------------------------------- /test/unittests/test_phal.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from unittest.mock import patch 4 | from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes 5 | 6 | 7 | class TestPHALTemplate(unittest.TestCase): 8 | def test_PHAL_Validator(self): 9 | from ovos_plugin_manager.templates.phal import PHALValidator 10 | self.assertTrue(PHALValidator.validate()) 11 | self.assertTrue(PHALValidator.validate({"test": "val"})) 12 | self.assertTrue(PHALValidator.validate({"enabled": True})) 13 | self.assertFalse(PHALValidator.validate({"enabled": False})) 14 | self.assertFalse(PHALValidator.validate({"enabled": None})) 15 | 16 | def test_PHAL_Plugin(self): 17 | from ovos_plugin_manager.templates.phal import PHALValidator 18 | # TODO 19 | 20 | def test_Admin_Validator(self): 21 | from ovos_plugin_manager.templates.phal import AdminValidator 22 | self.assertTrue(AdminValidator.validate()) 23 | self.assertTrue(AdminValidator.validate({"test": "val"})) 24 | self.assertTrue(AdminValidator.validate({"enabled": True})) 25 | self.assertFalse(AdminValidator.validate({"enabled": False})) 26 | self.assertFalse(AdminValidator.validate({"enabled": None})) 27 | 28 | def test_Admin_Plugin(self): 29 | from ovos_plugin_manager.templates.phal import AdminPlugin 30 | # TODO 31 | 32 | class TestPHAL(unittest.TestCase): 33 | PLUGIN_TYPE = PluginTypes.PHAL 34 | CONFIG_TYPE = PluginConfigTypes.PHAL 35 | TEST_CONFIG = {"test": True} 36 | CONFIG_SECTION = "" 37 | TEST_LANG = "en-US" 38 | 39 | @patch("ovos_plugin_manager.utils.find_plugins") 40 | def test_find_plugins(self, find_plugins): 41 | from ovos_plugin_manager.phal import find_phal_plugins 42 | find_phal_plugins() 43 | find_plugins.assert_called_once_with(self.PLUGIN_TYPE) 44 | 45 | @patch("ovos_plugin_manager.utils.config.load_configs_for_plugin_type") 46 | def test_get_configs(self, load_configs): 47 | from ovos_plugin_manager.phal import get_phal_configs 48 | get_phal_configs() 49 | load_configs.assert_called_once_with(self.PLUGIN_TYPE) 50 | 51 | @patch("ovos_plugin_manager.utils.config.load_plugin_configs") 52 | def test_get_module_configs(self, load_plugin_configs): 53 | from ovos_plugin_manager.phal import get_phal_module_configs 54 | get_phal_module_configs("test_mod") 55 | load_plugin_configs.assert_called_once_with("test_mod", 56 | self.CONFIG_TYPE) 57 | 58 | 59 | class TestAdminPHAL(unittest.TestCase): 60 | PLUGIN_TYPE = PluginTypes.ADMIN 61 | CONFIG_TYPE = PluginConfigTypes.ADMIN 62 | TEST_CONFIG = {"test": True} 63 | CONFIG_SECTION = "" 64 | TEST_LANG = "en-US" 65 | 66 | @patch("ovos_plugin_manager.utils.find_plugins") 67 | def test_find_plugins(self, find_plugins): 68 | from ovos_plugin_manager.phal import find_admin_plugins 69 | find_admin_plugins() 70 | find_plugins.assert_called_once_with(self.PLUGIN_TYPE) 71 | 72 | @patch("ovos_plugin_manager.utils.config.load_configs_for_plugin_type") 73 | def test_get_configs(self, load_configs): 74 | from ovos_plugin_manager.phal import get_admin_configs 75 | get_admin_configs() 76 | load_configs.assert_called_once_with(self.PLUGIN_TYPE) 77 | 78 | @patch("ovos_plugin_manager.utils.config.load_plugin_configs") 79 | def test_get_module_configs(self, load_plugin_configs): 80 | from ovos_plugin_manager.phal import get_admin_module_configs 81 | get_admin_module_configs("test_mod") 82 | load_plugin_configs.assert_called_once_with("test_mod", 83 | self.CONFIG_TYPE) 84 | -------------------------------------------------------------------------------- /test/unittests/test_plugin_entry.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | class TestPluginEntry(unittest.TestCase): 5 | def test_ovos_plugin(self): 6 | from ovos_plugin_manager.plugin_entry import OpenVoiceOSPlugin 7 | # TODO 8 | -------------------------------------------------------------------------------- /test/unittests/test_postag.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from unittest.mock import patch 4 | from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes 5 | 6 | 7 | class TestPostag(unittest.TestCase): 8 | PLUGIN_TYPE = PluginTypes.POSTAG 9 | CONFIG_TYPE = PluginConfigTypes.POSTAG 10 | TEST_CONFIG = {"test": True} 11 | CONFIG_SECTION = "postag" 12 | TEST_LANG = "en-US" 13 | 14 | @patch("ovos_plugin_manager.utils.find_plugins") 15 | def test_find_plugins(self, find_plugins): 16 | from ovos_plugin_manager.postag import find_postag_plugins 17 | find_postag_plugins() 18 | find_plugins.assert_called_once_with(self.PLUGIN_TYPE) 19 | 20 | @patch("ovos_plugin_manager.utils.load_plugin") 21 | def test_load_plugin(self, load_plugin): 22 | from ovos_plugin_manager.postag import load_postag_plugin 23 | load_postag_plugin("test_mod") 24 | load_plugin.assert_called_once_with("test_mod", self.PLUGIN_TYPE) 25 | 26 | @patch("ovos_plugin_manager.utils.config.load_configs_for_plugin_type") 27 | def test_get_configs(self, load_configs): 28 | from ovos_plugin_manager.postag import get_postag_configs 29 | get_postag_configs() 30 | load_configs.assert_called_once_with(self.PLUGIN_TYPE) 31 | 32 | @patch("ovos_plugin_manager.utils.config.load_plugin_configs") 33 | def test_get_module_configs(self, load_plugin_configs): 34 | from ovos_plugin_manager.postag import get_postag_module_configs 35 | get_postag_module_configs("test_mod") 36 | load_plugin_configs.assert_called_once_with("test_mod", 37 | self.CONFIG_TYPE, True) 38 | 39 | @patch("ovos_plugin_manager.utils.config.get_plugin_language_configs") 40 | def test_get_lang_configs(self, get_language_configs): 41 | from ovos_plugin_manager.postag import get_postag_lang_configs 42 | get_postag_lang_configs(self.TEST_LANG) 43 | get_language_configs.assert_called_once_with(self.PLUGIN_TYPE, 44 | self.TEST_LANG, False) 45 | 46 | @patch("ovos_plugin_manager.utils.config.get_plugin_supported_languages") 47 | def test_get_supported_langs(self, get_supported_languages): 48 | from ovos_plugin_manager.postag import get_postag_supported_langs 49 | get_postag_supported_langs() 50 | get_supported_languages.assert_called_once_with(self.PLUGIN_TYPE) 51 | 52 | @patch("ovos_plugin_manager.utils.config.get_plugin_config") 53 | def test_get_config(self, get_config): 54 | from ovos_plugin_manager.postag import get_postag_config 55 | get_postag_config(self.TEST_CONFIG) 56 | get_config.assert_called_once_with(self.TEST_CONFIG, 57 | self.CONFIG_SECTION) 58 | 59 | 60 | class TestPosTaggerFactory(unittest.TestCase): 61 | from ovos_plugin_manager.postag import OVOSPosTaggerFactory 62 | # TODO 63 | -------------------------------------------------------------------------------- /test/unittests/test_segmentation.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from unittest.mock import patch 4 | from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes 5 | 6 | 7 | class TestSegmentation(unittest.TestCase): 8 | PLUGIN_TYPE = PluginTypes.UTTERANCE_SEGMENTATION 9 | CONFIG_TYPE = PluginConfigTypes.UTTERANCE_SEGMENTATION 10 | TEST_CONFIG = {"test": True} 11 | CONFIG_SECTION = "segmentation" 12 | TEST_LANG = "en-US" 13 | 14 | @patch("ovos_plugin_manager.utils.find_plugins") 15 | def test_find_plugins(self, find_plugins): 16 | from ovos_plugin_manager.segmentation import find_segmentation_plugins 17 | find_segmentation_plugins() 18 | find_plugins.assert_called_once_with(self.PLUGIN_TYPE) 19 | 20 | @patch("ovos_plugin_manager.utils.load_plugin") 21 | def test_load_plugin(self, load_plugin): 22 | from ovos_plugin_manager.segmentation import load_segmentation_plugin 23 | load_segmentation_plugin("test_mod") 24 | load_plugin.assert_called_once_with("test_mod", self.PLUGIN_TYPE) 25 | 26 | @patch("ovos_plugin_manager.utils.config.load_configs_for_plugin_type") 27 | def test_get_configs(self, load_configs): 28 | from ovos_plugin_manager.segmentation import get_segmentation_configs 29 | get_segmentation_configs() 30 | load_configs.assert_called_once_with(self.PLUGIN_TYPE) 31 | 32 | @patch("ovos_plugin_manager.utils.config.load_plugin_configs") 33 | def test_get_module_configs(self, load_plugin_configs): 34 | from ovos_plugin_manager.segmentation import get_segmentation_module_configs 35 | get_segmentation_module_configs("test_mod") 36 | load_plugin_configs.assert_called_once_with("test_mod", 37 | self.CONFIG_TYPE, True) 38 | 39 | @patch("ovos_plugin_manager.utils.config.get_plugin_language_configs") 40 | def test_get_lang_configs(self, get_language_configs): 41 | from ovos_plugin_manager.segmentation import get_segmentation_lang_configs 42 | get_segmentation_lang_configs(self.TEST_LANG) 43 | get_language_configs.assert_called_once_with(self.PLUGIN_TYPE, 44 | self.TEST_LANG, False) 45 | 46 | @patch("ovos_plugin_manager.utils.config.get_plugin_supported_languages") 47 | def test_get_supported_langs(self, get_supported_languages): 48 | from ovos_plugin_manager.segmentation import get_segmentation_supported_langs 49 | get_segmentation_supported_langs() 50 | get_supported_languages.assert_called_once_with(self.PLUGIN_TYPE) 51 | 52 | @patch("ovos_plugin_manager.utils.config.get_plugin_config") 53 | def test_get_config(self, get_config): 54 | from ovos_plugin_manager.segmentation import get_segmentation_config 55 | get_segmentation_config(self.TEST_CONFIG) 56 | get_config.assert_called_once_with(self.TEST_CONFIG, 57 | self.CONFIG_SECTION) 58 | 59 | 60 | class TestUtteranceSegmenterFactory(unittest.TestCase): 61 | from ovos_plugin_manager.segmentation import OVOSUtteranceSegmenterFactory 62 | # TODO 63 | 64 | -------------------------------------------------------------------------------- /test/unittests/test_skills.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import patch 3 | 4 | from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes 5 | 6 | 7 | class TestSkills(unittest.TestCase): 8 | PLUGIN_TYPE = PluginTypes.SKILL 9 | CONFIG_TYPE = PluginConfigTypes.SKILL 10 | TEST_CONFIG = {"test": True} 11 | CONFIG_SECTION = "" 12 | 13 | @patch("ovos_plugin_manager.utils.find_plugins") 14 | def test_find_plugins(self, find_plugins): 15 | from ovos_plugin_manager.skills import find_skill_plugins 16 | find_skill_plugins() 17 | find_plugins.assert_called_once_with(self.PLUGIN_TYPE) 18 | 19 | def test_load_skill_plugins(self): 20 | from ovos_plugin_manager.skills import load_skill_plugins 21 | # TODO 22 | -------------------------------------------------------------------------------- /test/unittests/test_stt.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from copy import copy 3 | 4 | from unittest.mock import patch, Mock 5 | from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes 6 | 7 | 8 | class TestSTTTemplate(unittest.TestCase): 9 | def test_stt(self): 10 | from ovos_plugin_manager.templates.stt import STT 11 | # TODO 12 | 13 | def test_stream_thread(self): 14 | from ovos_plugin_manager.templates.stt import StreamThread 15 | # TODO 16 | 17 | def test_streaming_stt(self): 18 | from ovos_plugin_manager.templates.stt import StreamingSTT 19 | # TODO 20 | 21 | 22 | class TestSTT(unittest.TestCase): 23 | PLUGIN_TYPE = PluginTypes.STT 24 | CONFIG_TYPE = PluginConfigTypes.STT 25 | TEST_CONFIG = {"test": True} 26 | CONFIG_SECTION = "stt" 27 | TEST_LANG = "en-US" 28 | 29 | @patch("ovos_plugin_manager.utils.find_plugins") 30 | def test_find_plugins(self, find_plugins): 31 | from ovos_plugin_manager.stt import find_stt_plugins 32 | find_stt_plugins() 33 | find_plugins.assert_called_once_with(self.PLUGIN_TYPE) 34 | 35 | @patch("ovos_plugin_manager.utils.load_plugin") 36 | def test_load_plugin(self, load_plugin): 37 | from ovos_plugin_manager.stt import load_stt_plugin 38 | load_stt_plugin("test_mod") 39 | load_plugin.assert_called_once_with("test_mod", self.PLUGIN_TYPE) 40 | 41 | @patch("ovos_plugin_manager.utils.config.load_configs_for_plugin_type") 42 | def test_get_configs(self, load_configs): 43 | from ovos_plugin_manager.stt import get_stt_configs 44 | get_stt_configs() 45 | load_configs.assert_called_once_with(self.PLUGIN_TYPE) 46 | 47 | @patch("ovos_plugin_manager.utils.config.load_plugin_configs") 48 | def test_get_module_configs(self, load_plugin_configs): 49 | from ovos_plugin_manager.stt import get_stt_module_configs 50 | get_stt_module_configs("test_mod") 51 | load_plugin_configs.assert_called_once_with("test_mod", 52 | self.CONFIG_TYPE, True) 53 | 54 | @patch("ovos_plugin_manager.utils.config.get_plugin_language_configs") 55 | def test_get_lang_configs(self, get_language_configs): 56 | from ovos_plugin_manager.stt import get_stt_lang_configs 57 | get_stt_lang_configs(self.TEST_LANG) 58 | get_language_configs.assert_called_once_with(self.PLUGIN_TYPE, 59 | self.TEST_LANG, False) 60 | 61 | @patch("ovos_plugin_manager.utils.config.get_plugin_supported_languages") 62 | def test_get_supported_langs(self, get_supported_languages): 63 | from ovos_plugin_manager.stt import get_stt_supported_langs 64 | get_stt_supported_langs() 65 | get_supported_languages.assert_called_once_with(self.PLUGIN_TYPE) 66 | 67 | @patch("ovos_plugin_manager.utils.config.get_plugin_config") 68 | def test_get_stt_config(self, get_config): 69 | from ovos_plugin_manager.stt import get_stt_config 70 | config = copy(self.TEST_CONFIG) 71 | get_stt_config(self.TEST_CONFIG) 72 | get_config.assert_called_once_with(self.TEST_CONFIG, 73 | self.CONFIG_SECTION, None) 74 | self.assertEqual(config, self.TEST_CONFIG) 75 | 76 | 77 | class TestSTTFactory(unittest.TestCase): 78 | 79 | @patch("ovos_plugin_manager.stt.load_stt_plugin") 80 | def test_get_class(self, load_plugin): 81 | from ovos_plugin_manager.stt import OVOSSTTFactory 82 | global_config = {"stt": {"module": "ovos-stt-plugin-dummy"}} 83 | tts_config = {"module": "test-stt-plugin-test"} 84 | 85 | # Test load plugin mapped global config 86 | OVOSSTTFactory.get_class(global_config) 87 | load_plugin.assert_called_with("ovos-stt-plugin-dummy") 88 | 89 | # Test load plugin explicit STT config 90 | OVOSSTTFactory.get_class(tts_config) 91 | load_plugin.assert_called_with("test-stt-plugin-test") 92 | 93 | @patch("ovos_plugin_manager.stt.OVOSSTTFactory.get_class") 94 | def test_create(self, get_class): 95 | from ovos_plugin_manager.stt import OVOSSTTFactory 96 | plugin_class = Mock() 97 | get_class.return_value = plugin_class 98 | 99 | global_config = {"lang": "en-gb", 100 | "stt": {"module": "ovos-stt-plugin-dummy", 101 | "ovos-stt-plugin-dummy": {"config": True, 102 | "lang": "en-ca"}}} 103 | stt_config = {"lang": "es-es", 104 | "module": "test-stt-plugin-test"} 105 | 106 | stt_config_2 = {"lang": "es-es", 107 | "module": "test-stt-plugin-test", 108 | "test-stt-plugin-test": {"config": True, 109 | "lang": "es-mx"}} 110 | 111 | # Test create with global config and lang override 112 | plugin = OVOSSTTFactory.create(global_config) 113 | expected_config = {"module": "ovos-stt-plugin-dummy", 114 | "config": True, 115 | "lang": "en-ca"} 116 | get_class.assert_called_once_with(expected_config) 117 | plugin_class.assert_called_once_with(expected_config) 118 | self.assertEqual(plugin, plugin_class()) 119 | 120 | # Test create with STT config and no module config 121 | plugin = OVOSSTTFactory.create(stt_config) 122 | get_class.assert_called_with(stt_config) 123 | plugin_class.assert_called_with(stt_config) 124 | self.assertEqual(plugin, plugin_class()) 125 | 126 | # Test create with STT config with module-specific config 127 | plugin = OVOSSTTFactory.create(stt_config_2) 128 | expected_config = {"module": "test-stt-plugin-test", 129 | "config": True, "lang": "es-mx"} 130 | get_class.assert_called_with(expected_config) 131 | plugin_class.assert_called_with(expected_config) 132 | self.assertEqual(plugin, plugin_class()) 133 | 134 | -------------------------------------------------------------------------------- /test/unittests/test_text_transformers.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import patch 3 | 4 | from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes 5 | 6 | 7 | class TestTextTransformersTemplate(unittest.TestCase): 8 | def test_utterance_transformer(self): 9 | from ovos_plugin_manager.templates.transformers import UtteranceTransformer 10 | # TODO 11 | 12 | 13 | class TestTextTransformers(unittest.TestCase): 14 | PLUGIN_TYPE = PluginTypes.UTTERANCE_TRANSFORMER 15 | CONFIG_TYPE = PluginConfigTypes.UTTERANCE_TRANSFORMER 16 | TEST_CONFIG = {"test": True} 17 | CONFIG_SECTION = "" 18 | TEST_LANG = "en-US" 19 | 20 | @patch("ovos_plugin_manager.utils.find_plugins") 21 | def test_find_plugins(self, find_plugins): 22 | from ovos_plugin_manager.text_transformers import \ 23 | find_utterance_transformer_plugins 24 | find_utterance_transformer_plugins() 25 | find_plugins.assert_called_once_with(self.PLUGIN_TYPE) 26 | 27 | @patch("ovos_plugin_manager.utils.load_plugin") 28 | def test_load_plugin(self, load_plugin): 29 | from ovos_plugin_manager.text_transformers import \ 30 | load_utterance_transformer_plugin 31 | load_utterance_transformer_plugin("test_mod") 32 | load_plugin.assert_called_once_with("test_mod", self.PLUGIN_TYPE) 33 | 34 | @patch("ovos_plugin_manager.utils.config.load_configs_for_plugin_type") 35 | def test_get_configs(self, load_configs): 36 | from ovos_plugin_manager.text_transformers import \ 37 | get_utterance_transformer_configs 38 | get_utterance_transformer_configs() 39 | load_configs.assert_called_once_with(self.PLUGIN_TYPE) 40 | 41 | @patch("ovos_plugin_manager.utils.config.load_plugin_configs") 42 | def test_get_module_configs(self, load_plugin_configs): 43 | from ovos_plugin_manager.text_transformers import \ 44 | get_utterance_transformer_module_configs 45 | get_utterance_transformer_module_configs("test_mod") 46 | load_plugin_configs.assert_called_once_with("test_mod", 47 | self.CONFIG_TYPE, True) 48 | 49 | @patch("ovos_plugin_manager.utils.config.get_plugin_language_configs") 50 | def test_get_lang_configs(self, get_language_configs): 51 | from ovos_plugin_manager.text_transformers import \ 52 | get_utterance_transformer_lang_configs 53 | get_utterance_transformer_lang_configs(self.TEST_LANG) 54 | get_language_configs.assert_called_once_with(self.PLUGIN_TYPE, 55 | self.TEST_LANG, False) 56 | 57 | @patch("ovos_plugin_manager.utils.config.get_plugin_supported_languages") 58 | def test_get_supported_langs(self, get_supported_languages): 59 | from ovos_plugin_manager.text_transformers import \ 60 | get_utterance_transformer_supported_langs 61 | get_utterance_transformer_supported_langs() 62 | get_supported_languages.assert_called_once_with(self.PLUGIN_TYPE) 63 | -------------------------------------------------------------------------------- /test/unittests/test_tokenization.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from unittest.mock import patch 4 | from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes 5 | 6 | 7 | class TestTokenization(unittest.TestCase): 8 | PLUGIN_TYPE = PluginTypes.TOKENIZATION 9 | CONFIG_TYPE = PluginConfigTypes.TOKENIZATION 10 | TEST_CONFIG = {"test": True} 11 | CONFIG_SECTION = "tokenization" 12 | TEST_LANG = "en-US" 13 | 14 | @patch("ovos_plugin_manager.utils.find_plugins") 15 | def test_find_plugins(self, find_plugins): 16 | from ovos_plugin_manager.tokenization import find_tokenization_plugins 17 | find_tokenization_plugins() 18 | find_plugins.assert_called_once_with(self.PLUGIN_TYPE) 19 | 20 | @patch("ovos_plugin_manager.utils.load_plugin") 21 | def test_load_plugin(self, load_plugin): 22 | from ovos_plugin_manager.tokenization import load_tokenization_plugin 23 | load_tokenization_plugin("test_mod") 24 | load_plugin.assert_called_once_with("test_mod", self.PLUGIN_TYPE) 25 | 26 | @patch("ovos_plugin_manager.utils.config.load_configs_for_plugin_type") 27 | def test_get_configs(self, load_configs): 28 | from ovos_plugin_manager.tokenization import get_tokenization_configs 29 | get_tokenization_configs() 30 | load_configs.assert_called_once_with(self.PLUGIN_TYPE) 31 | 32 | @patch("ovos_plugin_manager.utils.config.load_plugin_configs") 33 | def test_get_module_configs(self, load_plugin_configs): 34 | from ovos_plugin_manager.tokenization import get_tokenization_module_configs 35 | get_tokenization_module_configs("test_mod") 36 | load_plugin_configs.assert_called_once_with("test_mod", 37 | self.CONFIG_TYPE, True) 38 | 39 | @patch("ovos_plugin_manager.utils.config.get_plugin_language_configs") 40 | def test_get_lang_configs(self, get_language_configs): 41 | from ovos_plugin_manager.tokenization import get_tokenization_lang_configs 42 | get_tokenization_lang_configs(self.TEST_LANG) 43 | get_language_configs.assert_called_once_with(self.PLUGIN_TYPE, 44 | self.TEST_LANG, False) 45 | 46 | @patch("ovos_plugin_manager.utils.config.get_plugin_supported_languages") 47 | def test_get_supported_langs(self, get_supported_languages): 48 | from ovos_plugin_manager.tokenization import get_tokenization_supported_langs 49 | get_tokenization_supported_langs() 50 | get_supported_languages.assert_called_once_with(self.PLUGIN_TYPE) 51 | 52 | @patch("ovos_plugin_manager.utils.config.get_plugin_config") 53 | def test_get_config(self, get_config): 54 | from ovos_plugin_manager.tokenization import get_tokenization_config 55 | get_tokenization_config(self.TEST_CONFIG) 56 | get_config.assert_called_once_with(self.TEST_CONFIG, 57 | self.CONFIG_SECTION) 58 | 59 | 60 | class TestTokenizerFactory(unittest.TestCase): 61 | from ovos_plugin_manager.tokenization import OVOSTokenizerFactory 62 | # TODO 63 | -------------------------------------------------------------------------------- /test/unittests/test_vad.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from copy import copy, deepcopy 3 | from unittest.mock import patch, Mock 4 | 5 | from ovos_plugin_manager.utils import PluginTypes, PluginConfigTypes 6 | 7 | _TEST_CONFIG = { 8 | "lang": "en-US", 9 | "listener": { 10 | "VAD": { 11 | "module": "dummy", 12 | "dummy": { 13 | "vad_param": True 14 | }, 15 | "ovos-vad-plugin-webrtcvad": { 16 | "vad_mode": 2 17 | } 18 | } 19 | } 20 | } 21 | _FALLBACK_CONFIG = { 22 | "VAD": { 23 | "module": "bad", 24 | "bad": {"fallback_module": "good"}, 25 | "good": {"a": "b"} 26 | } 27 | } 28 | 29 | 30 | class TestVADTemplate(unittest.TestCase): 31 | from ovos_plugin_manager.templates.vad import VADEngine 32 | # TODO 33 | 34 | 35 | class TestVAD(unittest.TestCase): 36 | PLUGIN_TYPE = PluginTypes.VAD 37 | CONFIG_TYPE = PluginConfigTypes.VAD 38 | TEST_CONFIG = _TEST_CONFIG['listener'] 39 | CONFIG_SECTION = "VAD" 40 | TEST_LANG = "en-US" 41 | 42 | @patch("ovos_plugin_manager.utils.find_plugins") 43 | def test_find_plugins(self, find_plugins): 44 | from ovos_plugin_manager.vad import find_vad_plugins 45 | find_vad_plugins() 46 | find_plugins.assert_called_once_with(self.PLUGIN_TYPE) 47 | 48 | @patch("ovos_plugin_manager.utils.load_plugin") 49 | def test_load_plugin(self, load_plugin): 50 | from ovos_plugin_manager.vad import load_vad_plugin 51 | load_vad_plugin("test_mod") 52 | load_plugin.assert_called_once_with("test_mod", self.PLUGIN_TYPE) 53 | 54 | @patch("ovos_plugin_manager.utils.config.load_configs_for_plugin_type") 55 | def test_get_configs(self, load_configs): 56 | from ovos_plugin_manager.vad import get_vad_configs 57 | get_vad_configs() 58 | load_configs.assert_called_once_with(self.PLUGIN_TYPE) 59 | 60 | @patch("ovos_plugin_manager.utils.config.load_plugin_configs") 61 | def test_get_module_configs(self, load_plugin_configs): 62 | from ovos_plugin_manager.vad import get_vad_module_configs 63 | get_vad_module_configs("test_mod") 64 | load_plugin_configs.assert_called_once_with("test_mod", 65 | self.CONFIG_TYPE) 66 | 67 | @patch("ovos_plugin_manager.utils.config.get_plugin_config") 68 | def test_get_config(self, get_config): 69 | from ovos_plugin_manager.vad import get_vad_config 70 | get_vad_config(self.TEST_CONFIG) 71 | get_config.assert_called_once_with(self.TEST_CONFIG, 72 | self.CONFIG_SECTION) 73 | 74 | 75 | class TestVADFactory(unittest.TestCase): 76 | def test_create(self): 77 | from ovos_plugin_manager.vad import OVOSVADFactory 78 | real_get_class = OVOSVADFactory.get_class 79 | mock_class = Mock() 80 | 81 | mock_get_class = Mock(return_value=mock_class) 82 | OVOSVADFactory.get_class = mock_get_class 83 | 84 | OVOSVADFactory.create(config=_TEST_CONFIG) 85 | mock_get_class.assert_called_once_with(_TEST_CONFIG['listener']['VAD']) 86 | mock_class.assert_called_once_with(_TEST_CONFIG['listener']["VAD"]['dummy']) 87 | 88 | # Test invalid config 89 | with self.assertRaises(ValueError): 90 | OVOSVADFactory.create({'VAD': {'value': None}}) 91 | 92 | OVOSVADFactory.get_class = real_get_class 93 | 94 | def test_create_fallback(self): 95 | from ovos_plugin_manager.vad import OVOSVADFactory 96 | real_get_class = OVOSVADFactory.get_class 97 | mock_class = Mock() 98 | call_args = None 99 | bad_call_args = None 100 | 101 | def _copy_args(*args): 102 | nonlocal call_args, bad_call_args 103 | if args[0]["module"] == "bad": 104 | bad_call_args = deepcopy(args) 105 | return None 106 | call_args = deepcopy(args) 107 | return mock_class 108 | 109 | mock_get_class = Mock(side_effect=_copy_args) 110 | OVOSVADFactory.get_class = mock_get_class 111 | 112 | OVOSVADFactory.create(config=_FALLBACK_CONFIG) 113 | mock_get_class.assert_called() 114 | self.assertEqual(call_args[0]["module"], 'good') 115 | self.assertEqual(bad_call_args[0]["module"], 'bad') 116 | mock_class.assert_called_once_with(_FALLBACK_CONFIG['VAD']['good']) 117 | OVOSVADFactory.get_class = real_get_class 118 | 119 | @patch("ovos_plugin_manager.utils.load_plugin") 120 | def test_get_class(self, load_plugin): 121 | mock = Mock() 122 | load_plugin.return_value = mock 123 | from ovos_plugin_manager.vad import OVOSVADFactory 124 | from ovos_plugin_manager.templates.vad import VADEngine 125 | 126 | # Test invalid config 127 | with self.assertRaises(ValueError): 128 | OVOSVADFactory.get_class({'module': None}) 129 | 130 | # Test dummy module 131 | module = OVOSVADFactory.get_class(_TEST_CONFIG) 132 | load_plugin.assert_not_called() 133 | self.assertEqual(VADEngine, module) 134 | 135 | # Test valid module 136 | config = deepcopy(_TEST_CONFIG) 137 | config['listener']['VAD']['module'] = 'ovos-vad-plugin-webrtcvad' 138 | module = OVOSVADFactory.get_class(config) 139 | load_plugin.assert_called_once_with('ovos-vad-plugin-webrtcvad', 140 | PluginTypes.VAD) 141 | self.assertEqual(module, mock) 142 | 143 | def test_get_vad_config(self): 144 | from ovos_plugin_manager.vad import get_vad_config 145 | config = copy(_TEST_CONFIG) 146 | dummy_config = get_vad_config(config) 147 | self.assertEqual(dummy_config, 148 | {**_TEST_CONFIG['listener']['VAD']['dummy'], 149 | **{'module': 'dummy'}}) 150 | config = copy(_TEST_CONFIG) 151 | config['listener']['VAD']['module'] = 'ovos-vad-plugin-webrtcvad' 152 | webrtc_config = get_vad_config(config) 153 | self.assertEqual(webrtc_config, 154 | {**_TEST_CONFIG['listener']['VAD'] 155 | ['ovos-vad-plugin-webrtcvad'], 156 | **{'module': 'ovos-vad-plugin-webrtcvad'}}) 157 | config = copy(_TEST_CONFIG) 158 | config['VAD'] = {'module': 'fake'} 159 | fake_config = get_vad_config(config) 160 | self.assertEqual(fake_config, {'module': 'fake'}) 161 | --------------------------------------------------------------------------------