mirror of
https://github.com/iperov/DeepFaceLive
synced 2025-08-20 21:43:22 -07:00
StreamOutput: added mpegts udp output
This commit is contained in:
parent
0a076dbfdf
commit
5e75e7822d
9 changed files with 203 additions and 25 deletions
|
@ -10,6 +10,7 @@ from xlib import os as lib_os
|
|||
from xlib import time as lib_time
|
||||
from xlib.image import ImageProcessor
|
||||
from xlib.mp import csw as lib_csw
|
||||
from xlib.streamer import FFMPEGStreamer
|
||||
|
||||
from .BackendBase import (BackendConnection, BackendDB, BackendHost,
|
||||
BackendSignal, BackendWeakHeap, BackendWorker,
|
||||
|
@ -43,16 +44,17 @@ class SourceType(IntEnum):
|
|||
SOURCE_N_MERGED_FRAME = 5
|
||||
SOURCE_N_MERGED_FRAME_OR_SOURCE_FRAME = 6
|
||||
|
||||
ViewModeNames = ['@StreamOutput.SourceType.SOURCE_FRAME',
|
||||
'@StreamOutput.SourceType.ALIGNED_FACE',
|
||||
ViewModeNames = ['@StreamOutput.SourceType.SOURCE_FRAME',
|
||||
'@StreamOutput.SourceType.ALIGNED_FACE',
|
||||
'@StreamOutput.SourceType.SWAPPED_FACE',
|
||||
'@StreamOutput.SourceType.MERGED_FRAME',
|
||||
'@StreamOutput.SourceType.MERGED_FRAME_OR_SOURCE_FRAME',
|
||||
'@StreamOutput.SourceType.MERGED_FRAME',
|
||||
'@StreamOutput.SourceType.MERGED_FRAME_OR_SOURCE_FRAME',
|
||||
'@StreamOutput.SourceType.SOURCE_N_MERGED_FRAME',
|
||||
'@StreamOutput.SourceType.SOURCE_N_MERGED_FRAME_OR_SOURCE_FRAME',
|
||||
]
|
||||
|
||||
|
||||
|
||||
class StreamOutputWorker(BackendWorker):
|
||||
def get_state(self) -> 'WorkerState': return super().get_state()
|
||||
def get_control_sheet(self) -> 'Sheet.Worker': return super().get_control_sheet()
|
||||
|
@ -73,7 +75,9 @@ class StreamOutputWorker(BackendWorker):
|
|||
|
||||
self._wnd_name = 'DeepFaceLive output'
|
||||
self._wnd_showing = False
|
||||
|
||||
|
||||
self._streamer = FFMPEGStreamer()
|
||||
|
||||
lib_os.set_timer_resolution(1)
|
||||
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
|
@ -84,7 +88,10 @@ class StreamOutputWorker(BackendWorker):
|
|||
cs.target_delay.call_on_number(self.on_cs_target_delay)
|
||||
cs.save_sequence_path.call_on_paths(self.on_cs_save_sequence_path)
|
||||
cs.save_fill_frame_gap.call_on_flag(self.on_cs_save_fill_frame_gap)
|
||||
|
||||
cs.is_streaming.call_on_flag(self.on_cs_is_streaming)
|
||||
cs.stream_addr.call_on_text(self.on_cs_stream_addr)
|
||||
cs.stream_port.call_on_number(self.on_cs_stream_port)
|
||||
|
||||
cs.source_type.enable()
|
||||
cs.source_type.set_choices(SourceType, ViewModeNames, none_choice_name='@misc.menu_select')
|
||||
cs.source_type.select(state.source_type)
|
||||
|
@ -107,16 +114,26 @@ class StreamOutputWorker(BackendWorker):
|
|||
state.is_showing_window = not state.is_showing_window
|
||||
cs.show_hide_window.signal()
|
||||
|
||||
|
||||
cs.save_sequence_path.enable()
|
||||
cs.save_sequence_path.set_config( lib_csw.Paths.Config.Directory('Choose output sequence directory', directory_path=save_default_path) )
|
||||
cs.save_sequence_path.set_paths(state.sequence_path)
|
||||
|
||||
cs.save_fill_frame_gap.enable()
|
||||
cs.save_fill_frame_gap.set_flag(state.save_fill_frame_gap if state.save_fill_frame_gap is not None else True )
|
||||
|
||||
cs.is_streaming.enable()
|
||||
cs.is_streaming.set_flag(state.is_streaming if state.is_streaming is not None else False )
|
||||
|
||||
cs.stream_addr.enable()
|
||||
cs.stream_addr.set_text(state.stream_addr if state.stream_addr is not None else '127.0.0.1')
|
||||
|
||||
cs.stream_port.enable()
|
||||
cs.stream_port.set_config(lib_csw.Number.Config(min=1, max=9999, decimals=0, allow_instant_update=True))
|
||||
cs.stream_port.set_number(state.stream_port if state.stream_port is not None else 1234)
|
||||
|
||||
|
||||
|
||||
def on_stop(self):
|
||||
self._streamer.stop()
|
||||
|
||||
def on_cs_source_type(self, idx, source_type):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
if source_type == SourceType.ALIGNED_FACE:
|
||||
|
@ -126,10 +143,10 @@ class StreamOutputWorker(BackendWorker):
|
|||
else:
|
||||
cs.aligned_face_id.disable()
|
||||
state.source_type = source_type
|
||||
|
||||
|
||||
self.save_state()
|
||||
self.reemit_frame_signal.send()
|
||||
|
||||
|
||||
def show_window(self):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
cv2.namedWindow(self._wnd_name)
|
||||
|
@ -189,6 +206,23 @@ class StreamOutputWorker(BackendWorker):
|
|||
state.save_fill_frame_gap = save_fill_frame_gap
|
||||
self.save_state()
|
||||
|
||||
def on_cs_is_streaming(self, is_streaming):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
state.is_streaming = is_streaming
|
||||
self.save_state()
|
||||
|
||||
def on_cs_stream_addr(self, stream_addr):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
state.stream_addr = stream_addr
|
||||
self.save_state()
|
||||
self._streamer.set_addr_port(state.stream_addr, state.stream_port)
|
||||
|
||||
def on_cs_stream_port(self, stream_port):
|
||||
state, cs = self.get_state(), self.get_control_sheet()
|
||||
state.stream_port = stream_port
|
||||
self.save_state()
|
||||
self._streamer.set_addr_port(state.stream_addr, state.stream_port)
|
||||
|
||||
def on_tick(self):
|
||||
cs, state = self.get_control_sheet(), self.get_state()
|
||||
|
||||
|
@ -204,7 +238,9 @@ class StreamOutputWorker(BackendWorker):
|
|||
|
||||
source_type = state.source_type
|
||||
if source_type is not None and \
|
||||
(state.is_showing_window or state.sequence_path is not None):
|
||||
(state.is_showing_window or \
|
||||
state.sequence_path is not None or \
|
||||
state.is_streaming):
|
||||
buffered_frames = self.buffered_frames
|
||||
|
||||
view_image = None
|
||||
|
@ -213,7 +249,7 @@ class StreamOutputWorker(BackendWorker):
|
|||
view_image = bcd.get_image(bcd.get_frame_image_name())
|
||||
elif source_type in [SourceType.MERGED_FRAME, SourceType.MERGED_FRAME_OR_SOURCE_FRAME]:
|
||||
view_image = bcd.get_image(bcd.get_merged_image_name())
|
||||
if view_image is None and source_type == SourceType.MERGED_FRAME_OR_SOURCE_FRAME:
|
||||
if view_image is None and source_type == SourceType.MERGED_FRAME_OR_SOURCE_FRAME:
|
||||
view_image = bcd.get_image(bcd.get_frame_image_name())
|
||||
|
||||
elif source_type == SourceType.ALIGNED_FACE:
|
||||
|
@ -228,17 +264,17 @@ class StreamOutputWorker(BackendWorker):
|
|||
view_image = bcd.get_image(fsi.face_swap_image_name)
|
||||
if view_image is not None:
|
||||
break
|
||||
|
||||
|
||||
elif source_type in [SourceType.SOURCE_N_MERGED_FRAME, SourceType.SOURCE_N_MERGED_FRAME_OR_SOURCE_FRAME]:
|
||||
source_frame = bcd.get_image(bcd.get_frame_image_name())
|
||||
if source_frame is not None:
|
||||
source_frame = ImageProcessor(source_frame).to_ufloat32().get_image('HWC')
|
||||
|
||||
|
||||
merged_frame = bcd.get_image(bcd.get_merged_image_name())
|
||||
|
||||
if merged_frame is None and source_type == SourceType.SOURCE_N_MERGED_FRAME_OR_SOURCE_FRAME:
|
||||
|
||||
if merged_frame is None and source_type == SourceType.SOURCE_N_MERGED_FRAME_OR_SOURCE_FRAME:
|
||||
merged_frame = source_frame
|
||||
|
||||
|
||||
if source_frame is not None and merged_frame is not None:
|
||||
view_image = np.concatenate( (source_frame, merged_frame), 1 )
|
||||
|
||||
|
@ -259,8 +295,13 @@ class StreamOutputWorker(BackendWorker):
|
|||
pr = buffered_frames.process()
|
||||
|
||||
img = pr.new_data
|
||||
if state.is_showing_window and img is not None:
|
||||
cv2.imshow(self._wnd_name, img)
|
||||
if img is not None:
|
||||
if state.is_streaming:
|
||||
img = ImageProcessor(view_image).to_uint8().get_image('HWC')
|
||||
self._streamer.push_frame(img)
|
||||
|
||||
if state.is_showing_window:
|
||||
cv2.imshow(self._wnd_name, img)
|
||||
|
||||
if state.is_showing_window:
|
||||
cv2.waitKey(1)
|
||||
|
@ -277,7 +318,10 @@ class Sheet:
|
|||
self.save_sequence_path = lib_csw.Paths.Client()
|
||||
self.save_sequence_path_error = lib_csw.Error.Client()
|
||||
self.save_fill_frame_gap = lib_csw.Flag.Client()
|
||||
|
||||
self.is_streaming = lib_csw.Flag.Client()
|
||||
self.stream_addr = lib_csw.Text.Client()
|
||||
self.stream_port = lib_csw.Number.Client()
|
||||
|
||||
class Worker(lib_csw.Sheet.Worker):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
@ -289,7 +333,10 @@ class Sheet:
|
|||
self.save_sequence_path = lib_csw.Paths.Host()
|
||||
self.save_sequence_path_error = lib_csw.Error.Host()
|
||||
self.save_fill_frame_gap = lib_csw.Flag.Host()
|
||||
|
||||
self.is_streaming = lib_csw.Flag.Host()
|
||||
self.stream_addr = lib_csw.Text.Host()
|
||||
self.stream_port = lib_csw.Number.Host()
|
||||
|
||||
class WorkerState(BackendWorkerState):
|
||||
source_type : SourceType = None
|
||||
is_showing_window : bool = None
|
||||
|
@ -297,3 +344,6 @@ class WorkerState(BackendWorkerState):
|
|||
target_delay : int = None
|
||||
sequence_path : Path = None
|
||||
save_fill_frame_gap : bool = None
|
||||
is_streaming : bool = None
|
||||
stream_addr : str = None
|
||||
stream_port : int = None
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from localization import L
|
||||
from resources.fonts import QXFontDB
|
||||
from xlib import qt as qtx
|
||||
from xlib.qt.widgets.QXLabel import QXLabel
|
||||
|
||||
from ..backend import StreamOutput
|
||||
from .widgets.QBackendPanel import QBackendPanel
|
||||
|
@ -9,6 +11,7 @@ from .widgets.QComboBoxCSWDynamicSingleSwitch import \
|
|||
from .widgets.QErrorCSWError import QErrorCSWError
|
||||
from .widgets.QLabelCSWNumber import QLabelCSWNumber
|
||||
from .widgets.QLabelPopupInfo import QLabelPopupInfo
|
||||
from .widgets.QLineEditCSWText import QLineEditCSWText
|
||||
from .widgets.QPathEditCSWPaths import QPathEditCSWPaths
|
||||
from .widgets.QSpinBoxCSWNumber import QSpinBoxCSWNumber
|
||||
from .widgets.QXPushButtonCSWSignal import QXPushButtonCSWSignal
|
||||
|
@ -39,6 +42,12 @@ class QStreamOutput(QBackendPanel):
|
|||
q_save_fill_frame_gap_label = QLabelPopupInfo(label=L('@QStreamOutput.save_fill_frame_gap'), popup_info_text=L('@QStreamOutput.help.save_fill_frame_gap'))
|
||||
q_save_fill_frame_gap = QCheckBoxCSWFlag(cs.save_fill_frame_gap, reflect_state_widgets=[q_save_fill_frame_gap_label])
|
||||
|
||||
q_is_streaming_label = QLabelPopupInfo(label='mpegts udp://')
|
||||
q_is_streaming = QCheckBoxCSWFlag(cs.is_streaming, reflect_state_widgets=[q_is_streaming_label])
|
||||
|
||||
q_stream_addr = QLineEditCSWText(cs.stream_addr, font=QXFontDB.get_fixedwidth_font())
|
||||
q_stream_port = QSpinBoxCSWNumber(cs.stream_port)
|
||||
|
||||
grid_l = qtx.QXGridLayout(spacing=5)
|
||||
row = 0
|
||||
grid_l.addWidget(q_average_fps_label, row, 0, 1, 1, alignment=qtx.AlignRight | qtx.AlignVCenter )
|
||||
|
@ -61,9 +70,11 @@ class QStreamOutput(QBackendPanel):
|
|||
row += 1
|
||||
grid_l.addLayout( qtx.QXHBoxLayout([q_save_fill_frame_gap, 4, q_save_fill_frame_gap_label]), row, 1, 1, 2, alignment=qtx.AlignLeft | qtx.AlignVCenter )
|
||||
row += 1
|
||||
|
||||
grid_l.addWidget(q_save_sequence_path_error, row, 0, 1, 3)
|
||||
row += 1
|
||||
grid_l.addLayout( qtx.QXHBoxLayout([q_is_streaming, 4, q_is_streaming_label]), row, 0, 1, 1, alignment=qtx.AlignRight | qtx.AlignVCenter )
|
||||
grid_l.addLayout( qtx.QXHBoxLayout([q_stream_addr, qtx.QXLabel(text=':'), q_stream_port]), row, 1, 1, 2, alignment=qtx.AlignLeft | qtx.AlignVCenter )
|
||||
row += 1
|
||||
|
||||
super().__init__(backend, L('@QStreamOutput.module_title'),
|
||||
layout=grid_l)
|
||||
|
|
45
apps/DeepFaceLive/ui/widgets/QLineEditCSWText.py
Normal file
45
apps/DeepFaceLive/ui/widgets/QLineEditCSWText.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
from pathlib import Path
|
||||
|
||||
from resources.fonts import QXFontDB
|
||||
from resources.gfx import QXImageDB
|
||||
from xlib import qt as qtx
|
||||
from xlib.mp import csw as lib_csw
|
||||
|
||||
from .QCSWControl import QCSWControl
|
||||
|
||||
|
||||
class QLineEditCSWText(QCSWControl):
|
||||
def __init__(self, csw_text : lib_csw.Text.Client,
|
||||
font = None,
|
||||
reflect_state_widgets=None):
|
||||
"""
|
||||
Implements lib_csw.Text control as LineEdit
|
||||
"""
|
||||
if not isinstance(csw_text, lib_csw.Text.Client):
|
||||
raise ValueError('csw_path must be an instance of Text.Client')
|
||||
|
||||
self._csw_text = csw_text
|
||||
self._dlg = None
|
||||
|
||||
csw_text.call_on_text(self._on_csw_text)
|
||||
|
||||
if font is None:
|
||||
font = QXFontDB.get_default_font()
|
||||
lineedit = self._lineedit = qtx.QXLineEdit(font=font,
|
||||
placeholder_text='...',
|
||||
size_policy=('expanding', 'fixed'),
|
||||
editingFinished=self.on_lineedit_editingFinished)
|
||||
|
||||
super().__init__(csw_control=csw_text, reflect_state_widgets=reflect_state_widgets,
|
||||
layout=qtx.QXHBoxLayout([lineedit]) )
|
||||
|
||||
def _on_csw_text(self, text):
|
||||
|
||||
with qtx.BlockSignals(self._lineedit):
|
||||
self._lineedit.setText(text)
|
||||
|
||||
def on_lineedit_editingFinished(self):
|
||||
text = self._lineedit.text()
|
||||
if len(text) == 0:
|
||||
text = None
|
||||
self._csw_text.set_text(text)
|
Loading…
Add table
Add a link
Reference in a new issue