├── -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 | [![license](https://img.shields.io/github/license/beardedtek-com/fevr)](https://github.com/BeardedTek-com/fevr/blob/0.1.0/LICENSE) 6 | [![telegram](https://img.shields.io/badge/Support-Telegram-blue)](https://t.me/BeardedTekfEVR) 7 | [![Discussions](https://img.shields.io/github/discussions/beardedtek-com/fevr)](https://github.com/BeardedTek-com/fEVR/discussions) 8 | [![commits since last release](https://img.shields.io/github/commits-since/beardedtek-com/fevr/latest?include_prereleases)](https://github.com/BeardedTek-com/fEVR/releases) 9 | [![Build Status](https://drone.beardedtek.com/api/badges/BeardedTek-com/fEVR/status.svg)](https://drone.beardedtek.com/BeardedTek-com/fEVR) 10 | [![Image Size](https://img.shields.io/docker/image-size/beardedtek/fevr)](https://hub.docker.com/r/beardedtek/fevr) 11 | [![Twitter URL](https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Fgithub.com%2Fbeardedtek-com%2Ffevr)](https://twitter.com/intent/tweet?url=https%3A%2F%2Ffevr.video&text=AI%20Object%20Detection%20with%20fEVR%20-%20frigate%20Event%20Video%20Recorder) 12 | [![twitter-follow](https://img.shields.io/twitter/follow/beardedtek?style=social)](https://twitter.com/intent/user?screen_name=beardedtek) 13 | 14 | [![Pinterest](https://img.shields.io/badge/Share-Pin%20It!-e60023)](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 | [![Reddit](https://img.shields.io/badge/Share-Reddit-orange)](https://reddit.com/submit?url=https://fevr.video&title=AI%20Object%20Detection%20with%20fEVR%20-%20frigate%20Event%20Video%20Recorder) 16 | [![LinkedIn](https://img.shields.io/badge/Share-LinkedIn-blue)](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 | [![Donate - Paypal](https://img.shields.io/badge/Donate-Paypal-0070e0)](https://www.paypal.com/donate/?hosted_button_id=ZAHLQF24WAKES) 18 | [![Donate - GitHub](https://img.shields.io/badge/Donate-GitHub%20Sponsors-blue)](https://github.com/sponsors/BeardedTek-com) 19 | [![Donate - Tallyco.in](https://img.shields.io/badge/Donate-Tallyco.in-fdc948)](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 | fEVR v0.6 Screenshots 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 | Feature comparison to leading cloud event detection providers 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 | 61 | -------------------------------------------------------------------------------- /app/static/img/fevrdocs.svg: -------------------------------------------------------------------------------- 1 | 2 | 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 | {{event.eventid}} - {{event.object}} in {{event.camera}} 8 | {% elif view == 'ack' or view == 'unack' %} 9 | {{event.eventid}} - {{event.object}} in {{event.camera}} 10 | {% elif view == 'clip' %} 11 | 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 | 23 | 24 | {% else %} 25 | {% if view == 'clip' %} 26 | 27 | 28 | {% else %} 29 | 30 | 31 | {% endif %} 32 | {% if event['ack'] == "true" %} 33 | 34 | {% else %} 35 | 36 | {% endif %} 37 | 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 | {{event.eventid}} - {{event.object}} in {{event.camera}} 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 |
61 |
62 |
63 | Sorry, No events found matching your criteria.

64 | Please go back. 65 |
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 | 30 | {% if current_user.is_authenticated %} 31 | 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 | 49 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/menu.html: -------------------------------------------------------------------------------- 1 | {% macro navbar(current_user,page) -%} 2 | {% if current_user.is_authenticated %} 3 | 6 | 7 | {% else %} 8 | 9 | 10 | {% endif %} 11 | {%- endmacro %} 12 | 13 | {% macro cameras(cameras) -%} 14 |
15 | {% for camera in cameras %} 16 | 17 | {% endfor %} 18 |
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 | 18 | {% for item in items['db'] %} 19 |
20 | {{item}} 21 |
22 | {% endfor %} 23 | 24 |
25 |
26 | {% block setupContent %} 27 | {% endblock %} 28 |
29 |
30 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/setupadmin.html: -------------------------------------------------------------------------------- 1 | {% extends "setup.html" %} 2 | 58 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/setupcameras.html: -------------------------------------------------------------------------------- 1 | {% extends "setup.html" %} 2 | {% block setupContent %} 3 |
4 | 37 |
38 | {% endif %} 39 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/setupconfig.html: -------------------------------------------------------------------------------- 1 | {% extends "setup.html" %} 2 | {% block setupContent %} 3 |
4 | 9 |
10 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/setupfrigate.html: -------------------------------------------------------------------------------- 1 | {% extends "setup.html" %} 2 | {% block setupContent %} 3 |
4 | 34 |
35 | {% endif %} 36 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/setupmqtt.html: -------------------------------------------------------------------------------- 1 | {% extends "setup.html" %} 2 | {% block setupContent %} 3 | 4 |
5 | 47 |
48 | {% endif %} 49 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/setupuser.html: -------------------------------------------------------------------------------- 1 | {% extends "setup.html" %} 2 | {% block setupContent %} 3 |
4 | 26 |
27 | {% endif %} 28 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/signup.html: -------------------------------------------------------------------------------- 1 | {% extends "home.html" %} 2 | -------------------------------------------------------------------------------- /app/templates/user.html: -------------------------------------------------------------------------------- 1 | {% extends "home.html" %} 2 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------