Skip to content

Commit

Permalink
Some refinements
Browse files Browse the repository at this point in the history
  • Loading branch information
tropicoo committed Sep 25, 2022
1 parent fe4732d commit 6b873ac
Show file tree
Hide file tree
Showing 31 changed files with 170 additions and 156 deletions.
100 changes: 50 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ Telegram Bot which sends snapshots from your Hikvision cameras.
Version: 1.5. [Release details](releases/release_1.5.md).

## Features
1. Send full/resized pictures on request
2. Auto-send pictures on **Motion**, **Line Crossing** and **Intrusion (Field) Detection**
3. Send so-called Telegram video-gifs on request and alert events from previous paragraph
4. YouTube, Telegram and Icecast direct or re-encoded livestreams
5. DVR to local storage with upload to Telegram group
6. SRS re-stream server
1. Send full/resized pictures on request.
2. Auto-send pictures on **Motion**, **Line Crossing** and **Intrusion (Field) Detection**.
3. Send so-called Telegram video-gifs on request and alert events from the previous paragraph.
4. YouTube, Telegram, and Icecast direct or re-encoded livestreams.
5. DVR to local storage with upload to Telegram group.
6. SRS re-stream server.


![frames](img/screenshot-1.png)
Expand All @@ -23,13 +23,13 @@ cd hikvision-camera-bot
```

# Configuration
Configuration files are stored in JSON format and can be found in `configs` directory.
Configuration files are stored in JSON format and can be found in the `configs` directory.

## Quick Setup
1. [Create and start Telegram Bot](https://core.telegram.org/bots#6-botfather)
and get its API token
2. [Get your own Telegram API key](https://my.telegram.org/apps) (`api_id` and `api_hash`)
3. Copy 3 default configuration files with predefined templates in `configs` directory:
2. [Get your Telegram API key](https://my.telegram.org/apps) (`api_id` and `api_hash`)
3. Copy 3 default configuration files with predefined templates in the `configs` directory:

```bash
cd configs
Expand All @@ -38,21 +38,21 @@ Configuration files are stored in JSON format and can be found in `configs` dire
cp livestream_templates-template.json livestream_templates.json
```
4. Edit **config.json**:
1. Put the obtained `api_id` and `api_hash` strings to same keys
2. Put the obtained bot API token string to `token` key
1. Put the obtained `api_id` and `api_hash` strings to the same keys
2. Put the obtained bot API token string to the `token` key
3. [Find](https://stackoverflow.com/a/32777943) your Telegram user id
and put it to `chat_users`, `alert_users` and `startup_message_users` lists as
integer value. Multiple ids can be used, just separate them with a comma.
4. Hikvision camera settings are placed inside the `camera_list` section. Template
4. Hikvision camera settings are placed inside the `camera_list` section. The template
comes with two cameras

**Camera names should start with `cam_` prefix and end with
**Camera names should start with the `cam_` prefix and end with
digit suffix**: `cam_1`, `cam_2`, `cam_<digit>` with any description.

5. Write authentication credentials in `user` and `password` keys for every camera
6. Same for `host`, which should include protocol, e.g. `http://192.168.1.1`
7. In `alert` section you can enable sending picture on alert (Motion,
Line Crossing and Intrusion (Field) Detection). Configure `delay` setting
6. Same for `host`, which should include protocol e.g., `http://192.168.1.1`
7. In the `alert` section you can enable sending pictures on alert (Motion,
Line Crossing and Intrusion (Field) Detection). Configure the `delay` setting
in seconds between pushing alert pictures. To send resized picture change
`fullpic` to `false`

Expand Down Expand Up @@ -188,10 +188,10 @@ Configuration files are stored in JSON format and can be found in `configs` dire

# Usage
## Launch by using Docker and Docker Compose
1. Set your timezone by editing `.env` file (`TZ=Europe/Kiev`).
Currently, there is Ukrainian timezone because I live there.
1. Set your timezone by editing the `.env` file (`TZ=Europe/Kiev`).
Currently, there is a Ukrainian timezone because I live there.
Look for your timezone here [http://www.timezoneconverter.com/cgi-bin/zoneinfo](http://www.timezoneconverter.com/cgi-bin/zoneinfo).
If you want to use default UTC time format, set Greenwich Mean Time timezone `TZ=GMT`
If you want to use the default UTC time format, set Greenwich Mean Time timezone `TZ=GMT`

2. Build an image and run a container in a detached mode
```bash
Expand All @@ -201,12 +201,12 @@ If you want to use default UTC time format, set Greenwich Mean Time timezone `TZ
# Commands
| Command | Description |
|---|---|
| `/start` | Start the bot (one-time action during first start) and show help |
| `/start` | Start the bot (one-time action during the first start) and show help |
| `/help` | Show help message |
| `/list_cams` | List all your cameras |
| `/cmds_cam_*` | List commands for particular camera |
| `/getpic_cam_*` | Get resized picture from your Hikvision camera |
| `/getfullpic_cam_*` | Get full-sized picture from your Hikvision camera |
| `/getfullpic_cam_*` | Get a full-sized picture from your Hikvision camera |
| `/ir_on_cam_*` | Turn on Infrared mode |
| `/ir_off_cam_*` | Turn off Infrared mode |
| `/ir_auto_cam_*` | Turn on Infrared auto mode |
Expand All @@ -216,61 +216,61 @@ If you want to use default UTC time format, set Greenwich Mean Time timezone `TZ
| `/ld_off_cam_*` | Disable Line Crossing Detection |
| `/intr_on_cam_*` | Enable Intrusion (Field) Detection |
| `/intr_off_cam_*` | Disable Intrusion (Field) Detection |
| `/alert_on_cam_*` | Enable Alert (Alarm) mode. It means it will send respective alert to your account in Telegram |
| `/alert_off_cam_*` | Disable Alert (Alarm) mode, no alerts will be sent when something detected |
| `/alert_on_cam_*` | Enable Alert (Alarm) mode. It means it will send a respective alert to your account in Telegram |
| `/alert_off_cam_*` | Disable Alert (Alarm) mode, no alerts will be sent when something is detected |
| `/yt_on_cam_*` | Enable YouTube stream |
| `/yt_off_cam_*` | Disable YouTube stream |
| `/icecast_on_cam_*` | Enable Icecast stream |
| `/icecast_off_cam_*` | Disable Icecast stream |

`*` - camera digit id, e.g. `cam_1`.
`*` - camera digit id e.g., `cam_1`.

# Advanced Configuration
## SRS
[SRS](https://github.com/ossrs/srs/tree/4.0release) (Simple Realtime Server) is a re-stream server which takes a stream from your camera and re-streams it
to any destination without touching native camera stream multiple times.
SRS release version used in the bot is `4.0`.
[SRS](https://github.com/ossrs/srs/tree/4.0release) (Simple Realtime Server) is a re-stream server that takes a stream from your camera and re-streams it
to any destination without touching the native camera stream multiple times.
The SRS release version used in the bot is `4.0`.

SRS decreases CPU time and network load on the camera when you enable something like DVR,
YouTube Livestream or try to get Video GIF at the same time. Pictures are taken
directly from the camera stream, not from the SRS.

How it works - if you have two cameras with enabled SRS for both, there will be two
running 24/7 bot tasks taking streams from the cameras to the SRS server. Eventually, when you
request Video Gif, or it's triggered by some alert, video will be taken from SRS server.
request Video Gif, or it's triggered by some alert, the video will be taken from the SRS server.

You can also connect to SRS server with any video player like VLC and watch the stream
You can also connect to the SRS server with any video player like VLC and watch the stream
without any interruptions. URL looks like this: `rtmp://192.168.1.100/live/livestream_101_cam_2`,
where:
1. `192.168.1.100` is an IP address or a host of your server.
2. `101` is camera's configured stream channel.
3. `cam_2` is ID of your second configured camera.
2. `101` is the camera's configured stream channel.
3. `cam_2` is the ID of your second configured camera.

SRS runs in a separate docker container. SRS config and `Dockerfile` are placed
in `srs_prod` directory. Service name is `hikvision-srs-server` in `docker-compose.yml`.
in the `srs_prod` directory. The service name is `hikvision-srs-server` in `docker-compose.yml`.



If `docker-compose.yml` is a list of forwarded and open SRS ports to the world:
```yaml
# If you don't plan to use anything from this, just comment out the whole section.
ports:
- "1935:1935" # SRS RTMP port, if you comment this out, you won't be able to connect with video player
- "1935:1935" # SRS RTMP port, if you comment this out, you won't be able to connect with the video player
- "1985:1985" # SRS API port, can be commented out since not used
- "8080:8080" # SRS WebUI port
```

## DVR
You can record your videos from the camera to a local storage mounted as volume in
You can record your videos from the camera to local storage mounted as a volume in
volumes section of `hikvision-camera-bot` service in `docker-compose.yml`.

DVR configuration is per camera in `config.json` with livestream template name from `livestream_templates.json`.

It's very simple:
1. Use `enabled` key to turn on/off this feature.
1. Use the `enabled` key to turn on/off this feature.
2. `local_storage_path` is a path inside the container to which videos will be recorded.
Don't change this default value (`/data/dvr`) since it's written in the volumes mapping section.
If you need to change it for some reason - you need to change both here and in the volumes mapping.
If you need to change it for some reason - you must change it both here and in the volumes mapping.
3. `livestream_template` has a template name located inside the `livestream_templates.json`
file with DVR stream settings:
```json
Expand All @@ -284,11 +284,11 @@ file with DVR stream settings:
}
}
```
a) `segment_time` is time in seconds when DVR record file will be split to a new one.
a) `segment_time` is the time in seconds when the DVR record file will be split into a new one.

b) `1800` seconds mean every file will have 30 minutes of video recording.

c) File is named as `cam_1_101_1800_2022-04-15_21-19-32.mp4` with cam ID, channel name, segment time, and record start datetime.
c) File is named `cam_1_101_1800_2022-04-15_21-19-32.mp4` with cam ID, channel name, segment time, and record start datetime.

4. Configuration part from the `config.json`:
```json
Expand All @@ -308,12 +308,12 @@ file with DVR stream settings:
}
}
```
Recorded files can be uploaded to Telegram group. Right now upload will work only
if `delete_after_upload` is set to `true` meaning uploaded file will be deleted
Recorded files can be uploaded to the Telegram group. Right now, the upload will work only
if `delete_after_upload` is set to `true` meaning the uploaded file will be deleted
from the local storage. You need to make sure your file size will be up to 2GB since
Telegram rejects larger ones. Just experiment with segment time.
5. Local storage (the real one, not in the container) by default is `/data/dvr` in volumes mapping (the first path string, not the last).
Change it to any location you need, e.g. to `- "D:\Videos:/data/dvr"` if you're on Windows.
Change it to any location you need e.g., `- "D:\Videos:/data/dvr"` if you're on Windows.
```yaml
volumes:
- "/data/dvr:/data/dvr"
Expand All @@ -332,22 +332,22 @@ To enable YouTube Live Stream enable it in the `youtube` key.

**Livestream templates**

To start particular livestream, user needs to set both *livestream* and
To start a particular livestream, a user needs to set both *livestream* and
*encoding* templates with stream settings and encoding type/arguments.

Encoding templates

`direct` means that video stream will not be re-encoded (transcoded) and will
`direct` means that the video stream will not be re-encoded (transcoded) and will
be sent to YouTube/Icecast servers "as is", only audio can be disabled.

`x264` or `vp9` means that video stream will be re-encoded on your machine/server
where bot is running using respective encoding codecs.
`x264` or `vp9` means that the video stream will be re-encoded on your machine/server
where the bot is running using respective encoding codecs.

User can create its own templates in file named `livestream_templates.json`
User can create their templates in a file named `livestream_templates.json`
and `encoding_templates.json`.

Default dummy template file is named `livestream_templates_template.json`
(not very funny name but anyway) which should be copied or renamed to
The default dummy template file is named `livestream_templates_template.json`
(not a very funny name but anyway) which should be copied or renamed to
`livestream_templates.json`.

Same for `encoding_templates-template.json` -> `encoding_templates.json`
Expand Down Expand Up @@ -411,7 +411,7 @@ Where:

| Parameter | Value | Description |
|---|---|---|
| `channel` | `101` | camera channel. 101 is main stream, 102 is substream. |
| `channel` | `101` | camera channel. 101 is the main stream, and 102 is the substream. |
| `restart_period` | `39600` | stream restart period in seconds |
| `restart_pause` | `10` | stream pause before starting on restart |
| `url` | `"rtmp://a.rtmp.youtube.com/live2"` | YouTube rtmp server |
Expand All @@ -420,7 +420,7 @@ Where:
| `ice_name` | `"Default"` | Icecast stream name |
| `ice_description` | `"Default"` | Icecast stream description |
| `ice_public` | `0` | Icecast public switch, default 0 |
| `url` | `"icecast://source@x.x.x.x:8000/video.webm"` | Icecast server URL, Port and media mount point |
| `url` | `"icecast://source@x.x.x.x:8000/video.webm"` | Icecast server URL, Port, and media mount point |
| `password` | `"xxxx"` | Icecast authentication password |
| `content_type` | `"video/webm"` | FFMPEG content-type for Icecast stream |

Expand Down
2 changes: 1 addition & 1 deletion bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import asyncio

from hikcamerabot.launcher import BotLauncher
from hikcamerabot.utils.utils import setup_logging
from hikcamerabot.utils.shared import setup_logging


async def main() -> None:
Expand Down
2 changes: 1 addition & 1 deletion hikcamerabot/bot_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from hikcamerabot.camerabot import CameraBot
from hikcamerabot.commands import setup_commands
from hikcamerabot.config.config import get_main_config
from hikcamerabot.utils.utils import build_command_presentation
from hikcamerabot.utils.shared import build_command_presentation


class BotSetup:
Expand Down
4 changes: 2 additions & 2 deletions hikcamerabot/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from hikcamerabot.camera import HikvisionCam
from hikcamerabot.camerabot import CameraBot
from hikcamerabot.clients.github_version.version_checker import (
from hikcamerabot.clients.github.version_checker import (
HikCameraBotVersionChecker,
)
from hikcamerabot.clients.hikvision.enums import IrcutFilterType
Expand All @@ -19,7 +19,7 @@
IrcutConfEvent,
StreamEvent,
)
from hikcamerabot.utils.utils import bold, send_text
from hikcamerabot.utils.shared import bold, send_text

log = logging.getLogger(__name__)

Expand Down
4 changes: 2 additions & 2 deletions hikcamerabot/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def __init__(self, id: str, conf: Dict, bot: 'CameraBot') -> None:
self.hashtag = f'#{conf.hashtag.lower() if conf.hashtag else self.id}'
self.group = conf.group or 'Default group'
self.bot = bot
self._log.debug('Initializing %s', self.description)
self._log.debug('Initializing camera %s', self.description)
self._api = HikvisionAPI(api_client=HikvisionAPIClient(conf=conf.api))
self._img_processor = ImageProcessor()

Expand All @@ -119,7 +119,7 @@ def __init__(self, id: str, conf: Dict, bot: 'CameraBot') -> None:
self._videogif = VideoGifRecorder(cam=self)

def __repr__(self) -> str:
return f'<HikvisionCam desc="{self.description}">'
return f'<HikvisionCam id="{self.id}" desc="{self.description}">'

async def start_videogif_record(
self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

@dataclass
class BotVersion:
"""Bot version DTO class."""

current: str
latest: str

Expand All @@ -27,11 +29,12 @@ def get_current_version(self) -> str:
return __version__

async def get_latest_version(self) -> str:
"""Get latest version number from latest GitHub tag URL."""
self._log.info('Get latest hikvision-camera-bot version number')
client: AsyncClient
async with AsyncClient() as client:
response = await client.head(self.LATEST_TAG_URL)
version = response.headers.get('location').split('/')[-1]
version: str = response.headers.get('location').split('/')[-1]
self._log.info('Latest hikvision-camera-bot version number: %s', version)
return version

Expand Down
23 changes: 3 additions & 20 deletions hikcamerabot/clients/hikvision/api_client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""Hikvision camera API client module."""
import abc
import logging
from typing import Any
from urllib.parse import urljoin
Expand All @@ -13,29 +12,13 @@
from hikcamerabot.exceptions import APIBadResponseCodeError, APIRequestError


class AbstractHikvisionAPIClient(metaclass=abc.ABCMeta):
class HikvisionAPIClient:
"""Hikvision API Class."""

def __init__(self, conf: Dict) -> None:
self._log = logging.getLogger(self.__class__.__name__)
self._conf = conf
self.host: str = self._conf.host

@abc.abstractmethod
async def request(
self,
endpoint: str,
data: Any = None,
headers: dict = None,
method: str = 'GET',
timeout: float = CONN_TIMEOUT,
) -> Any:
pass


class HikvisionAPIClient(AbstractHikvisionAPIClient):
"""Hikvision API Class."""

def __init__(self, conf: Dict) -> None:
super().__init__(conf)
self.session = httpx.AsyncClient(
auth=DigestAuthCached(
username=self._conf.auth.user,
Expand Down
14 changes: 7 additions & 7 deletions hikcamerabot/clients/hikvision/api_wrapper.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging

from hikcamerabot.clients.hikvision.api_client import AbstractHikvisionAPIClient
from hikcamerabot.clients.hikvision import HikvisionAPIClient
from hikcamerabot.clients.hikvision.endpoints.endpoints import (
AlertStreamEndpoint,
ExposureEndpoint,
Expand All @@ -13,12 +13,12 @@
class HikvisionAPI:
"""Hikvision API Wrapper. API methods are Endpoint instances."""

def __init__(self, api_client: AbstractHikvisionAPIClient) -> None:
def __init__(self, api_client: HikvisionAPIClient) -> None:
self._log = logging.getLogger(self.__class__.__name__)
self._api_client = api_client

self.alert_stream = AlertStreamEndpoint(self._api_client)
self.take_snapshot = TakeSnapshotEndpoint(self._api_client)
self.set_ircut_filter = IrcutFilterEndpoint(self._api_client)
self.set_exposure = ExposureEndpoint(self._api_client)
self.switch = SwitchEndpoint(self._api_client)
self.alert_stream = AlertStreamEndpoint(api_client)
self.take_snapshot = TakeSnapshotEndpoint(api_client)
self.set_ircut_filter = IrcutFilterEndpoint(api_client)
self.set_exposure = ExposureEndpoint(api_client)
self.switch = SwitchEndpoint(api_client)
Loading

0 comments on commit 6b873ac

Please sign in to comment.