diff --git a/app.py b/app.py index 9f89da6..2066333 100644 --- a/app.py +++ b/app.py @@ -1,39 +1,12 @@ -# app.py — Streamaster, DB-Only, Clean from flask import Flask -import os - -# ───────── CONFIG ───────── # -app = Flask(__name__) - -THUMB_DIR = "static/thumbnails" -VIDEOS_PER_PAGE = 40 -DASHBOARD_PER_PAGE = 100 -THUMB_WIDTH = 640 -FF_QUALITY = "80" - -os.makedirs(THUMB_DIR, exist_ok=True) - -VIDEO_DIRS = [ - "U:/encoded", - "U:/count_sorted", - "E:/streamaster/downloaded" -] - -def find_video_file(filename: str) -> str | None: - for directory in VIDEO_DIRS: - candidate = os.path.join(directory, filename) - if os.path.exists(candidate): - return candidate - return None - -# ───────── BLUEPRINTS ───────── # +from helpers.favorites import db_init_favorites_table from routes.web import web from routes.api import api -from helpers.favorites import db_init_favorites_table +app = Flask(__name__) app.register_blueprint(web) app.register_blueprint(api) if __name__ == "__main__": db_init_favorites_table() - app.run(debug=True) \ No newline at end of file + app.run(debug=True) diff --git a/config.py b/config.py index 9dfa8f3..5992df7 100644 --- a/config.py +++ b/config.py @@ -28,4 +28,16 @@ def get_local_db_connection(): password='j0cy8c311024x3' ) cursor = conn.cursor(cursor_factory=RealDictCursor) - return conn, cursor \ No newline at end of file + return conn, cursor + +VIDEOS_PER_PAGE = 40 +DASHBOARD_PER_PAGE = 100 +THUMB_DIR = "static/thumbnails" +THUMB_WIDTH = 640 +FF_QUALITY = "80" + +VIDEO_DIRS = [ + "U:/encoded", + "U:/count_sorted", + "E:/streamaster/downloaded" +] \ No newline at end of file diff --git a/helpers/cache.py b/helpers/cache.py index b5f5828..3ed5ef3 100644 --- a/helpers/cache.py +++ b/helpers/cache.py @@ -1,3 +1,6 @@ +import time +from helpers.db import db_get_videos +# from helpers.thumbnails import generate_thumbnails_for_videos # optional # ───────── CACHE BUILDER ───────── # def build_cache(): @@ -28,6 +31,7 @@ def build_cache(): video_map[key] = vids + # Thumbnail generation is optional, uncomment if you want it auto-built: # generate_thumbnails_for_videos(videos) return { diff --git a/helpers/db.py b/helpers/db.py index 5ac4ad7..027505d 100644 --- a/helpers/db.py +++ b/helpers/db.py @@ -1,17 +1,27 @@ +import psycopg2.extras +from config import get_local_db_connection # central config + # ───────── DB HELPER ───────── # -def db_get_videos(username = None): +def db_get_videos(username: str = None): conn, cur = get_local_db_connection() cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) - query = "SELECT video_id, username, site AS platform, filepath, size, duration, gender, created_at, updated_at, thumbnail FROM videos WHERE status != 'missing'" + query = """ + SELECT video_id, username, site AS platform, + filepath, size, duration, gender, + created_at, updated_at, thumbnail + FROM videos + WHERE status != 'missing' + """ + params = [] + if username: - query += f" AND username = '{username}'" - - if True: - query += " ORDER BY created_at DESC" - - cur.execute(query) + query += " AND username = %s" + params.append(username) + + query += " ORDER BY created_at DESC" + cur.execute(query, params) rows = cur.fetchall() cur.close(); conn.close() return [dict(r) for r in rows] @@ -29,14 +39,13 @@ def db_get_video(video_id: str): """, (video_id,)) row = cur.fetchone() cur.close(); conn.close() - return dict(row) + return dict(row) if row else None def db_get_recent(page: int, per_page: int): offset = (page - 1) * per_page conn, cur = get_local_db_connection() cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) - # Order by created_at desc (fallback to updated_at if you prefer) cur.execute(""" SELECT video_id, username, site AS platform, diff --git a/helpers/favorites.py b/helpers/favorites.py index 45faca1..b85b4fb 100644 --- a/helpers/favorites.py +++ b/helpers/favorites.py @@ -1,6 +1,5 @@ - - - +import psycopg2.extras +from config import get_local_db_connection # centralize DB connection # ───────── FAVORITES ───────── # def db_init_favorites_table(): @@ -8,7 +7,7 @@ def db_init_favorites_table(): cur = conn.cursor() cur.execute(""" CREATE TABLE IF NOT EXISTS favorites ( - video_id TEXT PRIMARY KEY, + video_id UUID PRIMARY KEY, -- better: store as uuid created_at TIMESTAMPTZ DEFAULT NOW() ) """) @@ -19,14 +18,13 @@ def db_get_fav_set(): conn, cur = get_local_db_connection() cur = conn.cursor() cur.execute("SELECT video_id FROM favorites") - favs = {row[0] for row in cur.fetchall()} + favs = {str(row[0]) for row in cur.fetchall()} cur.close(); conn.close() return favs def db_toggle_fav(video_id: str): conn, cur = get_local_db_connection() cur = conn.cursor() - # Try to delete; if nothing deleted, insert cur.execute("DELETE FROM favorites WHERE video_id = %s", (video_id,)) if cur.rowcount == 0: cur.execute("INSERT INTO favorites (video_id) VALUES (%s)", (video_id,)) @@ -72,4 +70,4 @@ def db_get_favorites(page: int, per_page: int): total = cur.fetchone()[0] cur.close(); conn.close() - return rows, total + return rows, total \ No newline at end of file diff --git a/helpers/thumbnails.py b/helpers/thumbnails.py index 202528b..5148cfe 100644 --- a/helpers/thumbnails.py +++ b/helpers/thumbnails.py @@ -1,4 +1,9 @@ -# ───────── THUMBNAIL HELPERS ───────── # +import os, hashlib, subprocess +from concurrent.futures import ThreadPoolExecutor + +# pull constants from app.py +from app import THUMB_DIR, THUMB_WIDTH, FF_QUALITY + def _hashed_thumb_path(video_id: str): h = hashlib.md5(video_id.encode()).hexdigest() sub1, sub2 = h[:2], h[2:4] @@ -21,7 +26,7 @@ def generate_thumbnails_for_videos(videos): for v in videos: video_id = v.get("video_id") - filepath = v.get("filepath") + filepath = v.get("filepath") thumb_path = _hashed_thumb_path(video_id) if not filepath: @@ -39,8 +44,11 @@ def generate_thumbnails_for_videos(videos): if tasks: with ThreadPoolExecutor(max_workers=os.cpu_count() * 2) as exe: - list(exe.map(lambda t: subprocess.run( - _gen_thumb_cmd(*t), - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL - ), tasks)) + list(exe.map( + lambda t: subprocess.run( + _gen_thumb_cmd(*t), + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL + ), + tasks + )) diff --git a/routes/api.py b/routes/api.py index fa7f47d..6a93d58 100644 --- a/routes/api.py +++ b/routes/api.py @@ -1,4 +1,12 @@ -@app.route('/open-folder', methods=['POST']) +from flask import Blueprint, request, jsonify +import os, subprocess +from helpers.db import db_get_video +from helpers.favorites import db_toggle_fav, db_get_fav_set +from config import get_local_db_connection + +api = Blueprint("api", __name__) + +@api.route('/open-folder', methods=['POST']) def open_folder(): data = request.json file_path = data.get("file_path") @@ -13,7 +21,7 @@ def open_folder(): return jsonify({"success": True}) -@app.route("/api/fav/toggle/", methods=["POST"]) +@api.route("/api/fav/toggle/", methods=["POST"]) def api_fav_toggle(video_id): # Optional: validate video exists try: @@ -24,11 +32,13 @@ def api_fav_toggle(video_id): is_fav = db_toggle_fav(video_id) return jsonify({"ok": True, "video_id": video_id, "is_favorite": is_fav}) -@app.route("/api/fav/list") + +@api.route("/api/fav/list") def api_fav_list(): return jsonify({"favorites": sorted(list(db_get_fav_set()))}) -@app.route('/delete-file', methods=['POST']) + +@api.route('/delete-file', methods=['POST']) def delete_file(): data = request.json file_path = data.get("file_path") diff --git a/routes/web.py b/routes/web.py index f0ce9b2..30b973f 100644 --- a/routes/web.py +++ b/routes/web.py @@ -1,7 +1,9 @@ -from flask import Blueprint, render_template, request, send_file +from flask import Blueprint, render_template, request, send_file, jsonify +import math, time from helpers.db import db_get_videos, db_get_video, db_get_recent from helpers.favorites import mark_favorites, db_get_favorites, db_get_fav_set from helpers.cache import build_cache +from config import VIDEOS_PER_PAGE, DASHBOARD_PER_PAGE web = Blueprint("web", __name__) @@ -9,23 +11,20 @@ web = Blueprint("web", __name__) def dashboard(): cache = build_cache() query = request.args.get("q", "").lower().strip() - sort = request.args.get("sort", "total_size") # user | platform | total_size | video_count | avg_size - dir_ = request.args.get("dir", "desc") # asc | desc + sort = request.args.get("sort", "total_size") + dir_ = request.args.get("dir", "desc") reverse = (dir_ == "desc") - # Start with list of (key, stats) items = list(cache["storage_usage"].items()) - # Search (by user) if query: items = [e for e in items if query in e[0].split("::")[0].lower()] - # Sorting - def k_user(x): return x[0].split("::")[0].lower() - def k_platform(x): return x[0].split("::")[1].lower() - def k_total(x): return x[1]["total_size"] - def k_count(x): return x[1]["video_count"] - def k_avg(x): return cache["avg_sizes"][x[0]] + def k_user(x): return x[0].split("::")[0].lower() + def k_platform(x): return x[0].split("::")[1].lower() + def k_total(x): return x[1]["total_size"] + def k_count(x): return x[1]["video_count"] + def k_avg(x): return cache["avg_sizes"][x[0]] key_map = { "user": k_user, @@ -36,7 +35,6 @@ def dashboard(): } items.sort(key=key_map.get(sort, k_total), reverse=reverse) - # Pagination page = max(1, int(request.args.get("page", 1))) total_pages = max(1, math.ceil(len(items) / DASHBOARD_PER_PAGE)) start = (page - 1) * DASHBOARD_PER_PAGE @@ -52,9 +50,7 @@ def dashboard(): sort=sort, dir=dir_ ) - - - + @web.route("/refresh") def refresh(): cache = build_cache() @@ -67,7 +63,6 @@ def refresh(): @web.route("/user/") def user_page(username): videos = db_get_videos(username=username) - page = max(1, int(request.args.get("page", 1))) total_pages = max(1, math.ceil(len(videos) / VIDEOS_PER_PAGE)) start = (page - 1) * VIDEOS_PER_PAGE @@ -101,10 +96,8 @@ def view_video(video_id): def recent(): page = max(1, int(request.args.get("page", 1))) per_page = VIDEOS_PER_PAGE - videos, total = db_get_recent(page, per_page) total_pages = max(1, math.ceil(total / per_page)) - mark_favorites(videos) return render_template( @@ -118,11 +111,8 @@ def recent(): def favorites_page(): page = max(1, int(request.args.get("page", 1))) per_page = VIDEOS_PER_PAGE - videos, total = db_get_favorites(page, per_page) total_pages = max(1, math.ceil(total / per_page)) - - # tag stars (not strictly necessary since everything here is fav, but keeps UI consistent) mark_favorites(videos) return render_template( @@ -130,5 +120,4 @@ def favorites_page(): videos=videos, page=page, total_pages=total_pages - ) - + ) \ No newline at end of file