From 3341325ca76918c9b39febe13360d4e9519f5849 Mon Sep 17 00:00:00 2001 From: Matthew Welch Date: Tue, 30 Jul 2019 15:19:03 -0700 Subject: [PATCH] Added support for tv shows. --- app.py | 29 +++++ comics/comics.py | 4 +- .../templates/comics/PublisherSeriesList.html | 2 +- comics/templates/comics/comicGallery.html | 2 +- comics/templates/comics/index.html | 10 +- comics/templates/comics/publisherList.html | 2 +- comics/templates/comics/seriesList.html | 2 +- movies/movies.py | 5 - movies/templates/movies/index.html | 10 +- movies/templates/movies/moviesList.html | 2 +- scripts/database.py | 115 ++++++++++++++++++ scripts/func.py | 102 +++++++++++++--- scripts/tmdb.py | 48 ++++++++ static/images/default.png | Bin 0 -> 17826 bytes static/images/default_banner.png | Bin 0 -> 8329 bytes templates/base.html | 3 + test.py | 2 +- tv/templates/tv/episodeViewer.html | 56 +++++++++ tv/templates/tv/index.html | 34 ++++++ tv/templates/tv/search.html | 38 ++++++ tv/templates/tv/tvShowList.html | 12 ++ tv/tv.py | 66 ++++++++++ 22 files changed, 511 insertions(+), 33 deletions(-) create mode 100644 static/images/default.png create mode 100644 static/images/default_banner.png create mode 100644 tv/templates/tv/episodeViewer.html create mode 100644 tv/templates/tv/index.html create mode 100644 tv/templates/tv/search.html create mode 100644 tv/templates/tv/tvShowList.html create mode 100644 tv/tv.py diff --git a/app.py b/app.py index 91d00ae..72628ba 100644 --- a/app.py +++ b/app.py @@ -12,6 +12,7 @@ from scripts import database from admin import admin from movies import movies from comics import comics +from tv import tv class NullHandler(logging.Handler): @@ -28,6 +29,7 @@ app = Flask(__name__) app.register_blueprint(comics.Comics) app.register_blueprint(admin.Admin) app.register_blueprint(movies.Movies) +app.register_blueprint(tv.TV) app.config["SECRET_KEY"] = "***REMOVED***" app.config["FLASK_LOG_LEVEL"] = "DEBUG" flask_log = Logging(app) @@ -63,6 +65,12 @@ def get_movies(): func.get_movies() +def get_tv_shows(): + with app.app_context(): + func.get_tv_shows() + func.get_tv_episodes() + + with app.app_context(): database.initialize_db() thread = threading.Thread(target=get_comics, args=()) @@ -71,6 +79,9 @@ with app.app_context(): thread2 = threading.Thread(target=get_movies, args=()) thread2.daemon = True thread2.start() + thread3 = threading.Thread(target=get_tv_shows, args=()) + thread3.daemon = True + thread3.start() @app.teardown_appcontext @@ -94,8 +105,26 @@ def update_movie_db(sender, **kw): print(e) +def update_tv_show_db(sender, **kw): + try: + database.add_tv_shows(kw["tv_show"]) + if kw["tv_episodes"]: + database.add_tv_episodes(kw["tv_episodes"]) + except Exception as e: + print(e) + + +def update_tv_episodes_db(sender, **kw): + try: + database.add_tv_episodes(kw["tv_episodes"]) + except Exception as e: + print(e) + + func.comic_loaded.connect(update_comic_db) func.movie_loaded.connect(update_movie_db) +func.tv_show_loaded.connect(update_tv_show_db) +func.tv_episodes_loaded.connect(update_tv_episodes_db) @login_manager.user_loader diff --git a/comics/comics.py b/comics/comics.py index f1651c7..0cba804 100644 --- a/comics/comics.py +++ b/comics/comics.py @@ -146,8 +146,8 @@ def comic_gallery(publisher, series, series_year, issue): def get_comic_page(comic_id, page_number): meta = database.db_get_comic_by_id(comic_id) comic = func.open_comic(meta["path"]) - byteImage = BytesIO(comic.getPage(page_number)) - image = Image(file=byteImage) + byte_image = BytesIO(comic.getPage(page_number)) + image = Image(file=byte_image) response = make_response(image.make_blob()) response.headers["cache-control"] = "public" date = pytz.utc.localize(datetime.datetime.utcfromtimestamp(os.path.getmtime(meta["path"]))) diff --git a/comics/templates/comics/PublisherSeriesList.html b/comics/templates/comics/PublisherSeriesList.html index f2361e9..3cfe5dc 100644 --- a/comics/templates/comics/PublisherSeriesList.html +++ b/comics/templates/comics/PublisherSeriesList.html @@ -2,7 +2,7 @@
- +
{{ publisher_series[i]["series"] }} {{ publisher_series[i]["seriesYear"] }}
diff --git a/comics/templates/comics/comicGallery.html b/comics/templates/comics/comicGallery.html index 05a6e6d..fe61716 100644 --- a/comics/templates/comics/comicGallery.html +++ b/comics/templates/comics/comicGallery.html @@ -9,7 +9,7 @@ {% for page_number in range(start, end) %}
diff --git a/comics/templates/comics/index.html b/comics/templates/comics/index.html index dffdc8c..f72aebf 100644 --- a/comics/templates/comics/index.html +++ b/comics/templates/comics/index.html @@ -1,7 +1,15 @@ {% extends "base.html" %} {% block nav %} - Search + {% endblock %} {% block content %} diff --git a/comics/templates/comics/publisherList.html b/comics/templates/comics/publisherList.html index 10c8b3e..1b183e0 100644 --- a/comics/templates/comics/publisherList.html +++ b/comics/templates/comics/publisherList.html @@ -2,7 +2,7 @@
- +
{{ publishers[i] }}
diff --git a/comics/templates/comics/seriesList.html b/comics/templates/comics/seriesList.html index a0778b2..e6138f0 100644 --- a/comics/templates/comics/seriesList.html +++ b/comics/templates/comics/seriesList.html @@ -2,7 +2,7 @@
- +
{{ comics[i]["series"] }} {% if comics[i]["issue"] > 0 %}{{ "#{0:g}".format(comics[i]["issue"]) }}{% endif %} {% if comics[i]["title"] != None %}{{ comics[i]["title"] }} {% endif %}
diff --git a/movies/movies.py b/movies/movies.py index a0c814d..6cfc55d 100644 --- a/movies/movies.py +++ b/movies/movies.py @@ -1,11 +1,6 @@ from flask import Blueprint, render_template, request, make_response, send_file, send_from_directory from flask_login import login_required -from urllib import parse -from io import BytesIO -from wand.image import Image -import os, pytz, datetime - from scripts import database, func Movies = Blueprint("movies", __name__, template_folder="templates") diff --git a/movies/templates/movies/index.html b/movies/templates/movies/index.html index 0b64ce5..93841bc 100644 --- a/movies/templates/movies/index.html +++ b/movies/templates/movies/index.html @@ -1,7 +1,15 @@ {% extends "base.html" %} {% block nav %} -
Search + {% endblock %} {% block content %} diff --git a/movies/templates/movies/moviesList.html b/movies/templates/movies/moviesList.html index 9d12efb..85eebd4 100644 --- a/movies/templates/movies/moviesList.html +++ b/movies/templates/movies/moviesList.html @@ -2,7 +2,7 @@
- +
{{ movies[i]["title"] }} ({{ movies[i]["year"] }})
diff --git a/scripts/database.py b/scripts/database.py index dbdd7b1..0f29371 100644 --- a/scripts/database.py +++ b/scripts/database.py @@ -109,6 +109,26 @@ def initialize_db(): "directors_cut" INTEGER, "poster_path" TEXT, "backdrop_path" TEXT +)""") + get_db().execute("""CREATE TABLE IF NOT EXISTS "tv_shows" ( + "imdb_id" TEXT UNIQUE, + "tmdb_id" INTEGER UNIQUE, + "title" TEXT, + "year" INTEGER, + "description" TEXT, + "poster_path" TEXT, + "path" TEXT + )""") + get_db().execute("""CREATE TABLE IF NOT EXISTS "tv_episodes" ( + "imdb_id" INTEGER UNIQUE, + "parent_imdb_id" INTEGER, + "tmdb_id" INTEGER UNIQUE, + "title" TEXT, + "season" INTEGER, + "episode" INTEGER, + "description" TEXT, + "still_path" TEXT, + "path" TEXT )""") get_db().execute("CREATE INDEX IF NOT EXISTS path_index ON comics(path);") get_db().execute("CREATE INDEX IF NOT EXISTS id_index ON comic_thumbnails(id);") @@ -116,6 +136,7 @@ def initialize_db(): get_imdb().execute("CREATE INDEX IF NOT EXISTS original_title_index ON title_basics(originalTitle)") get_imdb().execute("CREATE INDEX IF NOT EXISTS primary_title_index ON title_basics(primaryTitle)") + get_imdb().execute("CREATE INDEX IF NOT EXISTS parent_tconst_index ON title_episode(parentTconst)") get_imdb().commit() @@ -134,6 +155,23 @@ def add_movies(movies): get_db().commit() +def add_tv_shows(tv_show): + try: + get_db().execute("INSERT INTO tv_shows(imdb_id, tmdb_id, title, year, description, poster_path, path) VALUES(?,?,?,?,?,?,?)", tv_show) + get_db().commit() + except Exception as e: + print(type(e), e) + + +def add_tv_episodes(episodes): + for episode in episodes: + try: + get_db().execute("INSERT INTO tv_episodes(imdb_id, parent_imdb_id, tmdb_id, title, season, episode, description, still_path, path) VALUES(?,?,?,?,?,?,?,?,?)", episode) + get_db().commit() + except Exception as e: + print(type(e), e) + + def add_comics(meta, thumbnails): data = [] for info in meta: @@ -253,6 +291,29 @@ def movie_path_in_db(path): return False +def tv_show_path_in_db(path): + try: + result = get_db().execute("SELECT path FROM tv_shows WHERE path=?", [path]).fetchone() + if result: + return True + except Exception as e: + print(path) + print(type(e), e) + return False + + +def tv_episode_path_in_db(path): + try: + result = get_db().execute("SELECT path FROM tv_episodes WHERE path=?", [path]).fetchone() + if result: + return True + except Exception as e: + print(path) + print(type(e), e) + return False + + + def verify_paths(): rows = get_db().execute("SELECT path FROM comics").fetchall() get_db().commit() @@ -278,11 +339,35 @@ def imdb_get_movie(title, year): return row +def imdb_get_tv_show(title, year): + row = get_imdb().execute( + "SELECT tconst FROM title_basics WHERE (originalTitle LIKE ? OR primaryTitle LIKE ?) AND (titleType LIKE 'tvSeries' OR titleType LIKE 'tvMiniSeries') AND startYear=?", + (title, title, year)).fetchone() + return row + + +def imdb_get_tv_episode(imdb_id, season, episode): + row = get_imdb().execute( + "SELECT tconst FROM title_episode WHERE parentTconst=? AND seasonNumber=? AND episodeNumber=?", + [imdb_id, season, episode]).fetchone() + return row + + def tmdb_get_movie_by_imdb_id(imdb_id): data = tmdb.get_movie_data(imdb_id) return data +def tmdb_get_tv_show_by_imdb_id(imdb_id): + data = tmdb.get_tv_show_data(imdb_id) + return data + + +def tmdb_get_tv_episode_by_imdb_id(imdb_id): + data = tmdb.get_tv_episode_data(imdb_id) + return data + + def db_get_all_movies(): rows = get_db().execute("SELECT * FROM movies ORDER BY title, year;").fetchall() return rows @@ -293,6 +378,21 @@ def db_get_movie_by_imdb_id(imdb_id, extended=0, directors_cut=0): return row +def get_all_tv_shows(): + rows = get_db().execute("SELECT * FROM tv_shows ORDER BY title, year;").fetchall() + return rows + + +def get_tv_show_episodes_by_imdb_id(imdb_id): + rows = get_db().execute("SELECT * FROM tv_episodes WHERE parent_imdb_id=? ORDER BY season, episode", [imdb_id]).fetchall() + return rows + + +def db_get_episode_by_imdb_id(imdb_id): + row = get_db().execute("SELECT * FROM tv_episodes WHERE imdb_id=?", [imdb_id]).fetchone() + return row + + def db_search_table_columns_by_query(query, table, columns, group="", order=""): results = {} final_query = "%"+query.replace(" ", "%")+"%" @@ -356,6 +456,21 @@ def db_search_movies(query): return movies +def db_search_tv_shows(query): + results = db_search_table_columns_by_query(query, "tv_shows", ["title", "year", "description"], order="title") + tv_shows = [] + for show in results["title"]: + if show not in tv_shows: + tv_shows.append(show) + for show in results["description"]: + if show not in tv_shows: + tv_shows.append(show) + for show in results["year"]: + if show not in tv_shows: + tv_shows.append(show) + return tv_shows + + def resize_image(image, new_width=256, new_height=256): new_image = image orig_height = new_image.height diff --git a/scripts/func.py b/scripts/func.py index b6c46bc..c511214 100644 --- a/scripts/func.py +++ b/scripts/func.py @@ -10,6 +10,8 @@ from scripts import database rpi_signals = Namespace() comic_loaded = rpi_signals.signal("comic-loaded") movie_loaded = rpi_signals.signal("movie-loaded") +tv_show_loaded = rpi_signals.signal("tv_show_loaded") +tv_episodes_loaded = rpi_signals.signal("tv_episodes_loaded") publishers_to_ignore = ["***REMOVED***"] @@ -26,6 +28,7 @@ MC_COMICS_DIRECTORY = "C:\\Users\\Matthew\\Documents\\Comics" COMICS_DIRECTORY = RPI_COMICS_DIRECTORY if os.path.exists(RPI_COMICS_DIRECTORY) else MC_COMICS_DIRECTORY MOVIES_DIRECTORY = RPI_MOVIES_DIRECTORY +TV_SHOWS_DIRECTORY = RPI_TV_SHOWS_DIRECTORY ############# @@ -74,20 +77,21 @@ def get_comics(): def get_comic(path): meta = [] thumbnails = [] - if not database.comic_path_in_db(path): - try: - test_path = path.encode("utf8") - except Exception as e: - print("encoding failed on:", path) - return - archive = open_comic(path) - md = archive.readCIX() - if md.publisher in publishers_to_ignore: - return - print(path) - meta.append((path, md)) - thumbnails.append(get_comic_thumbnails(archive)) - comic_loaded.send("anonymous", meta=meta, thumbnails=thumbnails) + if path.endswith(".cbr"): + if not database.comic_path_in_db(path): + try: + test_path = path.encode("utf8") + except Exception as e: + print("encoding failed on:", path) + return + archive = open_comic(path) + md = archive.readCIX() + if md.publisher in publishers_to_ignore: + return + print(path) + meta.append((path, md)) + thumbnails.append(get_comic_thumbnails(archive)) + comic_loaded.send("anonymous", meta=meta, thumbnails=thumbnails) def get_comic_thumbnails(comic): @@ -118,7 +122,7 @@ def open_comic(path): def get_movies(): print("start load movies") - pattern = r"(.+)( \(....\))(\(extended\))?( Director's Cut)?(\.mkv)" + pattern = r"(.+) \((....)\)(\(extended\))?( Director's Cut)?(\.mkv)" movies = [] total_movies = 0 movies_in_db = 0 @@ -135,9 +139,9 @@ def get_movies(): print(f, "did not match regex.") continue print("movie path:", path) - title = f[:-4].replace(match.group(2), "") + title = match.group(1) print("movie title:", title) - year = int(match.group(2)[2:-1]) + year = int(match.group(2)) extended = 0 directors_cut = 0 if match.group(3): @@ -149,7 +153,7 @@ def get_movies(): else: imdb_data = database.imdb_get_movie(title, year) if not imdb_data: - print("could not get imdb data") + print("could not get imdb data for:", title, year) continue imdb_id = imdb_data["tconst"] length = imdb_data["runtimeMinutes"] @@ -176,3 +180,65 @@ def get_movies(): print("total movies:", total_movies) print("movies in database:", movies_in_db) print("movies added:", movies_added) + + +def get_tv_shows(): + dir_pattern = r"(.+) \((....)\)" + for dir in sorted(os.listdir(TV_SHOWS_DIRECTORY)): + dir_match = re.fullmatch(dir_pattern, dir) + if dir_match: + path = TV_SHOWS_DIRECTORY+dir+"/" + if not database.tv_show_path_in_db(path): + series_name = dir_match.group(1) + series_year = int(dir_match.group(2)) + imdb_data = database.imdb_get_tv_show(series_name, series_year) + if not imdb_data: + print("could not get imdb data for:", series_name, series_year) + continue + imdb_id = imdb_data["tconst"] + tmdb_data = database.tmdb_get_tv_show_by_imdb_id(imdb_id) + if not tmdb_data: + print("could not get tmdb data for:", series_name, series_year) + with open("/var/lib/rpiWebApp/log.txt", "a") as f: + f.write("could not get tmdb data for: " + imdb_id + " " + series_name + " " + str(series_year)+"\n") + continue + tmdb_id = tmdb_data[0] + description = tmdb_data[1] + poster_path = tmdb_data[2] + tv_show_data = (imdb_id, tmdb_id, series_name, series_year, description, poster_path, path) + tv_show_loaded.send("anonymous", tv_show=tv_show_data) + print("finished load tv shows.") + + +def get_tv_episodes(): + video_pattern = r"S(..)E(..) - (.+)(.mp4|.mkv)" + rows = database.get_all_tv_shows() + for tv_show in rows: + episodes = [] + for video in sorted(os.listdir(tv_show["path"])): + video_match = re.fullmatch(video_pattern, video) + if video_match: + path = tv_show["path"] + video + if not database.tv_episode_path_in_db(path): + season = int(video_match.group(1)) + episode = int(video_match.group(2)) + episode_name = video_match.group(3) + episode_imdb_data = database.imdb_get_tv_episode(tv_show["imdb_id"], season, episode) + if not episode_imdb_data: + print("could not get imdb data for:", tv_show["title"], tv_show["year"], season, episode) + continue + episode_imdb_id = episode_imdb_data["tconst"] + episode_tmdb_data = database.tmdb_get_tv_episode_by_imdb_id(episode_imdb_id) + if not episode_tmdb_data: + print("could not get tmdb data for:", tv_show["title"], tv_show["year"], season, episode) + with open("/var/lib/rpiWebApp/log.txt", "a") as f: + f.write("could not get tmdb data for: " + episode_imdb_id + " " + tv_show["title"] + " " + str( + tv_show["year"]) + " " + str(season) + " " + str(episode) + "\n") + continue + episode_tmdb_id = episode_tmdb_data[0] + episode_description = episode_tmdb_data[1] + episode_still_path = episode_tmdb_data[2] + episodes.append((episode_imdb_id, tv_show["imdb_id"], episode_tmdb_id, episode_name, season, episode, + episode_description, episode_still_path, path)) + tv_episodes_loaded.send("anonymous", tv_episodes=episodes) + print("finish load tv episodes.") diff --git a/scripts/tmdb.py b/scripts/tmdb.py index aadc21e..0d5ffb8 100644 --- a/scripts/tmdb.py +++ b/scripts/tmdb.py @@ -1,4 +1,7 @@ import requests +import logging +logging.getLogger("requests").setLevel(logging.WARNING) +logging.getLogger("urllib3").setLevel(logging.WARNING) API_KEY = "***REMOVED***" TMDB_FIND_URL = "https://api.themoviedb.org/3/find/" @@ -24,3 +27,48 @@ def get_movie_data(imdb_id): backdrop_path = info["movie_results"][0]["backdrop_path"] return movie_id, overview, poster_path, backdrop_path + + +def get_tv_show_data(imdb_id): + data = { + "api_key": API_KEY, + "language": "en-US", + "external_source": "imdb_id" + } + r = requests.get(TMDB_FIND_URL+imdb_id, data=data) + info = dict(r.json()) + if "status_code" in info.keys(): + print("error getting tmdb data, status code:", info["status_code"]) + return None + if info["tv_results"] == []: + print("no tmdb results for:", imdb_id) + return None + print("tmdb movie title:", info["tv_results"][0]["name"]) + tv_show_id = info["tv_results"][0]["id"] + overview = info["tv_results"][0]["overview"] + poster_path = info["tv_results"][0]["poster_path"] + + return tv_show_id, overview, poster_path + + +def get_tv_episode_data(imdb_id): + data = { + "api_key": API_KEY, + "language": "en-US", + "external_source": "imdb_id" + } + r = requests.get(TMDB_FIND_URL+imdb_id, data=data) + info = dict(r.json()) + if "status_code" in info.keys(): + print("error getting tmdb data, status code:", info["status_code"]) + return None + if info["tv_episode_results"] == []: + print("no tmdb results for:", imdb_id) + return None + print("tmdb movie title:", info["tv_episode_results"][0]["name"]) + tv_episode_id = info["tv_episode_results"][0]["id"] + name = info["tv_episode_results"][0]["name"] + overview = info["tv_episode_results"][0]["overview"] + still_path = info["tv_episode_results"][0]["still_path"] + + return tv_episode_id, overview, still_path diff --git a/static/images/default.png b/static/images/default.png new file mode 100644 index 0000000000000000000000000000000000000000..352dc8bc3fa2caf49a7b506803fc24feedd54540 GIT binary patch literal 17826 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
!IP56clzmefkuUa6~L!OZWFf*JZ* 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 No#SUUvyWN?{U5w;z4ibA literal 0 HcmV?d00001 diff --git a/static/images/default_banner.png b/static/images/default_banner.png new file mode 100644 index 0000000000000000000000000000000000000000..ad276f96c3b66a24538f8ebbe3fe9529e9511d72 GIT binary patch literal 8329 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 X4Xi&py9I&ceJl-C9hF)o>&X8B Movies +