├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ └── publish-addon.yml ├── .gitignore ├── .pre-commit-config.yaml ├── LICENSE ├── Pipfile ├── README.md ├── pyproject.toml └── script.service.hyperion-control ├── addon.xml ├── resources ├── __init__.py ├── fanart.png ├── icon.png ├── language │ ├── resource.language.de_de │ │ └── strings.po │ ├── resource.language.en_gb │ │ └── strings.po │ ├── resource.language.es_es │ │ └── strings.po │ ├── resource.language.fr_fr │ │ └── strings.po │ ├── resource.language.hu_hu │ │ └── strings.po │ └── resource.language.pl_pl │ │ └── strings.po ├── lib │ ├── __init__.py │ ├── api_client.py │ ├── gui.py │ ├── hyperion.py │ ├── interfaces.py │ ├── logger.py │ ├── monitor.py │ ├── player.py │ ├── settings_manager.py │ ├── ssdp.py │ └── utils.py ├── screenshot-01.png └── settings.xml └── service.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "monthly" 8 | -------------------------------------------------------------------------------- /.github/workflows/publish-addon.yml: -------------------------------------------------------------------------------- 1 | name: Check and publish addon 2 | 3 | on: [push] 4 | 5 | env: 6 | ADDON_NAME: script.service.hyperion-control 7 | TARGET_KODI_VER: nexus 8 | 9 | jobs: 10 | check: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Set up Python 3.11 15 | uses: actions/setup-python@v5 16 | with: 17 | python-version: "3.11" 18 | - name: Install addon checker 19 | run: | 20 | pip install -q kodi-addon-checker 21 | - name: Check with addon-checker 22 | run: | 23 | kodi-addon-checker --branch $TARGET_KODI_VER --allow-folder-id-mismatch $ADDON_NAME 24 | 25 | github_release: 26 | runs-on: ubuntu-latest 27 | permissions: 28 | contents: write 29 | 30 | needs: check 31 | if: github.ref_type == 'tag' 32 | steps: 33 | - uses: actions/checkout@v4 34 | - name: Set up Python 3.11 35 | uses: actions/setup-python@v5 36 | with: 37 | python-version: "3.11" 38 | - name: Install addon submitter 39 | run: | 40 | pip install -q git+https://github.com/xbmc/kodi-addon-submitter.git 41 | - name: Package addon 42 | run: | 43 | submit-addon -s -z $ADDON_NAME 44 | - name: Publish release 45 | uses: ncipollo/release-action@v1 46 | with: 47 | artifacts: "*.zip" 48 | 49 | kodi_publish: 50 | runs-on: ubuntu-latest 51 | 52 | needs: check 53 | if: github.ref_type == 'tag' 54 | steps: 55 | - uses: actions/checkout@v4 56 | - name: Set up Python 3.11 57 | uses: actions/setup-python@v5 58 | with: 59 | python-version: "3.11" 60 | - name: Install addon submitter 61 | run: | 62 | pip install -q git+https://github.com/xbmc/kodi-addon-submitter.git 63 | - name: Submit addon 64 | run: | 65 | submit-addon -r repo-scripts -b $TARGET_KODI_VER -s --pull-request $ADDON_NAME 66 | env: 67 | GH_USERNAME: ${{ github.actor }} 68 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 69 | EMAIL: "${{ github.actor }}@users.noreply.github.com" 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # python 9 | *.pyo 10 | __pycache__ 11 | .idea 12 | Pipfile.lock 13 | .mypy_cache 14 | .ruff_cache 15 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | default_language_version: 2 | python: python3 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v4.3.0 6 | hooks: 7 | - id: check-toml 8 | - id: check-yaml 9 | - id: end-of-file-fixer 10 | - id: trailing-whitespace 11 | - id: check-added-large-files 12 | - repo: https://github.com/executablebooks/mdformat 13 | rev: 0.7.16 14 | hooks: 15 | - id: mdformat 16 | exclude: CHANGELOG.md 17 | additional_dependencies: 18 | - mdformat-black 19 | - mdformat-frontmatter 20 | - mdformat-admon 21 | - repo: https://github.com/psf/black 22 | rev: 22.10.0 23 | hooks: 24 | - id: black 25 | - repo: https://github.com/charliermarsh/ruff-pre-commit 26 | rev: 'v0.0.252' 27 | hooks: 28 | - id: ruff 29 | args: [--fix, --exit-non-zero-on-fix] 30 | - repo: https://github.com/pre-commit/mirrors-mypy 31 | rev: 'v1.4.1' 32 | hooks: 33 | - id: mypy 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 hyperion-project 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | pre-commit = "*" 8 | cfgv = "*" 9 | 10 | [dev-packages] 11 | kodistubs = "*" 12 | kodi-addon-checker = "*" 13 | 14 | [requires] 15 | python_version = "3.11" 16 | 17 | [scripts] 18 | pre-commit = "pre-commit install" 19 | lint = "pre-commit run --all" 20 | check = "kodi-addon-checker --branch nexus --allow-folder-id-mismatch script.service.hyperion-control" 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hyperion Control for Kodi 20 (Nexus) 2 | 3 | [![Latest-Release](https://img.shields.io/github/v/release/hyperion-project/hyperion.control?include_prereleases&label=Latest%20Release&logo=github&logoColor=white&color=0f83e7)](https://github.com/hyperion-project/hyperion.control/releases) 4 | [![Join Translation](https://img.shields.io/badge/POEditor-translate-green.svg)](https://poeditor.com/join/project?hash=WauIsJeEx4) 5 | [![Forum](https://img.shields.io/website/https/hyperion-project.org.svg?label=Forum&down_color=red&down_message=offline&up_color=4bc51d&up_message=online&logo=homeadvisor&logoColor=white)](https://www.hyperion-project.org) 6 | 7 | Hyperion Control is an addon for Kodi Mediacenter that observes Kodi events (playing state, screensaver, ...). Based on these events you can control Hyperion components to enable or disable accordingly. 8 | 9 | For example playing a video should enable screen capture, if you pause the video or you are at the Kodi menu the screen capture should be disabled to show a background effect as mood light 10 | 11 | ### Features 12 | 13 | - Supports various Hyperion components across multiple instances 14 | - Setup wizard with Hyperion Server detection (zero configuration) 15 | - Token Authorization (optional) 16 | - Languages: English, German, Spanish, French, Polish, Hungarian 17 | - Execute from settings dialog: Hyperion Server detection 18 | 19 | ### Installation 20 | 21 | - Download .zip from release page and use "Install from zip file" dialog at the Kodi addons section. 22 | 23 | ### Credits 24 | 25 | - Dan Krause for the SSDP client 26 | - Paulchen-Panther for service development 27 | - brindosch for service development 28 | - derBernhard for Kodi 19 migration 29 | - sanzoghenzo for Kodi 20 (Nexus) support and major refactoring 30 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.mypy] 2 | check_untyped_defs = true 3 | disallow_any_generics = true 4 | disallow_incomplete_defs = true 5 | disallow_untyped_calls = false 6 | disallow_untyped_defs = true 7 | no_implicit_optional = true 8 | show_error_codes = true 9 | strict_equality = true 10 | strict_optional = true 11 | warn_redundant_casts = true 12 | warn_unreachable = true 13 | warn_unused_configs = true 14 | warn_unused_ignores = true 15 | 16 | [[tool.mypy.overrides]] 17 | module = ["requests.*"] 18 | ignore_missing_imports = true 19 | 20 | [tool.ruff] 21 | select = ["B", "C", "D", "E", "F", "I", "N", "S", "U", "W"] 22 | ignore = ["E501", "D212", "D103", "D107"] 23 | target-version = "py310" 24 | src = ["resources"] 25 | 26 | [tool.ruff.per-file-ignores] 27 | "script.service.hyperion-control/resources/lib/player.py" = ["N802"] 28 | "script.service.hyperion-control/resources/lib/monitor.py" = ["N802"] 29 | 30 | [tool.ruff.pydocstyle] 31 | convention = "google" 32 | 33 | [tool.ruff.isort] 34 | force-single-line = true 35 | 36 | [tool.ruff.pyupgrade] 37 | keep-runtime-typing = true 38 | -------------------------------------------------------------------------------- /script.service.hyperion-control/addon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Control Hyperion Ambilight 11 | Hyperion Ambilight Steuerung 12 | Enable and disable components (like capture) of Hyperion automatically based on playing/screensaver state of Kodi[CR]-Supports auto detection of Hyperion Servers[CR]-Token authentication 13 | Aktiviere und deaktiviere automatisch Hyperion Komponenten basierend auf dem aktuellen Kodi Status[CR]-Unterstützt Hyperion Server suche[CR]-Token Autorisierung 14 | all 15 | MIT 16 | https://hyperion-project.org/forum 17 | https://hyperion-project.org/forum 18 | https://github.com/hyperion-project/hyperion.control 19 | 20 | 20.0.1 21 | - Removed redundant debug setting 22 | 23 | 20.0.0 24 | - Kodi 20 (Nexus) support 25 | - Multi instance handling 26 | - New language: Hungarian 27 | 28 | 19.0.2 29 | - New languages: Español, Français, Polski 30 | 31 | 19.0.1 32 | - Kodi 19 support 33 | - minor cleanups and corrections 34 | 35 | 1.0.1 36 | - Fixed a crash with 3d mode @b-jesch 37 | 38 | 1.0.0 39 | - Added support for server search 40 | - Added support token authentication 41 | - Fixed issue where kodi api does not properly announce video playing states 42 | 43 | 44 | resources/icon.png 45 | resources/fanart.png 46 | resources/screenshot-01.png 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /script.service.hyperion-control/resources/__init__.py: -------------------------------------------------------------------------------- 1 | """Hyperion control addon resources.""" 2 | -------------------------------------------------------------------------------- /script.service.hyperion-control/resources/fanart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperion-project/hyperion.control/931034fbbb7b35a2aa1eeb4aec3c547fe3cd1fae/script.service.hyperion-control/resources/fanart.png -------------------------------------------------------------------------------- /script.service.hyperion-control/resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperion-project/hyperion.control/931034fbbb7b35a2aa1eeb4aec3c547fe3cd1fae/script.service.hyperion-control/resources/icon.png -------------------------------------------------------------------------------- /script.service.hyperion-control/resources/language/resource.language.de_de/strings.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "MIME-Version: 1.0\n" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "X-Generator: POEditor.com\n" 7 | "Project-Id-Version: Hyperion Control\n" 8 | "Language: de\n" 9 | 10 | #: 11 | msgctxt "#32000" 12 | msgid "General" 13 | msgstr "Allgemein" 14 | 15 | #: 16 | msgctxt "#32001" 17 | msgid "Hyperion IP" 18 | msgstr "Hyperion IP" 19 | 20 | #: 21 | msgctxt "#32002" 22 | msgid "Hyperion Webserver Port" 23 | msgstr "Hyperion Webserver Port" 24 | 25 | #: 26 | msgctxt "#32003" 27 | msgid "Switch automatically between 2D/3D" 28 | msgstr "Automatisch auf 2D/3D umstellen" 29 | 30 | #: 31 | msgctxt "#32004" 32 | msgid "Enable Hyperion on startup" 33 | msgstr "Aktiviere Hyperion beim Starten" 34 | 35 | #: 36 | msgctxt "#32005" 37 | msgid "Disable Hyperion on shutdown" 38 | msgstr "Deaktiviere Hyperion beim Beenden" 39 | 40 | #: 41 | msgctxt "#32007" 42 | msgid "Show changelog after update" 43 | msgstr "Zeige Änderungen nach Update" 44 | 45 | #: 46 | msgctxt "#32008" 47 | msgid "Authorization Token" 48 | msgstr "Autorisierungs-Token" 49 | 50 | #: 51 | msgctxt "#32025" 52 | msgid "Component" 53 | msgstr "Komponente" 54 | 55 | #: 56 | msgctxt "#32026" 57 | msgid "Enable the chosen component in these situations else disable it." 58 | msgstr "Aktiviere die Komponente in folgenden Situationen, sonst wird sie deaktiviert." 59 | 60 | #: 61 | msgctxt "#32027" 62 | msgid "Hyperion component" 63 | msgstr "Hyperion Komponente" 64 | 65 | #: 66 | msgctxt "#32028" 67 | msgid "Enable during video playback" 68 | msgstr "Aktiviere bei Videos" 69 | 70 | #: 71 | msgctxt "#32029" 72 | msgid "Enable during music playback" 73 | msgstr "Aktiviere bei Musik" 74 | 75 | #: 76 | msgctxt "#32030" 77 | msgid "Enable during player pause" 78 | msgstr "Aktiviere bei pausiertem Player" 79 | 80 | #: 81 | msgctxt "#32031" 82 | msgid "Enable in Kodi menu" 83 | msgstr "Aktiviere im Kodi Menü" 84 | 85 | #: 86 | msgctxt "#32032" 87 | msgid "Enable during active screensaver" 88 | msgstr "Aktiviere bei Bildschirmschoner" 89 | 90 | #: 91 | msgctxt "#32041" 92 | msgid "USB Capture" 93 | msgstr "USB Aufnahme" 94 | 95 | #: 96 | msgctxt "#32042" 97 | msgid "LED Hardware" 98 | msgstr "LED Hardware" 99 | 100 | #: 101 | msgctxt "#32043" 102 | msgid "Smoothing" 103 | msgstr "Glättung" 104 | 105 | #: 106 | msgctxt "#32045" 107 | msgid "Forwarder" 108 | msgstr "Weiterleitung" 109 | 110 | #: 111 | msgctxt "#32101" 112 | msgid "Would you like to search for a Hyperion Server and adjust settings?" 113 | msgstr "Hyperion Server suchen und Einstellungen anpassen?" 114 | 115 | #: 116 | msgctxt "#32102" 117 | msgid "Select a Hyperion Server, we found more than one" 118 | msgstr "Wähle einen Hyperion Server, es wurden mehrere gefunden" 119 | 120 | #: 121 | msgctxt "#32104" 122 | msgid "We are sorry, no Hyperion Server has been found. You need to configure IP address and port by hand" 123 | msgstr "Leider wurde kein Hyperion Server gefunden, du musst IP-Adresse und Port selbst eintragen" 124 | 125 | #: 126 | msgctxt "#32105" 127 | msgid "The Authorization Token isn't valid" 128 | msgstr "Das Autorisierungs-Token ist nicht gültig" 129 | 130 | #: 131 | msgctxt "#32150" 132 | msgid "Execute" 133 | msgstr "Ausführen" 134 | 135 | #: 136 | msgctxt "#32151" 137 | msgid "Execute a specific task you might need again.[CR]Select a task from the tasklist and press OK to close the settings dialog." 138 | msgstr "Führe eine spezifische Aufgabe nochmals aus.[CR]Wähle dazu aus der Liste eine Aufgabe und drücke den OK Button." 139 | 140 | #: 141 | msgctxt "#32152" 142 | msgid "The addon will handle your request immediately.[CR]Be aware that the addon should be already enabled" 143 | msgstr "Die Einstellungen schließen sich.[CR]Das Addon wird sofort mit der Ausführung beginnen, sofern es aktiviert ist" 144 | 145 | #: 146 | msgctxt "#32153" 147 | msgid "Tasklist" 148 | msgstr "Aufgabenliste" 149 | 150 | #: 151 | msgctxt "#32154" 152 | msgid "No Task" 153 | msgstr "keine Aufgabe" 154 | 155 | #: 156 | msgctxt "#32155" 157 | msgid "Search: Hyperion Server" 158 | msgstr "Suchen: Hyperion Server" 159 | 160 | #: 161 | msgctxt "#32044" 162 | msgid "Blackbar detector" 163 | msgstr "Schwarze Balken Erkennung" 164 | 165 | #: 166 | msgctxt "#32040" 167 | msgid "Screen Capture" 168 | msgstr "Bildschirm Aufnahme" 169 | 170 | #: 171 | msgctxt "#32100" 172 | msgid "Welcome to Hyperion Control!" 173 | msgstr "Willkommen zu Hyperion Control!" 174 | 175 | #: 176 | msgctxt "#32103" 177 | msgid "We found the following Hyperion Server for usage:" 178 | msgstr "Es wurde folgender Hyperion Server gefunden:" 179 | 180 | #: 181 | msgctxt "#32046" 182 | msgid "Boblight Server" 183 | msgstr "Boblight Server" 184 | 185 | #: 186 | msgctxt "#32047" 187 | msgid "Hyperion" 188 | msgstr "Hyperion" 189 | 190 | #: 191 | msgctxt "#32048" 192 | msgid "Audio Capture" 193 | msgstr "Audio Aufnahme" 194 | 195 | -------------------------------------------------------------------------------- /script.service.hyperion-control/resources/language/resource.language.en_gb/strings.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "MIME-Version: 1.0\n" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "X-Generator: POEditor.com\n" 7 | "Project-Id-Version: Hyperion Control\n" 8 | "Language: en\n" 9 | 10 | #: 11 | msgctxt "#32000" 12 | msgid "General" 13 | msgstr "General" 14 | 15 | #: 16 | msgctxt "#32001" 17 | msgid "Hyperion IP" 18 | msgstr "Hyperion IP" 19 | 20 | #: 21 | msgctxt "#32002" 22 | msgid "Hyperion Webserver Port" 23 | msgstr "Hyperion Webserver Port" 24 | 25 | #: 26 | msgctxt "#32003" 27 | msgid "Switch automatically between 2D/3D" 28 | msgstr "Switch automatically between 2D/3D" 29 | 30 | #: 31 | msgctxt "#32004" 32 | msgid "Enable Hyperion on startup" 33 | msgstr "Enable Hyperion on startup" 34 | 35 | #: 36 | msgctxt "#32005" 37 | msgid "Disable Hyperion on shutdown" 38 | msgstr "Disable Hyperion on shutdown" 39 | 40 | #: 41 | msgctxt "#32007" 42 | msgid "Show changelog after update" 43 | msgstr "Show changelog after update" 44 | 45 | #: 46 | msgctxt "#32008" 47 | msgid "Authorization Token" 48 | msgstr "Authorization Token" 49 | 50 | #: 51 | msgctxt "#32025" 52 | msgid "Component" 53 | msgstr "Component" 54 | 55 | #: 56 | msgctxt "#32026" 57 | msgid "Enable the chosen component in these situations else disable it." 58 | msgstr "Enable the chosen component in these situations else disable it." 59 | 60 | #: 61 | msgctxt "#32027" 62 | msgid "Hyperion component" 63 | msgstr "Hyperion component" 64 | 65 | #: 66 | msgctxt "#32028" 67 | msgid "Enable during video playback" 68 | msgstr "Enable during video playback" 69 | 70 | #: 71 | msgctxt "#32029" 72 | msgid "Enable during music playback" 73 | msgstr "Enable during music playback" 74 | 75 | #: 76 | msgctxt "#32030" 77 | msgid "Enable during player pause" 78 | msgstr "Enable during player pause" 79 | 80 | #: 81 | msgctxt "#32031" 82 | msgid "Enable in Kodi menu" 83 | msgstr "Enable in Kodi menu" 84 | 85 | #: 86 | msgctxt "#32032" 87 | msgid "Enable during active screensaver" 88 | msgstr "Enable during active screensaver" 89 | 90 | #: 91 | msgctxt "#32041" 92 | msgid "USB Capture" 93 | msgstr "USB Capture" 94 | 95 | #: 96 | msgctxt "#32042" 97 | msgid "LED Hardware" 98 | msgstr "LED Hardware" 99 | 100 | #: 101 | msgctxt "#32043" 102 | msgid "Smoothing" 103 | msgstr "Smoothing" 104 | 105 | #: 106 | msgctxt "#32045" 107 | msgid "Forwarder" 108 | msgstr "Forwarder" 109 | 110 | #: 111 | msgctxt "#32101" 112 | msgid "Would you like to search for a Hyperion Server and adjust settings?" 113 | msgstr "Would you like to search for a Hyperion Server and adjust settings?" 114 | 115 | #: 116 | msgctxt "#32102" 117 | msgid "Select a Hyperion Server, we found more than one" 118 | msgstr "Select a Hyperion Server, we found more than one" 119 | 120 | #: 121 | msgctxt "#32104" 122 | msgid "We are sorry, no Hyperion Server has been found. You need to configure IP address and port by hand" 123 | msgstr "We are sorry, no Hyperion Server has been found. You need to configure IP address and port by hand" 124 | 125 | #: 126 | msgctxt "#32105" 127 | msgid "The Authorization Token isn't valid" 128 | msgstr "The Authorization Token isn't valid" 129 | 130 | #: 131 | msgctxt "#32150" 132 | msgid "Execute" 133 | msgstr "Execute" 134 | 135 | #: 136 | msgctxt "#32151" 137 | msgid "Execute a specific task you might need again.[CR]Select a task from the tasklist and press OK to close the settings dialog." 138 | msgstr "Execute a specific task you might need again.[CR]Select a task from the tasklist and press OK to close the settings dialog." 139 | 140 | #: 141 | msgctxt "#32152" 142 | msgid "The addon will handle your request immediately.[CR]Be aware that the addon should be already enabled" 143 | msgstr "The addon will handle your request immediately.[CR]Be aware that the addon should be already enabled" 144 | 145 | #: 146 | msgctxt "#32153" 147 | msgid "Tasklist" 148 | msgstr "Tasklist" 149 | 150 | #: 151 | msgctxt "#32154" 152 | msgid "No Task" 153 | msgstr "No Task" 154 | 155 | #: 156 | msgctxt "#32155" 157 | msgid "Search: Hyperion Server" 158 | msgstr "Search: Hyperion Server" 159 | 160 | #: 161 | msgctxt "#32044" 162 | msgid "Blackbar detector" 163 | msgstr "Blackbar detector" 164 | 165 | #: 166 | msgctxt "#32040" 167 | msgid "Screen Capture" 168 | msgstr "Screen Capture" 169 | 170 | #: 171 | msgctxt "#32100" 172 | msgid "Welcome to Hyperion Control!" 173 | msgstr "Welcome to Hyperion Control!" 174 | 175 | #: 176 | msgctxt "#32103" 177 | msgid "We found the following Hyperion Server for usage:" 178 | msgstr "We found the following Hyperion Server for usage:" 179 | 180 | #: 181 | msgctxt "#32046" 182 | msgid "Boblight Server" 183 | msgstr "Boblight Server" 184 | 185 | #: 186 | msgctxt "#32047" 187 | msgid "Hyperion" 188 | msgstr "Hyperion" 189 | 190 | #: 191 | msgctxt "#32048" 192 | msgid "Audio Capture" 193 | msgstr "Audio Capture" 194 | 195 | -------------------------------------------------------------------------------- /script.service.hyperion-control/resources/language/resource.language.es_es/strings.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "MIME-Version: 1.0\n" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "X-Generator: POEditor.com\n" 7 | "Project-Id-Version: Hyperion Control\n" 8 | "Language: es\n" 9 | 10 | #: 11 | msgctxt "#32000" 12 | msgid "General" 13 | msgstr "General" 14 | 15 | #: 16 | msgctxt "#32001" 17 | msgid "Hyperion IP" 18 | msgstr "IP de Hyperion" 19 | 20 | #: 21 | msgctxt "#32002" 22 | msgid "Hyperion Webserver Port" 23 | msgstr "Puerto del servidor web de Hyperion" 24 | 25 | #: 26 | msgctxt "#32003" 27 | msgid "Switch automatically between 2D/3D" 28 | msgstr "Cambio automático entre 2D/3D" 29 | 30 | #: 31 | msgctxt "#32004" 32 | msgid "Enable Hyperion on startup" 33 | msgstr "Habilitar Hyperion al inicio" 34 | 35 | #: 36 | msgctxt "#32005" 37 | msgid "Disable Hyperion on shutdown" 38 | msgstr "Deshabilitar Hyperion al apagar" 39 | 40 | #: 41 | msgctxt "#32007" 42 | msgid "Show changelog after update" 43 | msgstr "Mostrar el registro de cambios tras la actualización" 44 | 45 | #: 46 | msgctxt "#32008" 47 | msgid "Authorization Token" 48 | msgstr "Token de autorización" 49 | 50 | #: 51 | msgctxt "#32025" 52 | msgid "Component" 53 | msgstr "Componente" 54 | 55 | #: 56 | msgctxt "#32026" 57 | msgid "Enable the chosen component in these situations else disable it." 58 | msgstr "Habilitar el componente elegido en estas situaciones o deshabilitarlo." 59 | 60 | #: 61 | msgctxt "#32027" 62 | msgid "Hyperion component" 63 | msgstr "Componente Hyperion" 64 | 65 | #: 66 | msgctxt "#32028" 67 | msgid "Enable during video playback" 68 | msgstr "Activar durante la reproducción de vídeo" 69 | 70 | #: 71 | msgctxt "#32029" 72 | msgid "Enable during music playback" 73 | msgstr "Activar durante la reproducción de música" 74 | 75 | #: 76 | msgctxt "#32030" 77 | msgid "Enable during player pause" 78 | msgstr "Habilitar durante la pausa del reproductor" 79 | 80 | #: 81 | msgctxt "#32031" 82 | msgid "Enable in Kodi menu" 83 | msgstr "Habilitar en el menú de Kodi" 84 | 85 | #: 86 | msgctxt "#32032" 87 | msgid "Enable during active screensaver" 88 | msgstr "Habilitar durante el salvapantallas activo" 89 | 90 | #: 91 | msgctxt "#32041" 92 | msgid "USB Capture" 93 | msgstr "Captura USB" 94 | 95 | #: 96 | msgctxt "#32042" 97 | msgid "LED Hardware" 98 | msgstr "Hardware LED" 99 | 100 | #: 101 | msgctxt "#32043" 102 | msgid "Smoothing" 103 | msgstr "Suavizado" 104 | 105 | #: 106 | msgctxt "#32045" 107 | msgid "Forwarder" 108 | msgstr "Transmisor" 109 | 110 | #: 111 | msgctxt "#32101" 112 | msgid "Would you like to search for a Hyperion Server and adjust settings?" 113 | msgstr "¿Desea buscar un Servidor Hyperion y ajustar la configuración?" 114 | 115 | #: 116 | msgctxt "#32102" 117 | msgid "Select a Hyperion Server, we found more than one" 118 | msgstr "Seleccione un servidor Hyperion, encontramos más de uno" 119 | 120 | #: 121 | msgctxt "#32104" 122 | msgid "We are sorry, no Hyperion Server has been found. You need to configure IP address and port by hand" 123 | msgstr "Lo sentimos, no se ha encontrado ningún Hyperion Server. Debe configurar la dirección IP y el puerto a mano" 124 | 125 | #: 126 | msgctxt "#32105" 127 | msgid "The Authorization Token isn't valid" 128 | msgstr "Lo sentimos, no se ha encontrado ningún Servidor Hyperion. Es necesario configurar la dirección IP y el puerto a mano" 129 | 130 | #: 131 | msgctxt "#32150" 132 | msgid "Execute" 133 | msgstr "Ejecutar" 134 | 135 | #: 136 | msgctxt "#32151" 137 | msgid "Execute a specific task you might need again.[CR]Select a task from the tasklist and press OK to close the settings dialog." 138 | msgstr "Ejecutar una tarea específica que se pueda necesitar de nuevo.[CR]Seleccionar una tarea de la lista de tareas y pulsar OK para cerrar el diálogo de configuración." 139 | 140 | #: 141 | msgctxt "#32152" 142 | msgid "The addon will handle your request immediately.[CR]Be aware that the addon should be already enabled" 143 | msgstr "El complemento se encargará de la solicitud inmediatamente.[CR]Ten en cuenta que el complemento debe estar ya activado" 144 | 145 | #: 146 | msgctxt "#32153" 147 | msgid "Tasklist" 148 | msgstr "Lista de tareas" 149 | 150 | #: 151 | msgctxt "#32154" 152 | msgid "No Task" 153 | msgstr "Sin Tareas" 154 | 155 | #: 156 | msgctxt "#32155" 157 | msgid "Search: Hyperion Server" 158 | msgstr "Buscar: Servidor Hyperion" 159 | 160 | #: 161 | msgctxt "#32044" 162 | msgid "Blackbar detector" 163 | msgstr "Detección de bordes negros" 164 | 165 | #: 166 | msgctxt "#32040" 167 | msgid "Screen Capture" 168 | msgstr "Captura de Plataforma" 169 | 170 | #: 171 | msgctxt "#32100" 172 | msgid "Welcome to Hyperion Control!" 173 | msgstr "Bienvenidol Control de Hyperion!" 174 | 175 | #: 176 | msgctxt "#32103" 177 | msgid "We found the following Hyperion Server for usage:" 178 | msgstr "Encontramos los siguientes servidores Hyperion para su uso:" 179 | 180 | #: 181 | msgctxt "#32046" 182 | msgid "Boblight Server" 183 | msgstr "Servidor Boblight" 184 | 185 | #: 186 | msgctxt "#32047" 187 | msgid "Hyperion" 188 | msgstr "Hyperion" 189 | 190 | #: 191 | msgctxt "#32048" 192 | msgid "Audio Capture" 193 | msgstr "Captura del Audio" 194 | 195 | -------------------------------------------------------------------------------- /script.service.hyperion-control/resources/language/resource.language.fr_fr/strings.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "MIME-Version: 1.0\n" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "X-Generator: POEditor.com\n" 7 | "Project-Id-Version: Hyperion Control\n" 8 | "Language: fr\n" 9 | 10 | #: 11 | msgctxt "#32000" 12 | msgid "General" 13 | msgstr "Général" 14 | 15 | #: 16 | msgctxt "#32001" 17 | msgid "Hyperion IP" 18 | msgstr "Adresse IP Hyperion" 19 | 20 | #: 21 | msgctxt "#32002" 22 | msgid "Hyperion Webserver Port" 23 | msgstr "Port Webserver Hyperion" 24 | 25 | #: 26 | msgctxt "#32003" 27 | msgid "Switch automatically between 2D/3D" 28 | msgstr "Basculer automatiquement entre 2D/3D" 29 | 30 | #: 31 | msgctxt "#32004" 32 | msgid "Enable Hyperion on startup" 33 | msgstr "Activer Hyperion au démarrage" 34 | 35 | #: 36 | msgctxt "#32005" 37 | msgid "Disable Hyperion on shutdown" 38 | msgstr "Désactiver Hyperion à l'extinction" 39 | 40 | #: 41 | msgctxt "#32007" 42 | msgid "Show changelog after update" 43 | msgstr "Afficher le changelog après la mise à jour" 44 | 45 | #: 46 | msgctxt "#32008" 47 | msgid "Authorization Token" 48 | msgstr "Token d'autorisation" 49 | 50 | #: 51 | msgctxt "#32025" 52 | msgid "Component" 53 | msgstr "Composant" 54 | 55 | #: 56 | msgctxt "#32026" 57 | msgid "Enable the chosen component in these situations else disable it." 58 | msgstr "Activer le composant pour ces situations sinon le désactiver." 59 | 60 | #: 61 | msgctxt "#32027" 62 | msgid "Hyperion component" 63 | msgstr "Composant Hyperion" 64 | 65 | #: 66 | msgctxt "#32028" 67 | msgid "Enable during video playback" 68 | msgstr "Activer durant la lecture vidéo" 69 | 70 | #: 71 | msgctxt "#32029" 72 | msgid "Enable during music playback" 73 | msgstr "Activer durant la lecture audio" 74 | 75 | #: 76 | msgctxt "#32030" 77 | msgid "Enable during player pause" 78 | msgstr "Activer pendant la pause du lecteur" 79 | 80 | #: 81 | msgctxt "#32031" 82 | msgid "Enable in Kodi menu" 83 | msgstr "Activer dans le menu de Kodi" 84 | 85 | #: 86 | msgctxt "#32032" 87 | msgid "Enable during active screensaver" 88 | msgstr "Activer quand l'économiseur d'écran est actif" 89 | 90 | #: 91 | msgctxt "#32041" 92 | msgid "USB Capture" 93 | msgstr "Capture USB" 94 | 95 | #: 96 | msgctxt "#32042" 97 | msgid "LED Hardware" 98 | msgstr "LEDs" 99 | 100 | #: 101 | msgctxt "#32043" 102 | msgid "Smoothing" 103 | msgstr "Lissage" 104 | 105 | #: 106 | msgctxt "#32045" 107 | msgid "Forwarder" 108 | msgstr "Forwarder" 109 | 110 | #: 111 | msgctxt "#32101" 112 | msgid "Would you like to search for a Hyperion Server and adjust settings?" 113 | msgstr "Voulez vous chercher un serveur Hyperion et mettre à jour les paramètres ?" 114 | 115 | #: 116 | msgctxt "#32102" 117 | msgid "Select a Hyperion Server, we found more than one" 118 | msgstr "Plusieurs serveurs Hyperion ont été trouvés, veuillez en sélectionner un" 119 | 120 | #: 121 | msgctxt "#32104" 122 | msgid "We are sorry, no Hyperion Server has been found. You need to configure IP address and port by hand" 123 | msgstr "Désolé, aucun serveur Hyperion trouvé. Configurez l'adresse IP et le port manuellement." 124 | 125 | #: 126 | msgctxt "#32105" 127 | msgid "The Authorization Token isn't valid" 128 | msgstr "Le token d'autorisation n'est pas valide" 129 | 130 | #: 131 | msgctxt "#32150" 132 | msgid "Execute" 133 | msgstr "Exécuter" 134 | 135 | #: 136 | msgctxt "#32151" 137 | msgid "Execute a specific task you might need again.[CR]Select a task from the tasklist and press OK to close the settings dialog." 138 | msgstr "Exécuter une tâche spécifique dont vous pourriez avoir besoin à nouveau.\n" 139 | "Choisir une tâche dans la liste et appuyer sur OK pour fermer la boite de dialogue." 140 | 141 | #: 142 | msgctxt "#32152" 143 | msgid "The addon will handle your request immediately.[CR]Be aware that the addon should be already enabled" 144 | msgstr "L'addon va exécuter votre requête immédiatement.\n" 145 | "Attention : l'addon doit déjà être actif" 146 | 147 | #: 148 | msgctxt "#32153" 149 | msgid "Tasklist" 150 | msgstr "Liste de tâches" 151 | 152 | #: 153 | msgctxt "#32154" 154 | msgid "No Task" 155 | msgstr "Pas de tâche" 156 | 157 | #: 158 | msgctxt "#32155" 159 | msgid "Search: Hyperion Server" 160 | msgstr "Recherche : serveur Hyperion" 161 | 162 | #: 163 | msgctxt "#32044" 164 | msgid "Blackbar detector" 165 | msgstr "Détecteur de bandes noires" 166 | 167 | #: 168 | msgctxt "#32040" 169 | msgid "Screen Capture" 170 | msgstr "Capture interne" 171 | 172 | #: 173 | msgctxt "#32100" 174 | msgid "Welcome to Hyperion Control!" 175 | msgstr "Bienvenue dans l'addon Hyperion!" 176 | 177 | #: 178 | msgctxt "#32103" 179 | msgid "We found the following Hyperion Server for usage:" 180 | msgstr "Le serveur Hyperion suivant a été trouvé pour utilisation:" 181 | 182 | #: 183 | msgctxt "#32046" 184 | msgid "Boblight Server" 185 | msgstr "Serveur Boblight" 186 | 187 | #: 188 | msgctxt "#32047" 189 | msgid "Hyperion" 190 | msgstr "Hyperion" 191 | 192 | #: 193 | msgctxt "#32048" 194 | msgid "Audio Capture" 195 | msgstr "Capture Audio" 196 | 197 | -------------------------------------------------------------------------------- /script.service.hyperion-control/resources/language/resource.language.hu_hu/strings.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "MIME-Version: 1.0\n" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "X-Generator: POEditor.com\n" 7 | "Project-Id-Version: Hyperion Control\n" 8 | "Language: hu\n" 9 | 10 | #: 11 | msgctxt "#32000" 12 | msgid "General" 13 | msgstr "Általános" 14 | 15 | #: 16 | msgctxt "#32001" 17 | msgid "Hyperion IP" 18 | msgstr "Hyperion IP" 19 | 20 | #: 21 | msgctxt "#32002" 22 | msgid "Hyperion Webserver Port" 23 | msgstr "Hyperion Webserver Port" 24 | 25 | #: 26 | msgctxt "#32003" 27 | msgid "Switch automatically between 2D/3D" 28 | msgstr "Automatikus átkapcsolás 2D/3D" 29 | 30 | #: 31 | msgctxt "#32004" 32 | msgid "Enable Hyperion on startup" 33 | msgstr "Hyperion automatikus indítás" 34 | 35 | #: 36 | msgctxt "#32005" 37 | msgid "Disable Hyperion on shutdown" 38 | msgstr "Hyperion kikapcsolása leállításkor" 39 | 40 | #: 41 | msgctxt "#32007" 42 | msgid "Show changelog after update" 43 | msgstr "Változásnapló megjelenítése frissítés után" 44 | 45 | #: 46 | msgctxt "#32008" 47 | msgid "Authorization Token" 48 | msgstr "Hozzáférési Token" 49 | 50 | #: 51 | msgctxt "#32025" 52 | msgid "Component" 53 | msgstr "Komponens" 54 | 55 | #: 56 | msgctxt "#32026" 57 | msgid "Enable the chosen component in these situations else disable it." 58 | msgstr "Engedélyezze a kiválasztott összetevőt, ellenkező esetben tiltsa le." 59 | 60 | #: 61 | msgctxt "#32027" 62 | msgid "Hyperion component" 63 | msgstr "Hyperion komponens" 64 | 65 | #: 66 | msgctxt "#32028" 67 | msgid "Enable during video playback" 68 | msgstr "Engedélyezés videólejátszás közben" 69 | 70 | #: 71 | msgctxt "#32029" 72 | msgid "Enable during music playback" 73 | msgstr "Engedélyezés zenelejátszás közben" 74 | 75 | #: 76 | msgctxt "#32030" 77 | msgid "Enable during player pause" 78 | msgstr "Engedélyezés lejátszási szünetben" 79 | 80 | #: 81 | msgctxt "#32031" 82 | msgid "Enable in Kodi menu" 83 | msgstr "Engedélyezés a Kodi menüben" 84 | 85 | #: 86 | msgctxt "#32032" 87 | msgid "Enable during active screensaver" 88 | msgstr "Engedélyezés aktív képernyővédő alatt" 89 | 90 | #: 91 | msgctxt "#32041" 92 | msgid "USB Capture" 93 | msgstr "USB Capture" 94 | 95 | #: 96 | msgctxt "#32042" 97 | msgid "LED Hardware" 98 | msgstr "LED Hardver" 99 | 100 | #: 101 | msgctxt "#32043" 102 | msgid "Smoothing" 103 | msgstr "Símítás" 104 | 105 | #: 106 | msgctxt "#32045" 107 | msgid "Forwarder" 108 | msgstr "Továbbító" 109 | 110 | #: 111 | msgctxt "#32101" 112 | msgid "Would you like to search for a Hyperion Server and adjust settings?" 113 | msgstr "Szeretne keresni egy Hyperion szervert és módosítani a beállításokat?" 114 | 115 | #: 116 | msgctxt "#32102" 117 | msgid "Select a Hyperion Server, we found more than one" 118 | msgstr "Válasszon egy Hyperion kiszolgálót, többet is találtunk" 119 | 120 | #: 121 | msgctxt "#32104" 122 | msgid "We are sorry, no Hyperion Server has been found. You need to configure IP address and port by hand" 123 | msgstr "Sajnáljuk, nem található Hyperion Server. Az IP-címet és a portot kézzel kell konfigurálnia!" 124 | 125 | #: 126 | msgctxt "#32105" 127 | msgid "The Authorization Token isn't valid" 128 | msgstr "Az engedélyezési token nem érvényes" 129 | 130 | #: 131 | msgctxt "#32150" 132 | msgid "Execute" 133 | msgstr "Végrehajtás" 134 | 135 | #: 136 | msgctxt "#32151" 137 | msgid "Execute a specific task you might need again.[CR]Select a task from the tasklist and press OK to close the settings dialog." 138 | msgstr "Hajtsa végre újra az adott feladatot, amelyre szüksége lehet.[CR]Válasszon ki egy feladatot a feladatlistából, és nyomja meg az OK gombot a beállítások párbeszédpanel bezárásához." 139 | 140 | #: 141 | msgctxt "#32152" 142 | msgid "The addon will handle your request immediately.[CR]Be aware that the addon should be already enabled" 143 | msgstr "A bővítmény azonnal kezeli a kérést.[CR]Ne feledje, hogy a kiegészítőnek már engedélyezve kell lennie" 144 | 145 | #: 146 | msgctxt "#32153" 147 | msgid "Tasklist" 148 | msgstr "Feladat lista" 149 | 150 | #: 151 | msgctxt "#32154" 152 | msgid "No Task" 153 | msgstr "Nincs feladat" 154 | 155 | #: 156 | msgctxt "#32155" 157 | msgid "Search: Hyperion Server" 158 | msgstr "Keresés: Hyperion Server" 159 | 160 | #: 161 | msgctxt "#32044" 162 | msgid "Blackbar detector" 163 | msgstr "Feketesáv detector" 164 | 165 | #: 166 | msgctxt "#32040" 167 | msgid "Screen Capture" 168 | msgstr "Screen Capture" 169 | 170 | #: 171 | msgctxt "#32100" 172 | msgid "Welcome to Hyperion Control!" 173 | msgstr "Üdvözli a Hyperion Control!" 174 | 175 | #: 176 | msgctxt "#32103" 177 | msgid "We found the following Hyperion Server for usage:" 178 | msgstr "A következő Hyperion szervert találtuk használatra:" 179 | 180 | #: 181 | msgctxt "#32046" 182 | msgid "Boblight Server" 183 | msgstr "Boblight Server" 184 | 185 | #: 186 | msgctxt "#32047" 187 | msgid "Hyperion" 188 | msgstr "Hyperion" 189 | 190 | #: 191 | msgctxt "#32048" 192 | msgid "Audio Capture" 193 | msgstr "Hangrögzítés" 194 | 195 | -------------------------------------------------------------------------------- /script.service.hyperion-control/resources/language/resource.language.pl_pl/strings.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "MIME-Version: 1.0\n" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "X-Generator: POEditor.com\n" 7 | "Project-Id-Version: Hyperion Control\n" 8 | "Language: pl\n" 9 | 10 | #: 11 | msgctxt "#32000" 12 | msgid "General" 13 | msgstr "Ogólne" 14 | 15 | #: 16 | msgctxt "#32001" 17 | msgid "Hyperion IP" 18 | msgstr "Hyperion IP" 19 | 20 | #: 21 | msgctxt "#32002" 22 | msgid "Hyperion Webserver Port" 23 | msgstr "Port Hyperion Webserver" 24 | 25 | #: 26 | msgctxt "#32003" 27 | msgid "Switch automatically between 2D/3D" 28 | msgstr "Zmieniaj automatycznie 2D/3D" 29 | 30 | #: 31 | msgctxt "#32004" 32 | msgid "Enable Hyperion on startup" 33 | msgstr "Uruchom Hyperion wraz ze startem systemu" 34 | 35 | #: 36 | msgctxt "#32005" 37 | msgid "Disable Hyperion on shutdown" 38 | msgstr "Wyłącz Hyperion przy zamknięciu systemu" 39 | 40 | #: 41 | msgctxt "#32007" 42 | msgid "Show changelog after update" 43 | msgstr "Pokaż dziennik zmian po aktualizacji" 44 | 45 | #: 46 | msgctxt "#32008" 47 | msgid "Authorization Token" 48 | msgstr "Token autoryzacyjny" 49 | 50 | #: 51 | msgctxt "#32025" 52 | msgid "Component" 53 | msgstr "Komponent" 54 | 55 | #: 56 | msgctxt "#32026" 57 | msgid "Enable the chosen component in these situations else disable it." 58 | msgstr "Włącz wybrany komponent w wybranych sytuacjach - w przeciwnym wypadku wyłącz komponent." 59 | 60 | #: 61 | msgctxt "#32027" 62 | msgid "Hyperion component" 63 | msgstr "Komponent Hyperion" 64 | 65 | #: 66 | msgctxt "#32028" 67 | msgid "Enable during video playback" 68 | msgstr "Włącz podczas odtwarzania wideo" 69 | 70 | #: 71 | msgctxt "#32029" 72 | msgid "Enable during music playback" 73 | msgstr "Włącz podczas odtwarzania muzyki" 74 | 75 | #: 76 | msgctxt "#32030" 77 | msgid "Enable during player pause" 78 | msgstr "Włącz podczas zapauzowania odtwarzacza" 79 | 80 | #: 81 | msgctxt "#32031" 82 | msgid "Enable in Kodi menu" 83 | msgstr "Włącz w menu Kodi" 84 | 85 | #: 86 | msgctxt "#32032" 87 | msgid "Enable during active screensaver" 88 | msgstr "Włącz podczas aktywnego wygaszacza ekranu" 89 | 90 | #: 91 | msgctxt "#32041" 92 | msgid "USB Capture" 93 | msgstr "Przechwytywanie USB" 94 | 95 | #: 96 | msgctxt "#32042" 97 | msgid "LED Hardware" 98 | msgstr "Sprzęt LED" 99 | 100 | #: 101 | msgctxt "#32043" 102 | msgid "Smoothing" 103 | msgstr "Wygładzanie" 104 | 105 | #: 106 | msgctxt "#32045" 107 | msgid "Forwarder" 108 | msgstr "Przekierowywacz" 109 | 110 | #: 111 | msgctxt "#32101" 112 | msgid "Would you like to search for a Hyperion Server and adjust settings?" 113 | msgstr "Czy chcesz wyszukać serwer Hyperion i dostosować ustawienia?" 114 | 115 | #: 116 | msgctxt "#32102" 117 | msgid "Select a Hyperion Server, we found more than one" 118 | msgstr "Wybierz serwer Hyperion, znaleziono więcej niż jeden" 119 | 120 | #: 121 | msgctxt "#32104" 122 | msgid "We are sorry, no Hyperion Server has been found. You need to configure IP address and port by hand" 123 | msgstr "Niestety, nie znaleziono serwera Hyperion. Skonfiguruj adres IP oraz port manualnie" 124 | 125 | #: 126 | msgctxt "#32105" 127 | msgid "The Authorization Token isn't valid" 128 | msgstr "Nieprawidłowy token autoryzacyjny" 129 | 130 | #: 131 | msgctxt "#32150" 132 | msgid "Execute" 133 | msgstr "Wykonaj" 134 | 135 | #: 136 | msgctxt "#32151" 137 | msgid "Execute a specific task you might need again.[CR]Select a task from the tasklist and press OK to close the settings dialog." 138 | msgstr "Wykonaj zadanie ponownie. Wybierz zadanie z listy zadań i wciśnij OK aby zamknąć okno ustawień." 139 | 140 | #: 141 | msgctxt "#32152" 142 | msgid "The addon will handle your request immediately.[CR]Be aware that the addon should be already enabled" 143 | msgstr "Twoje zadanie zostanie wykonane natychmiastowo. Miej na uwadze, że addon powinien być wcześniej włączony" 144 | 145 | #: 146 | msgctxt "#32153" 147 | msgid "Tasklist" 148 | msgstr "Lista zadań" 149 | 150 | #: 151 | msgctxt "#32154" 152 | msgid "No Task" 153 | msgstr "Brak zadania" 154 | 155 | #: 156 | msgctxt "#32155" 157 | msgid "Search: Hyperion Server" 158 | msgstr "Wyszukaj: Serwer Hyperion" 159 | 160 | #: 161 | msgctxt "#32044" 162 | msgid "Blackbar detector" 163 | msgstr "Detekcja czarnych ramek" 164 | 165 | #: 166 | msgctxt "#32040" 167 | msgid "Screen Capture" 168 | msgstr "Przechwytywanie platformy" 169 | 170 | #: 171 | msgctxt "#32100" 172 | msgid "Welcome to Hyperion Control!" 173 | msgstr "Witaj w Hyperion Control!" 174 | 175 | #: 176 | msgctxt "#32103" 177 | msgid "We found the following Hyperion Server for usage:" 178 | msgstr "Znaleziono następujący serwer Hyperion:" 179 | 180 | #: 181 | msgctxt "#32046" 182 | msgid "Boblight Server" 183 | msgstr "Server Boblight" 184 | 185 | #: 186 | msgctxt "#32047" 187 | msgid "Hyperion" 188 | msgstr "Hyperion" 189 | 190 | #: 191 | msgctxt "#32048" 192 | msgid "Audio Capture" 193 | msgstr "Przechwytywanie Dźwięku" 194 | 195 | -------------------------------------------------------------------------------- /script.service.hyperion-control/resources/lib/__init__.py: -------------------------------------------------------------------------------- 1 | """Hyperion control addon library.""" 2 | -------------------------------------------------------------------------------- /script.service.hyperion-control/resources/lib/api_client.py: -------------------------------------------------------------------------------- 1 | """Hyperion JSON RPC/HTTP(S) API client.""" 2 | from __future__ import annotations 3 | 4 | import contextlib 5 | import random 6 | import string 7 | from json import JSONDecodeError 8 | from typing import Any 9 | 10 | import requests 11 | from requests.exceptions import ConnectTimeout 12 | 13 | from resources.lib.interfaces import GuiHandler 14 | from resources.lib.interfaces import Logger 15 | from resources.lib.interfaces import SettingsManager 16 | 17 | 18 | class ApiClient: 19 | """Manages the request to the hyperion server.""" 20 | 21 | def __init__( 22 | self, logger: Logger, gui: GuiHandler, settings: SettingsManager 23 | ) -> None: 24 | self._settings = settings 25 | self._logger = logger 26 | self._gui = gui 27 | self._session = requests.Session() 28 | 29 | @property 30 | def headers(self) -> dict[str, str]: 31 | """Request headers.""" 32 | headers = {"Content-type": "application/json"} 33 | if self._settings.auth_token: 34 | headers["Authorization"] = f"token {self._settings.auth_token}" 35 | return headers 36 | 37 | def _send( 38 | self, body: dict[str, Any], timeout: float = 0.5 39 | ) -> dict[str, Any] | None: 40 | url = self._settings.base_url 41 | logger = self._logger 42 | logger.log(f"Send to: {url} payload: {body}") 43 | with contextlib.suppress(ConnectTimeout, JSONDecodeError): 44 | response = self._session.post( 45 | url, json=body, headers=self.headers, timeout=timeout 46 | ) 47 | json_content = response.json() 48 | if json_content.get("success"): 49 | return json_content.get("info") 50 | if json_content["error"] == "No Authorization": 51 | self._gui.notify_text("Error: No Authorization, API Token required") 52 | logger.error(json_content["error"]) 53 | return None 54 | 55 | def needs_auth(self) -> bool: 56 | """Whether the hyperion server needs API authentication.""" 57 | if res := self._send({"command": "authorize", "subcommand": "tokenRequired"}): 58 | return res["required"] 59 | return False 60 | 61 | def get_token(self) -> str: 62 | """Requests the authentication token.""" 63 | pool = string.ascii_uppercase + string.ascii_lowercase + string.digits 64 | control_code = "".join(random.choice(pool) for _ in range(16)) 65 | message = { 66 | "command": "authorize", 67 | "subcommand": "requestToken", 68 | "comment": "Kodi Hyperion Control", 69 | "id": control_code, 70 | } 71 | return res["token"] if (res := self._send(message, timeout=180)) else "" 72 | 73 | def send_component_state(self, component: str, state: bool) -> None: 74 | """Sends the component state.""" 75 | body = { 76 | "command": "componentstate", 77 | "componentstate": {"component": component, "state": state}, 78 | } 79 | if component == "FORWARDER": 80 | self.switch_to_instance(0) 81 | self._send(body) 82 | else: 83 | self.send_to_all_instances(body) 84 | 85 | def send_video_mode(self, mode: str) -> None: 86 | """Sends the current video mode.""" 87 | self._send({"command": "videoMode", "videoMode": mode}) 88 | 89 | def get_server_info(self) -> dict[str, Any] | None: 90 | """Gets the server info.""" 91 | return self._send({"command": "serverinfo"}) 92 | 93 | def get_instances(self) -> list[dict[str, Any]]: 94 | """Gets the server info.""" 95 | server_info = self.get_server_info() 96 | return server_info["instance"] if server_info else [] 97 | 98 | def switch_to_instance(self, instance_num: int) -> None: 99 | """Switches to the specified instance.""" 100 | self._send( 101 | {"command": "instance", "subcommand": "switchTo", "instance": instance_num} 102 | ) 103 | 104 | def send_to_all_instances(self, body: dict[str, Any]) -> None: 105 | """Sends a command to all instances.""" 106 | for instance in self.get_instances(): 107 | self.switch_to_instance(instance["instance"]) 108 | self._send(body) 109 | -------------------------------------------------------------------------------- /script.service.hyperion-control/resources/lib/gui.py: -------------------------------------------------------------------------------- 1 | """Kodi GUI handler.""" 2 | from __future__ import annotations 3 | 4 | import xbmcaddon 5 | import xbmcgui 6 | 7 | from resources.lib import ssdp 8 | from resources.lib.interfaces import SettingsManager 9 | 10 | 11 | class GuiHandler: 12 | """Kodi GUI handler.""" 13 | 14 | def __init__( 15 | self, addon: xbmcaddon.Addon, settings_manager: SettingsManager 16 | ) -> None: 17 | self._addon = addon 18 | self._settings = settings_manager 19 | self._dialog = xbmcgui.Dialog() # TODO: DI with embedded getlocalizedstring 20 | self._addon_name = addon.getAddonInfo("name") 21 | self._addon_icon = addon.getAddonInfo("icon") 22 | 23 | def _get_localized_string(self, label_id: int) -> str: 24 | """Returns the localized string of a label ID.""" 25 | return self._addon.getLocalizedString(label_id) 26 | 27 | def notify_label(self, label_id: int) -> None: 28 | """Displays a notification with the localized message.""" 29 | message = self._get_localized_string(label_id) 30 | self.notify_text(message, time=1000, icon=self._addon_icon) 31 | 32 | def notify_text( 33 | self, message: str, time: int = 3000, icon: str = xbmcgui.NOTIFICATION_INFO 34 | ) -> None: 35 | """Displays a notification.""" 36 | self._dialog.notification(self._addon_name, message, icon, time) 37 | 38 | def do_ssdp_discovery(self) -> None: 39 | """Perform the SSDP discovery and lets the user choose the service.""" 40 | servers = ssdp.discover() 41 | 42 | if not servers: 43 | self._dialog.ok("Hyperion Control", self._get_localized_string(32104)) 44 | return 45 | # if there is more than one entry the user should select one 46 | if len(servers) > 1: 47 | selection_idx = self._dialog.select( 48 | self._get_localized_string(32102), build_select_list(servers) 49 | ) 50 | selected_server = servers[selection_idx] if selection_idx > -1 else None 51 | else: 52 | selected_server = servers[0] 53 | self._dialog.ok( 54 | "Hyperion Control", 55 | f'{self._get_localized_string(32103)}[CR]{selected_server["ip"]}:{selected_server["port"]}', 56 | ) 57 | 58 | if selected_server: 59 | self._settings.address = selected_server["ip"] 60 | self._settings.port = selected_server["port"] 61 | 62 | def do_initial_wizard(self) -> None: 63 | """Displays the initial wizard.""" 64 | if self._dialog.yesno( 65 | "Hyperion Control", 66 | f"{self._get_localized_string(32100)}[CR]{self._get_localized_string(32101)}", 67 | ): 68 | self.do_ssdp_discovery() 69 | self._addon.openSettings() 70 | 71 | def do_changelog_display(self) -> None: 72 | """Displays the changelog.""" 73 | if self._settings.current_version != self._addon.getAddonInfo("version"): 74 | self._dialog.textviewer( 75 | "Hyperion Control - Changelog", self._addon.getAddonInfo("changelog") 76 | ) 77 | 78 | 79 | def build_select_list(data: list[dict[str, str | int | None]]) -> list[str]: 80 | return [f"{item['ip']}:{item['port']}" for item in data] 81 | -------------------------------------------------------------------------------- /script.service.hyperion-control/resources/lib/hyperion.py: -------------------------------------------------------------------------------- 1 | """Hyperion Controller.""" 2 | from __future__ import annotations 3 | 4 | from collections.abc import Callable 5 | 6 | from resources.lib.interfaces import ApiClient 7 | from resources.lib.interfaces import GuiHandler 8 | from resources.lib.interfaces import Logger 9 | from resources.lib.interfaces import SettingsManager 10 | 11 | 12 | class Hyperion: 13 | """Main instance class.""" 14 | 15 | def __init__( 16 | self, 17 | settings_manager: SettingsManager, 18 | logger: Logger, 19 | gui_handler: GuiHandler, 20 | api_client: ApiClient, 21 | get_video_mode_function: Callable[[], str], 22 | addon_version: str, 23 | ) -> None: 24 | self._prev_video_mode = "2D" 25 | 26 | self._addon_version = addon_version 27 | self._client = api_client 28 | self._settings = settings_manager 29 | self._logger = logger 30 | self._gui = gui_handler 31 | self._video_mode_fn = get_video_mode_function 32 | 33 | self._kodi_state: str = "menu" 34 | self._prev_comp_state: bool | None = None 35 | self._initialize() 36 | 37 | def _initialize(self) -> None: 38 | # check for changelog display, but not on first run 39 | settings = self._settings 40 | if settings.should_display_changelog: 41 | self._gui.do_changelog_display() 42 | 43 | # check for setup wizard 44 | if settings.first_run: 45 | # be sure to fill in the current version 46 | settings.set_addon_version(self._addon_version) 47 | self._gui.do_initial_wizard() 48 | 49 | if settings.enable_hyperion: 50 | self._client.send_component_state("ALL", True) 51 | settings.set_first_run_done() 52 | 53 | def notify(self, command: str) -> None: 54 | """Process the commands sent by the observables.""" 55 | self._logger.log(f"received command: {command}") 56 | if command == "updateSettings": 57 | self.update_settings() 58 | else: 59 | self._kodi_state = command 60 | self._update_state() 61 | 62 | def update_settings(self) -> None: 63 | """Update the settings.""" 64 | settings = self._settings 65 | settings.read_settings() 66 | 67 | auth_token = settings.auth_token 68 | if auth_token and len(auth_token) != 36: 69 | self._gui.notify_label(32105) 70 | 71 | # Checkout Tasklist for pending tasks 72 | if settings.tasks == 1: 73 | settings.set_tasks(0) 74 | self._gui.do_ssdp_discovery() 75 | 76 | def _update_state(self) -> None: 77 | comp_state = self._get_comp_state() 78 | if self._prev_comp_state != comp_state: 79 | self._client.send_component_state(self._settings.target_comp, comp_state) 80 | self._prev_comp_state = comp_state 81 | 82 | # update stereoscopic mode always, better apis for detection available? 83 | # Bug: race condition, return of jsonapi has wrong gui state 84 | # after onPlayBackStopped after a 3D movie 85 | if self._settings.video_mode_enabled: 86 | new_mode = self._video_mode_fn() 87 | if self._prev_video_mode != new_mode: 88 | self._client.send_video_mode(new_mode) 89 | self._prev_video_mode = new_mode 90 | 91 | def _get_comp_state(self) -> bool: 92 | """Get the desired state of the target component based on kodi state.""" 93 | settings = self._settings 94 | state = self._kodi_state 95 | if state == "screensaver": 96 | return settings.screensaver_enabled 97 | if state == "pause": 98 | return settings.pause_enabled 99 | if state == "playAudio": 100 | return settings.audio_enabled 101 | if state == "playVideo": 102 | return settings.video_enabled 103 | return settings.menu_enabled 104 | 105 | def stop(self) -> None: 106 | """Stops the hyperion control.""" 107 | if self._settings.disable_hyperion: 108 | self._client.send_component_state("ALL", False) 109 | self._logger.log("Hyperion-control stopped") 110 | -------------------------------------------------------------------------------- /script.service.hyperion-control/resources/lib/interfaces.py: -------------------------------------------------------------------------------- 1 | """Observer interface.""" 2 | from typing import Protocol 3 | 4 | 5 | class Observer(Protocol): 6 | """Observer interface.""" 7 | 8 | def notify(self, command: str) -> None: 9 | """Process the received command.""" 10 | pass 11 | 12 | 13 | class Logger(Protocol): 14 | """Logger interface.""" 15 | 16 | def log(self, message: str, level: int = 0) -> None: 17 | """Logs a message.""" 18 | 19 | def debug(self, message: str) -> None: 20 | """Writes a debug message to the log.""" 21 | 22 | def info(self, message: str) -> None: 23 | """Writes an info message to the log.""" 24 | 25 | def error(self, message: str) -> None: 26 | """Writes an error message to the log.""" 27 | 28 | 29 | class SettingsManager(Protocol): 30 | """Settings manager interface.""" 31 | 32 | auth_token: str 33 | current_version: str 34 | address: str 35 | port: int 36 | video_mode_enabled: bool 37 | enable_hyperion: bool 38 | disable_hyperion: bool 39 | target_comp: str 40 | screensaver_enabled: bool 41 | video_enabled: bool 42 | audio_enabled: bool 43 | pause_enabled: bool 44 | menu_enabled: bool 45 | show_changelog_on_update: bool 46 | tasks: int 47 | first_run: bool 48 | 49 | @property 50 | def should_display_changelog(self) -> bool: 51 | """Whether the changelog should be displayed.""" 52 | pass 53 | 54 | @property 55 | def base_url(self) -> str: 56 | """Hyperion server JSON RPC base url.""" 57 | pass 58 | 59 | def set_tasks(self, value: int) -> None: 60 | """Sets the tasks to run.""" 61 | 62 | def set_addon_version(self, value: str) -> None: 63 | """Sets the current addon version for changelog checks.""" 64 | 65 | def set_first_run_done(self) -> None: 66 | """Sets the first run settings to false.""" 67 | 68 | def read_settings(self) -> None: 69 | """Read all settings.""" 70 | 71 | 72 | class GuiHandler(Protocol): 73 | """GUI handler interface.""" 74 | 75 | def notify_label(self, label_id: int) -> None: 76 | """Displays a notification with the localized message.""" 77 | 78 | def notify_text(self, message: str, time: int = 3000, icon: str = "info") -> None: 79 | """Displays a notification.""" 80 | 81 | def do_ssdp_discovery(self) -> None: 82 | """Perform the SSDP discovery and lets the user choose the service.""" 83 | 84 | def do_initial_wizard(self) -> None: 85 | """Displays the initial wizard.""" 86 | 87 | def do_changelog_display(self) -> None: 88 | """Displays the changelog.""" 89 | 90 | 91 | class ApiClient(Protocol): 92 | """API client interface.""" 93 | 94 | def send_component_state(self, component: str, state: bool) -> None: 95 | """Sends the component state.""" 96 | 97 | def send_video_mode(self, mode: str) -> None: 98 | """Sends the current video mode.""" 99 | -------------------------------------------------------------------------------- /script.service.hyperion-control/resources/lib/logger.py: -------------------------------------------------------------------------------- 1 | """Logging facility.""" 2 | import xbmc 3 | 4 | 5 | class Logger: 6 | """Logging facility for Kodi add-ons.""" 7 | 8 | def __init__(self, addon_name: str) -> None: 9 | self._addon_name = addon_name 10 | 11 | def log(self, message: str, level: int = xbmc.LOGDEBUG) -> None: 12 | """Writes the message to the logger with the addon name as prefix.""" 13 | xbmc.log(f"[{self._addon_name}] - {message}", level=level) 14 | 15 | def debug(self, message: str) -> None: 16 | """Writes a debug message to the log.""" 17 | self.log(message) 18 | 19 | def info(self, message: str) -> None: 20 | """Writes an info message to the log.""" 21 | self.log(message, level=xbmc.LOGINFO) 22 | 23 | def error(self, message: str) -> None: 24 | """Writes an error message to the log.""" 25 | self.log(message, level=xbmc.LOGERROR) 26 | -------------------------------------------------------------------------------- /script.service.hyperion-control/resources/lib/monitor.py: -------------------------------------------------------------------------------- 1 | """Observable monitor.""" 2 | from __future__ import annotations 3 | 4 | import xbmc 5 | 6 | from resources.lib.interfaces import Observer 7 | 8 | 9 | class XBMCMonitor(xbmc.Monitor): 10 | """xbmc monitor class.""" 11 | 12 | def __init__(self) -> None: 13 | super().__init__() 14 | self._observers: list[Observer] = [] 15 | 16 | def register_observer(self, observer: Observer) -> None: 17 | """Register an observer to the events.""" 18 | self._observers.append(observer) 19 | 20 | def notify_observers(self, command: str) -> None: 21 | """Sends the command to the observers.""" 22 | for observer in self._observers: 23 | observer.notify(command) 24 | 25 | def onSettingsChanged(self) -> None: 26 | """Settings changed event.""" 27 | self.notify_observers("updateSettings") 28 | 29 | def onScreensaverActivated(self) -> None: 30 | """Screensaver activated event.""" 31 | self.notify_observers("screensaver") 32 | 33 | def onScreensaverDeactivated(self) -> None: 34 | """Screensaver deactivated event.""" 35 | self.notify_observers("menu") 36 | -------------------------------------------------------------------------------- /script.service.hyperion-control/resources/lib/player.py: -------------------------------------------------------------------------------- 1 | """Observable player.""" 2 | from __future__ import annotations 3 | 4 | import xbmc 5 | 6 | from resources.lib.interfaces import Observer 7 | 8 | 9 | class Player(xbmc.Player): 10 | """xbmc player class.""" 11 | 12 | def __init__(self) -> None: 13 | super().__init__() 14 | self._observers: list[Observer] = [] 15 | 16 | def register_observer(self, observer: Observer) -> None: 17 | """Register an observer to the events.""" 18 | self._observers.append(observer) 19 | 20 | def notify_observers(self, command: str) -> None: 21 | """Sends the command to the observers.""" 22 | for observer in self._observers: 23 | observer.notify(command) 24 | 25 | def onPlayBackPaused(self) -> None: 26 | """Playback paused event.""" 27 | self.notify_observers("pause") 28 | 29 | def onPlayBackResumed(self) -> None: 30 | """Playback resumed event.""" 31 | self._play_handler() 32 | 33 | def onAVStarted(self) -> None: 34 | """Audio or Video started event.""" 35 | self._play_handler() 36 | 37 | def onPlayBackStopped(self) -> None: 38 | """Playback stopped event.""" 39 | self.notify_observers("menu") 40 | 41 | def onPlayBackEnded(self) -> None: 42 | """Playback end event.""" 43 | self.notify_observers("menu") 44 | 45 | def _play_handler(self) -> None: 46 | command = "playAudio" if self.isPlayingAudio() else "playVideo" 47 | self.notify_observers(command) 48 | -------------------------------------------------------------------------------- /script.service.hyperion-control/resources/lib/settings_manager.py: -------------------------------------------------------------------------------- 1 | """Settings manager.""" 2 | from __future__ import annotations 3 | 4 | from typing import TYPE_CHECKING 5 | from typing import Any 6 | 7 | from resources.lib.interfaces import Logger 8 | 9 | if TYPE_CHECKING: 10 | import xbmcaddon 11 | 12 | 13 | INT_TO_COMP_STRING = { 14 | 0: "GRABBER", 15 | 1: "V4L", 16 | 2: "AUDIO", 17 | 3: "LEDDEVICE", 18 | 4: "SMOOTHING", 19 | 5: "BLACKBORDER", 20 | 6: "FORWARDER", 21 | 7: "BOBLIGHTSERVER", 22 | 8: "ALL", 23 | } 24 | 25 | 26 | class SettingsManager: 27 | """Class which contains all addon settings.""" 28 | 29 | def __init__( 30 | self, settings: xbmcaddon.Settings, logger: Logger, addon: xbmcaddon.Addon 31 | ) -> None: 32 | self._logger = logger 33 | self.rev = 0 34 | self._settings = settings 35 | self._addon = addon 36 | self.current_version: str = "" 37 | self._address: str = "localhost" 38 | self._port: int = 8090 39 | self._base_url = "http://localhost:8090/json-rpc" 40 | self.video_mode_enabled: bool 41 | self.enable_hyperion: bool 42 | self.disable_hyperion: bool 43 | self.auth_token: str 44 | self.target_comp: str 45 | self.screensaver_enabled: bool 46 | self.video_enabled: bool 47 | self.audio_enabled: bool 48 | self.pause_enabled: bool 49 | self.menu_enabled: bool 50 | self.show_changelog_on_update: bool 51 | self.tasks: int 52 | self.first_run: bool 53 | self.read_settings() 54 | 55 | @property 56 | def address(self) -> str: 57 | """Hyperion server address.""" 58 | return self._address 59 | 60 | @address.setter 61 | def address(self, value: str) -> None: 62 | """Hyperion server address.""" 63 | self._address = value 64 | self._update_url() 65 | self._set_string("ip", value) 66 | 67 | @property 68 | def port(self) -> int: 69 | """Hyperion server port.""" 70 | return self._port 71 | 72 | @port.setter 73 | def port(self, value: int) -> None: 74 | """Hyperion server port.""" 75 | self._port = value 76 | self._update_url() 77 | self._set_int("port", value) 78 | 79 | def _update_url(self) -> None: 80 | self._base_url = f"http://{self._address}:{self._port}/json-rpc" 81 | 82 | @property 83 | def base_url(self) -> str: 84 | """Hyperion server JSON RPC base url.""" 85 | return self._base_url 86 | 87 | @property 88 | def should_display_changelog(self) -> bool: 89 | """Whether the changelog should be displayed.""" 90 | return self.show_changelog_on_update and not self.first_run 91 | 92 | def _set_string(self, name: str, value: str) -> None: 93 | self._addon.setSettingString(name, value) 94 | outcome = value == self._settings.getString(name) 95 | self._log_set_outcome(name, value, outcome) 96 | 97 | def _set_int(self, name: str, value: int) -> None: 98 | self._addon.setSettingInt(name, value) 99 | outcome = value == self._settings.getInt(name) 100 | self._log_set_outcome(name, value, outcome) 101 | 102 | def _set_bool(self, name: str, value: bool) -> None: 103 | self._addon.setSettingBool(name, value) 104 | outcome = value == self._settings.getBool(name) 105 | self._log_set_outcome(name, value, outcome) 106 | 107 | def _log_set_outcome(self, name: str, value: Any, outcome: bool) -> None: 108 | if not outcome: 109 | self._logger.error(f"Error setting {name} to {value} (outcome={outcome})") 110 | else: 111 | self._logger.log(f"Set {name} to {value}") 112 | 113 | def set_tasks(self, value: int) -> None: 114 | """Sets the tasks to run.""" 115 | self._set_int("tasks", value) 116 | self.tasks = value 117 | 118 | def set_addon_version(self, value: str) -> None: 119 | """Sets the current addon version for changelog checks.""" 120 | self._set_string("currAddonVersion", value) 121 | self.current_version = value 122 | 123 | def set_first_run_done(self) -> None: 124 | """Sets the first run settings to false.""" 125 | self._set_bool("firstRun", False) 126 | self.first_run = False 127 | 128 | def read_settings(self) -> None: 129 | """Read all settings.""" 130 | settings = self._settings 131 | get_bool = settings.getBool 132 | get_string = settings.getString 133 | get_int = settings.getInt 134 | self._address = get_string("ip") 135 | self._port = get_int("port") 136 | self._update_url() 137 | self.auth_token = get_string("authToken") 138 | self.video_mode_enabled = get_bool("videoModeEnabled") 139 | self.enable_hyperion = get_bool("enableHyperion") 140 | self.disable_hyperion = get_bool("disableHyperion") 141 | self.show_changelog_on_update = get_bool("showChangelogOnUpdate") 142 | self.first_run = get_bool("firstRun") 143 | self.current_version = get_string("currAddonVersion") 144 | 145 | self.target_comp = INT_TO_COMP_STRING.get( 146 | get_int("targetComponent"), "NOT_FOUND" 147 | ) 148 | self.video_enabled = get_bool("videoEnabled") 149 | self.audio_enabled = get_bool("audioEnabled") 150 | self.pause_enabled = get_bool("pauseEnabled") 151 | self.menu_enabled = get_bool("menuEnabled") 152 | self.screensaver_enabled = get_bool("screensaverEnabled") 153 | self.tasks = get_int("tasks") 154 | self.rev += 1 155 | 156 | self._log_settings() 157 | 158 | def _log_settings(self) -> None: 159 | log = self._logger.log 160 | log("Settings updated!") 161 | log(f"Hyperion ip: {self.address}") 162 | log(f"Hyperion port: {self.port}") 163 | log(f"Enable H on start: {self.enable_hyperion}") 164 | log(f"Disable H on stop: {self.disable_hyperion}") 165 | log(f"VideoMode enabled: {self.video_mode_enabled}") 166 | log(f"Hyperion target comp: {self.target_comp}") 167 | log(f"Screensaver enabled: {self.screensaver_enabled}") 168 | log(f"Video enabled: {self.video_enabled}") 169 | log(f"Audio enabled: {self.audio_enabled}") 170 | log(f"Pause enabled: {self.pause_enabled}") 171 | log(f"Menu enabled: {self.menu_enabled}") 172 | log(f"ChangelogOnUpdate: {self.show_changelog_on_update}") 173 | log(f"tasks: {self.tasks}") 174 | log(f"first run: {self.first_run}") 175 | log(f"current version: {self.current_version}") 176 | -------------------------------------------------------------------------------- /script.service.hyperion-control/resources/lib/ssdp.py: -------------------------------------------------------------------------------- 1 | """ 2 | Hyperion UPnP / SSDP service discovery. 3 | 4 | Copyright 2014 Dan Krause 5 | Copyright 2023 Andrea Ghensi 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | 19 | All credits to Dan Krause at Github: https://gist.github.com/dankrause/6000248 20 | """ 21 | from __future__ import annotations 22 | 23 | import http.client 24 | import socket 25 | from io import BytesIO 26 | from typing import Any 27 | 28 | 29 | class _FakeSocket(BytesIO): 30 | """Fake socket to make ssdp response compatible with HTTPResponse.""" 31 | 32 | def makefile(self, *_args: Any, **_kw: Any) -> BytesIO: 33 | """Duck-types the call to make the socket available.""" 34 | return self 35 | 36 | 37 | def _parse_location(location: str | None) -> tuple[str, int]: 38 | if location is None: 39 | return "", 0 40 | start = location.find("//") + 2 41 | end = location.rfind("/") 42 | hostname, port = location[start:end].split(":") 43 | return hostname, int(port) 44 | 45 | 46 | class SSDPResponse: 47 | """ 48 | Response from SSDP discovery. 49 | 50 | Typical Hyperion response: 51 | 52 | CACHE-CONTROL: max-age = 1800 53 | DATE: Sun, 23 Jul 2023 19:58:01 G7T19444 54 | EXT: 55 | LOCATION: http://192.168.2.180:8090/description.xml 56 | SERVER: LibreELEC (official): 11.0.1/11.0 UPnP/1.0 Hyperion/2.0.16-beta.1+PR1617 57 | ST: urn:hyperion-project.org:device:basic:1 58 | USN: uuid:04928741-2192-5c24-93e6-638c9a184443 59 | HYPERION-FBS-PORT: 19400 60 | HYPERION-JSS-PORT: 19444 61 | HYPERION-NAME: My Hyperion Config 62 | """ 63 | 64 | def __init__(self, response: bytes) -> None: 65 | r = http.client.HTTPResponse(_FakeSocket(response)) # type: ignore 66 | r.begin() 67 | hostname, port = _parse_location(r.getheader("location")) 68 | self.hostname = hostname 69 | self.port = port 70 | self.usn = r.getheader("usn") 71 | self.st = r.getheader("st") 72 | cache = r.getheader("cache-control") 73 | self.cache = cache.split("=")[1] if cache else "" 74 | 75 | 76 | def discover(timeout: int = 3, retries: int = 1, mx: int = 2) -> list[dict[str, Any]]: 77 | service = "urn:hyperion-project.org:device:basic:1" 78 | group = ("239.255.255.250", 1900) 79 | lines = [ 80 | "M-SEARCH * HTTP/1.1", 81 | f"HOST: {group[0]}:{group[1]}", 82 | 'MAN: "ssdp:discover"', 83 | f"ST: {service}", 84 | f"MX: {mx}", 85 | "", 86 | "", 87 | ] 88 | message = "\r\n".join(lines).encode("utf-8") 89 | socket.setdefaulttimeout(timeout) 90 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) 91 | sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2) 92 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 93 | responses = [] 94 | for _ in range(retries): 95 | sock.sendto(message, group) 96 | while True: 97 | try: 98 | res_data = SSDPResponse(sock.recv(1024)) 99 | if res_data.st != service: 100 | continue 101 | responses.append( 102 | { 103 | "ip": res_data.hostname, 104 | "port": res_data.port, 105 | "usn": res_data.usn, 106 | } 107 | ) 108 | except socket.timeout: 109 | break 110 | sock.close() 111 | return responses 112 | -------------------------------------------------------------------------------- /script.service.hyperion-control/resources/lib/utils.py: -------------------------------------------------------------------------------- 1 | """Stereoscopic mode detection.""" 2 | import json 3 | 4 | import xbmc 5 | 6 | from resources.lib.interfaces import Logger 7 | 8 | 9 | def get_stereoscopic_mode(logger: Logger) -> str: 10 | """Returns the currently active stereoscopic mode.""" 11 | msg = { 12 | "jsonrpc": "2.0", 13 | "method": "GUI.GetProperties", 14 | "params": {"properties": ["stereoscopicmode"]}, 15 | "id": 669, 16 | } 17 | try: 18 | response = json.loads(xbmc.executeJSONRPC(json.dumps(msg))) 19 | mode = response["result"]["stereoscopicmode"]["mode"] 20 | except Exception: 21 | logger.error("Error executing JSONRPC call") 22 | return "2D" 23 | if mode == "split_vertical": 24 | return "3DSBS" 25 | return "3DTAB" if mode == "split_horizontal" else "2D" 26 | -------------------------------------------------------------------------------- /script.service.hyperion-control/resources/screenshot-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperion-project/hyperion.control/931034fbbb7b35a2aa1eeb4aec3c547fe3cd1fae/script.service.hyperion-control/resources/screenshot-01.png -------------------------------------------------------------------------------- /script.service.hyperion-control/resources/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /script.service.hyperion-control/service.py: -------------------------------------------------------------------------------- 1 | """Hyperion control addon entrypoint.""" 2 | import xbmcaddon 3 | from resources.lib.api_client import ApiClient 4 | from resources.lib.gui import GuiHandler 5 | from resources.lib.hyperion import Hyperion 6 | from resources.lib.logger import Logger 7 | from resources.lib.monitor import XBMCMonitor 8 | from resources.lib.player import Player 9 | from resources.lib.settings_manager import SettingsManager 10 | from resources.lib.utils import get_stereoscopic_mode 11 | 12 | ADDON_NAME = "script.service.hyperion-control" 13 | 14 | 15 | def main() -> None: 16 | addon = xbmcaddon.Addon(ADDON_NAME) 17 | logger = Logger(addon.getAddonInfo("name")) 18 | settings_manager = SettingsManager(addon.getSettings(), logger, addon) 19 | gui_handler = GuiHandler(addon, settings_manager) 20 | api_client = ApiClient(logger, gui_handler, settings_manager) 21 | 22 | def get_video_mode_fn() -> str: 23 | return get_stereoscopic_mode(logger) 24 | 25 | hyperion = Hyperion( 26 | settings_manager, 27 | logger, 28 | gui_handler, 29 | api_client, 30 | get_video_mode_fn, 31 | addon.getAddonInfo("version"), 32 | ) 33 | player = Player() 34 | player.register_observer(hyperion) 35 | monitor = XBMCMonitor() 36 | monitor.register_observer(hyperion) 37 | 38 | while not monitor.abortRequested(): 39 | if monitor.waitForAbort(10): 40 | hyperion.stop() 41 | break 42 | 43 | 44 | if __name__ == "__main__": 45 | main() 46 | --------------------------------------------------------------------------------