Merge branch 'develop' into translations
This commit is contained in:
commit
1808d0b609
13 changed files with 65 additions and 345 deletions
|
@ -1,5 +1,5 @@
|
|||
[![stable version](https://img.shields.io/badge/stable_version-1.7.7-blue.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect/bin/repository.plexkodiconnect/repository.plexkodiconnect-1.0.0.zip)
|
||||
[![beta version](https://img.shields.io/badge/beta_version-1.7.7-red.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect_BETA/bin-BETA/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.0.zip)
|
||||
[![beta version](https://img.shields.io/badge/beta_version-1.7.8-red.svg?maxAge=60&style=flat) ](https://dl.bintray.com/croneter/PlexKodiConnect_BETA/bin-BETA/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.0.zip)
|
||||
|
||||
[![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)
|
||||
|
|
44
addon.xml
44
addon.xml
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="1.7.7" provider-name="croneter">
|
||||
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="1.7.8" provider-name="croneter">
|
||||
<requires>
|
||||
<import addon="xbmc.python" version="2.1.0"/>
|
||||
<import addon="script.module.requests" version="2.3.0" />
|
||||
|
@ -50,7 +50,14 @@
|
|||
<disclaimer lang="da_DK">Brug på eget ansvar</disclaimer>
|
||||
<disclaimer lang="nl_NL">Gebruik op eigen risico</disclaimer>
|
||||
<disclaimer lang="zh_TW">使用風險由您自己承擔</disclaimer>
|
||||
<news>version 1.7.7
|
||||
<news>version 1.7.8 (beta only)
|
||||
- Fix IMDB id for movies (resync by going to the PKC settings, Advanced, then Repair Local Database)
|
||||
- Increase timeouts for PMS, should fix some connection issues
|
||||
- Move translations to new strings.po system
|
||||
- Fix some TypeErrors
|
||||
- Some code refactoring
|
||||
|
||||
version 1.7.7
|
||||
- Chinese Traditional, thanks @old2tan
|
||||
- Chinese Simplified, thanks @everdream
|
||||
- Browse by folder: also sort by Date Added
|
||||
|
@ -60,6 +67,37 @@ version 1.7.6
|
|||
- Hotfix: Revert Cache missing artwork on PKC startup. This should help with slow PKC startup, videos not being started, lagging PKC, etc.
|
||||
|
||||
version 1.7.5
|
||||
- Dutch translation, thanks @mvanbaak</news>
|
||||
- Dutch translation, thanks @mvanbaak
|
||||
|
||||
version 1.7.4 (beta only)
|
||||
- Show menu item only for appropriate Kodi library: Be careful to start video content through Videos -> Video Addons -> ... and pictures through Pictures -> Picture Addons -> ...
|
||||
- Fix playback error popup when using Alexa
|
||||
- New Italian translations, thanks @nikkux, @chicco83
|
||||
- Update translations
|
||||
- Rewire Kodi ListItem stuff
|
||||
- Fix TypeError for setting ListItem streams
|
||||
- Fix Kodi setContent for images
|
||||
- Fix AttributeError due to missing Kodi sort methods
|
||||
|
||||
version 1.7.3 (beta only)
|
||||
- Fix KeyError for channels if no media streams
|
||||
- Move plex node navigation, playback to main thread
|
||||
- Fix TypeError for malformed browsing xml
|
||||
- Fix IndexError if we can't get a valid xml from PMS
|
||||
- Pass 'None' instead of empty string in url args
|
||||
|
||||
version 1.7.2
|
||||
- Fix for some channels not starting playback
|
||||
|
||||
version 1.7.1
|
||||
- Fix Alexa not doing anything
|
||||
|
||||
version 1.7.0
|
||||
- Amazon Alexa support! Be sure to check the Plex Alexa forum first if you encounter issues; there are still many bugs completely unrelated to PKC
|
||||
- Plex Channels!
|
||||
- Browse video nodes by folder/path
|
||||
- Fix IndexError for playqueues
|
||||
- Update translations
|
||||
- Code optimization</news>
|
||||
</extension>
|
||||
</addon>
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
version 1.7.8 (beta only)
|
||||
- Fix IMDB id for movies (resync by going to the PKC settings, Advanced, then Repair Local Database)
|
||||
- Increase timeouts for PMS, should fix some connection issues
|
||||
- Move translations to new strings.po system
|
||||
- Fix some TypeErrors
|
||||
- Some code refactoring
|
||||
|
||||
version 1.7.7
|
||||
- Chinese Traditional, thanks @old2tan
|
||||
- Chinese Simplified, thanks @everdream
|
||||
|
|
|
@ -123,9 +123,7 @@ class Main():
|
|||
elif mode in ('manualsync', 'repair'):
|
||||
if window('plex_online') != 'true':
|
||||
# Server is not online, do not run the sync
|
||||
dialog('ok',
|
||||
heading=lang(29999),
|
||||
message=lang(39205))
|
||||
dialog('ok', lang(29999), lang(39205))
|
||||
log.error('Not connected to a PMS.')
|
||||
else:
|
||||
if mode == 'repair':
|
||||
|
|
|
@ -1408,7 +1408,7 @@ msgid "Playback Mode"
|
|||
msgstr ""
|
||||
|
||||
msgctxt "#39028"
|
||||
msgid "CAUTION! If you choose \"Native\" mode , you might loose access to certain Plex features such as: Plex trailers and transcoding options. ALL Plex shares need to use direct paths (e.g. smb://myNAS/mymovie.mkv or \\myNAS/mymovie.mkv)!"
|
||||
msgid "CAUTION! If you choose \"Native\" mode , you might loose access to certain Plex features such as: Plex trailers and transcoding options. ALL Plex shares need to use direct paths (e.g. smb://myNAS/mymovie.mkv or \\\\myNAS/mymovie.mkv)!"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#39029"
|
||||
|
@ -1428,15 +1428,15 @@ msgid "Please verify the path. You may need to verify your network credentials i
|
|||
msgstr ""
|
||||
|
||||
msgctxt "#39033"
|
||||
msgid "Transform Plex UNC library paths \\myNas\mymovie.mkv automatically to smb paths, smb://myNas/mymovie.mkv? (recommended)"
|
||||
msgid "Transform Plex UNC library paths \\\\myNas\\mymovie.mkv automatically to smb paths, smb://myNas/mymovie.mkv? (recommended)"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#39034"
|
||||
msgid "Replace Plex UNC paths \\myNas with smb://myNas"
|
||||
msgid "Replace Plex UNC paths \\\\myNas with smb://myNas"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#39035"
|
||||
msgid "Replace Plex paths /volume1/media or \\myserver\media with custom SMB paths smb://NAS/mystuff"
|
||||
msgid "Replace Plex paths /volume1/media or \\\\myserver\\media with custom SMB paths smb://NAS/mystuff"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "#39037"
|
||||
|
|
|
@ -63,10 +63,6 @@ REGEX_TVDB = re_compile(r'''thetvdb:\/\/(.+?)\?''')
|
|||
|
||||
|
||||
class PlexAPI():
|
||||
# CONSTANTS
|
||||
# Timeout for POST/GET commands, I guess in seconds
|
||||
timeout = 10
|
||||
|
||||
def __init__(self):
|
||||
self.g_PMS = {}
|
||||
self.doUtils = DownloadUtils().downloadUrl
|
||||
|
@ -259,7 +255,6 @@ class PlexAPI():
|
|||
"""
|
||||
Checks connection to a Plex server, available at url. Can also be used
|
||||
to check for connection with plex.tv.
|
||||
Will check up to 3x until reply with False
|
||||
|
||||
Override SSL to skip the check by setting verifySSL=False
|
||||
if 'None', SSL will be checked (standard requests setting)
|
||||
|
@ -288,14 +283,13 @@ class PlexAPI():
|
|||
url = url + '/library/onDeck'
|
||||
log.debug("Checking connection to server %s with verifySSL=%s"
|
||||
% (url, verifySSL))
|
||||
# Check up to 3 times before giving up
|
||||
count = 0
|
||||
while count < 1:
|
||||
answer = self.doUtils(url,
|
||||
authenticate=False,
|
||||
headerOptions=headerOptions,
|
||||
verifySSL=verifySSL,
|
||||
timeout=4)
|
||||
timeout=10)
|
||||
if answer is None:
|
||||
log.debug("Could not connect to %s" % url)
|
||||
count += 1
|
||||
|
|
|
@ -377,7 +377,7 @@ def GetMachineIdentifier(url):
|
|||
xml = downloadutils.DownloadUtils().downloadUrl('%s/identity' % url,
|
||||
authenticate=False,
|
||||
verifySSL=False,
|
||||
timeout=4)
|
||||
timeout=10)
|
||||
try:
|
||||
machineIdentifier = xml.attrib['machineIdentifier']
|
||||
except (AttributeError, KeyError):
|
||||
|
|
|
@ -1,257 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
##################################################################################################
|
||||
|
||||
import json
|
||||
import requests
|
||||
import logging
|
||||
|
||||
import clientinfo
|
||||
from utils import window
|
||||
|
||||
##################################################################################################
|
||||
|
||||
# Disable requests logging
|
||||
from requests.packages.urllib3.exceptions import InsecureRequestWarning, InsecurePlatformWarning
|
||||
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
||||
requests.packages.urllib3.disable_warnings(InsecurePlatformWarning)
|
||||
|
||||
log = logging.getLogger("EMBY."+__name__)
|
||||
|
||||
##################################################################################################
|
||||
|
||||
|
||||
class ConnectUtils():
|
||||
|
||||
# Borg - multiple instances, shared state
|
||||
_shared_state = {}
|
||||
clientInfo = clientinfo.ClientInfo()
|
||||
|
||||
# Requests session
|
||||
c = None
|
||||
timeout = 30
|
||||
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.__dict__ = self._shared_state
|
||||
|
||||
|
||||
def setUserId(self, userId):
|
||||
# Reserved for userclient only
|
||||
self.userId = userId
|
||||
log.debug("Set connect userId: %s" % userId)
|
||||
|
||||
def setServer(self, server):
|
||||
# Reserved for userclient only
|
||||
self.server = server
|
||||
log.debug("Set connect server: %s" % server)
|
||||
|
||||
def setToken(self, token):
|
||||
# Reserved for userclient only
|
||||
self.token = token
|
||||
log.debug("Set connect token: %s" % token)
|
||||
|
||||
|
||||
def startSession(self):
|
||||
|
||||
self.deviceId = self.clientInfo.getDeviceId()
|
||||
|
||||
# User is identified from this point
|
||||
# Attach authenticated header to the session
|
||||
verify = False
|
||||
header = self.getHeader()
|
||||
|
||||
# If user enabled host certificate verification
|
||||
try:
|
||||
verify = self.sslverify
|
||||
if self.sslclient is not None:
|
||||
verify = self.sslclient
|
||||
except:
|
||||
log.info("Could not load SSL settings.")
|
||||
|
||||
# Start session
|
||||
self.c = requests.Session()
|
||||
self.c.headers = header
|
||||
self.c.verify = verify
|
||||
# Retry connections to the server
|
||||
self.c.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
|
||||
self.c.mount("https://", requests.adapters.HTTPAdapter(max_retries=1))
|
||||
|
||||
log.info("Requests session started on: %s" % self.server)
|
||||
|
||||
def stopSession(self):
|
||||
try:
|
||||
self.c.close()
|
||||
except Exception:
|
||||
log.warn("Requests session could not be terminated")
|
||||
|
||||
def getHeader(self, authenticate=True):
|
||||
|
||||
version = self.clientInfo.getVersion()
|
||||
|
||||
if not authenticate:
|
||||
# If user is not authenticated
|
||||
header = {
|
||||
|
||||
'X-Application': "Kodi/%s" % version,
|
||||
'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
'Accept': "application/json"
|
||||
}
|
||||
log.info("Header: %s" % header)
|
||||
|
||||
else:
|
||||
token = self.token
|
||||
# Attached to the requests session
|
||||
header = {
|
||||
|
||||
'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
'Accept': "application/json",
|
||||
'X-Application': "Kodi/%s" % version,
|
||||
'X-Connect-UserToken': token
|
||||
}
|
||||
log.info("Header: %s" % header)
|
||||
|
||||
return header
|
||||
|
||||
def doUrl(self, url, data=None, postBody=None, rtype="GET",
|
||||
parameters=None, authenticate=True, timeout=None):
|
||||
|
||||
log.debug("=== ENTER connectUrl ===")
|
||||
|
||||
default_link = ""
|
||||
|
||||
if timeout is None:
|
||||
timeout = self.timeout
|
||||
|
||||
# Get requests session
|
||||
try:
|
||||
# If connect user is authenticated
|
||||
if authenticate:
|
||||
try:
|
||||
c = self.c
|
||||
# Replace for the real values
|
||||
url = url.replace("{server}", self.server)
|
||||
url = url.replace("{UserId}", self.userId)
|
||||
|
||||
# Prepare request
|
||||
if rtype == "GET":
|
||||
r = c.get(url, json=postBody, params=parameters, timeout=timeout)
|
||||
elif rtype == "POST":
|
||||
r = c.post(url, data=data, timeout=timeout)
|
||||
elif rtype == "DELETE":
|
||||
r = c.delete(url, json=postBody, timeout=timeout)
|
||||
|
||||
except AttributeError:
|
||||
# request session does not exists
|
||||
self.server = "https://connect.emby.media/service"
|
||||
self.userId = window('embyco_currUser')
|
||||
self.token = window('embyco_accessToken%s' % self.userId)
|
||||
|
||||
header = self.getHeader()
|
||||
verifyssl = False
|
||||
|
||||
# If user enables ssl verification
|
||||
try:
|
||||
verifyssl = self.sslverify
|
||||
if self.sslclient is not None:
|
||||
verifyssl = self.sslclient
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# Prepare request
|
||||
if rtype == "GET":
|
||||
r = requests.get(url,
|
||||
json=postBody,
|
||||
params=parameters,
|
||||
headers=header,
|
||||
timeout=timeout,
|
||||
verify=verifyssl)
|
||||
|
||||
elif rtype == "POST":
|
||||
r = requests.post(url,
|
||||
data=data,
|
||||
headers=header,
|
||||
timeout=timeout,
|
||||
verify=verifyssl)
|
||||
# If user is not authenticated
|
||||
else:
|
||||
header = self.getHeader(authenticate=False)
|
||||
verifyssl = False
|
||||
|
||||
# If user enables ssl verification
|
||||
try:
|
||||
verifyssl = self.sslverify
|
||||
if self.sslclient is not None:
|
||||
verifyssl = self.sslclient
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# Prepare request
|
||||
if rtype == "GET":
|
||||
r = requests.get(url,
|
||||
json=postBody,
|
||||
params=parameters,
|
||||
headers=header,
|
||||
timeout=timeout,
|
||||
verify=verifyssl)
|
||||
|
||||
elif rtype == "POST":
|
||||
r = requests.post(url,
|
||||
data=data,
|
||||
headers=header,
|
||||
timeout=timeout,
|
||||
verify=verifyssl)
|
||||
|
||||
##### THE RESPONSE #####
|
||||
log.info(r.url)
|
||||
log.info(r)
|
||||
|
||||
if r.status_code == 204:
|
||||
# No body in the response
|
||||
log.info("====== 204 Success ======")
|
||||
|
||||
elif r.status_code == requests.codes.ok:
|
||||
|
||||
try:
|
||||
# UNICODE - JSON object
|
||||
r = r.json()
|
||||
log.info("====== 200 Success ======")
|
||||
log.info("Response: %s" % r)
|
||||
return r
|
||||
|
||||
except:
|
||||
if r.headers.get('content-type') != "text/html":
|
||||
log.info("Unable to convert the response for: %s" % url)
|
||||
else:
|
||||
r.raise_for_status()
|
||||
|
||||
##### EXCEPTIONS #####
|
||||
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
# Make the addon aware of status
|
||||
pass
|
||||
|
||||
except requests.exceptions.ConnectTimeout as e:
|
||||
log.warn("Server timeout at: %s" % url)
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
|
||||
if r.status_code == 401:
|
||||
# Unauthorized
|
||||
pass
|
||||
|
||||
elif r.status_code in (301, 302):
|
||||
# Redirects
|
||||
pass
|
||||
elif r.status_code == 400:
|
||||
# Bad requests
|
||||
pass
|
||||
|
||||
except requests.exceptions.SSLError as e:
|
||||
log.warn("Invalid SSL certificate for: %s" % url)
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
log.warn("Unknown error connecting to: %s" % url)
|
||||
|
||||
return default_link
|
|
@ -34,11 +34,11 @@ class DownloadUtils():
|
|||
connectionAttempts = 2
|
||||
# How many 401 returns before declaring unauthorized?
|
||||
unauthorizedAttempts = 2
|
||||
# How long should we wait for an answer from the
|
||||
timeout = 30.0
|
||||
|
||||
def __init__(self):
|
||||
self.__dict__ = self._shared_state
|
||||
# Requests session
|
||||
self.timeout = 30.0
|
||||
|
||||
def setUsername(self, username):
|
||||
"""
|
||||
|
|
|
@ -768,7 +768,7 @@ def channels():
|
|||
xml = downloadutils.DownloadUtils().downloadUrl('{server}/channels/all')
|
||||
try:
|
||||
xml[0].attrib
|
||||
except (ValueError, AttributeError, IndexError):
|
||||
except (ValueError, AttributeError, IndexError, TypeError):
|
||||
log.error('Could not download Plex Channels')
|
||||
return xbmcplugin.endOfDirectory(HANDLE, False)
|
||||
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
#################################################################################################
|
||||
|
||||
import logging
|
||||
import threading
|
||||
import requests
|
||||
|
||||
# Disable annoying requests warnings
|
||||
import requests.packages.urllib3
|
||||
requests.packages.urllib3.disable_warnings()
|
||||
#################################################################################################
|
||||
|
||||
log = logging.getLogger("PLEX."+__name__)
|
||||
|
||||
#################################################################################################
|
||||
|
||||
|
||||
class ImageCacheThread(threading.Thread):
|
||||
|
||||
url_to_process = None
|
||||
is_finished = False
|
||||
|
||||
xbmc_host = ""
|
||||
xbmc_port = ""
|
||||
xbmc_username = ""
|
||||
xbmc_password = ""
|
||||
|
||||
|
||||
def __init__(self):
|
||||
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
|
||||
def set_url(self, url):
|
||||
|
||||
self.url_to_process = url
|
||||
|
||||
def set_host(self, host, port):
|
||||
|
||||
self.xbmc_host = host
|
||||
self.xbmc_port = port
|
||||
|
||||
def set_auth(self, username, password):
|
||||
|
||||
self.xbmc_username = username
|
||||
self.xbmc_password = password
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
response = requests.head(
|
||||
url=("http://%s:%s/image/image://%s"
|
||||
% (self.xbmc_host, self.xbmc_port, self.url_to_process)),
|
||||
auth=(self.xbmc_username, self.xbmc_password),
|
||||
timeout=(5, 5))
|
||||
# We don't need the result
|
||||
except Exception:
|
||||
pass
|
||||
self.is_finished = True
|
|
@ -407,11 +407,9 @@ class InitialSetup():
|
|||
|
||||
# If a Plex server IP has already been set
|
||||
# return only if the right machine identifier is found
|
||||
getNewIP = False
|
||||
if self.server:
|
||||
log.info("PMS is already set: %s. Checking now..." % self.server)
|
||||
getNewIP = not self.CheckPMS()
|
||||
if getNewIP is False:
|
||||
if self.CheckPMS():
|
||||
log.info("Using PMS %s with machineIdentifier %s"
|
||||
% (self.server, self.serverid))
|
||||
self._write_PMS_settings(self.server, self.pms_token)
|
||||
|
|
|
@ -341,7 +341,7 @@ class Movies(Items):
|
|||
WHERE idMovie = ?
|
||||
'''
|
||||
kodicursor.execute(query, (title, plot, shortplot, tagline,
|
||||
votecount, rating_id, writer, year, imdb, sorttitle,
|
||||
votecount, rating_id, writer, year, uniqueid, sorttitle,
|
||||
runtime, mpaa, genre, director, title, studio, trailer,
|
||||
country, playurl, pathid, fileid, year,
|
||||
userdata['UserRating'], movieid))
|
||||
|
@ -372,7 +372,8 @@ class Movies(Items):
|
|||
rating,
|
||||
votecount)
|
||||
# add new uniqueid Kodi 17
|
||||
self.kodi_db.add_uniqueid(self.kodi_db.create_entry_uniqueid(),
|
||||
uniqueid = self.kodi_db.create_entry_uniqueid()
|
||||
self.kodi_db.add_uniqueid(uniqueid,
|
||||
movieid,
|
||||
v.KODI_TYPE_MOVIE,
|
||||
imdb,
|
||||
|
@ -386,8 +387,8 @@ class Movies(Items):
|
|||
'''
|
||||
kodicursor.execute(query, (movieid, fileid, title, plot,
|
||||
shortplot, tagline, votecount, rating_id, writer, year,
|
||||
imdb, sorttitle, runtime, mpaa, genre, director, title,
|
||||
studio, trailer, country, playurl, pathid, year,
|
||||
uniqueid, sorttitle, runtime, mpaa, genre, director,
|
||||
title, studio, trailer, country, playurl, pathid, year,
|
||||
userdata['UserRating']))
|
||||
else:
|
||||
query = '''
|
||||
|
|
Loading…
Reference in a new issue