1# -*- coding: utf-8 -*-
2
3# This file is part of Tautulli.
4#
5#  Tautulli is free software: you can redistribute it and/or modify
6#  it under the terms of the GNU General Public License as published by
7#  the Free Software Foundation, either version 3 of the License, or
8#  (at your option) any later version.
9#
10#  Tautulli is distributed in the hope that it will be useful,
11#  but WITHOUT ANY WARRANTY; without even the implied warranty of
12#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13#  GNU General Public License for more details.
14#
15#  You should have received a copy of the GNU General Public License
16#  along with Tautulli.  If not, see <http://www.gnu.org/licenses/>.
17
18from __future__ import unicode_literals
19from future.builtins import next
20from future.builtins import str
21from future.builtins import object
22
23import json
24import os
25import time
26from future.moves.urllib.parse import quote, quote_plus, urlencode
27from xml.dom.minidom import Node
28
29import plexpy
30if plexpy.PYTHON2:
31    import activity_processor
32    import common
33    import helpers
34    import http_handler
35    import libraries
36    import logger
37    import plextv
38    import session
39    import users
40else:
41    from plexpy import activity_processor
42    from plexpy import common
43    from plexpy import helpers
44    from plexpy import http_handler
45    from plexpy import libraries
46    from plexpy import logger
47    from plexpy import plextv
48    from plexpy import session
49    from plexpy import users
50
51
52def get_server_friendly_name():
53    logger.info("Tautulli Pmsconnect :: Requesting name from server...")
54    server_name = PmsConnect().get_server_pref(pref='FriendlyName')
55
56    # If friendly name is blank
57    if not server_name:
58        servers_info = PmsConnect().get_servers_info()
59        for server in servers_info:
60            if server['machine_identifier'] == plexpy.CONFIG.PMS_IDENTIFIER:
61                server_name = server['name']
62                break
63
64    if server_name and server_name != plexpy.CONFIG.PMS_NAME:
65        plexpy.CONFIG.__setattr__('PMS_NAME', server_name)
66        plexpy.CONFIG.write()
67        logger.info("Tautulli Pmsconnect :: Server name retrieved.")
68
69    return server_name
70
71
72class PmsConnect(object):
73    """
74    Retrieve data from Plex Server
75    """
76
77    def __init__(self, url=None, token=None):
78        self.url = url
79        self.token = token
80
81        if not self.url and plexpy.CONFIG.PMS_URL:
82            self.url = plexpy.CONFIG.PMS_URL
83        elif not self.url:
84            self.url = 'http://{hostname}:{port}'.format(hostname=plexpy.CONFIG.PMS_IP,
85                                                         port=plexpy.CONFIG.PMS_PORT)
86        self.timeout = plexpy.CONFIG.PMS_TIMEOUT
87
88        if not self.token:
89            # Check if we should use the admin token, or the guest server token
90            if session.get_session_user_id():
91                user_data = users.Users()
92                user_tokens = user_data.get_tokens(user_id=session.get_session_user_id())
93                self.token = user_tokens['server_token']
94            else:
95                self.token = plexpy.CONFIG.PMS_TOKEN
96
97        self.ssl_verify = plexpy.CONFIG.VERIFY_SSL_CERT
98
99        self.request_handler = http_handler.HTTPHandler(urls=self.url,
100                                                        token=self.token,
101                                                        timeout=self.timeout,
102                                                        ssl_verify=self.ssl_verify)
103
104    def get_sessions(self, output_format=''):
105        """
106        Return current sessions.
107
108        Optional parameters:    output_format { dict, json }
109
110        Output: array
111        """
112        uri = '/status/sessions'
113        request = self.request_handler.make_request(uri=uri,
114                                                    request_type='GET',
115                                                    output_format=output_format)
116
117        return request
118
119    def get_sessions_terminate(self, session_id='', reason=''):
120        """
121        Return current sessions.
122
123        Optional parameters:    output_format { dict, json }
124
125        Output: array
126        """
127        uri = '/status/sessions/terminate?sessionId=%s&reason=%s' % (session_id, quote_plus(reason))
128        request = self.request_handler.make_request(uri=uri,
129                                                    request_type='GET',
130                                                    return_response=True)
131
132        return request
133
134    def get_metadata(self, rating_key='', output_format=''):
135        """
136        Return metadata for request item.
137
138        Parameters required:    rating_key { Plex ratingKey }
139        Optional parameters:    output_format { dict, json }
140
141        Output: array
142        """
143        uri = '/library/metadata/' + rating_key
144        request = self.request_handler.make_request(uri=uri,
145                                                    request_type='GET',
146                                                    output_format=output_format)
147
148        return request
149
150    def get_metadata_children(self, rating_key='', section_id='', artist=False, collection=False, output_format=''):
151        """
152        Return metadata for children of the request item.
153
154        Parameters required:    rating_key { Plex ratingKey }
155        Optional parameters:    output_format { dict, json }
156
157        Output: array
158        """
159        if artist:
160            uri = '/library/sections/{}/all?artist.id={}&type=9'.format(
161                section_id, rating_key
162            )
163        else:
164            uri = '/library/{}/{}/children'.format(
165                'collections' if collection else 'metadata', rating_key
166            )
167        request = self.request_handler.make_request(uri=uri,
168                                                    request_type='GET',
169                                                    output_format=output_format)
170
171        return request
172
173    def get_metadata_grandchildren(self, rating_key='', output_format=''):
174        """
175        Return metadata for graandchildren of the request item.
176
177        Parameters required:    rating_key { Plex ratingKey }
178        Optional parameters:    output_format { dict, json }
179
180        Output: array
181        """
182        uri = '/library/metadata/' + rating_key + '/grandchildren'
183        request = self.request_handler.make_request(uri=uri,
184                                                    request_type='GET',
185                                                    output_format=output_format)
186
187        return request
188
189    def get_playlist_items(self, rating_key='', output_format=''):
190        """
191        Return metadata for items of the requested playlist.
192
193        Parameters required:    rating_key { Plex ratingKey }
194        Optional parameters:    output_format { dict, json }
195
196        Output: array
197        """
198        uri = '/playlists/' + rating_key + '/items'
199        request = self.request_handler.make_request(uri=uri,
200                                                    request_type='GET',
201                                                    output_format=output_format)
202
203        return request
204
205    def get_recently_added(self, start='0', count='0', output_format=''):
206        """
207        Return list of recently added items.
208
209        Parameters required:    count { number of results to return }
210        Optional parameters:    output_format { dict, json }
211
212        Output: array
213        """
214        uri = '/library/recentlyAdded?X-Plex-Container-Start=%s&X-Plex-Container-Size=%s' % (start, count)
215        request = self.request_handler.make_request(uri=uri,
216                                                    request_type='GET',
217                                                    output_format=output_format)
218
219        return request
220
221    def get_library_recently_added(self, section_id='', start='0', count='0', output_format=''):
222        """
223        Return list of recently added items.
224
225        Parameters required:    count { number of results to return }
226        Optional parameters:    output_format { dict, json }
227
228        Output: array
229        """
230        uri = '/library/sections/%s/recentlyAdded?X-Plex-Container-Start=%s&X-Plex-Container-Size=%s' % (section_id, start, count)
231        request = self.request_handler.make_request(uri=uri,
232                                                    request_type='GET',
233                                                    output_format=output_format)
234
235        return request
236
237    def get_children_list_related(self, rating_key='', output_format=''):
238        """
239        Return list of related children in requested collection item.
240
241        Parameters required:    rating_key { ratingKey of parent }
242        Optional parameters:    output_format { dict, json }
243
244        Output: array
245        """
246        uri = '/hubs/metadata/' + rating_key + '/related'
247        request = self.request_handler.make_request(uri=uri,
248                                                    request_type='GET',
249                                                    output_format=output_format)
250
251        return request
252
253    def get_childrens_list(self, rating_key='', output_format=''):
254        """
255        Return list of children in requested library item.
256
257        Parameters required:    rating_key { ratingKey of parent }
258        Optional parameters:    output_format { dict, json }
259
260        Output: array
261        """
262        uri = '/library/metadata/' + rating_key + '/allLeaves'
263        request = self.request_handler.make_request(uri=uri,
264                                                    request_type='GET',
265                                                    output_format=output_format)
266
267        return request
268
269    def get_server_list(self, output_format=''):
270        """
271        Return list of local servers.
272
273        Optional parameters:    output_format { dict, json }
274
275        Output: array
276        """
277        uri = '/servers'
278        request = self.request_handler.make_request(uri=uri,
279                                                    request_type='GET',
280                                                    output_format=output_format)
281
282        return request
283
284    def get_server_prefs(self, output_format=''):
285        """
286        Return the local servers preferences.
287
288        Optional parameters:    output_format { dict, json }
289
290        Output: array
291        """
292        uri = '/:/prefs'
293        request = self.request_handler.make_request(uri=uri,
294                                                    request_type='GET',
295                                                    output_format=output_format)
296
297        return request
298
299    def get_local_server_identity(self, output_format=''):
300        """
301        Return the local server identity.
302
303        Optional parameters:    output_format { dict, json }
304
305        Output: array
306        """
307        uri = '/identity'
308        request = self.request_handler.make_request(uri=uri,
309                                                    request_type='GET',
310                                                    output_format=output_format)
311
312        return request
313
314    def get_libraries_list(self, output_format=''):
315        """
316        Return list of libraries on server.
317
318        Optional parameters:    output_format { dict, json }
319
320        Output: array
321        """
322        uri = '/library/sections'
323        request = self.request_handler.make_request(uri=uri,
324                                                    request_type='GET',
325                                                    output_format=output_format)
326
327        return request
328
329    def get_library_list(self, section_id='', list_type='all', count='0', sort_type='', label_key='', output_format=''):
330        """
331        Return list of items in library on server.
332
333        Optional parameters:    output_format { dict, json }
334
335        Output: array
336        """
337        count = '&X-Plex-Container-Size=' + count if count else ''
338        label_key = '&label=' + label_key if label_key else ''
339
340        uri = '/library/sections/' + section_id + '/' + list_type + '?X-Plex-Container-Start=0' + count + sort_type + label_key
341        request = self.request_handler.make_request(uri=uri,
342                                                    request_type='GET',
343                                                    output_format=output_format)
344
345        return request
346
347    def get_library_labels(self, section_id='', output_format=''):
348        """
349        Return list of labels for a library on server.
350
351        Optional parameters:    output_format { dict, json }
352
353        Output: array
354        """
355        uri = '/library/sections/' + section_id + '/label'
356        request = self.request_handler.make_request(uri=uri,
357                                                    request_type='GET',
358                                                    output_format=output_format)
359
360        return request
361
362    def get_sync_item(self, sync_id='', output_format=''):
363        """
364        Return sync item details.
365
366        Parameters required:    sync_id { unique sync id for item }
367        Optional parameters:    output_format { dict, json }
368
369        Output: array
370        """
371        uri = '/sync/items/' + sync_id
372        request = self.request_handler.make_request(uri=uri,
373                                                    request_type='GET',
374                                                    output_format=output_format)
375
376        return request
377
378    def get_sync_transcode_queue(self, output_format=''):
379        """
380        Return sync transcode queue.
381
382        Optional parameters:    output_format { dict, json }
383
384        Output: array
385        """
386        uri = '/sync/transcodeQueue'
387        request = self.request_handler.make_request(uri=uri,
388                                                    request_type='GET',
389                                                    output_format=output_format)
390
391        return request
392
393    def get_search(self, query='', limit='', output_format=''):
394        """
395        Return search results.
396
397        Optional parameters:    output_format { dict, json }
398
399        Output: array
400        """
401        uri = '/hubs/search?query=' + quote(query.encode('utf8')) + '&limit=' + limit + '&includeCollections=1'
402        request = self.request_handler.make_request(uri=uri,
403                                                    request_type='GET',
404                                                    output_format=output_format)
405
406        return request
407
408    def get_account(self, output_format=''):
409        """
410        Return account details.
411
412        Optional parameters:    output_format { dict, json }
413
414        Output: array
415        """
416        uri = '/myplex/account'
417        request = self.request_handler.make_request(uri=uri,
418                                                    request_type='GET',
419                                                    output_format=output_format)
420
421        return request
422
423    def put_refresh_reachability(self):
424        """
425        Refresh Plex remote access port mapping.
426
427        Optional parameters:    None
428
429        Output: None
430        """
431        uri = '/myplex/refreshReachability'
432        request = self.request_handler.make_request(uri=uri,
433                                                    request_type='PUT')
434
435        return request
436
437    def put_updater(self, output_format=''):
438        """
439        Refresh updater status.
440
441        Optional parameters:    output_format { dict, json }
442
443        Output: array
444        """
445        uri = '/updater/check?download=0'
446        request = self.request_handler.make_request(uri=uri,
447                                                    request_type='PUT',
448                                                    output_format=output_format)
449
450        return request
451
452    def get_updater(self, output_format=''):
453        """
454        Return updater status.
455
456        Optional parameters:    output_format { dict, json }
457
458        Output: array
459        """
460        uri = '/updater/status'
461        request = self.request_handler.make_request(uri=uri,
462                                                    request_type='GET',
463                                                    output_format=output_format)
464
465        return request
466
467    def get_hub_recently_added(self, start='0', count='0', media_type='', other_video=False, output_format=''):
468        """
469        Return Plex hub recently added.
470
471        Parameters required:    start { item number to start from }
472                                count { number of results to return }
473                                media_type { str }
474        Optional parameters:    output_format { dict, json }
475
476        Output: array
477        """
478        personal = '&personal=1' if other_video else ''
479        uri = '/hubs/home/recentlyAdded?X-Plex-Container-Start=%s&X-Plex-Container-Size=%s&type=%s%s' \
480              % (start, count, media_type, personal)
481        request = self.request_handler.make_request(uri=uri,
482                                                    request_type='GET',
483                                                    output_format=output_format)
484
485        return request
486
487    def get_recently_added_details(self, start='0', count='0',  media_type='', section_id=''):
488        """
489        Return processed and validated list of recently added items.
490
491        Parameters required:    count { number of results to return }
492
493        Output: array
494        """
495        media_types = ('movie', 'show', 'artist', 'other_video')
496        recents_list = []
497
498        if media_type in media_types:
499            other_video = False
500            if media_type == 'movie':
501                media_type = '1'
502            elif media_type == 'show':
503                media_type = '2'
504            elif media_type == 'artist':
505                media_type = '8'
506            elif media_type == 'other_video':
507                media_type = '1'
508                other_video = True
509            recent = self.get_hub_recently_added(start, count, media_type, other_video, output_format='xml')
510        elif section_id:
511            recent = self.get_library_recently_added(section_id, start, count, output_format='xml')
512        else:
513            for media_type in media_types:
514                recents = self.get_recently_added_details(start, count, media_type)
515                recents_list += recents['recently_added']
516
517            output = {'recently_added': sorted(recents_list, key=lambda k: k['added_at'], reverse=True)[:int(count)]}
518            return output
519
520        try:
521            xml_head = recent.getElementsByTagName('MediaContainer')
522        except Exception as e:
523            logger.warn("Tautulli Pmsconnect :: Unable to parse XML for get_recently_added: %s." % e)
524            return {'recently_added': []}
525
526        for a in xml_head:
527            if a.getAttribute('size'):
528                if a.getAttribute('size') == '0':
529                    output = {'recently_added': []}
530                    return output
531
532            recents_main = []
533            if a.getElementsByTagName('Directory'):
534                recents_main += a.getElementsByTagName('Directory')
535            if a.getElementsByTagName('Video'):
536                recents_main += a.getElementsByTagName('Video')
537
538            for m in recents_main:
539                directors = []
540                writers = []
541                actors = []
542                genres = []
543                labels = []
544                collections = []
545                guids = []
546
547                if m.getElementsByTagName('Director'):
548                    for director in m.getElementsByTagName('Director'):
549                        directors.append(helpers.get_xml_attr(director, 'tag'))
550
551                if m.getElementsByTagName('Writer'):
552                    for writer in m.getElementsByTagName('Writer'):
553                        writers.append(helpers.get_xml_attr(writer, 'tag'))
554
555                if m.getElementsByTagName('Role'):
556                    for actor in m.getElementsByTagName('Role'):
557                        actors.append(helpers.get_xml_attr(actor, 'tag'))
558
559                if m.getElementsByTagName('Genre'):
560                    for genre in m.getElementsByTagName('Genre'):
561                        genres.append(helpers.get_xml_attr(genre, 'tag'))
562
563                if m.getElementsByTagName('Label'):
564                    for label in m.getElementsByTagName('Label'):
565                        labels.append(helpers.get_xml_attr(label, 'tag'))
566
567                if m.getElementsByTagName('Collection'):
568                    for collection in m.getElementsByTagName('Collection'):
569                        collections.append(helpers.get_xml_attr(collection, 'tag'))
570
571                if m.getElementsByTagName('Guid'):
572                    for guid in m.getElementsByTagName('Guid'):
573                        guids.append(helpers.get_xml_attr(guid, 'id'))
574
575                recent_item = {'media_type': helpers.get_xml_attr(m, 'type'),
576                               'section_id': helpers.get_xml_attr(m, 'librarySectionID'),
577                               'library_name': helpers.get_xml_attr(m, 'librarySectionTitle'),
578                               'rating_key': helpers.get_xml_attr(m, 'ratingKey'),
579                               'parent_rating_key': helpers.get_xml_attr(m, 'parentRatingKey'),
580                               'grandparent_rating_key': helpers.get_xml_attr(m, 'grandparentRatingKey'),
581                               'title': helpers.get_xml_attr(m, 'title'),
582                               'parent_title': helpers.get_xml_attr(m, 'parentTitle'),
583                               'grandparent_title': helpers.get_xml_attr(m, 'grandparentTitle'),
584                               'original_title': helpers.get_xml_attr(m, 'originalTitle'),
585                               'sort_title': helpers.get_xml_attr(m, 'titleSort'),
586                               'media_index': helpers.get_xml_attr(m, 'index'),
587                               'parent_media_index': helpers.get_xml_attr(m, 'parentIndex'),
588                               'studio': helpers.get_xml_attr(m, 'studio'),
589                               'content_rating': helpers.get_xml_attr(m, 'contentRating'),
590                               'summary': helpers.get_xml_attr(m, 'summary'),
591                               'tagline': helpers.get_xml_attr(m, 'tagline'),
592                               'rating': helpers.get_xml_attr(m, 'rating'),
593                               'rating_image': helpers.get_xml_attr(m, 'ratingImage'),
594                               'audience_rating': helpers.get_xml_attr(m, 'audienceRating'),
595                               'audience_rating_image': helpers.get_xml_attr(m, 'audienceRatingImage'),
596                               'user_rating': helpers.get_xml_attr(m, 'userRating'),
597                               'duration': helpers.get_xml_attr(m, 'duration'),
598                               'year': helpers.get_xml_attr(m, 'year'),
599                               'thumb': helpers.get_xml_attr(m, 'thumb'),
600                               'parent_thumb': helpers.get_xml_attr(m, 'parentThumb'),
601                               'grandparent_thumb': helpers.get_xml_attr(m, 'grandparentThumb'),
602                               'art': helpers.get_xml_attr(m, 'art'),
603                               'banner': helpers.get_xml_attr(m, 'banner'),
604                               'originally_available_at': helpers.get_xml_attr(m, 'originallyAvailableAt'),
605                               'added_at': helpers.get_xml_attr(m, 'addedAt'),
606                               'updated_at': helpers.get_xml_attr(m, 'updatedAt'),
607                               'last_viewed_at': helpers.get_xml_attr(m, 'lastViewedAt'),
608                               'guid': helpers.get_xml_attr(m, 'guid'),
609                               'directors': directors,
610                               'writers': writers,
611                               'actors': actors,
612                               'genres': genres,
613                               'labels': labels,
614                               'collections': collections,
615                               'guids': guids,
616                               'full_title': helpers.get_xml_attr(m, 'title'),
617                               'child_count': helpers.get_xml_attr(m, 'childCount')
618                               }
619
620                recents_list.append(recent_item)
621
622        output = {'recently_added': sorted(recents_list, key=lambda k: k['added_at'], reverse=True)}
623
624        return output
625
626    def get_metadata_details(self, rating_key='', sync_id='', plex_guid='', section_id='',
627                             skip_cache=False, cache_key=None, return_cache=False, media_info=True):
628        """
629        Return processed and validated metadata list for requested item.
630
631        Parameters required:    rating_key { Plex ratingKey }
632
633        Output: array
634        """
635        metadata = {}
636
637        if not skip_cache and cache_key:
638            in_file_folder = os.path.join(plexpy.CONFIG.CACHE_DIR, 'session_metadata')
639            in_file_path = os.path.join(in_file_folder, 'metadata-sessionKey-%s.json' % cache_key)
640
641            if not os.path.exists(in_file_folder):
642                os.mkdir(in_file_folder)
643
644            try:
645                with open(in_file_path, 'r') as inFile:
646                    metadata = json.load(inFile)
647            except (IOError, ValueError) as e:
648                pass
649
650            if metadata:
651                _cache_time = metadata.pop('_cache_time', 0)
652                # Return cached metadata if less than cache_seconds ago
653                if return_cache or helpers.timestamp() - _cache_time <= plexpy.CONFIG.METADATA_CACHE_SECONDS:
654                    return metadata
655
656        if rating_key:
657            metadata_xml = self.get_metadata(str(rating_key), output_format='xml')
658        elif sync_id:
659            metadata_xml = self.get_sync_item(str(sync_id), output_format='xml')
660        elif plex_guid.startswith(('plex://movie', 'plex://episode')):
661            rating_key = plex_guid.rsplit('/', 1)[-1]
662            plextv_metadata = PmsConnect(url='https://metadata.provider.plex.tv', token=plexpy.CONFIG.PMS_TOKEN)
663            metadata_xml = plextv_metadata.get_metadata(rating_key, output_format='xml')
664        else:
665            return metadata
666
667        try:
668            xml_head = metadata_xml.getElementsByTagName('MediaContainer')
669        except Exception as e:
670            logger.warn("Tautulli Pmsconnect :: Unable to parse XML for get_metadata_details: %s." % e)
671            return {}
672
673        for a in xml_head:
674            if a.getAttribute('size'):
675                if a.getAttribute('size') == '0':
676                    return metadata
677
678            if a.getElementsByTagName('Directory'):
679                metadata_main_list = a.getElementsByTagName('Directory')
680            elif a.getElementsByTagName('Video'):
681                metadata_main_list = a.getElementsByTagName('Video')
682            elif a.getElementsByTagName('Track'):
683                metadata_main_list = a.getElementsByTagName('Track')
684            elif a.getElementsByTagName('Photo'):
685                metadata_main_list = a.getElementsByTagName('Photo')
686            elif a.getElementsByTagName('Playlist'):
687                metadata_main_list = a.getElementsByTagName('Playlist')
688            else:
689                logger.debug("Tautulli Pmsconnect :: Metadata failed")
690                return {}
691
692            if sync_id and len(metadata_main_list) > 1:
693                for metadata_main in metadata_main_list:
694                    if helpers.get_xml_attr(metadata_main, 'ratingKey') == rating_key:
695                        break
696            else:
697                metadata_main = metadata_main_list[0]
698
699            metadata_type = helpers.get_xml_attr(metadata_main, 'type')
700            if metadata_main.nodeName == 'Directory' and metadata_type == 'photo':
701                metadata_type = 'photo_album'
702
703            section_id = helpers.get_xml_attr(a, 'librarySectionID') or section_id
704            library_name = helpers.get_xml_attr(a, 'librarySectionTitle')
705
706            if not library_name and section_id:
707                library_data = libraries.Libraries().get_details(section_id)
708                library_name = library_data['section_name']
709
710        directors = []
711        writers = []
712        actors = []
713        genres = []
714        labels = []
715        collections = []
716        guids = []
717
718        if metadata_main.getElementsByTagName('Director'):
719            for director in metadata_main.getElementsByTagName('Director'):
720                directors.append(helpers.get_xml_attr(director, 'tag'))
721
722        if metadata_main.getElementsByTagName('Writer'):
723            for writer in metadata_main.getElementsByTagName('Writer'):
724                writers.append(helpers.get_xml_attr(writer, 'tag'))
725
726        if metadata_main.getElementsByTagName('Role'):
727            for actor in metadata_main.getElementsByTagName('Role'):
728                actors.append(helpers.get_xml_attr(actor, 'tag'))
729
730        if metadata_main.getElementsByTagName('Genre'):
731            for genre in metadata_main.getElementsByTagName('Genre'):
732                genres.append(helpers.get_xml_attr(genre, 'tag'))
733
734        if metadata_main.getElementsByTagName('Label'):
735            for label in metadata_main.getElementsByTagName('Label'):
736                labels.append(helpers.get_xml_attr(label, 'tag'))
737
738        if metadata_main.getElementsByTagName('Collection'):
739            for collection in metadata_main.getElementsByTagName('Collection'):
740                collections.append(helpers.get_xml_attr(collection, 'tag'))
741
742        if metadata_main.getElementsByTagName('Guid'):
743            for guid in metadata_main.getElementsByTagName('Guid'):
744                guids.append(helpers.get_xml_attr(guid, 'id'))
745
746        if metadata_type == 'movie':
747            metadata = {'media_type': metadata_type,
748                        'section_id': section_id,
749                        'library_name': library_name,
750                        'rating_key': helpers.get_xml_attr(metadata_main, 'ratingKey'),
751                        'parent_rating_key': helpers.get_xml_attr(metadata_main, 'parentRatingKey'),
752                        'grandparent_rating_key': helpers.get_xml_attr(metadata_main, 'grandparentRatingKey'),
753                        'title': helpers.get_xml_attr(metadata_main, 'title'),
754                        'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
755                        'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
756                        'original_title': helpers.get_xml_attr(metadata_main, 'originalTitle'),
757                        'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'),
758                        'media_index': helpers.get_xml_attr(metadata_main, 'index'),
759                        'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
760                        'studio': helpers.get_xml_attr(metadata_main, 'studio'),
761                        'content_rating': helpers.get_xml_attr(metadata_main, 'contentRating'),
762                        'summary': helpers.get_xml_attr(metadata_main, 'summary'),
763                        'tagline': helpers.get_xml_attr(metadata_main, 'tagline'),
764                        'rating': helpers.get_xml_attr(metadata_main, 'rating'),
765                        'rating_image': helpers.get_xml_attr(metadata_main, 'ratingImage'),
766                        'audience_rating': helpers.get_xml_attr(metadata_main, 'audienceRating'),
767                        'audience_rating_image': helpers.get_xml_attr(metadata_main, 'audienceRatingImage'),
768                        'user_rating': helpers.get_xml_attr(metadata_main, 'userRating'),
769                        'duration': helpers.get_xml_attr(metadata_main, 'duration'),
770                        'year': helpers.get_xml_attr(metadata_main, 'year'),
771                        'thumb': helpers.get_xml_attr(metadata_main, 'thumb'),
772                        'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
773                        'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
774                        'art': helpers.get_xml_attr(metadata_main, 'art'),
775                        'banner': helpers.get_xml_attr(metadata_main, 'banner'),
776                        'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
777                        'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
778                        'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
779                        'last_viewed_at': helpers.get_xml_attr(metadata_main, 'lastViewedAt'),
780                        'guid': helpers.get_xml_attr(metadata_main, 'guid'),
781                        'parent_guid': helpers.get_xml_attr(metadata_main, 'parentGuid'),
782                        'grandparent_guid': helpers.get_xml_attr(metadata_main, 'grandparentGuid'),
783                        'directors': directors,
784                        'writers': writers,
785                        'actors': actors,
786                        'genres': genres,
787                        'labels': labels,
788                        'collections': collections,
789                        'guids': guids,
790                        'full_title': helpers.get_xml_attr(metadata_main, 'title'),
791                        'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
792                        'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
793                        }
794
795        elif metadata_type == 'show':
796            # Workaround for for duration sometimes reported in minutes for a show
797            duration = helpers.get_xml_attr(metadata_main, 'duration')
798            if duration.isdigit() and int(duration) < 1000:
799                duration = str(int(duration) * 60 * 1000)
800
801            metadata = {'media_type': metadata_type,
802                        'section_id': section_id,
803                        'library_name': library_name,
804                        'rating_key': helpers.get_xml_attr(metadata_main, 'ratingKey'),
805                        'parent_rating_key': helpers.get_xml_attr(metadata_main, 'parentRatingKey'),
806                        'grandparent_rating_key': helpers.get_xml_attr(metadata_main, 'grandparentRatingKey'),
807                        'title': helpers.get_xml_attr(metadata_main, 'title'),
808                        'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
809                        'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
810                        'original_title': helpers.get_xml_attr(metadata_main, 'originalTitle'),
811                        'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'),
812                        'media_index': helpers.get_xml_attr(metadata_main, 'index'),
813                        'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
814                        'studio': helpers.get_xml_attr(metadata_main, 'studio'),
815                        'content_rating': helpers.get_xml_attr(metadata_main, 'contentRating'),
816                        'summary': helpers.get_xml_attr(metadata_main, 'summary'),
817                        'tagline': helpers.get_xml_attr(metadata_main, 'tagline'),
818                        'rating': helpers.get_xml_attr(metadata_main, 'rating'),
819                        'rating_image': helpers.get_xml_attr(metadata_main, 'ratingImage'),
820                        'audience_rating': helpers.get_xml_attr(metadata_main, 'audienceRating'),
821                        'audience_rating_image': helpers.get_xml_attr(metadata_main, 'audienceRatingImage'),
822                        'user_rating': helpers.get_xml_attr(metadata_main, 'userRating'),
823                        'duration': duration,
824                        'year': helpers.get_xml_attr(metadata_main, 'year'),
825                        'thumb': helpers.get_xml_attr(metadata_main, 'thumb'),
826                        'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
827                        'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
828                        'art': helpers.get_xml_attr(metadata_main, 'art'),
829                        'banner': helpers.get_xml_attr(metadata_main, 'banner'),
830                        'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
831                        'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
832                        'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
833                        'last_viewed_at': helpers.get_xml_attr(metadata_main, 'lastViewedAt'),
834                        'guid': helpers.get_xml_attr(metadata_main, 'guid'),
835                        'parent_guid': helpers.get_xml_attr(metadata_main, 'parentGuid'),
836                        'grandparent_guid': helpers.get_xml_attr(metadata_main, 'grandparentGuid'),
837                        'directors': directors,
838                        'writers': writers,
839                        'actors': actors,
840                        'genres': genres,
841                        'labels': labels,
842                        'collections': collections,
843                        'guids': guids,
844                        'full_title': helpers.get_xml_attr(metadata_main, 'title'),
845                        'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
846                        'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
847                        }
848
849        elif metadata_type == 'season':
850            parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey')
851            parent_guid = helpers.get_xml_attr(metadata_main, 'parentGuid')
852            show_details = {}
853            if plex_guid and parent_guid:
854                show_details = self.get_metadata_details(plex_guid=parent_guid)
855            elif not plex_guid and parent_rating_key:
856                show_details = self.get_metadata_details(parent_rating_key)
857
858            metadata = {'media_type': metadata_type,
859                        'section_id': section_id,
860                        'library_name': library_name,
861                        'rating_key': helpers.get_xml_attr(metadata_main, 'ratingKey'),
862                        'parent_rating_key': helpers.get_xml_attr(metadata_main, 'parentRatingKey'),
863                        'grandparent_rating_key': helpers.get_xml_attr(metadata_main, 'grandparentRatingKey'),
864                        'title': helpers.get_xml_attr(metadata_main, 'title'),
865                        'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
866                        'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
867                        'original_title': helpers.get_xml_attr(metadata_main, 'originalTitle'),
868                        'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'),
869                        'media_index': helpers.get_xml_attr(metadata_main, 'index'),
870                        'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
871                        'studio': show_details.get('studio', ''),
872                        'content_rating': show_details.get('content_rating', ''),
873                        'summary': show_details.get('summary', ''),
874                        'tagline': helpers.get_xml_attr(metadata_main, 'tagline'),
875                        'rating': helpers.get_xml_attr(metadata_main, 'rating'),
876                        'rating_image': helpers.get_xml_attr(metadata_main, 'ratingImage'),
877                        'audience_rating': helpers.get_xml_attr(metadata_main, 'audienceRating'),
878                        'audience_rating_image': helpers.get_xml_attr(metadata_main, 'audienceRatingImage'),
879                        'user_rating': helpers.get_xml_attr(metadata_main, 'userRating'),
880                        'duration': show_details.get('duration', ''),
881                        'year': helpers.get_xml_attr(metadata_main, 'year'),
882                        'thumb': helpers.get_xml_attr(metadata_main, 'thumb'),
883                        'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
884                        'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
885                        'art': helpers.get_xml_attr(metadata_main, 'art'),
886                        'banner': show_details.get('banner', ''),
887                        'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
888                        'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
889                        'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
890                        'last_viewed_at': helpers.get_xml_attr(metadata_main, 'lastViewedAt'),
891                        'guid': helpers.get_xml_attr(metadata_main, 'guid'),
892                        'parent_guid': helpers.get_xml_attr(metadata_main, 'parentGuid'),
893                        'grandparent_guid': helpers.get_xml_attr(metadata_main, 'grandparentGuid'),
894                        'directors': show_details.get('directors', []),
895                        'writers': show_details.get('writers', []),
896                        'actors': show_details.get('actors', []),
897                        'genres': show_details.get('genres', []),
898                        'labels': show_details.get('labels', []),
899                        'collections': show_details.get('collections', []),
900                        'guids': show_details.get('guids', []),
901                        'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'parentTitle'),
902                                                       helpers.get_xml_attr(metadata_main, 'title')),
903                        'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
904                        'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
905                        }
906
907        elif metadata_type == 'episode':
908            grandparent_rating_key = helpers.get_xml_attr(metadata_main, 'grandparentRatingKey')
909            grandparent_guid = helpers.get_xml_attr(metadata_main, 'grandparentGuid')
910            show_details = {}
911            if plex_guid and grandparent_guid:
912                show_details = self.get_metadata_details(plex_guid=grandparent_guid)
913            elif not plex_guid and grandparent_rating_key:
914                show_details = self.get_metadata_details(grandparent_rating_key)
915
916            parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey')
917            parent_media_index = helpers.get_xml_attr(metadata_main, 'parentIndex')
918            parent_thumb = helpers.get_xml_attr(metadata_main, 'parentThumb')
919
920            if not plex_guid and not parent_rating_key:
921                # Try getting the parent_rating_key from the parent_thumb
922                if parent_thumb.startswith('/library/metadata/'):
923                    parent_rating_key = parent_thumb.split('/')[3]
924
925                # Try getting the parent_rating_key from the grandparent's children
926                if not parent_rating_key and grandparent_rating_key:
927                    children_list = self.get_item_children(grandparent_rating_key)
928                    parent_rating_key = next((c['rating_key'] for c in children_list['children_list']
929                                              if c['media_index'] == parent_media_index), '')
930
931            metadata = {'media_type': metadata_type,
932                        'section_id': section_id,
933                        'library_name': library_name,
934                        'rating_key': helpers.get_xml_attr(metadata_main, 'ratingKey'),
935                        'parent_rating_key': parent_rating_key,
936                        'grandparent_rating_key': helpers.get_xml_attr(metadata_main, 'grandparentRatingKey'),
937                        'title': helpers.get_xml_attr(metadata_main, 'title'),
938                        'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
939                        'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
940                        'original_title': helpers.get_xml_attr(metadata_main, 'originalTitle'),
941                        'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'),
942                        'media_index': helpers.get_xml_attr(metadata_main, 'index'),
943                        'parent_media_index': parent_media_index,
944                        'studio': show_details.get('studio', ''),
945                        'content_rating': helpers.get_xml_attr(metadata_main, 'contentRating'),
946                        'summary': helpers.get_xml_attr(metadata_main, 'summary'),
947                        'tagline': helpers.get_xml_attr(metadata_main, 'tagline'),
948                        'rating': helpers.get_xml_attr(metadata_main, 'rating'),
949                        'rating_image': helpers.get_xml_attr(metadata_main, 'ratingImage'),
950                        'audience_rating': helpers.get_xml_attr(metadata_main, 'audienceRating'),
951                        'audience_rating_image': helpers.get_xml_attr(metadata_main, 'audienceRatingImage'),
952                        'user_rating': helpers.get_xml_attr(metadata_main, 'userRating'),
953                        'duration': helpers.get_xml_attr(metadata_main, 'duration'),
954                        'year': helpers.get_xml_attr(metadata_main, 'year'),
955                        'thumb': helpers.get_xml_attr(metadata_main, 'thumb'),
956                        'parent_thumb': parent_thumb,
957                        'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
958                        'art': helpers.get_xml_attr(metadata_main, 'art'),
959                        'banner': show_details.get('banner', ''),
960                        'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
961                        'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
962                        'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
963                        'last_viewed_at': helpers.get_xml_attr(metadata_main, 'lastViewedAt'),
964                        'guid': helpers.get_xml_attr(metadata_main, 'guid'),
965                        'parent_guid': helpers.get_xml_attr(metadata_main, 'parentGuid'),
966                        'grandparent_guid': helpers.get_xml_attr(metadata_main, 'grandparentGuid'),
967                        'directors': directors,
968                        'writers': writers,
969                        'actors': show_details.get('actors', []),
970                        'genres': show_details.get('genres', []),
971                        'labels': show_details.get('labels', []),
972                        'collections': show_details.get('collections', []),
973                        'guids': show_details.get('guids', []),
974                        'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
975                                                       helpers.get_xml_attr(metadata_main, 'title')),
976                        'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
977                        'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
978                        }
979
980        elif metadata_type == 'artist':
981            metadata = {'media_type': metadata_type,
982                        'section_id': section_id,
983                        'library_name': library_name,
984                        'rating_key': helpers.get_xml_attr(metadata_main, 'ratingKey'),
985                        'parent_rating_key': helpers.get_xml_attr(metadata_main, 'parentRatingKey'),
986                        'grandparent_rating_key': helpers.get_xml_attr(metadata_main, 'grandparentRatingKey'),
987                        'title': helpers.get_xml_attr(metadata_main, 'title'),
988                        'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
989                        'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
990                        'original_title': helpers.get_xml_attr(metadata_main, 'originalTitle'),
991                        'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'),
992                        'media_index': helpers.get_xml_attr(metadata_main, 'index'),
993                        'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
994                        'studio': helpers.get_xml_attr(metadata_main, 'studio'),
995                        'content_rating': helpers.get_xml_attr(metadata_main, 'contentRating'),
996                        'summary': helpers.get_xml_attr(metadata_main, 'summary'),
997                        'tagline': helpers.get_xml_attr(metadata_main, 'tagline'),
998                        'rating': helpers.get_xml_attr(metadata_main, 'rating'),
999                        'rating_image': helpers.get_xml_attr(metadata_main, 'ratingImage'),
1000                        'audience_rating': helpers.get_xml_attr(metadata_main, 'audienceRating'),
1001                        'audience_rating_image': helpers.get_xml_attr(metadata_main, 'audienceRatingImage'),
1002                        'user_rating': helpers.get_xml_attr(metadata_main, 'userRating'),
1003                        'duration': helpers.get_xml_attr(metadata_main, 'duration'),
1004                        'year': helpers.get_xml_attr(metadata_main, 'year'),
1005                        'thumb': helpers.get_xml_attr(metadata_main, 'thumb'),
1006                        'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
1007                        'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
1008                        'art': helpers.get_xml_attr(metadata_main, 'art'),
1009                        'banner': helpers.get_xml_attr(metadata_main, 'banner'),
1010                        'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
1011                        'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
1012                        'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
1013                        'last_viewed_at': helpers.get_xml_attr(metadata_main, 'lastViewedAt'),
1014                        'guid': helpers.get_xml_attr(metadata_main, 'guid'),
1015                        'parent_guid': helpers.get_xml_attr(metadata_main, 'parentGuid'),
1016                        'grandparent_guid': helpers.get_xml_attr(metadata_main, 'grandparentGuid'),
1017                        'directors': directors,
1018                        'writers': writers,
1019                        'actors': actors,
1020                        'genres': genres,
1021                        'labels': labels,
1022                        'collections': collections,
1023                        'guids': guids,
1024                        'full_title': helpers.get_xml_attr(metadata_main, 'title'),
1025                        'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
1026                        'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
1027                        }
1028
1029        elif metadata_type == 'album':
1030            parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey')
1031            artist_details = self.get_metadata_details(parent_rating_key) if parent_rating_key else {}
1032            metadata = {'media_type': metadata_type,
1033                        'section_id': section_id,
1034                        'library_name': library_name,
1035                        'rating_key': helpers.get_xml_attr(metadata_main, 'ratingKey'),
1036                        'parent_rating_key': helpers.get_xml_attr(metadata_main, 'parentRatingKey'),
1037                        'grandparent_rating_key': helpers.get_xml_attr(metadata_main, 'grandparentRatingKey'),
1038                        'title': helpers.get_xml_attr(metadata_main, 'title'),
1039                        'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
1040                        'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
1041                        'original_title': helpers.get_xml_attr(metadata_main, 'originalTitle'),
1042                        'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'),
1043                        'media_index': helpers.get_xml_attr(metadata_main, 'index'),
1044                        'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
1045                        'studio': helpers.get_xml_attr(metadata_main, 'studio'),
1046                        'content_rating': helpers.get_xml_attr(metadata_main, 'contentRating'),
1047                        'summary': helpers.get_xml_attr(metadata_main, 'summary') or artist_details.get('summary', ''),
1048                        'tagline': helpers.get_xml_attr(metadata_main, 'tagline'),
1049                        'rating': helpers.get_xml_attr(metadata_main, 'rating'),
1050                        'rating_image': helpers.get_xml_attr(metadata_main, 'ratingImage'),
1051                        'audience_rating': helpers.get_xml_attr(metadata_main, 'audienceRating'),
1052                        'audience_rating_image': helpers.get_xml_attr(metadata_main, 'audienceRatingImage'),
1053                        'user_rating': helpers.get_xml_attr(metadata_main, 'userRating'),
1054                        'duration': helpers.get_xml_attr(metadata_main, 'duration'),
1055                        'year': helpers.get_xml_attr(metadata_main, 'year'),
1056                        'thumb': helpers.get_xml_attr(metadata_main, 'thumb'),
1057                        'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
1058                        'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
1059                        'art': helpers.get_xml_attr(metadata_main, 'art'),
1060                        'banner': artist_details.get('banner', ''),
1061                        'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
1062                        'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
1063                        'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
1064                        'last_viewed_at': helpers.get_xml_attr(metadata_main, 'lastViewedAt'),
1065                        'guid': helpers.get_xml_attr(metadata_main, 'guid'),
1066                        'parent_guid': helpers.get_xml_attr(metadata_main, 'parentGuid'),
1067                        'grandparent_guid': helpers.get_xml_attr(metadata_main, 'grandparentGuid'),
1068                        'directors': directors,
1069                        'writers': writers,
1070                        'actors': actors,
1071                        'genres': genres,
1072                        'labels': labels,
1073                        'collections': collections,
1074                        'guids': guids,
1075                        'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'parentTitle'),
1076                                                       helpers.get_xml_attr(metadata_main, 'title')),
1077                        'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
1078                        'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
1079                        }
1080
1081        elif metadata_type == 'track':
1082            parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey')
1083            album_details = self.get_metadata_details(parent_rating_key) if parent_rating_key else {}
1084            track_artist = helpers.get_xml_attr(metadata_main, 'originalTitle') or \
1085                           helpers.get_xml_attr(metadata_main, 'grandparentTitle')
1086            metadata = {'media_type': metadata_type,
1087                        'section_id': section_id,
1088                        'library_name': library_name,
1089                        'rating_key': helpers.get_xml_attr(metadata_main, 'ratingKey'),
1090                        'parent_rating_key': helpers.get_xml_attr(metadata_main, 'parentRatingKey'),
1091                        'grandparent_rating_key': helpers.get_xml_attr(metadata_main, 'grandparentRatingKey'),
1092                        'title': helpers.get_xml_attr(metadata_main, 'title'),
1093                        'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
1094                        'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
1095                        'original_title': helpers.get_xml_attr(metadata_main, 'originalTitle'),
1096                        'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'),
1097                        'media_index': helpers.get_xml_attr(metadata_main, 'index'),
1098                        'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
1099                        'studio': helpers.get_xml_attr(metadata_main, 'studio'),
1100                        'content_rating': helpers.get_xml_attr(metadata_main, 'contentRating'),
1101                        'summary': helpers.get_xml_attr(metadata_main, 'summary'),
1102                        'tagline': helpers.get_xml_attr(metadata_main, 'tagline'),
1103                        'rating': helpers.get_xml_attr(metadata_main, 'rating'),
1104                        'rating_image': helpers.get_xml_attr(metadata_main, 'ratingImage'),
1105                        'audience_rating': helpers.get_xml_attr(metadata_main, 'audienceRating'),
1106                        'audience_rating_image': helpers.get_xml_attr(metadata_main, 'audienceRatingImage'),
1107                        'user_rating': helpers.get_xml_attr(metadata_main, 'userRating'),
1108                        'duration': helpers.get_xml_attr(metadata_main, 'duration'),
1109                        'year': album_details.get('year', ''),
1110                        'thumb': helpers.get_xml_attr(metadata_main, 'thumb'),
1111                        'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
1112                        'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
1113                        'art': helpers.get_xml_attr(metadata_main, 'art'),
1114                        'banner': album_details.get('banner', ''),
1115                        'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
1116                        'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
1117                        'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
1118                        'last_viewed_at': helpers.get_xml_attr(metadata_main, 'lastViewedAt'),
1119                        'guid': helpers.get_xml_attr(metadata_main, 'guid'),
1120                        'parent_guid': helpers.get_xml_attr(metadata_main, 'parentGuid'),
1121                        'grandparent_guid': helpers.get_xml_attr(metadata_main, 'grandparentGuid'),
1122                        'directors': directors,
1123                        'writers': writers,
1124                        'actors': actors,
1125                        'genres': album_details.get('genres', []),
1126                        'labels': album_details.get('labels', []),
1127                        'collections': album_details.get('collections', []),
1128                        'guids': album_details.get('guids', []),
1129                        'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'title'),
1130                                                       track_artist),
1131                        'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
1132                        'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
1133                        }
1134
1135        elif metadata_type == 'photo_album':
1136            metadata = {'media_type': metadata_type,
1137                        'section_id': section_id,
1138                        'library_name': library_name,
1139                        'rating_key': helpers.get_xml_attr(metadata_main, 'ratingKey'),
1140                        'parent_rating_key': helpers.get_xml_attr(metadata_main, 'parentRatingKey'),
1141                        'grandparent_rating_key': helpers.get_xml_attr(metadata_main, 'grandparentRatingKey'),
1142                        'title': helpers.get_xml_attr(metadata_main, 'title'),
1143                        'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
1144                        'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
1145                        'original_title': helpers.get_xml_attr(metadata_main, 'originalTitle'),
1146                        'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'),
1147                        'media_index': helpers.get_xml_attr(metadata_main, 'index'),
1148                        'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
1149                        'studio': helpers.get_xml_attr(metadata_main, 'studio'),
1150                        'content_rating': helpers.get_xml_attr(metadata_main, 'contentRating'),
1151                        'summary': helpers.get_xml_attr(metadata_main, 'summary'),
1152                        'tagline': helpers.get_xml_attr(metadata_main, 'tagline'),
1153                        'rating': helpers.get_xml_attr(metadata_main, 'rating'),
1154                        'rating_image': helpers.get_xml_attr(metadata_main, 'ratingImage'),
1155                        'audience_rating': helpers.get_xml_attr(metadata_main, 'audienceRating'),
1156                        'audience_rating_image': helpers.get_xml_attr(metadata_main, 'audienceRatingImage'),
1157                        'user_rating': helpers.get_xml_attr(metadata_main, 'userRating'),
1158                        'duration': helpers.get_xml_attr(metadata_main, 'duration'),
1159                        'year': helpers.get_xml_attr(metadata_main, 'year'),
1160                        'thumb': helpers.get_xml_attr(metadata_main, 'thumb'),
1161                        'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
1162                        'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
1163                        'art': helpers.get_xml_attr(metadata_main, 'art'),
1164                        'banner': helpers.get_xml_attr(metadata_main, 'banner'),
1165                        'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
1166                        'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
1167                        'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
1168                        'last_viewed_at': helpers.get_xml_attr(metadata_main, 'lastViewedAt'),
1169                        'guid': helpers.get_xml_attr(metadata_main, 'guid'),
1170                        'parent_guid': helpers.get_xml_attr(metadata_main, 'parentGuid'),
1171                        'grandparent_guid': helpers.get_xml_attr(metadata_main, 'grandparentGuid'),
1172                        'directors': directors,
1173                        'writers': writers,
1174                        'actors': actors,
1175                        'genres': genres,
1176                        'labels': labels,
1177                        'collections': collections,
1178                        'guids': guids,
1179                        'full_title': helpers.get_xml_attr(metadata_main, 'title'),
1180                        'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
1181                        'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
1182                        }
1183
1184        elif metadata_type == 'photo':
1185            parent_rating_key = helpers.get_xml_attr(metadata_main, 'parentRatingKey')
1186            photo_album_details = self.get_metadata_details(parent_rating_key) if parent_rating_key else {}
1187            metadata = {'media_type': metadata_type,
1188                        'section_id': section_id,
1189                        'library_name': library_name,
1190                        'rating_key': helpers.get_xml_attr(metadata_main, 'ratingKey'),
1191                        'parent_rating_key': helpers.get_xml_attr(metadata_main, 'parentRatingKey'),
1192                        'grandparent_rating_key': helpers.get_xml_attr(metadata_main, 'grandparentRatingKey'),
1193                        'title': helpers.get_xml_attr(metadata_main, 'title'),
1194                        'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
1195                        'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
1196                        'original_title': helpers.get_xml_attr(metadata_main, 'originalTitle'),
1197                        'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'),
1198                        'media_index': helpers.get_xml_attr(metadata_main, 'index'),
1199                        'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
1200                        'studio': helpers.get_xml_attr(metadata_main, 'studio'),
1201                        'content_rating': helpers.get_xml_attr(metadata_main, 'contentRating'),
1202                        'summary': helpers.get_xml_attr(metadata_main, 'summary'),
1203                        'tagline': helpers.get_xml_attr(metadata_main, 'tagline'),
1204                        'rating': helpers.get_xml_attr(metadata_main, 'rating'),
1205                        'rating_image': helpers.get_xml_attr(metadata_main, 'ratingImage'),
1206                        'audience_rating': helpers.get_xml_attr(metadata_main, 'audienceRating'),
1207                        'audience_rating_image': helpers.get_xml_attr(metadata_main, 'audienceRatingImage'),
1208                        'user_rating': helpers.get_xml_attr(metadata_main, 'userRating'),
1209                        'duration': helpers.get_xml_attr(metadata_main, 'duration'),
1210                        'year': helpers.get_xml_attr(metadata_main, 'year'),
1211                        'thumb': helpers.get_xml_attr(metadata_main, 'thumb'),
1212                        'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
1213                        'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
1214                        'art': helpers.get_xml_attr(metadata_main, 'art'),
1215                        'banner': photo_album_details.get('banner', ''),
1216                        'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
1217                        'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
1218                        'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
1219                        'last_viewed_at': helpers.get_xml_attr(metadata_main, 'lastViewedAt'),
1220                        'guid': helpers.get_xml_attr(metadata_main, 'guid'),
1221                        'parent_guid': helpers.get_xml_attr(metadata_main, 'parentGuid'),
1222                        'grandparent_guid': helpers.get_xml_attr(metadata_main, 'grandparentGuid'),
1223                        'directors': directors,
1224                        'writers': writers,
1225                        'actors': actors,
1226                        'genres': photo_album_details.get('genres', []),
1227                        'labels': photo_album_details.get('labels', []),
1228                        'collections': photo_album_details.get('collections', []),
1229                        'guids': photo_album_details.get('guids', []),
1230                        'full_title': '{} - {}'.format(helpers.get_xml_attr(metadata_main, 'parentTitle') or library_name,
1231                                                       helpers.get_xml_attr(metadata_main, 'title')),
1232                        'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
1233                        'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
1234                        }
1235
1236        elif metadata_type == 'collection':
1237            metadata = {'media_type': metadata_type,
1238                        'sub_media_type': helpers.get_xml_attr(metadata_main, 'subtype'),
1239                        'section_id': section_id,
1240                        'library_name': library_name,
1241                        'rating_key': helpers.get_xml_attr(metadata_main, 'ratingKey'),
1242                        'parent_rating_key': helpers.get_xml_attr(metadata_main, 'parentRatingKey'),
1243                        'grandparent_rating_key': helpers.get_xml_attr(metadata_main, 'grandparentRatingKey'),
1244                        'title': helpers.get_xml_attr(metadata_main, 'title'),
1245                        'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
1246                        'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
1247                        'original_title': helpers.get_xml_attr(metadata_main, 'originalTitle'),
1248                        'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'),
1249                        'media_index': helpers.get_xml_attr(metadata_main, 'index'),
1250                        'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
1251                        'studio': helpers.get_xml_attr(metadata_main, 'studio'),
1252                        'content_rating': helpers.get_xml_attr(metadata_main, 'contentRating'),
1253                        'summary': helpers.get_xml_attr(metadata_main, 'summary'),
1254                        'tagline': helpers.get_xml_attr(metadata_main, 'tagline'),
1255                        'rating': helpers.get_xml_attr(metadata_main, 'rating'),
1256                        'rating_image': helpers.get_xml_attr(metadata_main, 'ratingImage'),
1257                        'audience_rating': helpers.get_xml_attr(metadata_main, 'audienceRating'),
1258                        'audience_rating_image': helpers.get_xml_attr(metadata_main, 'audienceRatingImage'),
1259                        'user_rating': helpers.get_xml_attr(metadata_main, 'userRating'),
1260                        'duration': helpers.get_xml_attr(metadata_main, 'duration'),
1261                        'year': helpers.get_xml_attr(metadata_main, 'year'),
1262                        'min_year': helpers.get_xml_attr(metadata_main, 'minYear'),
1263                        'max_year': helpers.get_xml_attr(metadata_main, 'maxYear'),
1264                        'thumb': helpers.get_xml_attr(metadata_main, 'thumb').split('?')[0],
1265                        'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
1266                        'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
1267                        'art': helpers.get_xml_attr(metadata_main, 'art'),
1268                        'banner': helpers.get_xml_attr(metadata_main, 'banner'),
1269                        'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
1270                        'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
1271                        'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
1272                        'last_viewed_at': helpers.get_xml_attr(metadata_main, 'lastViewedAt'),
1273                        'guid': helpers.get_xml_attr(metadata_main, 'guid'),
1274                        'parent_guid': helpers.get_xml_attr(metadata_main, 'parentGuid'),
1275                        'grandparent_guid': helpers.get_xml_attr(metadata_main, 'grandparentGuid'),
1276                        'child_count': helpers.get_xml_attr(metadata_main, 'childCount'),
1277                        'directors': directors,
1278                        'writers': writers,
1279                        'actors': actors,
1280                        'genres': genres,
1281                        'labels': labels,
1282                        'collections': collections,
1283                        'guids': guids,
1284                        'full_title': helpers.get_xml_attr(metadata_main, 'title'),
1285                        'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'childCount')),
1286                        'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1'),
1287                        'smart': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'smart'))
1288                        }
1289
1290        elif metadata_type == 'playlist':
1291            metadata = {'media_type': metadata_type,
1292                        'section_id': section_id,
1293                        'library_name': library_name,
1294                        'rating_key': helpers.get_xml_attr(metadata_main, 'ratingKey'),
1295                        'guid': helpers.get_xml_attr(metadata_main, 'guid'),
1296                        'title': helpers.get_xml_attr(metadata_main, 'title'),
1297                        'summary': helpers.get_xml_attr(metadata_main, 'summary'),
1298                        'duration': helpers.get_xml_attr(metadata_main, 'duration'),
1299                        'composite': helpers.get_xml_attr(metadata_main, 'composite'),
1300                        'thumb': helpers.get_xml_attr(metadata_main, 'composite'),
1301                        'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
1302                        'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
1303                        'last_viewed_at': helpers.get_xml_attr(metadata_main, 'lastViewedAt'),
1304                        'children_count': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'leafCount')),
1305                        'smart': helpers.cast_to_int(helpers.get_xml_attr(metadata_main, 'smart')),
1306                        'playlist_type': helpers.get_xml_attr(metadata_main, 'playlistType'),
1307                        'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
1308                        }
1309
1310        elif metadata_type == 'clip':
1311            metadata = {'media_type': metadata_type,
1312                        'section_id': section_id,
1313                        'library_name': library_name,
1314                        'rating_key': helpers.get_xml_attr(metadata_main, 'ratingKey'),
1315                        'parent_rating_key': helpers.get_xml_attr(metadata_main, 'parentRatingKey'),
1316                        'grandparent_rating_key': helpers.get_xml_attr(metadata_main, 'grandparentRatingKey'),
1317                        'title': helpers.get_xml_attr(metadata_main, 'title'),
1318                        'parent_title': helpers.get_xml_attr(metadata_main, 'parentTitle'),
1319                        'grandparent_title': helpers.get_xml_attr(metadata_main, 'grandparentTitle'),
1320                        'original_title': helpers.get_xml_attr(metadata_main, 'originalTitle'),
1321                        'sort_title': helpers.get_xml_attr(metadata_main, 'titleSort'),
1322                        'media_index': helpers.get_xml_attr(metadata_main, 'index'),
1323                        'parent_media_index': helpers.get_xml_attr(metadata_main, 'parentIndex'),
1324                        'studio': helpers.get_xml_attr(metadata_main, 'studio'),
1325                        'content_rating': helpers.get_xml_attr(metadata_main, 'contentRating'),
1326                        'summary': helpers.get_xml_attr(metadata_main, 'summary'),
1327                        'tagline': helpers.get_xml_attr(metadata_main, 'tagline'),
1328                        'rating': helpers.get_xml_attr(metadata_main, 'rating'),
1329                        'rating_image': helpers.get_xml_attr(metadata_main, 'ratingImage'),
1330                        'audience_rating': helpers.get_xml_attr(metadata_main, 'audienceRating'),
1331                        'audience_rating_image': helpers.get_xml_attr(metadata_main, 'audienceRatingImage'),
1332                        'user_rating': helpers.get_xml_attr(metadata_main, 'userRating'),
1333                        'duration': helpers.get_xml_attr(metadata_main, 'duration'),
1334                        'year': helpers.get_xml_attr(metadata_main, 'year'),
1335                        'thumb': helpers.get_xml_attr(metadata_main, 'thumb'),
1336                        'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
1337                        'grandparent_thumb': helpers.get_xml_attr(metadata_main, 'grandparentThumb'),
1338                        'art': helpers.get_xml_attr(metadata_main, 'art'),
1339                        'banner': helpers.get_xml_attr(metadata_main, 'banner'),
1340                        'originally_available_at': helpers.get_xml_attr(metadata_main, 'originallyAvailableAt'),
1341                        'added_at': helpers.get_xml_attr(metadata_main, 'addedAt'),
1342                        'updated_at': helpers.get_xml_attr(metadata_main, 'updatedAt'),
1343                        'last_viewed_at': helpers.get_xml_attr(metadata_main, 'lastViewedAt'),
1344                        'guid': helpers.get_xml_attr(metadata_main, 'guid'),
1345                        'parent_guid': helpers.get_xml_attr(metadata_main, 'parentGuid'),
1346                        'grandparent_guid': helpers.get_xml_attr(metadata_main, 'grandparentGuid'),
1347                        'directors': directors,
1348                        'writers': writers,
1349                        'actors': actors,
1350                        'genres': genres,
1351                        'labels': labels,
1352                        'collections': collections,
1353                        'guids': guids,
1354                        'full_title': helpers.get_xml_attr(metadata_main, 'title'),
1355                        'extra_type': helpers.get_xml_attr(metadata_main, 'extraType'),
1356                        'sub_type': helpers.get_xml_attr(metadata_main, 'subtype'),
1357                        'live': int(helpers.get_xml_attr(metadata_main, 'live') == '1')
1358                        }
1359
1360        else:
1361            return metadata
1362
1363        # Get additional metadata from metadata.provider.plex.tv
1364        if not plex_guid and metadata['live']:
1365            metadata['section_id'] = common.LIVE_TV_SECTION_ID
1366            metadata['library_name'] = common.LIVE_TV_SECTION_NAME
1367
1368            plextv_metadata = self.get_metadata_details(plex_guid=metadata['guid'])
1369            if plextv_metadata:
1370                keys_to_update = ['summary', 'rating', 'thumb', 'grandparent_thumb', 'duration',
1371                                  'guid', 'grandparent_guid', 'genres']
1372                for key in keys_to_update:
1373                    metadata[key] = plextv_metadata[key]
1374                metadata['originally_available_at'] = helpers.iso_to_YMD(plextv_metadata['originally_available_at'])
1375
1376        if metadata and media_info:
1377            medias = []
1378            media_items = metadata_main.getElementsByTagName('Media')
1379            for media in media_items:
1380                video_full_resolution_scan_type = ''
1381
1382                parts = []
1383                part_items = media.getElementsByTagName('Part')
1384                for part in part_items:
1385
1386                    streams = []
1387                    stream_items = part.getElementsByTagName('Stream')
1388                    for stream in stream_items:
1389                        if helpers.get_xml_attr(stream, 'streamType') == '1':
1390                            video_scan_type = helpers.get_xml_attr(stream, 'scanType')
1391                            video_full_resolution_scan_type = (video_full_resolution_scan_type or video_scan_type)
1392
1393                            streams.append({'id': helpers.get_xml_attr(stream, 'id'),
1394                                            'type': helpers.get_xml_attr(stream, 'streamType'),
1395                                            'video_codec': helpers.get_xml_attr(stream, 'codec'),
1396                                            'video_codec_level': helpers.get_xml_attr(stream, 'level'),
1397                                            'video_bitrate': helpers.get_xml_attr(stream, 'bitrate'),
1398                                            'video_bit_depth': helpers.get_xml_attr(stream, 'bitDepth'),
1399                                            'video_chroma_subsampling': helpers.get_xml_attr(stream, 'chromaSubsampling'),
1400                                            'video_color_primaries': helpers.get_xml_attr(stream, 'colorPrimaries'),
1401                                            'video_color_range': helpers.get_xml_attr(stream, 'colorRange'),
1402                                            'video_color_space': helpers.get_xml_attr(stream, 'colorSpace'),
1403                                            'video_color_trc': helpers.get_xml_attr(stream, 'colorTrc'),
1404                                            'video_frame_rate': helpers.get_xml_attr(stream, 'frameRate'),
1405                                            'video_ref_frames': helpers.get_xml_attr(stream, 'refFrames'),
1406                                            'video_height': helpers.get_xml_attr(stream, 'height'),
1407                                            'video_width': helpers.get_xml_attr(stream, 'width'),
1408                                            'video_language': helpers.get_xml_attr(stream, 'language'),
1409                                            'video_language_code': helpers.get_xml_attr(stream, 'languageCode'),
1410                                            'video_profile': helpers.get_xml_attr(stream, 'profile'),
1411                                            'video_scan_type': helpers.get_xml_attr(stream, 'scanType'),
1412                                            'selected': int(helpers.get_xml_attr(stream, 'selected') == '1')
1413                                            })
1414
1415                        elif helpers.get_xml_attr(stream, 'streamType') == '2':
1416                            streams.append({'id': helpers.get_xml_attr(stream, 'id'),
1417                                            'type': helpers.get_xml_attr(stream, 'streamType'),
1418                                            'audio_codec': helpers.get_xml_attr(stream, 'codec'),
1419                                            'audio_bitrate': helpers.get_xml_attr(stream, 'bitrate'),
1420                                            'audio_bitrate_mode': helpers.get_xml_attr(stream, 'bitrateMode'),
1421                                            'audio_channels': helpers.get_xml_attr(stream, 'channels'),
1422                                            'audio_channel_layout': helpers.get_xml_attr(stream, 'audioChannelLayout'),
1423                                            'audio_sample_rate': helpers.get_xml_attr(stream, 'samplingRate'),
1424                                            'audio_language': helpers.get_xml_attr(stream, 'language'),
1425                                            'audio_language_code': helpers.get_xml_attr(stream, 'languageCode'),
1426                                            'audio_profile': helpers.get_xml_attr(stream, 'profile'),
1427                                            'selected': int(helpers.get_xml_attr(stream, 'selected') == '1')
1428                                            })
1429
1430                        elif helpers.get_xml_attr(stream, 'streamType') == '3':
1431                            streams.append({'id': helpers.get_xml_attr(stream, 'id'),
1432                                            'type': helpers.get_xml_attr(stream, 'streamType'),
1433                                            'subtitle_codec': helpers.get_xml_attr(stream, 'codec'),
1434                                            'subtitle_container': helpers.get_xml_attr(stream, 'container'),
1435                                            'subtitle_format': helpers.get_xml_attr(stream, 'format'),
1436                                            'subtitle_forced': int(helpers.get_xml_attr(stream, 'forced') == '1'),
1437                                            'subtitle_location': 'external' if helpers.get_xml_attr(stream, 'key') else 'embedded',
1438                                            'subtitle_language': helpers.get_xml_attr(stream, 'language'),
1439                                            'subtitle_language_code': helpers.get_xml_attr(stream, 'languageCode'),
1440                                            'selected': int(helpers.get_xml_attr(stream, 'selected') == '1')
1441                                            })
1442
1443                    parts.append({'id': helpers.get_xml_attr(part, 'id'),
1444                                  'file': helpers.get_xml_attr(part, 'file'),
1445                                  'file_size': helpers.get_xml_attr(part, 'size'),
1446                                  'indexes': int(helpers.get_xml_attr(part, 'indexes') == 'sd'),
1447                                  'streams': streams,
1448                                  'selected': int(helpers.get_xml_attr(part, 'selected') == '1')
1449                                  })
1450
1451                video_resolution = helpers.get_xml_attr(media, 'videoResolution').lower().rstrip('ip')
1452                video_full_resolution = common.VIDEO_RESOLUTION_OVERRIDES.get(
1453                    video_resolution, video_resolution + (video_full_resolution_scan_type[:1] or 'p')
1454                )
1455
1456                audio_channels = helpers.get_xml_attr(media, 'audioChannels')
1457
1458                media_info = {'id': helpers.get_xml_attr(media, 'id'),
1459                              'container': helpers.get_xml_attr(media, 'container'),
1460                              'bitrate': helpers.get_xml_attr(media, 'bitrate'),
1461                              'height': helpers.get_xml_attr(media, 'height'),
1462                              'width': helpers.get_xml_attr(media, 'width'),
1463                              'aspect_ratio': helpers.get_xml_attr(media, 'aspectRatio'),
1464                              'video_codec': helpers.get_xml_attr(media, 'videoCodec'),
1465                              'video_resolution': video_resolution,
1466                              'video_full_resolution': video_full_resolution,
1467                              'video_framerate': helpers.get_xml_attr(media, 'videoFrameRate'),
1468                              'video_profile': helpers.get_xml_attr(media, 'videoProfile'),
1469                              'audio_codec': helpers.get_xml_attr(media, 'audioCodec'),
1470                              'audio_channels': audio_channels,
1471                              'audio_channel_layout': common.AUDIO_CHANNELS.get(audio_channels, audio_channels),
1472                              'audio_profile': helpers.get_xml_attr(media, 'audioProfile'),
1473                              'optimized_version': int(helpers.get_xml_attr(media, 'proxyType') == '42'),
1474                              'channel_call_sign': helpers.get_xml_attr(media, 'channelCallSign'),
1475                              'channel_identifier': helpers.get_xml_attr(media, 'channelIdentifier'),
1476                              'channel_thumb': helpers.get_xml_attr(media, 'channelThumb'),
1477                              'parts': parts
1478                              }
1479
1480                medias.append(media_info)
1481
1482            metadata['media_info'] = medias
1483
1484        if metadata:
1485            if cache_key:
1486                metadata['_cache_time'] = helpers.timestamp()
1487
1488                out_file_folder = os.path.join(plexpy.CONFIG.CACHE_DIR, 'session_metadata')
1489                out_file_path = os.path.join(out_file_folder, 'metadata-sessionKey-%s.json' % cache_key)
1490
1491                if not os.path.exists(out_file_folder):
1492                    os.mkdir(out_file_folder)
1493
1494                try:
1495                    with open(out_file_path, 'w') as outFile:
1496                        json.dump(metadata, outFile)
1497                except (IOError, ValueError) as e:
1498                    logger.error("Tautulli Pmsconnect :: Unable to create cache file for metadata (sessionKey %s): %s"
1499                                 % (cache_key, e))
1500
1501            return metadata
1502        else:
1503            return metadata
1504
1505    def get_metadata_children_details(self, rating_key='', get_children=False):
1506        """
1507        Return processed and validated metadata list for all children of requested item.
1508
1509        Parameters required:    rating_key { Plex ratingKey }
1510
1511        Output: array
1512        """
1513        metadata = self.get_metadata_children(str(rating_key), output_format='xml')
1514
1515        try:
1516            xml_head = metadata.getElementsByTagName('MediaContainer')
1517        except Exception as e:
1518            logger.warn("Tautulli Pmsconnect :: Unable to parse XML for get_metadata_children: %s." % e)
1519            return []
1520
1521        metadata_list = []
1522
1523        for a in xml_head:
1524            if a.getAttribute('size'):
1525                if a.getAttribute('size') == '0':
1526                    return metadata_list
1527
1528            if a.getElementsByTagName('Video'):
1529                metadata_main = a.getElementsByTagName('Video')
1530                for item in metadata_main:
1531                    child_rating_key = helpers.get_xml_attr(item, 'ratingKey')
1532                    metadata = self.get_metadata_details(str(child_rating_key))
1533                    if metadata:
1534                        metadata_list.append(metadata)
1535
1536            elif a.getElementsByTagName('Track'):
1537                metadata_main = a.getElementsByTagName('Track')
1538                for item in metadata_main:
1539                    child_rating_key = helpers.get_xml_attr(item, 'ratingKey')
1540                    metadata = self.get_metadata_details(str(child_rating_key))
1541                    if metadata:
1542                        metadata_list.append(metadata)
1543
1544            elif get_children and a.getElementsByTagName('Directory'):
1545                dir_main = a.getElementsByTagName('Directory')
1546                metadata_main = [d for d in dir_main if helpers.get_xml_attr(d, 'ratingKey')]
1547                for item in metadata_main:
1548                    child_rating_key = helpers.get_xml_attr(item, 'ratingKey')
1549                    metadata = self.get_metadata_children_details(str(child_rating_key), get_children)
1550                    if metadata:
1551                        metadata_list.extend(metadata)
1552
1553        return metadata_list
1554
1555    def get_library_metadata_details(self, section_id=''):
1556        """
1557        Return processed and validated metadata list for requested library.
1558
1559        Parameters required:    section_id { Plex library key }
1560
1561        Output: array
1562        """
1563        libraries_data = self.get_libraries_list(output_format='xml')
1564
1565        try:
1566            xml_head = libraries_data.getElementsByTagName('MediaContainer')
1567        except Exception as e:
1568            logger.warn("Tautulli Pmsconnect :: Unable to parse XML for get_library_metadata_details: %s." % e)
1569            return []
1570
1571        metadata_list = []
1572
1573        for a in xml_head:
1574            if a.getAttribute('size'):
1575                if a.getAttribute('size') == '0':
1576                    metadata_list = {'metadata': None}
1577                    return metadata_list
1578
1579            if a.getElementsByTagName('Directory'):
1580                result_data = a.getElementsByTagName('Directory')
1581                for result in result_data:
1582                    key = helpers.get_xml_attr(result, 'key')
1583                    if key == section_id:
1584                        metadata = {'media_type': 'library',
1585                                    'section_id': helpers.get_xml_attr(result, 'key'),
1586                                    'library': helpers.get_xml_attr(result, 'type'),
1587                                    'title': helpers.get_xml_attr(result, 'title'),
1588                                    'art': helpers.get_xml_attr(result, 'art'),
1589                                    'thumb': helpers.get_xml_attr(result, 'thumb')
1590                                    }
1591                        if metadata['library'] == 'movie':
1592                            metadata['section_type'] = 'movie'
1593                        elif metadata['library'] == 'show':
1594                            metadata['section_type'] = 'episode'
1595                        elif metadata['library'] == 'artist':
1596                            metadata['section_type'] = 'track'
1597
1598            metadata_list = {'metadata': metadata}
1599
1600        return metadata_list
1601
1602    def get_current_activity(self, skip_cache=False):
1603        """
1604        Return processed and validated session list.
1605
1606        Output: array
1607        """
1608        session_data = self.get_sessions(output_format='xml')
1609
1610        try:
1611            xml_head = session_data.getElementsByTagName('MediaContainer')
1612        except Exception as e:
1613            logger.warn("Tautulli Pmsconnect :: Unable to parse XML for get_current_activity: %s." % e)
1614            return []
1615
1616        session_list = []
1617
1618        for a in xml_head:
1619            if a.getAttribute('size'):
1620                if a.getAttribute('size') == '0':
1621                    session_list = {'stream_count': '0',
1622                                    'sessions': []
1623                                    }
1624                    return session_list
1625
1626            if a.getElementsByTagName('Track'):
1627                session_data = a.getElementsByTagName('Track')
1628                for session_ in session_data:
1629                    # Filter out background theme music sessions
1630                    if helpers.get_xml_attr(session_, 'guid').startswith('library://'):
1631                        continue
1632                    session_output = self.get_session_each(session_, skip_cache=skip_cache)
1633                    session_list.append(session_output)
1634            if a.getElementsByTagName('Video'):
1635                session_data = a.getElementsByTagName('Video')
1636                for session_ in session_data:
1637                    session_output = self.get_session_each(session_, skip_cache=skip_cache)
1638                    session_list.append(session_output)
1639            if a.getElementsByTagName('Photo'):
1640                session_data = a.getElementsByTagName('Photo')
1641                for session_ in session_data:
1642                    session_output = self.get_session_each(session_, skip_cache=skip_cache)
1643                    session_list.append(session_output)
1644
1645        session_list = sorted(session_list, key=lambda k: k['session_key'])
1646
1647        output = {'stream_count': helpers.get_xml_attr(xml_head[0], 'size'),
1648                  'sessions': session.mask_session_info(session_list)
1649                  }
1650
1651        return output
1652
1653    def get_session_each(self, session=None, skip_cache=False):
1654        """
1655        Return selected data from current sessions.
1656        This function processes and validates session data
1657
1658        Parameters required:    session { the session dictionary }
1659        Output: dict
1660        """
1661
1662        # Get the source media type
1663        media_type = helpers.get_xml_attr(session, 'type')
1664        rating_key = helpers.get_xml_attr(session, 'ratingKey')
1665        session_key = helpers.get_xml_attr(session, 'sessionKey')
1666
1667        # Get the user details
1668        user_info = session.getElementsByTagName('User')[0]
1669        user_id = helpers.get_xml_attr(user_info, 'id')
1670        if user_id == '1':  # Admin user
1671            user_details = users.Users().get_details(user=helpers.get_xml_attr(user_info, 'title'))
1672        else:
1673            user_details = users.Users().get_details(user_id=user_id)
1674
1675        # Get the player details
1676        player_info = session.getElementsByTagName('Player')[0]
1677
1678        # Override platform names
1679        platform = helpers.get_xml_attr(player_info, 'platform')
1680        platform = common.PLATFORM_NAME_OVERRIDES.get(platform, platform)
1681        if not platform and helpers.get_xml_attr(player_info, 'product') == 'DLNA':
1682            platform = 'DLNA'
1683
1684        platform_name = next((v for k, v in common.PLATFORM_NAMES.items() if k in platform.lower()), 'default')
1685
1686        player_details = {'ip_address': helpers.get_xml_attr(player_info, 'address').split('::ffff:')[-1],
1687                          'ip_address_public': helpers.get_xml_attr(player_info, 'remotePublicAddress').split('::ffff:')[-1],
1688                          'device': helpers.get_xml_attr(player_info, 'device'),
1689                          'platform': platform,
1690                          'platform_name': platform_name,
1691                          'platform_version': helpers.get_xml_attr(player_info, 'platformVersion'),
1692                          'product': helpers.get_xml_attr(player_info, 'product'),
1693                          'product_version': helpers.get_xml_attr(player_info, 'version'),
1694                          'profile': helpers.get_xml_attr(player_info, 'profile'),
1695                          'player': helpers.get_xml_attr(player_info, 'title') or helpers.get_xml_attr(player_info, 'product'),
1696                          'machine_id': helpers.get_xml_attr(player_info, 'machineIdentifier'),
1697                          'state': helpers.get_xml_attr(player_info, 'state'),
1698                          'local': int(helpers.get_xml_attr(player_info, 'local') == '1'),
1699                          'relayed': helpers.get_xml_attr(player_info, 'relayed', default_return=None),
1700                          'secure': helpers.get_xml_attr(player_info, 'secure', default_return=None)
1701                          }
1702
1703        # Get the session details
1704        if session.getElementsByTagName('Session'):
1705            session_info = session.getElementsByTagName('Session')[0]
1706
1707            session_details = {'session_id': helpers.get_xml_attr(session_info, 'id'),
1708                               'bandwidth': helpers.get_xml_attr(session_info, 'bandwidth'),
1709                               'location': helpers.get_xml_attr(session_info, 'location')
1710                               }
1711        else:
1712            session_details = {'session_id': '',
1713                               'bandwidth': '',
1714                               'location': 'lan' if player_details['local'] else 'wan'
1715                               }
1716
1717        # Check if using Plex Relay
1718        if player_details['relayed'] is None:
1719            player_details['relayed'] = int(session_details['location'] != 'lan' and
1720                                            player_details['ip_address_public'] == '127.0.0.1')
1721
1722        else:
1723            player_details['relayed'] = helpers.cast_to_int(player_details['relayed'])
1724
1725        # Check if secure connection
1726        if player_details['secure'] is not None:
1727            player_details['secure'] = int(player_details['secure'] == '1')
1728
1729        # Get the transcode details
1730        if session.getElementsByTagName('TranscodeSession'):
1731            transcode_session = True
1732
1733            transcode_info = session.getElementsByTagName('TranscodeSession')[0]
1734
1735            transcode_progress = helpers.get_xml_attr(transcode_info, 'progress')
1736            transcode_speed = helpers.get_xml_attr(transcode_info, 'speed')
1737
1738            transcode_details = {'transcode_key': helpers.get_xml_attr(transcode_info, 'key'),
1739                                 'transcode_throttled': int(helpers.get_xml_attr(transcode_info, 'throttled') == '1'),
1740                                 'transcode_progress': int(round(helpers.cast_to_float(transcode_progress), 0)),
1741                                 'transcode_speed': str(round(helpers.cast_to_float(transcode_speed), 1)),
1742                                 'transcode_audio_channels': helpers.get_xml_attr(transcode_info, 'audioChannels'),
1743                                 'transcode_audio_codec': helpers.get_xml_attr(transcode_info, 'audioCodec'),
1744                                 'transcode_video_codec': helpers.get_xml_attr(transcode_info, 'videoCodec'),
1745                                 'transcode_width': helpers.get_xml_attr(transcode_info, 'width'),  # Blank but keep for backwards compatibility
1746                                 'transcode_height': helpers.get_xml_attr(transcode_info, 'height'),  # Blank but keep backwards compatibility
1747                                 'transcode_container': helpers.get_xml_attr(transcode_info, 'container'),
1748                                 'transcode_protocol': helpers.get_xml_attr(transcode_info, 'protocol'),
1749                                 'transcode_hw_requested': int(helpers.get_xml_attr(transcode_info, 'transcodeHwRequested') == '1'),
1750                                 'transcode_hw_decode': helpers.get_xml_attr(transcode_info, 'transcodeHwDecoding'),
1751                                 'transcode_hw_decode_title': helpers.get_xml_attr(transcode_info, 'transcodeHwDecodingTitle'),
1752                                 'transcode_hw_encode': helpers.get_xml_attr(transcode_info, 'transcodeHwEncoding'),
1753                                 'transcode_hw_encode_title': helpers.get_xml_attr(transcode_info, 'transcodeHwEncodingTitle'),
1754                                 'transcode_hw_full_pipeline': int(helpers.get_xml_attr(transcode_info, 'transcodeHwFullPipeline') == '1'),
1755                                 'audio_decision': helpers.get_xml_attr(transcode_info, 'audioDecision'),
1756                                 'video_decision': helpers.get_xml_attr(transcode_info, 'videoDecision'),
1757                                 'subtitle_decision': helpers.get_xml_attr(transcode_info, 'subtitleDecision'),
1758                                 'throttled': '1' if helpers.get_xml_attr(transcode_info, 'throttled') == '1' else '0'  # Keep for backwards compatibility
1759                                 }
1760        else:
1761            transcode_session = False
1762
1763            transcode_details = {'transcode_key': '',
1764                                 'transcode_throttled': 0,
1765                                 'transcode_progress': 0,
1766                                 'transcode_speed': '',
1767                                 'transcode_audio_channels': '',
1768                                 'transcode_audio_codec': '',
1769                                 'transcode_video_codec': '',
1770                                 'transcode_width': '',
1771                                 'transcode_height': '',
1772                                 'transcode_container': '',
1773                                 'transcode_protocol': '',
1774                                 'transcode_hw_requested': 0,
1775                                 'transcode_hw_decode': '',
1776                                 'transcode_hw_decode_title': '',
1777                                 'transcode_hw_encode': '',
1778                                 'transcode_hw_encode_title': '',
1779                                 'transcode_hw_full_pipeline': 0,
1780                                 'audio_decision': 'direct play',
1781                                 'video_decision': 'direct play',
1782                                 'subtitle_decision': '',
1783                                 'throttled': '0'  # Keep for backwards compatibility
1784                                 }
1785
1786        # Check HW decoding/encoding
1787        transcode_details['transcode_hw_decoding'] = int(transcode_details['transcode_hw_decode'].lower() in common.HW_DECODERS)
1788        transcode_details['transcode_hw_encoding'] = int(transcode_details['transcode_hw_encode'].lower() in common.HW_ENCODERS)
1789
1790        # Determine if a synced version is being played
1791        sync_id = synced_session_data = synced_item_details = None
1792        if media_type not in ('photo', 'clip') \
1793                and not session.getElementsByTagName('Session') \
1794                and not session.getElementsByTagName('TranscodeSession') \
1795                and helpers.get_xml_attr(session, 'ratingKey').isdigit() \
1796                and plexpy.CONFIG.PMS_PLEXPASS:
1797            plex_tv = plextv.PlexTV()
1798            parent_rating_key = helpers.get_xml_attr(session, 'parentRatingKey')
1799            grandparent_rating_key = helpers.get_xml_attr(session, 'grandparentRatingKey')
1800
1801            synced_items = plex_tv.get_synced_items(client_id_filter=player_details['machine_id'],
1802                                                    rating_key_filter=[rating_key, parent_rating_key, grandparent_rating_key])
1803            if synced_items:
1804                synced_item_details = synced_items[0]
1805                sync_id = synced_item_details['sync_id']
1806                synced_xml = self.get_sync_item(sync_id=sync_id, output_format='xml')
1807                synced_xml_head = synced_xml.getElementsByTagName('MediaContainer')
1808
1809                synced_xml_items = []
1810                if synced_xml_head[0].getElementsByTagName('Track'):
1811                    synced_xml_items = synced_xml_head[0].getElementsByTagName('Track')
1812                elif synced_xml_head[0].getElementsByTagName('Video'):
1813                    synced_xml_items = synced_xml_head[0].getElementsByTagName('Video')
1814
1815                for synced_session_data in synced_xml_items:
1816                    if helpers.get_xml_attr(synced_session_data, 'ratingKey') == rating_key:
1817                        break
1818
1819        # Figure out which version is being played
1820        if sync_id and synced_session_data:
1821            media_info_all = synced_session_data.getElementsByTagName('Media')
1822        else:
1823            media_info_all = session.getElementsByTagName('Media')
1824        stream_media_info = next((m for m in media_info_all if helpers.get_xml_attr(m, 'selected') == '1'), media_info_all[0])
1825        part_info_all = stream_media_info.getElementsByTagName('Part')
1826        stream_media_parts_info = next((p for p in part_info_all if helpers.get_xml_attr(p, 'selected') == '1'), part_info_all[0])
1827
1828        # Get the stream details
1829        video_stream_info = audio_stream_info = subtitle_stream_info = None
1830        for stream in stream_media_parts_info.getElementsByTagName('Stream'):
1831            if helpers.get_xml_attr(stream, 'streamType') == '1':
1832                if video_stream_info is None or helpers.get_xml_attr(stream, 'selected') == '1':
1833                    video_stream_info = stream
1834
1835            elif helpers.get_xml_attr(stream, 'streamType') == '2':
1836                if audio_stream_info is None or helpers.get_xml_attr(stream, 'selected') == '1':
1837                    audio_stream_info = stream
1838
1839            elif helpers.get_xml_attr(stream, 'streamType') == '3':
1840                if subtitle_stream_info is None or helpers.get_xml_attr(stream, 'selected') == '1':
1841                    subtitle_stream_info = stream
1842
1843        video_id = audio_id = subtitle_id = None
1844        if video_stream_info:
1845            video_id = helpers.get_xml_attr(video_stream_info, 'id')
1846            video_details = {'stream_video_bitrate': helpers.get_xml_attr(video_stream_info, 'bitrate'),
1847                             'stream_video_bit_depth': helpers.get_xml_attr(video_stream_info, 'bitDepth'),
1848                             'stream_video_chroma_subsampling': helpers.get_xml_attr(video_stream_info, 'chromaSubsampling'),
1849                             'stream_video_color_primaries': helpers.get_xml_attr(video_stream_info, 'colorPrimaries'),
1850                             'stream_video_color_range': helpers.get_xml_attr(video_stream_info, 'colorRange'),
1851                             'stream_video_color_space': helpers.get_xml_attr(video_stream_info, 'colorSpace'),
1852                             'stream_video_color_trc': helpers.get_xml_attr(video_stream_info, 'colorTrc'),
1853                             'stream_video_codec_level': helpers.get_xml_attr(video_stream_info, 'level'),
1854                             'stream_video_ref_frames': helpers.get_xml_attr(video_stream_info, 'refFrames'),
1855                             'stream_video_language': helpers.get_xml_attr(video_stream_info, 'language'),
1856                             'stream_video_language_code': helpers.get_xml_attr(video_stream_info, 'languageCode'),
1857                             'stream_video_scan_type': helpers.get_xml_attr(video_stream_info, 'scanType'),
1858                             'stream_video_decision': helpers.get_xml_attr(video_stream_info, 'decision') or 'direct play'
1859                             }
1860        else:
1861            video_details = {'stream_video_bitrate': '',
1862                             'stream_video_bit_depth': '',
1863                             'stream_video_chroma_subsampling': '',
1864                             'stream_video_color_primaries': '',
1865                             'stream_video_color_range': '',
1866                             'stream_video_color_space': '',
1867                             'stream_video_color_trc': '',
1868                             'stream_video_codec_level': '',
1869                             'stream_video_ref_frames': '',
1870                             'stream_video_language': '',
1871                             'stream_video_language_code': '',
1872                             'stream_video_scan_type': '',
1873                             'stream_video_decision': ''
1874                             }
1875
1876        if audio_stream_info:
1877            audio_id = helpers.get_xml_attr(audio_stream_info, 'id')
1878            audio_details = {'stream_audio_bitrate': helpers.get_xml_attr(audio_stream_info, 'bitrate'),
1879                             'stream_audio_bitrate_mode': helpers.get_xml_attr(audio_stream_info, 'bitrateMode'),
1880                             'stream_audio_sample_rate': helpers.get_xml_attr(audio_stream_info, 'samplingRate'),
1881                             'stream_audio_channel_layout_': helpers.get_xml_attr(audio_stream_info, 'audioChannelLayout'),
1882                             'stream_audio_language': helpers.get_xml_attr(audio_stream_info, 'language'),
1883                             'stream_audio_language_code': helpers.get_xml_attr(audio_stream_info, 'languageCode'),
1884                             'stream_audio_decision': helpers.get_xml_attr(audio_stream_info, 'decision') or 'direct play'
1885                             }
1886        else:
1887            audio_details = {'stream_audio_bitrate': '',
1888                             'stream_audio_bitrate_mode': '',
1889                             'stream_audio_sample_rate': '',
1890                             'stream_audio_channel_layout_': '',
1891                             'stream_audio_language': '',
1892                             'stream_audio_language_code': '',
1893                             'stream_audio_decision': ''
1894                             }
1895
1896        if subtitle_stream_info:
1897            subtitle_id = helpers.get_xml_attr(subtitle_stream_info, 'id')
1898            subtitle_selected = helpers.get_xml_attr(subtitle_stream_info, 'selected')
1899            subtitle_details = {'stream_subtitle_codec': helpers.get_xml_attr(subtitle_stream_info, 'codec'),
1900                                'stream_subtitle_container': helpers.get_xml_attr(subtitle_stream_info, 'container'),
1901                                'stream_subtitle_format': helpers.get_xml_attr(subtitle_stream_info, 'format'),
1902                                'stream_subtitle_forced': int(helpers.get_xml_attr(subtitle_stream_info, 'forced') == '1'),
1903                                'stream_subtitle_location': helpers.get_xml_attr(subtitle_stream_info, 'location'),
1904                                'stream_subtitle_language': helpers.get_xml_attr(subtitle_stream_info, 'language'),
1905                                'stream_subtitle_language_code': helpers.get_xml_attr(subtitle_stream_info, 'languageCode'),
1906                                'stream_subtitle_decision': helpers.get_xml_attr(subtitle_stream_info, 'decision'),
1907                                'stream_subtitle_transient': int(helpers.get_xml_attr(subtitle_stream_info, 'transient') == '1')
1908                                }
1909        else:
1910            subtitle_selected = None
1911            subtitle_details = {'stream_subtitle_codec': '',
1912                                'stream_subtitle_container': '',
1913                                'stream_subtitle_format': '',
1914                                'stream_subtitle_forced': 0,
1915                                'stream_subtitle_location': '',
1916                                'stream_subtitle_language': '',
1917                                'stream_subtitle_language_code': '',
1918                                'stream_subtitle_decision': '',
1919                                'stream_subtitle_transient': 0
1920                                }
1921
1922        # Get the bif thumbnail
1923        indexes = helpers.get_xml_attr(stream_media_parts_info, 'indexes')
1924        view_offset = helpers.get_xml_attr(session, 'viewOffset')
1925        if indexes == 'sd':
1926            part_id = helpers.get_xml_attr(stream_media_parts_info, 'id')
1927            bif_thumb = '/library/parts/{part_id}/indexes/sd/{view_offset}'.format(part_id=part_id, view_offset=view_offset)
1928        else:
1929            bif_thumb = ''
1930
1931        stream_video_width = helpers.get_xml_attr(stream_media_info, 'width')
1932        if helpers.cast_to_int(stream_video_width) >= 3840:
1933            stream_video_resolution = '4k'
1934        else:
1935            stream_video_resolution = helpers.get_xml_attr(stream_media_info, 'videoResolution').lower().rstrip('ip')
1936
1937        stream_audio_channels = helpers.get_xml_attr(stream_media_info, 'audioChannels')
1938
1939        stream_details = {'stream_container': helpers.get_xml_attr(stream_media_info, 'container'),
1940                          'stream_bitrate': helpers.get_xml_attr(stream_media_info, 'bitrate'),
1941                          'stream_aspect_ratio': helpers.get_xml_attr(stream_media_info, 'aspectRatio'),
1942                          'stream_audio_codec': helpers.get_xml_attr(stream_media_info, 'audioCodec'),
1943                          'stream_audio_channels': stream_audio_channels,
1944                          'stream_audio_channel_layout': audio_details.get('stream_audio_channel_layout_') or common.AUDIO_CHANNELS.get(stream_audio_channels, stream_audio_channels),
1945                          'stream_video_codec': helpers.get_xml_attr(stream_media_info, 'videoCodec'),
1946                          'stream_video_framerate': helpers.get_xml_attr(stream_media_info, 'videoFrameRate'),
1947                          'stream_video_resolution': stream_video_resolution,
1948                          'stream_video_height': helpers.get_xml_attr(stream_media_info, 'height'),
1949                          'stream_video_width': helpers.get_xml_attr(stream_media_info, 'width'),
1950                          'stream_duration': helpers.get_xml_attr(stream_media_info, 'duration') or helpers.get_xml_attr(session, 'duration'),
1951                          'stream_container_decision': 'direct play' if sync_id else helpers.get_xml_attr(stream_media_parts_info, 'decision').replace('directplay', 'direct play'),
1952                          'optimized_version': int(helpers.get_xml_attr(stream_media_info, 'proxyType') == '42'),
1953                          'optimized_version_title': helpers.get_xml_attr(stream_media_info, 'title'),
1954                          'synced_version': 1 if sync_id else 0,
1955                          'live': int(helpers.get_xml_attr(session, 'live') == '1'),
1956                          'live_uuid': helpers.get_xml_attr(stream_media_info, 'uuid'),
1957                          'indexes': int(indexes == 'sd'),
1958                          'bif_thumb': bif_thumb,
1959                          'subtitles': 1 if subtitle_id and subtitle_selected else 0
1960                          }
1961
1962        # Get the source media info
1963        source_media_details = source_media_part_details = \
1964            source_video_details = source_audio_details = source_subtitle_details = {}
1965
1966        if not helpers.get_xml_attr(session, 'ratingKey').isdigit():
1967            channel_stream = 1
1968
1969            audio_channels = helpers.get_xml_attr(stream_media_info, 'audioChannels')
1970            metadata_details = {'media_type': media_type,
1971                                'section_id': helpers.get_xml_attr(session, 'librarySectionID'),
1972                                'library_name': helpers.get_xml_attr(session, 'librarySectionTitle'),
1973                                'rating_key': helpers.get_xml_attr(session, 'ratingKey'),
1974                                'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'),
1975                                'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'),
1976                                'title': helpers.get_xml_attr(session, 'title'),
1977                                'parent_title': helpers.get_xml_attr(session, 'parentTitle'),
1978                                'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'),
1979                                'original_title': helpers.get_xml_attr(session, 'originalTitle'),
1980                                'sort_title': helpers.get_xml_attr(session, 'titleSort'),
1981                                'media_index': helpers.get_xml_attr(session, 'index'),
1982                                'parent_media_index': helpers.get_xml_attr(session, 'parentIndex'),
1983                                'studio': helpers.get_xml_attr(session, 'studio'),
1984                                'content_rating': helpers.get_xml_attr(session, 'contentRating'),
1985                                'summary': helpers.get_xml_attr(session, 'summary'),
1986                                'tagline': helpers.get_xml_attr(session, 'tagline'),
1987                                'rating': helpers.get_xml_attr(session, 'rating'),
1988                                'rating_image': helpers.get_xml_attr(session, 'ratingImage'),
1989                                'audience_rating': helpers.get_xml_attr(session, 'audienceRating'),
1990                                'audience_rating_image': helpers.get_xml_attr(session, 'audienceRatingImage'),
1991                                'user_rating': helpers.get_xml_attr(session, 'userRating'),
1992                                'duration': helpers.get_xml_attr(session, 'duration'),
1993                                'year': helpers.get_xml_attr(session, 'year'),
1994                                'thumb': helpers.get_xml_attr(session, 'thumb'),
1995                                'parent_thumb': helpers.get_xml_attr(session, 'parentThumb'),
1996                                'grandparent_thumb': helpers.get_xml_attr(session, 'grandparentThumb'),
1997                                'art': helpers.get_xml_attr(session, 'art'),
1998                                'banner': helpers.get_xml_attr(session, 'banner'),
1999                                'originally_available_at': helpers.get_xml_attr(session, 'originallyAvailableAt'),
2000                                'added_at': helpers.get_xml_attr(session, 'addedAt'),
2001                                'updated_at': helpers.get_xml_attr(session, 'updatedAt'),
2002                                'last_viewed_at': helpers.get_xml_attr(session, 'lastViewedAt'),
2003                                'guid': helpers.get_xml_attr(session, 'guid'),
2004                                'directors': [],
2005                                'writers': [],
2006                                'actors': [],
2007                                'genres': [],
2008                                'labels': [],
2009                                'full_title': helpers.get_xml_attr(session, 'title'),
2010                                'container': helpers.get_xml_attr(stream_media_info, 'container') \
2011                                             or helpers.get_xml_attr(stream_media_parts_info, 'container'),
2012                                'bitrate': helpers.get_xml_attr(stream_media_info, 'bitrate'),
2013                                'height': helpers.get_xml_attr(stream_media_info, 'height'),
2014                                'width': helpers.get_xml_attr(stream_media_info, 'width'),
2015                                'aspect_ratio': helpers.get_xml_attr(stream_media_info, 'aspectRatio'),
2016                                'video_codec': helpers.get_xml_attr(stream_media_info, 'videoCodec'),
2017                                'video_resolution': helpers.get_xml_attr(stream_media_info, 'videoResolution').lower(),
2018                                'video_full_resolution': helpers.get_xml_attr(stream_media_info, 'videoResolution').lower(),
2019                                'video_framerate': helpers.get_xml_attr(stream_media_info, 'videoFrameRate'),
2020                                'video_profile': helpers.get_xml_attr(stream_media_info, 'videoProfile'),
2021                                'audio_codec': helpers.get_xml_attr(stream_media_info, 'audioCodec'),
2022                                'audio_channels': audio_channels,
2023                                'audio_channel_layout': common.AUDIO_CHANNELS.get(audio_channels, audio_channels),
2024                                'audio_profile': helpers.get_xml_attr(stream_media_info, 'audioProfile'),
2025                                'channel_icon': helpers.get_xml_attr(session, 'sourceIcon'),
2026                                'channel_title': helpers.get_xml_attr(session, 'sourceTitle'),
2027                                'extra_type': helpers.get_xml_attr(session, 'extraType'),
2028                                'sub_type': helpers.get_xml_attr(session, 'subtype')
2029                                }
2030        else:
2031            channel_stream = 0
2032
2033            media_id = helpers.get_xml_attr(stream_media_info, 'id')
2034            part_id = helpers.get_xml_attr(stream_media_parts_info, 'id')
2035
2036            if sync_id:
2037                metadata_details = self.get_metadata_details(rating_key=rating_key, sync_id=sync_id,
2038                                                             skip_cache=skip_cache, cache_key=session_key)
2039            else:
2040                metadata_details = self.get_metadata_details(rating_key=rating_key,
2041                                                             skip_cache=skip_cache, cache_key=session_key)
2042
2043            # Get the media info, fallback to first item if match id is not found
2044            source_medias = metadata_details.pop('media_info', [])
2045            source_media_details = next((m for m in source_medias if m['id'] == media_id), next((m for m in source_medias), {}))
2046            source_media_parts = source_media_details.pop('parts', [])
2047            source_media_part_details = next((p for p in source_media_parts if p['id'] == part_id), next((p for p in source_media_parts), {}))
2048            source_media_part_streams = source_media_part_details.pop('streams', [])
2049
2050            source_video_details = {'id': '',
2051                                    'type': '',
2052                                    'video_codec': '',
2053                                    'video_codec_level': '',
2054                                    'video_bitrate': '',
2055                                    'video_bit_depth': '',
2056                                    'video_chroma_subsampling': '',
2057                                    'video_color_primaries': '',
2058                                    'video_color_range': '',
2059                                    'video_color_space': '',
2060                                    'video_color_trc': '',
2061                                    'video_frame_rate': '',
2062                                    'video_ref_frames': '',
2063                                    'video_height': '',
2064                                    'video_width': '',
2065                                    'video_language': '',
2066                                    'video_language_code': '',
2067                                    'video_scan_type': '',
2068                                    'video_profile': ''
2069                                    }
2070            source_audio_details = {'id': '',
2071                                    'type': '',
2072                                    'audio_codec': '',
2073                                    'audio_bitrate': '',
2074                                    'audio_bitrate_mode': '',
2075                                    'audio_channels': '',
2076                                    'audio_channel_layout': '',
2077                                    'audio_sample_rate': '',
2078                                    'audio_language': '',
2079                                    'audio_language_code': '',
2080                                    'audio_profile': ''
2081                                    }
2082            source_subtitle_details = {'id': '',
2083                                       'type': '',
2084                                       'subtitle_codec': '',
2085                                       'subtitle_container': '',
2086                                       'subtitle_format': '',
2087                                       'subtitle_forced': 0,
2088                                       'subtitle_location': '',
2089                                       'subtitle_language': '',
2090                                       'subtitle_language_code': ''
2091                                       }
2092            if video_id:
2093                source_video_details = next((p for p in source_media_part_streams if p['id'] == video_id),
2094                                            next((p for p in source_media_part_streams if p['type'] == '1'), source_video_details))
2095            if audio_id:
2096                source_audio_details = next((p for p in source_media_part_streams if p['id'] == audio_id),
2097                                            next((p for p in source_media_part_streams if p['type'] == '2'), source_audio_details))
2098            if subtitle_id:
2099                source_subtitle_details = next((p for p in source_media_part_streams if p['id'] == subtitle_id),
2100                                               next((p for p in source_media_part_streams if p['type'] == '3'), source_subtitle_details))
2101
2102        # Override the thumb for clips
2103        if media_type == 'clip' and metadata_details.get('extra_type') and metadata_details['art']:
2104            metadata_details['thumb'] = metadata_details['art'].replace('/art', '/thumb')
2105
2106        # Overrides for live sessions
2107        if stream_details['live'] and transcode_session:
2108            stream_details['stream_container_decision'] = 'transcode'
2109            stream_details['stream_container'] = transcode_details['transcode_container']
2110
2111            video_details['stream_video_decision'] = transcode_details['video_decision']
2112            stream_details['stream_video_codec'] = transcode_details['transcode_video_codec']
2113
2114            audio_details['stream_audio_decision'] = transcode_details['audio_decision']
2115            stream_details['stream_audio_codec'] = transcode_details['transcode_audio_codec']
2116            stream_details['stream_audio_channels'] = transcode_details['transcode_audio_channels']
2117            stream_details['stream_audio_channel_layout'] = common.AUDIO_CHANNELS.get(
2118                transcode_details['transcode_audio_channels'], transcode_details['transcode_audio_channels'])
2119
2120        # Generate a combined transcode decision value
2121        if video_details['stream_video_decision'] == 'transcode' or audio_details['stream_audio_decision'] == 'transcode':
2122            transcode_decision = 'transcode'
2123        elif video_details['stream_video_decision'] == 'copy' or audio_details['stream_audio_decision'] == 'copy':
2124            transcode_decision = 'copy'
2125        else:
2126            transcode_decision = 'direct play'
2127
2128        stream_details['transcode_decision'] = transcode_decision
2129        stream_details['container_decision'] = stream_details['stream_container_decision']
2130
2131        # Override * in audio codecs
2132        if stream_details['stream_audio_codec'] == '*':
2133            stream_details['stream_audio_codec'] = source_audio_details.get('audio_codec', '')
2134        if transcode_details['transcode_audio_codec'] == '*':
2135            transcode_details['transcode_audio_codec'] = source_audio_details.get('audio_codec', '')
2136
2137        # Override * in video codecs
2138        if stream_details['stream_video_codec'] == '*':
2139            stream_details['stream_video_codec'] = source_video_details.get('video_codec', '')
2140        if transcode_details['transcode_video_codec'] == '*':
2141            transcode_details['transcode_video_codec'] = source_video_details.get('video_codec', '')
2142
2143        if media_type in ('movie', 'episode', 'clip'):
2144            # Set the full resolution by combining stream_video_resolution and stream_video_scan_type
2145            stream_details['stream_video_full_resolution'] = common.VIDEO_RESOLUTION_OVERRIDES.get(
2146                stream_details['stream_video_resolution'],
2147                stream_details['stream_video_resolution'] + (video_details['stream_video_scan_type'][:1] or 'p'))
2148
2149            if helpers.cast_to_int(source_video_details.get('video_bit_depth')) > 8 \
2150                    and source_video_details.get('video_color_space') == 'bt2020nc':
2151                stream_details['video_dynamic_range'] = 'HDR'
2152            else:
2153                stream_details['video_dynamic_range'] = 'SDR'
2154
2155            if stream_details['video_dynamic_range'] == 'HDR' \
2156                    and video_details['stream_video_decision'] != 'transcode' \
2157                    or helpers.cast_to_int(video_details['stream_video_bit_depth']) > 8 \
2158                    and video_details['stream_video_color_space'] == 'bt2020nc':
2159                stream_details['stream_video_dynamic_range'] = 'HDR'
2160            else:
2161                stream_details['stream_video_dynamic_range'] = 'SDR'
2162        else:
2163            stream_details['video_dynamic_range'] = ''
2164            stream_details['stream_video_dynamic_range'] = ''
2165
2166        # Get the quality profile
2167        if media_type in ('movie', 'episode', 'clip') and 'stream_bitrate' in stream_details:
2168            if sync_id:
2169                quality_profile = 'Original'
2170
2171                synced_item_bitrate = helpers.cast_to_int(synced_item_details['video_bitrate'])
2172                try:
2173                    synced_bitrate = max(b for b in common.VIDEO_QUALITY_PROFILES if b <= synced_item_bitrate)
2174                    synced_version_profile = common.VIDEO_QUALITY_PROFILES[synced_bitrate]
2175                except ValueError:
2176                    synced_version_profile = 'Original'
2177            elif video_details['stream_video_decision'] == 'transcode':
2178                synced_version_profile = ''
2179
2180                stream_bitrate = helpers.cast_to_int(stream_details['stream_bitrate'])
2181                source_bitrate = helpers.cast_to_int(source_media_details.get('bitrate'))
2182                try:
2183                    quailtiy_bitrate = min(
2184                        b for b in common.VIDEO_QUALITY_PROFILES if stream_bitrate <= b <= source_bitrate)
2185                    quality_profile = common.VIDEO_QUALITY_PROFILES[quailtiy_bitrate]
2186                except ValueError:
2187                    quality_profile = 'Original'
2188            else:
2189                synced_version_profile = ''
2190                quality_profile = 'Original'
2191
2192            if stream_details['optimized_version']:
2193                source_bitrate = helpers.cast_to_int(source_media_details.get('bitrate'))
2194                optimized_version_profile = '{} Mbps {}'.format(round(source_bitrate / 1000.0, 1),
2195                                                                source_media_details.get('video_full_resolution'))
2196            else:
2197                optimized_version_profile = ''
2198
2199        elif media_type == 'track' and 'stream_bitrate' in stream_details:
2200            if sync_id:
2201                quality_profile = 'Original'
2202
2203                synced_item_bitrate = helpers.cast_to_int(synced_item_details['audio_bitrate'])
2204                try:
2205                    synced_bitrate = max(b for b in common.AUDIO_QUALITY_PROFILES if b <= synced_item_bitrate)
2206                    synced_version_profile = common.AUDIO_QUALITY_PROFILES[synced_bitrate]
2207                except ValueError:
2208                    synced_version_profile = 'Original'
2209            else:
2210                synced_version_profile = ''
2211
2212                stream_bitrate = helpers.cast_to_int(stream_details['stream_bitrate'])
2213                source_bitrate = helpers.cast_to_int(source_media_details.get('bitrate'))
2214                try:
2215                    quailtiy_bitrate = min(b for b in common.AUDIO_QUALITY_PROFILES if stream_bitrate <= b <= source_bitrate)
2216                    quality_profile = common.AUDIO_QUALITY_PROFILES[quailtiy_bitrate]
2217                except ValueError:
2218                    quality_profile = 'Original'
2219
2220            optimized_version_profile = ''
2221
2222        elif media_type == 'photo':
2223            quality_profile = 'Original'
2224            synced_version_profile = ''
2225            optimized_version_profile = ''
2226
2227        else:
2228            quality_profile = 'Unknown'
2229            synced_version_profile = ''
2230            optimized_version_profile = ''
2231
2232        # Entire session output (single dict for backwards compatibility)
2233        session_output = {'session_key': session_key,
2234                          'media_type': media_type,
2235                          'view_offset': view_offset,
2236                          'progress_percent': str(helpers.get_percent(view_offset, stream_details['stream_duration'])),
2237                          'quality_profile': quality_profile,
2238                          'synced_version_profile': synced_version_profile,
2239                          'optimized_version_profile': optimized_version_profile,
2240                          'user': user_details['username'],  # Keep for backwards compatibility
2241                          'channel_stream': channel_stream
2242                          }
2243
2244        session_output.update(metadata_details)
2245        session_output.update(source_media_details)
2246        session_output.update(source_media_part_details)
2247        session_output.update(source_video_details)
2248        session_output.update(source_audio_details)
2249        session_output.update(source_subtitle_details)
2250        session_output.update(user_details)
2251        session_output.update(player_details)
2252        session_output.update(session_details)
2253        session_output.update(transcode_details)
2254        session_output.update(stream_details)
2255        session_output.update(video_details)
2256        session_output.update(audio_details)
2257        session_output.update(subtitle_details)
2258
2259        return session_output
2260
2261    def terminate_session(self, session_key='', session_id='', message=''):
2262        """
2263        Terminates a streaming session.
2264        """
2265        plex_tv = plextv.PlexTV()
2266        if not plex_tv.get_plexpass_status():
2267            msg = 'No Plex Pass subscription'
2268            logger.warn("Tautulli Pmsconnect :: Failed to terminate session: %s." % msg)
2269            return msg
2270
2271        message = message.encode('utf-8') or 'The server owner has ended the stream.'
2272
2273        ap = activity_processor.ActivityProcessor()
2274
2275        if session_key:
2276            session = ap.get_session_by_key(session_key=session_key)
2277            if session and not session_id:
2278                session_id = session['session_id']
2279
2280        elif session_id:
2281            session = ap.get_session_by_id(session_id=session_id)
2282            if session and not session_key:
2283                session_key = session['session_key']
2284
2285        else:
2286            session = session_key = session_id = None
2287
2288        if not session:
2289            msg = 'Invalid session_key (%s) or session_id (%s)' % (session_key, session_id)
2290            logger.warn("Tautulli Pmsconnect :: Failed to terminate session: %s." % msg)
2291            return msg
2292
2293        if session_id:
2294            logger.info("Tautulli Pmsconnect :: Terminating session %s (session_id %s)." % (session_key, session_id))
2295            response = self.get_sessions_terminate(session_id=session_id, reason=message)
2296            return response.ok
2297        else:
2298            msg = 'Missing session_id'
2299            logger.warn("Tautulli Pmsconnect :: Failed to terminate session: %s." % msg)
2300            return msg
2301
2302    def get_item_children(self, rating_key='', media_type=None, get_grandchildren=False):
2303        """
2304        Return processed and validated children list.
2305
2306        Output: array
2307        """
2308        default_return = {'children_count': 0,
2309                          'children_list': []
2310                          }
2311
2312        if media_type == 'playlist':
2313            children_data = self.get_playlist_items(rating_key, output_format='xml')
2314        elif media_type == 'collection':
2315            children_data = self.get_metadata_children(rating_key, collection=True, output_format='xml')
2316        elif get_grandchildren:
2317            children_data = self.get_metadata_grandchildren(rating_key, output_format='xml')
2318        elif media_type == 'artist':
2319            artist_metadata = self.get_metadata_details(rating_key)
2320            section_id = artist_metadata['section_id']
2321            children_data = self.get_metadata_children(rating_key, section_id, artist=True, output_format='xml')
2322        else:
2323            children_data = self.get_metadata_children(rating_key, output_format='xml')
2324
2325        try:
2326            xml_head = children_data.getElementsByTagName('MediaContainer')
2327        except Exception as e:
2328            logger.warn("Tautulli Pmsconnect :: Unable to parse XML for get_item_children: %s." % e)
2329            return default_return
2330
2331        children_list = []
2332
2333        for a in xml_head:
2334            if a.getAttribute('size'):
2335                if a.getAttribute('size') == '0':
2336                    logger.debug("Tautulli Pmsconnect :: No children data.")
2337                    return default_return
2338
2339            result_data = []
2340
2341            for x in a.childNodes:
2342                if x.nodeType == Node.ELEMENT_NODE and x.tagName in ('Directory', 'Video', 'Track', 'Photo'):
2343                    result_data.append(x)
2344
2345            if result_data:
2346                for m in result_data:
2347                    directors = []
2348                    writers = []
2349                    actors = []
2350                    genres = []
2351                    labels = []
2352
2353                    if m.getElementsByTagName('Director'):
2354                        for director in m.getElementsByTagName('Director'):
2355                            directors.append(helpers.get_xml_attr(director, 'tag'))
2356
2357                    if m.getElementsByTagName('Writer'):
2358                        for writer in m.getElementsByTagName('Writer'):
2359                            writers.append(helpers.get_xml_attr(writer, 'tag'))
2360
2361                    if m.getElementsByTagName('Role'):
2362                        for actor in m.getElementsByTagName('Role'):
2363                            actors.append(helpers.get_xml_attr(actor, 'tag'))
2364
2365                    if m.getElementsByTagName('Genre'):
2366                        for genre in m.getElementsByTagName('Genre'):
2367                            genres.append(helpers.get_xml_attr(genre, 'tag'))
2368
2369                    if m.getElementsByTagName('Label'):
2370                        for label in m.getElementsByTagName('Label'):
2371                            labels.append(helpers.get_xml_attr(label, 'tag'))
2372
2373                    media_type = helpers.get_xml_attr(m, 'type')
2374                    if m.nodeName == 'Directory' and media_type == 'photo':
2375                        media_type = 'photo_album'
2376
2377                    children_output = {'media_type': media_type,
2378                                       'section_id': helpers.get_xml_attr(a, 'librarySectionID'),
2379                                       'library_name': helpers.get_xml_attr(a, 'librarySectionTitle'),
2380                                       'rating_key': helpers.get_xml_attr(m, 'ratingKey'),
2381                                       'parent_rating_key': helpers.get_xml_attr(m, 'parentRatingKey'),
2382                                       'grandparent_rating_key': helpers.get_xml_attr(m, 'grandparentRatingKey'),
2383                                       'title': helpers.get_xml_attr(m, 'title'),
2384                                       'parent_title': helpers.get_xml_attr(m, 'parentTitle'),
2385                                       'grandparent_title': helpers.get_xml_attr(m, 'grandparentTitle'),
2386                                       'original_title': helpers.get_xml_attr(m, 'originalTitle'),
2387                                       'sort_title': helpers.get_xml_attr(m, 'titleSort'),
2388                                       'media_index': helpers.get_xml_attr(m, 'index'),
2389                                       'parent_media_index': helpers.get_xml_attr(m, 'parentIndex'),
2390                                       'studio': helpers.get_xml_attr(m, 'studio'),
2391                                       'content_rating': helpers.get_xml_attr(m, 'contentRating'),
2392                                       'summary': helpers.get_xml_attr(m, 'summary'),
2393                                       'tagline': helpers.get_xml_attr(m, 'tagline'),
2394                                       'rating': helpers.get_xml_attr(m, 'rating'),
2395                                       'rating_image': helpers.get_xml_attr(m, 'ratingImage'),
2396                                       'audience_rating': helpers.get_xml_attr(m, 'audienceRating'),
2397                                       'audience_rating_image': helpers.get_xml_attr(m, 'audienceRatingImage'),
2398                                       'user_rating': helpers.get_xml_attr(m, 'userRating'),
2399                                       'duration': helpers.get_xml_attr(m, 'duration'),
2400                                       'year': helpers.get_xml_attr(m, 'year'),
2401                                       'thumb': helpers.get_xml_attr(m, 'thumb'),
2402                                       'parent_thumb': helpers.get_xml_attr(m, 'parentThumb'),
2403                                       'grandparent_thumb': helpers.get_xml_attr(m, 'grandparentThumb'),
2404                                       'art': helpers.get_xml_attr(m, 'art'),
2405                                       'banner': helpers.get_xml_attr(m, 'banner'),
2406                                       'originally_available_at': helpers.get_xml_attr(m, 'originallyAvailableAt'),
2407                                       'added_at': helpers.get_xml_attr(m, 'addedAt'),
2408                                       'updated_at': helpers.get_xml_attr(m, 'updatedAt'),
2409                                       'last_viewed_at': helpers.get_xml_attr(m, 'lastViewedAt'),
2410                                       'guid': helpers.get_xml_attr(m, 'guid'),
2411                                       'directors': directors,
2412                                       'writers': writers,
2413                                       'actors': actors,
2414                                       'genres': genres,
2415                                       'labels': labels,
2416                                       'full_title': helpers.get_xml_attr(m, 'title')
2417                                       }
2418                    children_list.append(children_output)
2419
2420        output = {'children_count': helpers.cast_to_int(helpers.get_xml_attr(xml_head[0], 'size')),
2421                  'children_type': helpers.get_xml_attr(xml_head[0], 'viewGroup'),
2422                  'title': helpers.get_xml_attr(xml_head[0], 'title2'),
2423                  'children_list': children_list
2424                  }
2425
2426        return output
2427
2428    def get_item_children_related(self, rating_key=''):
2429        """
2430        Return processed and validated children list.
2431
2432        Output: array
2433        """
2434        children_data = self.get_children_list_related(rating_key, output_format='xml')
2435
2436        try:
2437            xml_head = children_data.getElementsByTagName('MediaContainer')
2438        except Exception as e:
2439            logger.warn("Tautulli Pmsconnect :: Unable to parse XML for get_item_children_related: %s." % e)
2440            return []
2441
2442        children_results_list = {'movie': [],
2443                                 'show': [],
2444                                 'season': [],
2445                                 'episode': [],
2446                                 'artist': [],
2447                                 'album': [],
2448                                 'track': [],
2449                                 }
2450
2451        for a in xml_head:
2452            section_id = helpers.get_xml_attr(a, 'librarySectionID')
2453            hubs = a.getElementsByTagName('Hub')
2454
2455            for h in hubs:
2456                size = helpers.get_xml_attr(h, 'size')
2457                media_type = helpers.get_xml_attr(h, 'type')
2458                title = helpers.get_xml_attr(h, 'title')
2459                hub_identifier = helpers.get_xml_attr(h, 'hubIdentifier')
2460
2461                if size == '0' or not hub_identifier.startswith('collection.related') or \
2462                        media_type not in children_results_list:
2463                    continue
2464
2465                result_data = []
2466
2467                if h.getElementsByTagName('Video'):
2468                    result_data = h.getElementsByTagName('Video')
2469                if h.getElementsByTagName('Directory'):
2470                    result_data = h.getElementsByTagName('Directory')
2471                if h.getElementsByTagName('Track'):
2472                    result_data = h.getElementsByTagName('Track')
2473
2474                for result in result_data:
2475                    children_output = {'section_id': section_id,
2476                                       'rating_key': helpers.get_xml_attr(result, 'ratingKey'),
2477                                       'parent_rating_key': helpers.get_xml_attr(result, 'parentRatingKey'),
2478                                       'media_index': helpers.get_xml_attr(result, 'index'),
2479                                       'title': helpers.get_xml_attr(result, 'title'),
2480                                       'parent_title': helpers.get_xml_attr(result, 'parentTitle'),
2481                                       'year': helpers.get_xml_attr(result, 'year'),
2482                                       'thumb': helpers.get_xml_attr(result, 'thumb'),
2483                                       'parent_thumb': helpers.get_xml_attr(a, 'thumb'),
2484                                       'duration': helpers.get_xml_attr(result, 'duration')
2485                                      }
2486                    children_results_list[media_type].append(children_output)
2487
2488            output = {'results_count': sum(len(v) for k, v in children_results_list.items()),
2489                      'results_list': children_results_list,
2490                      }
2491
2492            return output
2493
2494    def get_servers_info(self):
2495        """
2496        Return the list of local servers.
2497
2498        Output: array
2499        """
2500        recent = self.get_server_list(output_format='xml')
2501
2502        try:
2503            xml_head = recent.getElementsByTagName('Server')
2504        except Exception as e:
2505            logger.warn("Tautulli Pmsconnect :: Unable to parse XML for get_server_list: %s." % e)
2506            return []
2507
2508        server_info = []
2509        for a in xml_head:
2510            output = {"name": helpers.get_xml_attr(a, 'name'),
2511                      "machine_identifier": helpers.get_xml_attr(a, 'machineIdentifier'),
2512                      "host": helpers.get_xml_attr(a, 'host'),
2513                      "port": helpers.get_xml_attr(a, 'port'),
2514                      "version": helpers.get_xml_attr(a, 'version')
2515                      }
2516
2517            server_info.append(output)
2518
2519        return server_info
2520
2521    def get_server_identity(self):
2522        """
2523        Return the local machine identity.
2524
2525        Output: dict
2526        """
2527        identity = self.get_local_server_identity(output_format='xml')
2528
2529        try:
2530            xml_head = identity.getElementsByTagName('MediaContainer')
2531        except Exception as e:
2532            logger.warn("Tautulli Pmsconnect :: Unable to parse XML for get_local_server_identity: %s." % e)
2533            return {}
2534
2535        server_identity = {}
2536        for a in xml_head:
2537            server_identity = {"machine_identifier": helpers.get_xml_attr(a, 'machineIdentifier'),
2538                               "version": helpers.get_xml_attr(a, 'version')
2539                               }
2540
2541        return server_identity
2542
2543    def get_server_pref(self, pref=None):
2544        """
2545        Return a specified server preference.
2546
2547        Parameters required:    pref { name of preference }
2548
2549        Output: string
2550        """
2551        if pref:
2552            prefs = self.get_server_prefs(output_format='xml')
2553
2554            try:
2555                xml_head = prefs.getElementsByTagName('Setting')
2556            except Exception as e:
2557                logger.warn("Tautulli Pmsconnect :: Unable to parse XML for get_local_server_name: %s." % e)
2558                return ''
2559
2560            pref_value = 'None'
2561            for a in xml_head:
2562                if helpers.get_xml_attr(a, 'id') == pref:
2563                    pref_value = helpers.get_xml_attr(a, 'value')
2564                    break
2565
2566            return pref_value
2567        else:
2568            logger.debug("Tautulli Pmsconnect :: Server preferences queried but no parameter received.")
2569            return None
2570
2571    def get_server_children(self):
2572        """
2573        Return processed and validated server libraries list.
2574
2575        Output: array
2576        """
2577        libraries_data = self.get_libraries_list(output_format='xml')
2578
2579        try:
2580            xml_head = libraries_data.getElementsByTagName('MediaContainer')
2581        except Exception as e:
2582            logger.warn("Tautulli Pmsconnect :: Unable to parse XML for get_libraries_list: %s." % e)
2583            return []
2584
2585        libraries_list = []
2586
2587        for a in xml_head:
2588            if a.getAttribute('size'):
2589                if a.getAttribute('size') == '0':
2590                    logger.debug("Tautulli Pmsconnect :: No libraries data.")
2591                    libraries_list = {'libraries_count': '0',
2592                                      'libraries_list': []
2593                                      }
2594                    return libraries_list
2595
2596            if a.getElementsByTagName('Directory'):
2597                result_data = a.getElementsByTagName('Directory')
2598                for result in result_data:
2599                    libraries_output = {'section_id': helpers.get_xml_attr(result, 'key'),
2600                                        'section_type': helpers.get_xml_attr(result, 'type'),
2601                                        'section_name': helpers.get_xml_attr(result, 'title'),
2602                                        'agent': helpers.get_xml_attr(result, 'agent'),
2603                                        'thumb': helpers.get_xml_attr(result, 'thumb'),
2604                                        'art': helpers.get_xml_attr(result, 'art')
2605                                        }
2606                    libraries_list.append(libraries_output)
2607
2608        output = {'libraries_count': helpers.get_xml_attr(xml_head[0], 'size'),
2609                  'title': helpers.get_xml_attr(xml_head[0], 'title1'),
2610                  'libraries_list': libraries_list
2611                  }
2612
2613        return output
2614
2615    def get_library_children_details(self, section_id='', section_type='', list_type='all', count='',
2616                                     rating_key='', label_key='', get_media_info=False):
2617        """
2618        Return processed and validated server library items list.
2619
2620        Parameters required:    section_type { movie, show, episode, artist }
2621                                section_id { unique library key }
2622
2623        Output: array
2624        """
2625
2626        if section_type == 'movie':
2627            sort_type = '&type=1'
2628        elif section_type == 'show':
2629            sort_type = '&type=2'
2630        elif section_type == 'season':
2631            sort_type = '&type=3'
2632        elif section_type == 'episode':
2633            sort_type = '&type=4'
2634        elif section_type == 'artist':
2635            sort_type = '&type=8'
2636        elif section_type == 'album':
2637            sort_type = '&type=9'
2638        elif section_type == 'track':
2639            sort_type = '&type=10'
2640        elif section_type == 'photo':
2641            sort_type = ''
2642        elif section_type == 'photo_album':
2643            sort_type = '&type=14'
2644        elif section_type == 'picture':
2645            sort_type = '&type=13&clusterZoomLevel=1'
2646        elif section_type == 'clip':
2647            sort_type = '&type=12&clusterZoomLevel=1'
2648        else:
2649            sort_type = ''
2650
2651        if str(rating_key).isdigit():
2652            _artist = section_type == 'album'
2653            library_data = self.get_metadata_children(str(rating_key), str(section_id), artist=_artist, output_format='xml')
2654        elif str(section_id).isdigit():
2655            library_data = self.get_library_list(str(section_id), list_type, count, sort_type, label_key, output_format='xml')
2656        else:
2657            logger.warn("Tautulli Pmsconnect :: get_library_children called by invalid section_id or rating_key provided.")
2658            return []
2659
2660        try:
2661            xml_head = library_data.getElementsByTagName('MediaContainer')
2662        except Exception as e:
2663            logger.warn("Tautulli Pmsconnect :: Unable to parse XML for get_library_children_details: %s." % e)
2664            return []
2665
2666        children_list = []
2667
2668        for a in xml_head:
2669            if a.getAttribute('size'):
2670                if a.getAttribute('size') == '0':
2671                    logger.debug("Tautulli Pmsconnect :: No library data.")
2672                    children_list = {'library_count': '0',
2673                                     'children_list': []
2674                                     }
2675                    return children_list
2676
2677            if rating_key:
2678                library_count = helpers.get_xml_attr(xml_head[0], 'size')
2679            else:
2680                library_count = helpers.get_xml_attr(xml_head[0], 'totalSize')
2681
2682            # Get show/season info from xml_head
2683
2684            item_main = []
2685            if a.getElementsByTagName('Directory'):
2686                dir_main = a.getElementsByTagName('Directory')
2687                item_main += [d for d in dir_main if helpers.get_xml_attr(d, 'ratingKey')]
2688            if a.getElementsByTagName('Video'):
2689                item_main += a.getElementsByTagName('Video')
2690            if a.getElementsByTagName('Track'):
2691                item_main += a.getElementsByTagName('Track')
2692            if a.getElementsByTagName('Photo'):
2693                item_main += a.getElementsByTagName('Photo')
2694
2695            for item in item_main:
2696                media_type = helpers.get_xml_attr(item, 'type')
2697                if item.nodeName == 'Directory' and media_type == 'photo':
2698                    media_type = 'photo_album'
2699
2700                item_info = {'section_id': helpers.get_xml_attr(a, 'librarySectionID'),
2701                             'media_type': media_type,
2702                             'rating_key': helpers.get_xml_attr(item, 'ratingKey'),
2703                             'parent_rating_key': helpers.get_xml_attr(item, 'parentRatingKey'),
2704                             'grandparent_rating_key': helpers.get_xml_attr(item, 'grandparentRatingKey'),
2705                             'title': helpers.get_xml_attr(item, 'title'),
2706                             'parent_title': helpers.get_xml_attr(item, 'parentTitle'),
2707                             'grandparent_title': helpers.get_xml_attr(item, 'grandparentTitle'),
2708                             'original_title': helpers.get_xml_attr(item, 'originalTitle'),
2709                             'sort_title': helpers.get_xml_attr(item, 'titleSort'),
2710                             'media_index': helpers.get_xml_attr(item, 'index'),
2711                             'parent_media_index': helpers.get_xml_attr(item, 'parentIndex'),
2712                             'year': helpers.get_xml_attr(item, 'year'),
2713                             'thumb': helpers.get_xml_attr(item, 'thumb'),
2714                             'parent_thumb': helpers.get_xml_attr(item, 'thumb'),
2715                             'grandparent_thumb': helpers.get_xml_attr(item, 'grandparentThumb'),
2716                             'added_at': helpers.get_xml_attr(item, 'addedAt')
2717                             }
2718
2719                if get_media_info:
2720                    item_media = item.getElementsByTagName('Media')
2721                    for media in item_media:
2722                        media_info = {'container': helpers.get_xml_attr(media, 'container'),
2723                                      'bitrate': helpers.get_xml_attr(media, 'bitrate'),
2724                                      'video_codec': helpers.get_xml_attr(media, 'videoCodec'),
2725                                      'video_resolution': helpers.get_xml_attr(media, 'videoResolution').lower(),
2726                                      'video_framerate': helpers.get_xml_attr(media, 'videoFrameRate'),
2727                                      'audio_codec': helpers.get_xml_attr(media, 'audioCodec'),
2728                                      'audio_channels': helpers.get_xml_attr(media, 'audioChannels'),
2729                                      'file': helpers.get_xml_attr(media.getElementsByTagName('Part')[0], 'file'),
2730                                      'file_size': helpers.get_xml_attr(media.getElementsByTagName('Part')[0], 'size'),
2731                                      }
2732                        item_info.update(media_info)
2733
2734                children_list.append(item_info)
2735
2736        output = {'library_count': library_count,
2737                  'children_list': children_list
2738                  }
2739
2740        return output
2741
2742    def get_library_details(self):
2743        """
2744        Return processed and validated library statistics.
2745
2746        Output: array
2747        """
2748        server_libraries = self.get_server_children()
2749
2750        server_library_stats = []
2751
2752        if server_libraries and server_libraries['libraries_count'] != '0':
2753            libraries_list = server_libraries['libraries_list']
2754
2755            for library in libraries_list:
2756                section_type = library['section_type']
2757                section_id = library['section_id']
2758                children_list = self.get_library_children_details(section_id=section_id, section_type=section_type, count='1')
2759
2760                if children_list:
2761                    library_stats = {'section_id': section_id,
2762                                     'section_name': library['section_name'],
2763                                     'section_type': section_type,
2764                                     'agent': library['agent'],
2765                                     'thumb': library['thumb'],
2766                                     'art': library['art'],
2767                                     'count': children_list['library_count'],
2768                                     'is_active': 1
2769                                     }
2770
2771                    if section_type == 'show':
2772                        parent_list = self.get_library_children_details(section_id=section_id, section_type='season', count='1')
2773                        if parent_list:
2774                            parent_stats = {'parent_count': parent_list['library_count']}
2775                            library_stats.update(parent_stats)
2776
2777                        child_list = self.get_library_children_details(section_id=section_id, section_type='episode', count='1')
2778                        if child_list:
2779                            child_stats = {'child_count': child_list['library_count']}
2780                            library_stats.update(child_stats)
2781
2782                    if section_type == 'artist':
2783                        parent_list = self.get_library_children_details(section_id=section_id, section_type='album', count='1')
2784                        if parent_list:
2785                            parent_stats = {'parent_count': parent_list['library_count']}
2786                            library_stats.update(parent_stats)
2787
2788                        child_list = self.get_library_children_details(section_id=section_id, section_type='track', count='1')
2789                        if child_list:
2790                            child_stats = {'child_count': child_list['library_count']}
2791                            library_stats.update(child_stats)
2792
2793                    if section_type == 'photo':
2794                        parent_list = self.get_library_children_details(section_id=section_id, section_type='picture', count='1')
2795                        if parent_list:
2796                            parent_stats = {'parent_count': parent_list['library_count']}
2797                            library_stats.update(parent_stats)
2798
2799                        child_list = self.get_library_children_details(section_id=section_id, section_type='clip', count='1')
2800                        if child_list:
2801                            child_stats = {'child_count': child_list['library_count']}
2802                            library_stats.update(child_stats)
2803
2804                    server_library_stats.append(library_stats)
2805
2806        return server_library_stats
2807
2808    def get_library_label_details(self, section_id=''):
2809        labels_data = self.get_library_labels(section_id=str(section_id), output_format='xml')
2810
2811        try:
2812            xml_head = labels_data.getElementsByTagName('MediaContainer')
2813        except Exception as e:
2814            logger.warn("Tautulli Pmsconnect :: Unable to parse XML for get_library_label_details: %s." % e)
2815            return None
2816
2817        labels_list = []
2818
2819        for a in xml_head:
2820            if a.getAttribute('size'):
2821                if a.getAttribute('size') == '0':
2822                    logger.debug("Tautulli Pmsconnect :: No labels data.")
2823                    return labels_list
2824
2825            if a.getElementsByTagName('Directory'):
2826                labels_main = a.getElementsByTagName('Directory')
2827                for item in labels_main:
2828                    label = {'label_key': helpers.get_xml_attr(item, 'key'),
2829                             'label_title': helpers.get_xml_attr(item, 'title')
2830                             }
2831                    labels_list.append(label)
2832
2833        return labels_list
2834
2835    def get_image(self, img=None, width=1000, height=1500, opacity=None, background=None, blur=None,
2836                  img_format='png', clip=False, refresh=False, **kwargs):
2837        """
2838        Return image data as array.
2839        Array contains the image content type and image binary
2840
2841        Parameters required:    img { Plex image location }
2842        Optional parameters:    width { the image width }
2843                                height { the image height }
2844                                opacity { the image opacity 0-100 }
2845                                background { the image background HEX }
2846                                blur { the image blur 0-100 }
2847        Output: array
2848        """
2849
2850        width = width or 1000
2851        height = height or 1500
2852
2853        if img:
2854            web_img = img.startswith('http')
2855            resource_img = img.startswith('/:/resources')
2856
2857            if 'collection' in img and 'composite' in img:
2858                img = img.replace('composite', 'thumb')
2859
2860            if refresh and not web_img and not resource_img:
2861                img_split = img.split('/')
2862                if img_split[-1].isdigit():
2863                    img = '/'.join(img_split[:-1])
2864                img = '{}/{}'.format(img.rstrip('/'), helpers.timestamp())
2865
2866            if web_img:
2867                params = {'url': '%s' % img}
2868            elif clip:
2869                params = {'url': '%s&%s' % (img, urlencode({'X-Plex-Token': self.token}))}
2870            else:
2871                params = {'url': 'http://127.0.0.1:32400%s?%s' % (img, urlencode({'X-Plex-Token': self.token}))}
2872
2873            params['width'] = width
2874            params['height'] = height
2875            params['format'] = img_format
2876
2877            if opacity:
2878                params['opacity'] = opacity
2879            if background:
2880                params['background'] = background
2881            if blur:
2882                params['blur'] = blur
2883
2884            uri = '/photo/:/transcode?%s' % urlencode(params)
2885            result = self.request_handler.make_request(uri=uri,
2886                                                       request_type='GET',
2887                                                       return_type=True)
2888
2889            if result is None:
2890                return
2891            else:
2892                return result[0], result[1]
2893
2894        else:
2895            logger.error("Tautulli Pmsconnect :: Image proxy queried but no input received.")
2896
2897    def get_search_results(self, query='', limit=''):
2898        """
2899        Return processed list of search results.
2900
2901        Output: array
2902        """
2903        search_results = self.get_search(query=query, limit=limit, output_format='xml')
2904
2905        try:
2906            xml_head = search_results.getElementsByTagName('MediaContainer')
2907        except Exception as e:
2908            logger.warn("Tautulli Pmsconnect :: Unable to parse XML for get_search_result: %s." % e)
2909            return []
2910
2911        search_results_list = {'movie': [],
2912                               'show': [],
2913                               'season': [],
2914                               'episode': [],
2915                               'artist': [],
2916                               'album': [],
2917                               'track': [],
2918                               'collection': []
2919                               }
2920
2921        for a in xml_head:
2922            hubs = a.getElementsByTagName('Hub')
2923
2924            for h in hubs:
2925                if helpers.get_xml_attr(h, 'size') == '0' or \
2926                    helpers.get_xml_attr(h, 'type') not in search_results_list:
2927                    continue
2928
2929                if h.getElementsByTagName('Video'):
2930                    result_data = h.getElementsByTagName('Video')
2931                    for result in result_data:
2932                        rating_key = helpers.get_xml_attr(result, 'ratingKey')
2933                        metadata = self.get_metadata_details(rating_key=rating_key)
2934                        search_results_list[metadata['media_type']].append(metadata)
2935
2936                if h.getElementsByTagName('Directory'):
2937                    result_data = h.getElementsByTagName('Directory')
2938                    for result in result_data:
2939                        rating_key = helpers.get_xml_attr(result, 'ratingKey')
2940                        metadata = self.get_metadata_details(rating_key=rating_key)
2941                        search_results_list[metadata['media_type']].append(metadata)
2942
2943                        if metadata['media_type'] == 'show':
2944                            show_seasons = self.get_item_children(rating_key=metadata['rating_key'])
2945                            if show_seasons['children_count'] != 0:
2946                                for season in show_seasons['children_list']:
2947                                    if season['rating_key']:
2948                                        metadata = self.get_metadata_details(rating_key=season['rating_key'])
2949                                        search_results_list['season'].append(metadata)
2950
2951                if h.getElementsByTagName('Track'):
2952                    result_data = h.getElementsByTagName('Track')
2953                    for result in result_data:
2954                        rating_key = helpers.get_xml_attr(result, 'ratingKey')
2955                        metadata = self.get_metadata_details(rating_key=rating_key)
2956                        search_results_list[metadata['media_type']].append(metadata)
2957
2958        output = {'results_count': sum(len(s) for s in search_results_list.values()),
2959                  'results_list': search_results_list
2960                  }
2961
2962        return output
2963
2964    def get_rating_keys_list(self, rating_key='', media_type=''):
2965        """
2966        Return processed list of grandparent/parent/child rating keys.
2967
2968        Output: array
2969        """
2970
2971        if media_type == 'movie':
2972            key_list = {0: {'rating_key': int(rating_key)}}
2973            return key_list
2974
2975        if media_type == 'artist' or media_type == 'album' or media_type == 'track':
2976            match_type = 'title'
2977        else:
2978            match_type = 'index'
2979
2980        section_id = None
2981        library_name = None
2982
2983        # get grandparent rating key
2984        if media_type == 'season' or media_type == 'album':
2985            try:
2986                metadata = self.get_metadata_details(rating_key=rating_key)
2987                rating_key = metadata['parent_rating_key']
2988                section_id = metadata['section_id']
2989                library_name = metadata['library_name']
2990            except Exception as e:
2991                logger.warn("Tautulli Pmsconnect :: Unable to get parent_rating_key for get_rating_keys_list: %s." % e)
2992                return {}
2993
2994        elif media_type == 'episode' or media_type == 'track':
2995            try:
2996                metadata = self.get_metadata_details(rating_key=rating_key)
2997                rating_key = metadata['grandparent_rating_key']
2998                section_id = metadata['section_id']
2999                library_name = metadata['library_name']
3000            except Exception as e:
3001                logger.warn("Tautulli Pmsconnect :: Unable to get grandparent_rating_key for get_rating_keys_list: %s." % e)
3002                return {}
3003
3004        elif media_type == 'artist':
3005            try:
3006                metadata = self.get_metadata_details(rating_key=rating_key)
3007                section_id = metadata['section_id']
3008                library_name = metadata['library_name']
3009            except Exception as e:
3010                logger.warn("Tautulli Pmsconnect :: Unable to get grandparent_rating_key for get_rating_keys_list: %s." % e)
3011                return {}
3012
3013
3014        # get parent_rating_keys
3015        _artist = media_type in ('artist', 'album', 'track')
3016        metadata = self.get_metadata_children(str(rating_key), str(section_id), artist=_artist, output_format='xml')
3017
3018        try:
3019            xml_head = metadata.getElementsByTagName('MediaContainer')
3020        except Exception as e:
3021            logger.warn("Tautulli Pmsconnect :: Unable to parse XML for get_rating_keys_list: %s." % e)
3022            return {}
3023
3024        for a in xml_head:
3025            if a.getAttribute('size'):
3026                if a.getAttribute('size') == '0':
3027                    return {}
3028
3029            title = helpers.get_xml_attr(a, 'title2')
3030
3031            if a.getElementsByTagName('Directory'):
3032                parents_metadata = a.getElementsByTagName('Directory')
3033            else:
3034                parents_metadata = []
3035
3036            parents = {}
3037            for item in parents_metadata:
3038                parent_rating_key = helpers.get_xml_attr(item, 'ratingKey')
3039                parent_index = helpers.get_xml_attr(item, 'index')
3040                parent_title = helpers.get_xml_attr(item, 'title')
3041
3042                if parent_rating_key:
3043                    # get rating_keys
3044                    metadata = self.get_metadata_children(str(parent_rating_key), output_format='xml')
3045
3046                    try:
3047                        xml_head = metadata.getElementsByTagName('MediaContainer')
3048                    except Exception as e:
3049                        logger.warn("Tautulli Pmsconnect :: Unable to parse XML for get_rating_keys_list: %s." % e)
3050                        return {}
3051
3052                    for a in xml_head:
3053                        if a.getAttribute('size'):
3054                            if a.getAttribute('size') == '0':
3055                                return {}
3056
3057                        if a.getElementsByTagName('Video'):
3058                            children_metadata = a.getElementsByTagName('Video')
3059                        elif a.getElementsByTagName('Track'):
3060                            children_metadata = a.getElementsByTagName('Track')
3061                        else:
3062                            children_metadata = []
3063
3064                        children = {}
3065                        for item in children_metadata:
3066                            child_rating_key = helpers.get_xml_attr(item, 'ratingKey')
3067                            child_index = helpers.get_xml_attr(item, 'index')
3068                            child_title = helpers.get_xml_attr(item, 'title')
3069
3070                            if child_rating_key:
3071                                key = int(child_index) if child_index else child_title
3072                                children.update({key: {'rating_key': int(child_rating_key)}})
3073
3074                    key = int(parent_index) if match_type == 'index' else str(parent_title).lower()
3075                    parents.update({key:
3076                                    {'rating_key': int(parent_rating_key),
3077                                     'children': children}
3078                                    })
3079
3080        key = 0 if match_type == 'index' else str(title).lower()
3081        key_list = {key: {'rating_key': int(rating_key),
3082                          'children': parents},
3083                    'section_id': section_id,
3084                    'library_name': library_name
3085                    }
3086
3087        return key_list
3088
3089    def get_server_response(self):
3090        account_data = self.get_account(output_format='xml')
3091
3092        try:
3093            xml_head = account_data.getElementsByTagName('MyPlex')
3094        except Exception as e:
3095            logger.warn("Tautulli Pmsconnect :: Unable to parse XML for get_server_response: %s." % e)
3096            return None
3097
3098        server_response = {}
3099
3100        for a in xml_head:
3101            server_response = {'mapping_state': helpers.get_xml_attr(a, 'mappingState'),
3102                               'mapping_error': helpers.get_xml_attr(a, 'mappingError'),
3103                               'sign_in_state': helpers.get_xml_attr(a, 'signInState'),
3104                               'public_address': helpers.get_xml_attr(a, 'publicAddress'),
3105                               'public_port': helpers.get_xml_attr(a, 'publicPort'),
3106                               'private_address': helpers.get_xml_attr(a, 'privateAddress'),
3107                               'private_port': helpers.get_xml_attr(a, 'privatePort')
3108                               }
3109
3110            if server_response['mapping_state'] == 'unknown':
3111                server_response['reason'] = 'Plex remote access port mapping unknown'
3112            elif server_response['mapping_state'] not in ('mapped', 'waiting'):
3113                server_response['reason'] = 'Plex remote access port not mapped'
3114            elif server_response['mapping_error'] == 'unreachable':
3115                server_response['reason'] = 'Plex remote access port mapped, ' \
3116                                            'but the port is unreachable from Plex.tv'
3117            elif server_response['mapping_error'] == 'publisherror':
3118                server_response['reason'] = 'Plex remote access port mapped, ' \
3119                                            'but failed to publish the port to Plex.tv'
3120            else:
3121                server_response['reason'] = ''
3122
3123        return server_response
3124
3125    def get_update_staus(self):
3126        # Refresh the Plex updater status first
3127        self.put_updater()
3128        updater_status = self.get_updater(output_format='xml')
3129
3130        try:
3131            xml_head = updater_status.getElementsByTagName('MediaContainer')
3132        except Exception as e:
3133            logger.warn("Tautulli Pmsconnect :: Unable to parse XML for get_update_staus: %s." % e)
3134
3135            # Catch the malformed XML on certain PMX version.
3136            # XML parser helper returns empty list if there is an error parsing XML
3137            if updater_status == []:
3138                logger.warn("Plex API updater XML is broken on the current PMS version. Please update your PMS manually.")
3139                logger.info("Tautulli is unable to check for Plex updates. Disabling check for Plex updates.")
3140
3141                # Disable check for Plex updates
3142                plexpy.CONFIG.MONITOR_PMS_UPDATES = 0
3143                plexpy.initialize_scheduler()
3144                plexpy.CONFIG.write()
3145
3146            return {}
3147
3148        updater_info = {}
3149        for a in xml_head:
3150            if a.getElementsByTagName('Release'):
3151                release = a.getElementsByTagName('Release')
3152                for item in release:
3153                    updater_info = {'can_install': helpers.get_xml_attr(a, 'canInstall'),
3154                                    'download_url': helpers.get_xml_attr(a, 'downloadURL'),
3155                                    'version': helpers.get_xml_attr(item, 'version'),
3156                                    'state': helpers.get_xml_attr(item, 'state'),
3157                                    'changelog': helpers.get_xml_attr(item, 'fixed')
3158                                    }
3159
3160        return updater_info
3161
3162    def set_server_version(self):
3163        identity = self.get_server_identity()
3164        version = identity.get('version', plexpy.CONFIG.PMS_VERSION)
3165
3166        plexpy.CONFIG.__setattr__('PMS_VERSION', version)
3167        plexpy.CONFIG.write()
3168
3169    def get_server_update_channel(self):
3170        if plexpy.CONFIG.PMS_UPDATE_CHANNEL == 'plex':
3171            update_channel_value = self.get_server_pref('ButlerUpdateChannel')
3172
3173            if update_channel_value == '8':
3174                return 'beta'
3175            else:
3176                return 'public'
3177
3178        return plexpy.CONFIG.PMS_UPDATE_CHANNEL
3179