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