Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds RedisMpegVideoClass for a redis camera video-streamer #1100

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 172 additions & 0 deletions mxcubecore/HardwareObjects/RedisMpegVideo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
"""
Class for streaming MPEG1 or MJPEG video with cameras Redis Pub/Sub server
Example configuration:

<object class="RedisMpegVideo">
<username>Camera redis</username>
<host>localhost</host>
<uri>redis://localhost:6379</uri>
<cam_type>redis</cam_type>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is no cam_type parameter in the class, hence we might not need to add it to an example

<port>8000</port>
<width>1360</width>
<height>1024</height>
<format>MJPEG</format>
</object>
"""

import subprocess
import uuid

import psutil

from mxcubecore.BaseHardwareObjects import HardwareObject


class RedisMpegVideo(HardwareObject):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A solution that would result in less duplication and easier maintenance would be to merge this code with TangoLimaMpegVideo so that both functionalities can be achieved via configuration. It looks like the principal difference is the use of "-irc", "mxcubeweb" which could be added via configuration.

The resulting class could arguably be renamed, perhaps to MpegVideo , what do you think @walesch-yan

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing the comment above it looks like we had the same thought :), sorry for the repetition

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem at all:)
After some more investigation, one other important difference is that this class would directly inherit from hardwareobjects, where TangoLimaMPEGVideo inherits from TangoLimaVideo, which seems to contain some more properties, similar to the ones in the RedisMPEGVideoClass, but also some code for directly pulling images from a camera, instead of using the video-streamer, so we might have to check if this is still used

Copy link
Member

@marcus-oscarsson marcus-oscarsson Jan 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure that the inheritance from TangoLimaVideo can be removed now, there might be some methods that needs to be moved but I think TangoLimaVideo can be retired soon. ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the inputs. I like the idea of merging the two classes into one MpegVideo class. I am looking into it.

def __init__(self, name):
super().__init__(name)
self._video_stream_process = None
self._current_stream_size = "0, 0"
self.stream_hash = str(uuid.uuid1())
self._quality_str = "High"
self._QUALITY_STR_TO_INT = {"High": 4, "Medium": 10, "Low": 20, "Adaptive": -1}

def init(self):
super().init()
self._debug = self.get_property("debug", False)
self._quality = self.get_property("compression", 10)
self._mpeg_scale = self.get_property("mpeg_scale", 1)
self._image_size = (self.get_width(), self.get_height())
self._uri = self.get_property("uri")
self._host = self.get_property("host")
self._port = str(self.get_property("port"))
self._format = self.get_property("format")

@property
def uri(self):
return self._uri

@property
def host(self):
return self._host

@property
def format(self):
return self._format

@format.setter
def format(self, format):
self._format = format

@property
def port(self):
return self._port

@port.setter
def port(self, p):
self._port = str(p)

def get_width(self):
w = int(self.get_property("width"))
return w

def get_height(self):
h = int(self.get_property("height"))
return h

def get_quality(self):
return self._quality_str

def set_quality(self, q):
self._quality_str = q
self._quality = self._QUALITY_STR_TO_INT[q]
self.restart_streaming()

def set_stream_size(self, w, h):
self._current_stream_size = "%s,%s" % (int(w), int(h))

def get_stream_size(self):
current_size = self._current_stream_size.split(",")
scale = float(current_size[0]) / self.get_width()
return current_size + list((scale,))

def get_quality_options(self):
return list(self._QUALITY_STR_TO_INT.keys())

def get_available_stream_sizes(self):
try:
w, h = self.get_width(), self.get_height()
video_sizes = [(w, h), (int(w / 2), int(h / 2)), (int(w / 4), int(h / 4))]
except (ValueError, AttributeError):
video_sizes = []
return video_sizes

def start_video_stream_process(self, p):
print(f"STARTING ! Video stream on port: {self.port} in format: {self.format}")

if (
not self._video_stream_process
or self._video_stream_process.poll() is not None
):

self._video_stream_process = subprocess.Popen(
[
"video-streamer",
"-uri",
self.uri,
"-hs",
self.host,
"-p",
self.port,
"-q",
str(self._quality),
"-s",
self._current_stream_size,
"-of",
self.format,
"-id",
self.stream_hash,
"-irc",
"mxcubeweb",
# "-d",
Comment on lines +129 to +131
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the redis-channel should not be hardcoded but be configurable through the xml files, as it depends on the actual channel name

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point ! I missed that one.

],
close_fds=True,
)
with open("/tmp/mxcube.pid", "a") as f:
f.write("%s " % self._video_stream_process.pid)

def stop_streaming(self):
if self._video_stream_process:
ps = psutil.Process(self._video_stream_process.pid).children() + [
self._video_stream_process
]

for p in ps:
p.kill()

self._video_stream_process = None

def start_streaming(self, _format=None, size=(0, 0), port=None):
_s = size

Comment on lines +150 to +151
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this can be removed as you are setting it below

if _format:
self.format = _format

if port:
self.port = port

if not size[0]:
_s = (self.get_width(), self.get_height())
else:
_s = size

self.set_stream_size(_s[0], _s[1])
try:
self.start_video_stream_process(str(self.port))
except Exception as e:
print(f"Cannot start video streaming process ! {e}")
exit()

def restart_streaming(self, size):
self.stop_streaming()
self.start_streaming(self.format, size)
Loading