1
+ from abc import ABC , abstractmethod
2
+ from enum import Enum
1
3
import logging
2
4
import urllib .request , urllib .parse , urllib .error
3
5
import urllib .parse
16
18
17
19
# http://192.168.0.12:32400/photo/:/transcode?url=http%3A%2F%2F127.0.0.1%3A32400%2F%3A%2Fresources%2Fvideo.png&width=75&height=75
18
20
19
- class MediaItem (object ):
20
- pass
21
+ class MediaType (Enum ):
22
+ VIDEO = "video"
23
+ MUSIC = "music"
21
24
22
- class Video (object ):
25
+ class MediaItem (ABC ):
26
+ def __init__ (self , media_type , node , parent ):
27
+ self .media_type = media_type
28
+ self .node = node
29
+ self .parent = parent
30
+
31
+ @abstractmethod
32
+ def get_playback_url (self , offset = 0 ):
33
+ pass
34
+
35
+ @abstractmethod
36
+ def is_multipart (self ):
37
+ pass
38
+
39
+ def get_duration (self ):
40
+ return self .node .get ("duration" )
41
+
42
+ def get_rating_key (self ):
43
+ return self .node .get ("ratingKey" )
44
+
45
+ def get_attr (self , attr , default = None ):
46
+ return self .node .get (attr , default )
47
+
48
+ def get_proper_title (self ):
49
+ if not hasattr (self , "_title" ):
50
+ setattr (self , "_title" , self .node .get ('title' ))
51
+ return getattr (self , "_title" )
52
+
53
+ def set_played (self , watched = True ):
54
+ rating_key = self .get_rating_key ()
55
+
56
+ if rating_key is None :
57
+ log .error ("No 'ratingKey' could be found in XML from URL '%s'" % (sanitize_msg (self .parent .path .geturl ())))
58
+ return False
59
+
60
+ if watched :
61
+ act = '/:/scrobble'
62
+ else :
63
+ act = '/:/unscrobble'
64
+
65
+ url = urllib .parse .urljoin (self .parent .server_url , act )
66
+ data = {
67
+ "key" : rating_key ,
68
+ "identifier" : "com.plexapp.plugins.library"
69
+ }
70
+
71
+ self .played = safe_urlopen (url , data )
72
+ return self .played
73
+
74
+ class Video (MediaItem ):
23
75
def __init__ (self , node , parent , media = 0 , part = 0 ):
24
- self .parent = parent
25
- self .node = node
76
+ super ().__init__ (MediaType .VIDEO , node , parent )
26
77
self .played = False
27
78
self ._media = 0
28
79
self ._media_node = None
@@ -388,15 +439,6 @@ def get_external_sub(self, id):
388
439
url = "/library/streams/{0}" .format (id )
389
440
return get_plex_url (urllib .parse .urljoin (self .parent .server_url , url ))
390
441
391
- def get_duration (self ):
392
- return self .node .get ("duration" )
393
-
394
- def get_rating_key (self ):
395
- return self .node .get ("ratingKey" )
396
-
397
- def get_video_attr (self , attr , default = None ):
398
- return self .node .get (attr , default )
399
-
400
442
def update_position (self , ms ):
401
443
"""
402
444
Sets the state of the media as "playing" with a progress of ``ms`` milliseconds.
@@ -417,26 +459,72 @@ def update_position(self, ms):
417
459
418
460
return safe_urlopen (url , data )
419
461
420
- def set_played (self , watched = True ):
421
- rating_key = self .get_rating_key ()
462
+ class Track (MediaItem ):
463
+ def __init__ (self , node , parent , media = 0 , part = 0 ):
464
+ super ().__init__ (MediaType .MUSIC , node , parent )
465
+ self .played = False
466
+ self ._media = 0
467
+ self ._media_node = None
468
+ self ._part = 0
469
+ self ._part_node = None
470
+ self .is_transcode = False
471
+ self .trs_aid = None
472
+ self .trs_sid = None
473
+ self .trs_ovr = None
474
+ self .audio_seq = {}
475
+ self .audio_uid = {}
422
476
423
- if rating_key is None :
424
- log .error ("No 'ratingKey' could be found in XML from URL '%s'" % (sanitize_msg (self .parent .path .geturl ())))
477
+ if media :
478
+ self .select_media (media , part )
479
+
480
+ if not self ._media_node :
481
+ self .select_best_media (part )
482
+
483
+ self .map_streams ()
484
+
485
+ def map_streams (self ):
486
+ if not self ._part_node :
487
+ return
488
+
489
+ for index , stream in enumerate (self ._part_node .findall ("./Stream[@streamType='2']" ) or []):
490
+ self .audio_uid [index + 1 ] = stream .attrib ["id" ]
491
+ self .audio_seq [stream .attrib ["id" ]] = index + 1
492
+
493
+ def select_best_media (self , part = 0 ):
494
+ # Audio is much easier :)
495
+ self .select_media (0 )
496
+
497
+ def select_media (self , media , part = 0 ):
498
+ node = self .node .find ('./Media[%s]' % (media + 1 ))
499
+ if node :
500
+ self ._media = media
501
+ self ._media_node = node
502
+ if self .select_part (part ):
503
+ log .debug ("Track::select_media selected media %d" % media )
504
+ return True
505
+
506
+ log .error ("Track::select_media error selecting media %d" % media )
507
+ return False
508
+
509
+ def select_part (self , part ):
510
+ if self ._media_node is None :
425
511
return False
426
512
427
- if watched :
428
- act = '/:/scrobble'
429
- else :
430
- act = '/:/unscrobble'
513
+ node = self ._media_node .find ('./Part[%s]' % (part + 1 ))
514
+ if node :
515
+ self ._part = part
516
+ self ._part_node = node
517
+ return True
431
518
432
- url = urllib .parse .urljoin (self .parent .server_url , act )
433
- data = {
434
- "key" : rating_key ,
435
- "identifier" : "com.plexapp.plugins.library"
436
- }
519
+ log .error ("Track::select_media error selecting part %s" % part )
520
+ return False
437
521
438
- self .played = safe_urlopen (url , data )
439
- return self .played
522
+ def get_playback_url (self , offset = 0 ):
523
+ url = urllib .parse .urljoin (self .parent .server_url , self ._part_node .get ("key" , "" ))
524
+ return get_plex_url (url )
525
+
526
+ def is_multipart (self ):
527
+ return False
440
528
441
529
class XMLCollection (object ):
442
530
def __init__ (self , url ):
@@ -455,7 +543,7 @@ def __str__(self):
455
543
return self .path .path
456
544
457
545
class Media (XMLCollection ):
458
- def __init__ (self , url , series = None , seq = None , play_queue = None , play_queue_xml = None ):
546
+ def __init__ (self , url , series = None , seq = None , play_queue = None , play_queue_xml = None , media_type = MediaType . VIDEO ):
459
547
# Include Markers
460
548
if "?" in url :
461
549
sep = "&"
@@ -464,8 +552,15 @@ def __init__(self, url, series=None, seq=None, play_queue=None, play_queue_xml=N
464
552
url = url + sep + "includeMarkers=1"
465
553
466
554
XMLCollection .__init__ (self , url )
467
- self .video = self .tree .find ('./Video' )
468
- self .is_tv = self .video .get ("type" ) == "episode"
555
+
556
+ self .media_type = media_type
557
+
558
+ if self .media_type == MediaType .VIDEO :
559
+ self .media_item = self .tree .find ('./Video' )
560
+ self .is_tv = self .media_item .get ("type" ) == "episode"
561
+ elif self .media_type == MediaType .MUSIC :
562
+ self .media_item = self .tree .find ('./Track' )
563
+
469
564
self .seq = None
470
565
self .has_next = False
471
566
self .has_prev = False
@@ -487,11 +582,11 @@ def __init__(self, url, series=None, seq=None, play_queue=None, play_queue_xml=N
487
582
else :
488
583
self .series = []
489
584
specials = []
490
- series_xml = XMLCollection (self .get_path (self .video .get ("grandparentKey" )+ "/allLeaves" ))
585
+ series_xml = XMLCollection (self .get_path (self .media_item .get ("grandparentKey" )+ "/allLeaves" ))
491
586
videos = series_xml .tree .findall ('./Video' )
492
587
493
588
# This part is kind of nasty, so we only try to do it once per cast session.
494
- key = self .video .get ('key' )
589
+ key = self .media_item .get ('key' )
495
590
is_special = False
496
591
for i , video in enumerate (videos ):
497
592
if video .get ('key' ) == key :
@@ -511,18 +606,32 @@ def __init__(self, url, series=None, seq=None, play_queue=None, play_queue_xml=N
511
606
512
607
def upd_play_queue (self ):
513
608
if self .play_queue :
514
- self .play_queue_xml = XMLCollection (self .get_path (self .play_queue ))
515
- videos = self .play_queue_xml .tree .findall ('./Video' )
516
- self .series = []
609
+ if self .media_type == MediaType .VIDEO :
610
+ self .play_queue_xml = XMLCollection (self .get_path (self .play_queue ))
611
+ videos = self .play_queue_xml .tree .findall ('./Video' )
612
+ self .series = []
517
613
518
- key = self .video .get ('key' )
519
- for i , video in enumerate (videos ):
520
- if video .get ('key' ) == key :
521
- self .seq = i
522
- self .series .append (video )
614
+ key = self .media_item .get ('key' )
615
+ for i , video in enumerate (videos ):
616
+ if video .get ('key' ) == key :
617
+ self .seq = i
618
+ self .series .append (video )
523
619
524
- self .has_next = self .seq < len (self .series ) - 1
525
- self .has_prev = self .seq > 0
620
+ self .has_next = self .seq < len (self .series ) - 1
621
+ self .has_prev = self .seq > 0
622
+ elif self .media_type == MediaType .MUSIC :
623
+ self .play_queue_xml = XMLCollection (self .get_path (self .play_queue ))
624
+ tracks = self .play_queue_xml .tree .findall ('./Track' )
625
+ self .series = []
626
+
627
+ key = self .media_item .get ('key' )
628
+ for i , track in enumerate (tracks ):
629
+ if track .get ('key' ) == key :
630
+ self .seq = i
631
+ self .series .append (track )
632
+
633
+ self .has_next = self .seq < len (self .series ) - 1
634
+ self .has_prev = self .seq > 0
526
635
527
636
def get_queue_info (self ):
528
637
return {
@@ -537,34 +646,43 @@ def get_next(self):
537
646
if self .play_queue and self .seq + 2 == len (self .series ):
538
647
self .upd_play_queue ()
539
648
next_video = self .series [self .seq + 1 ]
540
- return Media (self .get_path (next_video .get ('key' )), self .series , self .seq + 1 , self .play_queue , self .play_queue_xml )
649
+ return Media (self .get_path (next_video .get ('key' )), self .series , self .seq + 1 , self .play_queue , self .play_queue_xml , self . media_type )
541
650
542
651
def get_prev (self ):
543
652
if self .has_prev :
544
653
if self .play_queue and self .seq - 1 == 0 :
545
654
self .upd_play_queue ()
546
655
prev_video = self .series [self .seq - 1 ]
547
- return Media (self .get_path (prev_video .get ('key' )), self .series , self .seq - 1 , self .play_queue , self .play_queue_xml )
656
+ return Media (self .get_path (prev_video .get ('key' )), self .series , self .seq - 1 , self .play_queue , self .play_queue_xml , self . media_type )
548
657
549
658
def get_from_key (self , key ):
550
659
if self .play_queue :
551
660
self .upd_play_queue ()
552
661
for i , video in enumerate (self .series ):
553
662
if video .get ("key" ) == key :
554
- return Media (self .get_path (key ), self .series , i , self .play_queue , self .play_queue_xml )
663
+ return Media (self .get_path (key ), self .series , i , self .play_queue , self .play_queue_xml , self . media_type )
555
664
return None
556
665
else :
557
- return Media (self .get_path (key ))
558
-
559
- def get_video (self , index , media = 0 , part = 0 ):
560
- if index == 0 and self .video :
561
- return Video (self .video , self , media , part )
562
-
563
- video = self .tree .find ('./Video[%s]' % (index + 1 ))
564
- if video :
565
- return Video (video , self , media , part )
566
-
567
- log .error ("Media::get_video couldn't find video at index %s" % video )
666
+ return Media (self .get_path (key ), media_type = self .media_type )
667
+
668
+ def get_media_item (self , index , media = 0 , part = 0 ):
669
+ if self .media_type == MediaType .VIDEO :
670
+ if index == 0 and self .media_item :
671
+ return Video (self .media_item , self , media , part )
672
+
673
+ video = self .tree .find ('./Video[%s]' % (index + 1 ))
674
+ if video :
675
+ return Video (video , self , media , part )
676
+
677
+ elif self .media_type == MediaType .MUSIC :
678
+ if index == 0 and self .media_item :
679
+ return Track (self .media_item , self , media , part )
680
+
681
+ track = self .tree .find ('./Track[%s]' % (index + 1 ))
682
+ if track :
683
+ return Track (track , self , media , part )
684
+
685
+ log .error ("Media::get_media_item couldn't find {} at index {}" .format (self .media_type , index ))
568
686
569
687
def get_machine_identifier (self ):
570
688
if not hasattr (self , "_machine_identifier" ):
0 commit comments