fix ffmpeg and FileSource error handling.

This commit is contained in:
iperov 2021-08-08 09:57:40 +04:00
parent 552b9d49f2
commit e1aa8a82f1
6 changed files with 51 additions and 59 deletions

View file

@ -24,8 +24,6 @@ class BackendConnectionData:
self._weak_heap_refs = {} self._weak_heap_refs = {}
self._weak_heap_image_infos = {} self._weak_heap_image_infos = {}
self._errors = []
self._uid = uid self._uid = uid
self._is_frame_reemitted = None self._is_frame_reemitted = None
@ -76,9 +74,6 @@ class BackendConnectionData:
return np.ndarray(shape, dtype=dtype, buffer=buffer) return np.ndarray(shape, dtype=dtype, buffer=buffer)
return None return None
def add_error(self, err : str):
self._errors.append(err)
def get_uid(self) -> int: return self._uid def get_uid(self) -> int: return self._uid
def get_is_frame_reemitted(self) -> Union[bool, None]: return self._is_frame_reemitted def get_is_frame_reemitted(self) -> Union[bool, None]: return self._is_frame_reemitted

View file

@ -112,7 +112,7 @@ class FileSourceWorker(BackendWorker):
def on_cs_input_paths(self, paths, prev_paths): def on_cs_input_paths(self, paths, prev_paths):
state, cs = self.get_state(), self.get_control_sheet() 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_type = state.input_type
input_path = paths[0] if len(paths) != 0 else None input_path = paths[0] if len(paths) != 0 else None
@ -174,7 +174,7 @@ class FileSourceWorker(BackendWorker):
cs.seek_begin.enable() cs.seek_begin.enable()
cs.seek_end.enable() cs.seek_end.enable()
else: else:
cs.input_paths_error.set_error(err) cs.error.set_error(err)
cs.input_paths.set_paths(prev_paths, block_event=True) cs.input_paths.set_paths(prev_paths, block_event=True)
self.save_state() self.save_state()
@ -263,6 +263,9 @@ class FileSourceWorker(BackendWorker):
cs.seek_backward.enable() cs.seek_backward.enable()
cs.seek_forward.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: if pr.new_frame_idx is not None:
cs.frame_index.set_number(pr.new_frame_idx, block_event=True) 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_num(p_frame.frame_num)
bcd.set_frame_fps(p_frame.fps) bcd.set_frame_fps(p_frame.fps)
bcd.set_frame_timestamp(p_frame.timestamp) bcd.set_frame_timestamp(p_frame.timestamp)
bcd.set_frame_name(p_frame.name) bcd.set_frame_name(p_frame.name)
image = ImageProcessor(p_frame.image).to_uint8().get_image('HWC') image = ImageProcessor(p_frame.image).to_uint8().get_image('HWC')
bcd.set_image(p_frame.name, image) 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.stop_profile_timing()
self.pending_bcd = bcd self.pending_bcd = bcd
@ -325,7 +323,7 @@ class Sheet:
super().__init__() super().__init__()
self.input_type = lib_csw.DynamicSingleSwitch.Client() self.input_type = lib_csw.DynamicSingleSwitch.Client()
self.input_paths = lib_csw.Paths.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.target_width = lib_csw.Number.Client()
self.fps = lib_csw.Number.Client() self.fps = lib_csw.Number.Client()
@ -348,7 +346,7 @@ class Sheet:
super().__init__() super().__init__()
self.input_type = lib_csw.DynamicSingleSwitch.Host() self.input_type = lib_csw.DynamicSingleSwitch.Host()
self.input_paths = lib_csw.Paths.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.target_width = lib_csw.Number.Host()
self.fps = lib_csw.Number.Host() self.fps = lib_csw.Number.Host()

View file

@ -22,32 +22,32 @@ class QFileSource(QBackendPanel):
def __init__(self, backend : FileSource): def __init__(self, backend : FileSource):
cs = backend.get_control_sheet() cs = backend.get_control_sheet()
q_input_type = self._q_input_type = QButtonCSWDynamicSingleSwitch(cs.input_type, horizontal=True, radio_buttons=True) 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 = QPathEditCSWPaths(cs.input_paths)
q_input_paths_error = self._q_input_paths_error = QErrorCSWError(cs.input_paths_error) 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_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_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_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_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_size=(32,32)
btn_color= '#E01010' btn_color= '#E01010'
btn_play = self._btn_play = QXPushButtonCSWSignal(cs.play, image=QXImageDB.play_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 = self._btn_pause = QXPushButtonCSWSignal(cs.pause, image=QXImageDB.pause_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 = self._btn_seek_backward = QXPushButtonCSWSignal(cs.seek_backward, image=QXImageDB.play_back_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 = self._btn_seek_forward = QXPushButtonCSWSignal(cs.seek_forward, image=QXImageDB.play_forward_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 = self._btn_seek_begin = QXPushButtonCSWSignal(cs.seek_begin, image=QXImageDB.play_skip_back_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 = self._btn_seek_end = QXPushButtonCSWSignal(cs.seek_end, image=QXImageDB.play_skip_forward_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) grid_l = lib_qt.QXGridLayout(spacing=5)
row = 0 row = 0
@ -68,7 +68,7 @@ class QFileSource(QBackendPanel):
main_l = lib_qt.QXVBoxLayout([q_input_type, main_l = lib_qt.QXVBoxLayout([q_input_type,
q_input_paths, q_input_paths,
q_input_paths_error, q_error,
grid_l grid_l
], spacing=5) ], spacing=5)

View file

@ -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 stdout_stream = subprocess.PIPE if pipe_stdout else None
stderr_stream = subprocess.PIPE if pipe_stderr 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 stderr_stream = subprocess.DEVNULL
try: try:

View file

@ -14,13 +14,12 @@ class FramePlayer(Disposable):
class Frame: class Frame:
__slots__ = ['image','timestamp','fps','frame_num','frame_count','name','error'] __slots__ = ['image','timestamp','fps','frame_num','frame_count','name','error']
image : np.ndarray # if none - error during loading frame image : np.ndarray
timestamp : float timestamp : float
fps : float fps : float
frame_num : int frame_num : int
frame_count : int frame_count : int
name : str name : str
error : str
def __init__(self): def __init__(self):
self.image = None self.image = None
@ -29,7 +28,6 @@ class FramePlayer(Disposable):
self.frame_num = None self.frame_num = None
self.frame_count = None self.frame_count = None
self.name = None self.name = None
self.error = None
def __init__(self, default_fps, frame_count): def __init__(self, default_fps, frame_count):
if frame_count == 0: if frame_count == 0:
@ -142,12 +140,13 @@ class FramePlayer(Disposable):
self._req_is_playing = False self._req_is_playing = False
class ProcessResult: 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): def __init__(self):
self.new_is_playing = None self.new_is_playing = None
self.new_frame_idx = None self.new_frame_idx = None
self.new_frame = None self.new_frame = None
self.new_error : str = None
def process(self) -> 'FramePlayer.ProcessResult': def process(self) -> 'FramePlayer.ProcessResult':
""" """
@ -225,19 +224,11 @@ class FramePlayer(Disposable):
update_frame = True update_frame = True
if update_frame: if update_frame:
# Frame changed, construct Frame() with current values # Frame changed, construct Frame() with current values
_frame_idx = self._frame_idx _frame_idx = self._frame_idx
_cached_frames = self._cached_frames _cached_frames = self._cached_frames
_cached_frames_idxs = self._cached_frames_idxs _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) frame_tuple = _cached_frames.get(_frame_idx, None)
if frame_tuple is None: if frame_tuple is None:
frame_tuple = self._on_get_frame(_frame_idx) frame_tuple = self._on_get_frame(_frame_idx)
@ -249,11 +240,18 @@ class FramePlayer(Disposable):
frame_image, name_or_err = frame_tuple frame_image, name_or_err = frame_tuple
if frame_image is None: 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 new_is_playing = False
p_frame.error = name_or_err result.new_error = name_or_err
else: else:
# frame is provided. # 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) ip = ImageProcessor(frame_image)
if self._target_width != 0: if self._target_width != 0:
ip.fit_in(TW=self._target_width) ip.fit_in(TW=self._target_width)
@ -262,7 +260,6 @@ class FramePlayer(Disposable):
p_frame.image = frame_image p_frame.image = frame_image
p_frame.name = name_or_err p_frame.name = name_or_err
if new_is_playing is not None and self._is_playing and not new_is_playing: if new_is_playing is not None and self._is_playing and not new_is_playing:
# Stop playing # Stop playing
result.new_is_playing = self._is_playing = False result.new_is_playing = self._is_playing = False

View file

@ -50,16 +50,13 @@ class VideoFilePlayer(FramePlayer):
probe_info = lib_ffmpeg.probe (str(filepath)) probe_info = lib_ffmpeg.probe (str(filepath))
# Analize probe_info # Analize probe_info
stream_idx = None stream_v_idx = None
stream_fps = None stream_fps = None
stream_width = None stream_width = None
stream_height = None stream_height = None
for stream in probe_info['streams']: for stream in probe_info['streams']:
if stream_idx is None and stream['codec_type'] == 'video': if stream_v_idx is None and stream['codec_type'] == 'video':
#print(stream) stream_v_idx = 0
stream_idx = stream.get('index',None)
if stream_idx is not None:
stream_idx = int(stream_idx)
stream_width = stream.get('width', None) stream_width = stream.get('width', None)
if stream_width is not None: if stream_width is not None:
stream_width = int(stream_width) stream_width = int(stream_width)
@ -75,12 +72,12 @@ class VideoFilePlayer(FramePlayer):
stream_fps = eval(stream_fps) stream_fps = eval(stream_fps)
break 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.') raise Exception(f'Incorrect video file.')
stream_frame_count = round( ( float(stream_duration)-float(stream_start_time) ) / (1.0/stream_fps) ) 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_width = stream_width
self._stream_height = stream_height self._stream_height = stream_height
self._stream_fps = stream_fps self._stream_fps = stream_fps
@ -128,7 +125,7 @@ class VideoFilePlayer(FramePlayer):
'-map', f'0:v:{self._stream_idx}', '-map', f'0:v:{self._stream_idx}',
'pipe:'] '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 return self._ffmpeg_proc is not None
def _ffmpeg_next_frame(self, frames_idx_offset=1): def _ffmpeg_next_frame(self, frames_idx_offset=1):
@ -137,15 +134,20 @@ class VideoFilePlayer(FramePlayer):
while frames_idx_offset != 0: while frames_idx_offset != 0:
frame_buffer = self._ffmpeg_proc.stdout.read(self._ffmpeg_height*self._ffmpeg_width*3) frame_buffer = self._ffmpeg_proc.stdout.read(self._ffmpeg_height*self._ffmpeg_width*3)
if len(frame_buffer) == 0: 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 # End reached
self._ffmpeg_stop() self._ffmpeg_stop()
return None
return None, err
frames_idx_offset -= 1 frames_idx_offset -= 1
if frame_buffer is not None: if frame_buffer is not None:
frame_image = np.ndarray( (self._ffmpeg_height, self._ffmpeg_width, 3), dtype=np.uint8, buffer=frame_buffer).copy() frame_image = np.ndarray( (self._ffmpeg_height, self._ffmpeg_width, 3), dtype=np.uint8, buffer=frame_buffer).copy()
return frame_image return frame_image, None
return None return None, None
def _on_target_width_changed(self): def _on_target_width_changed(self):
self._ffmpeg_need_restart = True self._ffmpeg_need_restart = True
@ -168,9 +170,9 @@ class VideoFilePlayer(FramePlayer):
frame_diff = max(1, frame_diff) frame_diff = max(1, frame_diff)
#frame_diff += 1 #frame_diff += 1
image = self._ffmpeg_next_frame(frame_diff) image, err = self._ffmpeg_next_frame(frame_diff)
if image is None: if image is None:
return (None, 'Unpredicted end of stream.') return (None, f'ffmpeg error: {err}')
return (image, f'{self._filepath.name}_{idx:06}') return (image, f'{self._filepath.name}_{idx:06}')