Skip to content

renaudallard/surveillance-station-client

Repository files navigation

Surveillance Station Client

Native GTK4 desktop client for Synology Surveillance Station

Lint & Type Check Latest Release Python 3.11+ GTK4 License

No browser needed. Connect directly to your Synology NAS and get live camera feeds, recording playback, PTZ control, snapshots, event alerts, and home mode management — all from a lightweight native desktop application.


Features

  • Live View — Real-time camera streams in 1×1, 2×2, 3×3, or 4×4 grid layouts. Hardware-accelerated rendering via mpv + OpenGL. Works on X11 and Wayland.
  • Recordings — Browse, filter by camera, play back with full transport controls (seek, pause, volume), and download to disk. Search by camera(s) and time range. Per-event thumbnails and smart detection labels (person, vehicle, animal, etc.) shown for each recording.
  • PTZ Control — Direction pad, zoom in/out, preset positions, and patrol routes. Appears automatically below the live view when a PTZ-capable camera is active.
  • Snapshots — Take live snapshots from any camera, browse saved snapshots, download or delete.
  • Time Lapse — Browse, play back, download, lock/unlock, and delete Smart Time Lapse recordings. Filter by time lapse task.
  • Events & Alerts — View motion detection and alarm events with smart detection labels. Notification bell with unread badge and alert popover, polled every 30 seconds.
  • Home Mode — Toggle Surveillance Station home mode directly from the header bar.
  • License Management — View, add, and delete camera licenses. Online and offline activation.
  • Session Persistence — Grid layout, active page, and camera assignments are restored on restart.
  • Two-Factor Authentication — MFA/OTP login support. When 2FA is enabled on your Synology account, the client prompts for a 6-digit authenticator code and optionally registers as a trusted device to skip OTP on future logins.
  • Multi-Profile — Save multiple NAS connection profiles and switch between them from the login screen.
  • Secure Credentials — Passwords stored in your system keyring (GNOME Keyring, KWallet, macOS Keychain).
  • Theming — Auto (follow OS), dark, or light theme selectable from the header bar.

Quick Start

AppImage (Linux, no install needed)

Download the latest AppImage for your architecture from the Releases page:

chmod +x Surveillance-*-x86_64.AppImage
./Surveillance-*-x86_64.AppImage

Available for x86_64 and aarch64. A new release with AppImages is built automatically every time the version is bumped.

From source

  1. Install system dependencies for your distro
  2. Clone and install:
git clone https://github.com/renaudallard/surveillance-station-client.git
cd surveillance-station-client
python3 -m venv --system-site-packages .venv
source .venv/bin/activate
pip install .

Note: --system-site-packages is required so the venv can access the system-installed PyGObject and cairo bindings, which cannot be built via pip without extensive C development headers.

  1. Run:
surveillance

Usage

surveillance            # launch the application
surveillance --debug    # enable debug logging to stderr
python -m surveillance  # run directly from the source tree

Debug logs automatically redact passwords, session tokens, and usernames.

On launch, a login dialog asks for your NAS connection details:

Field Description Default
Profile name Label for this connection (e.g. home-nas) hostname
Host NAS IP address or hostname
Port DSM port 5001
Use HTTPS Enable HTTPS (recommended) on
Verify SSL Validate the SSL certificate (disable for self-signed) off
Username DSM user with Surveillance Station permissions
Password DSM password
Remember credentials Store in system keyring on

After connecting, the camera list appears in the sidebar. Click a camera to start its live stream. Use the navigation buttons at the bottom of the sidebar to switch between Live View, Recordings, Snapshots, Events, Time Lapse, and Licenses.

Keyboard shortcuts

Key Action
Ctrl+Q Quit

Configuration

Configuration is stored in TOML format following the XDG base directory specification:

~/.config/surveillance-station/config.toml
Example configuration
[general]
default_profile = "home-nas"
theme = "auto"                  # "auto" (follow OS), "dark", or "light"
poll_interval_cameras = 30      # seconds
poll_interval_alerts = 30
poll_interval_homemode = 60
snapshot_dir = "/home/user/.local/share/surveillance-station/snapshots"

[session]
grid_layout = "2x2"            # "1x1", "2x2", "3x3", or "4x4"
last_page = "live"             # last active page

[session.layout_cameras]
# Camera IDs per layout (0 = empty slot).  Each layout remembers its
# own assignment independently.
"1x1" = [1]
"2x2" = [1, 3, 0, 5]
"3x3" = [1, 3, 7, 0, 5, 8, 2, 0, 0]

# Recording search filters (persisted from last search)
# search_camera_ids = [1, 3]
# search_from_time = "2026-02-01T00:00:00"
# search_to_time = "2026-02-19T23:59:59"

[camera_overrides]
# Direct RTSP URLs keyed by camera ID.
# Use when Synology's RTSP proxy corrupts a stream (e.g. Reolink Duo 3 PoE h265).
# 5 = "rtsp://admin:password@192.168.1.50:554/h265Preview_01_main"

[camera_protocols]
# Stream protocol per camera ID:
# auto, websocket, mjpeg, rtsp_over_http, rtsp, multicast, direct
# Auto tries: websocket → mjpeg → rtsp_over_http → rtsp → multicast.
# "websocket" uses a WebSocket stream bridged to mpv via an in-memory pipe.
# "direct" uses the URL from [camera_overrides].
# 5 = "direct"

[profiles.home-nas]
host = "192.168.1.100"
port = 5001
https = true
verify_ssl = false

The [session] section is managed automatically — the application restores the grid layout, active page, and camera assignments from the previous session on restart.

Stream protocols and direct RTSP overrides can also be configured from the UI: right-click a camera in the sidebar to choose the protocol.

Credentials are never stored in the config file. They are kept in the system keyring under the service name surveillance-station.

When two-factor authentication (2FA/MFA) is enabled on the Synology account, the client will prompt for a 6-digit OTP code after entering credentials. Checking "Trust this device" stores a device token in the profile so subsequent logins skip the OTP step. If the trust is revoked on the NAS, the client will prompt for OTP again automatically.


Dependencies

System packages

These must be installed before the Python dependencies.

Debian / Ubuntu
sudo apt install \
    gir1.2-gtk-4.0 \
    libgtk-4-dev \
    libmpv-dev \
    libmpv2 \
    python3-gi \
    python3-gi-cairo \
    python3-cairo
Arch Linux
sudo pacman -S gtk4 mpv python-gobject python-cairo
Fedora
sudo dnf install \
    gtk4-devel \
    mpv-devel \
    python3-gobject \
    python3-cairo
openSUSE
sudo zypper install \
    gtk4-devel \
    mpv-devel \
    python3-gobject \
    python3-gobject-cairo
FreeBSD
pkg install gtk4 mpv py311-gobject3 py311-cairo
OpenBSD
pkg_add gtk4 mpv py3-gobject3 py3-cairo

Python packages

Python 3.11 or later is required. These are installed automatically by pip:

Package Purpose
PyGObject >= 3.50 GTK4 bindings with native asyncio integration
httpx[http2] >= 0.27 Async HTTP/2 client for Synology REST API
python-mpv >= 1.0 libmpv bindings for video rendering
PyOpenGL >= 3.1 OpenGL context for mpv render in GTK4 GLArea
keyring >= 25.0 Secure credential storage
tomli-w >= 1.0 TOML config writing
websockets >= 13.0 WebSocket stream bridge for live view

Architecture

┌─────────────────────────────────────────┐
│  UI Layer          GTK4 widgets         │
│  window, sidebar, liveview, recordings, │
│  player, ptz, snapshots, events,        │
│  timelapse, licenses, notifications     │
├─────────────────────────────────────────┤
│  Service Layer     domain logic         │
│  camera, live, recording, ptz,          │
│  snapshot, event, homemode, license,    │
│  timelapse                              │
├─────────────────────────────────────────┤
│  API Layer         httpx (async)        │
│  client, auth, models                   │
└─────────────────────────────────────────┘

Three event systems are integrated:

  • GLib main loop drives the GTK4 UI
  • asyncio runs in a background thread, bridged to GLib via GLib.idle_add()
  • mpv threads bridge back to the main thread via GLib.idle_add()

Video is rendered through mpv's OpenGL render API into a Gtk.GLArea widget, which works on both X11 and Wayland without window ID embedding.

Project structure
surveillance-station-client/
├── pyproject.toml
├── README.md
├── surveillance.1                      man page
├── build-appimage.sh                   AppImage build script
├── appimage_entry.py                   PyInstaller entry point
├── data/
│   ├── org.surveillance.desktop
│   └── style.css
├── .github/workflows/
│   ├── lint.yml                        CI: ruff + mypy
│   └── release.yml                     AppImage build + GitHub release
├── src/surveillance/
│   ├── __main__.py                     entry point
│   ├── app.py                          Gtk.Application
│   ├── config.py                       TOML config + XDG paths
│   ├── credentials.py                  keyring wrapper
│   ├── api/
│   │   ├── client.py                   SurveillanceAPI (httpx)
│   │   ├── auth.py                     login / logout / SID
│   │   └── models.py                   dataclasses
│   ├── services/
│   │   ├── camera.py                   camera list
│   │   ├── live.py                     stream URL resolution
│   │   ├── ws_bridge.py               WebSocket-to-pipe bridge
│   │   ├── recording.py               recording management
│   │   ├── ptz.py                      PTZ commands
│   │   ├── snapshot.py                 snapshot management
│   │   ├── event.py                    events + alerts
│   │   ├── homemode.py                 home mode toggle
│   │   ├── license.py                  license management
│   │   └── timelapse.py                time lapse management
│   ├── ui/
│   │   ├── window.py                   main window
│   │   ├── login.py                    login dialog
│   │   ├── headerbar.py                header bar controls
│   │   ├── sidebar.py                  camera list sidebar
│   │   ├── liveview.py                 live stream grid
│   │   ├── mpv_widget.py               GLArea + mpv render
│   │   ├── recordings.py               recording browser
│   │   ├── recording_search.py         recording search dialog
│   │   ├── player.py                   playback controls
│   │   ├── ptz_controls.py             PTZ direction pad
│   │   ├── snapshots.py                snapshot browser
│   │   ├── events.py                   event list
│   │   ├── licenses.py                 license management
│   │   ├── timelapse.py                time lapse browser
│   │   └── notifications.py            alert popover
│   └── util/
│       └── async_bridge.py             GLib + asyncio bridge
└── tests/
    ├── conftest.py
    ├── test_api_client.py
    ├── test_models.py
    ├── test_config.py
    └── test_services.py

Development

CI runs automatically on push and pull requests to main:

Workflow Trigger What it does
lint.yml push / PR to main ruff check, ruff format, mypy
release.yml version bump on main Build AppImages (x86_64 + aarch64), create GitHub release

Running checks locally

pip install -e ".[dev]"

ruff check src/ tests/       # lint (rules: E, F, W, I, B, S, SIM, RET, PLR, PLW, PLC, TRY, RUF)
ruff format src/ tests/       # format
mypy src/surveillance/        # type check
pytest tests/ -v              # tests

Building an AppImage locally

./build-appimage.sh

This produces Surveillance-<version>-<arch>.AppImage in the project root. Requires libmpv, GTK4 development files, and libfuse2 on the build machine.


Synology API Reference

Endpoints used by this client
API Purpose
SYNO.API.Info Discover available APIs and CGI paths
SYNO.API.Auth Login / logout / session management
SYNO.SurveillanceStation.Camera Camera list, snapshots, live view paths
SYNO.SurveillanceStation.PTZ Pan, tilt, zoom, presets, patrols
SYNO.SurveillanceStation.Recording List, stream, download recordings
SYNO.SurveillanceStation.SnapShot List, download, delete snapshots
SYNO.SurveillanceStation.TimeLapse Time lapse task listing
SYNO.SurveillanceStation.TimeLapse.Recording Time lapse recording management
SYNO.SurveillanceStation.Event Motion and alarm event history
SYNO.SurveillanceStation.Notification Alert list, unread count, mark read
SYNO.SurveillanceStation.HomeMode Get/set home mode status
SYNO.SurveillanceStation.License License management
SYNO.SurveillanceStation.Info NAS device info

Support

If you find this project useful, you can support its development:

PayPal


Disclaimer

This project is not affiliated with, endorsed by, or sponsored by Synology Inc. Synology, Surveillance Station, and DiskStation Manager (DSM) are trademarks of Synology Inc. This software is an independent, third-party client that interacts with the publicly documented Synology Web API. Use it at your own risk.


License

BSD-2-Clause — see LICENSE for details.

Copyright (c) 2026, Renaud Allard <renaud@allard.it>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.