├── -d
├── .dockerignore
├── .drone.yml
├── .gitattributes
├── .github
├── dependabot.yml
└── workflows
│ ├── build-docker.yml
│ └── codeql-analysis.yml
├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── app
├── __init__.py
├── blueprints
│ ├── api.py
│ ├── auth.py
│ ├── main.py
│ └── setup.py
├── config.json
├── data
│ └── .data
├── helpers
│ ├── __init__.py
│ ├── cookies.py
│ ├── drawSVG.py
│ ├── fetch.py
│ ├── getConfig.py
│ ├── include_file.py
│ ├── iterateQuery.py
│ ├── logit.py
│ ├── rndpwd.py
│ └── tz.py
├── models
│ ├── apiauth.py
│ ├── cameras.py
│ ├── config.py
│ ├── events.py
│ ├── frigate.py
│ ├── liveview.py
│ └── user.py
├── mqtt_client
├── static
│ ├── css
│ │ ├── colors.css
│ │ ├── events.css
│ │ ├── landscape.css
│ │ ├── main.css
│ │ ├── portrait.css
│ │ └── setup.css
│ ├── img
│ │ ├── PayPal.svg
│ │ ├── PayPal.svg.2022_03_30_00_23_47.1.svg
│ │ ├── back.svg
│ │ ├── beard-hover.svg
│ │ ├── beard.svg
│ │ ├── beardedtek-grey.png
│ │ ├── beardedtek-square.png
│ │ ├── beardmenu.png
│ │ ├── bitcoin.svg
│ │ ├── close.svg
│ │ ├── donate-bitcoin.png
│ │ ├── fEVR-192.png
│ │ ├── fEVR-352.png
│ │ ├── fEVR-512.png
│ │ ├── fEVRgithubCard.png
│ │ ├── facebook-grey.svg
│ │ ├── favicon.ico
│ │ ├── favicon.png
│ │ ├── fevr.svg
│ │ ├── fevrdocs.svg
│ │ ├── filter.svg
│ │ ├── github-large.svg
│ │ ├── github-sponsor.svg
│ │ ├── github.svg
│ │ ├── paypal-donate.png
│ │ ├── paypal-donate.svg
│ │ ├── paypal-small.svg
│ │ ├── popout.svg
│ │ ├── screenshot.png
│ │ ├── share-close.svg
│ │ ├── share-fb.svg
│ │ ├── share-insta.svg
│ │ ├── share-linkedin.svg
│ │ ├── share-pin.svg
│ │ ├── share-reddit.svg
│ │ ├── share-twitter.svg
│ │ ├── share.svg
│ │ └── tallycoin-donate.png
│ └── js
│ │ ├── main.js
│ │ ├── modal.js
│ │ └── setup.js
└── templates
│ ├── api.html
│ ├── event.html
│ ├── events.html
│ ├── home.html
│ ├── login.html
│ ├── menu.html
│ ├── setup.html
│ ├── setupadmin.html
│ ├── setupcameras.html
│ ├── setupconfig.html
│ ├── setupfrigate.html
│ ├── setupmqtt.html
│ ├── setupuser.html
│ ├── signup.html
│ └── user.html
├── config.yml.template
├── docker-compose.yml
├── docs
├── API.md
├── INSTALL.md
├── MQTT_CLIENT.md
├── NOTIFICATIONS.md
├── SETUP.md
├── images
│ ├── features.webp
│ ├── hpf-screenshot.png
│ ├── latest.webp
│ ├── menu.webp
│ ├── profile-apiauthkey-1.webp
│ ├── profile-apiauthkey-2.webp
│ ├── profile-apiauthkey-3.webp
│ ├── profile-apiauthkey-4.webp
│ ├── profile-apiauthkey.webp
│ ├── profile.webp
│ ├── screenshot.png
│ ├── screenshot.webp
│ ├── setup-admin.webp
│ ├── setup-cameras-blank.webp
│ ├── setup-cameras.webp
│ ├── setup-frigate-blank.webp
│ ├── setup-frigate.webp
│ └── social.png
└── source_images
│ ├── Google__G__Logo.svg
│ ├── Wyze_Labs_Logo.svg
│ ├── fevr-title.png
│ ├── frigate.video-banner.webp
│ ├── installation-tutorial.webp
│ ├── installation-tutorial.xcf
│ ├── main.png
│ ├── nest_aware.svg
│ ├── ring-logo.svg
│ ├── ring_protect_plan_plus.png
│ ├── wyze.svg
│ └── wyze_cam_plus.svg
├── fevr.py
├── install.sh
├── requirements.txt
├── rootfs
└── etc
│ └── services.d
│ ├── fevr
│ ├── finish
│ └── run
│ ├── mqtt_client
│ ├── finish
│ └── run
│ └── tailscale
│ └── run
├── run_fevr.sh
├── template.env
└── uwsgi.ini
/-d:
--------------------------------------------------------------------------------
1 | # Netscape HTTP Cookie File
2 | # https://curl.se/docs/http-cookies.html
3 | # This file was generated by libcurl! Edit at your own risk.
4 |
5 | #HttpOnly_localhost FALSE / FALSE 1657536008 fEVR_Session eyJfcGVybWFuZW50Ijp0cnVlfQ.Ysv3AA.nb-2fPLFOwCK9MyzB-wiSRfX5Eo
6 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | .cache
2 | .env
3 | .git
4 | .gitattributes
5 | .gitignore
6 | .github
7 | docs
8 | docker-compose.yml
9 | install.sh
10 | README.md
11 | template.env
12 | test
13 | venv
--------------------------------------------------------------------------------
/.drone.yml:
--------------------------------------------------------------------------------
1 | kind: pipeline
2 | name: default
3 | image_pull_secrets:
4 | - docker-auth-beardedtek.com
5 | trigger:
6 | branch:
7 | - 0.6-dev
8 | - main
9 | event:
10 | - push
11 | steps:
12 | - name: beardedtek-PUSH
13 | image: plugins/docker
14 | when:
15 | event:
16 | - push
17 | settings:
18 | username: beardedtek
19 | password:
20 | from_secret: docker_password
21 | repo: docker.beardedtek.com/beardedtek/fevr
22 | registry: docker.beardedtek.com
23 | tags: dev
24 | experimental: true
25 | compress: true
26 | squash: true
27 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "pip" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "daily"
12 | - package-ecosystem: "github-actions" # See documentation for possible values
13 | directory: "/" # Location of package manifests
14 | schedule:
15 | interval: "daily"
16 | - package-ecosystem: "docker" # See documentation for possible values
17 | directory: "/" # Location of package manifests
18 | schedule:
19 | interval: "daily"
20 |
--------------------------------------------------------------------------------
/.github/workflows/build-docker.yml:
--------------------------------------------------------------------------------
1 | # This workflow uses actions that are not certified by GitHub.
2 | # They are provided by a third-party and are governed by
3 | # separate terms of service, privacy policy, and support
4 | # documentation.
5 |
6 | name: Create and publish ghcr.io/beardedtek/fevr Docker image
7 |
8 | on:
9 | release:
10 | types: [published]
11 |
12 | env:
13 | REGISTRY: ghcr.io
14 | IMAGE_NAME: beardedtek-com/fevr
15 |
16 | jobs:
17 | build-and-push-image:
18 | runs-on: ubuntu-latest
19 | permissions:
20 | contents: read
21 | packages: write
22 |
23 | steps:
24 | - name: Checkout repository
25 | uses: actions/checkout@v3
26 |
27 | - name: log into docker hub
28 | uses: docker/login-action@v2
29 | with:
30 | registry: docker.io
31 | username: ${{ secrets.DOCKER_BEARDEDTEK_USER }}
32 | password: ${{ secrets.DOCKER_BEARDEDTEK_PASSWORD }}
33 |
34 | - name: log into docker.beardedtek.com
35 | uses: docker/login-action@v2
36 | with:
37 | registry: docker.beardedtek.com
38 | username: ${{ secrets.DOCKER_BEARDEDTEK_USER }}
39 | password: ${{ secrets.DOCKER_BEARDEDTEK_PASSWORD }}
40 |
41 | - name: Log in to the Container registry
42 | uses: docker/login-action@49ed152c8eca782a232dede0303416e8f356c37b
43 | with:
44 | registry: ${{ env.REGISTRY }}
45 | username: ${{ github.actor }}
46 | password: ${{ secrets.GITHUB_TOKEN }}
47 |
48 | - name: Extract metadata (tags, labels) for Docker
49 | id: meta
50 | uses: docker/metadata-action@69f6fc9d46f2f8bf0d5491e4aabe0bb8c6a4678a
51 | with:
52 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
53 |
54 | - name: Build and push Docker image
55 | uses: docker/build-push-action@c84f38281176d4c9cdb1626ffafcd6b3911b5d94
56 | with:
57 | context: .
58 | push: true
59 | tags: ${{ steps.meta.outputs.tags }},ghcr.io/beardedtek-com/fevr:0.6,ghcr.io/beardedtek-com/fevr:v0.6,beardedtek/fevr:0.6,beardedtek/fevr:v0.6,beardedtek/fevr:latest,beardedtek/fevr:main
60 | labels: ${{ steps.meta.outputs.labels }}
61 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ main, v0.6 ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ main, v0.6]
20 | schedule:
21 | - cron: '27 7 * * 1'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'javascript', 'python' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support
38 |
39 | steps:
40 | - name: Checkout repository
41 | uses: actions/checkout@v3
42 |
43 | # Initializes the CodeQL tools for scanning.
44 | - name: Initialize CodeQL
45 | uses: github/codeql-action/init@v2
46 | with:
47 | languages: ${{ matrix.language }}
48 | # If you wish to specify custom queries, you can do so here or in a config file.
49 | # By default, queries listed here will override any specified in a config file.
50 | # Prefix the list here with "+" to use these queries and those in the config file.
51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
52 |
53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
54 | # If this step fails, then you should remove it and run the build manually (see below)
55 | - name: Autobuild
56 | uses: github/codeql-action/autobuild@v2
57 |
58 | # ℹ️ Command-line programs to run using the OS shell.
59 | # 📚 https://git.io/JvXDl
60 |
61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
62 | # and modify them (or add more) to build your code if your project
63 | # uses a compiled language
64 |
65 | #- run: |
66 | # make bootstrap
67 | # make release
68 |
69 | - name: Perform CodeQL Analysis
70 | uses: github/codeql-action/analyze@v2
71 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | venv
3 | app/static/events/*
4 | app/*.sqlite
5 | .cache
6 | .local
7 | .bash_history
8 | .env
9 | __pycache__
10 | .python_history
11 | .cookies
12 | vol
13 | run_mqtt_client.sh
14 | events/*
15 | data/*
16 | test
17 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # This code is a portion of frigate Event Video Recorder (fEVR)
2 | #
3 | # Copyright (C) 2021-2022 The Bearded Tek (http://www.beardedtek.com) William Kenny
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU Affero General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU AfferoGeneral Public License
16 | # along with this program. If not, see .
17 |
18 | FROM docker.beardedtek.com/beardedtek-com/fevr-base:0.6
19 | COPY . /fevr
20 | COPY rootfs /
21 | RUN chown -R fevr /fevr
22 | WORKDIR /fevr
23 | EXPOSE 5090
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # fEVR - frigate Event Video Recorder
2 |
3 | ## Development has ceased on this project. Frigate has matured to a point where I feel this is no longer necessary.
4 |
5 | [](https://github.com/BeardedTek-com/fevr/blob/0.1.0/LICENSE)
6 | [](https://t.me/BeardedTekfEVR)
7 | [](https://github.com/BeardedTek-com/fEVR/discussions)
8 | [](https://github.com/BeardedTek-com/fEVR/releases)
9 | [](https://drone.beardedtek.com/BeardedTek-com/fEVR)
10 | [](https://hub.docker.com/r/beardedtek/fevr)
11 | [](https://twitter.com/intent/tweet?url=https%3A%2F%2Ffevr.video&text=AI%20Object%20Detection%20with%20fEVR%20-%20frigate%20Event%20Video%20Recorder)
12 | [](https://twitter.com/intent/user?screen_name=beardedtek)
13 |
14 | [](http://pinterest.com/pin/create/button/?url=http%3A%2F%2Ffevr.video&media=&description=AI%20Object%20Detection%20with%20fEVR%20-%20frigate%20Event%20Video%20Recorder)
15 | [](https://reddit.com/submit?url=https://fevr.video&title=AI%20Object%20Detection%20with%20fEVR%20-%20frigate%20Event%20Video%20Recorder)
16 | [](http://www.linkedin.com/shareArticle?mini=true&url=http%3A%2F%2Ffevr.video&title=AI%20Object%20Detection%20with%20fEVR%20-%20frigate%20Event%20Video%20Recorder)
17 | [](https://www.paypal.com/donate/?hosted_button_id=ZAHLQF24WAKES)
18 | [](https://github.com/sponsors/BeardedTek-com)
19 | [](https://tallyco.in/s/waqwip/)
20 |
21 | fEVR works along side of [frigate](https://frigate.video) to collect video and snapshots of objects detected using your existing camera systems.
22 |
23 |
24 |
25 |
26 | fEVR v0.6 Screenshots
27 |
28 |
29 | # Own Your Home's Security
30 |
31 | fEVR allows you to own your home's camera system. Instead of paying multiple cloud providers varying rates to perform object detection and recording, bring them all into fEVR in your very own open source self-hosted solution! Google, Wyze, Ring, and varying Tuya based cameras all use your data AND want to charge you to store it in the cloud.
32 |
33 |
34 |
35 |
36 | Feature comparison to leading cloud event detection providers
37 |
38 |
39 | ---
40 |
41 | # Requirements:
42 | - Frigate fully setup and working
43 | - MQTT Broker (if you have frigate running, you have this) listening to 0.0.0.0
44 | - This caused me many headaches, hopefully it saves you some hair pulling.
45 | It allows mqtt clients on different subnets to access the broker.
46 | If setup within your local lan this does not alone open up external access, only to other subnets which already have access.
47 | - Example mosquitto.conf listener section if using port 1883
48 | ```
49 | listener 1883 0.0.0.0
50 | ```
51 |
52 | ## Optional but nice:
53 | - Tailscale Account (for secure remote access)
54 |
55 | ---
56 |
57 | # Documentation
58 | A special thanks to @renarena for help with proofreading docs
59 |
60 | ## [Installation - docs/INSTALL.md](docs/INSTALL.md)
61 | ## [Setup - SETUP.md](docs/SETUP.md)
62 | ## [More Info on mqtt_client - MQTT_CLIENT.md](docs/MQTT_CLIENT.md)
63 |
64 | ## [Main API Calls](docs/API.md)
65 |
66 | ## [Tutorial Videos](https://beardedtek.net/c/tutorials/videos)
67 |
68 | ## [Notifications](docs/NOTIFICATIONS.md)
69 |
70 | ---
71 |
72 | # Support
73 | Please note, I will generally answer questions within 24 hours, and most times even faster unless I'm on vacation or going on adventures with the family.
74 |
75 | ## [Submit an Issue](https://github.com/BeardedTek-com/fEVR/issues)
76 | This is the preferred method if you find an error in the code or something that crashes fEVR.
77 |
78 | ## [Start a discussion](https://github.com/BeardedTek-com/fEVR/discussions)
79 | For discussing configuration issues or things that bug you (UI tweaks or process improvements)
80 |
81 | ## [Telegram Support Channel](https://t.me/BeardedTekfEVR)
82 | ## [Matrix Support Space](https://matrix.to/#/#fevrsupport:matrix.org)
83 | For troubleshooting, a quick question, or you just want to say hi!
84 |
85 |
86 | ---
87 | # Development
88 | ## Main Branch
89 | The main branch is the current release branch. When I do a release on 0.6 it will be merged to here.
90 |
91 | ## 0.6 Branch
92 | Each major version will have its own branch. This is the current gold standard for the newest release in 0.6
93 |
94 | ## 0.6-dev Branch
95 | This is the development branch for v0.6 Any changes will be added here before being merged with the 0.6 Branch
96 |
97 | ## 0.7-dev Branch
98 | This is the development branch for the next version. If it introduces breaking changes, it belongs here.
99 |
100 | # Releases
101 | ## Docker
102 | **docker compose** is the recommended method to deploy fEVR
103 |
104 | #### GitHub Container Repository ghcr.io (preferred location)
105 | ghcr.io/beardedtek-com/fevr
106 | #### Docker Hub
107 | beardedtek/fevr
108 |
109 | The following tags are available:
110 | - **RECOMMENDED**
111 | - latest
112 | - This contains the latest release in the current stable branch
113 |
114 | - 0.6
115 | - This contains the latest release in the 0.6 branch
116 |
117 | - **NOT RECOMMENDED**
118 | - 0.6-dev
119 | - Latest development image in the 0.6 branch. This could potentially change a couple times a day when under heavy development.
120 | - 0.7-dev **NOT RECOMMENDED**
121 | - Bleeding Edge and almost guaranteed to contain **breaking changes**
122 |
123 | ## PyPi
124 | ### **NOT RECOMMENDED**
125 | I'm starting to release some code on pypi as I break a few things apart for the 0.7 branch. Use at your own risk. It may or may not work as intended for now and breaking changes are certainly coming before this is an official release channel.
126 |
--------------------------------------------------------------------------------
/app/__init__.py:
--------------------------------------------------------------------------------
1 | # This code is a portion of frigate Event Video Recorder (fEVR)
2 | #
3 | # Copyright (C) 2021-2022 The Bearded Tek (http://www.beardedtek.com) William Kenny
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU Affero General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU AfferoGeneral Public License
16 | # along with this program. If not, see .
17 |
18 | # External Imports
19 | from flask import Flask, session
20 | from flask_sqlalchemy import SQLAlchemy
21 | import json
22 | from flask_login import LoginManager
23 | from datetime import timedelta
24 | import pytz
25 |
26 | # Config File
27 | configFile = "data/config"
28 |
29 | # Flask app Setup
30 | app = Flask(__name__)
31 | app.config.from_file('config.json',load=json.load)
32 |
33 |
34 |
35 | # Setup Session Timeout
36 | @app.before_request
37 | def before_request():
38 | session.permanent = True
39 | app.permanent_session_lifetime = timedelta(minutes=30)
40 |
41 | # Database Setup
42 | db = SQLAlchemy(app)
43 | app.SQLALCHEMY_TRACK_MODIFICATIONS=False
44 |
45 | from app.models.user import User
46 |
47 | # Flask Login Setup
48 | login_manager = LoginManager()
49 | login_manager.login_view = 'auth.login'
50 | login_manager.init_app(app)
51 |
52 | @login_manager.user_loader
53 | def load_user(user_id):
54 | return User.query.get(int(user_id))
55 |
56 | # Import Blueprints
57 | from app.blueprints.api import api as api_blueprint
58 | app.register_blueprint(api_blueprint)
59 |
60 | from app.blueprints.main import main as main_blueprint
61 | app.register_blueprint(main_blueprint)
62 |
63 | from app.blueprints.auth import auth as auth_blueprint
64 | app.register_blueprint(auth_blueprint)
65 |
66 | from app.blueprints.setup import setup as setup_blueprint
67 | app.register_blueprint(setup_blueprint)
68 |
69 |
70 | # Define Templates
71 | @app.template_filter('timezone')
72 | def convertTZ(time,clockFmt=12,Timezone="America/Anchorage"):
73 | dt_utc = time
74 | dt_utc = dt_utc.replace(tzinfo=pytz.UTC)
75 | dt = dt_utc.astimezone(pytz.timezone(Timezone))
76 | if clockFmt == 12:
77 | outformat = "%-m/%-d/%y %-I:%M:%S %p"
78 | else:
79 | outformat = "%-m/%-d/%y %H:%M:%S"
80 | outTime = dt.strftime(outformat).lower()
81 | return outTime
--------------------------------------------------------------------------------
/app/blueprints/api.py:
--------------------------------------------------------------------------------
1 | # This code is a portion of frigate Event Video Recorder (fEVR)
2 | #
3 | # Copyright (C) 2021-2022 The Bearded Tek (http://www.beardedtek.com) William Kenny
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU Affero General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU AfferoGeneral Public License
16 | # along with this program. If not, see .
17 |
18 | from flask import Blueprint, render_template, escape, redirect, url_for, jsonify, make_response
19 | from flask_login import login_required, current_user
20 | from flask_sqlalchemy import inspect
21 | from sqlalchemy import desc
22 | import subprocess
23 | from datetime import datetime
24 | import os
25 | import shutil
26 |
27 | from app.models.frigate import frigate
28 | from app.models.events import events
29 | from app.models.frigate import frigate
30 | from app.models.cameras import cameras
31 | from app import db
32 | from app.helpers.fetch import Fetch
33 | from app.helpers.cookies import cookies
34 | from app.helpers.iterateQuery import iterateQuery
35 | from app.helpers.logit import logit
36 |
37 | # API Routes
38 | api = Blueprint('api',__name__)
39 |
40 | @api.route('/routes')
41 | @login_required
42 | def apiHome():
43 | Cookies = cookies.getCookies(['menu','page'])
44 | cookiejar = {'page':'/'}
45 | title = "fEVR API Routes"
46 | routes = subprocess.Popen("flask routes", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0].decode("utf-8")
47 | contents = ""
48 | contents += f"
Method Path
\n"
49 | for count, line in enumerate(routes.split("\n")):
50 | if count > 2:
51 | method = line[29:36]
52 | link = line[38:].replace('<','<').replace('>','>')
53 | if (count % 2) == 0:
54 | contents += "
"
55 | else:
56 | contents += "
"
57 | contents += f"
{method} {link} \n"
58 | contents += "
"
59 | contents += "
"
60 | resp = render_template('api.html',menu=Cookies['menu'],page='/routes',title=title, contents=contents)
61 | return cookies.setCookies(cookiejar,make_response(resp))
62 |
63 | @api.route('/api/frigate/add/
///')
64 | def apiAddFrigate(name,http,ip,port):
65 | db.create_all()
66 | url = f"{http}://{ip}:{port}/"
67 | Frigate = frigate(name=name,url=url)
68 | db.session.add(Frigate)
69 | db.session.commit()
70 | return jsonify({'name':escape(name),'url':escape(url)})
71 |
72 | @api.route('/api/frigate')
73 | def apiFrigate():
74 | if inspect(db.engine).has_table("frigate"):
75 | db.create_all()
76 | query = frigate.query.all()
77 | return iterateQuery(query)
78 |
79 | @api.route('/api/events/add////')
80 | @login_required
81 | def apiAddEvent(eventid,camera,score,object):
82 | source = "fEVR | EVENT ADD"
83 | time = datetime.fromtimestamp(int(eventid.split('.')[0]))
84 | # Define default JSON return value
85 | rVal = {'error':0,
86 | 'msg':'OK',
87 | 'time':time,
88 | 'eventid':eventid,
89 | 'camera':camera,
90 | 'object':object,
91 | 'score':score}
92 | db.create_all()
93 | Cameras = cameras.query.filter_by(camera=camera).first()
94 | if Cameras:
95 | show = True if Cameras.show else False
96 | # Check if eventid already exists
97 | if events.query.filter_by(eventid=eventid).first():
98 | rVal["msg"] = 'Event Already Exists'
99 | rVal["error"] = 2
100 | else:
101 | try:
102 | fetchPath = f"{os.getcwd()}/app/static/events/{eventid}/"
103 | logit.execute(f"Fetching event into {fetchPath}",src=source)
104 | frigateConfig = apiFrigate()
105 | fetched = False
106 | for frigate in frigateConfig:
107 | logit.execute(f"Trying to fetch from {frigateConfig[frigate]['url']}",src=source)
108 | frigateURL = frigateConfig[frigate]["url"]
109 | Fetched = Fetch(fetchPath,eventid,frigateURL)
110 | logit.execute(f"Fetched {Fetched.event}", src=source)
111 | fetched = True
112 | if not fetched:
113 | rVal["msg"] = "Cannot Fetch"
114 | rVal["error"] = 3
115 | except Exception as e:
116 | rVal["error"] = 4
117 | rVal["msg"] = str(e).replace('"','')
118 | try:
119 | event = events(eventid=eventid,camera=camera,object=object,score=int(score),ack='',time=time,show=show)
120 | db.session.add(event)
121 | db.session.commit()
122 | except Exception as e:
123 | rVal["error"] = 5
124 | rVal["msg"] = str(e).replace('"','')
125 | else:
126 | rVal["msg"] = f"Camera '{camera}' Not Defined"
127 | rVal["error"] = 1
128 | return jsonify(rVal)
129 |
130 | @api.route('/api/events/ack/')
131 | @login_required
132 | def apiAckEvent(eventid):
133 | try:
134 | query = events.query.filter_by(eventid=eventid).first()
135 | query.ack = "true"
136 | db.session.commit()
137 | rVal = {'msg': 'Success'}
138 | except:
139 | rVal = {'error': 1, 'msg': 'Failed'}
140 | return jsonify(rVal)
141 |
142 | @api.route('/api/events/unack/')
143 | @login_required
144 | def apiUnackEvent(eventid):
145 | query = events.query.filter_by(eventid=eventid).first()
146 | query.ack = ""
147 | db.session.commit()
148 | return jsonify({'msg':'Success'})
149 |
150 | @api.route('/api/events/del/')
151 | @login_required
152 | def apiDelEvent(eventid):
153 | Cameras = cameras.query.all()
154 | cookiejar = {}
155 | cookiejar['menu'] = cookies.getCookie('menu') if cookies.getCookie('menu') else "closed"
156 | cookiejar['page'] = cookies.getCookie('page') if cookies.getCookie('page') else "/"
157 | cookiejar['cameras'] = str(Cameras)
158 | events.query.filter_by(eventid=eventid).delete()
159 | # Delete Event Files if they exist
160 | eventPath = f"{os.getcwd()}/app/static/events/{eventid}"
161 | if os.path.exists(eventPath):
162 | shutil.rmtree(eventPath)
163 | db.session.commit()
164 | return redirect(cookiejar['page'])
165 |
166 | @api.route('/api/events/latest')
167 | @login_required
168 | def apiShowLatest():
169 | if not inspect(db.engine).has_table("events"):
170 | db.create_all()
171 | query = events.query.order_by(desc(events.time)).limit(12).all()
172 | return iterateQuery(query)
173 |
174 | @api.route('/api/events/all')
175 | @login_required
176 | def apiShowAllEvents():
177 | if not inspect(db.engine).has_table("events"):
178 | db.create_all()
179 | query = events.query.order_by(desc(events.time)).all()
180 | return iterateQuery(query)
181 |
182 | @api.route('/api/event/')
183 | @login_required
184 | def apiSingleEvent(eventid):
185 | query = events.query.filter_by(eventid=eventid)
186 | return iterateQuery(query)
187 |
188 | @api.route('/api/events/camera/')
189 | @login_required
190 | def apiEventsByCamera(camera):
191 | query = events.query.filter_by(camera=camera)
192 | return iterateQuery(query)
193 |
194 | @api.route('/api/cameras/add///')
195 | @login_required
196 | def apiAddCamera(camera,server,show):
197 | db.create_all()
198 | hls = f"http://{server}:5084/{camera}"
199 | rtsp = f"rtsp://{server}:5082/{camera}"
200 | show = True if show == "true" or show == "True" else False
201 | camera = cameras(camera=camera,hls=hls,rtsp=rtsp,show=show)
202 | db.session.add(camera)
203 | db.session.commit()
204 | return jsonify({'msg': 'Camera Added Successfully'})
205 |
206 | @api.route('/api/cameras/')
207 | @login_required
208 | def apiCameras(camera):
209 | if not inspect(db.engine).has_table("cameras"):
210 | db.create_all()
211 | if camera == "all":
212 | query = cameras.query.all()
213 | else:
214 | query = cameras.query.filter_by(camera=camera)
215 | return iterateQuery(query)
216 |
--------------------------------------------------------------------------------
/app/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "SECRET_KEY" : "SECRET_KEY",
3 | "SESSION_COOKIE_NAME" : "fEVR_Session",
4 | "STATIC_FOLDER" : "static",
5 | "TEMPLATES_FOLDER" : "TEMPLATES_FOLDER",
6 | "DEBUG" : true,
7 | "TESTING" : true,
8 | "SQLALCHEMY_DATABASE_URI" : "sqlite:///data/fEVR.sqlite",
9 | "SQLALCHEMY_TRACK_MODIFICATIONS" : false
10 | }
--------------------------------------------------------------------------------
/app/data/.data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/app/data/.data
--------------------------------------------------------------------------------
/app/helpers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/app/helpers/__init__.py
--------------------------------------------------------------------------------
/app/helpers/cookies.py:
--------------------------------------------------------------------------------
1 | # This code is a portion of frigate Event Video Recorder (fEVR)
2 | #
3 | # Copyright (C) 2021-2022 The Bearded Tek (http://www.beardedtek.com) William Kenny
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU Affero General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU AfferoGeneral Public License
16 | # along with this program. If not, see .
17 |
18 | from flask import request, make_response
19 | from app.helpers.logit import logit
20 | class cookies:
21 | def getCookie(cookie):
22 | if request.cookies.get(cookie):
23 | return request.cookies.get(cookie)
24 | else:
25 | return None
26 |
27 | def getMenuCookie():
28 | if request.cookies.get('menu'):
29 | return request.cookies.get('menu')
30 | else:
31 | return "closed"
32 |
33 | def getCookies(jar):
34 | cookies = {}
35 | for cookie in jar:
36 | if cookie in request.cookies:
37 | cookies[cookie] = request.cookies.get(cookie)
38 | else:
39 | cookies[cookie] = None
40 | return cookies
41 |
42 | def setCookies(jar,resp):
43 | Response = resp
44 | for cookie in jar:
45 | Response.set_cookie(cookie,jar[cookie])
46 | return Response
--------------------------------------------------------------------------------
/app/helpers/drawSVG.py:
--------------------------------------------------------------------------------
1 | # This code is a portion of frigate Event Video Recorder (fEVR)
2 | #
3 | # Copyright (C) 2021-2022 The Bearded Tek (http://www.beardedtek.com) William Kenny
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU Affero General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU AfferoGeneral Public License
16 | # along with this program. If not, see .
17 | import svgwrite
18 | class drawSVG:
19 | svg = {
20 | 'text':'fEVR',
21 | 'stroke':'none',
22 | 'insert':(0,20),
23 | 'fill':'#000000',
24 | 'font':{
25 | 'family':'Arial',
26 | 'size':'20px',
27 | 'weight':'bold'
28 | }
29 | }
30 | def toString(svg=svg):
31 | dwg = svgwrite.Drawing('test2.svg', profile='tiny')
32 | dwg.add(dwg.text(svg['text'],
33 | insert=svg['insert'],
34 | stroke=svg['stroke'],
35 | fill=svg['fill'],
36 | font_size=svg['font']['size'],
37 | font_weight=svg['font']['weight'],
38 | font_family=svg['font']['family'])
39 | )
40 | return dwg.tostring()
--------------------------------------------------------------------------------
/app/helpers/fetch.py:
--------------------------------------------------------------------------------
1 | # This code is a portion of frigate Event Video Recorder (fEVR)
2 | #
3 | # Copyright (C) 2021-2022 The Bearded Tek (http://www.beardedtek.com) William Kenny
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU Affero General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU AfferoGeneral Public License
16 | # along with this program. If not, see .
17 |
18 | import os
19 | from PIL import Image
20 | import requests
21 | from app.helpers.logit import logit
22 |
23 | class Fetch:
24 | def __init__(self,path,eventid,frigate,thumbsize=180):
25 | self.path = path
26 | self.event = eventid
27 | self.frigate = frigate
28 | self.thumbSize = thumbsize
29 | self.thumbPATH = f"{self.path}/thumb.jpg"
30 | self.clipPATH = f"{self.path}/clip.mp4"
31 | self.snapPATH = f"{self.path}/snapshot.jpg"
32 | self.snap = f"{self.frigate}/api/events/{eventid}/snapshot.jpg".replace("//api","/api")
33 | self.clip = f"{self.frigate}/api/events/{eventid}/clip.mp4".replace("//api","/api")
34 | self.source = "fEVR | FETCH"
35 | self.getEvent()
36 | def getEvent(self):
37 | logit.execute(f"Getting {self.event}",src=self.source)
38 | try:
39 | if not os.path.exists(self.thumbPATH):
40 | if not os.path.exists(self.path):
41 | os.makedirs(self.path)
42 | else:
43 | rVal = {"error":3,"msg":f"{self.path} already exists"}
44 | with open(self.snapPATH,'wb') as snap:
45 | logit.execute(f"Fetching {self.snap} into {self.snapPATH}",src=self.source)
46 | snap.write(requests.get(self.snap, allow_redirects=True).content)
47 | resize = self.resizeImg(self.snapPATH,self.thumbSize)
48 | if resize["error"] != 0:
49 | rVal = {"error":4,"msg":f"{self.snapPATH} resize failed"}
50 | with open(self.clipPATH,'wb') as clip:
51 | logit.execute(f"Fetching {self.clip} into {self.snapPATH}",src=self.source)
52 | clip.write(requests.get(self.clip, allow_redirects=True).content)
53 | else:
54 | rVal = {"error": 1,"msg":f"{self.thumbPATH} already exists"}
55 | rVal = {"error": 0,"msg":f"Fetched {self.event} from {self.frigate}"}
56 | except Exception as err:
57 | rVal = {"error": 1,"msg":f"Failed to fetch {self.event} from frigate at {self.frigate}. Check settings or maybe Frigate is down?"}
58 | logit.execute(rVal['msg'] if rVal['error'] == 0 else rVal,src=self.source)
59 | return rVal
60 |
61 | def resizeImg(self,img,height=180,ratio=1.777777778):
62 | # Resizes an image from the filesystem
63 | if os.path.exists(img):
64 | Image.open(img).resize((int(height*ratio),height), Image.ANTIALIAS).save(self.thumbPATH,"JPEG", quality=75,optimize=True)
65 | rVal = {"error":0,"msg": "Image Resized"}
66 | else:
67 | rVal = {"error":1,"msg": "Image path does not exist"}
68 | logit.execute(rVal['msg'] if rVal['error'] == 0 else rVal,src=self.source)
69 | return rVal
70 |
71 |
--------------------------------------------------------------------------------
/app/helpers/getConfig.py:
--------------------------------------------------------------------------------
1 | # This code is a portion of frigate Event Video Recorder (fEVR)
2 | #
3 | # Copyright (C) 2021-2022 The Bearded Tek (http://www.beardedtek.com) William Kenny
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU Affero General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU AfferoGeneral Public License
16 | # along with this program. If not, see .
17 |
18 | import yaml
19 | from os import path, access, R_OK
20 | from app.helpers.logit import logit
21 | from app import configFile
22 | def getConfig(self,config):
23 | if path.isfile(config) and access(config, R_OK):
24 | with open(config) as configFile:
25 | try:
26 | Config = yaml.safe_load(configFile)
27 | except Exception as e:
28 | logit.execute(f"ERROR: {e}",src=self.script)
29 | Config = {}
30 | values = ['fevr_host','fevr_port','fevr_transport','mqtt_apikey','mqtt_broker','mqtt_port','mqtt_user','mqtt_password','mqtt_topics','verbose']
31 | for value in values:
32 | try:
33 | # Check if value exists
34 | test = Config[value]
35 | except KeyError:
36 | # If not, set it to None
37 | Config[value] = None
38 | Config["error"] = None
39 | else:
40 | if path.isfile(config):
41 | Config["error"] = "File not readable"
42 | Config["error"] = "File does not exist"
43 | return Config
44 |
45 | if __name__ == "__main__":
46 | print(getConfig(configFile))
--------------------------------------------------------------------------------
/app/helpers/include_file.py:
--------------------------------------------------------------------------------
1 | # This code is a portion of frigate Event Video Recorder (fEVR)
2 | #
3 | # Copyright (C) 2021-2022 The Bearded Tek (http://www.beardedtek.com) William Kenny
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU Affero General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU AfferoGeneral Public License
16 | # along with this program. If not, see .
17 |
18 | import jinja2
19 | class include_file:
20 | def include_file(name):
21 | return jinja2.Markup(loader.get_source(env, name)[0])
22 |
23 | loader = jinja2.PackageLoader(__name__, 'templates')
24 | env = jinja2.Environment(loader=loader)
25 | env.globals['include_file'] = include_file
26 |
27 | def render():
28 | return env.get_template('page.html').render()
29 |
30 | if __name__ == '__main__':
31 | print(include_file.render())
--------------------------------------------------------------------------------
/app/helpers/iterateQuery.py:
--------------------------------------------------------------------------------
1 | # This code is a portion of frigate Event Video Recorder (fEVR)
2 | #
3 | # Copyright (C) 2021-2022 The Bearded Tek (http://www.beardedtek.com) William Kenny
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU Affero General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU AfferoGeneral Public License
16 | # along with this program. If not, see .
17 |
18 | from flask import jsonify
19 |
20 | def iterateQuery(query):
21 | output={}
22 | if query:
23 | for q in query:
24 | output[q.id] = {}
25 | for item in q.__dict__:
26 | if not item.startswith('_') and item != "id":
27 | output[q.id][item] = q.__dict__[item]
28 | return output
--------------------------------------------------------------------------------
/app/helpers/logit.py:
--------------------------------------------------------------------------------
1 | # This code is a portion of frigate Event Video Recorder (fEVR)
2 | #
3 | # Copyright (C) 2021-2022 The Bearded Tek (http://www.beardedtek.com) William Kenny
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU Affero General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU AfferoGeneral Public License
16 | # along with this program. If not, see .
17 |
18 | from time import time
19 | import sys
20 |
21 | class logit:
22 | def execute(msg,src='fEVR',debug=True):
23 | def to_stderr(*a):
24 | print(*a, file=sys.stderr)
25 | logtime = "{:.2f}".format(time())
26 | logentry = f"{logtime} {str(msg)}"
27 | if debug:
28 | to_stderr(f"[ {src:16} ] {logentry}")
29 |
--------------------------------------------------------------------------------
/app/helpers/rndpwd.py:
--------------------------------------------------------------------------------
1 | # This code is a portion of frigate Event Video Recorder (fEVR)
2 | #
3 | # Copyright (C) 2021-2022 The Bearded Tek (http://www.beardedtek.com) William Kenny
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU Affero General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU AfferoGeneral Public License
16 | # along with this program. If not, see .
17 |
18 | from random import randint,choice
19 | import string
20 |
21 | class randpwd:
22 | def generate(count=None,key=False):
23 | if key == True:
24 | count = 128
25 | elif count == None and key == False:
26 | count = randint(10,24)
27 | elif count > 24 and key == False:
28 | count = 24
29 | elif count == 0 and key == False:
30 | count = randint(10,24)
31 |
32 | password = ""
33 | for x in range(count):
34 | num = randint(0,2)
35 | if num == 0:
36 | password += choice(string.ascii_lowercase)
37 | elif num == 1:
38 | password += choice(string.ascii_uppercase)
39 | elif num == 2:
40 | password += choice(string.digits)
41 | return password
42 |
43 | if __name__ == '__main__':
44 | print(randpwd.generate(key=True))
--------------------------------------------------------------------------------
/app/helpers/tz.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/app/helpers/tz.py
--------------------------------------------------------------------------------
/app/models/apiauth.py:
--------------------------------------------------------------------------------
1 | from app import db
2 | from flask_login import UserMixin
3 |
4 | class apiAuth(UserMixin,db.Model):
5 | id = db.Column(db.Integer, primary_key=True)
6 | name = db.Column(db.String(50),unique=True)
7 | key = db.Column(db.String(150),unique=True)
8 | authIP = db.Column(db.String(20))
9 | limit = db.Column(db.Integer)
10 | expired = db.Column(db.Boolean)
--------------------------------------------------------------------------------
/app/models/cameras.py:
--------------------------------------------------------------------------------
1 | from app import db
2 |
3 | class cameras(db.Model):
4 | # Table : cameras
5 | # Columns : - id (auto incrementing primary key)
6 | # : - camera (Camera Name)
7 | # : - hls (HLS stream URL)
8 | # : - rtsp (RTSP stream URL)
9 | id = db.Column(db.Integer,primary_key = True)
10 | camera = db.Column(db.String(20), unique = True)
11 | hls = db.Column(db.String(200))
12 | rtsp = db.Column(db.String(200))
13 | show = db.Column(db.Boolean)
--------------------------------------------------------------------------------
/app/models/config.py:
--------------------------------------------------------------------------------
1 | from app import db
2 |
3 | class config(db.Model):
4 | id = db.Column(db.Integer, primary_key=True)
5 | param = db.Column(db.String(50),unique=True)
6 | description = db.Column(db.String(500))
7 | value = db.Column(db.String(100))
--------------------------------------------------------------------------------
/app/models/events.py:
--------------------------------------------------------------------------------
1 | from app import db
2 | from datetime import datetime
3 |
4 | class events(db.Model):
5 | # Table : events
6 | # Columns : - id (auto incrementing primary key)
7 | # : - eventid (Event ID from Frigate)
8 | # : - time (DateTime generated from eventid timestamp)
9 | # : - camera (Camera which generated event)
10 | # : - object (Type of object detected)
11 | # : - score (Score generated by frigate (math.floor(frigate_score*100)))
12 | # : - ack (blank if unacknowledged, 'true' if acknowledged This is a string so that it can be set to "","ack","seen","delete")
13 | id = db.Column(db.Integer,primary_key = True)
14 | eventid = db.Column(db.String(25), unique = True)
15 | time = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
16 | camera = db.Column(db.String(50))
17 | object = db.Column(db.String(25))
18 | score = db.Column(db.Integer)
19 | ack = db.Column(db.String(10))
20 | show = db.Column(db.Boolean)
--------------------------------------------------------------------------------
/app/models/frigate.py:
--------------------------------------------------------------------------------
1 | from app import db
2 |
3 | class frigate(db.Model):
4 | # Table : frigate
5 | # Columns : - id (auto incrementing primary key)
6 | # : - url (URL of frigate instance ex: http://192.168.101.10:5000)
7 | # : - name (MQTT name of frigate instance)
8 | id = db.Column(db.Integer,primary_key = True)
9 | url = db.Column(db.String(200))
10 | name = db.Column(db.String(100), unique = True)
--------------------------------------------------------------------------------
/app/models/liveview.py:
--------------------------------------------------------------------------------
1 | from app import db
2 |
3 | class liveview(db.Model):
4 | # Table : liveview
5 | # Defines Live Camera View Layouts
6 | # Columns : - id (auto incrementing primary key)
7 | # : - name Name of layout
8 | # : - layout Layout style (2x2, 4x4, etc)
9 | # : - members Comma separated list of camera id (from cameras table)
10 | id = db.Column(db.Integer, primary_key=True)
11 | name = db.Column(db.String(10))
12 | layout = db.Column(db.String(10))
13 | members = db.Column(db.String(200))
--------------------------------------------------------------------------------
/app/models/user.py:
--------------------------------------------------------------------------------
1 | from app import db
2 | from flask_login import UserMixin
3 |
4 | class User(UserMixin,db.Model):
5 | id = db.Column(db.Integer, primary_key=True)
6 | email = db.Column(db.String(100), unique=True)
7 | password = db.Column(db.String(100))
8 | name = db.Column(db.String(1000))
9 | group = db.Column(db.String(35))
10 | enabled = db.Column(db.Boolean)
11 | resetpwd = db.Column(db.Boolean)
--------------------------------------------------------------------------------
/app/static/css/colors.css:
--------------------------------------------------------------------------------
1 | /* Classes defining colors here */
2 | /* background colors */
3 | .bg-brand{
4 | background-color: rgb(21, 31, 90);
5 | }
6 | .bg-charcoal{
7 | background-color: rgb(59, 66, 72);
8 | }
9 | .bg-dark{
10 | background-color: rgb(49, 55, 66);
11 | }
12 | .bg-dark-accent{
13 | background-color: rgb(94, 93, 104);
14 | }
15 | .bg-light-accent{
16 | background-color: rgb(160, 168, 170);
17 | }
18 | .bg-light-accent-40{
19 | background-color: rgba(160, 168, 170, 0.4);
20 | }
21 | .bg-light-accent-30{
22 | background-color: rgba(160, 168, 170, 0.3);
23 | }
24 | .bg-light{
25 | background-color: rgb(230, 230, 230);
26 | }
27 | .bg-light-40{
28 | background-color: rgba(230, 230, 230, 0.4);
29 | }
30 | /* border colors */
31 | .border-brand{
32 | border-color: rgb(21, 31, 90);
33 | }
34 | .border-charcoal{
35 | border-color: rgb(59, 66, 72)
36 | }
37 | .border-dark{
38 | border-color: rgb(49, 55, 66);
39 | }
40 | .border-dark-accent{
41 | border-color: rgb(94, 93, 104);
42 | }
43 | .border-light-accent{
44 | border-color: rgb(160, 168, 170);
45 | }
46 | .border-light{
47 | border-color: rgb(230, 230, 230);
48 | }
49 | /* text colors */
50 | .text-light{
51 | color: rgb(230, 230, 230);
52 | }
53 | .text-light-accent{
54 | color: rgb(160, 168, 170);
55 | }
56 | .text-dark-accent{
57 | color: rgb(94, 93, 104);
58 | }
59 | .text-brand{
60 | color: rgb(21, 31, 90);
61 | }
62 | .text-dark{
63 | color: rgb(49, 55, 66);
64 | }
65 | .text-charcoal{
66 | color: rgb(59, 66, 72);
67 | }
68 |
--------------------------------------------------------------------------------
/app/static/css/landscape.css:
--------------------------------------------------------------------------------
1 | @media screen and (min-height: 750px) {
2 | .content{
3 | font-size: 1em;
4 |
5 | }
6 | }
7 | @media screen and (max-height: 575px) {
8 | .content{
9 | font-size: 0.95em;
10 |
11 | }
12 | }
13 | @media screen and (max-height: 525px) {
14 | .content{
15 | font-size: 0.9em;
16 |
17 | }
18 | }
19 | @media screen and (max-height: 475px) {
20 | .content{
21 | font-size: 0.8em;
22 |
23 | }
24 | }
25 | @media screen and (max-height: 425px) {
26 | .content{
27 | font-size: 0.75em;
28 |
29 | }
30 | }
31 | @media screen and (max-height: 400px) {
32 | .content{
33 | font-size: 0.725em;
34 |
35 | }
36 | }
37 | @media screen and (max-height: 380px) {
38 | .content{
39 | font-size: 0.675em;
40 |
41 | }
42 | }
43 | @media screen and (max-height: 360px) {
44 | .content{
45 | font-size: 0.625em;
46 |
47 | }
48 | }
49 | @media screen and (max-height: 330px) {
50 | .content{
51 | font-size: 0.6em;
52 |
53 | }
54 | }
--------------------------------------------------------------------------------
/app/static/css/portrait.css:
--------------------------------------------------------------------------------
1 | @media screen and (max-width: 750px) {
2 | .navbar{
3 | height:3em;
4 | }
5 | }
6 | @media screen and (max-width: 575px) {
7 | body{
8 | font-size: 0.9em;
9 | }
10 | }
11 | @media screen and (max-width: 520px) {
12 | body{
13 | font-size: 0.8em;
14 | }
15 | }
16 | @media screen and (max-width: 465px) {
17 | body{
18 | font-size: 0.75em;
19 | }
20 | }
21 | @media screen and (max-width: 435px) {
22 | body{
23 | font-size: 0.7em;
24 | }
25 | }
26 | @media screen and (max-width: 405px) {
27 | body{
28 | font-size: 0.65em;
29 | }
30 | }
31 | @media screen and (max-width: 380px) {
32 | body{
33 | font-size: 0.6em;
34 | }
35 | }
36 | @media screen and (max-width: 350px) {
37 | body{
38 | font-size: 0.55em;
39 | }
40 | }
41 | @media screen and (max-width: 320px) {
42 | body{
43 | font-size: 0.5em;
44 | }
45 | }
46 |
47 | @media screen and (max-width: 300px) {
48 | body{
49 | font-size: 0.45em;
50 | }
51 | }
52 | @media screen and (max-width: 270px) {
53 | body{
54 | font-size: 0.4em;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/static/css/setup.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/app/static/css/setup.css
--------------------------------------------------------------------------------
/app/static/img/PayPal.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
11 |
17 |
21 |
26 |
32 |
34 |
41 |
46 |
50 |
55 |
56 |
--------------------------------------------------------------------------------
/app/static/img/PayPal.svg.2022_03_30_00_23_47.1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
62 |
63 |
--------------------------------------------------------------------------------
/app/static/img/back.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/static/img/beard-hover.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
19 |
39 |
47 |
48 |
50 |
55 |
61 |
62 |
67 |
71 |
75 |
79 |
83 |
87 |
91 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/app/static/img/beard.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
19 |
39 |
47 |
48 |
50 |
55 |
61 |
62 |
67 |
71 |
75 |
79 |
83 |
87 |
91 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/app/static/img/beardedtek-grey.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/app/static/img/beardedtek-grey.png
--------------------------------------------------------------------------------
/app/static/img/beardedtek-square.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/app/static/img/beardedtek-square.png
--------------------------------------------------------------------------------
/app/static/img/beardmenu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/app/static/img/beardmenu.png
--------------------------------------------------------------------------------
/app/static/img/bitcoin.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/static/img/close.svg:
--------------------------------------------------------------------------------
1 |
2 |
37 |
--------------------------------------------------------------------------------
/app/static/img/donate-bitcoin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/app/static/img/donate-bitcoin.png
--------------------------------------------------------------------------------
/app/static/img/fEVR-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/app/static/img/fEVR-192.png
--------------------------------------------------------------------------------
/app/static/img/fEVR-352.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/app/static/img/fEVR-352.png
--------------------------------------------------------------------------------
/app/static/img/fEVR-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/app/static/img/fEVR-512.png
--------------------------------------------------------------------------------
/app/static/img/fEVRgithubCard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/app/static/img/fEVRgithubCard.png
--------------------------------------------------------------------------------
/app/static/img/facebook-grey.svg:
--------------------------------------------------------------------------------
1 |
2 |
14 |
16 |
34 |
38 |
42 |
43 |
--------------------------------------------------------------------------------
/app/static/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/app/static/img/favicon.ico
--------------------------------------------------------------------------------
/app/static/img/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/app/static/img/favicon.png
--------------------------------------------------------------------------------
/app/static/img/fevr.svg:
--------------------------------------------------------------------------------
1 |
2 |
16 |
34 |
36 |
42 |
47 |
51 |
55 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/app/static/img/fevrdocs.svg:
--------------------------------------------------------------------------------
1 |
2 |
12 |
14 |
20 |
25 |
29 |
33 |
37 |
41 |
45 |
49 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/app/static/img/github-large.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
35 |
37 |
42 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/app/static/img/github-sponsor.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
35 |
37 |
42 |
51 |
52 |
57 |
61 | Sponsor
72 |
73 |
74 |
--------------------------------------------------------------------------------
/app/static/img/github.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/static/img/paypal-donate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/app/static/img/paypal-donate.png
--------------------------------------------------------------------------------
/app/static/img/paypal-small.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
62 |
63 |
--------------------------------------------------------------------------------
/app/static/img/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/app/static/img/screenshot.png
--------------------------------------------------------------------------------
/app/static/img/share-close.svg:
--------------------------------------------------------------------------------
1 |
2 |
37 |
--------------------------------------------------------------------------------
/app/static/img/share-fb.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
35 |
37 |
42 |
46 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/app/static/img/share-insta.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
35 |
37 |
42 |
46 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/app/static/img/share-linkedin.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
35 |
37 |
42 |
46 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/app/static/img/share-pin.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
35 |
37 |
42 |
46 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/app/static/img/share-reddit.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
35 |
37 |
42 |
46 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/app/static/img/share-twitter.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
35 |
37 |
42 |
46 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/app/static/img/share.svg:
--------------------------------------------------------------------------------
1 |
2 |
37 |
--------------------------------------------------------------------------------
/app/static/img/tallycoin-donate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/app/static/img/tallycoin-donate.png
--------------------------------------------------------------------------------
/app/static/js/main.js:
--------------------------------------------------------------------------------
1 | function clickIt(id){
2 | /* calls modalsActionOpen(id) or modalActionClose(id) from modal.js */
3 | if ( document.querySelector(id).classList.contains('hidden') ){
4 | modalActionOpen(id)
5 | }
6 | else{
7 | modalActionClose(id)
8 | }
9 | }
10 |
11 | function shareButton(id,btn,el){
12 | shareIcons = document.querySelector(id)
13 | shareDiv = document.querySelector(el)
14 | shareBtn = document.getElementById(btn)
15 | closeBtn = document.getElementById('closeBtn')
16 | if (window.getComputedStyle(shareIcons).getPropertyValue("opacity") == 0){
17 | shareIcons.style="opacity:1;"
18 | shareIcons.classList.remove('hidden')
19 | //shareDiv.style="background-color:rgba(240,255,255,0.3);border: solid 1px rgb(28,62,211); border-radius:2.5em;"
20 | shareBtn.classList.add('invisible')
21 | shareBtn.classList.remove('visible')
22 | shareBtn.classList.add('hidden')
23 | closeBtn.classList.remove('invisible')
24 | closeBtn.classList.add('visible')
25 | closeBtn.classList.remove('hidden')
26 | }
27 | else{
28 | shareIcons.style="opacity: 0; z-index: -1000";
29 | shareIcons.classList.add('hidden')
30 | //shareDiv.style = "border-radius:2.5em;";
31 | shareBtn.classList.remove('hidden')
32 | shareBtn.classList.add('visible')
33 | shareBtn.classList.remove('invisible')
34 | closeBtn.classList.add('hidden')
35 | closeBtn.classList.remove('visible')
36 | closeBtn.classList.add('invisible')
37 | }
38 | }
39 | function menu(Menu){
40 | m = document.getElementById(Menu);
41 | if (m.classList.contains('menuHide')){
42 | m.classList.remove('menuHide')
43 | document.cookie = "menu=open;path=/";
44 |
45 | }
46 | else{
47 | m.classList.add('menuHide')
48 | document.cookie = "menu=closed;path=/";
49 | }
50 | }
--------------------------------------------------------------------------------
/app/static/js/modal.js:
--------------------------------------------------------------------------------
1 |
2 | function modalActionOpen(id){
3 | var ua = navigator.userAgent.toLowerCase();
4 | if (ua.indexOf('safari') != -1) {
5 | if (ua.indexOf('chrome') > -1) {
6 | document.querySelector(id).classList.remove('hidden')
7 | document.querySelector(id).showModal()
8 | } else {
9 | document.querySelector(id).classList.remove('hidden')
10 | document.querySelector('.logo').classList.add('hidden')
11 | document.querySelector('.content').style="display: none;"
12 | document.querySelector(id).style="height: 80%; width: 90%; margin-left: 5%; margin-top: 10%;"
13 | }
14 | }
15 | }
16 |
17 | function modalActionClose(id){
18 | var ua = navigator.userAgent.toLowerCase();
19 | if (ua.indexOf('safari') != -1) {
20 | if (ua.indexOf('chrome') > -1) {
21 | document.querySelector(id).classList.add('hidden')
22 | document.querySelector(id).close()
23 | } else {
24 | document.querySelector(id).classList.add('hidden')
25 | document.querySelector('.logo').classList.remove('hidden')
26 | document.querySelector('.content').style=""
27 | document.querySelector(id).style=""
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/app/static/js/setup.js:
--------------------------------------------------------------------------------
1 | function fevrURLdetect(id){
2 | URL = window.location.href.split("://");
3 | https = false;
4 | if (URL[0] == "https"){
5 | https = true;
6 | }
7 | fqdn = URL[1].split("/")[0]
8 | if (Boolean(fqdn[1]) == true){
9 | fqdn = URL[1].split("/")[0].split(":")[0]
10 | }
11 | port = URL[1].split("/")[0].split(":")
12 | if (Boolean(port[1])){
13 | port = port[1]
14 | }
15 | else{
16 | if (https == true){
17 | port = 443
18 | }
19 | else{
20 | port = 80
21 | }
22 | }
23 | httpsID = id + "https"
24 | httpID = id + "http"
25 | fqdnID = id + "fqdn"
26 | portID = id + "port"
27 | if (https){
28 | document.getElementById(httpsID).selected = true;
29 | document.getElementById(httpID).selected = false;
30 | }
31 | else{
32 | document.getElementById(httpID).selected = true;
33 | document.getElementById(httpsID).selected = false;
34 | }
35 | document.getElementById(fqdnID).value = fqdn
36 | document.getElementById(portID).value = port
37 | }
--------------------------------------------------------------------------------
/app/templates/api.html:
--------------------------------------------------------------------------------
1 | {% extends "home.html" %}
2 |
3 | {% block content %}
4 |
5 | {{ contents|safe }}
6 |
7 | {% endblock %}
--------------------------------------------------------------------------------
/app/templates/event.html:
--------------------------------------------------------------------------------
1 | {% extends "home.html" %}
2 |
3 | {% block content %}
4 |
5 |
6 | {% if view == 'snap' or view == 'del' %}
7 |
8 | {% elif view == 'ack' or view == 'unack' %}
9 |
10 | {% elif view == 'clip' %}
11 |
12 |
13 |
14 | {% elif view == 'live' %}
15 |
16 | {% endif %}
17 |
NEW
18 |
19 |
20 | {% if view == 'del' %}
21 |
Are you sure you want to delete this event? it cannot be undone!!!!
22 |
Yes, Delete Event
23 |
Cancel
24 | {% else %}
25 | {% if view == 'clip' %}
26 |
Event Snapshot
27 |
Event Video Clip
28 | {% else %}
29 |
Event Video Clip
30 |
Event Snapshot
31 | {% endif %}
32 | {% if event['ack'] == "true" %}
33 |
Mark Unseen
34 | {% else %}
35 |
Mark Seen
36 | {% endif %}
37 |
Delete Event
38 | {% endif %}
39 |
40 |
41 | {% endblock %}
--------------------------------------------------------------------------------
/app/templates/events.html:
--------------------------------------------------------------------------------
1 | {% extends "home.html" %}
2 |
3 | {% block content %}
4 |
5 | {% if camera == "all" %}
6 |
7 |
8 |
9 |
10 |
11 | {% else %}
12 |
13 |
14 |
15 |
16 |
17 | {% endif %}
18 |
19 | {{ Pages["eventCount"] }} events total
20 |
21 |
22 | {% if Pages["prevURL"] %}
23 |
Prev
24 | {% else %}
25 | Prev
26 | {% endif %}
27 | Page {{ Pages["page"] }} of {{ Pages["pageCount"] }}
28 | {% if Pages["nextURL"] %}
29 |
Next
30 | {% else %}
31 | Next
32 | {% endif %}
33 |
34 |
35 |
36 | {% if events %}
37 | {% for event in events %}
38 | {% for Camera in cameras %}
39 | {% if event.camera == Camera.camera %}
40 | {% if Camera.show or camera != "all" %}
41 |
42 |
43 |
44 |
45 |
46 |
47 |
{{ event.time|timezone }}
48 |
49 |
{{ event.object }}
50 |
{{ event.score }}%
51 |
52 |
NEW
53 |
54 |
55 | {% endif %}
56 | {% endif %}
57 | {% endfor %}
58 | {% endfor %}
59 | {% else %}
60 |
66 | {% endif %}
67 | {% endblock %}
--------------------------------------------------------------------------------
/app/templates/home.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | fEVR by BeardedTek
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | {% import "menu.html" as menu %}
27 | {{ menu.navbar(current_user,page) }}
28 |
29 |
30 | {% if current_user.is_authenticated %}
31 |
32 |
38 |
48 | {{ menu.cameras(cameras) }}
49 |
50 | {% endif %}
51 |
52 |
53 |
54 | {% if event %}
55 |
{{ event.object|title }} in {{ event.camera|title }}
56 | {% if view == "clip" or view == "snap" %}
57 |
Event {{ view|title }}
58 | {% elif view == "live" %}
59 |
{{ view|title }}
60 | {% else %}
61 |
62 | {% endif %}
63 | {% endif %}
64 |
65 |
66 | {% block content %}
67 | {% endblock %}
68 |
69 |
70 |
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/app/templates/login.html:
--------------------------------------------------------------------------------
1 | {% extends "home.html" %}
2 |
3 | {% block content %}
4 |
5 |
6 |
7 |
11 |
12 |
13 | {% with messages = get_flashed_messages() %}
14 | {% if messages %}
15 |
16 | {{ messages[0]|replace('access this page',fwdName) }}
17 |
18 | {% else %}
19 |
20 |
21 | {% endif %}
22 |
23 | {% endwith %}
24 |
47 |
48 |
49 | {% endblock %}
--------------------------------------------------------------------------------
/app/templates/menu.html:
--------------------------------------------------------------------------------
1 | {% macro navbar(current_user,page) -%}
2 | {% if current_user.is_authenticated %}
3 |
4 |
5 |
6 |
7 | {% else %}
8 | Login
9 | Register
10 | {% endif %}
11 | {%- endmacro %}
12 |
13 | {% macro cameras(cameras) -%}
14 |
19 |
27 | {%- endmacro %}
--------------------------------------------------------------------------------
/app/templates/setup.html:
--------------------------------------------------------------------------------
1 | {% extends "home.html" %}
2 |
3 | {% block content %}
4 |
5 | {% with messages = get_flashed_messages() %}
6 | {% if messages %}
7 |
8 | {{ messages[0] }}
9 |
10 | {% else %}
11 |
12 |
13 | {% endif %}
14 | {% endwith %}
15 |
16 |
17 |
Admin
18 | {% for item in items['db'] %}
19 |
20 | {{item}}
21 |
22 | {% endfor %}
23 |
Next
24 |
25 |
26 | {% block setupContent %}
27 | {% endblock %}
28 |
29 |
30 | {% endblock %}
--------------------------------------------------------------------------------
/app/templates/setupadmin.html:
--------------------------------------------------------------------------------
1 | {% extends "setup.html" %}
2 |
3 | {% block setupContent %}
4 |
5 |
6 |
Admin Setup
7 | {% if admin %}
8 | Admin user already exists:
9 |
10 | Please contact {{admin[0]}} to be added as an admin.
11 |
12 |
{{admin[1]}}
13 | {% else %}
14 | Create the initial admin user.
15 |
16 |
17 | {% with messages = get_flashed_messages() %}
18 | {% if messages %}
19 |
20 | {{ messages[0] }}
21 |
22 | {% endif %}
23 | {% endwith %}
24 |
25 |
26 |
27 |
54 |
55 | {% endif%}
56 |
57 |
58 | {% endblock %}
--------------------------------------------------------------------------------
/app/templates/setupcameras.html:
--------------------------------------------------------------------------------
1 | {% extends "setup.html" %}
2 | {% block setupContent %}
3 |
4 |
5 | View/Edit/Add Cameras
6 |
7 |
15 |
16 | {% if user.group == "admin" %}
17 |
Active Cameras
18 | {% for camera in Cameras %}
19 |
20 |
35 | {% endfor %}
36 |
37 |
38 | {% endif %}
39 | {% endblock %}
--------------------------------------------------------------------------------
/app/templates/setupconfig.html:
--------------------------------------------------------------------------------
1 | {% extends "setup.html" %}
2 | {% block setupContent %}
3 |
10 | {% endblock %}
--------------------------------------------------------------------------------
/app/templates/setupfrigate.html:
--------------------------------------------------------------------------------
1 | {% extends "setup.html" %}
2 | {% block setupContent %}
3 |
4 |
5 | View/Edit/Add Frigate Instances
6 |
7 | Please provide the MQTT name and URL of your frigate instances.
8 |
9 | In an upcoming release multiple instances of Frigate will be supported.
10 |
11 |
12 |
17 |
18 | {% if user.group == "admin" %}
19 |
Active Frigate Instances
20 | {% for server in frigate %}
21 |
22 |
23 |
28 |
31 |
32 | {% endfor %}
33 |
34 |
35 | {% endif %}
36 | {% endblock %}
--------------------------------------------------------------------------------
/app/templates/setupmqtt.html:
--------------------------------------------------------------------------------
1 | {% extends "setup.html" %}
2 | {% block setupContent %}
3 |
4 |
5 |
6 | Setup MQTT Client
7 |
8 | Provide the following parameters to setup fEVR's MQTT Client. This is required to listen for Frigate's generated events.
9 |
10 | You need to generate an API Key in order for the mqtt client to function.
11 |
Click here to generate or view your API Keys.
12 |
13 | MQTT Topics is comma separated ex: frigate/+,frigate2/+,frigate3/+
14 |
15 |
16 |
28 |
29 | {% if user.group == "admin" %}
30 |
31 |
32 |
Current MQTT Client Command:
33 |
34 |
35 | {{ "No command configured" if not command }}
36 | {% if mqtt %}
37 |
38 |
39 | Current mqtt_client command:
40 |
41 |
{{ command }}
42 |
43 | {% endif %}
44 |
45 |
46 |
47 |
48 | {% endif %}
49 | {% endblock %}
--------------------------------------------------------------------------------
/app/templates/setupuser.html:
--------------------------------------------------------------------------------
1 | {% extends "setup.html" %}
2 | {% block setupContent %}
3 |
4 |
5 | View/Edit/Add Frigate Instances
6 |
7 |
12 |
13 | {% if user.group == "admin" %}
14 |
Active Frigate Instances
15 | {% for server in frigate %}
16 |
17 |
24 | {% endfor %}
25 |
26 |
27 | {% endif %}
28 | {% endblock %}
--------------------------------------------------------------------------------
/app/templates/signup.html:
--------------------------------------------------------------------------------
1 | {% extends "home.html" %}
2 |
3 | {% block content %}
4 |
5 |
6 |
7 |
8 |
12 | {% with messages = get_flashed_messages() %}
13 | {% if messages %}
14 |
15 | {{ messages[0]|safe }}
16 |
17 | {% endif %}
18 | {% endwith %}
19 |
20 |
21 |
42 |
43 |
44 | {% endblock %}
45 |
--------------------------------------------------------------------------------
/app/templates/user.html:
--------------------------------------------------------------------------------
1 | {% extends "home.html" %}
2 |
3 | {% block content %}
4 |
5 |
6 |
{{ user.name }}
7 | View/Edit your profile information
8 |
9 |
10 | {% with messages = get_flashed_messages() %}
11 | {% if messages %}
12 |
13 | {{ messages[0] }}
14 |
15 | {% endif %}
16 | {% endwith %}
17 |
18 |
19 |
20 |
57 |
58 |
59 | {% if user.group == "admin" %}
60 |
61 |
New API Auth Key
62 |
63 |
64 |
70 |
71 |
72 |
73 |
Active Keys
74 |
75 |
Name
76 |
IPv4 Addr
77 |
Limit
78 |
Expire
79 |
Del
80 |
81 | {% for key in keys %}
82 |
83 |
{{ key.name }}
84 |
{{ key.authIP }}
85 |
{{ key.limit }}
86 | {% if key.expired == None %}
87 | {% set expired = False %}
88 | {% else %}
89 | {% set expired = key.expired%}
90 | {% endif %}
91 |
{{ expired }}
92 |
93 |
{{ key.key }}
94 |
95 | {% endfor %}
96 |
97 | {% endif %}
98 | {% endblock %}
99 |
--------------------------------------------------------------------------------
/config.yml.template:
--------------------------------------------------------------------------------
1 | fevr_host: localhost
2 | fevr_port: 5090
3 | fevr_transport: http://
4 | mqtt_broker: mqtt
5 | mqtt_port: 1883
6 | mqtt_user: ~
7 | mqtt_password: ~
8 | mqtt_topics:
9 | - frigate/available
10 | - frigate/events
11 | - frigate/stats
12 | mqtt_apikey: 128-bit-apikey-from-fevr
13 | verbose: true
14 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2.4'
2 | services:
3 | fevr:
4 | image: ghcr.io/beardedtek-com/fevr:0.6
5 | container_name: fevr
6 | restart: unless-stopped
7 | privileged: true
8 | ports:
9 | - 5090:${FEVR_PORT:-5090}
10 | volumes:
11 | - ./events:/fevr/app/static/events
12 | - ./data:/fevr/app/data
13 | - ./varlib:/var/lib
14 | environment:
15 | FEVR_DEVELOPMENT: ${FEVR_DEVELOPMENT:-false}
16 | TAILSCALE_ENABLE: ${TAILSCALE_ENABLE:-false}
17 | TAILSCALE_AUTHKEY: ${TAILSCALE_AUTHKEY}
18 | TAILSCALE_HOSTNAME: ${TAILSCALE_HOSTNAME:-fevr}
19 | TAILSCALE_TAGS: ${TAILSCALE_TAGS}
20 |
--------------------------------------------------------------------------------
/docs/API.md:
--------------------------------------------------------------------------------
1 | # fEVR API
2 |
3 | ## **NOTE**:
4 | Throughout the documentation, any url for fEVR will be listed as ```http://fevr:5090/```. It is assumed that you will replace this url for your setup.
5 |
6 | ## Authentication
7 | In order use the API, you must be logged into fEVR. This can be accomplished by regular login if accessed via a browser, or using an Auth Key.
8 | ### Admin Login
9 | - Login as you normally would in any supported web browser at ```http://fevr:5090/login```
10 | ### Auth Key
11 | Used to login via the command line or other non-interactive means. You need to use a cookie jar to save an reuse the session cookie provided by fEVR
12 | Auth Keys can be created at ```http://fevr:5090/profile```
13 | ```
14 | curl -X Post http://fevr:5090/apiAuth \
15 | -c /tmp/fevr_cookiejar
16 | -H 'Content-Type: application/json' \
17 | -d '{"key":"FEVR_AUTH_KEY"}'
18 | ```
19 | Each subsequent call to the api should reference the cookie jar created. See API calls below for use.
20 |
21 | ### **/auth/add/key/``/``/``**
22 | Adds an API Key to fEVR with the following parameters:
23 | - name: Name to reference the API Key
24 | - ip: IP Addresses allowed to access key (CIDR Format: 0.0.0.0/0 for all)
25 | - limit: How many times the key can be used (0 for unlimited)
26 | - if limit is set greater than 1, each time the key is used, this value will be decreased by 1. If it reaches 0, it will be decreased to -1 and disabled
27 |
28 | ### **/auth/add/key [POST]**
29 | Adds an API Key using form data with the same parameters as above.
30 |
31 |
32 | ## Setup
33 |
34 | ### **/api/frigate/add/``/``/``/``**
35 | Adds an instance of frigate
36 | - name: Name as defined to MQTT broker
37 | - external: if set to external, this is the externally viewable address for frigate (not recommended as it exposes frigate with no authentication)
38 | - http: set to `http` or `https`
39 | - ip: IP Address or URL of the frigate server
40 | - port: Port number frigate is running on (usually 5000)
41 | ```
42 | curl http://fevr:5090/api/frigate/add/frigate/http/frigate/5000 \
43 | -c /tmp/fevr_cookiejar
44 | ```
45 | Returns:
46 | ```
47 | {
48 | "name": "frigate",
49 | "url": "http://frigate:5000/"
50 | }
51 | ```
52 |
53 | ### **/api/cameras/add/``/``/``**
54 | Adds a camera to fEVR
55 | - camera: camera name
56 | - server: ip or url of rtsp-simple-server
57 | - rtsp-simple server is not required for fEVR functionality
58 | - Future releases will further integrate with rtsp-simple-server or some custom solution. If you wish to future proof, you can use the server name ```rtsp```.
59 | - show: show in all/latest views (useful for indoor or sensitive areas)
60 | - set to "true" or "True" otherwise it will be False.
61 | ```
62 | curl http://fevr:5090/api/cameras/add/front/rtsp/true \
63 | -c /tmp/fevr_cookiejar
64 | ```
65 |
66 | ## Adding or Modifying Events
67 |
68 | ### **/api/events/add/``/``/``/``**
69 | Adds an event from Frigate
70 |
71 | **NOTE**: This is what mqtt_client uses to insert data into fEVR. While you can insert data by looking at frigate, it is not necessary unless mqtt_client fails or you want to archive a non-qualified event.
72 | - eventid: Event ID from frigate
73 | - camera: Name of camera as defined in fEVR
74 | - object: Object name (person, vehicle, etc)
75 | - score: Score (as an integer) as provided by frigate
76 | ```
77 | curl http://fevr:5090/api/events/add/1657137967.016307-5qek8n/front/person/84 \
78 | -c /tmp/fevr_cookiejar
79 | ```
80 | Return if OK:
81 | ```
82 | {
83 | "camera": "front",
84 | "error": 0,
85 | "eventid": "1657137967.016307-5qek8n",
86 | "msg": "",
87 | "object": "person",
88 | "score": "84",
89 | "time": "Wed, 06 Jul 2022 20:06:07 GMT"
90 | }
91 | ```
92 | Return if Camera Not Defined:
93 | ```
94 | {
95 | "error": 1,
96 | "msg": "Camera Not Defined
97 | }
98 | ```
99 | Return if Event Exists:
100 | ```
101 | {
102 | "camera": "front",
103 | "error": 2,
104 | "eventid": "1657137967.016307-5qek8n",
105 | "msg": "Event Already Exists",
106 | "object": "person",
107 | "score": "84",
108 | "time": "Wed, 06 Jul 2022 20:06:07 GMT"
109 | }
110 | ```
111 | Return if Event Does Not Exist:
112 | ```
113 | {
114 | "error": 3,
115 | "msg": "Unable to Fetch Event"
116 | }
117 | ```
118 | ### **/api/events/ack/``**
119 | Acknowledge an Event
120 | - eventid: Event ID from frigate
121 | ```
122 | curl http://fevr:5090/api/events/ack/1657137967.016307-5qek8n \
123 | -c /tmp/fevr_cookiejar
124 | ```
125 | Return:
126 | ```
127 | {
128 | "msg": "Success"
129 | }
130 | ```
131 | Return if Failed:
132 | ```
133 | {
134 | "error": 1,
135 | "msg": "Failed"
136 | }
137 | ```
138 |
139 | ### **/api/events/unack/``**
140 | Unacknowledge an Event
141 | - eventid: Event ID from frigate
142 | ```
143 | curl http://fevr:5090/api/events/unack/1657137967.016307-5qek8n \
144 | -c /tmp/fevr_cookiejar
145 | ```
146 | Return:
147 | ```
148 | {
149 | "msg": "Success"
150 | }
151 | ```
152 | Return if Failed:
153 | ```
154 | {
155 | "error": 1,
156 | "msg": "Failed"
157 | }
158 | ```
159 |
160 | ## Retrieve Information
161 |
162 | ### **/api/frigate**
163 | Returns information about configured frigate instances
164 | ```
165 | curl http://fevr:5090/api/frigate \
166 | -c /tmp/fevr_cookiejar
167 | ```
168 | Returns:
169 | ```
170 | {
171 | "1": {
172 | "name": "frigate",
173 | "url": "http://frigate:5000"
174 | }
175 | }
176 | ```
177 |
178 | ### **/api/cameras/``**
179 | Returns information about cameras in JSON
180 | - use all to get info on all cameras
181 | - use camera name to get info on specific camera
182 | ```
183 | curl http://fevr:5090/api/cameras/front \
184 | -c /tmp/fevr_cookiejar
185 | ```
186 | Returns:
187 | ```
188 | {
189 | "front": {
190 | "camera": "front",
191 | "hls": "http://rtsp:5084/front",
192 | "id": 1,
193 | "rtsp": "rtsp://rtsp:5082/front",
194 | "show": true
195 | }
196 | }
197 | ```
198 |
199 | ### **/api/events/latest**
200 | Displays the last 12 events
201 | ```
202 | curl http://fevr:5090/api/events/latest \
203 | -c /tmp/fevr_cookiejar
204 | ```
205 | Return:
206 | ```
207 | {
208 | "8070": {
209 | "ack": "",
210 | "camera": "front",
211 | "eventid": "1657328017.442961-lpdap6",
212 | "object": "person",
213 | "score": 75,
214 | "show": false,
215 | "time": "Sat, 09 Jul 2022 00:53:37 GMT"
216 | },
217 |
218 | ...
219 |
220 | "8083": {
221 | "ack": "",
222 | "camera": "front",
223 | "eventid": "1657329656.072334-0sggw2",
224 | "object": "person",
225 | "score": 73,
226 | "show": false,
227 | "time": "Sat, 09 Jul 2022 01:20:56 GMT"
228 | }
229 | }
230 | ```
231 |
232 | ### **/api/events/all**
233 | Displays all events
234 | ```
235 | curl http://fevr:5090/api/events/latest \
236 | -c /tmp/fevr_cookiejar
237 | ```
238 | Return:
239 | ```
240 | {
241 | "1": {
242 | "ack": "",
243 | "camera": "front",
244 | "eventid": "1655439416.135376-md3t78",
245 | "object": "person",
246 | "score": 76,
247 | "show": false,
248 | "time": "Fri, 17 Jun 2022 04:16:56 GMT"
249 | },
250 |
251 | ...
252 |
253 | "8083": {
254 | "ack": "",
255 | "camera": "back",
256 | "eventid": "1657329686.811457-xkj95z",
257 | "object": "person",
258 | "score": 70,
259 | "show": false,
260 | "time": "Sat, 09 Jul 2022 01:21:26 GMT"
261 | }
262 | }
263 | ```
264 |
265 | ### **/api/events/camera/``**
266 | Displays all events from a camera
267 | ```
268 | curl http://fevr:5090/api/events/camera/front \
269 | -c /tmp/fevr_cookiejar
270 | ```
271 | Return:
272 | ```
273 | {
274 | "1": {
275 | "ack": "",
276 | "camera": "front",
277 | "eventid": "1655439416.135376-md3t78",
278 | "object": "person",
279 | "score": 76,
280 | "show": false,
281 | "time": "Fri, 17 Jun 2022 04:16:56 GMT"
282 | },
283 |
284 | ...
285 |
286 | "8084": {
287 | "ack": "",
288 | "camera": "front",
289 | "eventid": "1657329686.811457-xkj95z",
290 | "object": "person",
291 | "score": 70,
292 | "show": false,
293 | "time": "Sat, 09 Jul 2022 01:21:26 GMT"
294 | }
295 | }
296 | ```
297 | ### **/api/event/``**
298 | Displays information about an event
299 | - eventid: Event ID from frigate
300 | ```
301 | curl http://fevr:5090/api/event/1657329686.811457-xkj95z \
302 | -c /tmp/fevr_cookiejar
303 | ```
304 | Return:
305 | ```
306 | {
307 | "8084": {
308 | "ack": "",
309 | "camera": "back",
310 | "eventid": "1657329686.811457-xkj95z",
311 | "object": "person",
312 | "score": 70,
313 | "show": false,
314 | "time": "Sat, 09 Jul 2022 01:21:26 GMT"
315 | }
316 | }
317 | ```
318 |
319 | # **The following API Calls are intened to be used from the UI Only.**
320 |
321 | ## Deleting Events
322 |
323 | ### **/api/events/del/``**
324 | Deletes an Event
325 | **NOTE**: This api call is intended to be used from the UI only. There are no checks to acknowledge deleting this event.
326 | - eventid: Event ID from frigate
327 | ```
328 | curl http://fevr:5090/api/events/del/1657137967.016307-5qek8n \
329 | -c /tmp/fevr_cookiejar
330 | ```
331 | Return:
332 | ```
333 | Returns user to last page
334 | ```
335 |
--------------------------------------------------------------------------------
/docs/INSTALL.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | ## Docker Compose:
4 | docker-compose is the preferred installation method
5 | ## Create directory structure
6 | ```
7 | mkdir fevr
8 | cd fevr
9 | mkdir data
10 | mkdir events
11 | ```
12 |
13 | ### Create .env file
14 | A template .env file is available [on GitHub](https://raw.githubusercontent.com/BeardedTek-com/fEVR/main/template.env) and down below:
15 | NOTE: The IP addresses in the .env file are for internal bridge networking and SHOULD NOT be on the same subnet as your home network.
16 | The default values should serve you well.
17 |
18 | ```
19 | curl -o .env https://raw.githubusercontent.com/BeardedTek-com/fEVR/main/template.env
20 | nano .env
21 | ```
22 | ```
23 | ### fEVR Setup ######################################################
24 |
25 | # Set fevr in development mode using built in flask server (true/false)
26 | FEVR_DEVELOPMENT=false
27 |
28 | # Changes the port fEVR runs on DEFAULT: 5090
29 | FEVR_PORT=5090
30 |
31 | ### Tailscale #######################################################
32 |
33 | # Set to false to disable tailscale
34 | TAILSCALE_ENABLE=true
35 |
36 | TAILSCALE_TAGS=tag:fevr
37 | TAILSCALE_HOSTNAME=fevr
38 |
39 | # Obtain Auth Key from https://login.tailscale.com/admin/authkeys
40 | TAILSCALE_AUTHKEY=tskey-XXXXXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXX
41 | ```
42 | ### Edit docker-compose.yml
43 | A template docker.compose.yml file is provided [on GitHub](https://raw.githubusercontent.com/BeardedTek-com/fEVR/main/docker-compose.yml) and down below:
44 |
45 | ```
46 | cd ..
47 | curl -o docker-compose.yml https://raw.githubusercontent.com/BeardedTek-com/fEVR/main/docker-compose.yml
48 | nano docker-compose.yml
49 | ```
50 | ```
51 | version: '2.4'
52 | services:
53 | fevr:
54 | image: ghcr.io/beardedtek-com/fevr:0.6
55 | container_name: fevr
56 | restart: unless-stopped
57 | privileged: true
58 | ports:
59 | - 5090:${FEVR_PORT:-5090}
60 | volumes:
61 | - ./events:/fevr/app/static/events
62 | - ./data:/fevr/app/data
63 | - ./varlib:/var/lib
64 | environment:
65 | FEVR_DEVELOPMENT: ${FEVR_DEVELOPMENT:-false}
66 | TAILSCALE_ENABLE: ${TAILSCALE_ENABLE:-false}
67 | TAILSCALE_AUTHKEY: ${TAILSCALE_AUTHKEY}
68 | TAILSCALE_HOSTNAME: ${TAILSCALE_HOSTNAME:-fevr}
69 | TAILSCALE_TAGS: ${TAILSCALE_TAGS}
70 | ```
71 |
72 | ### Create config.yml for mqtt_client
73 | This config.yml is separate from fEVR as mqtt_client can run separately from fEVR.
74 | In most use cases, running inside the docker container will be sufficient.
75 | At least 1 mqtt_client instance needs to be running either in the docker container or on a separate host as long as it can communicate with the main fEVR instance.
76 | **config.yml should be placed in the defined volume for `data` listed in the docker-compose.yml file provided.**
77 |
78 | More Information on mqtt_client is available [here](https://ghost.fevr.video/mqtt-client)
79 |
80 | An example config.yml is provided [on GitHub](https://raw.githubusercontent.com/BeardedTek-com/fEVR/main/config.yml.template) and below
81 | ```
82 | cd data
83 | wget -o config.yml https://raw.githubusercontent.com/BeardedTek-com/fEVR/main/config.yml.template
84 | nano config.yml
85 | ```
86 | ```
87 | fevr_host: localhost
88 | fevr_port: 5090
89 | fevr_transport: http://
90 | mqtt_broker: mqtt
91 | mqtt_port: 1883
92 | mqtt_user: ~
93 | mqtt_password: ~
94 | mqtt_topics:
95 | - frigate/available
96 | - frigate/events
97 | - frigate/stats
98 | mqtt_apikey: 128-bit-apikey-from-fevr
99 | verbose: true
100 | ```
101 | ```
102 | cd ..
103 | ```
104 |
105 | ## Bring the system up:
106 | ```
107 | docker-compose up -d
108 | ```
--------------------------------------------------------------------------------
/docs/MQTT_CLIENT.md:
--------------------------------------------------------------------------------
1 | # mqtt_client
2 |
3 | mqtt_client is an integral part of fEVR. It functions as the link between fEVR and frigate.
4 |
5 | # Features
6 | - Can run on any host that can reach fEVR
7 | - Configurable by yaml, json, or command line arguments
8 |
9 | # What is it doing?
10 | - Loads config from either yaml, Json, command line arguments, or leaves the default
11 | - Priority:
12 | 1. Command Line Argument
13 | 2. Config File
14 | 3. Defaults
15 | - Defaults:
16 | - 'fevr': "localhost:5090"
17 | - 'mqtt_broker': "mqtt"
18 | - 'mqtt_port': 1883
19 | - 'mqtt_user': ''
20 | - 'mqtt_password': ''
21 | - 'mqtt_apikey': ''
22 | - 'verbose': False
23 | - 'fevr_transport': 'http://'
24 | - 'mqtt_topics': "frigate/+"
25 | - Connects to MQTT Broker
26 | - Subscribes to topics
27 | - Listens for frigate/events
28 | - When it sees an event type of "end" it connects to fEVR's API
29 | - sends an http request to **http(s)://``:``/api/events/add/``/``/``/``**
30 | - waits for a response from fEVR's API
31 | - goes back to listening
32 |
33 | # Why is it a separate program?
34 | - If mqtt_client fails, it can restart itself independently of fEVR so it does not take down the entire stack.
35 |
36 | # Configuration
37 |
38 | ## Yaml
39 | Here is a sample yaml configuration file:
40 | [yaml 1.2 specification](https://yaml.org/spec/1.2.2/)
41 | [yamllint.com](https://yamllint.com) - Test to see if your yaml is properly formatted.
42 | ```
43 | fevr_host: localhost
44 | fevr_port: 5090
45 | fevr_transport: http://
46 | mqtt_broker: mqtt
47 | mqtt_port: 1883
48 | mqtt_user: ~
49 | mqtt_password: ~
50 | mqtt_topics:
51 | - frigate/available
52 | - frigate/events
53 | - frigate/stats
54 | mqtt_apikey: 128-bit-apikey-from-fevr
55 | verbose: true
56 | ```
57 | ## Json
58 | Here is a sample Json configuration file:
59 | ```
60 | # See the docs for setup instructions
61 | {
62 | "fevr_host": "localhost",
63 | "fevr_port": 5090,
64 | "fevr_transport": "http://",
65 | "mqtt_broker": "mqtt",
66 | "mqtt_port": 1883,
67 | "mqtt_user": null,
68 | "mqtt_password": null,
69 | "mqtt_topics": ["frigate/available","frigate/events","frigate/stats"],
70 | "mqtt_apikey": "128-char-apikey-from-fevr",
71 | "verbose": true
72 | }
73 | ```
74 |
75 | ## Command Line
76 |
77 | **(venv) user@localhost:~> *mqtt_client -h***
78 | ```
79 | usage: mqtt_client [-h] [-c CONFIG] [-m MQTT] [-k KEY] [-p PORT] [-t TOPICS] [-u USER] [-P PASSWORD] [-f FEVR] [-s] [-v]
80 |
81 | options:
82 | -h, --help show this help message and exit
83 | -c CONFIG, --config CONFIG
84 | If set, uses command line options instead of the database (default: empty)
85 | -m MQTT, --mqtt MQTT MQTT Broker IP/FQDN (default: mqtt)
86 | -k KEY, --key KEY fEVR API Key (default: blank string)
87 | -p PORT, --port PORT MQTT Port (default: 1883)
88 | -t TOPICS, --topics TOPICS
89 | MQTT Topics (default: 'frigate/+')
90 | -u USER, --user USER MQTT Username (default: '')
91 | -P PASSWORD, --password PASSWORD
92 | MQTT Password (default: '')
93 | -f FEVR, --fevr FEVR fEVR IP Address/FQDN (default: '127.0.0.1:5090)
94 | -s, --https If set uses https:// for fEVR API Calls (default: False)
95 | -v, --verbose If set, outputs verbosely to stdout
96 | ```
--------------------------------------------------------------------------------
/docs/NOTIFICATIONS.md:
--------------------------------------------------------------------------------
1 | # Notifications
2 |
3 | ## More coming in 0.7
4 | In the next major version, I am introducing notifications through [ntfy.sh](https://ntfy.sh) and [apprise](https://github.com/caronc/apprise-api).
5 |
6 | Apprise has the capability to send notifications on over 80 different platforms including Home Assistant.
7 |
8 | ## Home Asisstant
9 | ### ***Depricated***
10 | ### While this was a usable procedure when originally written, I don't plan on providing support, help, or developing this further
11 | As noted above, notifications are coming in 0.7 using ntfy.sh and apprise. Home Assistant is changing way too fast to keep up with all the cool new ways to do things. Feel free to try this method, but no guarantees.
12 |
13 | ---
14 |
15 | Honestly, this is a bit of a pain in the rear. For each notification type you want for each camera, a helper entity must be added.
16 | For example, I have notifications setup for my driveway camera for person, animal, and vehicle, so I have the following helpers:
17 | - fevrDrivewayAnimal
18 | - fevrDrivewayCar
19 | - fevrDrivewayPerson
20 |
21 | The automation uses this helper entity for 2 purposes.
22 | - As a motion sensor
23 | - If the helper is on, that means a notification is active
24 | - As a pause for notifications
25 | - If the helper is on, it does not allow further notifications until it is turned off.
26 | - In the automation, this time can be adjusted to your liking
27 |
28 | Here is the automation I'm currently using:
29 | As displayed when:
30 | - editing the automation via the UI
31 | - click on overflow menu (3 dots)
32 | - click Edit in YAML
33 |
34 | NOTES:
35 | `<>` is the camera name
36 | `<>` is the entity you created for this notification
37 | `<>` is the url to your fevr instance
38 |
39 | ```
40 | alias: fEVR <> Person Alert
41 | description: fEVR Object Detection Alerts
42 | trigger:
43 | - platform: mqtt
44 | topic: frigate/events
45 | condition:
46 | - condition: template
47 | value_template: '{{ trigger.payload_json["type"] == "end" }}'
48 | - condition: template
49 | value_template: |-
50 | {{
51 | trigger.payload_json["after"]["label"] == "person"
52 | }}
53 | - condition: template
54 | value_template: |-
55 | {{
56 | trigger.payload_json["after"]["top_score"] > 0.76
57 | }}
58 | - condition: template
59 | value_template: |-
60 | {{
61 | trigger.payload_json["after"]["camera"] == "<>"
62 | }}
63 | action:
64 | - choose:
65 | - conditions:
66 | - condition: state
67 | state: 'off'
68 | entity_id: input_boolean.fevrbackyardperson
69 | sequence:
70 | - service: notify.mobile_app_sg20plus
71 | data:
72 | message: '{{ trigger.payload_json["after"]["label"] | title }} Detected'
73 | data:
74 | notification_icon: mdi:cctv
75 | ttl: 0
76 | priority: high
77 | sticky: true
78 | actions:
79 | - action: URI
80 | title: Clip
81 | uri: >-
82 | <>/event/{{trigger.payload_json['after']['id']}}/snap
83 | - action: URI
84 | title: Snapshot
85 | uri: >-
86 | <>/event/{{trigger.payload_json['after']['id']}}/snap
87 | image: >-
88 | <>/static/events/{{trigger.payload_json['after']['id']}}/snapshot.jpg
89 | tag: '{{trigger.payload_json["after"]["id"]}}'
90 | alert_once: true
91 | - service: input_boolean.turn_on
92 | data: {}
93 | target:
94 | entity_id: input_boolean.<>
95 | - delay:
96 | hours: 0
97 | minutes: 1
98 | seconds: 0
99 | milliseconds: 0
100 | - service: input_boolean.turn_off
101 | data: {}
102 | target:
103 | entity_id: input_boolean.<>
104 | default: []
105 | mode: single
106 | ```
107 |
--------------------------------------------------------------------------------
/docs/SETUP.md:
--------------------------------------------------------------------------------
1 | # Setup
2 |
3 | ### Visit http(s):///setup
4 |
5 |
6 | ### Create admin account
7 |
8 |
9 | ### Login to new admin account
10 |
11 |
12 | #### Add all of your cameras.
13 | Enter your camera's name as it is defined in Frigate.
14 | HLS and RTSP fields are not yet used, but will be in 0.7
15 |
16 | Click Next
17 |
18 | ***NOTE:** I recommend using an rtsp relay that way you only have 1 connection to your cameras, reducing bandwidth use and minimizing connections to your rtsp devices.*
19 |
20 | ##### Popular Open Source RTSP Relays:
21 | [rtsp-simple-server](https://github.com/aler9/rtsp-simple-server)
22 | [docker-wyze-bridge](https://github.com/mrlt8/docker-wyze-bridge) < uses rtsp-simple-server at its core
23 |
24 | ### Configure Frigate MQTT Settings
25 | Frigate allows you to change the mqtt topic for hosting multiple servers on the same mqtt broker. Unless you have changed this default behavior or have multiple frigate servers on one broker, name this entry frigate and fill in the server's details.
26 |
27 | Click Next
28 |
29 | ### Other
30 | Other is not populated yet, There are future plans for this page, just click Next again and you'll be brought to the main interface.
31 |
32 | ### Generate API Key for mqtt_client
33 | mqtt_client authenticates with fEVR using a 128 character API Key.
34 |
35 | You can generate an API Key from `/profile` or click on the beard icon to drop down the menu, and click Profile.
36 |
37 | Here you can view any API keys you have generated in the past and generate a new one.
38 |
39 | There are 3 fields to fill out:
40 | **Name:** A unique name to identify this key (no duplicates allowed)
41 | **IPv4 Address:** Not used as of now but required to be a valid IP or network (without cidr notation)
42 | - Enter 0.0.0.0 to future proof.
43 |
44 | **Login Count:** this can be used to limit the amount of times this API Key can be used. While not used at the moment, it will function as the base of a limited login solution (say one time passwords for law enforcement and the like)
45 | - Enter 0 for unlimited usage
46 |
47 | ### Configure mqtt_client
48 | Once you have your API Key you can generate your config file for the mqtt_client.
49 | - See [Installation](/installation) for an example
50 |
51 | This will be automated further in a future release.
--------------------------------------------------------------------------------
/docs/images/features.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/docs/images/features.webp
--------------------------------------------------------------------------------
/docs/images/hpf-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/docs/images/hpf-screenshot.png
--------------------------------------------------------------------------------
/docs/images/latest.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/docs/images/latest.webp
--------------------------------------------------------------------------------
/docs/images/menu.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/docs/images/menu.webp
--------------------------------------------------------------------------------
/docs/images/profile-apiauthkey-1.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/docs/images/profile-apiauthkey-1.webp
--------------------------------------------------------------------------------
/docs/images/profile-apiauthkey-2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/docs/images/profile-apiauthkey-2.webp
--------------------------------------------------------------------------------
/docs/images/profile-apiauthkey-3.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/docs/images/profile-apiauthkey-3.webp
--------------------------------------------------------------------------------
/docs/images/profile-apiauthkey-4.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/docs/images/profile-apiauthkey-4.webp
--------------------------------------------------------------------------------
/docs/images/profile-apiauthkey.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/docs/images/profile-apiauthkey.webp
--------------------------------------------------------------------------------
/docs/images/profile.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/docs/images/profile.webp
--------------------------------------------------------------------------------
/docs/images/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/docs/images/screenshot.png
--------------------------------------------------------------------------------
/docs/images/screenshot.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/docs/images/screenshot.webp
--------------------------------------------------------------------------------
/docs/images/setup-admin.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/docs/images/setup-admin.webp
--------------------------------------------------------------------------------
/docs/images/setup-cameras-blank.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/docs/images/setup-cameras-blank.webp
--------------------------------------------------------------------------------
/docs/images/setup-cameras.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/docs/images/setup-cameras.webp
--------------------------------------------------------------------------------
/docs/images/setup-frigate-blank.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/docs/images/setup-frigate-blank.webp
--------------------------------------------------------------------------------
/docs/images/setup-frigate.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/docs/images/setup-frigate.webp
--------------------------------------------------------------------------------
/docs/images/social.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/docs/images/social.png
--------------------------------------------------------------------------------
/docs/source_images/Google__G__Logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/docs/source_images/Wyze_Labs_Logo.svg:
--------------------------------------------------------------------------------
1 | Wyze_Labs_Logo_new
--------------------------------------------------------------------------------
/docs/source_images/fevr-title.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/docs/source_images/fevr-title.png
--------------------------------------------------------------------------------
/docs/source_images/frigate.video-banner.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/docs/source_images/frigate.video-banner.webp
--------------------------------------------------------------------------------
/docs/source_images/installation-tutorial.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/docs/source_images/installation-tutorial.webp
--------------------------------------------------------------------------------
/docs/source_images/installation-tutorial.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/docs/source_images/installation-tutorial.xcf
--------------------------------------------------------------------------------
/docs/source_images/main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/docs/source_images/main.png
--------------------------------------------------------------------------------
/docs/source_images/nest_aware.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/source_images/ring-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 | Asset 1
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/docs/source_images/ring_protect_plan_plus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeardedTek-com/fEVR/f8f8798763f78d31035520bb886431fb7c96c73e/docs/source_images/ring_protect_plan_plus.png
--------------------------------------------------------------------------------
/docs/source_images/wyze.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/source_images/wyze_cam_plus.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/fevr.py:
--------------------------------------------------------------------------------
1 | # This code is a portion of frigate Event Video Recorder (fEVR)
2 | #
3 | # Copyright (C) 2021-2022 The Bearded Tek (http://www.beardedtek.com) William Kenny
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU Affero General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU AfferoGeneral Public License
16 | # along with this program. If not, see .
17 |
18 | from app import app
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # This code is a portion of frigate Event Video Recorder (fEVR)
3 | #
4 | # Copyright (C) 2021-2022 The Bearded Tek (http://www.beardedtek.com) William Kenny
5 | #
6 | # This program is free software: you can redistribute it and/or modify
7 | # it under the terms of the GNU Affero General Public License as published by
8 | # the Free Software Foundation, either version 3 of the License, or
9 | # (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU AfferoGeneral Public License
17 | # along with this program. If not, see .
18 |
19 | release=$(lsb_release -d)
20 | if [ $(echo $release | awk -F' ' '{print $2}') == "Ubuntu" ]; then
21 | vers=$(echo $release | awk -F' ' '{print $3}')
22 | if [ $vers == "22.04" ] || [ $vers == "20.10" ] || [ $vers == "21.04" ] || \
23 | [ $vers == "20.04" ]; then
24 | echo "Installing for Ubuntu > 20.04"
25 | DEBIAN_FRONTEND=noninteractive sudo apt-get update && \
26 | sudo apt-get -y --no-install-recommends install python3.10 \
27 | python-is-python3 python3-pip
28 |
29 | python3 -m venv venv
30 | source venv/bin/activate
31 | python3 -m pip install -r requirements.txt
32 | fi
33 | elif [ $(echo $release | awk -F' ' '{print $2}') == "openSUSE" ]; then
34 | echo "Installing for openSUSE"
35 | sudo zypper --non-interactive --no-recommends install python310-pip
36 | if [ ! -d venv ]; then
37 | python3.10 -m venv venv
38 | fi
39 | python3.10 -m pip install -r requirements.txt
40 | else
41 | echo "Your distro is not supported yet."
42 | echo "Currently supported distros:"
43 | echo " - openSUSE: Tested on Tumbleweed"
44 | echo " - Ubuntu >= 22.04: Tested on 22.04 Server"
45 | echo ""
46 | echo "Requirements:"
47 | echo " - python >= 3.8 (3.10 recommended)"
48 | echo " - pip3"
49 | echo " - create virtual environment"
50 | echo " - pip install -r requirements.txt"
51 | fi
52 | pythonversion=$(python --version)
53 | pythonver=$(echo $pythonversion | awk -F' ' '{print $2}')
54 | pymaj=$(echo $pythonver | awk -F'.' '{print $1}')
55 | pymin=$(echo $pythonver | awk -F'.' '{print $2}')
56 |
57 | # Start checks to see if requirements are satisfied
58 | clear
59 | echo "fEVR Installation Script Results:"
60 | echo ""
61 | if [ $pymaj == "3" ]; then
62 | if [[ $pymin -gt 8 ]]; then
63 | echo -e "[\e[32mPASS\e[39m] $pythonver"
64 | pipver=$(pip3 --version)
65 | if [ $(echo $pipver | awk -F ' ' '{print $1}') ]; then
66 | echo -e "[\e[32mPASS\e[39m] $pipver"
67 | else
68 | echo -e "[\e[31mFAIL\e[39m] $pipver"
69 | installchk="FAIL"
70 | fi
71 | else
72 | echo -e "[\e[31mFAIL\e[39m] $pythonver"
73 | installchk="FAIL"
74 | fi
75 | fi
76 |
77 | if [ -d venv ]; then
78 | echo -e "[\e[32mPASS\e[39m] venv exists"
79 | uwsgiver=$(source venv/bin/activate && uwsgi --version)
80 | if [ $(echo $uwsgiver | awk -F'.' '{print $1}') == "2" ]; then
81 | echo -e "[\e[32mPASS\e[39m] $uwsgiver"
82 | fi
83 | else
84 | echo -e "[\e[31mFAIL\e[39m] venv does not exist"
85 | echo -e "[\e[31mFAIL\e[39m] No Virtual Environment"
86 | installchk="FAIL"
87 | fi
88 | echo ""
89 | if [ "$installchk" == "FAIL" ]; then
90 | echo -e "[\e[31mFAIL\e[39m] Checks failed"
91 | echo ""
92 | echo "Please correct failures above manually."
93 | else
94 | echo -e "[\e[32mPASS\e[39m] Installation Successful"
95 | fi
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | requests~=2.28.1
2 | Pillow~=9.2.0
3 | paho-mqtt~=1.6.1
4 | Flask~=2.2.2
5 | flasK_sqlalchemy~=2.5.1
6 | python-dotenv~=0.20.0
7 | flask-login~=0.6.2
8 | IPy
9 | svgwrite
10 | python-dateutil
11 | pytz
12 | uwsgi
13 | netifaces
14 | PyYAML
15 | rtspscanner~=0.1.3
--------------------------------------------------------------------------------
/rootfs/etc/services.d/fevr/finish:
--------------------------------------------------------------------------------
1 | #!/usr/bin/execlineb -S1
2 | # ==============================================================================
3 | # Take down the S6 supervision tree when example fails
4 | # s6-overlay docs: https://github.com/just-containers/s6-overlay
5 | # ==============================================================================
6 | if { s6-test ${1} -ne 0 }
7 | if { s6-test ${1} -ne 256 }
8 |
9 | s6-svscanctl -t /var/run/s6/services
10 |
--------------------------------------------------------------------------------
/rootfs/etc/services.d/fevr/run:
--------------------------------------------------------------------------------
1 | #!/usr/bin/with-contenv bash
2 | # This code is a portion of frigate Event Video Recorder (fEVR)
3 | #
4 | # Copyright (C) 2021-2022 The Bearded Tek (http://www.beardedtek.com) William Kenny
5 | #
6 | # This program is free software: you can redistribute it and/or modify
7 | # it under the terms of the GNU Affero General Public License as published by
8 | # the Free Software Foundation, either version 3 of the License, or
9 | # (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU AfferoGeneral Public License
17 | # along with this program. If not, see .
18 |
19 | # Enter our working directory
20 | cd /fevr
21 |
22 | # Make sure /fevr has the right permissions
23 | chown -R 1000:1000 /fevr
24 | # Check if Virtual Environment is initialized
25 | echo "Python Virtual Environment already installed."
26 | echo "Activating Python Virtual Environment"
27 | source /fevr/venv/bin/activate
28 |
29 | FEVR_HOST=${FEVR_HOST:-0.0.0.0}
30 | FEVR_PORT=${FEVR_PORT:-5090}
31 | UWSGI_WORKERS=${UWSGI_WORKERS:-4}
32 | echo "Starting fEVR on $FEVR_HOST:$FEVR_PORT with $UWSGI_WORKERS workers."
33 | FEVR_DEVELOPMENT=${FEVR_DEVELOPMENT}
34 | if [ "$FEVR_DEVELOPMENT" == "true" ]; then
35 | export FLASK_ENV='development'
36 | /fevr/venv/bin/flask run -h "0.0.0.0" -p $FEVR_PORT
37 | else
38 | export UWSGI_STARTING=1
39 | uwsgi --http $FEVR_HOST:$FEVR_PORT --wsgi-file fevr.py --callable app --workers $UWSGI_WORKERS --uid 1000 --gid 1000
40 | fi
--------------------------------------------------------------------------------
/rootfs/etc/services.d/mqtt_client/finish:
--------------------------------------------------------------------------------
1 | #!/usr/bin/execlineb -S1
2 | # ==============================================================================
3 | # Take down the S6 supervision tree when example fails
4 | # s6-overlay docs: https://github.com/just-containers/s6-overlay
5 | # ==============================================================================
--------------------------------------------------------------------------------
/rootfs/etc/services.d/mqtt_client/run:
--------------------------------------------------------------------------------
1 | #!/usr/bin/with-contenv bash
2 | # This code is a portion of frigate Event Video Recorder (fEVR)
3 | #
4 | # Copyright (C) 2021-2022 The Bearded Tek (http://www.beardedtek.com) William Kenny
5 | #
6 | # This program is free software: you can redistribute it and/or modify
7 | # it under the terms of the GNU Affero General Public License as published by
8 | # the Free Software Foundation, either version 3 of the License, or
9 | # (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU AfferoGeneral Public License
17 | # along with this program. If not, see .
18 |
19 | while :; do
20 | if [ -f "/fevr/app/data/config.yml" ]; then
21 | /fevr/venv/bin/python /fevr/app/mqtt_client -c "/fevr/app/data/config.yml"
22 | elif [ -f "/fevr/app/data/config" ]; then
23 | /fevr/venv/bin/python /fevr/app/mqtt_client -c "/fevr/app/data/config"
24 | fi
25 | done
26 |
--------------------------------------------------------------------------------
/rootfs/etc/services.d/tailscale/run:
--------------------------------------------------------------------------------
1 | #!/usr/bin/with-contenv bash
2 | # This code is a portion of frigate Event Video Recorder (fEVR)
3 | #
4 | # Copyright (C) 2021-2022 The Bearded Tek (http://www.beardedtek.com) William Kenny
5 | #
6 | # This program is free software: you can redistribute it and/or modify
7 | # it under the terms of the GNU Affero General Public License as published by
8 | # the Free Software Foundation, either version 3 of the License, or
9 | # (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU AfferoGeneral Public License
17 | # along with this program. If not, see .
18 |
19 | if [ "$TAILSCALE_ENABLE" == "true" ]; then
20 | # Get local docker network to forward so all containers are accessible via tailscale
21 |
22 | if [ ! $TAILSCALE_ADVERTISE_ROUTE ]; then
23 | TAILSCALE_ADVERTISE_ROUTE=$(getnetwork)
24 | fi
25 | # Enable IP forwarding
26 | rm -f /etc/sysctl.conf
27 | touch /etc/sysctl.conf
28 | echo 'net.ipv4.ip_forward = 1' | tee -a /etc/sysctl.conf 2> /dev/null
29 | echo 'net.ipv6.conf.all.forwarding = 1' | tee -a /etc/sysctl.conf 2> /dev/null
30 | sysctl -p /etc/sysctl.conf
31 |
32 | #Start tailscaled
33 | tailscaled &
34 |
35 | # Wait until tailscaled is up...
36 | OK=1
37 | while [ $OK != 0 ]; do
38 | if ps | grep tailscaled | > /tmp/test.txt; then OK=0; else OK=1; fi
39 | done
40 |
41 | # Bring up tailscale
42 | tailscale up --authkey="${TAILSCALE_AUTHKEY}" \
43 | --advertise-routes=${TAILSCALE_ADVERTISE_ROUTE} \
44 | --hostname=${TAILSCALE_HOSTNAME:-fEVR} \
45 | --advertise-tags=${TAILSCALE_TAGS} \
46 | --accept-routes \
47 | --exit-node=${TAILSCALE_EXIT_NODE} \
48 | --accept-dns \
49 | --reset
50 |
51 | # Run a loop to make sure it doesn't exit out. Will expand here more eventually
52 | TS=0
53 | while [ $TS == 0 ]; do
54 | if [[ ! $(ip a show tailscale0 up | grep inet) ]] ;then
55 | tailscale up --authkey="${TAILSCALE_AUTHKEY}" \
56 | --advertise-routes=${TAILSCALE_ADVERTISE_ROUTE} \
57 | --hostname=${TAILSCALE_HOSTNAME:-fEVR} \
58 | --advertise-tags=${TAILSCALE_TAGS} \
59 | --accept-routes \
60 | --exit-node=${TAILSCALE_EXIT_NODE} \
61 | --accept-dns \
62 | --reset
63 | fi
64 | sleep 30s
65 | done
66 | fi
67 |
--------------------------------------------------------------------------------
/run_fevr.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # This code is a portion of frigate Event Video Recorder (fEVR)
3 | #
4 | # Copyright (C) 2021-2022 The Bearded Tek (http://www.beardedtek.com) William Kenny
5 | #
6 | # This program is free software: you can redistribute it and/or modify
7 | # it under the terms of the GNU Affero General Public License as published by
8 | # the Free Software Foundation, either version 3 of the License, or
9 | # (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU AfferoGeneral Public License
17 | # along with this program. If not, see .
18 |
19 | [ ! -d "/fevr/venv" ] && \
20 | echo "Initializing Virtual Environment" && \
21 | python -m venv venv && \
22 | echo "Starting Virtual Environment" && \
23 | source /fevr/venv/bin/activate \
24 | || \
25 | echo "Python Virtual Environment already installed."
26 | echo "Activating Python Virtual Environment"
27 | source /fevr/venv/bin/activate
28 |
29 | echo "Starting fEVR"
30 | uwsgi --http 127.0.0.1:${FEVR_PORT:-5090} --wsgi-file fevr.py --callable app --processes ${UWSGI_PROCESSES:-8} --threads ${UWSGI_THREADS:-4} &
31 |
32 | TS=0
33 | while [ $TS == 0 ]; do
34 | [ -f "run_mqtt_client.sh" ] && ./run_mqtt_client.sh && sleep 30
35 | done
36 |
--------------------------------------------------------------------------------
/template.env:
--------------------------------------------------------------------------------
1 | ### fEVR Setup ######################################################
2 |
3 | # Set fevr in development mode using built in flask server (true/false)
4 | FEVR_DEVELOPMENT=false
5 |
6 | # Changes the port fEVR runs on DEFAULT: 5090
7 | FEVR_PORT=5090
8 |
9 | ### Tailscale #######################################################
10 |
11 | # Set to false to disable tailscale
12 | TAILSCALE_ENABLE=true
13 |
14 | TAILSCALE_TAGS=tag:fevr
15 | TAILSCALE_HOSTNAME=fevr
16 |
17 | # Obtain Auth Key from https://login.tailscale.com/admin/authkeys
18 | TAILSCALE_AUTHKEY=tskey-XXXXXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXX
--------------------------------------------------------------------------------
/uwsgi.ini:
--------------------------------------------------------------------------------
1 | [uwsgi]
2 | wsgi-file = fevr.py
3 | callable = app
4 | uid = 1000
5 | gid = 1000
6 | single-interpreter = true
7 | enable-threads = true
8 | master = true
9 |
10 | static-map = /static=/fevr/app/static
11 | static-expires = /* 7776000
12 | offload-threads = %k
13 |
--------------------------------------------------------------------------------