Skip to content

Commit d42efbe

Browse files
authored
Merge pull request #29 from romosborne/master
Issue #10: Get music working
2 parents 2ca97b7 + a454459 commit d42efbe

File tree

5 files changed

+297
-161
lines changed

5 files changed

+297
-161
lines changed

plex_mpv_shim/action_thread.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def stop(self):
1717
def run(self):
1818
force_next = False
1919
while not self.halt:
20-
if (playerManager._player and playerManager._video) or force_next:
20+
if (playerManager._player and playerManager._media_item) or force_next:
2121
playerManager.update()
2222

2323
force_next = False

plex_mpv_shim/client.py

+12-5
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from http.server import HTTPServer
1414
from http.server import SimpleHTTPRequestHandler
1515
from socketserver import ThreadingMixIn
16+
from .media import MediaType
1617
from .utils import upd_token, sanitize_msg, plex_color_to_mpv
1718
from .conf import settings
1819

@@ -300,24 +301,30 @@ def playMedia(self, path, arguments):
300301
offset = int(int(arguments.get("offset", 0))/1e3)
301302
url = urllib.parse.urljoin("%s://%s:%s" % (protocol, address, port), key)
302303
playQueue = arguments.get("containerKey", None)
304+
mediaType = arguments.get("type", "video")
305+
306+
if mediaType == "video":
307+
parsed_media_type = MediaType.VIDEO
308+
elif mediaType == "music":
309+
parsed_media_type = MediaType.MUSIC
303310

304311
token = arguments.get("token", None)
305312
if token:
306313
upd_token(address, token)
307314

308315
if settings.enable_play_queue and playQueue.startswith("/playQueue"):
309-
media = Media(url, play_queue=playQueue)
316+
media = Media(url, media_type=parsed_media_type, play_queue=playQueue)
310317
else:
311-
media = Media(url)
318+
media = Media(url, media_type=parsed_media_type)
312319

313320
log.debug("HttpHandler::playMedia %s" % media)
314321

315322
# TODO: Select video, media and part here based off user settings
316-
video = media.get_video(0)
317-
if video:
323+
media_item = media.get_media_item(0)
324+
if media_item:
318325
if settings.pre_media_cmd:
319326
os.system(settings.pre_media_cmd)
320-
playerManager.play(video, offset)
327+
playerManager.play(media_item, offset)
321328
timelineManager.SendTimelineToSubscribers()
322329

323330
def stop(self, path, arguments):

plex_mpv_shim/media.py

+176-58
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from abc import ABC, abstractmethod
2+
from enum import Enum
13
import logging
24
import urllib.request, urllib.parse, urllib.error
35
import urllib.parse
@@ -16,13 +18,62 @@
1618

1719
# 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
1820

19-
class MediaItem(object):
20-
pass
21+
class MediaType(Enum):
22+
VIDEO = "video"
23+
MUSIC = "music"
2124

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):
2375
def __init__(self, node, parent, media=0, part=0):
24-
self.parent = parent
25-
self.node = node
76+
super().__init__(MediaType.VIDEO, node, parent)
2677
self.played = False
2778
self._media = 0
2879
self._media_node = None
@@ -388,15 +439,6 @@ def get_external_sub(self, id):
388439
url = "/library/streams/{0}".format(id)
389440
return get_plex_url(urllib.parse.urljoin(self.parent.server_url, url))
390441

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-
400442
def update_position(self, ms):
401443
"""
402444
Sets the state of the media as "playing" with a progress of ``ms`` milliseconds.
@@ -417,26 +459,72 @@ def update_position(self, ms):
417459

418460
return safe_urlopen(url, data)
419461

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 = {}
422476

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:
425511
return False
426512

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
431518

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
437521

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
440528

441529
class XMLCollection(object):
442530
def __init__(self, url):
@@ -455,7 +543,7 @@ def __str__(self):
455543
return self.path.path
456544

457545
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):
459547
# Include Markers
460548
if "?" in url:
461549
sep = "&"
@@ -464,8 +552,15 @@ def __init__(self, url, series=None, seq=None, play_queue=None, play_queue_xml=N
464552
url = url + sep + "includeMarkers=1"
465553

466554
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+
469564
self.seq = None
470565
self.has_next = False
471566
self.has_prev = False
@@ -487,11 +582,11 @@ def __init__(self, url, series=None, seq=None, play_queue=None, play_queue_xml=N
487582
else:
488583
self.series = []
489584
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"))
491586
videos = series_xml.tree.findall('./Video')
492587

493588
# 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')
495590
is_special = False
496591
for i, video in enumerate(videos):
497592
if video.get('key') == key:
@@ -511,18 +606,32 @@ def __init__(self, url, series=None, seq=None, play_queue=None, play_queue_xml=N
511606

512607
def upd_play_queue(self):
513608
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 = []
517613

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)
523619

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
526635

527636
def get_queue_info(self):
528637
return {
@@ -537,34 +646,43 @@ def get_next(self):
537646
if self.play_queue and self.seq+2 == len(self.series):
538647
self.upd_play_queue()
539648
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)
541650

542651
def get_prev(self):
543652
if self.has_prev:
544653
if self.play_queue and self.seq-1 == 0:
545654
self.upd_play_queue()
546655
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)
548657

549658
def get_from_key(self, key):
550659
if self.play_queue:
551660
self.upd_play_queue()
552661
for i, video in enumerate(self.series):
553662
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)
555664
return None
556665
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))
568686

569687
def get_machine_identifier(self):
570688
if not hasattr(self, "_machine_identifier"):

0 commit comments

Comments
 (0)