first commit

This commit is contained in:
2026-04-20 08:26:37 +02:00
commit cce2b9bc39
21 changed files with 14944 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
venv
__pycache__
timelapse
static/output
archive
output
app/config/cameras.yaml
+75
View File
@@ -0,0 +1,75 @@
# CameraCrawler
This is a flask website to display Images from Webcams and timelaps, dependen on a .yaml config.
## Sample cameras.yaml
```yaml
- name: "Dummy" # name of camera
url: "" # camera-image url
interval: 5 # Capture intervall in minutes
timelapse: True # create timelapse every night
website:
picture: True # display camera on website
timelapse: True # display timelapse on website
- name: "Dummy"
url: ""
interval: 5
timelapse: True
website:
picture: True
timelapse: True
```
## Sample docker-compose.yml
```yaml
version: '3.4'
services:
cam-flask:
build: ./website
restart: unless-stopped
volumes:
- ./cam/logs:/logs:rw
- ./cam/output:/static/output:ro
- /etc/localtime:/etc/localtime:ro
environment:
- TZ="Europe/Berlin"
command: gunicorn -w 1
-b :8000 app:app \
--access-logfile ./logs/log.txt \
--log-level info \
--timeout 90 \
--workers 25 \
--worker-class gevent
labels:
- "traefik.enable=true"
- "traefik.http.routers.cam.rule=Host(`cam.domain.com`)"
- "traefik.http.routers.cam.entrypoints=websecure"
- "traefik.http.services.cam.loadbalancer.server.port=8000"
- "traefik.http.routers.cam.service=cam"
- "traefik.http.routers.cam.tls.certresolver=production"
networks:
- traefik_default
cam-crawler:
build: ./crawler
restart: unless-stopped
volumes:
- ./cam/timelapse:/archive:rw
- ./cam/output:/output:rw
- ./cam/config:/config:ro
- /etc/localtime:/etc/localtime:ro
command: python app.py
networks:
traefik_default:
external: true
```
+59
View File
@@ -0,0 +1,59 @@
from flask import Flask, send_file, render_template, request, abort
from prometheus_flask_exporter import PrometheusMetrics
import logging
import time
import os
import yaml
os.environ['TZ'] = "Europe/Berlin"
time.tzset()
logging.basicConfig(
filename='logs/flask.log',
encoding='utf-8',
level=logging.INFO,
format='%(asctime)s %(levelname)s:%(message)s'
)
with open("config/cameras.yaml", "r") as yamlfile:
config = yaml.load(yamlfile, Loader=yaml.FullLoader)
print("camera config read successful")
#print(yaml)
app = Flask(__name__)
metrics = PrometheusMetrics(app)
metrics.info('app_info', 'Application info', version='1.0.3')
#@app.errorhandler(500)
#def internal_error(error):
#
# return "500 error"
@app.errorhandler(404)
def not_found(error):
filename = 'static/404.jpg'
return send_file(filename, mimetype='image/jpg'), 404
@app.route('/error',methods = ['POST', 'GET'])
def errorTest():
abort(500)
@app.route('/',methods = ['POST', 'GET'])
def index():
return render_template('index.html', config=config)
#@app.route('/BirdStalker')
#def get_image():
# filename = 'static/BirdStalker.jpg'
# return send_file(filename, mimetype='image/jpg')
# main driver function
if __name__ == '__main__':
app.run()
+15
View File
@@ -0,0 +1,15 @@
- name: "Dummy" # name of camera
url: "" # camera-image url
interval: 5 # Capture intervall in minutes
timelapse: True # create timelapse every night
website:
picture: True # display camera on website
timelapse: True # display timelapse on website
- name: "Dummy"
url: ""
interval: 5
timelapse: True
website:
picture: True
timelapse: True
+5
View File
@@ -0,0 +1,5 @@
Flask
gunicorn
pyyaml
requests
prometheus-flask-exporter
Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 408 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 766 KiB

+78
View File
@@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="de" data-bs-theme="dark">
<head>
<meta charset='UTF-8'>
<title>{% block title %}Birdstalker{% endblock %}</title>
<meta name="robots" content="noindex">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<link rel="icon" type="image/vnd.icon" href="/static/favicon.ico">
<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.0/css/all.min.css'>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
</head>
<body>
{% for camera in config%}
{% if camera.website.picture %}
<div class="webcam">
<h1>{{ camera.name }}</h1>
<img src="/static/output/{{camera.name}}.jpg" />
{% if camera.website.timelapse and camera.timelapse %}
<h2>Gestern:</h2>
<video controls width="100%">
<source src="/static/output/{{camera.name}}.mp4" type="video/mp4" />
Download the
<a href="/static/output/{{camera.name}}.mp4">MP4</a>
video.
</video>
{% endif %}
</div>
{% endif %}
{%endfor%}
</body>
</html>
<style>
video {
background-color: hsla(0,0%,0%,.3);
}
body {
color: black;
background-color: white ;
line-height: 5px;
font-family: Arial, sans-serif;
background: url(/static/images/background.svg) no-repeat center center fixed;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
}
a {
line-height:normal;
}
.webcam {
max-width: 900px;
background-color: hsla(40,70%,60%,.9);
padding-left: 10px;
padding-right: 10px;
padding-bottom: 10px;
border-radius: 9px;
border: 1px solid black;
margin-bottom: 10px;
}
.webcam img {
width: 100%;
}
</style>
<script>
</script>
+13
View File
@@ -0,0 +1,13 @@
# syntax=docker/dockerfile:1
FROM python:3.10
COPY app/ .
#COPY requirements.txt requirements.txt
#RUN apt-get update && apt-get install ffmpeg -y
RUN pip3 install -r requirements.txt
CMD ["python", "app.py"]
#CMD ["gunicorn" , "-b", "0.0.0.0:8000" , "app:app"]