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 + https://github.com/hyperion-project/hyperion.kodi + 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()