Merge pull request #894 from croneter/beta-version

Bump master
This commit is contained in:
croneter 2019-06-15 11:01:32 +02:00 committed by GitHub
commit 495e0ef99b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
57 changed files with 2430 additions and 2506 deletions

View file

@ -1,5 +1,5 @@
[![stable version](https://img.shields.io/badge/stable_version-2.8.0-blue.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/stable/repository.plexkodiconnect/repository.plexkodiconnect-1.0.2.zip) [![stable version](https://img.shields.io/badge/stable_version-2.8.3-blue.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/stable/repository.plexkodiconnect/repository.plexkodiconnect-1.0.2.zip)
[![beta version](https://img.shields.io/badge/beta_version-2.8.0-red.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/beta/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.2.zip) [![beta version](https://img.shields.io/badge/beta_version-2.8.3-red.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/beta/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.2.zip)
[![Installation](https://img.shields.io/badge/wiki-installation-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/Installation) [![Installation](https://img.shields.io/badge/wiki-installation-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/Installation)
[![FAQ](https://img.shields.io/badge/wiki-FAQ-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/faq) [![FAQ](https://img.shields.io/badge/wiki-FAQ-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/faq)

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.8.0" provider-name="croneter"> <addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.8.3" provider-name="croneter">
<requires> <requires>
<import addon="xbmc.python" version="2.1.0"/> <import addon="xbmc.python" version="2.1.0"/>
<import addon="script.module.requests" version="2.9.1" /> <import addon="script.module.requests" version="2.9.1" />
@ -77,7 +77,25 @@
<summary lang="uk_UA">Нативна інтеграція Plex в Kodi</summary> <summary lang="uk_UA">Нативна інтеграція Plex в Kodi</summary>
<description lang="uk_UA">Підключає Kodi до серверу Plex. Цей плагін передбачає, що ви керуєте всіма своїми відео за допомогою Plex (і ніяк не Kodi). Ви можете втратити дані, які вже зберігаються у відео та музичних БД Kodi (оскільки цей плагін безпосередньо їх змінює). Використовуйте на свій страх і ризик!</description> <description lang="uk_UA">Підключає Kodi до серверу Plex. Цей плагін передбачає, що ви керуєте всіма своїми відео за допомогою Plex (і ніяк не Kodi). Ви можете втратити дані, які вже зберігаються у відео та музичних БД Kodi (оскільки цей плагін безпосередньо їх змінює). Використовуйте на свій страх і ризик!</description>
<disclaimer lang="uk_UA">Використовуйте на свій ризик</disclaimer> <disclaimer lang="uk_UA">Використовуйте на свій ризик</disclaimer>
<news>version 2.8.0: <news>version 2.8.3:
- Versions 2.8.1-2.8.2 for everyone
version 2.8.2 (beta only):
- Add an additional, faster On Deck node for movies (for tv shows, this is impossible, unfortunately)
- Introduce limits to the number of videos shown in PKC widgets to speed them up
- Fix TypeError for Direct Paths: init() got an unexpected keyword argument item
- Fix In Progress widgets being broken and tv shows showing up as completely watched
- Update translations
version 2.8.1 (beta only):
- Fix playback startup and RuntimeError: Unknown exception thrown from the call "XBMCAddon::xbmcplugin::setResolvedUrl"
- Refactor Plex API
- Fix TV Show clearlogo not displaying during playback
- Fix rare UnicodeDecodeError on library sync
- Add additional info dialog for PKC synching playlists
- Update translations
version 2.8.0:
- Finally fix Kodi crashing on playback startup for add-on paths! - Finally fix Kodi crashing on playback startup for add-on paths!
- All the good stuff from 2.7.15-2.7.18 for everyone - All the good stuff from 2.7.15-2.7.18 for everyone

View file

@ -1,3 +1,21 @@
version 2.8.3:
- Versions 2.8.1-2.8.2 for everyone
version 2.8.2 (beta only):
- Add an additional, faster On Deck node for movies (for tv shows, this is impossible, unfortunately)
- Introduce limits to the number of videos shown in PKC widgets to speed them up
- Fix TypeError for Direct Paths: init() got an unexpected keyword argument item
- Fix In Progress widgets being broken and tv shows showing up as completely watched
- Update translations
version 2.8.1 (beta only):
- Fix playback startup and RuntimeError: Unknown exception thrown from the call "XBMCAddon::xbmcplugin::setResolvedUrl"
- Refactor Plex API
- Fix TV Show clearlogo not displaying during playback
- Fix rare UnicodeDecodeError on library sync
- Add additional info dialog for PKC synching playlists
- Update translations
version 2.8.0: version 2.8.0:
- Finally fix Kodi crashing on playback startup for add-on paths! - Finally fix Kodi crashing on playback startup for add-on paths!
- All the good stuff from 2.7.15-2.7.18 for everyone - All the good stuff from 2.7.15-2.7.18 for everyone

View file

@ -1048,11 +1048,6 @@ msgctxt "#39057"
msgid "Customize Paths" msgid "Customize Paths"
msgstr "Přizpůsobit cesty" msgstr "Přizpůsobit cesty"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr "Rozšířit seriály na obrazovce \"Aktuální\" na všechny seriály"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39059" msgctxt "#39059"
msgid "Recently Added: Append show title to episode" msgid "Recently Added: Append show title to episode"
@ -1092,12 +1087,8 @@ msgstr "Vynutit obnovení vzhledu Kodi po skončení přehrávání"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39066" msgctxt "#39066"
msgid "" msgid "Recently Added: Also show already watched movies"
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr "" msgstr ""
"Naposledy přidané: Zobrazovat také už shlédnuté filmy (Obnovte playlisty "
"Plexu!)"
# PKC Settings - Connection # PKC Settings - Connection
msgctxt "#39067" msgctxt "#39067"
@ -1151,8 +1142,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39077" msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")" msgid "Maximum number of videos to show in widgets"
msgstr "Počet položek pro zobrazení ve widgetech (např. \"Aktuální\")" msgstr ""
# PKC Settings - Plex # PKC Settings - Plex
msgctxt "#39078" msgctxt "#39078"
@ -1199,6 +1190,11 @@ msgctxt "#39084"
msgid "Enter PMS port" msgid "Enter PMS port"
msgstr "Zadejte port PMS" msgstr "Zadejte port PMS"
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
msgctxt "#39200" msgctxt "#39200"
msgid "Log-out Plex Home User " msgid "Log-out Plex Home User "
msgstr "Odhlásit uživatele Plex Home " msgstr "Odhlásit uživatele Plex Home "
@ -1443,6 +1439,10 @@ msgctxt "#39501"
msgid "Collections" msgid "Collections"
msgstr "Kolekce" msgstr "Kolekce"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr ""
msgctxt "#39600" msgctxt "#39600"
msgid "" msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the " "Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1559,8 +1559,8 @@ msgstr "Synchronizuji"
# Shown during sync process # Shown during sync process
msgctxt "#39715" msgctxt "#39715"
msgid "items" msgid "Synching playlists"
msgstr "položek" msgstr ""
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is # Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}! # screwed up; formated the wrong way). Do NOT replace {0} and {1}!

View file

@ -1050,11 +1050,6 @@ msgctxt "#39057"
msgid "Customize Paths" msgid "Customize Paths"
msgstr "Tilpasse stier" msgstr "Tilpasse stier"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr "Udvide Plex TV serie \"Senest Tilføjet\" Til vis alle shows"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39059" msgctxt "#39059"
msgid "Recently Added: Append show title to episode" msgid "Recently Added: Append show title to episode"
@ -1095,11 +1090,8 @@ msgstr "Gennemtving genindlæsning af Kodi skin når afspilning stopper"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39066" msgctxt "#39066"
msgid "" msgid "Recently Added: Also show already watched movies"
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr "" msgstr ""
"Senest tilføjet: Vis allerede set film (Opdater Plex spilleliste/noder!)"
# PKC Settings - Connection # PKC Settings - Connection
msgctxt "#39067" msgctxt "#39067"
@ -1152,8 +1144,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39077" msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")" msgid "Maximum number of videos to show in widgets"
msgstr "Antallet af PMS elementer at vise i widgets (fx \"Igangværende\")" msgstr ""
# PKC Settings - Plex # PKC Settings - Plex
msgctxt "#39078" msgctxt "#39078"
@ -1201,6 +1193,11 @@ msgctxt "#39084"
msgid "Enter PMS port" msgid "Enter PMS port"
msgstr "" msgstr ""
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
msgctxt "#39200" msgctxt "#39200"
msgid "Log-out Plex Home User " msgid "Log-out Plex Home User "
msgstr "Log ud Plex hjemme bruger " msgstr "Log ud Plex hjemme bruger "
@ -1447,6 +1444,10 @@ msgctxt "#39501"
msgid "Collections" msgid "Collections"
msgstr "Samlinger" msgstr "Samlinger"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr ""
msgctxt "#39600" msgctxt "#39600"
msgid "" msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the " "Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1563,8 +1564,8 @@ msgstr "Synkronisér"
# Shown during sync process # Shown during sync process
msgctxt "#39715" msgctxt "#39715"
msgid "items" msgid "Synching playlists"
msgstr "elementer" msgstr ""
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is # Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}! # screwed up; formated the wrong way). Do NOT replace {0} and {1}!

View file

@ -1064,11 +1064,6 @@ msgctxt "#39057"
msgid "Customize Paths" msgid "Customize Paths"
msgstr "Pfade ändern" msgstr "Pfade ändern"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr "Standard Plex Ansicht \"Aktuell\" auf alle TV Shows erweitern"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39059" msgctxt "#39059"
msgid "Recently Added: Append show title to episode" msgid "Recently Added: Append show title to episode"
@ -1108,12 +1103,8 @@ msgstr "Kodi Skin nach Playback-Stop neu laden"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39066" msgctxt "#39066"
msgid "" msgid "Recently Added: Also show already watched movies"
"Recently Added: Also show already watched movies (Refresh Plex " msgstr "\"Zuletzt hinzugefügt\": gesehene Filme anzeigen"
"playlist/nodes!)"
msgstr ""
"\"Zuletzt hinzugefügt\": gesehene Filme anzeigen (Plex Playlisten und Nodes "
"zurücksetzen!)"
# PKC Settings - Connection # PKC Settings - Connection
msgctxt "#39067" msgctxt "#39067"
@ -1166,8 +1157,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39077" msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")" msgid "Maximum number of videos to show in widgets"
msgstr "Anzahl anzuzeigender PMS Einträge in Widgets (z.B. \"Aktuell\")" msgstr "Maximale Anzahl anzuzeigende Videos in Widgets"
# PKC Settings - Plex # PKC Settings - Plex
msgctxt "#39078" msgctxt "#39078"
@ -1215,6 +1206,11 @@ msgctxt "#39084"
msgid "Enter PMS port" msgid "Enter PMS port"
msgstr "PMS Port eingeben" msgstr "PMS Port eingeben"
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr "Kodi neu laden um Einstellungen unten zu übernehmen"
msgctxt "#39200" msgctxt "#39200"
msgid "Log-out Plex Home User " msgid "Log-out Plex Home User "
msgstr "Plex Home Benutzer abmelden: " msgstr "Plex Home Benutzer abmelden: "
@ -1470,6 +1466,10 @@ msgctxt "#39501"
msgid "Collections" msgid "Collections"
msgstr "Kollektionen" msgstr "Kollektionen"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr "PKC Aktuell (schneller)"
msgctxt "#39600" msgctxt "#39600"
msgid "" msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the " "Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1588,8 +1588,8 @@ msgstr "Sync"
# Shown during sync process # Shown during sync process
msgctxt "#39715" msgctxt "#39715"
msgid "items" msgid "Synching playlists"
msgstr "Einträge" msgstr "Synchronisiere Wiedergabelisten"
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is # Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}! # screwed up; formated the wrong way). Do NOT replace {0} and {1}!

View file

@ -1058,7 +1058,7 @@ msgstr ""
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39077" msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")" msgid "Maximum number of videos to show in widgets"
msgstr "" msgstr ""
# PKC Settings - Plex # PKC Settings - Plex
@ -1308,6 +1308,10 @@ msgctxt "#39501"
msgid "Collections" msgid "Collections"
msgstr "" msgstr ""
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr ""
msgctxt "#39600" msgctxt "#39600"
msgid "Are you sure you want to reset your local Kodi database? A re-sync of the Plex data will take time afterwards." msgid "Are you sure you want to reset your local Kodi database? A re-sync of the Plex data will take time afterwards."
msgstr "" msgstr ""
@ -1398,7 +1402,7 @@ msgstr ""
# Shown during sync process # Shown during sync process
msgctxt "#39715" msgctxt "#39715"
msgid "items" msgid "Synching playlists"
msgstr "" msgstr ""
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is screwed up; formated the wrong way). Do NOT replace {0} and {1}! # Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is screwed up; formated the wrong way). Do NOT replace {0} and {1}!

View file

@ -1064,11 +1064,6 @@ msgctxt "#39057"
msgid "Customize Paths" msgid "Customize Paths"
msgstr "Personalizar rutas" msgstr "Personalizar rutas"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr "Extender vista de series \"On Deck\" de Plex a todoas las series"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39059" msgctxt "#39059"
msgid "Recently Added: Append show title to episode" msgid "Recently Added: Append show title to episode"
@ -1109,12 +1104,8 @@ msgstr "Refrescar el skin de Kodi al detener la reproducción"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39066" msgctxt "#39066"
msgid "" msgid "Recently Added: Also show already watched movies"
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr "" msgstr ""
"Añadidos Recientemente: También mostrar películas ya vistas (¡Actualize las "
"listas de reproducción/nodos Plex!)"
# PKC Settings - Connection # PKC Settings - Connection
msgctxt "#39067" msgctxt "#39067"
@ -1168,9 +1159,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39077" msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")" msgid "Maximum number of videos to show in widgets"
msgstr "" msgstr ""
"Número de elementos del PMS a mostrar en widgets (por ejemplo \"On Deck\")"
# PKC Settings - Plex # PKC Settings - Plex
msgctxt "#39078" msgctxt "#39078"
@ -1219,6 +1209,11 @@ msgctxt "#39084"
msgid "Enter PMS port" msgid "Enter PMS port"
msgstr "" msgstr ""
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
msgctxt "#39200" msgctxt "#39200"
msgid "Log-out Plex Home User " msgid "Log-out Plex Home User "
msgstr "Terminar sesión del usuario de Plex Home " msgstr "Terminar sesión del usuario de Plex Home "
@ -1467,6 +1462,10 @@ msgctxt "#39501"
msgid "Collections" msgid "Collections"
msgstr "Sagas" msgstr "Sagas"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr ""
msgctxt "#39600" msgctxt "#39600"
msgid "" msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the " "Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1583,8 +1582,8 @@ msgstr "Sincronizar"
# Shown during sync process # Shown during sync process
msgctxt "#39715" msgctxt "#39715"
msgid "items" msgid "Synching playlists"
msgstr "objetos" msgstr ""
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is # Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}! # screwed up; formated the wrong way). Do NOT replace {0} and {1}!

View file

@ -1065,11 +1065,6 @@ msgctxt "#39057"
msgid "Customize Paths" msgid "Customize Paths"
msgstr "Personalizar rutas" msgstr "Personalizar rutas"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr "Extender vista de series \"On Deck\" de Plex a todoas las series"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39059" msgctxt "#39059"
msgid "Recently Added: Append show title to episode" msgid "Recently Added: Append show title to episode"
@ -1110,12 +1105,8 @@ msgstr "Refrescar el skin de Kodi al detener la reproducción"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39066" msgctxt "#39066"
msgid "" msgid "Recently Added: Also show already watched movies"
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr "" msgstr ""
"Añadidos Recientemente: También mostrar películas ya vistas (¡Actualize las "
"listas de reproducción/nodos Plex!)"
# PKC Settings - Connection # PKC Settings - Connection
msgctxt "#39067" msgctxt "#39067"
@ -1169,9 +1160,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39077" msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")" msgid "Maximum number of videos to show in widgets"
msgstr "" msgstr ""
"Número de elementos del PMS a mostrar en widgets (por ejemplo \"On Deck\")"
# PKC Settings - Plex # PKC Settings - Plex
msgctxt "#39078" msgctxt "#39078"
@ -1220,6 +1210,11 @@ msgctxt "#39084"
msgid "Enter PMS port" msgid "Enter PMS port"
msgstr "" msgstr ""
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
msgctxt "#39200" msgctxt "#39200"
msgid "Log-out Plex Home User " msgid "Log-out Plex Home User "
msgstr "Terminar sesión del usuario de Plex Home " msgstr "Terminar sesión del usuario de Plex Home "
@ -1468,6 +1463,10 @@ msgctxt "#39501"
msgid "Collections" msgid "Collections"
msgstr "Sagas" msgstr "Sagas"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr ""
msgctxt "#39600" msgctxt "#39600"
msgid "" msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the " "Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1584,8 +1583,8 @@ msgstr "Sincronizar"
# Shown during sync process # Shown during sync process
msgctxt "#39715" msgctxt "#39715"
msgid "items" msgid "Synching playlists"
msgstr "objetos" msgstr ""
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is # Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}! # screwed up; formated the wrong way). Do NOT replace {0} and {1}!

View file

@ -1064,11 +1064,6 @@ msgctxt "#39057"
msgid "Customize Paths" msgid "Customize Paths"
msgstr "Personalizar rutas" msgstr "Personalizar rutas"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr "Extender vista de series \"On Deck\" de Plex a todoas las series"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39059" msgctxt "#39059"
msgid "Recently Added: Append show title to episode" msgid "Recently Added: Append show title to episode"
@ -1109,12 +1104,8 @@ msgstr "Refrescar el skin de Kodi al detener la reproducción"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39066" msgctxt "#39066"
msgid "" msgid "Recently Added: Also show already watched movies"
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr "" msgstr ""
"Añadidos Recientemente: También mostrar películas ya vistas (¡Actualize las "
"listas de reproducción/nodos Plex!)"
# PKC Settings - Connection # PKC Settings - Connection
msgctxt "#39067" msgctxt "#39067"
@ -1168,9 +1159,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39077" msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")" msgid "Maximum number of videos to show in widgets"
msgstr "" msgstr ""
"Número de elementos del PMS a mostrar en widgets (por ejemplo \"On Deck\")"
# PKC Settings - Plex # PKC Settings - Plex
msgctxt "#39078" msgctxt "#39078"
@ -1219,6 +1209,11 @@ msgctxt "#39084"
msgid "Enter PMS port" msgid "Enter PMS port"
msgstr "" msgstr ""
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
msgctxt "#39200" msgctxt "#39200"
msgid "Log-out Plex Home User " msgid "Log-out Plex Home User "
msgstr "Terminar sesión del usuario de Plex Home " msgstr "Terminar sesión del usuario de Plex Home "
@ -1467,6 +1462,10 @@ msgctxt "#39501"
msgid "Collections" msgid "Collections"
msgstr "Sagas" msgstr "Sagas"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr ""
msgctxt "#39600" msgctxt "#39600"
msgid "" msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the " "Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1583,8 +1582,8 @@ msgstr "Sincronizar"
# Shown during sync process # Shown during sync process
msgctxt "#39715" msgctxt "#39715"
msgid "items" msgid "Synching playlists"
msgstr "objetos" msgstr ""
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is # Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}! # screwed up; formated the wrong way). Do NOT replace {0} and {1}!

View file

@ -1073,11 +1073,6 @@ msgctxt "#39057"
msgid "Customize Paths" msgid "Customize Paths"
msgstr "Personnalisation des chemins" msgstr "Personnalisation des chemins"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr "Extend Plex TV Series \"On Deck\" view to all shows"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39059" msgctxt "#39059"
msgid "Recently Added: Append show title to episode" msgid "Recently Added: Append show title to episode"
@ -1119,12 +1114,8 @@ msgstr "Forcer le rafraîchissement du skin de Kodi va arrêter le playback."
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39066" msgctxt "#39066"
msgid "" msgid "Recently Added: Also show already watched movies"
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr "" msgstr ""
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
# PKC Settings - Connection # PKC Settings - Connection
msgctxt "#39067" msgctxt "#39067"
@ -1178,8 +1169,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39077" msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")" msgid "Maximum number of videos to show in widgets"
msgstr "Number of PMS items to show in widgets (e.g. \"On Deck\")" msgstr ""
# PKC Settings - Plex # PKC Settings - Plex
msgctxt "#39078" msgctxt "#39078"
@ -1226,6 +1217,11 @@ msgctxt "#39084"
msgid "Enter PMS port" msgid "Enter PMS port"
msgstr "Entrez le port de PMS" msgstr "Entrez le port de PMS"
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
msgctxt "#39200" msgctxt "#39200"
msgid "Log-out Plex Home User " msgid "Log-out Plex Home User "
msgstr "Log-out Plex Home User " msgstr "Log-out Plex Home User "
@ -1477,6 +1473,10 @@ msgctxt "#39501"
msgid "Collections" msgid "Collections"
msgstr "Sagas" msgstr "Sagas"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr ""
msgctxt "#39600" msgctxt "#39600"
msgid "" msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the " "Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1595,8 +1595,8 @@ msgstr "Synchro"
# Shown during sync process # Shown during sync process
msgctxt "#39715" msgctxt "#39715"
msgid "items" msgid "Synching playlists"
msgstr "éléments" msgstr ""
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is # Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}! # screwed up; formated the wrong way). Do NOT replace {0} and {1}!

View file

@ -1076,11 +1076,6 @@ msgctxt "#39057"
msgid "Customize Paths" msgid "Customize Paths"
msgstr "Personnalisation des chemins" msgstr "Personnalisation des chemins"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr "Extend Plex TV Series \"On Deck\" view to all shows"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39059" msgctxt "#39059"
msgid "Recently Added: Append show title to episode" msgid "Recently Added: Append show title to episode"
@ -1122,12 +1117,8 @@ msgstr "Forcer le rafraîchissement du skin de Kodi va arrêter le playback."
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39066" msgctxt "#39066"
msgid "" msgid "Recently Added: Also show already watched movies"
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr "" msgstr ""
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
# PKC Settings - Connection # PKC Settings - Connection
msgctxt "#39067" msgctxt "#39067"
@ -1181,8 +1172,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39077" msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")" msgid "Maximum number of videos to show in widgets"
msgstr "Number of PMS items to show in widgets (e.g. \"On Deck\")" msgstr ""
# PKC Settings - Plex # PKC Settings - Plex
msgctxt "#39078" msgctxt "#39078"
@ -1229,6 +1220,11 @@ msgctxt "#39084"
msgid "Enter PMS port" msgid "Enter PMS port"
msgstr "Entrez le port de PMS" msgstr "Entrez le port de PMS"
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
msgctxt "#39200" msgctxt "#39200"
msgid "Log-out Plex Home User " msgid "Log-out Plex Home User "
msgstr "Log-out Plex Home User " msgstr "Log-out Plex Home User "
@ -1480,6 +1476,10 @@ msgctxt "#39501"
msgid "Collections" msgid "Collections"
msgstr "Sagas" msgstr "Sagas"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr ""
msgctxt "#39600" msgctxt "#39600"
msgid "" msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the " "Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1598,8 +1598,8 @@ msgstr "Synchro"
# Shown during sync process # Shown during sync process
msgctxt "#39715" msgctxt "#39715"
msgid "items" msgid "Synching playlists"
msgstr "éléments" msgstr ""
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is # Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}! # screwed up; formated the wrong way). Do NOT replace {0} and {1}!

View file

@ -1061,13 +1061,6 @@ msgctxt "#39057"
msgid "Customize Paths" msgid "Customize Paths"
msgstr "Útvonalak testreszabása" msgstr "Útvonalak testreszabása"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr ""
"A Plex TV sorozatok \"A fedélzeten\" nézetének kiterjesztése az összes "
"sorozatra"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39059" msgctxt "#39059"
msgid "Recently Added: Append show title to episode" msgid "Recently Added: Append show title to episode"
@ -1107,12 +1100,8 @@ msgstr "Kodi felület kényszerített frissítése a lejátszás megállításak
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39066" msgctxt "#39066"
msgid "" msgid "Recently Added: Also show already watched movies"
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr "" msgstr ""
"Mostanában hozzáadott: már látott filmek mutatása (frissítse a Plex "
"lejátszási listát/csomópontokat!)"
# PKC Settings - Connection # PKC Settings - Connection
msgctxt "#39067" msgctxt "#39067"
@ -1167,8 +1156,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39077" msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")" msgid "Maximum number of videos to show in widgets"
msgstr "Megjelenítendő elemek száma a widgetekben (pl. \"A fedélzeten\")" msgstr ""
# PKC Settings - Plex # PKC Settings - Plex
msgctxt "#39078" msgctxt "#39078"
@ -1217,6 +1206,11 @@ msgctxt "#39084"
msgid "Enter PMS port" msgid "Enter PMS port"
msgstr "" msgstr ""
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
msgctxt "#39200" msgctxt "#39200"
msgid "Log-out Plex Home User " msgid "Log-out Plex Home User "
msgstr "Kijelentkezés az otthoni Plex felhasználó fiókból: " msgstr "Kijelentkezés az otthoni Plex felhasználó fiókból: "
@ -1465,6 +1459,10 @@ msgctxt "#39501"
msgid "Collections" msgid "Collections"
msgstr "Gyűjtemények" msgstr "Gyűjtemények"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr ""
msgctxt "#39600" msgctxt "#39600"
msgid "" msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the " "Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1582,8 +1580,8 @@ msgstr "Szinkronizáció"
# Shown during sync process # Shown during sync process
msgctxt "#39715" msgctxt "#39715"
msgid "items" msgid "Synching playlists"
msgstr "elemek" msgstr ""
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is # Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}! # screwed up; formated the wrong way). Do NOT replace {0} and {1}!

View file

@ -1068,11 +1068,6 @@ msgctxt "#39057"
msgid "Customize Paths" msgid "Customize Paths"
msgstr "Personalizza i percorsi" msgstr "Personalizza i percorsi"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr "Estendi la vista \"On Deck\" delle Serie TV a tutte le serie"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39059" msgctxt "#39059"
msgid "Recently Added: Append show title to episode" msgid "Recently Added: Append show title to episode"
@ -1114,12 +1109,8 @@ msgstr "Forza aggiornamento della skin Kodi dopo lo stop di un contenuto"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39066" msgctxt "#39066"
msgid "" msgid "Recently Added: Also show already watched movies"
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr "" msgstr ""
"Aggiunti di recente: mostra anche i film già visti (Aggiornare playlist/nodi"
" Plex!)"
# PKC Settings - Connection # PKC Settings - Connection
msgctxt "#39067" msgctxt "#39067"
@ -1172,8 +1163,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39077" msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")" msgid "Maximum number of videos to show in widgets"
msgstr "Numero di elementi PMS da mostrare nei widget (es. \"On Deck\")" msgstr ""
# PKC Settings - Plex # PKC Settings - Plex
msgctxt "#39078" msgctxt "#39078"
@ -1221,6 +1212,11 @@ msgctxt "#39084"
msgid "Enter PMS port" msgid "Enter PMS port"
msgstr "" msgstr ""
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
msgctxt "#39200" msgctxt "#39200"
msgid "Log-out Plex Home User " msgid "Log-out Plex Home User "
msgstr "Logout utente Plex " msgstr "Logout utente Plex "
@ -1473,6 +1469,10 @@ msgctxt "#39501"
msgid "Collections" msgid "Collections"
msgstr "Collezioni" msgstr "Collezioni"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr ""
msgctxt "#39600" msgctxt "#39600"
msgid "" msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the " "Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1591,8 +1591,8 @@ msgstr "Sync"
# Shown during sync process # Shown during sync process
msgctxt "#39715" msgctxt "#39715"
msgid "items" msgid "Synching playlists"
msgstr "contenuti" msgstr ""
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is # Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}! # screwed up; formated the wrong way). Do NOT replace {0} and {1}!

View file

@ -95,7 +95,7 @@ msgstr "Savienojums"
# Pop-up notification if user tried to manually initiate fanart download # Pop-up notification if user tried to manually initiate fanart download
msgctxt "#30015" msgctxt "#30015"
msgid "Fanart download already running" msgid "Fanart download already running"
msgstr "" msgstr "Fanart lejupielāde jau notiek"
msgctxt "#30016" msgctxt "#30016"
msgid "Device Name" msgid "Device Name"
@ -157,7 +157,7 @@ msgstr "Prefikss Kodi spēļsaraksta nosaukumā, lai iniciētu sinhronizēšanu"
# PKC settings artwork options: status info # PKC settings artwork options: status info
msgctxt "#30028" msgctxt "#30028"
msgid "PKC-only image caching completed" msgid "PKC-only image caching completed"
msgstr "" msgstr "Tikai-PKC attēlu kešošana pabeigta"
msgctxt "#30030" msgctxt "#30030"
msgid "Port Number" msgid "Port Number"
@ -1038,11 +1038,6 @@ msgctxt "#39057"
msgid "Customize Paths" msgid "Customize Paths"
msgstr "Pielāgot Ceļus" msgstr "Pielāgot Ceļus"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr "Paplašināt Plext TV Seriālu \"Izcelts\" skatījumu uz visiem seriāliem"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39059" msgctxt "#39059"
msgid "Recently Added: Append show title to episode" msgid "Recently Added: Append show title to episode"
@ -1082,12 +1077,8 @@ msgstr "Uzspiest atjaunošanu Kodi ādiņai apturot atskaņošanu"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39066" msgctxt "#39066"
msgid "" msgid "Recently Added: Also show already watched movies"
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr "" msgstr ""
"Nesen Pievienots: Rādīt arī jau noskatītas filmas (Atjaunināt Plex "
"spēļsarakstu/mezglus!)"
# PKC Settings - Connection # PKC Settings - Connection
msgctxt "#39067" msgctxt "#39067"
@ -1138,8 +1129,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39077" msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")" msgid "Maximum number of videos to show in widgets"
msgstr "PMS vienumu skaits, kurus rādīt logrīkos (piem. \"Izcelts\")" msgstr ""
# PKC Settings - Plex # PKC Settings - Plex
msgctxt "#39078" msgctxt "#39078"
@ -1182,6 +1173,11 @@ msgctxt "#39084"
msgid "Enter PMS port" msgid "Enter PMS port"
msgstr "" msgstr ""
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
msgctxt "#39200" msgctxt "#39200"
msgid "Log-out Plex Home User " msgid "Log-out Plex Home User "
msgstr "" msgstr ""
@ -1409,6 +1405,10 @@ msgctxt "#39501"
msgid "Collections" msgid "Collections"
msgstr "" msgstr ""
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr ""
msgctxt "#39600" msgctxt "#39600"
msgid "" msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the " "Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1515,7 +1515,7 @@ msgstr ""
# Shown during sync process # Shown during sync process
msgctxt "#39715" msgctxt "#39715"
msgid "items" msgid "Synching playlists"
msgstr "" msgstr ""
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is # Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is

View file

@ -1055,11 +1055,6 @@ msgctxt "#39057"
msgid "Customize Paths" msgid "Customize Paths"
msgstr "Aanpassen van paden" msgstr "Aanpassen van paden"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr "On Deck van TV-series laat alle series zien"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39059" msgctxt "#39059"
msgid "Recently Added: Append show title to episode" msgid "Recently Added: Append show title to episode"
@ -1099,12 +1094,8 @@ msgstr "Forceer Kodi thema reset bij afspelen stoppen"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39066" msgctxt "#39066"
msgid "" msgid "Recently Added: Also show already watched movies"
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr "" msgstr ""
"Onlangs Toegevoegd: Toon ook al bekeken films (Ververs Plex "
"afspeellijst/nodes!)"
# PKC Settings - Connection # PKC Settings - Connection
msgctxt "#39067" msgctxt "#39067"
@ -1158,8 +1149,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39077" msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")" msgid "Maximum number of videos to show in widgets"
msgstr "Aantal PMS items in widgets laten zien (bijv. \"On Deck\")" msgstr ""
# PKC Settings - Plex # PKC Settings - Plex
msgctxt "#39078" msgctxt "#39078"
@ -1207,6 +1198,11 @@ msgctxt "#39084"
msgid "Enter PMS port" msgid "Enter PMS port"
msgstr "Kies PMS poort" msgstr "Kies PMS poort"
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
msgctxt "#39200" msgctxt "#39200"
msgid "Log-out Plex Home User " msgid "Log-out Plex Home User "
msgstr "Log-out Plex Home gebruiker " msgstr "Log-out Plex Home gebruiker "
@ -1450,6 +1446,10 @@ msgctxt "#39501"
msgid "Collections" msgid "Collections"
msgstr "Verzamelingen" msgstr "Verzamelingen"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr ""
msgctxt "#39600" msgctxt "#39600"
msgid "" msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the " "Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1567,8 +1567,8 @@ msgstr "Sync"
# Shown during sync process # Shown during sync process
msgctxt "#39715" msgctxt "#39715"
msgid "items" msgid "Synching playlists"
msgstr "items" msgstr ""
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is # Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}! # screwed up; formated the wrong way). Do NOT replace {0} and {1}!

View file

@ -1049,11 +1049,6 @@ msgctxt "#39057"
msgid "Customize Paths" msgid "Customize Paths"
msgstr "Egendefinerte stier" msgstr "Egendefinerte stier"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr "Utvid visning av Plex TV serier \"On Deck\" til alle"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39059" msgctxt "#39059"
msgid "Recently Added: Append show title to episode" msgid "Recently Added: Append show title to episode"
@ -1093,12 +1088,8 @@ msgstr "Oppdater Kodi skin ved stopping av avspilling"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39066" msgctxt "#39066"
msgid "" msgid "Recently Added: Also show already watched movies"
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr "" msgstr ""
"Nylig lagt til: Vis også allerede sette filmer (Oppdater Plex "
"spilliste/noder!)"
# PKC Settings - Connection # PKC Settings - Connection
msgctxt "#39067" msgctxt "#39067"
@ -1151,8 +1142,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39077" msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")" msgid "Maximum number of videos to show in widgets"
msgstr "Antall PMS elementer som skal vises i widgets (eksempel \"On Deck\")" msgstr ""
# PKC Settings - Plex # PKC Settings - Plex
msgctxt "#39078" msgctxt "#39078"
@ -1200,6 +1191,11 @@ msgctxt "#39084"
msgid "Enter PMS port" msgid "Enter PMS port"
msgstr "Legg til PMS port" msgstr "Legg til PMS port"
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
msgctxt "#39200" msgctxt "#39200"
msgid "Log-out Plex Home User " msgid "Log-out Plex Home User "
msgstr "Logg av Plex Home User" msgstr "Logg av Plex Home User"
@ -1447,6 +1443,10 @@ msgctxt "#39501"
msgid "Collections" msgid "Collections"
msgstr "Samlinger" msgstr "Samlinger"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr ""
msgctxt "#39600" msgctxt "#39600"
msgid "" msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the " "Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1563,8 +1563,8 @@ msgstr "Synk"
# Shown during sync process # Shown during sync process
msgctxt "#39715" msgctxt "#39715"
msgid "items" msgid "Synching playlists"
msgstr "Elementer" msgstr ""
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is # Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}! # screwed up; formated the wrong way). Do NOT replace {0} and {1}!

View file

@ -1039,12 +1039,6 @@ msgctxt "#39057"
msgid "Customize Paths" msgid "Customize Paths"
msgstr "Personalizar Caminhos" msgstr "Personalizar Caminhos"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr ""
"Extender Plex Séries de TV na vista \"Na Plataforma\" a todos os programas"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39059" msgctxt "#39059"
msgid "Recently Added: Append show title to episode" msgid "Recently Added: Append show title to episode"
@ -1085,12 +1079,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39066" msgctxt "#39066"
msgid "" msgid "Recently Added: Also show already watched movies"
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr "" msgstr ""
"Adicionado Recentemente: Mostrar também filmes visualizados (Actualize "
"listas de reprodução/nós do Plex!)"
# PKC Settings - Connection # PKC Settings - Connection
msgctxt "#39067" msgctxt "#39067"
@ -1143,9 +1133,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39077" msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")" msgid "Maximum number of videos to show in widgets"
msgstr "" msgstr ""
"Numero de items do PMS a aparecer nas aplicacções (e.x. \"Na plataforma\")"
# PKC Settings - Plex # PKC Settings - Plex
msgctxt "#39078" msgctxt "#39078"
@ -1190,6 +1179,11 @@ msgctxt "#39084"
msgid "Enter PMS port" msgid "Enter PMS port"
msgstr "" msgstr ""
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
msgctxt "#39200" msgctxt "#39200"
msgid "Log-out Plex Home User " msgid "Log-out Plex Home User "
msgstr "Sair da sessão do Utilizador Caseiro Plex" msgstr "Sair da sessão do Utilizador Caseiro Plex"
@ -1435,6 +1429,10 @@ msgctxt "#39501"
msgid "Collections" msgid "Collections"
msgstr "Coleções" msgstr "Coleções"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr ""
msgctxt "#39600" msgctxt "#39600"
msgid "" msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the " "Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1553,8 +1551,8 @@ msgstr "Sincronizar "
# Shown during sync process # Shown during sync process
msgctxt "#39715" msgctxt "#39715"
msgid "items" msgid "Synching playlists"
msgstr "items" msgstr ""
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is # Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}! # screwed up; formated the wrong way). Do NOT replace {0} and {1}!

View file

@ -1047,12 +1047,6 @@ msgctxt "#39057"
msgid "Customize Paths" msgid "Customize Paths"
msgstr "Personalizar Caminhos" msgstr "Personalizar Caminhos"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr ""
"Extender Plex Séries de TV na vista \"Na Plataforma\" a todos os programas"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39059" msgctxt "#39059"
msgid "Recently Added: Append show title to episode" msgid "Recently Added: Append show title to episode"
@ -1093,12 +1087,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39066" msgctxt "#39066"
msgid "" msgid "Recently Added: Also show already watched movies"
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr "" msgstr ""
"Adicionado Recentemente: Mostrar também filmes visualizados (Actualize "
"listas de reprodução/nós do Plex!)"
# PKC Settings - Connection # PKC Settings - Connection
msgctxt "#39067" msgctxt "#39067"
@ -1151,9 +1141,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39077" msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")" msgid "Maximum number of videos to show in widgets"
msgstr "" msgstr ""
"Numero de items do PMS a aparecer nas aplicacções (e.x. \"Na plataforma\")"
# PKC Settings - Plex # PKC Settings - Plex
msgctxt "#39078" msgctxt "#39078"
@ -1198,6 +1187,11 @@ msgctxt "#39084"
msgid "Enter PMS port" msgid "Enter PMS port"
msgstr "" msgstr ""
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
msgctxt "#39200" msgctxt "#39200"
msgid "Log-out Plex Home User " msgid "Log-out Plex Home User "
msgstr "Sair da sessão do Utilizador Caseiro Plex" msgstr "Sair da sessão do Utilizador Caseiro Plex"
@ -1443,6 +1437,10 @@ msgctxt "#39501"
msgid "Collections" msgid "Collections"
msgstr "Coleções" msgstr "Coleções"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr ""
msgctxt "#39600" msgctxt "#39600"
msgid "" msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the " "Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1561,8 +1559,8 @@ msgstr "Sincronizar "
# Shown during sync process # Shown during sync process
msgctxt "#39715" msgctxt "#39715"
msgid "items" msgid "Synching playlists"
msgstr "items" msgstr ""
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is # Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}! # screwed up; formated the wrong way). Do NOT replace {0} and {1}!

View file

@ -1058,11 +1058,6 @@ msgctxt "#39057"
msgid "Customize Paths" msgid "Customize Paths"
msgstr "Изменить пути" msgstr "Изменить пути"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr "В \"Текущем\" показывать все сериалы"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39059" msgctxt "#39059"
msgid "Recently Added: Append show title to episode" msgid "Recently Added: Append show title to episode"
@ -1102,12 +1097,8 @@ msgstr "Принудительное обновление обложки Kodi п
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39066" msgctxt "#39066"
msgid "" msgid "Recently Added: Also show already watched movies"
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr "" msgstr ""
"Недавно добавлено: также показывать просмотренные фильмы (обновите "
"плейлисты/списки Plex)"
# PKC Settings - Connection # PKC Settings - Connection
msgctxt "#39067" msgctxt "#39067"
@ -1161,8 +1152,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39077" msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")" msgid "Maximum number of videos to show in widgets"
msgstr "Количество элементов, отображаемых в виджетах (например \"Текущие\")" msgstr ""
# PKC Settings - Plex # PKC Settings - Plex
msgctxt "#39078" msgctxt "#39078"
@ -1209,6 +1200,11 @@ msgctxt "#39084"
msgid "Enter PMS port" msgid "Enter PMS port"
msgstr "" msgstr ""
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
msgctxt "#39200" msgctxt "#39200"
msgid "Log-out Plex Home User " msgid "Log-out Plex Home User "
msgstr "Выйти из Plex" msgstr "Выйти из Plex"
@ -1455,6 +1451,10 @@ msgctxt "#39501"
msgid "Collections" msgid "Collections"
msgstr "Коллекции" msgstr "Коллекции"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr ""
msgctxt "#39600" msgctxt "#39600"
msgid "" msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the " "Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1571,8 +1571,8 @@ msgstr "Синхронизация"
# Shown during sync process # Shown during sync process
msgctxt "#39715" msgctxt "#39715"
msgid "items" msgid "Synching playlists"
msgstr "элементов" msgstr ""
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is # Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}! # screwed up; formated the wrong way). Do NOT replace {0} and {1}!

View file

@ -1034,11 +1034,6 @@ msgctxt "#39057"
msgid "Customize Paths" msgid "Customize Paths"
msgstr "" msgstr ""
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr ""
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39059" msgctxt "#39059"
msgid "Recently Added: Append show title to episode" msgid "Recently Added: Append show title to episode"
@ -1078,9 +1073,7 @@ msgstr ""
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39066" msgctxt "#39066"
msgid "" msgid "Recently Added: Also show already watched movies"
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr "" msgstr ""
# PKC Settings - Connection # PKC Settings - Connection
@ -1132,7 +1125,7 @@ msgstr ""
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39077" msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")" msgid "Maximum number of videos to show in widgets"
msgstr "" msgstr ""
# PKC Settings - Plex # PKC Settings - Plex
@ -1176,6 +1169,11 @@ msgctxt "#39084"
msgid "Enter PMS port" msgid "Enter PMS port"
msgstr "" msgstr ""
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
msgctxt "#39200" msgctxt "#39200"
msgid "Log-out Plex Home User " msgid "Log-out Plex Home User "
msgstr "Logga ut Plex Home-användare" msgstr "Logga ut Plex Home-användare"
@ -1403,6 +1401,10 @@ msgctxt "#39501"
msgid "Collections" msgid "Collections"
msgstr "" msgstr ""
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr ""
msgctxt "#39600" msgctxt "#39600"
msgid "" msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the " "Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1511,7 +1513,7 @@ msgstr "Synka"
# Shown during sync process # Shown during sync process
msgctxt "#39715" msgctxt "#39715"
msgid "items" msgid "Synching playlists"
msgstr "" msgstr ""
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is # Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is

View file

@ -1044,11 +1044,6 @@ msgctxt "#39057"
msgid "Customize Paths" msgid "Customize Paths"
msgstr "Налаштування шляхів" msgstr "Налаштування шляхів"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr "Поширити відображення серій у Поточному на весь серіал"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39059" msgctxt "#39059"
msgid "Recently Added: Append show title to episode" msgid "Recently Added: Append show title to episode"
@ -1089,12 +1084,8 @@ msgstr "Примусово оновлювати обкладинку Kodi піс
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39066" msgctxt "#39066"
msgid "" msgid "Recently Added: Also show already watched movies"
"Recently Added: Also show already watched movies (Refresh Plex "
"playlist/nodes!)"
msgstr "" msgstr ""
"Нещодавно додане: також відображати вже переглянуті фільми (оновити Plex "
"вузли/листи відтворювання!)"
# PKC Settings - Connection # PKC Settings - Connection
msgctxt "#39067" msgctxt "#39067"
@ -1148,8 +1139,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39077" msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")" msgid "Maximum number of videos to show in widgets"
msgstr "Кількість елементів PMS для показування у віджетах (типу \"Поточне\")" msgstr ""
# PKC Settings - Plex # PKC Settings - Plex
msgctxt "#39078" msgctxt "#39078"
@ -1194,6 +1185,11 @@ msgctxt "#39084"
msgid "Enter PMS port" msgid "Enter PMS port"
msgstr "" msgstr ""
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
msgctxt "#39200" msgctxt "#39200"
msgid "Log-out Plex Home User " msgid "Log-out Plex Home User "
msgstr "Вийти з профілю користувача Plex Home" msgstr "Вийти з профілю користувача Plex Home"
@ -1438,6 +1434,10 @@ msgctxt "#39501"
msgid "Collections" msgid "Collections"
msgstr "Колекції" msgstr "Колекції"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr ""
msgctxt "#39600" msgctxt "#39600"
msgid "" msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the " "Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1554,8 +1554,8 @@ msgstr "Синхронізація"
# Shown during sync process # Shown during sync process
msgctxt "#39715" msgctxt "#39715"
msgid "items" msgid "Synching playlists"
msgstr "елементів" msgstr ""
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is # Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is
# screwed up; formated the wrong way). Do NOT replace {0} and {1}! # screwed up; formated the wrong way). Do NOT replace {0} and {1}!

View file

@ -1014,11 +1014,6 @@ msgctxt "#39057"
msgid "Customize Paths" msgid "Customize Paths"
msgstr "自定义路径" msgstr "自定义路径"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr "扩展Plex TV Series \"On Deck\"视图到所有节目"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39059" msgctxt "#39059"
msgid "Recently Added: Append show title to episode" msgid "Recently Added: Append show title to episode"
@ -1058,10 +1053,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39066" msgctxt "#39066"
msgid "" msgid "Recently Added: Also show already watched movies"
"Recently Added: Also show already watched movies (Refresh Plex " msgstr ""
"playlist/nodes!)"
msgstr "最近添加:同时显示已观看电影(Refresh Plex 列表/节点!)"
# PKC Settings - Connection # PKC Settings - Connection
msgctxt "#39067" msgctxt "#39067"
@ -1112,8 +1105,8 @@ msgstr "如果您使用了同一类多个Plex库e.g. “儿童电影”和”
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39077" msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")" msgid "Maximum number of videos to show in widgets"
msgstr "小部件上显示的PMS项目数(e.g. \"On Deck\")" msgstr ""
# PKC Settings - Plex # PKC Settings - Plex
msgctxt "#39078" msgctxt "#39078"
@ -1156,6 +1149,11 @@ msgctxt "#39084"
msgid "Enter PMS port" msgid "Enter PMS port"
msgstr "" msgstr ""
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
msgctxt "#39200" msgctxt "#39200"
msgid "Log-out Plex Home User " msgid "Log-out Plex Home User "
msgstr "退出Plex家庭用户 " msgstr "退出Plex家庭用户 "
@ -1383,6 +1381,10 @@ msgctxt "#39501"
msgid "Collections" msgid "Collections"
msgstr "收藏" msgstr "收藏"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr ""
msgctxt "#39600" msgctxt "#39600"
msgid "" msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the " "Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1489,7 +1491,7 @@ msgstr ""
# Shown during sync process # Shown during sync process
msgctxt "#39715" msgctxt "#39715"
msgid "items" msgid "Synching playlists"
msgstr "" msgstr ""
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is # Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is

View file

@ -1010,11 +1010,6 @@ msgctxt "#39057"
msgid "Customize Paths" msgid "Customize Paths"
msgstr "自訂路徑" msgstr "自訂路徑"
# PKC Settings - Appearance Tweaks
msgctxt "#39058"
msgid "Extend Plex TV Series \"On Deck\" view to all shows"
msgstr "延伸plex電視節目系列\"上架\"視圖,到所有節目"
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39059" msgctxt "#39059"
msgid "Recently Added: Append show title to episode" msgid "Recently Added: Append show title to episode"
@ -1054,10 +1049,8 @@ msgstr ""
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39066" msgctxt "#39066"
msgid "" msgid "Recently Added: Also show already watched movies"
"Recently Added: Also show already watched movies (Refresh Plex " msgstr ""
"playlist/nodes!)"
msgstr "最近添加︰ 也顯示已觀看的電影 (刷新Plex播放清單/節點!)"
# PKC Settings - Connection # PKC Settings - Connection
msgctxt "#39067" msgctxt "#39067"
@ -1108,8 +1101,8 @@ msgstr "如果您使用多個同類的Plex資料庫例如\"兒童電影\"和\
# PKC Settings - Appearance Tweaks # PKC Settings - Appearance Tweaks
msgctxt "#39077" msgctxt "#39077"
msgid "Number of PMS items to show in widgets (e.g. \"On Deck\")" msgid "Maximum number of videos to show in widgets"
msgstr "PMS 中顯示在小工具集 (例如\"上架\") 品項的數目" msgstr ""
# PKC Settings - Plex # PKC Settings - Plex
msgctxt "#39078" msgctxt "#39078"
@ -1152,6 +1145,11 @@ msgctxt "#39084"
msgid "Enter PMS port" msgid "Enter PMS port"
msgstr "" msgstr ""
# PKC settings - Appearance Tweaks
msgctxt "#39085"
msgid "Reload Kodi node files to apply all the settings below"
msgstr ""
msgctxt "#39200" msgctxt "#39200"
msgid "Log-out Plex Home User " msgid "Log-out Plex Home User "
msgstr "登出Plex Home用戶 " msgstr "登出Plex Home用戶 "
@ -1379,6 +1377,10 @@ msgctxt "#39501"
msgid "Collections" msgid "Collections"
msgstr "收藏" msgstr "收藏"
msgctxt "#39502"
msgid "PKC On Deck (faster)"
msgstr ""
msgctxt "#39600" msgctxt "#39600"
msgid "" msgid ""
"Are you sure you want to reset your local Kodi database? A re-sync of the " "Are you sure you want to reset your local Kodi database? A re-sync of the "
@ -1485,7 +1487,7 @@ msgstr ""
# Shown during sync process # Shown during sync process
msgctxt "#39715" msgctxt "#39715"
msgid "items" msgid "Synching playlists"
msgstr "" msgstr ""
# Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is # Error message if an xml, e.g. advancedsettings.xml cannot be parsed (xml is

View file

@ -15,7 +15,7 @@ from xbmcgui import ListItem
from . import utils from . import utils
from . import path_ops from . import path_ops
from .downloadutils import DownloadUtils as DU from .downloadutils import DownloadUtils as DU
from .plex_api import API from .plex_api import API, mass_api
from . import plex_functions as PF from . import plex_functions as PF
from . import variables as v from . import variables as v
# Be careful - your using app in another Python instance! # Be careful - your using app in another Python instance!
@ -217,13 +217,13 @@ def show_listing(xml, plex_type=None, section_id=None, synched=True, key=None,
# Need to chain keys for navigation # Need to chain keys for navigation
widgets.KEY = key widgets.KEY = key
# Process all items to show # Process all items to show
widgets.attach_kodi_ids(xml) all_items = mass_api(xml)
all_items = widgets.process_method_on_list(widgets.generate_item, xml) all_items = utils.process_method_on_list(widgets.generate_item, all_items)
all_items = widgets.process_method_on_list(widgets.prepare_listitem, all_items = utils.process_method_on_list(widgets.prepare_listitem,
all_items) all_items)
# fill that listing... # fill that listing...
all_items = widgets.process_method_on_list(widgets.create_listitem, all_items = utils.process_method_on_list(widgets.create_listitem,
all_items) all_items)
xbmcplugin.addDirectoryItems(int(sys.argv[1]), all_items, len(all_items)) xbmcplugin.addDirectoryItems(int(sys.argv[1]), all_items, len(all_items))
# end directory listing # end directory listing
xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_UNSORTED) xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_UNSORTED)
@ -397,13 +397,13 @@ def hub(content_type):
for entry in reversed(xml): for entry in reversed(xml):
api = API(entry) api = API(entry)
append = False append = False
if content_type == 'video' and api.plex_type() in v.PLEX_VIDEOTYPES: if content_type == 'video' and api.plex_type in v.PLEX_VIDEOTYPES:
append = True append = True
elif content_type == 'audio' and api.plex_type() in v.PLEX_AUDIOTYPES: elif content_type == 'audio' and api.plex_type in v.PLEX_AUDIOTYPES:
append = True append = True
elif content_type == 'image' and api.plex_type() == v.PLEX_TYPE_PHOTO: elif content_type == 'image' and api.plex_type == v.PLEX_TYPE_PHOTO:
append = True append = True
elif content_type != 'image' and api.plex_type() == v.PLEX_TYPE_PLAYLIST: elif content_type != 'image' and api.plex_type == v.PLEX_TYPE_PLAYLIST:
append = True append = True
elif content_type is None: elif content_type is None:
# Needed for widgets, where no content_type is provided # Needed for widgets, where no content_type is provided

View file

@ -20,7 +20,7 @@ class Movie(ItemBase):
Process single movie Process single movie
""" """
api = API(xml) api = API(xml)
plex_id = api.plex_id() plex_id = api.plex_id
# Cannot parse XML, abort # Cannot parse XML, abort
if not plex_id: if not plex_id:
LOG.error('Cannot parse XML data for movie: %s', xml.attrib) LOG.error('Cannot parse XML data for movie: %s', xml.attrib)
@ -35,20 +35,6 @@ class Movie(ItemBase):
update_item = False update_item = False
kodi_id = self.kodidb.new_movie_id() kodi_id = self.kodidb.new_movie_id()
userdata = api.userdata()
playcount = userdata['PlayCount']
dateplayed = userdata['LastPlayedDate']
resume = userdata['Resume']
runtime = userdata['Runtime']
rating = userdata['Rating']
title = api.title()
people = api.people()
genres = api.genre_list()
collections = api.collection_list()
countries = api.country_list()
studios = api.music_studio_list()
# GET THE FILE AND PATH ##### # GET THE FILE AND PATH #####
do_indirect = not app.SYNC.direct_paths do_indirect = not app.SYNC.direct_paths
if app.SYNC.direct_paths: if app.SYNC.direct_paths:
@ -58,7 +44,7 @@ class Movie(ItemBase):
# Something went wrong, trying to use non-direct paths # Something went wrong, trying to use non-direct paths
do_indirect = True do_indirect = True
else: else:
playurl = api.validate_playurl(playurl, api.plex_type()) playurl = api.validate_playurl(playurl, api.plex_type)
if playurl is None: if playurl is None:
return False return False
if '\\' in playurl: if '\\' in playurl:
@ -92,7 +78,7 @@ class Movie(ItemBase):
self.kodidb.update_ratings(kodi_id, self.kodidb.update_ratings(kodi_id,
v.KODI_TYPE_MOVIE, v.KODI_TYPE_MOVIE,
"default", "default",
rating, api.rating(),
api.votecount(), api.votecount(),
rating_id) rating_id)
# update new uniqueid Kodi 17 # update new uniqueid Kodi 17
@ -109,13 +95,13 @@ class Movie(ItemBase):
uniqueid = -1 uniqueid = -1
self.kodidb.modify_people(kodi_id, self.kodidb.modify_people(kodi_id,
v.KODI_TYPE_MOVIE, v.KODI_TYPE_MOVIE,
api.people_list()) api.people())
if app.SYNC.artwork: if app.SYNC.artwork:
self.kodidb.modify_artwork(api.artwork(), self.kodidb.modify_artwork(api.artwork(),
kodi_id, kodi_id,
v.KODI_TYPE_MOVIE) v.KODI_TYPE_MOVIE)
else: else:
LOG.info("ADD movie plex_id: %s - %s", plex_id, title) LOG.info("ADD movie plex_id: %s - %s", plex_id, api.title())
file_id = self.kodidb.add_file(filename, file_id = self.kodidb.add_file(filename,
kodi_pathid, kodi_pathid,
api.date_created()) api.date_created())
@ -124,7 +110,7 @@ class Movie(ItemBase):
kodi_id, kodi_id,
v.KODI_TYPE_MOVIE, v.KODI_TYPE_MOVIE,
"default", "default",
rating, api.rating(),
api.votecount()) api.votecount())
if api.provider('imdb') is not None: if api.provider('imdb') is not None:
uniqueid = self.kodidb.add_uniqueid_id() uniqueid = self.kodidb.add_uniqueid_id()
@ -137,7 +123,7 @@ class Movie(ItemBase):
uniqueid = -1 uniqueid = -1
self.kodidb.add_people(kodi_id, self.kodidb.add_people(kodi_id,
v.KODI_TYPE_MOVIE, v.KODI_TYPE_MOVIE,
api.people_list()) api.people())
if app.SYNC.artwork: if app.SYNC.artwork:
self.kodidb.add_artwork(api.artwork(), self.kodidb.add_artwork(api.artwork(),
kodi_id, kodi_id,
@ -146,37 +132,39 @@ class Movie(ItemBase):
# Update Kodi's main entry # Update Kodi's main entry
self.kodidb.add_movie(kodi_id, self.kodidb.add_movie(kodi_id,
file_id, file_id,
title, api.title(),
api.plot(), api.plot(),
api.shortplot(), api.shortplot(),
api.tagline(), api.tagline(),
api.votecount(), api.votecount(),
rating_id, rating_id,
api.list_to_string(people['Writer']), api.list_to_string(api.writers()),
api.year(), api.year(),
uniqueid, uniqueid,
api.sorttitle(), api.sorttitle(),
runtime, api.runtime(),
api.content_rating(), api.content_rating(),
api.list_to_string(genres), api.list_to_string(api.genres()),
api.list_to_string(people['Director']), api.list_to_string(api.directors()),
title, api.title(),
api.list_to_string(studios), api.list_to_string(api.studios()),
api.trailer(), api.trailer(),
api.list_to_string(countries), api.list_to_string(api.countries()),
playurl, playurl,
kodi_pathid, kodi_pathid,
api.premiere_date(), api.premiere_date(),
userdata['UserRating']) api.userrating())
self.kodidb.modify_countries(kodi_id, v.KODI_TYPE_MOVIE, countries) self.kodidb.modify_countries(kodi_id,
self.kodidb.modify_genres(kodi_id, v.KODI_TYPE_MOVIE, genres) v.KODI_TYPE_MOVIE,
api.countries())
self.kodidb.modify_genres(kodi_id, v.KODI_TYPE_MOVIE, api.genres())
self.kodidb.modify_streams(file_id, api.mediastreams(), runtime) self.kodidb.modify_streams(file_id, api.mediastreams(), api.runtime())
self.kodidb.modify_studios(kodi_id, v.KODI_TYPE_MOVIE, studios) self.kodidb.modify_studios(kodi_id, v.KODI_TYPE_MOVIE, api.studios())
tags = [section_name] tags = [section_name]
if collections: if api.collections():
for plex_set_id, set_name in collections: for plex_set_id, set_name in api.collections():
set_api = None set_api = None
tags.append(set_name) tags.append(set_name)
# Add any sets from Plex collection tags # Add any sets from Plex collection tags
@ -211,10 +199,10 @@ class Movie(ItemBase):
self.kodidb.modify_tags(kodi_id, v.KODI_TYPE_MOVIE, tags) self.kodidb.modify_tags(kodi_id, v.KODI_TYPE_MOVIE, tags)
# Process playstate # Process playstate
self.kodidb.set_resume(file_id, self.kodidb.set_resume(file_id,
resume, api.resume_point(),
runtime, api.runtime(),
playcount, api.viewcount(),
dateplayed) api.lastplayed())
self.plexdb.add_movie(plex_id=plex_id, self.plexdb.add_movie(plex_id=plex_id,
checksum=api.checksum(), checksum=api.checksum(),
section_id=section_id, section_id=section_id,
@ -267,19 +255,17 @@ class Movie(ItemBase):
""" """
api = API(xml_element) api = API(xml_element)
# Get key and db entry on the Kodi db side # Get key and db entry on the Kodi db side
db_item = self.plexdb.item_by_id(api.plex_id(), plex_type) db_item = self.plexdb.item_by_id(api.plex_id, plex_type)
if not db_item: if not db_item:
LOG.info('Item not yet synced: %s', xml_element.attrib) LOG.info('Item not yet synced: %s', xml_element.attrib)
return False return False
# Grab the user's viewcount, resume points etc. from PMS' answer
userdata = api.userdata()
# Write to Kodi DB # Write to Kodi DB
self.kodidb.set_resume(db_item['kodi_fileid'], self.kodidb.set_resume(db_item['kodi_fileid'],
userdata['Resume'], api.resume_point(),
userdata['Runtime'], api.runtime(),
userdata['PlayCount'], api.viewcount(),
userdata['LastPlayedDate']) api.lastplayed())
self.kodidb.update_userrating(db_item['kodi_id'], self.kodidb.update_userrating(db_item['kodi_id'],
db_item['kodi_type'], db_item['kodi_type'],
userdata['UserRating']) api.userrating())
return True return True

View file

@ -42,18 +42,17 @@ class MusicMixin(object):
""" """
api = API(xml_element) api = API(xml_element)
# Get key and db entry on the Kodi db side # Get key and db entry on the Kodi db side
db_item = self.plexdb.item_by_id(api.plex_id(), plex_type) db_item = self.plexdb.item_by_id(api.plex_id, plex_type)
if not db_item: if not db_item:
LOG.info('Item not yet synced: %s', xml_element.attrib) LOG.info('Item not yet synced: %s', xml_element.attrib)
return False return False
# Grab the user's viewcount, resume points etc. from PMS' answer # Grab the user's viewcount, resume points etc. from PMS' answer
userdata = api.userdata()
self.kodidb.update_userrating(db_item['kodi_id'], self.kodidb.update_userrating(db_item['kodi_id'],
db_item['kodi_type'], db_item['kodi_type'],
userdata['UserRating']) api.userrating())
if plex_type == v.PLEX_TYPE_SONG: if plex_type == v.PLEX_TYPE_SONG:
self.kodidb.set_playcount(userdata['PlayCount'], self.kodidb.set_playcount(api.viewcount(),
userdata['LastPlayedDate'], api.lastplayed(),
db_item['kodi_id'],) db_item['kodi_id'],)
return True return True
@ -160,7 +159,7 @@ class Artist(MusicMixin, ItemBase):
Process a single artist Process a single artist
""" """
api = API(xml) api = API(xml)
plex_id = api.plex_id() plex_id = api.plex_id
if not plex_id: if not plex_id:
LOG.error('Cannot process artist %s', xml.attrib) LOG.error('Cannot process artist %s', xml.attrib)
return return
@ -198,7 +197,7 @@ class Artist(MusicMixin, ItemBase):
# Kodi doesn't allow that. In case that happens we just merge the # Kodi doesn't allow that. In case that happens we just merge the
# artist entries. # artist entries.
kodi_id = self.kodidb.add_artist(api.title(), musicBrainzId) kodi_id = self.kodidb.add_artist(api.title(), musicBrainzId)
self.kodidb.update_artist(api.list_to_string(api.genre_list()), self.kodidb.update_artist(api.list_to_string(api.genres()),
api.plot(), api.plot(),
thumb, thumb,
fanart, fanart,
@ -224,7 +223,7 @@ class Album(MusicMixin, ItemBase):
avoid infinite loops avoid infinite loops
""" """
api = API(xml) api = API(xml)
plex_id = api.plex_id() plex_id = api.plex_id
if not plex_id: if not plex_id:
LOG.error('Error processing album: %s', xml.attrib) LOG.error('Error processing album: %s', xml.attrib)
return return
@ -274,11 +273,9 @@ class Album(MusicMixin, ItemBase):
compilation = 1 compilation = 1
break break
name = api.title() name = api.title()
userdata = api.userdata()
# Not yet implemented by Plex, let's use unique last.fm or gracenote # Not yet implemented by Plex, let's use unique last.fm or gracenote
musicBrainzId = None musicBrainzId = None
genres = api.genre_list() genre = api.list_to_string(api.genres())
genre = api.list_to_string(genres)
if app.SYNC.artwork: if app.SYNC.artwork:
artworks = api.artwork() artworks = api.artwork()
if 'poster' in artworks: if 'poster' in artworks:
@ -300,8 +297,8 @@ class Album(MusicMixin, ItemBase):
compilation, compilation,
api.plot(), api.plot(),
thumb, thumb,
api.music_studio(), api.list_to_string(api.studios()),
userdata['UserRating'], api.userrating(),
timing.unix_date_to_kodi(self.last_sync), timing.unix_date_to_kodi(self.last_sync),
'album', 'album',
kodi_id) kodi_id)
@ -314,8 +311,8 @@ class Album(MusicMixin, ItemBase):
compilation, compilation,
api.plot(), api.plot(),
thumb, thumb,
api.music_studio(), api.list_to_string(api.studios()),
userdata['UserRating'], api.userrating(),
timing.unix_date_to_kodi(self.last_sync), timing.unix_date_to_kodi(self.last_sync),
'album', 'album',
kodi_id) kodi_id)
@ -333,8 +330,8 @@ class Album(MusicMixin, ItemBase):
compilation, compilation,
api.plot(), api.plot(),
thumb, thumb,
api.music_studio(), api.list_to_string(api.studios()),
userdata['UserRating'], api.userrating(),
timing.unix_date_to_kodi(self.last_sync), timing.unix_date_to_kodi(self.last_sync),
'album') 'album')
else: else:
@ -347,15 +344,15 @@ class Album(MusicMixin, ItemBase):
compilation, compilation,
api.plot(), api.plot(),
thumb, thumb,
api.music_studio(), api.list_to_string(api.studios()),
userdata['UserRating'], api.userrating(),
timing.unix_date_to_kodi(self.last_sync), timing.unix_date_to_kodi(self.last_sync),
'album') 'album')
self.kodidb.add_albumartist(artist_id, kodi_id, api.artist_name()) self.kodidb.add_albumartist(artist_id, kodi_id, api.artist_name())
if v.KODIVERSION < 18: if v.KODIVERSION < 18:
self.kodidb.add_discography(artist_id, name, api.year()) self.kodidb.add_discography(artist_id, name, api.year())
self.kodidb.add_music_genres(kodi_id, self.kodidb.add_music_genres(kodi_id,
genres, api.genres(),
v.KODI_TYPE_ALBUM) v.KODI_TYPE_ALBUM)
if app.SYNC.artwork: if app.SYNC.artwork:
self.kodidb.modify_artwork(artworks, self.kodidb.modify_artwork(artworks,
@ -378,7 +375,7 @@ class Album(MusicMixin, ItemBase):
section_name=section_name, section_name=section_name,
section_id=section_id, section_id=section_id,
album_xml=xml, album_xml=xml,
genres=genres, genres=api.genres(),
genre=genre, genre=genre,
compilation=compilation) compilation=compilation)
@ -391,7 +388,7 @@ class Song(MusicMixin, ItemBase):
Process single song/track Process single song/track
""" """
api = API(xml) api = API(xml)
plex_id = api.plex_id() plex_id = api.plex_id
if not plex_id: if not plex_id:
LOG.error('Error processing song: %s', xml.attrib) LOG.error('Error processing song: %s', xml.attrib)
return return
@ -492,11 +489,6 @@ class Song(MusicMixin, ItemBase):
# Not yet implemented by Plex # Not yet implemented by Plex
musicBrainzId = None musicBrainzId = None
comment = None comment = None
userdata = api.userdata()
playcount = userdata['PlayCount']
if playcount is None:
# This is different to Video DB!
playcount = 0
# Getting artists name is complicated # Getting artists name is complicated
if compilation is not None: if compilation is not None:
if compilation == 0: if compilation == 0:
@ -506,7 +498,7 @@ class Song(MusicMixin, ItemBase):
else: else:
# compilation not set # compilation not set
artists = xml.get('originalTitle', api.grandparent_title()) artists = xml.get('originalTitle', api.grandparent_title())
tracknumber = api.track_number() or 0 tracknumber = api.index() or 0
disc = api.disc_number() or 1 disc = api.disc_number() or 1
if disc == 1: if disc == 1:
track = tracknumber track = tracknumber
@ -532,7 +524,7 @@ class Song(MusicMixin, ItemBase):
# Something went wrong, trying to use non-direct paths # Something went wrong, trying to use non-direct paths
do_indirect = True do_indirect = True
else: else:
playurl = api.validate_playurl(playurl, api.plex_type()) playurl = api.validate_playurl(playurl, api.plex_type)
if playurl is None: if playurl is None:
return False return False
if "\\" in playurl: if "\\" in playurl:
@ -562,12 +554,12 @@ class Song(MusicMixin, ItemBase):
genre, genre,
title, title,
track, track,
userdata['Runtime'], api.runtime(),
year, year,
filename, filename,
playcount, api.viewcount(),
userdata['LastPlayedDate'], api.lastplayed(),
userdata['UserRating'], api.userrating(),
comment, comment,
mood, mood,
api.date_created(), api.date_created(),
@ -578,12 +570,12 @@ class Song(MusicMixin, ItemBase):
genre, genre,
title, title,
track, track,
userdata['Runtime'], api.runtime(),
year, year,
filename, filename,
playcount, api.viewcount(),
userdata['LastPlayedDate'], api.lastplayed(),
userdata['UserRating'], api.userrating(),
comment, comment,
mood, mood,
api.date_created(), api.date_created(),
@ -603,13 +595,13 @@ class Song(MusicMixin, ItemBase):
genre, genre,
title, title,
track, track,
userdata['Runtime'], api.runtime(),
year, year,
filename, filename,
musicBrainzId, musicBrainzId,
playcount, api.viewcount(),
userdata['LastPlayedDate'], api.lastplayed(),
userdata['UserRating'], api.userrating(),
0, 0,
0, 0,
mood, mood,
@ -622,13 +614,13 @@ class Song(MusicMixin, ItemBase):
genre, genre,
title, title,
track, track,
userdata['Runtime'], api.runtime(),
year, year,
filename, filename,
musicBrainzId, musicBrainzId,
playcount, api.viewcount(),
userdata['LastPlayedDate'], api.lastplayed(),
userdata['UserRating'], api.userrating(),
0, 0,
0, 0,
mood, mood,
@ -639,7 +631,7 @@ class Song(MusicMixin, ItemBase):
parent_id, parent_id,
track, track,
title, title,
userdata['Runtime']) api.runtime())
# Link song to artists # Link song to artists
artist_name = api.grandparent_title() artist_name = api.grandparent_title()
# Do the actual linking # Do the actual linking

View file

@ -18,27 +18,26 @@ class TvShowMixin(object):
""" """
api = API(xml_element) api = API(xml_element)
# Get key and db entry on the Kodi db side # Get key and db entry on the Kodi db side
db_item = self.plexdb.item_by_id(api.plex_id(), plex_type) db_item = self.plexdb.item_by_id(api.plex_id, plex_type)
if not db_item: if not db_item:
LOG.info('Item not yet synced: %s', xml_element.attrib) LOG.info('Item not yet synced: %s', xml_element.attrib)
return False return False
# Grab the user's viewcount, resume points etc. from PMS' answer # Grab the user's viewcount, resume points etc. from PMS' answer
userdata = api.userdata()
self.kodidb.update_userrating(db_item['kodi_id'], self.kodidb.update_userrating(db_item['kodi_id'],
db_item['kodi_type'], db_item['kodi_type'],
userdata['UserRating']) api.userrating())
if plex_type == v.PLEX_TYPE_EPISODE: if plex_type == v.PLEX_TYPE_EPISODE:
self.kodidb.set_resume(db_item['kodi_fileid'], self.kodidb.set_resume(db_item['kodi_fileid'],
userdata['Resume'], api.resume_point(),
userdata['Runtime'], api.runtime(),
userdata['PlayCount'], api.viewcount(),
userdata['LastPlayedDate']) api.lastplayed())
if db_item['kodi_fileid_2']: if db_item['kodi_fileid_2']:
self.kodidb.set_resume(db_item['kodi_fileid_2'], self.kodidb.set_resume(db_item['kodi_fileid_2'],
userdata['Resume'], api.resume_point(),
userdata['Runtime'], api.runtime(),
userdata['PlayCount'], api.viewcount(),
userdata['LastPlayedDate']) api.lastplayed())
return True return True
def remove(self, plex_id, plex_type=None): def remove(self, plex_id, plex_type=None):
@ -149,7 +148,7 @@ class Show(TvShowMixin, ItemBase):
Process a single show Process a single show
""" """
api = API(xml) api = API(xml)
plex_id = api.plex_id() plex_id = api.plex_id
if not plex_id: if not plex_id:
LOG.error("Cannot parse XML data for TV show: %s", xml.attrib) LOG.error("Cannot parse XML data for TV show: %s", xml.attrib)
return return
@ -162,16 +161,11 @@ class Show(TvShowMixin, ItemBase):
kodi_id = show['kodi_id'] kodi_id = show['kodi_id']
kodi_pathid = show['kodi_pathid'] kodi_pathid = show['kodi_pathid']
genres = api.genre_list()
genre = api.list_to_string(genres)
studios = api.music_studio_list()
studio = api.list_to_string(studios)
# GET THE FILE AND PATH ##### # GET THE FILE AND PATH #####
if app.SYNC.direct_paths: if app.SYNC.direct_paths:
# Direct paths is set the Kodi way # Direct paths is set the Kodi way
playurl = api.validate_playurl(api.tv_show_path(), playurl = api.validate_playurl(api.tv_show_path(),
api.plex_type(), api.plex_type,
folder=True) folder=True)
if playurl is None: if playurl is None:
return return
@ -197,7 +191,7 @@ class Show(TvShowMixin, ItemBase):
self.kodidb.update_ratings(kodi_id, self.kodidb.update_ratings(kodi_id,
v.KODI_TYPE_SHOW, v.KODI_TYPE_SHOW,
"default", "default",
api.audience_rating(), api.rating(),
api.votecount(), api.votecount(),
rating_id) rating_id)
if api.provider('tvdb') is not None: if api.provider('tvdb') is not None:
@ -213,7 +207,7 @@ class Show(TvShowMixin, ItemBase):
uniqueid = -1 uniqueid = -1
self.kodidb.modify_people(kodi_id, self.kodidb.modify_people(kodi_id,
v.KODI_TYPE_SHOW, v.KODI_TYPE_SHOW,
api.people_list()) api.people())
if app.SYNC.artwork: if app.SYNC.artwork:
self.kodidb.modify_artwork(api.artwork(), self.kodidb.modify_artwork(api.artwork(),
kodi_id, kodi_id,
@ -223,11 +217,11 @@ class Show(TvShowMixin, ItemBase):
api.plot(), api.plot(),
rating_id, rating_id,
api.premiere_date(), api.premiere_date(),
genre, api.list_to_string(api.genres()),
api.title(), api.title(),
uniqueid, uniqueid,
api.content_rating(), api.content_rating(),
studio, api.list_to_string(api.studios()),
api.sorttitle(), api.sorttitle(),
kodi_id) kodi_id)
# OR ADD THE TVSHOW ##### # OR ADD THE TVSHOW #####
@ -240,7 +234,7 @@ class Show(TvShowMixin, ItemBase):
kodi_id, kodi_id,
v.KODI_TYPE_SHOW, v.KODI_TYPE_SHOW,
"default", "default",
api.audience_rating(), api.rating(),
api.votecount()) api.votecount())
if api.provider('tvdb'): if api.provider('tvdb'):
uniqueid = self.kodidb.add_uniqueid_id() uniqueid = self.kodidb.add_uniqueid_id()
@ -253,7 +247,7 @@ class Show(TvShowMixin, ItemBase):
uniqueid = -1 uniqueid = -1
self.kodidb.add_people(kodi_id, self.kodidb.add_people(kodi_id,
v.KODI_TYPE_SHOW, v.KODI_TYPE_SHOW,
api.people_list()) api.people())
if app.SYNC.artwork: if app.SYNC.artwork:
self.kodidb.add_artwork(api.artwork(), self.kodidb.add_artwork(api.artwork(),
kodi_id, kodi_id,
@ -264,18 +258,18 @@ class Show(TvShowMixin, ItemBase):
api.plot(), api.plot(),
rating_id, rating_id,
api.premiere_date(), api.premiere_date(),
genre, api.list_to_string(api.genres()),
api.title(), api.title(),
uniqueid, uniqueid,
api.content_rating(), api.content_rating(),
studio, api.list_to_string(api.studios()),
api.sorttitle()) api.sorttitle())
self.kodidb.modify_genres(kodi_id, v.KODI_TYPE_SHOW, genres) self.kodidb.modify_genres(kodi_id, v.KODI_TYPE_SHOW, api.genres())
# Process studios # Process studios
self.kodidb.modify_studios(kodi_id, v.KODI_TYPE_SHOW, studios) self.kodidb.modify_studios(kodi_id, v.KODI_TYPE_SHOW, api.studios())
# Process tags: view, PMS collection tags # Process tags: view, PMS collection tags
tags = [section_name] tags = [section_name]
tags.extend([i for _, i in api.collection_list()]) tags.extend([i for _, i in api.collections()])
self.kodidb.modify_tags(kodi_id, v.KODI_TYPE_SHOW, tags) self.kodidb.modify_tags(kodi_id, v.KODI_TYPE_SHOW, tags)
self.plexdb.add_show(plex_id=plex_id, self.plexdb.add_show(plex_id=plex_id,
checksum=api.checksum(), checksum=api.checksum(),
@ -292,7 +286,7 @@ class Season(TvShowMixin, ItemBase):
Process a single season of a certain tv show Process a single season of a certain tv show
""" """
api = API(xml) api = API(xml)
plex_id = api.plex_id() plex_id = api.plex_id
if not plex_id: if not plex_id:
LOG.error('Error getting plex_id for season, skipping: %s', LOG.error('Error getting plex_id for season, skipping: %s',
xml.attrib) xml.attrib)
@ -339,7 +333,7 @@ class Season(TvShowMixin, ItemBase):
v.KODI_TYPE_SEASON) v.KODI_TYPE_SEASON)
else: else:
LOG.info('ADD season plex_id %s - %s', plex_id, api.title()) LOG.info('ADD season plex_id %s - %s', plex_id, api.title())
kodi_id = self.kodidb.add_season(parent_id, api.season_number()) kodi_id = self.kodidb.add_season(parent_id, api.index())
if app.SYNC.artwork: if app.SYNC.artwork:
self.kodidb.add_artwork(artwork, self.kodidb.add_artwork(artwork,
kodi_id, kodi_id,
@ -360,7 +354,7 @@ class Episode(TvShowMixin, ItemBase):
Process single episode Process single episode
""" """
api = API(xml) api = API(xml)
plex_id = api.plex_id() plex_id = api.plex_id
if not plex_id: if not plex_id:
LOG.error('Error getting plex_id for episode, skipping: %s', LOG.error('Error getting plex_id for episode, skipping: %s',
xml.attrib) xml.attrib)
@ -376,58 +370,48 @@ class Episode(TvShowMixin, ItemBase):
old_kodi_fileid_2 = episode['kodi_fileid_2'] old_kodi_fileid_2 = episode['kodi_fileid_2']
kodi_pathid = episode['kodi_pathid'] kodi_pathid = episode['kodi_pathid']
peoples = api.people()
director = api.list_to_string(peoples['Director'])
writer = api.list_to_string(peoples['Writer'])
userdata = api.userdata()
show_id, season_id, _, season_no, episode_no = api.episode_data()
if season_no is None:
season_no = -1
if episode_no is None:
episode_no = -1
airs_before_season = "-1" airs_before_season = "-1"
airs_before_episode = "-1" airs_before_episode = "-1"
# The grandparent TV show # The grandparent TV show
show = self.plexdb.show(show_id) show = self.plexdb.show(api.show_id())
if not show: if not show:
LOG.warn('Grandparent TV show %s not found in DB, adding it', show_id) LOG.warn('Grandparent TV show %s not found in DB, adding it', api.show_id())
show_xml = PF.GetPlexMetadata(show_id) show_xml = PF.GetPlexMetadata(api.show_id())
try: try:
show_xml[0].attrib show_xml[0].attrib
except (TypeError, IndexError, AttributeError): except (TypeError, IndexError, AttributeError):
LOG.error("Grandparent tvshow %s xml download failed", show_id) LOG.error("Grandparent tvshow %s xml download failed", api.show_id())
return False return False
Show(self.last_sync, Show(self.last_sync,
plexdb=self.plexdb, plexdb=self.plexdb,
kodidb=self.kodidb).add_update(show_xml[0], kodidb=self.kodidb).add_update(show_xml[0],
section_name, section_name,
section_id) section_id)
show = self.plexdb.show(show_id) show = self.plexdb.show(api.show_id())
if not show: if not show:
LOG.error('Still could not find grandparent tv show %s', show_id) LOG.error('Still could not find grandparent tv show %s', api.show_id())
return return
grandparent_id = show['kodi_id'] grandparent_id = show['kodi_id']
# The parent Season # The parent Season
season = self.plexdb.season(season_id) season = self.plexdb.season(api.season_id())
if not season and season_id: if not season and api.season_id():
LOG.warn('Parent season %s not found in DB, adding it', season_id) LOG.warn('Parent season %s not found in DB, adding it', api.season_id())
season_xml = PF.GetPlexMetadata(season_id) season_xml = PF.GetPlexMetadata(api.season_id())
try: try:
season_xml[0].attrib season_xml[0].attrib
except (TypeError, IndexError, AttributeError): except (TypeError, IndexError, AttributeError):
LOG.error("Parent season %s xml download failed", season_id) LOG.error("Parent season %s xml download failed", api.season_id())
return False return False
Season(self.last_sync, Season(self.last_sync,
plexdb=self.plexdb, plexdb=self.plexdb,
kodidb=self.kodidb).add_update(season_xml[0], kodidb=self.kodidb).add_update(season_xml[0],
section_name, section_name,
section_id) section_id)
season = self.plexdb.season(season_id) season = self.plexdb.season(api.season_id())
if not season: if not season:
LOG.error('Still could not find parent season %s', season_id) LOG.error('Still could not find parent season %s', api.season_id())
return return
parent_id = season['kodi_id'] if season else None parent_id = season['kodi_id'] if season else None
@ -453,7 +437,7 @@ class Episode(TvShowMixin, ItemBase):
# Set plugin path - do NOT use "intermediate" paths for the show # Set plugin path - do NOT use "intermediate" paths for the show
# as with direct paths! # as with direct paths!
filename = api.file_name(force_first_media=True) filename = api.file_name(force_first_media=True)
path = 'plugin://%s.tvshows/%s/' % (v.ADDON_ID, show_id) path = 'plugin://%s.tvshows/%s/' % (v.ADDON_ID, api.show_id())
filename = ('%s?plex_id=%s&plex_type=%s&mode=play&filename=%s' filename = ('%s?plex_id=%s&plex_type=%s&mode=play&filename=%s'
% (path, plex_id, v.PLEX_TYPE_EPISODE, filename)) % (path, plex_id, v.PLEX_TYPE_EPISODE, filename))
playurl = filename playurl = filename
@ -491,7 +475,7 @@ class Episode(TvShowMixin, ItemBase):
self.kodidb.update_ratings(kodi_id, self.kodidb.update_ratings(kodi_id,
v.KODI_TYPE_EPISODE, v.KODI_TYPE_EPISODE,
"default", "default",
userdata['Rating'], api.rating(),
api.votecount(), api.votecount(),
ratingid) ratingid)
if api.provider('tvdb'): if api.provider('tvdb'):
@ -507,7 +491,7 @@ class Episode(TvShowMixin, ItemBase):
uniqueid = -1 uniqueid = -1
self.kodidb.modify_people(kodi_id, self.kodidb.modify_people(kodi_id,
v.KODI_TYPE_EPISODE, v.KODI_TYPE_EPISODE,
api.people_list()) api.people())
if app.SYNC.artwork: if app.SYNC.artwork:
self.kodidb.modify_artwork(api.artwork(), self.kodidb.modify_artwork(api.artwork(),
kodi_id, kodi_id,
@ -515,12 +499,12 @@ class Episode(TvShowMixin, ItemBase):
self.kodidb.update_episode(api.title(), self.kodidb.update_episode(api.title(),
api.plot(), api.plot(),
ratingid, ratingid,
writer, api.list_to_string(api.writers()),
api.premiere_date(), api.premiere_date(),
api.runtime(), api.runtime(),
director, api.list_to_string(api.directors()),
season_no, api.season_number(),
episode_no, api.index(),
api.title(), api.title(),
airs_before_season, airs_before_season,
airs_before_episode, airs_before_episode,
@ -528,25 +512,25 @@ class Episode(TvShowMixin, ItemBase):
kodi_pathid, kodi_pathid,
kodi_fileid, # and NOT kodi_fileid_2 kodi_fileid, # and NOT kodi_fileid_2
parent_id, parent_id,
userdata['UserRating'], api.userrating(),
kodi_id) kodi_id)
self.kodidb.set_resume(kodi_fileid, self.kodidb.set_resume(kodi_fileid,
api.resume_point(), api.resume_point(),
api.runtime(), api.runtime(),
userdata['PlayCount'], api.viewcount(),
userdata['LastPlayedDate']) api.lastplayed())
if not app.SYNC.direct_paths: if not app.SYNC.direct_paths:
self.kodidb.set_resume(kodi_fileid_2, self.kodidb.set_resume(kodi_fileid_2,
api.resume_point(), api.resume_point(),
api.runtime(), api.runtime(),
userdata['PlayCount'], api.viewcount(),
userdata['LastPlayedDate']) api.lastplayed())
self.plexdb.add_episode(plex_id=plex_id, self.plexdb.add_episode(plex_id=plex_id,
checksum=api.checksum(), checksum=api.checksum(),
section_id=section_id, section_id=section_id,
show_id=show_id, show_id=api.show_id(),
grandparent_id=grandparent_id, grandparent_id=grandparent_id,
season_id=season_id, season_id=api.season_id(),
parent_id=parent_id, parent_id=parent_id,
kodi_id=kodi_id, kodi_id=kodi_id,
kodi_fileid=kodi_fileid, kodi_fileid=kodi_fileid,
@ -571,7 +555,7 @@ class Episode(TvShowMixin, ItemBase):
kodi_id, kodi_id,
v.KODI_TYPE_EPISODE, v.KODI_TYPE_EPISODE,
"default", "default",
userdata['Rating'], api.rating(),
api.votecount()) api.votecount())
if api.provider('tvdb'): if api.provider('tvdb'):
uniqueid = self.kodidb.add_uniqueid_id() uniqueid = self.kodidb.add_uniqueid_id()
@ -582,7 +566,7 @@ class Episode(TvShowMixin, ItemBase):
"tvdb") "tvdb")
self.kodidb.add_people(kodi_id, self.kodidb.add_people(kodi_id,
v.KODI_TYPE_EPISODE, v.KODI_TYPE_EPISODE,
api.people_list()) api.people())
if app.SYNC.artwork: if app.SYNC.artwork:
self.kodidb.add_artwork(api.artwork(), self.kodidb.add_artwork(api.artwork(),
kodi_id, kodi_id,
@ -592,12 +576,12 @@ class Episode(TvShowMixin, ItemBase):
api.title(), api.title(),
api.plot(), api.plot(),
rating_id, rating_id,
writer, api.list_to_string(api.writers()),
api.premiere_date(), api.premiere_date(),
api.runtime(), api.runtime(),
director, api.list_to_string(api.directors()),
season_no, api.season_number(),
episode_no, api.index(),
api.title(), api.title(),
grandparent_id, grandparent_id,
airs_before_season, airs_before_season,
@ -605,24 +589,24 @@ class Episode(TvShowMixin, ItemBase):
playurl, playurl,
kodi_pathid, kodi_pathid,
parent_id, parent_id,
userdata['UserRating']) api.userrating())
self.kodidb.set_resume(kodi_fileid, self.kodidb.set_resume(kodi_fileid,
api.resume_point(), api.resume_point(),
api.runtime(), api.runtime(),
userdata['PlayCount'], api.viewcount(),
userdata['LastPlayedDate']) api.lastplayed())
if not app.SYNC.direct_paths: if not app.SYNC.direct_paths:
self.kodidb.set_resume(kodi_fileid_2, self.kodidb.set_resume(kodi_fileid_2,
api.resume_point(), api.resume_point(),
api.runtime(), api.runtime(),
userdata['PlayCount'], api.viewcount(),
userdata['LastPlayedDate']) api.lastplayed())
self.plexdb.add_episode(plex_id=plex_id, self.plexdb.add_episode(plex_id=plex_id,
checksum=api.checksum(), checksum=api.checksum(),
section_id=section_id, section_id=section_id,
show_id=show_id, show_id=api.show_id(),
grandparent_id=grandparent_id, grandparent_id=grandparent_id,
season_id=season_id, season_id=api.season_id(),
parent_id=parent_id, parent_id=parent_id,
kodi_id=kodi_id, kodi_id=kodi_id,
kodi_fileid=kodi_fileid, kodi_fileid=kodi_fileid,

View file

@ -611,8 +611,9 @@ class KodiVideoDB(common.KodiDBBase):
# Delete existing resume point # Delete existing resume point
self.cursor.execute('DELETE FROM bookmark WHERE idFile = ?', (file_id,)) self.cursor.execute('DELETE FROM bookmark WHERE idFile = ?', (file_id,))
# Set watched count # Set watched count
# Be careful to set playCount to None, NOT the int zero!
self.cursor.execute('UPDATE files SET playCount = ?, lastPlayed = ? WHERE idFile = ?', self.cursor.execute('UPDATE files SET playCount = ?, lastPlayed = ? WHERE idFile = ?',
(playcount, dateplayed, file_id)) (playcount or None, dateplayed, file_id))
# Set the resume bookmark # Set the resume bookmark
if resume_seconds: if resume_seconds:
self.cursor.execute( self.cursor.execute(

View file

@ -129,7 +129,7 @@ def process_fanart(plex_id, plex_type, refresh=False):
db_item['kodi_type']) db_item['kodi_type'])
# Additional fanart for sets/collections # Additional fanart for sets/collections
if plex_type == v.PLEX_TYPE_MOVIE: if plex_type == v.PLEX_TYPE_MOVIE:
for _, setname in api.collection_list(): for _, setname in api.collections():
LOG.debug('Getting artwork for movie set %s', setname) LOG.debug('Getting artwork for movie set %s', setname)
with KodiVideoDB() as kodidb: with KodiVideoDB() as kodidb:
setid = kodidb.create_collection(setname) setid = kodidb.create_collection(setname)

View file

@ -428,9 +428,13 @@ class FullSync(common.fullsync_mixin):
if self.isCanceled() or not self.full_library_sync(): if self.isCanceled() or not self.full_library_sync():
self.successful = False self.successful = False
return return
if common.PLAYLIST_SYNC_ENABLED and not playlists.full_sync(): if common.PLAYLIST_SYNC_ENABLED:
self.successful = False if self.dialog:
return self.dialog.close()
self.dialog = xbmcgui.DialogProgressBG()
self.dialog.create(utils.lang(39715))
if not playlists.full_sync():
self.successful = False
finally: finally:
common.update_kodi_library(video=True, music=True) common.update_kodi_library(video=True, music=True)
if self.dialog: if self.dialog:

View file

@ -56,7 +56,7 @@ class GetMetadataTask(common.fullsync_mixin, backgroundthread.Task):
[(utils.cast(int, x.get('index')), [(utils.cast(int, x.get('index')),
utils.cast(int, x.get('ratingKey'))) for x in COLLECTION_MATCH] utils.cast(int, x.get('ratingKey'))) for x in COLLECTION_MATCH]
item['children'] = {} item['children'] = {}
for plex_set_id, set_name in api.collection_list(): for plex_set_id, set_name in api.collections():
if self.isCanceled(): if self.isCanceled():
return return
if plex_set_id not in COLLECTION_XMLS: if plex_set_id not in COLLECTION_XMLS:

View file

@ -28,6 +28,11 @@ NODE_TYPES = {
}, },
'movies', 'movies',
True), True),
('pkc_ondeck',
utils.lang(39502), # "PKC On Deck (faster)"
{},
'movies',
False),
('recent', ('recent',
utils.lang(30174), # "Recently Added" utils.lang(30174), # "Recently Added"
{ {
@ -244,6 +249,29 @@ def node_pms(section, node_name, args):
return xml return xml
def node_pkc_ondeck(section, node_name):
"""
For movies only - returns in-progress movies sorted by last played
"""
xml = etree.Element('node', attrib={'order': unicode(section.order),
'type': 'filter'})
etree.SubElement(xml, 'match').text = 'all'
rule = etree.SubElement(xml, 'rule', attrib={'field': 'tag',
'operator': 'is'})
etree.SubElement(rule, 'value').text = section.name
etree.SubElement(xml, 'rule', attrib={'field': 'inprogress',
'operator': 'true'})
etree.SubElement(xml, 'label').text = node_name
etree.SubElement(xml, 'icon').text = ICON_PATH
etree.SubElement(xml, 'content').text = section.content
etree.SubElement(xml, 'limit').text = utils.settings('widgetLimit')
etree.SubElement(xml,
'order',
attrib={'direction':
'descending'}).text = 'lastplayed'
return xml
def node_recent(section, node_name): def node_recent(section, node_name):
xml = etree.Element('node', xml = etree.Element('node',
attrib={'order': unicode(section.order), attrib={'order': unicode(section.order),
@ -266,6 +294,7 @@ def node_recent(section, node_name):
etree.SubElement(xml, 'label').text = node_name etree.SubElement(xml, 'label').text = node_name
etree.SubElement(xml, 'icon').text = ICON_PATH etree.SubElement(xml, 'icon').text = ICON_PATH
etree.SubElement(xml, 'content').text = section.content etree.SubElement(xml, 'content').text = section.content
etree.SubElement(xml, 'limit').text = utils.settings('widgetLimit')
etree.SubElement(xml, etree.SubElement(xml,
'order', 'order',
attrib={'direction': attrib={'direction':
@ -303,6 +332,7 @@ def node_recommended(section, node_name):
etree.SubElement(xml, 'label').text = node_name etree.SubElement(xml, 'label').text = node_name
etree.SubElement(xml, 'icon').text = ICON_PATH etree.SubElement(xml, 'icon').text = ICON_PATH
etree.SubElement(xml, 'content').text = section.content etree.SubElement(xml, 'content').text = section.content
etree.SubElement(xml, 'limit').text = utils.settings('widgetLimit')
etree.SubElement(xml, etree.SubElement(xml,
'order', 'order',
attrib={'direction': attrib={'direction':
@ -357,6 +387,7 @@ def node_random(section, node_name):
etree.SubElement(xml, 'label').text = node_name etree.SubElement(xml, 'label').text = node_name
etree.SubElement(xml, 'icon').text = ICON_PATH etree.SubElement(xml, 'icon').text = ICON_PATH
etree.SubElement(xml, 'content').text = section.content etree.SubElement(xml, 'content').text = section.content
etree.SubElement(xml, 'limit').text = utils.settings('widgetLimit')
etree.SubElement(xml, etree.SubElement(xml,
'order', 'order',
attrib={'direction': attrib={'direction':
@ -377,6 +408,7 @@ def node_lastplayed(section, node_name):
etree.SubElement(xml, 'label').text = node_name etree.SubElement(xml, 'label').text = node_name
etree.SubElement(xml, 'icon').text = ICON_PATH etree.SubElement(xml, 'icon').text = ICON_PATH
etree.SubElement(xml, 'content').text = section.content etree.SubElement(xml, 'content').text = section.content
etree.SubElement(xml, 'limit').text = utils.settings('widgetLimit')
etree.SubElement(xml, etree.SubElement(xml,
'order', 'order',
attrib={'direction': attrib={'direction':

View file

@ -177,7 +177,7 @@ class Section(object):
api = API(xml_element) api = API(xml_element)
self.section_id = utils.cast(int, xml_element.get('key')) self.section_id = utils.cast(int, xml_element.get('key'))
self.name = api.title() self.name = api.title()
self.section_type = api.plex_type() self.section_type = api.plex_type
self.icon = api.one_artwork('composite') self.icon = api.one_artwork('composite')
self.artwork = api.one_artwork('art') self.artwork = api.one_artwork('art')
self.thumb = api.one_artwork('thumb') self.thumb = api.one_artwork('thumb')

View file

@ -313,9 +313,8 @@ def process_playing(data):
plex_id) plex_id)
continue continue
api = API(xml[0]) api = API(xml[0])
userdata = api.userdata() session['duration'] = api.runtime()
session['duration'] = userdata['Runtime'] session['viewCount'] = api.viewcount()
session['viewCount'] = userdata['PlayCount']
# Sometimes, Plex tells us resume points in milliseconds and # Sometimes, Plex tells us resume points in milliseconds and
# not in seconds - thank you very much! # not in seconds - thank you very much!
if message['viewOffset'] > session['duration']: if message['viewOffset'] > session['duration']:

View file

@ -36,4 +36,10 @@ def check_migration():
from .library_sync.sections import delete_files from .library_sync.sections import delete_files
delete_files() delete_files()
if not utils.compare_version(last_migration, '2.8.3'):
LOG.info('Migrating to version 2.8.2')
from .library_sync import sections
sections.clear_window_vars()
sections.delete_videonode_files()
utils.settings('last_migrated_PKC_version', value=v.ADDON_VERSION) utils.settings('last_migrated_PKC_version', value=v.ADDON_VERSION)

View file

@ -3,7 +3,7 @@
from __future__ import absolute_import, division, unicode_literals from __future__ import absolute_import, division, unicode_literals
from logging import getLogger from logging import getLogger
from .plex_api import API from .plex_api.media import Media
from . import utils from . import utils
from . import variables as v from . import variables as v
@ -23,7 +23,7 @@ def excludefromscan_music_folders(sections):
""" """
paths = [] paths = []
reboot = False reboot = False
api = API(item=None) api = Media()
for section in sections: for section in sections:
if section.section_type != v.PLEX_TYPE_ARTIST: if section.section_type != v.PLEX_TYPE_ARTIST:
# Only look at music libraries # Only look at music libraries

View file

@ -332,11 +332,11 @@ def _prep_playlist_stack(xml, resume):
for i, item in enumerate(xml): for i, item in enumerate(xml):
api = API(item) api = API(item)
if (app.PLAYSTATE.context_menu_play is False and if (app.PLAYSTATE.context_menu_play is False and
api.plex_type() not in (v.PLEX_TYPE_CLIP, v.PLEX_TYPE_EPISODE)): api.plex_type not in (v.PLEX_TYPE_CLIP, v.PLEX_TYPE_EPISODE)):
# If user chose to play via PMS or force transcode, do not # If user chose to play via PMS or force transcode, do not
# use the item path stored in the Kodi DB # use the item path stored in the Kodi DB
with PlexDB(lock=False) as plexdb: with PlexDB(lock=False) as plexdb:
db_item = plexdb.item_by_id(api.plex_id(), api.plex_type()) db_item = plexdb.item_by_id(api.plex_id, api.plex_type)
kodi_id = db_item['kodi_id'] if db_item else None kodi_id = db_item['kodi_id'] if db_item else None
kodi_type = db_item['kodi_type'] if db_item else None kodi_type = db_item['kodi_type'] if db_item else None
else: else:
@ -349,7 +349,7 @@ def _prep_playlist_stack(xml, resume):
kodi_id = None kodi_id = None
kodi_type = None kodi_type = None
for part, _ in enumerate(item[0]): for part, _ in enumerate(item[0]):
api.set_part_number(part) api.part = part
if kodi_id is None: if kodi_id is None:
# Need to redirect again to PKC to conclude playback # Need to redirect again to PKC to conclude playback
path = api.path(force_addon=True, force_first_media=True) path = api.path(force_addon=True, force_first_media=True)
@ -361,7 +361,7 @@ def _prep_playlist_stack(xml, resume):
# 'plugin.video.plexkodiconnect', 1) # 'plugin.video.plexkodiconnect', 1)
# path = path.replace('plugin.video.plexkodiconnect.movies', # path = path.replace('plugin.video.plexkodiconnect.movies',
# 'plugin.video.plexkodiconnect', 1) # 'plugin.video.plexkodiconnect', 1)
listitem = api.create_listitem() listitem = api.listitem()
listitem.setPath(path.encode('utf-8')) listitem.setPath(path.encode('utf-8'))
else: else:
# Will add directly via the Kodi DB # Will add directly via the Kodi DB
@ -458,16 +458,16 @@ def _conclude_playback(playqueue, pos):
return PKC listitem attached to result return PKC listitem attached to result
""" """
LOG.info('Concluding playback for playqueue position %s', pos) LOG.info('Concluding playback for playqueue position %s', pos)
listitem = transfer.PKCListItem()
item = playqueue.items[pos] item = playqueue.items[pos]
if item.xml is not None: if item.xml is not None:
# Got a Plex element # Got a Plex element
api = API(item.xml) api = API(item.xml)
api.set_part_number(item.part) api.part = item.part or 0
api.create_listitem(listitem) listitem = api.listitem(listitem=transfer.PKCListItem)
playutils = PlayUtils(api, item) playutils = PlayUtils(api, item)
playurl = playutils.getPlayUrl() playurl = playutils.getPlayUrl()
else: else:
listitem = transfer.PKCListItem()
api = None api = None
playurl = item.file playurl = item.file
if not playurl: if not playurl:
@ -514,10 +514,9 @@ def process_indirect(key, offset, resolve=True):
return return
api = API(xml[0]) api = API(xml[0])
listitem = transfer.PKCListItem() listitem = api.listitem(listitem=transfer.PKCListItem)
api.create_listitem(listitem)
playqueue = PQ.get_playqueue_from_type( playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type()]) v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type])
playqueue.clear() playqueue.clear()
item = PL.Playlist_Item() item = PL.Playlist_Item()
item.xml = xml[0] item.xml = xml[0]
@ -574,7 +573,7 @@ def play_xml(playqueue, xml, offset=None, start_plex_id=None):
else playqueue.selectedItemID else playqueue.selectedItemID
for startpos, video in enumerate(xml): for startpos, video in enumerate(xml):
api = API(video) api = API(video)
if api.plex_id() == start_item: if api.plex_id == start_item:
break break
else: else:
startpos = 0 startpos = 0

View file

@ -423,8 +423,8 @@ def playlist_item_from_xml(xml_video_element, kodi_id=None, kodi_type=None):
""" """
item = Playlist_Item() item = Playlist_Item()
api = API(xml_video_element) api = API(xml_video_element)
item.plex_id = api.plex_id() item.plex_id = api.plex_id
item.plex_type = api.plex_type() item.plex_type = api.plex_type
# item.id will only be set if you passed in an xml_video_element from e.g. # item.id will only be set if you passed in an xml_video_element from e.g.
# a playQueue # a playQueue
item.id = api.item_id() item.id = api.item_id()

View file

@ -170,32 +170,32 @@ def _full_sync():
return False return False
api = API(xml_playlist) api = API(xml_playlist)
try: try:
old_plex_ids.remove(api.plex_id()) old_plex_ids.remove(api.plex_id)
except ValueError: except ValueError:
pass pass
if not sync_plex_playlist(xml=xml_playlist): if not sync_plex_playlist(xml=xml_playlist):
continue continue
playlist = db.get_playlist(plex_id=api.plex_id()) playlist = db.get_playlist(plex_id=api.plex_id)
if not playlist: if not playlist:
LOG.debug('New Plex playlist %s discovered: %s', LOG.debug('New Plex playlist %s discovered: %s',
api.plex_id(), api.title()) api.plex_id, api.title())
try: try:
kodi_pl.create(api.plex_id()) kodi_pl.create(api.plex_id)
except PlaylistError: except PlaylistError:
LOG.info('Skipping creation of playlist %s', api.plex_id()) LOG.info('Skipping creation of playlist %s', api.plex_id)
elif playlist.plex_updatedat != api.updated_at(): elif playlist.plex_updatedat != api.updated_at():
LOG.debug('Detected changed Plex playlist %s: %s', LOG.debug('Detected changed Plex playlist %s: %s',
api.plex_id(), api.title()) api.plex_id, api.title())
# Since we are DELETING a playlist, we need to catch with path! # Since we are DELETING a playlist, we need to catch with path!
try: try:
kodi_pl.delete(playlist) kodi_pl.delete(playlist)
except PlaylistError: except PlaylistError:
LOG.info('Skipping recreation of playlist %s', api.plex_id()) LOG.info('Skipping recreation of playlist %s', api.plex_id)
else: else:
try: try:
kodi_pl.create(api.plex_id()) kodi_pl.create(api.plex_id)
except PlaylistError: except PlaylistError:
LOG.info('Could not recreate playlist %s', api.plex_id()) LOG.info('Could not recreate playlist %s', api.plex_id)
# Get rid of old Plex playlists that were deleted on the Plex side # Get rid of old Plex playlists that were deleted on the Plex side
for plex_id in old_plex_ids: for plex_id in old_plex_ids:
if isCanceled(): if isCanceled():

View file

@ -35,7 +35,7 @@ def create(plex_id):
raise PlaylistError('Could not get Plex playlist %s' % plex_id) raise PlaylistError('Could not get Plex playlist %s' % plex_id)
api = API(xml_metadata[0]) api = API(xml_metadata[0])
playlist = Playlist() playlist = Playlist()
playlist.plex_id = api.plex_id() playlist.plex_id = api.plex_id
playlist.kodi_type = v.KODI_PLAYLIST_TYPE_FROM_PLEX[api.playlist_type()] playlist.kodi_type = v.KODI_PLAYLIST_TYPE_FROM_PLEX[api.playlist_type()]
playlist.plex_name = api.title() playlist.plex_name = api.title()
playlist.plex_updatedat = api.updated_at() playlist.plex_updatedat = api.updated_at()
@ -104,24 +104,16 @@ def _write_playlist_to_file(playlist, xml):
text = '#EXTCPlayListM3U::M3U\n' text = '#EXTCPlayListM3U::M3U\n'
for element in xml: for element in xml:
api = API(element) api = API(element)
append_season_episode = False if api.plex_type == v.PLEX_TYPE_EPISODE:
if api.plex_type() == v.PLEX_TYPE_EPISODE: if api.season_number() is not None and api.index() is not None:
_, _, show, season_no, episode_no = api.episode_data()
try:
season_no = int(season_no)
episode_no = int(episode_no)
except ValueError:
pass
else:
append_season_episode = True
if append_season_episode:
text += ('#EXTINF:%s,%s S%.2dE%.2d - %s\n%s\n' text += ('#EXTINF:%s,%s S%.2dE%.2d - %s\n%s\n'
% (api.runtime(), show, season_no, episode_no, % (api.runtime(), api.show_title(),
api.season_number(), api.index(),
api.title(), api.path())) api.title(), api.path()))
else: else:
# Only append the TV show name # Only append the TV show name
text += ('#EXTINF:%s,%s - %s\n%s\n' text += ('#EXTINF:%s,%s - %s\n%s\n'
% (api.runtime(), show, api.title(), api.path())) % (api.runtime(), api.show_title(), api.title(), api.path()))
else: else:
text += ('#EXTINF:%s,%s\n%s\n' text += ('#EXTINF:%s,%s\n%s\n'
% (api.runtime(), api.title(), api.path())) % (api.runtime(), api.title(), api.path()))

View file

@ -68,7 +68,7 @@ def initialize(playlist, plex_id):
plex_id) plex_id)
raise PlaylistError('Could not initialize Plex playlist %s', plex_id) raise PlaylistError('Could not initialize Plex playlist %s', plex_id)
api = API(xml[0]) api = API(xml[0])
playlist.plex_id = api.plex_id() playlist.plex_id = api.plex_id
playlist.plex_updatedat = api.updated_at() playlist.plex_updatedat = api.updated_at()
@ -121,7 +121,7 @@ def add_items(playlist, plex_ids):
raise PlaylistError('Could not add items to a new Plex playlist %s' % raise PlaylistError('Could not add items to a new Plex playlist %s' %
playlist) playlist)
api = API(xml[0]) api = API(xml[0])
playlist.plex_id = api.plex_id() playlist.plex_id = api.plex_id
playlist.plex_updatedat = api.updated_at() playlist.plex_updatedat = api.updated_at()

View file

@ -86,7 +86,7 @@ def init_playqueue_from_plex_children(plex_id, transient_token=None):
playqueue.clear() playqueue.clear()
for i, child in enumerate(xml): for i, child in enumerate(xml):
api = API(child) api = API(child)
PL.add_item_to_playlist(playqueue, i, plex_id=api.plex_id()) PL.add_item_to_playlist(playqueue, i, plex_id=api.plex_id)
playqueue.plex_transient_token = transient_token playqueue.plex_transient_token = transient_token
LOG.debug('Firing up Kodi player') LOG.debug('Firing up Kodi player')
app.APP.player.play(playqueue.kodi_pl, None, False, 0) app.APP.player.play(playqueue.kodi_pl, None, False, 0)

View file

@ -66,7 +66,7 @@ class PlayUtils():
if path is not None and path.endswith('.strm'): if path is not None and path.endswith('.strm'):
LOG.info('.strm file detected') LOG.info('.strm file detected')
playurl = self.api.validate_playurl(path, playurl = self.api.validate_playurl(path,
self.api.plex_type(), self.api.plex_type,
force_check=True) force_check=True)
return playurl return playurl
# set to either 'Direct Stream=1' or 'Transcode=2' # set to either 'Direct Stream=1' or 'Transcode=2'
@ -78,7 +78,7 @@ class PlayUtils():
if self.mustTranscode(): if self.mustTranscode():
return return
return self.api.validate_playurl(path, return self.api.validate_playurl(path,
self.api.plex_type(), self.api.plex_type,
force_check=True) force_check=True)
def mustTranscode(self): def mustTranscode(self):
@ -93,7 +93,7 @@ class PlayUtils():
- video bitrate above specified settings bitrate - video bitrate above specified settings bitrate
if the corresponding file settings are set to 'true' if the corresponding file settings are set to 'true'
""" """
if self.api.plex_type() in (v.PLEX_TYPE_CLIP, v.PLEX_TYPE_SONG): if self.api.plex_type in (v.PLEX_TYPE_CLIP, v.PLEX_TYPE_SONG):
LOG.info('Plex clip or music track, not transcoding') LOG.info('Plex clip or music track, not transcoding')
return False return False
videoCodec = self.api.video_codec() videoCodec = self.api.video_codec()
@ -139,7 +139,7 @@ class PlayUtils():
def isDirectStream(self): def isDirectStream(self):
# Never transcode Music # Never transcode Music
if self.api.plex_type() == 'track': if self.api.plex_type == 'track':
return True return True
# set to 'Transcode=2' # set to 'Transcode=2'
if utils.settings('playType') == "2": if utils.settings('playType') == "2":

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,31 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
plex_api interfaces with all Plex Media Server (and plex.tv) xml responses
"""
from __future__ import absolute_import, division, unicode_literals
from .base import Base
from .artwork import Artwork
from .file import File
from .media import Media
from .user import User
from ..plex_db import PlexDB
class API(Base, Artwork, File, Media, User):
pass
def mass_api(xml):
"""
Pass in an entire XML PMS response with e.g. several movies or episodes
Will Look-up Kodi ids in the Plex.db for every element (thus speeding up
this process for several PMS items!)
"""
apis = [API(x) for x in xml]
with PlexDB(lock=False) as plexdb:
for api in apis:
api.check_db(plexdb=plexdb)
return apis

View file

@ -0,0 +1,428 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from re import sub
from ..kodi_db import KodiVideoDB, KodiMusicDB
from ..downloadutils import DownloadUtils as DU
from .. import utils, variables as v, app
LOG = getLogger('PLEX.api')
class Artwork(object):
def one_artwork(self, art_kind, aspect=None):
"""
aspect can be: 'square', '16:9', 'poster'. Defaults to 'poster'
"""
aspect = 'poster' if not aspect else aspect
if aspect == 'poster':
width = 1000
height = 1500
elif aspect == '16:9':
width = 1920
height = 1080
elif aspect == 'square':
width = 1000
height = 1000
else:
raise NotImplementedError('aspect ratio not yet implemented: %s'
% aspect)
artwork = self.xml.get(art_kind)
if not artwork or artwork.startswith('http'):
return artwork
if '/composite/' in artwork:
try:
# e.g. Plex collections where artwork already contains width and
# height. Need to upscale for better resolution
artwork, args = artwork.split('?')
args = dict(utils.parse_qsl(args))
width = int(args.get('width', 400))
height = int(args.get('height', 400))
# Adjust to 4k resolution 1920x1080
scaling = 1920.0 / float(max(width, height))
width = int(scaling * width)
height = int(scaling * height)
except ValueError:
# e.g. playlists
pass
artwork = '%s?width=%s&height=%s' % (artwork, width, height)
artwork = ('%s/photo/:/transcode?width=1920&height=1920&'
'minSize=1&upscale=0&url=%s'
% (app.CONN.server, utils.quote(artwork)))
artwork = self.attach_plex_token_to_url(artwork)
return artwork
def artwork_episode(self, full_artwork):
"""
Episodes are special, they only get the thumb, because all the other
artwork will be saved under season and show EXCEPT if you're
constructing a listitem and the item has NOT been synched to the Kodi db
"""
artworks = {}
# Item is currently NOT in the Kodi DB
art = self.one_artwork('thumb')
if art:
artworks['thumb'] = art
if not full_artwork:
# For episodes, only get the thumb. Everything else stemms from
# either the season or the show
return artworks
for kodi_artwork, plex_artwork in \
v.KODI_TO_PLEX_ARTWORK_EPISODE.iteritems():
art = self.one_artwork(plex_artwork)
if art:
artworks[kodi_artwork] = art
return artworks
def artwork(self, kodi_id=None, kodi_type=None, full_artwork=False):
"""
Gets the URLs to the Plex artwork. Dict keys will be missing if there
is no corresponding artwork.
Pass kodi_id and kodi_type to grab the artwork saved in the Kodi DB
(thus potentially more artwork, e.g. clearart, discart).
Output ('max' version)
{
'thumb'
'poster'
'banner'
'clearart'
'clearlogo'
'fanart'
}
'landscape' and 'icon' might be implemented later
Passing full_artwork=True returns ALL the artwork for the item, so not
just 'thumb' for episodes, but also season and show artwork
"""
if self.plex_type == v.PLEX_TYPE_EPISODE:
return self.artwork_episode(full_artwork)
artworks = {}
if kodi_id:
# in Kodi database, potentially with additional e.g. clearart
if self.plex_type in v.PLEX_VIDEOTYPES:
with KodiVideoDB(lock=False) as kodidb:
return kodidb.get_art(kodi_id, kodi_type)
else:
with KodiMusicDB(lock=False) as kodidb:
return kodidb.get_art(kodi_id, kodi_type)
for kodi_artwork, plex_artwork in v.KODI_TO_PLEX_ARTWORK.iteritems():
art = self.one_artwork(plex_artwork)
if art:
artworks[kodi_artwork] = art
if self.plex_type in (v.PLEX_TYPE_SONG, v.PLEX_TYPE_ALBUM):
# Get parent item artwork if the main item is missing artwork
if 'fanart' not in artworks:
art = self.one_artwork('parentArt')
if art:
artworks['fanart1'] = art
if 'poster' not in artworks:
art = self.one_artwork('parentThumb')
if art:
artworks['poster'] = art
if self.plex_type in (v.PLEX_TYPE_SONG,
v.PLEX_TYPE_ALBUM,
v.PLEX_TYPE_ARTIST):
# need to set poster also as thumb
art = self.one_artwork('thumb')
if art:
artworks['thumb'] = art
if self.plex_type == v.PLEX_TYPE_PLAYLIST:
art = self.one_artwork('composite')
if art:
artworks['thumb'] = art
return artworks
def fanart_artwork(self, artworks):
"""
Downloads additional fanart from third party sources (well, link to
fanart only).
"""
external_id = self.retrieve_external_item_id()
if external_id is not None:
artworks = self.lookup_fanart_tv(external_id[0], artworks)
return artworks
def set_artwork(self):
"""
Gets the URLs to the Plex artwork, or empty string if not found.
Only call on movies!
"""
artworks = {}
# Plex does not get much artwork - go ahead and get the rest from
# fanart tv only for movie or tv show
external_id = self.retrieve_external_item_id(collection=True)
if external_id is not None:
external_id, poster, background = external_id
if poster is not None:
artworks['poster'] = poster
if background is not None:
artworks['fanart'] = background
artworks = self.lookup_fanart_tv(external_id, artworks)
else:
LOG.info('Did not find a set/collection ID on TheMovieDB using %s.'
' Artwork will be missing.', self.title())
return artworks
def retrieve_external_item_id(self, collection=False):
"""
Returns the set
media_id [unicode]: the item's IMDB id for movies or tvdb id for
TV shows
poster [unicode]: path to the item's poster artwork
background [unicode]: path to the item's background artwork
The last two might be None if not found. Generally None is returned
if unsuccessful.
If not found in item's Plex metadata, check themovidedb.org.
"""
item = self.xml.attrib
media_type = self.plex_type
media_id = None
# Return the saved Plex id's, if applicable
# Always seek collection's ids since not provided by PMS
if collection is False:
if media_type == v.PLEX_TYPE_MOVIE:
media_id = self.provider('imdb')
elif media_type == v.PLEX_TYPE_SHOW:
media_id = self.provider('tvdb')
if media_id is not None:
return media_id, None, None
LOG.info('Plex did not provide ID for IMDB or TVDB. Start '
'lookup process')
else:
LOG.debug('Start movie set/collection lookup on themoviedb with %s',
item.get('title', ''))
api_key = utils.settings('themoviedbAPIKey')
if media_type == v.PLEX_TYPE_SHOW:
media_type = 'tv'
title = self.title()
# if the title has the year in remove it as tmdb cannot deal with it...
# replace e.g. 'The Americans (2015)' with 'The Americans'
title = sub(r'\s*\(\d{4}\)$', '', title, count=1)
url = 'https://api.themoviedb.org/3/search/%s' % media_type
parameters = {
'api_key': api_key,
'language': v.KODILANGUAGE,
'query': title.encode('utf-8')
}
data = DU().downloadUrl(url,
authenticate=False,
parameters=parameters,
timeout=7)
try:
data.get('test')
except AttributeError:
LOG.warning('Could not download data from FanartTV')
return
if not data.get('results'):
LOG.info('No match found on themoviedb for type: %s, title: %s',
media_type, title)
return
year = item.get('year')
match_found = None
# find year match
if year:
for entry in data['results']:
if year in entry.get('first_air_date', ''):
match_found = entry
break
elif year in entry.get('release_date', ''):
match_found = entry
break
# find exact match based on title, if we haven't found a year match
if match_found is None:
LOG.info('No themoviedb match found using year %s', year)
replacements = (
' ',
'-',
'&',
',',
':',
';'
)
for entry in data['results']:
name = entry.get('name', entry.get('title', ''))
original_name = entry.get('original_name', '')
title_alt = title.lower()
name_alt = name.lower()
org_name_alt = original_name.lower()
for replace_string in replacements:
title_alt = title_alt.replace(replace_string, '')
name_alt = name_alt.replace(replace_string, '')
org_name_alt = org_name_alt.replace(replace_string, '')
if name == title or original_name == title:
# match found for exact title name
match_found = entry
break
elif (name.split(' (')[0] == title or title_alt == name_alt or
title_alt == org_name_alt):
# match found with substituting some stuff
match_found = entry
break
# if a match was not found, we accept the closest match from TMDB
if match_found is None and data.get('results'):
LOG.info('Using very first match from themoviedb')
match_found = entry = data.get('results')[0]
if match_found is None:
LOG.info('Still no themoviedb match for type: %s, title: %s, '
'year: %s', media_type, title, year)
LOG.debug('themoviedb answer was %s', data['results'])
return
LOG.info('Found themoviedb match for %s: %s',
item.get('title'), match_found)
tmdb_id = str(entry.get('id', ''))
if tmdb_id == '':
LOG.error('No themoviedb ID found, aborting')
return
if media_type == 'multi' and entry.get('media_type'):
media_type = entry.get('media_type')
name = entry.get('name', entry.get('title'))
# lookup external tmdb_id and perform artwork lookup on fanart.tv
parameters = {'api_key': api_key}
if media_type == 'movie':
url = 'https://api.themoviedb.org/3/movie/%s' % tmdb_id
parameters['append_to_response'] = 'videos'
elif media_type == 'tv':
url = 'https://api.themoviedb.org/3/tv/%s' % tmdb_id
parameters['append_to_response'] = 'external_ids,videos'
media_id, poster, background = None, None, None
for language in [v.KODILANGUAGE, 'en']:
parameters['language'] = language
data = DU().downloadUrl(url,
authenticate=False,
parameters=parameters,
timeout=7)
try:
data.get('test')
except AttributeError:
LOG.warning('Could not download %s with parameters %s',
url, parameters)
continue
if collection is False:
if data.get('imdb_id'):
media_id = str(data.get('imdb_id'))
break
if (data.get('external_ids') and
data['external_ids'].get('tvdb_id')):
media_id = str(data['external_ids']['tvdb_id'])
break
else:
if not data.get('belongs_to_collection'):
continue
media_id = data.get('belongs_to_collection').get('id')
if not media_id:
continue
media_id = str(media_id)
LOG.debug('Retrieved collections tmdb id %s for %s',
media_id, title)
url = 'https://api.themoviedb.org/3/collection/%s' % media_id
data = DU().downloadUrl(url,
authenticate=False,
parameters=parameters,
timeout=7)
try:
data.get('poster_path')
except AttributeError:
LOG.debug('Could not find TheMovieDB poster paths for %s'
' in the language %s', title, language)
continue
if not poster and data.get('poster_path'):
poster = ('https://image.tmdb.org/t/p/original%s' %
data.get('poster_path'))
if not background and data.get('backdrop_path'):
background = ('https://image.tmdb.org/t/p/original%s' %
data.get('backdrop_path'))
return media_id, poster, background
def lookup_fanart_tv(self, media_id, artworks):
"""
perform artwork lookup on fanart.tv
media_id: IMDB id for movies, tvdb id for TV shows
"""
api_key = utils.settings('FanArtTVAPIKey')
typus = self.plex_type
if typus == v.PLEX_TYPE_SHOW:
typus = 'tv'
if typus == v.PLEX_TYPE_MOVIE:
url = 'http://webservice.fanart.tv/v3/movies/%s?api_key=%s' \
% (media_id, api_key)
elif typus == 'tv':
url = 'http://webservice.fanart.tv/v3/tv/%s?api_key=%s' \
% (media_id, api_key)
else:
# Not supported artwork
return artworks
data = DU().downloadUrl(url, authenticate=False, timeout=15)
try:
data.get('test')
except AttributeError:
LOG.error('Could not download data from FanartTV')
return artworks
fanart_tv_types = list(v.FANART_TV_TO_KODI_TYPE)
if typus == v.PLEX_TYPE_ARTIST:
fanart_tv_types.append(("thumb", "folder"))
else:
fanart_tv_types.append(("thumb", "thumb"))
prefixes = (
"hd" + typus,
"hd",
typus,
"",
)
for fanart_tv_type, kodi_type in fanart_tv_types:
# Skip the ones we already have
if kodi_type in artworks:
continue
for prefix in prefixes:
fanarttvimage = prefix + fanart_tv_type
if fanarttvimage not in data:
continue
# select image in preferred language
for entry in data[fanarttvimage]:
if entry.get("lang") == v.KODILANGUAGE:
artworks[kodi_type] = \
entry.get("url", "").replace(' ', '%20')
break
# just grab the first english OR undefinded one as fallback
# (so we're actually grabbing the more popular one)
if kodi_type not in artworks:
for entry in data[fanarttvimage]:
if entry.get("lang") in ("en", "00"):
artworks[kodi_type] = \
entry.get("url", "").replace(' ', '%20')
break
# grab extrafanarts in list
fanartcount = 1 if 'fanart' in artworks else ''
for prefix in prefixes:
fanarttvimage = prefix + 'background'
if fanarttvimage not in data:
continue
for entry in data[fanarttvimage]:
if entry.get("url") is None:
continue
artworks['fanart%s' % fanartcount] = \
entry['url'].replace(' ', '%20')
try:
fanartcount += 1
except TypeError:
fanartcount = 1
if fanartcount >= v.MAX_BACKGROUND_COUNT:
break
return artworks

View file

@ -0,0 +1,644 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from re import sub
import xbmcgui
from ..utils import cast
from ..plex_db import PlexDB
from .. import utils, timing, variables as v, app, plex_functions as PF
from .. import widgets
LOG = getLogger('PLEX.api')
class Base(object):
"""
Processes a Plex media server's XML response
xml: xml.etree.ElementTree element
"""
def __init__(self, xml):
self.xml = xml
# which media part in the XML response shall we look at if several
# media files are present for the SAME video? (e.g. a 4k and a 1080p
# version)
self.part = 0
self.mediastream = None
# Make sure we're only checking our Plex DB once
self._checked_db = False
# In order to run through the leaves of the xml only once
self._scanned_children = False
self._genres = []
self._countries = []
self._collections = []
self._people = []
self._cast = []
self._directors = []
self._writers = []
self._producers = []
self._locations = []
self._coll_match = None
# Plex DB attributes
self._section_id = None
self._kodi_id = None
self._last_sync = None
self._last_checksum = None
self._kodi_fileid = None
self._kodi_pathid = None
self._fanart_synced = None
@property
def tag(self):
"""
Returns the xml etree tag, e.g. 'Directory', 'Playlist', 'Hub', 'Video'
"""
return self.xml.tag
@property
def attrib(self):
"""
Returns the xml etree attrib dict
"""
return self.xml.attrib
@property
def plex_id(self):
"""
Returns the Plex ratingKey as an integer or None
"""
return cast(int, self.xml.get('ratingKey'))
@property
def plex_type(self):
"""
Returns the type of media, e.g. 'movie' or 'clip' for trailers as
Unicode or None.
"""
return self.xml.get('type')
@property
def section_id(self):
self.check_db()
return self._section_id
@property
def kodi_id(self):
self.check_db()
return self._kodi_id
@property
def kodi_type(self):
return v.KODITYPE_FROM_PLEXTYPE[self.plex_type]
@property
def last_sync(self):
self.check_db()
return self._last_sync
@property
def last_checksum(self):
self.check_db()
return self._last_checksum
@property
def kodi_fileid(self):
self.check_db()
return self._kodi_fileid
@property
def kodi_pathid(self):
self.check_db()
return self._kodi_pathid
@property
def fanart_synced(self):
self.check_db()
return self._fanart_synced
def check_db(self, plexdb=None):
"""
Check's whether we synched this item to Kodi. If so, then retrieve the
appropriate Kodi info like the kodi_id and kodi_fileid
Pass in a plexdb DB-connection for a faster lookup
"""
if self._checked_db:
return
self._checked_db = True
if self.plex_type == v.PLEX_TYPE_CLIP:
# Clips won't ever be synched to Kodi
return
if plexdb:
db_item = plexdb.item_by_id(self.plex_id, self.plex_type)
else:
with PlexDB(lock=False) as plexdb:
db_item = plexdb.item_by_id(self.plex_id, self.plex_type)
if not db_item:
return
self._section_id = db_item['section_id']
self._kodi_id = db_item['kodi_id']
self._last_sync = db_item['last_sync']
self._last_checksum = db_item['checksum']
if 'kodi_fileid' in db_item:
self._kodi_fileid = db_item['kodi_fileid']
if 'kodi_pathid' in db_item:
self._kodi_pathid = db_item['kodi_pathid']
if 'fanart_synced' in db_item:
self._fanart_synced = db_item['fanart_synced']
def path_and_plex_id(self):
"""
Returns the Plex key such as '/library/metadata/246922' or None
"""
return self.xml.get('key')
def item_id(self):
"""
Returns current playQueueItemID or if unsuccessful the playListItemID
as int.
If not found, None is returned
"""
return (cast(int, self.xml.get('playQueueItemID')) or
cast(int, self.xml.get('playListItemID')))
def playlist_type(self):
"""
Returns the playlist type ('video', 'audio') or None
"""
return self.xml.get('playlistType')
def library_section_id(self):
"""
Returns the id of the Plex library section (for e.g. a movies section)
as an int or None
"""
return cast(int, self.xml.get('librarySectionID'))
def guid_html_escaped(self):
"""
Returns the 'guid' attribute, e.g.
'com.plexapp.agents.thetvdb://76648/2/4?lang=en'
as an HTML-escaped string or None
"""
guid = self.xml.get('guid')
return utils.escape_html(guid) if guid else None
def date_created(self):
"""
Returns the date when this library item was created in Kodi-time as
unicode
If not found, returns 2000-01-01 10:00:00
"""
res = self.xml.get('addedAt')
return timing.plex_date_to_kodi(res) if res else '2000-01-01 10:00:00'
def updated_at(self):
"""
Returns the last time this item was updated as an int, e.g.
1524739868 or None
"""
return cast(int, self.xml.get('updatedAt'))
def checksum(self):
"""
Returns the unique int <ratingKey><updatedAt>. If updatedAt is not set,
addedAt is used.
"""
return int('%s%s' % (self.xml.get('ratingKey'),
self.xml.get('updatedAt') or
self.xml.get('addedAt', '1541572987')))
def title(self):
"""
Returns the title of the element as unicode or 'Missing Title'
"""
return self.xml.get('title', 'Missing Title')
def sorttitle(self):
"""
Returns an item's sorting name/title or the title itself if not found
"Missing Title" if both are not present
"""
return self.xml.get('titleSort',
self.xml.get('title', 'Missing Title'))
def plex_media_streams(self):
"""
Returns the media streams directly from the PMS xml.
Mind to set self.mediastream and self.part before calling this method!
"""
return self.xml[self.mediastream][self.part]
def plot(self):
"""
Returns the plot or None.
"""
return self.xml.get('summary')
def tagline(self):
"""
Returns a shorter tagline of the plot or None
"""
return self.xml.get('tagline')
def shortplot(self):
"""
Not yet implemented - returns None
"""
pass
def premiere_date(self):
"""
Returns the "originallyAvailableAt", e.g. "2018-11-16" or None
"""
return self.xml.get('originallyAvailableAt')
def kodi_premiere_date(self):
"""
Takes Plex' originallyAvailableAt of the form "yyyy-mm-dd" and returns
Kodi's "dd.mm.yyyy" or None
"""
date = self.premiere_date()
if date is None:
return
try:
date = sub(r'(\d+)-(\d+)-(\d+)', r'\3.\2.\1', date)
except Exception:
date = None
return date
def year(self):
"""
Returns the production(?) year ("year") as Unicode or None
"""
return self.xml.get('year')
def studios(self):
"""
Returns a list of the 'studio' - currently only ever 1 entry.
Or returns an empty list
"""
return [self.xml.get('studio')] if self.xml.get('studio') else []
def content_rating(self):
"""
Get the content rating or None
"""
mpaa = self.xml.get('contentRating')
if not mpaa:
return
# Convert more complex cases
if mpaa in ('NR', 'UR'):
# Kodi seems to not like NR, but will accept Rated Not Rated
mpaa = 'Rated Not Rated'
elif mpaa.startswith('gb/'):
mpaa = mpaa.replace('gb/', 'UK:', 1)
return mpaa
def rating(self):
"""
Returns the rating [float] first from 'audienceRating', if that fails
from 'rating'.
Returns 0.0 if both are not found
"""
return cast(float, self.xml.get('audienceRating',
self.xml.get('rating'))) or 0.0
def votecount(self):
"""
Not implemented by Plex yet - returns None
"""
pass
def runtime(self):
"""
Returns the total duration of the element in seconds as int.
0 if not found
"""
runtime = cast(float, self.xml.get('duration')) or 0.0
return int(runtime * v.PLEX_TO_KODI_TIMEFACTOR)
def leave_count(self):
"""
Returns the following dict or None
{
'totalepisodes': unicode('leafCount'),
'watchedepisodes': unicode('viewedLeafCount'),
'unwatchedepisodes': unicode(totalepisodes - watchedepisodes)
}
"""
try:
total = int(self.xml.attrib['leafCount'])
watched = int(self.xml.attrib['viewedLeafCount'])
return {
'totalepisodes': unicode(total),
'watchedepisodes': unicode(watched),
'unwatchedepisodes': unicode(total - watched)
}
except (KeyError, TypeError):
pass
# Stuff having to do with parent and grandparent items
######################################################
def index(self):
"""
Returns the 'index' of the element [int]. Depicts e.g. season number of
the season or the track number of the song
"""
return cast(int, self.xml.get('index'))
def show_id(self):
"""
Returns the episode's tv show's Plex id [int] or None
"""
return self.grandparent_id()
def show_title(self):
"""
Returns the episode's tv show's name/title [unicode] or None
"""
return self.grandparent_title()
def season_id(self):
"""
Returns the episode's season's Plex id [int] or None
"""
return self.parent_id()
def season_number(self):
"""
Returns the episode's season number (e.g. season '2') as an int or None
"""
return self.parent_index()
def artist_name(self):
"""
Returns the artist name for an album: first it attempts to return
'parentTitle', if that failes 'originalTitle'
"""
return self.xml.get('parentTitle', self.xml.get('originalTitle'))
def parent_id(self):
"""
Returns the 'parentRatingKey' as int or None
"""
return cast(int, self.xml.get('parentRatingKey'))
def parent_index(self):
"""
Returns the 'parentRatingKey' as int or None
"""
return cast(int, self.xml.get('parentIndex'))
def grandparent_id(self):
"""
Returns the ratingKey for the corresponding grandparent, e.g. a TV show
for episodes, or None
"""
return cast(int, self.xml.get('grandparentRatingKey'))
def grandparent_title(self):
"""
Returns the title for the corresponding grandparent, e.g. a TV show
name for episodes, or None
"""
return self.xml.get('grandparentTitle')
def disc_number(self):
"""
Returns the song's disc number as an int or None if not found
"""
return self.parent_index()
def _scan_children(self):
"""
Ensures that we're scanning the xml's subelements only once
"""
if self._scanned_children:
return
self._scanned_children = True
cast_order = 0
for child in self.xml:
if child.tag == 'Role':
self._cast.append((child.get('tag'),
child.get('thumb'),
child.get('role'),
cast_order))
cast_order += 1
elif child.tag == 'Genre':
self._genres.append(child.get('tag'))
elif child.tag == 'Country':
self._countries.append(child.get('tag'))
elif child.tag == 'Director':
self._directors.append(child.get('tag'))
elif child.tag == 'Writer':
self._writers.append(child.get('tag'))
elif child.tag == 'Producer':
self._producers.append(child.get('tag'))
elif child.tag == 'Location':
self._locations.append(child.get('path'))
elif child.tag == 'Collection':
self._collections.append((cast(int, child.get('id')),
child.get('tag')))
def cast(self):
"""
Returns a list of tuples of the cast:
[(<name of actor [unicode]>,
<thumb url [unicode, may be None]>,
<role [unicode, may be None]>,
<order of appearance [int]>)]
"""
self._scan_children()
return self._cast
def genres(self):
"""
Returns a list of genres found
"""
self._scan_children()
return self._genres
def countries(self):
"""
Returns a list of all countries
"""
self._scan_children()
return self._countries
def directors(self):
"""
Returns a list of all directors
"""
self._scan_children()
return self._directors
def writers(self):
"""
Returns a list of all writers
"""
self._scan_children()
return self._writers
def producers(self):
"""
Returns a list of all producers
"""
self._scan_children()
return self._producers
def tv_show_path(self):
"""
Returns the direct path to the TV show, e.g. '\\NAS\tv\series'
or None
"""
self._scan_children()
if self._locations:
return self._locations[0]
def collections(self):
"""
Returns a list of tuples of the collection id and tags or an empty list
[(<collection id 1>, <collection name 1>), ...]
"""
self._scan_children()
return self._collections
def people(self):
"""
Returns a dict with lists of tuples:
{
'actor': [(<name of actor [unicode]>,
<thumb url [unicode, may be None]>,
<role [unicode, may be None]>,
<order of appearance [int]>)]
'director': [..., (<name>, ), ...],
'writer': [..., (<name>, ), ...]
}
Everything in unicode, except <cast order> which is an int.
Only <art-url> and <role> may be None if not found.
"""
self._scan_children()
return {
'actor': self._cast,
'director': [(x, ) for x in self._directors],
'writer': [(x, ) for x in self._writers]
}
def provider(self, providername=None):
"""
providername: e.g. 'imdb', 'tvdb'
Return IMDB, e.g. "tt0903624". Returns None if not found
"""
item = self.xml.get('guid')
if not item:
return
if providername == 'imdb':
regex = utils.REGEX_IMDB
elif providername == 'tvdb':
# originally e.g. com.plexapp.agents.thetvdb://276564?lang=en
regex = utils.REGEX_TVDB
else:
raise NotImplementedError('Not implemented: %s' % providername)
provider = regex.findall(item)
try:
provider = provider[0]
except IndexError:
provider = None
return provider
def extras(self):
"""
Returns an iterator for etree elements for each extra, e.g. trailers
Returns None if no extras are found
"""
extras = self.xml.find('Extras')
if not extras:
return
return (x for x in extras)
def trailer(self):
"""
Returns the URL for a single trailer (local trailer preferred; first
trailer found returned) or an add-on path to list all Plex extras
if the user setting showExtrasInsteadOfTrailer is set.
Returns None if nothing is found.
"""
url = None
for extras in self.xml.iterfind('Extras'):
# There will always be only 1 extras element
if (len(extras) > 0 and
app.SYNC.show_extras_instead_of_playing_trailer):
return ('plugin://%s?mode=route_to_extras&plex_id=%s'
% (v.ADDON_ID, self.plex_id))
for extra in extras:
typus = cast(int, extra.get('extraType'))
if typus != 1:
# Skip non-trailers
continue
if extra.get('guid', '').startswith('file:'):
url = extra.get('ratingKey')
# Always prefer local trailers (first one listed)
break
elif not url:
url = extra.get('ratingKey')
if url:
url = ('plugin://%s.movies/?plex_id=%s&plex_type=%s&mode=play'
% (v.ADDON_ID, url, v.PLEX_TYPE_CLIP))
return url
def listitem(self, listitem=xbmcgui.ListItem):
"""
Returns a xbmcgui.ListItem() (or PKCListItem) for this Plex element
"""
item = widgets.generate_item(self)
item = widgets.prepare_listitem(item)
return widgets.create_listitem(item, as_tuple=False, listitem=listitem)
def collections_match(self, section_id):
"""
Downloads one additional xml from the PMS in order to return a list of
tuples [(collection_id, plex_id), ...] for all collections of the
current item's Plex library sectin
Pass in the collection id of e.g. the movie's metadata
"""
if self._coll_match is None:
self._coll_match = PF.collections(section_id)
if self._coll_match is None:
LOG.error('Could not download collections for %s',
self.library_section_id())
self._coll_match = []
self._coll_match = \
[(utils.cast(int, x.get('index')),
utils.cast(int, x.get('ratingKey'))) for x in self._coll_match]
return self._coll_match
@staticmethod
def attach_plex_token_to_url(url):
"""
Returns an extended URL with the Plex token included as 'X-Plex-Token='
url may or may not already contain a '?'
"""
if not app.ACCOUNT.pms_token:
return url
if '?' not in url:
return "%s?X-Plex-Token=%s" % (url, app.ACCOUNT.pms_token)
else:
return "%s&X-Plex-Token=%s" % (url, app.ACCOUNT.pms_token)
@staticmethod
def list_to_string(input_list):
"""
Concatenates input_list (list of unicodes) with a separator ' / '
Returns None if the list was empty
"""
return ' / '.join(input_list) or None

View file

@ -0,0 +1,168 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from ..utils import cast
from .. import utils, variables as v, app
def _transcode_image_path(key, AuthToken, path, width, height):
"""
Transcode Image support
parameters:
key
AuthToken
path - source path of current XML: path[srcXML]
width
height
result:
final path to image file
"""
# external address - can we get a transcoding request for external images?
if key.startswith('http'):
path = key
elif key.startswith('/'): # internal full path.
path = 'http://127.0.0.1:32400' + key
else: # internal path, add-on
path = 'http://127.0.0.1:32400' + path + '/' + key
# This is bogus (note the extra path component) but ATV is stupid when it
# comes to caching images, it doesn't use querystrings. Fortunately PMS is
# lenient...
transcode_path = ('/photo/:/transcode/%sx%s/%s'
% (width, height, utils.quote_plus(path)))
args = {
'width': width,
'height': height,
'url': path
}
if AuthToken:
args['X-Plex-Token'] = AuthToken
return utils.extend_url(transcode_path, args)
class File(object):
def path(self, force_first_media=True, force_addon=False,
direct_paths=None):
"""
Returns a "fully qualified path": add-on paths or direct paths
depending on the current settings. Will NOT valide the playurl
Returns unicode or None if something went wrong.
Pass direct_path=True if you're calling from another Plex python
instance - because otherwise direct paths will evaluate to False!
"""
direct_paths = direct_paths or app.SYNC.direct_paths
filename = self.file_path(force_first_media=force_first_media)
if (not direct_paths or force_addon or
self.plex_type == v.PLEX_TYPE_CLIP):
if filename and '/' in filename:
filename = filename.rsplit('/', 1)
elif filename:
filename = filename.rsplit('\\', 1)
try:
filename = filename[1]
except (TypeError, IndexError):
filename = None
# Set plugin path and media flags using real filename
if self.plex_type == v.PLEX_TYPE_EPISODE:
# need to include the plex show id in the path
path = ('plugin://plugin.video.plexkodiconnect.tvshows/%s/'
% self.grandparent_id())
else:
path = 'plugin://%s/' % v.ADDON_TYPE[self.plex_type]
path = ('%s?plex_id=%s&plex_type=%s&mode=play&filename=%s'
% (path, self.plex_id, self.plex_type, filename))
else:
# Direct paths is set the Kodi way
path = self.validate_playurl(filename,
self.plex_type,
omit_check=True)
return path
def directory_path(self, section_id=None, plex_type=None, old_key=None,
synched=True):
key = self.xml.get('fastKey')
if not key:
key = self.xml.get('key')
if old_key:
key = '%s/%s' % (old_key, key)
elif not key.startswith('/'):
key = '/library/sections/%s/%s' % (section_id, key)
params = {
'mode': 'browseplex',
'key': key,
'plex_type': plex_type or self.plex_type
}
if not synched:
# No item to be found in the Kodi DB
params['synched'] = 'false'
if self.xml.get('prompt'):
# User input needed, e.g. search for a movie or episode
params['prompt'] = self.xml.get('prompt')
if section_id:
params['id'] = section_id
return utils.extend_url('plugin://%s/' % v.ADDON_ID, params)
def file_name(self, force_first_media=False):
"""
Returns only the filename, e.g. 'movie.mkv' as unicode or None if not
found
"""
ans = self.file_path(force_first_media=force_first_media)
if ans is None:
return
if "\\" in ans:
# Local path
filename = ans.rsplit("\\", 1)[1]
else:
try:
# Network share
filename = ans.rsplit("/", 1)[1]
except IndexError:
# E.g. certain Plex channels
filename = None
return filename
def file_path(self, force_first_media=False):
"""
Returns the direct path to this item, e.g. '\\NAS\movies\movie.mkv'
as unicode or None
force_first_media=True:
will always use 1st media stream, e.g. when several different
files are present for the same PMS item
"""
if self.mediastream is None and force_first_media is False:
if self.mediastream_number() is None:
return
try:
if force_first_media is False:
ans = self.xml[self.mediastream][self.part].attrib['file']
else:
ans = self.xml[0][self.part].attrib['file']
except (TypeError, AttributeError, IndexError, KeyError):
return
return ans
def get_picture_path(self):
"""
Returns the item's picture path (transcode, if necessary) as string.
Will always use addon paths, never direct paths
"""
path = self.xml[0][0].get('key')
extension = path[path.rfind('.'):].lower()
if app.SYNC.force_transcode_pix or extension not in v.KODI_SUPPORTED_IMAGES:
# Let Plex transcode
# max width/height supported by plex image transcoder is 1920x1080
path = app.CONN.server + _transcode_image_path(
path,
app.ACCOUNT.pms_token,
"%s%s" % (app.CONN.server, path),
1920,
1080)
else:
path = self.attach_plex_token_to_url('%s%s' % (app.CONN.server, path))
# Attach Plex id to url to let it be picked up by our playqueue agent
# later
return '%s&plex_id=%s' % (path, self.plex_id)

View file

@ -0,0 +1,410 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from ..utils import cast
from ..downloadutils import DownloadUtils as DU
from .. import utils, variables as v, app, path_ops, clientinfo
LOG = getLogger('PLEX.api')
class Media(object):
def should_stream(self):
"""
Returns True if the item's 'optimizedForStreaming' is set, False other-
wise
"""
return cast(bool, self.xml[0].get('optimizedForStreaming')) or False
def _from_part_or_media(self, key):
"""
Retrieves XML data 'key' first from the active part. If unsuccessful,
tries to retrieve the data from the Media response part.
If all fails, None is returned.
"""
return self.xml[0][self.part].get(key, self.xml[0].get(key))
def video_codec(self):
"""
Returns the video codec and resolution for the child and part selected.
If any data is not found on a part-level, the Media-level data is
returned.
If that also fails (e.g. for old trailers, None is returned)
Output:
{
'videocodec': xxx, e.g. 'h264'
'resolution': xxx, e.g. '720' or '1080'
'height': xxx, e.g. '816'
'width': xxx, e.g. '1920'
'aspectratio': xxx, e.g. '1.78'
'bitrate': xxx, e.g. '10642'
'container': xxx e.g. 'mkv',
'bitDepth': xxx e.g. '8', '10'
}
"""
answ = {
'videocodec': self._from_part_or_media('videoCodec'),
'resolution': self._from_part_or_media('videoResolution'),
'height': self._from_part_or_media('height'),
'width': self._from_part_or_media('width'),
'aspectratio': self._from_part_or_media('aspectratio'),
'bitrate': self._from_part_or_media('bitrate'),
'container': self._from_part_or_media('container'),
}
try:
answ['bitDepth'] = self.xml[0][self.part][self.mediastream].get('bitDepth')
except (TypeError, AttributeError, KeyError, IndexError):
answ['bitDepth'] = None
return answ
def mediastreams(self):
"""
Returns the media streams for metadata purposes
Output: each track contains a dictionaries
{
'video': videotrack-list, 'codec', 'height', 'width',
'aspect', 'video3DFormat'
'audio': audiotrack-list, 'codec', 'channels',
'language'
'subtitle': list of subtitle languages (or "Unknown")
}
"""
videotracks = []
audiotracks = []
subtitlelanguages = []
try:
# Sometimes, aspectratio is on the "toplevel"
aspect = cast(float, self.xml[0].get('aspectRatio'))
except IndexError:
# There is no stream info at all, returning empty
return {
'video': videotracks,
'audio': audiotracks,
'subtitle': subtitlelanguages
}
# Loop over parts
for child in self.xml[0]:
container = child.get('container')
# Loop over Streams
for stream in child:
media_type = int(stream.get('streamType', 999))
track = {}
if media_type == 1: # Video streams
if 'codec' in stream.attrib:
track['codec'] = stream.get('codec').lower()
if "msmpeg4" in track['codec']:
track['codec'] = "divx"
elif "mpeg4" in track['codec']:
pass
elif "h264" in track['codec']:
if container in ("mp4", "mov", "m4v"):
track['codec'] = "avc1"
track['height'] = cast(int, stream.get('height'))
track['width'] = cast(int, stream.get('width'))
# track['Video3DFormat'] = item.get('Video3DFormat')
track['aspect'] = cast(float,
stream.get('aspectRatio') or aspect)
track['duration'] = self.runtime()
track['video3DFormat'] = None
videotracks.append(track)
elif media_type == 2: # Audio streams
if 'codec' in stream.attrib:
track['codec'] = stream.get('codec').lower()
if ("dca" in track['codec'] and
"ma" in stream.get('profile', '').lower()):
track['codec'] = "dtshd_ma"
track['channels'] = cast(int, stream.get('channels'))
# 'unknown' if we cannot get language
track['language'] = stream.get('languageCode',
utils.lang(39310).lower())
audiotracks.append(track)
elif media_type == 3: # Subtitle streams
# 'unknown' if we cannot get language
subtitlelanguages.append(
stream.get('languageCode', utils.lang(39310)).lower())
return {
'video': videotracks,
'audio': audiotracks,
'subtitle': subtitlelanguages
}
def mediastream_number(self):
"""
Returns the Media stream as an int (mostly 0). Will let the user choose
if several media streams are present for a PMS item (if settings are
set accordingly)
Returns None if the user aborted selection (leaving self.mediastream at
its default of None)
"""
# How many streams do we have?
count = 0
for entry in self.xml.iterfind('./Media'):
count += 1
if (count > 1 and (
(self.plex_type != v.PLEX_TYPE_CLIP and
utils.settings('bestQuality') == 'false')
or
(self.plex_type == v.PLEX_TYPE_CLIP and
utils.settings('bestTrailer') == 'false'))):
# Several streams/files available.
dialoglist = []
for entry in self.xml.iterfind('./Media'):
# Get additional info (filename / languages)
if 'file' in entry[0].attrib:
option = entry[0].get('file')
option = path_ops.basename(option)
else:
option = self.title() or ''
# Languages of audio streams
languages = []
for stream in entry[0]:
if (cast(int, stream.get('streamType')) == 1 and
'language' in stream.attrib):
language = stream.get('language')
languages.append(language)
languages = ', '.join(languages)
if languages:
if option:
option = '%s (%s): ' % (option, languages)
else:
option = '%s: ' % languages
else:
option = '%s ' % option
if 'videoResolution' in entry.attrib:
res = entry.get('videoResolution')
option = '%s%sp ' % (option, res)
if 'videoCodec' in entry.attrib:
codec = entry.get('videoCodec')
option = '%s%s' % (option, codec)
option = option.strip() + ' - '
if 'audioProfile' in entry.attrib:
profile = entry.get('audioProfile')
option = '%s%s ' % (option, profile)
if 'audioCodec' in entry.attrib:
codec = entry.get('audioCodec')
option = '%s%s ' % (option, codec)
option = cast(str, option.strip())
dialoglist.append(option)
media = utils.dialog('select', 'Select stream', dialoglist)
LOG.info('User chose media stream number: %s', media)
if media == -1:
LOG.info('User cancelled media stream selection')
return
else:
media = 0
self.mediastream = media
return media
def transcode_video_path(self, action, quality=None):
"""
To be called on a VIDEO level of PMS xml response!
Transcode Video support; returns the URL to get a media started
Input:
action 'DirectStream' or 'Transcode'
quality: {
'videoResolution': e.g. '1024x768',
'videoQuality': e.g. '60',
'maxVideoBitrate': e.g. '2000' (in kbits)
}
(one or several of these options)
Output:
final URL to pull in PMS transcoder
TODO: mediaIndex
"""
if self.mediastream is None and self.mediastream_number() is None:
return
quality = {} if quality is None else quality
xargs = clientinfo.getXArgsDeviceInfo()
# For DirectPlay, path/key of PART is needed
# trailers are 'clip' with PMS xmls
if action == "DirectStream":
path = self.xml[self.mediastream][self.part].get('key')
url = app.CONN.server + path
# e.g. Trailers already feature an '?'!
return utils.extend_url(url, xargs)
# For Transcoding
headers = {
'X-Plex-Platform': 'Android',
'X-Plex-Platform-Version': '7.0',
'X-Plex-Product': 'Plex for Android',
'X-Plex-Version': '5.8.0.475'
}
# Path/key to VIDEO item of xml PMS response is needed, not part
path = self.xml.get('key')
transcode_path = app.CONN.server + \
'/video/:/transcode/universal/start.m3u8'
args = {
'audioBoost': utils.settings('audioBoost'),
'autoAdjustQuality': 0,
'directPlay': 0,
'directStream': 1,
'protocol': 'hls', # seen in the wild: 'dash', 'http', 'hls'
'session': v.PKC_MACHINE_IDENTIFIER, # TODO: create new unique id
'fastSeek': 1,
'path': path,
'mediaIndex': self.mediastream,
'partIndex': self.part,
'hasMDE': 1,
'location': 'lan',
'subtitleSize': utils.settings('subtitleSize')
}
LOG.debug("Setting transcode quality to: %s", quality)
xargs.update(headers)
xargs.update(args)
xargs.update(quality)
return utils.extend_url(transcode_path, xargs)
def cache_external_subs(self):
"""
Downloads external subtitles temporarily to Kodi and returns a list
of their paths
"""
externalsubs = []
try:
mediastreams = self.xml[0][self.part]
except (TypeError, KeyError, IndexError):
return
kodiindex = 0
fileindex = 0
for stream in mediastreams:
# Since plex returns all possible tracks together, have to pull
# only external subtitles - only for these a 'key' exists
if cast(int, stream.get('streamType')) != 3:
# Not a subtitle
continue
# Only set for additional external subtitles NOT lying beside video
key = stream.get('key')
# Only set for dedicated subtitle files lying beside video
# ext = stream.attrib.get('format')
if key:
# We do know the language - temporarily download
if stream.get('languageCode') is not None:
language = stream.get('languageCode')
codec = stream.get('codec')
path = self.download_external_subtitles(
"{server}%s" % key,
"subtitle%02d.%s.%s" % (fileindex, language, codec))
fileindex += 1
# We don't know the language - no need to download
else:
path = self.attach_plex_token_to_url(
"%s%s" % (app.CONN.server, key))
externalsubs.append(path)
kodiindex += 1
LOG.info('Found external subs: %s', externalsubs)
return externalsubs
@staticmethod
def download_external_subtitles(url, filename):
"""
One cannot pass the subtitle language for ListItems. Workaround; will
download the subtitle at url to the Kodi PKC directory in a temp dir
Returns the path to the downloaded subtitle or None
"""
path = path_ops.path.join(v.EXTERNAL_SUBTITLE_TEMP_PATH, filename)
response = DU().downloadUrl(url, return_response=True)
try:
response.status_code
except AttributeError:
LOG.error('Could not temporarily download subtitle %s', url)
return
else:
LOG.debug('Writing temp subtitle to %s', path)
with open(path_ops.encode_path(path), 'wb') as filer:
filer.write(response.content)
return path
def validate_playurl(self, path, typus, force_check=False, folder=False,
omit_check=False):
"""
Returns a valid path for Kodi, e.g. with '\' substituted to '\\' in
Unicode. Returns None if this is not possible
path : Unicode
typus : Plex type from PMS xml
force_check : Will always try to check validity of path
Will also skip confirmation dialog if path not found
folder : Set to True if path is a folder
omit_check : Will entirely omit validity check if True
"""
if path is None:
return
typus = v.REMAP_TYPE_FROM_PLEXTYPE[typus]
if app.SYNC.remap_path:
path = path.replace(getattr(app.SYNC, 'remapSMB%sOrg' % typus),
getattr(app.SYNC, 'remapSMB%sNew' % typus),
1)
# There might be backslashes left over:
path = path.replace('\\', '/')
elif app.SYNC.replace_smb_path:
if path.startswith('\\\\'):
path = 'smb:' + path.replace('\\', '/')
if app.SYNC.escape_path:
try:
protocol, hostname, args = path.split(':', 2)
except ValueError:
pass
else:
args = utils.quote(args)
path = '%s:%s:%s' % (protocol, hostname, args)
if (app.SYNC.path_verified and not force_check) or omit_check:
return path
# exist() needs a / or \ at the end to work for directories
if not folder:
# files
check = path_ops.exists(path)
else:
# directories
if "\\" in path:
if not path.endswith('\\'):
# Add the missing backslash
check = path_ops.exists(path + "\\")
else:
check = path_ops.exists(path)
else:
if not path.endswith('/'):
check = path_ops.exists(path + "/")
else:
check = path_ops.exists(path)
if not check:
if force_check is False:
# Validate the path is correct with user intervention
if self.ask_to_validate(path):
app.APP.stop_threads(block=False)
path = None
app.SYNC.path_verified = True
else:
path = None
elif not force_check:
# Only set the flag if we were not force-checking the path
app.SYNC.path_verified = True
return path
@staticmethod
def ask_to_validate(url):
"""
Displays a YESNO dialog box:
Kodi can't locate file: <url>. Please verify the path.
You may need to verify your network credentials in the
add-on settings or use different Plex paths. Stop syncing?
Returns True if sync should stop, else False
"""
LOG.warn('Cannot access file: %s', url)
# Kodi cannot locate the file #s. Please verify your PKC settings. Stop
# syncing?
return utils.yesno_dialog(utils.lang(29999), utils.lang(39031) % url)

View file

@ -0,0 +1,59 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from ..utils import cast
from .. import timing, variables as v, app
class User(object):
def viewcount(self):
"""
Returns the play count for the item as an int or the int 0 if not found
"""
return cast(int, self.xml.get('viewCount')) or 0
def resume_point(self):
"""
Returns the resume point of time in seconds as float. 0.0 if not found
"""
resume = cast(float, self.xml.get('viewOffset')) or 0.0
return resume * v.PLEX_TO_KODI_TIMEFACTOR
def resume_point_plex(self):
"""
Returns the resume point of time in microseconds as float.
0.0 if not found
"""
return cast(float, self.xml.get('viewOffset')) or 0.0
def userrating(self):
"""
Returns the userRating [int].
If the user chose to replace user ratings with the number of different
file versions for a specific video, that number is returned instead
(at most 10)
0 is returned if something goes wrong
"""
if (app.SYNC.indicate_media_versions is True and
self.plex_type in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_EPISODE)):
userrating = 0
for _ in self.xml.findall('./Media'):
userrating += 1
# Don't show a value of '1' - which we'll always have for normal
# Plex library items
return 0 if userrating == 1 else min(userrating, 10)
else:
return cast(int, self.xml.get('userRating')) or 0
def lastplayed(self):
"""
Returns the Kodi timestamp [unicode] for the last point of time, when
this item was played.
Returns None if this fails - item has never been played
"""
try:
return timing.plex_date_to_kodi(int(self.xml.get('lastViewedAt')))
except TypeError:
pass

View file

@ -88,10 +88,10 @@ class PlexCompanion(backgroundthread.KillableThread):
LOG.error('Could not download Plex metadata for: %s', data) LOG.error('Could not download Plex metadata for: %s', data)
return return
api = API(xml[0]) api = API(xml[0])
if api.plex_type() == v.PLEX_TYPE_ALBUM: if api.plex_type == v.PLEX_TYPE_ALBUM:
LOG.debug('Plex music album detected') LOG.debug('Plex music album detected')
PQ.init_playqueue_from_plex_children( PQ.init_playqueue_from_plex_children(
api.plex_id(), api.plex_id,
transient_token=data.get('token')) transient_token=data.get('token'))
elif data['containerKey'].startswith('/playQueues/'): elif data['containerKey'].startswith('/playQueues/'):
_, container_key, _ = PF.ParseContainerKey(data['containerKey']) _, container_key, _ = PF.ParseContainerKey(data['containerKey'])
@ -104,7 +104,7 @@ class PlexCompanion(backgroundthread.KillableThread):
icon='{error}') icon='{error}')
return return
playqueue = PQ.get_playqueue_from_type( playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type()]) v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type])
playqueue.clear() playqueue.clear()
PL.get_playlist_details_from_xml(playqueue, xml) PL.get_playlist_details_from_xml(playqueue, xml)
playqueue.plex_transient_token = data.get('token') playqueue.plex_transient_token = data.get('token')
@ -117,8 +117,8 @@ class PlexCompanion(backgroundthread.KillableThread):
app.CONN.plex_transient_token = data.get('token') app.CONN.plex_transient_token = data.get('token')
if data.get('offset') != '0': if data.get('offset') != '0':
app.PLAYSTATE.resume_playback = True app.PLAYSTATE.resume_playback = True
playback.playback_triage(api.plex_id(), playback.playback_triage(api.plex_id,
api.plex_type(), api.plex_type,
resolve=False) resolve=False)
@staticmethod @staticmethod
@ -153,7 +153,7 @@ class PlexCompanion(backgroundthread.KillableThread):
return return
api = API(xml[0]) api = API(xml[0])
playqueue = PQ.get_playqueue_from_type( playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type()]) v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type])
update_playqueue_from_PMS(playqueue, update_playqueue_from_PMS(playqueue,
playqueue_id=container_key, playqueue_id=container_key,
repeat=query.get('repeat'), repeat=query.get('repeat'),

View file

@ -1029,40 +1029,3 @@ def GetUserArtworkURL(username):
url = user.thumb url = user.thumb
LOG.debug("Avatar url for user %s is: %s", username, url) LOG.debug("Avatar url for user %s is: %s", username, url)
return url return url
def transcode_image_path(key, AuthToken, path, width, height):
"""
Transcode Image support
parameters:
key
AuthToken
path - source path of current XML: path[srcXML]
width
height
result:
final path to image file
"""
# external address - can we get a transcoding request for external images?
if key.startswith('http://') or key.startswith('https://'):
path = key
elif key.startswith('/'): # internal full path.
path = 'http://127.0.0.1:32400' + key
else: # internal path, add-on
path = 'http://127.0.0.1:32400' + path + '/' + key
# This is bogus (note the extra path component) but ATV is stupid when it
# comes to caching images, it doesn't use querystrings. Fortunately PMS is
# lenient...
path = path.encode('utf-8')
transcode_path = ('/photo/:/transcode/%sx%s/%s'
% (width, height, utils.quote_plus(path)))
transcode_path = transcode_path.decode('utf-8')
args = {
'width': width,
'height': height,
'url': path
}
if AuthToken:
args['X-Plex-Token'] = AuthToken
return utils.extend_url(transcode_path, args)

View file

@ -12,7 +12,6 @@ import xbmc
import xbmcgui import xbmcgui
LOG = getLogger('PLEX.transfer') LOG = getLogger('PLEX.transfer')
MONITOR = xbmc.Monitor()
WINDOW = xbmcgui.Window(10000) WINDOW = xbmcgui.Window(10000)
WINDOW_UPSTREAM = 'plexkodiconnect.result.upstream'.encode('utf-8') WINDOW_UPSTREAM = 'plexkodiconnect.result.upstream'.encode('utf-8')
WINDOW_DOWNSTREAM = 'plexkodiconnect.result.downstream'.encode('utf-8') WINDOW_DOWNSTREAM = 'plexkodiconnect.result.downstream'.encode('utf-8')
@ -21,32 +20,47 @@ WINDOW_COMMAND = 'plexkodiconnect.command'.encode('utf-8')
def cast(func, value): def cast(func, value):
""" """
Cast the specified value to the specified type (returned by func). Currently this Cast the specified value to the specified type (returned by func). Currently
only support int, float, bool. Should be extended if needed. this only support int, float, bool. Should be extended if needed.
Parameters: Parameters:
func (func): Calback function to used cast to type (int, bool, float). func (func): Calback function to used cast to type (int, bool, float).
value (any): value to be cast and returned. value (any): value to be cast and returned.
Returns None if something goes wrong
""" """
if value is not None: if value is None:
if func == bool: return value
return bool(int(value)) elif func == bool:
elif func == unicode: return bool(int(value))
if isinstance(value, (int, long, float)): elif func == unicode:
return unicode(value) if isinstance(value, (int, long, float)):
else: return unicode(value)
return value.decode('utf-8') elif isinstance(value, unicode):
elif func == str: return value
if isinstance(value, (int, long, float)): else:
return str(value) return value.decode('utf-8')
else: elif func == str:
return value.encode('utf-8') if isinstance(value, (int, long, float)):
elif func in (int, float): return str(value)
elif isinstance(value, str):
return value
else:
return value.encode('utf-8')
elif func == int:
try:
return int(value)
except ValueError:
try: try:
return func(value) # Converting e.g. '8.0' fails; need to convert to float first
return int(float(value))
except ValueError: except ValueError:
return float('nan') return
return func(value) elif func == float:
return value try:
return float(value)
except ValueError:
return
return func(value)
def kodi_window(property, value=None, clear=False): def kodi_window(property, value=None, clear=False):
@ -67,8 +81,7 @@ def plex_command(value):
safe - let's hope the Kodi user can't click fast enough safe - let's hope the Kodi user can't click fast enough
""" """
while kodi_window(WINDOW_COMMAND): while kodi_window(WINDOW_COMMAND):
if MONITOR.waitForAbort(20): xbmc.sleep(50)
return
kodi_window(WINDOW_COMMAND, value=value) kodi_window(WINDOW_COMMAND, value=value)
@ -114,6 +127,7 @@ def wait_for_transfer(source='main'):
Set source='default' if you wait for data FROM another Python default.py Set source='default' if you wait for data FROM another Python default.py
instance, 'main' if your default.py needs to wait for the main thread instance, 'main' if your default.py needs to wait for the main thread
""" """
LOG.debug('Waiting for transfer from %s', source)
window = WINDOW_DOWNSTREAM if source == 'main' else WINDOW_UPSTREAM window = WINDOW_DOWNSTREAM if source == 'main' else WINDOW_UPSTREAM
result = '' result = ''
while not result: while not result:
@ -123,8 +137,7 @@ def wait_for_transfer(source='main'):
LOG.debug('Received') LOG.debug('Received')
result = json.loads(result) result = json.loads(result)
return de_serialize(result) return de_serialize(result)
elif MONITOR.waitForAbort(0.05): xbmc.sleep(50)
return
def convert_pkc_to_listitem(pkc_listitem): def convert_pkc_to_listitem(pkc_listitem):
@ -134,7 +147,8 @@ def convert_pkc_to_listitem(pkc_listitem):
data = pkc_listitem.data data = pkc_listitem.data
listitem = xbmcgui.ListItem(label=data.get('label'), listitem = xbmcgui.ListItem(label=data.get('label'),
label2=data.get('label2'), label2=data.get('label2'),
path=data.get('path')) path=data.get('path'),
offscreen=True)
if data['info']: if data['info']:
listitem.setInfo(**data['info']) listitem.setInfo(**data['info'])
for stream in data['stream_info']: for stream in data['stream_info']:
@ -147,6 +161,8 @@ def convert_pkc_to_listitem(pkc_listitem):
listitem.setProperty(key, cast(str, value)) listitem.setProperty(key, cast(str, value))
if data['subtitles']: if data['subtitles']:
listitem.setSubtitles(data['subtitles']) listitem.setSubtitles(data['subtitles'])
if data['contextmenu']:
listitem.addContextMenuItems(data['contextmenu'])
return listitem return listitem
@ -157,7 +173,7 @@ class PKCListItem(object):
WARNING: set/get path only via setPath and getPath! (not getProperty) WARNING: set/get path only via setPath and getPath! (not getProperty)
""" """
def __init__(self, label=None, label2=None, path=None): def __init__(self, label=None, label2=None, path=None, offscreen=True):
self.data = { self.data = {
'stream_info': [], # (type, values: dict { label: value }) 'stream_info': [], # (type, values: dict { label: value })
'art': {}, # dict 'art': {}, # dict
@ -167,9 +183,10 @@ class PKCListItem(object):
'path': path, # string 'path': path, # string
'property': {}, # (key, value) 'property': {}, # (key, value)
'subtitles': [], # strings 'subtitles': [], # strings
'contextmenu': None
} }
def addContextMenuItems(self, items, replaceItems): def addContextMenuItems(self, items):
""" """
Adds item(s) to the context menu for media lists. Adds item(s) to the context menu for media lists.
@ -187,7 +204,7 @@ class PKCListItem(object):
Once you use a keyword, all following arguments require the keyword. Once you use a keyword, all following arguments require the keyword.
""" """
raise NotImplementedError self.data['contextmenu'] = items
def addStreamInfo(self, type, values): def addStreamInfo(self, type, values):
""" """

View file

@ -20,6 +20,12 @@ from functools import wraps
import hashlib import hashlib
import re import re
import gc import gc
try:
from multiprocessing.pool import ThreadPool
SUPPORTS_POOL = True
except Exception:
SUPPORTS_POOL = False
import xbmc import xbmc
import xbmcaddon import xbmcaddon
@ -262,11 +268,13 @@ class AttributeDict(dict):
def cast(func, value): def cast(func, value):
""" """
Cast the specified value to the specified type (returned by func). Currently this Cast the specified value to the specified type (returned by func). Currently
only support int, float, bool. Should be extended if needed. this only support int, float, bool. Should be extended if needed.
Parameters: Parameters:
func (func): Calback function to used cast to type (int, bool, float). func (func): Calback function to used cast to type (int, bool, float).
value (any): value to be cast and returned. value (any): value to be cast and returned.
Returns None if something goes wrong
""" """
if value is None: if value is None:
return value return value
@ -286,11 +294,20 @@ def cast(func, value):
return value return value
else: else:
return value.encode('utf-8') return value.encode('utf-8')
elif func in (int, float): elif func == int:
try: try:
return func(value) return int(value)
except ValueError: except ValueError:
return float('nan') try:
# Converting e.g. '8.0' fails; need to convert to float first
return int(float(value))
except ValueError:
return
elif func == float:
try:
return float(value)
except ValueError:
return
return func(value) return func(value)
@ -930,6 +947,27 @@ def generate_file_md5(path):
return m.hexdigest().decode('utf-8') return m.hexdigest().decode('utf-8')
def process_method_on_list(method_to_run, items):
"""
helper method that processes a method on each item with pooling if the
system supports it
"""
all_items = []
if SUPPORTS_POOL:
pool = ThreadPool()
try:
all_items = pool.map(method_to_run, items)
except Exception:
# catch exception to prevent threadpool running forever
ERROR(notify=True)
pool.close()
pool.join()
else:
all_items = [method_to_run(item) for item in items]
all_items = filter(None, all_items)
return all_items
############################################################################### ###############################################################################
# WRAPPERS # WRAPPERS

View file

@ -8,18 +8,11 @@ e.g. plugin://... calls. Hence be careful to only rely on window variables.
""" """
from __future__ import absolute_import, division, unicode_literals from __future__ import absolute_import, division, unicode_literals
from logging import getLogger from logging import getLogger
try:
from multiprocessing.pool import ThreadPool
SUPPORTS_POOL = True
except Exception:
SUPPORTS_POOL = False
import xbmc import xbmc
import xbmcgui import xbmcgui
import xbmcvfs import xbmcvfs
from .plex_api import API
from .plex_db import PlexDB
from . import json_rpc as js, utils, variables as v from . import json_rpc as js, utils, variables as v
LOG = getLogger('PLEX.widget') LOG = getLogger('PLEX.widget')
@ -34,27 +27,6 @@ SYNCHED = True
KEY = None KEY = None
def process_method_on_list(method_to_run, items):
"""
helper method that processes a method on each listitem with pooling if the
system supports it
"""
all_items = []
if SUPPORTS_POOL:
pool = ThreadPool()
try:
all_items = pool.map(method_to_run, items)
except Exception:
# catch exception to prevent threadpool running forever
utils.ERROR(notify=True)
pool.close()
pool.join()
else:
all_items = [method_to_run(item) for item in items]
all_items = filter(None, all_items)
return all_items
def get_clean_image(image): def get_clean_image(image):
''' '''
helper to strip all kodi tags/formatting of an image path/url helper to strip all kodi tags/formatting of an image path/url
@ -82,7 +54,7 @@ def get_clean_image(image):
return image.decode('utf-8') return image.decode('utf-8')
def generate_item(xml_element): def generate_item(api):
""" """
Meant to be consumed by metadatautils.kodidb.prepare_listitem(), and then Meant to be consumed by metadatautils.kodidb.prepare_listitem(), and then
subsequently by metadatautils.kodidb.create_listitem() subsequently by metadatautils.kodidb.create_listitem()
@ -94,20 +66,19 @@ def generate_item(xml_element):
The key 'file' needs to be set later with the item's path The key 'file' needs to be set later with the item's path
""" """
try: try:
if xml_element.tag in ('Directory', 'Playlist', 'Hub'): if api.tag in ('Directory', 'Playlist', 'Hub'):
return _generate_folder(xml_element) return _generate_folder(api)
else: else:
return _generate_content(xml_element) return _generate_content(api)
except Exception: except Exception:
# Usefull to catch everything here since we're using threadpool # Usefull to catch everything here since we're using threadpool
LOG.error('xml that caused the crash: "%s": %s', LOG.error('xml that caused the crash: "%s": %s',
xml_element.tag, xml_element.attrib) api.tag, api.attrib)
utils.ERROR(notify=True) utils.ERROR(notify=True)
def _generate_folder(xml_element): def _generate_folder(api):
'''Generates "folder"/"directory" items that user can further navigate''' '''Generates "folder"/"directory" items that user can further navigate'''
api = API(xml_element)
art = api.artwork() art = api.artwork()
return { return {
'title': api.title(), 'title': api.title(),
@ -128,60 +99,54 @@ def _generate_folder(xml_element):
} }
def _generate_content(xml_element): def _generate_content(api):
api = API(xml_element) plex_type = api.plex_type
plex_type = api.plex_type() if api.kodi_id:
kodi_type = v.KODITYPE_FROM_PLEXTYPE[plex_type]
userdata = api.userdata()
_, _, tvshowtitle, season_no, episode_no = api.episode_data()
db_item = xml_element.get('pkc_db_item')
if db_item:
# Item is synched to the Kodi db - let's use that info # Item is synched to the Kodi db - let's use that info
# (will thus e.g. include additional artwork or metadata) # (will thus e.g. include additional artwork or metadata)
item = js.item_details(db_item['kodi_id'], kodi_type) item = js.item_details(api.kodi_id, api.kodi_type)
else: else:
people = api.people()
cast = [{ cast = [{
'name': x[0], 'name': x[0],
'thumbnail': x[1], 'thumbnail': x[1],
'role': x[2], 'role': x[2],
'order': x[3], 'order': x[3],
} for x in api.people_list()['actor']] } for x in api.people()['actor']]
item = { item = {
'cast': cast, 'cast': cast,
'country': api.country_list(), 'country': api.countries(),
'dateadded': api.date_created(), # e.g '2019-01-03 19:40:59' 'dateadded': api.date_created(), # e.g '2019-01-03 19:40:59'
'director': people['Director'], # list of [str] 'director': api.directors(), # list of [str]
'duration': userdata['Runtime'], 'duration': api.runtime(),
'episode': episode_no, 'episode': api.index(),
# 'file': '', # e.g. 'videodb://tvshows/titles/20' # 'file': '', # e.g. 'videodb://tvshows/titles/20'
'genre': api.genre_list(), 'genre': api.genres(),
# 'imdbnumber': '', # e.g.'341663' # 'imdbnumber': '', # e.g.'341663'
'label': api.title(), # e.g. '1x05. Category 55 Emergency Doomsday Crisis' 'label': api.title(), # e.g. '1x05. Category 55 Emergency Doomsday Crisis'
'lastplayed': userdata['LastPlayedDate'], # e.g. '2019-01-04 16:05:03' 'lastplayed': api.lastplayed(), # e.g. '2019-01-04 16:05:03'
'mpaa': api.content_rating(), # e.g. 'TV-MA' 'mpaa': api.content_rating(), # e.g. 'TV-MA'
'originaltitle': '', # e.g. 'Titans (2018)' 'originaltitle': '', # e.g. 'Titans (2018)'
'playcount': userdata['PlayCount'], # [int] 'playcount': api.viewcount(), # [int]
'plot': api.plot(), # [str] 'plot': api.plot(), # [str]
'plotoutline': api.tagline(), 'plotoutline': api.tagline(),
'premiered': api.premiere_date(), # '2018-10-12' 'premiered': api.premiere_date(), # '2018-10-12'
'rating': api.audience_rating(), # [float] 'rating': api.rating(), # [float]
'season': season_no, 'season': api.season_number(),
'sorttitle': api.sorttitle(), # 'Titans (2018)' 'sorttitle': api.sorttitle(), # 'Titans (2018)'
'studio': api.music_studio_list(), # e.g. 'DC Universe' 'studio': api.studios(),
'tag': [], # List of tags this item belongs to 'tag': [], # List of tags this item belongs to
'tagline': api.tagline(), 'tagline': api.tagline(),
'thumbnail': '', # e.g. 'image://https%3a%2f%2fassets.tv' 'thumbnail': '', # e.g. 'image://https%3a%2f%2fassets.tv'
'title': api.title(), # 'Titans (2018)' 'title': api.title(), # 'Titans (2018)'
'type': kodi_type, 'type': api.kodi_type,
'trailer': api.trailer(), 'trailer': api.trailer(),
'tvshowtitle': tvshowtitle, 'tvshowtitle': api.show_title(),
'uniqueid': { 'uniqueid': {
'imdbnumber': api.provider('imdb') or '', 'imdbnumber': api.provider('imdb') or '',
'tvdb_id': api.provider('tvdb') or '' 'tvdb_id': api.provider('tvdb') or ''
}, },
'votes': '0', # [str]! 'votes': '0', # [str]!
'writer': people['Writer'], # list of [str] 'writer': api.writers(), # list of [str]
'year': api.year(), # [int] 'year': api.year(), # [int]
} }
@ -206,18 +171,18 @@ def _generate_content(xml_element):
if resume: if resume:
item['resume'] = { item['resume'] = {
'position': resume, 'position': resume,
'total': userdata['Runtime'] 'total': api.runtime()
} }
item['icon'] = v.ICON_FROM_PLEXTYPE[plex_type] item['icon'] = v.ICON_FROM_PLEXTYPE[plex_type]
# Some customization # Some customization
if plex_type == v.PLEX_TYPE_EPISODE: if plex_type == v.PLEX_TYPE_EPISODE:
# Prefix to the episode's title/label # Prefix to the episode's title/label
if season_no is not None and episode_no is not None: if api.season_number() is not None and api.index() is not None:
if APPEND_SXXEXX is True: if APPEND_SXXEXX is True:
item['title'] = "S%.2dE%.2d - %s" % (season_no, episode_no, item['title']) item['title'] = "S%.2dE%.2d - %s" % (api.season_number(), api.index(), item['title'])
if APPEND_SHOW_TITLE is True: if APPEND_SHOW_TITLE is True:
item['title'] = "%s - %s " % (tvshowtitle, item['title']) item['title'] = "%s - %s " % (api.show_title(), item['title'])
item['label'] = item['title'] item['label'] = item['title']
# Determine the path for this item # Determine the path for this item
@ -226,14 +191,14 @@ def _generate_content(xml_element):
params = { params = {
'mode': 'plex_node', 'mode': 'plex_node',
'key': key, 'key': key,
'offset': xml_element.attrib.get('viewOffset', '0'), 'offset': api.resume_point_plex()
} }
url = utils.extend_url('plugin://%s' % v.ADDON_ID, params) url = utils.extend_url('plugin://%s' % v.ADDON_ID, params)
elif plex_type == v.PLEX_TYPE_PHOTO: elif plex_type == v.PLEX_TYPE_PHOTO:
url = api.get_picture_path() url = api.get_picture_path()
else: else:
url = api.path() url = api.path()
if not db_item and plex_type == v.PLEX_TYPE_EPISODE: if not api.kodi_id and plex_type == v.PLEX_TYPE_EPISODE:
# Hack - Item is not synched to the Kodi database # Hack - Item is not synched to the Kodi database
# We CANNOT use paths that show up in the Kodi paths table! # We CANNOT use paths that show up in the Kodi paths table!
url = url.replace('plugin.video.plexkodiconnect.tvshows', url = url.replace('plugin.video.plexkodiconnect.tvshows',
@ -242,20 +207,6 @@ def _generate_content(xml_element):
return item return item
def attach_kodi_ids(xml):
"""
Attaches the kodi db_item to the xml's children, attribute 'pkc_db_item'
"""
if not SYNCHED:
return
with PlexDB(lock=False) as plexdb:
for child in xml:
api = API(child)
db_item = plexdb.item_by_id(api.plex_id(), api.plex_type())
child.set('pkc_db_item', db_item)
return xml
def prepare_listitem(item): def prepare_listitem(item):
""" """
helper to convert kodi output from json api to compatible format for helper to convert kodi output from json api to compatible format for
@ -460,7 +411,8 @@ def prepare_listitem(item):
LOG.error('item that caused crash: %s', item) LOG.error('item that caused crash: %s', item)
def create_listitem(item, as_tuple=True, offscreen=True): def create_listitem(item, as_tuple=True, offscreen=True,
listitem=xbmcgui.ListItem):
""" """
helper to create a kodi listitem from kodi compatible dict with mediainfo helper to create a kodi listitem from kodi compatible dict with mediainfo
@ -472,13 +424,13 @@ def create_listitem(item, as_tuple=True, offscreen=True):
""" """
try: try:
if v.KODIVERSION > 17: if v.KODIVERSION > 17:
liz = xbmcgui.ListItem( liz = listitem(
label=item.get("label", ""), label=item.get("label", ""),
label2=item.get("label2", ""), label2=item.get("label2", ""),
path=item['file'], path=item['file'],
offscreen=offscreen) offscreen=offscreen)
else: else:
liz = xbmcgui.ListItem( liz = listitem(
label=item.get("label", ""), label=item.get("label", ""),
label2=item.get("label2", ""), label2=item.get("label2", ""),
path=item['file']) path=item['file'])
@ -585,11 +537,9 @@ def create_listitem(item, as_tuple=True, offscreen=True):
liz.setInfo(type=nodetype, infoLabels=infolabels) liz.setInfo(type=nodetype, infoLabels=infolabels)
# artwork # artwork
liz.setArt(item.get("art", {}))
if "icon" in item: if "icon" in item:
liz.setIconImage(item['icon']) item['art']['icon'] = item['icon']
if "thumbnail" in item: liz.setArt(item.get("art", {}))
liz.setThumbnailImage(item['thumbnail'])
# contextmenu # contextmenu
if item["type"] in ["episode", "season"] and "season" in item and "tvshowid" in item: if item["type"] in ["episode", "season"] and "season" in item and "tvshowid" in item:
@ -627,4 +577,3 @@ def create_main_entry(item):
'type': '', 'type': '',
'IsPlayable': 'false' 'IsPlayable': 'false'
} }

View file

@ -158,6 +158,7 @@
<category label="39073"><!-- Appearance Tweaks --> <category label="39073"><!-- Appearance Tweaks -->
<setting label="[COLOR yellow]$ADDON[plugin.video.plexkodiconnect 39085][/COLOR]" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=refreshplaylist)" option="close" /><!-- Reload Kodi node files to apply all the settings below --> <setting label="[COLOR yellow]$ADDON[plugin.video.plexkodiconnect 39085][/COLOR]" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=refreshplaylist)" option="close" /><!-- Reload Kodi node files to apply all the settings below -->
<setting id="widgetLimit" type="slider" label="39077" default="30" range="10,10,100" option="int" /><!-- Maximum number of videos to show in widgets -->
<setting type="lsep" /> <setting type="lsep" />
<setting id="fetch_pms_item_number" label="39077" type="number" default="50" option="int" visible="false" /> <setting id="fetch_pms_item_number" label="39077" type="number" default="50" option="int" visible="false" />
<setting type="lsep" label="39074" /><!-- TV Shows --> <setting type="lsep" label="39074" /><!-- TV Shows -->