# -*- coding: utf-8 -*-
"""
Taken from iBaa, https://github.com/iBaa/PlexConnect
Point of time: December 22, 2015


Collection of "connector functions" to Plex Media Server/MyPlex


PlexGDM:
loosely based on hippojay's plexGDM:
https://github.com/hippojay/script.plexbmc.helper... /resources/lib/plexgdm.py


Plex Media Server communication:
source (somewhat): https://github.com/hippojay/plugin.video.plexbmc
later converted from httplib to urllib2


Transcoder support:
PlexAPI_getTranscodePath() based on getTranscodeURL from pyplex/plexAPI
https://github.com/megawubs/pyplex/blob/master/plexAPI/info.py


MyPlex - Basic Authentication:
http://www.voidspace.org.uk/python/articles/urllib2.shtml
http://www.voidspace.org.uk/python/articles/authentication.shtml
http://stackoverflow.com/questions/2407126/python-urllib2-basic-auth-problem
http://stackoverflow.com/questions/111945/is-there-any-way-to-do-http-put-in-python
(and others...)
"""

import logging
from time import time
import urllib2
import socket
from threading import Thread
import xml.etree.ElementTree as etree
from re import compile as re_compile, sub
from json import dumps
from urllib import urlencode, quote_plus, unquote

import xbmcgui
from xbmc import sleep, executebuiltin
from xbmcvfs import exists

import clientinfo as client
from downloadutils import DownloadUtils
from utils import window, settings, language as lang, tryDecode, tryEncode, \
    DateToKodi
from PlexFunctions import PMSHttpsEnabled
import plexdb_functions as plexdb
import variables as v

###############################################################################

log = logging.getLogger("PLEX."+__name__)

REGEX_IMDB = re_compile(r'''/(tt\d+)''')
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

    def GetPlexLoginFromSettings(self):
        """
        Returns a dict:
            'plexLogin': settings('plexLogin'),
            'plexToken': settings('plexToken'),
            'plexhome': settings('plexhome'),
            'plexid': settings('plexid'),
            'myplexlogin': settings('myplexlogin'),
            'plexAvatar': settings('plexAvatar'),
            'plexHomeSize': settings('plexHomeSize')

        Returns strings or unicode

        Returns empty strings '' for a setting if not found.

        myplexlogin is 'true' if user opted to log into plex.tv (the default)
        plexhome is 'true' if plex home is used (the default)
        """
        return {
            'plexLogin': settings('plexLogin'),
            'plexToken': settings('plexToken'),
            'plexhome': settings('plexhome'),
            'plexid': settings('plexid'),
            'myplexlogin': settings('myplexlogin'),
            'plexAvatar': settings('plexAvatar'),
            'plexHomeSize': settings('plexHomeSize')
        }

    def GetPlexLoginAndPassword(self):
        """
        Signs in to plex.tv.

        plexLogin, authtoken = GetPlexLoginAndPassword()

        Input: nothing
        Output:
            plexLogin       plex.tv username
            authtoken       token for plex.tv

        Also writes 'plexLogin' and 'token_plex.tv' to Kodi settings file
        If not logged in, empty strings are returned for both.
        """
        retrievedPlexLogin = ''
        plexLogin = 'dummy'
        authtoken = ''
        dialog = xbmcgui.Dialog()
        while retrievedPlexLogin == '' and plexLogin != '':
            # Enter plex.tv username. Or nothing to cancel.
            plexLogin = dialog.input(lang(29999) + lang(39300),
                                     type=xbmcgui.INPUT_ALPHANUM)
            if plexLogin != "":
                # Enter password for plex.tv user
                plexPassword = dialog.input(
                    lang(39301) + plexLogin,
                    type=xbmcgui.INPUT_ALPHANUM,
                    option=xbmcgui.ALPHANUM_HIDE_INPUT)
                retrievedPlexLogin, authtoken = self.MyPlexSignIn(
                    plexLogin,
                    plexPassword,
                    {'X-Plex-Client-Identifier': window('plex_client_Id')})
                log.debug("plex.tv username and token: %s, %s"
                          % (plexLogin, authtoken))
                if plexLogin == '':
                    # Could not sign in user
                    dialog.ok(lang(29999), lang(39302) + plexLogin)
        # Write to Kodi settings file
        settings('plexLogin', value=retrievedPlexLogin)
        settings('plexToken', value=authtoken)
        return (retrievedPlexLogin, authtoken)

    def PlexTvSignInWithPin(self):
        """
        Prompts user to sign in by visiting https://plex.tv/pin

        Writes to Kodi settings file. Also returns:
        {
            'plexhome':          'true' if Plex Home, 'false' otherwise
            'username':
            'avatar':             URL to user avator
            'token':
            'plexid':             Plex user ID
            'homesize':           Number of Plex home users (defaults to '1')
        }
        Returns False if authentication did not work.
        """
        code, identifier = self.GetPlexPin()
        dialog = xbmcgui.Dialog()
        if not code:
            # Problems trying to contact plex.tv. Try again later
            dialog.ok(lang(29999), lang(39303))
            return False
        # Go to https://plex.tv/pin and enter the code:
        # Or press No to cancel the sign in.
        answer = dialog.yesno(lang(29999),
                              lang(39304) + "\n\n",
                              code + "\n\n",
                              lang(39311))
        if not answer:
            return False
        count = 0
        # Wait for approx 30 seconds (since the PIN is not visible anymore :-))
        while count < 30:
            xml = self.CheckPlexTvSignin(identifier)
            if xml is not False:
                break
            # Wait for 1 seconds
            sleep(1000)
            count += 1
        if xml is False:
            # Could not sign in to plex.tv Try again later
            dialog.ok(lang(29999), lang(39305))
            return False
        # Parse xml
        userid = xml.attrib.get('id')
        home = xml.get('home', '0')
        if home == '1':
            home = 'true'
        else:
            home = 'false'
        username = xml.get('username', '')
        avatar = xml.get('thumb', '')
        token = xml.findtext('authentication-token')
        homeSize = xml.get('homeSize', '1')
        result = {
            'plexhome': home,
            'username': username,
            'avatar': avatar,
            'token': token,
            'plexid': userid,
            'homesize': homeSize
        }
        settings('plexLogin', username)
        settings('plexToken', token)
        settings('plexhome', home)
        settings('plexid', userid)
        settings('plexAvatar', avatar)
        settings('plexHomeSize', homeSize)
        # Let Kodi log into plex.tv on startup from now on
        settings('myplexlogin', 'true')
        settings('plex_status', value='Logged in to plex.tv')
        return result

    def CheckPlexTvSignin(self, identifier):
        """
        Checks with plex.tv whether user entered the correct PIN on plex.tv/pin

        Returns False if not yet done so, or the XML response file as etree
        """
        # Try to get a temporary token
        xml = self.doUtils('https://plex.tv/pins/%s.xml' % identifier,
                           authenticate=False)
        try:
            temp_token = xml.find('auth_token').text
        except:
            log.error("Could not find token in plex.tv answer")
            return False
        if not temp_token:
            return False
        # Use temp token to get the final plex credentials
        xml = self.doUtils('https://plex.tv/users/account',
                           authenticate=False,
                           parameters={'X-Plex-Token': temp_token})
        return xml

    def GetPlexPin(self):
        """
        For plex.tv sign-in: returns 4-digit code and identifier as 2 str
        """
        code = None
        identifier = None
        # Download
        xml = self.doUtils('https://plex.tv/pins.xml',
                           authenticate=False,
                           action_type="POST")
        try:
            xml.attrib
        except:
            log.error("Error, no PIN from plex.tv provided")
            return None, None
        code = xml.find('code').text
        identifier = xml.find('id').text
        log.info('Successfully retrieved code and id from plex.tv')
        return code, identifier

    def CheckConnection(self, url, token=None, verifySSL=None):
        """
        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)
        if 'True', SSL settings from file settings are used (False/True)

        Input:
            url         URL to Plex server (e.g. https://192.168.1.1:32400)
            token       appropriate token to access server. If None is passed,
                        the current token is used
        Output:
            False       if server could not be reached or timeout occured
            200         if connection was successfull
            int         or other HTML status codes as received from the server
        """
        # Add '/clients' to URL because then an authentication is necessary
        # If a plex.tv URL was passed, this does not work.
        headerOptions = None
        if token is not None:
            headerOptions = {'X-Plex-Token': token}
        if verifySSL is True:
            verifySSL = None if settings('sslverify') == 'true' \
                else False
        if 'plex.tv' in url:
            url = 'https://plex.tv/api/home/users'
        else:
            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)
            if answer is None:
                log.debug("Could not connect to %s" % url)
                count += 1
                sleep(500)
                continue
            try:
                # xml received?
                answer.attrib
            except:
                if answer is True:
                    # Maybe no xml but connection was successful nevertheless
                    answer = 200
            else:
                # Success - we downloaded an xml!
                answer = 200
            # We could connect but maybe were not authenticated. No worries
            log.debug("Checking connection successfull. Answer: %s" % answer)
            return answer
        log.debug('Failed to connect to %s too many times. PMS is dead' % url)
        return False

    def GetgPMSKeylist(self):
        """
        Returns a list of all keys that are saved for every entry in the
        g_PMS variable. 
        """
        keylist = [
            'address',
            'baseURL',
            'enableGzip',
            'ip',
            'local',
            'name',
            'owned',
            'port',
            'scheme'
        ]
        return keylist

    def declarePMS(self, uuid, name, scheme, ip, port):
        """
        Plex Media Server handling

        parameters:
            uuid - PMS ID
            name, scheme, ip, port, type, owned, token
        """
        address = ip + ':' + port
        baseURL = scheme + '://' + ip + ':' + port
        self.g_PMS[uuid] = {
            'name': name,
            'scheme': scheme,
            'ip': ip,
            'port': port,
            'address': address,
            'baseURL': baseURL,
            'local': '1',
            'owned': '1',
            'accesstoken': '',
            'enableGzip': False
        }

    def updatePMSProperty(self, uuid, tag, value):
        # set property element of PMS by UUID
        try:
            self.g_PMS[uuid][tag] = value
        except:
            log.error('%s has not yet been declared ' % uuid)
            return False

    def getPMSProperty(self, uuid, tag):
        # get name of PMS by UUID
        try:
            answ = self.g_PMS[uuid].get(tag, '')
        except:
            log.error('%s not found in PMS catalogue' % uuid)
            answ = False
        return answ

    def PlexGDM(self):
        """
        PlexGDM

        parameters:
            none
        result:
            PMS_list - dict() of PMSs found
        """
        import struct

        IP_PlexGDM = '239.0.0.250'  # multicast to PMS
        Port_PlexGDM = 32414
        Msg_PlexGDM = 'M-SEARCH * HTTP/1.0'

        # setup socket for discovery -> multicast message
        GDM = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        GDM.settimeout(2.0)

        # Set the time-to-live for messages to 2 for local network
        ttl = struct.pack('b', 2)
        GDM.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)

        returnData = []
        try:
            # Send data to the multicast group
            GDM.sendto(Msg_PlexGDM, (IP_PlexGDM, Port_PlexGDM))

            # Look for responses from all recipients
            while True:
                try:
                    data, server = GDM.recvfrom(1024)
                    returnData.append({'from': server,
                                       'data': data})
                except socket.timeout:
                    break
        except Exception as e:
            # Probably error: (101, 'Network is unreachable')
            log.error(e)
            import traceback
            log.error("Traceback:\n%s" % traceback.format_exc())
        finally:
            GDM.close()

        pmsList = {}
        for response in returnData:
            update = {'ip': response.get('from')[0]}
            # Check if we had a positive HTTP response
            if "200 OK" not in response.get('data'):
                continue
            for each in response.get('data').split('\n'):
                # decode response data
                update['discovery'] = "auto"
                # update['owned']='1'
                # update['master']= 1
                # update['role']='master'

                if "Content-Type:" in each:
                    update['content-type'] = each.split(':')[1].strip()
                elif "Resource-Identifier:" in each:
                    update['uuid'] = each.split(':')[1].strip()
                elif "Name:" in each:
                    update['serverName'] = tryDecode(
                        each.split(':')[1].strip())
                elif "Port:" in each:
                    update['port'] = each.split(':')[1].strip()
                elif "Updated-At:" in each:
                    update['updated'] = each.split(':')[1].strip()
                elif "Version:" in each:
                    update['version'] = each.split(':')[1].strip()
            pmsList[update['uuid']] = update
        return pmsList

    def discoverPMS(self, IP_self, plexToken=None):
        """
        parameters:
            IP_self         Own IP
        optional:
            plexToken       token for plex.tv
        result:
            self.g_PMS      dict set
        """
        self.g_PMS = {}

        # Look first for local PMS in the LAN
        pmsList = self.PlexGDM()
        log.debug('PMS found in the local LAN via GDM: %s' % pmsList)

        # Get PMS from plex.tv
        if plexToken:
            log.info('Checking with plex.tv for more PMS to connect to')
            self.getPMSListFromMyPlex(plexToken)
        else:
            log.info('No plex token supplied, only checked LAN for PMS')

        for uuid in pmsList:
            PMS = pmsList[uuid]
            if PMS['uuid'] in self.g_PMS:
                log.debug('We already know of PMS %s from plex.tv'
                          % PMS['serverName'])
                # Update with GDM data - potentially more reliable than plex.tv
                self.updatePMSProperty(PMS['uuid'], 'ip', PMS['ip'])
                self.updatePMSProperty(PMS['uuid'], 'port', PMS['port'])
                self.updatePMSProperty(PMS['uuid'], 'local', '1')
                self.updatePMSProperty(PMS['uuid'], 'scheme', 'http')
                self.updatePMSProperty(PMS['uuid'],
                                       'baseURL',
                                       'http://%s:%s' % (PMS['ip'],
                                                         PMS['port']))
            else:
                self.declarePMS(PMS['uuid'], PMS['serverName'], 'http',
                                PMS['ip'], PMS['port'])
            # Ping to check whether we need HTTPs or HTTP
            https = PMSHttpsEnabled('%s:%s' % (PMS['ip'], PMS['port']))
            if https is None:
                # Error contacting url. Skip for now
                continue
            elif https is True:
                self.updatePMSProperty(PMS['uuid'], 'scheme', 'https')
                self.updatePMSProperty(
                    PMS['uuid'],
                    'baseURL',
                    'https://%s:%s' % (PMS['ip'], PMS['port']))
            else:
                # Already declared with http
                pass

        # install plex.tv "virtual" PMS - for myPlex, PlexHome
        # self.declarePMS('plex.tv', 'plex.tv', 'https', 'plex.tv', '443')
        # self.updatePMSProperty('plex.tv', 'local', '-')
        # self.updatePMSProperty('plex.tv', 'owned', '-')
        # self.updatePMSProperty(
        #     'plex.tv', 'accesstoken', plexToken)
        # (remote and local) servers from plex.tv

    def getPMSListFromMyPlex(self, token):
        """
        getPMSListFromMyPlex

        get Plex media Server List from plex.tv/pms/resources
        """
        xml = self.doUtils('https://plex.tv/api/resources',
                           authenticate=False,
                           parameters={'includeHttps': 1},
                           headerOptions={'X-Plex-Token': token})
        try:
            xml.attrib
        except AttributeError:
            log.error('Could not get list of PMS from plex.tv')
            return

        import Queue
        queue = Queue.Queue()
        threadQueue = []

        maxAgeSeconds = 2*60*60*24
        for Dir in xml.findall('Device'):
            if 'server' not in Dir.get('provides'):
                # No PMS - skip
                continue
            if Dir.find('Connection') is None:
                # no valid connection - skip
                continue

            # check MyPlex data age - skip if >2 days
            PMS = {}
            PMS['name'] = Dir.get('name')
            infoAge = time() - int(Dir.get('lastSeenAt'))
            if infoAge > maxAgeSeconds:
                log.debug("Server %s not seen for 2 days - skipping."
                          % PMS['name'])
                continue

            PMS['uuid'] = Dir.get('clientIdentifier')
            PMS['token'] = Dir.get('accessToken', token)
            PMS['owned'] = Dir.get('owned', '1')
            PMS['local'] = Dir.get('publicAddressMatches')
            PMS['ownername'] = Dir.get('sourceTitle', '')
            PMS['path'] = '/'
            PMS['options'] = None

            # Try a local connection first
            # Backup to remote connection, if that failes
            PMS['connections'] = []
            for Con in Dir.findall('Connection'):
                if Con.get('local') == '1':
                    PMS['connections'].append(Con)
            # Append non-local
            for Con in Dir.findall('Connection'):
                if Con.get('local') != '1':
                    PMS['connections'].append(Con)

            t = Thread(target=self.pokePMS,
                       args=(PMS, queue))
            threadQueue.append(t)

        maxThreads = 5
        threads = []
        # poke PMS, own thread for each PMS
        while True:
            # Remove finished threads
            for t in threads:
                if not t.isAlive():
                    threads.remove(t)
            if len(threads) < maxThreads:
                try:
                    t = threadQueue.pop()
                except IndexError:
                    # We have done our work
                    break
                else:
                    t.start()
                    threads.append(t)
            else:
                sleep(50)

        # wait for requests being answered
        for t in threads:
            t.join()

        # declare new PMSs
        while not queue.empty():
            PMS = queue.get()
            self.declarePMS(PMS['uuid'], PMS['name'],
                            PMS['protocol'], PMS['ip'], PMS['port'])
            self.updatePMSProperty(
                PMS['uuid'], 'accesstoken', PMS['token'])
            self.updatePMSProperty(
                PMS['uuid'], 'owned', PMS['owned'])
            self.updatePMSProperty(
                PMS['uuid'], 'local', PMS['local'])
            # set in declarePMS, overwrite for https encryption
            self.updatePMSProperty(
                PMS['uuid'], 'baseURL', PMS['baseURL'])
            self.updatePMSProperty(
                PMS['uuid'], 'ownername', PMS['ownername'])
            log.debug('Found PMS %s: %s'
                      % (PMS['uuid'], self.g_PMS[PMS['uuid']]))
            queue.task_done()

    def pokePMS(self, PMS, queue):
        data = PMS['connections'][0].attrib
        if data['local'] == '1':
            protocol = data['protocol']
            address = data['address']
            port = data['port']
            url = '%s://%s:%s' % (protocol, address, port)
        else:
            url = data['uri']
            if url.count(':') == 1:
                url = '%s:%s' % (url, data['port'])
            protocol, address, port = url.split(':', 2)
            address = address.replace('/', '')

        xml = self.doUtils('%s/identity' % url,
                           authenticate=False,
                           headerOptions={'X-Plex-Token': PMS['token']},
                           verifySSL=False,
                           timeout=3)
        try:
            xml.attrib['machineIdentifier']
        except (AttributeError, KeyError):
            # No connection, delete the one we just tested
            del PMS['connections'][0]
            if len(PMS['connections']) > 0:
                # Still got connections left, try them
                return self.pokePMS(PMS, queue)
            return
        else:
            # Connection successful - correct PMS?
            if xml.get('machineIdentifier') == PMS['uuid']:
                # process later
                PMS['baseURL'] = url
                PMS['protocol'] = protocol
                PMS['ip'] = address
                PMS['port'] = port
                queue.put(PMS)
                return
        log.info('Found a PMS at %s, but the expected machineIdentifier of '
                 '%s did not match the one we found: %s'
                 % (url, PMS['uuid'], xml.get('machineIdentifier')))

    def MyPlexSignIn(self, username, password, options):
        """
        MyPlex Sign In, Sign Out

        parameters:
            username - Plex forum name, MyPlex login, or email address
            password
            options - dict() of PlexConnect-options as received from aTV - necessary: PlexConnectUDID
        result:
            username
            authtoken - token for subsequent communication with MyPlex
        """
        # MyPlex web address
        MyPlexHost = 'plex.tv'
        MyPlexSignInPath = '/users/sign_in.xml'
        MyPlexURL = 'https://' + MyPlexHost + MyPlexSignInPath

        # create POST request
        xargs = client.getXArgsDeviceInfo(options)
        request = urllib2.Request(MyPlexURL, None, xargs)
        request.get_method = lambda: 'POST'

        passmanager = urllib2.HTTPPasswordMgr()
        passmanager.add_password(MyPlexHost, MyPlexURL, username, password)
        authhandler = urllib2.HTTPBasicAuthHandler(passmanager)
        urlopener = urllib2.build_opener(authhandler)

        # sign in, get MyPlex response
        try:
            response = urlopener.open(request).read()
        except urllib2.HTTPError as e:
            if e.code == 401:
                log.info("Authentication failed")
                return ('', '')
            else:
                raise
        # analyse response
        XMLTree = etree.ElementTree(etree.fromstring(response))

        el_username = XMLTree.find('username')
        el_authtoken = XMLTree.find('authentication-token')
        if el_username is None or \
           el_authtoken is None:
            username = ''
            authtoken = ''
        else:
            username = el_username.text
            authtoken = el_authtoken.text
        return (username, authtoken)

    def MyPlexSignOut(self, authtoken):
        """
        TO BE DONE!
        """
        # MyPlex web address
        MyPlexHost = 'plex.tv'
        MyPlexSignOutPath = '/users/sign_out.xml'
        MyPlexURL = 'http://' + MyPlexHost + MyPlexSignOutPath

        # create POST request
        xargs = {'X-Plex-Token': authtoken}
        request = urllib2.Request(MyPlexURL, None, xargs)
        # turn into 'POST' - done automatically with data!=None. But we don't
        # have data.
        request.get_method = lambda: 'POST'

        response = urllib2.urlopen(request).read()

    def GetUserArtworkURL(self, username):
        """
        Returns the URL for the user's Avatar. Or False if something went
        wrong.
        """
        plexToken = settings('plexToken')
        users = self.MyPlexListHomeUsers(plexToken)
        url = ''
        # If an error is encountered, set to False
        if not users:
            log.info("Couldnt get user from plex.tv. No URL for user avatar")
            return False
        for user in users:
            if username in user['title']:
                url = user['thumb']
        log.debug("Avatar url for user %s is: %s" % (username, url))
        return url

    def ChoosePlexHomeUser(self, plexToken):
        """
        Let's user choose from a list of Plex home users. Will switch to that
        user accordingly.

        Returns a dict:
        {
            'username':             Unicode
            'userid': ''            Plex ID of the user
            'token': ''             User's token
            'protected':            True if PIN is needed, else False
        }

        Will return False if something went wrong (wrong PIN, no connection)
        """
        dialog = xbmcgui.Dialog()

        # Get list of Plex home users
        users = self.MyPlexListHomeUsers(plexToken)
        if not users:
            log.error("User download failed.")
            return False

        userlist = []
        userlistCoded = []
        for user in users:
            username = user['title']
            userlist.append(username)
            # To take care of non-ASCII usernames
            userlistCoded.append(tryEncode(username))
        usernumber = len(userlist)

        username = ''
        usertoken = ''
        trials = 0
        while trials < 3:
            if usernumber > 1:
                # Select user
                user_select = dialog.select(
                    lang(29999) + lang(39306),
                    userlistCoded)
                if user_select == -1:
                    log.info("No user selected.")
                    settings('username', value='')
                    executebuiltin('Addon.OpenSettings(%s)'
                                   % v.ADDON_ID)
                    return False
            # Only 1 user received, choose that one
            else:
                user_select = 0
            selected_user = userlist[user_select]
            log.info("Selected user: %s" % selected_user)
            user = users[user_select]
            # Ask for PIN, if protected:
            pin = None
            if user['protected'] == '1':
                log.debug('Asking for users PIN')
                pin = dialog.input(
                    lang(39307) + selected_user,
                    '',
                    xbmcgui.INPUT_NUMERIC,
                    xbmcgui.ALPHANUM_HIDE_INPUT)
                # User chose to cancel
                # Plex bug: don't call url for protected user with empty PIN
                if not pin:
                    trials += 1
                    continue
            # Switch to this Plex Home user, if applicable
            result = self.PlexSwitchHomeUser(
                user['id'],
                pin,
                plexToken,
                settings('plex_machineIdentifier'))
            if result:
                # Successfully retrieved username: break out of while loop
                username = result['username']
                usertoken = result['usertoken']
                break
            # Couldn't get user auth
            else:
                trials += 1
                # Could not login user, please try again
                if not dialog.yesno(lang(29999),
                                    lang(39308) + selected_user,
                                    lang(39309)):
                    # User chose to cancel
                    break
        if not username:
            log.error('Failed signing in a user to plex.tv')
            executebuiltin('Addon.OpenSettings(%s)' % v.ADDON_ID)
            return False
        return {
            'username': username,
            'userid': user['id'],
            'protected': True if user['protected'] == '1' else False,
            'token': usertoken
        }

    def PlexSwitchHomeUser(self, userId, pin, token, machineIdentifier):
        """
        Retrieves Plex home token for a Plex home user.
        Returns False if unsuccessful

        Input:
            userId          id of the Plex home user
            pin             PIN of the Plex home user, if protected
            token           token for plex.tv

        Output:
            {
                'username'
                'usertoken'         Might be empty strings if no token found
                                    for the machineIdentifier that was chosen
            }

        settings('userid') and settings('username') with new plex token
        """
        log.info('Switching to user %s' % userId)
        url = 'https://plex.tv/api/home/users/' + userId + '/switch'
        if pin:
            url += '?pin=' + pin
        answer = self.doUtils(url,
                              authenticate=False,
                              action_type="POST",
                              headerOptions={'X-Plex-Token': token})
        try:
            answer.attrib
        except:
            log.error('Error: plex.tv switch HomeUser change failed')
            return False

        username = answer.attrib.get('title', '')
        token = answer.attrib.get('authenticationToken', '')

        # Write to settings file
        settings('username', username)
        settings('accessToken', token)
        settings('userid', answer.attrib.get('id', ''))
        settings('plex_restricteduser',
                 'true' if answer.attrib.get('restricted', '0') == '1'
                 else 'false')

        # Get final token to the PMS we've chosen
        url = 'https://plex.tv/api/resources?includeHttps=1'
        xml = self.doUtils(url,
                           authenticate=False,
                           headerOptions={'X-Plex-Token': token})
        try:
            xml.attrib
        except:
            log.error('Answer from plex.tv not as excepted')
            # Set to empty iterable list for loop
            xml = []

        found = 0
        log.debug('Our machineIdentifier is %s' % machineIdentifier)
        for device in xml:
            identifier = device.attrib.get('clientIdentifier')
            log.debug('Found a Plex machineIdentifier: %s' % identifier)
            if (identifier in machineIdentifier or
                    machineIdentifier in identifier):
                found += 1
                token = device.attrib.get('accessToken')

        result = {
            'username': username,
        }
        if found == 0:
            log.info('No tokens found for your server! Using empty string')
            result['usertoken'] = ''
        else:
            result['usertoken'] = token
        log.info('Plex.tv switch HomeUser change successfull for user %s'
                 % username)
        return result

    def MyPlexListHomeUsers(self, token):
        """
        Returns a list for myPlex home users for the current plex.tv account.

        Input:
            token for plex.tv
        Output:
            List of users, where one entry is of the form:
                "id": userId,
                "admin": '1'/'0',
                "guest": '1'/'0',
                "restricted": '1'/'0',
                "protected": '1'/'0',
                "email": email,
                "title": title,
                "username": username,
                "thumb": thumb_url
            }
        If any value is missing, None is returned instead (or "" from plex.tv)
        If an error is encountered, False is returned
        """
        xml = self.doUtils('https://plex.tv/api/home/users/',
                           authenticate=False,
                           headerOptions={'X-Plex-Token': token})
        try:
            xml.attrib
        except:
            log.error('Download of Plex home users failed.')
            return False
        users = []
        for user in xml:
            users.append(user.attrib)
        return users

    def getDirectVideoPath(self, key, AuthToken):
        """
        Direct Video Play support

        parameters:
            path
            AuthToken
            Indirect - media indirect specified, grab child XML to gain real path
            options
        result:
            final path to media file
        """
        if key.startswith('http://') or key.startswith('https://'):  # external address - keep
            path = key
        else:
            if AuthToken == '':
                path = key
            else:
                xargs = dict()
                xargs['X-Plex-Token'] = AuthToken
                if key.find('?') == -1:
                    path = key + '?' + urlencode(xargs)
                else:
                    path = key + '&' + urlencode(xargs)

        return path

    def getTranscodeImagePath(self, 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
        """
        if key.startswith('http://') or key.startswith('https://'):  # external address - can we get a transcoding request for external images?
            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
        path = tryEncode(path)

        # 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...
        transcodePath = '/photo/:/transcode/' + \
            str(width) + 'x' + str(height) + '/' + quote_plus(path)

        args = dict()
        args['width'] = width
        args['height'] = height
        args['url'] = path

        if not AuthToken == '':
            args['X-Plex-Token'] = AuthToken

        return transcodePath + '?' + urlencode(args)

    def getDirectImagePath(self, path, AuthToken):
        """
        Direct Image support

        parameters:
            path
            AuthToken
        result:
            final path to image file
        """
        if not AuthToken == '':
            xargs = dict()
            xargs['X-Plex-Token'] = AuthToken
            if path.find('?') == -1:
                path = path + '?' + urlencode(xargs)
            else:
                path = path + '&' + urlencode(xargs)

        return path

    def getTranscodeAudioPath(self, path, AuthToken, options, maxAudioBitrate):
        """
        Transcode Audio support

        parameters:
            path
            AuthToken
            options - dict() of PlexConnect-options as received from aTV
            maxAudioBitrate - [kbps]
        result:
            final path to pull in PMS transcoder
        """
        UDID = options['PlexConnectUDID']

        transcodePath = '/music/:/transcode/universal/start.mp3?'

        args = dict()
        args['path'] = path
        args['session'] = UDID
        args['protocol'] = 'http'
        args['maxAudioBitrate'] = maxAudioBitrate

        xargs = client.getXArgsDeviceInfo(options)
        if not AuthToken == '':
            xargs['X-Plex-Token'] = AuthToken

        return transcodePath + urlencode(args) + '&' + urlencode(xargs)

    def getDirectAudioPath(self, path, AuthToken):
        """
        Direct Audio support

        parameters:
            path
            AuthToken
        result:
            final path to audio file
        """
        if not AuthToken == '':
            xargs = dict()
            xargs['X-Plex-Token'] = AuthToken
            if path.find('?') == -1:
                path = path + '?' + urlencode(xargs)
            else:
                path = path + '&' + urlencode(xargs)

        return path

    def returnServerList(self, data):
        """
        Returns a nicer list of all servers found in data, where data is in
        g_PMS format, for the client device with unique ID ATV_udid

        Input:
            data                    e.g. self.g_PMS

        Output: List of all servers, with an entry of the form:
        {
        'name': friendlyName,      the Plex server's name
        'address': ip:port
        'ip': ip,                   without http/https
        'port': port
        'scheme': 'http'/'https',   nice for checking for secure connections
        'local': '1'/'0',           Is the server a local server?
        'owned': '1'/'0',           Is the server owned by the user?
        'machineIdentifier': id,    Plex server machine identifier
        'accesstoken': token        Access token to this server
        'baseURL': baseURL          scheme://ip:port
        'ownername'                 Plex username of PMS owner
        }
        """
        serverlist = []
        for key, value in data.items():
            serverlist.append({
                'name': value.get('name'),
                'address': value.get('address'),
                'ip': value.get('ip'),
                'port': value.get('port'),
                'scheme': value.get('scheme'),
                'local': value.get('local'),
                'owned': value.get('owned'),
                'machineIdentifier': key,
                'accesstoken': value.get('accesstoken'),
                'baseURL': value.get('baseURL'),
                'ownername': value.get('ownername')
            })
        return serverlist


class API():
    """
    API(item)

    Processes a Plex media server's XML response

    item: xml.etree.ElementTree element
    """

    def __init__(self, item):
        self.item = item
        # which media part in the XML response shall we look at?
        self.part = 0
        self.mediastream = None
        self.server = window('pms_server')

    def setPartNumber(self, number=None):
        """
        Sets the part number to work with (used to deal with Movie with several
        parts).
        """
        self.part = number or 0

    def getPartNumber(self):
        """
        Returns the current media part number we're dealing with.
        """
        return self.part

    def getType(self):
        """
        Returns the type of media, e.g. 'movie' or 'clip' for trailers
        """
        return self.item.attrib.get('type')

    def getChecksum(self):
        """
        Returns a string, not int. 

        WATCH OUT - time in Plex, not Kodi ;-)
        """
        # Include a letter to prohibit saving as an int!
        checksum = "K%s%s" % (self.getRatingKey(),
                              self.item.attrib.get('updatedAt', ''))
        return checksum

    def getRatingKey(self):
        """
        Returns the Plex key such as '246922' as a string
        """
        return self.item.attrib.get('ratingKey')

    def getKey(self):
        """
        Returns the Plex key such as '/library/metadata/246922'
        """
        return self.item.attrib.get('key')

    def getFilePath(self, forceFirstMediaStream=False):
        """
        Returns the direct path to this item, e.g. '\\NAS\movies\movie.mkv'
        or None

        forceFirstMediaStream=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 forceFirstMediaStream is False:
            self.getMediastreamNumber()
        try:
            if forceFirstMediaStream is False:
                ans = self.item[self.mediastream][self.part].attrib['file']
            else:
                ans = self.item[0][self.part].attrib['file']
        except:
            ans = None
        if ans is not None:
            try:
                ans = tryDecode(unquote(ans))
            except UnicodeDecodeError:
                # Sometimes, Plex seems to have encoded in latin1
                ans = unquote(ans).decode('latin1')
        return ans

    def getTVShowPath(self):
        """
        Returns the direct path to the TV show, e.g. '\\NAS\tv\series'
        or None
        """
        res = None
        for child in self.item:
            if child.tag == 'Location':
                res = child.attrib.get('path')
        return res

    def getIndex(self):
        """
        Returns the 'index' of an PMS XML reply. Depicts e.g. season number.
        """
        return self.item.attrib.get('index')

    def getDateCreated(self):
        """
        Returns the date when this library item was created.

        If not found, returns 2000-01-01 10:00:00
        """
        res = self.item.attrib.get('addedAt')
        if res is not None:
            res = DateToKodi(res)
        else:
            res = '2000-01-01 10:00:00'
        return res

    def getUserData(self):
        """
        Returns a dict with None if a value is missing
        {
            'Favorite': favorite,                  # False, because n/a in Plex
            'PlayCount': playcount,
            'Played': played,                      # True/False
            'LastPlayedDate': lastPlayedDate,
            'Resume': resume,                      # Resume time in seconds
            'Runtime': runtime,
            'Rating': rating
        }
        """
        item = self.item.attrib
        # Default - attributes not found with Plex
        favorite = False
        try:
            playcount = int(item['viewCount'])
        except (KeyError, ValueError):
            playcount = None
        played = True if playcount else False

        try:
            lastPlayedDate = DateToKodi(int(item['lastViewedAt']))
        except (KeyError, ValueError):
            lastPlayedDate = None

        try:
            userrating = int(float(item['userRating']))
        except (KeyError, ValueError):
            userrating = 0

        try:
            rating = float(item['audienceRating'])
        except (KeyError, ValueError):
            try:
                rating = float(item['rating'])
            except (KeyError, ValueError):
                rating = 0.0

        resume, runtime = self.getRuntime()
        return {
            'Favorite': favorite,
            'PlayCount': playcount,
            'Played': played,
            'LastPlayedDate': lastPlayedDate,
            'Resume': resume,
            'Runtime': runtime,
            'Rating': rating,
            'UserRating': userrating
        }

    def getCollections(self):
        """
        Returns a list of PMS collection tags or an empty list
        """
        collections = []
        for child in self.item:
            if child.tag == 'Collection':
                if child.attrib['tag']:
                    collections.append(child.attrib['tag'])
        return collections

    def getPeople(self):
        """
        Returns a dict of lists of people found.
        {
            'Director': list,
            'Writer': list,
            'Cast': list,
            'Producer': list
        }
        """
        director = []
        writer = []
        cast = []
        producer = []
        for child in self.item:
            if child.tag == 'Director':
                director.append(child.attrib['tag'])
            elif child.tag == 'Writer':
                writer.append(child.attrib['tag'])
            elif child.tag == 'Role':
                cast.append(child.attrib['tag'])
            elif child.tag == 'Producer':
                producer.append(child.attrib['tag'])
        return {
            'Director': director,
            'Writer': writer,
            'Cast': cast,
            'Producer': producer
        }

    def getPeopleList(self):
        """
        Returns a list of people from item, with a list item of the form
        {
            'Name': xxx,
            'Type': xxx,
            'Id': xxx
            'imageurl': url to picture, None otherwise
            ('Role': xxx for cast/actors only, None if not found)
        }
        """
        people = []
        # Key of library: Plex-identifier. Value represents the Kodi/emby side
        people_of_interest = {
            'Director': 'Director',
            'Writer': 'Writer',
            'Role': 'Actor',
            'Producer': 'Producer'
        }
        for child in self.item:
            if child.tag in people_of_interest.keys():
                name = child.attrib['tag']
                name_id = child.attrib['id']
                Type = child.tag
                Type = people_of_interest[Type]

                url = child.attrib.get('thumb')
                Role = child.attrib.get('role')

                people.append({
                    'Name': name,
                    'Type': Type,
                    'Id': name_id,
                    'imageurl': url
                })
                if url:
                    people[-1].update({'imageurl': url})
                if Role:
                    people[-1].update({'Role': Role})
        return people

    def getGenres(self):
        """
        Returns a list of genres found. (Not a string)
        """
        genre = []
        for child in self.item:
            if child.tag == 'Genre':
                genre.append(child.attrib['tag'])
        return genre

    def getGuid(self):
        return self.item.attrib.get('guid')

    def getProvider(self, providername=None):
        """
        providername:  e.g. 'imdb', 'tvdb'

        Return IMDB, e.g. "tt0903624". Returns None if not found
        """
        item = self.item.attrib
        try:
            item = item['guid']
        except KeyError:
            return None

        if providername == 'imdb':
            regex = REGEX_IMDB
        elif providername == 'tvdb':
            # originally e.g. com.plexapp.agents.thetvdb://276564?lang=en
            regex = REGEX_TVDB
        else:
            return None

        provider = regex.findall(item)
        try:
            provider = provider[0]
        except IndexError:
            provider = None
        return provider

    def getTitle(self):
        """
        Returns an item's name/title or "Missing Title Name".
        Output:
            title, sorttitle

        sorttitle = title, if no sorttitle is found
        """
        title = self.item.attrib.get('title', 'Missing Title Name')
        sorttitle = self.item.attrib.get('titleSort', title)
        return title, sorttitle

    def getPlot(self):
        """
        Returns the plot or None.
        """
        return self.item.attrib.get('summary', None)

    def getTagline(self):
        """
        Returns a shorter tagline or None
        """
        return self.item.attrib.get('tagline', None)

    def getAudienceRating(self):
        """
        Returns the audience rating, 'rating' itself or 0.0
        """
        res = self.item.attrib.get('audienceRating')
        if res is None:
            res = self.item.attrib.get('rating')
        try:
            res = float(res)
        except (ValueError, TypeError):
            res = 0.0
        return res

    def getYear(self):
        """
        Returns the production(?) year ("year") or None
        """
        return self.item.attrib.get('year', None)

    def getRuntime(self):
        """
        Resume point of time and runtime/totaltime in rounded to seconds.
        Time from Plex server is measured in milliseconds.
        Kodi: seconds

        Output:
            resume, runtime         as ints. 0 if not found
        """
        item = self.item.attrib

        try:
            runtime = float(item['duration'])
        except (KeyError, ValueError):
            runtime = 0.0
        try:
            resume = float(item['viewOffset'])
        except (KeyError, ValueError):
            resume = 0.0

        runtime = int(runtime * v.PLEX_TO_KODI_TIMEFACTOR)
        resume = int(resume * v.PLEX_TO_KODI_TIMEFACTOR)
        return resume, runtime

    def getMpaa(self):
        """
        Get the content rating or None
        """
        mpaa = self.item.attrib.get('contentRating', None)
        # 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"
        return mpaa

    def getCountry(self):
        """
        Returns a list of all countries found in item.
        """
        country = []
        for child in self.item:
            if child.tag == 'Country':
                country.append(child.attrib['tag'])
        return country

    def getPremiereDate(self):
        """
        Returns the "originallyAvailableAt" or None
        """
        return self.item.attrib.get('originallyAvailableAt')

    def getMusicStudio(self):
        return self.item.attrib.get('studio', '')

    def getStudios(self):
        """
        Returns a list with a single entry for the studio, or an empty list
        """
        studio = []
        try:
            studio.append(self.getStudio(self.item.attrib['studio']))
        except KeyError:
            pass
        return studio

    def getStudio(self, studioName):
        """
        Convert studio for Kodi to properly detect them
        """
        studios = {
            'abc (us)': "ABC",
            'fox (us)': "FOX",
            'mtv (us)': "MTV",
            'showcase (ca)': "Showcase",
            'wgn america': "WGN"
        }
        return studios.get(studioName.lower(), studioName)

    def joinList(self, listobject):
        """
        Smart-joins the listobject into a single string using a " / "
        separator.
        If the list is empty, smart_join returns an empty string.
        """
        string = " / ".join(listobject)
        return string

    def getParentRatingKey(self):
        return self.item.attrib.get('parentRatingKey', '')

    def getEpisodeDetails(self):
        """
        Call on a single episode.

        Output: for the corresponding the TV show and season:
            [
                TV show key,        Plex: 'grandparentRatingKey'
                TV show title,      Plex: 'grandparentTitle'
                TV show season,     Plex: 'parentIndex'
                Episode number,     Plex: 'index'
            ]
        """
        item = self.item.attrib
        key = item.get('grandparentRatingKey')
        title = item.get('grandparentTitle')
        season = item.get('parentIndex')
        episode = item.get('index')
        return key, title, season, episode

    def addPlexHeadersToUrl(self, url, arguments={}):
        """
        Takes an URL and optional arguments (also to be URL-encoded); returns
        an extended URL with e.g. the Plex token included.

        arguments overrule everything
        """
        xargs = client.getXArgsDeviceInfo()
        xargs.update(arguments)
        if '?' not in url:
            url = "%s?%s" % (url, urlencode(xargs))
        else:
            url = "%s&%s" % (url, urlencode(xargs))
        return url

    def addPlexCredentialsToUrl(self, url):
        """
        Returns an extended URL with the Plex token included as 'X-Plex-Token='

        url may or may not already contain a '?'
        """
        if window('pms_token') == '':
            return url
        if '?' not in url:
            url = "%s?X-Plex-Token=%s" % (url, window('pms_token'))
        else:
            url = "%s&X-Plex-Token=%s" % (url, window('pms_token'))
        return url

    def GetPlayQueueItemID(self):
        """
        Returns current playQueueItemID for the item.

        If not found, empty str is returned
        """
        return self.item.attrib.get('playQueueItemID')

    def getDataFromPartOrMedia(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.
        """
        media = self.item[0].attrib
        part = self.item[0][self.part].attrib

        try:
            try:
                value = part[key]
            except KeyError:
                value = media[key]
        except KeyError:
            value = None
        return value

    def getVideoCodec(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.getDataFromPartOrMedia('videoCodec'),
            'resolution': self.getDataFromPartOrMedia('videoResolution'),
            'height': self.getDataFromPartOrMedia('height'),
            'width': self.getDataFromPartOrMedia('width'),
            'aspectratio': self.getDataFromPartOrMedia('aspectratio'),
            'bitrate': self.getDataFromPartOrMedia('bitrate'),
            'container': self.getDataFromPartOrMedia('container'),
        }
        try:
            answ['bitDepth'] = self.item[0][self.part][self.mediastream].attrib.get('bitDepth')
        except:
            answ['bitDepth'] = None
        return answ

    def getExtras(self):
        """
        Currently ONLY returns the very first trailer found!

        Returns a list of trailer and extras from PMS XML. Returns [] if
        no extras are found.
        Extratypes:
            1:    Trailer
            5:    Behind the scenes

        Output: list of dicts with one entry of the form:
            'key':                     e.g. /library/metadata/xxxx
            'title':
            'thumb':                    artwork
            'duration':
            'extraType':
            'originallyAvailableAt':
            'year':
        """
        elements = []
        extras = self.item.find('Extras')
        if extras is None:
            return elements
        for extra in extras:
            try:
                extraType = int(extra.attrib['extraType'])
            except:
                extraType = None
            if extraType != 1:
                continue
            key = extra.attrib.get('key', None)
            title = extra.attrib.get('title', None)
            thumb = extra.attrib.get('thumb', None)
            duration = float(extra.attrib.get('duration', 0.0))
            year = extra.attrib.get('year', None)
            originallyAvailableAt = extra.attrib.get(
                'originallyAvailableAt', None)
            elements.append(
                {
                    'key': key,
                    'title': title,
                    'thumb': thumb,
                    'duration': int(duration * v.PLEX_TO_KODI_TIMEFACTOR),
                    'extraType': extraType,
                    'originallyAvailableAt': originallyAvailableAt,
                    'year': year
                })
            break
        return elements

    def getMediaStreams(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 = []
        # Sometimes, aspectratio is on the "toplevel"
        aspectratio = self.item[0].attrib.get('aspectRatio', None)
        # TODO: what if several Media tags exist?!?
        # Loop over parts
        for child in self.item[0]:
            container = child.attrib.get('container', None)
            # Loop over Streams
            for grandchild in child:
                mediaStream = grandchild.attrib
                mediaType = int(mediaStream.get('streamType', 999))
                if mediaType == 1:  # Video streams
                    videotrack = {}
                    videotrack['codec'] = mediaStream['codec'].lower()
                    if "msmpeg4" in videotrack['codec']:
                        videotrack['codec'] = "divx"
                    elif "mpeg4" in videotrack['codec']:
                        # if "simple profile" in profile or profile == "":
                        #    videotrack['codec'] = "xvid"
                        pass
                    elif "h264" in videotrack['codec']:
                        if container in ("mp4", "mov", "m4v"):
                            videotrack['codec'] = "avc1"
                    videotrack['height'] = mediaStream.get('height', None)
                    videotrack['width'] = mediaStream.get('width', None)
                    # TODO: 3d Movies?!?
                    # videotrack['Video3DFormat'] = item.get('Video3DFormat')
                    aspectratio = mediaStream.get('aspectRatio', aspectratio)
                    videotrack['aspect'] = aspectratio
                    # TODO: Video 3d format
                    videotrack['video3DFormat'] = None
                    videotracks.append(videotrack)

                elif mediaType == 2:  # Audio streams
                    audiotrack = {}
                    audiotrack['codec'] = mediaStream['codec'].lower()
                    if ("dca" in audiotrack['codec'] and
                            "ma" in mediaStream.get('profile', '').lower()):
                        audiotrack['codec'] = "dtshd_ma"
                    audiotrack['channels'] = mediaStream.get('channels')
                    # 'unknown' if we cannot get language
                    audiotrack['language'] = mediaStream.get(
                        'languageCode', lang(39310)).lower()
                    audiotracks.append(audiotrack)

                elif mediaType == 3:  # Subtitle streams
                    # 'unknown' if we cannot get language
                    subtitlelanguages.append(
                        mediaStream.get('languageCode',
                                        lang(39310)).lower())
        return {
            'video': videotracks,
            'audio': audiotracks,
            'subtitle': subtitlelanguages
        }

    def __getOneArtwork(self, entry):
        try:
            artwork = self.item.attrib[entry]
            if artwork.startswith('http'):
                pass
            else:
                artwork = "%s%s" % (self.server, artwork)
                artwork = self.addPlexCredentialsToUrl(artwork)
        except KeyError:
            artwork = ""
        return artwork

    def getAllArtwork(self, parentInfo=False):
        """
        Gets the URLs to the Plex artwork, or empty string if not found.
        parentInfo=True will check for parent's artwork if None is found

        Output:
        {
            'Primary'
            'Art'
            'Banner'
            'Logo'
            'Thumb'
            'Disc'
            'Backdrop' : LIST with the first entry xml key "art"
        }
        """
        allartworks = {
            'Primary': "",  # corresponds to Plex poster ('thumb')
            'Art': "",
            'Banner': "",   # corresponds to Plex banner ('banner') for series
            'Logo': "",
            'Thumb': "",    # corresponds to Plex (grand)parent posters (thumb)
            'Disc': "",
            'Backdrop': []  # Corresponds to Plex fanart ('art')
        }
        # Process backdrops
        # Get background artwork URL
        allartworks['Backdrop'].append(self.__getOneArtwork('art'))
        # Get primary "thumb" pictures:
        allartworks['Primary'] = self.__getOneArtwork('thumb')
        # Banner (usually only on tv series level)
        allartworks['Banner'] = self.__getOneArtwork('banner')
        # For e.g. TV shows, get series thumb
        allartworks['Thumb'] = self.__getOneArtwork('grandparentThumb')

        # Process parent items if the main item is missing artwork
        if parentInfo:
            # Process parent backdrops
            if not allartworks['Backdrop']:
                allartworks['Backdrop'].append(
                    self.__getOneArtwork('parentArt'))
            if not allartworks['Primary']:
                allartworks['Primary'] = self.__getOneArtwork('parentThumb')
        return allartworks

    def getFanartArtwork(self, allartworks, parentInfo=False):
        """
        Downloads additional fanart from third party sources (well, link to
        fanart only).

        allartworks = {
            'Primary': "",
            'Art': "",
            'Banner': "",
            'Logo': "",
            'Thumb': "",
            'Disc': "",
            'Backdrop': []
        }
        """
        externalId = self.getExternalItemId()
        if externalId is not None:
            allartworks = self.getFanartTVArt(externalId, allartworks)
        return allartworks

    def getExternalItemId(self, collection=False):
        """
        Returns the item's IMDB id for movies or tvdb id for TV shows

        If not found in item's Plex metadata, check themovidedb.org

        collection=True will try to return the collection's ID

        None is returned if unsuccessful
        """
        item = self.item.attrib
        media_type = item.get('type')
        mediaId = 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:
                mediaId = self.getProvider('imdb')
            elif media_type == v.PLEX_TYPE_SHOW:
                mediaId = self.getProvider('tvdb')
            if mediaId is not None:
                return mediaId
            log.info('Plex did not provide ID for IMDB or TVDB. Start '
                     'lookup process')
        else:
            log.info('Start movie set/collection lookup on themoviedb')

        apiKey = settings('themoviedbAPIKey')
        if media_type == v.PLEX_TYPE_SHOW:
            media_type = 'tv'
        title = item.get('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 = 'http://api.themoviedb.org/3/search/%s' % media_type
        parameters = {
            'api_key': apiKey,
            'language': v.KODILANGUAGE,
            'query': tryEncode(title)
        }
        data = DownloadUtils().downloadUrl(
            url,
            authenticate=False,
            parameters=parameters,
            timeout=7)
        try:
            data.get('test')
        except:
            log.error('Could not download data from FanartTV')
            return
        if data.get('results') is None:
            log.info('No match found on themoviedb for type: %s, title: %s'
                     % (media_type, title))
            return

        year = item.get('year')
        matchFound = None
        # find year match
        if year is not None:
            for entry in data["results"]:
                if year in entry.get("first_air_date", ""):
                    matchFound = entry
                    break
                elif year in entry.get("release_date", ""):
                    matchFound = entry
                    break
        # find exact match based on title, if we haven't found a year match
        if matchFound 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 replaceString in replacements:
                    title_alt = title_alt.replace(replaceString, '')
                    name_alt = name_alt.replace(replaceString, '')
                    org_name_alt = org_name_alt.replace(replaceString, '')
                if name == title or original_name == title:
                    # match found for exact title name
                    matchFound = entry
                    break
                elif (name.split(" (")[0] == title or title_alt == name_alt
                        or title_alt == org_name_alt):
                    # match found with substituting some stuff
                    matchFound = entry
                    break

        # if a match was not found, we accept the closest match from TMDB
        if matchFound is None and len(data.get("results")) > 0:
            log.info('Using very first match from themoviedb')
            matchFound = entry = data.get("results")[0]

        if matchFound 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'), matchFound))

        tmdbId = str(entry.get("id", ""))
        if tmdbId == '':
            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 tmdbId and perform artwork lookup on fanart.tv
        parameters = {
            'api_key': apiKey
        }
        for language in [v.KODILANGUAGE, "en"]:
            parameters['language'] = language
            if media_type == "movie":
                url = 'http://api.themoviedb.org/3/movie/%s' % tmdbId
                parameters['append_to_response'] = 'videos'
            elif media_type == "tv":
                url = 'http://api.themoviedb.org/3/tv/%s' % tmdbId
                parameters['append_to_response'] = 'external_ids,videos'
            data = DownloadUtils().downloadUrl(
                url,
                authenticate=False,
                parameters=parameters,
                timeout=7)
            try:
                data.get('test')
            except:
                log.error('Could not download %s with parameters %s'
                          % (url, parameters))
                continue
            if collection is False:
                if data.get("imdb_id") is not None:
                    mediaId = str(data.get("imdb_id"))
                    break
                if data.get("external_ids") is not None:
                    mediaId = str(data["external_ids"].get("tvdb_id"))
                    break
            else:
                if data.get("belongs_to_collection") is not None:
                    mediaId = str(data.get("belongs_to_collection").get("id"))
                    log.debug('Retrieved collections tmdb id %s' % mediaId)
        return mediaId

    def getFanartTVArt(self, mediaId, allartworks, setInfo=False):
        """
        perform artwork lookup on fanart.tv

        mediaId: IMDB id for movies, tvdb id for TV shows
        """
        item = self.item.attrib
        api_key = settings('FanArtTVAPIKey')
        typus = item.get('type')
        if typus == 'show':
            typus = 'tv'

        if typus == "movie":
            url = 'http://webservice.fanart.tv/v3/movies/%s?api_key=%s' \
                % (mediaId, api_key)
        elif typus == 'tv':
            url = 'http://webservice.fanart.tv/v3/tv/%s?api_key=%s' \
                % (mediaId, api_key)
        else:
            # Not supported artwork
            return allartworks
        data = DownloadUtils().downloadUrl(
            url,
            authenticate=False,
            timeout=15)
        try:
            data.get('test')
        except:
            log.error('Could not download data from FanartTV')
            return allartworks

        # we need to use a little mapping between fanart.tv arttypes and kodi
        # artttypes
        fanartTVTypes = [
            ("logo", "Logo"),
            ("musiclogo", "clearlogo"),
            ("disc", "Disc"),
            ("clearart", "Art"),
            ("banner", "Banner"),
            ("clearlogo", "Logo"),
            ("background", "fanart"),
            ("showbackground", "fanart"),
            ("characterart", "characterart")
        ]
        if typus == "artist":
            fanartTVTypes.append(("thumb", "folder"))
        else:
            fanartTVTypes.append(("thumb", "Thumb"))

        if setInfo:
            fanartTVTypes.append(("poster", "Primary"))

        prefixes = (
            "hd" + typus,
            "hd",
            typus,
            "",
        )
        for fanarttype in fanartTVTypes:
            # Skip the ones we already have
            if allartworks.get(fanarttype[1]):
                continue
            for prefix in prefixes:
                fanarttvimage = prefix + fanarttype[0]
                if fanarttvimage not in data:
                    continue
                # select image in preferred language
                for entry in data[fanarttvimage]:
                    if entry.get("lang") == v.KODILANGUAGE:
                        allartworks[fanarttype[1]] = 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 not allartworks.get(fanarttype[1]):
                    for entry in data[fanarttvimage]:
                        if entry.get("lang") in ("en", "00"):
                            allartworks[fanarttype[1]] = entry.get("url", "").replace(' ', '%20')
                            break

        # grab extrafanarts in list
        maxfanarts = 10
        fanartcount = 0
        for prefix in prefixes:
            fanarttvimage = prefix + 'background'
            if fanarttvimage not in data:
                continue
            for entry in data[fanarttvimage]:
                if fanartcount < maxfanarts:
                    if exists(entry.get("url")):
                        allartworks['Backdrop'].append(
                            entry.get("url", "").replace(' ', '%20'))
                        fanartcount += 1
        return allartworks

    def getSetArtwork(self, parentInfo=False):
        """
        Gets the URLs to the Plex artwork, or empty string if not found.
        parentInfo=True will check for parent's artwork if None is found

        Only call on movies

        Output:
        {
            'Primary'
            'Art'
            'Banner'
            'Logo'
            'Thumb'
            'Disc'
            'Backdrop' : LIST with the first entry xml key "art"
        }
        """
        allartworks = {
            'Primary': "",
            'Art': "",
            'Banner': "",
            'Logo': "",
            'Thumb': "",
            'Disc': "",
            'Backdrop': []
        }

        # Plex does not get much artwork - go ahead and get the rest from
        # fanart tv only for movie or tv show
        externalId = self.getExternalItemId(collection=True)
        if externalId is not None:
            allartworks = self.getFanartTVArt(externalId, allartworks, True)
        return allartworks

    def shouldStream(self):
        """
        Returns True if the item's 'optimizedForStreaming' is set, False other-
        wise
        """
        return self.item[0].attrib.get('optimizedForStreaming') == '1'

    def getMediastreamNumber(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)
        """
        # How many streams do we have?
        count = 0
        for entry in self.item.findall('./Media'):
            count += 1
        if (count > 1 and (
                (self.getType() != 'clip' and
                 settings('bestQuality') == 'false')
                or
                (self.getType() == 'clip' and
                 settings('bestTrailer') == 'false'))):
            # Several streams/files available.
            dialoglist = []
            for entry in self.item.findall('./Media'):
                dialoglist.append(
                    "%sp %s - %s (%s)"
                    % (entry.attrib.get('videoResolution', 'unknown'),
                       entry.attrib.get('videoCodec', 'unknown'),
                       entry.attrib.get('audioProfile', 'unknown'),
                       entry.attrib.get('audioCodec', 'unknown'))
                )
            media = xbmcgui.Dialog().select('Select stream', dialoglist)
        else:
            media = 0
        self.mediastream = media
        return media

    def getTranscodeVideoPath(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:
            self.getMediastreamNumber()
        if quality is None:
            quality = {}
        xargs = client.getXArgsDeviceInfo()
        # For DirectPlay, path/key of PART is needed
        # trailers are 'clip' with PMS xmls
        if action == "DirectStream":
            path = self.item[self.mediastream][self.part].attrib['key']
            url = self.server + path
            # e.g. Trailers already feature an '?'!
            if '?' in url:
                url += '&' + urlencode(xargs)
            else:
                url += '?' + urlencode(xargs)
            return url

        # For Transcoding
        # Path/key to VIDEO item of xml PMS response is needed, not part
        path = self.item.attrib['key']
        transcodePath = self.server + \
            '/video/:/transcode/universal/start.m3u8?'
        args = {
            'protocol': 'hls',   # seen in the wild: 'dash', 'http', 'hls'
            'session':  window('plex_client_Id'),
            'fastSeek': 1,
            'path': path,
            'mediaIndex': self.mediastream,
            'partIndex': self.part,
            # 'copyts': 1,
            # 'offset': 0,           # Resume point
        }
        # Seem like PHT to let the PMS use the transcoding profile
        xargs['X-Plex-Device'] = 'Plex Home Theater'
        # Currently not used!
        if action == "DirectStream":
            argsUpdate = {
                'directPlay': '0',
                'directStream': '1',
            }
            args.update(argsUpdate)
        elif action == 'Transcode':
            argsUpdate = {
                'directPlay': '0',
                'directStream': '0'
            }
            log.debug("Setting transcode quality to: %s" % quality)
            args.update(quality)
            args.update(argsUpdate)
        url = transcodePath + urlencode(xargs) + '&' + urlencode(args)
        return url

    def externalSubs(self, playurl):
        externalsubs = []
        mapping = {}
        try:
            mediastreams = self.item[0][self.part]
        except (TypeError, KeyError, IndexError):
            return
        kodiindex = 0
        for stream in mediastreams:
            index = stream.attrib['id']
            # Since plex returns all possible tracks together, have to pull
            # only external subtitles.
            key = stream.attrib.get('key')
            # IsTextSubtitleStream if true, is available to download from plex.
            if stream.attrib.get('streamType') == "3" and key:
                # Direct stream
                url = ("%s%s" % (self.server, key))
                url = self.addPlexCredentialsToUrl(url)
                # map external subtitles for mapping
                mapping[kodiindex] = index
                externalsubs.append(url)
                kodiindex += 1
        mapping = dumps(mapping)
        window('plex_%s.indexMapping' % playurl, value=mapping)
        log.info('Found external subs: %s' % externalsubs)
        return externalsubs

    def CreateListItemFromPlexItem(self,
                                   listItem=None,
                                   appendShowTitle=False,
                                   appendSxxExx=False):
        if self.getType() == 'photo':
            listItem = self._createPhotoListItem(listItem)
        else:
            listItem = self._createVideoListItem(listItem,
                                                 appendShowTitle,
                                                 appendSxxExx)
        return listItem

    def GetKodiPremierDate(self):
        """
        Takes Plex' originallyAvailableAt of the form "yyyy-mm-dd" and returns
        Kodi's "dd.mm.yyyy"
        """
        date = self.getPremiereDate()
        if date is None:
            return
        try:
            date = sub(r'(\d+)-(\d+)-(\d+)', r'\3.\2.\1', date)
        except:
            date = None
        return date

    def _createPhotoListItem(self, listItem=None):
        """
        Use for photo items only
        """
        title, _ = self.getTitle()
        if listItem is None:
            listItem = xbmcgui.ListItem(title)
        else:
            listItem.setLabel(title)
        listItem.setProperty('IsPlayable', 'true')
        extension = self.item[0][0].attrib['key'][self.item[0][0].attrib['key'].rfind('.'):].lower()
        if (window('plex_force_transcode_pix') == 'true' or
                extension not in v.KODI_SUPPORTED_IMAGES):
            # Let Plex transcode
            # max width/height supported by plex image transcoder is 1920x1080
            path = self.server + PlexAPI().getTranscodeImagePath(
                self.item[0][0].attrib.get('key'),
                window('pms_token'),
                "%s%s" % (self.server, self.item[0][0].attrib.get('key')),
                1920,
                1080)
        else:
            # Don't transcode
            if window('useDirectPaths') == 'true':
                # Addon Mode. Just give the path of the file to Kodi
                path = self.addPlexCredentialsToUrl(
                    '%s%s' % (window('pms_server'),
                              self.item[0][0].attrib['key']))
            else:
                # Native direct paths
                path = self.validatePlayurl(
                    self.getFilePath(forceFirstMediaStream=True),
                    'photo')

        path = tryEncode(path)
        metadata = {
            'date': self.GetKodiPremierDate(),
            'picturepath': path,
            'size': long(self.item[0][0].attrib.get('size', 0)),
            'exif:width': self.item[0].attrib.get('width', ''),
            'exif:height': self.item[0].attrib.get('height', ''),
            'title': title
        }
        listItem.setInfo('pictures', infoLabels=metadata)
        try:
            if int(metadata['exif:width']) > int(metadata['exif:height']):
                # add image as fanart for use with skinhelper auto thumb/
                # backgrund creation
                listItem.setArt({'fanart':  path})
        except ValueError:
            pass
        # Stuff that we CANNOT set with listItem.setInfo
        listItem.setProperty('path', path)
        listItem.setProperty('plot', self.getPlot())
        listItem.setProperty('plexid', self.getRatingKey())
        # We do NOT set these props
        # listItem.setProperty('isPlayable', 'true')
        # listItem.setProperty('isFolder', 'true')
        # Further stuff
        listItem.setArt({'icon': 'DefaultPicture.png'})
        return listItem

    def _createVideoListItem(self,
                             listItem=None,
                             appendShowTitle=False,
                             appendSxxExx=False):
        """
        Use for video items only
        Call on a child level of PMS xml response (e.g. in a for loop)

        listItem        : existing xbmcgui.ListItem to work with
                          otherwise, a new one is created
        appendShowTitle : True to append TV show title to episode title
        appendSxxExx    : True to append SxxExx to episode title

        Returns XBMC listitem for this PMS library item
        """
        title, sorttitle = self.getTitle()
        typus = self.getType()

        if listItem is None:
            listItem = xbmcgui.ListItem(title)
        listItem.setProperty('IsPlayable', 'true')

        # Video items, e.g. movies and episodes or clips
        people = self.getPeople()
        userdata = self.getUserData()
        metadata = {
            'genre': self.joinList(self.getGenres()),
            'year': self.getYear(),
            'rating': self.getAudienceRating(),
            'playcount': userdata['PlayCount'],
            'cast': people['Cast'],
            'director': self.joinList(people.get('Director')),
            'plot': self.getPlot(),
            'sorttitle': sorttitle,
            'duration': userdata['Runtime'],
            'studio': self.joinList(self.getStudios()),
            'tagline': self.getTagline(),
            'writer': self.joinList(people.get('Writer')),
            'premiered': self.getPremiereDate(),
            'dateadded': self.getDateCreated(),
            'lastplayed': userdata['LastPlayedDate'],
            'mpaa': self.getMpaa(),
            'aired': self.getPremiereDate()
        }
        listItem.setProperty('resumetime', str(userdata['Resume']))
        listItem.setProperty('totaltime', str(userdata['Runtime']))

        if typus == "episode":
            # Only for tv shows
            key, show, season, episode = self.getEpisodeDetails()
            season = -1 if season is None else int(season)
            episode = -1 if episode is None else int(episode)
            metadata['episode'] = episode
            metadata['season'] = season
            metadata['tvshowtitle'] = show
            if season and episode:
                listItem.setProperty('episodeno',
                                     "s%.2de%.2d" % (season, episode))
                if appendSxxExx is True:
                    title = "S%.2dE%.2d - %s" % (season, episode, title)
            listItem.setArt({'icon': 'DefaultTVShows.png'})
            if appendShowTitle is True:
                title = "%s - %s " % (show, title)
        elif typus == "movie":
            listItem.setArt({'icon': 'DefaultMovies.png'})
        else:
            # E.g. clips, trailers, ...
            listItem.setArt({'icon': 'DefaultVideo.png'})

        plexId = self.getRatingKey()
        listItem.setProperty('plexid', plexId)
        with plexdb.Get_Plex_DB() as plex_db:
            try:
                listItem.setProperty('dbid',
                                     str(plex_db.getItem_byId(plexId)[0]))
            except TypeError:
                pass
        # Expensive operation
        metadata['title'] = title
        listItem.setLabel(title)
        listItem.setInfo('video', infoLabels=metadata)
        return listItem

    def AddStreamInfo(self, listItem):
        """
        Add media stream information to xbmcgui.ListItem
        """
        mediastreams = self.getMediaStreams()
        videostreamFound = False
        if mediastreams:
            for key, value in mediastreams.iteritems():
                if key == "video" and value:
                    videostreamFound = True
                if value:
                    listItem.addStreamInfo(key, value)
        if not videostreamFound:
            # just set empty streamdetails to prevent errors in the logs
            listItem.addStreamInfo(
                "video", {'duration': self.getRuntime()[1]})

    def validatePlayurl(self, path, typus, forceCheck=False, folder=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
            forceCheck : 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
        """
        if path is None:
            return None
        typus = v.REMAP_TYPE_FROM_PLEXTYPE[typus]
        if window('remapSMB') == 'true':
            path = path.replace(window('remapSMB%sOrg' % typus),
                                window('remapSMB%sNew' % typus),
                                1)
            # There might be backslashes left over:
            path = path.replace('\\', '/')
        elif window('replaceSMB') == 'true':
            if path.startswith('\\\\'):
                path = 'smb:' + path.replace('\\', '/')
        if window('plex_pathverified') == 'true' and forceCheck is False:
            return path

        # exist() needs a / or \ at the end to work for directories
        if folder is False:
            # files
            check = exists(tryEncode(path)) == 1
        else:
            # directories
            if "\\" in path:
                # Add the missing backslash
                check = exists(tryEncode(path + "\\")) == 1
            else:
                check = exists(tryEncode(path + "/")) == 1

        if check is False:
            if forceCheck is False:
                # Validate the path is correct with user intervention
                if self.askToValidate(path):
                    window('plex_shouldStop', value="true")
                    path = None
                window('plex_pathverified', value='true')
            else:
                path = None
        elif forceCheck is False:
            if window('plex_pathverified') != 'true':
                window('plex_pathverified', value='true')
        return path

    def askToValidate(self, 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)
        resp = xbmcgui.Dialog().yesno(
            heading=lang(29999),
            line1=lang(39031) + url,
            line2=lang(39032))
        return resp

    def set_listitem_artwork(self, listitem):
        """
        Set all artwork to the listitem
        """
        allartwork = self.getAllArtwork(parentInfo=True)
        arttypes = {
            'poster': "Primary",
            'tvshow.poster': "Thumb",
            'clearart': "Art",
            'tvshow.clearart': "Art",
            'clearart': "Primary",
            'tvshow.clearart': "Primary",
            'clearlogo': "Logo",
            'tvshow.clearlogo': "Logo",
            'discart': "Disc",
            'fanart_image': "Backdrop",
            'landscape': "Backdrop",
            "banner": "Banner"
        }
        for arttype in arttypes:
            art = arttypes[arttype]
            if art == "Backdrop":
                try:
                    # Backdrop is a list, grab the first backdrop
                    self._set_listitem_artprop(listitem,
                                               arttype,
                                               allartwork[art][0])
                except:
                    pass
            else:
                self._set_listitem_artprop(listitem, arttype, allartwork[art])

    def _set_listitem_artprop(self, listitem, arttype, path):
        if arttype in (
                'thumb', 'fanart_image', 'small_poster', 'tiny_poster',
                'medium_landscape', 'medium_poster', 'small_fanartimage',
                'medium_fanartimage', 'fanart_noindicators'):
            listitem.setProperty(arttype, path)
        else:
            listitem.setArt({arttype: path})

    def set_playback_win_props(self, playurl, listitem):
        """
        Set all properties necessary for plugin path playback for listitem
        """
        itemtype = self.getType()
        userdata = self.getUserData()

        plexitem = "plex_%s" % playurl
        window('%s.runtime' % plexitem, value=str(userdata['Runtime']))
        window('%s.type' % plexitem, value=itemtype)
        window('%s.itemid' % plexitem, value=self.getRatingKey())
        window('%s.playcount' % plexitem, value=str(userdata['PlayCount']))

        if itemtype == v.PLEX_TYPE_EPISODE:
            window('%s.refreshid' % plexitem, value=self.getParentRatingKey())
        else:
            window('%s.refreshid' % plexitem, value=self.getRatingKey())

        # Append external subtitles to stream
        playmethod = window('%s.playmethod' % plexitem)
        if playmethod in ("DirectStream", "DirectPlay"):
            subtitles = self.externalSubs(playurl)
            listitem.setSubtitles(subtitles)