From c77c1a6f6f76b5693868bca5686792f4aaacdcb5 Mon Sep 17 00:00:00 2001 From: Pat Teruel Date: Sun, 1 Mar 2026 10:16:21 +0800 Subject: [PATCH] Add mac launcher, py2app setup, and build script Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 14 ++++++++++++++ app.py | 2 +- build_mac.sh | 19 +++++++++++++++++++ mac_launcher.py | 40 ++++++++++++++++++++++++++++++++++++++++ setup.py | 22 ++++++++++++++++++++++ 5 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 build_mac.sh create mode 100644 mac_launcher.py create mode 100644 setup.py diff --git a/README.md b/README.md index 06e02a3..06fe40a 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,20 @@ docker compose up Open [http://localhost:5000](http://localhost:5000) in your browser. +### Building a macOS .app (Standalone) + +A convenience build script is provided to create a standalone macOS application bundle that includes a Python runtime and the app. Note: the build must be run on macOS and requires internet access to install dependencies. + +Run the following on your Mac: + +```bash +chmod +x build_mac.sh +./build_mac.sh +``` + +The built .app will appear in dist/ (example: `dist/mac_launcher.app`). Double-click the `.app` to launch; it will start a local web server and open your default browser to the UI. + + ## Usage 1. The date range defaults to the previous week (Mon–Sat). diff --git a/app.py b/app.py index a467302..2841054 100644 --- a/app.py +++ b/app.py @@ -7,7 +7,7 @@ load_dotenv() from clients import jellyfin -app = Flask(__name__) +app = Flask(__name__, template_folder=os.path.join(os.path.dirname(__file__), 'templates')) PHT = timezone(timedelta(hours=8)) diff --git a/build_mac.sh b/build_mac.sh new file mode 100644 index 0000000..3b008fb --- /dev/null +++ b/build_mac.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [ "$(uname)" != "Darwin" ]; then + echo "This build script must be run on macOS." >&2 + exit 1 +fi + +python3 -m venv .venv +source .venv/bin/activate + +pip install --upgrade pip setuptools wheel +pip install -r requirements.txt +pip install py2app + +# Build the macOS .app +python3 setup.py py2app + +echo "Build complete. The application bundle is in dist/ (e.g. dist/mac_launcher.app)." diff --git a/mac_launcher.py b/mac_launcher.py new file mode 100644 index 0000000..a5c4730 --- /dev/null +++ b/mac_launcher.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +""" +Launcher for macOS .app bundle: starts the Flask server bound to 127.0.0.1 and opens the default browser. +""" +import os +import time +import threading +import webbrowser + +# Import the Flask app defined in app.py +from app import app + + +def _run_server(): + port = int(os.environ.get("PORT", 5000)) + # Bind to loopback only for local use + app.run(host="127.0.0.1", port=port, debug=False, use_reloader=False) + + +if __name__ == "__main__": + t = threading.Thread(target=_run_server, daemon=True) + t.start() + + # Give the server a moment to start + time.sleep(1.0) + + port = os.environ.get("PORT", "5000") + url = f"http://127.0.0.1:{port}/" + try: + webbrowser.open(url) + except Exception: + # If opening the browser fails, just continue; user can open manually + pass + + try: + # Keep the main thread alive while the server thread runs + while t.is_alive(): + t.join(0.5) + except KeyboardInterrupt: + pass diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..8409435 --- /dev/null +++ b/setup.py @@ -0,0 +1,22 @@ +from setuptools import setup + +APP = ['mac_launcher.py'] +DATA_FILES = [] +OPTIONS = { + 'argv_emulation': True, + 'packages': ['clients'], + 'includes': ['jinja2', 'requests', 'dotenv'], + 'resources': ['templates'], + 'plist': { + 'CFBundleName': 'MediaEpisodesViewer', + 'CFBundleShortVersionString': '0.1.0', + 'CFBundleIdentifier': 'com.example.mediaepisodesviewer' + } +} + +setup( + app=APP, + data_files=DATA_FILES, + options={'py2app': OPTIONS}, + setup_requires=['py2app'], +)