diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..44b6e05
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,9 @@
+# These are supported funding model platforms
+
+github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+patreon: # Replace with a single Patreon username
+open_collective: # Replace with a single Open Collective username
+ko_fi: # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+custom: https://www.paypal.me/sualfred
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1b659ed
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+
+*.pyo
+
+*.pyc
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f4bd783
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+## Embuary Helper Script
+
+For more information please [visit the wiki](https://github.com/sualfred/script.embuary.helper/wiki)
\ No newline at end of file
diff --git a/addon.xml b/addon.xml
new file mode 100644
index 0000000..79d5d11
--- /dev/null
+++ b/addon.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+ executable
+
+
+ video
+
+
+
+ Embuary Helper Script
+ Helper script to provide features and functions to the Embuary skin. A full documentation can be found on GitHub: https://github.com/sualfred/script.embuary.helper/wiki
+ all
+ GPL-2.0-only
+ https://forum.kodi.tv/showthread.php?tid=345318
+ https://github.com/sualfred/script.embuary.helper
+
+ resources/icon.png
+ resources/fanart.jpg
+
+
+
\ No newline at end of file
diff --git a/default.py b/default.py
new file mode 100644
index 0000000..50b2f68
--- /dev/null
+++ b/default.py
@@ -0,0 +1,43 @@
+#!/usr/bin/python
+
+########################
+
+import xbmcgui
+
+from resources.lib.helper import *
+from resources.lib.utils import *
+from resources.lib.cinema_mode import *
+
+########################
+
+class Main:
+ def __init__(self):
+ self.action = False
+ self._parse_argv()
+
+ if self.action:
+ self.getactions()
+ else:
+ DIALOG.ok(ADDON.getLocalizedString(32000), ADDON.getLocalizedString(32001))
+
+ def _parse_argv(self):
+ args = sys.argv
+
+ for arg in args:
+ if arg == ADDON_ID:
+ continue
+ if arg.startswith('action='):
+ self.action = arg[7:].lower()
+ else:
+ try:
+ self.params[arg.split("=")[0].lower()] = "=".join(arg.split("=")[1:]).strip()
+ except:
+ self.params = {}
+
+ def getactions(self):
+ util = globals()[self.action]
+ util(self.params)
+
+
+if __name__ == '__main__':
+ Main()
diff --git a/plugin.py b/plugin.py
new file mode 100644
index 0000000..6cc4de4
--- /dev/null
+++ b/plugin.py
@@ -0,0 +1,70 @@
+#!/usr/bin/python
+
+########################
+
+import xbmcplugin
+import urllib.parse as urlparse
+
+from resources.lib.helper import *
+from resources.lib.plugin_listing import *
+from resources.lib.plugin_content import *
+from resources.lib.plugin_actions import *
+
+########################
+
+class Main:
+ def __init__(self):
+ self._parse_argv()
+ self.info = self.params.get('info')
+ self.action = self.params.get('action')
+ if self.info:
+ self.getinfos()
+ elif self.action:
+ self.actions()
+ else:
+ self.listing()
+
+ def _parse_argv(self):
+ base_url = sys.argv[0]
+ path = sys.argv[2]
+
+ try:
+ args = path[1:]
+ self.params = dict(urlparse.parse_qsl(args))
+
+ ''' workaround to get the correct values for titles with special characters
+ '''
+ if ('title=\'\"' and '\"\'') in args:
+ start_pos=args.find('title=\'\"')
+ end_pos=args.find('\"\'')
+ clean_title = args[start_pos+8:end_pos]
+ self.params['title'] = clean_title
+
+ except Exception:
+ self.params = {}
+
+ def listing(self):
+ li = list()
+ PluginListing(self.params,li)
+ self._additems(li)
+
+ def getinfos(self):
+ li = list()
+ plugin = PluginContent(self.params,li)
+ self._execute(plugin,self.info)
+ self._additems(li)
+
+ def actions(self):
+ plugin = PluginActions(self.params)
+ self._execute(plugin,self.action)
+
+ def _execute(self,plugin,action):
+ getattr(plugin,action.lower())()
+
+ def _additems(self,li):
+ xbmcplugin.addDirectoryItems(int(sys.argv[1]), li)
+ xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
+
+
+if __name__ == '__main__':
+ Main()
\ No newline at end of file
diff --git a/resources/__init__.py b/resources/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/resources/fanart.jpg b/resources/fanart.jpg
new file mode 100644
index 0000000..8e1626b
Binary files /dev/null and b/resources/fanart.jpg differ
diff --git a/resources/icon.png b/resources/icon.png
new file mode 100644
index 0000000..0f35d26
Binary files /dev/null and b/resources/icon.png differ
diff --git a/resources/language/resource.language.de_DE/strings.po b/resources/language/resource.language.de_DE/strings.po
new file mode 100644
index 0000000..ca8d2c2
--- /dev/null
+++ b/resources/language/resource.language.de_DE/strings.po
@@ -0,0 +1,205 @@
+# Embuary Helper language file
+# Addon Name: Embuary Helper
+# Addon id: script.embuary.helper
+# Addon Provider: sualfred
+# Translators:
+# sualfred , 2019
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Embuary Helper\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2019-10-06 17:40+0000\n"
+"Last-Translator: sualfred , 2019\n"
+"Language-Team: German (Germany) (https://www.transifex.com/sualfred/teams/80018/de_DE/)\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 "#32000"
+msgid "Error"
+msgstr "Fehler"
+
+msgctxt "#32001"
+msgid ""
+"This is a tool to provide features to a skin and requires skin integration."
+msgstr ""
+"Dieses Tool stellt Features für Skins bereit und eine benötigt Skin-"
+"Integration."
+
+msgctxt "#32002"
+msgid "Enable logging"
+msgstr "Aktiviere Logging"
+
+msgctxt "#32004"
+msgid "detected"
+msgstr "erkannt"
+
+msgctxt "#32005"
+msgid "Changed font to"
+msgstr "Wechsle Schriftart zu"
+
+#: /resources/settings.xml
+msgctxt "#32003"
+msgid "Delete add-on image cache"
+msgstr "Add-on Image Cache löschen"
+
+msgctxt "#32006"
+msgid "Applying changes"
+msgstr "Änderungen werden übernommen"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32007"
+msgid "Suggestions based on random watched item"
+msgstr "Vorschläge basierend auf zufälligen gesehenen Video"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32008"
+msgid "Next up"
+msgstr "Als nächstes"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32009"
+msgid "Suggestions by random genre"
+msgstr "Vorschläge basierend auf zufälligen Genre"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32010"
+msgid "Recently added episodes and shows"
+msgstr "Kürzlich hinzugefügte Episoden und Serien"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32011"
+msgid "All media"
+msgstr "Alle Medien"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32012"
+msgid "Recommended by rating"
+msgstr "Empfohlen nach Bewertung"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32013"
+msgid "Continue watching"
+msgstr "Weiterschauen"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32014"
+msgid "Suggestions based on the last watched item"
+msgstr "Vorschläge basierend auf dem zuletzt gesehenen Video"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32015"
+msgid "Recently added unwatched episodes and shows"
+msgstr "Kürzlich hinzugefügte ungesehene Episoden und Serien"
+
+#: /resources/settings.xml
+msgctxt "#32016"
+msgid "Enable service module"
+msgstr "Aktiviere Servicemodul"
+
+#: /resources/settings.xml
+msgctxt "#32017"
+msgid "Service interval time"
+msgstr "Service Zeitintervall"
+
+#: /resources/settings.xml
+msgctxt "#32018"
+msgid "Default blurring radius"
+msgstr "Standard Radius des Weichzeichners"
+
+#: /resources/settings.xml
+msgctxt "#32020"
+msgid "Misc."
+msgstr "Sonstiges"
+
+#: /resources/settings.xml
+msgctxt "#32021"
+msgid "Background change interval"
+msgstr "Hintergrund-Wechselintervall"
+
+#: /resources/lib/utils.py
+msgctxt "#32019"
+msgid ""
+"This will delete all created images (blurred backgrounds, genre thumbs, "
+"etc.) in the addon_data folder. Do you want to proceed?"
+msgstr ""
+"Dies löscht alle erstellen Bilder (weich gezeichnete Hintergründe, Genre "
+"Vorschaubilder, usw.) im addon_data Ordner. Fortfahren?"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32022"
+msgid "All available database items"
+msgstr "Alle Datenbankeinträge"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32023"
+msgid "items"
+msgstr "Inhalte"
+
+#: /resources/lib/utils.py
+msgctxt "#32024"
+msgid "The video database has no library tags stored."
+msgstr "Deine Videodatenbank enthält keine Tags."
+
+#: /resources/lib/utils.py
+msgctxt "#32025"
+msgid "Attention"
+msgstr "Achtung"
+
+#: /resources/lib/utils.py
+msgctxt "#32026"
+msgid "Enable library tag detection for"
+msgstr "Aktiviere Tag-Erkennung für"
+
+#: /addon.xml
+msgctxt "#32027"
+msgid "Add/remove movie favourite"
+msgstr "Film-Favorit hinzufügen/entfernen"
+
+#: /addon.xml
+msgctxt "#32028"
+msgid "Add/remove TV show favourite"
+msgstr "Serien-Favorit hinzufügen/entfernen"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32029"
+msgid "Directed by"
+msgstr "Unter der Regie von"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32030"
+msgid "Items starring"
+msgstr "Einträge mit"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32031"
+msgid "Because you watched"
+msgstr "Ähnliche Titel wie"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32032"
+msgid "Christmas holiday season"
+msgstr "Weihnachtszeit"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32033"
+msgid "Spooky suggestions"
+msgstr "Gruselige Vorschläge"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32034"
+msgid "May the force be with you"
+msgstr "Möge die Macht mit dir sein"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32035"
+msgid "Live long and prosper"
+msgstr "Lebe lang und in Frieden"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32036"
+msgid "Special and seasonal widgets"
+msgstr "Spezielle und saisonale Widgets"
diff --git a/resources/language/resource.language.en_GB/strings.po b/resources/language/resource.language.en_GB/strings.po
new file mode 100644
index 0000000..f986fbc
--- /dev/null
+++ b/resources/language/resource.language.en_GB/strings.po
@@ -0,0 +1,184 @@
+# Embuary Helper language file
+# Addon Name: Embuary Helper
+# Addon id: script.embuary.helper
+# Addon Provider: sualfred
+msgid ""
+msgstr ""
+"Project-Id-Version: Embuary Helper\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Language-Team: \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 "#32000"
+msgid "Error"
+msgstr ""
+
+msgctxt "#32001"
+msgid "This is a tool to provide features to a skin and requires skin integration."
+msgstr ""
+
+msgctxt "#32002"
+msgid "Enable logging"
+msgstr ""
+
+msgctxt "#32004"
+msgid "detected"
+msgstr ""
+
+msgctxt "#32005"
+msgid "Changed font to"
+msgstr ""
+
+#: /resources/settings.xml
+msgctxt "#32003"
+msgid "Delete add-on image cache"
+msgstr ""
+
+msgctxt "#32006"
+msgid "Applying changes"
+msgstr ""
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32007"
+msgid "Suggestions based on random watched item"
+msgstr ""
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32008"
+msgid "Next up"
+msgstr ""
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32009"
+msgid "Suggestions by random genre"
+msgstr ""
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32010"
+msgid "Recently added episodes and shows"
+msgstr ""
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32011"
+msgid "All media"
+msgstr ""
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32012"
+msgid "Recommended by rating"
+msgstr ""
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32013"
+msgid "Continue watching"
+msgstr ""
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32014"
+msgid "Suggestions based on the last watched item"
+msgstr ""
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32015"
+msgid "Recently added unwatched episodes and shows"
+msgstr ""
+
+#: /resources/settings.xml
+msgctxt "#32016"
+msgid "Enable service module"
+msgstr ""
+
+#: /resources/settings.xml
+msgctxt "#32017"
+msgid "Service interval time"
+msgstr ""
+
+#: /resources/settings.xml
+msgctxt "#32018"
+msgid "Default blurring radius"
+msgstr ""
+
+#: /resources/settings.xml
+msgctxt "#32020"
+msgid "Misc."
+msgstr ""
+
+#: /resources/settings.xml
+msgctxt "#32021"
+msgid "Background change interval"
+msgstr ""
+
+#: /resources/lib/utils.py
+msgctxt "#32019"
+msgid "This will delete all created images (blurred backgrounds, genre thumbs, etc.) in the addon_data folder. Do you want to proceed?"
+msgstr ""
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32022"
+msgid "All available database items"
+msgstr ""
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32023"
+msgid "items"
+msgstr ""
+
+#: /resources/lib/utils.py
+msgctxt "#32024"
+msgid "The video database has no library tags stored."
+msgstr ""
+
+#: /resources/lib/utils.py
+msgctxt "#32025"
+msgid "Attention"
+msgstr ""
+
+#: /resources/lib/utils.py
+msgctxt "#32026"
+msgid "Enable library tag detection for"
+msgstr ""
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32029"
+msgid "Directed by"
+msgstr ""
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32030"
+msgid "Items starring"
+msgstr ""
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32031"
+msgid "Because you watched"
+msgstr ""
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32032"
+msgid "Christmas holiday season"
+msgstr ""
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32033"
+msgid "Spooky suggestions"
+msgstr ""
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32034"
+msgid "May the force be with you"
+msgstr ""
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32035"
+msgid "Live long and prosper"
+msgstr ""
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32036"
+msgid "Special and seasonal widgets"
+msgstr ""
diff --git a/resources/language/resource.language.es_ES/strings.po b/resources/language/resource.language.es_ES/strings.po
new file mode 100644
index 0000000..ba87de8
--- /dev/null
+++ b/resources/language/resource.language.es_ES/strings.po
@@ -0,0 +1,197 @@
+# Embuary Helper language file
+# Addon Name: Embuary Helper
+# Addon id: script.embuary.helper
+# Addon Provider: sualfred
+# Translators:
+# sualfred , 2019
+# Rafa Oliveros , 2020
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Embuary Helper\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2019-10-06 17:40+0000\n"
+"Last-Translator: Rafa Oliveros , 2020\n"
+"Language-Team: Spanish (Spain) (https://www.transifex.com/sualfred/teams/80018/es_ES/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: es_ES\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "#32000"
+msgid "Error"
+msgstr "Error"
+
+msgctxt "#32001"
+msgid ""
+"This is a tool to provide features to a skin and requires skin integration."
+msgstr ""
+"Esta es una herramienta para proveer funcionalidades para un skin y requiere"
+" de integración en el skin."
+
+msgctxt "#32002"
+msgid "Enable logging"
+msgstr "Activar bitácora"
+
+msgctxt "#32004"
+msgid "detected"
+msgstr "detectado"
+
+msgctxt "#32005"
+msgid "Changed font to"
+msgstr "Font cambiado a"
+
+#: /resources/settings.xml
+msgctxt "#32003"
+msgid "Delete add-on image cache"
+msgstr "Borrar caché de imágenes del addon"
+
+msgctxt "#32006"
+msgid "Applying changes"
+msgstr "Aplicando cambios"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32007"
+msgid "Suggestions based on random watched item"
+msgstr "Sugerencias basadas en un elemento visto al azar"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32008"
+msgid "Next up"
+msgstr "Próximo"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32009"
+msgid "Suggestions by random genre"
+msgstr "Sugerencias por género aleatorio"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32010"
+msgid "Recently added episodes and shows"
+msgstr "Episodios y series añadidos recientemente"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32011"
+msgid "All media"
+msgstr "Todos los medios"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32012"
+msgid "Recommended by rating"
+msgstr "Recomendados por valoración"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32013"
+msgid "Continue watching"
+msgstr "Continuar viendo"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32014"
+msgid "Suggestions based on the last watched item"
+msgstr "Sugerencias basadas en el ultimo elemento visto"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32015"
+msgid "Recently added unwatched episodes and shows"
+msgstr "Episodios y series no vistos añadidos recientemente"
+
+#: /resources/settings.xml
+msgctxt "#32016"
+msgid "Enable service module"
+msgstr "Activar módulo de servicio"
+
+#: /resources/settings.xml
+msgctxt "#32017"
+msgid "Service interval time"
+msgstr "Intervalo de tiempo del servicio"
+
+#: /resources/settings.xml
+msgctxt "#32018"
+msgid "Default blurring radius"
+msgstr "Radio de difuminado por defecto"
+
+#: /resources/settings.xml
+msgctxt "#32020"
+msgid "Misc."
+msgstr "Misc."
+
+#: /resources/settings.xml
+msgctxt "#32021"
+msgid "Background change interval"
+msgstr "Intervalo de cambio de fondo"
+
+#: /resources/lib/utils.py
+msgctxt "#32019"
+msgid ""
+"This will delete all created images (blurred backgrounds, genre thumbs, "
+"etc.) in the addon_data folder. Do you want to proceed?"
+msgstr ""
+"Esto eliminará todas las imágenes creadas (fondos difuminados, miniaturas de"
+" géneros, etc.) en la carpeta addon_data. ¿Desea proceder?"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32022"
+msgid "All available database items"
+msgstr "Todos los elementos de la base de datos disponibles."
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32023"
+msgid "items"
+msgstr "Elementos"
+
+#: /resources/lib/utils.py
+msgctxt "#32024"
+msgid "The video database has no library tags stored."
+msgstr ""
+"La base de datos de vídeo no tiene etiquetas de biblioteca almacenadas."
+
+#: /resources/lib/utils.py
+msgctxt "#32025"
+msgid "Attention"
+msgstr "Atención"
+
+#: /resources/lib/utils.py
+msgctxt "#32026"
+msgid "Enable library tag detection for"
+msgstr "Activar la detección de etiquetas de biblioteca para"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32029"
+msgid "Directed by"
+msgstr "Dirigido por"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32030"
+msgid "Items starring"
+msgstr "Elementos protagonizados"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32031"
+msgid "Because you watched"
+msgstr "Porque viste"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32032"
+msgid "Christmas holiday season"
+msgstr "Temporada navideña"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32033"
+msgid "Spooky suggestions"
+msgstr "Sugerencias misteriosas"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32034"
+msgid "May the force be with you"
+msgstr "Que la fuerza esté contigo"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32035"
+msgid "Live long and prosper"
+msgstr "Larga vida y prosperidad"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32036"
+msgid "Special and seasonal widgets"
+msgstr "Widgets de especiales y de temporada"
diff --git a/resources/language/resource.language.it_IT/strings.po b/resources/language/resource.language.it_IT/strings.po
new file mode 100644
index 0000000..c15ddf1
--- /dev/null
+++ b/resources/language/resource.language.it_IT/strings.po
@@ -0,0 +1,207 @@
+# Embuary Helper language file
+# Addon Name: Embuary Helper
+# Addon id: script.embuary.helper
+# Addon Provider: sualfred
+# Translators:
+# sualfred , 2019
+# Andrea Matesi , 2019
+# EffeF, 2019
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Embuary Helper\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2019-10-06 17:40+0000\n"
+"Last-Translator: EffeF, 2019\n"
+"Language-Team: Italian (Italy) (https://www.transifex.com/sualfred/teams/80018/it_IT/)\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 "#32000"
+msgid "Error"
+msgstr "Errore"
+
+msgctxt "#32001"
+msgid ""
+"This is a tool to provide features to a skin and requires skin integration."
+msgstr ""
+"Questo è uno strumento per fornire funzionalità a una skin e richiede "
+"l'integrazione nella skin."
+
+msgctxt "#32002"
+msgid "Enable logging"
+msgstr "Attiva logging"
+
+msgctxt "#32004"
+msgid "detected"
+msgstr "rilevato"
+
+msgctxt "#32005"
+msgid "Changed font to"
+msgstr "Font modificato in"
+
+#: /resources/settings.xml
+msgctxt "#32003"
+msgid "Delete add-on image cache"
+msgstr "Elimina cache immagini add-on"
+
+msgctxt "#32006"
+msgid "Applying changes"
+msgstr "Applicazione modifiche"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32007"
+msgid "Suggestions based on random watched item"
+msgstr "Suggerimenti basati su elementi guardati a caso"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32008"
+msgid "Next up"
+msgstr "Prossimo"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32009"
+msgid "Suggestions by random genre"
+msgstr "Suggerimenti per genere casuale"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32010"
+msgid "Recently added episodes and shows"
+msgstr "Episodi e serie aggiunte di recente"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32011"
+msgid "All media"
+msgstr "Tutti i media"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32012"
+msgid "Recommended by rating"
+msgstr "Raccomandazioni basate sulla valutazione"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32013"
+msgid "Continue watching"
+msgstr "Continua a guardare"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32014"
+msgid "Suggestions based on the last watched item"
+msgstr "Suggerimenti basati sull'ultimo elemento guardato"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32015"
+msgid "Recently added unwatched episodes and shows"
+msgstr "Episodi e serie non viste aggiunte di recente"
+
+#: /resources/settings.xml
+msgctxt "#32016"
+msgid "Enable service module"
+msgstr "Abilita modulo di servizio"
+
+#: /resources/settings.xml
+msgctxt "#32017"
+msgid "Service interval time"
+msgstr "Intervallo servizio"
+
+#: /resources/settings.xml
+msgctxt "#32018"
+msgid "Default blurring radius"
+msgstr "Raggio di sfocatura predefinito"
+
+#: /resources/settings.xml
+msgctxt "#32020"
+msgid "Misc."
+msgstr "Varie"
+
+#: /resources/settings.xml
+msgctxt "#32021"
+msgid "Background change interval"
+msgstr "Intervallo cambio dello sfondo"
+
+#: /resources/lib/utils.py
+msgctxt "#32019"
+msgid ""
+"This will delete all created images (blurred backgrounds, genre thumbs, "
+"etc.) in the addon_data folder. Do you want to proceed?"
+msgstr ""
+"Ciò eliminerà tutte le immagini create (sfondi sfocati, immagini genere, "
+"ecc.) nella cartella addon_data. Vuoi procedere?"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32022"
+msgid "All available database items"
+msgstr "Tutti gli elementi del database disponibili"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32023"
+msgid "items"
+msgstr "elementi"
+
+#: /resources/lib/utils.py
+msgctxt "#32024"
+msgid "The video database has no library tags stored."
+msgstr "Il database video non ha alcuna libreria tag disponibile."
+
+#: /resources/lib/utils.py
+msgctxt "#32025"
+msgid "Attention"
+msgstr "Attenzione"
+
+#: /resources/lib/utils.py
+msgctxt "#32026"
+msgid "Enable library tag detection for"
+msgstr "Attiva riconoscimento tag libreria per"
+
+#: /addon.xml
+msgctxt "#32027"
+msgid "Add/remove movie favourite"
+msgstr "Aggiungi/rimuovi film preferito"
+
+#: /addon.xml
+msgctxt "#32028"
+msgid "Add/remove TV show favourite"
+msgstr "Aggiungi/rimuovi programma TV preferito"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32029"
+msgid "Directed by"
+msgstr "Diretto da"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32030"
+msgid "Items starring"
+msgstr "Elementi con protagonista"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32031"
+msgid "Because you watched"
+msgstr "Perché hai visto"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32032"
+msgid "Christmas holiday season"
+msgstr "Festività natalizie"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32033"
+msgid "Spooky suggestions"
+msgstr "Suggerimenti spettrali"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32034"
+msgid "May the force be with you"
+msgstr "Che la forza sia con te"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32035"
+msgid "Live long and prosper"
+msgstr "Lunga vita e prosperità"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32036"
+msgid "Special and seasonal widgets"
+msgstr "Widget speciali e stagionali"
diff --git a/resources/language/resource.language.nl_NL/strings.po b/resources/language/resource.language.nl_NL/strings.po
new file mode 100644
index 0000000..e0c6438
--- /dev/null
+++ b/resources/language/resource.language.nl_NL/strings.po
@@ -0,0 +1,155 @@
+# Embuary Helper language file
+# Addon Name: Embuary Helper
+# Addon id: script.embuary.helper
+# Addon Provider: sualfred
+# Translators:
+# David Claes , 2019
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Embuary Helper\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2019-10-06 17:40+0000\n"
+"Last-Translator: David Claes , 2019\n"
+"Language-Team: Dutch (Netherlands) (https://www.transifex.com/sualfred/teams/80018/nl_NL/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: nl_NL\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "#32000"
+msgid "Error"
+msgstr "Fout"
+
+msgctxt "#32001"
+msgid ""
+"This is a tool to provide features to a skin and requires skin integration."
+msgstr ""
+"Dit is een hulpmiddel om functies aan een skin te voorzien en vereist "
+"skinintegratie."
+
+msgctxt "#32002"
+msgid "Enable logging"
+msgstr "Logging inschakelen"
+
+msgctxt "#32004"
+msgid "detected"
+msgstr "gedetecteerd"
+
+msgctxt "#32005"
+msgid "Changed font to"
+msgstr "Lettertype wijzigen naar"
+
+#: /resources/settings.xml
+msgctxt "#32003"
+msgid "Delete add-on image cache"
+msgstr "Add-on afbeeldingscache verwijderen"
+
+msgctxt "#32006"
+msgid "Applying changes"
+msgstr "Wijzigingen toepassen"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32007"
+msgid "Suggestions based on random watched item"
+msgstr "Suggesties op basis van willekeurig bekeken item"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32008"
+msgid "Next up"
+msgstr "Volgende"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32009"
+msgid "Suggestions by random genre"
+msgstr "Suggesties op basis van willekeurig genre"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32010"
+msgid "Recently added episodes and shows"
+msgstr "Recent toegevoegde afleveringen en series"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32011"
+msgid "All media"
+msgstr "Alle media"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32012"
+msgid "Recommended by rating"
+msgstr "Aanbevolen op basis van beoordeling"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32013"
+msgid "Continue watching"
+msgstr "Verder kijken"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32014"
+msgid "Suggestions based on the last watched item"
+msgstr "Suggesties op basis van het laatst bekeken item"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32015"
+msgid "Recently added unwatched episodes and shows"
+msgstr "Recent toegevoegde onbekeken afleveringen en series"
+
+#: /resources/settings.xml
+msgctxt "#32016"
+msgid "Enable service module"
+msgstr "Servicemodule inschakelen"
+
+#: /resources/settings.xml
+msgctxt "#32017"
+msgid "Service interval time"
+msgstr "Intervaltijd service"
+
+#: /resources/settings.xml
+msgctxt "#32018"
+msgid "Default blurring radius"
+msgstr "Standaard vervagingsradius"
+
+#: /resources/settings.xml
+msgctxt "#32020"
+msgid "Misc."
+msgstr "Div."
+
+#: /resources/settings.xml
+msgctxt "#32021"
+msgid "Background change interval"
+msgstr "Achtergrondveranderingsinterval"
+
+#: /resources/lib/utils.py
+msgctxt "#32019"
+msgid ""
+"This will delete all created images (blurred backgrounds, genre thumbs, "
+"etc.) in the addon_data folder. Do you want to proceed?"
+msgstr ""
+"Hiermee worden alle aangemaakte afbeeldingen (vervaagde achtergronden, "
+"miniaturen genres, enz.) verwijderd in de map addon_data. Wilt u doorgaan?"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32022"
+msgid "All available database items"
+msgstr "Alle beschikbare database-items"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32023"
+msgid "items"
+msgstr "items"
+
+#: /resources/lib/utils.py
+msgctxt "#32024"
+msgid "The video database has no library tags stored."
+msgstr "De video-database heeft geen bibliotheek-tags opgeslagen."
+
+#: /resources/lib/utils.py
+msgctxt "#32025"
+msgid "Attention"
+msgstr "Opgelet"
+
+#: /resources/lib/utils.py
+msgctxt "#32026"
+msgid "Enable library tag detection for"
+msgstr "Bibliotheektagdetectie inschakelen voor"
diff --git a/resources/language/resource.language.pl_PL/strings.po b/resources/language/resource.language.pl_PL/strings.po
new file mode 100644
index 0000000..9344c01
--- /dev/null
+++ b/resources/language/resource.language.pl_PL/strings.po
@@ -0,0 +1,88 @@
+# Embuary Helper language file
+# Addon Name: Embuary Helper
+# Addon id: script.embuary.helper
+# Addon Provider: sualfred
+# Translators:
+# Michał Sawicz , 2019
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Embuary Helper\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2019-07-14 05:59+0000\n"
+"Last-Translator: Michał Sawicz , 2019\n"
+"Language-Team: Polish (Poland) (https://www.transifex.com/sualfred/teams/80018/pl_PL/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: pl_PL\n"
+"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
+
+msgctxt "#32000"
+msgid "Error"
+msgstr "Błąd"
+
+msgctxt "#32001"
+msgid ""
+"This is a tool to provide features to a skin and requires skin integration."
+msgstr "To narzędzie wspomaga skóry i wymaga jej integracji."
+
+msgctxt "#32002"
+msgid "Enable logging"
+msgstr "Włącz dziennik zdarzeń"
+
+msgctxt "#32003"
+msgid "Enable debug logging"
+msgstr "Włącz dziennik debugowania"
+
+msgctxt "#32004"
+msgid "detected"
+msgstr "wykryto"
+
+msgctxt "#32005"
+msgid "Changed font to"
+msgstr "Zmieniono czcionkę na"
+
+msgctxt "#32006"
+msgid "Service is restarting"
+msgstr "Usługa jest uruchamiana ponownie"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32007"
+msgid "Suggestions based on random watched item"
+msgstr "Sugestie na podstawie losowej obejrzanej pozycji"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32008"
+msgid "Next up"
+msgstr "Następny"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32009"
+msgid "Suggestions by random genre"
+msgstr "Sugestie na podstawie losowego gatunku"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32010"
+msgid "Mix of recently added episodes and shows"
+msgstr "Miks ostatnio dodanych odcinków i seriali"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32011"
+msgid "All media"
+msgstr "Wszystkie media"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32012"
+msgid "Recommended by rating"
+msgstr "Sugerowane na podstawie ocen"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32013"
+msgid "Continue watching"
+msgstr ""
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32014"
+msgid "Suggestions based on the last watched item"
+msgstr ""
diff --git a/resources/language/resource.language.pt_BR/strings.po b/resources/language/resource.language.pt_BR/strings.po
new file mode 100644
index 0000000..40e1c23
--- /dev/null
+++ b/resources/language/resource.language.pt_BR/strings.po
@@ -0,0 +1,141 @@
+# Embuary Helper language file
+# Addon Name: Embuary Helper
+# Addon id: script.embuary.helper
+# Addon Provider: sualfred
+# Translators:
+# sualfred , 2019
+# Edjalmo Bf , 2019
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Embuary Helper\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2019-10-06 17:40+0000\n"
+"Last-Translator: Edjalmo Bf , 2019\n"
+"Language-Team: Portuguese (Brazil) (https://www.transifex.com/sualfred/teams/80018/pt_BR/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: pt_BR\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+msgctxt "#32000"
+msgid "Error"
+msgstr "Erro"
+
+msgctxt "#32001"
+msgid ""
+"This is a tool to provide features to a skin and requires skin integration."
+msgstr ""
+"Esta é uma ferramenta para fornecer recursos a uma skin e requer integração "
+"com a skin."
+
+msgctxt "#32002"
+msgid "Enable logging"
+msgstr "Habilitar log"
+
+msgctxt "#32004"
+msgid "detected"
+msgstr "detectado"
+
+msgctxt "#32005"
+msgid "Changed font to"
+msgstr "Fonte alterada para"
+
+#: /resources/settings.xml
+msgctxt "#32003"
+msgid "Delete add-on image cache"
+msgstr "Apagar cache de imagem do add-on"
+
+msgctxt "#32006"
+msgid "Applying changes"
+msgstr "Aplicando mudanças"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32007"
+msgid "Suggestions based on random watched item"
+msgstr "Sugestões baseadas em itens aleatórios assistidos"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32008"
+msgid "Next up"
+msgstr "Próximo"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32009"
+msgid "Suggestions by random genre"
+msgstr "Sugestões por gênero aleatório"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32010"
+msgid "Recently added episodes and shows"
+msgstr "Novos episódios e programas adicionados"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32011"
+msgid "All media"
+msgstr "Todas as mídias"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32012"
+msgid "Recommended by rating"
+msgstr "Recomendado por classificação"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32013"
+msgid "Continue watching"
+msgstr "Continue assistindo"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32014"
+msgid "Suggestions based on the last watched item"
+msgstr "Sugestões baseadas no último item assistido"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32015"
+msgid "Recently added unwatched episodes and shows"
+msgstr "Episódios e programas não assistidos adicionados recentemente"
+
+#: /resources/settings.xml
+msgctxt "#32016"
+msgid "Enable service module"
+msgstr "Habilitar módulo de serviço"
+
+#: /resources/settings.xml
+msgctxt "#32017"
+msgid "Service interval time"
+msgstr "Tempo de intervalo do serviço"
+
+#: /resources/settings.xml
+msgctxt "#32018"
+msgid "Default blurring radius"
+msgstr "Raio de desfoque padrão"
+
+#: /resources/settings.xml
+msgctxt "#32020"
+msgid "Misc."
+msgstr "Diversos"
+
+#: /resources/settings.xml
+msgctxt "#32021"
+msgid "Background change interval"
+msgstr "Intervalo da mudança do plano de fundo"
+
+#: /resources/lib/utils.py
+msgctxt "#32019"
+msgid ""
+"This will delete all created images (blurred backgrounds, genre thumbs, "
+"etc.) in the addon_data folder. Do you want to proceed?"
+msgstr ""
+"Isto apagará todas as imagens criadas (fundos desfocados, thumbs de gêneros,"
+" etc.) da pasta addon_data. Você deseja continuar?"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32022"
+msgid "All available database items"
+msgstr "Todos os itens disponíveis no banco de dados"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32023"
+msgid "items"
+msgstr "itens"
diff --git a/resources/language/resource.language.pt_PT/strings.po b/resources/language/resource.language.pt_PT/strings.po
new file mode 100644
index 0000000..2bf2553
--- /dev/null
+++ b/resources/language/resource.language.pt_PT/strings.po
@@ -0,0 +1,120 @@
+# Embuary Helper language file
+# Addon Name: Embuary Helper
+# Addon id: script.embuary.helper
+# Addon Provider: sualfred
+# Translators:
+# Pedro Pinto , 2019
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Embuary Helper\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2019-07-14 05:59+0000\n"
+"Last-Translator: Pedro Pinto , 2019\n"
+"Language-Team: Portuguese (Portugal) (https://www.transifex.com/sualfred/teams/80018/pt_PT/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: pt_PT\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "#32000"
+msgid "Error"
+msgstr "Erro"
+
+msgctxt "#32001"
+msgid ""
+"This is a tool to provide features to a skin and requires skin integration."
+msgstr ""
+"Esta ferramenta disponibiliza recursos para serem utilizados num tema e "
+"necessita integração com esse tema."
+
+msgctxt "#32002"
+msgid "Enable logging"
+msgstr "Activar log"
+
+msgctxt "#32003"
+msgid "Enable debug logging"
+msgstr "Activar log de depuração"
+
+msgctxt "#32004"
+msgid "detected"
+msgstr "encontrado"
+
+msgctxt "#32005"
+msgid "Changed font to"
+msgstr "Alterar fonte para"
+
+msgctxt "#32006"
+msgid "Applying changes"
+msgstr "Aplicar alterações"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32007"
+msgid "Suggestions based on random watched item"
+msgstr "Sugestões baseadas em itens visualizados aleatoriamente"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32008"
+msgid "Next up"
+msgstr "Próximo (Next up)"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32009"
+msgid "Suggestions by random genre"
+msgstr "Sugestões por género aleatoriamente"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32010"
+msgid "Recently added episodes and shows"
+msgstr "Episódios e séries de TV adicionados recentemente"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32011"
+msgid "All media"
+msgstr "Todos os mídias"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32012"
+msgid "Recommended by rating"
+msgstr "Recomendados por avaliação"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32013"
+msgid "Continue watching"
+msgstr "Continuar a ver"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32014"
+msgid "Suggestions based on the last watched item"
+msgstr "Sugestões baseadas no último item visualizado"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32015"
+msgid "Recently added unwatched episodes and shows"
+msgstr "Episódios e séries de TV por visualizar adicionados recentemente"
+
+#: /resources/settings.xml
+msgctxt "#32016"
+msgid "Enable service module"
+msgstr "Activar módulo de serviço"
+
+#: /resources/settings.xml
+msgctxt "#32017"
+msgid "Service interval time"
+msgstr "Intervalo de tempo do serviço"
+
+#: /resources/settings.xml
+msgctxt "#32018"
+msgid "Default blurring radius"
+msgstr "Raio de desfocagem por defeito"
+
+#: /resources/settings.xml
+msgctxt "#32020"
+msgid "Misc."
+msgstr "Outros"
+
+#: /resources/settings.xml
+msgctxt "#32021"
+msgid "Background change interval"
+msgstr ""
diff --git a/resources/language/resource.language.ru_RU/strings.po b/resources/language/resource.language.ru_RU/strings.po
new file mode 100644
index 0000000..4e5c5fd
--- /dev/null
+++ b/resources/language/resource.language.ru_RU/strings.po
@@ -0,0 +1,195 @@
+# Embuary Helper language file
+# Addon Name: Embuary Helper
+# Addon id: script.embuary.helper
+# Addon Provider: sualfred
+# Translators:
+# Эдуард Приутеса , 2020
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Embuary Helper\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2019-10-06 17:40+0000\n"
+"Last-Translator: Эдуард Приутеса , 2020\n"
+"Language-Team: Russian (Russia) (https://www.transifex.com/sualfred/teams/80018/ru_RU/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: ru_RU\n"
+"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
+
+msgctxt "#32000"
+msgid "Error"
+msgstr "Ошибка"
+
+msgctxt "#32001"
+msgid ""
+"This is a tool to provide features to a skin and requires skin integration."
+msgstr ""
+"Это инструмент для предоставления функций для скина и требует интеграции "
+"скина."
+
+msgctxt "#32002"
+msgid "Enable logging"
+msgstr "Включить ведение журнала"
+
+msgctxt "#32004"
+msgid "detected"
+msgstr "Обнаружены"
+
+msgctxt "#32005"
+msgid "Changed font to"
+msgstr "Изменен шрифт на"
+
+#: /resources/settings.xml
+msgctxt "#32003"
+msgid "Delete add-on image cache"
+msgstr "Удалить дополнительный кеш изображений"
+
+msgctxt "#32006"
+msgid "Applying changes"
+msgstr "Применение изменений"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32007"
+msgid "Suggestions based on random watched item"
+msgstr "Предложения на основе случайно просматриваемого элемента"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32008"
+msgid "Next up"
+msgstr "Следующий"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32009"
+msgid "Suggestions by random genre"
+msgstr "Предложения по случайному жанру"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32010"
+msgid "Recently added episodes and shows"
+msgstr "Недавно добавленные эпизоды и сериалы"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32011"
+msgid "All media"
+msgstr "Все медия"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32012"
+msgid "Recommended by rating"
+msgstr "Рекомендуется по рейтингу"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32013"
+msgid "Continue watching"
+msgstr "Продолжайте смотреть"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32014"
+msgid "Suggestions based on the last watched item"
+msgstr "Предложения, основанные на последнем просмотренном элементе"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32015"
+msgid "Recently added unwatched episodes and shows"
+msgstr "Недавно добавленные неназванные эпизоды и сериалы"
+
+#: /resources/settings.xml
+msgctxt "#32016"
+msgid "Enable service module"
+msgstr "Включить сервисный модуль"
+
+#: /resources/settings.xml
+msgctxt "#32017"
+msgid "Service interval time"
+msgstr "Время обслуживания"
+
+#: /resources/settings.xml
+msgctxt "#32018"
+msgid "Default blurring radius"
+msgstr "Радиус размытия по умолчанию"
+
+#: /resources/settings.xml
+msgctxt "#32020"
+msgid "Misc."
+msgstr "Разное."
+
+#: /resources/settings.xml
+msgctxt "#32021"
+msgid "Background change interval"
+msgstr "Интервал фоновых изменений"
+
+#: /resources/lib/utils.py
+msgctxt "#32019"
+msgid ""
+"This will delete all created images (blurred backgrounds, genre thumbs, "
+"etc.) in the addon_data folder. Do you want to proceed?"
+msgstr ""
+"Это позволит удалить все созданные изображения ( фоны, жанры и т.д.) в "
+"папке addon_data. Ты хочешь продолжить?"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32022"
+msgid "All available database items"
+msgstr "Все доступные элементы базы данных"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32023"
+msgid "items"
+msgstr "Элементы"
+
+#: /resources/lib/utils.py
+msgctxt "#32024"
+msgid "The video database has no library tags stored."
+msgstr "В базе данных видео нет библиотечных тегов."
+
+#: /resources/lib/utils.py
+msgctxt "#32025"
+msgid "Attention"
+msgstr "Внимание"
+
+#: /resources/lib/utils.py
+msgctxt "#32026"
+msgid "Enable library tag detection for"
+msgstr "Включить обнаружение библиотечных тегов для"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32029"
+msgid "Directed by"
+msgstr "Режиссер:"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32030"
+msgid "Items starring"
+msgstr "Предметы в главной роли"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32031"
+msgid "Because you watched"
+msgstr "Потому что вы смотрели"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32032"
+msgid "Christmas holiday season"
+msgstr "Сезон рождественских праздников"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32033"
+msgid "Spooky suggestions"
+msgstr " Жуткие предложения"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32034"
+msgid "May the force be with you"
+msgstr "Да пребудет с тобой сила"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32035"
+msgid "Live long and prosper"
+msgstr "Живи долго и процветай"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32036"
+msgid "Special and seasonal widgets"
+msgstr "Специальные и сезонные виджеты"
diff --git a/resources/language/resource.language.sk_SK/strings.po b/resources/language/resource.language.sk_SK/strings.po
new file mode 100644
index 0000000..65bfedb
--- /dev/null
+++ b/resources/language/resource.language.sk_SK/strings.po
@@ -0,0 +1,165 @@
+# Embuary Helper language file
+# Addon Name: Embuary Helper
+# Addon id: script.embuary.helper
+# Addon Provider: sualfred
+# Translators:
+# Miro Valko , 2019
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Embuary Helper\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2019-10-06 17:40+0000\n"
+"Last-Translator: Miro Valko , 2019\n"
+"Language-Team: Slovak (Slovakia) (https://www.transifex.com/sualfred/teams/80018/sk_SK/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: sk_SK\n"
+"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);\n"
+
+msgctxt "#32000"
+msgid "Error"
+msgstr "Chyba"
+
+msgctxt "#32001"
+msgid ""
+"This is a tool to provide features to a skin and requires skin integration."
+msgstr ""
+"Toto je nástroj na poskytovanie služieb pre vzhľad a vyžaduje integráciu vo "
+"vzhľade."
+
+msgctxt "#32002"
+msgid "Enable logging"
+msgstr "Povoliť zapisovať ladiace informácie"
+
+msgctxt "#32004"
+msgid "detected"
+msgstr "zistené"
+
+msgctxt "#32005"
+msgid "Changed font to"
+msgstr "Písmo zmenené na"
+
+#: /resources/settings.xml
+msgctxt "#32003"
+msgid "Delete add-on image cache"
+msgstr "Vymazať obrázkovú medzipamäť doplnku"
+
+msgctxt "#32006"
+msgid "Applying changes"
+msgstr "Aplikujem zmeny"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32007"
+msgid "Suggestions based on random watched item"
+msgstr "Odporúčania podľa náhodnej sledovanej položky"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32008"
+msgid "Next up"
+msgstr "Ďalej"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32009"
+msgid "Suggestions by random genre"
+msgstr "Odporúčania podľa náhodne zvoleného žánru"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32010"
+msgid "Recently added episodes and shows"
+msgstr "Naposledy pridané seriály a epizódy"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32011"
+msgid "All media"
+msgstr "Všetky médiá"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32012"
+msgid "Recommended by rating"
+msgstr "Doporučené podľa hodnotenia"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32013"
+msgid "Continue watching"
+msgstr "Pokračovať v sledovaní"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32014"
+msgid "Suggestions based on the last watched item"
+msgstr "Odporúčania podľa naposledy sledovanej položky"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32015"
+msgid "Recently added unwatched episodes and shows"
+msgstr "Naposledy pridané nevidené seriály a epizódy"
+
+#: /resources/settings.xml
+msgctxt "#32016"
+msgid "Enable service module"
+msgstr "Povoliť službu na pozadí"
+
+#: /resources/settings.xml
+msgctxt "#32017"
+msgid "Service interval time"
+msgstr "Časový interval služby"
+
+#: /resources/settings.xml
+msgctxt "#32018"
+msgid "Default blurring radius"
+msgstr "Predvolený polomer rozmazania"
+
+#: /resources/settings.xml
+msgctxt "#32020"
+msgid "Misc."
+msgstr "Rôzne"
+
+#: /resources/settings.xml
+msgctxt "#32021"
+msgid "Background change interval"
+msgstr "Interval zmeny pozadia"
+
+#: /resources/lib/utils.py
+msgctxt "#32019"
+msgid ""
+"This will delete all created images (blurred backgrounds, genre thumbs, "
+"etc.) in the addon_data folder. Do you want to proceed?"
+msgstr ""
+"Týmto vymažete všetky vytvorené obrázky v zložke tohoto doplnku (rozmazané "
+"pozadia, miniatúry, náhľady, a pod.). Prajete si pokračovať?"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32022"
+msgid "All available database items"
+msgstr "Všetky dostupné položky databázy"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32023"
+msgid "items"
+msgstr "položky"
+
+#: /resources/lib/utils.py
+msgctxt "#32024"
+msgid "The video database has no library tags stored."
+msgstr "Vo video databáze nie sú uložené žiadne značky."
+
+#: /resources/lib/utils.py
+msgctxt "#32025"
+msgid "Attention"
+msgstr "Upozornenie"
+
+#: /resources/lib/utils.py
+msgctxt "#32026"
+msgid "Enable library tag detection for"
+msgstr "Povoliť detekciu značiek v knižnici pre"
+
+#: /addon.xml
+msgctxt "#32027"
+msgid "Add/remove movie favourite"
+msgstr "Pridať/Odobrať obľúbený film"
+
+#: /addon.xml
+msgctxt "#32028"
+msgid "Add/remove TV show favourite"
+msgstr "Pridať/Odobrať obľubený seriál"
diff --git a/resources/language/resource.language.tr_TR/strings.po b/resources/language/resource.language.tr_TR/strings.po
new file mode 100644
index 0000000..f83d2d2
--- /dev/null
+++ b/resources/language/resource.language.tr_TR/strings.po
@@ -0,0 +1,165 @@
+# Embuary Helper language file
+# Addon Name: Embuary Helper
+# Addon id: script.embuary.helper
+# Addon Provider: sualfred
+# Translators:
+# Snn 1452 , 2019
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Embuary Helper\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2019-10-06 17:40+0000\n"
+"Last-Translator: Snn 1452 , 2019\n"
+"Language-Team: Turkish (Turkey) (https://www.transifex.com/sualfred/teams/80018/tr_TR/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: tr_TR\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+msgctxt "#32000"
+msgid "Error"
+msgstr "Hata"
+
+msgctxt "#32001"
+msgid ""
+"This is a tool to provide features to a skin and requires skin integration."
+msgstr ""
+"Bu, dış görünüme özellikler sağlayan ve dış görünümün tamamlanması için "
+"gerekli bir araçtır."
+
+msgctxt "#32002"
+msgid "Enable logging"
+msgstr "Günlük kaydı etkinleştir"
+
+msgctxt "#32004"
+msgid "detected"
+msgstr "algılandı"
+
+msgctxt "#32005"
+msgid "Changed font to"
+msgstr "Yazı tipi olarak değiştirildi"
+
+#: /resources/settings.xml
+msgctxt "#32003"
+msgid "Delete add-on image cache"
+msgstr "Eklenti resim önbelleğini sil"
+
+msgctxt "#32006"
+msgid "Applying changes"
+msgstr "Değişiklikleri uygulama"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32007"
+msgid "Suggestions based on random watched item"
+msgstr "Rastgele izlenen öğeye dayalı öneriler"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32008"
+msgid "Next up"
+msgstr "Sıradaki"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32009"
+msgid "Suggestions by random genre"
+msgstr "Rastgele türe göre öneriler"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32010"
+msgid "Recently added episodes and shows"
+msgstr "Son eklenen bölümler ve programlar"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32011"
+msgid "All media"
+msgstr "Tüm medya"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32012"
+msgid "Recommended by rating"
+msgstr "Derecelendirmeye göre önerilir"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32013"
+msgid "Continue watching"
+msgstr "İzlemeye devam et"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32014"
+msgid "Suggestions based on the last watched item"
+msgstr "Son izlenen öğeye dayalı öneriler"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32015"
+msgid "Recently added unwatched episodes and shows"
+msgstr "Son eklenen izlenmemiş bölümler ve programlar"
+
+#: /resources/settings.xml
+msgctxt "#32016"
+msgid "Enable service module"
+msgstr "Servis modülünü etkinleştir"
+
+#: /resources/settings.xml
+msgctxt "#32017"
+msgid "Service interval time"
+msgstr "Servis zaman aralığı"
+
+#: /resources/settings.xml
+msgctxt "#32018"
+msgid "Default blurring radius"
+msgstr "Varsayılan bulanıklaştırma yarıçapı"
+
+#: /resources/settings.xml
+msgctxt "#32020"
+msgid "Misc."
+msgstr "Çeşitli"
+
+#: /resources/settings.xml
+msgctxt "#32021"
+msgid "Background change interval"
+msgstr "Arkaplan değişim aralığı"
+
+#: /resources/lib/utils.py
+msgctxt "#32019"
+msgid ""
+"This will delete all created images (blurred backgrounds, genre thumbs, "
+"etc.) in the addon_data folder. Do you want to proceed?"
+msgstr ""
+"Bu, addon_data klasöründe oluşturulan tüm görüntüleri (bulanık arka planlar,"
+" tür küçük resimleri vb.) siler. Devam etmek istiyor musunuz?"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32022"
+msgid "All available database items"
+msgstr "Kullanılabilir tüm veritabanı öğeleri"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32023"
+msgid "items"
+msgstr "Öğeler"
+
+#: /resources/lib/utils.py
+msgctxt "#32024"
+msgid "The video database has no library tags stored."
+msgstr "Video veritabanında kayıtlı kitaplık etiketi yok."
+
+#: /resources/lib/utils.py
+msgctxt "#32025"
+msgid "Attention"
+msgstr "Dikkat"
+
+#: /resources/lib/utils.py
+msgctxt "#32026"
+msgid "Enable library tag detection for"
+msgstr "Kütüphane etiketi algılamayı etkinleştir"
+
+#: /addon.xml
+msgctxt "#32027"
+msgid "Add/remove movie favourite"
+msgstr "Favori film ekle/kaldır"
+
+#: /addon.xml
+msgctxt "#32028"
+msgid "Add/remove TV show favourite"
+msgstr "Favori TV programı ekle/kaldır"
diff --git a/resources/language/resource.language.zh_CN/strings.po b/resources/language/resource.language.zh_CN/strings.po
new file mode 100644
index 0000000..6818826
--- /dev/null
+++ b/resources/language/resource.language.zh_CN/strings.po
@@ -0,0 +1,201 @@
+# Embuary Helper language file
+# Addon Name: Embuary Helper
+# Addon id: script.embuary.helper
+# Addon Provider: sualfred
+# Translators:
+# Noisy✘ <164980316@qq.com>, 2019
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Embuary Helper\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2019-10-06 17:40+0000\n"
+"Last-Translator: Noisy✘ <164980316@qq.com>, 2019\n"
+"Language-Team: Chinese (China) (https://www.transifex.com/sualfred/teams/80018/zh_CN/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: zh_CN\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+msgctxt "#32000"
+msgid "Error"
+msgstr "错误"
+
+msgctxt "#32001"
+msgid ""
+"This is a tool to provide features to a skin and requires skin integration."
+msgstr "这是一个为皮肤提供功能的插件,需要皮肤集成。"
+
+msgctxt "#32002"
+msgid "Enable logging"
+msgstr "启用日志记录"
+
+msgctxt "#32004"
+msgid "detected"
+msgstr "检测到"
+
+msgctxt "#32005"
+msgid "Changed font to"
+msgstr "已将字体更改为"
+
+#: /resources/settings.xml
+msgctxt "#32003"
+msgid "Delete add-on image cache"
+msgstr "删除附加图像缓存"
+
+msgctxt "#32006"
+msgid "Applying changes"
+msgstr "应用更改"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32007"
+msgid "Suggestions based on random watched item"
+msgstr "基于随机观看项目的建议"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32008"
+msgid "Next up"
+msgstr "接下来"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32009"
+msgid "Suggestions by random genre"
+msgstr "按随机类型列出的建议"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32010"
+msgid "Recently added episodes and shows"
+msgstr "最近添加的剧集和节目"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32011"
+msgid "All media"
+msgstr "所有媒体"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32012"
+msgid "Recommended by rating"
+msgstr "按评级推荐"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32013"
+msgid "Continue watching"
+msgstr "继续观看"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32014"
+msgid "Suggestions based on the last watched item"
+msgstr "基于上次观看项目的建议"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32015"
+msgid "Recently added unwatched episodes and shows"
+msgstr "最近添加的未观看剧集和节目"
+
+#: /resources/settings.xml
+msgctxt "#32016"
+msgid "Enable service module"
+msgstr "启用服务模块"
+
+#: /resources/settings.xml
+msgctxt "#32017"
+msgid "Service interval time"
+msgstr "服务间隔时间"
+
+#: /resources/settings.xml
+msgctxt "#32018"
+msgid "Default blurring radius"
+msgstr "默认模糊范围"
+
+#: /resources/settings.xml
+msgctxt "#32020"
+msgid "Misc."
+msgstr "杂项"
+
+#: /resources/settings.xml
+msgctxt "#32021"
+msgid "Background change interval"
+msgstr "背景更改间隔"
+
+#: /resources/lib/utils.py
+msgctxt "#32019"
+msgid ""
+"This will delete all created images (blurred backgrounds, genre thumbs, "
+"etc.) in the addon_data folder. Do you want to proceed?"
+msgstr "这将删除加载项数据文件夹中创建的所有图像(模糊背景、类型、流派等),你要继续吗?"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32022"
+msgid "All available database items"
+msgstr "所有可用的数据库项"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32023"
+msgid "items"
+msgstr "项目"
+
+#: /resources/lib/utils.py
+msgctxt "#32024"
+msgid "The video database has no library tags stored."
+msgstr "视频数据库没有存储库标记。"
+
+#: /resources/lib/utils.py
+msgctxt "#32025"
+msgid "Attention"
+msgstr "注意"
+
+#: /resources/lib/utils.py
+msgctxt "#32026"
+msgid "Enable library tag detection for"
+msgstr "启用库标记检测"
+
+#: /addon.xml
+msgctxt "#32027"
+msgid "Add/remove movie favourite"
+msgstr "添加 / 删除电影收藏夹"
+
+#: /addon.xml
+msgctxt "#32028"
+msgid "Add/remove TV show favourite"
+msgstr "添加 / 删除电视节目收藏夹"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32029"
+msgid "Directed by"
+msgstr "导演"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32030"
+msgid "Items starring"
+msgstr "星号项目"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32031"
+msgid "Because you watched"
+msgstr "因为你在观看"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32032"
+msgid "Christmas holiday season"
+msgstr "圣诞季"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32033"
+msgid "Spooky suggestions"
+msgstr "诡异的建议"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32034"
+msgid "May the force be with you"
+msgstr "愿力量与你同在"
+
+#: /resources/lib/plugin_content.py
+msgctxt "#32035"
+msgid "Live long and prosper"
+msgstr "长寿和繁荣"
+
+#: /resources/lib/plugin_listing.py
+msgctxt "#32036"
+msgid "Special and seasonal widgets"
+msgstr "特别篇和季小工具"
diff --git a/resources/lib/__init__.py b/resources/lib/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/resources/lib/cinema_mode.py b/resources/lib/cinema_mode.py
new file mode 100644
index 0000000..794e949
--- /dev/null
+++ b/resources/lib/cinema_mode.py
@@ -0,0 +1,127 @@
+#!/usr/bin/python
+# coding: utf-8
+
+#################################################################################################
+
+import xbmc
+import xbmcgui
+import xbmcvfs
+import random
+import os
+
+from resources.lib.helper import *
+from resources.lib.json_map import *
+
+#################################################################################################
+
+class CinemaMode(object):
+ def __init__(self,dbid,dbtype):
+ self.trailer_count = xbmc.getInfoLabel('Skin.String(TrailerCount)') if xbmc.getInfoLabel('Skin.String(TrailerCount)') != '0' else False
+ self.intro_path = xbmc.getInfoLabel('Skin.String(IntroPath)')
+
+ self.dbid = dbid
+ self.dbtype = dbtype
+
+ if not self.dbid or not self.dbtype:
+ for i in range(30):
+ if xbmc.getInfoLabel('Container.ListItem.Label'):
+ break
+ xbmc.sleep(100)
+
+ self.dbid = xbmc.getInfoLabel('Container.ListItem.DBID')
+ self.dbtype = xbmc.getInfoLabel('Container.ListItem.DBTYPE')
+
+ if self.dbid and self.dbtype:
+ self.run()
+ else:
+ log('Play with cinema mode: Not enough arguments')
+
+ def run(self):
+ clear_playlists()
+ index = 0
+
+ if self.trailer_count:
+ movies = self.get_trailers()
+ for trailer in movies:
+
+ trailer_title = '%s (%s)' % (trailer['title'], xbmc.getLocalizedString(20410))
+ trailer_rating = str(round(trailer['rating'], 1))
+ trailer_thumb = trailer['art'].get('landscape') or trailer['art'].get('fanart') or trailer['art'].get('poster', '')
+
+ listitem = xbmcgui.ListItem(trailer_title)
+ listitem.setInfo('video', {'Title': trailer_title,
+ 'mediatype': 'video',
+ 'plot': trailer.get('plot', ''),
+ 'year': trailer.get('year', ''),
+ 'mpaa': trailer.get('mpaa', ''),
+ 'rating': trailer_rating
+ })
+
+ listitem.setArt({'thumb': trailer_thumb,
+ 'clearlogo': trailer['art'].get('clearlogo') or trailer['art'].get('logo') or ''
+ })
+
+ VIDEOPLAYLIST.add(url=trailer['trailer'], listitem=listitem, index=index)
+ log('Play with cinema mode: Adding trailer %s' % trailer_title)
+
+ index += 1
+
+ if self.intro_path:
+ intro = self.get_intros()
+ if intro:
+ listitem = xbmcgui.ListItem('Intro')
+ listitem.setInfo('video', {'Title': 'Intro',
+ 'mediatype': 'video'}
+ )
+
+ listitem.setArt({'thumb':'special://home/addons/script.embuary.helper/resources/trailer.jpg'})
+
+ VIDEOPLAYLIST.add(url=intro, listitem=listitem, index=index)
+ log('Play with cinema mode: Adding intro %s' % intro)
+
+ index += 1
+
+
+ json_call('Playlist.Add',
+ item={'%sid' % self.dbtype: int(self.dbid)},
+ params={'playlistid': 1}
+ )
+
+ log('Play with cinema mode: Grab your popcorn')
+
+ execute('Dialog.Close(all,true)')
+
+ json_call('Player.Open',
+ item={'playlistid': 1, 'position': 0},
+ options={'shuffled': False}
+ )
+
+ def get_trailers(self):
+ movies = json_call('VideoLibrary.GetMovies',
+ properties=JSON_MAP['movie_properties'],
+ query_filter={'and': [{'field': 'playcount', 'operator': 'lessthan', 'value': '1'}, {'field': 'hastrailer', 'operator': 'true', 'value': []}]},
+ sort={'method': 'random'}, limit=int(self.trailer_count)
+ )
+
+ try:
+ movies = movies['result']['movies']
+ except KeyError:
+ log('Play with cinema mode: No unwatched movies with available trailer found')
+ return
+
+ return movies
+
+ def get_intros(self):
+ dirs, files = xbmcvfs.listdir(self.intro_path)
+ intros = []
+
+ for file in files:
+ if file.endswith(('.mp4', '.mkv', '.mpg', '.mpeg', '.avi', '.wmv', '.mov')):
+ intros.append(file)
+
+ if intros:
+ url = os.path.join(self.intro_path, random.choice(intros))
+ return url
+
+ log('Play with cinema mode: No intros found')
+ return
\ No newline at end of file
diff --git a/resources/lib/helper.py b/resources/lib/helper.py
new file mode 100644
index 0000000..1798b93
--- /dev/null
+++ b/resources/lib/helper.py
@@ -0,0 +1,436 @@
+#!/usr/bin/python
+# coding: utf-8
+
+########################
+
+import xbmc
+import xbmcaddon
+import xbmcgui
+import xbmcvfs
+import xbmcplugin
+import json
+import time
+import datetime
+import os
+import sys
+import hashlib
+import urllib.request as urllib
+
+########################
+
+ADDON = xbmcaddon.Addon()
+ADDON_ID = ADDON.getAddonInfo('id')
+ADDON_DATA_PATH = os.path.join(xbmc.translatePath("special://profile/addon_data/%s" % ADDON_ID))
+ADDON_DATA_IMG_PATH = os.path.join(xbmc.translatePath("special://profile/addon_data/%s/img" % ADDON_ID))
+ADDON_DATA_IMG_TEMP_PATH = os.path.join(xbmc.translatePath("special://profile/addon_data/%s/img/tmp" % ADDON_ID))
+
+NOTICE = xbmc.LOGNOTICE
+WARNING = xbmc.LOGWARNING
+DEBUG = xbmc.LOGDEBUG
+ERROR = xbmc.LOGERROR
+
+DIALOG = xbmcgui.Dialog()
+
+PLAYER = xbmc.Player()
+VIDEOPLAYLIST = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
+MUSICPLAYLIST = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
+
+########################
+
+def log(txt,loglevel=DEBUG,force=False):
+ if (ADDON.getSettingBool('log') or force) and loglevel not in [WARNING, ERROR]:
+ loglevel = NOTICE
+
+ message = u'[ %s ] %s' % (ADDON_ID,txt)
+ xbmc.log(msg=message, level=loglevel)
+
+
+def remove_quotes(label):
+ if not label:
+ return ''
+
+ if label.startswith("'") and label.endswith("'") and len(label) > 2:
+ label = label[1:-1]
+ if label.startswith('"') and label.endswith('"') and len(label) > 2:
+ label = label[1:-1]
+ elif label.startswith('"') and label.endswith('"'):
+ label = label[6:-6]
+
+ return label
+
+
+def get_clean_path(path):
+ path = remove_quotes(path)
+
+ if 'activatewindow' in path.lower() and '://' in path and ',' in path:
+ path = path.split(',')[1]
+ path = remove_quotes("'" + path + "'") # be sure to remove unwanted quotes from the path
+
+ return path
+
+
+def get_joined_items(item):
+ if len(item) > 0 and item is not None:
+ item = ' / '.join(item)
+ else:
+ item = ''
+
+ return item
+
+
+def get_date(date_time):
+ date_time_obj = datetime.datetime.strptime(date_time, '%Y-%m-%d %H:%M:%S')
+ date_obj = date_time_obj.date()
+
+ return date_obj
+
+
+def execute(cmd):
+ log('Execute: %s' % cmd, DEBUG)
+ xbmc.executebuiltin(cmd)
+
+
+def condition(condition):
+ return xbmc.getCondVisibility(condition)
+
+
+def clear_playlists():
+ log('Clearing existing playlists')
+ VIDEOPLAYLIST.clear()
+ MUSICPLAYLIST.clear()
+
+
+def go_to_path(path,target='videos'):
+ execute('Dialog.Close(all,true)')
+ execute('Container.Update(%s)' % path) if condition('Window.IsMedia') else execute('ActivateWindow(%s,%s,return)' % (target,path))
+
+
+def get_bool(value,string='true'):
+ try:
+ if value.lower() == string:
+ return True
+ raise Exception
+
+ except Exception:
+ return False
+
+
+def url_quote(string):
+ return urllib.quote(string)
+
+
+def url_unquote(string):
+ return urllib.unquote(string)
+
+
+def md5hash(value):
+ value = str(value).encode()
+ return hashlib.md5(value).hexdigest()
+
+
+def touch_file(filepath):
+ os.utime(filepath,None)
+
+
+def winprop(key, value=None, clear=False, window_id=10000):
+ window = xbmcgui.Window(window_id)
+
+ if clear:
+ window.clearProperty(key.replace('.json', '').replace('.bool', ''))
+
+ elif value is not None:
+
+ if key.endswith('.json'):
+ key = key.replace('.json', '')
+ value = json.dumps(value)
+
+ elif key.endswith('.bool'):
+ key = key.replace('.bool', '')
+ value = 'true' if value else 'false'
+
+ window.setProperty(key, value)
+
+ else:
+ result = window.getProperty(key.replace('.json', '').replace('.bool', ''))
+
+ if result:
+ if key.endswith('.json'):
+ result = json.loads(result)
+ elif key.endswith('.bool'):
+ result = result in ('true', '1')
+
+ return result
+
+
+def get_channeldetails(channel_name):
+ channel_details = {}
+
+ channels = json_call('PVR.GetChannels',
+ properties=['channel', 'uniqueid', 'icon', 'thumbnail'],
+ params={'channelgroupid': 'alltv'},
+ )
+
+ try:
+ for channel in channels['result']['channels']:
+ if channel['channel'].encode('utf-8') == channel_name:
+ channel_details['channelid'] = channel['channelid']
+ channel_details['channel'] = channel['channel']
+ channel_details['icon'] = channel['icon']
+ break
+ except Exception:
+ return
+
+ return channel_details
+
+
+def json_call(method,properties=None,sort=None,query_filter=None,limit=None,params=None,item=None,options=None,limits=None,debug=False):
+ json_string = {'jsonrpc': '2.0', 'id': 1, 'method': method, 'params': {}}
+
+ if properties is not None:
+ json_string['params']['properties'] = properties
+
+ if limit is not None:
+ json_string['params']['limits'] = {'start': 0, 'end': int(limit)}
+
+ if sort is not None:
+ json_string['params']['sort'] = sort
+
+ if query_filter is not None:
+ json_string['params']['filter'] = query_filter
+
+ if options is not None:
+ json_string['params']['options'] = options
+
+ if limits is not None:
+ json_string['params']['limits'] = limits
+
+ if item is not None:
+ json_string['params']['item'] = item
+
+ if params is not None:
+ json_string['params'].update(params)
+
+ jsonrpc_call = json.dumps(json_string)
+ result = xbmc.executeJSONRPC(jsonrpc_call)
+ result = json.loads(result)
+
+ if debug:
+ log('--> JSON CALL: ' + json_prettyprint(json_string), force=True)
+ log('--> JSON RESULT: ' + json_prettyprint(result), force=True)
+
+ return result
+
+
+def json_prettyprint(string):
+ return json.dumps(string, sort_keys=True, indent=4, separators=(',', ': '))
+
+
+def reload_widgets(instant=False,reason='Timer'):
+ log('Force widgets to refresh (%s)' % reason)
+
+ timestamp = time.strftime('%Y%m%d%H%M%S', time.gmtime())
+
+ if instant:
+ if condition('System.HasAlarm(WidgetRefresh)'):
+ execute('CancelAlarm(WidgetRefresh,silent)')
+
+ winprop('EmbuaryWidgetUpdate', timestamp)
+
+ else:
+ execute('AlarmClock(WidgetRefresh,SetProperty(EmbuaryWidgetUpdate,%s,home),00:10,silent)' % timestamp)
+
+
+def sync_library_tags(tags=None,recreate=False):
+ save = False
+
+ if tags is None:
+ tags = get_library_tags()
+
+ try:
+ whitelist = addon_data('tags_whitelist.' + xbmc.getSkinDir() +'.data')
+ except Exception:
+ whitelist = []
+ save = True
+
+ try:
+ old_tags = addon_data('tags_all.data')
+ except Exception:
+ old_tags = []
+ save = True
+
+ ''' cleanup removed old tags
+ '''
+ for tag in old_tags:
+ if tag not in tags:
+ save = True
+ old_tags.remove(tag)
+ if tag in whitelist:
+ whitelist.remove(tag)
+
+ ''' recognize new available tags
+ '''
+ new_tags = []
+ for tag in tags:
+ if tag not in old_tags:
+ save = True
+ new_tags.append(tag)
+
+ if save or recreate:
+ known_tags = old_tags + new_tags
+
+ ''' automatically whitelist new tags if enabled
+ '''
+ if condition('Skin.HasSetting(AutoLibraryTags)'):
+ tags_to_whitelist = known_tags if recreate else new_tags
+
+ for tag in tags_to_whitelist:
+ if tag not in whitelist:
+ whitelist.append(tag)
+
+ addon_data('tags_all.data', known_tags)
+
+ set_library_tags(tags, whitelist, save=save)
+
+
+def get_library_tags():
+ tags = {}
+ all_tags = []
+ duplicate_handler = []
+ tag_blacklist = ['Favorite tvshows', # Emby
+ 'Favorite movies' # Emby
+ ]
+
+ movie_tags = json_call('VideoLibrary.GetTags',
+ properties=['title'],
+ params={'type': 'movie'}
+ )
+
+ tvshow_tags = json_call('VideoLibrary.GetTags',
+ properties=['title'],
+ params={'type': 'tvshow'}
+ )
+
+ try:
+ for tag in movie_tags['result']['tags']:
+ label, tagid = tag['label'], tag['tagid']
+
+ if label in tag_blacklist:
+ continue
+
+ tags[label] = {'type': 'movies', 'id': str(tagid)}
+ all_tags.append(label)
+ duplicate_handler.append(label)
+
+ except KeyError:
+ pass
+
+ try:
+ for tag in tvshow_tags['result']['tags']:
+ label, tagid = tag['label'], tag['tagid']
+
+ if label in tag_blacklist:
+ continue
+
+ if label not in duplicate_handler:
+ tags[label] = {'type': 'tvshows', 'id': str(tagid)}
+ all_tags.append(label)
+ else:
+ tags[label] = {'type': 'mixed', 'id': str(tagid)}
+
+ except KeyError:
+ pass
+
+ all_tags.sort()
+ winprop('library.tags.all', get_joined_items(all_tags))
+
+ return tags
+
+
+def set_library_tags(tags,whitelist=None,save=True,clear=False):
+ setting = 'tags_whitelist.' + xbmc.getSkinDir() +'.data'
+ index = 0
+
+ if tags and not clear:
+ if not whitelist:
+ try:
+ whitelist = addon_data('tags_whitelist.' + xbmc.getSkinDir() +'.data')
+ except Exception:
+ pass
+
+ for item in tags:
+ if item in whitelist:
+ winprop('library.tags.%d.title' % index, item)
+ winprop('library.tags.%d.type' % index, tags[item].get('type'))
+ winprop('library.tags.%d.id' % index, tags[item].get('id'))
+ index += 1
+
+ for clean in range(index,30):
+ winprop('library.tags.%d.title' % clean, clear=True)
+ winprop('library.tags.%d.type' % clean, clear=True)
+ winprop('library.tags.%d.id' % clean, clear=True)
+
+ whitelist.sort()
+ winprop('library.tags', get_joined_items(whitelist))
+
+ if save:
+ addon_data('tags_whitelist.' + xbmc.getSkinDir() +'.data', whitelist)
+
+
+def addon_data_cleanup(number_of_days=60):
+ time_in_secs = time.time() - (number_of_days * 24 * 60 * 60)
+
+ ''' Image storage maintaining. Deletes all created images which were unused in the
+ last 60 days. The image functions are touching existing files to update the
+ modification date. Often used images are never get deleted by this task.
+ '''
+ try:
+ for file in os.listdir(ADDON_DATA_IMG_PATH):
+ full_path = os.path.join(ADDON_DATA_IMG_PATH, file)
+ if os.path.isfile(full_path):
+ stat = os.stat(full_path)
+ if stat.st_mtime <= time_in_secs:
+ os.remove(full_path)
+ except Exception:
+ return
+
+ ''' Deletes old temporary files on startup
+ '''
+ try:
+ for file in os.listdir(ADDON_DATA_IMG_TEMP_PATH):
+ full_path = os.path.join(ADDON_DATA_IMG_TEMP_PATH, file)
+ if os.path.isfile(full_path):
+ os.remove(full_path)
+ except Exception:
+ pass
+
+
+def addon_data(file,content=False):
+ targetfile = os.path.join(ADDON_DATA_PATH, file)
+
+ if content is False:
+ data = []
+
+ if xbmcvfs.exists(targetfile):
+ with open(targetfile, 'r') as f:
+ try:
+ setting = json.load(f)
+ data = setting['data']
+
+ except Exception:
+ pass
+
+ return data
+
+ else:
+ data = {}
+ data['data'] = content
+
+ with open(targetfile, 'w') as f:
+ json.dump(data, f)
+
+
+def set_plugincontent(content=None,category=None):
+ if category:
+ xbmcplugin.setPluginCategory(int(sys.argv[1]), category)
+ if content:
+ xbmcplugin.setContent(int(sys.argv[1]), content)
\ No newline at end of file
diff --git a/resources/lib/image.py b/resources/lib/image.py
new file mode 100644
index 0000000..d3fb83a
--- /dev/null
+++ b/resources/lib/image.py
@@ -0,0 +1,242 @@
+#!/usr/bin/python
+# coding: utf-8
+
+#################################################################################################
+
+from __future__ import division
+
+import xbmc
+import xbmcaddon
+import xbmcvfs
+import os
+from PIL import ImageFilter,Image,ImageOps
+
+from resources.lib.helper import *
+
+#################################################################################################
+
+BLUR_CONTAINER = xbmc.getInfoLabel('Skin.String(BlurContainer)') or 100000
+BLUR_RADIUS = xbmc.getInfoLabel('Skin.String(BlurRadius)') or ADDON.getSetting('blur_radius')
+OLD_IMAGE = ''
+
+#################################################################################################
+
+
+''' create image storage folders
+'''
+try:
+ if not os.path.exists(ADDON_DATA_IMG_PATH):
+ os.makedirs(ADDON_DATA_IMG_PATH)
+ os.makedirs(ADDON_DATA_IMG_TEMP_PATH)
+
+except OSError as e:
+ # fix for race condition
+ if e.errno != os.errno.EEXIST:
+ raise
+ pass
+
+
+''' blur image and store result in addon data folder
+'''
+class ImageBlur():
+ def __init__(self,prop='listitem',file=None,radius=None):
+ global OLD_IMAGE
+ self.image = file if file is not None else xbmc.getInfoLabel('Control.GetLabel(%s)' % BLUR_CONTAINER)
+ self.radius = int(radius) if radius is not None else int(BLUR_RADIUS)
+
+ if self.image:
+ if self.image == OLD_IMAGE:
+ log('Image blurring: Image has not changed. Skip %s.' % self.image, DEBUG)
+
+ else:
+ log('Image blurring: Image changed. Blur %s.' % self.image, DEBUG)
+ OLD_IMAGE = self.image
+
+ self.filepath = self.blur()
+ self.avgcolor = self.color()
+
+ winprop(prop + '_blurred', self.filepath)
+ winprop(prop + '_color', self.avgcolor)
+ winprop(prop + '_color_noalpha', self.avgcolor[2:])
+
+ def __str__(self):
+ return self.filepath, self.avgcolor
+
+ def blur(self):
+ filename = md5hash(self.image) + str(self.radius) + '.png'
+ targetfile = os.path.join(ADDON_DATA_IMG_PATH, filename)
+
+ try:
+ if xbmcvfs.exists(targetfile):
+ touch_file(targetfile)
+ else:
+ img = _openimage(self.image,ADDON_DATA_IMG_PATH,filename)
+ img.thumbnail((200, 200), Image.ANTIALIAS)
+ img = img.convert('RGB')
+ img = img.filter(ImageFilter.GaussianBlur(self.radius))
+ img.save(targetfile)
+
+ return targetfile
+
+ except Exception:
+ return ''
+
+ ''' get average image color
+ '''
+ def color(self):
+ imagecolor = 'FFF0F0F0'
+
+ try:
+ img = Image.open(self.filepath)
+ imgResize = img.resize((1,1), Image.ANTIALIAS)
+ col = imgResize.getpixel((0,0))
+ imagecolor = 'FF%s%s%s' % (format(col[0], '02x'), format(col[1], '02x'), format(col[2], '02x'))
+ log('Average color: ' + imagecolor, DEBUG)
+
+ except:
+ log('Use fallback average color: ' + imagecolor, DEBUG)
+ pass
+
+ return imagecolor
+
+
+''' generate genre thumb and store result in addon data folder
+'''
+class CreateGenreThumb():
+ def __init__(self,genre,images):
+ self.images = images
+ self.filename = 'genre_' + md5hash(images) + '.jpg'
+ self.filepath = os.path.join(ADDON_DATA_IMG_PATH, self.filename)
+
+ if xbmcvfs.exists(self.filepath):
+ self.thumb = self.filepath
+ touch_file(self.filepath)
+ else:
+ self.temp_files = self.copy_files()
+ self.thumb = self.create_thumb()
+
+ def __str__(self):
+ return self.thumb
+
+ def copy_files(self):
+ ''' copy source posters to addon_data/img/tmp
+ '''
+ posters = list()
+ for poster in self.images:
+ posterfile = self.images.get(poster)
+ temp_filename = md5hash(posterfile) + '.jpg'
+ image = _openimage(posterfile,ADDON_DATA_IMG_TEMP_PATH,temp_filename)
+
+ if image:
+ posters.append(image)
+
+ return posters
+
+ def create_thumb(self):
+ ''' create collage with copied posteres
+ '''
+ width, height = 500, 750
+ cols, rows = 2, 2
+ thumbnail_width = int(width / cols)
+ thumbnail_height = int(height / rows)
+ size = thumbnail_width, thumbnail_height
+
+ try:
+ collage_images = []
+ for poster in self.temp_files:
+ image = ImageOps.fit(poster, (size), method=Image.ANTIALIAS, bleed=0.0, centering=(0.5, 0.5))
+ collage_images.append(image)
+
+ collage = Image.new('RGB', (width, height), (5,5,5))
+ i, x, y = 0, 0 ,0
+ for row in range(rows):
+ for col in range(cols):
+ try:
+ collage.paste(collage_images[i],(int(x), int(y)))
+ except Exception:
+ pass
+ i += 1
+ x += thumbnail_width
+ y += thumbnail_height
+ x = 0
+
+ collage.save(self.filepath,optimize=True,quality=75)
+
+ return self.filepath
+
+ except Exception:
+ return ''
+
+
+''' get image dimension and aspect ratio
+'''
+def image_info(image):
+ width, height, ar = '', '', ''
+
+ if image:
+ try:
+ filename = md5hash(image) + '.jpg'
+ img = _openimage(image,ADDON_DATA_IMG_TEMP_PATH,filename)
+ width,height = img.size
+ ar = round(width / height,2)
+ except Exception:
+ pass
+
+ return width, height, ar
+
+
+''' get cached images or copy to temp if file has not been cached yet
+'''
+def _openimage(image,targetpath,filename):
+ # some paths require unquoting to get a valid cached thumb hash
+ cached_image_path = url_unquote(image.replace('image://', ''))
+ if cached_image_path.endswith('/'):
+ cached_image_path = cached_image_path[:-1]
+
+ cached_files = []
+ for path in [xbmc.getCacheThumbName(cached_image_path), xbmc.getCacheThumbName(image)]:
+ cached_files.append(os.path.join('special://profile/Thumbnails/', path[0], path[:-4] + '.jpg'))
+ cached_files.append(os.path.join('special://profile/Thumbnails/', path[0], path[:-4] + '.png'))
+ cached_files.append(os.path.join('special://profile/Thumbnails/Video/', path[0], path))
+
+ for i in range(1, 4):
+ try:
+ ''' Try to get cached image at first
+ '''
+ for cache in cached_files:
+ if xbmcvfs.exists(cache):
+ try:
+ img = Image.open(xbmc.translatePath(cache))
+ return img
+
+ except Exception as error:
+ log('Image error: Could not open cached image --> %s' % error, WARNING)
+
+ ''' Skin images will be tried to be accessed directly. For all other ones
+ the source will be copied to the addon_data folder to get access.
+ '''
+ if xbmc.skinHasImage(image):
+ if not image.startswith('special://skin'):
+ image = os.path.join('special://skin/media/', image)
+
+ try: # in case image is packed in textures.xbt
+ img = Image.open(xbmc.translatePath(image))
+ return img
+
+ except Exception:
+ return ''
+
+ else:
+ targetfile = os.path.join(targetpath, filename)
+ if not xbmcvfs.exists(targetfile):
+ xbmcvfs.copy(image, targetfile)
+
+ img = Image.open(targetfile)
+ return img
+
+ except Exception as error:
+ log('Image error: Could not get image for %s (try %d) -> %s' % (image, i, error), ERROR)
+ xbmc.sleep(500)
+ pass
+
+ return ''
\ No newline at end of file
diff --git a/resources/lib/json_map.py b/resources/lib/json_map.py
new file mode 100644
index 0000000..e70d859
--- /dev/null
+++ b/resources/lib/json_map.py
@@ -0,0 +1,210 @@
+#!/usr/bin/python
+
+JSON_MAP = {
+ 'movie_properties': [
+ 'title',
+ 'originaltitle',
+ 'sorttitle',
+ 'votes',
+ 'playcount',
+ 'year',
+ 'genre',
+ 'studio',
+ 'country',
+ 'tagline',
+ 'tag',
+ 'plot',
+ 'runtime',
+ 'file',
+ 'plotoutline',
+ 'lastplayed',
+ 'trailer',
+ 'rating',
+ 'ratings',
+ 'userrating',
+ 'resume',
+ 'art',
+ 'streamdetails',
+ 'mpaa',
+ 'director',
+ 'premiered',
+ 'writer',
+ 'cast',
+ 'dateadded',
+ 'imdbnumber',
+ 'set',
+ 'setid',
+ 'top250'
+ ],
+
+ 'episode_properties': [
+ 'title',
+ 'playcount',
+ 'season',
+ 'episode',
+ 'showtitle',
+ 'originaltitle',
+ 'plot',
+ 'votes',
+ 'file',
+ 'rating',
+ 'ratings',
+ 'userrating',
+ 'resume',
+ 'tvshowid',
+ 'firstaired',
+ 'art',
+ 'streamdetails',
+ 'runtime',
+ 'director',
+ 'writer',
+ 'cast',
+ 'dateadded',
+ 'lastplayed'
+ ],
+
+ 'season_properties': [
+ 'season',
+ 'episode',
+ 'art',
+ 'userrating',
+ 'watchedepisodes',
+ 'showtitle',
+ 'playcount',
+ 'tvshowid'
+ ],
+
+ 'tvshow_properties': [
+ 'title',
+ 'studio',
+ 'year',
+ 'plot',
+ 'cast',
+ 'rating',
+ 'ratings',
+ 'userrating',
+ 'votes',
+ 'genre',
+ 'episode',
+ 'season',
+ 'runtime',
+ 'mpaa',
+ 'premiered',
+ 'playcount',
+ 'lastplayed',
+ 'sorttitle',
+ 'originaltitle',
+ 'art',
+ 'tag',
+ 'dateadded',
+ 'watchedepisodes',
+ 'imdbnumber'
+ ],
+
+ 'playlist_properties': [
+ 'title',
+ 'artist',
+ 'albumartist',
+ 'genre',
+ 'year',
+ 'rating',
+ 'album',
+ 'track',
+ 'duration',
+ 'comment',
+ 'lyrics',
+ 'musicbrainztrackid',
+ 'musicbrainzartistid',
+ 'musicbrainzalbumid',
+ 'musicbrainzalbumartistid',
+ 'playcount',
+ 'fanart',
+ 'director',
+ 'trailer',
+ 'tagline',
+ 'plot',
+ 'plotoutline',
+ 'originaltitle',
+ 'lastplayed',
+ 'writer',
+ 'studio',
+ 'mpaa',
+ 'cast',
+ 'country',
+ 'imdbnumber',
+ 'premiered',
+ 'productioncode',
+ 'runtime',
+ 'set',
+ 'showlink',
+ 'streamdetails',
+ 'top250',
+ 'votes',
+ 'firstaired',
+ 'season',
+ 'episode',
+ 'showtitle',
+ 'thumbnail',
+ 'file',
+ 'resume',
+ 'artistid',
+ 'albumid',
+ 'tvshowid',
+ 'setid',
+ 'watchedepisodes',
+ 'disc',
+ 'tag',
+ 'art',
+ 'genreid',
+ 'displayartist',
+ 'albumartistid',
+ 'description',
+ 'theme',
+ 'mood',
+ 'style',
+ 'albumlabel',
+ 'sorttitle',
+ 'episodeguide',
+ 'uniqueid',
+ 'dateadded',
+ 'channel',
+ 'channeltype',
+ 'hidden',
+ 'locked',
+ 'channelnumber',
+ 'starttime',
+ 'endtime',
+ 'specialsortseason',
+ 'specialsortepisode',
+ 'compilation',
+ 'releasetype',
+ 'albumreleasetype',
+ 'contributors',
+ 'displaycomposer',
+ 'displayconductor',
+ 'displayorchestra',
+ 'displaylyricist',
+ 'userrating'
+ ],
+
+ 'artist_properties': [
+ "instrument",
+ "style",
+ "mood",
+ "born",
+ "formed",
+ "description",
+ "genre",
+ "died",
+ "disbanded",
+ "yearsactive",
+ "musicbrainzartistid",
+ "fanart",
+ "thumbnail",
+ "compilationartist",
+ "dateadded",
+ "roles",
+ "songgenres",
+ "isalbumartist"
+ ]
+}
\ No newline at end of file
diff --git a/resources/lib/library.py b/resources/lib/library.py
new file mode 100644
index 0000000..3e9bb14
--- /dev/null
+++ b/resources/lib/library.py
@@ -0,0 +1,362 @@
+#!/usr/bin/python
+
+########################
+
+import xbmc
+import xbmcgui
+
+from time import gmtime, strftime
+from resources.lib.json_map import *
+from resources.lib.helper import *
+
+########################
+
+def add_items(li,json_query,type,searchstring=None):
+ for item in json_query:
+ if type == 'movie':
+ handle_movies(li, item, searchstring)
+ elif type == 'tvshow':
+ handle_tvshows(li, item, searchstring)
+ elif type == 'season':
+ handle_seasons(li, item)
+ elif type == 'episode':
+ handle_episodes(li, item)
+ elif type == 'genre':
+ handle_genre(li, item)
+ elif type == 'cast':
+ handle_cast(li, item)
+
+
+def handle_movies(li,item,searchstring=None):
+ genre = item.get('genre', '')
+ studio = item.get('studio', '')
+ country = item.get('country', '')
+ director = item.get('director', '')
+ writer = item.get('writer', '')
+
+ if 'cast' in item:
+ cast = _get_cast(item['cast'])
+
+ li_item = xbmcgui.ListItem(item['title'])
+ li_item.setInfo(type='Video', infoLabels={'title': item['title'],
+ 'originaltitle': item['originaltitle'],
+ 'sorttitle': item['sorttitle'],
+ 'year': item['year'],
+ 'genre': get_joined_items(genre),
+ 'studio': get_joined_items(studio),
+ 'country': get_joined_items(country),
+ 'director': get_joined_items(director),
+ 'writer': get_joined_items(writer),
+ 'plot': item['plot'],
+ 'plotoutline': item['plotoutline'],
+ 'dbid': item['movieid'],
+ 'imdbnumber': item['imdbnumber'],
+ 'tagline': item['tagline'],
+ 'tag': item['tag'],
+ 'rating': str(float(item['rating'])),
+ 'userrating': str(float(item['userrating'])),
+ 'votes': item['votes'],
+ 'mpaa': item['mpaa'],
+ 'lastplayed': item['lastplayed'],
+ 'cast': cast[0],
+ 'castandrole': cast[1],
+ 'mediatype': 'movie',
+ 'trailer': item['trailer'],
+ 'dateadded': item['dateadded'],
+ 'premiered': item['premiered'],
+ 'path': item['file'],
+ 'playcount': item['playcount'],
+ 'set': item['set'],
+ 'setid': item['setid'],
+ 'top250': item['top250']
+ })
+
+ _set_ratings(li_item,item['ratings'])
+
+ _set_unique_properties(li_item,genre,'genre')
+ _set_unique_properties(li_item,studio,'studio')
+ _set_unique_properties(li_item,country,'country')
+ _set_unique_properties(li_item,director,'director')
+ _set_unique_properties(li_item,writer,'writer')
+ _set_unique_properties(li_item,cast[0],'cast')
+
+ li_item.setProperty('resumetime', str(item['resume']['position']))
+ li_item.setProperty('totaltime', str(item['resume']['total']))
+
+ li_item.setArt(item['art'])
+ li_item.setArt({'icon': 'DefaultVideo.png'})
+
+ hasVideo = False
+ for key, value in iter(list(item['streamdetails'].items())):
+ for stream in value:
+ if 'video' in key:
+ hasVideo = True
+ li_item.addStreamInfo(key, stream)
+
+ if not hasVideo: # if duration wasnt in the streaminfo try adding the scraped one
+ stream = {'duration': item['runtime']}
+ li_item.addStreamInfo('video', stream)
+
+ if searchstring:
+ li_item.setProperty('searchstring', searchstring)
+
+ li.append((item['file'], li_item, False))
+
+
+def handle_tvshows(li,item,searchstring=None):
+ genre = item.get('genre', '')
+ studio = item.get('studio', '')
+ dbid = item['tvshowid']
+ season = item['season']
+ episode = item['episode']
+ watchedepisodes = item['watchedepisodes']
+ unwatchedepisodes = get_unwatched(episode,watchedepisodes)
+
+ if 'cast' in item:
+ cast = _get_cast(item['cast'])
+
+ if not condition('Window.IsVisible(movieinformation)'):
+ folder = True
+ item['file'] = 'videodb://tvshows/titles/%s/' % dbid
+ else:
+ folder = False
+ item['file'] = 'plugin://script.embuary.helper/?action=folderjump&type=tvshow&dbid=%s' % dbid
+
+ li_item = xbmcgui.ListItem(item['title'])
+ li_item.setInfo(type='Video', infoLabels={'title': item['title'],
+ 'year': item['year'],
+ 'tvshowtitle': item['title'],
+ 'sorttitle': item['sorttitle'],
+ 'originaltitle': item['originaltitle'],
+ 'genre': get_joined_items(genre),
+ 'studio': get_joined_items(studio),
+ 'plot': item['plot'],
+ 'rating': str(float(item['rating'])),
+ 'userrating': str(float(item['userrating'])),
+ 'votes': item['votes'],
+ 'premiered': item['premiered'],
+ 'mpaa': item['mpaa'],
+ 'tag': item['tag'],
+ 'cast': cast[0],
+ 'castandrole': cast[1],
+ 'mediatype': 'tvshow',
+ 'dbid': dbid,
+ 'season': season,
+ 'episode': episode,
+ 'imdbnumber': item['imdbnumber'],
+ 'lastplayed': item['lastplayed'],
+ 'path': item['file'],
+ 'duration': item['runtime'],
+ 'dateadded': item['dateadded'],
+ 'playcount': item['playcount']
+ })
+
+ _set_ratings(li_item,item['ratings'])
+
+ _set_unique_properties(li_item,genre,'genre')
+ _set_unique_properties(li_item,studio,'studio')
+ _set_unique_properties(li_item,cast[0],'cast')
+
+ li_item.setProperty('totalseasons', str(season))
+ li_item.setProperty('totalepisodes', str(episode))
+ li_item.setProperty('watchedepisodes', str(watchedepisodes))
+ li_item.setProperty('unwatchedepisodes', str(unwatchedepisodes))
+
+ li_item.setArt(item['art'])
+ li_item.setArt({'icon': 'DefaultVideo.png'})
+
+ if searchstring:
+ li_item.setProperty('searchstring', searchstring)
+
+ li.append((item['file'], li_item, folder))
+
+
+def handle_seasons(li,item):
+ tvshowdbid = item['tvshowid']
+ season = item['season']
+ episode = item['episode']
+ watchedepisodes = item['watchedepisodes']
+ unwatchedepisodes = get_unwatched(episode,watchedepisodes)
+
+ if season == 0:
+ title = '%s' % (xbmc.getLocalizedString(20381))
+ special = 'true'
+ else:
+ title = '%s %s' % (xbmc.getLocalizedString(20373), season)
+ special = 'false'
+
+ if not condition('Window.IsVisible(movieinformation)'):
+ folder = True
+ file = 'videodb://tvshows/titles/%s/%s/' % (tvshowdbid, season)
+ else:
+ folder = False
+ file = 'plugin://script.embuary.helper/?action=folderjump&type=season&dbid=%s&season=%s' % (tvshowdbid, season)
+
+ li_item = xbmcgui.ListItem(title)
+ li_item.setInfo(type='Video', infoLabels={'title': title,
+ 'season': season,
+ 'episode': episode,
+ 'tvshowtitle': item['showtitle'],
+ 'playcount': item['playcount'],
+ 'mediatype': 'season',
+ 'dbid': item['seasonid']
+ })
+
+ li_item.setArt(item['art'])
+ li_item.setArt({'icon': 'DefaultVideo.png',
+ 'fanart': item['art'].get('tvshow.fanart', '')
+ })
+
+ li_item.setProperty('watchedepisodes', str(watchedepisodes))
+ li_item.setProperty('unwatchedepisodes', str(unwatchedepisodes))
+ li_item.setProperty('isspecial', special)
+ li_item.setProperty('season_label', item.get('label', ''))
+
+ li.append((file, li_item, folder))
+
+
+def handle_episodes(li,item):
+ director = item.get('director', '')
+ writer = item.get('writer', '')
+
+ if 'cast' in item:
+ cast = _get_cast(item['cast'])
+
+ li_item = xbmcgui.ListItem(item['title'])
+ li_item.setInfo(type='Video', infoLabels={'title': item['title'],
+ 'episode': item['episode'],
+ 'season': item['season'],
+ 'premiered': item['firstaired'],
+ 'dbid': item['episodeid'],
+ 'plot': item['plot'],
+ 'tvshowtitle': item['showtitle'],
+ 'originaltitle': item['originaltitle'],
+ 'lastplayed': item['lastplayed'],
+ 'rating': str(float(item['rating'])),
+ 'userrating': str(float(item['userrating'])),
+ 'votes': item['votes'],
+ 'playcount': item['playcount'],
+ 'director': get_joined_items(director),
+ 'writer': get_joined_items(writer),
+ 'cast': cast[0],
+ 'path': item['file'],
+ 'dateadded': item['dateadded'],
+ 'castandrole': cast[1],
+ 'mediatype': 'episode'
+ })
+
+ _set_ratings(li_item,item['ratings'])
+
+ _set_unique_properties(li_item,director,'director')
+ _set_unique_properties(li_item,writer,'writer')
+ _set_unique_properties(li_item,cast[0],'cast')
+
+ li_item.setProperty('resumetime', str(item['resume']['position']))
+ li_item.setProperty('totaltime', str(item['resume']['total']))
+ li_item.setProperty('season_label', item.get('season_label', ''))
+
+ li_item.setArt({'icon': 'DefaultTVShows.png',
+ 'fanart': item['art'].get('tvshow.fanart', ''),
+ 'poster': item['art'].get('tvshow.poster', ''),
+ 'banner': item['art'].get('tvshow.banner', ''),
+ 'clearlogo': item['art'].get('tvshow.clearlogo') or item['art'].get('tvshow.logo') or '',
+ 'landscape': item['art'].get('tvshow.landscape', ''),
+ 'clearart': item['art'].get('tvshow.clearart', '')
+ })
+ li_item.setArt(item['art'])
+
+ hasVideo = False
+ for key, value in iter(list(item['streamdetails'].items())):
+ for stream in value:
+ if 'video' in key:
+ hasVideo = True
+ li_item.addStreamInfo(key, stream)
+
+ if not hasVideo: # if duration wasnt in the streaminfo try adding the scraped one
+ stream = {'duration': item['runtime']}
+ li_item.addStreamInfo('video', stream)
+
+ if item['season'] == '0':
+ li_item.setProperty('IsSpecial', 'true')
+
+ li.append((item['file'], li_item, False))
+
+
+def handle_cast(li,item):
+ li_item = xbmcgui.ListItem(item['name'])
+ li_item.setLabel(item['name'])
+ li_item.setLabel2(item['role'])
+ li_item.setProperty('role', item['role'])
+
+ li_item.setArt({'icon': 'DefaultActor.png',
+ 'thumb': item.get('thumbnail', '')
+ })
+
+ li.append(('', li_item, False))
+
+
+def handle_genre(li,item):
+ li_item = xbmcgui.ListItem(item['label'])
+ li_item.setInfo(type='Video', infoLabels={'title': item['label'],
+ 'dbid': str(item['genreid']),
+ 'path': item['url']
+ })
+
+ li_item.setArt(item['art'])
+ li_item.setArt({'icon': 'DefaultGenre.png'})
+
+ li.append((item['url'], li_item, True))
+
+
+def get_unwatched(episode,watchedepisodes):
+ if episode > watchedepisodes:
+ unwatchedepisodes = episode - watchedepisodes
+ return unwatchedepisodes
+ else:
+ return 0
+
+
+def _get_cast(castData):
+ listcast = []
+ listcastandrole = []
+
+ for castmember in castData:
+ listcast.append(castmember['name'])
+ listcastandrole.append((castmember['name'], castmember['role']))
+
+ return [listcast, listcastandrole]
+
+
+def _set_unique_properties(li_item,item,prop):
+ try:
+ i = 0
+ for value in item:
+ li_item.setProperty('%s.%s' % (prop,i), value)
+ i += 1
+ except Exception:
+ pass
+
+ return li_item
+
+
+def _set_ratings(li_item,item):
+ for key in item:
+ try:
+ rating = item[key]['rating']
+ votes = item[key]['votes'] or 0
+ default = True if key == 'default' or len(item) == 1 else False
+
+ ''' Kodi only supports floats up to 10.0. But Rotten Tomatoes is using 0-100.
+ To get the values correctly set it's required to transform the value.
+ '''
+ if rating > 100:
+ raise Exception
+ elif rating > 10:
+ rating = rating / 10
+
+ li_item.setRating(key, float(rating), votes, default)
+
+ except Exception:
+ pass
+
+ return li_item
diff --git a/resources/lib/player_monitor.py b/resources/lib/player_monitor.py
new file mode 100644
index 0000000..0e14d17
--- /dev/null
+++ b/resources/lib/player_monitor.py
@@ -0,0 +1,240 @@
+#!/usr/bin/python
+
+########################
+
+import xbmc
+import xbmcgui
+import json
+import datetime
+
+from resources.lib.helper import *
+from resources.lib.json_map import *
+from resources.lib.image import *
+
+########################
+
+class PlayerMonitor(xbmc.Monitor):
+ def __init__(self):
+ log('Service: Player monitor started', force=True)
+ self.pvr_playback = False
+
+ def onNotification(self, sender, method, data):
+ if method in ['Player.OnPlay', 'Player.OnStop', 'Player.OnAVChange', 'Playlist.OnAdd', 'Playlist.OnRemove', 'VideoLibrary.OnUpdate', 'AudioLibrary.OnUpdate']:
+ self.data = json.loads(data)
+
+ ''' Clear music or video playlist based on player content.
+ '''
+ if method == 'Playlist.OnAdd':
+ self.clear_playlists()
+
+ ''' Get stuff when playback starts.
+ '''
+ if method == 'Player.OnPlay':
+ xbmc.stopSFX()
+ self.pvr_playback = condition('String.StartsWith(Player.Filenameandpath,pvr://)')
+
+ self.get_art_info()
+
+ if condition('Skin.HasSetting(BlurPlayerIcon)'):
+ self.blur_player_icon()
+
+ if self.pvr_playback:
+ self.get_channellogo()
+
+ if PLAYER.isPlayingVideo() and not self.pvr_playback:
+ self.get_videoinfo()
+ self.get_nextitem()
+
+ ''' Playlist changed. Fetch nextitem again.
+ '''
+ if method in ['Playlist.OnAdd', 'Playlist.OnRemove'] and PLAYER.isPlayingVideo() and not self.pvr_playback:
+ self.get_nextitem()
+
+ ''' Check if multiple audio tracks are available and refetch
+ artwork info for PVR playback.
+ '''
+ if method == 'Player.OnAVChange':
+ self.get_audiotracks()
+
+ if self.pvr_playback:
+ self.get_art_info()
+
+ ''' Playback stopped. Clean up.
+ '''
+ if method == 'Player.OnStop':
+ while not self.abortRequested(): # workaround for unwanted behaviours on slow systems
+ self.waitForAbort(3)
+ break
+
+ if not PLAYER.isPlaying() and xbmcgui.getCurrentWindowId() not in [12005, 12006, 10028, 10500, 10138, 10160]:
+ self.pvr_playback = False
+ self.get_nextitem(clear=True)
+ self.get_channellogo(clear=True)
+ self.get_audiotracks(clear=True)
+ self.get_videoinfo(clear=True)
+ self.get_art_info(clear=True)
+
+ ''' Kodi doesn't reset shuffle to false automatically. To prevent issues with Emby for Kodi we have to
+ set shuffle to false for the next video playback if it was enabled by the script before.
+ '''
+ if winprop('script.shuffle.bool'):
+ winprop('script.shuffle', clear=True)
+
+ json_call('Player.SetShuffle',
+ params={'playerid': 1, 'shuffle': False}
+ )
+
+ def clear_playlists(self):
+ if self.data['position'] == 0 and condition('Skin.HasSetting(ClearPlaylist)'):
+ if self.data['playlistid'] == 0:
+ VIDEOPLAYLIST.clear()
+ log('Music playlist has been filled. Clear existing video playlist')
+
+ elif self.data['playlistid'] == 1:
+ MUSICPLAYLIST.clear()
+ log('Video playlist has been filled. Clear existing music playlist')
+
+ def get_audiotracks(self,clear=False):
+ xbmc.sleep(100)
+ audiotracks = PLAYER.getAvailableAudioStreams()
+ if len(audiotracks) > 1 and not clear:
+ winprop('EmbuaryPlayerAudioTracks.bool', True)
+ else:
+ winprop('EmbuaryPlayerAudioTracks', clear=True)
+
+ def get_channellogo(self,clear=False):
+ try:
+ if clear:
+ raise Exception
+
+ channel_details = get_channeldetails(xbmc.getInfoLabel('VideoPlayer.ChannelName'))
+ winprop('Player.ChannelLogo', channel_details['icon'])
+
+ except Exception:
+ winprop('Player.ChannelLogo', clear=True)
+
+ def get_videoinfo(self,clear=False):
+ dbid = xbmc.getInfoLabel('VideoPlayer.DBID')
+
+ for i in range(1,50):
+ winprop('VideoPlayer.AudioCodec.%d' % i, clear=True)
+ winprop('VideoPlayer.AudioChannels.%d' % i, clear=True)
+ winprop('VideoPlayer.AudioLanguage.%d' % i, clear=True)
+ winprop('VideoPlayer.SubtitleLanguage.%d' % i, clear=True)
+
+ if clear or not dbid:
+ return
+
+ if condition('VideoPlayer.Content(movies)'):
+ method = 'VideoLibrary.GetMovieDetails'
+ mediatype = 'movieid'
+ details = 'moviedetails'
+ elif condition('VideoPlayer.Content(episodes)'):
+ method = 'VideoLibrary.GetEpisodeDetails'
+ mediatype = 'episodeid'
+ details = 'episodedetails'
+ else:
+ return
+
+ json_query = json_call(method,
+ properties=['streamdetails'],
+ params={mediatype: int(dbid)}
+ )
+
+ try:
+ results_audio = json_query['result'][details]['streamdetails']['audio']
+
+ i = 1
+ for track in results_audio:
+ winprop('VideoPlayer.AudioCodec.%d' % i, track['codec'])
+ winprop('VideoPlayer.AudioChannels.%d' % i, str(track['channels']))
+ winprop('VideoPlayer.AudioLanguage.%d' % i, track['language'])
+ i += 1
+
+ except Exception:
+ pass
+
+ try:
+ results_subtitle = json_query['result'][details]['streamdetails']['subtitle']
+
+ i = 1
+ for subtitle in results_subtitle:
+ winprop('VideoPlayer.SubtitleLanguage.%d' % i, subtitle['language'])
+ i += 1
+
+ except Exception:
+ return
+
+ def get_nextitem(self,clear=False):
+ try:
+ if clear:
+ raise Exception
+
+ position = int(VIDEOPLAYLIST.getposition())
+
+ json_query = json_call('Playlist.GetItems',
+ properties=JSON_MAP['playlist_properties'],
+ limits={"start": position+1, "end": position+2},
+ params={'playlistid': 1}
+ )
+
+ nextitem = json_query['result']['items'][0]
+
+ arts = nextitem['art']
+ for art in arts:
+ if art in ['clearlogo', 'logo', 'tvshow.clearlogo', 'tvshow.logo', 'landscape', 'tvshow.landscape', 'poster', 'tvshow.poster', 'clearart', 'tvshow.clearart', 'banner', 'tvshow.banner']:
+ winprop('VideoPlayer.Next.Art(%s)' % art, arts[art])
+
+ try:
+ runtime = int(nextitem.get('runtime'))
+ minutes = runtime / 60
+ winprop('VideoPlayer.Next.Duration(m)', str(round(minutes)))
+ winprop('VideoPlayer.Next.Duration', str(datetime.timedelta(seconds=runtime)))
+ winprop('VideoPlayer.Next.Duration(s)', str(runtime))
+
+ except Exception:
+ winprop('VideoPlayer.Next.Duration', clear=True)
+ winprop('VideoPlayer.Next.Duration(m)', clear=True)
+ winprop('VideoPlayer.Next.Duration(s)', clear=True)
+
+ winprop('VideoPlayer.Next.Title', nextitem.get('title',''))
+ winprop('VideoPlayer.Next.TVShowTitle', nextitem.get('showtitle',''))
+ winprop('VideoPlayer.Next.Genre', get_joined_items(nextitem.get('genre','')))
+ winprop('VideoPlayer.Next.Plot', nextitem.get('plot',''))
+ winprop('VideoPlayer.Next.Tagline', nextitem.get('tagline',''))
+ winprop('VideoPlayer.Next.Season', str(nextitem.get('season','')))
+ winprop('VideoPlayer.Next.Episode', str(nextitem.get('episode','')))
+ winprop('VideoPlayer.Next.Year', str(nextitem.get('year','')))
+ winprop('VideoPlayer.Next.Rating', str(float(nextitem.get('rating','0'))))
+ winprop('VideoPlayer.Next.UserRating', str(float(nextitem.get('userrating','0'))))
+ winprop('VideoPlayer.Next.DBID', str(nextitem.get('id','')))
+ winprop('VideoPlayer.Next.DBType', nextitem.get('type',''))
+ winprop('VideoPlayer.Next.Art(fanart)', nextitem.get('fanart',''))
+ winprop('VideoPlayer.Next.Art(thumb)', nextitem.get('thumbnail',''))
+
+ except Exception:
+ for art in ['fanart', 'thumb', 'clearlogo', 'logo', 'tvshow.clearlogo', 'tvshow.logo', 'landscape', 'tvshow.landscape', 'poster', 'tvshow.poster', 'clearart', 'tvshow.clearart', 'banner', 'tvshow.banner']:
+ winprop('VideoPlayer.Next.Art(%s)' % art, clear=True)
+
+ for info in ['Duration','Duration(m)','Duration(s)','Title','TVShowTitle','Genre','Plot','Tagline','Season','Episode','Year','Rating','UserRating','DBID','DBType']:
+ winprop('VideoPlayer.Next.%s' % info, clear=True)
+
+ def get_art_info(self,clear=False):
+ for art in ['Player.Icon', 'Player.Art(poster)', 'Player.Art(tvshow.poster)', 'Pvr.EPGEventIcon']:
+ image = xbmc.getInfoLabel(art)
+
+ if not clear and image:
+ width,height,ar = image_info(image)
+ winprop(art + '.width',str(width))
+ winprop(art + '.height',str(height))
+ winprop(art + '.ar',str(ar))
+ else:
+ winprop(art + '.width',clear=True)
+ winprop(art + '.height',clear=True)
+ winprop(art + '.ar',clear=True)
+
+ def blur_player_icon(self):
+ ImageBlur(prop='playericon',
+ file=xbmc.getInfoLabel('Player.Icon'),
+ radius=5
+ )
\ No newline at end of file
diff --git a/resources/lib/plugin_actions.py b/resources/lib/plugin_actions.py
new file mode 100644
index 0000000..8622acd
--- /dev/null
+++ b/resources/lib/plugin_actions.py
@@ -0,0 +1,74 @@
+#!/usr/bin/python
+
+########################
+
+import sys
+import xbmc
+import xbmcplugin
+import xbmcgui
+
+from resources.lib.library import *
+from resources.lib.helper import *
+
+########################
+
+class PluginActions(object):
+ def __init__(self,params):
+ self.params = params
+
+ def folderjump(self):
+ type = self.params.get('type')
+ dbid = self.params.get('dbid')
+
+ if type == 'tvshow':
+ path = 'videodb://tvshows/titles/%s/' % dbid
+ elif type == 'season':
+ path = 'videodb://tvshows/titles/%s/%s/' % (dbid, self.params.get('season'))
+
+ try:
+ xbmcplugin.setResolvedUrl(handle=int(sys.argv[1]), succeeded=False, listitem=xbmcgui.ListItem())
+ except Exception:
+ pass
+
+ go_to_path(path)
+
+ def smsjump(self):
+ letter = self.params.get('letter').upper()
+ jumpcmd = None
+
+ try:
+ xbmcplugin.setResolvedUrl(handle=int(sys.argv[1]), succeeded=False, listitem=xbmcgui.ListItem())
+ except Exception:
+ pass
+
+ if letter == '0':
+ jumpcmd = 'lastpage' if xbmc.getInfoLabel('Container.SortOrder') == 'Descending' else 'firstpage'
+ elif letter in ['A', 'B', 'C']:
+ jumpcmd = 'jumpsms2'
+ elif letter in ['D', 'E', 'F']:
+ jumpcmd = 'jumpsms3'
+ elif letter in ['G', 'H', 'I']:
+ jumpcmd = 'jumpsms4'
+ elif letter in ['J', 'K', 'L']:
+ jumpcmd = 'jumpsms5'
+ elif letter in ['M', 'N', 'O']:
+ jumpcmd = 'jumpsms6'
+ elif letter in ['P', 'Q', 'R', 'S']:
+ jumpcmd = 'jumpsms7'
+ elif letter in ['T', 'U', 'V']:
+ jumpcmd = 'jumpsms8'
+ elif letter in ['W', 'X', 'Y', 'Z']:
+ jumpcmd = 'jumpsms9'
+
+ if jumpcmd is not None:
+ execute('SetFocus(50)')
+
+ for i in range(40):
+ json_call('Input.ExecuteAction',
+ params={'action': '%s' % jumpcmd}
+ )
+
+ xbmc.sleep(50)
+
+ if xbmc.getInfoLabel('ListItem.Sortletter').upper() == letter or letter == '0':
+ break
\ No newline at end of file
diff --git a/resources/lib/plugin_content.py b/resources/lib/plugin_content.py
new file mode 100644
index 0000000..7efd5f7
--- /dev/null
+++ b/resources/lib/plugin_content.py
@@ -0,0 +1,1179 @@
+#!/usr/bin/python
+# coding: utf-8
+
+########################
+
+import random
+import xbmcvfs
+
+from resources.lib.helper import *
+from resources.lib.library import *
+from resources.lib.image import *
+
+########################
+
+class PluginContent(object):
+ def __init__(self,params,li):
+ self.params = params
+ self.dbtitle = remove_quotes(params.get('title'))
+ self.dblabel = remove_quotes(params.get('label'))
+ self.dbtype = remove_quotes(params.get('type'))
+ self.exclude = remove_quotes(params.get('exclude'))
+ self.dbcontent = remove_quotes(params.get('content'))
+ self.dbid = remove_quotes(params.get('dbid'))
+ self.idtype = remove_quotes(params.get('idtype'))
+ self.season = remove_quotes(params.get('season'))
+ self.tag = remove_quotes(params.get('tag'))
+ self.playlist = remove_quotes(params.get('playlist'))
+ self.unwatched = remove_quotes(params.get('unwatched'))
+ self.limit = remove_quotes(params.get('limit'))
+ self.retry_count = 1
+ self.li = li
+
+ if self.limit:
+ self.limit = int(self.limit)
+
+ if self.dbtype:
+ if self.dbtype in ['movie', 'tvshow', 'season', 'episode', 'musicvideo']:
+ library = 'Video'
+ else:
+ library = 'Audio'
+
+ self.method_details = '%sLibrary.Get%sDetails' % (library, self.dbtype)
+ self.method_item = '%sLibrary.Get%ss' % (library, self.dbtype)
+ self.param = '%sid' % self.dbtype
+ self.key_details = '%sdetails' % self.dbtype
+ self.key_items = '%ss' % self.dbtype
+ self.properties = JSON_MAP.get('%s_properties' % self.dbtype)
+
+ self.sort_lastplayed = {'order': 'descending', 'method': 'lastplayed'}
+ self.sort_recent = {'order': 'descending', 'method': 'dateadded'}
+ self.sort_random = {'method': 'random'}
+
+ self.filter_unwatched = {'field': 'playcount', 'operator': 'lessthan', 'value': '1'}
+ self.filter_watched = {'field': 'playcount', 'operator': 'greaterthan', 'value': '0'}
+ self.filter_unwatched_episodes = {'field': 'numwatched', 'operator': 'lessthan', 'value': ['1']}
+ self.filter_watched_episodes = {'field': 'numwatched', 'operator': 'greaterthan','value': ['0']}
+ self.filter_no_specials = {'field': 'season', 'operator': 'greaterthan', 'value': '0'}
+ self.filter_inprogress = {'field': 'inprogress', 'operator': 'true', 'value': ''}
+ self.filter_not_inprogress = {'field': 'inprogress', 'operator': 'false', 'value': ''}
+ self.filter_tag = {'operator': 'is', 'field': 'tag', 'value': self.tag}
+ self.filter_title = {'operator': 'is', 'field': 'title', 'value': self.dbtitle}
+
+ if self.playlist:
+ playlist_li = []
+ for item in self.playlist.split(' '): # params parsing replaces ++ with a double whitespace
+ playlist_li.append({'operator': 'is', 'field': 'playlist', 'value': item})
+
+ self.filter_playlist = {'or': playlist_li}
+
+ else:
+ self.filter_playlist = None
+
+ ''' by dbid to get all available listitems
+ '''
+ def getbydbid(self):
+ try:
+ if self.dbtype == 'tvshow' and self.idtype in ['season', 'episode']:
+ self.dbid = self._gettvshowid()
+
+ json_query = json_call(self.method_details,
+ properties=self.properties,
+ params={self.param: int(self.dbid)}
+ )
+
+ result = json_query['result'][self.key_details]
+
+ if self.dbtype == 'episode':
+ try:
+ season_query = json_call('VideoLibrary.GetSeasons',
+ properties=JSON_MAP['season_properties'],
+ sort={'order': 'ascending', 'method': 'season'},
+ params={'tvshowid': int(result.get('tvshowid'))}
+ )
+
+ season_query = season_query['result']['seasons']
+
+ for season in season_query:
+ if season.get('season') == result.get('season'):
+ result['season_label'] = season.get('label')
+ break
+
+ except Exception:
+ pass
+
+ except Exception as error:
+ log('Get by DBID: No result found: %s' % error)
+ return
+
+ add_items(self.li,[result],type=self.dbtype)
+ plugin_category = 'DBID #' + str(self.dbid) + ' (' + self.dbtype + ')'
+ set_plugincontent(content=self.key_items, category=plugin_category)
+
+
+ ''' by custom args to parse own json
+ '''
+ def getbyargs(self):
+ limit = self.limit or None
+ filter_args = remove_quotes(self.params.get('filter_args')) or None
+ sort_args = remove_quotes(self.params.get('sort_args')) or None
+ plugin_category = self.params.get('category_label')
+
+ filters = []
+ if filter_args is not None:
+ filters.append(eval(filter_args))
+ if self.tag:
+ filters.append(self.filter_tag)
+
+ if sort_args is not None:
+ sort_args = eval(sort_args)
+
+ try:
+ json_query = json_call(self.method_item,
+ properties=self.properties,
+ sort=sort_args, limit=limit,
+ query_filter={'and': filters} if filters else None
+ )
+
+ result = json_query['result'][self.key_items]
+
+ except Exception as error:
+ log('Get by args: No result found: %s' % error)
+ return
+
+ add_items(self.li, result, type=self.dbtype)
+ set_plugincontent(content=self.key_items, category=plugin_category)
+
+
+ ''' resource helper to create a list will all existing and matching resource images
+ '''
+ def getresourceimages(self):
+ resource_addon = self.params.get('addon')
+ resource_dir = xbmc.translatePath('resource://%s/' % resource_addon)
+
+ string = remove_quotes(self.params.get('string'))
+ separator = remove_quotes(self.params.get('separator'))
+
+ if separator:
+ values = string.split(separator)
+ else:
+ values = string.splitlines()
+
+ for item in values:
+ for filename in ['%s.jpg' % item, '%s.png' % item]:
+ filepath = resource_dir + filename
+ if xbmcvfs.exists(filepath):
+ list_item = xbmcgui.ListItem(label=item)
+ list_item.setArt({'icon': filepath})
+ self.li.append(('', list_item, False))
+ break
+
+ set_plugincontent(content='files', category=resource_addon)
+
+
+ ''' season widgets to display library content that fit a special seasson or date
+ '''
+ def getseasonal(self):
+ xmas = ['xmas', 'christmas', 'x-mas', 'santa claus', 'st. claus', 'happy holidays', 'st. nick', 'Weihnacht',
+ 'fest der liebe', 'heilige nacht', 'heiliger abend', 'heiligabend', 'nikolaus', 'christkind', 'Noël',
+ 'Meilleurs vœux', 'feliz navidad', 'joyeux noel', 'Natale', 'szczęśliwe święta', 'Veselé Vánoce',
+ 'Vrolijk kerstfeest', 'Kerstmis', 'Boże Narodzenie', 'Kalėdos', 'Crăciun'
+ ]
+
+ horror = ['ужас', 'užas', 'rædsel', 'horror', 'φρίκη', 'õudus', 'kauhu', 'horreur', 'užas',
+ 'borzalom', 'hryllingi', 'ホラー', 'siaubas', 'verschrikking', 'skrekk', 'przerażenie',
+ 'groază', 'фильм ужасов', 'hrôza', 'grozo', 'Skräck', 'korku', 'жах', 'halloween'
+ ]
+
+ starwars = ['Star Wars', 'Krieg der Sterne', 'Luke Skywalker', 'Darth Vader', 'Jedi ', 'Ewoks',
+ 'Starwars', 'Kylo Ren', 'Yoda ', 'Chewbacca', 'Anakin Skywalker', 'Han Solo', 'r2-d2',
+ 'bb-8', 'Millennium Falcon', 'Millenium Falke', 'Stormtrooper', 'Sturmtruppler'
+ ]
+
+ startrek = ['Star Trek', 'Captain Kirk', 'Cpt. Kirk', 'James Kirk', 'James T. Kirk', 'James Tiberius Kirk',
+ 'Jean-Luc Picard', 'Commander Spock', 'Deep Space Nine', 'Deep Space 9', 'Raumschiff Enterprise',
+ 'Raumschiff Voyager', 'Klingonen', 'Klingons', 'Commander Data', 'Commander Geordi La Forge',
+ 'Counselor Deanna Troi', 'William Thomas Riker', 'Captain Benjamin Sisko', 'Cpt. Benjamin Sisko',
+ 'Captain Kathryn Janeway', 'Cpt. Kathryn Janeway'
+ ]
+
+ use_episodes = False
+ add_episodes = False
+
+ filters = []
+ filters_episode = []
+ list_type = self.params.get('list')
+
+ if list_type == 'xmas':
+ use_episodes = True
+ plugin_category = ADDON.getLocalizedString(32032)
+ for keyword in xmas:
+ filters.append({'operator': 'contains', 'field': 'title', 'value': keyword})
+ filters.append({'operator': 'contains', 'field': 'originaltitle', 'value': keyword})
+ filters.append({'operator': 'contains', 'field': 'plot', 'value': keyword})
+ filters_episode.append({'operator': 'contains', 'field': 'title', 'value': keyword})
+ filters_episode.append({'operator': 'contains', 'field': 'plot', 'value': keyword})
+
+ elif list_type == 'horror':
+ add_episodes = True
+ plugin_category = ADDON.getLocalizedString(32033)
+ filters_episode.append({'operator': 'contains', 'field': 'plot', 'value': 'Halloween'})
+ filters_episode.append({'operator': 'contains', 'field': 'title', 'value': 'Halloween'})
+ filters.append({'operator': 'contains', 'field': 'title', 'value': 'Halloween'})
+ filters.append({'operator': 'contains', 'field': 'originaltitle', 'value': 'Halloween'})
+ for keyword in horror:
+ filters.append({'operator': 'contains', 'field': 'genre', 'value': keyword})
+
+ elif list_type == 'starwars':
+ plugin_category = ADDON.getLocalizedString(32034)
+ for keyword in starwars:
+ filters.append({'operator': 'contains', 'field': 'title', 'value': keyword})
+ filters.append({'operator': 'contains', 'field': 'originaltitle', 'value': keyword})
+ filters.append({'operator': 'contains', 'field': 'plot', 'value': keyword})
+
+ elif list_type == 'startrek':
+ plugin_category = ADDON.getLocalizedString(32035)
+ for keyword in startrek:
+ filters.append({'operator': 'contains', 'field': 'title', 'value': keyword})
+ filters.append({'operator': 'contains', 'field': 'originaltitle', 'value': keyword})
+ filters.append({'operator': 'contains', 'field': 'plot', 'value': keyword})
+
+ else:
+ return
+
+ limit = self.limit or 26
+
+ if self.dbtype != 'tvshow':
+ json_query = json_call('VideoLibrary.GetMovies',
+ properties=JSON_MAP['movie_properties'],
+ sort=self.sort_random, limit=limit,
+ query_filter={'or': filters}
+ )
+ try:
+ json_query = json_query['result']['movies']
+ except Exception:
+ log('Movies by seasonal keyword: No movies found.')
+ else:
+ add_items(self.li, json_query, type='movie')
+
+ if self.dbtype != 'movie':
+ if add_episodes:
+ limit = int(limit/2)
+
+ if not use_episodes:
+ json_query = json_call('VideoLibrary.GetTVShows',
+ properties=JSON_MAP['tvshow_properties'],
+ sort=self.sort_random, limit=limit,
+ query_filter={'or': filters}
+ )
+ try:
+ json_query = json_query['result']['tvshows']
+ except Exception:
+ log('TV shows by seasonal keyword: No shows found.')
+ else:
+ add_items(self.li, json_query, type='tvshow')
+
+ if use_episodes or add_episodes:
+ json_query = json_call('VideoLibrary.GetEpisodes',
+ properties=JSON_MAP['episode_properties'],
+ sort=self.sort_random, limit=limit,
+ query_filter={'or': filters_episode}
+ )
+ try:
+ json_query = json_query['result']['episodes']
+ except Exception:
+ log('Episodes by seasonal keyword: No episodes found.')
+ else:
+ add_items(self.li, json_query, type='episode')
+
+ random.shuffle(self.li)
+ set_plugincontent(content='videos', category=plugin_category)
+
+
+ ''' get seasons of a show
+ '''
+ def getseasons(self):
+ if not self.dbid:
+ get_dbid = json_call('VideoLibrary.GetTVShows',
+ properties=['title'], limit=1,
+ query_filter=self.filter_title
+ )
+
+ try:
+ tvshow_dbid = get_dbid['result']['tvshows'][0]['tvshowid']
+ except Exception:
+ log('Get seasons by TV show: Show not found')
+ return
+
+ else:
+ if self.idtype in ['season', 'episode']:
+ tvshow_dbid = self._gettvshowid()
+ else:
+ tvshow_dbid = self.dbid
+
+ season_query = json_call('VideoLibrary.GetSeasons',
+ properties=JSON_MAP['season_properties'],
+ sort={'order': 'ascending', 'method': 'season'},
+ params={'tvshowid': int(tvshow_dbid)}
+ )
+
+ try:
+ season_query = season_query['result']['seasons']
+ if not len(season_query) > 1 and self.params.get('allseasons') == 'false':
+ return
+
+ except Exception:
+ log('Get seasons by TV show: No seasons found')
+ else:
+ add_items(self.li, season_query, type='season')
+ set_plugincontent(content='seasons', category=season_query[0].get('showtitle'))
+
+
+ ''' get more episodes from the same season
+ '''
+ def getseasonepisodes(self):
+ if not self.dbid:
+ get_dbid = json_call('VideoLibrary.GetTVShows',
+ properties=['title'], limit=1,
+ query_filter=self.filter_title
+ )
+
+ try:
+ tvshow_dbid = get_dbid['result']['tvshows'][0]['tvshowid']
+ except Exception:
+ log('Get more episodes by season: Show not found')
+ return
+
+ else:
+ if self.idtype == 'episode':
+ tvshow_dbid = self._gettvshowid()
+ else:
+ tvshow_dbid = self.dbid
+
+ episode_query = json_call('VideoLibrary.GetEpisodes',
+ properties=JSON_MAP['episode_properties'],
+ sort={'order': 'ascending', 'method': 'episode'},
+ query_filter={'operator': 'is', 'field': 'season', 'value': self.season},
+ params={'tvshowid': int(tvshow_dbid)}
+ )
+
+ try:
+ episode_query = episode_query['result']['episodes']
+ except Exception:
+ log('Get more episodes by season: No episodes found')
+ else:
+ add_items(self.li, episode_query, type='episode')
+ plugin_category = episode_query[0].get('showtitle') + ' - ' + xbmc.getLocalizedString(20373) + ' ' + str(episode_query[0].get('season'))
+ set_plugincontent(content='episodes', category=plugin_category)
+
+
+ ''' get nextup of inprogress TV shows
+ '''
+ def getnextup(self):
+ filters = [self.filter_inprogress]
+ if self.tag:
+ filters.append(self.filter_tag)
+ if self.playlist:
+ filters.append(self.filter_playlist)
+
+ json_query = json_call('VideoLibrary.GetTVShows',
+ properties=['title', 'lastplayed'],
+ sort=self.sort_lastplayed, limit=25,
+ query_filter={'and': filters}
+ )
+
+ try:
+ json_query = json_query['result']['tvshows']
+ except Exception:
+ log('Get next up episodes: No TV shows found')
+ return
+
+ for episode in json_query:
+ use_last_played_season = True
+ last_played_query = json_call('VideoLibrary.GetEpisodes',
+ properties=['seasonid', 'season'],
+ sort={'order': 'descending', 'method': 'lastplayed'}, limit=1,
+ query_filter={'and': [{'or': [self.filter_inprogress, self.filter_watched]}, self.filter_no_specials]},
+ params={'tvshowid': int(episode['tvshowid'])}
+ )
+
+ if last_played_query['result']['limits']['total'] < 1:
+ use_last_played_season = False
+
+ ''' Return the next episode of last played season
+ '''
+ if use_last_played_season:
+ episode_query = json_call('VideoLibrary.GetEpisodes',
+ properties=JSON_MAP['episode_properties'],
+ sort={'order': 'ascending', 'method': 'episode'}, limit=1,
+ query_filter={'and': [self.filter_unwatched, {'field': 'season', 'operator': 'is', 'value': str(last_played_query['result']['episodes'][0].get('season'))}]},
+ params={'tvshowid': int(episode['tvshowid'])}
+ )
+
+ if episode_query['result']['limits']['total'] < 1:
+ use_last_played_season = False
+
+ ''' If no episode is left of the last played season, fall back to the very first unwatched episode
+ '''
+ if not use_last_played_season:
+ episode_query = json_call('VideoLibrary.GetEpisodes',
+ properties=JSON_MAP['episode_properties'],
+ sort={'order': 'ascending', 'method': 'episode'}, limit=1,
+ query_filter={'and': [self.filter_unwatched, self.filter_no_specials]},
+ params={'tvshowid': int(episode['tvshowid'])}
+ )
+
+ try:
+ episode_details = episode_query['result']['episodes']
+ except Exception:
+ log('Get next up episodes: No next episodes found for %s' % episode['title'])
+ else:
+ add_items(self.li, episode_details, type='episode')
+ set_plugincontent(content='episodes', category=ADDON.getLocalizedString(32008))
+
+
+ ''' get recently added episodes of unwatched shows
+ '''
+ def getnewshows(self):
+ show_all = get_bool(self.params.get('showall'))
+
+ if show_all:
+ filter = self.filter_tag if self.tag else None
+ plugin_category = ADDON.getLocalizedString(32010)
+
+ else:
+ filters = [self.filter_unwatched]
+ if self.tag:
+ filters.append(self.filter_tag)
+ filter = {'and': filters}
+ plugin_category = ADDON.getLocalizedString(32015)
+
+
+ json_query = json_call('VideoLibrary.GetTVShows',
+ properties=['episode', 'watchedepisodes'],
+ sort=self.sort_recent, limit=25,
+ query_filter=filter
+ )
+
+ try:
+ json_query = json_query['result']['tvshows']
+ except Exception:
+ log('Get new media: No TV shows found')
+ return
+
+ for tvshow in json_query:
+ try:
+ unwatchedepisodes = get_unwatched(tvshow['episode'], tvshow['watchedepisodes'])
+ append_tvshow = False
+
+ if show_all:
+ ''' All recently added episodes. Watched state is ignored and only items added of the same date
+ will be grouped.
+ '''
+ episode_query = json_call('VideoLibrary.GetEpisodes',
+ properties=JSON_MAP['episode_properties'],
+ sort=self.sort_recent, limit=2,
+ params={'tvshowid': int(tvshow['tvshowid'])}
+ )
+
+ episode_query = episode_query['result']['episodes']
+
+ try:
+ if not get_date(episode_query[0]['dateadded']) == get_date(episode_query[1]['dateadded']):
+ raise Exception
+ append_tvshow = True
+
+ except Exception:
+ add_items(self.li, [episode_query[0]], type='episode')
+
+ elif unwatchedepisodes == 1:
+ ''' Recently added episodes based on unwatched or in progress TV shows. Episodes will be grouped
+ if more than one unwatched episode is available.
+ '''
+ episode_query = json_call('VideoLibrary.GetEpisodes',
+ properties=JSON_MAP['episode_properties'],
+ sort=self.sort_recent,limit=1,
+ query_filter=self.filter_unwatched,
+ params={'tvshowid': int(tvshow['tvshowid'])}
+ )
+
+ episode_query = episode_query['result']['episodes']
+ add_items(self.li,episode_query, type='episode')
+
+ else:
+ append_tvshow = True
+
+ ''' Group episodes to show if more than one valid episode is available
+ '''
+ if append_tvshow:
+ tvshow_query = json_call('VideoLibrary.GetTVShowDetails',
+ properties=JSON_MAP['tvshow_properties'],
+ params={'tvshowid': int(tvshow['tvshowid'])}
+ )
+
+ tvshow_query = tvshow_query['result']['tvshowdetails']
+ add_items(self.li, [tvshow_query], type='tvshow')
+
+ set_plugincontent(content='tvshows', category=plugin_category)
+
+ except Exception as error:
+ log('Get new media: Not able to parse data for show %s - %s' % (tvshow, error))
+ pass
+
+
+ ''' get custom media by genre
+ '''
+ def getbygenre(self):
+ genre = remove_quotes(self.params.get('genre'))
+
+ if not genre:
+ genres = []
+
+ if self.dbtype != 'tvshow':
+ movies_genres_query = json_call('VideoLibrary.GetGenres',
+ sort={'method': 'label'},
+ params={'type': 'movie'}
+ )
+ try:
+ for item in movies_genres_query['result']['genres']:
+ genres.append(item.get('label'))
+ except Exception:
+ log('Get movies by genre: no genres found')
+
+ if self.dbtype != 'movie':
+ tvshow_genres_query = json_call('VideoLibrary.GetGenres',
+ sort={'method': 'label'},
+ params={'type': 'tvshow'}
+ )
+ try:
+ for item in tvshow_genres_query['result']['genres']:
+ genres.append(item.get('label'))
+ except Exception:
+ log('Get TV shows by genre: no genres found')
+
+ if genres:
+ genre = random.choice(genres)
+
+ if genre:
+ filters = [{'operator': 'contains', 'field': 'genre', 'value': genre}]
+ if self.unwatched == 'True':
+ filters.append(self.filter_unwatched)
+ if self.tag:
+ filters.append(self.filter_tag)
+
+ if self.dbtype != 'tvshow':
+ json_query = json_call('VideoLibrary.GetMovies',
+ properties=JSON_MAP['movie_properties'],
+ sort=self.sort_random, limit=10,
+ query_filter={'and': filters}
+ )
+ try:
+ json_query = json_query['result']['movies']
+ except Exception:
+ log('Movies by genre %s: No movies found.' % genre)
+ else:
+ add_items(self.li, json_query, type='movie', searchstring=genre)
+
+ if self.dbtype != 'movie':
+ json_query = json_call('VideoLibrary.GetTVShows',
+ properties=JSON_MAP['tvshow_properties'],
+ sort=self.sort_random, limit=10,
+ query_filter={'and': filters}
+ )
+ try:
+ json_query = json_query['result']['tvshows']
+ except Exception:
+ log('TV shows by genre %s: No shows found.' % genre)
+ else:
+ add_items(self.li, json_query, type='tvshow', searchstring=genre)
+
+ if not self.li:
+ self._retry('getbygenre')
+
+ random.shuffle(self.li)
+
+ set_plugincontent(content='videos', category=ADDON.getLocalizedString(32009))
+
+
+ ''' get inprogress media
+ '''
+ def getinprogress(self):
+ filters = [self.filter_inprogress]
+ if self.tag:
+ filters.append(self.filter_tag)
+ if self.playlist:
+ filters.append(self.filter_playlist)
+
+ if self.dbtype != 'tvshow':
+ json_query = json_call('VideoLibrary.GetMovies',
+ properties=JSON_MAP['movie_properties'],
+ sort=self.sort_lastplayed,
+ query_filter={'and': filters}
+ )
+ try:
+ json_query = json_query['result']['movies']
+ except Exception:
+ log('In progress media: No movies found.')
+ else:
+ add_items(self.li, json_query, type='movie')
+
+ if self.dbtype != 'movie':
+ json_query = json_call('VideoLibrary.GetEpisodes',
+ properties=JSON_MAP['episode_properties'],
+ sort=self.sort_lastplayed,
+ query_filter={'and': filters}
+ )
+ try:
+ json_query = json_query['result']['episodes']
+ except Exception:
+ log('In progress media: No episodes found.')
+ else:
+ add_items(self.li, json_query, type='episode')
+
+ set_plugincontent(content='videos', category=ADDON.getLocalizedString(32013))
+
+
+ ''' genres listing with 4 posters of each available genre content
+ '''
+ def getgenre(self):
+ json_query = json_call('VideoLibrary.GetGenres',
+ sort={'method': 'label'},
+ params={'type': self.dbtype}
+ )
+ try:
+ json_query = json_query['result']['genres']
+ except KeyError:
+ log('Get genres: No genres found')
+ return
+
+ genres = []
+ for genre in json_query:
+ filters = [{'operator': 'is', 'field': 'genre', 'value': genre['label']}]
+ if self.tag:
+ filters.append(self.filter_tag)
+
+ genre_items = json_call(self.method_item,
+ properties=['art'],
+ sort=self.sort_recent, limit=4,
+ query_filter={'and': filters}
+ )
+
+ try:
+ genre_items = genre_items['result'][self.key_items]
+ if not genre_items:
+ raise Exception
+
+ except Exception:
+ continue
+
+ posters = {}
+ index = 0
+ try:
+ for art in genre_items:
+ poster = 'poster.%s' % index
+ posters[poster] = art['art'].get('poster', '')
+ index += 1
+ except Exception:
+ pass
+
+ genre['art'] = posters
+
+ generated_thumb = CreateGenreThumb(genre['label'], posters)
+ if generated_thumb:
+ genre['art']['thumb'] = str(generated_thumb)
+
+ if self.tag:
+ xsp = '{"rules":{"and":[{"field":"genre","operator":"is","value":["%s"]},{"field":"tag","operator":"is","value":["%s"]}]},"type":"%ss"}' % (genre['label'], self.tag, self.dbtype)
+ else:
+ xsp = '{"rules":{"and":[{"field":"genre","operator":"is","value":["%s"]}]},"type":"%ss"}' % (genre['label'],self.dbtype)
+
+ genre['url'] = 'videodb://{0}s/titles/?xsp={1}'.format(self.dbtype, url_quote(xsp))
+
+ genres.append(genre)
+
+ add_items(self.li, genres, type='genre')
+ set_plugincontent(content='videos', category=xbmc.getLocalizedString(135))
+
+
+ ''' get movies by director
+ '''
+ def getdirectedby(self):
+ if self.dbid:
+ json_query = json_call('VideoLibrary.GetMovieDetails',
+ properties=['title', 'director'],
+ params={'movieid': int(self.dbid)}
+ )
+
+ try:
+ directors = json_query['result']['moviedetails']['director']
+ title = json_query['result']['moviedetails']['title']
+ joineddirectors = ' / '.join(directors)
+ except Exception:
+ log('Movies by director: No director found')
+ return
+
+ filters=[]
+ for director in directors:
+ filters.append({'operator': 'is', 'field': 'director', 'value': director})
+
+ json_query = json_call('VideoLibrary.GetMovies',
+ properties=JSON_MAP['movie_properties'],
+ sort=self.sort_random,
+ query_filter={'and': [{'or': filters}, {'operator': 'isnot', 'field': 'title', 'value': title}]}
+ )
+ try:
+ json_query = json_query['result']['movies']
+ except Exception:
+ log('Movies by director %s: No additional movies found' % joineddirectors)
+ self._retry('getdirectedby')
+ return
+
+ add_items(self.li, json_query, type='movie', searchstring=joineddirectors)
+ plugin_category = ADDON.getLocalizedString(32029) + ' ' + joineddirectors
+ set_plugincontent(content='movies', category=plugin_category)
+
+
+ ''' get items by actor
+ '''
+ def getitemsbyactor(self):
+ ''' Pick random actor of provided DBID item
+ '''
+ if self.dbid:
+ json_query = json_call(self.method_details,
+ properties=['title', 'cast'],
+ params={self.param: int(self.dbid)}
+ )
+
+ try:
+ cast = json_query['result'][self.key_details]['cast']
+ exclude = json_query['result'][self.key_details]['label']
+
+ if not cast:
+ raise Exception
+
+ except Exception:
+ log('Items by actor: No cast found')
+ return
+
+ cast_range=[]
+ i = 0
+ for actor in cast:
+ if i > 3:
+ break
+ cast_range.append(actor['name'])
+ i += 1
+
+ actor = ''.join(random.choice(cast_range))
+
+ else:
+ actor = self.dblabel
+ exclude = self.exclude
+
+ if actor:
+ filters = [{'operator': 'is', 'field': 'actor', 'value': actor}]
+ if exclude:
+ filters.append({'operator': 'isnot', 'field': 'title', 'value': exclude})
+
+ if self.dbcontent != 'tvshow':
+ movie_query = json_call('VideoLibrary.GetMovies',
+ properties=JSON_MAP['movie_properties'],
+ sort=self.sort_random,
+ query_filter={'and': filters}
+ )
+
+ try:
+ movie_query = movie_query['result']['movies']
+ except Exception:
+ log('Items by actor %s: No movies found' % actor)
+ else:
+ add_items(self.li, movie_query, type='movie', searchstring=actor)
+
+ if self.dbcontent != 'movie':
+ tvshow_query = json_call('VideoLibrary.GetTVShows',
+ properties=JSON_MAP['tvshow_properties'],
+ sort=self.sort_random,
+ query_filter={'and': filters}
+ )
+
+ try:
+ tvshow_query = tvshow_query['result']['tvshows']
+ except Exception:
+ log('Items by actor %s: No shows found' % actor)
+ else:
+ add_items(self.li, tvshow_query, type='tvshow', searchstring=actor)
+
+ ''' Retry if query is based on dbid and a random actor
+ '''
+ if self.dbid and not self.li:
+ self._retry('getitemsbyactor')
+
+ plugin_category = ADDON.getLocalizedString(32030) + ' ' + actor
+ set_plugincontent(content='videos', category=plugin_category)
+
+ random.shuffle(self.li)
+
+
+ ''' because you watched xyz
+ '''
+ def getsimilar(self):
+ ''' Based on show or movie of the database
+ '''
+ if self.dbid:
+ json_query = json_call(self.method_details,
+ properties=['title', 'genre'],
+ params={self.param: int(self.dbid)}
+ )
+
+ ''' Based on a random one of the last 10 watched items
+ '''
+ if not self.dbid:
+ if self.dbtype == 'tvshow':
+ query_filter={'or': [self.filter_watched, self.filter_watched_episodes]}
+ else:
+ query_filter=self.filter_watched
+
+ json_query = json_call(self.method_item,
+ properties=['title', 'genre'],
+ sort={'method': 'lastplayed','order': 'descending'}, limit=10,
+ query_filter=query_filter
+ )
+
+ ''' Get the genres of the selected item
+ '''
+ try:
+ if self.dbid:
+ title = json_query['result'][self.key_details]['title']
+ genres = json_query['result'][self.key_details]['genre']
+ else:
+ similar_list = []
+ for x in json_query['result'][self.key_items]:
+ if x['genre']:
+ similar_list.append(x)
+
+ item_pos = self.params.get('pos')
+ if not item_pos:
+ random.shuffle(similar_list)
+ i = 0
+ else:
+ i = int(item_pos)
+
+ title = similar_list[i]['title']
+ genres = similar_list[i]['genre']
+
+ if not genres:
+ raise Exception
+
+ except Exception:
+ log ('Get similar: Not able to get genres')
+ return
+
+ random.shuffle(genres)
+
+ ''' Get movies or shows based on one or two genres of selected watched item
+ '''
+ filters = [{'operator': 'isnot', 'field': 'title', 'value': title}, {'operator': 'is', 'field': 'genre', 'value': genres[0]}]
+ if len(genres) > 1:
+ filters.append({'operator': 'is', 'field': 'genre', 'value': genres[1]})
+ if self.tag:
+ filters.append(self.filter_tag)
+
+ json_query = json_call(self.method_item,
+ properties=self.properties,
+ sort=self.sort_random, limit=15,
+ query_filter={'and': filters}
+ )
+
+ try:
+ json_query = json_query['result'][self.key_items]
+ except KeyError:
+ log('Get similar: No matching items found')
+ self._retry('getsimilar')
+ return
+
+ add_items(self.li, json_query, type=self.dbtype, searchstring=title)
+ plugin_category = ADDON.getLocalizedString(32031) + ' ' + title
+ set_plugincontent(content='%ss' % self.dbtype, category=plugin_category)
+
+
+ ''' get cast of item
+ '''
+ def getcast(self):
+ try:
+ if self.dbtitle:
+ json_query = json_call(self.method_item,
+ properties=['cast'],
+ limit=1,
+ query_filter=self.filter_title
+ )
+
+ elif self.dbid:
+ if self.dbtype == 'tvshow' and self.idtype in ['season', 'episode']:
+ self.dbid = self._gettvshowid()
+
+ json_query = json_call(self.method_details,
+ properties=['cast'],
+ params={self.param: int(self.dbid)}
+ )
+
+ if self.key_details in json_query['result']:
+ cast = json_query['result'][self.key_details]['cast']
+
+ ''' Fallback to TV show cast if episode has no cast stored
+ '''
+ if not cast and self.dbtype == 'episode':
+ tvshow_id = self._gettvshowid(idtype='episode', dbid=self.dbid)
+
+ json_query = json_call('VideoLibrary.GetTVShowDetails',
+ properties=['cast'],
+ params={'tvshowid': int(tvshow_id)}
+ )
+
+ cast = json_query['result']['tvshowdetails']['cast']
+
+ else:
+ cast = json_query['result'][self.key_items][0]['cast']
+
+ if not cast:
+ raise Exception
+
+ except Exception:
+ log('Get cast: No cast found.')
+ return
+
+ add_items(self.li, cast, type='cast')
+
+
+ ''' get full cast of movie set
+ '''
+ def getsetcast(self):
+ movies = json_call('VideoLibrary.GetMovieSetDetails',
+ params={'setid': int(self.dbid)})
+
+ try:
+ movies = movies['result']['setdetails']['movies']
+ except KeyError:
+ return
+
+ cast_list = {}
+ for movie in movies:
+ dbid = movie.get('movieid')
+ dbtitle = movie.get('title')
+
+ json_query = json_call('VideoLibrary.GetMovieDetails',
+ properties=['cast'],
+ params={'movieid': dbid}
+ )
+ try:
+ cast = json_query['result']['moviedetails']['cast']
+ except KeyError:
+ continue
+
+ for actor in cast:
+ actor_name = actor.get('name')
+ actor_role = actor.get('role')
+ actor_thumbnail = actor.get('thumbnail')
+
+ if actor_name not in cast_list:
+ cast_list[actor_name] = {'thumbnail': actor.get('thumbnail')}
+
+ if not cast_list[actor_name].get('thumbnail') and actor_thumbnail:
+ cast_list[actor_name].update({'thumbnail': actor_thumbnail})
+
+ if actor_role:
+ roles = cast_list[actor_name].get('roles', [])
+
+ if actor_role not in roles:
+ roles.append(actor_role)
+ cast_list[actor_name].update({'roles': roles})
+
+
+ cast_cleaned = []
+ for actor in cast_list:
+ cast_cleaned.append({'name': actor,
+ 'thumbnail': cast_list[actor].get('thumbnail'),
+ 'role': get_joined_items(cast_list[actor].get('roles', []))
+ })
+
+ add_items(self.li, cast_cleaned, type='cast')
+
+
+ ''' jump to letter for smsjump navigation
+ '''
+ def jumptoletter(self):
+ if xbmc.getInfoLabel('Container.NumItems'):
+ all_letters = []
+ for i in range(int(xbmc.getInfoLabel('Container.NumItems'))):
+ all_letters.append(xbmc.getInfoLabel('Listitem(%s).SortLetter' % i).upper())
+
+ if len(all_letters) > 1:
+ numbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
+ alphabet = ['#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
+ letter_count = 0
+ first_number = False
+
+ for item in numbers:
+ if item in all_letters:
+ letter_count += 1
+ first_number = item
+ break
+
+ for item in alphabet:
+ if item in all_letters:
+ letter_count += 1
+
+ if letter_count < 2:
+ return
+
+ for letter in alphabet:
+ li_item = xbmcgui.ListItem(label=letter)
+
+ if letter == '#' and first_number:
+ li_path = 'plugin://script.embuary.helper/?action=smsjump&letter=0'
+ li_item.setProperty('IsNumber', first_number)
+ append = True
+
+ elif letter in all_letters:
+ li_path = 'plugin://script.embuary.helper/?action=smsjump&letter=%s' % letter
+ append = True
+
+ elif get_bool(self.params.get('showall', 'true')):
+ li_path = ''
+ li_item.setProperty('NotAvailable', 'true')
+ append = True
+
+ else:
+ append = False
+
+ if append:
+ self.li.append((li_path, li_item, False))
+
+
+ ''' get a list of items with existing fanart for backgrounds based on a playlist
+ '''
+ def getfanartsbypath(self):
+ path = get_clean_path(self.params.get('path'))
+
+ json_query = json_call('Files.GetDirectory',
+ properties=['art', 'title'],
+ params={'directory': path}
+ )
+
+ try:
+ for item in json_query['result']['files']:
+ arts = item.get('art', {})
+
+ if not arts.get('fanart'):
+ continue
+
+ li_item = xbmcgui.ListItem(label=item.get('title'))
+ li_item.setArt(arts)
+ self.li.append(('', li_item, False))
+
+ except Exception:
+ return
+
+ ''' get path stats of playlists etc
+ '''
+ def getpathstats(self):
+ path = get_clean_path(self.params.get('path'))
+ prop_prefix = self.params.get('prefix', 'Stats')
+
+ played = 0
+ numitems = 0
+ inprogress = 0
+ episodes = 0
+ watchedepisodes = 0
+ tvshowscount = 0
+ tvshows = []
+
+ json_query = json_call('Files.GetDirectory',
+ properties=['playcount', 'resume', 'episode', 'watchedepisodes', 'tvshowid'],
+ params={'directory': path, 'media': 'video'}
+ )
+
+ try:
+ for item in json_query['result']['files']:
+ if 'type' not in item:
+ continue
+
+ if item['type'] == 'episode':
+ episodes += 1
+ if item['playcount'] > 0:
+ watchedepisodes += 1
+ if item['tvshowid'] not in tvshows:
+ tvshows.append(item['tvshowid'])
+ tvshowscount += 1
+
+ elif item['type'] == 'tvshow':
+ episodes += item['episode']
+ watchedepisodes += item['watchedepisodes']
+ tvshowscount += 1
+
+ else:
+ numitems += 1
+ if 'playcount' in list(item.keys()):
+ if item['playcount'] > 0:
+ played += 1
+ if item['resume']['position'] > 0:
+ inprogress += 1
+
+ except Exception:
+ pass
+
+ winprop('%s_Watched' % prop_prefix, str(played))
+ winprop('%s_Count' % prop_prefix, str(numitems))
+ winprop('%s_TVShowCount' % prop_prefix, str(tvshowscount))
+ winprop('%s_InProgress' % prop_prefix, str(inprogress))
+ winprop('%s_Unwatched' % prop_prefix, str(numitems - played))
+ winprop('%s_Episodes' % prop_prefix, str(episodes))
+ winprop('%s_WatchedEpisodes' % prop_prefix, str(watchedepisodes))
+ winprop('%s_UnwatchedEpisodes' % prop_prefix, str(episodes - watchedepisodes))
+
+
+ ''' function to return the TV show id based on a season or episode id
+ '''
+ def _gettvshowid(self,dbid=None,idtype=None):
+ try:
+ if not dbid:
+ dbid = self.dbid
+
+ if not idtype:
+ idtype = self.idtype
+
+ if idtype == 'season':
+ method_details = 'VideoLibrary.GetSeasonDetails'
+ param = 'seasonid'
+ key_details = 'seasondetails'
+ elif idtype == 'episode':
+ method_details = 'VideoLibrary.GetEpisodeDetails'
+ param = 'episodeid'
+ key_details = 'episodedetails'
+ else:
+ raise Exception
+
+ json_query = json_call(method_details,
+ properties=['tvshowid'],
+ params={param: int(dbid)}
+ )
+
+ result = json_query['result'][key_details]
+ dbid = result.get('tvshowid')
+
+ return dbid
+
+ except Exception:
+ return ''
+
+
+ ''' retry loop for random based widgets if previous run has not returned any single item
+ '''
+ def _retry(self,type):
+ log('Retry to get content (%s)' % str(self.retry_count))
+
+ if self.retry_count < 5:
+ self.retry_count += 1
+ getattr(self, type)()
+
+ else:
+ log('No content found. Stop retrying.')
+ self.retry_count = 1
diff --git a/resources/lib/plugin_listing.py b/resources/lib/plugin_listing.py
new file mode 100644
index 0000000..2a7afe1
--- /dev/null
+++ b/resources/lib/plugin_listing.py
@@ -0,0 +1,198 @@
+#!/usr/bin/python
+# coding: utf-8
+
+########################
+
+import sys
+import xbmc
+import xbmcgui
+from urllib.parse import urlencode
+
+from resources.lib.helper import *
+
+########################
+
+LISTING = {
+ 'index': [
+ {'name': ADDON.getLocalizedString(32011), 'browse': 'folder', 'content': 'mixed'},
+ {'name': xbmc.getLocalizedString(342), 'browse': 'folder', 'content': 'movie'},
+ {'name': xbmc.getLocalizedString(20343), 'browse': 'folder', 'content': 'tvshow'},
+ {'name': ADDON.getLocalizedString(32036), 'browse': 'widgets', 'content': 'seasonal'}
+ ],
+ 'mixed': [
+ {'name': ADDON.getLocalizedString(32013), 'info': 'getinprogress'},
+ {'name': ADDON.getLocalizedString(32009), 'info': 'getbygenre'}
+ ],
+ 'movie': [
+ {'name': ADDON.getLocalizedString(32013), 'info': 'getinprogress'},
+ {'name': xbmc.getLocalizedString(20382), 'info': 'getbyargs', 'filter_args': '{"field": "playcount", "operator": "lessthan", "value": "1"}', 'limit': '50', 'sort_args': '{"order": "descending", "method": "dateadded"}'},
+ {'name': ADDON.getLocalizedString(32007), 'info': 'getsimilar'},
+ {'name': ADDON.getLocalizedString(32014), 'info': 'getsimilar', 'pos': '0'},
+ {'name': ADDON.getLocalizedString(32009), 'info': 'getbygenre'},
+ {'name': xbmc.getLocalizedString(135), 'info': 'getgenre'},
+ {'name': ADDON.getLocalizedString(32012), 'info': 'getbyargs', 'limit': '50', 'sort_args': '{"order": "descending", "method": "rating"}'},
+ {'name': xbmc.getLocalizedString(16101), 'info': 'getbyargs', 'filter_args': '{"field": "playcount", "operator": "lessthan", "value": "1"}', 'sort_args': '{"order": "ascending", "method": "title"}'},
+ {'name': xbmc.getLocalizedString(590), 'info': 'getbyargs', 'sort_args': '{"method": "random"}', 'limit': '50'}
+ ],
+ 'tvshow': [
+ {'name': ADDON.getLocalizedString(32013), 'info': 'getinprogress'},
+ {'name': ADDON.getLocalizedString(32008), 'info': 'getnextup'},
+ {'name': ADDON.getLocalizedString(32015), 'info': 'getnewshows'},
+ {'name': ADDON.getLocalizedString(32010), 'info': 'getnewshows', 'showall': 'true'},
+ {'name': ADDON.getLocalizedString(32007), 'info': 'getsimilar'},
+ {'name': ADDON.getLocalizedString(32014), 'info': 'getsimilar', 'pos': '0'},
+ {'name': ADDON.getLocalizedString(32009), 'info': 'getbygenre'},
+ {'name': xbmc.getLocalizedString(135), 'info': 'getgenre'},
+ {'name': ADDON.getLocalizedString(32012), 'info': 'getbyargs', 'limit': '50', 'sort_args': '{"order": "descending", "method": "rating"}'},
+ {'name': xbmc.getLocalizedString(16101), 'info': 'getbyargs', 'filter_args': '{"field": "numwatched", "operator": "lessthan", "value": "1"}', 'sort_args': '{"order": "ascending", "method": "title"}'},
+ {'name': xbmc.getLocalizedString(590), 'info': 'getbyargs', 'sort_args': '{"method": "random"}', 'limit': '50'}
+ ],
+ 'seasonal': [
+ {'name': ADDON.getLocalizedString(32032), 'info': 'getseasonal', 'list': 'xmas'},
+ {'name': ADDON.getLocalizedString(32032) + ' (' + xbmc.getLocalizedString(342) + ')', 'info': 'getseasonal', 'list': 'xmas', 'type': 'movie'},
+ {'name': ADDON.getLocalizedString(32032) + ' (' + xbmc.getLocalizedString(20343) + ')', 'info': 'getseasonal', 'list': 'xmas', 'type': 'tvshow'},
+ {'name': ADDON.getLocalizedString(32033) + ' (Halloween)', 'info': 'getseasonal', 'list': 'horror'},
+ {'name': ADDON.getLocalizedString(32033) + ' (Halloween - ' + xbmc.getLocalizedString(342) + ')', 'info': 'getseasonal', 'list': 'horror', 'type': 'movie'},
+ {'name': ADDON.getLocalizedString(32033) + ' (Halloween - ' + xbmc.getLocalizedString(20343) + ')', 'info': 'getseasonal', 'list': 'horror', 'type': 'tvshow'},
+ {'name': ADDON.getLocalizedString(32034) + ' (Star Wars)', 'info': 'getseasonal', 'list': 'starwars'},
+ {'name': ADDON.getLocalizedString(32035) + ' (Star Trek)', 'info': 'getseasonal', 'list': 'startrek'}
+ ]
+ }
+
+########################
+
+class PluginListing(object):
+ def __init__(self,params,li):
+ self.li = li
+ self.folder = params.get('folder')
+ self.browse = params.get('browse')
+ self.tag = params.get('tag')
+ self.available_tags = params.get('available_tags')
+
+ if self.folder == 'movie':
+ self.plugin_category = xbmc.getLocalizedString(342)
+ elif self.folder == 'tvshow':
+ self.plugin_category = xbmc.getLocalizedString(20343)
+ elif self.folder == 'mixed':
+ self.plugin_category = ADDON.getLocalizedString(32011)
+ else:
+ self.plugin_category = ''
+
+ if self.tag and self.plugin_category:
+ self.plugin_category = self.plugin_category + ' (' + self.tag + ')'
+
+ if self.browse == 'widgets':
+ self.list_widgets()
+ elif self.browse == 'folder':
+ self.list_folder()
+ else:
+ self.list_index()
+
+ def list_index(self):
+ tags_movies = self._get_tags('movie')
+ tags_tvshows = self._get_tags('tvshow')
+
+ for item in LISTING['index']:
+ content = item.get('content')
+ browse = 'widgets'
+ folder = None
+ available_tags = None
+
+ if content == 'movie' and tags_movies:
+ browse = 'folder'
+ available_tags = tags_movies
+
+ elif content == 'tvshow' and tags_tvshows:
+ browse = 'folder'
+ available_tags = tags_tvshows
+
+ elif content == 'mixed' and (tags_movies or tags_tvshows):
+ browse = 'folder'
+ available_tags = tags_movies + tags_tvshows
+
+ url = self._encode_url(browse=browse,
+ folder=content,
+ available_tags=available_tags
+ )
+
+ self._add_item(item['name'], url)
+
+ def list_folder(self):
+ folders = self._generate_subfolder()
+
+ for item in folders:
+ url = self._encode_url(browse='widgets',
+ folder=self.folder,
+ tag=item.get('tag')
+ )
+
+ self._add_item(item['name'], url)
+
+ def list_widgets(self):
+ content_type = self.folder if self.folder in ['tvshow','movie'] else None
+
+ for item in LISTING[self.folder]:
+ if item.get('info') == 'getbyargs':
+ category_label = item.get('name')
+ else:
+ category_label = None
+
+ if content_type == None and item.get('type') in ['tvshow','movie']:
+ dbtype = item.get('type')
+ else:
+ dbtype = content_type
+
+ url = self._encode_url(info=item.get('info'),
+ type=dbtype,
+ tag=self.tag,
+ pos=item.get('pos'),
+ filter_args=item.get('filter_args'),
+ sort_args=item.get('sort_args'),
+ limit=item.get('limit'),
+ showall=item.get('showall'),
+ list=item.get('list'),
+ category_label=category_label
+ )
+
+ self._add_item(item['name'],url)
+
+ def _generate_subfolder(self):
+ items = [{'name': ADDON.getLocalizedString(32022), 'browse': 'widgets'}]
+
+ duplicate_handler = []
+ for item in eval(self.available_tags):
+ if item not in duplicate_handler:
+ duplicate_handler.append(item)
+ items.append({'name': '"' + item + '" ' + ADDON.getLocalizedString(32023), 'browse': 'widgets', 'tag': item})
+
+ return items
+
+ def _get_tags(self,library):
+ tags = []
+ json_query = json_call('VideoLibrary.GetTags',
+ properties=['title'],
+ params={'type': library}
+ )
+
+ try:
+ for tag in json_query['result']['tags']:
+ tags.append(tag.get('label'))
+ except KeyError:
+ pass
+
+ return tags
+
+ def _encode_url(self,**kwargs):
+ empty_keys = [key for key,value in list(kwargs.items()) if not value or value is None]
+ for key in empty_keys:
+ del kwargs[key]
+
+ return '{0}?{1}'.format(sys.argv[0], urlencode(kwargs))
+
+ def _add_item(self,label,url):
+ icon = 'special://home/addons/' + ADDON_ID + '/resources/icon.png'
+ list_item = xbmcgui.ListItem(label=label)
+ list_item.setInfo('video', {'title': label, 'mediatype': 'video'})
+ list_item.setArt({'icon': 'DefaultFolder.png','thumb': icon})
+ self.li.append((url, list_item, True))
+ set_plugincontent(content='videos', category=self.plugin_category)
\ No newline at end of file
diff --git a/resources/lib/service_monitor.py b/resources/lib/service_monitor.py
new file mode 100644
index 0000000..8d4c710
--- /dev/null
+++ b/resources/lib/service_monitor.py
@@ -0,0 +1,221 @@
+#!/usr/bin/python
+# coding: utf-8
+
+########################
+
+import xbmc
+import xbmcgui
+import random
+import os
+import time
+
+from resources.lib.utils import split
+from resources.lib.helper import *
+from resources.lib.image import *
+from resources.lib.player_monitor import PlayerMonitor
+
+########################
+
+NOTIFICATION_METHOD = ['VideoLibrary.OnUpdate',
+ 'VideoLibrary.OnScanFinished',
+ 'VideoLibrary.OnCleanFinished',
+ 'AudioLibrary.OnUpdate',
+ 'AudioLibrary.OnScanFinished'
+ ]
+
+########################
+
+class Service(xbmc.Monitor):
+ def __init__(self):
+ self.player_monitor = False
+ self.restart = False
+ self.screensaver = False
+ self.service_enabled = ADDON.getSettingBool('service')
+
+ if self.service_enabled:
+ self.start()
+ else:
+ self.keep_alive()
+
+ def onNotification(self,sender,method,data):
+ if ADDON_ID in sender and 'restart' in method:
+ self.restart = True
+
+ if method in NOTIFICATION_METHOD:
+ sync_library_tags()
+
+ if method.endswith('Finished'):
+ reload_widgets(instant=True, reason=method)
+ else:
+ reload_widgets(reason=method)
+
+ def onSettingsChanged(self):
+ log('Service: Addon setting changed', force=True)
+ self.restart = True
+
+ def onScreensaverActivated(self):
+ self.screensaver = True
+
+ def onScreensaverDeactivated(self):
+ self.screensaver = False
+
+ def stop(self):
+ if self.service_enabled:
+ del self.player_monitor
+ log('Service: Player monitor stopped', force=True)
+ log('Service: Stopped', force=True)
+
+ if self.restart:
+ log('Service: Applying changes', force=True)
+ xbmc.sleep(500) # Give Kodi time to set possible changed skin settings. Just to be sure to bypass race conditions on slower systems.
+ DIALOG.notification(ADDON_ID, ADDON.getLocalizedString(32006))
+ self.__init__()
+
+ def keep_alive(self):
+ log('Service: Disabled', force=True)
+
+ while not self.abortRequested() and not self.restart:
+ self.waitForAbort(5)
+
+ self.stop()
+
+ def start(self):
+ log('Service: Started', force=True)
+
+ self.player_monitor = PlayerMonitor()
+
+ master_lock = None
+ login_reload = False
+
+ service_interval = xbmc.getInfoLabel('Skin.String(ServiceInterval)') or ADDON.getSetting('service_interval')
+ service_interval = float(service_interval)
+ background_interval = xbmc.getInfoLabel('Skin.String(BackgroundInterval)') or ADDON.getSetting('background_interval')
+ background_interval = int(background_interval)
+ widget_refresh = 0
+ get_backgrounds = 200
+
+ while not self.abortRequested() and not self.restart:
+
+ ''' Only run timed tasks if screensaver is inactive to avoid keeping NAS/servers awake
+ '''
+ if not self.screensaver:
+
+ ''' Grab fanarts
+ '''
+ if get_backgrounds >= 200:
+ log('Start new fanart grabber process')
+ arts = self.grabfanart()
+ get_backgrounds = 0
+
+ else:
+ get_backgrounds += service_interval
+
+ ''' Set background properties
+ '''
+ if background_interval >= 10:
+ if arts.get('all'):
+ self.setfanart('EmbuaryBackground', arts['all'])
+ if arts.get('videos'):
+ self.setfanart('EmbuaryBackgroundVideos', arts['videos'])
+ if arts.get('music'):
+ self.setfanart('EmbuaryBackgroundMusic', arts['music'])
+ if arts.get('movies'):
+ self.setfanart('EmbuaryBackgroundMovies', arts['movies'])
+ if arts.get('tvshows'):
+ self.setfanart('EmbuaryBackgroundTVShows', arts['tvshows'])
+ if arts.get('musicvideos'):
+ self.setfanart('EmbuaryBackgroundMusicVideos', arts['musicvideos'])
+ if arts.get('artists'):
+ self.setfanart('EmbuaryBackgroundMusic', arts['artists'])
+
+ background_interval = 0
+
+ else:
+ background_interval += service_interval
+
+ ''' Blur backgrounds
+ '''
+ if condition('Skin.HasSetting(BlurEnabled)'):
+ radius = xbmc.getInfoLabel('Skin.String(BlurRadius)') or ADDON.getSetting('blur_radius')
+ ImageBlur(radius=radius)
+
+ ''' Refresh widgets
+ '''
+ if widget_refresh >= 600:
+ reload_widgets(instant=True)
+ widget_refresh = 0
+
+ else:
+ widget_refresh += service_interval
+
+ ''' Workaround for login screen bug
+ '''
+ if not login_reload:
+ if condition('System.HasLoginScreen'):
+ log('System has login screen enabled. Reload the skin to load all strings correctly.')
+ execute('ReloadSkin()')
+ login_reload = True
+
+ ''' Master lock reload logic for widgets
+ '''
+ if condition('System.HasLocks'):
+ if master_lock is None:
+ master_lock = condition('System.IsMaster')
+ log('Master mode: %s' % master_lock)
+
+ if master_lock == True and not condition('System.IsMaster'):
+ log('Left master mode. Reload skin.')
+ master_lock = False
+ execute('ReloadSkin()')
+
+ elif master_lock == False and condition('System.IsMaster'):
+ log('Entered master mode. Reload skin.')
+ master_lock = True
+ execute('ReloadSkin()')
+
+ elif master_lock is not None:
+ master_lock = None
+
+ self.waitForAbort(service_interval)
+
+ self.stop()
+
+ def grabfanart(self):
+ arts = {}
+ arts['movies'] = []
+ arts['tvshows'] = []
+ arts['musicvideos'] = []
+ arts['artists'] = []
+ arts['all'] = []
+ arts['videos'] = []
+
+ for item in ['movies', 'tvshows', 'artists', 'musicvideos']:
+ dbtype = 'Video' if item != 'artists' else 'Audio'
+ query = json_call('%sLibrary.Get%s' % (dbtype, item),
+ properties=['art'],
+ sort={'method': 'random'}, limit=40
+ )
+
+ try:
+ for result in query['result'][item]:
+ if result['art'].get('fanart'):
+ data = {'title': result.get('label', '')}
+ data.update(result['art'])
+ arts[item].append(data)
+
+ except KeyError:
+ pass
+
+ arts['videos'] = arts['movies'] + arts['tvshows']
+
+ for cat in arts:
+ if arts[cat]:
+ arts['all'] = arts['all'] + arts[cat]
+
+ return arts
+
+ def setfanart(self,key,items):
+ arts = random.choice(items)
+ winprop(key, arts.get('fanart', ''))
+ for item in ['clearlogo', 'landscape', 'banner', 'poster', 'discart', 'title']:
+ winprop('%s.%s' % (key, item), arts.get(item, ''))
\ No newline at end of file
diff --git a/resources/lib/utils.py b/resources/lib/utils.py
new file mode 100644
index 0000000..8c42961
--- /dev/null
+++ b/resources/lib/utils.py
@@ -0,0 +1,734 @@
+#!/usr/bin/python
+# coding: utf-8
+
+########################
+
+from __future__ import division
+
+import xbmc
+import xbmcaddon
+import xbmcgui
+import xbmcvfs
+import json
+import random
+import os
+import locale
+
+from resources.lib.helper import *
+from resources.lib.library import *
+from resources.lib.json_map import *
+from resources.lib.image import *
+from resources.lib.cinema_mode import *
+
+########################
+
+''' Classes
+'''
+def blurimg(params):
+ ImageBlur(prop=params.get('prop', 'output'),
+ file=remove_quotes(params.get('file')),
+ radius=params.get('radius', None)
+ )
+
+
+def playcinema(params):
+ CinemaMode(dbid=params.get('dbid'),
+ dbtype=params.get('type')
+ )
+
+
+''' Dialogs
+'''
+def createcontext(params):
+ selectionlist = []
+ indexlist = []
+ splitby = remove_quotes(params.get('splitby', '||'))
+ window = params.get('window', '')
+
+ for i in range(1, 100):
+ label = xbmc.getInfoLabel('Window(%s).Property(Context.%d.Label)' % (window, i))
+
+ if label == '':
+ break
+
+ elif label != 'none' and label != '-':
+ selectionlist.append(label)
+ indexlist.append(i)
+
+ if selectionlist:
+ index = DIALOG.contextmenu(selectionlist)
+
+ if index > -1:
+ value = xbmc.getInfoLabel('Window(%s).Property(Context.%d.Builtin)' % (window, indexlist[index]))
+ for builtin in value.split(splitby):
+ execute(builtin)
+ xbmc.sleep(30)
+
+ for i in range(1, 100):
+ if window:
+ execute('ClearProperty(Context.%d.Builtin,%s)' % (i, window))
+ execute('ClearProperty(Context.%d.Label,%s)' % (i, window))
+ else:
+ execute('ClearProperty(Context.%d.Builtin)' % i)
+ execute('ClearProperty(Context.%d.Label)' % i)
+
+
+def createselect(params):
+ selectionlist = []
+ indexlist = []
+ headertxt = remove_quotes(params.get('header', ''))
+ splitby = remove_quotes(params.get('splitby', '||'))
+ window = params.get('window', '')
+ usedetails = True if params.get('usedetails') == 'true' else False
+ preselect = int(params.get('preselect', -1))
+
+ for i in range(1, 100):
+ label = xbmc.getInfoLabel('Window(%s).Property(Dialog.%d.Label)' % (window, i))
+ label2 = xbmc.getInfoLabel('Window(%s).Property(Dialog.%d.Label2)' % (window, i))
+ icon = xbmc.getInfoLabel('Window(%s).Property(Dialog.%d.Icon)' % (window, i))
+
+ if label == '':
+ break
+
+ elif label != 'none' and label != '-':
+ li_item = xbmcgui.ListItem(label=label, label2=label2)
+ li_item.setArt({'icon': icon})
+ selectionlist.append(li_item)
+ indexlist.append(i)
+
+ if selectionlist:
+ index = DIALOG.select(headertxt, selectionlist, preselect=preselect, useDetails=usedetails)
+
+ if index > -1:
+ value = xbmc.getInfoLabel('Window(%s).Property(Dialog.%d.Builtin)' % (window, indexlist[index]))
+ for builtin in value.split(splitby):
+ execute(builtin)
+ xbmc.sleep(30)
+
+ for i in range(1, 100):
+ if window:
+ execute('ClearProperty(Dialog.%d.Builtin,%s)' % (i, window))
+ execute('ClearProperty(Dialog.%d.Label,%s)' % (i, window))
+ execute('ClearProperty(Dialog.%d.Label2,%s)' % (i, window))
+ execute('ClearProperty(Dialog.%d.Icon,%s)' % (i, window))
+ else:
+ execute('ClearProperty(Dialog.%d.Builtin)' % i)
+ execute('ClearProperty(Dialog.%d.Label)' % i)
+ execute('ClearProperty(Dialog.%d.Label2)' % i)
+ execute('ClearProperty(Dialog.%d.Icon)' % i)
+
+
+def splitandcreateselect(params):
+ headertxt = remove_quotes(params.get('header', ''))
+ seperator = remove_quotes(params.get('seperator', ' / '))
+ splitby = remove_quotes(params.get('splitby', '||'))
+ window = params.get('window', '')
+
+ selectionlist = remove_quotes(params.get('items')).split(seperator)
+
+ if selectionlist:
+ index = DIALOG.select(headertxt, selectionlist)
+
+ if index > -1:
+ value = xbmc.getInfoLabel('Window(%s).Property(Dialog.Builtin)' % window)
+ value = value.replace('???', selectionlist[index])
+ for builtin in value.split(splitby):
+ execute(builtin)
+ xbmc.sleep(30)
+
+ if window:
+ execute('ClearProperty(Dialog.Builtin,%s)' % window)
+ else:
+ execute('ClearProperty(Dialog.Builtin)')
+
+
+def dialogok(params):
+ headertxt = remove_quotes(params.get('header', ''))
+ bodytxt = remove_quotes(params.get('message', ''))
+ DIALOG.ok(headertxt, bodytxt)
+
+
+def dialogyesno(params):
+ headertxt = remove_quotes(params.get('header', ''))
+ bodytxt = remove_quotes(params.get('message', ''))
+ yesactions = params.get('yesaction', '').split('|')
+ noactions = params.get('noaction', '').split('|')
+
+ if DIALOG.yesno(headertxt, bodytxt):
+ for action in yesactions:
+ execute(action)
+ else:
+ for action in noactions:
+ execute(action)
+
+
+def textviewer(params):
+ headertxt = remove_quotes(params.get('header', ''))
+ bodytxt = remove_quotes(params.get('message', ''))
+ DIALOG.textviewer(headertxt, bodytxt)
+
+
+''' Functions
+'''
+def restartservice(params):
+ execute('NotifyAll(%s, restart)' % ADDON_ID)
+
+
+def calc(params):
+ prop = remove_quotes(params.get('prop', 'CalcResult'))
+ formula = remove_quotes(params.get('do'))
+ result = eval(str(formula))
+ winprop(prop, str(result))
+
+
+def settimer(params):
+ actions = remove_quotes(params.get('do'))
+ time = params.get('time', '50')
+ delay = params.get('delay')
+ busydialog = get_bool(params.get('busydialog', 'true'))
+
+ if busydialog:
+ execute('ActivateWindow(busydialognocancel)')
+
+ xbmc.sleep(int(time))
+ execute('Dialog.Close(all,true)')
+
+ while condition('Window.IsVisible(busydialognocancel)'):
+ xbmc.sleep(10)
+
+ for action in actions.split('||'):
+ execute(action)
+ if delay:
+ xbmc.sleep(int(delay))
+
+
+def encode(params):
+ string = remove_quotes(params.get('string'))
+ prop = params.get('prop', 'EncodedString')
+ winprop(prop, url_quote(string))
+
+
+def decode(params):
+ string = remove_quotes(params.get('string'))
+ prop = params.get('prop', 'DecodedString')
+ winprop(prop, url_unquote(string))
+
+
+def getaddonsetting(params):
+ addon_id = params.get('addon')
+ addon_setting = params.get('setting')
+ prop = addon_id + '-' + addon_setting
+
+ try:
+ setting = xbmcaddon.Addon(addon_id).getSetting(addon_setting)
+ winprop(prop,str(setting))
+ except Exception:
+ winprop(prop, clear=True)
+
+
+def togglekodisetting(params):
+ settingname = params.get('setting', '')
+ value = False if condition('system.getbool(%s)' % settingname) else True
+
+ json_call('Settings.SetSettingValue',
+ params={'setting': '%s' % settingname, 'value': value}
+ )
+
+
+def getkodisetting(params):
+ setting = params.get('setting')
+ strip = params.get('strip')
+
+ json_query = json_call('Settings.GetSettingValue',
+ params={'setting': '%s' % setting}
+ )
+
+ try:
+ result = json_query['result']
+ result = result.get('value')
+
+ if strip == 'timeformat':
+ strip = ['(12h)', ('(24h)')]
+ for value in strip:
+ if value in result:
+ result = result[:-6]
+
+ result = str(result)
+ if result.startswith('[') and result.endswith(']'):
+ result = result[1:-1]
+
+ winprop(setting,result)
+
+ except Exception:
+ winprop(setting, clear=True)
+
+
+def setkodisetting(params):
+ settingname = params.get('setting', '')
+ value = params.get('value', '')
+
+ try:
+ value = int(value)
+ except Exception:
+ if value.lower() == 'true':
+ value = True
+ elif value.lower() == 'false':
+ value = False
+
+ json_call('Settings.SetSettingValue',
+ params={'setting': '%s' % settingname, 'value': value}
+ )
+
+
+def toggleaddons(params):
+ addonid = params.get('addonid').split('+')
+ enable = get_bool(params.get('enable'))
+
+ for addon in addonid:
+
+ try:
+ json_call('Addons.SetAddonEnabled',
+ params={'addonid': '%s' % addon, 'enabled': enable}
+ )
+ log('%s - enable: %s' % (addon, enable))
+ except Exception:
+ pass
+
+
+def playsfx(params):
+ xbmc.playSFX(remove_quotes(params.get('path', '')))
+
+
+def stopsfx(params):
+ xbmc.stopSFX()
+
+
+def imginfo(params):
+ prop = remove_quotes(params.get('prop', 'img'))
+ img = remove_quotes(params.get('img'))
+ if img:
+ width,height,ar = image_info(img)
+ winprop(prop + '.width', str(width))
+ winprop(prop + '.height', str(height))
+ winprop(prop + '.ar', str(ar))
+
+
+def playitem(params):
+ clear_playlists()
+ execute('Dialog.Close(all,true)')
+
+ dbtype = params.get('type')
+ dbid = params.get('dbid')
+ resume = params.get('resume', True)
+ file = remove_quotes(params.get('item'))
+
+ if dbtype == 'song':
+ param = 'songid'
+
+ elif dbtype == 'episode':
+ method_details = 'VideoLibrary.GetEpisodeDetails'
+ param = 'episodeid'
+ key_details = 'episodedetails'
+
+ else:
+ method_details = 'VideoLibrary.GetMovieDetails'
+ param = 'movieid'
+ key_details = 'moviedetails'
+
+ if dbid:
+ if dbtype == 'song' or not resume:
+ position = 0
+
+ else:
+ result = json_call(method_details,
+ properties=['resume', 'runtime'],
+ params={param: int(dbid)}
+ )
+
+ try:
+ result = result['result'][key_details]
+ position = result['resume'].get('position') / result['resume'].get('total') * 100
+ resume_time = result.get('runtime') / 100 * position
+ resume_time = str(datetime.timedelta(seconds=resume_time))
+ except Exception:
+ position = 0
+ resume_time = None
+
+ if position > 0:
+ resume_string = xbmc.getLocalizedString(12022)[:-5] + resume_time
+ contextdialog = DIALOG.contextmenu([resume_string, xbmc.getLocalizedString(12021)])
+
+ if contextdialog == 1:
+ position = 0
+ elif contextdialog == -1:
+ return
+
+ json_call('Player.Open',
+ item={param: int(dbid)},
+ options={'resume': position},
+ )
+
+ elif file:
+ # playmedia() because otherwise resume points get ignored
+ execute('PlayMedia(%s)' % file)
+
+
+def playfolder(params):
+ clear_playlists()
+
+ dbid = int(params.get('dbid'))
+ shuffled = get_bool(params.get('shuffle'))
+
+ if shuffled:
+ winprop('script.shuffle.bool', True)
+
+ if params.get('type') == 'season':
+ json_query = json_call('VideoLibrary.GetSeasonDetails',
+ properties=['title', 'season', 'tvshowid'],
+ params={'seasonid': dbid}
+ )
+ try:
+ result = json_query['result']['seasondetails']
+ except Exception as error:
+ log('Play folder error getting season details: %s' % error)
+ return
+
+ json_query = json_call('VideoLibrary.GetEpisodes',
+ properties=JSON_MAP['episode_properties'],
+ query_filter={'operator': 'is', 'field': 'season', 'value': '%s' % result['season']},
+ params={'tvshowid': int(result['tvshowid'])}
+ )
+ else:
+ json_query = json_call('VideoLibrary.GetEpisodes',
+ properties=JSON_MAP['episode_properties'],
+ params={'tvshowid': dbid}
+ )
+
+ try:
+ result = json_query['result']['episodes']
+ except Exception as error:
+ log('Play folder error getting episodes: %s' % error)
+ return
+
+ for episode in result:
+ json_call('Playlist.Add',
+ item={'episodeid': episode['episodeid']},
+ params={'playlistid': 1}
+ )
+
+ execute('Dialog.Close(all)')
+
+ json_call('Player.Open',
+ item={'playlistid': 1, 'position': 0},
+ options={'shuffled': shuffled}
+ )
+
+
+def playall(params):
+ clear_playlists()
+
+ container = params.get('id')
+ method = params.get('method')
+
+ playlistid = 0 if params.get('type') == 'music' else 1
+ shuffled = get_bool(method,'shuffle')
+
+ if shuffled:
+ winprop('script.shuffle.bool', True)
+
+ if method == 'fromhere':
+ method = 'Container(%s).ListItemNoWrap' % container
+ else:
+ method = 'Container(%s).ListItemAbsolute' % container
+
+ for i in range(int(xbmc.getInfoLabel('Container(%s).NumItems' % container))):
+
+ if condition('String.IsEqual(%s(%s).DBType,movie)' % (method,i)):
+ media_type = 'movie'
+ elif condition('String.IsEqual(%s(%s).DBType,episode)' % (method,i)):
+ media_type = 'episode'
+ elif condition('String.IsEqual(%s(%s).DBType,song)' % (method,i)):
+ media_type = 'song'
+ else:
+ media_type = None
+
+ dbid = xbmc.getInfoLabel('%s(%s).DBID' % (method,i))
+ url = xbmc.getInfoLabel('%s(%s).Filenameandpath' % (method,i))
+
+ if media_type and dbid:
+ json_call('Playlist.Add',
+ item={'%sid' % media_type: int(dbid)},
+ params={'playlistid': playlistid}
+ )
+ elif url:
+ json_call('Playlist.Add',
+ item={'file': url},
+ params={'playlistid': playlistid}
+ )
+
+ json_call('Player.Open',
+ item={'playlistid': playlistid, 'position': 0},
+ options={'shuffled': shuffled}
+ )
+
+
+def playrandom(params):
+ clear_playlists()
+ container = params.get('id')
+
+ i = random.randint(1,int(xbmc.getInfoLabel('Container(%s).NumItems' % container)))
+
+ if condition('String.IsEqual(Container(%s).ListItemAbsolute(%s).DBType,movie)' % (container,i)):
+ media_type = 'movie'
+ elif condition('String.IsEqual(Container(%s).ListItemAbsolute(%s).DBType,episode)' % (container,i)):
+ media_type = 'episode'
+ elif condition('String.IsEqual(Container(%s).ListItemAbsolute(%s).DBType,song)' % (container,i)):
+ media_type = 'song'
+ else:
+ media_type = None
+
+ item_dbid = xbmc.getInfoLabel('Container(%s).ListItemAbsolute(%s).DBID' % (dbid,i))
+ url = xbmc.getInfoLabel('Container(%s).ListItemAbsolute(%s).Filenameandpath' % (dbid,i))
+
+ playitem({'type': media_type, 'dbid': item_dbid, 'item': url, 'resume': False})
+
+
+def jumptoshow_by_episode(params):
+ episode_query = json_call('VideoLibrary.GetEpisodeDetails',
+ properties=['tvshowid'],
+ params={'episodeid': int(params.get('dbid'))}
+ )
+ try:
+ tvshow_id = str(episode_query['result']['episodedetails']['tvshowid'])
+ except Exception:
+ log('Could not get the TV show ID')
+ return
+
+ go_to_path('videodb://tvshows/titles/%s/' % tvshow_id)
+
+
+def goto(params):
+ go_to_path(remove_quotes(params.get('path')),params.get('target'))
+
+
+def resetposition(params):
+ containers = params.get('container').split('||')
+ only_inactive_container = get_bool(params.get('only'),'inactive')
+ current_control =xbmc.getInfoLabel('System.CurrentControlID')
+
+ for item in containers:
+
+ try:
+ if current_control == item and only_inactive_container:
+ raise Exception
+
+ current_item = int(xbmc.getInfoLabel('Container(%s).CurrentItem' % item))
+ if current_item > 1:
+ current_item -= 1
+ execute('Control.Move(%s,-%s)' % (item,str(current_item)))
+
+ except Exception:
+ pass
+
+
+def details_by_season(params):
+ season_query = json_call('VideoLibrary.GetSeasonDetails',
+ properties=JSON_MAP['season_properties'],
+ params={'seasonid': int(params.get('dbid'))}
+ )
+
+ try:
+ tvshow_id = str(season_query['result']['seasondetails']['tvshowid'])
+ except Exception:
+ log('Show details by season: Could not get TV show ID')
+ return
+
+ tvshow_query = json_call('VideoLibrary.GetTVShowDetails',
+ properties=JSON_MAP['tvshow_properties'],
+ params={'tvshowid': int(tvshow_id)}
+ )
+
+ try:
+ details = tvshow_query['result']['tvshowdetails']
+ except Exception:
+ log('Show details by season: Could not get TV show details')
+ return
+
+ episode = details['episode']
+ watchedepisodes = details['watchedepisodes']
+ unwatchedepisodes = get_unwatched(episode,watchedepisodes)
+
+ winprop('tvshow.dbid', str(details['tvshowid']))
+ winprop('tvshow.rating', str(round(details['rating'],1)))
+ winprop('tvshow.seasons', str(details['season']))
+ winprop('tvshow.episodes', str(episode))
+ winprop('tvshow.watchedepisodes', str(watchedepisodes))
+ winprop('tvshow.unwatchedepisodes', str(unwatchedepisodes))
+
+
+def txtfile(params):
+ prop = params.get('prop')
+ path = xbmc.translatePath(remove_quotes(params.get('path')))
+
+ if os.path.isfile(path):
+ log('Reading file %s' % path)
+ with open(path) as f:
+ text = f.read()
+
+ if prop:
+ winprop(prop,text)
+ else:
+ DIALOG.textviewer(remove_quotes(params.get('header')), text)
+
+ else:
+ log('Cannot find %s' % path)
+ winprop(prop, clear=True)
+
+
+def fontchange(params):
+ font = params.get('font')
+ fallback_locales = params.get('locales').split('+')
+
+ try:
+ defaultlocale = locale.getdefaultlocale()
+ shortlocale = defaultlocale[0][3:].lower()
+
+ for value in fallback_locales:
+ if value == shortlocale:
+ setkodisetting({'setting': 'lookandfeel.font', 'value': params.get('font')})
+ DIALOG.notification('%s %s' % (value.upper(),ADDON.getLocalizedString(32004)), '%s %s' % (ADDON.getLocalizedString(32005),font))
+ log('Locale %s is not supported by default font. Change to %s.' % (value.upper(),font))
+ break
+
+ except Exception:
+ log('Auto font change: No system locale found')
+
+
+def setinfo(params):
+ dbid = params.get('dbid')
+ dbtype = params.get('type')
+ value = remove_quotes(params.get('value'))
+
+ try:
+ value = int(value)
+ except Exception:
+ value = eval(value)
+ pass
+
+ if dbtype == 'movie':
+ method = 'VideoLibrary.SetMovieDetails'
+ key = 'movieid'
+
+ elif dbtype == 'episode':
+ method = 'VideoLibrary.SetEpisodeDetails'
+ key = 'episodeid'
+
+ elif dbtype == 'tvshow':
+ method = 'VideoLibrary.SetTVShowDetails'
+ key = 'tvshowid'
+
+ json_call(method,
+ params={key: int(dbid), params.get('field'): value}
+ )
+
+
+def split(params):
+ value = remove_quotes(params.get('value'))
+ prop = params.get('prop')
+ separator = remove_quotes(params.get('separator'))
+
+ if value:
+ if separator:
+ value = value.split(separator)
+ else:
+ value = value.splitlines()
+
+ i = 0
+ for item in value:
+ winprop('%s.%s' % (prop,i), item)
+ i += 1
+
+ for item in range(i,30):
+ winprop('%s.%s' % (prop,i), clear=True)
+ i += 1
+
+
+def lookforfile(params):
+ file = remove_quotes(params.get('file'))
+ prop = params.get('prop', 'FileExists')
+
+ if xbmcvfs.exists(file):
+ winprop('%s.bool' % prop, True)
+ log('File exists: %s' % file)
+
+ else:
+ winprop(prop, clear=True)
+ log('File does not exist: %s' % file)
+
+
+def getlocale(params):
+ try:
+ defaultlocale = locale.getdefaultlocale()
+ shortlocale = defaultlocale[0][3:].upper()
+ winprop('SystemLocale', shortlocale)
+
+ except Exception:
+ winprop('SystemLocale', clear=True)
+
+
+def deleteimgcache(params,path=ADDON_DATA_IMG_PATH,delete=False):
+ if not delete:
+ if DIALOG.yesno(ADDON.getLocalizedString(32003), ADDON.getLocalizedString(32019)):
+ delete = True
+
+ if delete:
+ for item in os.listdir(path):
+ full_path = os.path.join(path, item)
+ if os.path.isfile(full_path):
+ os.remove(full_path)
+ else:
+ deleteimgcache(params,full_path,True)
+
+
+def selecttags(params):
+ tags = get_library_tags()
+
+ if tags:
+ sync_library_tags(tags)
+
+ try:
+ whitelist = addon_data('tags_whitelist.' + xbmc.getSkinDir() +'.data')
+ except Exception:
+ whitelist = []
+
+ indexlist = {}
+ selectlist = []
+ preselectlist = []
+
+ index = 0
+ for item in sorted(tags):
+ selectlist.append(item)
+ indexlist[index] = item
+ if item in whitelist:
+ preselectlist.append(index)
+ index += 1
+
+ selectdialog = DIALOG.multiselect(ADDON.getLocalizedString(32026), selectlist, preselect=preselectlist)
+
+ if selectdialog is not None and not selectdialog:
+ set_library_tags(tags, [], clear=True)
+
+ elif selectdialog is not None:
+ whitelist_new = []
+ for item in selectdialog:
+ whitelist_new.append(indexlist[item])
+
+ if whitelist != whitelist_new:
+ set_library_tags(tags, whitelist_new)
+
+ elif params.get('silent') != 'true':
+ DIALOG.ok(ADDON.getLocalizedString(32000), ADDON.getLocalizedString(32024))
+
+
+def whitelisttags(params):
+ sync_library_tags(recreate=True)
\ No newline at end of file
diff --git a/resources/settings.xml b/resources/settings.xml
new file mode 100644
index 0000000..a09d1d3
--- /dev/null
+++ b/resources/settings.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/resources/trailer.jpg b/resources/trailer.jpg
new file mode 100644
index 0000000..cd51b73
Binary files /dev/null and b/resources/trailer.jpg differ
diff --git a/service.py b/service.py
new file mode 100644
index 0000000..cd59156
--- /dev/null
+++ b/service.py
@@ -0,0 +1,18 @@
+# coding: utf-8
+
+#################################################################################################
+
+from resources.lib.helper import *
+from resources.lib.service_monitor import *
+
+#################################################################################################
+
+if __name__ == "__main__":
+ ''' Kodi startup tasks
+ '''
+ sync_library_tags()
+ addon_data_cleanup()
+
+ ''' Start service
+ '''
+ Service()
\ No newline at end of file