Add nas_strm_proxy.py

This commit is contained in:
vijay 2026-06-12 08:28:10 +00:00
parent 07a2cfae4b
commit a712d5da15

193
nas_strm_proxy.py Normal file
View File

@ -0,0 +1,193 @@
# -*- coding: utf-8 -*-
"""
nas_strm_proxy.py
=================
Lightweight redirect-only HTTP proxy for Plex .strm playback on Synology NAS.
Compatible with Python 3.5+.
How it works:
1. SQLite triggers (installed by nas_setup.py) store proxy URLs in Plex DB:
http://127.0.0.1:8086/Movies/Hindi/Film.mp4
2. When Plex plays that item it sends:
GET /Movies/Hindi/Film.mp4 to this server
3. This server:
a. Strips .mp4 -> adds .strm
b. Reads: /volume1/Plex/Library/Streams/Movies/Hindi/Film.strm
c. Gets the raw IPTV URL from inside the file
d. Returns HTTP 302 -> real stream URL
4. Plex server follows the 302 on the server side.
Browser only talks HTTPS to Plex -- no mixed content block.
USAGE (SSH into Synology):
python3 nas_strm_proxy.py
python3 nas_strm_proxy.py --port 8086 --strm-root /volume1/Plex/Library/Streams
Run permanently on boot via Synology Task Scheduler (triggered task at boot):
nohup python3 /volume1/Plex/scripts/nas_strm_proxy.py > /tmp/strm_proxy.log 2>&1 &
"""
import argparse
import os
import re
import sys
from http.server import BaseHTTPRequestHandler, HTTPServer
try:
from urllib.parse import unquote
except ImportError:
from urllib import unquote # Python 2 fallback (not expected)
# -----------------------------------------------------------------------
# DEFAULTS -- adjust to match your Synology NAS layout
# -----------------------------------------------------------------------
DEFAULT_STRM_ROOT = "/volume1/Plex/Library/Streams"
DEFAULT_BIND_HOST = "127.0.0.1" # loopback -- Plex is on the same machine
DEFAULT_PORT = 8086
def make_handler(strm_root):
"""Return a request handler class with strm_root baked in."""
class StrmRedirectHandler(BaseHTTPRequestHandler):
def log_message(self, fmt, *args):
print("[" + self.address_string() + "] " + (fmt % args), flush=True)
def do_HEAD(self):
self._handle(head_only=True)
def do_GET(self):
self._handle(head_only=False)
def _handle(self, head_only=False):
method = "HEAD" if head_only else "GET"
# 1. Decode the request path
raw_path = (self.path or "/").split("?")[0].split("#")[0]
try:
decoded_path = unquote(raw_path)
except Exception:
decoded_path = raw_path
print("\n[->] Request: " + method + " " + decoded_path, flush=True)
# 2. Security: prevent path traversal
full_path = os.path.normpath(os.path.join(strm_root, decoded_path.lstrip("/")))
if not full_path.startswith(strm_root):
print("[-] Path traversal attempt blocked: " + decoded_path, flush=True)
self._send(403, "Forbidden")
return
# 3. Map .mp4 / .mkv / etc. -> .strm
strm_path = re.sub(r"\.(mp4|mkv|avi|mov|ts)$", ".strm", full_path, flags=re.IGNORECASE)
if not strm_path.endswith(".strm"):
strm_path = full_path + ".strm"
print("[*] Mapping to: " + strm_path, flush=True)
# 4. Check the .strm file exists
if not os.path.isfile(strm_path):
print("[-] .strm file not found: " + strm_path, flush=True)
self._send(404, "STRM file not found: " + os.path.basename(strm_path))
return
# 5. Read the URL from the .strm file
try:
with open(strm_path, "r") as f:
stream_url = f.read().strip()
except OSError as e:
print("[-] Cannot read .strm file: " + str(e), flush=True)
self._send(500, "Cannot read STRM file: " + str(e))
return
if not stream_url:
print("[-] .strm file is empty: " + strm_path, flush=True)
self._send(500, "STRM file is empty")
return
if not (stream_url.startswith("http://") or stream_url.startswith("https://")):
print("[-] Invalid URL in .strm file: " + stream_url[:80], flush=True)
self._send(500, "STRM file does not contain a valid HTTP/HTTPS URL")
return
# 6. Return 302 redirect -- Plex server follows this, browser never sees it
print("[->] 302 Redirect -> " + stream_url[:100], flush=True)
self.send_response(302)
self.send_header("Location", stream_url)
self.send_header("Content-Length", "0")
self.send_header("Cache-Control", "no-cache")
self.end_headers()
def _send(self, code, message):
body = message.encode("utf-8")
self.send_response(code)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
return StrmRedirectHandler
def run_server(host, port, strm_root):
strm_root = strm_root.rstrip("/")
print("=" * 65)
print(" PLEX STRM REDIRECT PROXY (NAS Edition)")
print("=" * 65)
print("[*] Listening on : http://" + host + ":" + str(port))
print("[*] STRM root : " + strm_root)
print("[*] Behaviour : GET /Movies/Film.mp4")
print(" reads " + strm_root + "/Movies/Film.strm")
print(" 302 redirect -> real stream URL")
print()
if not os.path.isdir(strm_root):
print("[!] WARNING: STRM root directory not found: " + strm_root)
print("[!] The server will start but all requests will return 404.")
print("[!] Check your --strm-root path.")
print()
handler = make_handler(strm_root)
server = HTTPServer((host, port), handler)
print("[+] Server started. Press CTRL+C to stop.\n", flush=True)
try:
server.serve_forever()
except KeyboardInterrupt:
print("\n[*] Shutting down proxy server...")
server.server_close()
def main():
parser = argparse.ArgumentParser(
description="Redirect-only HTTP proxy for Plex .strm file playback on Synology NAS."
)
parser.add_argument(
"--strm-root",
default=DEFAULT_STRM_ROOT,
help="Root directory containing .strm files (default: " + DEFAULT_STRM_ROOT + ")",
)
parser.add_argument(
"--port",
type=int,
default=DEFAULT_PORT,
help="Port to listen on (default: " + str(DEFAULT_PORT) + ")",
)
parser.add_argument(
"--bind",
default=DEFAULT_BIND_HOST,
help="IP to bind to (default: " + DEFAULT_BIND_HOST + " -- loopback only)",
)
args = parser.parse_args()
if not os.path.isdir(args.strm_root):
print("[!] WARNING: STRM root not found: " + args.strm_root)
run_server(args.bind, args.port, args.strm_root)
if __name__ == "__main__":
main()