added stream sanity checks (sound, singe color)

This commit is contained in:
2020-02-21 17:10:14 +01:00
parent 6971b4b618
commit 1745f56ac7
8 changed files with 246 additions and 48 deletions

Binary file not shown.

View File

@@ -9,7 +9,7 @@ from threading import Lock
from typing import Union, Callable, TypeVar, Generic, Set, List
from backend.models import Recorder
from backend.tools.simple_state_checker import check_capture_agent_state, ping_capture_agent
from backend.tools.recorder_state_checker import check_capture_agent_state, ping_capture_agent
logger = logging.getLogger("lrc.cron.recorder_state")

View File

@@ -144,6 +144,9 @@ class Recorder(db.Model, ModelBase):
_additional_notes_json_string = db.Column(db.UnicodeText, default='')
additional_camera_connected = db.Column(db.Boolean, default=False)
firmware_version = db.Column(db.String, nullable=True, default=None)
archive_stream1 = db.Column(db.String, nullable=True, default=None)
archive_stream2 = db.Column(db.String, nullable=True, default=None)
confidence_stream = db.Column(db.String, nullable=True, default=None)
room_id = db.Column(db.Integer, db.ForeignKey('room.id'))
room = db.relationship('Room', uselist=False, back_populates='recorder') # one-to-one relation (uselist=False)
recorder_model_id = db.Column(db.Integer, db.ForeignKey('recorder_model.id'))

View File

@@ -3,6 +3,7 @@ import os
import logging
import subprocess
import threading
import time
from io import StringIO
from logging.handlers import MemoryHandler
from pprint import pprint
@@ -124,6 +125,23 @@ def get_recorder_adapter(recorder_info: Union[dict, Recorder]) -> RecorderAdapte
return rec
def check_stream_sanity(recorder: Recorder, recorder_adapter: RecorderAdapter):
if recorder.archive_stream1 is None and recorder.archive_stream2 is None: # fall back to default names and rtsp
archive_stream_1_url = "rtsp://{}/{}".format(recorder_adapter.address, Config.DEFAULT_ARCHIVE_STREAM_1_NAME)
archive_stream_2_url = "rtsp://{}/{}".format(recorder_adapter.address, Config.DEFAULT_ARCHIVE_STREAM_2_NAME)
else:
archive_stream_1_url = recorder.archive_stream1
archive_stream_2_url = recorder.archive_stream2
for i in range(0, Config.STREAM_SANITY_CHECK_RETRIES):
do_checks = False
if do_checks:
return True
else:
time.sleep(Config.STREAM_SANITY_CHECK_INTERVAL_SEC)
return False # stream sanity check failed!
def check_capture_agent_state(recorder_agent: Union[Recorder, dict]):
if recorder_agent.get('offline', False):
logger.info("OK - Recorder {} is in offline / maintenance mode".format(recorder_agent.get('name')))
@@ -142,6 +160,7 @@ def check_capture_agent_state(recorder_agent: Union[Recorder, dict]):
logger.info("OK recorder {} is recording :)".format(recorder_agent.get('name')))
with agent_states_lock:
agent_states[recorder_agent.get('name')] = 'OK - recorder is recording'
else:
logger.info(rec.get_recording_status())
logger.error("FATAL - recorder {} must be recording but is not!!!!".format(recorder_agent.get('name')))

View File

@@ -0,0 +1,132 @@
import io
import sys
import ffmpeg
import os
import tempfile
from PIL import Image
from pydub import AudioSegment
from pydub.playback import play
def old_test():
file_name = tempfile.gettempdir() + os.path.sep + "test.jpg"
print(file_name)
if os.path.exists(file_name):
os.remove(file_name)
process = (
ffmpeg
.input('rtsp://172.22.246.207/extron1')
# .input('rtsp://172.22.246.207/extron3')
.output(file_name, vframes=1)
# .output('-', format='h264')
.run(capture_stdout=True)
)
image = Image.open(file_name)
r, g, b = image.split()
print(r.histogram())
print(g.histogram())
print(b.histogram())
image.show()
# old_test()
def is_single_color_image(image):
single_color_image = True
color = {}
count = 0
color_channels = image.split()
for c in color_channels: # r, g, b
hist = c.histogram()
num_of_non_zero_values = len([v for v in hist if v != 0])
if num_of_non_zero_values > 1:
single_color_image = False
break
else:
color[count] = [i for i in enumerate(hist) if i[1] != 0][0][0]
count += 1
return single_color_image, color
def check_frame_is_valid(stream_url, raise_errors=True):
try:
frame, _ = (
ffmpeg
.input(stream_url)
.output('pipe:', vframes=1, format='image2', vcodec='mjpeg')
.run(capture_stdout=True, capture_stderr=True)
)
image = Image.open(io.BytesIO(frame))
single_color_image, color = is_single_color_image(image)
if not single_color_image:
image.show()
return True, "all good :-)"
else:
if all(map(lambda x: x == 0, color.values())):
return False, "Image is entirely black! (color: {} (RGB))".format(
':'.join([str(x) for x in color.values()]))
return False, "Image has only one single color! (color: {} (RGB))".format(
':'.join([str(x) for x in color.values()]))
except ffmpeg.Error as e:
msg = "Could not connect to stream URL or other error!"
try:
msg += " ({})".format(e.stderr.decode('utf-8').strip().split("\n")[-1])
except IndexError:
pass
if raise_errors:
raise Exception(msg)
else:
return False, msg
# print(check_frame_is_valid('rtsp://172.22.246.207/extron2'))
def check_if_audio_is_valid(stream_url, sample_length_sec=3, lower_alert_limit_dBFS=-40.0, raise_errors=True):
file_name = tempfile.NamedTemporaryFile(suffix='.aac').name
if os.path.exists(file_name):
os.remove(file_name)
try:
frame, _ = (
ffmpeg
.input(stream_url, t=sample_length_sec)
.output(file_name)
.run(capture_stdout=True, capture_stderr=True)
)
sound = AudioSegment.from_file(file_name, "aac")
# print(sound.dBFS)
play(sound)
if sound.max_dBFS == -float('inf'):
return False, "No active audio signal detected!"
elif sound.max_dBFS < lower_alert_limit_dBFS:
return False, "Very low volume (< {} dBFS) detected! ({})".format(lower_alert_limit_dBFS, sound.max_dBFS)
return True, "good audio signal detected! ({} max dBFS in {}s sample)".format(sound.max_dBFS, sample_length_sec)
except ffmpeg.Error as e:
msg = "Could not connect to stream URL or other error!"
try:
msg += " ({})".format(e.stderr.decode('utf-8').strip().split("\n")[-1])
except IndexError:
pass
if raise_errors:
raise Exception(msg)
else:
return False, msg
print(check_if_audio_is_valid('rtsp://172.22.246.207/extron1'))
def check_if_audio_is_valid_stream(stream_url, raise_errors=True):
audio, _ = (
ffmpeg
.input(stream_url, t=2)
.output('pipe:', f="adts", acodec='copy')
.run(capture_stdout=False, capture_stderr=False)
)
audio = io.BytesIO(audio)
sound = AudioSegment.from_file(audio, "aac")
play(sound)
# check_if_audio_is_valid('rtsp://172.22.246.207/extron1')

View File

@@ -1,15 +0,0 @@
import ffmpeg
packet_size = 4096
process = (
ffmpeg
.input('rtsp://172.22.246.207/extron1')
#.input('rtsp://172.22.246.207/extron3')
.output('/tmp/test.jpg', vframes=1)
#.output('-', format='h264')
.run_async(pipe_stdout=True)
)
while process.poll() is None:
packet = process.stdout.read(packet_size)