├── .gitignore ├── README.md ├── plex_playlist_update.py ├── settings.ini.example └── tests ├── parse_test.py ├── print_test.py ├── promise_test.py ├── test.ini ├── test.py └── test0.py /.gitignore: -------------------------------------------------------------------------------- 1 | settings.ini 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # plex_top_playlists 2 | A python script to get top weekly or top popular lists and put them in plex as playlists. It will make a playlist for each user on your server. 3 | This is my first time ever creating a python script. Also the first time really adding something useful to GitHub 4 | 5 | Use at your own risk. I have it running nightly 6 | 7 | ## Read This 8 | 9 | This script is assuming you are using "Plex Movie" as your library check. If you are using TMDB, I can add the code here but I personally didnt need it. 10 | 11 | The TV Shows playlists give the last episode in your library for the show. You can easily click the show name to go to it. 12 | 13 | The playlists can be created for any of the following 14 | * All shared users 15 | * Specific users by username 16 | * Only the script running user 17 | 18 | ### What lists this script currently retreives 19 | 20 | * Trakt Movie Watched (weekly) 21 | * Trakt Movie Popular 22 | * Trakt Show Watched (weekly) 23 | * Trakt Show Popular 24 | * IMDB top 250 list (http://www.imdb.com/chart/top) 25 | 26 | # Getting Started 27 | 28 | ## Get initial information 29 | 30 | ### Get plex token 31 | 32 | This page will show you where your token is 33 | 34 | https://support.plex.tv/hc/en-us/articles/204059436-Finding-an-authentication-token-X-Plex-Token 35 | 36 | This page will show you how to view your xml to see that link you grab your token from 37 | 38 | https://support.plex.tv/hc/en-us/articles/201998867 39 | 40 | ### Get Trakt API key 41 | 42 | To connect to Trakt you need to get an api key. You can easily create one. Here are the steps for that 43 | 1) go to Trakt.tv 44 | 2) create a user if you dont have one 45 | 3) Sign in 46 | 4) go here https://trakt.tv/oauth/applications/new 47 | * Name - call it what you want 48 | * Description - needed, put whatever you want 49 | * redirect url - put any websit 50 | * dont need anything else filled out 51 | 5) grab the Client ID to use as your Trakt API Key 52 | 53 | ## Setup 54 | 55 | These instructions are for Ubuntu. Should also work with Debian or any debian based os. 56 | If you run the script python will yell at you what modules are missing and you can google what you need for your installation. 57 | 58 | Install pip: 59 | 60 | ```bash 61 | sudo apt-get install python-setuptools python-dev build-essential 62 | ``` 63 | 64 | Upgrade pip 65 | 66 | ```bash 67 | sudo pip install --upgrade pip 68 | ``` 69 | 70 | Install needed pip modules: 71 | 72 | ```bash 73 | sudo pip install plexapi 74 | ``` 75 | There are 2 more modules i dont have the install code for. If you encounter an error saying something like module missing, please let me know and I will add the correct instructions here. 76 | 77 | Create the script file. Go to the folder location you want to put it 78 | 79 | REPLACE user with your user 80 | ```bash 81 | sudo mkdir /usr/scripts 82 | sudo chown user:user /usr/scripts 83 | cd /usr/scripts 84 | ``` 85 | 86 | ```bash 87 | cp settings.ini.example settings.ini 88 | nano settings.ini 89 | ``` 90 | 91 | Copy conetents from the plex_playlist_update.py file into your new file 92 | 93 | Required - Fill in Plex Token 94 | Not Needed - Trakt API key, keep blank for lists 95 | Options - Change folder names and who to sync with 96 | 97 | Save and close (hit Ctrl+x, y, enter) 98 | 99 | Make the Script executable. 100 | 101 | ```bash 102 | sudo chmod +x plex_playlist_update.py 103 | ``` 104 | 105 | Run the script 106 | 107 | ```bash 108 | ./plex_playlist_update.py 109 | ``` 110 | 111 | ### Make script run nightly 112 | 113 | ```bash 114 | crontab -e 115 | ``` 116 | 117 | Add this line to the bottom of the file (will run at 4:05 am every day) 118 | 119 | ```bash 120 | 5 4 * * * /usr/scripts/plex_playlist_update.py 121 | ``` 122 | 123 | # Used references to create the script 124 | 125 | Thank you JonnyWong16 for his amazing scripts which I heavily referenced. 126 | 127 | https://gist.github.com/JonnyWong16/2607abf0e3431b6f133861bbe1bb694e 128 | https://gist.github.com/JonnyWong16/b1aa2c0f604ed92b9b3afaa6db18e5fd 129 | 130 | 131 | -------------------------------------------------------------------------------- /plex_playlist_update.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # a python test script 4 | # -*- coding: utf-8 -*- 5 | 6 | #------------------------------------------------------------------------------ 7 | # 8 | # Automated Ranked Playlists - RugbyHunter231 9 | # 10 | # *** Use at your own risk! *** 11 | # *** I am not responsible for damages to your Plex server or libraries. *** 12 | # 13 | #------------------------------------------------------------------------------ 14 | 15 | import time 16 | import re 17 | import json 18 | import os 19 | import requests 20 | import subprocess 21 | import time 22 | import xmltodict 23 | import ConfigParser 24 | from lxml.html import parse 25 | from plexapi.server import PlexServer 26 | import lxml 27 | import urllib2 28 | #from plexapi.utils import NA 29 | NA="" 30 | 31 | from urllib2 import Request, urlopen 32 | 33 | config_file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'settings.ini') 34 | config = ConfigParser.SafeConfigParser() 35 | config.read(config_file_path) 36 | 37 | PLEX_URL = config.get('Plex', 'plex-host') 38 | PLEX_TOKEN = config.get('Plex', 'plex-token') 39 | MOVIE_LIBRARY_NAME = config.get('Plex', 'movie-library') 40 | SHOW_LIBRARY_NAME = config.get('Plex', 'tv-library') 41 | REMOVE_ONLY = config.getboolean('Plex', 'remove') 42 | SYNC_WITH_SHARED_USERS = config.getboolean('Plex', 'shared') 43 | ALLOW_SYNCED_USERS = json.loads(config.get('Plex', 'users')) 44 | NOT_ALLOW_SYNCED_USERS = json.loads(config.get('Plex', 'not_users')) 45 | try: 46 | PLEX_TIMEOUT = int(config.get('Plex', 'timeout')) 47 | except: 48 | PLEX_TIMEOUT = 300 49 | TRAKT_API_KEY = config.get('Trakt', 'api-key') 50 | TRAKT_NUM_MOVIES = config.get('Trakt', 'movie-total') 51 | TRAKT_WEEKLY_PLAYLIST_NAME = config.get('Trakt', 'weekly-movie-name') 52 | TRAKT_POPULAR_PLAYLIST_NAME = config.get('Trakt', 'popular-movie-name') 53 | TRAKT_NUM_SHOWS = config.get('Trakt', 'tv-total') 54 | TRAKT_WEEKLY_SHOW_PLAYLIST_NAME = config.get('Trakt', 'weekly-tv-name') 55 | TRAKT_POPULAR_SHOW_PLAYLIST_NAME = config.get('Trakt', 'popular-tv-name') 56 | # these are the new lists 57 | IMDB_SEARCH_LISTS = json.loads(config.get('IMDb', 'search-lists')) 58 | IMDB_CHART_LISTS = json.loads(config.get('IMDb', 'chart-lists')) 59 | IMDB_CUSTOM_LISTS = json.loads(config.get('IMDb', 'custom-lists')) 60 | START_TIME = time.time() 61 | 62 | ####### CODE HERE (Nothing to change) ############ 63 | 64 | def log_timer(marker = ""): 65 | print ">>> {0} seconds {1}".format( 66 | time.time() - START_TIME, 67 | marker 68 | ) 69 | 70 | def get_user_tokens(server_id): 71 | headers = {'Accept': 'application/json', 'X-Plex-Token': PLEX_TOKEN} 72 | result = requests.get('https://plex.tv/api/servers/{server_id}/shared_servers?X-Plex-Token={token}'.format(server_id=server_id, token=PLEX_TOKEN), headers=headers) 73 | xmlData = xmltodict.parse(result.content) 74 | 75 | result2 = requests.get('https://plex.tv/api/users', headers=headers) 76 | xmlData2 = xmltodict.parse(result2.content) 77 | 78 | user_ids = {user['@id']: user.get('@username', user.get('@title')) for user in xmlData2['MediaContainer']['User']} 79 | users = {user_ids[user['@userID']]: user['@accessToken'] for user in xmlData['MediaContainer']['SharedServer']} 80 | 81 | return users 82 | 83 | def remove_playlist(plex, playlist_name): 84 | for playlist in plex.playlists(): 85 | if playlist.title == playlist_name: 86 | try: 87 | playlist.delete() 88 | #print("{}: Playlist deleted".format(playlist_name)) 89 | except: 90 | print("ERROR - cannot delete playlist: {}".format(playlist_name)) 91 | return None 92 | 93 | def create_playlists(plex, runlist, playlist_name): 94 | try: 95 | remove_playlist(plex, playlist_name) 96 | plex.createPlaylist(playlist_name, runlist) 97 | except: 98 | print """ 99 | ERROR trying to create playlist '{0}' 100 | The number of movies/shows in the list provided was {1} 101 | """.format( 102 | playlist_name, 103 | len(runlist) 104 | ) 105 | 106 | def loop_plex_users(plex, list, playlist_name): 107 | #update my list 108 | create_playlists(plex, list, playlist_name) 109 | 110 | #update list for shared users 111 | if SYNC_WITH_SHARED_USERS: 112 | plex_users = get_user_tokens(plex.machineIdentifier) 113 | for user in plex_users: 114 | if (not ALLOW_SYNCED_USERS or user in ALLOW_SYNCED_USERS) and user not in NOT_ALLOW_SYNCED_USERS: 115 | print("{}: updating playlist for user {}".format(playlist_name, user)) 116 | user_token = plex_users[user] 117 | user_plex = PlexServer(baseurl=PLEX_URL, token=user_token, timeout=PLEX_TIMEOUT) 118 | create_playlists(user_plex, list, playlist_name) 119 | else: 120 | print("Skipping adding to shared users") 121 | 122 | def get_tvdb_id(show): 123 | tvdb_id = None 124 | last_episode = show.episodes()[-1] 125 | if last_episode.guid != NA and 'thetvdb://' in last_episode.guid: 126 | tvdb_id = last_episode.guid.split('thetvdb://')[1].split('?')[0].split('/')[0] 127 | return tvdb_id 128 | 129 | def setup_show_playlist(plex, tvdb_ids, plex_shows, playlist_name): 130 | if tvdb_ids: 131 | # Create a list of matching shows using last episode 132 | print("{}: finding matching episodes for playlist with count {}".format(playlist_name, len(tvdb_ids))) 133 | matching_episodes = [] 134 | matching_episode_ids = [] 135 | sorted_shows = [] 136 | for show in plex_shows: 137 | tvdb_id = get_tvdb_id(show) 138 | 139 | if tvdb_id and tvdb_id in tvdb_ids: 140 | matching_episodes.append(show.episodes()[-1]) 141 | matching_episode_ids.append(tvdb_id) 142 | 143 | missing_episode_ids = list(set(tvdb_ids) - set(matching_episode_ids)) 144 | print("I found {match_len} of your episode IDs that matched the TVDB IDs top {tvdb_len} list".format(match_len=len(matching_episode_ids), tvdb_len=len(tvdb_ids))) 145 | print("That means you are missing {missing_len} of the TVDB IDs top {tvdb_len} list".format(missing_len=len(missing_episode_ids), tvdb_len=len(tvdb_ids))) 146 | if len(missing_episode_ids) > 0: 147 | print("The TVDB IDs are listed below .. You can copy/paste this info and put into sonarr ..") 148 | for tvdb_id in missing_episode_ids: 149 | print("tvdb: {}".format(tvdb_id)) 150 | 151 | print("{}: Sorting list in correct order".format(playlist_name)) 152 | 153 | for tvdb_id in tvdb_ids: 154 | for episode in matching_episodes: 155 | show_tvdb_id = episode.guid.split('thetvdb://')[1].split('?')[0].split('/')[0] 156 | if show_tvdb_id == tvdb_id: 157 | sorted_shows.append(episode) 158 | break; 159 | 160 | print("{}: Created shows list".format(playlist_name)) 161 | 162 | loop_plex_users(plex, sorted_shows, playlist_name) 163 | else: 164 | print('{}: WARNING - Playlist is empty'.format(playlist_name)) 165 | 166 | def get_imdb_id(movie): 167 | try: 168 | imdb_id = "tt" + re.search(r'tt(\d+)\?', movie.guid).group(1) 169 | except: 170 | imdb_id = None 171 | return imdb_id 172 | 173 | def append_movie_id_dict(movie, movie_id_dict): 174 | log_timer("start append movie id dict") 175 | imdb_id = get_imdb_id(movie) 176 | if imdb_id != None: 177 | movie_id_dict[imdb_id] = movie 178 | log_timer("end append movie id dict") 179 | return movie_id_dict 180 | 181 | def create_movie_id_dict(movies): 182 | movie_id_dict = {} 183 | log_timer("start create movie id dict") 184 | for movie in movies: 185 | movie_id_dict = append_movie_id_dict(movie, movie_id_dict) 186 | log_timer("end create movie id dict") 187 | return movie_id_dict 188 | 189 | def get_matching_movies(imdb_ids, movie_id_dict): 190 | movies = [] 191 | movie_ids = [] 192 | for imdb_id in imdb_ids: 193 | if imdb_id in movie_id_dict: 194 | movies.append(movie_id_dict[imdb_id]) 195 | movie_ids.append(imdb_id) 196 | returnme = [] 197 | returnme.append(movies) 198 | returnme.append(movie_ids) 199 | return returnme 200 | 201 | def print_imdb_info(matching_movie_ids, imdb_ids): 202 | missing_imdb_ids = list(set(imdb_ids) - set(matching_movie_ids)) 203 | print """ 204 | I found {match_ids_len} of your movie IDs that matched 205 | the IMDB IDs top {imdb_ids_len} list .. 206 | That means you are missing {miss_ids_len} of 207 | the IMDB IDs top {imdb_ids_len} list 208 | """.format( 209 | match_ids_len=len(matching_movie_ids), 210 | imdb_ids_len=len(imdb_ids), 211 | miss_ids_len=len(missing_imdb_ids) 212 | ) 213 | print_missing_imdb_info(missing_imdb_ids) 214 | 215 | def print_missing_imdb_info(missing_imdb_ids): 216 | if len(missing_imdb_ids) > 0: 217 | print """ 218 | The IMDB IDs are listed below .. You can 219 | copy/paste this info and put into radarr .. 220 | """ 221 | for imdb_id in missing_imdb_ids: 222 | print "imdb: {0}".format(imdb_id) 223 | print "\n\n" 224 | 225 | def setup_movie_playlist2(plex, imdb_ids, movie_id_dict, playlist_name): 226 | if imdb_ids: 227 | print "{0}: finding matching movies for playlist with count {1}".format( 228 | playlist_name, 229 | len(imdb_ids) 230 | ) 231 | 232 | matches = get_matching_movies(imdb_ids, movie_id_dict) 233 | matching_movies = matches[0] 234 | matching_movie_ids = matches[1] 235 | 236 | print_imdb_info(matching_movie_ids, imdb_ids) 237 | 238 | print "{}: Created movie list".format(playlist_name) 239 | log_timer() 240 | 241 | loop_plex_users(plex, matching_movies, playlist_name) 242 | else: 243 | print "{0}: WARNING - Playlist is empty".format(playlist_name) 244 | 245 | def trakt_watched_imdb_id_list(): 246 | # Get the weekly watched list 247 | print("Retrieving the Trakt weekly list...") 248 | imdb_ids = [] 249 | 250 | headers = { 251 | 'Content-Type': 'application/json', 252 | 'trakt-api-version': '2', 253 | 'trakt-api-key': TRAKT_API_KEY 254 | } 255 | 256 | request = Request('https://api.trakt.tv/movies/watched/weekly?page=1&limit={}'.format(TRAKT_NUM_MOVIES), headers=headers) 257 | try: 258 | response = urlopen(request) 259 | trakt_movies = json.load(response) 260 | 261 | # loop through movies and add movies to list if match 262 | for movie in trakt_movies: 263 | imdb_ids.append(movie['movie']['ids']['imdb']) 264 | except: 265 | print "Bad Trakt Code" 266 | return [] 267 | 268 | return imdb_ids 269 | 270 | def trakt_popular_imdb_id_list(): 271 | # Get the weekly watched list 272 | print("Retrieving the Trakt popular list...") 273 | imdb_ids = [] 274 | 275 | headers = { 276 | 'Content-Type': 'application/json', 277 | 'trakt-api-version': '2', 278 | 'trakt-api-key': TRAKT_API_KEY 279 | } 280 | 281 | try: 282 | request = Request('https://api.trakt.tv/movies/popular?page=1&limit={}'.format(TRAKT_NUM_MOVIES), headers=headers) 283 | response = urlopen(request) 284 | trakt_movies = json.load(response) 285 | 286 | # loop through movies and add movies to list if match 287 | for movie in trakt_movies: 288 | imdb_ids.append(movie['ids']['imdb']) 289 | except: 290 | print "Bad Trakt Code" 291 | return [] 292 | 293 | return imdb_ids 294 | 295 | def trakt_watched_show_imdb_id_list(): 296 | # Get the weekly watched list 297 | print("Retrieving the Trakt weekly list...") 298 | tvdb_ids = [] 299 | 300 | headers = { 301 | 'Content-Type': 'application/json', 302 | 'trakt-api-version': '2', 303 | 'trakt-api-key': TRAKT_API_KEY 304 | } 305 | try: 306 | request = Request('https://api.trakt.tv/shows/watched/weekly?page=1&limit={}'.format(TRAKT_NUM_SHOWS), headers=headers) 307 | response = urlopen(request) 308 | trakt_show = json.load(response) 309 | 310 | # loop through movies and add movies to list if match 311 | for show in trakt_show: 312 | tvdb_ids.append(str(show['show']['ids']['tvdb'])) 313 | except: 314 | print "Bad Trakt Code" 315 | return [] 316 | 317 | return tvdb_ids 318 | 319 | def trakt_popular_show_imdb_id_list(): 320 | # Get the weekly watched list 321 | print("Retrieving the Trakt popular list...") 322 | tvdb_ids = [] 323 | 324 | headers = { 325 | 'Content-Type': 'application/json', 326 | 'trakt-api-version': '2', 327 | 'trakt-api-key': TRAKT_API_KEY 328 | } 329 | 330 | try: 331 | request = Request('https://api.trakt.tv/shows/popular?page=1&limit={}'.format(TRAKT_NUM_SHOWS), headers=headers) 332 | response = urlopen(request) 333 | trakt_show = json.load(response) 334 | 335 | # loop through movies and add movies to list if match 336 | for show in trakt_show: 337 | tvdb_ids.append(str(show['ids']['tvdb'])) 338 | except: 339 | print "Bad Trakt Code" 340 | return [] 341 | 342 | return tvdb_ids 343 | 344 | def imdb_search_list(url): 345 | response = urllib2.urlopen(url) 346 | data = response.read() 347 | response.close() 348 | doc = lxml.html.document_fromstring(data) 349 | ids = doc.xpath("//img[@class='loadlate']/@data-tconst") 350 | return ids 351 | 352 | def imdb_search_list_name(url): 353 | response = urllib2.urlopen(url) 354 | data = response.read() 355 | response.close() 356 | doc = lxml.html.document_fromstring(data) 357 | name = doc.xpath("//h1[contains(@class, 'header')]")[0].text.strip() 358 | return name 359 | 360 | def imdb_search_lists(plex, movie_id_dict): 361 | for list in IMDB_SEARCH_LISTS: 362 | url = list.split("||")[0] 363 | try: 364 | name = runlist.split("||")[1] 365 | except: 366 | name = imdb_search_list_name(url) 367 | 368 | print "Creating IMDB search playlist '{0}' using URL {1}".format( 369 | name, 370 | url 371 | ) 372 | 373 | ids = imdb_search_list(url) 374 | setup_movie_playlist2(plex, ids, movie_id_dict, "IMDB - {0}".format(name)) 375 | 376 | def imdb_chart_list(url): 377 | response = urllib2.urlopen(url) 378 | data = response.read() 379 | response.close() 380 | doc = lxml.html.document_fromstring(data) 381 | ids = doc.xpath("//table[contains(@class, 'chart')]//td[@class='ratingColumn']/div//@data-titleid") 382 | return ids 383 | 384 | def imdb_chart_list_name(url): 385 | response = urllib2.urlopen(url) 386 | data = response.read() 387 | response.close() 388 | doc = lxml.html.document_fromstring(data) 389 | name = doc.xpath("//h1[contains(@class, 'header')]")[0].text.strip() 390 | return name 391 | 392 | def imdb_chart_lists(plex, movie_id_dict): 393 | for runlist in IMDB_CHART_LISTS: 394 | url = runlist.split(",")[0] 395 | try: 396 | name = runlist.split(",")[1] 397 | except: 398 | name = imdb_chart_list_name(url) 399 | 400 | print "Creating IMDB chart playlist '{0}' using URL {1}".format( 401 | name, 402 | url 403 | ) 404 | 405 | ids = imdb_chart_list(url) 406 | setup_movie_playlist2(plex, ids, movie_id_dict, "IMDB - {0}".format(name)) 407 | 408 | def imdb_custom_list(url): 409 | response = urllib2.urlopen(url) 410 | data = response.read() 411 | response.close() 412 | doc = lxml.html.document_fromstring(data) 413 | ids = doc.xpath("//div[contains(@class, 'lister-item-image ribbonize')]/@data-tconst") 414 | return ids 415 | 416 | def imdb_custom_list_name(url): 417 | response = urllib2.urlopen(url) 418 | data = response.read() 419 | response.close() 420 | doc = lxml.html.document_fromstring(data) 421 | name = doc.xpath("//h1[contains(@class, 'header list-name')]")[0].text.strip() 422 | return name 423 | 424 | def imdb_custom_lists(plex, movie_id_dict): 425 | for list in IMDB_CUSTOM_LISTS: 426 | url = list.split(",")[0] 427 | try: 428 | name = list.split(",")[1] 429 | except: 430 | name = imdb_custom_list_name(url) 431 | 432 | print "Creating IMDB custom playlist '{0}' using URL {1}".format( 433 | name, 434 | url 435 | ) 436 | 437 | ids = imdb_custom_list(url) 438 | setup_movie_playlist2(plex, ids, movie_id_dict, "IMDB - {0}".format(name)) 439 | 440 | def run_movies_lists(plex): 441 | # Get list of movies from the Plex server 442 | # split into array 443 | movie_libs = MOVIE_LIBRARY_NAME.split(",") 444 | all_movies = [] 445 | log_timer() 446 | 447 | tmp_movie_id_dict = {} 448 | 449 | # loop movie lib array 450 | for lib in movie_libs: 451 | lib = lib.strip() 452 | print("Retrieving a list of movies from the '{library}' library in Plex...".format(library=lib)) 453 | try: 454 | movie_library = plex.library.section(lib) 455 | new_movies = movie_library.all() 456 | all_movies = all_movies + new_movies 457 | print("Added {length} movies to your 'all movies' list from the '{library}' library in Plex...".format(library=lib, length=len(new_movies))) 458 | log_timer() 459 | except: 460 | print("The '{library}' library does not exist in Plex.".format(library=lib)) 461 | print("Exiting script.") 462 | return [], 0 463 | 464 | print "Found {0} movies total in 'all movies' list from Plex...".format( 465 | len(all_movies) 466 | ) 467 | 468 | print "Creating movie dictionary based on ID" 469 | movie_id_dict = create_movie_id_dict(all_movies) 470 | 471 | print("Retrieving new lists") 472 | if TRAKT_API_KEY: 473 | trakt_weekly_imdb_ids = trakt_watched_imdb_id_list() 474 | trakt_popular_imdb_ids = trakt_popular_imdb_id_list() 475 | setup_movie_playlist2(plex, trakt_weekly_imdb_ids, movie_id_dict, TRAKT_WEEKLY_PLAYLIST_NAME) 476 | setup_movie_playlist2(plex, trakt_popular_imdb_ids, movie_id_dict, TRAKT_POPULAR_PLAYLIST_NAME) 477 | else: 478 | print("No Trakt API key, skipping lists") 479 | 480 | imdb_search_lists(plex, movie_id_dict) 481 | imdb_chart_lists(plex, movie_id_dict) 482 | imdb_custom_lists(plex, movie_id_dict) 483 | 484 | def run_show_lists(plex): 485 | # Get list of shows from the Plex server 486 | # split into array 487 | show_libs = SHOW_LIBRARY_NAME.split(",") 488 | all_shows = [] 489 | log_timer() 490 | # loop movie lib array 491 | for lib in show_libs: 492 | lib = lib.strip() 493 | print("Retrieving a list of shows from the '{library}' library in Plex...".format(library=lib)) 494 | try: 495 | show_library = plex.library.section(lib) 496 | new_shows = show_library.all() 497 | all_shows = all_shows + new_shows 498 | print("Added {length} shows to your 'all shows' list from the '{library}' library in Plex...".format(library=lib, length=len(new_shows))) 499 | log_timer() 500 | except: 501 | print("The '{library}' library does not exist in Plex.".format(library=lib)) 502 | print("Exiting script.") 503 | return [], 0 504 | 505 | print "Found {0} show total in 'all shows' list from Plex...".format( 506 | len(all_shows) 507 | ) 508 | 509 | print("Retrieving new lists") 510 | if TRAKT_API_KEY: 511 | trakt_weekly_show_imdb_ids = trakt_watched_show_imdb_id_list() 512 | trakt_popular_show_imdb_ids = trakt_popular_show_imdb_id_list() 513 | setup_show_playlist(plex, trakt_weekly_show_imdb_ids, all_shows, TRAKT_WEEKLY_SHOW_PLAYLIST_NAME) 514 | setup_show_playlist(plex, trakt_popular_show_imdb_ids, all_shows, TRAKT_POPULAR_SHOW_PLAYLIST_NAME) 515 | # setup_show_playlist2(plex, trakt_weekly_show_imdb_ids, show_id_dict, TRAKT_WEEKLY_SHOW_PLAYLIST_NAME) 516 | # setup_show_playlist2(plex, trakt_popular_show_imdb_ids, show_id_dict, TRAKT_POPULAR_SHOW_PLAYLIST_NAME) 517 | else: 518 | print "No Trakt API key, skipping lists" 519 | 520 | def list_remover(plex, playlist_name): 521 | #update my list 522 | print("{}: removing playlist for script user".format(playlist_name)) 523 | remove_playlist(plex, playlist_name) 524 | 525 | #update list for shared users 526 | if SYNC_WITH_SHARED_USERS: 527 | plex_users = get_user_tokens(plex.machineIdentifier) 528 | for user in plex_users: 529 | if (not ALLOW_SYNCED_USERS or user in ALLOW_SYNCED_USERS): 530 | print "{0}: removing playlist for user {1}".format( 531 | playlist_name, 532 | user 533 | ) 534 | user_token = plex_users[user] 535 | user_plex = PlexServer(baseurl=PLEX_URL, token=user_token, timeout=PLEX_TIMEOUT) 536 | remove_playlist(user_plex, playlist_name) 537 | else: 538 | print "{0}: NOT removing playlist for user {1}".format( 539 | playlist_name, 540 | user 541 | ) 542 | else: 543 | print("Skipping removal from shared users") 544 | 545 | def imdb_playlist_remover(plex, name): 546 | list_remover(plex, name) 547 | list_remover(plex, "IMDB Custom List - {0}".format(name)) 548 | list_remover(plex, "IMDB Chart - {0}".format(name)) 549 | list_remover(plex, "IMDB Search List - {0}".format(name)) 550 | list_remover(plex, "IMDB - {0}".format(name)) 551 | 552 | def remove_lists(plex): 553 | for showlist in IMDB_CUSTOM_LISTS: 554 | url = showlist.split(",")[0] 555 | try: 556 | name = showlist.split(",")[1] 557 | except: 558 | name = imdb_custom_list_name(url) 559 | print "Removing IMDB custom playlist '{0}'".format( 560 | name 561 | ) 562 | imdb_playlist_remover(plex, name) 563 | 564 | for showlist in IMDB_CHART_LISTS: 565 | url = showlist.split(",")[0] 566 | try: 567 | name = showlist.split(",")[1] 568 | except: 569 | name = imdb_chart_list_name(url) 570 | print "Removing IMDB chart playlist '{0}'".format( 571 | name 572 | ) 573 | imdb_playlist_remover(plex, name) 574 | 575 | for showlist in IMDB_SEARCH_LISTS: 576 | url = showlist.split("||")[0] 577 | try: 578 | name = showlist.split("||")[1] 579 | except: 580 | name = imdb_search_list_name(url) 581 | print "Removing IMDB search playlist '{0}'".format( 582 | name 583 | ) 584 | imdb_playlist_remover(plex, name) 585 | 586 | def list_updater(): 587 | try: 588 | plex = PlexServer(baseurl=PLEX_URL, token=PLEX_TOKEN, timeout=PLEX_TIMEOUT) 589 | except: 590 | print("No Plex server found at: {base_url} or bad plex token code".format(base_url=PLEX_URL)) 591 | print("Exiting script.") 592 | raw_input("press enter to exit") 593 | return [], 0 594 | 595 | if REMOVE_ONLY: 596 | list_remover(plex, TRAKT_WEEKLY_PLAYLIST_NAME) 597 | list_remover(plex, TRAKT_POPULAR_PLAYLIST_NAME) 598 | list_remover(plex, TRAKT_WEEKLY_SHOW_PLAYLIST_NAME) 599 | list_remover(plex, TRAKT_POPULAR_SHOW_PLAYLIST_NAME) 600 | list_remover(plex, 'Movies All Time') 601 | list_remover(plex, 'Best Picture Winners') 602 | list_remover(plex, 'IMDB - []') 603 | remove_lists(plex) 604 | else: 605 | run_movies_lists(plex) 606 | run_show_lists(plex) 607 | 608 | if __name__ == "__main__": 609 | print("===================================================================") 610 | print(" Automated Playlist to Plex script ") 611 | print("===================================================================\n") 612 | 613 | list_updater() 614 | 615 | print("\n===================================================================") 616 | print(" Done! ") 617 | 618 | log_timer() 619 | 620 | print("===================================================================\n") 621 | -------------------------------------------------------------------------------- /settings.ini.example: -------------------------------------------------------------------------------- 1 | [Plex] 2 | ### [REQUIRED] Plex server details - Github for instructions on token ### 3 | plex-host: http://localhost:32400 4 | plex-token: 5 | 6 | ### [REQUIRED] Current library info - Change defaults to match your current librarys ### 7 | # supports multiple libs, if separate by comma, no spaces 8 | # e.g.: movie-library: Adult Movies,Kid Movies 9 | # e.g.: tv-library: Adult TV,Kid TV 10 | movie-library: Movies 11 | tv-library: Shows 12 | 13 | ### [OPTIONS] Change to have it work how you want ### 14 | # Set to True to remove playlists only, This will not grab lists. It will remove all playlists from variables below. Easy way to undo 15 | remove: False 16 | 17 | # Share playlist with other user? 18 | # Choices True, False -- Caps matter, (if True, syncs all or list, if false, only token user) 19 | shared: False 20 | 21 | # (keep empty list for all users, comma list for specific users.) 22 | # EX: ["username","anotheruser"] << notice the use of double-quotes 23 | # $shared must be True. 24 | users: [] 25 | 26 | # (keep empty list to allow all users, comma list for specific users to block.) 27 | # EX ["greg","luiz"] << notice the use of double-quotes 28 | # $shared must be True. 29 | not_users: [] 30 | 31 | ### [OPTIONAL] Plex server timeout setting .. default 300secs 32 | # Uncomment and increase if you are experiencing timeout issues 33 | # timeout: 300 34 | 35 | [Trakt] 36 | ### [OPTIONAL] Trakt API Info ### 37 | # This is REQUIRED for Trakt. Without it you will not get any Trakt playlists 38 | api-key: 39 | 40 | ### Trakt Movie Playlist info ### 41 | # max is 100 - required trakt-api-key above 42 | movie-total: 80 43 | weekly-movie-name: Movies Top Weekly 44 | popular-movie-name: Movies Popular 45 | 46 | ### Trakt Show Playlist info ### 47 | # max is ? - required trakt-api-key above 48 | tv-total: 20 49 | weekly-tv-name: Show Top Weekly 50 | popular-tv-name: Show Popular 51 | 52 | [IMDb] 53 | # this is a LIST of all your fav IMDB search lists 54 | # view MUST BE simple as a query param 55 | # e.g.: &view=simple 56 | # wrapped in double quotes, you provide the URL and NAME, separated by a double pipes (||), no spaces 57 | # OR .. wrapped in double quotes, you provide the URL and the list NAME will be discovered 58 | # put new list items on a new line 59 | # e.g.: "http://www.imdb.com/search/title?groups=oscar_best_picture_winners&sort=year,desc&view=simple||Yoyo Ma" 60 | # OR .. e.g.: "http://www.imdb.com/search/title?groups=oscar_best_picture_winners&sort=year,desc&view=simple" 61 | search-lists: [ 62 | "http://www.imdb.com/search/title?groups=oscar_best_picture_winners&sort=year,desc&view=simple||Latest Best Picture-Winning Titles", 63 | "http://www.imdb.com/search/title?year=2017,2017&title_type=feature&sort=moviemeter,asc&view=simple" 64 | ] 65 | 66 | # this is a LIST of all the cool IMDB charts 67 | # wrapped in double quotes, you provide the URL and NAME, separated by a comma, no spaces 68 | # OR .. wrapped in double quotes, you provide the URL and the list NAME will be discovered 69 | # put new list items on a new line 70 | # e.g.: "http://www.imdb.com/chart/top,Yoyo Ma" 71 | # OR .. e.g.: "http://www.imdb.com/chart/top" 72 | chart-lists: [ 73 | "http://www.imdb.com/chart/top,Top Rated Movies", 74 | "http://www.imdb.com/chart/moviemeter,Most Popular Movies", 75 | "http://www.imdb.com/chart/boxoffice,Top Box Office", 76 | "http://www.imdb.com/chart/top-english-movies,Top Rated English Movies", 77 | "http://www.imdb.com/chart/bottom" 78 | ] 79 | 80 | # this is a LIST of all your fav IMDB lists 81 | # wrapped in double quotes, you provide the URL and NAME, separated by a comma, no spaces 82 | # OR .. wrapped in double quotes, you provide the URL and the list NAME will be discovered 83 | # put new list items on a new line 84 | # e.g.: "http://www.imdb.com/list/1234567890/,Yoyo Ma" 85 | # OR .. e.g.: "http://www.imdb.com/list/1234567890/" 86 | custom-lists: [ 87 | "http://www.imdb.com/list/ls069751712/,Star Wars Machete Order", 88 | "http://www.imdb.com/list/ls021389231/,2018 Oscar Nominees", 89 | "http://www.imdb.com/list/ls009668531/" 90 | ] 91 | -------------------------------------------------------------------------------- /tests/parse_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from lxml.html import parse 4 | 5 | #tree = parse("http://www.imdb.com/list/ls069751712/") 6 | tree = parse("http://www.imdb.com/list/ls009668531/") 7 | #custom_ids = tree.xpath("//div[contains(@class, 'lister-item-image ribbonize')]/@data-tconst") 8 | name = tree.xpath("//h1[contains(@class, 'header list-name')]")[0].text.strip() 9 | 10 | print type(tree) 11 | print tree 12 | print name 13 | -------------------------------------------------------------------------------- /tests/print_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | def fun(): 4 | print """ 5 | this is a {0} 6 | lot of fun {2} 7 | are you having fun {1} 8 | """.format( 9 | 1, 10 | 2, 11 | 3 12 | ) 13 | 14 | fun() 15 | -------------------------------------------------------------------------------- /tests/promise_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from promise import Promise 4 | import time 5 | 6 | BIG_LIST = range(0,100000000) 7 | 8 | def big_list_breakup(big_list): 9 | chunk = len(big_list) / 3 10 | chunks = [] 11 | chunks.append(big_list[0:chunk]) 12 | chunks.append(big_list[chunk:chunk*2]) 13 | chunks.append(big_list[chunk*2:len(big_list)]) 14 | return chunks 15 | 16 | def iterme(mylist): 17 | cnt = 0 18 | for i in mylist: 19 | cnt = i 20 | return cnt 21 | 22 | st = time.time() 23 | p1 = Promise( 24 | lambda resolve, reject: resolve(iterme(BIG_LIST)) 25 | ) 26 | 27 | print p1.get() 28 | print time.time() - st 29 | 30 | st = time.time() 31 | print time.time() - st 32 | blb = big_list_breakup(BIG_LIST) 33 | p2 = Promise.all([ 34 | Promise.resolve(iterme(blb[0])), 35 | Promise.resolve(iterme(blb[1])), 36 | Promise.resolve(iterme(blb[2])) 37 | ]).then( 38 | lambda res: res 39 | ) 40 | 41 | print p2.get() 42 | print time.time() - st 43 | 44 | 45 | # print len(big_list_breakup(BIG_LIST)) 46 | # print len(big_list_breakup(BIG_LIST)[0]) 47 | # print len(big_list_breakup(BIG_LIST)[1]) 48 | # print len(big_list_breakup(BIG_LIST)[2]) 49 | # 50 | # print big_list_breakup(BIG_LIST)[0][0] 51 | # print big_list_breakup(BIG_LIST)[0][-1] 52 | # print big_list_breakup(BIG_LIST)[1][0] 53 | # print big_list_breakup(BIG_LIST)[1][-1] 54 | # print big_list_breakup(BIG_LIST)[2][0] 55 | # print big_list_breakup(BIG_LIST)[2][-1] 56 | 57 | # manlist = big_list_breakup(BIG_LIST)[0] + \ 58 | # big_list_breakup(BIG_LIST)[1] + \ 59 | # big_list_breakup(BIG_LIST)[2] 60 | # 61 | # for i in manlist: 62 | # print i 63 | -------------------------------------------------------------------------------- /tests/test.ini: -------------------------------------------------------------------------------- 1 | [Plex] 2 | users: [] 3 | not_users: ["frank"] 4 | -------------------------------------------------------------------------------- /tests/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import json 4 | import os 5 | import ConfigParser 6 | 7 | config_file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test.ini') 8 | config = ConfigParser.SafeConfigParser() 9 | config.read(config_file_path) 10 | 11 | USERS = json.loads(config.get('Plex', 'users')) 12 | NOT_USERS = json.loads(config.get('Plex', 'not_users')) 13 | 14 | def not_user(user): 15 | if (not USERS or user in USERS) and user not in NOT_USERS: 16 | print "adding user: {0}".format( 17 | user 18 | ) 19 | else: 20 | print "not adding user: {0}".format( 21 | user 22 | ) 23 | 24 | def empty_user(): 25 | if (not USERS): 26 | print "empty user array" 27 | 28 | if __name__ == "__main__": 29 | not_user("greg") 30 | not_user("frank") 31 | empty_user() 32 | -------------------------------------------------------------------------------- /tests/test0.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import re 4 | 5 | guid = "com.plexapp.agents.imdb://tt0366548?lang=en" 6 | 7 | print re.search(r'tt(\d+)\?', guid).group(0) 8 | print re.search(r'tt(\d+)\?', guid).group(1) 9 | print re.search(r'tt(\d+)\?', guid).group(2) 10 | --------------------------------------------------------------------------------