|
| 1 | +from flask import Flask, render_template, request, redirect, url_for, session, flash |
| 2 | +from plexapi.server import PlexServer |
| 3 | +import os, requests |
| 4 | + |
| 5 | +app = Flask(__name__) |
| 6 | +app.secret_key = os.urandom(24) |
| 7 | + |
| 8 | +def get_plex(): |
| 9 | + base_url = session.get('base_url') |
| 10 | + token = session.get('token') |
| 11 | + if not base_url or not token: |
| 12 | + return None |
| 13 | + return PlexServer(base_url, token) |
| 14 | + |
| 15 | +@app.route('/', methods=['GET', 'POST']) |
| 16 | +def connect(): |
| 17 | + if request.method == 'POST': |
| 18 | + session['base_url'] = request.form['base_url'] |
| 19 | + session['token'] = request.form['token'] |
| 20 | + session['asset_dir'] = 'assets' if not request.form['asset_dir'] else request.form['asset_dir'] |
| 21 | + return redirect(url_for('libraries')) |
| 22 | + return render_template('connect.html') |
| 23 | + |
| 24 | +@app.route('/libraries') |
| 25 | +def libraries(): |
| 26 | + plex = get_plex() |
| 27 | + if not plex: |
| 28 | + return redirect(url_for('connect')) |
| 29 | + sections = plex.library.sections() |
| 30 | + return render_template('libraries.html', sections=sections) |
| 31 | + |
| 32 | +# http://127.0.0.1:5000/browse/5?page=1&art_type=poster&art_page=1&season=3&episode= |
| 33 | + |
| 34 | +@app.route('/browse/<section_key>') |
| 35 | +def browse(section_key): |
| 36 | + plex = get_plex() |
| 37 | + if not plex: |
| 38 | + return redirect(url_for('connect')) |
| 39 | + section = next((s for s in plex.library.sections() if str(s.key) == section_key), None) |
| 40 | + if not section: |
| 41 | + flash('Library not found.') |
| 42 | + return redirect(url_for('libraries')) |
| 43 | + items = list(section.all()) |
| 44 | + pages = len(items) |
| 45 | + item_page = int(request.args.get('page', 1)) |
| 46 | + item_page = max(1, min(item_page, pages)) |
| 47 | + item = items[item_page - 1] |
| 48 | + |
| 49 | + art_type = request.args.get('art_type', 'poster') |
| 50 | + season = request.args.get('season') |
| 51 | + try: |
| 52 | + season_rating_key = item.season(int(season)).ratingKey |
| 53 | + except: |
| 54 | + season_rating_key = None |
| 55 | + |
| 56 | + episode = request.args.get('episode') |
| 57 | + art_page = int(request.args.get('art_page', 1)) |
| 58 | + |
| 59 | + return render_template('item.html', |
| 60 | + section=section, |
| 61 | + item=item, |
| 62 | + season_rating_key=season_rating_key, |
| 63 | + items=items, |
| 64 | + item_page=item_page, |
| 65 | + pages=pages, |
| 66 | + art_type=art_type, |
| 67 | + season=season, |
| 68 | + episode=episode, |
| 69 | + art_page=art_page) |
| 70 | + |
| 71 | +@app.route('/download', methods=['POST']) |
| 72 | +def download(): |
| 73 | + plex = get_plex() |
| 74 | + if not plex: |
| 75 | + return redirect(url_for('connect')) |
| 76 | + |
| 77 | + rating_key = int(request.form['rating_key']) |
| 78 | + try: |
| 79 | + season_rating_key = int(request.form['season_rating_key']) |
| 80 | + except: |
| 81 | + season_rating_key = None |
| 82 | + |
| 83 | + try: |
| 84 | + episode_rating_key = int(request.form['episode_rating_key']) |
| 85 | + except: |
| 86 | + episode_rating_key = None |
| 87 | + section_key = request.form['section_key'] |
| 88 | + art_type = request.form['art_type'] |
| 89 | + img_key = request.form['img_key'] |
| 90 | + season = None if request.form.get('season') == 'None' else request.form.get('season') |
| 91 | + episode = None if request.form.get('episode') == 'None' else request.form.get('episode') |
| 92 | + item_page = request.form.get('item_page', 1) |
| 93 | + art_page = request.form.get('art_page', 1) |
| 94 | + |
| 95 | + item = plex.fetchItem(rating_key) |
| 96 | + season_item = None if not season_rating_key else plex.fetchItem(season_rating_key) |
| 97 | + episode_item = None if not episode_rating_key else plex.fetchItem(episode_rating_key) |
| 98 | + |
| 99 | + try: |
| 100 | + asset_name = None |
| 101 | + if item.type == 'movie': |
| 102 | + media_file = item.media[0].parts[0].file |
| 103 | + elif item.type == 'show' or season_item: |
| 104 | + media_file = item.locations[0] |
| 105 | + asset_name = os.path.basename(media_file) |
| 106 | + elif item.type == 'episode': |
| 107 | + media_file = item.media[0].parts[0].file |
| 108 | + else: |
| 109 | + media_file = None |
| 110 | + if not asset_name: |
| 111 | + asset_name = os.path.basename(os.path.dirname(media_file)) |
| 112 | + except: |
| 113 | + asset_name = item.title |
| 114 | + |
| 115 | + base_dir = os.path.join(os.getcwd(), session['asset_dir'], |
| 116 | + 'movies' if item.type == 'movie' else 'series', |
| 117 | + asset_name) |
| 118 | + os.makedirs(base_dir, exist_ok=True) |
| 119 | + |
| 120 | + ext = os.path.splitext(img_key)[1] or '.jpg' |
| 121 | + if item.type == 'show': |
| 122 | + if episode: |
| 123 | + s, e = int(season), int(episode) |
| 124 | + name = f"S{s:02d}E{e:02d}" |
| 125 | + elif season: |
| 126 | + s = int(season) |
| 127 | + name = f"Season {s:02d}" |
| 128 | + else: |
| 129 | + name = art_type |
| 130 | + suffix = f"_{art_type}" if art_type == 'background' and (season or episode) else '' |
| 131 | + filename = f"{name}{suffix}{ext}" |
| 132 | + else: |
| 133 | + filename = f"{art_type}{ext}" |
| 134 | + |
| 135 | + if img_key.startswith("http"): |
| 136 | + img_url = img_key |
| 137 | + else: |
| 138 | + img_url = f"{session['base_url']}{img_key}&X-Plex-Token={session['token']}" |
| 139 | + |
| 140 | + resp = requests.get(img_url) |
| 141 | + with open(os.path.join(base_dir, filename), 'wb') as f: |
| 142 | + f.write(resp.content) |
| 143 | + |
| 144 | + flash(f"Saved to {os.path.join(base_dir, filename)}") |
| 145 | + return redirect(url_for('browse', |
| 146 | + section_key=section_key, |
| 147 | + page=item_page, |
| 148 | + art_type=art_type, |
| 149 | + season=season, |
| 150 | + episode=episode, |
| 151 | + art_page=art_page)) |
| 152 | + |
| 153 | +if __name__ == '__main__': |
| 154 | + app.run(host='0.0.0.0', port=5000) |
0 commit comments