diff --git a/apps/DeepFaceLive/backend/BackendBase.py b/apps/DeepFaceLive/backend/BackendBase.py index 86f52cc..eac82d3 100644 --- a/apps/DeepFaceLive/backend/BackendBase.py +++ b/apps/DeepFaceLive/backend/BackendBase.py @@ -24,8 +24,6 @@ class BackendConnectionData: self._weak_heap_refs = {} self._weak_heap_image_infos = {} - self._errors = [] - self._uid = uid self._is_frame_reemitted = None @@ -76,9 +74,6 @@ class BackendConnectionData: return np.ndarray(shape, dtype=dtype, buffer=buffer) return None - def add_error(self, err : str): - self._errors.append(err) - def get_uid(self) -> int: return self._uid def get_is_frame_reemitted(self) -> Union[bool, None]: return self._is_frame_reemitted diff --git a/apps/DeepFaceLive/backend/FileSource.py b/apps/DeepFaceLive/backend/FileSource.py index 53dc17d..3405e79 100644 --- a/apps/DeepFaceLive/backend/FileSource.py +++ b/apps/DeepFaceLive/backend/FileSource.py @@ -112,7 +112,7 @@ class FileSourceWorker(BackendWorker): def on_cs_input_paths(self, paths, prev_paths): state, cs = self.get_state(), self.get_control_sheet() - cs.input_paths_error.set_error(None) + cs.error.set_error(None) input_type = state.input_type input_path = paths[0] if len(paths) != 0 else None @@ -174,7 +174,7 @@ class FileSourceWorker(BackendWorker): cs.seek_begin.enable() cs.seek_end.enable() else: - cs.input_paths_error.set_error(err) + cs.error.set_error(err) cs.input_paths.set_paths(prev_paths, block_event=True) self.save_state() @@ -263,6 +263,9 @@ class FileSourceWorker(BackendWorker): cs.seek_backward.enable() cs.seek_forward.enable() + if pr.new_error is not None: + cs.error.set_error(pr.new_error) + if pr.new_frame_idx is not None: cs.frame_index.set_number(pr.new_frame_idx, block_event=True) @@ -284,16 +287,11 @@ class FileSourceWorker(BackendWorker): bcd.set_frame_num(p_frame.frame_num) bcd.set_frame_fps(p_frame.fps) bcd.set_frame_timestamp(p_frame.timestamp) - bcd.set_frame_name(p_frame.name) image = ImageProcessor(p_frame.image).to_uint8().get_image('HWC') - bcd.set_image(p_frame.name, image) - if p_frame.error is not None: - bcd.add_error(p_frame.error) - self.stop_profile_timing() self.pending_bcd = bcd @@ -325,7 +323,7 @@ class Sheet: super().__init__() self.input_type = lib_csw.DynamicSingleSwitch.Client() self.input_paths = lib_csw.Paths.Client() - self.input_paths_error = lib_csw.Error.Client() + self.error = lib_csw.Error.Client() self.target_width = lib_csw.Number.Client() self.fps = lib_csw.Number.Client() @@ -348,7 +346,7 @@ class Sheet: super().__init__() self.input_type = lib_csw.DynamicSingleSwitch.Host() self.input_paths = lib_csw.Paths.Host() - self.input_paths_error = lib_csw.Error.Host() + self.error = lib_csw.Error.Host() self.target_width = lib_csw.Number.Host() self.fps = lib_csw.Number.Host() diff --git a/apps/DeepFaceLive/ui/QFileSource.py b/apps/DeepFaceLive/ui/QFileSource.py index 69ee7c0..15c8709 100644 --- a/apps/DeepFaceLive/ui/QFileSource.py +++ b/apps/DeepFaceLive/ui/QFileSource.py @@ -22,32 +22,32 @@ class QFileSource(QBackendPanel): def __init__(self, backend : FileSource): cs = backend.get_control_sheet() - q_input_type = self._q_input_type = QButtonCSWDynamicSingleSwitch(cs.input_type, horizontal=True, radio_buttons=True) - q_input_paths = self._q_input_paths = QPathEditCSWPaths(cs.input_paths) - q_input_paths_error = self._q_input_paths_error = QErrorCSWError(cs.input_paths_error) + q_input_type = QButtonCSWDynamicSingleSwitch(cs.input_type, horizontal=True, radio_buttons=True) + q_input_paths = QPathEditCSWPaths(cs.input_paths) + q_error = QErrorCSWError(cs.error) q_target_width_label = QLabelPopupInfo(label=L('@QFileSource.target_width'), popup_info_text=L('@QFileSource.help.target_width') ) - q_target_width = self._q_target_width = QSpinBoxCSWNumber(cs.target_width, reflect_state_widgets=[q_target_width_label]) + q_target_width = QSpinBoxCSWNumber(cs.target_width, reflect_state_widgets=[q_target_width_label]) q_fps_label = QLabelPopupInfo(label=L('@QFileSource.fps'), popup_info_text=L('@QFileSource.help.fps') ) - q_fps = self._q_fps = QSpinBoxCSWNumber(cs.fps, reflect_state_widgets=[q_fps_label]) + q_fps = QSpinBoxCSWNumber(cs.fps, reflect_state_widgets=[q_fps_label]) q_is_realtime_label = QLabelPopupInfo(label=L('@QFileSource.is_realtime'), popup_info_text=L('@QFileSource.help.is_realtime') ) - q_is_realtime = self._q_is_realtime = QCheckBoxCSWFlag(cs.is_realtime, reflect_state_widgets=[q_is_realtime_label]) + q_is_realtime = QCheckBoxCSWFlag(cs.is_realtime, reflect_state_widgets=[q_is_realtime_label]) q_is_autorewind_label = QLabelPopupInfo(label=L('@QFileSource.is_autorewind')) - q_is_autorewind = self._q_is_autorewind = QCheckBoxCSWFlag(cs.is_autorewind, reflect_state_widgets=[q_is_autorewind_label]) + q_is_autorewind = QCheckBoxCSWFlag(cs.is_autorewind, reflect_state_widgets=[q_is_autorewind_label]) btn_size=(32,32) btn_color= '#E01010' - btn_play = self._btn_play = QXPushButtonCSWSignal(cs.play, image=QXImageDB.play_circle_outline(btn_color), button_size=btn_size ) - btn_pause = self._btn_pause = QXPushButtonCSWSignal(cs.pause, image=QXImageDB.pause_circle_outline(btn_color), button_size=btn_size ) - btn_seek_backward = self._btn_seek_backward = QXPushButtonCSWSignal(cs.seek_backward, image=QXImageDB.play_back_circle_outline(btn_color), button_size=btn_size ) - btn_seek_forward = self._btn_seek_forward = QXPushButtonCSWSignal(cs.seek_forward, image=QXImageDB.play_forward_circle_outline(btn_color), button_size=btn_size ) - btn_seek_begin = self._btn_seek_begin = QXPushButtonCSWSignal(cs.seek_begin, image=QXImageDB.play_skip_back_circle_outline(btn_color), button_size=btn_size ) - btn_seek_end = self._btn_seek_end = QXPushButtonCSWSignal(cs.seek_end, image=QXImageDB.play_skip_forward_circle_outline(btn_color), button_size=btn_size ) + btn_play = QXPushButtonCSWSignal(cs.play, image=QXImageDB.play_circle_outline(btn_color), button_size=btn_size ) + btn_pause = QXPushButtonCSWSignal(cs.pause, image=QXImageDB.pause_circle_outline(btn_color), button_size=btn_size ) + btn_seek_backward = QXPushButtonCSWSignal(cs.seek_backward, image=QXImageDB.play_back_circle_outline(btn_color), button_size=btn_size ) + btn_seek_forward = QXPushButtonCSWSignal(cs.seek_forward, image=QXImageDB.play_forward_circle_outline(btn_color), button_size=btn_size ) + btn_seek_begin = QXPushButtonCSWSignal(cs.seek_begin, image=QXImageDB.play_skip_back_circle_outline(btn_color), button_size=btn_size ) + btn_seek_end = QXPushButtonCSWSignal(cs.seek_end, image=QXImageDB.play_skip_forward_circle_outline(btn_color), button_size=btn_size ) - q_frame_slider = self._q_frame_slider = QSliderCSWNumbers(cs.frame_index, cs.frame_count) + q_frame_slider = QSliderCSWNumbers(cs.frame_index, cs.frame_count) grid_l = lib_qt.QXGridLayout(spacing=5) row = 0 @@ -68,7 +68,7 @@ class QFileSource(QBackendPanel): main_l = lib_qt.QXVBoxLayout([q_input_type, q_input_paths, - q_input_paths_error, + q_error, grid_l ], spacing=5) diff --git a/xlib/ffmpeg/ffmpeg.py b/xlib/ffmpeg/ffmpeg.py index 7dac1cd..4551420 100644 --- a/xlib/ffmpeg/ffmpeg.py +++ b/xlib/ffmpeg/ffmpeg.py @@ -14,7 +14,7 @@ def run(args, pipe_stdin=False, pipe_stdout=False, pipe_stderr=False, quiet_std_ stdout_stream = subprocess.PIPE if pipe_stdout else None stderr_stream = subprocess.PIPE if pipe_stderr else None - if quiet_std_err: + if quiet_std_err and not pipe_stderr: stderr_stream = subprocess.DEVNULL try: diff --git a/xlib/player/FramePlayer.py b/xlib/player/FramePlayer.py index ee0ccfc..34ca678 100644 --- a/xlib/player/FramePlayer.py +++ b/xlib/player/FramePlayer.py @@ -14,13 +14,12 @@ class FramePlayer(Disposable): class Frame: __slots__ = ['image','timestamp','fps','frame_num','frame_count','name','error'] - image : np.ndarray # if none - error during loading frame + image : np.ndarray timestamp : float fps : float frame_num : int frame_count : int name : str - error : str def __init__(self): self.image = None @@ -29,7 +28,6 @@ class FramePlayer(Disposable): self.frame_num = None self.frame_count = None self.name = None - self.error = None def __init__(self, default_fps, frame_count): if frame_count == 0: @@ -142,12 +140,13 @@ class FramePlayer(Disposable): self._req_is_playing = False class ProcessResult: - __slots__ = ['new_is_playing','new_frame_idx','new_frame'] + __slots__ = ['new_is_playing','new_frame_idx','new_frame','new_error'] def __init__(self): self.new_is_playing = None self.new_frame_idx = None self.new_frame = None + self.new_error : str = None def process(self) -> 'FramePlayer.ProcessResult': """ @@ -225,19 +224,11 @@ class FramePlayer(Disposable): update_frame = True if update_frame: - # Frame changed, construct Frame() with current values _frame_idx = self._frame_idx _cached_frames = self._cached_frames _cached_frames_idxs = self._cached_frames_idxs - p_frame = result.new_frame = FramePlayer.Frame() - p_frame.fps = fps - p_frame.timestamp = self._frame_timestamp - - p_frame.frame_num = _frame_idx - p_frame.frame_count = self._frame_count - frame_tuple = _cached_frames.get(_frame_idx, None) if frame_tuple is None: frame_tuple = self._on_get_frame(_frame_idx) @@ -249,11 +240,18 @@ class FramePlayer(Disposable): frame_image, name_or_err = frame_tuple if frame_image is None: - # frame is not provided, stop playing, but return p_frame without an image + # frame is not provided, stop playing new_is_playing = False - p_frame.error = name_or_err + result.new_error = name_or_err else: # frame is provided. + p_frame = result.new_frame = FramePlayer.Frame() + p_frame.fps = fps + p_frame.timestamp = self._frame_timestamp + + p_frame.frame_num = _frame_idx + p_frame.frame_count = self._frame_count + ip = ImageProcessor(frame_image) if self._target_width != 0: ip.fit_in(TW=self._target_width) @@ -262,7 +260,6 @@ class FramePlayer(Disposable): p_frame.image = frame_image p_frame.name = name_or_err - if new_is_playing is not None and self._is_playing and not new_is_playing: # Stop playing result.new_is_playing = self._is_playing = False diff --git a/xlib/player/VideoFilePlayer.py b/xlib/player/VideoFilePlayer.py index 147f92b..1aadf88 100644 --- a/xlib/player/VideoFilePlayer.py +++ b/xlib/player/VideoFilePlayer.py @@ -50,16 +50,13 @@ class VideoFilePlayer(FramePlayer): probe_info = lib_ffmpeg.probe (str(filepath)) # Analize probe_info - stream_idx = None + stream_v_idx = None stream_fps = None stream_width = None stream_height = None for stream in probe_info['streams']: - if stream_idx is None and stream['codec_type'] == 'video': - #print(stream) - stream_idx = stream.get('index',None) - if stream_idx is not None: - stream_idx = int(stream_idx) + if stream_v_idx is None and stream['codec_type'] == 'video': + stream_v_idx = 0 stream_width = stream.get('width', None) if stream_width is not None: stream_width = int(stream_width) @@ -75,12 +72,12 @@ class VideoFilePlayer(FramePlayer): stream_fps = eval(stream_fps) break - if any( x is None for x in [stream_idx, stream_width, stream_height, stream_start_time, stream_duration, stream_fps] ): + if any( x is None for x in [stream_v_idx, stream_width, stream_height, stream_start_time, stream_duration, stream_fps] ): raise Exception(f'Incorrect video file.') stream_frame_count = round( ( float(stream_duration)-float(stream_start_time) ) / (1.0/stream_fps) ) - self._stream_idx = stream_idx + self._stream_idx = stream_v_idx self._stream_width = stream_width self._stream_height = stream_height self._stream_fps = stream_fps @@ -128,7 +125,7 @@ class VideoFilePlayer(FramePlayer): '-map', f'0:v:{self._stream_idx}', 'pipe:'] - self._ffmpeg_proc = lib_ffmpeg.run (args, pipe_stdout=True, quiet_std_err=True) + self._ffmpeg_proc = lib_ffmpeg.run (args, pipe_stdout=True, pipe_stderr=True) return self._ffmpeg_proc is not None def _ffmpeg_next_frame(self, frames_idx_offset=1): @@ -137,15 +134,20 @@ class VideoFilePlayer(FramePlayer): while frames_idx_offset != 0: frame_buffer = self._ffmpeg_proc.stdout.read(self._ffmpeg_height*self._ffmpeg_width*3) if len(frame_buffer) == 0: + err = self._ffmpeg_proc.stderr.read() + err_lines = err.decode('utf-8').split('\r\n') + err = '\r\n'.join(err_lines[-5:]) + # End reached self._ffmpeg_stop() - return None + + return None, err frames_idx_offset -= 1 if frame_buffer is not None: frame_image = np.ndarray( (self._ffmpeg_height, self._ffmpeg_width, 3), dtype=np.uint8, buffer=frame_buffer).copy() - return frame_image - return None + return frame_image, None + return None, None def _on_target_width_changed(self): self._ffmpeg_need_restart = True @@ -168,9 +170,9 @@ class VideoFilePlayer(FramePlayer): frame_diff = max(1, frame_diff) #frame_diff += 1 - image = self._ffmpeg_next_frame(frame_diff) + image, err = self._ffmpeg_next_frame(frame_diff) if image is None: - return (None, 'Unpredicted end of stream.') + return (None, f'ffmpeg error: {err}') return (image, f'{self._filepath.name}_{idx:06}')