code release

This commit is contained in:
iperov 2021-07-23 17:34:49 +04:00
parent b941ba41a3
commit a902f11f74
354 changed files with 826570 additions and 1 deletions

View file

@ -0,0 +1,178 @@
from pathlib import Path
from typing import Tuple
import numpy as np
from xlib import ffmpeg as lib_ffmpeg
from .FramePlayer import FramePlayer
class VideoFilePlayer(FramePlayer):
"""
Play video track from the video file using subprocess ffmpeg.
arguments
filepath str/Path path to video file
is_realtime(True) bool False - process every frame as fast as possible
fps parameter will be ignored
True - process in real time with desired fps
is_autorewind(True) bool
fps(None) float specify fps.
None - video fps will be used
target_width(None) int if None : resolution will be not modified
raises
Exception path does not exists
ffprobe failed
file has no video tracks
"""
SUPPORTED_VIDEO_FILE_SUFFIXES = ['.avi','.mkv','.mp4']
def __init__(self, filepath):
self._ffmpeg_proc = None
self._filepath = filepath = Path(filepath)
if not filepath.exists():
raise Exception(f'{filepath} does not exist.')
if not filepath.is_file():
raise Exception(f'{filepath} is not a file.')
if not filepath.suffix in VideoFilePlayer.SUPPORTED_VIDEO_FILE_SUFFIXES:
raise Exception(f'Supported video files: {VideoFilePlayer.SUPPORTED_VIDEO_FILE_SUFFIXES}')
probe_info = lib_ffmpeg.probe (str(filepath))
# Analize probe_info
stream_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)
stream_width = stream.get('width', None)
if stream_width is not None:
stream_width = int(stream_width)
stream_height = stream.get('height', None)
if stream_height is not None:
stream_height = int(stream_height)
stream_start_time = stream.get('start_time', None)
stream_duration = stream.get('duration', None)
stream_fps = stream.get('avg_frame_rate', None)
if stream_fps is None:
stream_fps = stream.get('r_frame_rate', None)
if stream_fps is not None:
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] ):
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_width = stream_width
self._stream_height = stream_height
self._stream_fps = stream_fps
self._ffmpeg_need_restart = False
self._ffmpeg_frame_idx = -1
super().__init__(default_fps=stream_fps, frame_count=stream_frame_count)
def _on_dispose(self):
self._ffmpeg_stop()
super()._on_dispose()
def _ffmpeg_stop(self):
if self._ffmpeg_proc is not None:
self._ffmpeg_proc.kill()
self._ffmpeg_proc = None
def _ffmpeg_restart(self, start_frame_number=0):
#print('_ffmpeg_restart')
self._ffmpeg_stop()
_target_width = self._target_width
if _target_width == 0:
_width = self._ffmpeg_width = self._stream_width
_height = self._ffmpeg_height = self._stream_height
else:
_height = self._ffmpeg_height = int( _target_width / (self._stream_width / self._stream_height) )
_width = self._ffmpeg_width = _target_width
args = []
if start_frame_number != 0:
# -ss before -i to fast and accurate seek
# using time instead of frame, because '-vf select' does not work correctly with some videos
args += ['-ss', str(start_frame_number*(1.0 / self._stream_fps)) ]
args += ['-i', str(self._filepath),
'-s', f'{_width}:{_height}'
]
# Set exact FPS for constant framerate
args += ['-r', str(self._stream_fps)]
args += ['-f', 'rawvideo',
'-pix_fmt', 'bgr24',
'-map', f'0:v:{self._stream_idx}',
'pipe:']
self._ffmpeg_proc = lib_ffmpeg.run (args, pipe_stdout=True, quiet_std_err=True)
return self._ffmpeg_proc is not None
def _ffmpeg_next_frame(self, frames_idx_offset=1):
frame_buffer = None
while frames_idx_offset != 0:
frame_buffer = self._ffmpeg_proc.stdout.read(self._ffmpeg_height*self._ffmpeg_width*3)
if len(frame_buffer) == 0:
# End reached
self._ffmpeg_stop()
return None
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
def _on_target_width_changed(self):
self._ffmpeg_need_restart = True
def _on_get_frame(self, idx) -> Tuple[np.ndarray, str]:
frame_diff = idx - self._ffmpeg_frame_idx
self._ffmpeg_frame_idx = idx
if self._ffmpeg_proc is None or \
frame_diff <= 0 or frame_diff >= 100:
self._ffmpeg_need_restart = True
if self._ffmpeg_need_restart:
self._ffmpeg_need_restart = False
if not self._ffmpeg_restart(idx):
return (None, 'ffmpeg error')
frame_diff = 1
else:
frame_diff = max(1, frame_diff)
#frame_diff += 1
image = self._ffmpeg_next_frame(frame_diff)
if image is None:
return (None, 'Unpredicted end of stream.')
return (image, f'{self._filepath.name}_{idx:06}')