mirror of
https://github.com/pkkid/python-plexapi
synced 2024-11-10 14:14:19 +00:00
Merge branch 'master' into patch-8
This commit is contained in:
commit
bd9e1db4dc
8 changed files with 208 additions and 167 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -24,3 +24,4 @@ include/
|
|||
lib/
|
||||
pip-selfcheck.json
|
||||
pyvenv.cfg
|
||||
MANIFEST
|
||||
|
|
|
@ -6,6 +6,7 @@ from plexapi.compat import quote_plus, urlencode
|
|||
from plexapi.exceptions import BadRequest, NotFound, UnknownType, Unsupported
|
||||
from plexapi.utils import tag_helper
|
||||
|
||||
DONT_RELOAD_FOR_KEYS = ['key', 'session']
|
||||
OPERATORS = {
|
||||
'exact': lambda v, q: v == q,
|
||||
'iexact': lambda v, q: v.lower() == q.lower(),
|
||||
|
@ -278,7 +279,8 @@ class PlexPartialObject(PlexObject):
|
|||
# Dragons inside.. :-/
|
||||
value = super(PlexPartialObject, self).__getattribute__(attr)
|
||||
# Check a few cases where we dont want to reload
|
||||
if attr == 'key' or attr.startswith('_'): return value
|
||||
if attr in DONT_RELOAD_FOR_KEYS: return value
|
||||
if attr.startswith('_'): return value
|
||||
if value not in (None, []): return value
|
||||
if self.isFullObject(): return value
|
||||
# Log the reload.
|
||||
|
|
|
@ -187,24 +187,30 @@ class PlexClient(PlexObject):
|
|||
log.debug('Client %s doesnt support %s controller.'
|
||||
'What your trying might not work' % (self.title, controller))
|
||||
|
||||
proxy = self._proxyThroughServer if proxy is None else proxy
|
||||
query = self._server.query if proxy else self.query
|
||||
|
||||
# Workaround for ptp. See https://github.com/pkkid/python-plexapi/issues/244
|
||||
t = time.time()
|
||||
if t - self._last_call >= 80 and self.product in ('ptp', 'Plex Media Player'):
|
||||
url = '/player/timeline/poll?wait=0&commandID=%s' % self._nextCommandId()
|
||||
if proxy:
|
||||
self._server.query(url, headers=headers)
|
||||
else:
|
||||
self.query(url, headers=headers)
|
||||
query(url, headers=headers)
|
||||
self._last_call = t
|
||||
|
||||
params['commandID'] = self._nextCommandId()
|
||||
key = '/player/%s%s' % (command, utils.joinArgs(params))
|
||||
|
||||
proxy = self._proxyThroughServer if proxy is None else proxy
|
||||
|
||||
if proxy:
|
||||
return self._server.query(key, headers=headers)
|
||||
return self.query(key, headers=headers)
|
||||
try:
|
||||
return query(key, headers=headers)
|
||||
except ElementTree.ParseError:
|
||||
# Workaround for players which don't return valid XML on successful commands
|
||||
# - Plexamp: `b'OK'`
|
||||
if self.product in (
|
||||
'Plexamp',
|
||||
'Plex for Android (TV)',
|
||||
):
|
||||
return
|
||||
raise
|
||||
|
||||
def url(self, key, includeToken=False):
|
||||
""" Build a URL string with proper token argument. Token will be appended to the URL
|
||||
|
|
|
@ -85,6 +85,8 @@ class MediaPart(PlexObject):
|
|||
key (str): Key used to access this media part (ex: /library/parts/46618/1389985872/file.avi).
|
||||
size (int): Size of this file in bytes (ex: 733884416).
|
||||
streams (list<:class:`~plexapi.media.MediaPartStream`>): List of streams in this media part.
|
||||
exists (bool): Determine if file exists
|
||||
accessible (bool): Determine if file is accessible
|
||||
"""
|
||||
TAG = 'Part'
|
||||
|
||||
|
@ -104,6 +106,8 @@ class MediaPart(PlexObject):
|
|||
self.syncState = data.attrib.get('syncState')
|
||||
self.videoProfile = data.attrib.get('videoProfile')
|
||||
self.streams = self._buildStreams(data)
|
||||
self.exists = cast(bool, data.attrib.get('exists'))
|
||||
self.accessible = cast(bool, data.attrib.get('accessible'))
|
||||
|
||||
def _buildStreams(self, data):
|
||||
streams = []
|
||||
|
@ -463,6 +467,23 @@ class Mood(MediaTag):
|
|||
FILTER = 'mood'
|
||||
|
||||
|
||||
@utils.registerPlexObject
|
||||
class Poster(PlexObject):
|
||||
""" Represents a Poster.
|
||||
|
||||
Attributes:
|
||||
TAG (str): 'Photo'
|
||||
"""
|
||||
TAG = 'Photo'
|
||||
|
||||
def _loadData(self, data):
|
||||
self._data = data
|
||||
self.key = data.attrib.get('key')
|
||||
self.ratingKey = data.attrib.get('ratingKey')
|
||||
self.selected = data.attrib.get('selected')
|
||||
self.thumb = data.attrib.get('thumb')
|
||||
|
||||
|
||||
@utils.registerPlexObject
|
||||
class Producer(MediaTag):
|
||||
""" Represents a single Producer media tag.
|
||||
|
|
|
@ -123,6 +123,11 @@ class Video(PlexPartialObject):
|
|||
self._server.query(stream.key, self._server._session.delete)
|
||||
self.reload()
|
||||
|
||||
def posters(self):
|
||||
""" Returns list of available poster objects. :class:`~plexapi.media.Poster`:"""
|
||||
|
||||
return self.fetchItems('%s/posters' % self.key, cls=media.Poster)
|
||||
|
||||
def sync(self, videoQuality, client=None, clientId=None, limit=None, unwatched=False, title=None):
|
||||
""" Add current video (movie, tv-show, season or episode) as sync item for specified device.
|
||||
See :func:`plexapi.myplex.MyPlexAccount.sync()` for possible exceptions.
|
||||
|
|
|
@ -7,7 +7,7 @@ def test_audio_Artist_attr(artist):
|
|||
artist.reload()
|
||||
assert utils.is_datetime(artist.addedAt)
|
||||
assert artist.countries == []
|
||||
assert [i.tag for i in artist.genres] == ['Electronic']
|
||||
assert [i.tag for i in artist.genres] in [[], ['Electronic']]
|
||||
assert utils.is_string(artist.guid, gte=5)
|
||||
assert artist.index == '1'
|
||||
assert utils.is_metadata(artist._initpath)
|
||||
|
@ -79,6 +79,7 @@ def test_audio_Album_attrs(album):
|
|||
assert album.year == 2016
|
||||
assert album.artUrl is None
|
||||
|
||||
|
||||
def test_audio_Album_tracks(album):
|
||||
tracks = album.tracks()
|
||||
track = tracks[0]
|
||||
|
@ -96,7 +97,7 @@ def test_audio_Album_tracks(album):
|
|||
assert utils.is_int(track.parentRatingKey)
|
||||
assert utils.is_metadata(track.parentThumb, contains='/thumb/')
|
||||
assert track.parentTitle == 'Unmastered Impulses'
|
||||
#assert track.ratingCount == 9 # Flaky
|
||||
# assert track.ratingCount == 9 # Flaky
|
||||
assert utils.is_int(track.ratingKey)
|
||||
assert track._server._baseurl == utils.SERVER_BASEURL
|
||||
assert track.summary == ""
|
||||
|
@ -114,7 +115,7 @@ def test_audio_Album_track(album, track=None):
|
|||
# this is not reloaded. its not that much info missing.
|
||||
track = track or album.track('Holy Moment')
|
||||
assert utils.is_datetime(track.addedAt)
|
||||
assert track.duration == 298606
|
||||
assert track.duration in [298605, 298606]
|
||||
assert utils.is_metadata(track.grandparentKey)
|
||||
assert utils.is_int(track.grandparentRatingKey)
|
||||
assert track.grandparentTitle == 'Infinite State'
|
||||
|
@ -147,7 +148,7 @@ def test_audio_Album_track(album, track=None):
|
|||
assert media.audioCodec == 'mp3'
|
||||
assert media.bitrate == 385
|
||||
assert media.container == 'mp3'
|
||||
assert media.duration == 298606
|
||||
assert media.duration in [298605, 298606]
|
||||
assert media.height is None
|
||||
assert utils.is_int(media.id, gte=1)
|
||||
assert utils.is_metadata(media._initpath)
|
||||
|
@ -160,7 +161,7 @@ def test_audio_Album_track(album, track=None):
|
|||
assert media.videoResolution is None
|
||||
assert media.width is None
|
||||
assert part.container == 'mp3'
|
||||
assert part.duration == 298606
|
||||
assert part.duration in [298605, 298606]
|
||||
assert part.file.endswith('.mp3')
|
||||
assert utils.is_int(part.id)
|
||||
assert utils.is_metadata(part._initpath)
|
||||
|
@ -186,7 +187,7 @@ def test_audio_Track_attrs(album):
|
|||
assert utils.is_datetime(track.addedAt)
|
||||
assert track.art is None
|
||||
assert track.chapterSource is None
|
||||
assert track.duration == 298606
|
||||
assert track.duration in [298605, 298606]
|
||||
assert track.grandparentArt is None
|
||||
assert utils.is_metadata(track.grandparentKey)
|
||||
assert utils.is_int(track.grandparentRatingKey)
|
||||
|
@ -232,7 +233,7 @@ def test_audio_Track_attrs(album):
|
|||
assert media.audioCodec == 'mp3'
|
||||
assert media.bitrate == 385
|
||||
assert media.container == 'mp3'
|
||||
assert media.duration == 298606
|
||||
assert media.duration in [298605, 298606]
|
||||
assert media.height is None
|
||||
assert utils.is_int(media.id, gte=1)
|
||||
assert utils.is_metadata(media._initpath)
|
||||
|
@ -245,12 +246,12 @@ def test_audio_Track_attrs(album):
|
|||
assert media.videoResolution is None
|
||||
assert media.width is None
|
||||
assert part.container == 'mp3'
|
||||
assert part.duration == 298606
|
||||
assert part.duration in [298605, 298606]
|
||||
assert part.file.endswith('.mp3')
|
||||
assert utils.is_int(part.id)
|
||||
assert utils.is_metadata(part._initpath)
|
||||
assert utils.is_part(part.key)
|
||||
#assert part.media == <Media:Holy.Moment>
|
||||
# assert part.media == <Media:Holy.Moment>
|
||||
assert part._server._baseurl == utils.SERVER_BASEURL
|
||||
assert part.size == 14360402
|
||||
# Assign 0 part.streams
|
||||
|
@ -269,7 +270,7 @@ def test_audio_Track_attrs(album):
|
|||
assert utils.is_metadata(stream._initpath)
|
||||
assert stream.language is None
|
||||
assert stream.languageCode is None
|
||||
#assert stream.part == <MediaPart:22>
|
||||
# assert stream.part == <MediaPart:22>
|
||||
assert stream.samplingRate == 44100
|
||||
assert stream.selected is True
|
||||
assert stream._server._baseurl == utils.SERVER_BASEURL
|
||||
|
|
|
@ -125,6 +125,7 @@ def test_video_Movie_attrs(movies):
|
|||
assert [i.tag for i in movie.directors] == ['Nina Paley']
|
||||
assert movie.duration >= 160000
|
||||
assert movie.fields == []
|
||||
assert movie.posters()
|
||||
assert sorted([i.tag for i in movie.genres]) == ['Animation', 'Comedy', 'Fantasy', 'Musical', 'Romance']
|
||||
assert movie.guid == 'com.plexapp.agents.imdb://tt1172203?lang=en'
|
||||
assert utils.is_metadata(movie._initpath)
|
||||
|
@ -239,6 +240,8 @@ def test_video_Movie_attrs(movies):
|
|||
assert len(part.key) >= 10
|
||||
assert part._server._baseurl == utils.SERVER_BASEURL
|
||||
assert utils.is_int(part.size, gte=1000000)
|
||||
assert part.exists
|
||||
assert part.accessible
|
||||
# Stream 1
|
||||
stream1 = part.streams[0]
|
||||
assert stream1.bitDepth in (8, None)
|
||||
|
@ -530,6 +533,8 @@ def test_video_Episode_attrs(episode):
|
|||
assert len(part.key) >= 10
|
||||
assert part._server._baseurl == utils.SERVER_BASEURL
|
||||
assert utils.is_int(part.size, gte=18184197)
|
||||
assert part.exists
|
||||
assert part.accessible
|
||||
|
||||
|
||||
def test_video_Season(show):
|
||||
|
@ -638,3 +643,17 @@ def test_that_reload_return_the_same_object(plex):
|
|||
episode_section_get_key = episode_section_get.key
|
||||
assert episode_library_search_key == episode_library_search.reload().key == episode_search_key == episode_search.reload().key == episode_section_get_key == episode_section_get.reload().key # noqa
|
||||
|
||||
|
||||
def test_video_exists_accessible(movie, episode):
|
||||
assert movie.media[0].parts[0].exists is None
|
||||
assert movie.media[0].parts[0].accessible is None
|
||||
movie.reload()
|
||||
assert movie.media[0].parts[0].exists is True
|
||||
assert movie.media[0].parts[0].accessible is True
|
||||
|
||||
assert episode.media[0].parts[0].exists is None
|
||||
assert episode.media[0].parts[0].accessible is None
|
||||
episode.reload()
|
||||
assert episode.media[0].parts[0].exists is True
|
||||
assert episode.media[0].parts[0].accessible is True
|
||||
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
""" The script is used to bootstrap a docker container with Plex and with all the libraries required for testing.
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
The script is used to bootstrap a docker container with Plex and with
|
||||
all the libraries required for testing.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import plexapi
|
||||
import socket
|
||||
import time
|
||||
import zipfile
|
||||
from glob import glob
|
||||
from shutil import copyfile, rmtree
|
||||
from subprocess import call
|
||||
from time import time, sleep
|
||||
from uuid import uuid4
|
||||
from tqdm import tqdm
|
||||
|
||||
import plexapi
|
||||
from uuid import uuid4
|
||||
from plexapi.compat import which, makedirs
|
||||
from plexapi.exceptions import BadRequest, NotFound
|
||||
from plexapi.myplex import MyPlexAccount
|
||||
|
@ -42,23 +46,61 @@ DOCKER_CMD = [
|
|||
]
|
||||
|
||||
|
||||
def get_ips():
|
||||
import socket
|
||||
return list(set([i[4][0] for i in socket.getaddrinfo(socket.gethostname(), None)
|
||||
if i[4][0] not in ('127.0.0.1', '::1') and not i[4][0].startswith('fe80:')]))
|
||||
def get_default_ip():
|
||||
""" Return the first IP address of the current machine if available. """
|
||||
available_ips = list(set([i[4][0] for i in socket.getaddrinfo(socket.gethostname(), None)
|
||||
if i[4][0] not in ('127.0.0.1', '::1') and not i[4][0].startswith('fe80:')]))
|
||||
return available_ips[0] if len(available_ips) else None
|
||||
|
||||
|
||||
def create_section(server, section):
|
||||
def get_plex_account(opts):
|
||||
""" Authenitcate with Plex using the command line options. """
|
||||
if not opts.unclaimed:
|
||||
if opts.token:
|
||||
return MyPlexAccount(token=opts.token)
|
||||
return plexapi.utils.getMyPlexAccount(opts)
|
||||
return None
|
||||
|
||||
|
||||
def get_movie_path(name, year):
|
||||
""" Return a movie path given its title and year. """
|
||||
return os.path.join(movies_path, '%s (%d).mp4' % (name, year))
|
||||
|
||||
|
||||
def get_tvshow_path(name, season, episode):
|
||||
""" Return a TV show path given its title, season, and episode. """
|
||||
return os.path.join(tvshows_path, name, 'S%02dE%02d.mp4' % (season, episode))
|
||||
|
||||
|
||||
def add_library_section(server, section):
|
||||
""" Add the specified section to our Plex instance. This tends to be a bit
|
||||
flaky, so we retry a few times here.
|
||||
"""
|
||||
start = time.time()
|
||||
runtime = 0
|
||||
while runtime < 60:
|
||||
try:
|
||||
server.library.add(**section)
|
||||
return True
|
||||
except BadRequest as err:
|
||||
if 'server is still starting up. Please retry later' in str(err):
|
||||
time.sleep(1)
|
||||
continue
|
||||
raise
|
||||
runtime = time.time() - start
|
||||
raise SystemExit('Timeout adding section to Plex instance.')
|
||||
|
||||
|
||||
def create_section(server, section, opts):
|
||||
processed_media = 0
|
||||
expected_media_count = section.pop('expected_media_count', 0)
|
||||
|
||||
expected_media_type = (section['type'], )
|
||||
if section['type'] == 'artist':
|
||||
expected_media_type = ('artist', 'album', 'track')
|
||||
|
||||
expected_media_type = tuple(SEARCHTYPES[t] for t in expected_media_type)
|
||||
|
||||
def alert_callback(data):
|
||||
""" Listen to the Plex notifier to determine when metadata scanning is complete. """
|
||||
global processed_media
|
||||
if data['type'] == 'timeline':
|
||||
for entry in data['TimelineEntry']:
|
||||
|
@ -68,101 +110,67 @@ def create_section(server, section):
|
|||
# state=5 means record processed, applicable only when metadata source was set
|
||||
if entry['state'] == 5:
|
||||
cnt = 1
|
||||
|
||||
# Workaround for old Plex versions which not reports individual episodes' progress
|
||||
if entry['type'] == SEARCHTYPES['show']:
|
||||
show = server.library.sectionByID(str(entry['sectionID'])).get(entry['title'])
|
||||
cnt = show.leafCount
|
||||
bar.update(cnt)
|
||||
|
||||
processed_media += cnt
|
||||
# state=1 means record processed, when no metadata source was set
|
||||
elif entry['state'] == 1 and entry['type'] == SEARCHTYPES['photo']:
|
||||
bar.update()
|
||||
processed_media += 1
|
||||
|
||||
runtime = 0
|
||||
start = time.time()
|
||||
bar = tqdm(desc='Scanning section ' + section['name'], total=expected_media_count)
|
||||
notifier = server.startAlertListener(alert_callback)
|
||||
|
||||
# I don't know how to determinate of plex successfully started, so let's do it in creepy way
|
||||
success = False
|
||||
start_time = time()
|
||||
while not success and (time() - start_time < opts.bootstrap_timeout):
|
||||
try:
|
||||
server.library.add(**section)
|
||||
success = True
|
||||
except BadRequest as e:
|
||||
if 'the server is still starting up. Please retry later' in str(e):
|
||||
sleep(1)
|
||||
else:
|
||||
raise
|
||||
|
||||
if not success:
|
||||
print('Something went wrong :(')
|
||||
exit(1)
|
||||
|
||||
add_library_section(server, section)
|
||||
while bar.n < bar.total:
|
||||
if time() - start_time >= opts.bootstrap_timeout:
|
||||
print('Metadata scan takes too long, probably something went really wrong')
|
||||
exit(1)
|
||||
sleep(3)
|
||||
|
||||
if runtime >= 120:
|
||||
print('Metadata scan taking too long, but will continue anyway..')
|
||||
break
|
||||
time.sleep(3)
|
||||
runtime = time.time() - start
|
||||
bar.close()
|
||||
|
||||
notifier.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if which('docker') is None:
|
||||
print('Docker is required to be available')
|
||||
exit(1)
|
||||
|
||||
default_ip = None
|
||||
available_ips = get_ips()
|
||||
if len(available_ips) > 0:
|
||||
default_ip = available_ips[0]
|
||||
|
||||
default_ip = get_default_ip()
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
|
||||
# Authentication arguments
|
||||
mg = parser.add_mutually_exclusive_group()
|
||||
|
||||
g = mg.add_argument_group()
|
||||
g.add_argument('--username', help='Your Plex username')
|
||||
g.add_argument('--password', help='Your Plex password')
|
||||
mg.add_argument('--token', help='Plex.tv authentication token', default=plexapi.CONFIG.get('auth.server_token'))
|
||||
mg.add_argument('--unclaimed', help='Do not claim the server', default=False, action='store_true')
|
||||
|
||||
parser.add_argument('--timezone', help='Timezone to set inside plex', default='UTC')
|
||||
parser.add_argument('--destination', help='Local path where to store all the media',
|
||||
default=os.path.join(os.getcwd(), 'plex'))
|
||||
parser.add_argument('--advertise-ip', help='IP address which should be advertised by new Plex instance',
|
||||
required=default_ip is None, default=default_ip)
|
||||
parser.add_argument('--docker-tag', help='Docker image tag to install', default='latest')
|
||||
parser.add_argument('--bootstrap-timeout', help='Timeout for each step of bootstrap, in seconds (default: '
|
||||
'%(default)s)',
|
||||
default=180, type=int)
|
||||
parser.add_argument('--server-name', help='Name for the new server', default='plex-test-docker-%s' % str(uuid4()))
|
||||
parser.add_argument('--accept-eula', help='Accept Plex`s EULA', default=False, action='store_true')
|
||||
parser.add_argument('--without-movies', help='Do not create Movies section', default=True, dest='with_movies',
|
||||
action='store_false')
|
||||
parser.add_argument('--without-shows', help='Do not create TV Shows section', default=True, dest='with_shows',
|
||||
action='store_false')
|
||||
parser.add_argument('--without-music', help='Do not create Music section', default=True, dest='with_music',
|
||||
action='store_false')
|
||||
parser.add_argument('--without-photos', help='Do not create Photos section', default=True, dest='with_photos',
|
||||
action='store_false')
|
||||
parser.add_argument('--show-token', help='Display access token after bootstrap', default=False, action='store_true')
|
||||
# Test environment arguments
|
||||
parser.add_argument('--timezone', help='Timezone to set inside plex', default='UTC') # noqa
|
||||
parser.add_argument('--destination', help='Local path where to store all the media', default=os.path.join(os.getcwd(), 'plex')) # noqa
|
||||
parser.add_argument('--advertise-ip', help='IP address which should be advertised by new Plex instance', required=default_ip is None, default=default_ip) # noqa
|
||||
parser.add_argument('--docker-tag', help='Docker image tag to install', default='latest') # noqa
|
||||
parser.add_argument('--bootstrap-timeout', help='Timeout for each step of bootstrap, in seconds (default: %(default)s)', default=180, type=int) # noqa
|
||||
parser.add_argument('--server-name', help='Name for the new server', default='plex-test-docker-%s' % str(uuid4())) # noqa
|
||||
parser.add_argument('--accept-eula', help='Accept Plex`s EULA', default=False, action='store_true') # noqa
|
||||
parser.add_argument('--without-movies', help='Do not create Movies section', default=True, dest='with_movies', action='store_false') # noqa
|
||||
parser.add_argument('--without-shows', help='Do not create TV Shows section', default=True, dest='with_shows', action='store_false') # noqa
|
||||
parser.add_argument('--without-music', help='Do not create Music section', default=True, dest='with_music', action='store_false') # noqa
|
||||
parser.add_argument('--without-photos', help='Do not create Photos section', default=True, dest='with_photos', action='store_false') # noqa
|
||||
parser.add_argument('--show-token', help='Display access token after bootstrap', default=False, action='store_true') # noqa
|
||||
opts = parser.parse_args()
|
||||
print('I`m going to create a plex instance named %s with advertised ip "%s", be prepared!' % (opts.server_name,
|
||||
opts.advertise_ip))
|
||||
|
||||
# Download the Plex Docker image
|
||||
print('Creating Plex instance named %s with advertised ip %s' % (opts.server_name, opts.advertise_ip))
|
||||
if which('docker') is None:
|
||||
print('Docker is required to be available')
|
||||
exit(1)
|
||||
if call(['docker', 'pull', 'plexinc/pms-docker:%s' % opts.docker_tag]) != 0:
|
||||
print('Got an error when executing docker pull!')
|
||||
exit(1)
|
||||
|
||||
account = None
|
||||
if not opts.unclaimed:
|
||||
if opts.token:
|
||||
account = MyPlexAccount(token=opts.token)
|
||||
else:
|
||||
account = plexapi.utils.getMyPlexAccount(opts)
|
||||
# Start the Plex Docker container
|
||||
account = get_plex_account(opts)
|
||||
path = os.path.realpath(os.path.expanduser(opts.destination))
|
||||
makedirs(os.path.join(path, 'media'), exist_ok=True)
|
||||
arg_bindings = {
|
||||
|
@ -174,87 +182,71 @@ if __name__ == '__main__':
|
|||
'image_tag': opts.docker_tag,
|
||||
'container_name_extra': '' if account else 'unclaimed-'
|
||||
}
|
||||
|
||||
docker_cmd = [c % arg_bindings for c in DOCKER_CMD]
|
||||
exit_code = call(docker_cmd)
|
||||
|
||||
if exit_code != 0:
|
||||
exit(exit_code)
|
||||
raise SystemExit('Error %s while starting the Plex docker container' % exit_code)
|
||||
|
||||
print('Let`s wait while the instance boots...')
|
||||
start_time = time()
|
||||
# Wait for the Plex container to start
|
||||
print('Waiting for the Plex container to start..')
|
||||
start = time.time()
|
||||
runtime = 0
|
||||
server = None
|
||||
while not server and (time() - start_time < opts.bootstrap_timeout):
|
||||
while not server and (runtime < opts.bootstrap_timeout):
|
||||
try:
|
||||
if account:
|
||||
device = account.device(opts.server_name)
|
||||
server = device.connect()
|
||||
server = account.device(opts.server_name).connect()
|
||||
else:
|
||||
server = PlexServer('http://%s:32400' % opts.advertise_ip)
|
||||
if opts.accept_eula:
|
||||
server.settings.get('acceptedEULA').set(True)
|
||||
server.settings.save()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
sleep(1)
|
||||
|
||||
except Exception as err:
|
||||
print(err)
|
||||
time.sleep(1)
|
||||
runtime = time.time() - start
|
||||
if not server:
|
||||
print('Server didn`t appeared in your account after a lot of time, I have no idea what to do :( Dig into '
|
||||
'docker logs, check your internet connection, do something!')
|
||||
exit(1)
|
||||
|
||||
print('Ok, I got the server instance, let`s download what you`re missing')
|
||||
|
||||
def get_tvshow_path(name, season, episode):
|
||||
return os.path.join(tvshows_path, name, 'S%02dE%02d.mp4' % (season, episode))
|
||||
raise SystemExit('Server didnt appear in your account after %ss' % opts.bootstrap_timeout)
|
||||
print('Plex container started after %ss, downloading content' % int(runtime))
|
||||
|
||||
# Download video_stub.mp4
|
||||
print('Downloading video_stub.mp4..')
|
||||
if opts.with_movies or opts.with_shows:
|
||||
def get_movie_path(name, year):
|
||||
return os.path.join(movies_path, '%s (%d).mp4' % (name, year))
|
||||
|
||||
media_stub_path = os.path.join(path, 'media', 'video_stub.mp4')
|
||||
if not os.path.isfile(media_stub_path):
|
||||
download('http://www.mytvtestpatterns.com/mytvtestpatterns/Default/GetFile?p=PhilipsCircleMP4', '',
|
||||
filename='video_stub.mp4', savepath=os.path.join(path, 'media'), showstatus=True)
|
||||
filename='video_stub.mp4', savepath=os.path.join(path, 'media'), showstatus=True)
|
||||
|
||||
sections = []
|
||||
# Prepare Movies section
|
||||
if opts.with_movies:
|
||||
print('Preparing movie section..')
|
||||
movies_path = os.path.join(path, 'media', 'Movies')
|
||||
makedirs(movies_path, exist_ok=True)
|
||||
|
||||
required_movies = {
|
||||
'Elephants Dream': 2006,
|
||||
'Sita Sings the Blues': 2008,
|
||||
'Big Buck Bunny': 2008,
|
||||
'Sintel': 2010,
|
||||
}
|
||||
|
||||
expected_media_count = 0
|
||||
for name, year in required_movies.items():
|
||||
expected_media_count += 1
|
||||
if not os.path.isfile(get_movie_path(name, year)):
|
||||
copyfile(media_stub_path, get_movie_path(name, year))
|
||||
|
||||
print('Finished with movies...')
|
||||
sections.append(dict(name='Movies', type='movie', location='/data/Movies', agent='com.plexapp.agents.imdb',
|
||||
scanner='Plex Movie Scanner', expected_media_count=expected_media_count))
|
||||
scanner='Plex Movie Scanner', expected_media_count=expected_media_count))
|
||||
|
||||
# Prepare TV Show section
|
||||
if opts.with_shows:
|
||||
print('Preparing TV-Shows section..')
|
||||
tvshows_path = os.path.join(path, 'media', 'TV-Shows')
|
||||
makedirs(os.path.join(tvshows_path, 'Game of Thrones'), exist_ok=True)
|
||||
makedirs(os.path.join(tvshows_path, 'The 100'), exist_ok=True)
|
||||
|
||||
required_tv_shows = {
|
||||
'Game of Thrones': [
|
||||
list(range(1, 11)),
|
||||
list(range(1, 11)),
|
||||
],
|
||||
'The 100': [
|
||||
list(range(1, 14)),
|
||||
list(range(1, 17)),
|
||||
]
|
||||
'Game of Thrones': [list(range(1, 11)), list(range(1, 11))],
|
||||
'The 100': [list(range(1, 14)), list(range(1, 17))]
|
||||
}
|
||||
|
||||
expected_media_count = 0
|
||||
for show_name, seasons in required_tv_shows.items():
|
||||
for season_id, episodes in enumerate(seasons, start=1):
|
||||
|
@ -263,62 +255,56 @@ if __name__ == '__main__':
|
|||
episode_path = get_tvshow_path(show_name, season_id, episode_id)
|
||||
if not os.path.isfile(episode_path):
|
||||
copyfile(get_movie_path('Sintel', 2010), episode_path)
|
||||
sections.append(dict(name='TV Shows', type='show', location='/data/TV-Shows',
|
||||
agent='com.plexapp.agents.thetvdb', scanner='Plex Series Scanner',
|
||||
expected_media_count=expected_media_count))
|
||||
|
||||
print('Finished with TV Shows...')
|
||||
sections.append(dict(name='TV Shows', type='show', location='/data/TV-Shows', agent='com.plexapp.agents.thetvdb',
|
||||
scanner='Plex Series Scanner', expected_media_count=expected_media_count))
|
||||
|
||||
# Prepare Music section
|
||||
if opts.with_music:
|
||||
print('Preparing Music section..')
|
||||
music_path = os.path.join(path, 'media', 'Music')
|
||||
makedirs(music_path, exist_ok=True)
|
||||
expected_media_count = 0
|
||||
|
||||
artist_dst = os.path.join(music_path, 'Infinite State')
|
||||
dest_path = os.path.join(artist_dst, 'Unmastered Impulses')
|
||||
if not os.path.isdir(dest_path):
|
||||
zip_path = os.path.join(artist_dst, 'Unmastered Impulses.zip')
|
||||
if os.path.isfile(zip_path):
|
||||
import zipfile
|
||||
with zipfile.ZipFile(zip_path, 'r') as handle:
|
||||
handle.extractall(artist_dst)
|
||||
else:
|
||||
download('https://github.com/kennethreitz/unmastered-impulses/archive/master.zip', '',
|
||||
filename='Unmastered Impulses.zip', savepath=artist_dst, unpack=True, showstatus=True)
|
||||
filename='Unmastered Impulses.zip', savepath=artist_dst, unpack=True, showstatus=True)
|
||||
os.rename(os.path.join(artist_dst, 'unmastered-impulses-master', 'mp3'), dest_path)
|
||||
rmtree(os.path.join(artist_dst, 'unmastered-impulses-master'))
|
||||
|
||||
expected_media_count += len(glob(os.path.join(dest_path, '*.mp3'))) + 2 # wait for artist & album
|
||||
|
||||
artist_dst = os.path.join(music_path, 'Broke For Free')
|
||||
dest_path = os.path.join(artist_dst, 'Layers')
|
||||
if not os.path.isdir(dest_path):
|
||||
zip_path = os.path.join(artist_dst, 'Layers.zip')
|
||||
if not os.path.isfile(zip_path):
|
||||
download('https://archive.org/compress/Layers-11520/formats=VBR%20MP3&file=/Layers-11520.zip', '',
|
||||
filename='Layers.zip', savepath=artist_dst, showstatus=True)
|
||||
filename='Layers.zip', savepath=artist_dst, showstatus=True)
|
||||
makedirs(dest_path, exist_ok=True)
|
||||
import zipfile
|
||||
with zipfile.ZipFile(zip_path, 'r') as handle:
|
||||
handle.extractall(dest_path)
|
||||
|
||||
expected_media_count += len(glob(os.path.join(dest_path, '*.mp3'))) + 2 # wait for artist & album
|
||||
sections.append(dict(name='Music', type='artist', location='/data/Music',
|
||||
agent='com.plexapp.agents.lastfm', scanner='Plex Music Scanner',
|
||||
expected_media_count=expected_media_count))
|
||||
|
||||
print('Finished with Music...')
|
||||
sections.append(dict(name='Music', type='artist', location='/data/Music', agent='com.plexapp.agents.lastfm',
|
||||
scanner='Plex Music Scanner', expected_media_count=expected_media_count))
|
||||
|
||||
# Prepare Photos section
|
||||
if opts.with_photos:
|
||||
print('Preparing Photos section..')
|
||||
photos_path = os.path.join(path, 'media', 'Photos')
|
||||
makedirs(photos_path, exist_ok=True)
|
||||
expected_photo_count = 0
|
||||
|
||||
folders = {
|
||||
('Cats', ): 3,
|
||||
('Cats', 'Cats in bed'): 7,
|
||||
('Cats', 'Cats not in bed'): 1,
|
||||
('Cats', 'Not cats in bed'): 1,
|
||||
}
|
||||
|
||||
has_photos = 0
|
||||
for folder_path, required_cnt in folders.items():
|
||||
folder_path = os.path.join(photos_path, *folder_path)
|
||||
|
@ -326,30 +312,30 @@ if __name__ == '__main__':
|
|||
while photos_in_folder < required_cnt:
|
||||
photos_in_folder += 1
|
||||
download('https://picsum.photos/800/600/?random', '',
|
||||
filename='photo%d.jpg' % photos_in_folder, savepath=folder_path)
|
||||
filename='photo%d.jpg' % photos_in_folder, savepath=folder_path)
|
||||
has_photos += photos_in_folder
|
||||
sections.append(dict(name='Photos', type='photo', location='/data/Photos',
|
||||
agent='com.plexapp.agents.none', scanner='Plex Photo Scanner',
|
||||
expected_media_count=has_photos))
|
||||
|
||||
print('Finished with photos...')
|
||||
sections.append(dict(name='Photos', type='photo', location='/data/Photos', agent='com.plexapp.agents.none',
|
||||
scanner='Plex Photo Scanner', expected_media_count=has_photos))
|
||||
|
||||
# Create the Plex library in our instance
|
||||
if sections:
|
||||
print('Ok, got the media, it`s time to create a library for you!')
|
||||
|
||||
print('Creating the Plex libraries in our instance')
|
||||
for section in sections:
|
||||
create_section(server, section)
|
||||
create_section(server, section, opts)
|
||||
|
||||
# Share this instance with the specified username
|
||||
if account:
|
||||
shared_username = os.environ.get('SHARED_USERNAME', 'PKKid')
|
||||
try:
|
||||
user = account.user(shared_username)
|
||||
account.updateFriend(user, server)
|
||||
print('The server was shared with user "%s"' % shared_username)
|
||||
print('The server was shared with user %s' % shared_username)
|
||||
except NotFound:
|
||||
pass
|
||||
|
||||
# Finished: Display our Plex details
|
||||
print('Base URL is %s' % server.url('', False))
|
||||
if account and opts.show_token:
|
||||
print('Auth token is %s' % account.authenticationToken)
|
||||
|
||||
print('Server %s is ready to use!' % opts.server_name)
|
||||
|
|
Loading…
Reference in a new issue