diff --git a/script.service.hyperion/addon.xml b/script.service.hyperion/addon.xml
new file mode 100644
index 000000000..553fcc18f
--- /dev/null
+++ b/script.service.hyperion/addon.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+ Capture and send your current Kodi-picture to your Hyperion Ambient Lighting system.
+ Übermittle das aktuelle Kodi-Bild an dein Hyperion Ambient Lighting System.
+ Cattura e invia l'immagine corrente di Kodi ad tuo sistema di Ambient Lighting Hyperion.
+ Hyperion is an opensource Bias or Ambient Lighting implementation which runs on many devices.[CR]This addon sends through the Hyperion PROTO-port your current picture to your Hyperion daemon for further processing like black border detection or smoothing.
+ Hyperion ist ein Bias oder Ambient Lighting System, welches auf vielen Plattformen lauffähig ist.[CR]Dieses Addon sendet über den PROTO-Port laufend Bilder an Hyperion.
+ Hyperion è un'implementazione open source di Bias o Ambient Lighting che supporta molti dispositivi.[CR]Questo addon invia l'immagine corrente alla porta PROTO del demone Hyperion per ulteriori elaborazioni come il rilevamento bordi neri o lo smoothing.
+ all
+ https://hyperion-project.org/forum
+ https://www.hyperion-project.org
+
+ MIT
+
+ 20.0.1
+ - Clean-ups
+ - Handle missing PIL library on Android
+ 20.0.0
+ - Kodi 20 (Nexus) support
+ - New languages: Hebrew, Italian
+
+
+ resources/icon.png
+ resources/fanart.png
+ resources/screenshot-01.png
+
+
+
diff --git a/script.service.hyperion/resources/__init__.py b/script.service.hyperion/resources/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/script.service.hyperion/resources/fanart.png b/script.service.hyperion/resources/fanart.png
new file mode 100644
index 000000000..3944f0514
Binary files /dev/null and b/script.service.hyperion/resources/fanart.png differ
diff --git a/script.service.hyperion/resources/icon.png b/script.service.hyperion/resources/icon.png
new file mode 100644
index 000000000..c68c40a30
Binary files /dev/null and b/script.service.hyperion/resources/icon.png differ
diff --git a/script.service.hyperion/resources/language/resource.language.de_de/strings.po b/script.service.hyperion/resources/language/resource.language.de_de/strings.po
new file mode 100644
index 000000000..82d02c042
--- /dev/null
+++ b/script.service.hyperion/resources/language/resource.language.de_de/strings.po
@@ -0,0 +1,63 @@
+# Kodi Media Center language file
+# Addon Name: Hyperion Ambilight
+# Addon id: script.service.hyperion
+# Addon Provider: hyperion-project
+
+msgid ""
+msgstr ""
+"Project-Id-Version: Kodi Addons\n"
+"Report-Msgid-Bugs-To: hyperion-project.org\n"
+"Last-Translator: hyperion-project\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: de-DE\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "#32001"
+msgid "General"
+msgstr "Allgemein"
+
+msgctxt "#32002"
+msgid "Enable addon (Grabbing)"
+msgstr "Aktiviere das Addon (Grabbing)"
+
+msgctxt "#32003"
+msgid "Hyperion hostname or IP-address"
+msgstr "Hyperion Hostname oder IP-Adresse"
+
+msgctxt "#32004"
+msgid "Hyperion host PROTO-port"
+msgstr "Hyperion Host PROTO-Port"
+
+msgctxt "#32005"
+msgid "Hyperion priority channel"
+msgstr "Hyperion Priorität"
+
+msgctxt "#32006"
+msgid "Reconnect timeout (s)"
+msgstr "Zeitlimit für erneute Verbindung (s)"
+
+msgctxt "#32007"
+msgid "Enable when on screensaver"
+msgstr "Aktiviere bei Bildschirmschoner"
+
+msgctxt "#32008"
+msgid "Grabber picture width (pixel)"
+msgstr "Bildbreite (pixel)"
+
+msgctxt "#32010"
+msgid "Frame rate (fps)"
+msgstr "Bildfrequenz (fps)"
+
+msgctxt "#32100"
+msgid "Error: Unable to connect to Hyperion. Wrong hostname or IP-address?"
+msgstr "Fehler: Kann keine Verbindung zu Hyperion aufbauen. Falscher Hostname oder IP-Adresse?"
+
+msgctxt "#32101"
+msgid "Error: Unable to send image to Hyperion. Wrong PROTO-port?"
+msgstr "Fehler: Kann kein Bild an Hyperion senden. Falscher PROTO-Port?"
+
+msgctxt "#32102"
+msgid "ATTENTION: Higher the values below may lead to performance issues"
+msgstr "ACHTUNG: Ein Erhöhen der folgenden Werte kann zu hoher CPU-Last führen"
diff --git a/script.service.hyperion/resources/language/resource.language.en_gb/strings.po b/script.service.hyperion/resources/language/resource.language.en_gb/strings.po
new file mode 100644
index 000000000..f52092c6f
--- /dev/null
+++ b/script.service.hyperion/resources/language/resource.language.en_gb/strings.po
@@ -0,0 +1,63 @@
+# Kodi Media Center language file
+# Addon Name: Hyperion Ambilight
+# Addon id: script.service.hyperion
+# Addon Provider: hyperion-project
+
+msgid ""
+msgstr ""
+"Project-Id-Version: Kodi Addons\n"
+"Report-Msgid-Bugs-To: hyperion-project.org\n"
+"Last-Translator: hyperion-project\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: en_GB\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "#32001"
+msgid "General"
+msgstr "General"
+
+msgctxt "#32002"
+msgid "Enable addon (Grabbing)"
+msgstr "Enable addon (Grabbing)"
+
+msgctxt "#32003"
+msgid "Hyperion hostname or IP-address"
+msgstr "Hyperion hostname or IP-address"
+
+msgctxt "#32004"
+msgid "Hyperion host PROTO-port"
+msgstr "Hyperion host PROTO-port"
+
+msgctxt "#32005"
+msgid "Hyperion priority channel"
+msgstr "Hyperion priority channel"
+
+msgctxt "#32006"
+msgid "Reconnect timeout (s)"
+msgstr "Reconnect timeout (s)"
+
+msgctxt "#32007"
+msgid "Enable when on screensaver"
+msgstr "Enable when on screensaver"
+
+msgctxt "#32008"
+msgid "Grabber picture width (pixel)"
+msgstr "Grabber picture width (pixel)"
+
+msgctxt "#32010"
+msgid "Frame rate (fps)"
+msgstr "Frame rate (fps)"
+
+msgctxt "#32100"
+msgid "Error: Unable to connect to Hyperion. Wrong hostname or IP-address?"
+msgstr ""
+
+msgctxt "#32101"
+msgid "Error: Unable to send image to Hyperion. Wrong PROTO-port?"
+msgstr ""
+
+msgctxt "#32102"
+msgid "ATTENTION: Higher the values below may lead to performance issues"
+msgstr ""
diff --git a/script.service.hyperion/resources/language/resource.language.he_il/strings.po b/script.service.hyperion/resources/language/resource.language.he_il/strings.po
new file mode 100644
index 000000000..9d66c7df7
--- /dev/null
+++ b/script.service.hyperion/resources/language/resource.language.he_il/strings.po
@@ -0,0 +1,66 @@
+# Kodi Media Center language file
+# Addon Name: Hyperion Ambilight
+# Addon id: script.service.hyperion
+# Addon Provider: hyperion-project
+msgid ""
+msgstr ""
+"Project-Id-Version: Kodi Addons\n"
+"Report-Msgid-Bugs-To: hyperion-project.org\n"
+"POT-Creation-Date: 2014-10-26 17:05+0000\n"
+"PO-Revision-Date: 2017-10-09 10:05+0300\n"
+"Last-Translator: A. Dambledore\n"
+"Language-Team: Eng2Heb\n"
+"Language: he_IL\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+
+msgctxt "#32001"
+msgid "General"
+msgstr "כללי"
+
+msgctxt "#32002"
+msgid "Enable addon (Grabbing)"
+msgstr "אפשר הרחבה (Grabbing - תפיסה)"
+
+msgctxt "#32003"
+msgid "Hyperion hostname or IP-address"
+msgstr "היפריון כתובת ה-IP של המארח"
+
+msgctxt "#32004"
+msgid "Hyperion host PROTO-port"
+msgstr "היפריון פורט PROTO של המארח"
+
+msgctxt "#32005"
+msgid "Hyperion priority channel"
+msgstr "ערוץ עדיפות היפריון"
+
+msgctxt "#32006"
+msgid "Reconnect timeout (s)"
+msgstr "זמן קצוב לחיבור מחדש (ש)"
+
+msgctxt "#32007"
+msgid "Enable when on screensaver"
+msgstr "אפשר במצב שומר מסך"
+
+msgctxt "#32008"
+msgid "Grabber picture width (pixel)"
+msgstr "תופש התמונות רוחב (פיקסלים)"
+
+msgctxt "#32010"
+msgid "Frame rate (fps)"
+msgstr "קצב הפריימים (fps)"
+
+msgctxt "#32100"
+msgid "Error: Unable to connect to Hyperion. Wrong hostname or IP-address?"
+msgstr "שגיאה: אין אפשרות להתחבר אל היפריון. כתובת ה-IP שגויה?"
+
+msgctxt "#32101"
+msgid "Error: Unable to send image to Hyperion. Wrong PROTO-port?"
+msgstr "שגיאה: אין אפשרות להתחבר אל היפריון. פורט PROTO שגוי?"
+
+msgctxt "#32102"
+msgid "ATTENTION: Higher the values below may lead to performance issues"
+msgstr "שימו לב: הערכים הגבוהים שלהלן עלולים להוביל לבעיות ביצועים"
diff --git a/script.service.hyperion/resources/language/resource.language.it_it/strings.po b/script.service.hyperion/resources/language/resource.language.it_it/strings.po
new file mode 100644
index 000000000..b893343ed
--- /dev/null
+++ b/script.service.hyperion/resources/language/resource.language.it_it/strings.po
@@ -0,0 +1,63 @@
+# Kodi Media Center language file
+# Addon Name: Hyperion Ambilight
+# Addon id: script.service.hyperion
+# Addon Provider: hyperion-project
+
+msgid ""
+msgstr ""
+"Project-Id-Version: Kodi Addons\n"
+"Report-Msgid-Bugs-To: hyperion-project.org\n"
+"Last-Translator: hyperion-project\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: it-IT\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "#32001"
+msgid "General"
+msgstr "Generale"
+
+msgctxt "#32002"
+msgid "Enable addon (Grabbing)"
+msgstr "Abilita l'Addon (Acquisizione)"
+
+msgctxt "#32003"
+msgid "Hyperion hostname or IP-address"
+msgstr "Nome host o indirizzo IP di Hyperion"
+
+msgctxt "#32004"
+msgid "Hyperion host PROTO-port"
+msgstr "Porta PROTO di Hyperion"
+
+msgctxt "#32005"
+msgid "Hyperion priority channel"
+msgstr "Canale prioritario di Hyperion"
+
+msgctxt "#32006"
+msgid "Reconnect timeout (s)"
+msgstr "Timeout riconnessione (s)"
+
+msgctxt "#32007"
+msgid "Enable when on screensaver"
+msgstr "Abilita con lo screen saver"
+
+msgctxt "#32008"
+msgid "Grabber picture width (pixel)"
+msgstr "Larghezza immagine acquisita (pixel)"
+
+msgctxt "#32010"
+msgid "Frame rate (fps)"
+msgstr "Frame rate (fps)"
+
+msgctxt "#32100"
+msgid "Error: Unable to connect to Hyperion. Wrong hostname or IP-address?"
+msgstr "Errore: Impossible connettersi a Hyperion. Nome host o indirizzo IP sbagliato?"
+
+msgctxt "#32101"
+msgid "Error: Unable to send image to Hyperion. Wrong PROTO-port?"
+msgstr "Errore: Impossibile inviare l'immagine a Hyperion. porta PROTO sbagliata?"
+
+msgctxt "#32102"
+msgid "ATTENTION: Higher the values below may lead to performance issues"
+msgstr "ATTENZIONE: Alzare i valori seguenti può portare a problemi di performance"
diff --git a/script.service.hyperion/resources/lib/__init__.py b/script.service.hyperion/resources/lib/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/script.service.hyperion/resources/lib/gui.py b/script.service.hyperion/resources/lib/gui.py
new file mode 100644
index 000000000..4ed312594
--- /dev/null
+++ b/script.service.hyperion/resources/lib/gui.py
@@ -0,0 +1,34 @@
+"""Kodi GUI handler."""
+from __future__ import annotations
+
+import xbmcaddon
+import xbmcgui
+from resources.lib.settings import SettingsManager
+
+
+class GuiHandler:
+ """Kodi GUI handler."""
+
+ def __init__(
+ self, addon: xbmcaddon.Addon, settings_manager: SettingsManager
+ ) -> None:
+ self._addon = addon
+ self._settings = settings_manager
+ self._dialog = xbmcgui.Dialog() # TODO: DI with embedded getlocalizedstring
+ self._addon_name = addon.getAddonInfo("name")
+ self._addon_icon = addon.getAddonInfo("icon")
+
+ def _get_localized_string(self, label_id: int) -> str:
+ """Returns the localized string of a label ID."""
+ return self._addon.getLocalizedString(label_id)
+
+ def notify_label(self, label_id: int) -> None:
+ """Displays a notification with the localized message."""
+ message = self._get_localized_string(label_id)
+ self.notify_text(message, time=1000, icon=self._addon_icon)
+
+ def notify_text(
+ self, message: str, time: int = 3000, icon: str = xbmcgui.NOTIFICATION_INFO
+ ) -> None:
+ """Displays a notification."""
+ self._dialog.notification(self._addon_name, message, icon, time)
diff --git a/script.service.hyperion/resources/lib/hyperion/__init__.py b/script.service.hyperion/resources/lib/hyperion/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/script.service.hyperion/resources/lib/hyperion/hyperion.py b/script.service.hyperion/resources/lib/hyperion/hyperion.py
new file mode 100644
index 000000000..159791457
--- /dev/null
+++ b/script.service.hyperion/resources/lib/hyperion/hyperion.py
@@ -0,0 +1,146 @@
+"""
+Kodi video capturer for Hyperion.
+
+Copyright (c) 2013-2023 Hyperion Team
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+"""
+# mypy: ignore-errors
+import socket
+import struct
+from typing import Any
+
+from .message_pb2 import ClearRequest
+from .message_pb2 import ColorRequest
+from .message_pb2 import HyperionReply
+from .message_pb2 import HyperionRequest
+from .message_pb2 import ImageRequest
+
+
+class Hyperion:
+ """
+ Hyperion connection class.
+
+ A Hyperion object will connect to the Hyperion server and provide
+ easy to use functions to send requests
+
+ Note that the function will block until a reply has been received
+ from the Hyperion server (or the call has timed out)
+ """
+
+ def __init__(self, server: str, port: int) -> None:
+ """
+ Constructor.
+
+ Args:
+ server: server address of Hyperion
+ port: port number of Hyperion
+ """
+ # create a new socket
+ self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self._socket.settimeout(2)
+
+ # Connect socket to the provided server
+ self._socket.connect((server, port))
+
+ def __del__(self) -> None:
+ """Destructor."""
+ # close the socket
+ self._socket.close()
+
+ def send_color(self, color: int, priority: int, duration: int = -1) -> None:
+ """
+ Send a static color to Hyperion.
+
+ Args:
+ color: integer value with the color as 0x00RRGGBB
+ priority: the priority channel to use
+ duration: duration the leds should be set
+ """
+ request = HyperionRequest()
+ request.command = HyperionRequest.COLOR
+ color_request = request.Extensions[ColorRequest.colorRequest]
+ color_request.rgbColor = color
+ color_request.priority = priority
+ color_request.duration = duration
+ self._send_message(request)
+
+ def send_image(
+ self, width: int, height: int, data: bytes, priority: int, duration: int = -1
+ ) -> None:
+ """
+ Send an image to Hyperion.
+
+ Args:
+ width: width of the image
+ height: height of the image
+ data: image data (byte string containing 0xRRGGBB pixel values)
+ priority: the priority channel to use
+ duration: duration the leds should be set
+ """
+ request = HyperionRequest()
+ request.command = HyperionRequest.IMAGE
+ image_request = request.Extensions[ImageRequest.imageRequest]
+ image_request.imagewidth = width
+ image_request.imageheight = height
+ image_request.imagedata = bytes(data)
+ image_request.priority = priority
+ image_request.duration = duration
+ self._send_message(request)
+
+ def clear(self, priority: int) -> None:
+ """Clear the given priority channel.
+
+ Args:
+ priority: the priority channel to clear
+ """
+ request = HyperionRequest()
+ request.command = HyperionRequest.CLEAR
+ clear_request = request.Extensions[ClearRequest.clearRequest]
+ clear_request.priority = priority
+ self._send_message(request)
+
+ def clear_all(self) -> None:
+ """Clear all active priority channels."""
+ request = HyperionRequest()
+ request.command = HyperionRequest.CLEARALL
+ self._send_message(request)
+
+ def _send_message(self, message: Any) -> None:
+ """
+ Send the given proto message to Hyperion.
+
+ A RuntimeError will be raised if the reply contains an error
+
+ Args:
+ message : proto request to send
+ """
+ binary_request = message.SerializeToString()
+ binary_size = struct.pack(">I", len(binary_request))
+ self._socket.sendall(binary_size)
+ self._socket.sendall(binary_request)
+
+ # receive a reply from Hyperion
+ size = struct.unpack(">I", self._socket.recv(4))[0]
+ reply = HyperionReply()
+ reply.ParseFromString(self._socket.recv(size))
+
+ # check the reply
+ if not reply.success:
+ raise RuntimeError(f"Hyperion server error: {reply.error}")
diff --git a/script.service.hyperion/resources/lib/hyperion/message.proto b/script.service.hyperion/resources/lib/hyperion/message.proto
new file mode 100644
index 000000000..94a2c92c9
--- /dev/null
+++ b/script.service.hyperion/resources/lib/hyperion/message.proto
@@ -0,0 +1,82 @@
+syntax = "proto2";
+
+package proto;
+
+message HyperionRequest {
+ enum Command {
+ COLOR = 1;
+ IMAGE = 2;
+ CLEAR = 3;
+ CLEARALL = 4;
+ }
+
+ // command specification
+ required Command command = 1;
+
+ // extensions to define all specific requests
+ extensions 10 to 100;
+}
+
+message ColorRequest {
+ extend HyperionRequest {
+ optional ColorRequest colorRequest = 10;
+ }
+
+ // priority to use when setting the color
+ required int32 priority = 1;
+
+ // integer value containing the rgb color (0x00RRGGBB)
+ required int32 RgbColor = 2;
+
+ // duration of the request (negative results in infinite)
+ optional int32 duration = 3;
+}
+
+message ImageRequest {
+ extend HyperionRequest {
+ optional ImageRequest imageRequest = 11;
+ }
+
+ // priority to use when setting the image
+ required int32 priority = 1;
+
+ // width of the image
+ required int32 imagewidth = 2;
+
+ // height of the image
+ required int32 imageheight = 3;
+
+ // image data
+ required bytes imagedata = 4;
+
+ // duration of the request (negative results in infinite)
+ optional int32 duration = 5;
+}
+
+message ClearRequest {
+ extend HyperionRequest {
+ optional ClearRequest clearRequest = 12;
+ }
+
+ // priority which need to be cleared
+ required int32 priority = 1;
+}
+
+message HyperionReply {
+ enum Type {
+ REPLY = 1;
+ VIDEO = 2;
+ }
+
+ // Identifies which field is filled in.
+ required Type type = 1;
+
+ // flag indication success or failure
+ optional bool success = 2;
+
+ // string indicating the reason for failure (if applicable)
+ optional string error = 3;
+
+ // Proto Messages for video mode
+ optional int32 video = 4;
+}
diff --git a/script.service.hyperion/resources/lib/hyperion/message_pb2.py b/script.service.hyperion/resources/lib/hyperion/message_pb2.py
new file mode 100644
index 000000000..707b36c73
--- /dev/null
+++ b/script.service.hyperion/resources/lib/hyperion/message_pb2.py
@@ -0,0 +1,617 @@
+# mypy: ignore-errors
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: message.proto
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+ name="message.proto",
+ package="proto",
+ syntax="proto2",
+ serialized_options=None,
+ create_key=_descriptor._internal_create_key,
+ serialized_pb=b'\n\rmessage.proto\x12\x05proto"\x82\x01\n\x0fHyperionRequest\x12/\n\x07\x63ommand\x18\x01 \x02(\x0e\x32\x1e.proto.HyperionRequest.Command"8\n\x07\x43ommand\x12\t\n\x05\x43OLOR\x10\x01\x12\t\n\x05IMAGE\x10\x02\x12\t\n\x05\x43LEAR\x10\x03\x12\x0c\n\x08\x43LEARALL\x10\x04*\x04\x08\n\x10\x65"\x87\x01\n\x0c\x43olorRequest\x12\x10\n\x08priority\x18\x01 \x02(\x05\x12\x10\n\x08RgbColor\x18\x02 \x02(\x05\x12\x10\n\x08\x64uration\x18\x03 \x01(\x05\x32\x41\n\x0c\x63olorRequest\x12\x16.proto.HyperionRequest\x18\n \x01(\x0b\x32\x13.proto.ColorRequest"\xb1\x01\n\x0cImageRequest\x12\x10\n\x08priority\x18\x01 \x02(\x05\x12\x12\n\nimagewidth\x18\x02 \x02(\x05\x12\x13\n\x0bimageheight\x18\x03 \x02(\x05\x12\x11\n\timagedata\x18\x04 \x02(\x0c\x12\x10\n\x08\x64uration\x18\x05 \x01(\x05\x32\x41\n\x0cimageRequest\x12\x16.proto.HyperionRequest\x18\x0b \x01(\x0b\x32\x13.proto.ImageRequest"c\n\x0c\x43learRequest\x12\x10\n\x08priority\x18\x01 \x02(\x05\x32\x41\n\x0c\x63learRequest\x12\x16.proto.HyperionRequest\x18\x0c \x01(\x0b\x32\x13.proto.ClearRequest"\x85\x01\n\rHyperionReply\x12\'\n\x04type\x18\x01 \x02(\x0e\x32\x19.proto.HyperionReply.Type\x12\x0f\n\x07success\x18\x02 \x01(\x08\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12\r\n\x05video\x18\x04 \x01(\x05"\x1c\n\x04Type\x12\t\n\x05REPLY\x10\x01\x12\t\n\x05VIDEO\x10\x02',
+)
+
+
+_HYPERIONREQUEST_COMMAND = _descriptor.EnumDescriptor(
+ name="Command",
+ full_name="proto.HyperionRequest.Command",
+ filename=None,
+ file=DESCRIPTOR,
+ create_key=_descriptor._internal_create_key,
+ values=[
+ _descriptor.EnumValueDescriptor(
+ name="COLOR",
+ index=0,
+ number=1,
+ serialized_options=None,
+ type=None,
+ create_key=_descriptor._internal_create_key,
+ ),
+ _descriptor.EnumValueDescriptor(
+ name="IMAGE",
+ index=1,
+ number=2,
+ serialized_options=None,
+ type=None,
+ create_key=_descriptor._internal_create_key,
+ ),
+ _descriptor.EnumValueDescriptor(
+ name="CLEAR",
+ index=2,
+ number=3,
+ serialized_options=None,
+ type=None,
+ create_key=_descriptor._internal_create_key,
+ ),
+ _descriptor.EnumValueDescriptor(
+ name="CLEARALL",
+ index=3,
+ number=4,
+ serialized_options=None,
+ type=None,
+ create_key=_descriptor._internal_create_key,
+ ),
+ ],
+ containing_type=None,
+ serialized_options=None,
+ serialized_start=93,
+ serialized_end=149,
+)
+_sym_db.RegisterEnumDescriptor(_HYPERIONREQUEST_COMMAND)
+
+_HYPERIONREPLY_TYPE = _descriptor.EnumDescriptor(
+ name="Type",
+ full_name="proto.HyperionReply.Type",
+ filename=None,
+ file=DESCRIPTOR,
+ create_key=_descriptor._internal_create_key,
+ values=[
+ _descriptor.EnumValueDescriptor(
+ name="REPLY",
+ index=0,
+ number=1,
+ serialized_options=None,
+ type=None,
+ create_key=_descriptor._internal_create_key,
+ ),
+ _descriptor.EnumValueDescriptor(
+ name="VIDEO",
+ index=1,
+ number=2,
+ serialized_options=None,
+ type=None,
+ create_key=_descriptor._internal_create_key,
+ ),
+ ],
+ containing_type=None,
+ serialized_options=None,
+ serialized_start=682,
+ serialized_end=710,
+)
+_sym_db.RegisterEnumDescriptor(_HYPERIONREPLY_TYPE)
+
+
+_HYPERIONREQUEST = _descriptor.Descriptor(
+ name="HyperionRequest",
+ full_name="proto.HyperionRequest",
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ create_key=_descriptor._internal_create_key,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name="command",
+ full_name="proto.HyperionRequest.command",
+ index=0,
+ number=1,
+ type=14,
+ cpp_type=8,
+ label=2,
+ has_default_value=False,
+ default_value=1,
+ message_type=None,
+ enum_type=None,
+ containing_type=None,
+ is_extension=False,
+ extension_scope=None,
+ serialized_options=None,
+ file=DESCRIPTOR,
+ create_key=_descriptor._internal_create_key,
+ ),
+ ],
+ extensions=[],
+ nested_types=[],
+ enum_types=[
+ _HYPERIONREQUEST_COMMAND,
+ ],
+ serialized_options=None,
+ is_extendable=True,
+ syntax="proto2",
+ extension_ranges=[
+ (10, 101),
+ ],
+ oneofs=[],
+ serialized_start=25,
+ serialized_end=155,
+)
+
+
+_COLORREQUEST = _descriptor.Descriptor(
+ name="ColorRequest",
+ full_name="proto.ColorRequest",
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ create_key=_descriptor._internal_create_key,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name="priority",
+ full_name="proto.ColorRequest.priority",
+ index=0,
+ number=1,
+ type=5,
+ cpp_type=1,
+ label=2,
+ has_default_value=False,
+ default_value=0,
+ message_type=None,
+ enum_type=None,
+ containing_type=None,
+ is_extension=False,
+ extension_scope=None,
+ serialized_options=None,
+ file=DESCRIPTOR,
+ create_key=_descriptor._internal_create_key,
+ ),
+ _descriptor.FieldDescriptor(
+ name="RgbColor",
+ full_name="proto.ColorRequest.RgbColor",
+ index=1,
+ number=2,
+ type=5,
+ cpp_type=1,
+ label=2,
+ has_default_value=False,
+ default_value=0,
+ message_type=None,
+ enum_type=None,
+ containing_type=None,
+ is_extension=False,
+ extension_scope=None,
+ serialized_options=None,
+ file=DESCRIPTOR,
+ create_key=_descriptor._internal_create_key,
+ ),
+ _descriptor.FieldDescriptor(
+ name="duration",
+ full_name="proto.ColorRequest.duration",
+ index=2,
+ number=3,
+ type=5,
+ cpp_type=1,
+ label=1,
+ has_default_value=False,
+ default_value=0,
+ message_type=None,
+ enum_type=None,
+ containing_type=None,
+ is_extension=False,
+ extension_scope=None,
+ serialized_options=None,
+ file=DESCRIPTOR,
+ create_key=_descriptor._internal_create_key,
+ ),
+ ],
+ extensions=[
+ _descriptor.FieldDescriptor(
+ name="colorRequest",
+ full_name="proto.ColorRequest.colorRequest",
+ index=0,
+ number=10,
+ type=11,
+ cpp_type=10,
+ label=1,
+ has_default_value=False,
+ default_value=None,
+ message_type=None,
+ enum_type=None,
+ containing_type=None,
+ is_extension=True,
+ extension_scope=None,
+ serialized_options=None,
+ file=DESCRIPTOR,
+ create_key=_descriptor._internal_create_key,
+ ),
+ ],
+ nested_types=[],
+ enum_types=[],
+ serialized_options=None,
+ is_extendable=False,
+ syntax="proto2",
+ extension_ranges=[],
+ oneofs=[],
+ serialized_start=158,
+ serialized_end=293,
+)
+
+
+_IMAGEREQUEST = _descriptor.Descriptor(
+ name="ImageRequest",
+ full_name="proto.ImageRequest",
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ create_key=_descriptor._internal_create_key,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name="priority",
+ full_name="proto.ImageRequest.priority",
+ index=0,
+ number=1,
+ type=5,
+ cpp_type=1,
+ label=2,
+ has_default_value=False,
+ default_value=0,
+ message_type=None,
+ enum_type=None,
+ containing_type=None,
+ is_extension=False,
+ extension_scope=None,
+ serialized_options=None,
+ file=DESCRIPTOR,
+ create_key=_descriptor._internal_create_key,
+ ),
+ _descriptor.FieldDescriptor(
+ name="imagewidth",
+ full_name="proto.ImageRequest.imagewidth",
+ index=1,
+ number=2,
+ type=5,
+ cpp_type=1,
+ label=2,
+ has_default_value=False,
+ default_value=0,
+ message_type=None,
+ enum_type=None,
+ containing_type=None,
+ is_extension=False,
+ extension_scope=None,
+ serialized_options=None,
+ file=DESCRIPTOR,
+ create_key=_descriptor._internal_create_key,
+ ),
+ _descriptor.FieldDescriptor(
+ name="imageheight",
+ full_name="proto.ImageRequest.imageheight",
+ index=2,
+ number=3,
+ type=5,
+ cpp_type=1,
+ label=2,
+ has_default_value=False,
+ default_value=0,
+ message_type=None,
+ enum_type=None,
+ containing_type=None,
+ is_extension=False,
+ extension_scope=None,
+ serialized_options=None,
+ file=DESCRIPTOR,
+ create_key=_descriptor._internal_create_key,
+ ),
+ _descriptor.FieldDescriptor(
+ name="imagedata",
+ full_name="proto.ImageRequest.imagedata",
+ index=3,
+ number=4,
+ type=12,
+ cpp_type=9,
+ label=2,
+ has_default_value=False,
+ default_value=b"",
+ message_type=None,
+ enum_type=None,
+ containing_type=None,
+ is_extension=False,
+ extension_scope=None,
+ serialized_options=None,
+ file=DESCRIPTOR,
+ create_key=_descriptor._internal_create_key,
+ ),
+ _descriptor.FieldDescriptor(
+ name="duration",
+ full_name="proto.ImageRequest.duration",
+ index=4,
+ number=5,
+ type=5,
+ cpp_type=1,
+ label=1,
+ has_default_value=False,
+ default_value=0,
+ message_type=None,
+ enum_type=None,
+ containing_type=None,
+ is_extension=False,
+ extension_scope=None,
+ serialized_options=None,
+ file=DESCRIPTOR,
+ create_key=_descriptor._internal_create_key,
+ ),
+ ],
+ extensions=[
+ _descriptor.FieldDescriptor(
+ name="imageRequest",
+ full_name="proto.ImageRequest.imageRequest",
+ index=0,
+ number=11,
+ type=11,
+ cpp_type=10,
+ label=1,
+ has_default_value=False,
+ default_value=None,
+ message_type=None,
+ enum_type=None,
+ containing_type=None,
+ is_extension=True,
+ extension_scope=None,
+ serialized_options=None,
+ file=DESCRIPTOR,
+ create_key=_descriptor._internal_create_key,
+ ),
+ ],
+ nested_types=[],
+ enum_types=[],
+ serialized_options=None,
+ is_extendable=False,
+ syntax="proto2",
+ extension_ranges=[],
+ oneofs=[],
+ serialized_start=296,
+ serialized_end=473,
+)
+
+
+_CLEARREQUEST = _descriptor.Descriptor(
+ name="ClearRequest",
+ full_name="proto.ClearRequest",
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ create_key=_descriptor._internal_create_key,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name="priority",
+ full_name="proto.ClearRequest.priority",
+ index=0,
+ number=1,
+ type=5,
+ cpp_type=1,
+ label=2,
+ has_default_value=False,
+ default_value=0,
+ message_type=None,
+ enum_type=None,
+ containing_type=None,
+ is_extension=False,
+ extension_scope=None,
+ serialized_options=None,
+ file=DESCRIPTOR,
+ create_key=_descriptor._internal_create_key,
+ ),
+ ],
+ extensions=[
+ _descriptor.FieldDescriptor(
+ name="clearRequest",
+ full_name="proto.ClearRequest.clearRequest",
+ index=0,
+ number=12,
+ type=11,
+ cpp_type=10,
+ label=1,
+ has_default_value=False,
+ default_value=None,
+ message_type=None,
+ enum_type=None,
+ containing_type=None,
+ is_extension=True,
+ extension_scope=None,
+ serialized_options=None,
+ file=DESCRIPTOR,
+ create_key=_descriptor._internal_create_key,
+ ),
+ ],
+ nested_types=[],
+ enum_types=[],
+ serialized_options=None,
+ is_extendable=False,
+ syntax="proto2",
+ extension_ranges=[],
+ oneofs=[],
+ serialized_start=475,
+ serialized_end=574,
+)
+
+
+_HYPERIONREPLY = _descriptor.Descriptor(
+ name="HyperionReply",
+ full_name="proto.HyperionReply",
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ create_key=_descriptor._internal_create_key,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name="type",
+ full_name="proto.HyperionReply.type",
+ index=0,
+ number=1,
+ type=14,
+ cpp_type=8,
+ label=2,
+ has_default_value=False,
+ default_value=1,
+ message_type=None,
+ enum_type=None,
+ containing_type=None,
+ is_extension=False,
+ extension_scope=None,
+ serialized_options=None,
+ file=DESCRIPTOR,
+ create_key=_descriptor._internal_create_key,
+ ),
+ _descriptor.FieldDescriptor(
+ name="success",
+ full_name="proto.HyperionReply.success",
+ index=1,
+ number=2,
+ type=8,
+ cpp_type=7,
+ label=1,
+ has_default_value=False,
+ default_value=False,
+ message_type=None,
+ enum_type=None,
+ containing_type=None,
+ is_extension=False,
+ extension_scope=None,
+ serialized_options=None,
+ file=DESCRIPTOR,
+ create_key=_descriptor._internal_create_key,
+ ),
+ _descriptor.FieldDescriptor(
+ name="error",
+ full_name="proto.HyperionReply.error",
+ index=2,
+ number=3,
+ type=9,
+ cpp_type=9,
+ label=1,
+ has_default_value=False,
+ default_value=b"".decode("utf-8"),
+ message_type=None,
+ enum_type=None,
+ containing_type=None,
+ is_extension=False,
+ extension_scope=None,
+ serialized_options=None,
+ file=DESCRIPTOR,
+ create_key=_descriptor._internal_create_key,
+ ),
+ _descriptor.FieldDescriptor(
+ name="video",
+ full_name="proto.HyperionReply.video",
+ index=3,
+ number=4,
+ type=5,
+ cpp_type=1,
+ label=1,
+ has_default_value=False,
+ default_value=0,
+ message_type=None,
+ enum_type=None,
+ containing_type=None,
+ is_extension=False,
+ extension_scope=None,
+ serialized_options=None,
+ file=DESCRIPTOR,
+ create_key=_descriptor._internal_create_key,
+ ),
+ ],
+ extensions=[],
+ nested_types=[],
+ enum_types=[
+ _HYPERIONREPLY_TYPE,
+ ],
+ serialized_options=None,
+ is_extendable=False,
+ syntax="proto2",
+ extension_ranges=[],
+ oneofs=[],
+ serialized_start=577,
+ serialized_end=710,
+)
+
+_HYPERIONREQUEST.fields_by_name["command"].enum_type = _HYPERIONREQUEST_COMMAND
+_HYPERIONREQUEST_COMMAND.containing_type = _HYPERIONREQUEST
+_HYPERIONREPLY.fields_by_name["type"].enum_type = _HYPERIONREPLY_TYPE
+_HYPERIONREPLY_TYPE.containing_type = _HYPERIONREPLY
+DESCRIPTOR.message_types_by_name["HyperionRequest"] = _HYPERIONREQUEST
+DESCRIPTOR.message_types_by_name["ColorRequest"] = _COLORREQUEST
+DESCRIPTOR.message_types_by_name["ImageRequest"] = _IMAGEREQUEST
+DESCRIPTOR.message_types_by_name["ClearRequest"] = _CLEARREQUEST
+DESCRIPTOR.message_types_by_name["HyperionReply"] = _HYPERIONREPLY
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+HyperionRequest = _reflection.GeneratedProtocolMessageType(
+ "HyperionRequest",
+ (_message.Message,),
+ {
+ "DESCRIPTOR": _HYPERIONREQUEST,
+ "__module__": "message_pb2"
+ # @@protoc_insertion_point(class_scope:proto.HyperionRequest)
+ },
+)
+_sym_db.RegisterMessage(HyperionRequest)
+
+ColorRequest = _reflection.GeneratedProtocolMessageType(
+ "ColorRequest",
+ (_message.Message,),
+ {
+ "DESCRIPTOR": _COLORREQUEST,
+ "__module__": "message_pb2"
+ # @@protoc_insertion_point(class_scope:proto.ColorRequest)
+ },
+)
+_sym_db.RegisterMessage(ColorRequest)
+
+ImageRequest = _reflection.GeneratedProtocolMessageType(
+ "ImageRequest",
+ (_message.Message,),
+ {
+ "DESCRIPTOR": _IMAGEREQUEST,
+ "__module__": "message_pb2"
+ # @@protoc_insertion_point(class_scope:proto.ImageRequest)
+ },
+)
+_sym_db.RegisterMessage(ImageRequest)
+
+ClearRequest = _reflection.GeneratedProtocolMessageType(
+ "ClearRequest",
+ (_message.Message,),
+ {
+ "DESCRIPTOR": _CLEARREQUEST,
+ "__module__": "message_pb2"
+ # @@protoc_insertion_point(class_scope:proto.ClearRequest)
+ },
+)
+_sym_db.RegisterMessage(ClearRequest)
+
+HyperionReply = _reflection.GeneratedProtocolMessageType(
+ "HyperionReply",
+ (_message.Message,),
+ {
+ "DESCRIPTOR": _HYPERIONREPLY,
+ "__module__": "message_pb2"
+ # @@protoc_insertion_point(class_scope:proto.HyperionReply)
+ },
+)
+_sym_db.RegisterMessage(HyperionReply)
+
+_COLORREQUEST.extensions_by_name["colorRequest"].message_type = _COLORREQUEST
+HyperionRequest.RegisterExtension(_COLORREQUEST.extensions_by_name["colorRequest"])
+_IMAGEREQUEST.extensions_by_name["imageRequest"].message_type = _IMAGEREQUEST
+HyperionRequest.RegisterExtension(_IMAGEREQUEST.extensions_by_name["imageRequest"])
+_CLEARREQUEST.extensions_by_name["clearRequest"].message_type = _CLEARREQUEST
+HyperionRequest.RegisterExtension(_CLEARREQUEST.extensions_by_name["clearRequest"])
+
+# @@protoc_insertion_point(module_scope)
diff --git a/script.service.hyperion/resources/lib/logger.py b/script.service.hyperion/resources/lib/logger.py
new file mode 100644
index 000000000..98dd88688
--- /dev/null
+++ b/script.service.hyperion/resources/lib/logger.py
@@ -0,0 +1,25 @@
+"""Logging facility."""
+import xbmc
+
+
+class Logger:
+ """Logging facility for Kodi add-ons."""
+
+ def __init__(self, addon_name: str) -> None:
+ self._addon_name = addon_name
+
+ def log(self, message: str, level: int = xbmc.LOGDEBUG) -> None:
+ """Writes the message to the logger with the addon name as prefix."""
+ xbmc.log(f"[{self._addon_name}] - {message}", level=level)
+
+ def debug(self, message: str) -> None:
+ """Writes a debug message to the log."""
+ self.log(message)
+
+ def info(self, message: str) -> None:
+ """Writes an info message to the log."""
+ self.log(message, level=xbmc.LOGINFO)
+
+ def error(self, message: str) -> None:
+ """Writes an error message to the log."""
+ self.log(message, level=xbmc.LOGERROR)
diff --git a/script.service.hyperion/resources/lib/monitor.py b/script.service.hyperion/resources/lib/monitor.py
new file mode 100644
index 000000000..fe733b94a
--- /dev/null
+++ b/script.service.hyperion/resources/lib/monitor.py
@@ -0,0 +1,159 @@
+"""
+Kodi video capturer for Hyperion.
+
+Copyright (c) 2013-2023 Hyperion Team
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+"""
+from typing import Callable
+
+import xbmc
+from PIL import Image
+
+from resources.lib.gui import GuiHandler
+from resources.lib.hyperion.hyperion import Hyperion
+from resources.lib.logger import Logger
+from resources.lib.settings import SettingsManager
+
+State = Callable[[], "State"]
+
+
+class HyperionMonitor(xbmc.Monitor):
+ """Class to capture changes in settings and screensaver state."""
+
+ def __init__(
+ self,
+ settings: SettingsManager,
+ player: xbmc.Player,
+ output_handler: GuiHandler,
+ logger: Logger,
+ ) -> None:
+ super().__init__()
+ self.settings = settings
+ self.output_handler = output_handler
+ self._logger = logger
+ self._screensaver = xbmc.getCondVisibility("System.ScreenSaverActive")
+ self._player = player
+ self.show_error_message = True
+ self._hyperion: Hyperion
+ self._capture: xbmc.RenderCapture
+
+ def onSettingsChanged(self) -> None:
+ self.settings.read_settings()
+ if self.grabbing:
+ self.connect()
+
+ def onScreensaverDeactivated(self) -> None:
+ self._screensaver = False
+
+ def onScreensaverActivated(self) -> None:
+ self._screensaver = True
+
+ # TODO: onDPMSActivated/Deactivated when entering/exiting energy saving
+
+ @property
+ def grabbing(self) -> bool:
+ """Checks if grabbing is requested based on the current state and settings."""
+ return (
+ self.settings.enable
+ and self._player.isPlayingVideo()
+ and (self.settings.enable_screensaver or not self._screensaver)
+ )
+
+ def notify_error(self, label_id: int) -> None:
+ if self.show_error_message:
+ self.output_handler.notify_label(label_id)
+ self.show_error_message = False
+
+ def main_loop(self) -> None:
+ state = self.disconnected_state
+ while not self.abortRequested():
+ state = state()
+
+ def disconnected_state(self) -> State:
+ if not self.grabbing:
+ xbmc.sleep(500)
+ return self.disconnected_state
+ try:
+ self.connect()
+ return self.connected_state
+ except Exception:
+ self.notify_error(32100)
+ return self.error_state
+
+ def error_state(self) -> State:
+ rev = self.settings.rev
+ for _ in range(self.settings.timeout):
+ if rev != self.settings.rev:
+ break
+ if self.waitForAbort(1):
+ return self.error_state
+ return self.disconnected_state
+
+ def connect(self) -> None:
+ settings = self.settings
+ self._logger.info(
+ f"Establishing connection to hyperion at {settings.address}:{settings.port}"
+ )
+ self._hyperion = Hyperion(settings.address, settings.port)
+ self._capture = xbmc.RenderCapture()
+
+ def get_capture_size(self) -> tuple[tuple[int, int], int]:
+ width = self.settings.capture_width
+ aspect_ratio = self._capture.getAspectRatio()
+ height = int(width / aspect_ratio)
+ capture_size = width, height
+ expected_capture_size = width * height * 4 # size * 4 bytes - RGBA
+ return capture_size, expected_capture_size
+
+ def connected_state(self) -> State:
+ if not self.grabbing:
+ del self._hyperion
+ return self.disconnected_state
+
+ capture_size, expected_capture_size = self.get_capture_size()
+ self._capture.capture(*capture_size)
+ cap_image = self._capture.getImage(self.settings.sleep_time)
+ if cap_image is None or len(cap_image) < expected_capture_size:
+ self._logger.debug(
+ f"Captured image is none or < expected. "
+ f"captured: {len(cap_image) if cap_image is not None else 'None'}, "
+ f"expected: {expected_capture_size}"
+ )
+ xbmc.sleep(250)
+ return self.connected_state
+
+ # v17+ use BGRA format, converting to RGB
+ image = Image.frombytes("RGB", capture_size, bytes(cap_image), "raw", "BGRX")
+
+ try:
+ # send image to hyperion
+ self._hyperion.send_image(
+ image.width,
+ image.height,
+ image.tobytes(),
+ self.settings.priority,
+ self.settings.sleep_time,
+ )
+ except Exception:
+ # unable to send image. notify and go to the error state
+ self.output_handler.notify_label(32101)
+ return self.error_state
+
+ return self.connected_state
diff --git a/script.service.hyperion/resources/lib/settings.py b/script.service.hyperion/resources/lib/settings.py
new file mode 100644
index 000000000..3c777404c
--- /dev/null
+++ b/script.service.hyperion/resources/lib/settings.py
@@ -0,0 +1,78 @@
+"""
+Kodi video capturer for Hyperion.
+
+Copyright (c) 2013-2023 Hyperion Team
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+"""
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from resources.lib.logger import Logger
+
+if TYPE_CHECKING:
+ import xbmcaddon
+
+
+class SettingsManager:
+ """Class which contains all addon settings."""
+
+ def __init__(self, settings: xbmcaddon.Settings, logger: Logger) -> None:
+ self._logger = logger
+ self.rev = 0
+ self._settings = settings
+ self.address = "localhost"
+ self.port = 19445
+ self.enable: bool
+ self.enable_screensaver: bool
+ self.priority: int
+ self.timeout: int
+ self.capture_width: int
+ self.framerate: int
+ self.sleep_time: int
+
+ self.read_settings()
+
+ def read_settings(self) -> None:
+ """Read all settings."""
+ settings = self._settings
+ self.enable = settings.getBool("hyperion_enable")
+ self.enable_screensaver = settings.getBool("screensaver_enable")
+ self.priority = settings.getInt("hyperion_priority")
+ self.timeout = settings.getInt("reconnect_timeout")
+ self.capture_width = settings.getInt("capture_width")
+ self.framerate = settings.getInt("framerate")
+ self.sleep_time = int(1.0 / self.framerate * 1000)
+ self.address = settings.getString("hyperion_ip")
+ self.port = settings.getInt("hyperion_port")
+ self.rev += 1
+ self._log_settings()
+
+ def _log_settings(self) -> None:
+ log = self._logger.debug
+ log("Settings updated!")
+ log(f"Hyperion ip: {self.address}")
+ log(f"Hyperion port: {self.port}")
+ log(f"enabled: {self.enable}")
+ log(f"enabled on screensaver: {self.enable_screensaver}")
+ log(f"priority: {self.priority}")
+ log(f"timeout: {self.timeout}")
+ log(f"capture width: {self.capture_width}")
+ log(f"framerate: {self.framerate}")
diff --git a/script.service.hyperion/resources/screenshot-01.png b/script.service.hyperion/resources/screenshot-01.png
new file mode 100644
index 000000000..5b3fccb4f
Binary files /dev/null and b/script.service.hyperion/resources/screenshot-01.png differ
diff --git a/script.service.hyperion/resources/settings.xml b/script.service.hyperion/resources/settings.xml
new file mode 100644
index 000000000..2fe81ccb1
--- /dev/null
+++ b/script.service.hyperion/resources/settings.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/script.service.hyperion/service.py b/script.service.hyperion/service.py
new file mode 100644
index 000000000..3a02c59c3
--- /dev/null
+++ b/script.service.hyperion/service.py
@@ -0,0 +1,46 @@
+"""
+Kodi video capturer for Hyperion.
+
+Copyright (c) 2013-2023 Hyperion Team
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+"""
+
+import xbmc
+import xbmcaddon
+from resources.lib.gui import GuiHandler
+from resources.lib.logger import Logger
+from resources.lib.monitor import HyperionMonitor
+from resources.lib.settings import SettingsManager
+
+ADDON_NAME = "script.service.hyperion"
+
+
+def main() -> None:
+ addon = xbmcaddon.Addon(ADDON_NAME)
+ logger = Logger(addon.getAddonInfo("name"))
+ settings_manager = SettingsManager(addon.getSettings(), logger)
+ player = xbmc.Player()
+ output_handler = GuiHandler(addon, settings_manager)
+ monitor = HyperionMonitor(settings_manager, player, output_handler, logger)
+ monitor.main_loop()
+
+
+if __name__ == "__main__":
+ main()