Stream video frames to avoid disk I/O
parent
3dda4f2179
commit
cf814bbcee
|
@ -19,7 +19,24 @@ import modules.globals
|
||||||
import modules.metadata
|
import modules.metadata
|
||||||
import modules.ui as ui
|
import modules.ui as ui
|
||||||
from modules.processors.frame.core import get_frame_processors_modules
|
from modules.processors.frame.core import get_frame_processors_modules
|
||||||
from modules.utilities import has_image_extension, is_image, is_video, detect_fps, create_video, extract_frames, get_temp_frame_paths, restore_audio, create_temp, move_temp, clean_temp, normalize_output_path
|
from modules.utilities import (
|
||||||
|
has_image_extension,
|
||||||
|
is_image,
|
||||||
|
is_video,
|
||||||
|
detect_fps,
|
||||||
|
create_video,
|
||||||
|
extract_frames,
|
||||||
|
get_temp_frame_paths,
|
||||||
|
restore_audio,
|
||||||
|
create_temp,
|
||||||
|
move_temp,
|
||||||
|
clean_temp,
|
||||||
|
normalize_output_path,
|
||||||
|
start_ffmpeg_writer,
|
||||||
|
get_temp_output_path,
|
||||||
|
)
|
||||||
|
import cv2
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
if 'ROCMExecutionProvider' in modules.globals.execution_providers:
|
if 'ROCMExecutionProvider' in modules.globals.execution_providers:
|
||||||
del torch
|
del torch
|
||||||
|
@ -175,6 +192,45 @@ def update_status(message: str, scope: str = 'DLC.CORE') -> None:
|
||||||
if not modules.globals.headless:
|
if not modules.globals.headless:
|
||||||
ui.update_status(message)
|
ui.update_status(message)
|
||||||
|
|
||||||
|
|
||||||
|
def stream_video() -> None:
|
||||||
|
capture = cv2.VideoCapture(modules.globals.target_path)
|
||||||
|
if not capture.isOpened():
|
||||||
|
update_status('Failed to open video file.')
|
||||||
|
return
|
||||||
|
fps = capture.get(cv2.CAP_PROP_FPS) if modules.globals.keep_fps else 30.0
|
||||||
|
width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))
|
||||||
|
height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
||||||
|
total = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||||
|
|
||||||
|
update_status('Creating temp resources...')
|
||||||
|
create_temp(modules.globals.target_path)
|
||||||
|
temp_output_path = get_temp_output_path(modules.globals.target_path)
|
||||||
|
writer = start_ffmpeg_writer(width, height, fps, temp_output_path)
|
||||||
|
|
||||||
|
progress_bar_format = '{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}{postfix}]'
|
||||||
|
with tqdm(total=total, desc='Processing', unit='frame', dynamic_ncols=True, bar_format=progress_bar_format) as progress:
|
||||||
|
progress.set_postfix({'execution_providers': modules.globals.execution_providers, 'execution_threads': modules.globals.execution_threads, 'max_memory': modules.globals.max_memory})
|
||||||
|
while True:
|
||||||
|
ret, frame = capture.read()
|
||||||
|
if not ret:
|
||||||
|
break
|
||||||
|
for frame_processor in get_frame_processors_modules(modules.globals.frame_processors):
|
||||||
|
frame = frame_processor.process_frame_stream(modules.globals.source_path, frame)
|
||||||
|
writer.stdin.write(frame.tobytes())
|
||||||
|
progress.update(1)
|
||||||
|
|
||||||
|
capture.release()
|
||||||
|
writer.stdin.close()
|
||||||
|
writer.wait()
|
||||||
|
|
||||||
|
if modules.globals.keep_audio:
|
||||||
|
update_status('Restoring audio...')
|
||||||
|
restore_audio(modules.globals.target_path, modules.globals.output_path)
|
||||||
|
else:
|
||||||
|
move_temp(modules.globals.target_path, modules.globals.output_path)
|
||||||
|
clean_temp(modules.globals.target_path)
|
||||||
|
|
||||||
def start() -> None:
|
def start() -> None:
|
||||||
for frame_processor in get_frame_processors_modules(modules.globals.frame_processors):
|
for frame_processor in get_frame_processors_modules(modules.globals.frame_processors):
|
||||||
if not frame_processor.pre_start():
|
if not frame_processor.pre_start():
|
||||||
|
@ -202,10 +258,17 @@ def start() -> None:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not modules.globals.map_faces:
|
if not modules.globals.map_faces:
|
||||||
update_status('Creating temp resources...')
|
stream_video()
|
||||||
create_temp(modules.globals.target_path)
|
if is_video(modules.globals.target_path):
|
||||||
update_status('Extracting frames...')
|
update_status('Processing to video succeed!')
|
||||||
extract_frames(modules.globals.target_path)
|
else:
|
||||||
|
update_status('Processing to video failed!')
|
||||||
|
return
|
||||||
|
|
||||||
|
update_status('Creating temp resources...')
|
||||||
|
create_temp(modules.globals.target_path)
|
||||||
|
update_status('Extracting frames...')
|
||||||
|
extract_frames(modules.globals.target_path)
|
||||||
|
|
||||||
temp_frame_paths = get_temp_frame_paths(modules.globals.target_path)
|
temp_frame_paths = get_temp_frame_paths(modules.globals.target_path)
|
||||||
for frame_processor in get_frame_processors_modules(modules.globals.frame_processors):
|
for frame_processor in get_frame_processors_modules(modules.globals.frame_processors):
|
||||||
|
|
|
@ -14,7 +14,8 @@ FRAME_PROCESSORS_INTERFACE = [
|
||||||
'pre_start',
|
'pre_start',
|
||||||
'process_frame',
|
'process_frame',
|
||||||
'process_image',
|
'process_image',
|
||||||
'process_video'
|
'process_video',
|
||||||
|
'process_frame_stream'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -107,3 +107,8 @@ def process_frame_v2(temp_frame: Frame) -> Frame:
|
||||||
if target_face:
|
if target_face:
|
||||||
temp_frame = enhance_face(temp_frame)
|
temp_frame = enhance_face(temp_frame)
|
||||||
return temp_frame
|
return temp_frame
|
||||||
|
|
||||||
|
|
||||||
|
def process_frame_stream(source_path: str, frame: Frame) -> Frame:
|
||||||
|
return process_frame(None, frame)
|
||||||
|
|
||||||
|
|
|
@ -256,3 +256,20 @@ def process_video(source_path: str, temp_frame_paths: List[str]) -> None:
|
||||||
update_status('Many faces enabled. Using first source image (if applicable in v2). Processing...', NAME)
|
update_status('Many faces enabled. Using first source image (if applicable in v2). Processing...', NAME)
|
||||||
# The core processing logic is delegated, which is good.
|
# The core processing logic is delegated, which is good.
|
||||||
modules.processors.frame.core.process_video(source_path, temp_frame_paths, process_frames)
|
modules.processors.frame.core.process_video(source_path, temp_frame_paths, process_frames)
|
||||||
|
|
||||||
|
|
||||||
|
STREAM_SOURCE_FACE = None
|
||||||
|
|
||||||
|
|
||||||
|
def process_frame_stream(source_path: str, frame: Frame) -> Frame:
|
||||||
|
global STREAM_SOURCE_FACE
|
||||||
|
if not modules.globals.map_faces:
|
||||||
|
if STREAM_SOURCE_FACE is None:
|
||||||
|
source_img = cv2.imread(source_path)
|
||||||
|
if source_img is not None:
|
||||||
|
STREAM_SOURCE_FACE = get_one_face(source_img)
|
||||||
|
if STREAM_SOURCE_FACE is not None:
|
||||||
|
return process_frame(STREAM_SOURCE_FACE, frame)
|
||||||
|
return frame
|
||||||
|
else:
|
||||||
|
return process_frame_v2(frame)
|
||||||
|
|
|
@ -38,6 +38,38 @@ def run_ffmpeg(args: List[str]) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def start_ffmpeg_writer(width: int, height: int, fps: float, output_path: str) -> subprocess.Popen:
|
||||||
|
commands = [
|
||||||
|
"ffmpeg",
|
||||||
|
"-hide_banner",
|
||||||
|
"-hwaccel",
|
||||||
|
"auto",
|
||||||
|
"-loglevel",
|
||||||
|
modules.globals.log_level,
|
||||||
|
"-f",
|
||||||
|
"rawvideo",
|
||||||
|
"-pix_fmt",
|
||||||
|
"bgr24",
|
||||||
|
"-s",
|
||||||
|
f"{width}x{height}",
|
||||||
|
"-r",
|
||||||
|
str(fps),
|
||||||
|
"-i",
|
||||||
|
"-",
|
||||||
|
"-c:v",
|
||||||
|
modules.globals.video_encoder,
|
||||||
|
"-crf",
|
||||||
|
str(modules.globals.video_quality),
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vf",
|
||||||
|
"colorspace=bt709:iall=bt601-6-625:fast=1",
|
||||||
|
"-y",
|
||||||
|
output_path,
|
||||||
|
]
|
||||||
|
return subprocess.Popen(commands, stdin=subprocess.PIPE)
|
||||||
|
|
||||||
|
|
||||||
def detect_fps(target_path: str) -> float:
|
def detect_fps(target_path: str) -> float:
|
||||||
command = [
|
command = [
|
||||||
"ffprobe",
|
"ffprobe",
|
||||||
|
|
Loading…
Reference in New Issue