pkkid / python-plexapi

Python bindings for the Plex API.
BSD 3-Clause "New" or "Revised" License
1.14k stars 198 forks source link

Play music on clients #70

Closed Nixellion closed 7 years ago

Nixellion commented 7 years ago

I was trying out Plex Api for my python Voice Assistant, and I could not get a lot of stuff to work yet.

I need just a simple thing. A user asks with voice to play some track. This track name is then passed to the PLEX api's search, and I want to play the first entry of it, or make a playlist out of all entries and play them.

Or make a random playlist out of all music entries (songs) and just switch to the one that was found by user's query.

For example, saying "AI, Play Joe Satriani", should start playing all Joe Satriani songs it found in random or alphabet order. Once it finished playing it can either just start playing them again, or switch to another random song.

These are all accepted behaviours, at the moment I'm just looking for a simpler one.

Thanks.

The problems I encountered, is that I don't understand what 'section' means? It's certainly not a library name. But I get errors when trying to pass anything like MUSIC or music or Music into it, and then 'get'. Here is the code I tried:

for client in plex.clients():
    print(client.title)
    m = plex.library.section('Music').get(plex.search('Joe Satriani')[0])
    client.playMedia(m)

OR THIS ONE

client.playMedia(plex.search('Joe Satriani')[0])
pkkid commented 7 years ago

There are a few things to note here.

  1. Not all clients can accept a playMedia() command. For instance, the Chrome WebApps doesn't support this.
  2. A section is a plex library section named whatever you named it when you created it. These are the sections that show up under Libraries on the left hand side in the Plex Web client.
  3. When you call plex.search() you are getting Artists objects, not Song objects. You're better navigating into the section and searching from there. It's a much more powerful search function. Something like the following should work better.
  4. There is no 'One Search To Rule Them All' in the PlexAPI yet. Meaning, there is no generic search that will return both Artists and Tracks based on the best match. It's one or the other unfortunately, and you need to know ahead of time. A generic search was added to Plex itself (which they call the hub search or something like that), but only 4 months ago or so and I didn't have time to implement it into this API yet. -- That said, if you search all three artists, albums, and tracks and merge the result, you can get a good list. Something like this.
querystring = call_your_voice_recognition_app()
music = plex.library.section('Music')   # This string is the name of *your* library.
artists = music.searchArtists(title=querystring)
albums = music.searchAlbums(title=querystring)
tracks = music.searchTracks(title=querystring)

all_tracks = tracks
for artist in artists:
  all_tracks += artist.tracks()
for album in albums:
  all_tracks += album.tracks()
print(all_tracks)
Nixellion commented 7 years ago

And then I can in theory just use:

for client in plex.clients(): client.playMedia(all_tracks)

Or not? Also... It kinda stinks that chrome web client does not support it yet. I mean... Do you mean that if I just open a web-interface through Chrome it won't work? Or just the actual Chrome WebApp installed from the Chrome Store?

I plan to just have a few windows 10 tablets around my home with plex page open in the browser (or other soluation like VLC if that won't work), and I want to play music on all of them for now.

Thanks for you help

pkkid commented 7 years ago

You'd have to make a playlist of all the tracks first, then you can play the playlist. -- I can help come up with an example later tonight.

Nixellion commented 7 years ago

thank you! that'd be wonderful Also some directions to a more or less clear python api description if there is one? Or at leat an advice on how to approach searching and finding out and debugging it, even if I have to match it to the raw plex api calls.

Nixellion commented 7 years ago

Ok, making a playlist was easier than I thought:

playlist = Playlist.create(plex, querystring, all_tracks)

now, how do I play it...

Also, if I try to run `client.stop('music')` I get this error:

requests.exceptions.ConnectionError: HTTPConnectionPool(host='127.0.0.1', port=32400): Max retries exceeded with url: /player/playback/stop?commandID=1&type=music (Caused by NewConnectionError('<requests.packages.urllib3.connection.HTTPConnection object at 0x048E5510>: Failed to establish a new connection: [WinError 10061] No connection could be made because the target machine actively refused it',))

Also, why is it using 127.0.0.1? If plex server is running on another machine, can I somehow fix it? Or what's happening here?

Nixellion commented 7 years ago

Okay, I guess I had to run it on the machine where the plex server is launched. Commands like play and stop now work, but playMedia(playlist) or playMedia(all_tracks[0]) - just dont do anything :(

amazingr4b commented 7 years ago

I assume you are trying to do something like this? That voice control script I use doesn't run on my server. You can use something else on the same network as long as you have your script/server setup for it. If you are not using the username/password method you will need to configure the option on your plex server that allows access without authorization. In your server settings the box labeled something like "List of IP addresses and networks that are allowed without auth". As far as some of those commands go, I'll let the dev give you good examples. I'm sure the way I use their work is far from optimal.

Nixellion commented 7 years ago

Yes, thats very close to what I'm trying to do, good work! I mean my voice assisstant itself is working with multiple features, play\stop commands work, I can create a playlist from the list of songs as mentioned above, I just can't figure out why playMedia is not working.

Thanks for advice, I'll check Plex's settings out too. But my AI's logic will also run from the same server once deployed anyway, it's a flask server at it's core. I think I'm just usin playMedia somehow wrong...

I pasted every link generated by plexapi into the browser, and it returns "Bad Request". So either it's because there's some data attached to those requests, or..?

http://192.168.1.18:32400/player/playback/playMedia?address=192-168-1-18.xxxxxxxxxx.plex.direct&commandID=1&containerKey=/playQueues/46%3Fwindow%3D100%26own%3D1&key=/library/metadata/262&machineIdentifier=xxxxxxxxxxxx&port=32400

Nixellion commented 7 years ago

So, coming back to the problem, I still can't play the playlist with plexapi.

Here is the full code I'm using for testing:

from plexapi.server import PlexServer
from plexapi.myplex import MyPlexAccount
from plexapi.playlist import Playlist
import debug, traceback

class PlexControl():
    def __init__(self):
        self.account = MyPlexAccount.signin('login', 'password')
        self.plex = self.account.resource('server_name').connect()

    def play(self, song_name):
        querystring = song_name
        music = self.plex.library.section('Music')  # This string is the name of *your* library.
        artists = music.searchArtists(title=querystring)
        albums = music.searchAlbums(title=querystring)
        tracks = music.searchTracks(title=querystring)

        all_tracks = tracks
        for artist in artists:
            all_tracks += artist.tracks()
        for album in albums:
            all_tracks += album.tracks()

        try:
            playlist = Playlist.create(self.plex, "playlist_name", all_tracks)
        except Exception as e:
            traceback.print_exc()
        for client in self.plex.clients():
            client.playMedia(playlist)
            client.play()
debug.log ("START")
plexCtrl = PlexControl()
plexCtrl.play('search query')

It fails on client.playMedia(playlist) command.

Here is the final error I get: requests.exceptions.ConnectionError: HTTPConnectionPool(host='127.0.0.1', port=32400): Max retries exceeded with url: /player/playback/playMedia?address=192-168-1-17.xxxxxxxxxx.plex.direct&commandID=1&containerKey=/playQueues/149%3Fwindow%3D100%26own%3D1&key=/playlists/31179&machineIdentifier=xxxxxxxxxxxx&port=32400 (Caused by NewConnectionError('<requests.packages.urllib3.connection.HTTPConnection object at 0x042443F0>: Failed to establish a new connection: [WinError 10061] No connection could be made because the target machine actively refused it',))

Whats wrong? I just keep banging my head against the wall as it feels.

amazingr4b commented 7 years ago

I'm no expert, but I would first verify the various actions in your function work by testing them in a console. Something is wrong with how you are trying to login, and maybe other places too. This is how I have it setup. You'll have to fill in the variables as I cut those bits out. This probably not the best way to do it, but it works for me:

playlist = "playlistnamehere"
try:
    from plexapi.server import PlexServer
    baseurl = 'http://' + PLEXSERVERIP + ':' + PLEXSERVERPORT
    plex = PlexServer(baseurl)
except Exception:
    from plexapi.myplex import MyPlexAccount
    print ("Local Fail. Trying cloud access.")
    user = MyPlexAccount.signin(PLEXUN,PLEXPW)
    print ("\rSuccessfully logged into Plex cloud.\n")
    plex = user.resource(PLEXSVR).connect()
    client = plex.client(PLEXCLIENT)

for plist in plex.playlists():
    if playlist.lower() == str(plist.title).lower():
        playlist = plist.title
        title = plex.playlist(playlist)
        client.playMedia(title)
        return ("Now playing: " + playlist)
Nixellion commented 7 years ago

Thanks for your reply. I tried your code, changing ips and login data and all.

Same thing. As with my code - it logs in, finds clients, playlists and everything. But playMedia fails. Same error.

This time I'm trying it from a remote machine, not from the server itself, btw, but you mentioned that it works. Maybe there's something wrong with my plex server settings?

amazingr4b commented 7 years ago

Can you play the file on the client from plex if you are not using the script?

Assuming that works, I'd recommend adding some debug prints before the playmedia that should help you figure out where the problem is. From looking at the code you posted last, I'm guessing either the playlist variable here is the problem: client.playMedia(playlist), or it has something to do with how you are trying to cycle through and play it on all clients, and the first one it doesn't like.

I'd add some prints before this: client.playMedia(title)

I'd recommend adding: print (client) print (title)

You should get something like: playplaylist "Half Nelson" <plexapi.client.PlexClient object at 0x7567ef30> <Playlist:/playlists/183283:Half.Nelson> Now playing: Half Nelson (You probably wont see this, its a print from my script.)

Nixellion commented 7 years ago

Yes, of course I can play it on the client without the script.

I actually think there is something wrong with either plex, or my plex setup or something like that. Because I recently deployed Home Assistant on Raspberry Pi, and it works fine, it finds Plex clients. I launched plex in Chrome and Edge and they both showed up, when I play something in Plex I actually can see song names and even album art changing with delay in Home Assistant, But when I click on Play or next\previous track buttons - nothing works. I assume that the code in Home assistant should be working correctly, so there is something wrong with plex setup.

As for your advice of adding prints - yes, of course I added prints literally after each line of code, it did find all clients correctly, and it did find titles correctly, it's just that client.playMedia does nothing.

Hellowlol commented 7 years ago

Ok, this works for me:

import os
import sys
import logging

# Set this line if you want to run from master
#sys.path.insert(0, os.path.join('path_to_plexapi_folder'))

from plexapi.myplex import MyPlexAccount
import traceback

logging.basicConfig(level=logging.INFO)

class PlexControl():
    def __init__(self, username, password, servername):
        self.account = MyPlexAccount.signin(username, password)
        # This returns a Plexserver
        self.plex = self.account.resource(servername).connect()

    def play(self, song_name):
        querystring = song_name
        music = self.plex.library.section('Music')  # This string is the name of *your* library.
        artists = music.searchArtists(title=querystring)
        albums = music.searchAlbums(title=querystring)
        tracks = music.searchTracks(title=querystring)

        all_tracks = tracks
        for artist in artists:
            all_tracks += artist.tracks()
        for album in albums:
            all_tracks += album.tracks()

        try:
            playlist = self.plex.createPlaylist('search for ' + song_name, all_tracks)
        except Exception as e:
            traceback.print_exc()
        for client in self.plex.clients():
            client.playMedia(playlist)

PC = PlexControl('username', 'password', 'servername')

PC.play('The')
Nixellion commented 7 years ago

With that code I get requests.packages.urllib3.exceptions.NewConnectionError: <requests.packages.urllib3.connection.HTTPConnection object at 0x04648D10>: Failed to establish a new connection: [WinError 10061] No connection could be made because the target machine actively refused it

requests.packages.urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='127.0.0.1', port=32400): Max retries exceeded with url: /player/playback/playMedia?address=192-168-1-XXXXX.plex.direct&commandID=1&containerKey=/playQueues/17%3Fwindow%3D100%26own%3D1&key=/playlists/21713&machineIdentifier=XXXX&port=32400 (Caused by NewConnectionError('<requests.packages.urllib3.connection.HTTPConnection object at 0x04648D10>: Failed to establish a new connection: [WinError 10061] No connection could be made because the target machine actively refused it',))

I added print (client.title) right before client.playMedia(playlist), and i got this: "Plex Web (Chrome)"

Playlist is created succesfuly as well. It's just that playMedia fails.

pkkid commented 7 years ago

the target machine actively refused it.

This implies a settings issue or perhaps an invalid URL being called. I suspect the client is blocking the request because of some setting or doesn't actually support starting media files via the API.

  1. What exactly is the client you are trying to play on and are you sure it supports it?
  2. Are you connecting to the client directly, or controlling it through the server proxy?

FWIW: Home Assistant uses python-plexapi under the covers.

Nixellion commented 7 years ago

I'm trying it with web client, the default one that opens up when I enter my plex server's local network ip and port in Chrome browser (or any other browser, same issue).

What proxy are you talking about? All i'm doing is openning up the client in web browser. Internal or external server api.

pkkid commented 7 years ago

The web client doesn't support being sent commands to play media because JS can't really be listening on a port for commands. I use OpenPHT, the iOS app, or FireTV when testing all that locally. Other clients should work as well, but I don't have them around to test (such as Apple TV, Roku, etc). Just not the web client.

Nixellion commented 7 years ago

Ok, thanks! Works with OpenPHT perfectly. I think I was misguided somewhere along this discussion, or while google, that web client supports playMedia. Sorry for confusion.

pkkid commented 7 years ago

No problem. It's very confusing because Plex has so many clients and they all support different things and don't support others.

pergolafabio commented 4 years ago

can someone help me wit this issue? i think its related to this one ... i am using the non-official plex app for my samsung TV i also notice the 127.0.0.1 in client list ...

thnx in advance

https://github.com/pkkid/python-plexapi/issues/439