Skip to main content

Bulk TVT NVR camera scanner and management toolkit

Project description

pytvt

Bulk-scan TVT NVRs and enumerate every connected IP camera — pure Python, no vendor tools required.

Installation

pip install pytvt

Requires Python 3.10+. The published wheel contains only pure Python code.

SDK Installation Required

Native SDK features are optional. pytvt does not bundle, download, or reference proprietary SDK binaries, header files, or private repositories.

To enable SDK-backed features, obtain the TVT SDK directly from the vendor and configure one of these inputs:

Feature Requirement Configuration
netsdk libdvrnetsdk.so on Linux x86-64/aarch64 Set TVT_SDK_PATH=/path/to/libdvrnetsdk.so or TVT_SDK_PATH=/path/to/sdk-root
sdk-local Node.js 18+ and a local scan_nvr.mjs bridge Set TVT_SCAN_SCRIPT=/path/to/scan_nvr.mjs; set TVT_SDK_PATH if the bridge also needs the SDK location
sdk / sdk_http A compatible SDK bridge service Set TVT_API_URL=http://host:3000

Legacy environment variables PYTVT_NETSDK_LIB and PYTVT_SCAN_SCRIPT are still honored for existing developer workflows.

All SDK features fail gracefully with clear errors when dependencies are absent. The default protocol and webapi backends work with a plain pip install.

Example SDK loader

from pytvt.netsdk.loader import NetSdkUnavailable, load_sdk

try:
  sdk = load_sdk(sdk_path="/opt/tvt-sdk")
  print("SDK loaded:", sdk)
except NetSdkUnavailable as exc:
  print(f"SDK unavailable: {exc}")

Higher-level APIs accept the same explicit path:

from pytvt import DeviceManager

with DeviceManager("10.0.0.1", "admin", "password", sdk_path="/opt/tvt-sdk") as mgr:
  print(mgr.device_info())

What is pytvt?

pytvt is a Python toolkit for managing TVT (Shenzhen TVT Digital Technology) NVR and IPC devices at scale. It connects to NVRs, authenticates via the proprietary binary protocol, and retrieves full camera channel inventories — names, IPs, ports, models, online status — across dozens of sites in parallel.

It also supports LAN auto-discovery via SSDP multicast and remote subnet sweeps, so you can find devices without an inventory file.

Since v0.3.0, pytvt includes a Web API client for the TVT HTTP API (LAPI protocol), providing direct per-device management via HTTP Basic auth — device info, snapshots, password changes, image/stream configuration, and recording queries — all in pure Python with no binary dependencies.

Since v0.5.0, the DeviceManager provides a unified facade for device operations that auto-selects the best available backend — native SDK (netsdk ctypes) on Linux, or a compatible SDK bridge over HTTP — so the same code works on any platform.

Architecture

                       ┌───────────────────────────────────────────┐
                       │              pytvt (Python)               │
                       │                                           │
  Inventory JSON ─────▶│  CLI / Library API                        │
  or --discover        │    │                                      │
                       │    ├─ protocol (default)                  │
                       │    │    Pure Python TCP client ──────────▶│── NVR :6036
                       │    │                                      │
                       │    ├─ sdk (compat mode)                   │
                       │    │    HTTP POST ──▶ SDK bridge service ─▶│── NVR :6036
                       │    │                                      │
                       │    └─ sdk-local (direct mode)             │
                       │         Node subprocess ─▶ libdvrnetsdk ─▶│── NVR :6036
                       │                                           │
                       │  Output: console / CSV / JSON / XLSX      │
                       └───────────────────────────────────────────┘

The scanner supports three backends (integration modes), each suited to different deployment scenarios. You can also combine protocol with sdk fallback using --backend both.

Why use it?

  • No vendor software required — the default protocol backend is pure Python with zero native dependencies
  • Bulk operations — scan dozens of NVRs in parallel, export per-site XLSX workbooks
  • Multiple backends — choose the integration mode that fits your environment
  • Auto-discovery — find TVT devices via SSDP multicast or remote subnet sweep
  • Typed data models — clean dataclass-based API for programmatic use
  • CLI + library — use from the command line or import as a Python package

Features

  • Pure-Python TVT binary protocol client (standard XOR and head-variant SHA1 encryption)
  • Native SDK integration via an SDK bridge service or local subprocess
  • LAN device discovery via SSDP multicast
  • Remote subnet sweep via unicast UDP + TCP port fingerprinting
  • Concurrent bulk scanning with configurable parallelism
  • Scan diffing / change detection between scan runs
  • Output to console, CSV, JSON, or per-site XLSX
  • Failed device tracking with automatic retry support
  • Camera snapshot capture (SDK-based)
  • NVR web CGI client for configuration management
  • TVT HTTP Web API client (LAPI) — per-device management via HTTP Basic auth:
    • Capability detection (GetSupportedAPIs)
    • Device info, channels, disks, date/time
    • Password management
    • Image, video stream, audio stream, and OSD configuration
    • Snapshots with automatic fallback (Web API → RTSP)
    • Recording status and search

Backend Comparison

Backend Flag Requires Platform Best for
protocol --backend protocol Python only Any Default — works everywhere, no external deps
sdk --backend sdk SDK bridge service Any Compatibility mode
sdk-local --backend sdk-local Node.js 18+ + native SDK Linux x86-64 only Direct mode — no Docker required
both --backend both SDK bridge service Any Protocol first, SDK fallback on failure

Integration Modes

Compatibility mode (sdk backend) — uses a compatible SDK bridge service that wraps the native TVT SDK behind HTTP.

Direct mode (sdk-local backend) — runs a local scan_nvr.mjs bridge as a Node.js subprocess, calling the native libdvrnetsdk.so directly. Requires Linux x86-64, Node.js 18+, and a vendor-supplied SDK installation.

Protocol mode (default) — pure Python, no external dependencies. Works on any platform with TCP access to the NVR management port. Does not require the SDK at all.

Both integration paths are intentionally supported for compatibility.

Quick Start

# Install
pip install -e .

# Scan NVRs from an inventory file
pytvt devices.json -u admin -p password

# Discover devices on the local LAN
pytvt-discover

# Discover and scan in one command
pytvt --discover -u admin -p password --xlsx files/

Web API Client (v0.3.0+)

The WebApiClient provides direct HTTP access to TVT devices (NVRs and IPCs) using the TVT LAPI protocol. Unlike the NVR CGI client (NvrClient), it uses HTTP Basic auth per request and targets individual devices.

from pytvt.webapi import WebApiClient

client = WebApiClient("192.168.1.100", "admin", "password")

# Check what the device supports
apis = client.get_supported_apis()

# Device info
info = client.get_device_info()
print(f"{info.device_model} — FW {info.firmware_version}")

# Channels
for ch in client.get_channel_info():
    status = "online" if ch.online else "offline"
    print(f"  CH{ch.channel_id}: {ch.channel_name} ({status})")

# Snapshot (Web API with RTSP fallback)
result = client.get_snapshot(channel_id=1)
if result.success:
    with open("snapshot.jpg", "wb") as f:
        f.write(result.image_data)

# Change password
client.modify_password("old_pass", "new_pass")

The Web API service must be enabled on the device. Use ensure_webapi_available() to auto-enable it via the NVR CGI if needed:

client = WebApiClient("192.168.1.100", "admin", "password")
client.ensure_webapi_available()  # enables via NvrClient if disabled

DeviceManager (v0.5.0+)

The DeviceManager provides a unified facade for TVT device operations that automatically selects the best available backend:

  1. Native SDK (netsdk) — direct ctypes calls to libdvrnetsdk.so on Linux x86_64/aarch64
  2. SDK HTTP (sdk_http) — HTTP calls to a compatible SDK bridge service (any platform)
from pytvt import DeviceManager, available_backends

# Check what's available
print(available_backends(sdk_path="/opt/tvt-sdk"))

# Auto-detect best backend
with DeviceManager("10.200.50.251", "admin", "password", sdk_path="/opt/tvt-sdk") as mgr:
    print(f"Using: {mgr.backend}")

    info = mgr.device_info()
    print(f"{info.device_model} — SN {info.serial_number}")

    time = mgr.device_time()
    jpeg = mgr.snapshot(channel=0)
    url  = mgr.rtsp_url(channel=0)
    mgr.reboot()

# Force a specific backend
mgr = DeviceManager("10.200.50.251", "admin", "password", backend="sdk_http", api_url="http://localhost:3000")

All methods return the same result types regardless of which backend is active. If no backend is available, NoBackendAvailable is raised.

Installation

Public PyPI install

pip install pytvt

Requires Python 3.10+ and network access to NVR port 6036. No native dependencies are needed for protocol or webapi.

Enable native SDK features

export TVT_SDK_PATH=/opt/tvt-sdk

TVT_SDK_PATH can point either to libdvrnetsdk.so itself or to the root of the unpacked vendor SDK.

Enable sdk-local

export TVT_SCAN_SCRIPT=/opt/pytvt-bridges/scan_nvr.mjs
pytvt devices.json --backend sdk-local

Enable an SDK bridge service

export TVT_API_URL=http://localhost:3000
pytvt devices.json --backend sdk

Usage

SDK Bridge Mode

# Scan via an SDK HTTP bridge service
pytvt devices.json --backend sdk --api-url http://localhost:3000

# Protocol first, SDK fallback
pytvt devices.json --backend both --api-url http://localhost:3000

Direct Mode (sdk-local)

pytvt devices.json --backend sdk-local

Protocol Mode (default)

# Scan with default protocol backend
pytvt devices.json

# Override credentials
pytvt devices.json -u admin -p mypassword

# Export to CSV
pytvt devices.json -o cameras.csv

# Export to JSON
pytvt devices.json -o cameras.json

# One XLSX per site
pytvt devices.json --xlsx files/

# Filter by site name
pytvt devices.json --site "Downtown"

# Limit concurrency
pytvt devices.json -c 2

# Also works as a module
python -m pytvt devices.json

Discovery

# LAN multicast discovery
pytvt-discover

# Custom timeout
pytvt-discover --timeout 8 --retries 3

# JSON output
pytvt-discover --json

# Save as scanner-compatible inventory
pytvt-discover --scanner-json discovered.json --site "Office"

# Remote subnet sweep
pytvt-discover --subnet 10.200.50.0/24

# Multiple subnets
pytvt-discover --subnet 10.200.50.0/24 --subnet 10.200.51.0/24

# Discover + scan in one command
pytvt --discover --xlsx files/
pytvt --subnet 10.200.50.0/24 -o results.json

Scan Diffing

Compare two scan result files to detect changes between runs:

# Human-readable diff report
pytvt-diff old_scan.json new_scan.json

# Machine-readable JSON output
pytvt-diff --json old_scan.json new_scan.json

# One-line summary
pytvt-diff --summary old_scan.json new_scan.json

The diff detects:

  • Devices added/removed — NVRs appearing or disappearing between scans
  • Device metadata changes — firmware upgrades, model changes, scan failures
  • Camera channel changes — cameras added/removed/renamed, status changes (Online ↔ Offline), IP address changes
  • Failure drift — devices that started or stopped failing

Devices are matched by nvr_ip. Cameras within a device are matched by channel index. Only cameras with IP addresses are compared.

Console output example:

================================================================================
  SCAN DIFF REPORT
================================================================================
  Old: monday.json
  New: friday.json
  Devices: 3 → 3
  Changes: 1 changed, 2 unchanged
================================================================================

  ~~ CHANGED DEVICES (1)
  ----------------------------------------------------------------------------
  ~ 10.10.10.100 / NVR-01 @ Site A
      firmware: '5.1.0' → '5.2.3'
      cameras: 29 → 30
      + ch 29: New Entrance Cam
      ~ ch 5 (Lobby): status: 'Offline' → 'Online'

Library usage:

from pytvt import diff_scans, load_scan_file

old = load_scan_file("monday.json")
new = load_scan_file("friday.json")
diff = diff_scans(old, new)

for device in diff.devices_changed:
    print(f"{device.nvr_ip}: {len(device.field_changes)} field changes")

CLI Reference

Flag Description
input Path to NVR devices JSON file (optional with --discover)
--config Path to config.json (default: ./config.json)
-o, --output Output file path (.csv or .json)
-s, --site Filter by site name (partial match)
-u, --username Override NVR username
-p, --password Override NVR password
-c, --concurrency Max parallel scans (default: from config)
--backend protocol (default), sdk, sdk-local, or both
--api-url SDK bridge URL for sdk/both (default: http://localhost:3000)
--xlsx DIR Export one XLSX per site into DIR
--failed FILE Save failed devices to JSON for retry
--discover Run LAN discovery before scanning
--discover-only Discover only, no scanning
--discover-timeout Seconds per discovery probe (default: 5)
--subnet CIDR Sweep a remote subnet (repeatable)
--discover-concurrency Max parallel probes for subnet sweep (default: 50)
--no-tcp-fallback Skip TCP port-probe fallback during sweep

Output Examples

Console:

================================================================================
  Site: Demo Site
  NVR:  NVR-01 (10.10.10.100)
================================================================================
  Device: NVR32 | Model: TD-3332B4
  Firmware: 5.2.3.19033B241010
  S/N: ABC123456789
  Total Channels: 29
  ----------------------------------------------------------------------------
  Ch   Camera Name                  Address            Port   Status   Model
  --   ---------------------------  -----------------  ----   ------   -----
  1    Lobby                        192.168.1.100      9008   Online   IP-5IRD4S4C4-28
  2    Parking                      192.168.1.101      9008   Online   IP-5IRD4S4C4-28

================================================================================
  SUMMARY: 2/2 NVRs scanned successfully, 58 total cameras found
================================================================================

XLSX per-site: One .xlsx file per site with NVR Config (cameras) and NVR Info (device summary) tabs. Existing tabs in the file are preserved.

Input Format

JSON array of device objects. At minimum each needs ip:

[
  {
    "site": "Site A Downtown",
    "ip": "10.10.10.100",
    "mac": "58:5B:69:AA:BB:01",
    "hostname": "NVR1",
    "manufacturer": "TVT"
  }
]

Configuration

Credentials

cp .env.example .env
# Edit .env with your NVR credentials

Precedence (first wins):

  1. CLI flags (-u, -p)
  2. Environment variables (TVT_USERNAME, TVT_PASSWORD)
  3. config.json fields
  4. Built-in defaults

Settings (config.json)

{
    "port": 6036,
    "timeout": 10,
    "max_channels": 64,
    "concurrency": 4
}
Field Default Description
port 6036 TVT protocol port
timeout 10 Connection timeout (seconds)
max_channels 64 Max camera channels per NVR
concurrency 4 Parallel NVR scans

Environment variables override config file values: TVT_PORT, TVT_TIMEOUT, TVT_MAX_CHANNELS, TVT_CONCURRENCY, TVT_SDK_PATH, TVT_SCAN_SCRIPT.

Project Structure

pytvt/
├── src/pytvt/                 # Installable Python package (supported runtime)
│   ├── __init__.py            # Public API + version
│   ├── __main__.py            # python -m pytvt support
│   ├── cli.py                 # CLI entry points (pytvt, pytvt-discover, etc.)
│   ├── config.py              # Configuration loading + precedence
│   ├── constants.py           # Enums, ExecutionPlan, backend resolution
│   ├── exceptions.py          # Base exception hierarchy
│   ├── models.py              # Dataclasses: ScannerConfig, DeviceEntry, CameraInfo, ScanResult
│   ├── registry.py            # Backend/integration mode dispatch table
│   ├── scanner.py             # Backend dispatch + scan orchestration
│   ├── protocol.py            # Pure Python TVT binary protocol client
│   ├── discovery.py           # SSDP multicast + subnet sweep discovery
│   ├── diff.py                # Scan diffing / change detection
│   ├── sdk_http.py            # SDK HTTP backend (compat mode → external bridge)
│   ├── sdk_http_client.py     # Typed SDK HTTP client
│   ├── device_manager.py      # Unified facade with auto-backend selection
│   ├── sdk_local.py           # SDK local subprocess backend (direct mode)
│   ├── output.py              # CSV / JSON / XLSX formatters
│   ├── nvr_api.py             # NVR web CGI client
│   ├── snapshot.py            # Camera snapshot capture
│   └── netsdk/                # Native SDK ctypes bindings (Linux only)
│       ├── __init__.py        # Package exports
│       ├── loader.py          # Library search + loading
│       ├── bindings.py        # ctypes function prototypes
│       ├── types.py           # ctypes structure definitions
│       ├── constants.py       # Enums (PtzCommand, StreamType, etc.)
│       └── client.py          # NetSdkClient + DeviceSession
├── bridges/
│   └── sdk_local/             # Local-only Node.js SDK bridge (excluded from PyPI)
│       ├── scan_nvr.mjs       # SDK FFI subprocess script
│       └── package.json       # koffi dependency
├── tvt-api/                   # Local-only SDK bridge workspace (excluded from PyPI)
├── tests/                     # pytest test suite
│   ├── conftest.py            # Shared fixtures + sample data
│   ├── test_protocol.py       # Protocol packet/encryption tests
│   ├── test_architecture.py   # Enum, registry, execution plan, import boundary tests
│   ├── test_config.py         # Config precedence tests
│   ├── test_models.py         # Model construction + serialization
│   ├── test_scanner.py        # Backend dispatch + device loading
│   ├── test_output.py         # Output format tests
│   ├── test_discovery.py      # XML parsing + discovery
│   ├── test_diff.py           # Scan diffing + CLI tests
│   ├── test_cli.py            # CLI parser + helpers
│   ├── test_sdk_http.py       # SDK HTTP backend (mocked)
│   ├── test_sdk_http_client.py # SdkHttpClient tests
│   ├── test_netsdk_client.py  # netsdk ctypes binding tests
│   ├── test_device_manager.py # DeviceManager facade tests
│   └── test_sdk_local.py      # JSON extraction tests
├── tools/                     # Operational utilities (import from pytvt)
│   └── enable_nvr_services.py # Batch NVR RTSP + API enablement
├── research/                  # Reverse-engineering reference (NOT runtime code)
│   ├── README.md              # Context and usage notes
│   ├── parse_pcap.py          # Extract TVT init/login packets from pcap
│   ├── verify_pw.py           # Verify password encryption against live NVR
│   ├── verify_capture.py      # Verify encryption from captured SDK traffic
│   ├── capture_sdk.sh         # Capture SDK ↔ NVR traffic via tcpdump
│   ├── sdk_login.cjs          # Trigger SDK login for traffic capture
│   └── test_sha1.mjs          # Test SDK PUB_SHA1Encrypt directly
├── pyproject.toml             # Packaging (hatchling) + tool config
├── config.json                # Default non-sensitive settings
├── .env.example               # Credential template
├── .github/
│   ├── workflows/ci.yml       # CI: pytest + ruff (Python 3.10–3.13)
│   ├── PULL_REQUEST_TEMPLATE.md
│   └── ISSUE_TEMPLATE/
└── CONTRIBUTING.md

Supported vs Experimental Code

pytvt distinguishes between three tiers of code:

Published package (src/pytvt/)

The PyPI wheel and sdist ship only the Python package in src/pytvt/. This is the supported public runtime.

Local helper runtimes (bridges/, tvt-api/)

These folders support developer workflows only. They are excluded from PyPI artifacts and may rely on separate, vendor-supplied SDK installations.

Research / reference (research/)

Standalone scripts from the protocol reverse-engineering process. These scripts were used to discover and validate the TVT binary protocol — nonce exchange, password encryption, login packet structure. The findings are implemented in src/pytvt/protocol.py.

Research code is not imported by any runtime module. This boundary is enforced by a test (test_architecture.py::TestImportBoundary). The scripts are preserved as runnable documentation of how the protocol was reverse engineered.

Development

Setup

git clone https://github.com/dannielperez/pytvt.git
cd pytvt
pip install -e ".[dev]"

Testing

pytest                    # run all tests
pytest -v                 # verbose
pytest tests/test_protocol.py  # single module

Linting

ruff check src/ tests/    # lint
ruff format src/ tests/   # format
mypy src/pytvt/           # type check (optional)

Using as a library

from pytvt import load_config, load_devices, scan_single_nvr, filter_tvt_devices

config = load_config("config.json")
devices = filter_tvt_devices(load_devices("devices.json"))

for device in devices:
    result = scan_single_nvr(device, config, backend="protocol")
    if result.success:
        print(f"{result.device_name}: {result.camera_count} cameras")

Limitations

  • Protocol backend: Retrieves camera channel metadata only — no video streaming or recording
  • Head-variant NVRs (protocolVer ≥ 11): Return basic channel data (name, status) but not IPC IP addresses
  • SDK backends: Native SDK (libdvrnetsdk.so) is Linux x86-64 only
  • sdk-local backend: Requires Node.js and the SDK shared library on the host
  • Discovery: SSDP multicast is limited to the local broadcast domain; remote subnets need --subnet
  • No TLS: The TVT protocol does not support encryption in transit
  • Credential handling: Passwords are XOR-encrypted on the wire (standard variant) — treat the NVR management network as trusted

Related Projects

Project Description
2BAD/tvt Original TVT protocol reverse engineering (TypeScript)
koffi FFI bindings used by the SDK bridge

License

MIT License. See CHANGELOG.md for release history.

Starting from version 1.0.0, this project is licensed under the MIT License. Previous versions were licensed under AGPLv3.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pytvt-0.5.1.tar.gz (128.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pytvt-0.5.1-py3-none-any.whl (103.5 kB view details)

Uploaded Python 3

File details

Details for the file pytvt-0.5.1.tar.gz.

File metadata

  • Download URL: pytvt-0.5.1.tar.gz
  • Upload date:
  • Size: 128.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for pytvt-0.5.1.tar.gz
Algorithm Hash digest
SHA256 2ea6d4af70dc34e9c2c29ec086836b464258823cbf2d4cf04992fedfe419c8f6
MD5 e0f1cc1d9b29f92bd624edf66a7ed171
BLAKE2b-256 b8b1a73bed387aa54ef02c5400a0a05febd6b49d4e6177836d85bd0a3b72e85e

See more details on using hashes here.

File details

Details for the file pytvt-0.5.1-py3-none-any.whl.

File metadata

  • Download URL: pytvt-0.5.1-py3-none-any.whl
  • Upload date:
  • Size: 103.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for pytvt-0.5.1-py3-none-any.whl
Algorithm Hash digest
SHA256 a00b656da9f1248db46ca1d39ee6924c236fbe9229396fea5e6dafd49a2dbe36
MD5 50fa9952f5b02a11d6b7ebc178e2d36f
BLAKE2b-256 37c0a69ef19a411bc6a4c26cf1012f5f4ca66547b42690ff03a04822275fe41e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page