Skip to content

Commit f425436

Browse files
committed
Updated
1 parent 2567662 commit f425436

File tree

4 files changed

+154
-3
lines changed

4 files changed

+154
-3
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# -*- mode: python ; coding: utf-8 -*-
2+
from PyInstaller.utils.hooks import collect_all
3+
4+
# Collect all DLLs, .pyd extensions, data files (models/templates/resources),
5+
# and hidden imports for dynamsoft_capture_vision_bundle so they are bundled
6+
# into the executable and available at runtime via sys._MEIPASS.
7+
dcvb_datas, dcvb_binaries, dcvb_hiddenimports = collect_all('dynamsoft_capture_vision_bundle')
8+
9+
a = Analysis(
10+
['main.py'],
11+
pathex=[],
12+
binaries=dcvb_binaries,
13+
datas=dcvb_datas, # config.json is NOT embedded — distribute it next to the exe so users can edit the license key
14+
hiddenimports=dcvb_hiddenimports + [
15+
# Dynamsoft sub-modules loaded dynamically at runtime
16+
'dynamsoft_capture_vision_bundle',
17+
'dynamsoft_capture_vision_bundle.core',
18+
'dynamsoft_capture_vision_bundle.cvr',
19+
'dynamsoft_capture_vision_bundle.dbr',
20+
'dynamsoft_capture_vision_bundle.dcp',
21+
'dynamsoft_capture_vision_bundle.dcpd',
22+
'dynamsoft_capture_vision_bundle.ddn',
23+
'dynamsoft_capture_vision_bundle.dip',
24+
'dynamsoft_capture_vision_bundle.dlr',
25+
'dynamsoft_capture_vision_bundle.id_utility',
26+
'dynamsoft_capture_vision_bundle.license',
27+
'dynamsoft_capture_vision_bundle.utility',
28+
# Other runtime dependencies
29+
'cv2',
30+
'numpy',
31+
'requests',
32+
'PySide6',
33+
'PySide6.QtCore',
34+
'PySide6.QtGui',
35+
'PySide6.QtWidgets',
36+
'PySide6.QtNetwork',
37+
],
38+
hookspath=[],
39+
hooksconfig={},
40+
runtime_hooks=[],
41+
excludes=[],
42+
noarchive=False,
43+
optimize=0,
44+
)
45+
pyz = PYZ(a.pure)
46+
47+
# One-file executable: all binaries and datas are embedded in the EXE.
48+
# On first run the files are extracted to a temporary directory (sys._MEIPASS).
49+
exe = EXE(
50+
pyz,
51+
a.scripts,
52+
a.binaries,
53+
a.datas,
54+
[],
55+
name='IPCameraViewer',
56+
debug=False,
57+
bootloader_ignore_signals=False,
58+
strip=False,
59+
upx=True,
60+
upx_exclude=[],
61+
runtime_tmpdir=None,
62+
console=False,
63+
disable_windowed_traceback=False,
64+
argv_emulation=False,
65+
target_arch=None,
66+
codesign_identity=None,
67+
entitlements_file=None,
68+
)
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# IP Camera Viewer
2+
3+
A desktop application for viewing IP camera streams with real-time barcode/QR code scanning, built with PySide6 and the Dynamsoft Capture Vision SDK.
4+
5+
## Requirements
6+
7+
- Python 3.9 – 3.12
8+
- Windows x64 (the Dynamsoft native DLLs bundled in this project target `win_amd64`)
9+
10+
## Run from source
11+
12+
```bash
13+
pip install -r requirements.txt
14+
python main.py
15+
```
16+
17+
## Build a standalone executable with PyInstaller
18+
19+
The provided `IPCameraViewer.spec` packages everything—including all Dynamsoft DLLs, model files, and template JSON files—into a **single self-contained `.exe`** that can be copied to any Windows PC without requiring Python or any additional packages.
20+
21+
### 1. Install dependencies (one-time)
22+
23+
```bash
24+
pip install -r requirements.txt
25+
```
26+
27+
### 2. Build the executable
28+
29+
Run this command from the `gui_client` directory:
30+
31+
```bash
32+
pyinstaller IPCameraViewer.spec
33+
```
34+
35+
The finished executable is written to:
36+
37+
```
38+
dist/IPCameraViewer.exe
39+
```
40+
41+
### 3. Distribute
42+
43+
Copy **both files** to the same folder on the target machine:
44+
45+
```
46+
IPCameraViewer.exe
47+
config.json
48+
```
49+
50+
`config.json` is intentionally **not** embedded in the exe so the license key and other settings can be edited with any text editor without rebuilding.
51+
52+
> **Note:** On first launch the application unpacks its embedded files to a temporary folder (`%TEMP%\_MEIxxxxxx`). This is normal PyInstaller behaviour for one-file executables and takes a few seconds.
53+
54+
---
55+
56+
### What the spec does
57+
58+
| Problem | Fix applied in `IPCameraViewer.spec` |
59+
|---|---|
60+
| Dynamsoft DLLs (`Dynamsoft*x64.dll`) not found at runtime | `collect_all('dynamsoft_capture_vision_bundle')` adds all DLLs as `binaries` |
61+
| Dynamsoft model/template/resource `.data` and `.json` files missing | Same `collect_all` call adds them as `datas` under `dynamsoft_capture_vision_bundle/` |
62+
| Sub-modules imported by name at runtime not detected by PyInstaller | Full list added to `hiddenimports` |
63+
| One-folder output inconvenient for distribution | Switched to **one-file** mode (`a.binaries` + `a.datas` embedded directly in `EXE`) |
64+
| `config.json` (contains license key) inaccessible when embedded in exe | Excluded from `datas`; resolved at runtime from `sys.executable`'s directory so users can edit it freely |
65+
66+
### Regenerating the spec from scratch
67+
68+
If you need to rebuild the spec (e.g., after a major dependency change), use:
69+
70+
```bash
71+
pyinstaller --onefile --noconsole --name IPCameraViewer \
72+
--collect-all dynamsoft_capture_vision_bundle \
73+
--hidden-import cv2 \
74+
--hidden-import numpy \
75+
--hidden-import requests \
76+
--add-data "config.json;." \
77+
main.py
78+
```
79+
80+
> On Windows use `;` as the path separator in `--add-data`. On Linux/macOS use `:`.

examples/official/ip_camera/gui_client/barcode_scanner.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,8 @@ def _load_license_from_config(self):
133133
import json
134134

135135
try:
136-
config_path = os.path.join(os.path.dirname(__file__), 'config.json')
136+
_cfg_dir = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname(os.path.abspath(__file__))
137+
config_path = os.path.join(_cfg_dir, 'config.json')
137138
if os.path.exists(config_path):
138139
with open(config_path, 'r') as f:
139140
config = json.load(f)

examples/official/ip_camera/gui_client/main.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,8 @@ def show_about_dialog(self):
506506
def load_app_settings(self) -> Dict[str, Any]:
507507
"""Load application settings"""
508508
try:
509-
config_file = os.path.join(os.path.dirname(__file__), 'config.json')
509+
_cfg_dir = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname(os.path.abspath(__file__))
510+
config_file = os.path.join(_cfg_dir, 'config.json')
510511
if os.path.exists(config_file):
511512
with open(config_file, 'r') as f:
512513
return json.load(f)
@@ -530,7 +531,8 @@ def load_app_settings(self) -> Dict[str, Any]:
530531
def save_app_settings(self):
531532
"""Save application settings"""
532533
try:
533-
config_file = os.path.join(os.path.dirname(__file__), 'config.json')
534+
_cfg_dir = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname(os.path.abspath(__file__))
535+
config_file = os.path.join(_cfg_dir, 'config.json')
534536
with open(config_file, 'w') as f:
535537
json.dump(self.app_settings, f, indent=2)
536538
except Exception as e:

0 commit comments

Comments
 (0)