About Multi-camera viewer optimized for RTSP streams
1#!/usr/bin/env bash
2set -euo pipefail
3
4PYTHON_BIN="${PYTHON_BIN:-python3.12}"
5APP_NAME="${APP_NAME:-wildcam}"
6ENTRY_POINT="${ENTRY_POINT:-main.py}"
7PLATFORM="${PLATFORM:-macos}"
8ARTIFACT_SUFFIX="${ARTIFACT_SUFFIX:-$(date +%Y%m%d_%H%M%S)}"
9
10REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
11cd "$REPO_ROOT"
12
13if [[ ! -f "$ENTRY_POINT" ]]; then
14 echo "Entry point not found: $ENTRY_POINT" >&2
15 exit 1
16fi
17
18VENV_DIR="$REPO_ROOT/.venv"
19DIST_DIR="$REPO_ROOT/dist"
20BUILD_DIR="$REPO_ROOT/build"
21
22if [[ -d "$VENV_DIR" ]]; then
23 VENV_PY="$VENV_DIR/bin/python"
24 if [[ -x "$VENV_PY" ]]; then
25 VENV_VERSION="$($VENV_PY -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')"
26 REQUESTED_VERSION="$($PYTHON_BIN -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')"
27 if [[ "$VENV_VERSION" != "$REQUESTED_VERSION" ]]; then
28 if [[ "${RECREATE_VENV:-0}" == "1" ]]; then
29 rm -rf "$VENV_DIR"
30 else
31 echo "Existing venv uses Python $VENV_VERSION but requested is $REQUESTED_VERSION." >&2
32 echo "Delete $VENV_DIR or re-run with RECREATE_VENV=1" >&2
33 exit 1
34 fi
35 fi
36 fi
37fi
38
39if [[ ! -d "$VENV_DIR" ]]; then
40 "$PYTHON_BIN" -m venv "$VENV_DIR"
41fi
42
43PY="$VENV_DIR/bin/python"
44PIP="$VENV_DIR/bin/pip"
45
46"$PY" -m pip install --upgrade pip
47
48if [[ -f "requirements.txt" ]]; then
49 "$PIP" install -r requirements.txt
50fi
51
52"$PIP" install "pyinstaller>=6.0"
53
54rm -rf "$DIST_DIR" "$BUILD_DIR"
55
56"$PY" -m PyInstaller \
57 --noconfirm \
58 --clean \
59 --onedir \
60 --windowed \
61 --name "$APP_NAME" \
62 --add-data "assets/icons:assets/icons" \
63 --collect-all "PyQt6" \
64 --collect-all "cv2" \
65 --collect-all "numpy" \
66 --exclude-module "ultralytics" \
67 --exclude-module "torch" \
68 --exclude-module "torchvision" \
69 --exclude-module "nvidia" \
70 --hidden-import "detection" \
71 --hidden-import "notifications" \
72 "$ENTRY_POINT"
73
74BUNDLE_PATH="$DIST_DIR/${APP_NAME}.app"
75if [[ ! -e "$BUNDLE_PATH" ]]; then
76 BUNDLE_PATH="$DIST_DIR/$APP_NAME"
77fi
78
79if [[ ! -e "$BUNDLE_PATH" ]]; then
80 echo "Build failed: bundle not found under $DIST_DIR" >&2
81 exit 1
82fi
83
84if [[ -d "$BUNDLE_PATH" ]]; then
85 for extra_file in README.md docker-compose.yml reolinkproxy_manager.py camera_config.json.example; do
86 if [[ -f "$extra_file" ]]; then
87 cp "$extra_file" "$BUNDLE_PATH/"
88 fi
89 done
90
91fi
92
93ZIP_PATH="$DIST_DIR/${APP_NAME}_${PLATFORM}_${ARTIFACT_SUFFIX}.zip"
94
95export BUNDLE_PATH
96export ZIP_PATH
97
98"$PY" - <<PY
99import os
100import zipfile
101
102bundle_path = os.environ.get("BUNDLE_PATH")
103zip_path = os.environ.get("ZIP_PATH")
104parent_dir = os.path.dirname(bundle_path)
105
106with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as z:
107 if os.path.isdir(bundle_path):
108 for root, _, files in os.walk(bundle_path):
109 for file_name in files:
110 full_path = os.path.join(root, file_name)
111 arcname = os.path.relpath(full_path, parent_dir)
112 z.write(full_path, arcname=arcname)
113 else:
114 z.write(bundle_path, arcname=os.path.relpath(bundle_path, parent_dir))
115
116print(f"BUNDLE: {bundle_path}")
117print(f"ZIP: {zip_path}")
118PY