mirror of
https://github.com/facefusion/facefusion.git
synced 2026-07-28 21:08:54 +02:00
Drop the cv2 capture pool; all video reads go through ffmpeg/ffprobe
video_manager now pools only the ffmpeg reader and writer. read_video_frame seeks and reads through the reader; count_video_frame_total / detect_video_fps / detect_video_resolution read from ffprobe metadata (cached via lru_cache). Removes get_video_capture, conditional_set_video_frame_position, the cv2 import and the 'capture' pool key (and VideoCaptureSet). Inline the reader buffer margin as a local (buffer_margin = 16) instead of a module constant. Side effect: reference face-selector mode and the NSFW analyser now decode AV1 correctly, since they read frames through the reader instead of cv2. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
d88c1f6060
commit
f7dd894241
@@ -1,4 +1,5 @@
|
||||
import subprocess
|
||||
from functools import lru_cache
|
||||
from typing import Dict, List
|
||||
|
||||
from facefusion import ffprobe_builder
|
||||
@@ -47,6 +48,7 @@ def probe_video_entries(video_path : str, entries : List[str]) -> Dict[str, str]
|
||||
return parse_entries(output)
|
||||
|
||||
|
||||
@lru_cache(maxsize = 128)
|
||||
def extract_video_metadata(video_path : str) -> VideoMetadata:
|
||||
video_entries = probe_video_entries(video_path, [ 'duration', 'width', 'height', 'r_frame_rate', 'bit_rate' ])
|
||||
|
||||
|
||||
+2
-4
@@ -64,7 +64,6 @@ Language = Literal['en']
|
||||
Locales : TypeAlias = Dict[Language, Dict[str, Any]]
|
||||
LocalePoolSet : TypeAlias = Dict[str, Locales]
|
||||
|
||||
VideoCaptureSet : TypeAlias = Dict[str, cv2.VideoCapture]
|
||||
VideoWriterSet : TypeAlias = Dict[str, subprocess.Popen]
|
||||
CameraCaptureSet : TypeAlias = Dict[str, cv2.VideoCapture]
|
||||
CameraPoolSet = TypedDict('CameraPoolSet',
|
||||
@@ -111,9 +110,8 @@ VideoReader = TypedDict('VideoReader',
|
||||
VideoReaderSet : TypeAlias = Dict[str, VideoReader]
|
||||
VideoPoolSet = TypedDict('VideoPoolSet',
|
||||
{
|
||||
'capture' : VideoCaptureSet,
|
||||
'writer' : VideoWriterSet,
|
||||
'reader' : VideoReaderSet
|
||||
'reader' : VideoReaderSet,
|
||||
'writer' : VideoWriterSet
|
||||
})
|
||||
Color : TypeAlias = Tuple[int, int, int, int]
|
||||
Padding : TypeAlias = Tuple[int, int, int, int]
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import subprocess
|
||||
from typing import Optional, Tuple, cast
|
||||
|
||||
import cv2
|
||||
import numpy
|
||||
|
||||
from facefusion import ffmpeg_builder, state_manager
|
||||
@@ -12,27 +11,9 @@ from facefusion.types import Fps, Resolution, VideoFormat, VideoPoolSet, VideoRe
|
||||
|
||||
VIDEO_POOL_SET : VideoPoolSet =\
|
||||
{
|
||||
'capture': {},
|
||||
'writer': {},
|
||||
'reader': {}
|
||||
'reader': {},
|
||||
'writer': {}
|
||||
}
|
||||
VIDEO_READER_BUFFER_MARGIN = 16
|
||||
|
||||
|
||||
def get_video_capture(video_path : str) -> cv2.VideoCapture:
|
||||
if video_path not in VIDEO_POOL_SET.get('capture'):
|
||||
video_capture = cv2.VideoCapture(video_path)
|
||||
|
||||
if video_capture.isOpened():
|
||||
VIDEO_POOL_SET['capture'][video_path] = video_capture
|
||||
|
||||
return VIDEO_POOL_SET.get('capture').get(video_path)
|
||||
|
||||
|
||||
def conditional_set_video_frame_position(video_capture : cv2.VideoCapture, frame_position : int) -> bool:
|
||||
if not video_capture.get(cv2.CAP_PROP_POS_FRAMES) == frame_position:
|
||||
return video_capture.set(cv2.CAP_PROP_POS_FRAMES, frame_position)
|
||||
return True
|
||||
|
||||
|
||||
def create_video_reader_process(video_path : str, frame_position : int, video_fps : Fps) -> subprocess.Popen[bytes]:
|
||||
@@ -90,22 +71,23 @@ def read_video_reader_frame(video_reader : VideoReader) -> Tuple[bool, Optional[
|
||||
return False, None
|
||||
|
||||
|
||||
def evict_video_reader_buffer(video_reader : VideoReader, frame_start : int) -> None:
|
||||
def evict_video_reader_buffer(video_reader : VideoReader, frame_start : int, buffer_margin : int) -> None:
|
||||
frame_buffer = video_reader.get('frame_buffer')
|
||||
|
||||
for frame_number in [ key for key in frame_buffer if key < frame_start - VIDEO_READER_BUFFER_MARGIN ]:
|
||||
for frame_number in [ key for key in frame_buffer if key < frame_start - buffer_margin ]:
|
||||
del frame_buffer[frame_number]
|
||||
|
||||
|
||||
def read_video_reader_window(video_reader : VideoReader, frame_start : int, frame_end : int) -> VideoReaderBuffer:
|
||||
frame_buffer = video_reader.get('frame_buffer')
|
||||
buffer_margin = 16
|
||||
read_start = max(frame_start, 0)
|
||||
read_end = frame_end
|
||||
|
||||
if video_reader.get('frame_total') > 0:
|
||||
read_end = min(read_end, video_reader.get('frame_total') - 1)
|
||||
|
||||
if read_start not in frame_buffer and (read_start < video_reader.get('position') or read_start > video_reader.get('position') + VIDEO_READER_BUFFER_MARGIN):
|
||||
if read_start not in frame_buffer and (read_start < video_reader.get('position') or read_start > video_reader.get('position') + buffer_margin):
|
||||
restart_video_reader(video_reader, read_start)
|
||||
|
||||
reading = video_reader.get('position') <= read_end
|
||||
@@ -117,7 +99,7 @@ def read_video_reader_window(video_reader : VideoReader, frame_start : int, fram
|
||||
frame_buffer[video_reader.get('position') - 1] = vision_frame
|
||||
reading = has_vision_frame and video_reader.get('position') <= read_end
|
||||
|
||||
evict_video_reader_buffer(video_reader, read_start)
|
||||
evict_video_reader_buffer(video_reader, read_start, buffer_margin)
|
||||
return frame_buffer
|
||||
|
||||
|
||||
@@ -170,15 +152,11 @@ def close_video_writer(video_writer : subprocess.Popen[bytes]) -> bool:
|
||||
|
||||
|
||||
def clear_video_pool() -> None:
|
||||
for video_capture in VIDEO_POOL_SET.get('capture').values():
|
||||
video_capture.release()
|
||||
|
||||
for video_writer in VIDEO_POOL_SET.get('writer').values():
|
||||
video_writer.terminate()
|
||||
|
||||
for video_reader in VIDEO_POOL_SET.get('reader').values():
|
||||
video_reader.get('process').terminate()
|
||||
|
||||
VIDEO_POOL_SET['capture'].clear()
|
||||
VIDEO_POOL_SET['writer'].clear()
|
||||
VIDEO_POOL_SET['reader'].clear()
|
||||
VIDEO_POOL_SET['writer'].clear()
|
||||
|
||||
+10
-26
@@ -7,10 +7,11 @@ import numpy
|
||||
from cv2.typing import Size
|
||||
|
||||
from facefusion.common_helper import is_windows
|
||||
from facefusion.ffprobe import extract_video_metadata
|
||||
from facefusion.filesystem import get_file_extension, is_image, is_video
|
||||
from facefusion.thread_helper import thread_lock, thread_semaphore
|
||||
from facefusion.types import ColorMode, Duration, Fps, Mask, Orientation, Resolution, Scale, VisionFrame
|
||||
from facefusion.video_manager import conditional_set_video_frame_position, conditional_set_video_reader_position, get_video_capture, get_video_reader, read_video_reader_frame, read_video_reader_window
|
||||
from facefusion.video_manager import conditional_set_video_reader_position, get_video_reader, read_video_reader_frame, read_video_reader_window
|
||||
|
||||
|
||||
def read_static_images(image_paths : List[str], color_mode : ColorMode = 'rgb') -> List[VisionFrame]:
|
||||
@@ -77,15 +78,14 @@ def read_static_video_frame(video_path : str, frame_number : int = 0) -> Optiona
|
||||
|
||||
def read_video_frame(video_path : str, frame_number : int = 0) -> Optional[VisionFrame]:
|
||||
if is_video(video_path):
|
||||
video_capture = get_video_capture(video_path)
|
||||
video_reader = get_video_reader(video_path)
|
||||
|
||||
if video_capture and video_capture.isOpened():
|
||||
video_frame_total = int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||
video_frame_position = min(video_frame_total, frame_number)
|
||||
if video_reader:
|
||||
video_frame_position = min(video_reader.get('frame_total'), frame_number)
|
||||
|
||||
with thread_semaphore():
|
||||
conditional_set_video_frame_position(video_capture, video_frame_position)
|
||||
has_vision_frame, vision_frame = video_capture.read()
|
||||
conditional_set_video_reader_position(video_reader, video_frame_position)
|
||||
has_vision_frame, vision_frame = read_video_reader_frame(video_reader)
|
||||
|
||||
if has_vision_frame:
|
||||
return vision_frame
|
||||
@@ -146,12 +146,7 @@ def select_video_frames(video_path : str, frame_number : int = 0, frame_offset :
|
||||
|
||||
def count_video_frame_total(video_path : str) -> int:
|
||||
if is_video(video_path):
|
||||
video_capture = get_video_capture(video_path)
|
||||
|
||||
if video_capture and video_capture.isOpened():
|
||||
with thread_semaphore():
|
||||
video_frame_total = int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||
return video_frame_total
|
||||
return extract_video_metadata(video_path).get('frame_total')
|
||||
|
||||
return 0
|
||||
|
||||
@@ -166,12 +161,7 @@ def predict_video_frame_total(video_path : str, fps : Fps, trim_frame_start : in
|
||||
|
||||
def detect_video_fps(video_path : str) -> Optional[float]:
|
||||
if is_video(video_path):
|
||||
video_capture = get_video_capture(video_path)
|
||||
|
||||
if video_capture and video_capture.isOpened():
|
||||
with thread_semaphore():
|
||||
video_fps = video_capture.get(cv2.CAP_PROP_FPS)
|
||||
return video_fps
|
||||
return extract_video_metadata(video_path).get('fps')
|
||||
|
||||
return None
|
||||
|
||||
@@ -219,13 +209,7 @@ def restrict_trim_frame(video_path : str, trim_frame_start : Optional[int], trim
|
||||
|
||||
def detect_video_resolution(video_path : str) -> Optional[Resolution]:
|
||||
if is_video(video_path):
|
||||
video_capture = get_video_capture(video_path)
|
||||
|
||||
if video_capture and video_capture.isOpened():
|
||||
with thread_semaphore():
|
||||
width = video_capture.get(cv2.CAP_PROP_FRAME_WIDTH)
|
||||
height = video_capture.get(cv2.CAP_PROP_FRAME_HEIGHT)
|
||||
return int(width), int(height)
|
||||
return extract_video_metadata(video_path).get('resolution')
|
||||
|
||||
return None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user