├── .gitignore ├── PlexDBI.py ├── README.md └── config_empty /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.db* 3 | *.config 4 | *.conf 5 | crash.log 6 | sqlite3 7 | run.script 8 | venv 9 | requests 10 | __pycache__ 11 | config 12 | 13 | 14 | -------------------------------------------------------------------------------- /PlexDBI.py: -------------------------------------------------------------------------------- 1 | try: 2 | import sys 3 | import sqlite3 4 | from datetime import datetime 5 | from datetime import timedelta 6 | import json 7 | import urllib.request 8 | import urllib.error 9 | import configparser 10 | import inspect 11 | import time 12 | import os 13 | from shutil import copyfile 14 | from collections import OrderedDict 15 | from sys import platform as _platform 16 | except ImportError: 17 | print('-------------------------------------------------------------------') 18 | print('Unable to import one or more modules.') 19 | print('Make sure that you are running the script with an updated python 3.') 20 | print('-------------------------------------------------------------------') 21 | sys.exit() 22 | 23 | 24 | class PlexMoviesDBI: 25 | def __init__(self, cursor, config_file): 26 | self.cursor = cursor 27 | self.local_movie_list = dict() 28 | self.movies_provided = 0 29 | 30 | self.config = configparser.ConfigParser() 31 | self.config.read(config_file) 32 | try: 33 | self.library_section = self.config.get('REQUIRED', 'MOVIE_LIBRARY_SECTION') 34 | self.tmdb_api_key = self.config.get('OPTIONAL', 'TMDB_API_KEY') 35 | self.SET_REST_TO_RELEASE = self.config['OPTIONAL'].getboolean('SET_REST_TO_RELEASE', False) 36 | 37 | self.recent_releases_minimum_count = self.config.getint('RECENT_RELEASES', 'MIN_COUNT') 38 | self.recent_releases_maximum_count = self.config.getint('RECENT_RELEASES', 'MAX_COUNT') 39 | self.recent_releases_order = self.config.getint('RECENT_RELEASES', 'ORDER') 40 | self.recent_releases_day_limit = self.config.getint('RECENT_RELEASES', 'DAY_LIMIT') 41 | 42 | self.old_but_gold_count = self.config.getint('OLD_BUT_GOLD', 'COUNT') 43 | self.old_but_gold_order = self.config.getint('OLD_BUT_GOLD', 'ORDER') 44 | self.old_but_gold_year_limit = self.config.getint('OLD_BUT_GOLD', 'YEAR_LIMIT') 45 | self.old_but_gold_min_critic_score = self.config.getfloat('OLD_BUT_GOLD', 'MIN_CRITIC_SCORE') 46 | 47 | self.hidden_gem_count = self.config.getint('HIDDEN_GEM', 'COUNT') 48 | self.hidden_gem_order = self.config.getint('HIDDEN_GEM', 'ORDER') 49 | 50 | self.random_count = self.config.getint('RANDOM', 'COUNT') 51 | self.random_order = self.config.getint('RANDOM', 'ORDER') 52 | if self.recent_releases_minimum_count < 0: 53 | self.recent_releases_minimum_count = 0 54 | if self.recent_releases_minimum_count > self.recent_releases_maximum_count: 55 | self.recent_releases_minimum_count = self.recent_releases_maximum_count 56 | if self.old_but_gold_count < 0: 57 | self.old_but_gold_count = 0 58 | if self.hidden_gem_count < 0: 59 | self.hidden_gem_count = 0 60 | if self.random_count < 0: 61 | self.random_count = 0 62 | 63 | except ValueError: 64 | print('---------------------------------------------------------------------------') 65 | print('Something seems to be wrong in the config file.') 66 | print('Make sure that everything in the "REQUIRED" section is filled out correctly.') 67 | print('All the parameters expects a number except for the TMDB_API_KEY parameter.') 68 | print('---------------------------------------------------------------------------') 69 | raise 70 | except configparser.NoSectionError: 71 | print('---------------------------------------------------------------------------') 72 | print('Something seems to be wrong with the config file.') 73 | print('To fix this you could let the script generate a new config file and refill ') 74 | print('the config file. Or if the script isn\'t producing a config file as expected') 75 | print('you can fill in the empty one and rename it to "config".') 76 | print('---------------------------------------------------------------------------') 77 | raise 78 | 79 | if not self.check_library_section(self.library_section): 80 | raise ValueError 81 | 82 | self.order_power = max(self.recent_releases_maximum_count, 83 | self.random_count, 84 | self.hidden_gem_count, 85 | self.old_but_gold_count) 86 | self.compress_order() 87 | 88 | def compress_order(self): 89 | 90 | order_list = {'recent_releases': self.recent_releases_order, 91 | 'old_but_gold': self.old_but_gold_order, 92 | 'hidden_gem': self.hidden_gem_order, 93 | 'random_order': self.random_order} 94 | 95 | order_list = OrderedDict(sorted(order_list.items(), key=lambda t: t[1])) 96 | 97 | pos = 0 98 | for i in order_list: 99 | order_list[i] = pos 100 | pos += 1 101 | 102 | self.recent_releases_order = order_list['recent_releases'] 103 | self.old_but_gold_order = order_list['old_but_gold'] 104 | self.hidden_gem_order = order_list['hidden_gem'] 105 | self.random_order = order_list['random_order'] 106 | 107 | def find_movies(self): 108 | self.recent_releases(self.recent_releases_minimum_count, 109 | self.recent_releases_maximum_count, 110 | self.recent_releases_order, 111 | self.recent_releases_day_limit) 112 | 113 | self.old_but_gold(self.old_but_gold_count, 114 | self.old_but_gold_order, 115 | self.old_but_gold_year_limit, 116 | self.old_but_gold_min_critic_score) 117 | 118 | if len(self.tmdb_api_key) > 5: 119 | self.hidden_gem(self.hidden_gem_count, 120 | self.hidden_gem_order) 121 | else: 122 | print("INFO: You have not specified a tmdb api key.") 123 | 124 | self.random(self.random_count, 125 | self.random_order) 126 | if self.SET_REST_TO_RELEASE: 127 | self.set_rest_to_release() 128 | return self.local_movie_list 129 | 130 | def check_library_section(self, library_section): 131 | self.cursor.execute("SELECT language,section_type " 132 | "FROM library_sections " 133 | "WHERE id = ?", (library_section,)) 134 | 135 | info = self.cursor.fetchone() 136 | 137 | try: 138 | 139 | if info[0] == 'xn' or info[1] != 1: 140 | print('Your MOVIE_LIBRARY_SECTION parameter in the config file is not a Movie library.') 141 | print('These libraries are movie libraries and can be used in this script:') 142 | print('-----------------------------------------------------------------------------------------') 143 | 144 | for library in self.cursor.execute("SELECT id, name " 145 | "FROM library_sections " 146 | "WHERE language IS NOT 'xn' " 147 | "AND section_type = 1 " 148 | "ORDER BY id ASC "): 149 | print('The library "' + library[1] + '" have section_id: ' + str(library[0]) + '.') 150 | 151 | print('-----------------------------------------------------------------------------------------') 152 | print('Please use one of these ID\'s as your MOVIE_LIBRARY_SECTION parameter in the config file.') 153 | 154 | return False 155 | except TypeError: 156 | print('Your MOVIE_LIBRARY_SECTION parameter in the config file is not a Movie library.') 157 | print('These libraries are movie libraries and can be used in this script:') 158 | print('-----------------------------------------------------------------------------------------') 159 | 160 | for library in self.cursor.execute("SELECT id, name " 161 | "FROM library_sections " 162 | "WHERE language IS NOT 'xn' " 163 | "AND section_type = 1 " 164 | "ORDER BY id ASC "): 165 | print('The library "' + library[1] + '" have section_id: ' + str(library[0]) + '.') 166 | 167 | print('-----------------------------------------------------------------------------------------') 168 | print('Please use one of these ID\'s as your MOVIE_LIBRARY_SECTION parameter in the config file.') 169 | 170 | return False 171 | return True 172 | 173 | def get_reference_date(self): 174 | try: 175 | self.cursor.execute("SELECT originally_available_at " # most recent movie for reference 176 | "FROM metadata_items " 177 | "WHERE library_section_id = ? " 178 | "AND metadata_type = 1 " 179 | "AND duration > 1 " 180 | "ORDER BY originally_available_at DESC ", (self.library_section,)) 181 | except sqlite3.OperationalError: 182 | print('------------------------------------------------------------------') 183 | print('Remember to fill in the MOVIE_LIBRARY_SECTION in the config file. ') 184 | print('Also make sure that the "PlexDatabase.db" syslink isn\'t broken. ') 185 | print('here is a list on viable libraries you have: ') 186 | print('------------------------------------------------------------------') 187 | print('Exiting') 188 | 189 | return "error" 190 | 191 | for attempt in self.cursor.fetchall(): 192 | reference_date = attempt[0] 193 | if reference_date != 'None': 194 | reference_date = reference_date 195 | return reference_date 196 | 197 | print("----Script failed----") 198 | print("Failed finding a reference date.") 199 | return "error" 200 | 201 | def recent_releases(self, minimum, maximum, order, day_limit): 202 | self.movies_provided = 0 203 | if PlexMoviesDBI.get_reference_date(self) == 'error': 204 | return 205 | else: 206 | reference_date = datetime.strptime(PlexMoviesDBI.get_reference_date(self), '%Y-%m-%d %H:%M:%S') \ 207 | + timedelta(days=-day_limit) 208 | 209 | if maximum > 0: 210 | for movieInfo in self.cursor.execute("SELECT id,title " # modifies 7 movies within 14 days... 211 | "FROM metadata_items " 212 | "WHERE library_section_id = ? " 213 | "AND metadata_type = 1 " 214 | "AND duration > 1 " 215 | "AND originally_available_at > ? " 216 | "ORDER BY originally_available_at DESC " 217 | "LIMIT ?", (self.library_section, 218 | reference_date.isoformat().replace('T', ' '), 219 | maximum)): 220 | 221 | movie_id = movieInfo[0] 222 | title = str(movieInfo[1]) 223 | 224 | if title == 'None': 225 | print("----Script failed----") 226 | print("No title were found for a movie and it was skipped.") 227 | print("Not a critical error and can be ignored unless it's a common occurrence.") 228 | print("movie_id: " + movie_id) 229 | continue 230 | elif movie_id == 'None': 231 | print("----Script failed----") 232 | print("No id were found for a movie and it was skipped.") 233 | print("Not a critical error and can be ignored unless it's a common occurrence.") 234 | continue 235 | 236 | self.add_to_queue(movie_id, order, title) 237 | if minimum - len(self.local_movie_list) > 0: 238 | for movieInfo in self.cursor.execute("SELECT id,title " # ...but at least the last 3 movies 239 | "FROM metadata_items " 240 | "WHERE library_section_id = ? " 241 | "AND metadata_type = 1 " 242 | "AND duration > 1 " 243 | "AND originally_available_at < ? " 244 | "ORDER BY originally_available_at DESC " 245 | "LIMIT ?", (self.library_section, 246 | reference_date.isoformat().replace('T', ' '), 247 | minimum - len(self.local_movie_list),)): 248 | 249 | movie_id = movieInfo[0] 250 | title = str(movieInfo[1]) 251 | 252 | if title == 'None': 253 | print("----Script failed----") 254 | print("No title were found for a movie and it was skipped.") 255 | print("Not a critical error and can be ignored unless it's a common occurrence.") 256 | print("movie_id: " + movie_id) 257 | continue 258 | elif movie_id == 'None': 259 | print("----Script failed----") 260 | print("No id were found for a movie and it was skipped.") 261 | print("Not a critical error and can be ignored unless it's a common occurrence.") 262 | continue 263 | 264 | self.add_to_queue(movie_id, order, title) 265 | 266 | def old_but_gold(self, count, order, age_limit, score_limit): 267 | self.movies_provided = 0 268 | for movie_info in self.cursor.execute("SELECT id,title " # old but gold 269 | "FROM metadata_items " 270 | "WHERE id IN (SELECT id FROM metadata_items ORDER BY RANDOM()) " 271 | "AND originally_available_at < ? " 272 | "AND rating > ? " 273 | "AND library_section_id = ? " 274 | "AND metadata_type = 1 " 275 | "ORDER BY RANDOM() " 276 | "LIMIT ?", (datetime.now() + timedelta(days=-(age_limit*365)), 277 | score_limit, 278 | self.library_section, count,)): 279 | 280 | movie_id = movie_info[0] 281 | title = str(movie_info[1]) 282 | 283 | if title == 'None': 284 | print("----Script failed----") 285 | print("No title were found for a movie and it was skipped.") 286 | print("Not a critical error and can be ignored unless it's a common occurrence.") 287 | print("movieId: " + movie_id) 288 | continue 289 | elif movie_id == 'None': 290 | print("----Script failed----") 291 | print("No id were found for a movie and it was skipped.") 292 | print("Not a critical error and can be ignored unless it's a common occurrence.") 293 | continue 294 | 295 | self.add_to_queue(movie_id, order, title) 296 | 297 | def hidden_gem(self, count, order): 298 | self.movies_provided = 0 299 | 300 | for i in range(count): 301 | lowest_popularity = 100000.0 302 | selected_id = -1 303 | selected_title = '' 304 | 305 | for movie_info in self.cursor.execute("SELECT id,title,year,rating " # Potential hidden gem 306 | "FROM metadata_items " 307 | "WHERE id IN (SELECT id FROM metadata_items ORDER BY RANDOM()) " 308 | "AND library_section_id = ? " 309 | "AND metadata_type = 1 " 310 | "AND rating > 1 " 311 | "ORDER BY RANDOM() " 312 | "LIMIT 8", (self.library_section,)): 313 | movie_id = movie_info[0] 314 | title = str(movie_info[1]) 315 | year = str(movie_info[2]) 316 | rating = movie_info[3] 317 | 318 | if title == 'None': 319 | print("----Script failed----") 320 | print("No title were found for a movie and it was skipped.") 321 | print("Not a critical error and can be ignored unless it's a common occurrence.") 322 | print("movie_id: " + movie_id) 323 | print("year: " + year) 324 | print("rating: " + rating) 325 | continue 326 | elif movie_id == 'None': 327 | print("----Script failed----") 328 | print("No id were found for the movie \"" + title + "\" and it was skipped.") 329 | print("Not a critical error and can be ignored unless it's a common occurrence.") 330 | continue 331 | elif year == 'None': 332 | print("----Script failed----") 333 | print("No year were found for the movie \"" + title + "\" and it was skipped.") 334 | print("Not a critical error and can be ignored unless it's a common occurrence.") 335 | continue 336 | 337 | title = title.replace(' ', '+') 338 | title = title.encode('ascii', errors='ignore') # sanitise title from unicode characters 339 | title = title.decode('utf-8') 340 | 341 | try: 342 | 343 | response = urllib.request.urlopen('https://api.themoviedb.org/3/search/movie' 344 | '?api_key=' + self.tmdb_api_key + 345 | '&query=' + title + 346 | '&year=' + year) 347 | data = json.loads(response.read().decode('utf-8')) 348 | 349 | except urllib.error.HTTPError as urlib_error: 350 | print("----Script failed----") 351 | print("TMDB api failed, skipping one movie from the category 'Hidden Gems' see error: ") 352 | print("URL requested: " + urlib_error.geturl()) 353 | print("Reason: " + urlib_error.reason) 354 | continue 355 | 356 | if data['total_results'] > 0: 357 | compensated_pop = float(data['results'][0]['popularity']) * \ 358 | (1.05 ** (2015 - int(year))) * \ 359 | (1 / (rating ** 1.2)) 360 | if lowest_popularity > compensated_pop: 361 | lowest_popularity = compensated_pop 362 | selected_id = movie_id 363 | selected_title = title 364 | 365 | if selected_id >= 0: 366 | self.add_to_queue(selected_id, order, selected_title.replace('+', ' ')) 367 | 368 | def random(self, count, order): 369 | self.movies_provided = 0 370 | 371 | for row in self.cursor.execute("SELECT id,title " # Random 372 | "FROM metadata_items " 373 | "WHERE id IN (SELECT id FROM metadata_items ORDER BY RANDOM()) " 374 | "AND library_section_id = ? " 375 | "AND metadata_type = 1 " 376 | "ORDER BY RANDOM() " 377 | "LIMIT ?", (self.library_section, count,)): 378 | 379 | movie_id = row[0] 380 | title = str(row[1]) 381 | 382 | if title == 'None': 383 | print("----Script failed----") 384 | print("No title were found for a movie and it was skipped.") 385 | print("Not a critical error and can be ignored unless it's a common occurrence.") 386 | print("movie_id: " + movie_id) 387 | continue 388 | if movie_id == 'None': 389 | print("----Script failed----") 390 | print("No id were found for the movie \"" + title + "\" and it was skipped.") 391 | print("Not a critical error and can be ignored unless it's a common occurrence.") 392 | continue 393 | 394 | self.add_to_queue(movie_id, order, title) 395 | 396 | def set_rest_to_release(self): 397 | self.movies_provided = 0 398 | 399 | for row in self.cursor.execute("SELECT id,title,originally_available_at,added_at " # Random 400 | "FROM metadata_items " 401 | "WHERE library_section_id = ? " 402 | "AND metadata_type = 1 ", self.library_section): 403 | 404 | movie_id = row[0] 405 | title = str(row[1]) 406 | 407 | if title == 'None': 408 | print("----Script failed----") 409 | print("No title were found for a movie and it was skipped.") 410 | print("Not a critical error and can be ignored unless it's a common occurrence.") 411 | print("movie_id: " + movie_id) 412 | continue 413 | if movie_id == 'None': 414 | print("----Script failed----") 415 | print("No id were found for the movie \"" + title + "\" and it was skipped.") 416 | print("Not a critical error and can be ignored unless it's a common occurrence.") 417 | continue 418 | 419 | if movie_id not in self.local_movie_list and row[2] != row[3]: 420 | if 8 < len(row[2]) < 22: 421 | self.local_movie_list[movie_id] = row[2] 422 | 423 | def add_to_queue(self, movie_id, order, title): 424 | self.local_movie_list[movie_id] = self.movies_provided + order * self.order_power 425 | self.movies_provided += 1 426 | print('Adding item to movie queue. Title: "' + title + '"') 427 | 428 | 429 | class PlexDBI: 430 | def __init__(self, operative_system, root_access, database_file, config_file): 431 | self.database_file = database_file 432 | self.os = operative_system 433 | self.root_access = root_access 434 | self.operative_system = operative_system 435 | if not os.path.isfile(config_file): 436 | self.generate_config(config_file) 437 | 438 | if not os.path.isfile(database_file): 439 | self.symlink_database(database_file) 440 | self.database = sqlite3.connect(database_file) 441 | self.cursor = self.database.cursor() 442 | 443 | self.config = configparser.ConfigParser() 444 | self.config.read(config_file) 445 | 446 | try: 447 | movies = PlexMoviesDBI(self.cursor, config_file) 448 | mod_queue = movies.find_movies() 449 | if len(mod_queue) > 0: 450 | self.commit(mod_queue) 451 | else: 452 | print('Nothing to change. exiting.') 453 | except ValueError: 454 | raise 455 | 456 | self.database.close() 457 | 458 | def commit(self, mod_queue): 459 | if self.config.getboolean('OPTIONAL', 'BACKUP'): 460 | try: 461 | copyfile(self.database_file, os.path.join(os.path.dirname(os.path.realpath(__file__)), 'plex.bak.db')) 462 | except PermissionError: 463 | print('failed to create backup: permission denied.') 464 | if self.config.get('OPTIONAL', 'BACKUP').lower() == 'yes': 465 | if op_system == 'linux': 466 | if self.root_access: 467 | print('--Stopping plexmediaserver.') 468 | os.system("sudo service plexmediaserver stop") 469 | elif op_system == 'windows': 470 | print('--Stopping plexmediaserver.') 471 | # os.system('taskkill /IM "Plex Media Server.exe" /T') 472 | elif op_system == 'mac_os': 473 | os.system('killall "Plex Media Server"') 474 | 475 | print('----Processing movie queue.') 476 | timestamp = datetime.now().replace(microsecond=0) + timedelta(days=+1) 477 | for movie_id in mod_queue: 478 | if isinstance(mod_queue[movie_id], int): 479 | now = timestamp + timedelta(seconds=-mod_queue[movie_id]) 480 | self.cursor.execute("UPDATE metadata_items " 481 | "SET added_at = ?" 482 | "WHERE id = ?", (now.isoformat().replace('T', ' '), movie_id,)) 483 | else: 484 | self.cursor.execute("UPDATE metadata_items " 485 | "SET added_at = ?" 486 | "WHERE id = ?", (mod_queue[movie_id], movie_id,)) 487 | 488 | print('----Movie queue processed, committing to db.') 489 | self.database.commit() 490 | print('----Changes committed.') 491 | if op_system == 'linux': 492 | if self.root_access: 493 | print('--Starting plexmediaserver.') 494 | os.system("sudo service plexmediaserver start") 495 | else: 496 | print('You can now proceed to restart your Plex server.') 497 | elif op_system == 'windows': 498 | print('--Starting plexmediaserver.') 499 | # os.startfile("C:\Program Files (x86)\Plex\Plex Media Server\Plex Media Server.exe") 500 | # os.startfile("C:\Program Files\Plex\Plex Media Server\Plex Media Server.exe") 501 | print("") 502 | elif op_system == 'mac_os': 503 | print('--Starting plexmediaserver.') 504 | os.system('open /Applications/Plex\ Media\ Server.app') 505 | else: 506 | print('You can now proceed to restart your Plex server.') 507 | 508 | def symlink_database(self, database_file): 509 | if op_system == 'linux': 510 | if self.root_access: 511 | os.system('ln -s "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/' 512 | 'Plug-in Support/Databases/com.plexapp.plugins.library.db" ' 513 | '"' + database_file + '"') 514 | elif op_system == 'windows': 515 | os.system('mklink ' 516 | '"' + database_file + '" ' 517 | '%LOCALAPPDATA%\Plex Media Server\Plug-in Support\Databases\com.plexapp.plugins.library.db') 518 | elif op_system == 'mac_os': 519 | os.system('ln -s ~/Library/Application Support/Plex Media Server/Plug-in Support/Databases/' 520 | 'com.plexapp.plugins.library.db ' 521 | '"' + database_file + '"') 522 | else: 523 | pass 524 | 525 | def generate_config(self, config_file_name): 526 | f = open(config_file_name, "w+") 527 | 528 | if self.operative_system == 'linux': 529 | os.system("sudo chown --reference=" + os.path.dirname(os.path.realpath(__file__)) + "/PlexDBI.py " + config_file_name) 530 | 531 | f.write('') 532 | f.write('\n[REQUIRED]') 533 | f.write('\n MOVIE_LIBRARY_SECTION = 0') 534 | f.write('\n') 535 | f.write('\n[OPTIONAL]') 536 | f.write('\n # leave blank if you don\'t have a key.') 537 | f.write('\n TMDB_API_KEY = ') 538 | f.write('\n') 539 | f.write('\n # Do you want to make a copy of your database before modifying it? (yes/no)') 540 | f.write('\n BACKUP = yes') 541 | f.write('\n') 542 | f.write('\n # if you set the [SET_TO_RELEASE] to true then any other movie not modified by this script will') 543 | f.write('\n # get it\'s added date set to its release date.') 544 | f.write('\n # Use this with every category [COUNT] set to 0 to change all movies to their release date.') 545 | f.write('\n SET_REST_TO_RELEASE = false') 546 | f.write('\n') 547 | f.write('\n[MOVIES_SETTINGS]') 548 | f.write('\n # Here you can control the settings for each individual category') 549 | f.write('\n # the "COUNT" parameter specifies how manny movies from each category will be modified') 550 | f.write('\n # and the "ORDER" specifies in what order the categories will appear.') 551 | f.write('\n') 552 | f.write('\n # The [RECENT_RELEASES] category will provide a list of movies with the most recent releases') 553 | f.write('\n # that isn\'t older than the[DAY_LIMIT].') 554 | f.write('\n # At least [MIN_COUNT] and at the most [MAX_COUNT] will be provided.') 555 | f.write('\n [RECENT_RELEASES]') 556 | f.write('\n MIN_COUNT = 3') 557 | f.write('\n MAX_COUNT = 7') 558 | f.write('\n ORDER = 1') 559 | f.write('\n DAY_LIMIT = 14') 560 | f.write('\n') 561 | f.write('\n # The [OLD_BUT_GOLD] category will provide a random list of movies') 562 | f.write('\n # that is at least [YEAR_LIMIT] old') 563 | f.write('\n # years old but with a minimum critic score of [CRITIC_SCORE] (in a scale from 0 to 10)') 564 | f.write('\n [OLD_BUT_GOLD]') 565 | f.write('\n COUNT = 1') 566 | f.write('\n ORDER = 2') 567 | f.write('\n YEAR_LIMIT = 10') 568 | f.write('\n MIN_CRITIC_SCORE = 7.9') 569 | f.write('\n # The [HIDDEN_GEM] category will provide a random list of movies') 570 | f.write('\n # that have a low popularity on TMDB.') 571 | f.write('\n # Note that this category requires an TMDB_API_KEY to be used.') 572 | f.write('\n [HIDDEN_GEM]') 573 | f.write('\n COUNT = 1') 574 | f.write('\n ORDER = 3') 575 | f.write('\n # The [RANDOM] category will provide a list of random movies.') 576 | f.write('\n [RANDOM]') 577 | f.write('\n COUNT = 1') 578 | f.write('\n ORDER = 4') 579 | f.write('\n') 580 | f.close() 581 | 582 | 583 | start = time.time() 584 | 585 | if _platform == "linux" or _platform == "linux2": 586 | op_system = 'linux' 587 | elif _platform == "darwin": 588 | op_system = 'mac_os' 589 | elif _platform == "win32" or _platform == "win64": 590 | op_system = 'windows' 591 | else: 592 | op_system = 'unknown' 593 | print("---------------------------------------------------------------------------------------------------------") 594 | print("You are not running this script on a Linux, Windows or mac OS, I'm not sure how this will effect") 595 | print("this script. But anything that requires to input or output to the system will excluded when running this") 596 | print("script. It mostly need that capability when setting up files. So if you make sure you have the ") 597 | print("database and the config file in place the rest should run smoothly") 598 | print("---------------------------------------------------------------------------------------------------------") 599 | if op_system == 'linux': 600 | 601 | try: 602 | has_root_access = 0 == os.getuid() 603 | except AttributeError as e: 604 | print(e.args) 605 | has_root_access = False 606 | elif op_system == 'windows' or op_system == 'mac_os': 607 | has_root_access = True 608 | else: 609 | has_root_access = False 610 | 611 | db_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'PlexDatabase.db') 612 | cfg_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'config') 613 | try: 614 | modify_plex_server_1 = PlexDBI(op_system, has_root_access, db_dir, cfg_dir) 615 | except ValueError as e: 616 | print(e) 617 | end = time.time() 618 | 619 | print("----End of script----") 620 | print("Script completed in " + str(int(end - start)) + " seconds.") 621 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # SCRIPT IS CURRENTLY BROKEN AND OBSOLETE. ARCHIVED. 3 | 4 | 5 | # PlexDBI 6 | 7 | This Python3 script is intended to manipulate Plex's homescreen table "Recently added movies" it can also use the tmdb-api 8 | if the user have an api key. Otherwise that'll be ignored. 9 | 10 | Whenever this script is run, the Recently added movie table will be "refreshed" and from left to right 11 | the following movies will appear: 12 | 13 | 1. The movie in the database with the latest release date. 14 | 2. The movie in the database with the 2nd latest release date. 15 | 3. The movie in the database with the 3rd latest release date. 16 | 4. The movie in the database with the 4th latest release date. Provided that it isn't more than 14 day older than the latest. 17 | 5. The movie in the database with the 5th latest release date. Provided that it isn't more than 14 day older than the latest. 18 | 6. The movie in the database with the 6th latest release date. Provided that it isn't more than 14 day older than the latest. 19 | 7. The movie in the database with the 7th latest release date. Provided that it isn't more than 14 day older than the latest. 20 | 21 | 8. A movie that's older than 10 years with a Imdb score of 8 or more will be placed here. "Old but Gold" 22 | 23 | 9. With the tmdb api a random movie with a relative low popularity on tmdb will be chosen. 24 | young movies and good rating increases likely hood of getting "picked". I call it "The Hidden Gem". 25 | 26 | 10. A movie selected randomly in the entire library 27 | 28 | **This can now be modified to your liking in the config file.** 29 | 30 | ---------- 31 | ### Linux installation: 32 | 33 | download the PlexDBI.py file and put it somewhere. for example: 34 | ```sh 35 | cd /opt && git clone https://github.com/KBlixt/PlexDBI.git && cd PlexDBI 36 | ``` 37 | At this point you can run it if you give it sudo privileges. It will set up the syslink for the database and the config file for you. 38 | you'll still have to configure the config file but just run the script and it will tell you what's wrong and how to fix it. 39 | ```sh 40 | sudo python3 PlexDBI.py 41 | ``` 42 | Then it's up to you if you wish to add it to a crontab schedule, personally i've got it scheduled once a day at 10:30 43 | in the root crontab. 44 | ```sh 45 | 30 10 * * * cd /opt/PlexDBI/ && /usr/bin/python3 /opt/PlexDBI/PlexDBI.py 46 | ``` 47 | If you don't want to give it sudo privileges then you'll have make a symlink for the database. i.e: 48 | ```sh 49 | ln -s "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Plug-in Support/Databases/com.plexapp.plugins.library.db" PlexDatabase.db 50 | ``` 51 | Without sudo privilege you'll also have to remember to stop plexmediaserver before you run the script and then restart it 52 | since it can't shut down the plex service without permissions to the "sudo service plexmediaserver stop/start" commands. 53 | Now, I've been running this script plenty of times with plex still online so I think it's ok, but I'm not sure. 54 | better safe than sorry. Although, if you run it while plex is online it will act a bit funky until you restart the plex service. 55 | 56 | alternative you can allow the user running the script sudo access to the "service plexmediaserver \*" command and it should run nicely as long as you set up the symlink yourself. 57 | 58 | ---------- 59 | ### Other installations: 60 | #### for windows 61 | clone the repository, then you'll need to set up a syslink for the database manually using this command when your in the script : 62 | 63 | ```sh 64 | mklink PlexDatabase.db "C:\Users\USERNAME\AppData\Local\Plex Media Server\Plug-in Support\Databases\com.plexapp.plugins.library.db" 65 | ``` 66 | 67 | then run it and follow the instructions. 68 | 69 | #### for mac: 70 | As long as you guys haven't changed the default installation paths to the plex media server app 71 | you really just need to download the script and run it with the latest available python3, however you guys do that. 72 | Preferably run it in a command window, then it'll instruct you of what's wrong and how to fix it. Otherwise make sure 73 | the config file is filled out correctly. 74 | 75 | #### for NAS and other OS: 76 | I really have no clue. if you setup the config file and the database link manually then the 77 | script should run as long as you can run python3 code. 78 | 79 | ---------- 80 | ## FAIR WARNING: 81 | 82 | This script will edit the PlexMediaServer database directly! Specifically, it will change "added_at" in the 83 | "metadata_items" table. 84 | 85 | Backup your database! Just in case really because so far I haven't managed to corrupted mine. And I've been pretty rough. 86 | 87 | ---------- 88 | 89 | ### Incoming improvements 90 | I'll be improving it sometime soon. the following things will be added/modified, just send me a msg if you wish to add something and I'll 91 | take a look at it. 92 | 93 | - implementing a option so you can set movies added date to be their release date. (done) 94 | 95 | - removing the need to make a symlink, just providing the path to the plex home folder should be enough. although the symlink solution 96 | have the advantage of avoiding any perimission issue. 97 | 98 | - clean up the script so it work better in other systems. 99 | -------------------------------------------------------------------------------- /config_empty: -------------------------------------------------------------------------------- 1 | 2 | [REQUIRED] 3 | MOVIE_LIBRARY_SECTION = 0 4 | 5 | [OPTIONAL] 6 | # leave blank if you don't have a key. 7 | TMDB_API_KEY = 8 | 9 | # Do you want to make a copy of your database before modifying it? (yes/no)') 10 | BACKUP = yes 11 | 12 | # if you set the [SET_TO_RELEASE] to true then any other movie not modified by this script will 13 | # get it's added date set to its release date. 14 | # Use this with every category [COUNT] set to 0 to change all movies to their release date. 15 | SET_REST_TO_RELEASE = false 16 | 17 | [MOVIES_SETTINGS] 18 | # Here you can control the settings for each individual category 19 | # the "COUNT" parameter specifies how manny movies from each category will be modified 20 | # and the "ORDER" specifies in what order the categories will appear. 21 | 22 | # The [RECENT_RELEASES] category will provide a list of movies with the most recent releases 23 | # that isn't older than the[DAY_LIMIT]. 24 | # At least [MIN_COUNT] and at the most [MAX_COUNT] will be provided. 25 | [RECENT_RELEASES] 26 | MIN_COUNT = 3 27 | MAX_COUNT = 7 28 | ORDER = 1 29 | DAY_LIMIT = 14 30 | 31 | # The [OLD_BUT_GOLD] category will provide a random list of movies 32 | # that is at least [YEAR_LIMIT] old 33 | # years old but with a minimum critic score of [CRITIC_SCORE] (in a scale from 0 to 10) 34 | [OLD_BUT_GOLD] 35 | COUNT = 1 36 | ORDER = 2 37 | YEAR_LIMIT = 10 38 | MIN_CRITIC_SCORE = 7.9 39 | # The [HIDDEN_GEM] category will provide a random list of movies 40 | # that have a low popularity on TMDB. 41 | # Note that this category requires an TMDB_API_KEY to be used. 42 | [HIDDEN_GEM] 43 | COUNT = 1 44 | ORDER = 3 45 | # The [RANDOM] category will provide a list of random movies. 46 | [RANDOM] 47 | COUNT = 1 48 | ORDER = 4 49 | --------------------------------------------------------------------------------