ff6e727ae7
- Flask app with HLS proxy routes (/hls, /player, /) - yt-dlp integration with 365-day in-memory cache - URL validation with allowed domains (youtube, pornhub, etc) - HTML5 HLS player with hls.js - Unit tests: URL validation, cache, error handling - Integration tests: ffmpeg-generated test video, full proxy chain - Environment-based configuration (PORT, CACHE_TTL, LOG_LEVEL) - MIT license
102 lines
2.9 KiB
Python
102 lines
2.9 KiB
Python
import logging
|
|
import os
|
|
from flask import Flask, render_template, request, Response, abort, jsonify
|
|
from werkzeug.exceptions import HTTPException
|
|
|
|
import dlp
|
|
from utils import is_valid_url, get_error_message
|
|
|
|
app = Flask(__name__)
|
|
|
|
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
|
|
logging.basicConfig(
|
|
level=getattr(logging, LOG_LEVEL),
|
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
PORT = int(os.getenv("PORT", 5000))
|
|
|
|
|
|
@app.route("/")
|
|
def index():
|
|
return render_template("index.html")
|
|
|
|
|
|
@app.route("/player")
|
|
def player():
|
|
video_url = request.args.get("url")
|
|
if not video_url:
|
|
abort(400, description="Missing url parameter")
|
|
|
|
if not is_valid_url(video_url):
|
|
abort(400, description="Invalid or disallowed URL")
|
|
|
|
try:
|
|
stream_info = dlp.get_stream_info(video_url)
|
|
from urllib.parse import quote
|
|
encoded_url = quote(video_url, safe="")
|
|
proxy_hls_url = f"/hls?url={encoded_url}&path=index.m3u8"
|
|
return render_template(
|
|
"player.html",
|
|
video_url=video_url,
|
|
proxy_hls_url=proxy_hls_url,
|
|
title=stream_info.get("title", "Video"),
|
|
thumbnail=stream_info.get("thumbnail")
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Error getting stream info: {e}")
|
|
abort(500, description=str(e))
|
|
|
|
|
|
@app.route("/hls")
|
|
def hls_proxy():
|
|
try:
|
|
url_param = request.args.get("url", "")
|
|
if not url_param:
|
|
abort(400, description="Missing url parameter")
|
|
|
|
from urllib.parse import urlparse, unquote
|
|
|
|
path = request.args.get("path", "")
|
|
|
|
if ".m3u8" in url_param and not path:
|
|
video_url = url_param
|
|
elif ".m3u8" in url_param and path:
|
|
video_url = url_param
|
|
else:
|
|
video_url = url_param
|
|
|
|
video_url = unquote(video_url)
|
|
|
|
if not is_valid_url(video_url):
|
|
abort(400, description="Invalid URL")
|
|
|
|
if path.endswith(".m3u8") or not path:
|
|
playlist = dlp.get_hls_playlist(video_url)
|
|
return Response(playlist, mimetype="application/vnd.apple.mpegurl")
|
|
|
|
segment_data = dlp.get_hls_segment(video_url, path)
|
|
return Response(segment_data, mimetype="video/mp2t")
|
|
|
|
except HTTPException:
|
|
raise
|
|
except ValueError as e:
|
|
logger.warning(f"Validation error: {e}")
|
|
abort(400, description=str(e))
|
|
except Exception as e:
|
|
logger.error(f"HLS proxy error: {e}")
|
|
abort(500, description="Error fetching stream")
|
|
|
|
|
|
@app.errorhandler(Exception)
|
|
def handle_error(e):
|
|
if isinstance(e, HTTPException):
|
|
return jsonify({"error": get_error_message(e.code), "message": str(e.description)}), e.code
|
|
logger.error(f"Unexpected error: {e}")
|
|
return jsonify({"error": "Internal Server Error", "message": str(e)}), 500
|
|
|
|
|
|
if __name__ == "__main__":
|
|
app.run(host="0.0.0.0", port=PORT)
|