Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 61 #62

Merged
merged 11 commits into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ sinner uses the same ML libraries to perform its magic, but handles them in its
## Credits

- [s0md3v](https://github.com/s0md3v/): the original author of roop
- [henryruhs](https://github.com/henryruhs): the significant contributor to roop
- [ffmpeg](https://ffmpeg.org/): for making video related operations easy
- [deepinsight](https://github.com/deepinsight): for their [insightface](https://github.com/deepinsight/insightface) project which provided a well-made library and models.
- [ARC Lab, Tencent PCG](https://github.com/TencentARC): for their [GFPGAN](https://github.com/TencentARC/GFPGAN) project which provided a face restoration library and models.
Expand Down
20 changes: 18 additions & 2 deletions sinner/State.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from sinner.Status import Status, Mood
from sinner.handlers.frame.CV2VideoHandler import CV2VideoHandler
from sinner.typing import Frame
from sinner.utilities import is_absolute_path
from sinner.utilities import is_absolute_path, format_sequences
from sinner.validators.AttributeLoader import Rules


Expand All @@ -24,6 +24,7 @@ class State(Status):

final_check_state: bool = True
final_check_empty: bool = True
final_check_integrity: bool = True

def rules(self) -> Rules:
return [
Expand Down Expand Up @@ -147,7 +148,7 @@ def zfill_length(self) -> int:
self._zfill_length = len(str(self.frames_count))
return self._zfill_length

def final_check(self) -> bool:
def final_check(self) -> tuple[bool, List[int]]:
result = True
processed_frames_count = self.processed_frames_count
if self.final_check_state and not self.is_finished:
Expand All @@ -162,4 +163,19 @@ def final_check(self) -> bool:
if zero_sized_files_count > 0:
self.update_status(message=f"There is zero-sized files in {self.path} temp directory ({zero_sized_files_count} of {processed_frames_count}). Check for free disk space and access rights.", mood=Mood.BAD)
result = False
lost_frames = []
if self.final_check_integrity and not self.is_finished:
lost_frames = self.check_integrity()
if lost_frames:
self.update_status(message=f"There is lost frames in the processed sequence: {format_sequences(lost_frames)}", mood=Mood.BAD)
result = False

return result, lost_frames

def check_integrity(self) -> List[int]:
result: List[int] = []
for frame in range(self.frames_count):
f_name = self.get_frame_processed_name(frame)
if not os.path.exists(f_name):
result.append(frame)
return result
12 changes: 11 additions & 1 deletion sinner/processors/frame/BaseFrameProcessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,17 @@ def process(self, desc: str = 'Processing', set_progress: Callable[[int], None]
initial=self.state.processed_frames_count,
) as progress:
self.multi_process_frame(frames_iterator=self.handler, state=self.state, process_frames=self.process_frames, progress=progress)
if not self.state.final_check():
_, lost_frames = self.state.final_check()
if lost_frames:
with tqdm(
total=len(lost_frames),
desc="Processing lost frames", unit='frame',
dynamic_ncols=True,
bar_format='{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}{postfix}]',
) as progress:
self.multi_process_frame(frames_iterator=lost_frames, state=self.state, process_frames=self.process_frames, progress=progress)
is_ok, _ = self.state.final_check()
if not is_ok:
raise Exception("Something went wrong on processed frames check")

@abstractmethod
Expand Down
2 changes: 1 addition & 1 deletion sinner/processors/frame/FaceEnhancer.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def face_enhancer(self) -> GFPGANer:

def __init__(self, parameters: Namespace, target_path: str | None = None) -> None:
download_directory_path = get_app_dir('models')
conditional_download(download_directory_path, ['https://huggingface.co/henryruhs/roop/resolve/main/GFPGANv1.4.pth'])
conditional_download(download_directory_path, ['https://github.com/TencentARC/GFPGAN/releases/download/v1.3.4/GFPGANv1.4.pth'])
super().__init__(parameters, target_path)

def enhance_face(self, temp_frame: Frame) -> Frame:
Expand Down
2 changes: 1 addition & 1 deletion sinner/processors/frame/FaceSwapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def face_swapper(self) -> FaceSwapperType:

def __init__(self, parameters: Namespace, target_path: str | None = None) -> None:
download_directory_path = get_app_dir('models')
conditional_download(download_directory_path, ['https://huggingface.co/henryruhs/roop/resolve/main/inswapper_128.onnx'])
conditional_download(download_directory_path, ['https://github.com/pozitronik/sinner/releases/download/v200823/inswapper_128.onnx'])
super().__init__(parameters, target_path)

def process_frame(self, frame: Frame) -> Frame:
Expand Down
18 changes: 18 additions & 0 deletions sinner/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,3 +207,21 @@ def is_int(value: str) -> bool:
return True
except ValueError:
return False


def format_sequences(sorted_list: List[int]) -> str:
def format_sequence(s_start: int, s_end: int) -> str:
return str(start) if s_start == s_end else f"{start}..{end}"

sequences = []
start = end = sorted_list[0]

for num in sorted_list[1:]:
if num == end + 1:
end = num
else:
sequences.append(format_sequence(start, end))
start = end = num

sequences.append(format_sequence(start, end))
return ", ".join(sequences)
18 changes: 17 additions & 1 deletion tests/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

from sinner.Parameters import Parameters
from sinner.Core import Core
from sinner.utilities import limit_resources, suggest_max_memory, get_file_name
from sinner.processors.frame.DummyProcessor import DummyProcessor
from sinner.utilities import limit_resources, suggest_max_memory, get_file_name, get_app_dir, resolve_relative_path
from sinner.validators.LoaderException import LoadingException
from tests.constants import target_png, source_jpg, target_mp4, source_target_png_result, source_target_mp4_result, state_frames_dir, result_mp4, tmp_dir, result_png, TARGET_FC, images_dir, source_images_result

Expand Down Expand Up @@ -140,3 +141,18 @@ def test_set_execution_provider(capsys) -> None:
captured = capsys.readouterr()
assert "Error Unknown Provider Type" not in captured.out
assert os.path.exists(result_png) is True


def test_reprocess_lost_frames() -> None:
case_temp_dir = resolve_relative_path('temp/DummyProcessor/frames/source.jpg', get_app_dir())
assert os.path.exists(case_temp_dir) is False
params = Parameters(f'--target-path="{state_frames_dir}" --source-path="{source_jpg}" --output-path="{result_mp4}" --execution-treads={threads_count}')
current_processor = DummyProcessor(params.parameters)
current_processor.process()
assert os.path.exists(case_temp_dir) is True
assert len(glob.glob(os.path.join(case_temp_dir, '*.png'))) == 10
os.remove(os.path.join(case_temp_dir, '05.png'))
os.remove(os.path.join(case_temp_dir, '08.png'))
assert len(glob.glob(os.path.join(case_temp_dir, '*.png'))) == 8
current_processor.process()
assert len(glob.glob(os.path.join(case_temp_dir, '*.png'))) == 10
6 changes: 3 additions & 3 deletions tests/test_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,19 +103,19 @@ def test_states() -> None:
def test_final_check_ok():
state = State(parameters=Namespace(), target_path=target_mp4, temp_dir=tmp_dir, frames_count=TARGET_FC, processor_name='DummyProcessor')
shutil.copytree(state_frames_dir, state.path, dirs_exist_ok=True)
assert state.final_check() is True
assert state.final_check() == (True, [])


def test_final_check_fail_state():
state = State(parameters=Namespace(), target_path=target_mp4, temp_dir=tmp_dir, frames_count=TARGET_FC, processor_name='DummyProcessor')
shutil.copytree(state_frames_dir, state.path, dirs_exist_ok=True)
os.remove(os.path.join(state.path, '05.png'))
assert state.final_check() is False
assert state.final_check() == (False, [5])


def test_final_check_fail_zero_files():
state = State(parameters=Namespace(), target_path=target_mp4, temp_dir=tmp_dir, frames_count=TARGET_FC, processor_name='DummyProcessor')
shutil.copytree(state_frames_dir, state.path, dirs_exist_ok=True)
with open(os.path.join(state.path, '04.png'), 'r+') as file:
file.truncate(0)
assert state.final_check() is False
assert state.final_check() == (False, [])
7 changes: 6 additions & 1 deletion tests/test_utilities.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from sinner.utilities import get_all_base_names
from sinner.utilities import get_all_base_names, format_sequences


def test_get_all_base_names() -> None:
Expand Down Expand Up @@ -36,3 +36,8 @@ class F(D):
assert 'B' in get_all_base_names(F)
assert 'E' not in get_all_base_names(F)
assert 'C' not in get_all_base_names(F)


def test_find_sequences() -> None:
assert format_sequences([1, 2, 3, 4, 10, 20, 21, 22, 23]) == '1..4, 10, 20..23'
assert format_sequences([100, 3, 2, 3, 4, 5]) == '100, 3, 2..5'
Loading