Compare commits

...

16 Commits

Author SHA1 Message Date
Sebastian Goscik
34bc37bd0b Bump version: 0.8.1 → 0.8.2 2022-12-05 14:27:11 +00:00
Sebastian Goscik
f15cdf9a9b updated changelog 2022-12-05 14:27:06 +00:00
Sebastian Goscik
63d368f14c Added note to readme about 0.8 docker changes 2022-12-05 14:24:27 +00:00
Sebastian Goscik
ee01edf55c Make sure config directories exist in the container 2022-12-05 14:04:43 +00:00
Sebastian Goscik
4e10e0f10e Use run_command in downloader and uploader 2022-12-05 14:03:59 +00:00
Sebastian Goscik
385f115eab Add ability for run_command to pass data to stdin 2022-12-05 14:03:23 +00:00
Sebastian Goscik
b4062d3b53 Fix issue where indented stdout/stderr was being returned
The indentation was supposed to be only for the logging to make it easier to read but was also being returned, thus breaking parsing of the command output

Fixes #60
2022-12-05 13:40:32 +00:00
Sebastian Goscik
7bfcb548e2 Bump version: 0.8.0 → 0.8.1 2022-12-04 12:04:15 +00:00
Sebastian Goscik
a74e4b042d changelog 2022-12-04 12:03:57 +00:00
Sebastian Goscik
2c5308aa20 updated name in pyproject.toml 2022-12-04 12:03:54 +00:00
Sebastian Goscik
9d375d4e7b update bumpversion cfg to use new tar.gz name 2022-12-04 11:59:36 +00:00
Sebastian Goscik
df4390688b Update docs and dockerfile to save events database 2022-12-03 22:40:40 +00:00
Sebastian Goscik
3acfd1f543 Fix dockerfile - to _
I have no idea how this worked before but not now
2022-12-03 22:04:50 +00:00
Sebastian Goscik
49c11c1872 Make ci show all temp files 2022-12-03 22:00:22 +00:00
Sebastian Goscik
93cf297371 Bump version: 0.7.4 → 0.8.0 2022-12-03 21:54:45 +00:00
Sebastian Goscik
8baa413a23 Merge pull request #57 from ep1cman/restructure
Major Restructure
2022-12-03 21:51:20 +00:00
10 changed files with 54 additions and 47 deletions

View File

@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.7.4
current_version = 0.8.2
commit = True
tag = True
@@ -12,5 +12,5 @@ search = __version__ = '{current_version}'
replace = __version__ = '{new_version}'
[bumpversion:file:Dockerfile]
search = COPY dist/unifi-protect-backup-{current_version}.tar.gz sdist.tar.gz
replace = COPY dist/unifi-protect-backup-{new_version}.tar.gz sdist.tar.gz
search = COPY dist/unifi_protect_backup-{current_version}.tar.gz sdist.tar.gz
replace = COPY dist/unifi_protect_backup-{new_version}.tar.gz sdist.tar.gz

View File

@@ -52,7 +52,7 @@ jobs:
- name: show temporary files
run: >-
ls -l
ls -lR
- name: Set up QEMU
uses: docker/setup-qemu-action@v1

View File

@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.8.2] - 2022-12-05
### Fixed
- Fixed issue where command output was being returned with added indentation intended for logging only
- Fixed issue where some command logging was not indented
- Fixed issue where the tool could crash when run in a container if /config/database didn't exist
## [0.8.1] - 2022-12-04
version 0.8.0 was used by accident previously and PyPI would not accept it so bumping by one patch version
## [0.8.0] - 2022-12-03
Major internal refactoring. Each task is now its own class and asyncio task.

View File

@@ -8,7 +8,7 @@ LABEL maintainer="ep1cman"
WORKDIR /app
COPY dist/unifi-protect-backup-0.7.4.tar.gz sdist.tar.gz
COPY dist/unifi_protect_backup-0.8.2.tar.gz sdist.tar.gz
RUN \
echo "**** install build packages ****" && \
@@ -45,8 +45,11 @@ ENV RCLONE_DESTINATION=local:/data
ENV VERBOSITY="v"
ENV TZ=UTC
ENV IGNORE_CAMERAS=""
ENV SQLITE_PATH=/config/database/events.sqlite
COPY docker_root/ /
RUN mkdir -p /config/database /config/rclone
VOLUME [ "/config" ]
VOLUME [ "/data" ]

View File

@@ -165,6 +165,9 @@ You can optionally format the `event.start`/`event.end` timestamps as per the [`
You can run this tool as a container if you prefer with the following command.
Remember to change the variable to make your setup.
> **Note**
> As of version 0.8.0, the event database needs to be persisted for the tool to function properly
> please see the updated commands below
### Backing up locally
By default, if no rclone config is provided clips will be backed up to `/data`.
@@ -176,6 +179,7 @@ docker run \
-e UFP_ADDRESS='UNIFI_PROTECT_IP' \
-e UFP_SSL_VERIFY='false' \
-v '/path/to/save/clips':'/data' \
-v '/path/to/save/database':/config/database/ \
ghcr.io/ep1cman/unifi-protect-backup
```
@@ -198,7 +202,8 @@ docker run \
-e UFP_SSL_VERIFY='false' \
-e RCLONE_DESTINATION='my_remote:/unifi_protect_backup' \
-v '/path/to/save/clips':'/data' \
-v `/path/to/rclone.conf':'/config/rclone/rclone.conf'
-v `/path/to/rclone.conf':'/config/rclone/rclone.conf' \
-v '/path/to/save/database':/config/database/ \
ghcr.io/ep1cman/unifi-protect-backup
```

View File

@@ -1,7 +1,7 @@
[tool]
[tool.poetry]
name = "unifi-protect-backup"
version = "0.7.4"
name = "unifi_protect_backup"
version = "0.8.2"
homepage = "https://github.com/ep1cman/unifi-protect-backup"
description = "Python tool to backup unifi event clips in realtime."
authors = ["sebastian.goscik <sebastian@goscik.com>"]

View File

@@ -2,7 +2,7 @@
__author__ = """sebastian.goscik"""
__email__ = 'sebastian@goscik.com'
__version__ = '0.7.4'
__version__ = '0.8.2'
# from .unifi_protect_backup import UnifiProtectBackup
from .downloader import VideoDownloader

View File

@@ -10,30 +10,28 @@ from pyunifiprotect import ProtectApiClient
from pyunifiprotect.data.nvr import Event
from pyunifiprotect.data.types import EventType
from unifi_protect_backup.utils import SubprocessException, VideoQueue, get_camera_name, human_readable_size
from unifi_protect_backup.utils import (
SubprocessException,
VideoQueue,
get_camera_name,
human_readable_size,
run_command,
)
logger = logging.getLogger(__name__)
async def get_video_length(video: bytes) -> float:
"""Uses ffprobe to get the length of the video file passed in as a byte stream"""
cmd = 'ffprobe -v quiet -show_streams -select_streams v:0 -of json -'
proc = await asyncio.create_subprocess_shell(
cmd,
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
returncode, stdout, stderr = await run_command(
'ffprobe -v quiet -show_streams -select_streams v:0 -of json -', video
)
stdout, stderr = await proc.communicate(video)
if proc.returncode == 0:
logger.extra_debug(f"stdout:\n{stdout.decode()}") # type: ignore
logger.extra_debug(f"stderr:\n{stderr.decode()}") # type: ignore
json_data = json.loads(stdout.decode())
return float(json_data['streams'][0]['duration'])
if returncode != 0:
raise SubprocessException(stdout, stderr, returncode)
else:
raise SubprocessException(stdout.decode(), stderr.decode(), proc.returncode)
json_data = json.loads(stdout)
return float(json_data['streams'][0]['duration'])
class VideoDownloader:
@@ -139,4 +137,3 @@ class VideoDownloader:
logger.debug(msg)
except SubprocessException as e:
logger.warn(" `ffprobe` failed")
logger.exception(e)

View File

@@ -8,7 +8,7 @@ import aiosqlite
from pyunifiprotect.data.nvr import Event
from pyunifiprotect import ProtectApiClient
from unifi_protect_backup.utils import get_camera_name, SubprocessException, VideoQueue
from unifi_protect_backup.utils import get_camera_name, SubprocessException, VideoQueue, run_command
logger = logging.getLogger(__name__)
@@ -74,19 +74,9 @@ class VideoUploader:
Raises:
RuntimeError: If rclone returns a non-zero exit code
"""
cmd = f'rclone rcat -vv {rclone_args} "{destination}"'
proc = await asyncio.create_subprocess_shell(
cmd,
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await proc.communicate(video)
if proc.returncode == 0:
logger.extra_debug(f"stdout:\n{stdout.decode()}") # type: ignore
logger.extra_debug(f"stderr:\n{stderr.decode()}") # type: ignore
else:
raise SubprocessException(stdout.decode(), stderr.decode(), proc.returncode)
returncode, stdout, stderr = await run_command(f'rclone rcat -vv {rclone_args} "{destination}"', video)
if returncode != 0:
logger.warn(f" Failed to upload file: '{destination}'")
async def _update_database(self, event: Event, destination: str):
"""

View File

@@ -86,26 +86,29 @@ def parse_rclone_retention(retention: str) -> relativedelta:
)
async def run_command(cmd: str):
async def run_command(cmd: str, data=None):
"""
Runs the given command returning the exit code, stdout and stderr
"""
proc = await asyncio.create_subprocess_shell(
cmd,
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await proc.communicate()
stdout = stdout.decode().replace('\n', '\n\t').strip()
stderr = stderr.decode().replace('\n', '\n\t').strip()
stdout, stderr = await proc.communicate(data)
stdout = stdout.decode()
stdout_indented = '\t' + stdout.replace('\n', '\n\t').strip()
stderr = stderr.decode()
stderr_indented = '\t' + stderr.replace('\n', '\n\t').strip()
if proc.returncode != 0:
logger.warn(f"Failed to run: '{cmd}")
logger.warn(f"stdout:\n{stdout}")
logger.warn(f"stderr:\n{stderr}")
logger.warn(f"stdout:\n{stdout_indented}")
logger.warn(f"stderr:\n{stderr_indented}")
else:
logger.extra_debug(f"stdout:\n{stdout}")
logger.extra_debug(f"stderr:\n{stderr}")
logger.extra_debug(f"stdout:\n{stdout_indented}")
logger.extra_debug(f"stderr:\n{stderr_indented}")
return proc.returncode, stdout, stderr