You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

191 lines
5.7 KiB
Python

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# app.py DB-powered version
import os, time, json, zlib, math, hashlib, subprocess
from concurrent.futures import ThreadPoolExecutor
import psycopg2.extras
from flask import (
Flask, render_template, request, jsonify, send_file
)
from config import get_local_db_connection
# ───────── 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)
# ───────── DB HELPER ───────── #
def db_get_videos():
conn, cur = get_local_db_connection()
cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
cur.execute("""
SELECT
video_id, username, site AS platform,
filepath, size, duration, gender, created_at, updated_at
FROM videos
""")
rows = cur.fetchall()
cur.close(); conn.close()
return [dict(r) for r in rows]
# ───────── THUMB UTILS ───────── #
def _hashed_thumb_path(video_id: str):
h = hashlib.md5(video_id.encode()).hexdigest()
sub1, sub2 = h[:2], h[2:4]
path = os.path.join(THUMB_DIR, sub1, sub2)
os.makedirs(path, exist_ok=True)
return os.path.join(path, f"{video_id}.webp")
def _gen_thumb_cmd(src: str, dest: str):
return [
"ffmpeg", "-y", "-loglevel", "error",
"-ss", "0", "-i", src,
"-vframes", "1",
"-vf", f"thumbnail,scale={THUMB_WIDTH}:-1",
"-q:v", FF_QUALITY,
dest
]
def generate_thumbnail(task):
src, dest = task
if not os.path.exists(dest):
subprocess.run(_gen_thumb_cmd(src, dest), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
def build_cache():
videos = db_get_videos()
# group by (username, platform)
grouped = {}
for v in videos:
key = (v["username"], v["platform"])
grouped.setdefault(key, []).append(v)
storage_usage, avg_sizes, video_map = {}, {}, {}
thumb_tasks = []
for (username, platform), vids in grouped.items():
key = f"{username}::{platform}"
total_gb = 0
for v in vids:
try:
size_mb = float(v.get("size", 0) or 0)
except ValueError:
print(f"⚠️ Invalid size for video {v.get('video_id')}: {v.get('size')}")
size_mb = 0
total_gb += size_mb / 1024
storage_usage[key] = {
"total_size": total_gb,
"video_count": len(vids)
}
avg_sizes[key] = total_gb / len(vids) if vids else 0
for v in vids:
video_id = v["video_id"]
thumb_path = _hashed_thumb_path(video_id)
if not os.path.exists(thumb_path):
thumb_tasks.append((v["filepath"], thumb_path))
v["thumbnail"] = thumb_path
video_map[key] = vids
if thumb_tasks:
with ThreadPoolExecutor(max_workers=os.cpu_count()*2) as exe:
list(exe.map(generate_thumbnail, thumb_tasks))
return {
"timestamp" : time.time(),
"videos" : video_map,
"storage_usage" : storage_usage,
"avg_sizes" : avg_sizes
}
# ───────── ROUTES (unchanged logic) ───────── #
@app.route("/")
def dashboard():
cache = build_cache()
query = request.args.get("q", "").lower().strip()
sorted_usage = sorted(
cache["storage_usage"].items(),
key=lambda x: x[1]["total_size"],
reverse=True
)
if query:
sorted_usage = [e for e in sorted_usage if query in e[0].lower()]
page = max(1, int(request.args.get("page", 1)))
total_pages = max(1, math.ceil(len(sorted_usage) / DASHBOARD_PER_PAGE))
start = (page - 1) * DASHBOARD_PER_PAGE
paginated = sorted_usage[start:start + DASHBOARD_PER_PAGE]
return render_template(
"analytics.html",
storage_usage = paginated,
avg_sizes = cache["avg_sizes"],
page = page,
total_pages = total_pages,
query = query
)
@app.route("/refresh")
def refresh():
cache = build_cache()
return jsonify({
"status" : "ok",
"videos" : sum(x["video_count"] for x in cache["storage_usage"].values()),
"updated" : time.ctime(cache["timestamp"])
})
@app.route("/user/<username>")
def user_page(username):
cache = build_cache()
videos = [
v | {"platform": key.split("::")[1]}
for key, vids in cache["videos"].items()
if key.split("::")[0] == username
for v in vids
]
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
paginated = videos[start:start + VIDEOS_PER_PAGE]
return render_template(
"user_page.html",
username = username,
videos = paginated,
page = page,
total_pages = total_pages
)
@app.route("/video/stream/<video_id>")
def stream_video(video_id):
cache = build_cache()
for vids in cache["videos"].values():
for v in vids:
if v["video_id"] == video_id:
return send_file(v["filepath"], mimetype="video/mp4")
return "Video not found", 404
@app.route("/video/<video_id>")
def view_video(video_id):
cache = build_cache()
for vids in cache["videos"].values():
for v in vids:
if v["video_id"] == video_id:
return render_template("video_view.html", video=v)
return "Video not found", 404
if __name__ == "__main__":
app.run(debug=True)