Anleitungen erstellt und Bildrate einstellbar

This commit is contained in:
2026-05-25 17:09:31 +02:00
parent 3207b58772
commit 023017d1fb
10 changed files with 663 additions and 36 deletions
@@ -63,6 +63,8 @@ CAMERA_PROFILES = [
]
CAMERA_PROFILE_MAP = {profile['id']: profile for profile in CAMERA_PROFILES}
DEFAULT_CAMERA_PROFILE_ID = '1296x972'
CAMERA_FPS_OPTIONS = [1, 2, 3, 4, 5, 7, 10, 20]
DEFAULT_CAMERA_FPS = 10
class ReusableThreadingTCPServer(socketserver.ThreadingTCPServer):
@@ -292,6 +294,7 @@ def collect_status():
},
'services': {
'camera': get_systemd_state('exomy-camera-stream.service'),
'camera_settings_version': read_camera_settings_version(),
'admin_api': get_systemd_state('exomy-admin-api.service'),
'exomy': get_container_state(EXOMY_CONTAINER),
}
@@ -363,6 +366,16 @@ def normalize_camera_profile(profile_id):
return dict(profile)
def normalize_camera_fps(value):
try:
fps = int(value)
except (TypeError, ValueError):
raise ValueError('Unbekannte Bildrate')
if fps not in CAMERA_FPS_OPTIONS:
raise ValueError('Unbekannte Bildrate')
return fps
def read_camera_settings():
default_profile = get_default_camera_profile()
try:
@@ -371,37 +384,58 @@ def read_camera_settings():
except (FileNotFoundError, json.JSONDecodeError, OSError, ValueError, TypeError):
return default_profile
selected_profile_id = str(payload.get('profile', '')).strip()
if selected_profile_id in CAMERA_PROFILE_MAP:
return dict(CAMERA_PROFILE_MAP[selected_profile_id])
try:
width = int(payload.get('width', default_profile['width']))
height = int(payload.get('height', default_profile['height']))
fps = int(payload.get('fps', default_profile['fps']))
fps = normalize_camera_fps(payload.get('fps', DEFAULT_CAMERA_FPS))
except (TypeError, ValueError):
return default_profile
fps = DEFAULT_CAMERA_FPS
for profile in CAMERA_PROFILES:
if profile['width'] == width and profile['height'] == height and profile['fps'] == fps:
return dict(profile)
return default_profile
selected_profile_id = str(payload.get('profile', '')).strip()
if selected_profile_id in CAMERA_PROFILE_MAP:
profile = dict(CAMERA_PROFILE_MAP[selected_profile_id])
else:
profile = None
for item in CAMERA_PROFILES:
if item['width'] == width and item['height'] == height:
profile = dict(item)
break
if profile is None:
profile = default_profile
profile['fps'] = fps
return profile
def write_camera_settings(profile):
def write_camera_settings(profile, fps):
profile = dict(profile)
fps = normalize_camera_fps(fps)
os.makedirs(os.path.dirname(CAMERA_SETTINGS_FILE), exist_ok=True)
payload = {
'profile': profile['id'],
'width': profile['width'],
'height': profile['height'],
'fps': profile['fps'],
'fps': fps,
'updated_at': int(time.time()),
}
with open(CAMERA_SETTINGS_FILE, 'w', encoding='utf-8') as handle:
json.dump(payload, handle, indent=2, sort_keys=True)
handle.write('\n')
def read_camera_settings_version():
try:
with open(CAMERA_SETTINGS_FILE, 'r', encoding='utf-8') as handle:
payload = json.load(handle)
except (FileNotFoundError, json.JSONDecodeError, OSError, ValueError, TypeError):
return 0
try:
return int(payload.get('updated_at', 0))
except (TypeError, ValueError):
return 0
def get_camera_config():
profile = read_camera_settings()
return {
@@ -411,14 +445,23 @@ def get_camera_config():
'width': profile['width'],
'height': profile['height'],
'fps': profile['fps'],
'fps_options': list(CAMERA_FPS_OPTIONS),
'profiles': [dict(item) for item in CAMERA_PROFILES],
}
}
def set_camera_profile(profile_id):
profile = normalize_camera_profile(profile_id)
write_camera_settings(profile)
def set_camera_config(profile_id=None, fps=None):
current = read_camera_settings()
if profile_id is None:
profile = normalize_camera_profile(current['id'])
else:
profile = normalize_camera_profile(profile_id)
if fps is None:
fps = current.get('fps', DEFAULT_CAMERA_FPS)
fps = normalize_camera_fps(fps)
profile['fps'] = fps
write_camera_settings(profile, fps)
restart_camera_service()
return profile
@@ -1164,14 +1207,15 @@ class Handler(http.server.BaseHTTPRequestHandler):
self.write_json(400, {'status': 'Ungültige Anfrage'})
return
try:
profile = set_camera_profile(payload.get('profile'))
profile = set_camera_config(payload.get('profile'), payload.get('fps'))
self.write_json(200, {
'status': 'Kameraauflösung gespeichert, Kameradienst startet neu',
'status': 'Kameraprofil gespeichert, Kameradienst startet neu',
'camera': {
'selected_profile': profile['id'],
'width': profile['width'],
'height': profile['height'],
'fps': profile['fps'],
'fps_options': list(CAMERA_FPS_OPTIONS),
'profiles': [dict(item) for item in CAMERA_PROFILES],
}
})
@@ -15,8 +15,8 @@ LATEST_LOCK = threading.Lock()
PORT = 8081
CONFIG_FILE = os.path.join(os.path.dirname(__file__), '..', 'config', 'camera_settings.json')
DEFAULT_CAMERA_SETTINGS = {
'width': 1280,
'height': 720,
'width': 1296,
'height': 972,
'fps': 10,
}
@@ -11,6 +11,7 @@ import os
import socketserver
import threading
import time
import urllib.parse
import urllib.request
DELAY_FILE = '/tmp/exomy_delay.txt'
@@ -93,8 +94,11 @@ class ReusableTCPServer(socketserver.ThreadingTCPServer):
class Handler(http.server.BaseHTTPRequestHandler):
def normalized_path(self):
return urllib.parse.urlsplit(self.path).path
def do_GET(self):
if self.path != '/stream.mjpg':
if self.normalized_path() != '/stream.mjpg':
self.send_error(404)
return
self.send_response(200)