Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for NGINX VOD Module #1116

Merged

Conversation

hunterjm
Copy link
Contributor

@hunterjm hunterjm commented May 18, 2021

This adds support for viewing recordings as a "video on demand". It can be tested by loading the stream URL into this demo site or VLC.

The VOD stream format currently follows the recording directory format - i.e.: http:https://localhost:5000/vod/2021-05/17/20/{camera}/master.m3u8

This first version is obviously limited to an hour's worth of recordings based on the current recording file structure. We can expand it later by modifying the vod function in http.py. Regardless, The Home Assistant integration can be updated to use this new URL to allow Media Browser recordings of entire hour long segments without having to use FFMPEG to combine the videos. The Web UI can also use this along with something like videojs to build a recording playback page. I left the /recordings directive in tact for backwards compatibility until things switch over.

Copy link
Contributor Author

@hunterjm hunterjm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hope these comments/notes are helpful as you look through this.


try:
server.serve_forever()
except KeyboardInterrupt:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't necessarily belong as a part of this PR, but in testing I noticed some exceptions being raised on ctrl+C so this handles it.

@@ -35,7 +35,7 @@ def log_process(log_queue):
while True:
try:
record = log_queue.get(timeout=5)
except queue.Empty:
except (queue.Empty, KeyboardInterrupt):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to above, an exception was being raised here on shutdown, so handling it here.

@@ -446,10 +449,37 @@ def latest_frame(camera_name):
return "Camera named {} not found".format(camera_name), 404


@bp.route("/vod/<path:path>")
def vod(path):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This builds a JSON object to be consumed by nginx-vod-module. Currently we are just using the existing recording path, but can expand this to add additional filters or functionality later.

@@ -0,0 +1,46 @@
FROM ubuntu:20.04 AS base
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to build NGINX from source to include support for aio files and multiple architectures. nginx-vod-module has a repo we could add, but it is only built for amd64.

@@ -18,16 +20,13 @@ ENV DEBIAN_FRONTEND=noninteractive
# Install packages for apt repo
RUN apt-get -qq update \
&& apt-get upgrade -y \
&& apt-get -qq install --no-install-recommends -y \
gnupg wget unzip tzdata nginx libnginx-mod-rtmp \
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removing nginx and libnginx-mod-rtmp as they are built-in to our compiled version of NGINX along with nginx-vod-module.

@hunterjm
Copy link
Contributor Author

hunterjm commented May 18, 2021

Will update docs before you merge if this looks like a good path forward to you.

clips.append({"type": "source", "path": filename})
video = cv2.VideoCapture(filename)
duration = int(
video.get(cv2.CAP_PROP_FRAME_COUNT) / video.get(cv2.CAP_PROP_FPS) * 1000
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How long does it take to do this dynamically? It could take a while if using a network drive.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s two seconds on my dev machine, but it’s also pretty beefy and the files were local. I almost just hardcoded the 60 seconds here, but know that the command (and thus the time) can be changed in configuration.

I saw the use of ffprobe elsewhere for clips, but since OpenCV was already imported and this stackoverflow answer said it was faster, I just used it. Granted I did not do any verification myself. It’s probably worth a shot testing this on a Pi with video on a network drive to see.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified timing in comment below.

@mitchross
Copy link
Contributor

Just out of curiosity, what benefits would this have for the 'clips' feature?

@hunterjm
Copy link
Contributor Author

Just out of curiosity, what benefits would this have for the 'clips' feature?

This doesn't really have any benefit for clips as they are non-continuous and already are the length you expect them to be. This is just a way to combine the 60 second recording files together in an hour long video to make viewing the 24/7 recording easier.

What we could do, eventually, is in the front-end automatically add markers to the timeline for the continuous recording at the timestamps that events occurred to be able to seek to them with relative ease.

@stephengolub
Copy link

stephengolub commented May 18, 2021

I literally just built a small script to generate a downloadable mp4 file to facilitate large chunks of time. I'm wondering if instead of path, you take in camera and then have query parameters that are date/time objects...

Then you just glob the files and do a greater than/less than check. You could default to the last 60 minutes.

This is my relevant bash code:

file_list=$(ls **/**/**/$CAMERA/*.mp4)                                                                                    
                                                                                                                          
real_list=()                                                                                                              
                                                                                                                          
for file in $file_list; do                                                                                                
    if [ "$file" \> "$START_FILE" ] && [ "$file" \< "$END_FILE" ]; then                                                   
        real_list+=("file '$PWD/$file'")                                                                                  
    fi                                                                                                                    
done 

@hunterjm
Copy link
Contributor Author

I literally just built a small script to generate a downloadable mp4 file to facilitate large chunks of time. I'm wondering if instead of path, you take in camera and then have query parameters that are date/time objects...

Then you just glob the files and do a greater than/less than check. You could default to the last 60 minutes.

This is my relevant bash code:

file_list=$(ls **/**/**/$CAMERA/*.mp4)                                                                                    
                                                                                                                          
real_list=()                                                                                                              
                                                                                                                          
for file in $file_list; do                                                                                                
    if [ "$file" \> "$START_FILE" ] && [ "$file" \< "$END_FILE" ]; then                                                   
        real_list+=("file '$PWD/$file'")                                                                                  
    fi                                                                                                                    
done 

I honestly think that's the next step, but wanted to keep the initial implementation simple (easier to review just the relevant VOD module code) and expand the functionality of filters/etc in a follow-up PR.

@stephengolub
Copy link

If you take a look at the PR I opened against your branch, I illustrated what I meant (I haven't tested it fully yet), but, tbh, I don't think it's too much more complicated.

hunterjm#1

@hunterjm
Copy link
Contributor Author

If you take a look at the PR I opened against your branch, I illustrated what I meant (I haven't tested it fully yet), but, tbh, I don't think it's too much more complicated.

hunterjm#1

After some initial testing, query string parameters do not get passed on from the VOD module. We can do some path based filtering like camera/start/end But another potential issue is the amount of time it takes to recursively glob the entire recording directory and iterate over the 43,200 files that would exist with the default retention period. I am having trouble finding a fast solution for just the latest 24 hours. I’m happy to research further and put it in a follow-up PR, but don’t want to block this one because of it.

@stephengolub
Copy link

I was afraid of that. Everyone knows you shouldn’t use the directory tree as a database.

Might be best to table it like you said and work on optimizing how we look up available recordings and clips

@hunterjm
Copy link
Contributor Author

hunterjm commented May 19, 2021

Did some testing on the cv.VideoCapture time for a full hour's worth of recordings (60) on my dev box:

Glob: 0.000562
Sort: 0.000004
CV Read Total: 2.192811
CV Read Avg: 0.036547

versus FFProbe that is used to verify clips and recordings:

Glob: 0.000504
Sort: 0.000005
FFProbe Read Total: 2.946326
FFProbe Read Avg: 0.049105

Granted it is a Ryzen 3900x so it may take longer on a Raspberry Pi, but it's encouraging that the OpenCV method is not only faster than a ffprobe sub process, but is less lines of code as well.

Trying to load something like 24 hours worth of video would take 53 seconds however, which is too inefficient for the initial release.

I also have NGINX aggressively caching these manifests for 100 days, so this lookup is only seen the first time you load the VOD.

The only other option would be to default duration to 60, as that is what it will be the vast majority of time. I figured if it didn't take too long to be certain though, it's better for cases where recordings ended early or a user modified the recording segment time in their configuration.

I'm happy with this PR now - the next commit updating docs should be the last.

@blakeblackshear blakeblackshear merged commit f4bc68d into blakeblackshear:release-0.9.0 May 22, 2021
@hunterjm hunterjm deleted the nginx-vod-module branch May 22, 2021 15:37
@hunterjm
Copy link
Contributor Author

@blakeblackshear - could you run the nginx make command to push the images to docker hub? That would make it so others can successfully build frigate.

@blakeblackshear
Copy link
Owner

My buildx command is failing to build the armv7 image. Did you do some set up to get it to work?

@hunterjm
Copy link
Contributor Author

hunterjm commented May 22, 2021

No, it built just fine for me. What step does it fail at?

@hunterjm
Copy link
Contributor Author

Built and pushed for all 3 architectures here: https://hub.docker.com/r/hunterjm/frigate-nginx/tags?page=1&ordering=last_updated

@blakeblackshear
Copy link
Owner

Got it working with these steps. Mine are pushed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants