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