From f0f5af6f93a4aa8ccc8f3ed5d4e66b6f8fc5a0b3 Mon Sep 17 00:00:00 2001 From: Max Jesch Date: Tue, 22 Mar 2022 15:46:41 +0100 Subject: [PATCH 1/3] feat: various changes: - changed codec and container to reduce file size of videos by 75% - changed upload from being handled in a coroutine, to being synchronous using azure storage client --> that makes it a lot easier to implement automatic deletion of files after upload - had to remove video consistency check because it cause weird problems --- camera_stream_recording/Utils.py | 45 ++++++++++++------------ camera_stream_recording/env.example | 3 +- camera_stream_recording/main.py | 23 +++++++----- camera_stream_recording/requirements.txt | 1 + 4 files changed, 40 insertions(+), 32 deletions(-) diff --git a/camera_stream_recording/Utils.py b/camera_stream_recording/Utils.py index 4dd53a6..bfef0b9 100644 --- a/camera_stream_recording/Utils.py +++ b/camera_stream_recording/Utils.py @@ -57,24 +57,26 @@ def is_video_consistent(filename, video_duration): :return: if the video is consistent due to a threshold :rtype: bool """ - video_length = get_length(filename) - # Check if video is 8 percent shorter than expected - # check if the video is shorter than the expected length. This could be a "jump" in the video -> delete video - expected_length = video_duration - tolerance = expected_length - (expected_length * 0.1) - if video_length < tolerance: - print('video is 1 percent shorter than expected:') - print('video_length: ' + str(video_length)) - print('expected_length: ' + str(expected_length)) - print('tolerance: ' + str(tolerance)) - return False - else: - print('video_length: ' + str(video_length)) - print('expected_length: ' + str(expected_length)) + # This is causing weird "file not found" issues --> uncommenting for now. MJ 21.03.2021 + + # video_length = get_length(filename) + # # Check if video is 8 percent shorter than expected + # # check if the video is shorter than the expected length. This could be a "jump" in the video -> delete video + # expected_length = video_duration + # tolerance = expected_length - (expected_length * 0.1) + # if video_length < tolerance: + # print('video is 1 percent shorter than expected:') + # print('video_length: ' + str(video_length)) + # print('expected_length: ' + str(expected_length)) + # print('tolerance: ' + str(tolerance)) + # return False + # else: + # print('video_length: ' + str(video_length)) + # print('expected_length: ' + str(expected_length)) return True -def upload_video(filename, uploads_list, storage_addr, sas_token): +def upload_video(filename, SAS_TOKEN, STORAGE_ACCOUNT, STORAGE_CONTAINER): """ Prepare the upload of files to a azure Filestorage @@ -89,16 +91,15 @@ def upload_video(filename, uploads_list, storage_addr, sas_token): :return: - :rtype: void """ - print("Upload file " + filename) - dest = storage_addr + filename + sas_token - - sub_p_id = subprocess.Popen(["./azcopy", "copy", filename, dest], - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - uploads_list.append([sub_p_id, filename]) + print("Uploading file %s to %s / %s " % (filename, STORAGE_ACCOUNT, STORAGE_CONTAINER)) + block_blob_service = BlockBlobService(account_name=STORAGE_ACCOUNT, sas_token=SAS_TOKEN) + block_blob_service.create_blob_from_path(STORAGE_CONTAINER, filename, filename) + print("Upload successfull. Removing local file %s" % filename) + os.remove(filename) return + def get_end_timestamp_from_minutes_duration(video_max_duration): return datetime.now() + timedelta(minutes=video_max_duration) diff --git a/camera_stream_recording/env.example b/camera_stream_recording/env.example index 433c72c..a2462e3 100644 --- a/camera_stream_recording/env.example +++ b/camera_stream_recording/env.example @@ -2,4 +2,5 @@ CAMERA_IP_ADDR= CAMERA_USER_NAME= PASSWORD= SAS_TOKEN= -STORAGE_ADDR= \ No newline at end of file +STORAGE_ACCOUNT= +STORAGE_CONTAINER= \ No newline at end of file diff --git a/camera_stream_recording/main.py b/camera_stream_recording/main.py index 3817170..998f701 100644 --- a/camera_stream_recording/main.py +++ b/camera_stream_recording/main.py @@ -37,7 +37,6 @@ def __init__(self, username, password, ip_addr, is_uploaded): self.fourcc = cv2.VideoWriter_fourcc('M', 'J', 'P', 'G') self.link = f"rtsp://{username}:{password}@{ip_addr}:80/axis-media/media.amp?streamprofile=rtspStreamLow" self.cap = cv2.VideoCapture(self.link) - print(self.link) self.uploads = [] self.is_uploaded = is_uploaded if not self.cap.isOpened(): @@ -45,16 +44,17 @@ def __init__(self, username, password, ip_addr, is_uploaded): def start_capture_one_video(self): """Initialize the Video Writer""" - vid_name = generate_unique_name() + ".mp4" - out = cv2.VideoWriter(vid_name, self.fourcc, 30.0, - (1920, 1080)) + vid_name = generate_unique_name() + ".avi" + fourcc = cv2.VideoWriter_fourcc(*'MPEG') + out = cv2.VideoWriter(vid_name, fourcc, 30.0, + (1920, 1080)) return vid_name, out def handle_video_saving(self, filename, sub_procs_list, video_duration): """Method will start uploading videos and delete the files if there are not consistent""" if is_video_consistent(filename, video_duration): if self.is_uploaded: - upload_video(filename, sub_procs_list, config('STORAGE_ADDR'), config('SAS_TOKEN')) + upload_video(filename, config('SAS_TOKEN'), config('STORAGE_ACCOUNT'),config('STORAGE_CONTAINER') ) else: print("No upload, because the script runs locally\n") else: @@ -66,7 +66,7 @@ def handle_video_saving_specific_time(self, filename, sub_procs_list, """Method will start uploading and keep the files if there are not consistent""" if is_video_consistent(filename, video_duration): if self.is_uploaded: - upload_video(filename, sub_procs_list, config('STORAGE_ADDR'), config('SAS_TOKEN')) + upload_video(filename, config('SAS_TOKEN'), config('STORAGE_ACCOUNT'),config('STORAGE_CONTAINER') ) else: print("No upload, because the script runs locally\n") else: @@ -130,7 +130,7 @@ def capture_video_stream_for_specific_time(self, end_timestamp=None): cam_connect = self.cap.read() time.sleep(0.5) - cv2.namedWindow("CameraView", cv2.WINDOW_NORMAL) + # cv2.namedWindow("CameraView", cv2.WINDOW_NORMAL) while True: prev_stream_frame = stream_frame try: @@ -164,7 +164,7 @@ def capture_video_stream_for_specific_time(self, end_timestamp=None): vis_frame = Display("Connect: " + str(connect_count), vis_frame, (5, 80)).overlay_text() if cam_connect: - cv2.imshow("CameraView", vis_frame) + # cv2.imshow("CameraView", vis_frame) self.out.write(stream_frame) if end_timestamp is not None: @@ -187,7 +187,7 @@ def capture_video_stream_for_specific_time(self, end_timestamp=None): self.handle_video_saving_specific_time(self.vid_name, self.uploads, expected_length) self.check_for_completed_uploads(self.uploads, None) - cv2.destroyAllWindows() + # cv2.destroyAllWindows() # self.match_with_first_timestamp() def match_with_first_timestamp(self): @@ -299,6 +299,10 @@ def capture_images_every_minute(self, runtime_duration): if datetime.datetime.now() >= end_time_script: break +def entry_for_async_recording(): + video_capturing = MainCaptureHandling(config('CAMERA_USER_NAME'), + config('PASSWORD'), config('CAMERA_IP_ADDR'), True) + video_capturing.capture_video_stream_for_specific_time(get_end_timestamp_from_minutes_duration(1)) if __name__ == "__main__": @@ -352,6 +356,7 @@ def capture_images_every_minute(self, runtime_duration): # Run only one planned capturing of one video user defined if args.video_duration: vdm_duration = args.video_duration + print("running planned video capturing for a specific time") video_capturing.capture_video_stream_for_specific_time( get_end_timestamp_from_minutes_duration(vdm_duration)) else: diff --git a/camera_stream_recording/requirements.txt b/camera_stream_recording/requirements.txt index 8b9cd1e..68056c2 100644 --- a/camera_stream_recording/requirements.txt +++ b/camera_stream_recording/requirements.txt @@ -3,3 +3,4 @@ opencv-python==4.3.0.36 python-dateutil==2.8.1 python-decouple==3.4 six==1.15.0 +azure-storage-blob==2.1.0 \ No newline at end of file From d5e392455a8cb8f8c8c689477fe1c73ce14ebc55 Mon Sep 17 00:00:00 2001 From: Max Jesch Date: Tue, 22 Mar 2022 15:48:57 +0100 Subject: [PATCH 2/3] feat: built an async wrapper to call the main functions every 10 Minutes to record for 10 minutes. That way we can achieve close to nonstop recording (excluding the outages of the stream itself unfortunaly :-/ ) --- camera_stream_recording/Dockerfile | 8 +++++++ camera_stream_recording/async_wrapper.py | 6 ++++++ camera_stream_recording/deployment.yaml | 27 ++++++++++++++++++++++++ 3 files changed, 41 insertions(+) create mode 100644 camera_stream_recording/Dockerfile create mode 100644 camera_stream_recording/async_wrapper.py create mode 100644 camera_stream_recording/deployment.yaml diff --git a/camera_stream_recording/Dockerfile b/camera_stream_recording/Dockerfile new file mode 100644 index 0000000..53bce46 --- /dev/null +++ b/camera_stream_recording/Dockerfile @@ -0,0 +1,8 @@ +FROM python:3.7 + +COPY requirements.txt requirements.txt +RUN pip3 install -r requirements.txt + +COPY . . + +CMD [ "python","-u", "./async_wrapper.py" ] diff --git a/camera_stream_recording/async_wrapper.py b/camera_stream_recording/async_wrapper.py new file mode 100644 index 0000000..0b4930a --- /dev/null +++ b/camera_stream_recording/async_wrapper.py @@ -0,0 +1,6 @@ +import time +from subprocess import Popen +while True: + print("starting new process with video_recording_main.py") + p = Popen('python main.py -u -p -vd 10', shell=True) + time.sleep(600) diff --git a/camera_stream_recording/deployment.yaml b/camera_stream_recording/deployment.yaml new file mode 100644 index 0000000..93f62bd --- /dev/null +++ b/camera_stream_recording/deployment.yaml @@ -0,0 +1,27 @@ +kind: Namespace +apiVersion: v1 +metadata: + name: camera-recorder + labels: + name: camera-recorder +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: camera-recorder-deployment + namespace: camera-recorder + labels: + app: camera-recorder +spec: + replicas: 1 + selector: + matchLabels: + app: camera-recorder + template: + metadata: + labels: + app: camera-recorder + spec: + containers: + - name: camera-recorder + image: fkkstudents.azurecr.io/recording/camera_recorder:latest From bb0f10f3580795b3fee25e5f125fabbcaecead17 Mon Sep 17 00:00:00 2001 From: Max Jesch Date: Tue, 22 Mar 2022 15:49:23 +0100 Subject: [PATCH 3/3] feat: updated readme --- camera_stream_recording/README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/camera_stream_recording/README.md b/camera_stream_recording/README.md index 9eb723a..c9d6984 100644 --- a/camera_stream_recording/README.md +++ b/camera_stream_recording/README.md @@ -1,3 +1,22 @@ +## deploy 24/7 recording to aks +To have a 24/7 recording running in kubernetes and storing the results to azure blob follow these steps +````bash +az login +az acr login --name fkkstudents +```` +make sure you are in the ./camera_stream_recording/src folder +````bash +docker build -t fkkstudents.azurecr.io/recording/camera_recorder . +docker push fkkstudents.azurecr.io/recording/camera_recorder +```` + +get .env file with the correct credentials (a backup is stored in the SaveNow storage account fkk247/credentials) + +````bash +az aks get-credentials --resource-group fkkstudents --name fkkstudents +kubectl apply -f deployament.yaml +```` + ## Capture Videostream This tool has two basic mode. At first you can generate training data for manual labeling in cvat [here is the relevant fork](https://github.com/jul095/cvat).