mirror of
https://github.com/iperov/DeepFaceLive.git
synced 2025-02-23 02:27:20 -08:00
182 lines
6.3 KiB
Python
182 lines
6.3 KiB
Python
from pathlib import Path
|
|
from typing import Tuple
|
|
|
|
import numpy as np
|
|
from .. import ffmpeg as lib_ffmpeg
|
|
from .. import io as lib_io
|
|
|
|
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_v_idx = None
|
|
stream_fps = None
|
|
stream_width = None
|
|
stream_height = None
|
|
for stream in probe_info['streams']:
|
|
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)
|
|
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_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_v_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 = ffmpeg_proc = lib_ffmpeg.run (args, pipe_stdout=True, pipe_stderr=True)
|
|
self._ffmpeg_proc_stderr_lines = None
|
|
if ffmpeg_proc is not None:
|
|
self._ffmpeg_proc_stderr_lines = lib_io.IOThreadLinesReader(ffmpeg_proc.stderr, max_lines=5)
|
|
return True
|
|
return False
|
|
|
|
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:
|
|
# unpredicted end reached
|
|
err = '\r\n'.join(self._ffmpeg_proc_stderr_lines.get_lines(till_eof=True)[-5:])
|
|
self._ffmpeg_stop()
|
|
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, None
|
|
return None, 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, err = self._ffmpeg_next_frame(frame_diff)
|
|
if image is None:
|
|
return (None, f'ffmpeg error: {err}')
|
|
|
|
return (image, f'{self._filepath.name}_{idx:06}')
|
|
|
|
|
|
|