mirror of
https://github.com/ep1cman/unifi-protect-backup.git
synced 2025-12-05 23:53:30 +00:00
Compare commits
2 Commits
8b1805b792
...
4bde6487b3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4bde6487b3 | ||
|
|
1c9e2ef147 |
37
README.md
37
README.md
@@ -131,6 +131,11 @@ Options:
|
||||
Common usage for this would be to execute a permanent delete
|
||||
instead of using the recycle bin on a destination. Google Drive
|
||||
example: `--drive-use-trash=false`
|
||||
--postprocess-binary TEXT Optional binary or executable script to run after having
|
||||
downloaded a video. This can e.g. be a bash script with a CURL
|
||||
command to post-process the video (detection, move, ...). The
|
||||
script / binary receives the path where the video is persisted
|
||||
as first and only argument.
|
||||
--detection-types TEXT A comma separated list of which types of detections to backup.
|
||||
Valid options are: `motion`, `person`, `vehicle`, `ring`
|
||||
[default: motion,person,vehicle,ring]
|
||||
@@ -217,6 +222,7 @@ always take priority over environment variables):
|
||||
- `RCLONE_DESTINATION`
|
||||
- `RCLONE_ARGS`
|
||||
- `RCLONE_PURGE_ARGS`
|
||||
- `POSTPROCESS_BINARY`
|
||||
- `IGNORE_CAMERAS`
|
||||
- `CAMERAS`
|
||||
- `DETECTION_TYPES`
|
||||
@@ -303,6 +309,37 @@ such backends.
|
||||
|
||||
If you are running on a linux host you can setup `rclone` to use `tmpfs` (which is in RAM) to store its temp files, but this will significantly increase memory usage of the tool.
|
||||
|
||||
## Prostprocessing
|
||||
|
||||
To perform additional detection / cleaning / moving / ... on a video post downloading:
|
||||
- Use `--postprocess-binary` or env. var: `POSTPROCESS_BINARY`
|
||||
|
||||
The binary / executable script receives a first argument with the storage location for the downloaded video. You can easily mount a script from a local filesystem to the container:
|
||||
|
||||
```bash
|
||||
rm -r /tmp/unifi ; docker rmi ghcr.io/ep1cman/unifi-protect-backup ; poetry build && docker buildx build . -t ghcr.io/ep1cman/unifi-protect-backup ;
|
||||
docker run --rm \
|
||||
-e POSTPROCESS_BINARY='/postprocess.sh' \
|
||||
-v '/My/Local/Folder/postprocess.sh':/postprocess.sh \
|
||||
ghcr.io/ep1cman/unifi-protect-backup
|
||||
```
|
||||
|
||||
The script can be as simple as this (to display the upload path inside the container):
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
echo "$1"
|
||||
```
|
||||
|
||||
The logging output will show the stdout and stderr for the postprocess script/binary:
|
||||
|
||||
```
|
||||
Uploaded
|
||||
-- Postprocessing: 'local:/data/camname/date/vidname.pm4' returned status code: '0'
|
||||
> STDOUT: /data/camname/date/vidname.pm4
|
||||
> STDERR:
|
||||
```
|
||||
|
||||
### Running Docker Container (LINUX ONLY)
|
||||
Add the following arguments to your docker run command:
|
||||
```
|
||||
|
||||
@@ -90,6 +90,12 @@ def parse_rclone_retention(ctx, param, retention) -> relativedelta:
|
||||
"be to execute a permanent delete instead of using the recycle bin on a destination. "
|
||||
"Google Drive example: `--drive-use-trash=false`",
|
||||
)
|
||||
@click.option(
|
||||
"--postprocess-binary",
|
||||
default="",
|
||||
envvar="POSTPROCESS_BINARY",
|
||||
help="Optional path to binary to postprocess the processed video, gets video destination path as argument."
|
||||
)
|
||||
@click.option(
|
||||
"--detection-types",
|
||||
envvar="DETECTION_TYPES",
|
||||
|
||||
@@ -62,6 +62,7 @@ class UnifiProtectBackup:
|
||||
retention: relativedelta,
|
||||
rclone_args: str,
|
||||
rclone_purge_args: str,
|
||||
postprocess_binary: str,
|
||||
detection_types: List[str],
|
||||
ignore_cameras: List[str],
|
||||
cameras: List[str],
|
||||
@@ -95,6 +96,7 @@ class UnifiProtectBackup:
|
||||
rclone_args (str): A bandwidth limit which is passed to the `--bwlimit` argument of
|
||||
`rclone` (https://rclone.org/docs/#bwlimit-bandwidth-spec)
|
||||
rclone_purge_args (str): Optional extra arguments to pass to `rclone delete` directly.
|
||||
postprocess_binary (str): Optional path to a binary that gets called to postprocess, with download location as argument.
|
||||
detection_types (List[str]): List of which detection types to backup.
|
||||
ignore_cameras (List[str]): List of camera IDs for which to not backup events.
|
||||
cameras (List[str]): List of ONLY camera IDs for which to backup events.
|
||||
@@ -134,6 +136,7 @@ class UnifiProtectBackup:
|
||||
logger.debug(f" {retention=}")
|
||||
logger.debug(f" {rclone_args=}")
|
||||
logger.debug(f" {rclone_purge_args=}")
|
||||
logger.debug(f" {postprocess_binary=}")
|
||||
logger.debug(f" {ignore_cameras=}")
|
||||
logger.debug(f" {cameras=}")
|
||||
logger.debug(f" {verbose=}")
|
||||
@@ -152,6 +155,7 @@ class UnifiProtectBackup:
|
||||
self.retention = retention
|
||||
self.rclone_args = rclone_args
|
||||
self.rclone_purge_args = rclone_purge_args
|
||||
self.postprocess_binary = postprocess_binary
|
||||
self.file_structure_format = file_structure_format
|
||||
|
||||
self.address = address
|
||||
@@ -274,6 +278,7 @@ class UnifiProtectBackup:
|
||||
self.file_structure_format,
|
||||
self._db,
|
||||
self.color_logging,
|
||||
self.postprocess_binary,
|
||||
)
|
||||
tasks.append(uploader.start())
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ class VideoUploader:
|
||||
file_structure_format: str,
|
||||
db: aiosqlite.Connection,
|
||||
color_logging: bool,
|
||||
postprocess_binary: str,
|
||||
):
|
||||
"""Init.
|
||||
|
||||
@@ -45,11 +46,13 @@ class VideoUploader:
|
||||
file_structure_format (str): format string for how to structure the uploaded files
|
||||
db (aiosqlite.Connection): Async SQlite database connection
|
||||
color_logging (bool): Whether or not to add color to logging output
|
||||
postprocess_binary (str): Optional postprocess binary path (output location as arg)
|
||||
"""
|
||||
self._protect: ProtectApiClient = protect
|
||||
self.upload_queue: VideoQueue = upload_queue
|
||||
self._rclone_destination: str = rclone_destination
|
||||
self._rclone_args: str = rclone_args
|
||||
self._postprocess_binary: str = postprocess_binary
|
||||
self._file_structure_format: str = file_structure_format
|
||||
self._db: aiosqlite.Connection = db
|
||||
self.current_event = None
|
||||
@@ -82,9 +85,16 @@ class VideoUploader:
|
||||
self.logger.debug(f" Destination: {destination}")
|
||||
|
||||
try:
|
||||
await self._upload_video(video, destination, self._rclone_args)
|
||||
await self._upload_video(video, destination, self._rclone_args, self._postprocess_binary)
|
||||
await self._update_database(event, destination)
|
||||
self.logger.debug("Uploaded")
|
||||
|
||||
# Postprocess
|
||||
if self._postprocess_binary:
|
||||
returncode_postprocess, stdout_postprocess, stderr_postprocess = await run_command(f'"{self._postprocess_binary}" "{destination}"')
|
||||
self.logger.debug(f" -- Postprocessing: '{destination}' returned status code: '{returncode_postprocess}'")
|
||||
self.logger.debug(f" > STDOUT: {stdout_postprocess.strip()}")
|
||||
self.logger.debug(f" > STDERR: {stderr_postprocess.strip()}")
|
||||
except SubprocessException:
|
||||
self.logger.error(f" Failed to upload file: '{destination}'")
|
||||
|
||||
@@ -93,7 +103,7 @@ class VideoUploader:
|
||||
except Exception as e:
|
||||
self.logger.error(f"Unexpected exception occurred, abandoning event {event.id}:", exc_info=e)
|
||||
|
||||
async def _upload_video(self, video: bytes, destination: pathlib.Path, rclone_args: str):
|
||||
async def _upload_video(self, video: bytes, destination: pathlib.Path, rclone_args: str, postprocess_binary: str):
|
||||
"""Upload video using rclone.
|
||||
|
||||
In order to avoid writing to disk, the video file data is piped directly
|
||||
@@ -103,6 +113,7 @@ class VideoUploader:
|
||||
video (bytes): The data to be written to the file
|
||||
destination (pathlib.Path): Where rclone should write the file
|
||||
rclone_args (str): Optional extra arguments to pass to `rclone`
|
||||
postprocess_binary (str): Optional extra path to postprocessing binary
|
||||
|
||||
Raises:
|
||||
RuntimeError: If rclone returns a non-zero exit code
|
||||
|
||||
Reference in New Issue
Block a user