#!/usr/bin/env python3 """ Video Player Web App - Read-only video streaming server. Loads videos from the 'playlist' folder and serves them via a web interface. """ import os import json import mimetypes from http.server import HTTPServer, SimpleHTTPRequestHandler from urllib.parse import unquote, urlparse import re PLAYLIST_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "playlist") PORT = 8080 VIDEO_EXTENSIONS = {".mp4", ".webm", ".mov", ".m4v", ".avi", ".mkv", ".ogg", ".ogv"} def get_video_files(): """Get list of video files from playlist directory.""" videos = [] if not os.path.isdir(PLAYLIST_DIR): return videos for filename in sorted(os.listdir(PLAYLIST_DIR)): ext = os.path.splitext(filename)[1].lower() if ext in VIDEO_EXTENSIONS: filepath = os.path.join(PLAYLIST_DIR, filename) size = os.path.getsize(filepath) videos.append({ "name": filename, "title": os.path.splitext(filename)[0].replace("_", " ").replace("-", " ").title(), "path": f"/playlist/{filename}", "size": size, "size_human": f"{size / (1024*1024):.1f} MB" }) return videos class VideoPlayerHandler(SimpleHTTPRequestHandler): """Custom handler for video player - read-only, no modifications allowed.""" def do_GET(self): path = urlparse(self.path).path if path == "/" or path == "/index.html": self.serve_index() elif path == "/api/playlist": self.serve_playlist_api() elif path.startswith("/playlist/"): self.serve_video(path) elif path == "/style.css": self.serve_static("style.css", "text/css") elif path == "/script.js": self.serve_static("script.js", "application/javascript") else: self.send_error(404, "Not Found") def do_POST(self): self.send_error(405, "Method Not Allowed - Read Only") def do_PUT(self): self.send_error(405, "Method Not Allowed - Read Only") def do_DELETE(self): self.send_error(405, "Method Not Allowed - Read Only") def serve_index(self): """Serve the main HTML page.""" html = self.get_index_html() self.send_response(200) self.send_header("Content-Type", "text/html; charset=utf-8") self.send_header("Content-Length", len(html.encode())) self.end_headers() self.wfile.write(html.encode()) def serve_playlist_api(self): """Serve playlist as JSON.""" videos = get_video_files() data = json.dumps({"videos": videos}) self.send_response(200) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", len(data.encode())) self.end_headers() self.wfile.write(data.encode()) def serve_video(self, path): """Serve video file with range support for seeking.""" filename = unquote(path.replace("/playlist/", "")) if "/" in filename or ".." in filename: self.send_error(403, "Forbidden") return filepath = os.path.join(PLAYLIST_DIR, filename) if not os.path.isfile(filepath): self.send_error(404, "Video Not Found") return file_size = os.path.getsize(filepath) content_type, _ = mimetypes.guess_type(filepath) if not content_type: content_type = "video/mp4" range_header = self.headers.get("Range") if range_header: range_match = re.match(r"bytes=(\d+)-(\d*)", range_header) if range_match: start = int(range_match.group(1)) end = int(range_match.group(2)) if range_match.group(2) else file_size - 1 end = min(end, file_size - 1) length = end - start + 1 self.send_response(206) self.send_header("Content-Type", content_type) self.send_header("Content-Length", length) self.send_header("Content-Range", f"bytes {start}-{end}/{file_size}") self.send_header("Accept-Ranges", "bytes") self.end_headers() with open(filepath, "rb") as f: f.seek(start) remaining = length chunk_size = 64 * 1024 while remaining > 0: chunk = f.read(min(chunk_size, remaining)) if not chunk: break self.wfile.write(chunk) remaining -= len(chunk) return self.send_response(200) self.send_header("Content-Type", content_type) self.send_header("Content-Length", file_size) self.send_header("Accept-Ranges", "bytes") self.end_headers() with open(filepath, "rb") as f: chunk_size = 64 * 1024 while True: chunk = f.read(chunk_size) if not chunk: break self.wfile.write(chunk) def serve_static(self, filename, content_type): """Serve static files (CSS, JS).""" filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), filename) if not os.path.isfile(filepath): self.send_error(404, "Not Found") return with open(filepath, "r", encoding="utf-8") as f: content = f.read() self.send_response(200) self.send_header("Content-Type", f"{content_type}; charset=utf-8") self.send_header("Content-Length", len(content.encode())) self.end_headers() self.wfile.write(content.encode()) def get_index_html(self): return '''