Преглед изворни кода

Add support for NGINX VOD Module

Jason Hunter пре 4 година
родитељ
комит
aab6a00e4c
9 измењених фајлова са 142 додато и 25 уклоњено
  1. 4 1
      Makefile
  2. 1 1
      docker-compose.yml
  3. 7 7
      docker/Dockerfile.base
  4. 46 0
      docker/Dockerfile.nginx
  5. 5 1
      frigate/app.py
  6. 39 9
      frigate/http.py
  7. 1 1
      frigate/log.py
  8. 38 4
      nginx/nginx.conf
  9. 1 1
      run.sh

+ 4 - 1
Makefile

@@ -14,8 +14,11 @@ amd64_wheels:
 amd64_ffmpeg:
 	docker build --tag blakeblackshear/frigate-ffmpeg:1.1.0-amd64 --file docker/Dockerfile.ffmpeg.amd64 .
 
+nginx:
+	docker buildx build --platform linux/arm/v7,linux/arm64/v8,linux/amd64 --tag blakeblackshear/frigate-nginx:1.0.0 --file docker/Dockerfile.nginx .
+
 amd64_frigate: version web
-	docker build --tag frigate-base --build-arg ARCH=amd64 --build-arg FFMPEG_VERSION=1.1.0 --build-arg WHEELS_VERSION=1.0.3 --file docker/Dockerfile.base .
+	docker build --tag frigate-base --build-arg ARCH=amd64 --build-arg FFMPEG_VERSION=1.1.0 --build-arg WHEELS_VERSION=1.0.3 --build-arg NGINX_VERSION=1.0.0 --file docker/Dockerfile.base .
 	docker build --tag frigate --file docker/Dockerfile.amd64 .
 
 amd64_all: amd64_wheels amd64_ffmpeg amd64_frigate

+ 1 - 1
docker-compose.yml

@@ -23,7 +23,7 @@ services:
       - "5000:5000"
       - "5001:5001"
       - "8080:8080"
-    command: /bin/sh -c "sudo service nginx start; while sleep 1000; do :; done"
+    command: /bin/sh -c "sudo /usr/local/nginx/sbin/nginx; while sleep 1000; do :; done"
   mqtt:
     container_name: mqtt
     image: eclipse-mosquitto:1.6

+ 7 - 7
docker/Dockerfile.base

@@ -1,8 +1,10 @@
 ARG ARCH=amd64
 ARG WHEELS_VERSION
 ARG FFMPEG_VERSION
+ARG NGINX_VERSION
 FROM blakeblackshear/frigate-wheels:${WHEELS_VERSION}-${ARCH} as wheels
 FROM blakeblackshear/frigate-ffmpeg:${FFMPEG_VERSION}-${ARCH} as ffmpeg
+FROM blakeblackshear/frigate-nginx:${NGINX_VERSION} as nginx
 FROM frigate-web as web
 
 FROM ubuntu:20.04
@@ -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 \
-    && apt-get -qq install --no-install-recommends -y \
-    python3-pip \
+    && apt-get -qq install --no-install-recommends -y gnupg wget unzip tzdata libxml2 \
+    && apt-get -qq install --no-install-recommends -y python3-pip \
     && pip3 install -U /wheels/*.whl \
     && APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn apt-key adv --fetch-keys https://packages.cloud.google.com/apt/doc/apt-key.gpg \
     && echo "deb https://packages.cloud.google.com/apt coral-edgetpu-stable main" > /etc/apt/sources.list.d/coral-edgetpu.list \
     && echo "libedgetpu1-max libedgetpu/accepted-eula select true" | debconf-set-selections \
-    && apt-get -qq update && apt-get -qq install --no-install-recommends -y \
-    libedgetpu1-max=15.0 \
+    && apt-get -qq update && apt-get -qq install --no-install-recommends -y libedgetpu1-max=15.0 \
     && rm -rf /var/lib/apt/lists/* /wheels \
     && (apt-get autoremove -y; apt-get autoclean -y)
 
@@ -39,7 +38,8 @@ RUN pip3 install \
     gevent \
     gevent-websocket
 
-COPY nginx/nginx.conf /etc/nginx/nginx.conf
+COPY --from=nginx /usr/local/nginx/ /usr/local/nginx/
+COPY nginx/nginx.conf /usr/local/nginx/conf/nginx.conf
 
 # get model and labels
 COPY labelmap.txt /labelmap.txt

+ 46 - 0
docker/Dockerfile.nginx

@@ -0,0 +1,46 @@
+FROM ubuntu:20.04 AS base
+
+ENV DEBIAN_FRONTEND=noninteractive
+RUN apt-get -yqq update && \
+    apt-get install -yq --no-install-recommends ca-certificates expat libgomp1 && \
+    apt-get autoremove -y && \
+    apt-get clean -y
+
+FROM base as build
+
+ARG NGINX_VERSION=1.18.0
+ARG VOD_MODULE_VERSION=1.28
+ARG RTMP_MODULE_VERSION=1.2.1
+
+RUN cp /etc/apt/sources.list /etc/apt/sources.list~ \
+    && sed -Ei 's/^# deb-src /deb-src /' /etc/apt/sources.list \
+    && apt-get update
+
+RUN apt-get -yqq build-dep nginx
+
+RUN apt-get -yqq install --no-install-recommends curl \
+    && mkdir /tmp/nginx \
+    && curl -sL https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz | tar -C /tmp/nginx -zx --strip-components=1 \
+    && mkdir /tmp/nginx-vod-module \
+    && curl -sL https://github.com/kaltura/nginx-vod-module/archive/refs/tags/${VOD_MODULE_VERSION}.tar.gz | tar -C /tmp/nginx-vod-module -zx --strip-components=1 \
+    && mkdir /tmp/nginx-rtmp-module \
+    && curl -sL https://github.com/arut/nginx-rtmp-module/archive/refs/tags/v${RTMP_MODULE_VERSION}.tar.gz | tar -C /tmp/nginx-rtmp-module -zx --strip-components=1
+
+WORKDIR /tmp/nginx
+
+RUN ./configure --prefix=/usr/local/nginx \
+    --with-file-aio \
+    --with-http_sub_module \
+    --with-http_ssl_module \
+    --with-threads \
+    --add-module=../nginx-vod-module \
+    --add-module=../nginx-rtmp-module \
+    --with-cc-opt="-O3 -Wno-error=implicit-fallthrough"
+
+RUN make && make install
+RUN rm -rf /usr/local/nginx/html /usr/local/nginx/conf/*.default
+
+FROM base
+COPY --from=build /usr/local/nginx /usr/local/nginx
+ENTRYPOINT ["/usr/local/nginx/sbin/nginx"]
+CMD ["-g", "daemon off;"]

+ 5 - 1
frigate/app.py

@@ -316,7 +316,11 @@ class FrigateApp:
         server = pywsgi.WSGIServer(
             ("127.0.0.1", 5001), self.flask_app, handler_class=WebSocketHandler
         )
-        server.serve_forever()
+
+        try:
+            server.serve_forever()
+        except KeyboardInterrupt:
+            pass
 
         self.stop()
 

+ 39 - 9
frigate/http.py

@@ -1,6 +1,7 @@
 import base64
 import datetime
 import json
+import glob
 import logging
 import os
 import time
@@ -23,7 +24,7 @@ from flask_sockets import Sockets
 from peewee import SqliteDatabase, operator, fn, DoesNotExist
 from playhouse.shortcuts import model_to_dict
 
-from frigate.const import CLIPS_DIR
+from frigate.const import CLIPS_DIR, RECORD_DIR
 from frigate.models import Event
 from frigate.stats import stats_snapshot
 from frigate.util import calculate_region
@@ -186,13 +187,15 @@ def event(id):
     except DoesNotExist:
         return "Event not found", 404
 
-@bp.route('/events/<id>', methods=('DELETE',))
+
+@bp.route("/events/<id>", methods=("DELETE",))
 def delete_event(id):
     try:
         event = Event.get(Event.id == id)
     except DoesNotExist:
-        return make_response(jsonify({"success": False, "message": "Event"  + id + " not found"}),404)
-
+        return make_response(
+            jsonify({"success": False, "message": "Event" + id + " not found"}), 404
+        )
 
     media_name = f"{event.camera}-{event.id}"
     if event.has_snapshot:
@@ -203,12 +206,12 @@ def delete_event(id):
         media.unlink(missing_ok=True)
 
     event.delete_instance()
-    return make_response(jsonify({"success": True, "message": "Event"  + id + " deleted"}),200)
-
-
+    return make_response(
+        jsonify({"success": True, "message": "Event" + id + " deleted"}), 200
+    )
 
 
-@bp.route('/events/<id>/thumbnail.jpg')
+@bp.route("/events/<id>/thumbnail.jpg")
 def event_thumbnail(id):
     format = request.args.get("format", "ios")
     thumbnail_bytes = None
@@ -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):
+    if not os.path.isdir(f"{RECORD_DIR}/{path}"):
+        return "Recordings not found.", 404
+
+    files = glob.glob(f"{RECORD_DIR}/{path}/*.mp4")
+    files.sort()
+
+    clips = []
+    durations = []
+    for filename in files:
+        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
+        )
+        durations.append(duration)
+
+    return jsonify(
+        {
+            "discontinuity": False,
+            "durations": durations,
+            "sequences": [{"clips": clips}],
+        }
+    )
+
+
 def imagestream(detected_frames_processor, camera_name, fps, height, draw_options):
     while True:
         # max out at specified FPS
-        gevent.sleep(1/fps)
+        gevent.sleep(1 / fps)
         frame = detected_frames_processor.get_current_frame(camera_name, draw_options)
         if frame is None:
             frame = np.zeros((height, int(height * 16 / 9), 3), np.uint8)

+ 1 - 1
frigate/log.py

@@ -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):
             continue
         logger = logging.getLogger(record.name)
         logger.handle(record)

+ 38 - 4
nginx/nginx.conf

@@ -1,23 +1,24 @@
 worker_processes  1;
 
-error_log  /var/log/nginx/error.log warn;
+error_log  /usr/local/nginx/logs/error.log warn;
 pid        /var/run/nginx.pid;
 
-load_module "modules/ngx_rtmp_module.so";
+# load_module "modules/ngx_rtmp_module.so";
+# load_module "modules/ngx_http_vod_module.so";
 
 events {
     worker_connections  1024;
 }
 
 http {
-    include       /etc/nginx/mime.types;
+    include       mime.types;
     default_type  application/octet-stream;
 
     log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                         '$status $body_bytes_sent "$http_referer" '
                         '"$http_user_agent" "$http_x_forwarded_for"';
 
-    access_log  /var/log/nginx/access.log  main;
+    access_log  /usr/local/nginx/logs/access.log  main;
 
     sendfile        on;
 
@@ -37,6 +38,39 @@ http {
     server {
         listen 5000;
 
+        # vod settings
+        vod_mode mapped;
+        vod_max_mapping_response_size 1m;
+        vod_upstream_location /api;
+        vod_last_modified 'Sun, 19 Nov 2000 08:52:00 GMT';
+        vod_last_modified_types *;
+
+        # vod caches
+        vod_metadata_cache metadata_cache 512m;
+        vod_response_cache response_cache 128m;
+        vod_mapping_cache mapping_cache 5m;
+
+        # gzip manifests
+        gzip on;
+        gzip_types application/vnd.apple.mpegurl;
+
+        # file handle caching / aio
+        open_file_cache          max=1000 inactive=5m;
+        open_file_cache_valid    2m;
+        open_file_cache_min_uses 1;
+        open_file_cache_errors   on;
+        aio on;
+
+        location /vod/ {
+            vod hls;
+
+            add_header Access-Control-Allow-Headers '*';
+            add_header Access-Control-Expose-Headers 'Server,range,Content-Length,Content-Range';
+            add_header Access-Control-Allow-Methods 'GET, HEAD, OPTIONS';
+            add_header Access-Control-Allow-Origin '*';
+            expires 100d;
+        }
+
         location /stream/ {
             add_header 'Cache-Control' 'no-cache';
             add_header 'Access-Control-Allow-Origin' "$http_origin" always;

+ 1 - 1
run.sh

@@ -1,4 +1,4 @@
 #!/usr/bin/env bash
 
-service nginx start
+/usr/local/nginx/sbin/nginx
 exec python3 -u -m frigate