diff --git a/README.md b/README.md index 0530f9a..5961bc1 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,15 @@ Options: of `rclone` (https://rclone.org/filtering/#max-age-don- t-transfer-any-file-older-than-this) + --rclone-args TEXT Optional arguments which are directly passed + to `rclone rcat`. These can by used to set + parameters such as the bandwidth limit used + when pushing the files to the rclone + destination, e.g., '--bwlimit=500k'. Please + see the `rclone` documentation for the full + set of arguments it supports + (https://rclone.org/docs/). Please use + responsibly. --ignore-camera TEXT IDs of cameras for which events should not be backed up. Use multiple times to ignore multiple IDs. If being set as an environment @@ -106,6 +115,7 @@ always take priority over environment variables): - `UFP_SSL_VERIFY` - `RCLONE_RETENTION` - `RCLONE_DESTINATION` +- `RCLONE_ARGS` - `IGNORE_CAMERAS` ## Docker Container diff --git a/unifi_protect_backup/cli.py b/unifi_protect_backup/cli.py index ba82561..a61d726 100644 --- a/unifi_protect_backup/cli.py +++ b/unifi_protect_backup/cli.py @@ -32,6 +32,13 @@ from unifi_protect_backup import UnifiProtectBackup help="How long should event clips be backed up for. Format as per the `--max-age` argument of " "`rclone` (https://rclone.org/filtering/#max-age-don-t-transfer-any-file-older-than-this)", ) +@click.option( + '--rclone-args', + default='', + envvar='RCLONE_ARGS', + help="Optional extra arguments to pass to `rclone rcat` directly. Common usage for this would " + "be to set a bandwidth limit, for example.", +) @click.option( '--ignore-camera', 'ignore_cameras', diff --git a/unifi_protect_backup/unifi_protect_backup.py b/unifi_protect_backup/unifi_protect_backup.py index 5018774..a570d39 100644 --- a/unifi_protect_backup/unifi_protect_backup.py +++ b/unifi_protect_backup/unifi_protect_backup.py @@ -168,6 +168,7 @@ class UnifiProtectBackup: retention (str): How long should event clips be backed up for. Format as per the `--max-age` argument of `rclone` (https://rclone.org/filtering/#max-age-don-t-transfer-any-file-older-than-this) + rclone_args (str): Extra args passed directly to `rclone rcat`. ignore_cameras (List[str]): List of camera IDs for which to not backup events verbose (int): How verbose to setup logging, see :func:`setup_logging` for details. _download_queue (asyncio.Queue): Queue of events that need to be backed up @@ -182,6 +183,7 @@ class UnifiProtectBackup: verify_ssl: bool, rclone_destination: str, retention: str, + rclone_args: str, ignore_cameras: List[str], verbose: int, port: int = 443, @@ -200,6 +202,8 @@ class UnifiProtectBackup: retention (str): How long should event clips be backed up for. Format as per the `--max-age` argument of `rclone` (https://rclone.org/filtering/#max-age-don-t-transfer-any-file-older-than-this) + rclone_args (str): A bandwidth limit which is passed to the `--bwlimit` argument of + `rclone` (https://rclone.org/docs/#bwlimit-bandwidth-spec) ignore_cameras (List[str]): List of camera IDs for which to not backup events verbose (int): How verbose to setup logging, see :func:`setup_logging` for details. """ @@ -217,11 +221,13 @@ class UnifiProtectBackup: logger.debug(f" {verify_ssl=}") logger.debug(f" {rclone_destination=}") logger.debug(f" {retention=}") + logger.debug(f" {rclone_args=}") logger.debug(f" {ignore_cameras=}") logger.debug(f" {verbose=}") self.rclone_destination = rclone_destination self.retention = retention + self.rclone_args = rclone_args self._protect = ProtectApiClient( address, @@ -386,7 +392,7 @@ class UnifiProtectBackup: logger.debug(f" Size: {human_readable_size(len(video))}") for x in range(5): try: - await self._upload_video(video, destination) + await self._upload_video(video, destination, self.rclone_args) break except RcloneException as e: logger.warn(f" Failed upload attempt {x+1}, retying in 1s") @@ -402,7 +408,7 @@ class UnifiProtectBackup: logger.warn(f"Unexpected exception occurred, abandoning event {event.id}:") logger.exception(e) - async def _upload_video(self, video: bytes, destination: pathlib.Path): + async def _upload_video(self, video: bytes, destination: pathlib.Path, rclone_args: str): """Upload video using rclone. In order to avoid writing to disk, the video file data is piped directly @@ -411,11 +417,12 @@ class UnifiProtectBackup: Args: 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` Raises: RuntimeError: If rclone returns a non-zero exit code """ - cmd = f"rclone rcat -vv '{destination}'" + cmd = f"rclone rcat -vv {rclone_args} '{destination}'" proc = await asyncio.create_subprocess_shell( cmd, stdin=asyncio.subprocess.PIPE,