Add plex/nas_setup.py
This commit is contained in:
parent
8951f2fc24
commit
9520a30386
257
plex/nas_setup.py
Normal file
257
plex/nas_setup.py
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
nas_setup.py
|
||||||
|
============
|
||||||
|
One-time setup script to run on your Synology NAS via SSH.
|
||||||
|
Installs SQLite triggers into the Plex database.
|
||||||
|
|
||||||
|
Reads all paths from automation_config.json automatically.
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
python3 /volume1/Plex/scripts/nas_setup.py # Install + patch existing rows
|
||||||
|
python3 /volume1/Plex/scripts/nas_setup.py --dry-run # Preview SQL only, no changes
|
||||||
|
python3 /volume1/Plex/scripts/nas_setup.py --remove # Remove installed triggers
|
||||||
|
|
||||||
|
Run order (IMPORTANT):
|
||||||
|
1. Stop Plex: synoservice --stop "pkgctl-Plex Media Server"
|
||||||
|
2. Run this script
|
||||||
|
3. Start proxy: nohup python3 nas_strm_proxy.py > /tmp/strm_proxy.log 2>&1 &
|
||||||
|
4. Start Plex: synoservice --start "pkgctl-Plex Media Server"
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import sqlite3
|
||||||
|
import argparse
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
CONFIG_FILE = os.path.join(SCRIPT_DIR, "automation_config.json")
|
||||||
|
|
||||||
|
TRIGGER_NAMES = ["strm_proxy_insert", "strm_proxy_update"]
|
||||||
|
|
||||||
|
|
||||||
|
def load_config():
|
||||||
|
if not os.path.exists(CONFIG_FILE):
|
||||||
|
print("[-] Cannot find " + CONFIG_FILE)
|
||||||
|
print(" Make sure this script is in the same folder as automation_config.json")
|
||||||
|
sys.exit(1)
|
||||||
|
with open(CONFIG_FILE, "r") as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
def build_triggers(strm_root, proxy_base):
|
||||||
|
strm_root = strm_root.rstrip("/")
|
||||||
|
root_len = len(strm_root)
|
||||||
|
like_pattern = strm_root + "/%.strm"
|
||||||
|
|
||||||
|
# SQL expression: strip the strm_root prefix, swap .strm -> .mp4, prepend proxy base
|
||||||
|
# Example: /volume1/Plex/Library/Streams/Movies/Film.strm
|
||||||
|
# -> http://127.0.0.1:8086/Movies/Film.mp4
|
||||||
|
url_expr = "'" + proxy_base + "' || REPLACE(substr(NEW.file, " + str(root_len + 1) + "), '.strm', '.mp4')"
|
||||||
|
|
||||||
|
insert_sql = (
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS strm_proxy_insert\n"
|
||||||
|
"AFTER INSERT ON media_parts\n"
|
||||||
|
"WHEN NEW.file LIKE '" + like_pattern + "'\n"
|
||||||
|
"BEGIN\n"
|
||||||
|
" UPDATE media_parts SET file = " + url_expr + " WHERE id = NEW.id;\n"
|
||||||
|
"END;"
|
||||||
|
)
|
||||||
|
|
||||||
|
update_sql = (
|
||||||
|
"CREATE TRIGGER IF NOT EXISTS strm_proxy_update\n"
|
||||||
|
"AFTER UPDATE OF file ON media_parts\n"
|
||||||
|
"WHEN NEW.file LIKE '" + like_pattern + "'\n"
|
||||||
|
"BEGIN\n"
|
||||||
|
" UPDATE media_parts SET file = " + url_expr + " WHERE id = NEW.id;\n"
|
||||||
|
"END;"
|
||||||
|
)
|
||||||
|
|
||||||
|
return insert_sql, update_sql
|
||||||
|
|
||||||
|
|
||||||
|
def check_plex_running():
|
||||||
|
"""Warn if Plex appears to be running. Writing to a live DB risks corruption."""
|
||||||
|
try:
|
||||||
|
out = subprocess.check_output(["pgrep", "-f", "Plex Media Server"], stderr=subprocess.STDOUT).strip()
|
||||||
|
if out:
|
||||||
|
print("=" * 65)
|
||||||
|
print("[!] WARNING: Plex Media Server appears to be RUNNING!")
|
||||||
|
print("[!] Writing to the Plex database while Plex is open can")
|
||||||
|
print("[!] corrupt it. Stop Plex first, then run this script.")
|
||||||
|
print("[!] Stop with: synoservice --stop \"pkgctl-Plex Media Server\"")
|
||||||
|
print("=" * 65)
|
||||||
|
ans = raw_input("Continue anyway? (yes/no): ") if sys.version_info[0] < 3 else input("Continue anyway? (yes/no): ")
|
||||||
|
if ans.strip().lower() != "yes":
|
||||||
|
print("[*] Aborted.")
|
||||||
|
sys.exit(0)
|
||||||
|
except (subprocess.CalledProcessError, OSError):
|
||||||
|
pass # pgrep found no match (CalledProcessError) or not installed (OSError) -- assume safe
|
||||||
|
|
||||||
|
|
||||||
|
def install_triggers(db_path, strm_root, proxy_base, dry_run):
|
||||||
|
print("\n[*] Database : " + db_path)
|
||||||
|
print("[*] STRM root : " + strm_root)
|
||||||
|
print("[*] Proxy base : " + proxy_base)
|
||||||
|
|
||||||
|
if not os.path.exists(db_path):
|
||||||
|
print("\n[-] ERROR: Plex database not found at:\n " + db_path)
|
||||||
|
print(" Has Plex been started at least once to create its database?")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
insert_sql, update_sql = build_triggers(strm_root, proxy_base)
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
print("\n[DRY RUN] SQL that would be installed:\n")
|
||||||
|
print("-- INSERT TRIGGER:\n" + insert_sql)
|
||||||
|
print("\n-- UPDATE TRIGGER:\n" + update_sql)
|
||||||
|
print("\n[DRY RUN] No changes made.")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Remove old versions if they exist
|
||||||
|
cursor.execute(
|
||||||
|
"SELECT name FROM sqlite_master WHERE type='trigger' AND name IN (?,?)",
|
||||||
|
TRIGGER_NAMES
|
||||||
|
)
|
||||||
|
existing = [r[0] for r in cursor.fetchall()]
|
||||||
|
if existing:
|
||||||
|
print("[!] Removing old triggers: " + str(existing))
|
||||||
|
for name in existing:
|
||||||
|
cursor.execute("DROP TRIGGER IF EXISTS " + name)
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
print("[*] Installing INSERT trigger...")
|
||||||
|
cursor.executescript(insert_sql)
|
||||||
|
print("[*] Installing UPDATE trigger...")
|
||||||
|
cursor.executescript(update_sql)
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
cursor.execute(
|
||||||
|
"SELECT name FROM sqlite_master WHERE type='trigger' AND name IN (?,?)",
|
||||||
|
TRIGGER_NAMES
|
||||||
|
)
|
||||||
|
installed = [r[0] for r in cursor.fetchall()]
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
print("[+] Triggers installed: " + str(installed))
|
||||||
|
|
||||||
|
except sqlite3.Error as e:
|
||||||
|
print("[-] SQLite error: " + str(e))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def patch_existing(db_path, strm_root, proxy_base, dry_run):
|
||||||
|
"""Directly UPDATE existing media_parts rows that still hold raw .strm paths."""
|
||||||
|
print("\n[*] Patching already-scanned .strm rows in media_parts...")
|
||||||
|
strm_root = strm_root.rstrip("/")
|
||||||
|
root_len = len(strm_root)
|
||||||
|
like = strm_root + "/%.strm"
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute(
|
||||||
|
"SELECT id, file FROM media_parts WHERE file LIKE ? AND deleted_at IS NULL",
|
||||||
|
(like,)
|
||||||
|
)
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
|
if not rows:
|
||||||
|
print("[*] No unpatched .strm rows found -- nothing to do.")
|
||||||
|
conn.close()
|
||||||
|
return
|
||||||
|
|
||||||
|
print("[*] Found " + str(len(rows)) + " row(s) to patch:")
|
||||||
|
for part_id, file_path in rows:
|
||||||
|
rel = file_path[root_len:]
|
||||||
|
proxy_url = proxy_base + rel.replace(".strm", ".mp4")
|
||||||
|
print(" " + file_path)
|
||||||
|
print(" -> " + proxy_url)
|
||||||
|
if not dry_run:
|
||||||
|
cursor.execute("UPDATE media_parts SET file = ? WHERE id = ?", (proxy_url, part_id))
|
||||||
|
|
||||||
|
if not dry_run:
|
||||||
|
conn.commit()
|
||||||
|
print("\n[+] Patched " + str(len(rows)) + " row(s).")
|
||||||
|
else:
|
||||||
|
print("\n[DRY RUN] Would patch " + str(len(rows)) + " row(s) -- no changes made.")
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
except sqlite3.Error as e:
|
||||||
|
print("[-] SQLite error during patch: " + str(e))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_triggers(db_path):
|
||||||
|
print("[*] Removing STRM proxy triggers...")
|
||||||
|
try:
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
for name in TRIGGER_NAMES:
|
||||||
|
conn.execute("DROP TRIGGER IF EXISTS " + name)
|
||||||
|
print("[+] Dropped: " + name)
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
print("[+] Done. Plex will store raw .strm paths again on next scan.")
|
||||||
|
except sqlite3.Error as e:
|
||||||
|
print("[-] SQLite error: " + str(e))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="One-time Plex DB trigger installer for NAS-native STRM playback."
|
||||||
|
)
|
||||||
|
parser.add_argument("--dry-run", action="store_true", help="Preview SQL, write nothing")
|
||||||
|
parser.add_argument("--remove", action="store_true", help="Remove installed triggers")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
print("=" * 65)
|
||||||
|
print(" PLEX STRM TRIGGER SETUP")
|
||||||
|
print("=" * 65)
|
||||||
|
|
||||||
|
cfg = load_config()
|
||||||
|
|
||||||
|
db_path = cfg.get("plex_db_path",
|
||||||
|
"/volume1/Plex/Library/Application Support/"
|
||||||
|
"Plex Media Server/Plug-in Support/Databases/"
|
||||||
|
"com.plexapp.plugins.library.db"
|
||||||
|
)
|
||||||
|
strm_root = cfg.get("output_dir", "/volume1/Plex/Library/Streams").rstrip("/")
|
||||||
|
proxy_port = cfg.get("nas_proxy_port", 8086)
|
||||||
|
proxy_base = "http://127.0.0.1:" + str(proxy_port)
|
||||||
|
|
||||||
|
if args.remove:
|
||||||
|
remove_triggers(db_path)
|
||||||
|
return
|
||||||
|
|
||||||
|
check_plex_running()
|
||||||
|
install_triggers(db_path, strm_root, proxy_base, args.dry_run)
|
||||||
|
patch_existing(db_path, strm_root, proxy_base, args.dry_run)
|
||||||
|
|
||||||
|
if not args.dry_run:
|
||||||
|
print()
|
||||||
|
print("=" * 65)
|
||||||
|
print("[+] Setup complete!")
|
||||||
|
print()
|
||||||
|
print("Next steps:")
|
||||||
|
print(" 1. Start redirect proxy (keep running permanently):")
|
||||||
|
print(" nohup python3 " + SCRIPT_DIR + "/nas_strm_proxy.py > /tmp/strm_proxy.log 2>&1 &")
|
||||||
|
print()
|
||||||
|
print(" 2. Start Plex Media Server:")
|
||||||
|
print(" synoservice --start \"pkgctl-Plex Media Server\"")
|
||||||
|
print()
|
||||||
|
print(" 3. Run automation to refresh .strm files:")
|
||||||
|
print(" python3 " + SCRIPT_DIR + "/run_automation.py")
|
||||||
|
print("=" * 65)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
x
Reference in New Issue
Block a user