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 # URL encode for path (use -- as delimiter) encoded_url = quote(video_url, safe="") # Only set HLS URL if we actually have HLS hls_url = stream_info.get("hls_url") proxy_hls_url = f"/hls/{encoded_url}--index.m3u8" if hls_url else None return render_template( "player.html", video_url=video_url, proxy_hls_url=proxy_hls_url, direct_url=stream_info.get("direct_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(full_path): try: from urllib.parse import unquote # Split: last part is filename, rest is video URL # Format: /hls// # Since / is ambiguous (in URL and in video URL), we use a delimiter # Format: /hls/-- if "--" not in full_path: abort(400, description="Invalid path format") parts = full_path.rsplit("--", 1) if len(parts) != 2: abort(400, description="Invalid path format") encoded_video_url = parts[0] filename = parts[1] # Decode the video URL video_url = unquote(encoded_video_url) if not is_valid_url(video_url): abort(400, description="Invalid URL") # Main playlist request if filename == "index.m3u8": playlist = dlp.get_hls_playlist(video_url) return Response(playlist, mimetype="application/vnd.apple.mpegurl", headers={"Cache-Control": "public, max-age=31536000"}) # Sub-playlist or segment request segment_url = unquote(filename) segment_data = dlp.get_hls_segment_with_retry(video_url, segment_url) if segment_data is None: abort(500, description="Failed to fetch segment") # Determine content-type by filename extension if filename.endswith(".m3u8"): return Response(segment_data, mimetype="application/vnd.apple.mpegurl", headers={"Cache-Control": "public, max-age=31536000"}) return Response(segment_data, mimetype="video/mp2t", headers={"Cache-Control": "public, max-age=31536000"}) 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}") return Response(str(e), status=500, mimetype="text/plain") @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)