def main(): """Main script""" plex = PlexServer(PLEX_URL, PLEX_TOKEN) plex_users = get_user_tokens(plex.machineIdentifier) plex_playlists = {playlist.title: playlist.items() for playlist in plex.playlists()} for playlist in PLAYLISTS: playlist_items = plex_playlists.get(playlist) if not playlist_items: print("Playlist '{playlist}' not found on the server. Skipping.".format(playlist=playlist)) continue print("Cloning the '{title}' playlist...".format(title=playlist)) for user in USERS: user_token = plex_users.get(user) if not user_token: print("...User '{user}' not found in shared users. Skipping.".format(user=user)) continue user_plex = PlexServer(PLEX_URL, user_token) # Delete the old playlist try: user_playlist = user_plex.playlist(playlist) user_playlist.delete() except: pass # Create a new playlist user_plex.createPlaylist(playlist, playlist_items) print("...Created playlist for '{user}'.".format(user=user)) return
def change_playlist_content(plex: PlexServer, name: str, tracks: List[Track]) -> Playlist: """ Connects to the PlexServer, and fills the named playlist with the given track list. If the playlist does not yet exist, it will be created. :param plex: the PlexServer object :param name: the name of the playlist to replace the contents of :param tracks: list of Tracks that will be the contents of the playlist :return: the Playlist object """ timer = Stopwatch() timer.start() logger = config.logger if not any([name == pl.title for pl in plex.playlists()]): playlist = plex.createPlaylist(name, tracks) logger.debug(f"Created new playlist {name} in {timer.click():.2f}s") else: playlist = plex.playlist(name) [playlist.removeItem(item) for item in playlist.items()] logger.debug(f"Emptied playlist {name} in {timer.click():.2f}s") playlist.addItems(tracks) logger.debug( f"Added {len(tracks)} track to the playlist {name} in {timer.click():.2f}s" ) return playlist
def main(): """Main script""" global PLAYLISTS plex_server = PlexServer(PLEX_URL, PLEX_TOKEN) plex_users = get_user_tokens(plex_server.machineIdentifier) plex_users[plex_server.myPlexUsername] = plex_server._token plex_user = PlexServer(PLEX_URL, plex_users[FROM_USER]) plex_playlists = { playlist.title: playlist.items() for playlist in plex_user.playlists() } if not PLAYLISTS: PLAYLISTS = plex_playlists # Default to all playlists for playlist in PLAYLISTS: if playlist in SKIP_PLAYLISTS: print("Skipping '{playlist}'...".format(playlist=playlist)) continue playlist_items = plex_playlists.get(playlist) if not playlist_items: print("Playlist '{playlist}' not found on the server. Skipping.". format(playlist=playlist)) continue print("Cloning the '{title}' playlist...".format(title=playlist)) for user in TO_USERS: user_token = plex_users.get(user) if not user_token: print("...User '{user}' not found in shared users. Skipping.". format(user=user)) continue user_plex = PlexServer(PLEX_URL, user_token) # Delete the old playlist try: user_playlist = user_plex.playlist(playlist) user_playlist.delete() except: pass # Create a new playlist user_plex.createPlaylist(playlist, playlist_items) print("...Created playlist for '{user}'.".format(user=user)) return
def test_server_Server_session(): from requests import Session from plexapi.server import PlexServer class MySession(Session): def __init__(self): super(self.__class__, self).__init__() self.plexapi_session_test = True plex = PlexServer('http://138.68.157.5:32400', os.environ.get('PLEX_TEST_TOKEN'), session=MySession()) assert hasattr(plex.session, 'plexapi_session_test') pl = plex.playlists() assert hasattr(pl[0].server.session, 'plexapi_session_test')
def create_playlists(): plex = PlexServer(BASEURL, TOKEN) plex_scenes = {} plex_playlists = {} for library_name in LIBRARIES: plex_library = plex.library.section(library_name) plex_playlists[plex_library] = [ playlist for playlist in plex.playlists() if playlist.smart and playlist.items() and playlist.items()[0].section() == plex_library ] plex_scenes[plex_library] = plex_library.all() plex_actors = {} for library in plex_scenes: plex_actors[library] = [] for scene in plex_scenes[library]: for actor in scene.actors: actor_name = actor.tag if actor_name and actor_name not in plex_actors[library]: plex_actors[library].append(actor_name) for library in plex_scenes: playlist_titles = [ playlist.title for playlist in plex_playlists[library] ] for actor_name in plex_actors[library]: if actor_name not in playlist_titles: Playlist.create(plex, title=actor_name, section=library, smart=True, actor=actor_name)
class Plex: def __init__(self, url, token, server_name): self.url = url self.token = token self.server_name = server_name self.server = PlexServer(url, token) def get_users(self): return self.server.myPlexAccount().users() def user_has_server_access(self, user): for s in user.servers: if s.name == self.server_name: return True return False def get_playlists(self): return self.server.playlists() def get_library_sections(self): return self.server.library.sections() def get_all_section_items(self, section): return section.all()
class PlexAttributes(): def __init__(self, opts): self.opts = opts # command line options self.clsnames = [c for c in opts.clsnames.split(',') if c] # list of clsnames to report (blank=all) self.account = MyPlexAccount() # MyPlexAccount instance self.plex = PlexServer() # PlexServer instance self.total = 0 # Total objects parsed self.attrs = defaultdict(dict) # Attrs result set def run(self): starttime = time.time() self._parse_myplex() self._parse_server() self._parse_search() self._parse_library() self._parse_audio() self._parse_photo() self._parse_movie() self._parse_show() self._parse_client() self._parse_playlist() self._parse_sync() self.runtime = round((time.time() - starttime) / 60.0, 1) return self def _parse_myplex(self): self._load_attrs(self.account, 'myplex') self._load_attrs(self.account.devices(), 'myplex') for resource in self.account.resources(): self._load_attrs(resource, 'myplex') self._load_attrs(resource.connections, 'myplex') self._load_attrs(self.account.users(), 'myplex') def _parse_server(self): self._load_attrs(self.plex, 'serv') self._load_attrs(self.plex.account(), 'serv') self._load_attrs(self.plex.history()[:50], 'hist') self._load_attrs(self.plex.history()[50:], 'hist') self._load_attrs(self.plex.sessions(), 'sess') def _parse_search(self): for search in ('cre', 'ani', 'mik', 'she', 'bea'): self._load_attrs(self.plex.search(search), 'hub') def _parse_library(self): cat = 'lib' self._load_attrs(self.plex.library, cat) # self._load_attrs(self.plex.library.all()[:50], 'all') self._load_attrs(self.plex.library.onDeck()[:50], 'deck') self._load_attrs(self.plex.library.recentlyAdded()[:50], 'add') for search in ('cat', 'dog', 'rat', 'gir', 'mou'): self._load_attrs(self.plex.library.search(search)[:50], 'srch') # TODO: Implement section search (remove library search?) # TODO: Implement section search filters def _parse_audio(self): cat = 'lib' for musicsection in self.plex.library.sections(): if musicsection.TYPE == library.MusicSection.TYPE: self._load_attrs(musicsection, cat) for artist in musicsection.all(): self._load_attrs(artist, cat) for album in artist.albums(): self._load_attrs(album, cat) for track in album.tracks(): self._load_attrs(track, cat) def _parse_photo(self): cat = 'lib' for photosection in self.plex.library.sections(): if photosection.TYPE == library.PhotoSection.TYPE: self._load_attrs(photosection, cat) for photoalbum in photosection.all(): self._load_attrs(photoalbum, cat) for photo in photoalbum.photos(): self._load_attrs(photo, cat) def _parse_movie(self): cat = 'lib' for moviesection in self.plex.library.sections(): if moviesection.TYPE == library.MovieSection.TYPE: self._load_attrs(moviesection, cat) for movie in moviesection.all(): self._load_attrs(movie, cat) def _parse_show(self): cat = 'lib' for showsection in self.plex.library.sections(): if showsection.TYPE == library.ShowSection.TYPE: self._load_attrs(showsection, cat) for show in showsection.all(): self._load_attrs(show, cat) for season in show.seasons(): self._load_attrs(season, cat) for episode in season.episodes(): self._load_attrs(episode, cat) def _parse_client(self): for device in self.account.devices(): client = self._safe_connect(device) if client is not None: self._load_attrs(client, 'myplex') for client in self.plex.clients(): self._safe_connect(client) self._load_attrs(client, 'client') def _parse_playlist(self): for playlist in self.plex.playlists(): self._load_attrs(playlist, 'pl') for item in playlist.items(): self._load_attrs(item, 'pl') playqueue = PlayQueue.create(self.plex, playlist) self._load_attrs(playqueue, 'pq') def _parse_sync(self): # TODO: Get plexattrs._parse_sync() working. pass def _load_attrs(self, obj, cat=None): if isinstance(obj, (list, tuple)): return [self._parse_objects(item, cat) for item in obj] self._parse_objects(obj, cat) def _parse_objects(self, obj, cat=None): clsname = '%s.%s' % (obj.__module__, obj.__class__.__name__) clsname = clsname.replace('plexapi.', '') if self.clsnames and clsname not in self.clsnames: return None self._print_the_little_dot() if clsname not in self.attrs: self.attrs[clsname] = copy.deepcopy(NAMESPACE) self.attrs[clsname]['total'] += 1 self._load_xml_attrs(clsname, obj._data, self.attrs[clsname]['xml'], self.attrs[clsname]['examples'], self.attrs[clsname]['categories'], cat) self._load_obj_attrs(clsname, obj, self.attrs[clsname]['obj'], self.attrs[clsname]['docs']) def _print_the_little_dot(self): self.total += 1 if not self.total % 100: sys.stdout.write('.') if not self.total % 8000: sys.stdout.write('\n') sys.stdout.flush() def _load_xml_attrs(self, clsname, elem, attrs, examples, categories, cat): if elem is None: return None for attr in sorted(elem.attrib.keys()): attrs[attr] += 1 if cat: categories[attr].add(cat) if elem.attrib[attr] and len(examples[attr]) <= self.opts.examples: examples[attr].add(elem.attrib[attr]) for subelem in elem: attrname = TAGATTRS.get(subelem.tag, '%ss' % subelem.tag.lower()) attrs['%s[]' % attrname] += 1 def _load_obj_attrs(self, clsname, obj, attrs, docs): if clsname in STOP_RECURSING_AT: return None if isinstance(obj, PlexObject) and clsname not in DONT_RELOAD: self._safe_reload(obj) alldocs = '\n\n'.join(self._all_docs(obj.__class__)) for attr, value in obj.__dict__.items(): if value is None or isinstance(value, (str, bool, float, int, datetime)): if not attr.startswith('_') and attr not in IGNORES.get( clsname, []): attrs[attr] += 1 if re.search('\s{8}%s\s\(.+?\)\:' % attr, alldocs) is not None: docs[attr] += 1 if isinstance(value, list): if not attr.startswith('_') and attr not in IGNORES.get( clsname, []): if value and isinstance(value[0], PlexObject): attrs['%s[]' % attr] += 1 [self._parse_objects(obj) for obj in value] def _all_docs(self, cls, docs=None): import inspect docs = docs or [] if cls.__doc__ is not None: docs.append(cls.__doc__) for parent in inspect.getmro(cls): if parent != cls: docs += self._all_docs(parent) return docs def print_report(self): total_attrs = 0 for clsname in sorted(self.attrs.keys()): if self._clsname_match(clsname): meta = self.attrs[clsname] count = meta['total'] print(_('\n%s (%s)\n%s' % (clsname, count, '-' * 30), 'yellow')) attrs = sorted( set(list(meta['xml'].keys()) + list(meta['obj'].keys()))) for attr in attrs: state = self._attr_state(clsname, attr, meta) count = meta['xml'].get(attr, 0) categories = ','.join(meta['categories'].get(attr, ['--'])) examples = '; '.join( list(meta['examples'].get(attr, ['--']))[:3])[:80] print('%7s %3s %-30s %-20s %s' % (count, state, attr, categories, examples)) total_attrs += count print(_('\nSUMMARY\n%s' % ('-' * 30), 'yellow')) print('%7s %3s %3s %3s %-20s %s' % ('total', 'new', 'old', 'doc', 'categories', 'clsname')) for clsname in sorted(self.attrs.keys()): if self._clsname_match(clsname): print('%7s %12s %12s %12s %s' % (self.attrs[clsname]['total'], _(self.attrs[clsname]['new'] or '', 'cyan'), _(self.attrs[clsname]['old'] or '', 'red'), _(self.attrs[clsname]['doc'] or '', 'purple'), clsname)) print('\nPlex Version %s' % self.plex.version) print('PlexAPI Version %s' % plexapi.VERSION) print('Total Objects %s' % sum([x['total'] for x in self.attrs.values()])) print('Runtime %s min\n' % self.runtime) def _clsname_match(self, clsname): if not self.clsnames: return True for cname in self.clsnames: if cname.lower() in clsname.lower(): return True return False def _attr_state(self, clsname, attr, meta): if attr in meta['xml'].keys() and attr not in meta['obj'].keys(): self.attrs[clsname]['new'] += 1 return _('new', 'blue') if attr not in meta['xml'].keys() and attr in meta['obj'].keys(): self.attrs[clsname]['old'] += 1 return _('old', 'red') if attr not in meta['docs'].keys() and attr in meta['obj'].keys(): self.attrs[clsname]['doc'] += 1 return _('doc', 'purple') return _(' ', 'green') def _safe_connect(self, elem): try: return elem.connect() except: return None def _safe_reload(self, elem): try: elem.reload() except: pass
# Disable the warning that the request is insecure, we know that... import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) plex = PlexServer(PLEX_URL, PLEX_TOKEN, session=sess) account = plex.myPlexAccount() user_lst = [ x.title for x in plex.myPlexAccount().users() if x.servers and x.friend ] sections = plex.library.sections() sections_dict = {x.key: x.title for x in sections} filters_lst = list( set([y.key for x in sections if x.type != 'photo' for y in x.listFields()])) admin_playlist_lst = [x for x in plex.playlists()] today = datetime.datetime.now().date() weeknum = datetime.date(today.year, today.month, today.day).isocalendar()[1] def actions(): """ add - create new playlist for admin or users remove - remove playlist type or name from admin or users update - remove playlist type and create new playlist type for admin or users show - show contents of playlist type or admin or users current playlists share - share existing playlist by title from admin to users export - export playlist by title from admin to users """ return ['add', 'remove', 'update', 'show', 'share', 'export']
# If verify is set to a path to a directory, # the directory must have been processed using the c_rehash utility supplied # with OpenSSL. if sess.verify is False: # Disable the warning that the request is insecure, we know that... import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) plex = PlexServer(PLEX_URL, PLEX_TOKEN, session=sess) account = plex.myPlexAccount() user_lst = [x.title for x in plex.myPlexAccount().users()] sections = plex.library.sections() sections_lst = [x.title for x in sections] filter_lst = list(set([y for x in sections if x.type != 'photo' for y in x.ALLOWED_FILTERS])) playlist_lst = [x.title for x in plex.playlists()] today = datetime.datetime.now().date() weeknum = datetime.date(today.year, today.month, today.day).isocalendar()[1] def actions(): """ add - create new playlist for admin or users remove - remove playlist type or name from admin or users update - remove playlist type and create new playlist type for admin or users show - show contents of playlist type or admin or users current playlists share - share existing playlist by title from admin to users """ return ['add', 'remove', 'update', 'show', 'share'] def selectors():
class tizplexproxy(object): """A class that accesses Plex servers, retrieves track URLs and creates and manages a playback queue. """ def __init__(self, base_url, token, section): self.base_url = base_url self.queue = list() self.queue_index = -1 self.play_queue_order = list() self.play_modes = TizEnumeration(["NORMAL", "SHUFFLE"]) self.current_play_mode = self.play_modes.NORMAL self.now_playing_track = None self._plex = PlexServer(base_url, token) self._music = self._plex.library.section(section) def set_play_mode(self, mode): """ Set the playback mode. :param mode: current valid values are "NORMAL" and "SHUFFLE" """ self.current_play_mode = getattr(self.play_modes, mode) self._update_play_queue_order() def enqueue_audio_tracks(self, arg): """Search the Plex server for audio tracks and add them to the playback queue. :param arg: a search string """ logging.info("arg : %s", arg) print_msg("[Plex] [Track search in server] : '{0}'. ".format( self.base_url)) try: count = len(self.queue) try: tracks = self._music.searchTracks(title=arg) for track in tracks: track_info = TrackInfo(track, track.artist(), track.album()) self._add_to_playback_queue(track_info) except (NotFound): pass if count == len(self.queue): tracks = self._music.search(libtype="track") for track in tracks: track_name = track.title if fuzz.partial_ratio(arg, track_name) > 60: track_info = TrackInfo(track, track.artist(), track.album()) self._add_to_playback_queue(track_info) self._finalise_play_queue(count, arg) except ValueError: raise ValueError(str("Track not found : %s" % arg)) def enqueue_audio_artist(self, arg): """Obtain an artist from the Plex server and add all the artist's audio tracks to the playback queue. :param arg: an artist search term """ logging.info("arg : %s", arg) print_msg("[Plex] [Artist search in server] : '{0}'. ".format( self.base_url)) try: count = len(self.queue) artist = None artist_name = "" try: artists = self._music.searchArtists(title=arg) for artist in artists: artist_name = artist.title print_wrn("[Plex] Playing '{0}'.".format(artist_name)) for album in artist.albums(): for track in album.tracks(): track_info = TrackInfo(track, artist, album) self._add_to_playback_queue(track_info) except (NotFound): pass if count == len(self.queue): artist_dict = dict() artist_names = list() artists = self._music.search(libtype="artist") for art in artists: artist_names.append(art.title) artist_dict[art.title] = art if len(artist_names) > 1: artist_name = process.extractOne(arg, artist_names)[0] artist = artist_dict[artist_name] elif len(artist_names) == 1: artist_name = artist_names[0] artist = artist_dict[artist_name] if artist: print_adv("[Plex] '{0}' not found. " "Playing '{1}' instead.".format( arg, artist_name)) for album in artist.albums(): for track in album.tracks(): track_info = TrackInfo(track, artist, album) self._add_to_playback_queue(track_info) self._finalise_play_queue(count, arg) except ValueError: raise ValueError(str("Artist not found : %s" % arg)) def enqueue_audio_album(self, arg): """Obtain an album from the Plex server and add all its tracks to the playback queue. :param arg: an album search term """ logging.info("arg : %s", arg) print_msg("[Plex] [Album search in server] : '{0}'. ".format( self.base_url)) try: count = len(self.queue) album = None album_name = "" try: albums = self._music.searchAlbums(title=arg) for album in albums: album_name = album.title print_wrn("[Plex] Playing '{0}'.".format(album_name)) for track in album.tracks(): track_info = TrackInfo(track, track.artist(), album) self._add_to_playback_queue(track_info) except (NotFound): pass if count == len(self.queue): album_dict = dict() album_names = list() albums = self._music.search(libtype="album") for alb in albums: album_names.append(alb.title) album_dict[alb.title] = alb if len(album_names) > 1: album_name = process.extractOne(arg, album_names)[0] album = album_dict[album_name] elif len(album_names) == 1: album_name = album_names[0] album = album_dict[album_name] if album: print_adv("[Plex] '{0}' not found. " "Playing '{1}' instead.".format(arg, album_name)) for track in album.tracks(): track_info = TrackInfo(track, album, album) self._add_to_playback_queue(track_info) self._finalise_play_queue(count, arg) except ValueError: raise ValueError(str("Album not found : %s" % arg)) def enqueue_audio_playlist(self, arg): """Add all audio tracks in a Plex playlist to the playback queue. :param arg: a playlist search term """ logging.info("arg : %s", arg) print_msg("[Plex] [Playlist search in server] : '{0}'. ".format( self.base_url)) try: count = len(self.queue) playlist_title = "" playlist = None try: playlist = self._plex.playlist(title=arg) if playlist: playlist_title = playlist.title print_wrn("[Plex] Playing '{0}'.".format(playlist_title)) for item in list(playlist.items()): if item.TYPE == "track": track = item track_info = TrackInfo(track, track.artist(), track.album()) self._add_to_playback_queue(track_info) if count == len(self.queue): print_wrn( "[Plex] '{0}' No audio tracks found.".format( playlist_title)) raise ValueError except (NotFound): pass if count == len(self.queue): playlist_dict = dict() playlist_titles = list() playlists = self._plex.playlists() for pl in playlists: playlist_titles.append(pl.title) playlist_dict[pl.title] = pl if len(playlist_titles) > 1: playlist_title = process.extractOne(arg, playlist_titles)[0] playlist = playlist_dict[playlist_title] elif len(playlist_titles) == 1: playlist_title = playlist_titles[0] playlist = playlist_dict[playlist_title] if playlist: print_adv("[Plex] '{0}' not found. " "Playing '{1}' instead.".format( arg, playlist_title)) for item in list(playlist.items()): if item.TYPE == "track": track = item track_info = TrackInfo(track, track.artist(), track.album()) self._add_to_playback_queue(track_info) if count == len(self.queue): print_wrn( "[Plex] '{0}' No audio tracks found.".format( playlist_title)) self._finalise_play_queue(count, arg) except (ValueError, NotFound): raise ValueError( str("Playlist not found or no audio tracks in playlist : %s" % arg)) def current_audio_track_title(self): """ Retrieve the current track's title. """ logging.info("current_audio_track_title") track = self.now_playing_track title = "" if track: title = to_ascii(track.title) return title def current_audio_track_artist(self): """ Retrieve the current track's artist. """ logging.info("current_audio_track_artist") track = self.now_playing_track artist = "" if track: artist = to_ascii(track.artist) return artist def current_audio_track_album(self): """ Retrieve the current track's album. """ logging.info("current_audio_track_album") track = self.now_playing_track album = "" if track and track.album: album = to_ascii(track.album) return album def current_audio_track_year(self): """ Retrieve the current track's publication year. """ logging.info("current_audio_track_year") track = self.now_playing_track year = 0 if track: year = track.year return year def current_audio_track_file_size(self): """ Retrieve the current track's file size. """ logging.info("current_audio_track_file_size") track = self.now_playing_track size = 0 if track: size = track.size return size def current_audio_track_duration(self): """ Retrieve the current track's duration. """ logging.info("current_audio_track_duration") track = self.now_playing_track duration = 0 if track: duration = track.duration return duration def current_audio_track_bitrate(self): """ Retrieve the current track's bitrate. """ logging.info("current_audio_track_bitrate") track = self.now_playing_track bitrate = 0 if track: bitrate = track.bitrate return bitrate def current_audio_track_codec(self): """ Retrieve the current track's codec. """ logging.info("current_audio_track_codec") track = self.now_playing_track codec = "" if track: codec = to_ascii(track.codec) return codec def current_audio_track_album_art(self): """ Retrieve the current track's album_art. """ logging.info("current_audio_track_album_art") track = self.now_playing_track album_art = "" if track and track.thumb_url: album_art = to_ascii(track.thumb_url) return album_art def current_audio_track_queue_index_and_queue_length(self): """ Retrieve index in the queue (starting from 1) of the current track and the length of the playback queue. """ logging.info("current_audio_track_queue_index_and_queue_length") return self.play_queue_order[self.queue_index] + 1, len(self.queue) def clear_queue(self): """ Clears the playback queue. """ self.queue = list() self.queue_index = -1 def print_queue(self): """ Print the contents of the playback queue. """ for i in range(0, len(self.queue)): track = self.queue[self.play_queue_order[i]] order_num = str("#{:0{}d}".format(i + 1, len(str(len(self.queue))))) info_str = str("[Plex] [Track] [{0}] '{1}' [{2}] ({3})".format( order_num, to_ascii(track.title), to_ascii(track.artist), to_ascii(track.duration_str), )) print_nfo(info_str + ".") print_nfo("[Plex] [Tracks in queue] '{0}'.".format(len(self.queue))) def remove_current_url(self): """Remove the currently active url from the playback queue. """ logging.info("") if len(self.queue) and self.queue_index: track = self.queue[self.queue_index] print_nfo("[Plex] [Track] '{0}' removed.".format( to_ascii(track["i"].title))) del self.queue[self.queue_index] self.queue_index -= 1 if self.queue_index < 0: self.queue_index = 0 self._update_play_queue_order() def next_url(self): """ Retrieve the url of the next track in the playback queue. """ logging.info("") try: if len(self.queue): self.queue_index += 1 if (self.queue_index < len(self.queue)) and (self.queue_index >= 0): next_track = self.queue[self.play_queue_order[ self.queue_index]] return self._retrieve_track_url(next_track) else: self.queue_index = -1 return self.next_url() else: return "" except (KeyError, AttributeError): # TODO: We don't remove this for now # del self.queue[self.queue_index] logging.info("exception") return self.next_url() def prev_url(self): """ Retrieve the url of the previous track in the playback queue. """ logging.info("") try: if len(self.queue): self.queue_index -= 1 if (self.queue_index < len(self.queue)) and (self.queue_index >= 0): prev_track = self.queue[self.play_queue_order[ self.queue_index]] return self._retrieve_track_url(prev_track) else: self.queue_index = len(self.queue) return self.prev_url() else: return "" except (KeyError, AttributeError): # TODO: We don't remove this for now # del self.queue[self.queue_index] logging.info("exception") return self.prev_url() def get_url(self, position=None): """Retrieve the url on a particular position in the playback queue. If no position is given, the url at the current position of the playback is returned. """ logging.info("get_url {}".format(position if position else "-1")) try: if len(self.queue): queue_pos = self.play_queue_order[self.queue_index] if position and position > 0 and position <= len(self.queue): self.queue_index = position - 1 queue_pos = self.play_queue_order[self.queue_index] logging.info("get_url : self.queue_index {}".format( self.queue_index)) logging.info("get_url : play_queue_order {}".format( self.play_queue_order[self.queue_index])) track = self.queue[queue_pos] return self._retrieve_track_url(track) else: return "" except (KeyError, AttributeError): # TODO: We don't remove this for now # del self.queue[self.queue_index] logging.info("exception") return "" def _update_play_queue_order(self): """ Update the queue playback order. A sequential order is applied if the current play mode is "NORMAL" or a random order if current play mode is "SHUFFLE" """ total_tracks = len(self.queue) if total_tracks: if not len(self.play_queue_order): # Create a sequential play order, if empty self.play_queue_order = list(range(total_tracks)) if self.current_play_mode == self.play_modes.SHUFFLE: random.shuffle(self.play_queue_order) def _retrieve_track_url(self, track): """ Retrieve a track url """ try: self.now_playing_track = track return track.url except AttributeError: logging.info("Could not retrieve the track url!") raise def _add_to_playback_queue(self, track): """ Add to the playback queue. """ if not track: return self.queue.append(track) def _finalise_play_queue(self, count, arg): """ Helper function to grou the various actions needed to ready play queue. """ if count == len(self.queue): logging.info("no tracks found arg : %s", arg) raise ValueError self._update_play_queue_order() self.print_queue()
class Plex(GObject.Object): __gsignals__ = { 'login-status': (GObject.SignalFlags.RUN_FIRST, None, (bool, str)), 'shows-latest': (GObject.SignalFlags.RUN_FIRST, None, (object, )), 'shows-deck': (GObject.SignalFlags.RUN_FIRST, None, (object, )), 'section-shows-deck': (GObject.SignalFlags.RUN_FIRST, None, (object, )), 'download-cover': (GObject.SignalFlags.RUN_FIRST, None, (int, str)), 'download-from-url': (GObject.SignalFlags.RUN_FIRST, None, (str, str)), 'shows-retrieved': (GObject.SignalFlags.RUN_FIRST, None, (object, object)), 'item-retrieved': (GObject.SignalFlags.RUN_FIRST, None, (object, )), 'item-downloading': (GObject.SignalFlags.RUN_FIRST, None, (object, bool)), 'sync-status': (GObject.SignalFlags.RUN_FIRST, None, (bool, )), 'servers-retrieved': (GObject.SignalFlags.RUN_FIRST, None, (object, )), 'sections-retrieved': (GObject.SignalFlags.RUN_FIRST, None, (object, )), 'album-retrieved': (GObject.SignalFlags.RUN_FIRST, None, (object, object)), 'artist-retrieved': (GObject.SignalFlags.RUN_FIRST, None, (object, object)), 'playlists-retrieved': (GObject.SignalFlags.RUN_FIRST, None, (object, )), 'section-item-retrieved': (GObject.SignalFlags.RUN_FIRST, None, (object, )), 'search-item-retrieved': (GObject.SignalFlags.RUN_FIRST, None, (str, object)), 'connection-to-server': (GObject.SignalFlags.RUN_FIRST, None, ()), 'logout': (GObject.SignalFlags.RUN_FIRST, None, ()), 'loading': (GObject.SignalFlags.RUN_FIRST, None, (str, bool)), 'sync-items': (GObject.SignalFlags.RUN_FIRST, None, (object, )), } _config = {} _search_provider_data = {} _server = None _account = None _library = None _sync_busy = False def __init__(self, config_dir, data_dir, player, **kwargs): super().__init__(**kwargs) self._settings = Gio.Settings("nl.g4d.Girens") self._config_dir = config_dir self._data_dir = data_dir self._player = player self._player.set_plex(self) self._config = self.__open_file(self._config_dir + '/config') self._search_provider_data = self.__open_file(self._config_dir + '/search_provider_data') self._user_uuid = self._settings.get_string("user-uuid") self._token = self.get_token(self._user_uuid) self._server_uuid = self._settings.get_string("server-uuid") if (self._server_uuid is not ''): self._server_token = self.get_server_token(self._server_uuid) self._server_url = self._settings.get_string("server-url") else: self._server_token = None self._server_url = None def __open_file(self, file_path): if (os.path.isfile(file_path)): with open(file_path, 'r') as file: lines = file.readlines() return json.loads(lines[0]) return {} def has_token(self): return self._token is not None def get_server_token(self, uuid): return Secret.password_lookup_sync( Secret.Schema.new("nl.g4d.Girens", Secret.SchemaFlags.NONE, {'uuid': Secret.SchemaAttributeType.STRING}), {'uuid': uuid}, None) def get_token(self, uuid): return Secret.password_lookup_sync( Secret.Schema.new("nl.g4d.Girens", Secret.SchemaFlags.NONE, {'uuid': Secret.SchemaAttributeType.STRING}), {'uuid': uuid}, None) def set_server_token(self, token, server_url, server_uuid, name): self._settings.set_string("server-url", self._server._baseurl) self._settings.set_string("server-uuid", self._server.machineIdentifier) Secret.password_store( Secret.Schema.new( "nl.g4d.Girens", Secret.SchemaFlags.NONE, { 'name': Secret.SchemaAttributeType.STRING, 'url': Secret.SchemaAttributeType.STRING, 'uuid': Secret.SchemaAttributeType.STRING }), { 'name': name, 'url': server_url, 'uuid': server_uuid }, Secret.COLLECTION_DEFAULT, 'Girens server token', token, None, None) def set_token(self, token, username, email, uuid): self._settings.set_string("user-uuid", uuid) Secret.password_store( Secret.Schema.new( "nl.g4d.Girens", Secret.SchemaFlags.NONE, { 'username': Secret.SchemaAttributeType.STRING, 'email': Secret.SchemaAttributeType.STRING, 'uuid': Secret.SchemaAttributeType.STRING }), { 'username': username, 'email': email, 'uuid': uuid }, Secret.COLLECTION_DEFAULT, 'Girens token', token, None, None) def has_url(self): return self._server_url is not None def login_token(self, token): try: self._account = MyPlexAccount(token=token) self.set_token(self._account._token, self._account.username, self._account.email, self._account.uuid) self.emit('login-status', True, '') except: self.emit('login-status', False, 'Login failed') def login(self, username, password): try: self._account = MyPlexAccount(username, password) self.set_token(self._account._token, self._account.username, self._account.email, self._account.uuid) self.emit('login-status', True, '') except: self.emit('login-status', False, 'Login failed') def login_with_url(self, baseurl, token): try: self.emit('loading', _('Connecting to ') + baseurl, True) self._server = PlexServer(baseurl, token) self._account = self._server.account() self._library = self._server.library self.set_server_token(self._server._token, self._server._baseurl, self._server.machineIdentifier, self._server.friendlyName) Secret.password_clear_sync( Secret.Schema.new("nl.g4d.Girens", Secret.SchemaFlags.NONE, {'uuid': Secret.SchemaAttributeType.STRING}), {'uuid': self._user_uuid}, None) self._user_uuid = None self._token = None self.emit('connection-to-server') self.emit('loading', 'Success', False) self.emit('login-status', True, '') except: self.emit('loading', _('Connecting to ') + baseurl + _(' failed.'), True) self.emit('login-status', False, 'Login failed') print('connection failed (login with url)') def __save_config(self): with open(self._config_dir + '/config', 'w') as file: file.write(json.dumps(self._config)) def __save_search_provider_data(self): with open(self._config_dir + '/search_provider_data', 'w') as file: file.write(json.dumps(self._search_provider_data)) def logout(self): self._config = {} self._server = None self._account = None self._library = None self.__remove_login() self.emit('logout') def __remove_login(self): if (os.path.isfile(self._config_dir + '/config')): os.remove(self._config_dir + '/config') Secret.password_clear_sync( Secret.Schema.new("nl.g4d.Girens", Secret.SchemaFlags.NONE, {'uuid': Secret.SchemaAttributeType.STRING}), {'uuid': self._server_uuid}, None) Secret.password_clear_sync( Secret.Schema.new("nl.g4d.Girens", Secret.SchemaFlags.NONE, {'uuid': Secret.SchemaAttributeType.STRING}), {'uuid': self._user_uuid}, None) self._settings.set_string("server-url", '') self._settings.set_string("server-uuid", '') self._settings.set_string("user-uuid", '') self._user_uuid = None self._token = None self._server_uuid = None self._server_token = None self._server_url = None def get_latest(self): latest = self._library.recentlyAdded() self.emit('shows-latest', latest) def get_deck(self): deck = self._library.onDeck() self.emit('shows-deck', deck) def get_section_deck(self, section_id): deck = self._library.sectionByID(section_id).onDeck() self.emit('section-shows-deck', deck) def get_item(self, key): return self._server.fetchItem(int(key)) def get_show(self, key): show = self._server.fetchItem(int(key)) episodes = show.episodes() self.emit('shows-retrieved', show, episodes) def get_album(self, key): album = self._server.fetchItem(int(key)) tracks = album.tracks() self.emit('album-retrieved', album, tracks) def get_artist(self, key): artist = self._server.fetchItem(int(key)) albums = artist.albums() self.emit('artist-retrieved', artist, albums) def get_servers(self): servers = [] if (self.has_token()): for resource in self._account.resources(): if (resource.provides == 'server'): servers.append(resource) else: servers.append(self._server) self.emit('servers-retrieved', servers) def get_playlists(self): playlists = self._server.playlists() self.emit('playlists-retrieved', playlists) def get_sections(self): sections = self._library.sections() self.emit('sections-retrieved', sections) def get_section_filter(self, section): if ('sections' in self._config and section.uuid in self._config['sections'] and 'sort' in self._config['sections'][section.uuid]): return self._config['sections'][section.uuid] return None def get_section_items(self, section, container_start=0, container_size=10, sort=None, sort_value=None): if (sort != None): if 'sections' not in self._config: self._config['sections'] = {} if section.uuid not in self._config['sections']: self._config['sections'][section.uuid] = {} self._config['sections'][section.uuid]['sort'] = sort self._config['sections'][section.uuid]['sort_value'] = sort_value self.__save_config() sort = sort + ':' + sort_value items = section.all(container_start=container_start, container_size=container_size, sort=sort) self.emit('section-item-retrieved', items) def reload_search_provider_data(self): #section = self._library.sectionByID('22') self._search_provider_data['sections'] = {} for section in self._library.sections(): if (section.type not in ['photo', 'movie']): items = section.all() self._search_provider_data['sections'][section.uuid] = { 'key': section.key, 'server_machine_identifier': self._server.machineIdentifier, 'title': section.title } self._search_provider_data['sections'][ section.uuid]['items'] = [] for item in items: self._search_provider_data['sections'][ section.uuid]['items'].append({ 'title': item.title, 'titleSort': item.titleSort, 'ratingKey': item.ratingKey, 'type': item.type }) if (section.type == 'artist'): for item in section.albums(): self._search_provider_data['sections'][ section.uuid]['items'].append({ 'title': item.title, 'titleSort': item.titleSort, 'ratingKey': item.ratingKey, 'type': item.type }) self.__save_search_provider_data() def search_library(self, search, libtype=None): items = self._library.search(search, limit=10, libtype=libtype) self.emit('search-item-retrieved', search, items) def download_cover(self, key, thumb): url_image = self._server.transcodeImage(thumb, 300, 200) if (url_image is not None and url_image != ""): path = self.__download(url_image, 'thumb_' + str(key)) self.emit('download-cover', key, path) def download_from_url(self, name_image, url_image): if (url_image is not None and url_image != ""): path = self.__download(url_image, 'thumb_' + name_image) self.emit('download-from-url', name_image, path) def play_item(self, item, shuffle=0, from_beginning=None, sort=None): if type(item) is str: item = self._server.fetchItem(item) parent_item = None if item.TYPE == "track": parent_item = item.album() playqueue = PlayQueue.create(self._server, item, shuffle=shuffle, continuous=1, parent=parent_item, sort=sort) self._player.set_playqueue(playqueue) GLib.idle_add(self.__play_item, from_beginning) def __play_item(self, from_beginning): self._player.start(from_beginning=from_beginning) def get_sync_items(self): if 'sync' in self._config: self.emit('sync-items', self._config['sync']) def remove_from_sync(self, item_key): if str(item_key) in self._config['sync']: del self._config['sync'][item_key] self.__save_config() self.get_sync_items() def add_to_sync(self, item, converted=False, max_items=None, only_unwatched=False): if 'sync' not in self._config: self._config['sync'] = {} if str(item.ratingKey) not in self._config['sync']: self._config['sync'][str(item.ratingKey)] = {} self._config['sync'][str(item.ratingKey)]['rating_key'] = str( item.ratingKey) self._config['sync'][str(item.ratingKey)]['converted'] = converted self._config['sync'][str( item.ratingKey)]['only_unwatched'] = only_unwatched if (max_items != None): self._config['sync'][str( item.ratingKey)]['max_items'] = max_items self.__save_config() self.get_sync_items() self.sync() def sync(self): if (self._sync_busy == False): self.emit('sync-status', True) path_dir = self._data_dir + '/' + self._server.machineIdentifier download_files = [] for file in os.listdir(path_dir): if file.startswith("item_"): download_files.append(file) self._sync_busy = True if 'sync' in self._config: sync = self._config['sync'].copy() for item_keys in sync: item = self._server.fetchItem(int(item_keys)) download_items = [] if (item.TYPE == 'movie' or item.TYPE == 'episode'): download_items.append(item) elif (item.TYPE == 'album' or item.TYPE == 'artist'): download_items = item.tracks() elif (item.TYPE == 'playlist'): download_items = item.items() elif (item.TYPE == 'show'): download_items = item.episodes() count = 0 for download_item in download_items: sync_bool = False if ('only_unwatched' not in sync[item_keys]): sync_bool = True elif (sync[item_keys]['only_unwatched'] == False): sync_bool = True elif (sync[item_keys]['only_unwatched'] == True and (download_item.TYPE == 'movie' or download_item.TYPE == 'episode') and not download_item.isWatched): sync_bool = True if (sync_bool == True): count = count + 1 if ('max_items' in sync[item_keys] and count > int(sync[item_keys]['max_items'])): break if (self.get_item_download_path(download_item) == None): self.__download_item( download_item, converted=sync[item_keys]['converted']) if ('item_' + str(download_item.ratingKey) in download_files): download_files.remove( 'item_' + str(download_item.ratingKey)) for file in download_files: path_file = os.path.join(path_dir, file) if os.path.exists(path_file): os.remove(path_file) self.emit('sync-status', False) self._sync_busy = False def __download_item(self, item, converted=False): path_dir = self._data_dir + '/' + self._server.machineIdentifier filename = 'item_' + str(item.ratingKey) filename_tmp = filename + '.tmp' path = path_dir + '/' + filename path_tmp = path_dir + '/' + filename_tmp self.emit('item-downloading', item, True) if not os.path.exists(path_dir): os.makedirs(path_dir) if not os.path.exists(path): if os.path.exists(path_tmp): os.remove(path_tmp) locations = [i for i in item.iterParts() if i] if (converted == False): download_url = self._server.url('%s?download=1' % locations[0].key) else: download_url = item.getStreamURL() utils.download(download_url, self._server._token, filename=filename_tmp, savepath=path_dir, session=self._server._session) os.rename(path_tmp, path) self.emit('item-downloading', item, False) def get_item_download_path(self, item): path_dir = self._data_dir + '/' + self._server.machineIdentifier filename = 'item_' + str(item.ratingKey) path = path_dir + '/' + filename if not os.path.exists(path): return None return path def mark_as_played(self, item): item.markWatched() item.reload() self.emit('item-retrieved', item) def mark_as_unplayed(self, item): item.markUnwatched() item.reload() self.emit('item-retrieved', item) def retrieve_item(self, item_key): item = self._server.fetchItem(int(item_key)) self.emit('item-retrieved', item) def path_for_download(self, prefix): path_dir = self._data_dir + '/' + self._server.machineIdentifier path = path_dir + '/' + prefix return [path_dir, path] def __download(self, url_image, prefix): paths = self.path_for_download(prefix) path_dir = paths[0] path = paths[1] if not os.path.exists(path_dir): os.makedirs(path_dir) if not os.path.exists(path): parse = urllib.parse.urlparse(url_image) auth_user = parse.username auth_passwd = parse.password password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm() password_mgr.add_password(None, parse.scheme + "://" + parse.hostname, auth_user, auth_passwd) handler = urllib.request.HTTPBasicAuthHandler(password_mgr) opener = urllib.request.build_opener(handler) port = "" if parse.port != None: port = ":" + str(parse.port) url_img_combined = parse.scheme + "://" + parse.hostname + port + parse.path + "?" + parse.query img_raw = opener.open(url_img_combined) with open(path, 'w+b') as file: file.write(img_raw.read()) return path else: return path def connect_to_server(self): if (self._server_token is not None and self._server_url is not None): try: self.emit('loading', _('Connecting to ') + self._server_url + '.', True) self._server = PlexServer(self._server_url, self._server_token) self._library = self._server.library self.set_server_token(self._server._token, self._server._baseurl, self._server.machineIdentifier, self._server.friendlyName) self.emit('connection-to-server') self.emit('loading', 'Success', False) return None except: self.emit( 'loading', _('Connecting to ') + self._server_url + _(' failed.'), True) print('custom url connection failed') servers_found = False for resource in self._account.resources(): servers_found = True if ('server' in resource.provides.split(',')): if self.connect_to_resource(resource): break if (servers_found == False): self.emit('loading', _('No servers found for this account.'), True) def connect_to_resource(self, resource): try: self.emit( 'loading', _('Connecting to ') + resource.name + '.\n' + _('There are ') + str(len(resource.connections)) + _(' connection urls.') + '\n' + _('This may take a while'), True) self._server = resource.connect(ssl=self._account.secure) self._library = self._server.library self.set_server_token(self._server._token, self._server._baseurl, self._server.machineIdentifier, self._server.friendlyName) self.emit('connection-to-server') self.emit('loading', 'Success', False) return True except: self.emit('loading', _('Connecting to ') + resource.name + _(' failed.'), True) print('connection failed (when trying to connect to resource)') return False
import re from plexapi.server import PlexServer baseurl = 'http://plex.example.com:32400' token = 'abcdefghijklmnopqrstuvwxyz' plex = PlexServer(baseurl, token) playlist_title = 'Treehouse of Horror' for playlist in plex.playlists(): if playlist.title == playlist_title: playlist.delete() print('{} already exists. Deleting. Will rebuild.'.format(playlist_title)) tv_shows = plex.library.section(title='TV Shows') episode_list = [] for show in tv_shows.all(): match = re.search(r'Simpsons', show.title) if match is not None: print('{}:'.format(show.title)) for episode in show.episodes(): match = re.search(r'Treehouse', episode.title) if match is not None: print('\t{}'.format(episode.title)) episode_list += episode print('Adding {} to playlist {}.'.format(len(episode_list), playlist_title))
def get_playlists_from_user(token): plex_user = PlexServer(PLEX_URL, token) user_playlists = plex_user.playlists() return user_playlists
class PlexBackend(): def __init__(self, plexurl, token, libname, data_path, client_name): self.token = token self.plexurl = plexurl self.lib_name = libname self.data_path = data_path self.client_name = client_name self.plex = PlexServer(self.plexurl, self.token) self.music = self.plex.library.section(self.lib_name) def down_plex_lib(self): songs = {} try: playlists = self.plex.playlists() songs["playlist"] = {} for p in playlists: p_name = p.title songs["playlist"][p_name] = [] for track in p.items(): title = track.title album = track.album().title artist = track.artist().title file_key = self.get_file(track) file = self.get_tokenized_uri( file_key ) songs["playlist"][p_name].append([artist, album, title, file]) root = self.music.all() artists = defaultdict(list) albums = defaultdict(list) titles = defaultdict(list) count = 0 for artist in root: artist_title = artist.title songs[artist_title] = {} for album in artist.albums(): album_title = album.title songs[artist_title][album_title] = [] for track in album.tracks(): title = track.title key = track.key file_key = self.get_file(track) file = self.get_tokenized_uri( file_key ) try: print("""%d %s -- %s %s %s %s """ % (count, artist_title, album_title, title,file_key, key)) songs[artist_title][album_title].append([title, file, key]) count += 1 except Exception as ex: print(ex) self.json_save(songs, self.data_path) print("done loading library") except Exception as e: print(e) return None def json_save(self, data, fname): with open(fname, 'w') as fp: dump(data, fp) def json_load(self, fname): with open(fname, 'r') as fp: return load(fp) def get_tokenized_uri(self, uri): return self.plexurl+uri+"?X-Plex-Token="+self.token def get_file(self,track): for media in track.media: for p in media.parts: return p.key def play_media(self, key, media_type): client = self.plex.client(self.client_name) item = self.plex.library.fetchItem(key) if media_type == "album": item = self.plex.library.fetchItem(item.parentKey) client.playMedia(item) elif media_type == "artist": item = self.plex.library.fetchItem(item.grandparentKey) queue = self.plex.createPlayQueue(item, shuffle = 1) client.playMedia(queue) else: client.playMedia(item) def pause(self): client = self.plex.client(self.client_name) client.pause("music") def next(self): client = self.plex.client(self.client_name) client.skipNext("music") def previous(self): client = self.plex.client(self.client_name) client.skipPrevious("music") def resume(self): client = self.plex.client(self.client_name) client.play("music") def stop(self): client = self.plex.client(self.client_name) client.stop("music")
class PlexAttributes(): def __init__(self, opts): self.opts = opts # command line options self.clsnames = [c for c in opts.clsnames.split(',') if c] # list of clsnames to report (blank=all) self.account = MyPlexAccount() # MyPlexAccount instance self.plex = PlexServer() # PlexServer instance self.total = 0 # Total objects parsed self.attrs = defaultdict(dict) # Attrs result set def run(self): starttime = time.time() self._parse_myplex() self._parse_server() self._parse_search() self._parse_library() self._parse_audio() self._parse_photo() self._parse_movie() self._parse_show() self._parse_client() self._parse_playlist() self._parse_sync() self.runtime = round((time.time() - starttime) / 60.0, 1) return self def _parse_myplex(self): self._load_attrs(self.account, 'myplex') self._load_attrs(self.account.devices(), 'myplex') for resource in self.account.resources(): self._load_attrs(resource, 'myplex') self._load_attrs(resource.connections, 'myplex') self._load_attrs(self.account.users(), 'myplex') def _parse_server(self): self._load_attrs(self.plex, 'serv') self._load_attrs(self.plex.account(), 'serv') self._load_attrs(self.plex.history()[:50], 'hist') self._load_attrs(self.plex.history()[50:], 'hist') self._load_attrs(self.plex.sessions(), 'sess') def _parse_search(self): for search in ('cre', 'ani', 'mik', 'she', 'bea'): self._load_attrs(self.plex.search(search), 'hub') def _parse_library(self): cat = 'lib' self._load_attrs(self.plex.library, cat) # self._load_attrs(self.plex.library.all()[:50], 'all') self._load_attrs(self.plex.library.onDeck()[:50], 'deck') self._load_attrs(self.plex.library.recentlyAdded()[:50], 'add') for search in ('cat', 'dog', 'rat', 'gir', 'mou'): self._load_attrs(self.plex.library.search(search)[:50], 'srch') # TODO: Implement section search (remove library search?) # TODO: Implement section search filters def _parse_audio(self): cat = 'lib' for musicsection in self.plex.library.sections(): if musicsection.TYPE == library.MusicSection.TYPE: self._load_attrs(musicsection, cat) for artist in musicsection.all(): self._load_attrs(artist, cat) for album in artist.albums(): self._load_attrs(album, cat) for track in album.tracks(): self._load_attrs(track, cat) def _parse_photo(self): cat = 'lib' for photosection in self.plex.library.sections(): if photosection.TYPE == library.PhotoSection.TYPE: self._load_attrs(photosection, cat) for photoalbum in photosection.all(): self._load_attrs(photoalbum, cat) for photo in photoalbum.photos(): self._load_attrs(photo, cat) def _parse_movie(self): cat = 'lib' for moviesection in self.plex.library.sections(): if moviesection.TYPE == library.MovieSection.TYPE: self._load_attrs(moviesection, cat) for movie in moviesection.all(): self._load_attrs(movie, cat) def _parse_show(self): cat = 'lib' for showsection in self.plex.library.sections(): if showsection.TYPE == library.ShowSection.TYPE: self._load_attrs(showsection, cat) for show in showsection.all(): self._load_attrs(show, cat) for season in show.seasons(): self._load_attrs(season, cat) for episode in season.episodes(): self._load_attrs(episode, cat) def _parse_client(self): for device in self.account.devices(): client = self._safe_connect(device) if client is not None: self._load_attrs(client, 'myplex') for client in self.plex.clients(): self._safe_connect(client) self._load_attrs(client, 'client') def _parse_playlist(self): for playlist in self.plex.playlists(): self._load_attrs(playlist, 'pl') for item in playlist.items(): self._load_attrs(item, 'pl') playqueue = PlayQueue.create(self.plex, playlist) self._load_attrs(playqueue, 'pq') def _parse_sync(self): # TODO: Get plexattrs._parse_sync() working. pass def _load_attrs(self, obj, cat=None): if isinstance(obj, (list, tuple)): return [self._parse_objects(item, cat) for item in obj] self._parse_objects(obj, cat) def _parse_objects(self, obj, cat=None): clsname = '%s.%s' % (obj.__module__, obj.__class__.__name__) clsname = clsname.replace('plexapi.', '') if self.clsnames and clsname not in self.clsnames: return None self._print_the_little_dot() if clsname not in self.attrs: self.attrs[clsname] = copy.deepcopy(NAMESPACE) self.attrs[clsname]['total'] += 1 self._load_xml_attrs(clsname, obj._data, self.attrs[clsname]['xml'], self.attrs[clsname]['examples'], self.attrs[clsname]['categories'], cat) self._load_obj_attrs(clsname, obj, self.attrs[clsname]['obj'], self.attrs[clsname]['docs']) def _print_the_little_dot(self): self.total += 1 if not self.total % 100: sys.stdout.write('.') if not self.total % 8000: sys.stdout.write('\n') sys.stdout.flush() def _load_xml_attrs(self, clsname, elem, attrs, examples, categories, cat): if elem is None: return None for attr in sorted(elem.attrib.keys()): attrs[attr] += 1 if cat: categories[attr].add(cat) if elem.attrib[attr] and len(examples[attr]) <= self.opts.examples: examples[attr].add(elem.attrib[attr]) for subelem in elem: attrname = TAGATTRS.get(subelem.tag, '%ss' % subelem.tag.lower()) attrs['%s[]' % attrname] += 1 def _load_obj_attrs(self, clsname, obj, attrs, docs): if clsname in STOP_RECURSING_AT: return None if isinstance(obj, PlexObject) and clsname not in DONT_RELOAD: self._safe_reload(obj) alldocs = '\n\n'.join(self._all_docs(obj.__class__)) for attr, value in obj.__dict__.items(): if value is None or isinstance(value, (str, bool, float, int, datetime)): if not attr.startswith('_') and attr not in IGNORES.get(clsname, []): attrs[attr] += 1 if re.search('\s{8}%s\s\(.+?\)\:' % attr, alldocs) is not None: docs[attr] += 1 if isinstance(value, list): if not attr.startswith('_') and attr not in IGNORES.get(clsname, []): if value and isinstance(value[0], PlexObject): attrs['%s[]' % attr] += 1 [self._parse_objects(obj) for obj in value] def _all_docs(self, cls, docs=None): import inspect docs = docs or [] if cls.__doc__ is not None: docs.append(cls.__doc__) for parent in inspect.getmro(cls): if parent != cls: docs += self._all_docs(parent) return docs def print_report(self): total_attrs = 0 for clsname in sorted(self.attrs.keys()): if self._clsname_match(clsname): meta = self.attrs[clsname] count = meta['total'] print(_('\n%s (%s)\n%s' % (clsname, count, '-' * 30), 'yellow')) attrs = sorted(set(list(meta['xml'].keys()) + list(meta['obj'].keys()))) for attr in attrs: state = self._attr_state(clsname, attr, meta) count = meta['xml'].get(attr, 0) categories = ','.join(meta['categories'].get(attr, ['--'])) examples = '; '.join(list(meta['examples'].get(attr, ['--']))[:3])[:80] print('%7s %3s %-30s %-20s %s' % (count, state, attr, categories, examples)) total_attrs += count print(_('\nSUMMARY\n%s' % ('-' * 30), 'yellow')) print('%7s %3s %3s %3s %-20s %s' % ('total', 'new', 'old', 'doc', 'categories', 'clsname')) for clsname in sorted(self.attrs.keys()): if self._clsname_match(clsname): print('%7s %12s %12s %12s %s' % (self.attrs[clsname]['total'], _(self.attrs[clsname]['new'] or '', 'cyan'), _(self.attrs[clsname]['old'] or '', 'red'), _(self.attrs[clsname]['doc'] or '', 'purple'), clsname)) print('\nPlex Version %s' % self.plex.version) print('PlexAPI Version %s' % plexapi.VERSION) print('Total Objects %s' % sum([x['total'] for x in self.attrs.values()])) print('Runtime %s min\n' % self.runtime) def _clsname_match(self, clsname): if not self.clsnames: return True for cname in self.clsnames: if cname.lower() in clsname.lower(): return True return False def _attr_state(self, clsname, attr, meta): if attr in meta['xml'].keys() and attr not in meta['obj'].keys(): self.attrs[clsname]['new'] += 1 return _('new', 'blue') if attr not in meta['xml'].keys() and attr in meta['obj'].keys(): self.attrs[clsname]['old'] += 1 return _('old', 'red') if attr not in meta['docs'].keys() and attr in meta['obj'].keys(): self.attrs[clsname]['doc'] += 1 return _('doc', 'purple') return _(' ', 'green') def _safe_connect(self, elem): try: return elem.connect() except: return None def _safe_reload(self, elem): try: elem.reload() except: pass
class PlexAttributes(): def __init__(self, opts): self.opts = opts # command line options self.clsnames = [c for c in opts.clsnames.split(',') if c] # list of clsnames to report (blank=all) self.account = MyPlexAccount.signin() # MyPlexAccount instance self.plex = PlexServer() # PlexServer instance self.attrs = defaultdict(dict) # Attrs result set def run(self): # MyPlex self._load_attrs(self.account) self._load_attrs(self.account.devices()) for resource in self.account.resources(): self._load_attrs(resource) self._load_attrs(resource.connections) self._load_attrs(self.account.users()) # Server self._load_attrs(self.plex) self._load_attrs(self.plex.account()) self._load_attrs(self.plex.history()[:20]) self._load_attrs(self.plex.playlists()) for search in ('cre', 'ani', 'mik', 'she'): self._load_attrs(self.plex.search('cre')) self._load_attrs(self.plex.sessions()) # Library self._load_attrs(self.plex.library) self._load_attrs(self.plex.library.sections()) self._load_attrs(self.plex.library.all()[:20]) self._load_attrs(self.plex.library.onDeck()[:20]) self._load_attrs(self.plex.library.recentlyAdded()[:20]) for search in ('cat', 'dog', 'rat'): self._load_attrs(self.plex.library.search(search)[:20]) # Client self._load_attrs(self.plex.clients()) return self def _load_attrs(self, obj): if isinstance(obj, (list, tuple)): return [self._parse_objects(x) for x in obj] return self._parse_objects(obj) def _parse_objects(self, obj): clsname = '%s.%s' % (obj.__module__, obj.__class__.__name__) clsname = clsname.replace('plexapi.', '') if self.clsnames and clsname not in self.clsnames: return None sys.stdout.write('.') sys.stdout.flush() if clsname not in self.attrs: self.attrs[clsname] = copy.deepcopy(NAMESPACE) self.attrs[clsname]['total'] += 1 self._load_xml_attrs(clsname, obj._data, self.attrs[clsname]['xml'], self.attrs[clsname]['examples']) self._load_obj_attrs(clsname, obj, self.attrs[clsname]['obj']) def _load_xml_attrs(self, clsname, elem, attrs, examples): if elem in (None, NA): return None for attr in sorted(elem.attrib.keys()): attrs[attr] += 1 if elem.attrib[attr] and len(examples[attr]) <= self.opts.examples: examples[attr].add(elem.attrib[attr]) def _load_obj_attrs(self, clsname, obj, attrs): for attr, value in obj.__dict__.items(): if value in (None, NA) or isinstance( value, (str, bool, float, int, datetime)): if not attr.startswith('_') and attr not in IGNORES.get( clsname, []): attrs[attr] += 1 def print_report(self): total_attrs = 0 for clsname in sorted(self.attrs.keys()): meta = self.attrs[clsname] count = meta['total'] print( _('\n%s (%s)\n%s' % (clsname, count, '-' * (len(clsname) + 8)), 'yellow')) attrs = sorted( set(list(meta['xml'].keys()) + list(meta['obj'].keys()))) for attr in attrs: state = self._attr_state(attr, meta) count = meta['xml'].get(attr, 0) example = list(meta['examples'].get(attr, ['--']))[0][:80] print('%-4s %4s %-30s %s' % (state, count, attr, example)) total_attrs += count print(_('\nSUMMARY\n------------', 'yellow')) print('Plex Version %s' % self.plex.version) print('PlexAPI Version %s' % plexapi.VERSION) print('Total Objects %s\n' % sum([x['total'] for x in self.attrs.values()])) for clsname in sorted(self.attrs.keys()): print('%-34s %s' % (clsname, self.attrs[clsname]['total'])) print() def _attr_state(self, attr, meta): if attr in meta['xml'].keys() and attr not in meta['obj'].keys(): return _('new', 'blue') if attr not in meta['xml'].keys() and attr in meta['obj'].keys(): return _('old', 'red') return _(' ', 'green')
from plexapi.server import PlexServer from plexapi.playlist import Playlist import os import sys #PLEX INFO url = "http://192.168.1.#:32400" token = "##################" if hasattr(__builtins__, 'raw_input'): input=raw_input plex = PlexServer(url, token) for i, playlist in enumerate(plex.playlists()): print("{position}) {playlist_title}".format(position=i+1, playlist_title=playlist.title)) choice = -1 while choice == -1: selection = input("Select playlist: ") try: selection = int(selection) if selection > 0 and selection <= i+1: choice = selection - 1 else: print("Invalid selection") except: print("Invalid selection") new_playlist_name = input("Enter new playlist name: ") try: Playlist.create(plex, new_playlist_name, plex.playlists()[choice].items()) print("{playlist} created".format(playlist=new_playlist_name)) except:
class tizplexproxy(object): """A class that accesses Plex servers, retrieves track URLs and creates and manages a playback queue. """ def __init__(self, base_url, token): self.base_url = base_url self.queue = list() self.queue_index = -1 self.play_queue_order = list() self.play_modes = TizEnumeration(["NORMAL", "SHUFFLE"]) self.current_play_mode = self.play_modes.NORMAL self.now_playing_track = None self._plex = PlexServer(base_url, token) self._music = self._plex.library.section('Music') def set_play_mode(self, mode): """ Set the playback mode. :param mode: current valid values are "NORMAL" and "SHUFFLE" """ self.current_play_mode = getattr(self.play_modes, mode) self.__update_play_queue_order() def enqueue_audio_tracks(self, arg): """Search the Plex server for audio tracks and add them to the playback queue. :param arg: a search string """ logging.info('arg : %s', arg) print_msg("[Plex] [Track search in server] : '{0}'. " \ .format(self.base_url)) try: count = len(self.queue) try: tracks = self._music.searchTracks(title=arg) for track in tracks: track_info = TrackInfo(track, track.artist(), track.album()) self.add_to_playback_queue(track_info) except (NotFound): pass if count == len(self.queue): tracks = self._music.search(libtype='track') for track in tracks: track_name = track.title if fuzz.partial_ratio(arg, track_name) > 60: track_info = TrackInfo(track, track.artist(), track.album()) self.add_to_playback_queue(track_info) if count == len(self.queue): raise ValueError self.__update_play_queue_order() except ValueError: raise ValueError(str("Track not found : %s" % arg)) def enqueue_audio_artist(self, arg): """Obtain an artist from the Plex server and add all the artist's audio tracks to the playback queue. :param arg: an artist search term """ logging.info('arg : %s', arg) print_msg("[Plex] [Artist search in server] : '{0}'. " \ .format(self.base_url)) try: count = len(self.queue) artist = None artist_name = '' try: artists = self._music.searchArtists(title=arg) for artist in artists: artist_name = artist.title print_wrn("[Plex] Playing '{0}'." \ .format(artist_name.encode('utf-8'))) for album in artist.albums(): for track in album.tracks(): track_info = TrackInfo(track, artist, album) self.add_to_playback_queue(track_info) except (NotFound): pass if count == len(self.queue): artist_dict = dict() artist_names = list() artists = self._music.search(libtype='artist') for art in artists: artist_names.append(art.title) artist_dict[art.title] = art if len(artist_names) > 1: artist_name = process.extractOne(arg, artist_names)[0] artist = artist_dict[artist_name] elif len(artist_names) == 1: artist_name = artist_names[0] artist = artist_dict[artist_name] if artist: print_wrn("[Plex] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), \ artist_name.encode('utf-8'))) for album in artist.albums(): for track in album.tracks(): track_info = TrackInfo(track, artist, album) self.add_to_playback_queue(track_info) if count == len(self.queue): raise ValueError self.__update_play_queue_order() except ValueError: raise ValueError(str("Artist not found : %s" % arg)) def enqueue_audio_album(self, arg): """Obtain an album from the Plex server and add all its tracks to the playback queue. :param arg: an album search term """ logging.info('arg : %s', arg) print_msg("[Plex] [Album search in server] : '{0}'. " \ .format(self.base_url)) try: count = len(self.queue) album = None album_name = '' try: albums = self._music.searchAlbums(title=arg) for album in albums: album_name = album.title print_wrn("[Plex] Playing '{0}'." \ .format(album_name.encode('utf-8'))) for track in album.tracks(): track_info = TrackInfo(track, track.artist(), album) self.add_to_playback_queue(track_info) except (NotFound): pass if count == len(self.queue): album_dict = dict() album_names = list() albums = self._music.search(libtype='album') for alb in albums: album_names.append(alb.title) album_dict[alb.title] = alb if len(album_names) > 1: album_name = process.extractOne(arg, album_names)[0] album = album_dict[album_name] elif len(album_names) == 1: album_name = album_names[0] album = album_dict[album_name] if album: print_wrn("[Plex] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), \ album_name.encode('utf-8'))) for track in album.tracks(): track_info = TrackInfo(track, album, album) self.add_to_playback_queue(track_info) if count == len(self.queue): raise ValueError self.__update_play_queue_order() except ValueError: raise ValueError(str("Album not found : %s" % arg)) def enqueue_audio_playlist(self, arg): """Add all audio tracks in a Plex playlist to the playback queue. :param arg: a playlist search term """ logging.info('arg : %s', arg) print_msg("[Plex] [Playlist search in server] : '{0}'. " \ .format(self.base_url)) try: count = len(self.queue) playlist_title = '' playlist = None try: playlist = self._plex.playlist(title=arg) if playlist: playlist_title = playlist.title print_wrn("[Plex] Playing '{0}'." \ .format(playlist_title.encode('utf-8'))) for item in playlist.items(): if item.TYPE == 'track': track = item track_info = TrackInfo(track, track.artist(), \ track.album()) self.add_to_playback_queue(track_info) if count == len(self.queue): print_wrn("[Plex] '{0}' No audio tracks found." \ .format(playlist_title.encode('utf-8'))) raise ValueError except (NotFound): pass if count == len(self.queue): playlist_dict = dict() playlist_titles = list() playlists = self._plex.playlists() for pl in playlists: playlist_titles.append(pl.title) playlist_dict[pl.title] = pl if len(playlist_titles) > 1: playlist_title = process.extractOne(arg, playlist_titles)[0] playlist = playlist_dict[playlist_title] elif len(playlist_titles) == 1: playlist_title = playlist_titles[0] playlist = playlist_dict[playlist_title] if playlist: print_wrn("[Plex] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), \ playlist_title.encode('utf-8'))) for item in playlist.items(): if item.TYPE == 'track': track = item track_info = TrackInfo(track, track.artist(), \ track.album()) self.add_to_playback_queue(track_info) if count == len(self.queue): print_wrn("[Plex] '{0}' No audio tracks found." \ .format(playlist_title.encode('utf-8'))) if count == len(self.queue): raise ValueError self.__update_play_queue_order() except (ValueError, NotFound): raise ValueError( str("Playlist not found or no audio tracks in playlist : %s" % arg)) def current_audio_track_title(self): """ Retrieve the current track's title. """ track = self.now_playing_track title = '' if track: title = to_ascii(track.title).encode("utf-8") return title def current_audio_track_artist(self): """ Retrieve the current track's artist. """ track = self.now_playing_track artist = '' if track: artist = to_ascii(track.artist).encode("utf-8") return artist def current_audio_track_album(self): """ Retrieve the current track's album. """ track = self.now_playing_track album = '' if track: album = to_ascii(track.album).encode("utf-8") return album def current_audio_track_year(self): """ Retrieve the current track's publication year. """ track = self.now_playing_track year = 0 if track: year = track.year return year def current_audio_track_file_size(self): """ Retrieve the current track's file size. """ track = self.now_playing_track size = 0 if track: size = track.size return size def current_audio_track_duration(self): """ Retrieve the current track's duration. """ track = self.now_playing_track duration = 0 if track: duration = track.duration return duration def current_audio_track_bitrate(self): """ Retrieve the current track's bitrate. """ track = self.now_playing_track bitrate = 0 if track: bitrate = track.bitrate return bitrate def current_audio_track_codec(self): """ Retrieve the current track's codec. """ track = self.now_playing_track codec = '' if track: codec = to_ascii(track.codec).encode("utf-8") return codec def current_audio_track_album_art(self): """ Retrieve the current track's album_art. """ track = self.now_playing_track album_art = '' if track: album_art = to_ascii(track.thumb_url).encode("utf-8") return album_art def current_audio_track_queue_index_and_queue_length(self): """ Retrieve index in the queue (starting from 1) of the current track and the length of the playback queue. """ return self.play_queue_order[self.queue_index] + 1, len(self.queue) def clear_queue(self): """ Clears the playback queue. """ self.queue = list() self.queue_index = -1 def remove_current_url(self): """Remove the currently active url from the playback queue. """ logging.info("") if len(self.queue) and self.queue_index: track = self.queue[self.queue_index] print_nfo("[Plex] [Track] '{0}' removed." \ .format(to_ascii(track['i'].title).encode("utf-8"))) del self.queue[self.queue_index] self.queue_index -= 1 if self.queue_index < 0: self.queue_index = 0 self.__update_play_queue_order() def next_url(self): """ Retrieve the url of the next track in the playback queue. """ logging.info("") try: if len(self.queue): self.queue_index += 1 if (self.queue_index < len(self.queue)) \ and (self.queue_index >= 0): next_track = self.queue[self.play_queue_order \ [self.queue_index]] return self.__retrieve_track_url(next_track) else: self.queue_index = -1 return self.next_url() else: return '' except (KeyError, AttributeError): # TODO: We don't remove this for now # del self.queue[self.queue_index] logging.info("exception") return self.next_url() def prev_url(self): """ Retrieve the url of the previous track in the playback queue. """ logging.info("") try: if len(self.queue): self.queue_index -= 1 if (self.queue_index < len(self.queue)) \ and (self.queue_index >= 0): prev_track = self.queue[self.play_queue_order \ [self.queue_index]] return self.__retrieve_track_url(prev_track) else: self.queue_index = len(self.queue) return self.prev_url() else: return '' except (KeyError, AttributeError): # TODO: We don't remove this for now # del self.queue[self.queue_index] logging.info("exception") return self.prev_url() def __update_play_queue_order(self): """ Update the queue playback order. A sequential order is applied if the current play mode is "NORMAL" or a random order if current play mode is "SHUFFLE" """ total_tracks = len(self.queue) if total_tracks: if not len(self.play_queue_order): # Create a sequential play order, if empty self.play_queue_order = range(total_tracks) if self.current_play_mode == self.play_modes.SHUFFLE: random.shuffle(self.play_queue_order) print_nfo("[Plex] [Tracks in queue] '{0}'." \ .format(total_tracks)) def __retrieve_track_url(self, track): """ Retrieve a track url """ try: self.now_playing_track = track return track.url.encode("utf-8") except AttributeError: logging.info("Could not retrieve the track url!") raise def add_to_playback_queue(self, track): """ Add to the playback queue. """ print_nfo("[Plex] [Track] '{0}' [{1}]." \ .format(to_ascii(track.title).encode("utf-8"), \ to_ascii(track.codec))) queue_index = len(self.queue) self.queue.append(track)
class PlexInstance: def __init__(self, url: str, token: str, api, server_name: str = None, server_alt_name: str = None, server_number: int = 0, credentials_folder: str = None, tautulli_info=None, ombi_info=None, libraries=None): # Server info self.url = url self.token = token if not url or not token: raise Exception("Must include Plex Media Server url and token") self._api = api self.server = PlexServer(self.url, self.token) self.name = server_name if server_name else self.server.friendlyName self.alt_name = server_alt_name if server_alt_name else self.server.friendlyName self.number = server_number self.id = self.server.machineIdentifier # Auth self.auth_header = {'X-Plex-Token': token} self.cloud_key = None # Crypt self.crypt = None if credentials_folder: self.crypt = Encryption(key_file=f'{credentials_folder}/key.txt', key_folder=credentials_folder) # Libraries self.shows = defaultdict(list) self.movies = defaultdict(list) self.libraries = libraries # Ombi self.use_ombi = False self.ombi = None if ombi_info: self.set_ombi_connection(ombi_info) # Tautulli self.use_tautulli = False self.tautulli = None if tautulli_info: self.set_tautulli_connection(tautulli_info) def set_tautulli_connection(self, tautulli_info): self.use_tautulli = tautulli_info.get('enable', False) if self.use_tautulli: self.tautulli = TautulliConnector(url=tautulli_info.get('url'), api_key=tautulli_info.get('api_key')) def set_ombi_connection(self, ombi_info): self.use_ombi = ombi_info.get('enable', False) if self.use_ombi: self.ombi = OmbiConnector(url=ombi_info.get('url'), api_key=ombi_info.get('api_key')) def get_user_creds(self, user_id) -> dict: if self.crypt: creds_dict = {'username': None, 'password': None} if self.crypt.exists(f"{user_id}.json"): creds = self.crypt.decrypt_file(f'{self.crypt.key_folder}/{user_id}.json').splitlines() creds_dict = {'username': creds[0], 'password': creds[1]} return creds_dict return {} def ping(self) -> bool: response = requests.get(f"{self.url}/identity", timeout=10) if response: return True return False def save_user_creds(self, user_id, username, password) -> bool: if self.crypt: text = '{}\n{}'.format(username, password) return self.crypt.encrypt_file(text=text, filename=f'{self.crypt.key_folder}/{user_id}.json') return False def get_media_item(self, title: str, rating_key=None, library_id=None): library = self.server.library if library_id: library = library.sectionByID(str(library_id)) results = library.search(title=title) if results: if rating_key: # find exact match for item in results: if item.ratingKey == rating_key: return item return results[0] # assume first result is correct return None def get_watch_now_link(self, rating_key: str) -> str: return f"https://app.plex.tv/desktop#!/server/{self.id}//details?key=%2Flibrary%2Fmetadata%2F{rating_key}" def get_media_info(self, rating_key) -> Tuple[str, str]: r = requests.get(f'{self.url}/library/metadata/{rating_key}?X-Plex-Token={self.token}').content tree = ET.fromstring(r) return tree.get('librarySectionID'), tree[0].get('title') def get_rating_key(self, url = None) -> str: if not url: url = self.url return str(re.search('metadata%2F(\d*)', url).group(1)) def find_url(self, text) -> str: pattern = '{}\S*'.format(self.url.replace('.', '\.')) return str(re.search(pattern, text).group(0)) def get_playlist(self, playlist_name): for playlist in self.server.playlists(): if playlist.title == playlist_name: return playlist return None def add_to_playlist(self, playlist_title, rating_key, item_to_add) -> str: playlist = self.get_playlist(playlist_name=playlist_title) if playlist: for item in playlist.items(): if str(item.ratingKey) == str(rating_key): return "That item is already on your {}list".format( 'play' if item_to_add.type in ['artist', 'track', 'album'] else 'watch') playlist.addItems([item_to_add]) return "Item added to your {}list".format( 'play' if item_to_add.type in ['artist', 'track', 'album'] else 'watch') else: self.server.createPlaylist(title=playlist_title, items=[item_to_add]) return "New {}list created and item added.".format( 'play' if item_to_add.type in ['artist', 'track', 'album'] else 'watch') def url_in_message(self, message) -> Union[str, None]: server_id = self.server.machineIdentifier if server_id in message.content and 'metadata%2F' in message.content: return self.find_url(text=message.content) if message.embeds: for embed in message.embeds: if server_id in embed.title and 'metadata%2F' in embed.title: return self.find_url(text=embed.title) elif server_id in embed.description and 'metadata%2F' in embed.description: return self.find_url(embed.description) elif server_id in embed.description and 'metadata%2F' in embed.url: return self.find_url(embed.url) return None return None @property def sub_count(self) -> int: count = 0 for user in self.server.myPlexAccount().users(): for server in user.servers: if server.name in [self.name, self.alt_name]: count += 1 break return count @property def users(self) -> List[MyPlexUser]: try: return self.server.myPlexAccount().users() except Exception as e: print(f"Error in getServerUsers: {e}") return [] def get_user(self, username) -> Union[MyPlexUser, None]: try: return self.server.myPlexAccount().user(username=username) except Exception as e: print(f"Error in getServerUser: {e}") return None @property def plex_friends(self) -> List[MyPlexUser]: """ # Returns all Plex Friends (access in + access out) """ friends = [] for user in self.users: if user.friend: friends.append(user) return friends def user_has_access(self, plex_username: str) -> bool: for user in self.users: if user.username == plex_username: return True return False def add_user(self, plex_username: str) -> utils.StatusResponse: try: self.server.myPlexAccount().inviteFriend(user=plex_username, server=self.server, sections=None, allowSync=False, allowCameraUpload=False, allowChannels=False, filterMovies=None, filterTelevision=None, filterMusic=None) return utils.StatusResponse(success=True) except plex_exceptions.NotFound: return utils.StatusResponse(success=False, issue="Invalid Plex username") except Exception as e: return utils.StatusResponse(success=False, issue=e.__str__()) def remove_user(self, plex_username: str) -> utils.StatusResponse: try: self.server.myPlexAccount().removeFriend(user=plex_username) return utils.StatusResponse(success=True) except plex_exceptions.NotFound: return utils.StatusResponse(success=False, issue="Invalid Plex username") except Exception as e: return utils.StatusResponse(success=False, issue=e.__str__()) def refresh_tautulli_users(self) -> bool: if self.use_tautulli: return self.tautulli.refresh_users() return False def delete_user_from_tautulli(self, plex_username) -> bool: if self.use_tautulli: return self.tautulli.delete_user(plex_username=plex_username) return False def refresh_ombi_users(self) -> bool: if self.use_ombi: return self.ombi.refresh_users() return False def delete_user_from_ombi(self, username: str) -> bool: if self.use_ombi: return self.ombi.delete_user(plex_username=username) return False def get_live_tv_dvrs(self): data = self._get(hdr=self.auth_header, endpoint='/livetv/dvrs') if data: if data.get('MediaContainer').get('Dvr'): return [DVR(item) for item in data.get('MediaContainer').get('Dvr')] return None def get_cloud_key(self): if not self.cloud_key: data = self._get(hdr=self.auth_header, endpoint='/tv.plex.providers.epg.cloud') if data: self.cloud_key = data.get('MediaContainer').get('Directory')[1].get('title') else: return None return self.cloud_key def get_live_tv_sessions(self): data = self._get(hdr=self.auth_header, endpoint='/livetv/sessions') if data: if data.get('MediaContainer').get('Metadata'): return [TVSession(item) for item in data.get('MediaContainer').get('Metadata')] return None def get_hubs(self, identifier=None): data = self._get(hdr=self.auth_header, endpoint=f'/{self.get_cloud_key()}/hubs/discover') if data: if identifier: for hub in data['MediaContainer']['Hub']: if hub['title'] == identifier: return Hub(hub) return None return [Hub(hub) for hub in data['MediaContainer']['Hub']] return None def get_dvr_schedule(self): data = self._get(hdr=self.auth_header, endpoint='/media/subscriptions/scheduled') if data: return DVRSchedule(data.get('MediaContainer')) return None def get_dvr_items(self): data = self._get(hdr=self.auth_header, endpoint='/media/subscriptions') if data: return [DVRItem(item) for item in data.get('MediaContainer').get('MediaSubscription')] return None def delete_dvr_item(self, itemID): data = self._delete(hdr=self.auth_header, endpoint='/media/subscription/{}'.format(itemID)) if str(data.status_code).startswith('2'): return True return False def get_homepage_items(self): data = self._get(hdr=self.auth_header, endpoint='/hubs') if data: return [Hub(item) for item in data.get('MediaContainer').get('Hub')] return None def _get(self, hdr, endpoint, data=None): """ Returns JSON """ hdr = {'accept': 'application/json', **hdr} res = requests.get(f'{self.url}{endpoint}', headers=hdr, data=json.dumps(data)).json() return res def _post(self, hdr, endpoint, data=None) -> requests.Response: """ Returns response """ hdr = {'accept': 'application/json', **hdr} res = requests.post(f'{self.url}{endpoint}', headers=hdr, data=json.dumps(data)) return res def _delete(self, hdr, endpoint, data=None) -> requests.Response: """ Returns response """ hdr = {'accept': 'application/json', **hdr} res = requests.delete(f'{self.url}{endpoint}', headers=hdr, data=json.dumps(data)) return res def get_defined_libraries(self): names = [name for name in self.libraries.keys()] ids = [] for _, vs in self.libraries.items(): for v in vs: if v not in ids: ids.append(str(v)) return {'names': names, 'IDs': ids} def get_plex_share(self, share_name_or_number): if not self.libraries: return False if utils.is_positive_int(share_name_or_number): return [int(share_name_or_number)] else: for name, numbers in self.libraries.items(): if name == share_name_or_number: return numbers return False def _get_server_from_user_share(self, plex_user): try: return plex_user.server(self.name) except: return plex_user.server(self.alt_name) def get_user_restrictions(self, plex_username): user = self.get_user(username=plex_username) if user: if user.friend: try: sections = self._get_server_from_user_share(plex_user=user).sections() except: raise Exception("Could not load Plex user sections.") return {'allowSync': user.allowSync, 'filterMovies': user.filterMovies, 'filterShows': user.filterTelevision, 'sections': ([section.title for section in sections] if sections else []) } else: raise Exception(f"Plex user {plex_username} is not a Plex Friend.") else: raise Exception(f"Could not locate Plex user: {plex_username}") def update_user_restrictions(self, plex_username, sections_to_share=[], rating_limit={}, allow_sync: bool = None) -> bool: """ :param plex_username: :param sections_to_share: :param rating_limit: ex. {'Movie': 'PG-13', 'TV': 'TV-14'} :param allow_sync: :return: """ try: sections = [] for section in sections_to_share: section_numbers = self.get_plex_share(share_name_or_number=section) if section_numbers: for number in section_numbers: sections.append(str(number)) allowed_movie_ratings = [] allowed_tv_ratings = [] if rating_limit: # add max rating and all below it to allowed ratings # if non_existent rating is used as limit, all ratings will be added if rating_limit.get('Movie') and rating_limit.get('Movie') in all_movie_ratings: for rating in all_movie_ratings: allowed_movie_ratings.append(rating) if rating == rating_limit.get('Movie'): break if rating_limit.get('TV') and rating_limit.get('TV') in all_tv_ratings: for rating in all_tv_ratings: allowed_tv_ratings.append(rating) if rating == rating_limit.get('TV'): break self.server.myPlexAccount().updateFriend(user=plex_username, server=self.server, sections=(sections if sections else None), removeSections=False, allowSync=allow_sync, allowCameraUpload=None, allowChannels=None, filterMovies=( { 'contentRating': allowed_movie_ratings} if allowed_movie_ratings else None ), filterTelevision=( { 'contentRating': allowed_tv_ratings} if allowed_tv_ratings else None ), filterMusic=None) return True except: print(f"Could not update restrictions for Plex user: {plex_username}") return False
class tizplexproxy(object): """A class that accesses Plex servers, retrieves track URLs and creates and manages a playback queue. """ def __init__(self, base_url, token): self.base_url = base_url self.queue = list() self.queue_index = -1 self.play_queue_order = list() self.play_modes = TizEnumeration(["NORMAL", "SHUFFLE"]) self.current_play_mode = self.play_modes.NORMAL self.now_playing_track = None self._plex = PlexServer(base_url, token) self._music = self._plex.library.section('Music') def set_play_mode(self, mode): """ Set the playback mode. :param mode: current valid values are "NORMAL" and "SHUFFLE" """ self.current_play_mode = getattr(self.play_modes, mode) self.__update_play_queue_order() def enqueue_audio_tracks(self, arg): """Search the Plex server for audio tracks and add them to the playback queue. :param arg: a search string """ logging.info('arg : %s', arg) print_msg("[Plex] [Track search in server] : '{0}'. " \ .format(self.base_url)) try: count = len(self.queue) try: tracks = self._music.searchTracks(title=arg) for track in tracks: track_info = TrackInfo(track, track.artist(), track.album()) self.add_to_playback_queue(track_info) except (NotFound): pass if count == len(self.queue): tracks = self._music.search(libtype='track') for track in tracks: track_name = track.title if fuzz.partial_ratio(arg, track_name) > 60: track_info = TrackInfo(track, track.artist(), track.album()) self.add_to_playback_queue(track_info) if count == len(self.queue): raise ValueError self.__update_play_queue_order() except ValueError: raise ValueError(str("Track not found : %s" % arg)) def enqueue_audio_artist(self, arg): """Obtain an artist from the Plex server and add all the artist's audio tracks to the playback queue. :param arg: an artist search term """ logging.info('arg : %s', arg) print_msg("[Plex] [Artist search in server] : '{0}'. " \ .format(self.base_url)) try: count = len(self.queue) artist = None artist_name = '' try: artists = self._music.searchArtists(title=arg) for artist in artists: artist_name = artist.title print_wrn("[Plex] Playing '{0}'." \ .format(artist_name.encode('utf-8'))) for album in artist.albums(): for track in album.tracks(): track_info = TrackInfo(track, artist, album) self.add_to_playback_queue(track_info) except (NotFound): pass if count == len(self.queue): artist_dict = dict() artist_names = list() artists = self._music.search(libtype='artist') for art in artists: artist_names.append(art.title) artist_dict[art.title] = art if len(artist_names) > 1: artist_name = process.extractOne(arg, artist_names)[0] artist = artist_dict[artist_name] elif len(artist_names) == 1: artist_name = artist_names[0] artist = artist_dict[artist_name] if artist: print_wrn("[Plex] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), \ artist_name.encode('utf-8'))) for album in artist.albums(): for track in album.tracks(): track_info = TrackInfo(track, artist, album) self.add_to_playback_queue(track_info) if count == len(self.queue): raise ValueError self.__update_play_queue_order() except ValueError: raise ValueError(str("Artist not found : %s" % arg)) def enqueue_audio_album(self, arg): """Obtain an album from the Plex server and add all its tracks to the playback queue. :param arg: an album search term """ logging.info('arg : %s', arg) print_msg("[Plex] [Album search in server] : '{0}'. " \ .format(self.base_url)) try: count = len(self.queue) album = None album_name = '' try: albums = self._music.searchAlbums(title=arg) for album in albums: album_name = album.title print_wrn("[Plex] Playing '{0}'." \ .format(album_name.encode('utf-8'))) for track in album.tracks(): track_info = TrackInfo(track, track.artist(), album) self.add_to_playback_queue(track_info) except (NotFound): pass if count == len(self.queue): album_dict = dict() album_names = list() albums = self._music.search(libtype='album') for alb in albums: album_names.append(alb.title) album_dict[alb.title] = alb if len(album_names) > 1: album_name = process.extractOne(arg, album_names)[0] album = album_dict[album_name] elif len(album_names) == 1: album_name = album_names[0] album = album_dict[album_name] if album: print_wrn("[Plex] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), \ album_name.encode('utf-8'))) for track in album.tracks(): track_info = TrackInfo(track, album, album) self.add_to_playback_queue(track_info) if count == len(self.queue): raise ValueError self.__update_play_queue_order() except ValueError: raise ValueError(str("Album not found : %s" % arg)) def enqueue_audio_playlist(self, arg): """Add all audio tracks in a Plex playlist to the playback queue. :param arg: a playlist search term """ logging.info('arg : %s', arg) print_msg("[Plex] [Playlist search in server] : '{0}'. " \ .format(self.base_url)) try: count = len(self.queue) playlist_title = '' playlist = None try: playlist = self._plex.playlist(title=arg) if playlist: playlist_title = playlist.title print_wrn("[Plex] Playing '{0}'." \ .format(playlist_title.encode('utf-8'))) for item in playlist.items(): if item.TYPE == 'track': track = item track_info = TrackInfo(track, track.artist(), \ track.album()) self.add_to_playback_queue(track_info) if count == len(self.queue): print_wrn("[Plex] '{0}' No audio tracks found." \ .format(playlist_title.encode('utf-8'))) raise ValueError except (NotFound): pass if count == len(self.queue): playlist_dict = dict() playlist_titles = list() playlists = self._plex.playlists() for pl in playlists: playlist_titles.append(pl.title) playlist_dict[pl.title] = pl if len(playlist_titles) > 1: playlist_title = process.extractOne(arg, playlist_titles)[0] playlist = playlist_dict[playlist_title] elif len(playlist_titles) == 1: playlist_title = playlist_titles[0] playlist = playlist_dict[playlist_title] if playlist: print_wrn("[Plex] '{0}' not found. " \ "Playing '{1}' instead." \ .format(arg.encode('utf-8'), \ playlist_title.encode('utf-8'))) for item in playlist.items(): if item.TYPE == 'track': track = item track_info = TrackInfo(track, track.artist(), \ track.album()) self.add_to_playback_queue(track_info) if count == len(self.queue): print_wrn("[Plex] '{0}' No audio tracks found." \ .format(playlist_title.encode('utf-8'))) if count == len(self.queue): raise ValueError self.__update_play_queue_order() except (ValueError, NotFound): raise ValueError(str("Playlist not found or no audio tracks in playlist : %s" % arg)) def current_audio_track_title(self): """ Retrieve the current track's title. """ track = self.now_playing_track title = '' if track: title = to_ascii(track.title).encode("utf-8") return title def current_audio_track_artist(self): """ Retrieve the current track's artist. """ track = self.now_playing_track artist = '' if track: artist = to_ascii(track.artist).encode("utf-8") return artist def current_audio_track_album(self): """ Retrieve the current track's album. """ track = self.now_playing_track album = '' if track: album = to_ascii(track.album).encode("utf-8") return album def current_audio_track_year(self): """ Retrieve the current track's publication year. """ track = self.now_playing_track year = 0 if track: year = track.year return year def current_audio_track_file_size(self): """ Retrieve the current track's file size. """ track = self.now_playing_track size = 0 if track: size = track.size return size def current_audio_track_duration(self): """ Retrieve the current track's duration. """ track = self.now_playing_track duration = 0 if track: duration = track.duration return duration def current_audio_track_bitrate(self): """ Retrieve the current track's bitrate. """ track = self.now_playing_track bitrate = 0 if track: bitrate = track.bitrate return bitrate def current_audio_track_codec(self): """ Retrieve the current track's codec. """ track = self.now_playing_track codec = '' if track: codec = to_ascii(track.codec).encode("utf-8") return codec def current_audio_track_album_art(self): """ Retrieve the current track's album_art. """ track = self.now_playing_track album_art = '' if track: album_art = to_ascii(track.thumb_url).encode("utf-8") return album_art def current_audio_track_queue_index_and_queue_length(self): """ Retrieve index in the queue (starting from 1) of the current track and the length of the playback queue. """ return self.play_queue_order[self.queue_index] + 1, len(self.queue) def clear_queue(self): """ Clears the playback queue. """ self.queue = list() self.queue_index = -1 def remove_current_url(self): """Remove the currently active url from the playback queue. """ logging.info("") if len(self.queue) and self.queue_index: track = self.queue[self.queue_index] print_nfo("[Plex] [Track] '{0}' removed." \ .format(to_ascii(track['i'].title).encode("utf-8"))) del self.queue[self.queue_index] self.queue_index -= 1 if self.queue_index < 0: self.queue_index = 0 self.__update_play_queue_order() def next_url(self): """ Retrieve the url of the next track in the playback queue. """ logging.info("") try: if len(self.queue): self.queue_index += 1 if (self.queue_index < len(self.queue)) \ and (self.queue_index >= 0): next_track = self.queue[self.play_queue_order \ [self.queue_index]] return self.__retrieve_track_url(next_track) else: self.queue_index = -1 return self.next_url() else: return '' except (KeyError, AttributeError): # TODO: We don't remove this for now # del self.queue[self.queue_index] logging.info("exception") return self.next_url() def prev_url(self): """ Retrieve the url of the previous track in the playback queue. """ logging.info("") try: if len(self.queue): self.queue_index -= 1 if (self.queue_index < len(self.queue)) \ and (self.queue_index >= 0): prev_track = self.queue[self.play_queue_order \ [self.queue_index]] return self.__retrieve_track_url(prev_track) else: self.queue_index = len(self.queue) return self.prev_url() else: return '' except (KeyError, AttributeError): # TODO: We don't remove this for now # del self.queue[self.queue_index] logging.info("exception") return self.prev_url() def __update_play_queue_order(self): """ Update the queue playback order. A sequential order is applied if the current play mode is "NORMAL" or a random order if current play mode is "SHUFFLE" """ total_tracks = len(self.queue) if total_tracks: if not len(self.play_queue_order): # Create a sequential play order, if empty self.play_queue_order = range(total_tracks) if self.current_play_mode == self.play_modes.SHUFFLE: random.shuffle(self.play_queue_order) print_nfo("[Plex] [Tracks in queue] '{0}'." \ .format(total_tracks)) def __retrieve_track_url(self, track): """ Retrieve a track url """ try: self.now_playing_track = track return track.url.encode("utf-8") except AttributeError: logging.info("Could not retrieve the track url!") raise def add_to_playback_queue(self, track): """ Add to the playback queue. """ print_nfo("[Plex] [Track] '{0}' [{1}]." \ .format(to_ascii(track.title).encode("utf-8"), \ to_ascii(track.codec))) queue_index = len(self.queue) self.queue.append(track)
import os, sys from plexapi.server import PlexServer, CONFIG from plexapi import utils ## Edit ## PLEX_URL = '' PLEX_TOKEN = '' PLEX_URL = CONFIG.data['auth'].get('server_baseurl', PLEX_URL) PLEX_TOKEN = CONFIG.data['auth'].get('server_token', PLEX_TOKEN) plex = PlexServer(PLEX_URL, PLEX_TOKEN) playlists = [pl for pl in plex.playlists() if pl.isAudio] playlist = None if sys.argv[1]: for pl in playlists: if pl.title == sys.argv[1]: playlist = pl if playlist is None: print("Invalid playlist '{}'".format(sys.argv[1])) sys.exit() else: playlist = utils.choose('Playlist to export', playlists, lambda pl: '%s' % pl.title) for song in playlist.items(): print(song.media[0].parts[0].file)
class PlexBackend(): def __init__(self, plexurl, token, libname, data_path): self.token = token self.plexurl = plexurl self.lib_name = libname self.data_path = data_path self.plex = PlexServer(self.plexurl, self.token) self.music = self.plex.library.section(self.lib_name) def down_plex_lib(self): songs = {} try: playlists = self.plex.playlists() songs["playlist"] = {} for p in playlists: p_name = p.title songs["playlist"][p_name] = [] for track in p.items(): title = track.title album = track.album().title artist = track.artist().title file_key = self.get_file(track) file = self.get_tokenized_uri(file_key) songs["playlist"][p_name].append( [artist, album, title, file]) root = self.music.all() artists = defaultdict(list) albums = defaultdict(list) titles = defaultdict(list) count = 0 for artist in root: artist_title = artist.title songs[artist_title] = {} for album in artist.albums(): album_title = album.title songs[artist_title][album_title] = [] for track in album.tracks(): title = track.title file_key = self.get_file(track) file = self.get_tokenized_uri(file_key) try: print("""%d %s -- %s %s %s """ % (count, artist_title, album_title, title, file_key)) songs[artist_title][album_title].append( [title, file]) count += 1 except Exception as ex: print(ex) self.json_save(songs, self.data_path) print("done loading library") except Exception as e: print(e) return None def json_save(self, data, fname): with open(fname, 'w') as fp: dump(data, fp) def json_load(self, fname): with open(fname, 'r') as fp: return load(fp) def get_tokenized_uri(self, uri): return self.plexurl + uri + "?X-Plex-Token=" + self.token def get_file(self, track): for media in track.media: for p in media.parts: return p.key
import os from plexapi.server import PlexServer from plexapi import utils baseurl = 'https://plx.w00t.cloud' token = 'H6gqeSNE3yGthe72x1w7' plex = PlexServer(baseurl, token) playlists = [pl for pl in plex.playlists()] playlist = utils.choose('Choose Playlist', playlists, lambda pl: '%s' % pl.title) print(len(playlist.items())) for photo in playlist.items(): photomediapart = photo.media[0].parts[0] print('Download File: %s' % photomediapart.file) url = plex.url('%s?download=1' % photomediapart.key) utils.download(url, token, os.path.basename(photomediapart.file))
# If verify is set to a path to a directory, # the directory must have been processed using the c_rehash utility supplied # with OpenSSL. if sess.verify is False: # Disable the warning that the request is insecure, we know that... import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) plex = PlexServer(PLEX_URL, PLEX_TOKEN, session=sess) account = plex.myPlexAccount() user_lst = [x.title for x in plex.myPlexAccount().users() if x.servers and x.friend] sections = plex.library.sections() sections_dict = {x.key: x.title for x in sections} filters_lst = list(set([y for x in sections if x.type != 'photo' for y in x.ALLOWED_FILTERS])) playlist_lst = [x.title for x in plex.playlists()] today = datetime.datetime.now().date() weeknum = datetime.date(today.year, today.month, today.day).isocalendar()[1] def actions(): """ add - create new playlist for admin or users remove - remove playlist type or name from admin or users update - remove playlist type and create new playlist type for admin or users show - show contents of playlist type or admin or users current playlists share - share existing playlist by title from admin to users """ return ['add', 'remove', 'update', 'show', 'share']
# Bulk Download of photos from PLEX playlist import os from plexapi.server import PlexServer from plexapi import utils baseurl = 'http://localhost:32400' token = 'PLACE TOKEN HERE' plex = PlexServer(baseurl, token) playlists = [pl for pl in plex.playlists() if pl.isPhoto] playlist = utils.choose('Choose Playlist', playlists, lambda pl: '%s' % pl.title) for photo in playlist.items(): photomediapart = photo.media[0].parts[0] if photo.year == 2018 or photo.year == 2019: print('Downlod File: %s' % photomediapart.file) url = plex.url('%s?download=1' % photomediapart.key) utils.download(url, token, os.path.basename(photomediapart.file), '%s' % photo.year) else: print('Skip File: %s | %s' % (photomediapart.file, photo.year))