mirror of
https://github.com/deepfakes/faceswap.git
synced 2026-10-18 14:34:30 +08:00
179 lines
6.6 KiB
Python
179 lines
6.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Parent class for output writers for faceswap.py converter"""
|
|
|
|
import logging
|
|
import os
|
|
import re
|
|
import typing as T
|
|
from collections import deque
|
|
|
|
import numpy as np
|
|
|
|
from lib.logger import parse_class_init
|
|
from plugins.convert import convert_config
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class Output():
|
|
"""Parent class for writer plugins.
|
|
|
|
Parameters
|
|
----------
|
|
output_folder
|
|
The full path to the output folder where the converted media should be saved
|
|
config_file
|
|
The full path to a custom configuration ini file. If ``None`` is passed
|
|
then the file is loaded from the default location. Default: ``None``.
|
|
"""
|
|
def __init__(self, output_folder: str, config_file: str | None = None) -> None:
|
|
logger.debug(parse_class_init(locals()))
|
|
convert_config.load_config(config_file=config_file)
|
|
self.output_folder: str = output_folder
|
|
|
|
# For creating sub_folders when separate mask is selected
|
|
self._sub_folders_created: bool = False
|
|
|
|
# Methods for making sure frames are written out in frame order
|
|
self.re_search = re.compile(r"(\d+)(?=\.\w+$)") # Identify frame numbers
|
|
self.cache: dict = {} # Cache for when frames must be written in correct order
|
|
logger.debug("Initialized %s", self.__class__.__name__)
|
|
|
|
@property
|
|
def is_stream(self) -> bool:
|
|
"""Whether the writer outputs a stream or a series images.
|
|
|
|
Writers that write to a stream have a frame_order parameter to dictate
|
|
the order in which frames should be written out (eg. gif/ffmpeg) """
|
|
retval = hasattr(self, "_frame_order")
|
|
return retval
|
|
|
|
@property
|
|
def output_alpha(self) -> bool:
|
|
"""Override if the plugin can output an alpha channel and the user configuration
|
|
option is set to use it. Default ``False`` """
|
|
return False
|
|
|
|
@classmethod
|
|
def _set_frame_order(cls,
|
|
total_count: int,
|
|
frame_ranges: list[tuple[int, int]] | None) -> deque[int]:
|
|
"""Obtain the full list of frames to be converted in order.
|
|
|
|
Used for FFMPEG and Gif writers to ensure correct frame order
|
|
|
|
Parameters
|
|
----------
|
|
total_count
|
|
The total number of frames to be converted
|
|
frame_ranges
|
|
List of tuples for starting and end values of each frame range to be converted or
|
|
``None`` if all frames are to be converted
|
|
|
|
Returns
|
|
-------
|
|
Full Deque of all frame indices to be converted
|
|
"""
|
|
if frame_ranges is None:
|
|
retval = deque(range(1, total_count + 1))
|
|
else:
|
|
retval = deque()
|
|
for rng in frame_ranges:
|
|
retval.extend(list(range(rng[0], rng[1] + 1)))
|
|
logger.debug("frame_order: %s", retval)
|
|
return retval
|
|
|
|
def get_output_filename(self,
|
|
filename: str,
|
|
extension: str,
|
|
separate_mask: bool = False) -> list[str]:
|
|
"""Obtain the full path for the output file, including the correct extension, for the
|
|
given input filename.
|
|
|
|
Parameters
|
|
----------
|
|
filename
|
|
The input frame filename to generate the output file name for
|
|
extension
|
|
The extension to use for the output file
|
|
separate_mask
|
|
``True`` if the mask should be saved out to a sub-folder otherwise ``False``
|
|
|
|
Returns
|
|
-------
|
|
The full path for the output converted frame to be saved to in position 1. The full path
|
|
for the mask to be output to in position 2 (if requested)
|
|
"""
|
|
extension = extension.strip(".")
|
|
filename = os.path.splitext(os.path.basename(filename))[0]
|
|
out_filename = f"{filename}.{extension}"
|
|
retval = [os.path.join(self.output_folder, out_filename)]
|
|
if separate_mask:
|
|
retval.append(os.path.join(self.output_folder, "masks", out_filename))
|
|
|
|
if separate_mask and not self._sub_folders_created:
|
|
locations = [os.path.dirname(loc) for loc in retval]
|
|
logger.debug("Creating sub-folders: %s", locations)
|
|
for location in locations:
|
|
os.makedirs(location, exist_ok=True)
|
|
|
|
logger.trace("in filename: '%s', out filename: '%s'", filename, retval) # type:ignore
|
|
return retval
|
|
|
|
def cache_frame(self, filename: str, image: np.ndarray) -> None:
|
|
"""Add the incoming converted frame to the cache ready for writing out.
|
|
|
|
Used for ffmpeg and gif writers to ensure that the frames are written out in the correct
|
|
order.
|
|
|
|
Parameters
|
|
----------
|
|
filename
|
|
The filename of the incoming frame, where the frame index can be extracted from
|
|
image
|
|
The converted frame corresponding to the given filename
|
|
"""
|
|
re_frame = re.search(self.re_search, filename)
|
|
assert re_frame is not None
|
|
frame_no = int(re_frame.group())
|
|
self.cache[frame_no] = image
|
|
logger.trace("Added to cache. Frame no: %s", frame_no) # type: ignore
|
|
logger.trace("Current cache: %s", sorted(self.cache.keys())) # type:ignore
|
|
|
|
def write(self, filename: str, image: T.Any) -> None:
|
|
"""Override for specific frame writing method.
|
|
|
|
Parameters
|
|
----------
|
|
filename
|
|
The incoming frame filename.
|
|
image
|
|
The converted image to be written. Could be a numpy array, a bytes encoded image or
|
|
any other plugin specific format
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def pre_encode(self, image: np.ndarray, **kwargs) -> T.Any: # pylint:disable=unused-argument
|
|
""" Some writer plugins support the pre-encoding of images prior to saving out. As
|
|
patching is done in multiple threads, but writing is done in a single thread, it can
|
|
speed up the process to do any pre-encoding as part of the converter process.
|
|
|
|
If the writer supports pre-encoding then override this to pre-encode the image in
|
|
:mod:`lib.convert` to speed up saving.
|
|
|
|
Parameters
|
|
----------
|
|
image
|
|
The converted image that is to be run through the pre-encoding function
|
|
|
|
Returns
|
|
-------
|
|
If ``None`` then the writer does not support pre-encoding, otherwise return output of the
|
|
plugin specific pre-encode function
|
|
"""
|
|
return None
|
|
|
|
def close(self) -> None:
|
|
"""Override for specific converted frame writing close methods"""
|
|
raise NotImplementedError
|