Plex / Python / Chromecast

Search This thread

linc-thra

Member
Dec 16, 2016
10
4
So, I'm trying to cast plex content to my chromecast using a python script on the server. Using PlexAPI I can connect to the server and navigate my content just fine. Using pychromecast, I can connect python to my chromecast. Using the Plex Media Controller, I can even use .play() to bring the basic plex background up on the chromecast. What I -can't- seem to do is tell the plex on the chromecast WHAT to play from python... or to even actually start playing any media that isn't already playing there; it just sits there. Anyone have any insights?
 

linc-thra

Member
Dec 16, 2016
10
4
Alright, well here's where I am: As near as I can tell, there's no way to tell the Plex Chromecast Receiver to play a title/file/anything, even once it's up. I can, of course, use PlexAPI to do things like poll for the oldest unwatched episode of something. Therefore, I'm currently tinkering with getting the episode file with PlexAPI and then sending to the chromecast with stream2chromecast. This lets me transcode, at least, to make the right video play (with audio even). Unfortunately, when transcoding, the "pause"/"continue" functions of stream2chromecast don't work. Not sure what's up with that, but it's on their end.
EvenGhost was being a pita and kept crashing on me for some reason, so I gave up on it and wrote a Tornado server into the python script. Not ideal, nor do I really want a webserver up and running at home, but it is what it is for the moment.
So the current flow goes "Google Home -> IFTTT -> Maker -> Tornado Server (python code inside) -> Chromecast.
Using this, I can now say "Okay Google, Chromecast the latest episode of MayGyver"... and in a few seconds the oldest unwatched episode from my Plex server will start to play on my TV. (I can also say, "Okay Google, chromecast stop" and it will stop.)
Sadly, because I'm sidestepping Plex for the actual playing, it doesn't show up under "now playing". Nor does it mark the episode as watched if you finish it.
 

linc-thra

Member
Dec 16, 2016
10
4
Alright, well. Today's progress update. Rather than continue the external tracking path, I've dropped back to attempting to make it play to the Plex app on the Chromecast. I originally thought this impossible, but I did manage to make some progress.
I still cannot make a video play via this method, but I -am- able to bring up the "Details" page of any given movie or episode or whatever onto the Chromecast. I accomplished this by digging into the pychromecast Controllers and its notes about custom namespaces. I did the whole "net-internals/#capture" thing the github mentions to explore the namespace. Digging through, that gave me what commands are being sent to the Chromecast when I cast from my browser. I translated the two commands there (SHOWDETAILS and LOAD) into the Plex Controller. I was somewhat surprised when ShowDetails actually worked.... but then shatteringly disappointed when LOAD did not.
I feel like I'm so close on this now, but still missing a piece of the puzzle.
 
  • Like
Reactions: mcneishh

mcneishh

New member
Dec 30, 2016
4
2
Nice work. You've converted me - I was just using the standard media controller to access the respective Plex url. The tips above helped guide me in the right direction. So thanks - and keep posting your progress. I'll see what I can figure out as well. I assume you already sorted out the transient access tokens that look to be required?
 

mcneishh

New member
Dec 30, 2016
4
2
Ok I got the play working via Python.
I use this API - github.com/mjs7231/python-plexapi
The key things I had to do:
1. Grab a transient token for the request
Code:
server.query('/security/token?type=delegation&scope=all').attrib('token')
2. And I created a playQueue id for the request
Code:
server.createPlayQueue(video).playQueueID
3. The LOAD command must be run in the namespace:
Code:
urn:x-cast:com.google.cast.media
not
Code:
urn:x-cast:plex
I just switched namespaces in my PlexController (as I built a custom LOAD message) to prove it would work
 

MechaTech84

Member
Dec 30, 2016
5
0
Ok I got the play working via Python.
I use this API - github.com/mjs7231/python-plexapi
The key things I had to do:
1. Grab a transient token for the request
Code:
server.query('/security/token?type=delegation&scope=all').attrib('token')
2. And I created a playQueue id for the request
Code:
server.createPlayQueue(video).playQueueID
3. The LOAD command must be run in the namespace:
Code:
urn:x-cast:com.google.cast.media
not
Code:
urn:x-cast:plex
I just switched namespaces in my PlexController (as I built a custom LOAD message) to prove it would work


Can you provide some more details on how you did the LOAD command part? That's the part I'm having the most trouble with.
 

linc-thra

Member
Dec 16, 2016
10
4
Ah, I had NOT indeed worked out the transient token bit. I felt like maybe I needed to fetch one, but hadn't quite worked out how. Are you sending a SETSTREAM and a LOAD or just a LOAD to actually get things playing? If a SETSTREAM, is that going to the plex namespace or the media namespace?
 

MechaTech84

Member
Dec 30, 2016
5
0
Ah, I had NOT indeed worked out the transient token bit. I felt like maybe I needed to fetch one, but hadn't quite worked out how. Are you sending a SETSTREAM and a LOAD or just a LOAD to actually get things playing? If a SETSTREAM, is that going to the plex namespace or the media namespace?

How did you go about sending the commands to the chromecast? I've spent most of the day trying to figure out pychromecast, but I'm not having much luck. I just cannot figure out how to expand the namespace to add a new plex module. I feel like if I could get anything to send it would be a huge leap in the right direction...
 

linc-thra

Member
Dec 16, 2016
10
4
How did you go about sending the commands to the chromecast? I've spent most of the day trying to figure out pychromecast, but I'm not having much luck. I just cannot figure out how to expand the namespace to add a new plex module. I feel like if I could get anything to send it would be a huge leap in the right direction...

I'm headed out to dinner and won't be back for a while, BUT, it requires editing the plex.py file in the controller folder of pychromecast. I JUST got it to start playing and the like myself thanks to mcneishh's hints. I'm attaching a basic functional PlexApiController:

http://pastebin.com/qeLYZpW4

I'll try to improve it later.
An example of how to use this:

Code:
import pychromecast.controllers.plexapi as px
import pychromecast
from plexapi.myplex import MyPlexAccount
account = MyPlexAccount.signin('<USERNAME>', '<PASSWORD')
plex = account.resource('<SERVER_NAME>').connect()
pxr = px.PlexController()
cast = pychromecast.Chromecast("<CHROMECASTIP")
cast.register_handler(pxr)
pxr.namespace = 'urn:x-cast:com.google.cast.sse'
white = plex.library.section("TV Shows").get("White Collar")
epi = white.seasons()[0].episodes()[0]
pxr.play_media(epi,plex)
 
  • Like
Reactions: MechaTech84

linc-thra

Member
Dec 16, 2016
10
4
My bad, that first code line should be
Code:
import pychromecast.controllers.plex as px

if you edit the existing plex.py file, I think. I had made my own at plexapi.py so as not to lose the original.
 
  • Like
Reactions: MechaTech84

linc-thra

Member
Dec 16, 2016
10
4
Is there a reason why you are trying to do this and not use the Plex App? Is it just a coding experiment or is there some reason why you want to bypass the Plex app?

There is a reason. Now that this is working, I have tied it to my Google home without waiting a year for Plex to get their support sorted. I can now say, "Okay Google, watch Westworld season 1 episode 1" and it will start playing the appropriate episode on my Chromecast from Plex.
 
  • Like
Reactions: Asphyx

mcneishh

New member
Dec 30, 2016
4
2
Can you provide some more details on how you did the LOAD command part? That's the part I'm having the most trouble with.

I grabbed the relevant byte code cleaning it up I ended up with:
Code:
{"type":"LOAD","requestId":481982064,"sessionId":"81c3b38d-b2f4-4c33-929a-5365af184d70","media":
    {"contentId":"/library/metadata/105","streamType":"BUFFERED","contentType":"video","customData":
        {"offset":0,"directPlay":true,"directStream":true,"subtitleSize":100,"audioBoost":100,"server":
            {       
"machineIdentifier":"9a35df949e05bc86d0aa792c56e3db68c0c36250","transcoderVideo":true,"transcoderVideoRemuxOnly":false,"transcoderAudio":true,"version":"1.1.4.2757","myPlexSubscription":true,"isVerifiedHostname":false,"protocol":"http","address":"10.1.3.200","port":"32400","accessToken":"transient-74cce00a-4048-4fcc-a571-38f2fd9a2acf"
            },
            "user":{"username":"XXXXXXXX"},
            "containerKey":"/playQueues/1635?own=1&window=200"
        }
    },
    "autoplay":true,"currentTime":0
}

I haven't finished the method but here's what I have all the variables (except the last 2) are currently hardcoded.
Code:
def play_item(self, key, content_data, play_data, server_data, user_data, access_token, play_queue_id):
        key = "/library/metadata/105"
        requestId = self._socket_client._request_id
        sessionId = self._socket_client.session_id
        play_data = {"offset":0,"directPlay":True,"directStream":True,"subtitleSize":100,"audioBoost":100}
        server_data = {"machineIdentifier":"9a35df949e05bc86d0aa792c56e3db68c0c36250","transcoderVideo":True,"transcoderVideoRemuxOnly":False,"transcoderAudio":True,"version":"1.1.4.2757","myPlexSubscription":True,"isVerifiedHostname":False,"protocol":"http","address":"10.1.3.200","port":"32400"}
        user_data = {"username":"XXXXXX"}
        content_data = {"streamType":"BUFFERED","contentType":"video"}
        msg = {MESSAGE_TYPE:TYPE_LOAD,'requestId':requestId,'sessionId':sessionId}
        msg['media'] = {'contentId':key}
        msg['media'].update(content_data)
        msg['media']['customData'] = play_data.copy()
        msg['media']['customData']['server'] = server_data.copy()
        msg['media']['customData']['server']['accessToken'] = access_token
        msg['media']['customData']['user'] = user_data.copy()
        msg['media']['customData']['containerKey'] = '/playQueues/%s?own=1&window=200' % play_queue_id
        msg.update({'autoplay':True, 'currentTime':0})
        self.namespace = 'urn:x-cast:com.google.cast.media'
        self.send_message(msg)
        self.namesapce = 'urn:x-cast:plex'

Here's my full controller code.
I'm not 100% sure that I have the right session and request Ids, but assume they are the most likely to use.

Hopefully this helps.

Code:
MESSAGE_TYPE = 'type'
TYPE_SHOWDETAILS = 'SHOWDETAILS'
TYPE_LOAD = 'LOAD'

class MyPlexController(PlexController):
    def __init__(self):
        super().__init__()

    def receive_message(self, message, data):
        logging.info('PlexController: I received this message: {}'.format(data))
        return True  # indicate you handled this message

    def send_message(self, data, inc_session_id=False, callback_function=None):
        logging.info('PlexController: I send this message: {}'.format(data))
        super().send_message(data, inc_session_id, callback_function)
    
    def show_details(self, key, content_data, server_data, user_data, access_token):
        key = "/library/metadata/105"
        server_data = {"machineIdentifier":"9a35df949e05bc86d0aa792c56e3db68c0c36250","transcoderVideo":True,"transcoderVideoRemuxOnly":False,"transcoderAudio":True,"version":"1.1.4.2757","myPlexSubscription":True,"isVerifiedHostname":False,"protocol":"http","address":"10.1.3.200","port":"32400"}
        user_data = {"username":"XXXXXX"}
        content_data = {"streamType":"BUFFERED","contentType":"video"}
        msg = {MESSAGE_TYPE:TYPE_SHOWDETAILS}
        msg['media'] = {'contentId':key}
        msg['media'].update(content_data)
        msg['media']['customData'] = {}
        msg['media']['customData']['server'] = server_data.copy()
        msg['media']['customData']['server']['accessToken'] = access_token
        msg['media']['customData']['user'] = user_data.copy()
        self.send_message(msg)
# {"type":"SHOWDETAILS","media":
    # {"contentId":"/library/metadata/105","streamType":"BUFFERED","contentType":"video","customData":
        # {"server":
            # {"machineIdentifier":"9a35df949e05bc86d0aa792c56e3db68c0c36250","transcoderVideo":true,"transcoderVideoRemuxOnly":false,"transcoderAudio":true,"version":"1.1.4.2757","myPlexSubscription":true,"isVerifiedHostname":false,"protocol":"http","address":"10.1.3.200","port":"32400","accessToken":"transient-73832b14-e2bf-4943-b97d-468b9ae85a34"},
            # "user": {"username":"XXXXXX"}
        # }
    # }
# }
    
    def play_item(self, key, content_data, play_data, server_data, user_data, access_token, play_queue_id):
        key = "/library/metadata/105"
        requestId = self._socket_client._request_id
        sessionId = self._socket_client.session_id
        play_data = {"offset":0,"directPlay":True,"directStream":True,"subtitleSize":100,"audioBoost":100}
        server_data = {"machineIdentifier":"9a35df949e05bc86d0aa792c56e3db68c0c36250","transcoderVideo":True,"transcoderVideoRemuxOnly":False,"transcoderAudio":True,"version":"1.1.4.2757","myPlexSubscription":True,"isVerifiedHostname":False,"protocol":"http","address":"10.1.3.200","port":"32400"}
        user_data = {"username":"XXXXXX"}
        content_data = {"streamType":"BUFFERED","contentType":"video"}
        msg = {MESSAGE_TYPE:TYPE_LOAD,'requestId':requestId,'sessionId':sessionId}
        msg['media'] = {'contentId':key}
        msg['media'].update(content_data)
        msg['media']['customData'] = play_data.copy()
        msg['media']['customData']['server'] = server_data.copy()
        msg['media']['customData']['server']['accessToken'] = access_token
        msg['media']['customData']['user'] = user_data.copy()
        msg['media']['customData']['containerKey'] = '/playQueues/%s?own=1&window=200' % play_queue_id
        msg.update({'autoplay':True, 'currentTime':0})
        self.namespace = 'urn:x-cast:com.google.cast.media'
        self.send_message(msg)
        self.namesapce = 'urn:x-cast:plex'
# {"type":"LOAD","requestId":481982064,"sessionId":"81c3b38d-b2f4-4c33-929a-5365af184d70","media":
    # {"contentId":"/library/metadata/105","streamType":"BUFFERED","contentType":"video","customData":
        # {"offset":0,"directPlay":true,"directStream":true,"subtitleSize":100,"audioBoost":100,"server":
            # {
            # "machineIdentifier":"9a35df949e05bc86d0aa792c56e3db68c0c36250","transcoderVideo":true,"transcoderVideoRemuxOnly":false,"transcoderAudio":true,"version":"1.1.4.2757","myPlexSubscription":true,"isVerifiedHostname":false,"protocol":"http","address":"10.1.3.200","port":"32400","accessToken":"transient-74cce00a-4048-4fcc-a571-38f2fd9a2acf"
            # },
            # "user":{"username":"XXXXXX"},
            # "containerKey":"/playQueues/1635?own=1&window=200"
        # }
    # },
    # "autoplay":true,"currentTime":0
# }
 

mcneishh

New member
Dec 30, 2016
4
2
Ah, I had NOT indeed worked out the transient token bit. I felt like maybe I needed to fetch one, but hadn't quite worked out how. Are you sending a SETSTREAM and a LOAD or just a LOAD to actually get things playing? If a SETSTREAM, is that going to the plex namespace or the media namespace?

I just used a LOAD.
For SETSTREAM I'm using the following in my controller (using the Plex namespace).

Code:
def _send_command(self, chromecast, command):
        chromecast.register_handler(self)
        mc = chromecast.media_controller
        if mc.status is None or mc.status.media_session_id is None:
            raise PlexControllerException('No media_session_id was found unable to send command {}'.format(command))
        command['mediaSessionId'] = mc.status.media_session_id
        self.send_message(command, inc_session_id=True)

    def set_quality(self, chromecast, bitrate): self._send_command(chromecast, {"type":"SETQUALITY","bitrate":bitrate})
    def set_subtitles(self, chromecast, subtitle_id): self._send_command(chromecast, {"type":"SETSTREAM","stream":{"type":"subtitles","id":subtitle_id}})
    def disable_subtitles(self, chromecast): self._send_command(chromecast, {"type":"SETSTREAM","stream":{"type":"subtitles","id":0}})
    def set_audio(self, chromecast, audio_id): self._send_command(chromecast, {"type":"SETSTREAM","stream":{"type":"audio","id":audio_id}})
    def set_video(self, chromecast, video_id): self._send_command(chromecast, {"type":"SETSTREAM","stream":{"type":"video","id":video_id}})

You can get the resprective Ids from via the plexapi
Code:
videostreams = [s.id for s in video.videoStreams]
audiostreams = [s.id for s in video.audioStreams]
subtitlestreams = [s.id for s in video.subtitleStreams]
 

bluebird11

Senior Member
Apr 12, 2009
52
5
I'm headed out to dinner and won't be back for a while, BUT, it requires editing the plex.py file in the controller folder of pychromecast. I JUST got it to start playing and the like myself thanks to mcneishh's hints. I'm attaching a basic functional PlexApiController:

http://pastebin.com/qeLYZpW4

I'll try to improve it later.
An example of how to use this:

Code:
import pychromecast.controllers.plexapi as px
import pychromecast
from plexapi.myplex import MyPlexAccount
account = MyPlexAccount.signin('<USERNAME>', '<PASSWORD')
plex = account.resource('<SERVER_NAME>').connect()
pxr = px.PlexController()
cast = pychromecast.Chromecast("<CHROMECASTIP")
cast.register_handler(pxr)
pxr.namespace = 'urn:x-cast:com.google.cast.sse'
white = plex.library.section("TV Shows").get("White Collar")
epi = white.seasons()[0].episodes()[0]
pxr.play_media(epi,plex)

Hi, thanks a lot for your code! I managed to connect to the Chromecast using pychromecast and to plexapi, but unfortunately every time I try to play something I get "unable to cast this media is currently unavailable" on my Chromecast. Starting it manually works.

Do you have any ideas?
 

linc-thra

Member
Dec 16, 2016
10
4
Hi, thanks a lot for your code! I managed to connect to the Chromecast using pychromecast and to plexapi, but unfortunately every time I try to play something I get "unable to cast this media is currently unavailable" on my Chromecast. Starting it manually works.

Do you have any ideas?

Hmmm... not really, other than possibly you're calling or referencing the episode wrong from plexapi?
 

bluebird11

Senior Member
Apr 12, 2009
52
5
Hmmm... not really, other than possibly you're calling or referencing the episode wrong from plexapi?

Hmm, I'm getting it with the following code:

Code:
movies = plex.library.section('Movies')
for video in movies.search(unwatched=True):
    print(video.title)
    epi = plex.library.section('Movies').get(video.title)
    break

Another question: Do I need to connect a client first? I.e. do I have to connect to Plex with a browser or the app before I can do anything?
 

linc-thra

Member
Dec 16, 2016
10
4
Hmm, I'm getting it with the following code:

Code:
movies = plex.library.section('Movies')
for video in movies.search(unwatched=True):
    print(video.title)
    epi = plex.library.section('Movies').get(video.title)
    break

Another question: Do I need to connect a client first? I.e. do I have to connect to Plex with a browser or the app before I can do anything?

To answer your second question first: No, the controller should connect itself just find with no browser involved. The first question is more complicated... mainly because your code works for me.... so long as you are trying to get the first unwatched movie in the movie section. I didn't have any marked as "unwatched" to start with and it fell over, but once I marked one as unwatched, that code ran for me fine.
 

bluebird11

Senior Member
Apr 12, 2009
52
5
To answer your second question first: No, the controller should connect itself just find with no browser involved. The first question is more complicated... mainly because your code works for me.... so long as you are trying to get the first unwatched movie in the movie section. I didn't have any marked as "unwatched" to start with and it fell over, but once I marked one as unwatched, that code ran for me fine.

Thanks for the reply, it works now. Connecting directly to the plex server in the local network doesn't seem to work. What worked is connecting over plex.tv as shown here... I thought that it doesn't matter.

Anyway, what I was hoping for is that after connecting it would show up as a device / client in the Plex app so I could control it from there (and from amazon echo), but it doesn't show up :( Do you think that would be possible? If I connect manually from a browser it shows up as "Plex Web (Chrome)".
 

Top Liked Posts

  • There are no posts matching your filters.
  • 2
    Ok I got the play working via Python.
    I use this API - github.com/mjs7231/python-plexapi
    The key things I had to do:
    1. Grab a transient token for the request
    Code:
    server.query('/security/token?type=delegation&scope=all').attrib('token')
    2. And I created a playQueue id for the request
    Code:
    server.createPlayQueue(video).playQueueID
    3. The LOAD command must be run in the namespace:
    Code:
    urn:x-cast:com.google.cast.media
    not
    Code:
    urn:x-cast:plex
    I just switched namespaces in my PlexController (as I built a custom LOAD message) to prove it would work
    1
    Alright, well. Today's progress update. Rather than continue the external tracking path, I've dropped back to attempting to make it play to the Plex app on the Chromecast. I originally thought this impossible, but I did manage to make some progress.
    I still cannot make a video play via this method, but I -am- able to bring up the "Details" page of any given movie or episode or whatever onto the Chromecast. I accomplished this by digging into the pychromecast Controllers and its notes about custom namespaces. I did the whole "net-internals/#capture" thing the github mentions to explore the namespace. Digging through, that gave me what commands are being sent to the Chromecast when I cast from my browser. I translated the two commands there (SHOWDETAILS and LOAD) into the Plex Controller. I was somewhat surprised when ShowDetails actually worked.... but then shatteringly disappointed when LOAD did not.
    I feel like I'm so close on this now, but still missing a piece of the puzzle.
    1
    How did you go about sending the commands to the chromecast? I've spent most of the day trying to figure out pychromecast, but I'm not having much luck. I just cannot figure out how to expand the namespace to add a new plex module. I feel like if I could get anything to send it would be a huge leap in the right direction...

    I'm headed out to dinner and won't be back for a while, BUT, it requires editing the plex.py file in the controller folder of pychromecast. I JUST got it to start playing and the like myself thanks to mcneishh's hints. I'm attaching a basic functional PlexApiController:

    http://pastebin.com/qeLYZpW4

    I'll try to improve it later.
    An example of how to use this:

    Code:
    import pychromecast.controllers.plexapi as px
    import pychromecast
    from plexapi.myplex import MyPlexAccount
    account = MyPlexAccount.signin('<USERNAME>', '<PASSWORD')
    plex = account.resource('<SERVER_NAME>').connect()
    pxr = px.PlexController()
    cast = pychromecast.Chromecast("<CHROMECASTIP")
    cast.register_handler(pxr)
    pxr.namespace = 'urn:x-cast:com.google.cast.sse'
    white = plex.library.section("TV Shows").get("White Collar")
    epi = white.seasons()[0].episodes()[0]
    pxr.play_media(epi,plex)
    1
    My bad, that first code line should be
    Code:
    import pychromecast.controllers.plex as px

    if you edit the existing plex.py file, I think. I had made my own at plexapi.py so as not to lose the original.
    1
    Is there a reason why you are trying to do this and not use the Plex App? Is it just a coding experiment or is there some reason why you want to bypass the Plex app?

    There is a reason. Now that this is working, I have tied it to my Google home without waiting a year for Plex to get their support sorted. I can now say, "Okay Google, watch Westworld season 1 episode 1" and it will start playing the appropriate episode on my Chromecast from Plex.