Add plex-download.py tool; Added new utility to request user/pass from user, config, or env for use when creating cmd line tools

This commit is contained in:
Michael Shepanski 2017-08-13 01:50:40 -04:00
parent fe68c8f590
commit 63dc1507d2
4 changed files with 155 additions and 50 deletions

View file

@ -92,7 +92,7 @@ class PlexObject(object):
the first item in the result set is returned. the first item in the result set is returned.
Parameters: Parameters:
key (str or int): Path in Plex to fetch items from. If an int is passed ekey (str or int): Path in Plex to fetch items from. If an int is passed
in, the key will be translated to /library/metadata/<key>. This allows in, the key will be translated to /library/metadata/<key>. This allows
fetching an item only knowing its key-id. fetching an item only knowing its key-id.
cls (:class:`~plexapi.base.PlexObject`): If you know the class of the cls (:class:`~plexapi.base.PlexObject`): If you know the class of the

View file

@ -1,11 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import logging import logging, os, re, requests, time, zipfile
import os
import re
import requests
import time
import zipfile
from datetime import datetime from datetime import datetime
from getpass import getpass
from threading import Thread from threading import Thread
from plexapi import compat from plexapi import compat
from plexapi.exceptions import NotFound from plexapi.exceptions import NotFound
@ -281,20 +277,43 @@ def download(url, filename=None, savepath=None, session=None, chunksize=4024, un
def tag_helper(tag, items, locked=True, remove=False): def tag_helper(tag, items, locked=True, remove=False):
"""Simple tag helper for editing a object.""" """ Simple tag helper for editing a object. """
if not isinstance(items, list): if not isinstance(items, list):
items = [items] items = [items]
data = {}
d = {}
if not remove: if not remove:
for i, item in enumerate(items): for i, item in enumerate(items):
tag_name = '%s[%s].tag.tag' % (tag, i) tagname = '%s[%s].tag.tag' % (tag, i)
d[tag_name] = item data[tagname] = item
if remove: if remove:
tag_name = '%s[].tag.tag-' % tag tagname = '%s[].tag.tag-' % tag
d[tag_name] = ','.join(items) data[tagname] = ','.join(items)
data['%s.locked' % tag] = 1 if locked else 0
return data
d['%s.locked' % tag] = 1 if locked else 0
return d def getMyPlexAccount(opts=None):
""" Helper function tries to get a MyPlex Account instance by checking
the the following locations for a username and password. This is
useful to create user-friendly command line tools.
1. command-line options (opts).
2. environment variables and config.ini
3. Prompt on the command line.
"""
from plexapi import CONFIG
from plexapi.myplex import MyPlexAccount
# 1. Check command-line options
if opts and opts.username and opts.password:
print('Authenticating with Plex.tv as %s..' % opts.username)
return MyPlexAccount(opts.username, opts.password)
# 2. Check Plexconfig (environment variables and config.ini)
config_username = CONFIG.get('auth.myplex_username')
config_password = CONFIG.get('auth.myplex_password')
if config_username and config_password:
print('Authenticating with Plex.tv as %s..' % config_username)
return MyPlexAccount(config_username, config_password)
# 3. Prompt for username and password on the command line
username = input('What is your plex.tv username: ')
password = getpass('What is your plex.tv password: ')
print('Authenticating with Plex.tv as %s..' % username)
return MyPlexAccount(username, password)

67
tools/plex-download.py Executable file
View file

@ -0,0 +1,67 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Allows downloading a Plex media item from a local or shared library. You
may specify the item by the PlexWeb url (everything after !) or by
manually searching the items from the command line wizard.
Original contribution by lad1337.
"""
import argparse, re
from plexapi import utils
from plexapi.compat import unquote
from plexapi.video import Episode, Movie, Show
def choose(msg, items, attr):
print()
for index, i in enumerate(items):
name = attr(i) if callable(attr) else getattr(i, attr)
print(' %s: %s' % (index, name))
number = int(input('\n%s: ' % msg))
return items[number]
def search_for_item(url=None):
if url: return get_item_from_url(opts.url)
server = choose('Choose a Server', account.resources(), 'name').connect()
query = input('What are you looking for?: ')
item = choose('Choose result', server.search(query), lambda x: repr(x))
if isinstance(item, Show):
item = choose('Choose episode', item.episodes(), lambda x: x._prettyfilename())
if not isinstance(item, (Movie, Episode)):
raise SystemExit('Unable to download %s' % item.__class__.__name__)
return item
def get_item_from_url(url):
# Parse the ClientID and Key from the URL
clientid = re.findall('[a-f0-9]{40}', url)
key = re.findall('key=(.*?)(&.*)?$', url)
if not clientid or not key:
raise SystemExit('Cannot parse URL: %s' % url)
clientid = clientid[0]
key = unquote(key[0][0])
# Connect to the server and fetch the item
servers = [r for r in account.resources() if r.clientIdentifier == clientid]
if len(servers) != 1:
raise SystemExit('Unknown or ambiguous client id: %s' % clientid)
server = servers[0].connect()
return server.fetchItem(key)
if __name__ == '__main__':
# Command line parser
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--username', help='Your Plex username')
parser.add_argument('--password', help='Your Plex password')
parser.add_argument('--url', default=None, help='Download from URL (only paste after !)')
opts = parser.parse_args()
# Search item to download
account = utils.getMyPlexAccount(opts)
item = search_for_item(opts.url)
# Download the item
print("Downloading '%s' from %s.." % (item._prettyfilename(), item._server.friendlyName))
filepaths = item.download('./')
for filepath in filepaths:
print(' %s' % filepath)

View file

@ -8,45 +8,45 @@ and password. Alternatively, if you do not wish to enter your login
information below, you can retrieve the same information from plex.tv information below, you can retrieve the same information from plex.tv
at the URL: https://plex.tv/api/resources?includeHttps=1 at the URL: https://plex.tv/api/resources?includeHttps=1
""" """
from getpass import getpass import argparse
from plexapi import utils from plexapi import utils
from plexapi.exceptions import BadRequest from plexapi.exceptions import BadRequest
from plexapi.myplex import MyPlexAccount, _connect from plexapi.myplex import _connect
from plexapi.server import PlexServer from plexapi.server import PlexServer
FORMAT = ' %-17s %-25s %-20s %s'
FORMAT2 = ' %-17s %-25s %-20s %-30s (%s)'
SERVER = 'Plex Media Server' SERVER = 'Plex Media Server'
FORMAT = '%-8s %-6s %-17s %-25s %-20s %s (%s)'
def _list_resources(account, servers): def _list_resources(account, servers):
print('\nHTTPS Resources:') items = []
resources = MyPlexAccount(username, password).resources() print('Finding Plex resources..')
for r in resources: resources = account.resources()
if r.accessToken: for r in [r for r in resources if r.accessToken]:
for connection in r.connections: for connection in r.connections:
print(FORMAT % (r.product, r.name, r.accessToken, connection.uri)) local = 'Local' if connection.local else 'Remote'
servers[connection.uri] = r.accessToken extras = [r.provides]
print('\nDirect Resources:') items.append(FORMAT % ('Resource', local, r.product, r.name, r.accessToken, connection.uri, ','.join(extras)))
for r in resources: items.append(FORMAT % ('Resource', local, r.product, r.name, r.accessToken, connection.httpuri, ','.join(extras)))
if r.accessToken: servers[connection.httpuri] = r.accessToken
for connection in r.connections: servers[connection.uri] = r.accessToken
print(FORMAT % (r.product, r.name, r.accessToken, connection.httpuri)) return items
servers[connection.httpuri] = r.accessToken
def _list_devices(account, servers): def _list_devices(account, servers):
print('\nDevices:') items = []
for d in MyPlexAccount(username, password).devices(): print('Finding Plex devices..')
if d.token: for d in [d for d in account.devices() if d.token]:
for conn in d.connections: for connection in d.connections:
print(FORMAT % (d.product, d.name, d.token, conn)) extras = [d.provides]
servers[conn] = d.token items.append(FORMAT % ('Device', '--', d.product, d.name, d.token, connection, ','.join(extras)))
servers[connection] = d.token
return items
def _test_servers(servers): def _test_servers(servers):
seen = set() items, seen = [], set()
print('\nServer Clients:') print('Finding Plex clients..')
listargs = [[PlexServer, s, t, 5] for s,t in servers.items()] listargs = [[PlexServer, s, t, 5] for s,t in servers.items()]
results = utils.threaded(_connect, listargs) results = utils.threaded(_connect, listargs)
for url, token, plex, runtime in results: for url, token, plex, runtime in results:
@ -54,19 +54,38 @@ def _test_servers(servers):
if plex and clients: if plex and clients:
for c in plex.clients(): for c in plex.clients():
if c._baseurl not in seen: if c._baseurl not in seen:
print(FORMAT2 % (c.product, c.title, token, c._baseurl, plex.friendlyName)) extras = [plex.friendlyName] + c.protocolCapabilities
items.append(FORMAT % ('Client', '--', c.product, c.title, token, c._baseurl, ','.join(extras)))
seen.add(c._baseurl) seen.add(c._baseurl)
return items
def _print_items(items, _filter=None):
if _filter:
print('Displaying items matching filter: %s' % _filter)
print()
for item in items:
filtered_out = False
for f in _filter.split():
if f.lower() not in item.lower():
filtered_out = True
if not filtered_out:
print(item)
print()
if __name__ == '__main__': if __name__ == '__main__':
print(__doc__) parser = argparse.ArgumentParser(description=__doc__)
username = input('What is your plex.tv username: ') parser.add_argument('--username', help='Your Plex username')
password = getpass('What is your plex.tv password: ') parser.add_argument('--password', help='Your Plex password')
parser.add_argument('--filter', default='', help='Only display items containing specified filter')
opts = parser.parse_args()
try: try:
servers = {} servers = {}
account = MyPlexAccount(username, password) account = utils.getMyPlexAccount(opts)
_list_resources(account, servers) items = _list_resources(account, servers)
_list_devices(account, servers) items += _list_devices(account, servers)
_test_servers(servers) items += _test_servers(servers)
_print_items(items, opts.filter)
except BadRequest as err: except BadRequest as err:
print('Unable to login to plex.tv: %s' % err) print('Unable to login to plex.tv: %s' % err)