mirror of
https://github.com/iperov/DeepFaceLive
synced 2025-08-19 13:09:58 -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 import time as lib_time
|
||||||
from xlib.image import ImageProcessor
|
from xlib.image import ImageProcessor
|
||||||
from xlib.mp import csw as lib_csw
|
from xlib.mp import csw as lib_csw
|
||||||
|
from xlib.streamer import FFMPEGStreamer
|
||||||
|
|
||||||
from .BackendBase import (BackendConnection, BackendDB, BackendHost,
|
from .BackendBase import (BackendConnection, BackendDB, BackendHost,
|
||||||
BackendSignal, BackendWeakHeap, BackendWorker,
|
BackendSignal, BackendWeakHeap, BackendWorker,
|
||||||
|
@ -43,16 +44,17 @@ class SourceType(IntEnum):
|
||||||
SOURCE_N_MERGED_FRAME = 5
|
SOURCE_N_MERGED_FRAME = 5
|
||||||
SOURCE_N_MERGED_FRAME_OR_SOURCE_FRAME = 6
|
SOURCE_N_MERGED_FRAME_OR_SOURCE_FRAME = 6
|
||||||
|
|
||||||
ViewModeNames = ['@StreamOutput.SourceType.SOURCE_FRAME',
|
ViewModeNames = ['@StreamOutput.SourceType.SOURCE_FRAME',
|
||||||
'@StreamOutput.SourceType.ALIGNED_FACE',
|
'@StreamOutput.SourceType.ALIGNED_FACE',
|
||||||
'@StreamOutput.SourceType.SWAPPED_FACE',
|
'@StreamOutput.SourceType.SWAPPED_FACE',
|
||||||
'@StreamOutput.SourceType.MERGED_FRAME',
|
'@StreamOutput.SourceType.MERGED_FRAME',
|
||||||
'@StreamOutput.SourceType.MERGED_FRAME_OR_SOURCE_FRAME',
|
'@StreamOutput.SourceType.MERGED_FRAME_OR_SOURCE_FRAME',
|
||||||
'@StreamOutput.SourceType.SOURCE_N_MERGED_FRAME',
|
'@StreamOutput.SourceType.SOURCE_N_MERGED_FRAME',
|
||||||
'@StreamOutput.SourceType.SOURCE_N_MERGED_FRAME_OR_SOURCE_FRAME',
|
'@StreamOutput.SourceType.SOURCE_N_MERGED_FRAME_OR_SOURCE_FRAME',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class StreamOutputWorker(BackendWorker):
|
class StreamOutputWorker(BackendWorker):
|
||||||
def get_state(self) -> 'WorkerState': return super().get_state()
|
def get_state(self) -> 'WorkerState': return super().get_state()
|
||||||
def get_control_sheet(self) -> 'Sheet.Worker': return super().get_control_sheet()
|
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_name = 'DeepFaceLive output'
|
||||||
self._wnd_showing = False
|
self._wnd_showing = False
|
||||||
|
|
||||||
|
self._streamer = FFMPEGStreamer()
|
||||||
|
|
||||||
lib_os.set_timer_resolution(1)
|
lib_os.set_timer_resolution(1)
|
||||||
|
|
||||||
state, cs = self.get_state(), self.get_control_sheet()
|
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.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_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.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.enable()
|
||||||
cs.source_type.set_choices(SourceType, ViewModeNames, none_choice_name='@misc.menu_select')
|
cs.source_type.set_choices(SourceType, ViewModeNames, none_choice_name='@misc.menu_select')
|
||||||
cs.source_type.select(state.source_type)
|
cs.source_type.select(state.source_type)
|
||||||
|
@ -107,16 +114,26 @@ class StreamOutputWorker(BackendWorker):
|
||||||
state.is_showing_window = not state.is_showing_window
|
state.is_showing_window = not state.is_showing_window
|
||||||
cs.show_hide_window.signal()
|
cs.show_hide_window.signal()
|
||||||
|
|
||||||
|
|
||||||
cs.save_sequence_path.enable()
|
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_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_sequence_path.set_paths(state.sequence_path)
|
||||||
|
|
||||||
cs.save_fill_frame_gap.enable()
|
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.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):
|
def on_cs_source_type(self, idx, source_type):
|
||||||
state, cs = self.get_state(), self.get_control_sheet()
|
state, cs = self.get_state(), self.get_control_sheet()
|
||||||
if source_type == SourceType.ALIGNED_FACE:
|
if source_type == SourceType.ALIGNED_FACE:
|
||||||
|
@ -126,10 +143,10 @@ class StreamOutputWorker(BackendWorker):
|
||||||
else:
|
else:
|
||||||
cs.aligned_face_id.disable()
|
cs.aligned_face_id.disable()
|
||||||
state.source_type = source_type
|
state.source_type = source_type
|
||||||
|
|
||||||
self.save_state()
|
self.save_state()
|
||||||
self.reemit_frame_signal.send()
|
self.reemit_frame_signal.send()
|
||||||
|
|
||||||
def show_window(self):
|
def show_window(self):
|
||||||
state, cs = self.get_state(), self.get_control_sheet()
|
state, cs = self.get_state(), self.get_control_sheet()
|
||||||
cv2.namedWindow(self._wnd_name)
|
cv2.namedWindow(self._wnd_name)
|
||||||
|
@ -189,6 +206,23 @@ class StreamOutputWorker(BackendWorker):
|
||||||
state.save_fill_frame_gap = save_fill_frame_gap
|
state.save_fill_frame_gap = save_fill_frame_gap
|
||||||
self.save_state()
|
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):
|
def on_tick(self):
|
||||||
cs, state = self.get_control_sheet(), self.get_state()
|
cs, state = self.get_control_sheet(), self.get_state()
|
||||||
|
|
||||||
|
@ -204,7 +238,9 @@ class StreamOutputWorker(BackendWorker):
|
||||||
|
|
||||||
source_type = state.source_type
|
source_type = state.source_type
|
||||||
if source_type is not None and \
|
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
|
buffered_frames = self.buffered_frames
|
||||||
|
|
||||||
view_image = None
|
view_image = None
|
||||||
|
@ -213,7 +249,7 @@ class StreamOutputWorker(BackendWorker):
|
||||||
view_image = bcd.get_image(bcd.get_frame_image_name())
|
view_image = bcd.get_image(bcd.get_frame_image_name())
|
||||||
elif source_type in [SourceType.MERGED_FRAME, SourceType.MERGED_FRAME_OR_SOURCE_FRAME]:
|
elif source_type in [SourceType.MERGED_FRAME, SourceType.MERGED_FRAME_OR_SOURCE_FRAME]:
|
||||||
view_image = bcd.get_image(bcd.get_merged_image_name())
|
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())
|
view_image = bcd.get_image(bcd.get_frame_image_name())
|
||||||
|
|
||||||
elif source_type == SourceType.ALIGNED_FACE:
|
elif source_type == SourceType.ALIGNED_FACE:
|
||||||
|
@ -228,17 +264,17 @@ class StreamOutputWorker(BackendWorker):
|
||||||
view_image = bcd.get_image(fsi.face_swap_image_name)
|
view_image = bcd.get_image(fsi.face_swap_image_name)
|
||||||
if view_image is not None:
|
if view_image is not None:
|
||||||
break
|
break
|
||||||
|
|
||||||
elif source_type in [SourceType.SOURCE_N_MERGED_FRAME, SourceType.SOURCE_N_MERGED_FRAME_OR_SOURCE_FRAME]:
|
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())
|
source_frame = bcd.get_image(bcd.get_frame_image_name())
|
||||||
if source_frame is not None:
|
if source_frame is not None:
|
||||||
source_frame = ImageProcessor(source_frame).to_ufloat32().get_image('HWC')
|
source_frame = ImageProcessor(source_frame).to_ufloat32().get_image('HWC')
|
||||||
|
|
||||||
merged_frame = bcd.get_image(bcd.get_merged_image_name())
|
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
|
merged_frame = source_frame
|
||||||
|
|
||||||
if source_frame is not None and merged_frame is not None:
|
if source_frame is not None and merged_frame is not None:
|
||||||
view_image = np.concatenate( (source_frame, merged_frame), 1 )
|
view_image = np.concatenate( (source_frame, merged_frame), 1 )
|
||||||
|
|
||||||
|
@ -259,8 +295,13 @@ class StreamOutputWorker(BackendWorker):
|
||||||
pr = buffered_frames.process()
|
pr = buffered_frames.process()
|
||||||
|
|
||||||
img = pr.new_data
|
img = pr.new_data
|
||||||
if state.is_showing_window and img is not None:
|
if img is not None:
|
||||||
cv2.imshow(self._wnd_name, img)
|
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:
|
if state.is_showing_window:
|
||||||
cv2.waitKey(1)
|
cv2.waitKey(1)
|
||||||
|
@ -277,7 +318,10 @@ class Sheet:
|
||||||
self.save_sequence_path = lib_csw.Paths.Client()
|
self.save_sequence_path = lib_csw.Paths.Client()
|
||||||
self.save_sequence_path_error = lib_csw.Error.Client()
|
self.save_sequence_path_error = lib_csw.Error.Client()
|
||||||
self.save_fill_frame_gap = lib_csw.Flag.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):
|
class Worker(lib_csw.Sheet.Worker):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
@ -289,7 +333,10 @@ class Sheet:
|
||||||
self.save_sequence_path = lib_csw.Paths.Host()
|
self.save_sequence_path = lib_csw.Paths.Host()
|
||||||
self.save_sequence_path_error = lib_csw.Error.Host()
|
self.save_sequence_path_error = lib_csw.Error.Host()
|
||||||
self.save_fill_frame_gap = lib_csw.Flag.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):
|
class WorkerState(BackendWorkerState):
|
||||||
source_type : SourceType = None
|
source_type : SourceType = None
|
||||||
is_showing_window : bool = None
|
is_showing_window : bool = None
|
||||||
|
@ -297,3 +344,6 @@ class WorkerState(BackendWorkerState):
|
||||||
target_delay : int = None
|
target_delay : int = None
|
||||||
sequence_path : Path = None
|
sequence_path : Path = None
|
||||||
save_fill_frame_gap : bool = 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 localization import L
|
||||||
|
from resources.fonts import QXFontDB
|
||||||
from xlib import qt as qtx
|
from xlib import qt as qtx
|
||||||
|
from xlib.qt.widgets.QXLabel import QXLabel
|
||||||
|
|
||||||
from ..backend import StreamOutput
|
from ..backend import StreamOutput
|
||||||
from .widgets.QBackendPanel import QBackendPanel
|
from .widgets.QBackendPanel import QBackendPanel
|
||||||
|
@ -9,6 +11,7 @@ from .widgets.QComboBoxCSWDynamicSingleSwitch import \
|
||||||
from .widgets.QErrorCSWError import QErrorCSWError
|
from .widgets.QErrorCSWError import QErrorCSWError
|
||||||
from .widgets.QLabelCSWNumber import QLabelCSWNumber
|
from .widgets.QLabelCSWNumber import QLabelCSWNumber
|
||||||
from .widgets.QLabelPopupInfo import QLabelPopupInfo
|
from .widgets.QLabelPopupInfo import QLabelPopupInfo
|
||||||
|
from .widgets.QLineEditCSWText import QLineEditCSWText
|
||||||
from .widgets.QPathEditCSWPaths import QPathEditCSWPaths
|
from .widgets.QPathEditCSWPaths import QPathEditCSWPaths
|
||||||
from .widgets.QSpinBoxCSWNumber import QSpinBoxCSWNumber
|
from .widgets.QSpinBoxCSWNumber import QSpinBoxCSWNumber
|
||||||
from .widgets.QXPushButtonCSWSignal import QXPushButtonCSWSignal
|
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_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_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)
|
grid_l = qtx.QXGridLayout(spacing=5)
|
||||||
row = 0
|
row = 0
|
||||||
grid_l.addWidget(q_average_fps_label, row, 0, 1, 1, alignment=qtx.AlignRight | qtx.AlignVCenter )
|
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
|
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 )
|
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
|
row += 1
|
||||||
|
|
||||||
grid_l.addWidget(q_save_sequence_path_error, row, 0, 1, 3)
|
grid_l.addWidget(q_save_sequence_path_error, row, 0, 1, 3)
|
||||||
row += 1
|
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'),
|
super().__init__(backend, L('@QStreamOutput.module_title'),
|
||||||
layout=grid_l)
|
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)
|
BIN
doc/setup_tutorial_windows/Media_source.png
Normal file
BIN
doc/setup_tutorial_windows/Media_source.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
BIN
doc/setup_tutorial_windows/Media_source_config.png
Normal file
BIN
doc/setup_tutorial_windows/Media_source_config.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
BIN
doc/setup_tutorial_windows/Media_source_stream_output.png
Normal file
BIN
doc/setup_tutorial_windows/Media_source_stream_output.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
|
@ -9,7 +9,7 @@ DeepFaceLive only provides a video window of the replaced face. Since the face m
|
||||||
|
|
||||||
So, what do we need for streaming?
|
So, what do we need for streaming?
|
||||||
|
|
||||||
Capture window and sound with some delay, transmit to streaming service (e.g. twitch, youtube, ...)
|
Capture window (or receive mpegts udp stream) and sound with some delay, transmit to streaming service (e.g. twitch, youtube, ...)
|
||||||
|
|
||||||
Below, one of the solutions.
|
Below, one of the solutions.
|
||||||
|
|
||||||
|
@ -63,8 +63,27 @@ Below, one of the solutions.
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<tr><td colspan=2 align="center">
|
<tr><td colspan=2 align="center">
|
||||||
|
|
||||||
### DONE !
|
### **DONE** !
|
||||||
### Now you can stream yourself to a stream service.
|
### Now you can stream yourself to a stream service.
|
||||||
|
|
||||||
</td></tr>
|
</td></tr>
|
||||||
|
|
||||||
|
<tr><td colspan=2 align="center">
|
||||||
|
|
||||||
|
### instead of **Window capture**
|
||||||
|
|
||||||
|
you can use **Media Source**
|
||||||
|
|
||||||
|
<img src="media_source.png"></img>
|
||||||
|
|
||||||
|
with configuration:
|
||||||
|
|
||||||
|
<img src="media_source_config.png"></img>
|
||||||
|
|
||||||
|
enable mpegts in **_Stream Output_**
|
||||||
|
|
||||||
|
<img src="Media_source_stream_output.png"></img>
|
||||||
|
|
||||||
|
</td></tr>
|
||||||
|
|
||||||
</table>
|
</table>
|
52
xlib/streamer/FFMPEGStreamer.py
Normal file
52
xlib/streamer/FFMPEGStreamer.py
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from .. import ffmpeg as lib_ffmpeg
|
||||||
|
|
||||||
|
|
||||||
|
class FFMPEGStreamer:
|
||||||
|
def __init__(self):
|
||||||
|
self._ffmpeg_proc = None
|
||||||
|
self._addr = '127.0.0.1'
|
||||||
|
self._port = 1234
|
||||||
|
self._width = 320
|
||||||
|
self._height = 240
|
||||||
|
|
||||||
|
def set_addr_port(self, addr : str, port : int):
|
||||||
|
if self._addr != addr or self._port != port:
|
||||||
|
self._addr = addr
|
||||||
|
self._port = port
|
||||||
|
self.stop()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
if self._ffmpeg_proc is not None:
|
||||||
|
self._ffmpeg_proc.kill()
|
||||||
|
self._ffmpeg_proc = None
|
||||||
|
|
||||||
|
def _restart(self):
|
||||||
|
self.stop()
|
||||||
|
args = ['-y', '-re',
|
||||||
|
'-f', 'rawvideo',
|
||||||
|
'-vcodec','rawvideo',
|
||||||
|
'-pix_fmt', 'bgr24',
|
||||||
|
'-s', f'{self._width}:{self._height}',
|
||||||
|
'-i', '-',
|
||||||
|
'-f', 'mpegts',
|
||||||
|
'-q:v', '2',
|
||||||
|
f'udp://{self._addr}:{self._port}'
|
||||||
|
]
|
||||||
|
self._ffmpeg_proc = lib_ffmpeg.run (args, pipe_stdin=True, quiet_stderr=True)#, pipe_stderr=True)
|
||||||
|
|
||||||
|
def push_frame(self, img : np.ndarray):
|
||||||
|
H,W,C = img.shape
|
||||||
|
if self._width != W or self._height != H:
|
||||||
|
self._width = W
|
||||||
|
self._height = H
|
||||||
|
self.stop()
|
||||||
|
|
||||||
|
if self._ffmpeg_proc is None:
|
||||||
|
self._restart()
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._ffmpeg_proc.stdin.write(img)
|
||||||
|
except:
|
||||||
|
self.stop()
|
1
xlib/streamer/__init__.py
Normal file
1
xlib/streamer/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
from .FFMPEGStreamer import FFMPEGStreamer
|
Loading…
Add table
Add a link
Reference in a new issue