commit
5eb1c2aacd
36 changed files with 532 additions and 342 deletions
21
addon.xml
21
addon.xml
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.12.3" provider-name="croneter">
|
||||
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.12.6" provider-name="croneter">
|
||||
<requires>
|
||||
<import addon="xbmc.python" version="2.1.0"/>
|
||||
<import addon="script.module.requests" version="2.9.1" />
|
||||
|
@ -83,7 +83,24 @@
|
|||
<summary lang="lt_LT">Natūralioji „Plex“ integracija į „Kodi“</summary>
|
||||
<description lang="lt_LT">Prijunkite „Kodi“ prie „Plex Medija Serverio“. Šiame papildinyje daroma prielaida, kad valdote visus savo vaizdo įrašus naudodami „Plex“ (ir nė vieno su „Kodi“). Galite prarasti jau saugomus „Kodi“ vaizdo įrašų ir muzikos duomenų bazių duomenis (kadangi šis papildinys juos tiesiogiai pakeičia). Naudokite savo pačių rizika!</description>
|
||||
<disclaimer lang="lt_LT">Naudokite savo pačių rizika</disclaimer>
|
||||
<news>version 2.12.3:
|
||||
<news>version 2.12.6:
|
||||
- Fix rare KeyError when using PKC widgets
|
||||
- Fix suspension of artwork caching and PKC becoming unresponsive
|
||||
- Update translations
|
||||
- Versions 2.12.4 and 2.12.5 for everyone
|
||||
|
||||
version 2.12.5 (beta only):
|
||||
- Greatly improve matching logic for The Movie Database if Plex does not provide an appropriate id
|
||||
- Fix high transcoding resolutions not being available for Win10
|
||||
- Fix rare playback progress report failing and KeyError: u'containerKey'
|
||||
- Fix rare KeyError: None when trying to sync playlists
|
||||
- Fix TypeError when canceling Plex sync section dialog
|
||||
|
||||
version 2.12.4 (beta only):
|
||||
- Hopefully fix freeze during sync: Don't assign multiple sets/collections for a specific movie
|
||||
- Support metadata provider ids (e.g. for IMDB) for the new Plex Movie Agent
|
||||
|
||||
version 2.12.3:
|
||||
- Fix playback failing due to caching of subtitles with non-ascii chars
|
||||
- Fix ValueError: invalid literal for int() with base 10 during show sync
|
||||
- Fix UnboundLocalError when certain Plex sections are deleted or being un-synched
|
||||
|
|
|
@ -1,3 +1,20 @@
|
|||
version 2.12.6:
|
||||
- Fix rare KeyError when using PKC widgets
|
||||
- Fix suspension of artwork caching and PKC becoming unresponsive
|
||||
- Update translations
|
||||
- Versions 2.12.4 and 2.12.5 for everyone
|
||||
|
||||
version 2.12.5 (beta only):
|
||||
- Greatly improve matching logic for The Movie Database if Plex does not provide an appropriate id
|
||||
- Fix high transcoding resolutions not being available for Win10
|
||||
- Fix rare playback progress report failing and KeyError: u'containerKey'
|
||||
- Fix rare KeyError: None when trying to sync playlists
|
||||
- Fix TypeError when canceling Plex sync section dialog
|
||||
|
||||
version 2.12.4 (beta only):
|
||||
- Hopefully fix freeze during sync: Don't assign multiple sets/collections for a specific movie
|
||||
- Support metadata provider ids (e.g. for IMDB) for the new Plex Movie Agent
|
||||
|
||||
version 2.12.3:
|
||||
- Fix playback failing due to caching of subtitles with non-ascii chars
|
||||
- Fix ValueError: invalid literal for int() with base 10 during show sync
|
||||
|
|
|
@ -950,6 +950,11 @@ msgctxt "#39036"
|
|||
msgid "Escape special characters in path (e.g. space to %20)"
|
||||
msgstr "Nahrazovat speciální znaky v cestě (např. z mezery na %20)"
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39090"
|
||||
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
|
||||
msgstr ""
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39037"
|
||||
msgid "Original Plex MOVIE path to replace:"
|
||||
|
|
|
@ -954,6 +954,11 @@ msgctxt "#39036"
|
|||
msgid "Escape special characters in path (e.g. space to %20)"
|
||||
msgstr "Escape special characters in path (e.g. space to %20)"
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39090"
|
||||
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
|
||||
msgstr ""
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39037"
|
||||
msgid "Original Plex MOVIE path to replace:"
|
||||
|
|
|
@ -966,6 +966,11 @@ msgctxt "#39036"
|
|||
msgid "Escape special characters in path (e.g. space to %20)"
|
||||
msgstr "Sonderzeichen im Pfad escapen (z.B. Leerzeichen zu %20)"
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39090"
|
||||
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
|
||||
msgstr "Sichere Zeichen für http(s), dav(s) und (s)ftp urls"
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39037"
|
||||
msgid "Original Plex MOVIE path to replace:"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# XBMC Media Center language file
|
||||
# Translators:
|
||||
# Croneter None <croneter@gmail.com>, 2019
|
||||
# Croneter None <croneter@gmail.com>, 2020
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
|
@ -8,7 +8,7 @@ msgstr ""
|
|||
"Report-Msgid-Bugs-To: croneter@gmail.com\n"
|
||||
"POT-Creation-Date: 2017-04-15 13:13+0000\n"
|
||||
"PO-Revision-Date: 2017-04-30 08:30+0000\n"
|
||||
"Last-Translator: Croneter None <croneter@gmail.com>, 2019\n"
|
||||
"Last-Translator: Croneter None <croneter@gmail.com>, 2020\n"
|
||||
"Language-Team: Spanish (Argentina) (https://www.transifex.com/croneter/teams/73837/es_AR/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
@ -935,7 +935,7 @@ msgid ""
|
|||
"Kodi cannot locate the file %s. Please verify your PKC settings. Stop "
|
||||
"syncing?"
|
||||
msgstr ""
|
||||
"Kodi no puede localizer el archive %s. Por favoer verificar sus ajustes de "
|
||||
"Kodi no puede localizer el archivo %s. Por favor verificar sus ajustes de "
|
||||
"PKC. ¿Detener la sincronización?"
|
||||
|
||||
# Pop-up on initial sync
|
||||
|
@ -964,7 +964,12 @@ msgstr ""
|
|||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39036"
|
||||
msgid "Escape special characters in path (e.g. space to %20)"
|
||||
msgstr "Escapar caracteres especiales en la ruta (i.e. espacio a %20)"
|
||||
msgstr "Escapar caracteres especiales en la ruta (p. ej. espacio a %20)"
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39090"
|
||||
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
|
||||
msgstr "Caracteres seguros para urls http(s), dav(s) y (s)ftp"
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39037"
|
||||
|
@ -1430,7 +1435,7 @@ msgid ""
|
|||
"The current Kodi version is not supported by PKC. Please consult the Plex "
|
||||
"forum."
|
||||
msgstr ""
|
||||
"La version actual de Kodi no está soportada por PKC. Por favor consultar el"
|
||||
"La versión actual de Kodi no está soportada por PKC. Por favor consultar el"
|
||||
" fórum de Plex."
|
||||
|
||||
msgctxt "#39405"
|
||||
|
@ -1474,7 +1479,7 @@ msgstr "Sagas"
|
|||
|
||||
msgctxt "#39502"
|
||||
msgid "PKC On Deck (faster)"
|
||||
msgstr "Tablero de PKC (mas rapido)"
|
||||
msgstr "On Deck de PKC (más rápido)"
|
||||
|
||||
msgctxt "#39600"
|
||||
msgid ""
|
||||
|
@ -1616,8 +1621,8 @@ msgid ""
|
|||
"Do you want to replace your custom user ratings with an indicator of how "
|
||||
"many versions of a media item you posses?"
|
||||
msgstr ""
|
||||
"¿Quiere reemplazar su valoración personalizada con cuántas versione posee de"
|
||||
" un elemento de medios?"
|
||||
"¿Quiere reemplazar su valoración personalizada por cuántas versiones posee "
|
||||
"de un elemento de medios?"
|
||||
|
||||
# In PKC Settings under Sync
|
||||
msgctxt "#39719"
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
# Translators:
|
||||
# Dani <danichispa@gmail.com>, 2019
|
||||
# Bartolome Soriano <bsoriano@gmail.com>, 2019
|
||||
# Croneter None <croneter@gmail.com>, 2020
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
|
@ -9,7 +10,7 @@ msgstr ""
|
|||
"Report-Msgid-Bugs-To: croneter@gmail.com\n"
|
||||
"POT-Creation-Date: 2017-04-15 13:13+0000\n"
|
||||
"PO-Revision-Date: 2017-04-30 08:30+0000\n"
|
||||
"Last-Translator: Bartolome Soriano <bsoriano@gmail.com>, 2019\n"
|
||||
"Last-Translator: Croneter None <croneter@gmail.com>, 2020\n"
|
||||
"Language-Team: Spanish (Spain) (https://www.transifex.com/croneter/teams/73837/es_ES/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# XBMC Media Center language file
|
||||
# Translators:
|
||||
# Croneter None <croneter@gmail.com>, 2019
|
||||
# Croneter None <croneter@gmail.com>, 2020
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
|
@ -8,7 +8,7 @@ msgstr ""
|
|||
"Report-Msgid-Bugs-To: croneter@gmail.com\n"
|
||||
"POT-Creation-Date: 2017-04-15 13:13+0000\n"
|
||||
"PO-Revision-Date: 2017-04-30 08:30+0000\n"
|
||||
"Last-Translator: Croneter None <croneter@gmail.com>, 2019\n"
|
||||
"Last-Translator: Croneter None <croneter@gmail.com>, 2020\n"
|
||||
"Language-Team: Spanish (Mexico) (https://www.transifex.com/croneter/teams/73837/es_MX/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
@ -935,7 +935,7 @@ msgid ""
|
|||
"Kodi cannot locate the file %s. Please verify your PKC settings. Stop "
|
||||
"syncing?"
|
||||
msgstr ""
|
||||
"Kodi no puede localizer el archive %s. Por favoer verificar sus ajustes de "
|
||||
"Kodi no puede localizer el archivo %s. Por favor verificar sus ajustes de "
|
||||
"PKC. ¿Detener la sincronización?"
|
||||
|
||||
# Pop-up on initial sync
|
||||
|
@ -964,7 +964,12 @@ msgstr ""
|
|||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39036"
|
||||
msgid "Escape special characters in path (e.g. space to %20)"
|
||||
msgstr "Escapar caracteres especiales en la ruta (i.e. espacio a %20)"
|
||||
msgstr "Escapar caracteres especiales en la ruta (p. ej. espacio a %20)"
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39090"
|
||||
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
|
||||
msgstr "Caracteres seguros para urls http(s), dav(s) y (s)ftp"
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39037"
|
||||
|
@ -1430,7 +1435,7 @@ msgid ""
|
|||
"The current Kodi version is not supported by PKC. Please consult the Plex "
|
||||
"forum."
|
||||
msgstr ""
|
||||
"La version actual de Kodi no está soportada por PKC. Por favor consultar el"
|
||||
"La versión actual de Kodi no está soportada por PKC. Por favor consultar el"
|
||||
" fórum de Plex."
|
||||
|
||||
msgctxt "#39405"
|
||||
|
@ -1474,7 +1479,7 @@ msgstr "Sagas"
|
|||
|
||||
msgctxt "#39502"
|
||||
msgid "PKC On Deck (faster)"
|
||||
msgstr "Tablero de PKC (mas rapido)"
|
||||
msgstr "On Deck de PKC (más rápido)"
|
||||
|
||||
msgctxt "#39600"
|
||||
msgid ""
|
||||
|
@ -1616,8 +1621,8 @@ msgid ""
|
|||
"Do you want to replace your custom user ratings with an indicator of how "
|
||||
"many versions of a media item you posses?"
|
||||
msgstr ""
|
||||
"¿Quiere reemplazar su valoración personalizada con cuántas versione posee de"
|
||||
" un elemento de medios?"
|
||||
"¿Quiere reemplazar su valoración personalizada por cuántas versiones posee "
|
||||
"de un elemento de medios?"
|
||||
|
||||
# In PKC Settings under Sync
|
||||
msgctxt "#39719"
|
||||
|
|
|
@ -975,6 +975,11 @@ msgstr ""
|
|||
"Echapper les caractères spéciaux dans le chemin (ex: %20 au lieu des "
|
||||
"espaces)"
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39090"
|
||||
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
|
||||
msgstr "Caractères sûrs pour les urls http(s), dav(s) et (s)ftp"
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39037"
|
||||
msgid "Original Plex MOVIE path to replace:"
|
||||
|
|
|
@ -979,6 +979,11 @@ msgstr ""
|
|||
"Echapper les caractères spéciaux dans le chemin (ex: %20 au lieu des "
|
||||
"espaces)"
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39090"
|
||||
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
|
||||
msgstr "Caractères sûrs pour les urls http(s), dav(s) et (s)ftp"
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39037"
|
||||
msgid "Original Plex MOVIE path to replace:"
|
||||
|
|
|
@ -968,6 +968,11 @@ msgid "Escape special characters in path (e.g. space to %20)"
|
|||
msgstr ""
|
||||
"A speciális karakterek feloldása az elérési útban (pl. szóköz helyett %20)"
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39090"
|
||||
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
|
||||
msgstr ""
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39037"
|
||||
msgid "Original Plex MOVIE path to replace:"
|
||||
|
|
|
@ -967,6 +967,11 @@ msgstr ""
|
|||
"Esegui l'escape dei caratteri speciali nel percorso (es. \"spazio\" "
|
||||
"trasformato in \"%20\")"
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39090"
|
||||
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
|
||||
msgstr ""
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39037"
|
||||
msgid "Original Plex MOVIE path to replace:"
|
||||
|
|
|
@ -963,6 +963,11 @@ msgctxt "#39036"
|
|||
msgid "Escape special characters in path (e.g. space to %20)"
|
||||
msgstr "Kaita specialių simbolių kelyje (pvz., tarpas %20)"
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39090"
|
||||
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
|
||||
msgstr ""
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39037"
|
||||
msgid "Original Plex MOVIE path to replace:"
|
||||
|
|
|
@ -946,6 +946,11 @@ msgctxt "#39036"
|
|||
msgid "Escape special characters in path (e.g. space to %20)"
|
||||
msgstr ""
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39090"
|
||||
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
|
||||
msgstr ""
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39037"
|
||||
msgid "Original Plex MOVIE path to replace:"
|
||||
|
@ -1100,7 +1105,7 @@ msgstr "Uzspiest atjaunošanu Kodi ādiņai apturot atskaņošanu"
|
|||
# PKC Settings - Appearance Tweaks
|
||||
msgctxt "#39066"
|
||||
msgid "Recently Added: Also show already watched movies"
|
||||
msgstr ""
|
||||
msgstr "Nesen Pievienots: Rādīt arī jau skatītas filmas"
|
||||
|
||||
# PKC Settings - Connection
|
||||
msgctxt "#39067"
|
||||
|
@ -1110,7 +1115,7 @@ msgstr "Tavs pašreizējais Plex Media Serveris:"
|
|||
# PKC Settings - Connection
|
||||
msgctxt "#39068"
|
||||
msgid "Manually enter Plex Media Server address"
|
||||
msgstr ""
|
||||
msgstr "Pats ievadi Plex Media Server adresi"
|
||||
|
||||
# PKC Settings - Connection
|
||||
msgctxt "#39069"
|
||||
|
@ -1173,22 +1178,22 @@ msgstr ""
|
|||
# Button text for choosing PKC mode
|
||||
msgctxt "#39081"
|
||||
msgid "Add-on Paths"
|
||||
msgstr ""
|
||||
msgstr "Spraudņu Ceļš"
|
||||
|
||||
# Button text for choosing PKC mode
|
||||
msgctxt "#39082"
|
||||
msgid "Direct Paths"
|
||||
msgstr ""
|
||||
msgstr "Tiešie Ceļi"
|
||||
|
||||
# Dialog for manually entering PMS
|
||||
msgctxt "#39083"
|
||||
msgid "Enter PMS IP or URL"
|
||||
msgstr ""
|
||||
msgstr "Ievadi PMS IP vai URL"
|
||||
|
||||
# Dialog for manually entering PMS
|
||||
msgctxt "#39084"
|
||||
msgid "Enter PMS port"
|
||||
msgstr ""
|
||||
msgstr "Ievadi PMS portu"
|
||||
|
||||
# PKC settings - Appearance Tweaks
|
||||
msgctxt "#39085"
|
||||
|
@ -1291,23 +1296,23 @@ msgstr "Tikai trūkstošo"
|
|||
# Message in the PKC settings if user has not logged in to plex.tv
|
||||
msgctxt "#39226"
|
||||
msgid "Not logged in to plex.tv"
|
||||
msgstr ""
|
||||
msgstr "Nav pieteicies plex.tv"
|
||||
|
||||
# Message in the PKC settings if user is logged in to plex.tv
|
||||
msgctxt "#39227"
|
||||
msgid "Logged in to plex.tv"
|
||||
msgstr ""
|
||||
msgstr "Pieteicies plex.tv"
|
||||
|
||||
# Message in the PKC settings to display the plex.tv username
|
||||
msgctxt "#39228"
|
||||
msgid "Plex admin user"
|
||||
msgstr ""
|
||||
msgstr "Plex admin user"
|
||||
|
||||
# Error message if user could not log in; the actual user name will be
|
||||
# appended at the end of the string
|
||||
msgctxt "#39229"
|
||||
msgid "Login failed with plex.tv for user"
|
||||
msgstr ""
|
||||
msgstr "Lietotāja pieteikšanās plex.tv neizdevās"
|
||||
|
||||
# Message in the PKC settings to display the plex.tv username
|
||||
msgctxt "#39230"
|
||||
|
@ -1472,7 +1477,7 @@ msgstr ""
|
|||
# Addon Disclaimer
|
||||
msgctxt "#39705"
|
||||
msgid "Use at your own risk"
|
||||
msgstr ""
|
||||
msgstr "Lieto uz savu atbildību"
|
||||
|
||||
# If user gets prompted to choose between several subtitles to burn in
|
||||
msgctxt "#39706"
|
||||
|
@ -1530,7 +1535,7 @@ msgstr "Sinhronizēt"
|
|||
# Shown during sync process
|
||||
msgctxt "#39715"
|
||||
msgid "Synching playlists"
|
||||
msgstr ""
|
||||
msgstr "Sinhronizē spēļsarakstus"
|
||||
|
||||
# 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}!
|
||||
|
|
|
@ -957,6 +957,11 @@ msgctxt "#39036"
|
|||
msgid "Escape special characters in path (e.g. space to %20)"
|
||||
msgstr "Pas speciale tekens aan in pad (b.v. spatie naar %20)"
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39090"
|
||||
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
|
||||
msgstr ""
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39037"
|
||||
msgid "Original Plex MOVIE path to replace:"
|
||||
|
|
|
@ -951,6 +951,11 @@ msgctxt "#39036"
|
|||
msgid "Escape special characters in path (e.g. space to %20)"
|
||||
msgstr "Unngå spesielle tegn i stier (eksempel mellomrom til %20)"
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39090"
|
||||
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
|
||||
msgstr ""
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39037"
|
||||
msgid "Original Plex MOVIE path to replace:"
|
||||
|
|
|
@ -945,6 +945,11 @@ msgctxt "#39036"
|
|||
msgid "Escape special characters in path (e.g. space to %20)"
|
||||
msgstr ""
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39090"
|
||||
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
|
||||
msgstr ""
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39037"
|
||||
msgid "Original Plex MOVIE path to replace:"
|
||||
|
|
|
@ -948,6 +948,11 @@ msgctxt "#39036"
|
|||
msgid "Escape special characters in path (e.g. space to %20)"
|
||||
msgstr ""
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39090"
|
||||
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
|
||||
msgstr ""
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39037"
|
||||
msgid "Original Plex MOVIE path to replace:"
|
||||
|
|
|
@ -961,6 +961,11 @@ msgctxt "#39036"
|
|||
msgid "Escape special characters in path (e.g. space to %20)"
|
||||
msgstr "Преобразуйте специальные символы в пути. (например пробел в %20)"
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39090"
|
||||
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
|
||||
msgstr ""
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39037"
|
||||
msgid "Original Plex MOVIE path to replace:"
|
||||
|
|
|
@ -951,6 +951,11 @@ msgctxt "#39036"
|
|||
msgid "Escape special characters in path (e.g. space to %20)"
|
||||
msgstr "Omkoda specialtecken i sökväg (exempelvis mellanslag som %20)"
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39090"
|
||||
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
|
||||
msgstr ""
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39037"
|
||||
msgid "Original Plex MOVIE path to replace:"
|
||||
|
|
|
@ -957,6 +957,11 @@ msgctxt "#39036"
|
|||
msgid "Escape special characters in path (e.g. space to %20)"
|
||||
msgstr "Замінювати спеціальні символи у шляхах (наприклад, пробіл у %20)"
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39090"
|
||||
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
|
||||
msgstr "Безпечні символи для URL-адрес http(s), dav(s) та (s)ftp"
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39037"
|
||||
msgid "Original Plex MOVIE path to replace:"
|
||||
|
@ -1528,7 +1533,7 @@ msgstr "Використовуйте на свій ризик"
|
|||
# If user gets prompted to choose between several subtitles to burn in
|
||||
msgctxt "#39706"
|
||||
msgid "Don't burn-in any subtitle"
|
||||
msgstr ""
|
||||
msgstr "Не виводити жодних субтитрів"
|
||||
|
||||
# If user gets prompted to choose between several audio/subtitle tracks and
|
||||
# language is unknown
|
||||
|
|
|
@ -918,6 +918,11 @@ msgctxt "#39036"
|
|||
msgid "Escape special characters in path (e.g. space to %20)"
|
||||
msgstr ""
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39090"
|
||||
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
|
||||
msgstr ""
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39037"
|
||||
msgid "Original Plex MOVIE path to replace:"
|
||||
|
|
|
@ -916,6 +916,11 @@ msgctxt "#39036"
|
|||
msgid "Escape special characters in path (e.g. space to %20)"
|
||||
msgstr ""
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39090"
|
||||
msgid "Safe characters for http(s), dav(s) and (s)ftp urls"
|
||||
msgstr ""
|
||||
|
||||
# PKC Settings - Customize Paths
|
||||
msgctxt "#39037"
|
||||
msgid "Original Plex MOVIE path to replace:"
|
||||
|
|
|
@ -82,7 +82,7 @@ class ImageCachingThread(backgroundthread.KillableThread):
|
|||
for url in self._url_generator(kind, kodi_type):
|
||||
if self.should_suspend() or self.should_cancel():
|
||||
return False
|
||||
cache_url(url)
|
||||
cache_url(url, self.should_suspend)
|
||||
# Toggles Image caching completed to Yes
|
||||
utils.settings('plex_status_image_caching', value=utils.lang(107))
|
||||
return True
|
||||
|
@ -95,7 +95,7 @@ class ImageCachingThread(backgroundthread.KillableThread):
|
|||
break
|
||||
|
||||
|
||||
def cache_url(url):
|
||||
def cache_url(url, should_suspend=None):
|
||||
url = double_urlencode(url)
|
||||
sleeptime = 0
|
||||
while True:
|
||||
|
@ -113,11 +113,11 @@ def cache_url(url):
|
|||
# download. All is well
|
||||
break
|
||||
except requests.ConnectionError:
|
||||
if app.APP.stop_pkc:
|
||||
# Kodi terminated
|
||||
if app.APP.stop_pkc or (should_suspend and should_suspend()):
|
||||
break
|
||||
# Server thinks its a DOS attack, ('error 10053')
|
||||
# Wait before trying again
|
||||
# OR: Kodi refuses Webserver connection (no password set)
|
||||
if sleeptime > 5:
|
||||
LOG.error('Repeatedly got ConnectionError for url %s',
|
||||
double_urldecode(url))
|
||||
|
|
|
@ -145,3 +145,27 @@ class ItemBase(object):
|
|||
encountered by PKC
|
||||
"""
|
||||
return section_id in app.SYNC.section_ids
|
||||
|
||||
def update_provider_ids(self, api, kodi_id):
|
||||
"""
|
||||
Updates the unique metadata provider ids (such as the IMDB id). Returns
|
||||
a dict of the Kodi unique ids
|
||||
"""
|
||||
# We might have an old provider id stored!
|
||||
self.kodidb.remove_uniqueid(kodi_id, api.kodi_type)
|
||||
return self.add_provider_ids(api, kodi_id)
|
||||
|
||||
def add_provider_ids(self, api, kodi_id):
|
||||
"""
|
||||
Adds the unique ids for all metadata providers to the Kodi database,
|
||||
such as IMDB or The Movie Database TMDB.
|
||||
Returns a dict of the Kodi ids: {<provider>: <kodi_unique_id>}
|
||||
"""
|
||||
kodi_unique_ids = api.guids.copy()
|
||||
for provider, provider_id in api.guids.iteritems():
|
||||
kodi_unique_ids[provider] = self.kodidb.add_uniqueid(
|
||||
kodi_id,
|
||||
api.kodi_type,
|
||||
provider_id,
|
||||
provider)
|
||||
return kodi_unique_ids
|
||||
|
|
|
@ -56,19 +56,7 @@ class Movie(ItemBase):
|
|||
"default",
|
||||
api.rating(),
|
||||
api.votecount())
|
||||
if api.provider('imdb') is not None:
|
||||
uniqueid = self.kodidb.update_uniqueid(kodi_id,
|
||||
v.KODI_TYPE_MOVIE,
|
||||
'imdb',
|
||||
api.provider('imdb'))
|
||||
elif api.provider('tmdb') is not None:
|
||||
uniqueid = self.kodidb.update_uniqueid(kodi_id,
|
||||
v.KODI_TYPE_MOVIE,
|
||||
'tmdb',
|
||||
api.provider('tmdb'))
|
||||
else:
|
||||
self.kodidb.remove_uniqueid(kodi_id, v.KODI_TYPE_MOVIE)
|
||||
uniqueid = -1
|
||||
unique_id = self.update_provider_ids(api, kodi_id)
|
||||
self.kodidb.modify_people(kodi_id,
|
||||
v.KODI_TYPE_MOVIE,
|
||||
api.people())
|
||||
|
@ -86,18 +74,7 @@ class Movie(ItemBase):
|
|||
"default",
|
||||
api.rating(),
|
||||
api.votecount())
|
||||
if api.provider('imdb') is not None:
|
||||
uniqueid = self.kodidb.add_uniqueid(kodi_id,
|
||||
v.KODI_TYPE_MOVIE,
|
||||
api.provider('imdb'),
|
||||
"imdb")
|
||||
elif api.provider('tmdb') is not None:
|
||||
uniqueid = self.kodidb.add_uniqueid(kodi_id,
|
||||
v.KODI_TYPE_MOVIE,
|
||||
api.provider('tmdb'),
|
||||
"tmdb")
|
||||
else:
|
||||
uniqueid = -1
|
||||
unique_id = self.add_provider_ids(api, kodi_id)
|
||||
self.kodidb.add_people(kodi_id,
|
||||
v.KODI_TYPE_MOVIE,
|
||||
api.people())
|
||||
|
@ -106,6 +83,8 @@ class Movie(ItemBase):
|
|||
kodi_id,
|
||||
v.KODI_TYPE_MOVIE)
|
||||
|
||||
unique_id = self._prioritize_provider_id(unique_id)
|
||||
|
||||
# Update Kodi's main entry
|
||||
self.kodidb.add_movie(kodi_id,
|
||||
file_id,
|
||||
|
@ -117,7 +96,7 @@ class Movie(ItemBase):
|
|||
rating_id,
|
||||
api.list_to_string(api.writers()),
|
||||
api.year(),
|
||||
uniqueid,
|
||||
unique_id,
|
||||
api.sorttitle(),
|
||||
api.runtime(),
|
||||
api.content_rating(),
|
||||
|
@ -140,39 +119,7 @@ class Movie(ItemBase):
|
|||
self.kodidb.modify_streams(file_id, api.mediastreams(), api.runtime())
|
||||
self.kodidb.modify_studios(kodi_id, v.KODI_TYPE_MOVIE, api.studios())
|
||||
tags = [section_name]
|
||||
if api.collections():
|
||||
for plex_set_id, set_name in api.collections():
|
||||
set_api = None
|
||||
tags.append(set_name)
|
||||
# Add any sets from Plex collection tags
|
||||
kodi_set_id = self.kodidb.create_collection(set_name)
|
||||
self.kodidb.assign_collection(kodi_set_id, kodi_id)
|
||||
if not app.SYNC.artwork:
|
||||
# Rest below is to get collection artwork
|
||||
continue
|
||||
if children is None:
|
||||
# e.g. when added via websocket
|
||||
LOG.debug('Costly looking up Plex collection %s: %s',
|
||||
plex_set_id, set_name)
|
||||
for index, coll_plex_id in api.collections_match(section_id):
|
||||
# Get Plex artwork for collections - a pain
|
||||
if index == plex_set_id:
|
||||
set_xml = PF.GetPlexMetadata(coll_plex_id)
|
||||
try:
|
||||
set_xml.attrib
|
||||
except AttributeError:
|
||||
LOG.error('Could not get set metadata %s',
|
||||
coll_plex_id)
|
||||
continue
|
||||
set_api = API(set_xml[0])
|
||||
break
|
||||
elif plex_set_id in children:
|
||||
# Provided by get_metadata thread
|
||||
set_api = API(children[plex_set_id][0])
|
||||
if set_api:
|
||||
self.kodidb.modify_artwork(set_api.artwork(),
|
||||
kodi_set_id,
|
||||
v.KODI_TYPE_SET)
|
||||
self._process_collections(api, tags, kodi_id, section_id, children)
|
||||
self.kodidb.modify_tags(kodi_id, v.KODI_TYPE_MOVIE, tags)
|
||||
# Process playstate
|
||||
self.kodidb.set_resume(file_id,
|
||||
|
@ -246,3 +193,52 @@ class Movie(ItemBase):
|
|||
db_item['kodi_type'],
|
||||
api.userrating())
|
||||
return True
|
||||
|
||||
def _process_collections(self, api, tags, kodi_id, section_id, children):
|
||||
for plex_set_id, set_name in api.collections():
|
||||
set_api = None
|
||||
tags.append(set_name)
|
||||
# Add any sets from Plex collection tags
|
||||
kodi_set_id = self.kodidb.create_collection(set_name)
|
||||
self.kodidb.assign_collection(kodi_set_id, kodi_id)
|
||||
if not app.SYNC.artwork:
|
||||
# Rest below is to get collection artwork
|
||||
# TODO: continue instead of break (see TODO/break below)
|
||||
break
|
||||
if children is None:
|
||||
# e.g. when added via websocket
|
||||
LOG.debug('Costly looking up Plex collection %s: %s',
|
||||
plex_set_id, set_name)
|
||||
for index, coll_plex_id in api.collections_match(section_id):
|
||||
# Get Plex artwork for collections - a pain
|
||||
if index == plex_set_id:
|
||||
set_xml = PF.GetPlexMetadata(coll_plex_id)
|
||||
try:
|
||||
set_xml.attrib
|
||||
except AttributeError:
|
||||
LOG.error('Could not get set metadata %s',
|
||||
coll_plex_id)
|
||||
continue
|
||||
set_api = API(set_xml[0])
|
||||
break
|
||||
elif plex_set_id in children:
|
||||
# Provided by get_metadata thread
|
||||
set_api = API(children[plex_set_id][0])
|
||||
if set_api:
|
||||
self.kodidb.modify_artwork(set_api.artwork(),
|
||||
kodi_set_id,
|
||||
v.KODI_TYPE_SET)
|
||||
# TODO: Once Kodi (19?) supports SEVERAL sets/collections per
|
||||
# movie, support that. For now, we only take the very first
|
||||
# collection/set that Plex returns
|
||||
break
|
||||
|
||||
@staticmethod
|
||||
def _prioritize_provider_id(unique_ids):
|
||||
"""
|
||||
Prioritize which ID ends up in the SHOW table (there can only be 1)
|
||||
tvdb > imdb > tmdb
|
||||
"""
|
||||
return unique_ids.get('imdb',
|
||||
unique_ids.get('tmdb',
|
||||
unique_ids.get('tvdb')))
|
||||
|
|
|
@ -194,19 +194,8 @@ class Show(TvShowMixin, ItemBase):
|
|||
"default",
|
||||
api.rating(),
|
||||
api.votecount())
|
||||
if api.provider('tvdb') is not None:
|
||||
uniqueid = self.kodidb.update_uniqueid(kodi_id,
|
||||
v.KODI_TYPE_SHOW,
|
||||
'tvdb',
|
||||
api.provider('tvdb'))
|
||||
elif api.provider('tmdb') is not None:
|
||||
uniqueid = self.kodidb.update_uniqueid(kodi_id,
|
||||
v.KODI_TYPE_SHOW,
|
||||
'tmdb',
|
||||
api.provider('tmdb'))
|
||||
else:
|
||||
self.kodidb.remove_uniqueid(kodi_id, v.KODI_TYPE_SHOW)
|
||||
uniqueid = -1
|
||||
unique_id = self._prioritize_provider_id(
|
||||
self.update_provider_ids(api, kodi_id))
|
||||
self.kodidb.modify_people(kodi_id,
|
||||
v.KODI_TYPE_SHOW,
|
||||
api.people())
|
||||
|
@ -221,7 +210,7 @@ class Show(TvShowMixin, ItemBase):
|
|||
api.premiere_date(),
|
||||
api.list_to_string(api.genres()),
|
||||
api.title(),
|
||||
uniqueid,
|
||||
unique_id,
|
||||
api.content_rating(),
|
||||
api.list_to_string(api.studios()),
|
||||
api.sorttitle(),
|
||||
|
@ -236,18 +225,8 @@ class Show(TvShowMixin, ItemBase):
|
|||
"default",
|
||||
api.rating(),
|
||||
api.votecount())
|
||||
if api.provider('tvdb'):
|
||||
uniqueid = self.kodidb.add_uniqueid(kodi_id,
|
||||
v.KODI_TYPE_SHOW,
|
||||
api.provider('tvdb'),
|
||||
'tvdb')
|
||||
if api.provider('tmdb'):
|
||||
uniqueid = self.kodidb.add_uniqueid(kodi_id,
|
||||
v.KODI_TYPE_SHOW,
|
||||
api.provider('tmdb'),
|
||||
'tmdb')
|
||||
else:
|
||||
uniqueid = -1
|
||||
unique_id = self._prioritize_provider_id(
|
||||
self.add_provider_ids(api, kodi_id))
|
||||
self.kodidb.add_people(kodi_id,
|
||||
v.KODI_TYPE_SHOW,
|
||||
api.people())
|
||||
|
@ -263,7 +242,7 @@ class Show(TvShowMixin, ItemBase):
|
|||
api.premiere_date(),
|
||||
api.list_to_string(api.genres()),
|
||||
api.title(),
|
||||
uniqueid,
|
||||
unique_id,
|
||||
api.content_rating(),
|
||||
api.list_to_string(api.studios()),
|
||||
api.sorttitle())
|
||||
|
@ -281,6 +260,15 @@ class Show(TvShowMixin, ItemBase):
|
|||
kodi_pathid=kodi_pathid,
|
||||
last_sync=self.last_sync)
|
||||
|
||||
@staticmethod
|
||||
def _prioritize_provider_id(unique_ids):
|
||||
"""
|
||||
Prioritize which ID ends up in the SHOW table (there can only be 1)
|
||||
tvdb > imdb > tmdb
|
||||
"""
|
||||
return unique_ids.get('tvdb',
|
||||
unique_ids.get('imdb',
|
||||
unique_ids.get('tmdb')))
|
||||
|
||||
class Season(TvShowMixin, ItemBase):
|
||||
def add_update(self, xml, section_name=None, section_id=None,
|
||||
|
@ -459,19 +447,8 @@ class Episode(TvShowMixin, ItemBase):
|
|||
"default",
|
||||
api.rating(),
|
||||
api.votecount())
|
||||
if api.provider('tvdb'):
|
||||
uniqueid = self.kodidb.update_uniqueid(kodi_id,
|
||||
v.KODI_TYPE_EPISODE,
|
||||
'tvdb',
|
||||
api.provider('tvdb'))
|
||||
elif api.provider('tmdb'):
|
||||
uniqueid = self.kodidb.update_uniqueid(kodi_id,
|
||||
v.KODI_TYPE_EPISODE,
|
||||
'tmdb',
|
||||
api.provider('tmdb'))
|
||||
else:
|
||||
self.kodidb.remove_uniqueid(kodi_id, v.KODI_TYPE_EPISODE)
|
||||
uniqueid = -1
|
||||
unique_id = self._prioritize_provider_id(
|
||||
self.update_provider_ids(api, kodi_id))
|
||||
self.kodidb.modify_people(kodi_id,
|
||||
v.KODI_TYPE_EPISODE,
|
||||
api.people())
|
||||
|
@ -493,7 +470,7 @@ class Episode(TvShowMixin, ItemBase):
|
|||
airs_before_episode,
|
||||
fullpath,
|
||||
kodi_pathid,
|
||||
uniqueid,
|
||||
unique_id,
|
||||
kodi_fileid, # and NOT kodi_fileid_2
|
||||
parent_id,
|
||||
api.userrating(),
|
||||
|
@ -539,18 +516,8 @@ class Episode(TvShowMixin, ItemBase):
|
|||
"default",
|
||||
api.rating(),
|
||||
api.votecount())
|
||||
if api.provider('tvdb'):
|
||||
uniqueid = self.kodidb.add_uniqueid(kodi_id,
|
||||
v.KODI_TYPE_EPISODE,
|
||||
api.provider('tvdb'),
|
||||
"tvdb")
|
||||
elif api.provider('tmdb'):
|
||||
uniqueid = self.kodidb.add_uniqueid(kodi_id,
|
||||
v.KODI_TYPE_EPISODE,
|
||||
api.provider('tmdb'),
|
||||
"tmdb")
|
||||
else:
|
||||
uniqueid = -1
|
||||
unique_id = self._prioritize_provider_id(
|
||||
self.add_provider_ids(api, kodi_id))
|
||||
self.kodidb.add_people(kodi_id,
|
||||
v.KODI_TYPE_EPISODE,
|
||||
api.people())
|
||||
|
@ -575,7 +542,7 @@ class Episode(TvShowMixin, ItemBase):
|
|||
airs_before_episode,
|
||||
fullpath,
|
||||
kodi_pathid,
|
||||
uniqueid,
|
||||
unique_id,
|
||||
parent_id,
|
||||
api.userrating())
|
||||
self.kodidb.set_resume(kodi_fileid,
|
||||
|
@ -605,3 +572,13 @@ class Episode(TvShowMixin, ItemBase):
|
|||
self.kodidb.modify_streams(kodi_fileid, # and NOT kodi_fileid_2
|
||||
api.mediastreams(),
|
||||
api.runtime())
|
||||
|
||||
@staticmethod
|
||||
def _prioritize_provider_id(unique_ids):
|
||||
"""
|
||||
Prioritize which ID ends up in the SHOW table (there can only be 1)
|
||||
tvdb > imdb > tmdb
|
||||
"""
|
||||
return unique_ids.get('tvdb',
|
||||
unique_ids.get('imdb',
|
||||
unique_ids.get('tmdb')))
|
||||
|
|
|
@ -578,7 +578,7 @@ def _choose_libraries(sections):
|
|||
selectable_sections,
|
||||
preselect=preselected,
|
||||
useDetails=False)
|
||||
if selectable_sections is None:
|
||||
if selected_sections is None:
|
||||
LOG.info('User chose not to select which libraries to sync')
|
||||
return False
|
||||
index = 0
|
||||
|
|
|
@ -265,19 +265,17 @@ def get_resolution():
|
|||
"""
|
||||
chosen = utils.settings('transcoderVideoQualities')
|
||||
res = {
|
||||
'0': '420x420',
|
||||
'1': '576x320',
|
||||
'2': '720x480',
|
||||
'3': '1024x768',
|
||||
'4': '1280x720',
|
||||
'5': '1280x720',
|
||||
'0': '720x480',
|
||||
'1': '1024x768',
|
||||
'2': '1280x720',
|
||||
'3': '1280x720',
|
||||
'4': '1920x1080',
|
||||
'5': '1920x1080',
|
||||
'6': '1920x1080',
|
||||
'7': '1920x1080',
|
||||
'8': '1920x1080',
|
||||
'9': '1920x1080',
|
||||
'10': '1920x1080',
|
||||
'11': '3840x2160',
|
||||
'12': '3840x2160'
|
||||
'9': '3840x2160',
|
||||
'10': '3840x2160'
|
||||
}
|
||||
return res[chosen]
|
||||
|
||||
|
|
|
@ -357,6 +357,10 @@ def sync_plex_playlist(playlist=None, xml=None, plex_id=None):
|
|||
if api.playlist_type() == v.PLEX_TYPE_PHOTO_PLAYLIST:
|
||||
# Not supported by Kodi
|
||||
return False
|
||||
elif api.playlist_type() is None:
|
||||
# Encountered in logs, seems to be a malformed answer
|
||||
LOG.error('Playlist type is missing: %s', api.xml.attrib)
|
||||
return False
|
||||
name = api.title()
|
||||
typus = v.KODI_PLAYLIST_TYPE_FROM_PLEX[api.playlist_type()]
|
||||
if (not app.SYNC.enable_music and typus == v.PLEX_PLAYLIST_TYPE_AUDIO):
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
# -*- 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
|
||||
|
||||
from . import fanart_lookup
|
||||
|
||||
LOG = getLogger('PLEX.api')
|
||||
|
||||
|
||||
|
@ -186,9 +187,9 @@ class Artwork(object):
|
|||
# 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')
|
||||
media_id = self.guids.get('imdb')
|
||||
elif media_type == v.PLEX_TYPE_SHOW:
|
||||
media_id = self.provider('tvdb')
|
||||
media_id = self.guids.get('tvdb')
|
||||
if media_id is not None:
|
||||
return media_id, None, None
|
||||
LOG.info('Plex did not provide ID for IMDB or TVDB. Start '
|
||||
|
@ -196,154 +197,10 @@ class Artwork(object):
|
|||
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
|
||||
return fanart_lookup.external_item_id(self.title(),
|
||||
self.year(),
|
||||
self.plex_type,
|
||||
collection)
|
||||
|
||||
def lookup_fanart_tv(self, media_id, artworks):
|
||||
"""
|
||||
|
|
|
@ -13,6 +13,9 @@ from .. import widgets
|
|||
|
||||
LOG = getLogger('PLEX.api')
|
||||
|
||||
METADATA_PROVIDERS = (('imdb', utils.REGEX_IMDB),
|
||||
('tvdb', utils.REGEX_TVDB),
|
||||
('tmdb', utils.REGEX_TMDB))
|
||||
|
||||
class Base(object):
|
||||
"""
|
||||
|
@ -40,6 +43,7 @@ class Base(object):
|
|||
self._writers = []
|
||||
self._producers = []
|
||||
self._locations = []
|
||||
self._guids = {}
|
||||
self._coll_match = None
|
||||
# Plex DB attributes
|
||||
self._section_id = None
|
||||
|
@ -131,6 +135,11 @@ class Base(object):
|
|||
self.check_db()
|
||||
return self._fanart_synced
|
||||
|
||||
@property
|
||||
def guids(self):
|
||||
self._scan_children()
|
||||
return self._guids
|
||||
|
||||
def check_db(self, plexdb=None):
|
||||
"""
|
||||
Check's whether we synched this item to Kodi. If so, then retrieve the
|
||||
|
@ -457,6 +466,24 @@ class Base(object):
|
|||
elif child.tag == 'Collection':
|
||||
self._collections.append((cast(int, child.get('id')),
|
||||
child.get('tag')))
|
||||
elif child.tag == 'Guid':
|
||||
guid = child.get('id')
|
||||
guid = guid.split('://', 1)
|
||||
self._guids[guid[0]] = guid[1]
|
||||
# Plex Movie agent (legacy) or "normal" Plex tv show agent
|
||||
if not self._guids:
|
||||
guid = self.xml.get('guid')
|
||||
if not guid:
|
||||
return
|
||||
for provider, regex in METADATA_PROVIDERS:
|
||||
provider_id = regex.findall(guid)
|
||||
try:
|
||||
self._guids[provider] = provider_id[0]
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
# There will only ever be one entry
|
||||
break
|
||||
|
||||
def cast(self):
|
||||
"""
|
||||
|
@ -544,33 +571,6 @@ class Base(object):
|
|||
'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
|
||||
elif providername == 'tmdb':
|
||||
# originally e.g. com.plexapp.agents.themoviedb://603?lang=en
|
||||
regex = utils.REGEX_TMDB
|
||||
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
|
||||
|
|
182
resources/lib/plex_api/fanart_lookup.py
Normal file
182
resources/lib/plex_api/fanart_lookup.py
Normal file
|
@ -0,0 +1,182 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, division, unicode_literals
|
||||
from logging import getLogger
|
||||
from re import sub
|
||||
from string import punctuation
|
||||
|
||||
from ..downloadutils import DownloadUtils as DU
|
||||
from .. import utils, variables as v
|
||||
|
||||
LOG = getLogger('PLEX.api.fanartlookup')
|
||||
|
||||
API_KEY = utils.settings('themoviedbAPIKey')
|
||||
|
||||
# How far apart can a video's airing date be (in years)
|
||||
YEARS_APART = 1
|
||||
# levenshtein_distance_ratio() returns a value between 0 (no match) and 1 (full
|
||||
# match). What's the threshold?
|
||||
LEVENSHTEIN_RATIO_THRESHOLD = 0.95
|
||||
# Which character should we ignore when matching video titles?
|
||||
EXCLUDE_CHARS = set(punctuation)
|
||||
|
||||
|
||||
def external_item_id(title, year, plex_type, collection):
|
||||
LOG.debug('Start identifying %s (%s, %s)', title, year, plex_type)
|
||||
year = int(year) if year else None
|
||||
media_type = 'tv' if plex_type == v.PLEX_TYPE_SHOW else plex_type
|
||||
# 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 = data['results']
|
||||
except (AttributeError, KeyError, TypeError):
|
||||
LOG.debug('No match found on themoviedb for %s (%s, %s)',
|
||||
title, year, media_type)
|
||||
return
|
||||
LOG.debug('themoviedb returned results: %s', data)
|
||||
# Some entries don't contain a title or id - get rid of them
|
||||
data = [x for x in data if 'title' in x and 'id' in x]
|
||||
# Get rid of all results that do NOT have a matching release year
|
||||
if year:
|
||||
data = [x for x in data if __year_almost_matches(year, x)]
|
||||
if not data:
|
||||
LOG.debug('Empty results returned by themoviedb for %s (%s, %s)',
|
||||
title, year, media_type)
|
||||
return
|
||||
# Calculate how similar the titles are
|
||||
title = sanitize_string(title)
|
||||
for entry in data:
|
||||
entry['match_score'] = levenshtein_distance_ratio(
|
||||
sanitize_string(entry['title']), title)
|
||||
# (one of the possibly many) best match using levenshtein distance ratio
|
||||
entry = max(data, key=lambda x: x['match_score'])
|
||||
if entry['match_score'] < LEVENSHTEIN_RATIO_THRESHOLD:
|
||||
LOG.debug('Best themoviedb match not good enough: %s', entry)
|
||||
return
|
||||
|
||||
# Check if we got several matches. If so, take the most popular one
|
||||
best_matches = [x for x in data if
|
||||
x['match_score'] == entry['match_score']
|
||||
and 'popularity' in x]
|
||||
entry = max(best_matches, key=lambda x: x['popularity'])
|
||||
LOG.debug('Found themoviedb match: %s', entry)
|
||||
|
||||
# lookup external tmdb_id and perform artwork lookup on fanart.tv
|
||||
tmdb_id = entry.get('id')
|
||||
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 __year_almost_matches(year, entry):
|
||||
try:
|
||||
entry_year = int(entry['release_date'][0:4])
|
||||
except (KeyError, ValueError):
|
||||
return True
|
||||
return abs(year - entry_year) <= YEARS_APART
|
||||
|
||||
|
||||
def sanitize_string(s):
|
||||
s = s.lower().strip()
|
||||
# Get rid of chars in EXCLUDE_CHARS
|
||||
s = ''.join(character for character in s if character not in EXCLUDE_CHARS)
|
||||
# Get rid of multiple spaces
|
||||
s = ' '.join(s.split())
|
||||
return s
|
||||
|
||||
|
||||
def levenshtein_distance_ratio(s, t):
|
||||
"""
|
||||
Calculates levenshtein distance ratio between two strings.
|
||||
The more similar the strings, the closer the result will be to 1.
|
||||
The farther disjunct the string, the closer the result to 0
|
||||
|
||||
https://www.datacamp.com/community/tutorials/fuzzy-string-python
|
||||
"""
|
||||
# Initialize matrix of zeros
|
||||
rows = len(s) + 1
|
||||
cols = len(t) + 1
|
||||
distance = [[0 for x in range(cols)] for y in range(rows)]
|
||||
|
||||
# Populate matrix of zeros with the indeces of each character of both strings
|
||||
for i in range(1, rows):
|
||||
for k in range(1,cols):
|
||||
distance[i][0] = i
|
||||
distance[0][k] = k
|
||||
|
||||
# Iterate over the matrix to compute the cost of deletions,insertions and/or substitutions
|
||||
for col in range(1, cols):
|
||||
for row in range(1, rows):
|
||||
if s[row-1] == t[col-1]:
|
||||
cost = 0 # If the characters are the same in the two strings in a given position [i,j] then the cost is 0
|
||||
else:
|
||||
# In order to align the results with those of the Python Levenshtein package, if we choose to calculate the ratio
|
||||
# the cost of a substitution is 2. If we calculate just distance, then the cost of a substitution is 1.
|
||||
cost = 2
|
||||
distance[row][col] = min(distance[row-1][col] + 1, # Cost of deletions
|
||||
distance[row][col-1] + 1, # Cost of insertions
|
||||
distance[row-1][col-1] + cost) # Cost of substitutions
|
||||
return ((len(s)+len(t)) - distance[row][col]) / (len(s)+len(t))
|
|
@ -92,6 +92,9 @@ class PlexCompanion(backgroundthread.KillableThread):
|
|||
|
||||
@staticmethod
|
||||
def _process_alexa(data):
|
||||
if 'key' not in data or 'containerKey' not in data:
|
||||
LOG.error('Received malformed Alexa data: %s', data)
|
||||
return
|
||||
xml = PF.GetPlexMetadata(data['key'])
|
||||
try:
|
||||
xml[0].attrib
|
||||
|
@ -147,6 +150,9 @@ class PlexCompanion(backgroundthread.KillableThread):
|
|||
|
||||
@staticmethod
|
||||
def _process_playlist(data):
|
||||
if 'containerKey' not in data:
|
||||
LOG.error('Received malformed playlist data: %s', data)
|
||||
return
|
||||
# Get the playqueue ID
|
||||
_, container_key, query = PF.ParseContainerKey(data['containerKey'])
|
||||
try:
|
||||
|
@ -179,6 +185,9 @@ class PlexCompanion(backgroundthread.KillableThread):
|
|||
"""
|
||||
Plex Companion client adjusted audio or subtitle stream
|
||||
"""
|
||||
if 'type' not in data:
|
||||
LOG.error('Received malformed stream data: %s', data)
|
||||
return
|
||||
playqueue = PQ.get_playqueue_from_type(
|
||||
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[data['type']])
|
||||
pos = js.get_position(playqueue.playlistid)
|
||||
|
@ -201,6 +210,9 @@ class PlexCompanion(backgroundthread.KillableThread):
|
|||
"""
|
||||
example data: {'playQueueID': '8475', 'commandID': '11'}
|
||||
"""
|
||||
if 'playQueueID' not in data:
|
||||
LOG.error('Received malformed refresh data: %s', data)
|
||||
return
|
||||
xml = PL.get_pms_playqueue(data['playQueueID'])
|
||||
if xml is None:
|
||||
return
|
||||
|
|
|
@ -131,7 +131,11 @@ def _generate_content(api):
|
|||
# Item is synched to the Kodi db - let's use that info
|
||||
# (will thus e.g. include additional artwork or metadata)
|
||||
item = js.item_details(api.kodi_id, api.kodi_type)
|
||||
else:
|
||||
|
||||
# In rare cases, Kodi's JSON reply does not provide 'title' plus potentially
|
||||
# other fields - let's use the PMS answer to be safe
|
||||
# See https://github.com/croneter/PlexKodiConnect/issues/1129
|
||||
if not api.kodi_id or 'title' not in item:
|
||||
cast = [{
|
||||
'name': x[0],
|
||||
'thumbnail': x[1],
|
||||
|
@ -168,8 +172,9 @@ def _generate_content(api):
|
|||
'trailer': api.trailer(),
|
||||
'tvshowtitle': api.show_title(),
|
||||
'uniqueid': {
|
||||
'imdbnumber': api.provider('imdb') or '',
|
||||
'tvdb_id': api.provider('tvdb') or ''
|
||||
'imdbnumber': api.guids.get('imdb') or '',
|
||||
'tvdb_id': api.guids.get('tvdb') or '',
|
||||
'tmdb_id': api.guids.get('tmdb') or ''
|
||||
},
|
||||
'votes': '0', # [str]!
|
||||
'writer': api.writers(), # list of [str]
|
||||
|
|
|
@ -112,7 +112,7 @@
|
|||
<setting id="resumeJumpBack" type="slider" label="30521" default="10" range="0,1,120" option="int" visible="false"/>
|
||||
<setting type="sep" />
|
||||
<setting id="playType" type="enum" label="30002" values="Try Direct Path|Direct Play|Direct Stream|Force Transcode" default="0" />
|
||||
<setting id="transcoderVideoQualities" type="enum" label="30160" values="420x420, 320kbps|576x320, 720kbps|720x480, 1.5Mbps|1024x768, 2Mbps|720p, 3Mbps|720p, 4Mbps|1080p, 8Mbps|1080p, 10Mbps|1080p, 12Mbps|1080p, 20Mbps|1080p, 40Mbps|4K, 35Mbps|4K, 50Mbps" default="10" /><!-- Video Quality if Transcoding necessary -->
|
||||
<setting id="transcoderVideoQualities" type="enum" label="30160" values="720x480, 1.5Mbps|1024x768, 2Mbps|720p, 3Mbps|720p, 4Mbps|1080p, 8Mbps|1080p, 10Mbps|1080p, 12Mbps|1080p, 20Mbps|1080p, 40Mbps|4K, 35Mbps|4K, 50Mbps" default="10" /><!-- Video Quality if Transcoding necessary -->
|
||||
<setting id="auto_adjust_transcode_quality" label="30161" type="bool" default="false" /><!-- Auto-adjust transcoding quality (deactivate for Chromecast) -->
|
||||
<setting id="maxVideoQualities" type="enum" label="30143" values="320kbps|720kbps|1.5Mbps|2Mbps|3Mbps|4Mbps|8Mbps|10Mbps|12Mbps|20Mbps|40Mbps|deactivated" default="11" /><!-- Always transcode if video bitrate is above -->
|
||||
<setting id="transcodeH265" type="enum" label="30522" default="0" values="Disabled (default)|480p (and higher)|720p (and higher)|1080p (and higher)|4K" /><!-- Force transcode h265/HEVC -->
|
||||
|
|
Loading…
Reference in a new issue