387f252b9d
- Update module path from github.com/strix-project/strix to github.com/eduard256/Strix - Update all Go imports to use new repository path - Update documentation links in README.md and CHANGELOG.md - Update GitHub URLs in .goreleaser.yaml - Fix placeholder documentation URL in DATABASE_FORMAT.md - Remove old log files
518 lines
13 KiB
Markdown
518 lines
13 KiB
Markdown
# 📹 IoT2mqtt Camera Database Format Specification
|
|
|
|
**Version:** 1.0.0
|
|
**Last Updated:** 2025-10-17
|
|
|
|
---
|
|
|
|
## 🎯 Overview
|
|
|
|
The camera database is a collection of JSON files containing URL patterns and connection details for IP cameras from various manufacturers. This format is designed to be:
|
|
|
|
- **Universal**: Works with any IP camera brand
|
|
- **Extensible**: Easy to add new models and protocols
|
|
- **Human-readable**: Simple JSON structure
|
|
- **Parseable**: Straightforward for automated tools
|
|
|
|
---
|
|
|
|
## 📁 Directory Structure
|
|
|
|
```
|
|
connectors/cameras/data/brands/
|
|
├── index.json # Master list of all brands
|
|
├── d-link.json # D-Link camera models
|
|
├── hikvision.json # Hikvision camera models
|
|
├── dahua.json # Dahua camera models
|
|
├── axis.json # Axis camera models
|
|
└── ... # Additional brands
|
|
```
|
|
|
|
---
|
|
|
|
## 📋 File Formats
|
|
|
|
### 1. **index.json** - Brand Directory
|
|
|
|
Lists all available camera brands with metadata.
|
|
|
|
```json
|
|
[
|
|
{
|
|
"value": "d-link",
|
|
"label": "D-Link",
|
|
"models_count": 250,
|
|
"entries_count": 85,
|
|
"logo": "/assets/brands/d-link.svg"
|
|
},
|
|
{
|
|
"value": "hikvision",
|
|
"label": "Hikvision",
|
|
"models_count": 320,
|
|
"entries_count": 95,
|
|
"logo": "/assets/brands/hikvision.svg"
|
|
}
|
|
]
|
|
```
|
|
|
|
**Fields:**
|
|
- `value` (string, required): Brand identifier (lowercase, URL-safe)
|
|
- `label` (string, required): Display name
|
|
- `models_count` (integer): Total number of camera models
|
|
- `entries_count` (integer): Number of URL pattern entries
|
|
- `logo` (string, optional): Path to brand logo
|
|
|
|
---
|
|
|
|
### 2. **{brand}.json** - Brand Camera Database
|
|
|
|
Contains all URL patterns and connection details for a specific brand.
|
|
|
|
```json
|
|
{
|
|
"brand": "D-Link",
|
|
"brand_id": "d-link",
|
|
"last_updated": "2025-10-17",
|
|
"source": "ispyconnect.com",
|
|
"website": "https://www.dlink.com",
|
|
"entries": [
|
|
{
|
|
"models": ["DCS-930L", "DCS-930LB", "DCS-930LB1"],
|
|
"type": "FFMPEG",
|
|
"protocol": "rtsp",
|
|
"port": 554,
|
|
"url": "live3.sdp",
|
|
"notes": "Main HD stream"
|
|
},
|
|
{
|
|
"models": ["DCS-930L", "DCS-932L"],
|
|
"type": "MJPEG",
|
|
"protocol": "http",
|
|
"port": 80,
|
|
"url": "video.cgi?resolution=VGA",
|
|
"notes": "Medium quality fallback"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Root Fields:**
|
|
- `brand` (string, required): Brand display name
|
|
- `brand_id` (string, required): Brand identifier (must match filename)
|
|
- `last_updated` (string, ISO 8601 date): When database was last updated
|
|
- `source` (string): Where the data came from (e.g., "ispyconnect.com")
|
|
- `website` (string, optional): Manufacturer's official website
|
|
- `entries` (array, required): List of URL pattern entries
|
|
|
|
---
|
|
|
|
### 3. **Entry Object** - URL Pattern Entry
|
|
|
|
Each entry represents a specific URL pattern that works for one or more camera models.
|
|
|
|
```json
|
|
{
|
|
"models": ["DCS-930L", "DCS-930LB", "DCS-930LB1"],
|
|
"type": "FFMPEG",
|
|
"protocol": "rtsp",
|
|
"port": 554,
|
|
"url": "live3.sdp",
|
|
"auth_required": true,
|
|
"notes": "Main HD stream with audio"
|
|
}
|
|
```
|
|
|
|
**Fields:**
|
|
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| `models` | array[string] | ✅ Yes | List of camera model names/numbers this URL works for |
|
|
| `type` | string | ✅ Yes | Stream type: `FFMPEG`, `MJPEG`, `JPEG`, `VLC`, `H264` |
|
|
| `protocol` | string | ✅ Yes | Protocol: `rtsp`, `http`, `https` |
|
|
| `port` | integer | ✅ Yes | Port number (554 for RTSP, 80/443 for HTTP) |
|
|
| `url` | string | ✅ Yes | URL path (without protocol/host/port) |
|
|
| `auth_required` | boolean | No | Whether authentication is needed (default: true) |
|
|
| `notes` | string | No | Human-readable description |
|
|
|
|
---
|
|
|
|
## 🔧 URL Template Variables
|
|
|
|
URL paths support the following template variables:
|
|
|
|
| Variable | Description | Example |
|
|
|----------|-------------|---------|
|
|
| `{username}` | Camera username | `admin` |
|
|
| `{password}` | Camera password | `12345` |
|
|
| `{ip}` | Camera IP address | `192.168.1.100` |
|
|
| `{port}` | Port number | `554` |
|
|
| `{channel}` | Camera channel (for DVRs) | `1` |
|
|
| `{width}` | Video width | `1920` |
|
|
| `{height}` | Video height | `1080` |
|
|
|
|
**Example:**
|
|
```
|
|
Template: rtsp://{username}:{password}@{ip}:{port}/live3.sdp
|
|
Result: rtsp://admin:12345@192.168.1.100:554/live3.sdp
|
|
```
|
|
|
|
---
|
|
|
|
## 📊 Stream Types
|
|
|
|
### FFMPEG (Recommended)
|
|
- **Protocol**: RTSP, HTTP
|
|
- **Format**: H.264, H.265
|
|
- **Use case**: High-quality video with audio
|
|
- **Priority**: 🥇 First choice
|
|
|
|
### MJPEG
|
|
- **Protocol**: HTTP
|
|
- **Format**: Motion JPEG
|
|
- **Use case**: Medium quality, wide compatibility
|
|
- **Priority**: 🥈 Second choice
|
|
|
|
### JPEG
|
|
- **Protocol**: HTTP
|
|
- **Format**: Still images
|
|
- **Use case**: Snapshot-only cameras or fallback
|
|
- **Priority**: 🥉 Last resort
|
|
|
|
### VLC
|
|
- **Protocol**: RTSP, HTTP
|
|
- **Format**: Various (VLC-specific)
|
|
- **Use case**: Compatibility with VLC player
|
|
|
|
---
|
|
|
|
## 🎯 Priority Order for Testing
|
|
|
|
When testing multiple URLs for a camera model, use this priority:
|
|
|
|
1. **RTSP (type="FFMPEG")** - Best quality, supports audio
|
|
2. **HTTP MJPEG** - Good compatibility
|
|
3. **HTTP JPEG** - Snapshot fallback
|
|
|
|
**Example:**
|
|
```python
|
|
def get_urls_for_model(brand_data, model_name):
|
|
entries = [e for e in brand_data["entries"] if model_name in e["models"]]
|
|
|
|
# Sort by priority
|
|
priority = {"FFMPEG": 1, "MJPEG": 2, "JPEG": 3, "VLC": 4}
|
|
entries.sort(key=lambda e: priority.get(e["type"], 99))
|
|
|
|
return entries
|
|
```
|
|
|
|
---
|
|
|
|
## 🔍 Search and Lookup
|
|
|
|
### By Brand
|
|
```python
|
|
# Load brand file
|
|
with open(f"data/brands/{brand_id}.json") as f:
|
|
brand_data = json.load(f)
|
|
```
|
|
|
|
### By Model
|
|
```python
|
|
# Find all entries for a specific model
|
|
def find_model_entries(brand_data, model_name):
|
|
return [
|
|
entry for entry in brand_data["entries"]
|
|
if model_name.upper() in [m.upper() for m in entry["models"]]
|
|
]
|
|
```
|
|
|
|
### Fuzzy Search
|
|
```python
|
|
# Search across all models (case-insensitive, partial match)
|
|
def search_model(brand_data, query):
|
|
query = query.upper()
|
|
results = []
|
|
for entry in brand_data["entries"]:
|
|
if any(query in model.upper() for model in entry["models"]):
|
|
results.append(entry)
|
|
return results
|
|
```
|
|
|
|
---
|
|
|
|
## 🌐 URL Construction
|
|
|
|
### RTSP URL
|
|
```python
|
|
def build_rtsp_url(entry, ip, username, password):
|
|
return f"rtsp://{username}:{password}@{ip}:{entry['port']}/{entry['url']}"
|
|
|
|
# Example:
|
|
# rtsp://admin:12345@192.168.1.100:554/live3.sdp
|
|
```
|
|
|
|
### HTTP URL
|
|
```python
|
|
def build_http_url(entry, ip, username, password):
|
|
protocol = entry["protocol"] # "http" or "https"
|
|
return f"{protocol}://{username}:{password}@{ip}:{entry['port']}/{entry['url']}"
|
|
|
|
# Example:
|
|
# http://admin:12345@192.168.1.100:80/video.cgi?resolution=VGA
|
|
```
|
|
|
|
### With Template Variables
|
|
```python
|
|
def build_url(entry, ip, username, password, **kwargs):
|
|
url_path = entry["url"]
|
|
|
|
# Replace template variables
|
|
replacements = {
|
|
"username": username,
|
|
"password": password,
|
|
"ip": ip,
|
|
"port": str(entry["port"]),
|
|
**kwargs # Additional variables (channel, width, height, etc.)
|
|
}
|
|
|
|
for key, value in replacements.items():
|
|
url_path = url_path.replace(f"{{{key}}}", value)
|
|
|
|
# Build full URL
|
|
if entry["protocol"] == "rtsp":
|
|
return f"rtsp://{username}:{password}@{ip}:{entry['port']}/{url_path}"
|
|
else:
|
|
return f"{entry['protocol']}://{username}:{password}@{ip}:{entry['port']}/{url_path}"
|
|
```
|
|
|
|
---
|
|
|
|
## ✅ Validation Rules
|
|
|
|
### Entry Validation
|
|
```python
|
|
def validate_entry(entry):
|
|
# Required fields
|
|
assert "models" in entry and isinstance(entry["models"], list)
|
|
assert len(entry["models"]) > 0
|
|
assert "type" in entry and entry["type"] in ["FFMPEG", "MJPEG", "JPEG", "VLC", "H264"]
|
|
assert "protocol" in entry and entry["protocol"] in ["rtsp", "http", "https"]
|
|
assert "port" in entry and isinstance(entry["port"], int)
|
|
assert "url" in entry and isinstance(entry["url"], str)
|
|
|
|
# Port ranges
|
|
assert 1 <= entry["port"] <= 65535
|
|
|
|
# Common ports check
|
|
if entry["protocol"] == "rtsp":
|
|
assert entry["port"] in [554, 8554, 7447] # Common RTSP ports
|
|
elif entry["protocol"] == "http":
|
|
assert entry["port"] in [80, 8080, 8000, 8081] # Common HTTP ports
|
|
```
|
|
|
|
---
|
|
|
|
## 📝 Naming Conventions
|
|
|
|
### Brand IDs
|
|
- **Format**: lowercase, kebab-case
|
|
- **Examples**: `d-link`, `hikvision`, `tp-link`
|
|
- **Invalid**: `D-Link`, `D_Link`, `dlink`
|
|
|
|
### Model Names
|
|
- **Format**: UPPERCASE with hyphens (as manufacturer specifies)
|
|
- **Examples**: `DCS-930L`, `DS-2CD2142FWD-I`, `IPC-HFW1230S`
|
|
- **Keep original**: Don't normalize or change manufacturer names
|
|
|
|
### Protocol Values
|
|
- `rtsp` - RTSP protocol
|
|
- `http` - HTTP protocol
|
|
- `https` - HTTPS protocol
|
|
- **Invalid**: `RTSP`, `Http`, `tcp`
|
|
|
|
### Type Values
|
|
- `FFMPEG` - H.264/H.265 streams (RTSP or HTTP)
|
|
- `MJPEG` - Motion JPEG streams
|
|
- `JPEG` - Still image snapshots
|
|
- `VLC` - VLC-specific streams
|
|
|
|
---
|
|
|
|
## 🔄 Versioning and Updates
|
|
|
|
### Version Format
|
|
```json
|
|
{
|
|
"brand": "D-Link",
|
|
"brand_id": "d-link",
|
|
"database_version": "1.2.0",
|
|
"last_updated": "2025-10-17T14:30:00Z",
|
|
"entries": [...]
|
|
}
|
|
```
|
|
|
|
### Update Policy
|
|
- **Patch** (1.0.x): Add new models to existing entries
|
|
- **Minor** (1.x.0): Add new URL patterns/entries
|
|
- **Major** (x.0.0): Breaking changes to structure
|
|
|
|
---
|
|
|
|
## 📚 Examples
|
|
|
|
### Complete Brand File Example
|
|
|
|
**foscam.json:**
|
|
```json
|
|
{
|
|
"brand": "Foscam",
|
|
"brand_id": "foscam",
|
|
"last_updated": "2025-10-17",
|
|
"source": "ispyconnect.com",
|
|
"website": "https://www.foscam.com",
|
|
"entries": [
|
|
{
|
|
"models": ["FI9821P", "FI9826P", "FI9821W"],
|
|
"type": "FFMPEG",
|
|
"protocol": "rtsp",
|
|
"port": 554,
|
|
"url": "videoMain",
|
|
"notes": "Main stream HD"
|
|
},
|
|
{
|
|
"models": ["FI9821P", "FI9826P"],
|
|
"type": "FFMPEG",
|
|
"protocol": "rtsp",
|
|
"port": 554,
|
|
"url": "videoSub",
|
|
"notes": "Sub stream SD"
|
|
},
|
|
{
|
|
"models": ["FI9821P", "FI9826P", "FI9821W", "C1"],
|
|
"type": "MJPEG",
|
|
"protocol": "http",
|
|
"port": 88,
|
|
"url": "cgi-bin/CGIStream.cgi?cmd=GetMJStream&usr={username}&pwd={password}",
|
|
"notes": "MJPEG fallback"
|
|
},
|
|
{
|
|
"models": ["FI9821P", "C1", "C2"],
|
|
"type": "JPEG",
|
|
"protocol": "http",
|
|
"port": 88,
|
|
"url": "cgi-bin/CGIProxy.fcgi?cmd=snapPicture2&usr={username}&pwd={password}",
|
|
"notes": "Snapshot"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🛠️ Tools and Scripts
|
|
|
|
### Parser Script (Python)
|
|
```python
|
|
# scripts/parse_ispyconnect.py
|
|
import requests
|
|
from bs4 import BeautifulSoup
|
|
import json
|
|
|
|
def parse_brand_page(brand_id):
|
|
url = f"https://www.ispyconnect.com/camera/{brand_id}"
|
|
response = requests.get(url)
|
|
soup = BeautifulSoup(response.text, 'html.parser')
|
|
|
|
table = soup.find('table', class_='table-striped')
|
|
entries = []
|
|
|
|
for row in table.find_all('tr')[1:]: # Skip header
|
|
cols = row.find_all('td')
|
|
if len(cols) < 4:
|
|
continue
|
|
|
|
models_text = cols[0].get_text()
|
|
models = [m.strip() for m in models_text.split(',')]
|
|
|
|
entry = {
|
|
"models": models,
|
|
"type": cols[1].get_text(strip=True),
|
|
"protocol": cols[2].get_text(strip=True).replace('://', ''),
|
|
"port": int(row.get('data-port', 0)),
|
|
"url": cols[3].get_text(strip=True)
|
|
}
|
|
|
|
entries.append(entry)
|
|
|
|
return {
|
|
"brand": brand_id.title(),
|
|
"brand_id": brand_id,
|
|
"last_updated": "2025-10-17",
|
|
"source": "ispyconnect.com",
|
|
"entries": entries
|
|
}
|
|
```
|
|
|
|
### Validator Script
|
|
```python
|
|
# scripts/validate_database.py
|
|
import json
|
|
import os
|
|
|
|
def validate_brand_file(filepath):
|
|
with open(filepath) as f:
|
|
data = json.load(f)
|
|
|
|
# Check required fields
|
|
assert "brand" in data
|
|
assert "brand_id" in data
|
|
assert "entries" in data
|
|
|
|
# Validate each entry
|
|
for i, entry in enumerate(data["entries"]):
|
|
assert "models" in entry, f"Entry {i} missing models"
|
|
assert "type" in entry, f"Entry {i} missing type"
|
|
assert "protocol" in entry, f"Entry {i} missing protocol"
|
|
assert "port" in entry, f"Entry {i} missing port"
|
|
assert "url" in entry, f"Entry {i} missing url"
|
|
|
|
print(f"✅ {filepath} is valid")
|
|
|
|
# Run validation
|
|
for file in os.listdir('data/brands/'):
|
|
if file.endswith('.json') and file != 'index.json':
|
|
validate_brand_file(f'data/brands/{file}')
|
|
```
|
|
|
|
---
|
|
|
|
## 📄 License and Attribution
|
|
|
|
- **Source**: ispyconnect.com camera database
|
|
- **Usage**: Free for IoT2mqtt project
|
|
- **Attribution**: Must credit ispyconnect.com as data source
|
|
- **Updates**: Community-contributed updates welcome
|
|
|
|
---
|
|
|
|
## 🤝 Contributing
|
|
|
|
To add or update camera models:
|
|
|
|
1. Follow the JSON format specification
|
|
2. Validate using `scripts/validate_database.py`
|
|
3. Test URLs with real cameras when possible
|
|
4. Submit pull request with changes
|
|
|
|
---
|
|
|
|
## 📞 Support
|
|
|
|
For questions about the database format:
|
|
- GitHub Issues: https://github.com/eduard256/Strix/issues
|
|
- Documentation: https://github.com/eduard256/Strix#readme
|
|
|
|
---
|
|
|
|
**End of Specification**
|