diff --git a/FluidFrames.RIFE.py b/FluidFrames.RIFE.py index 6af9beb..0fd2016 100644 --- a/FluidFrames.RIFE.py +++ b/FluidFrames.RIFE.py @@ -1,11 +1,11 @@ # Standard library imports import sys -from timeit import default_timer as timer -from time import sleep -from webbrowser import open as open_browser +from timeit import default_timer as timer +from time import sleep +from threading import Thread +from webbrowser import open as open_browser -from threading import Thread from multiprocessing import ( Process, Queue as multiprocessing_Queue, @@ -14,13 +14,11 @@ from shutil import ( rmtree as remove_directory, - copytree ) from os import ( sep as os_separator, devnull as os_devnull, - chmod as os_chmod, cpu_count as os_cpu_count, makedirs as os_makedirs, remove as os_remove, @@ -36,7 +34,12 @@ # Third-party library imports -from PIL.Image import open as pillow_image_open + +from PIL.Image import ( + open as pillow_image_open, + fromarray as pillow_image_fromarray +) + from moviepy.editor import VideoFileClip from moviepy.video.io import ImageSequenceClip @@ -82,11 +85,15 @@ CAP_PROP_FRAME_COUNT, CAP_PROP_FRAME_HEIGHT, CAP_PROP_FRAME_WIDTH, + COLOR_BGR2RGB, IMREAD_UNCHANGED, INTER_LINEAR, + INTER_CUBIC, VideoCapture as opencv_VideoCapture, + cvtColor as opencv_cvtColor, imdecode as opencv_imdecode, imencode as opencv_imencode, + cvtColor as opencv_cvtColor, resize as opencv_resize, ) @@ -116,9 +123,7 @@ app_name = "FluidFrames" second_title = "RIFE" -version = "2.11" - -# RIFE 4.13 +version = "2.13" dark_color = "#080808" @@ -126,8 +131,10 @@ telegramme = "https://linktr.ee/j3ngystudio" AI_models_list = [ 'RIFE_4.13', 'RIFE_4.13_Lite' ] -fluidity_options_list = [ 'x2', 'x4', 'x8', - 'x2-slowmotion', 'x4-slowmotion', 'x8-slowmotion' ] +fluidity_options_list = [ + 'x2', 'x4', 'x8', + 'x2-slowmotion', 'x4-slowmotion', 'x8-slowmotion' + ] image_extension_list = [ '.jpg', '.png', '.bmp', '.tiff' ] video_extension_list = [ '.mp4 (x264)', '.mp4 (x265)', '.avi' ] @@ -148,11 +155,6 @@ ERROR_STATUS = "Error" STOP_STATUS = "Stop" -log_file_path = f"{app_name}.log" -temp_dir = f"{app_name}_temp" -audio_path = f"{app_name}_temp{os_separator}audio.mp3" -frame_sequence = f"{app_name}_temp{os_separator}frame_%01d.jpg" - if sys.stdout is None: sys.stdout = open(os_devnull, "w") if sys.stderr is None: sys.stderr = open(os_devnull, "w") @@ -180,7 +182,228 @@ # ------------------ AI ------------------ -backwarp_tenGrid = {} +class Head(Module): + def __init__(self): + super(Head, self).__init__() + self.cnn0 = Conv2d(3, 32, 3, 2, 1) + self.cnn1 = Conv2d(32, 32, 3, 1, 1) + self.cnn2 = Conv2d(32, 32, 3, 1, 1) + self.cnn3 = ConvTranspose2d(32, 8, 4, 2, 1) + self.relu = LeakyReLU(0.2, True) + + def forward(self, x, feat=False): + x0 = self.cnn0(x) + x = self.relu(x0) + x1 = self.cnn1(x) + x = self.relu(x1) + x2 = self.cnn2(x) + x = self.relu(x2) + x3 = self.cnn3(x) + if feat: + return [x0, x1, x2, x3] + return x3 + +class ResConv(Module): + def __init__(self, c, dilation=1): + super(ResConv, self).__init__() + self.conv = Conv2d(c, c, 3, 1, dilation, dilation=dilation, groups=1) + self.beta = Parameter(torch_ones((1, c, 1, 1)), requires_grad=True) + self.relu = LeakyReLU(0.2, True) + + def forward(self, x): + return self.relu(self.conv(x) * self.beta + x) + +class IFBlock(Module): + def __init__(self, in_planes, c=64): + super(IFBlock, self).__init__() + + self.conv0 = Sequential( + Sequential(Conv2d(in_planes, c//2, 3, 2, 1, bias=True), LeakyReLU(0.2, True)), + Sequential(Conv2d(c//2, c, 3, 2, 1, bias=True), LeakyReLU(0.2, True)) + ) + self.convblock = Sequential( + ResConv(c), + ResConv(c), + ResConv(c), + ResConv(c), + ResConv(c), + ResConv(c), + ResConv(c), + ResConv(c), + ) + self.lastconv = Sequential( + ConvTranspose2d(c, 4*6, 4, 2, 1), + PixelShuffle(2) + ) + + def forward(self, x, flow=None, scale=1): + x = torch_nn_interpolate(x, scale_factor= 1. / scale, mode="bilinear", align_corners=False) + if flow is not None: + flow = torch_nn_interpolate(flow, scale_factor= 1. / scale, mode="bilinear", align_corners=False) * 1. / scale + x = torch_cat((x, flow), 1) + feat = self.conv0(x) + feat = self.convblock(feat) + tmp = self.lastconv(feat) + tmp = torch_nn_interpolate(tmp, scale_factor=scale, mode="bilinear", align_corners=False) + flow = tmp[:, :4] * scale + mask = tmp[:, 4:5] + return flow, mask + +class RIFE_413(Module): + def __init__(self, backend): + super(RIFE_413, self).__init__() + self.block0 = IFBlock(7+16, c=192) + self.block1 = IFBlock(8+4+16, c=128) + self.block2 = IFBlock(8+4+16, c=96) + self.block3 = IFBlock(8+4+16, c=64) + self.encode = Head() + + self.backend = backend + + def forward(self, x, timestep=0.5, scale_list=[8, 4, 2, 1]): + channel = x.shape[1] // 2 + img0 = x[:, :channel] + img1 = x[:, channel:] + + if not torch_is_tensor(timestep): + timestep = (x[:, :1].clone() * 0 + 1) * timestep + else: + timestep = timestep.repeat(1, 1, img0.shape[2], img0.shape[3]) + + f0 = self.encode(img0[:, :3]) + f1 = self.encode(img1[:, :3]) + flow_list = [] + merged = [] + mask_list = [] + warped_img0 = img0 + warped_img1 = img1 + flow = None + mask = None + block = [self.block0, self.block1, self.block2, self.block3] + for i in range(4): + if flow is None: + flow, mask = block[i](torch_cat((img0[:, :3], img1[:, :3], f0, f1, timestep), 1), None, scale=scale_list[i]) + else: + wf0 = self.warp(f0, flow[:, :2]) + wf1 = self.warp(f1, flow[:, 2:4]) + fd, m0 = block[i](torch_cat((warped_img0[:, :3], warped_img1[:, :3], wf0, wf1, timestep, mask), 1), flow, scale=scale_list[i]) + mask = m0 + flow = flow + fd + + mask_list.append(mask) + flow_list.append(flow) + warped_img0 = self.warp(img0, flow[:, :2]) + warped_img1 = self.warp(img1, flow[:, 2:4]) + merged.append((warped_img0, warped_img1)) + + mask = torch_sigmoid(mask) + merged[3] = (warped_img0 * mask + warped_img1 * (1 - mask)) + + return flow_list, mask_list[3], merged + + def warp( + self, + tenInput: any, + tenFlow: any, + ) -> torch_tensor: + + backwarp_tenGrid = {} + + k = (str(tenFlow.device), str(tenFlow.size())) + + if k not in backwarp_tenGrid: + tenHorizontal = torch_linspace(-1.0, 1.0, tenFlow.shape[3], device = self.backend).view(1, 1, 1, tenFlow.shape[3]).expand(tenFlow.shape[0], -1, tenFlow.shape[2], -1) + tenVertical = torch_linspace(-1.0, 1.0, tenFlow.shape[2], device = self.backend).view(1, 1, tenFlow.shape[2], 1).expand(tenFlow.shape[0], -1, -1, tenFlow.shape[3]) + backwarp_tenGrid[k] = torch_cat([tenHorizontal, tenVertical], 1).to(self.backend, non_blocking = True) + + tenFlow = torch_cat([tenFlow[:, 0:1, :, :] / ((tenInput.shape[3] - 1.0) / 2.0), tenFlow[:, 1:2, :, :] / ((tenInput.shape[2] - 1.0) / 2.0)], 1) + + g = (backwarp_tenGrid[k] + tenFlow).permute(0, 2, 3, 1) + + return torch_nn_functional_grid_sample(input = tenInput, grid = g, mode = 'bilinear', padding_mode = 'border', align_corners = True) + +class RIFE_413_Lite(Module): + + def __init__(self, backend): + super(RIFE_413_Lite, self).__init__() + self.block0 = IFBlock(7+8, c=128) + self.block1 = IFBlock(8+4+8, c=96) + self.block2 = IFBlock(8+4+8, c=64) + self.block3 = IFBlock(8+4+8, c=48) + self.encode = Sequential( + Conv2d(3, 32, 3, 2, 1), + LeakyReLU(0.2, True), + Conv2d(32, 32, 3, 1, 1), + LeakyReLU(0.2, True), + Conv2d(32, 32, 3, 1, 1), + LeakyReLU(0.2, True), + ConvTranspose2d(32, 4, 4, 2, 1) + ) + + self.backend = backend + + def forward(self, x, timestep=0.5, scale_list=[8, 4, 2, 1]): + channel = x.shape[1] // 2 + img0 = x[:, :channel] + img1 = x[:, channel:] + + if not torch_is_tensor(timestep): + timestep = (x[:, :1].clone() * 0 + 1) * timestep + else: + timestep = timestep.repeat(1, 1, img0.shape[2], img0.shape[3]) + + f0 = self.encode(img0[:, :3]) + f1 = self.encode(img1[:, :3]) + flow_list = [] + merged = [] + mask_list = [] + warped_img0 = img0 + warped_img1 = img1 + flow = None + mask = None + block = [self.block0, self.block1, self.block2, self.block3] + + for i in range(4): + if flow is None: + flow, mask = block[i](torch_cat((img0[:, :3], img1[:, :3], f0, f1, timestep), 1), None, scale=scale_list[i]) + else: + wf0 = self.warp(f0, flow[:, :2]) + wf1 = self.warp(f1, flow[:, 2:4]) + fd, m0 = block[i](torch_cat((warped_img0[:, :3], warped_img1[:, :3], wf0, wf1, timestep, mask), 1), flow, scale=scale_list[i]) + mask = m0 + flow = flow + fd + + mask_list.append(mask) + flow_list.append(flow) + warped_img0 = self.warp(img0, flow[:, :2]) + warped_img1 = self.warp(img1, flow[:, 2:4]) + merged.append((warped_img0, warped_img1)) + + mask = torch_sigmoid(mask) + merged[3] = (warped_img0 * mask + warped_img1 * (1 - mask)) + + return flow_list, mask_list[3], merged + + def warp( + self, + tenInput: any, + tenFlow: any, + ) -> torch_tensor: + + backwarp_tenGrid = {} + + k = (str(tenFlow.device), str(tenFlow.size())) + + if k not in backwarp_tenGrid: + tenHorizontal = torch_linspace(-1.0, 1.0, tenFlow.shape[3], device = self.backend).view(1, 1, 1, tenFlow.shape[3]).expand(tenFlow.shape[0], -1, tenFlow.shape[2], -1) + tenVertical = torch_linspace(-1.0, 1.0, tenFlow.shape[2], device = self.backend).view(1, 1, tenFlow.shape[2], 1).expand(tenFlow.shape[0], -1, -1, tenFlow.shape[3]) + backwarp_tenGrid[k] = torch_cat([tenHorizontal, tenVertical], 1).to(self.backend, non_blocking = True) + + tenFlow = torch_cat([tenFlow[:, 0:1, :, :] / ((tenInput.shape[3] - 1.0) / 2.0), tenFlow[:, 1:2, :, :] / ((tenInput.shape[2] - 1.0) / 2.0)], 1) + + g = (backwarp_tenGrid[k] + tenFlow).permute(0, 2, 3, 1) + + return torch_nn_functional_grid_sample(input = tenInput, grid = g, mode = 'bilinear', padding_mode = 'border', align_corners = True) @torch_inference_mode(True) def load_AI_model( @@ -202,7 +425,11 @@ def convert(param): elif selected_AI_model == "RIFE_4.13_Lite": model = RIFE_413_Lite(backend) - pretrained_model = torch_load(model_path, map_location = torch_device('cpu')) + pretrained_model = torch_load( + model_path, + map_location = torch_device('cpu') + ) + model.load_state_dict(convert(pretrained_model)) model.eval() @@ -232,7 +459,7 @@ def AI_generate_frames( # fluidification x2 frame_1_1_name = f"{frame_base_name}_.1{selected_output_file_extension}" - frame_1_1 = tensor_to_frame(AI_model.inference(frame_1_tensor, frame_2_tensor), h, w) + frame_1_1 = tensor_to_frame(AI_interpolation(AI_model, frame_1_tensor, frame_2_tensor), h, w) image_write(frame_1_name, frame1) image_write(frame_1_1_name, frame_1_1) @@ -248,9 +475,9 @@ def AI_generate_frames( frame_1_2_name = f"{frame_base_name}_.2{selected_output_file_extension}" frame_1_3_name = f"{frame_base_name}_.3{selected_output_file_extension}" - frame_1_2_tensor = AI_model.inference(frame_1_tensor, frame_2_tensor) - frame_1_1_tensor = AI_model.inference(frame_1_tensor, frame_1_2_tensor) - frame_1_3_tensor = AI_model.inference(frame_1_2_tensor, frame_2_tensor) + frame_1_2_tensor = AI_interpolation(AI_model, frame_1_tensor, frame_2_tensor) + frame_1_1_tensor = AI_interpolation(AI_model, frame_1_tensor, frame_1_2_tensor) + frame_1_3_tensor = AI_interpolation(AI_model, frame_1_2_tensor, frame_2_tensor) frame_1_1 = tensor_to_frame(frame_1_1_tensor, h, w) frame_1_2 = tensor_to_frame(frame_1_2_tensor, h, w) @@ -278,14 +505,14 @@ def AI_generate_frames( frame_1_6_name = f"{frame_base_name}_.6{selected_output_file_extension}" frame_1_7_name = f"{frame_base_name}_.7{selected_output_file_extension}" - frame_1_4_tensor = AI_model.inference(frame_1_tensor, frame_2_tensor) - frame_1_2_tensor = AI_model.inference(frame_1_tensor, frame_1_4_tensor) - frame_1_1_tensor = AI_model.inference(frame_1_tensor, frame_1_2_tensor) - frame_1_3_tensor = AI_model.inference(frame_1_2_tensor, frame_1_4_tensor) + frame_1_4_tensor = AI_interpolation(AI_model, frame_1_tensor, frame_2_tensor) + frame_1_2_tensor = AI_interpolation(AI_model, frame_1_tensor, frame_1_4_tensor) + frame_1_1_tensor = AI_interpolation(AI_model, frame_1_tensor, frame_1_2_tensor) + frame_1_3_tensor = AI_interpolation(AI_model, frame_1_2_tensor, frame_1_4_tensor) - frame_1_6_tensor = AI_model.inference(frame_1_4_tensor, frame_2_tensor) - frame_1_5_tensor = AI_model.inference(frame_1_4_tensor, frame_1_6_tensor) - frame_1_7_tensor = AI_model.inference(frame_1_6_tensor, frame_2_tensor) + frame_1_6_tensor = AI_interpolation(AI_model, frame_1_4_tensor, frame_2_tensor) + frame_1_5_tensor = AI_interpolation(AI_model, frame_1_4_tensor, frame_1_6_tensor) + frame_1_7_tensor = AI_interpolation(AI_model, frame_1_6_tensor, frame_2_tensor) frame_1_1 = tensor_to_frame(frame_1_1_tensor, h, w) frame_1_2 = tensor_to_frame(frame_1_2_tensor, h, w) @@ -317,6 +544,18 @@ def AI_generate_frames( return all_video_frames_path_list +def AI_interpolation( + AI_model: any, + image1: numpy_ndarray, + image2: numpy_ndarray, + timestep = 0.5, + scale = 1.0) -> numpy_ndarray: + + imgs = torch_cat((image1, image2), 1) + scale_list = [8/scale, 4/scale, 2/scale, 1/scale] + _, _, merged = AI_model(imgs, timestep, scale_list) + return merged[3] + def frames_to_tensors( frame_1: numpy_ndarray, frame_2: numpy_ndarray, @@ -346,235 +585,355 @@ def tensor_to_frame( return (result[0] * 255).byte().cpu().numpy().transpose(1, 2, 0)[:height, :width] -def warp( - tenInput: any, - tenFlow: any, - backend: directml_device - ) -> torch_tensor: - - k = (str(tenFlow.device), str(tenFlow.size())) - if k not in backwarp_tenGrid: - tenHorizontal = torch_linspace(-1.0, 1.0, tenFlow.shape[3], device = backend).view(1, 1, 1, tenFlow.shape[3]).expand(tenFlow.shape[0], -1, tenFlow.shape[2], -1) - tenVertical = torch_linspace(-1.0, 1.0, tenFlow.shape[2], device = backend).view(1, 1, tenFlow.shape[2], 1).expand(tenFlow.shape[0], -1, -1, tenFlow.shape[3]) - backwarp_tenGrid[k] = torch_cat([tenHorizontal, tenVertical], 1).to(backend, non_blocking = True) - tenFlow = torch_cat([tenFlow[:, 0:1, :, :] / ((tenInput.shape[3] - 1.0) / 2.0), tenFlow[:, 1:2, :, :] / ((tenInput.shape[2] - 1.0) / 2.0)], 1) +# GUI utils --------------------------- - g = (backwarp_tenGrid[k] + tenFlow).permute(0, 2, 3, 1) +class ScrollableImagesTextFrame(CTkScrollableFrame): - return torch_nn_functional_grid_sample(input = tenInput, grid = g, mode = 'bilinear', padding_mode = 'border', align_corners = True) + def __init__( + self, + master, + selected_file_list, + **kwargs + ) -> None: + + super().__init__(master, **kwargs) + self.grid_columnconfigure(0, weight = 1) -def create_Conv2d( - in_planes: any, - out_planes: any, - kernel_size: int = 3, - stride: int = 1, - padding: int = 1, - dilation: int =1 - ) -> Sequential: - - return Sequential( - Conv2d(in_planes, - out_planes, - kernel_size = kernel_size, - stride = stride, - padding = padding, - dilation = dilation, - bias = True), - LeakyReLU(0.2, True) - ) + self.file_list = selected_file_list + self._create_widgets() -class Head(Module): - def __init__(self): - super(Head, self).__init__() - self.cnn0 = Conv2d(3, 32, 3, 2, 1) - self.cnn1 = Conv2d(32, 32, 3, 1, 1) - self.cnn2 = Conv2d(32, 32, 3, 1, 1) - self.cnn3 = ConvTranspose2d(32, 8, 4, 2, 1) - self.relu = LeakyReLU(0.2, True) + def _create_widgets( + self, + ) -> None: + + self.add_clean_button() - def forward(self, x, feat=False): - x0 = self.cnn0(x) - x = self.relu(x0) - x1 = self.cnn1(x) - x = self.relu(x1) - x2 = self.cnn2(x) - x = self.relu(x2) - x3 = self.cnn3(x) - if feat: - return [x0, x1, x2, x3] - return x3 + index_row = 1 -class ResConv(Module): - def __init__(self, c, dilation=1): - super(ResConv, self).__init__() - self.conv = Conv2d(c, c, 3, 1, dilation, dilation=dilation, groups=1) - self.beta = Parameter(torch_ones((1, c, 1, 1)), requires_grad=True) - self.relu = LeakyReLU(0.2, True) + for file_path in self.file_list: - def forward(self, x): - return self.relu(self.conv(x) * self.beta + x) + if check_if_file_is_video(file_path): + infos, icon = self.extract_video_info(file_path) + + label = CTkLabel( + self, + text = infos, + image = icon, + font = bold11, + text_color = "#E0E0E0", + compound = "left", + padx = 10, + pady = 5, + anchor = "center" + ) + + label.grid( + row = index_row, + column = 0, + pady = (3, 3), + padx = (3, 3), + sticky = "w" + ) + + index_row +=1 -class IFBlock(Module): - def __init__(self, in_planes, c=64): - super(IFBlock, self).__init__() - self.conv0 = Sequential( - create_Conv2d(in_planes, c//2, 3, 2, 1), - create_Conv2d(c//2, c, 3, 2, 1), + def add_clean_button( + self: any + ) -> None: + + button = CTkButton( + self, + image = clear_icon, + font = bold11, + text = "CLEAN", + compound = "left", + width = 100, + height = 28, + border_width = 1, + fg_color = "#282828", + text_color = "#E0E0E0", + border_color = "#0096FF" ) - self.convblock = Sequential( - ResConv(c), - ResConv(c), - ResConv(c), - ResConv(c), - ResConv(c), - ResConv(c), - ResConv(c), - ResConv(c), - ) - self.lastconv = Sequential( - ConvTranspose2d(c, 4*6, 4, 2, 1), - PixelShuffle(2) - ) - def forward(self, x, flow=None, scale=1): - x = torch_nn_interpolate(x, scale_factor= 1. / scale, mode="bilinear", align_corners=False) - if flow is not None: - flow = torch_nn_interpolate(flow, scale_factor= 1. / scale, mode="bilinear", align_corners=False) * 1. / scale - x = torch_cat((x, flow), 1) - feat = self.conv0(x) - feat = self.convblock(feat) - tmp = self.lastconv(feat) - tmp = torch_nn_interpolate(tmp, scale_factor=scale, mode="bilinear", align_corners=False) - flow = tmp[:, :4] * scale - mask = tmp[:, 4:5] - return flow, mask + button.configure(command=lambda: self.clean_all_items()) + button.grid(row = 0, column=2, pady=(7, 7), padx = (0, 7)) + + def get_selected_file_list( + self: any + ) -> list: -class RIFE_413(Module): - def __init__(self, backend): - super(RIFE_413, self).__init__() - self.block0 = IFBlock(7+16, c=192) - self.block1 = IFBlock(8+4+16, c=128) - self.block2 = IFBlock(8+4+16, c=96) - self.block3 = IFBlock(8+4+16, c=64) - self.encode = Head() + return self.file_list - self.backend = backend + def clean_all_items( + self: any + ) -> None: + + self.file_list = [] + self.destroy() + place_loadFile_section() - def forward(self, x, timestep=0.5, scale_list=[8, 4, 2, 1]): - channel = x.shape[1] // 2 - img0 = x[:, :channel] - img1 = x[:, channel:] + def extract_video_info( + self: any, + video_file: str + ) -> tuple: + + cap = opencv_VideoCapture(video_file) + width = round(cap.get(CAP_PROP_FRAME_WIDTH)) + height = round(cap.get(CAP_PROP_FRAME_HEIGHT)) + num_frames = int(cap.get(CAP_PROP_FRAME_COUNT)) + frame_rate = cap.get(CAP_PROP_FPS) + duration = num_frames/frame_rate + minutes = int(duration/60) + seconds = duration % 60 + video_name = str(video_file.split("/")[-1]) + + while(cap.isOpened()): + ret, frame = cap.read() + if ret == False: break + frame = opencv_cvtColor(frame, COLOR_BGR2RGB) + video_icon = CTkImage( + pillow_image_fromarray(frame, mode="RGB"), + size = (25, 25) + ) + break + cap.release() + + video_infos = f"{video_name} • {width}x{height} • {minutes}m:{round(seconds)}s • {num_frames}frames • {round(frame_rate, 2)}fps" + + return video_infos, video_icon + +class CTkMessageBox(CTkToplevel): - if not torch_is_tensor(timestep): - timestep = (x[:, :1].clone() * 0 + 1) * timestep - else: - timestep = timestep.repeat(1, 1, img0.shape[2], img0.shape[3]) + def __init__( + self, + messageType: str, + title: str, + subtitle: str, + default_value: str, + option_list: list, + ) -> None: - f0 = self.encode(img0[:, :3]) - f1 = self.encode(img1[:, :3]) - flow_list = [] - merged = [] - mask_list = [] - warped_img0 = img0 - warped_img1 = img1 - flow = None - mask = None - block = [self.block0, self.block1, self.block2, self.block3] - for i in range(4): - if flow is None: - flow, mask = block[i](torch_cat((img0[:, :3], img1[:, :3], f0, f1, timestep), 1), None, scale=scale_list[i]) - else: - wf0 = warp(f0, flow[:, :2], self.backend) - wf1 = warp(f1, flow[:, 2:4], self.backend) - fd, m0 = block[i](torch_cat((warped_img0[:, :3], warped_img1[:, :3], wf0, wf1, timestep, mask), 1), flow, scale=scale_list[i]) - mask = m0 - flow = flow + fd + super().__init__() - mask_list.append(mask) - flow_list.append(flow) - warped_img0 = warp(img0, flow[:, :2], self.backend) - warped_img1 = warp(img1, flow[:, 2:4], self.backend) - merged.append((warped_img0, warped_img1)) + self._running: bool = False - mask = torch_sigmoid(mask) - merged[3] = (warped_img0 * mask + warped_img1 * (1 - mask)) + self._messageType = messageType + self._title = title + self._subtitle = subtitle + self._default_value = default_value + self._option_list = option_list + self._ctkwidgets_index = 0 - return flow_list, mask_list[3], merged - - def inference(self, img0, img1, timestep=0.5, scale=1.0): - imgs = torch_cat((img0, img1), 1) - scale_list = [8/scale, 4/scale, 2/scale, 1/scale] - _, _, merged = self(imgs, timestep, scale_list) - return merged[3] + self.title('') + self.lift() # lift window on top + self.attributes("-topmost", True) # stay on top + self.protocol("WM_DELETE_WINDOW", self._on_closing) + self.after(10, self._create_widgets) # create widgets with slight delay, to avoid white flickering of background + self.resizable(False, False) + self.grab_set() # make other windows not clickable -class RIFE_413_Lite(Module): - def __init__(self, backend): - super(RIFE_413_Lite, self).__init__() - self.block0 = IFBlock(7+8, c=128) - self.block1 = IFBlock(8+4+8, c=96) - self.block2 = IFBlock(8+4+8, c=64) - self.block3 = IFBlock(8+4+8, c=48) - self.encode = Sequential( - Conv2d(3, 32, 3, 2, 1), - LeakyReLU(0.2, True), - Conv2d(32, 32, 3, 1, 1), - LeakyReLU(0.2, True), - Conv2d(32, 32, 3, 1, 1), - LeakyReLU(0.2, True), - ConvTranspose2d(32, 4, 4, 2, 1) - ) + def _ok_event( + self, + event = None + ) -> None: + self.grab_release() + self.destroy() - self.backend = backend + def _on_closing( + self + ) -> None: + self.grab_release() + self.destroy() - def forward(self, x, timestep=0.5, scale_list=[8, 4, 2, 1]): - channel = x.shape[1] // 2 - img0 = x[:, :channel] - img1 = x[:, channel:] + def createEmptyLabel( + self + ) -> CTkLabel: + + return CTkLabel(master = self, + fg_color = "transparent", + width = 500, + height = 17, + text = '') + + def placeInfoMessageTitleSubtitle( + self, + ) -> None: + + spacingLabel1 = self.createEmptyLabel() + spacingLabel2 = self.createEmptyLabel() + + if self._messageType == "info": + title_subtitle_text_color = "#3399FF" + elif self._messageType == "error": + title_subtitle_text_color = "#FF3131" + + titleLabel = CTkLabel( + master = self, + width = 500, + anchor = 'w', + justify = "left", + fg_color = "transparent", + text_color = title_subtitle_text_color, + font = bold22, + text = self._title + ) + + if self._default_value != None: + defaultLabel = CTkLabel( + master = self, + width = 500, + anchor = 'w', + justify = "left", + fg_color = "transparent", + text_color = "#3399FF", + font = bold17, + text = f"Default: {self._default_value}" + ) + + subtitleLabel = CTkLabel( + master = self, + width = 500, + anchor = 'w', + justify = "left", + fg_color = "transparent", + text_color = title_subtitle_text_color, + font = bold14, + text = self._subtitle + ) + + spacingLabel1.grid(row = self._ctkwidgets_index, column = 0, columnspan = 2, padx = 0, pady = 0, sticky = "ew") + + self._ctkwidgets_index += 1 + titleLabel.grid(row = self._ctkwidgets_index, column = 0, columnspan = 2, padx = 25, pady = 0, sticky = "ew") + + if self._default_value != None: + self._ctkwidgets_index += 1 + defaultLabel.grid(row = self._ctkwidgets_index, column = 0, columnspan = 2, padx = 25, pady = 0, sticky = "ew") + + self._ctkwidgets_index += 1 + subtitleLabel.grid(row = self._ctkwidgets_index, column = 0, columnspan = 2, padx = 25, pady = 0, sticky = "ew") + + self._ctkwidgets_index += 1 + spacingLabel2.grid(row = self._ctkwidgets_index, column = 0, columnspan = 2, padx = 0, pady = 0, sticky = "ew") + + def placeInfoMessageOptionsText( + self, + ) -> None: + + for option_text in self._option_list: + optionLabel = CTkLabel(master = self, + width = 600, + height = 45, + corner_radius = 6, + anchor = 'w', + justify = "left", + text_color = "#C0C0C0", + fg_color = "#282828", + bg_color = "transparent", + font = bold12, + text = option_text) + + self._ctkwidgets_index += 1 + optionLabel.grid(row = self._ctkwidgets_index, column = 0, columnspan = 2, padx = 25, pady = 4, sticky = "ew") + + spacingLabel3 = self.createEmptyLabel() + + self._ctkwidgets_index += 1 + spacingLabel3.grid(row = self._ctkwidgets_index, column = 0, columnspan = 2, padx = 0, pady = 0, sticky = "ew") + + def placeInfoMessageOkButton( + self + ) -> None: + + ok_button = CTkButton( + master = self, + command = self._ok_event, + text = 'OK', + width = 125, + font = bold11, + border_width = 1, + fg_color = "#282828", + text_color = "#E0E0E0", + border_color = "#0096FF" + ) + + self._ctkwidgets_index += 1 + ok_button.grid(row = self._ctkwidgets_index, column = 1, columnspan = 1, padx = (10, 20), pady = (10, 20), sticky = "e") - if not torch_is_tensor(timestep): - timestep = (x[:, :1].clone() * 0 + 1) * timestep - else: - timestep = timestep.repeat(1, 1, img0.shape[2], img0.shape[3]) + def _create_widgets( + self + ) -> None: - f0 = self.encode(img0[:, :3]) - f1 = self.encode(img1[:, :3]) - flow_list = [] - merged = [] - mask_list = [] - warped_img0 = img0 - warped_img1 = img1 - flow = None - mask = None - block = [self.block0, self.block1, self.block2, self.block3] + self.grid_columnconfigure((0, 1), weight=1) + self.rowconfigure(0, weight=1) - for i in range(4): - if flow is None: - flow, mask = block[i](torch_cat((img0[:, :3], img1[:, :3], f0, f1, timestep), 1), None, scale=scale_list[i]) - else: - wf0 = warp(f0, flow[:, :2], self.backend) - wf1 = warp(f1, flow[:, 2:4], self.backend) - fd, m0 = block[i](torch_cat((warped_img0[:, :3], warped_img1[:, :3], wf0, wf1, timestep, mask), 1), flow, scale=scale_list[i]) - mask = m0 - flow = flow + fd + self.placeInfoMessageTitleSubtitle() + self.placeInfoMessageOptionsText() + self.placeInfoMessageOkButton() - mask_list.append(mask) - flow_list.append(flow) - warped_img0 = warp(img0, flow[:, :2], self.backend) - warped_img1 = warp(img1, flow[:, 2:4], self.backend) - merged.append((warped_img0, warped_img1)) +def create_info_button( + command: any, + text: str + ) -> CTkButton: + + return CTkButton( + master = window, + command = command, + text = text, + fg_color = "transparent", + hover_color = "#181818", + text_color = "#C0C0C0", + anchor = "w", + height = 23, + width = 150, + corner_radius = 12, + font = bold12, + image = info_icon + ) - mask = torch_sigmoid(mask) - merged[3] = (warped_img0 * mask + warped_img1 * (1 - mask)) +def create_option_menu( + command: any, + values: list + ) -> CTkOptionMenu: + + return CTkOptionMenu( + master = window, + command = command, + values = values, + width = 150, + height = 31, + corner_radius = 6, + dropdown_font = bold11, + font = bold11, + anchor = "center", + text_color = "#C0C0C0", + fg_color = "#000000", + button_color = "#000000", + button_hover_color = "#000000", + dropdown_fg_color = "#000000" + ) - return flow_list, mask_list[3], merged +def create_text_box( + textvariable: StringVar, + ) -> CTkEntry: - def inference(self, img0, img1, timestep=0.5, scale=1.0): - imgs = torch_cat((img0, img1), 1) - scale_list = [8/scale, 4/scale, 2/scale, 1/scale] - _, _, merged = self(imgs, timestep, scale_list) - return merged[3] + return CTkEntry( + master = window, + textvariable = textvariable, + border_width = 1, + corner_radius = 6, + width = 150, + height = 30, + font = bold11, + justify = "center", + fg_color = "#000000", + border_color = "#404040", + ) + +def test_callback(a, b, c): + print("Pippo") @@ -601,7 +960,7 @@ def remove_dir( if os_path_exists(name_dir): remove_directory(name_dir) -def create_temp_dir( +def create_dir( name_dir: str ) -> None: @@ -648,7 +1007,7 @@ def resize_frames( return frame_1, frame_2 else: frame_1_resized = opencv_resize(frame_1, (target_width, target_height), interpolation = INTER_LINEAR) - frame_2_resized = opencv_resize(frame_2, (target_width, target_height), interpolation = INTER_LINEAR) + frame_2_resized = opencv_resize(frame_2, (target_width, target_height), interpolation = INTER_CUBIC) return frame_1_resized, frame_2_resized def extract_video_fps( @@ -661,25 +1020,36 @@ def extract_video_fps( return frame_rate def extract_video_frames_and_audio( + target_directory: str, video_path: str, ) -> list[str]: - - frame_rate = extract_video_fps(video_path) - video_file_clip = VideoFileClip(video_path) - try: - video_file_clip.audio.write_audiofile(audio_path, verbose = False, logger = None) - except: - pass + create_dir(target_directory) - video_frames_list = [] - video_frames_list = video_file_clip.write_images_sequence(frame_sequence, verbose = False, logger = None, fps = frame_rate) - - return video_frames_list + with VideoFileClip(video_path) as video_file_clip: + try: + audio_path = f"{target_directory}{os_separator}audio.mp3" + video_file_clip.audio.write_audiofile(audio_path, verbose = False, logger = None) + except: + pass + + video_frame_rate = extract_video_fps(video_path) + frames_sequence_path = f"{target_directory}{os_separator}frame_%01d.jpg" + video_frames_list = video_file_clip.write_images_sequence( + nameformat = frames_sequence_path, + fps = video_frame_rate, + verbose = False, + withmask = True, + logger = None, + ) + + return video_frames_list, audio_path def video_reconstruction_by_frames( - input_video_path: str, - all_video_frames_path_list: list, + video_path: str, + audio_path: str, + all_video_frames_paths: list, + selected_AI_model: str, fluidification_factor: int, slowmotion: bool, resize_factor: int, @@ -687,7 +1057,7 @@ def video_reconstruction_by_frames( selected_video_extension: str ) -> None: - frame_rate = extract_video_fps(input_video_path) + frame_rate = extract_video_fps(video_path) if not slowmotion: frame_rate = frame_rate * fluidification_factor @@ -703,28 +1073,39 @@ def video_reconstruction_by_frames( selected_video_extension = '.avi' codec = 'png' - upscaled_video_path = prepare_output_video_filename(input_video_path, fluidification_factor, slowmotion, resize_factor, selected_video_extension) + upscaled_video_path = prepare_output_video_filename( + video_path, + selected_AI_model, + fluidification_factor, + slowmotion, + resize_factor, + selected_video_extension + ) - clip = ImageSequenceClip.ImageSequenceClip(all_video_frames_path_list, fps = frame_rate) + clip = ImageSequenceClip.ImageSequenceClip(all_video_frames_paths, fps = frame_rate) if os_path_exists(audio_path): - clip.write_videofile(upscaled_video_path, - fps = frame_rate, - audio = audio_path, - codec = codec, - bitrate = '16M', - ffmpeg_params = [ '-vf', 'scale=out_range=full' ], - verbose = False, - logger = None, - threads = cpu_number) + clip.write_videofile( + upscaled_video_path, + fps = frame_rate, + audio = audio_path, + codec = codec, + bitrate = '16M', + #ffmpeg_params = [ '-vf', 'scale=out_range=full' ], + verbose = False, + logger = None, + threads = cpu_number + ) else: - clip.write_videofile(upscaled_video_path, - fps = frame_rate, - codec = codec, - bitrate = '16M', - ffmpeg_params = [ '-vf', 'scale=out_range=full' ], - verbose = False, - logger = None, - threads = cpu_number) + clip.write_videofile( + upscaled_video_path, + fps = frame_rate, + codec = codec, + bitrate = '16M', + #ffmpeg_params = [ '-vf', 'scale=out_range=full' ], + verbose = False, + logger = None, + threads = cpu_number + ) def calculate_time_to_complete_video( start_timer: float, @@ -769,12 +1150,6 @@ def update_process_status_videos( write_process_status(processing_queue, f"{file_number}. Fluidifying video {percent_complete:.2f}% ({time_left})") -def copy_files_from_temp_directory( - destination_dir: str - ) -> None: - - copytree(f"{app_name}_temp", destination_dir) - # Core functions ------------------------ @@ -845,36 +1220,64 @@ def stop_button_command() -> None: def prepare_output_video_filename( video_path: str, + selected_AI_model: str, fluidification_factor: int, slowmotion: bool, resize_factor: int, - selected_video_output: str + selected_video_extension: str ) -> str: - result_video_path = os_path_splitext(video_path)[0] - resize_percentage = str(int(resize_factor * 100)) + "%" - - if slowmotion: - to_append = f"_RIFEx{str(fluidification_factor)}_slowmo_{resize_percentage}{selected_video_output}" - else: - to_append = f"_RIFEx{str(fluidification_factor)}_{resize_percentage}{selected_video_output}" + result_path, _ = os_path_splitext(video_path) - result_video_path = result_video_path + to_append + # Selected AI model + to_append = f"_{selected_AI_model}x{str(fluidification_factor)}" - return result_video_path + # Slowmotion? + if slowmotion: to_append += f"_slowmo_" -def prepare_output_directory_name( + # Selected resize + to_append += f"_Resize-{str(int(resize_factor * 100))}" + + # Video output + to_append += f"{selected_video_extension}" + + result_path += to_append + + return result_path + +def prepare_output_video_frames_directory_name( video_path: str, + selected_AI_model: str, fluidification_factor: int, + slowmotion: bool, resize_factor: int, ) -> str: - result_video_path = os_path_splitext(video_path)[0] - resize_percentage = str(int(resize_factor * 100)) + "%" - to_append = f"_RIFEx{str(fluidification_factor)}_{resize_percentage}" - result_video_path = result_video_path + to_append + result_path, _ = os_path_splitext(video_path) + + # Selected AI model + to_append = f"_{selected_AI_model}x{str(fluidification_factor)}" + + # Slowmotion? + if slowmotion: to_append += f"_slowmo_" + + # Selected resize + to_append += f"_Resize-{str(int(resize_factor * 100))}" + + result_path += to_append + + return result_path + +def get_video_target_resolution( + first_video_frame: numpy_ndarray, + resize_factor: int + ) -> tuple: + + temp_frame = image_read(first_video_frame) + target_height = int(temp_frame.shape[0] * resize_factor) + target_width = int(temp_frame.shape[1] * resize_factor) - return result_video_path + return target_height, target_width def check_fluidification_option( selected_fluidity_option: str @@ -883,15 +1286,11 @@ def check_fluidification_option( slowmotion = False fluidification_factor = 0 - if 'slowmotion' in selected_fluidity_option: - slowmotion = True + if 'slowmotion' in selected_fluidity_option: slowmotion = True - if '2' in selected_fluidity_option: - fluidification_factor = 2 - elif '4' in selected_fluidity_option: - fluidification_factor = 4 - elif '8' in selected_fluidity_option: - fluidification_factor = 8 + if '2' in selected_fluidity_option: fluidification_factor = 2 + elif '4' in selected_fluidity_option: fluidification_factor = 4 + elif '8' in selected_fluidity_option: fluidification_factor = 8 return fluidification_factor, slowmotion @@ -929,23 +1328,25 @@ def fludify_button_command() -> None: place_stop_button() process_fluidify_orchestrator = Process( - target = fluidify_orchestrator, - args = (processing_queue, - selected_file_list, - selected_AI_model, - selected_fluidity_option, - backend, - selected_image_extension, - selected_video_extension, - resize_factor, - cpu_number, - selected_save_frames) - ) + target = fluidify_orchestrator, + args = ( + processing_queue, + selected_file_list, + selected_AI_model, + selected_fluidity_option, + backend, + selected_image_extension, + selected_video_extension, + resize_factor, + cpu_number, + selected_save_frames + ) + ) process_fluidify_orchestrator.start() thread_wait = Thread( - target = check_fluidify_steps - ) + target = check_fluidify_steps + ) thread_wait.start() def fluidify_orchestrator( @@ -967,26 +1368,29 @@ def fluidify_orchestrator( try: write_process_status(processing_queue, f"Loading AI model") + AI_model = load_AI_model(selected_AI_model, backend) how_many_files = len(selected_file_list) for file_number in range(how_many_files): file_path = selected_file_list[file_number] file_number = file_number + 1 + fluidify_video( - processing_queue, - file_path, - file_number, - AI_model, - backend, - fluidification_factor, - slowmotion, - resize_factor, - selected_image_extension, - selected_video_extension, - cpu_number, - selected_save_frames - ) + processing_queue, + file_path, + file_number, + AI_model, + backend, + selected_AI_model, + fluidification_factor, + slowmotion, + resize_factor, + selected_image_extension, + selected_video_extension, + cpu_number, + selected_save_frames + ) write_process_status(processing_queue, f"{COMPLETED_STATUS}") @@ -999,6 +1403,7 @@ def fluidify_video( file_number: int, AI_model: any, backend: directml_device, + selected_AI_model: str, fluidification_factor: int, slowmotion: bool, resize_factor: int, @@ -1008,56 +1413,67 @@ def fluidify_video( selected_save_frames: bool ) -> None: - create_temp_dir(f"{app_name}_temp") + # Directory for video frames and audio + target_directory = prepare_output_video_frames_directory_name( + video_path, + selected_AI_model, + fluidification_factor, + slowmotion, + resize_factor + ) - write_process_status(processing_queue, f"{file_number}. Extracting video frames and audio") - frame_list = extract_video_frames_and_audio(video_path) - - temp_frame = image_read(frame_list[0]) - target_height = int(temp_frame.shape[0] * resize_factor) - target_width = int(temp_frame.shape[1] * resize_factor) - - all_video_frames_path_list = [] - how_many_frames = len(frame_list) - - write_process_status(processing_queue, f"{file_number}. Fluidifying video") + # Extract video frames and audio + write_process_status(processing_queue, f"{file_number}. Extracting video frames") + frame_list_paths, audio_path = extract_video_frames_and_audio(target_directory, video_path) + target_height, target_width = get_video_target_resolution(frame_list_paths[0], resize_factor) + + write_process_status(processing_queue, f"{file_number}. Video frame generation") + all_video_frames_paths = [] + how_many_frames = len(frame_list_paths) for index_frame in range(how_many_frames-1): - start_timer = timer() - frame_base_name = os_path_splitext(frame_list[index_frame])[0] - frame_1_name = frame_list[index_frame] - frame_2_name = frame_list[index_frame + 1] - frame_1 = image_read(frame_list[index_frame]) - frame_2 = image_read(frame_list[index_frame + 1]) + start_timer = timer() - # Resize both frames - frame_1, frame_2 = resize_frames(frame_1, frame_2, resize_factor, target_width, target_height) + frame_base_name = os_path_splitext(frame_list_paths[index_frame])[0] + frame_1_name = frame_list_paths[index_frame] + frame_2_name = frame_list_paths[index_frame + 1] + + # Read frames and resize if needed + frame_1, frame_2 = resize_frames( + frame_1 = image_read(frame_list_paths[index_frame]), + frame_2 = image_read(frame_list_paths[index_frame + 1]), + resize_factor = resize_factor, + target_width = target_width, + target_height = target_height + ) # Generate frame/s beetween frame 1 and 2 - all_video_frames_path_list = AI_generate_frames( - AI_model, - backend, - frame_1, - frame_2, - frame_1_name, - frame_2_name, - frame_base_name, - all_video_frames_path_list, - selected_image_extension, - fluidification_factor - ) + all_video_frames_paths = AI_generate_frames( + AI_model, + backend, + frame_1, + frame_2, + frame_1_name, + frame_2_name, + frame_base_name, + all_video_frames_paths, + selected_image_extension, + fluidification_factor + ) # Update process status every 4 frames update_process_status_videos(processing_queue, file_number, start_timer, index_frame, how_many_frames) # Delete duplicates frames in list - all_video_frames_path_list = list(dict.fromkeys(all_video_frames_path_list)) + all_video_frames_paths = list(dict.fromkeys(all_video_frames_paths)) # Video reconstruction write_process_status(processing_queue, f"{file_number}. Processing fluidified video") video_reconstruction_by_frames( video_path, - all_video_frames_path_list, + audio_path, + all_video_frames_paths, + selected_AI_model, fluidification_factor, slowmotion, resize_factor, @@ -1065,10 +1481,8 @@ def fluidify_video( selected_video_extension ) - # Saving original and interpolated video frames - if selected_save_frames: - save_directory = prepare_output_directory_name(video_path, fluidification_factor, resize_factor) - copy_files_from_temp_directory(save_directory) + if not selected_save_frames: + remove_dir(target_directory) @@ -1183,7 +1597,7 @@ def select_AI_from_menu( global selected_AI_model selected_AI_model = selected_option -def select_fluidity_option_from_menu(new_value: str): +def select_framegeneration_option_from_menu(new_value: str): global selected_fluidity_option selected_fluidity_option = new_value @@ -1214,123 +1628,147 @@ def select_save_frame_from_menu(new_value: str): # GUI info functions --------------------------- def open_info_AI_model(): - messageBox_title = "AI model" - messageBox_text = """ This widget allows to choose between different RIFE models. - - Default: RIFE_4.13 - - RIFE_4.13 - • The complete RIFE AI model - • Excellent interpolation quality - • Recommended GPUs with VRAM >= 4GB - - RIFE_4.13_Lite - • Lightweight version of RIFE AI model - • High interpolation quality - • 25% faster than full model - • Use less GPU VRAM memory - • Recommended for GPUs with VRAM < 4GB - • Recommended to fluidify high definition videos - - """ + option_list = [ + "\n RIFE_4.13\n" + + " • The complete RIFE AI model\n" + + " • Excellent frame generation quality\n" + + " • Recommended GPUs with VRAM >= 4GB\n", + + "\n RIFE_4.13_Lite\n" + + " • Lightweight version of RIFE AI model\n" + + " • High frame generation quality\n" + + " • 25% faster than full model\n" + + " • Use less GPU VRAM memory\n" + + " • Recommended for GPUs with VRAM < 4GB \n", + ] - CTkMessageBox(text = messageBox_text, title = messageBox_title) + CTkMessageBox(messageType = "info", + title = "AI model", + subtitle = " This widget allows to choose between different RIFE models", + default_value = "RIFE_4.13", + option_list = option_list) def open_info_fluidity_option(): - messageBox_title = "AI fluidity" - messageBox_text = """This widget allows you to choose between different AI fluidity option. - - Default: x2 - - FLUIDIFY - • x2 ( doubles video framerate • 30fps => 60fps ) - • x4 ( quadruples video framerate • 30fps => 120fps ) - • x8 ( octuplicate video framerate • 30fps => 240fps ) - - SLOWMOTION (no audio) - • x2-slowmotion ( slowmotion effect by a factor of 2 ) - • x4-slowmotion ( slowmotion effect by a factor of 4 ) - • x8-slowmotion ( slowmotion effect by a factor of 8 ) -""" - - CTkMessageBox(text = messageBox_text, title = messageBox_title) + option_list = [ + "\n FLUIDIFY\n" + + " • x2 ( doubles video framerate • 30fps => 60fps )\n" + + " • x4 ( quadruples video framerate • 30fps => 120fps )\n" + + " • x8 ( octuplicate video framerate • 30fps => 240fps )\n", + + "\n SLOWMOTION (no audio)\n" + + " • x2-slowmotion ( slowmotion effect by a factor of 2 )\n" + + " • x4-slowmotion ( slowmotion effect by a factor of 4 )\n" + + " • x8-slowmotion ( slowmotion effect by a factor of 8 )\n" + ] -def open_info_device(): - messageBox_title = "GPU" - - messageBox_text = """This widget allows you to select the GPU for AI processing. - - • Keep in mind that more powerful the GPU you choose, the faster the process will be - • For optimal results, it's essential to regularly update your GPU drivers""" - - CTkMessageBox(text = messageBox_text, title = messageBox_title) + CTkMessageBox(messageType = "info", + title = "AI frame generation", + subtitle = " This widget allows to choose between different AI frame generation option", + default_value = "x2", + option_list = option_list) + +def open_info_gpu(): + option_list = [ + " Keep in mind that the more powerful the chosen gpu is, the faster the upscaling will be", + " For optimal results, it is essential to regularly update your GPU drivers" + ] + + CTkMessageBox(messageType = "info", + title = "GPU", + subtitle = "This widget allows to select the GPU for AI processing", + default_value = None, + option_list = option_list) def open_info_AI_output(): - messageBox_title = "AI output" - - messageBox_text = """This widget allows to choose the extension of upscaled frames. - - Default: .jpg - - • .jpg ( good quality • very fast ) - • .png ( very good quality • slow • supports transparent images ) - • .bmp ( highest quality • slow ) - • .tiff ( highest quality • very slow )""" - - CTkMessageBox(text = messageBox_text, title = messageBox_title) + option_list = [ + "\n PNG\n" + + " • very good quality\n" + + " • slow and heavy file\n" + + " • supports transparent images\n", + + "\n JPG\n" + + " • good quality\n" + + " • fast and lightweight file\n", + + "\n BMP\n" + + " • highest quality\n" + + " • slow and heavy file\n", + + "\n TIFF\n" + + " • highest quality\n" + + " • very slow and heavy file\n", + ] + + CTkMessageBox(messageType = "info", + title = "AI output", + subtitle = "This widget allows to choose the extension of generated frames", + default_value = ".png", + option_list = option_list) def open_info_input_resolution(): - messageBox_title = "Input resolution %" - - messageBox_text = """This widget allows to choose the resolution input to the AI. + option_list = [ + " A high value (>70%) will create high quality video but will be slower", + " While a low value (<40%) will create good quality video but will much faster", + + " \n For example, for a 1080p (1920x1080) video\n" + + " • Input resolution 25% => input to AI 270p (480x270)\n" + + " • Input resolution 50% => input to AI 540p (960x540)\n" + + " • Input resolution 75% => input to AI 810p (1440x810)\n" + + " • Input resolution 100% => input to AI 1080p (1920x1080) \n", + ] + + CTkMessageBox(messageType = "info", + title = "Input resolution %", + subtitle = "This widget allows to choose the resolution input to the AI", + default_value = "60%", + option_list = option_list) - Default: 60% - - For example for a 100x100px video: - • Input resolution 50% => input to AI 50x50px - • Input resolution 100% => input to AI 100x100px - • Input resolution 200% => input to AI 200x200px """ - - CTkMessageBox(text = messageBox_text, title = messageBox_title) - -def open_info_cpu(): - messageBox_title = "Cpu number" - - messageBox_text = """This widget allows you to choose how many cpus to devote to the app. - - Where possible the app will use the number of cpus selected.""" +def open_info_video_extension(): + option_list = [ + "\n MP4 (x264)\n" + + " • produces well compressed video using x264 codec\n", - CTkMessageBox(text = messageBox_text, title = messageBox_title) + "\n MP4 (x265)\n" + + " • produces well compressed video using x265 codec\n", -def open_info_video_extension(): - messageBox_title = "Video output" + "\n AVI\n" + + " • produces the highest quality video\n" + + " • the video produced can also be of large size\n", - messageBox_text = """ This widget allows you to choose the video output. - - Default: .mp4 (x264) + ] - • mp4 (x264) - produces well compressed video using x264 codec - • mp4 (x265) - produces well compressed video using x265 codec - • avi - produces the highest quality video""" - CTkMessageBox(text = messageBox_text, title = messageBox_title) + CTkMessageBox(messageType = "info", + title = "Video output", + subtitle = "This widget allows to choose the extension of the upscaled video", + default_value = ".mp4 (x264)", + option_list = option_list) def open_info_save_frames(): - messageBox_title = "Save frames" + option_list = [ + "\n ENABLED \n FluidFrames.RIFE will create \n • the fluidified video \n • a folder containing all original and interpolated frames \n", + "\n DISABLED \n FluidFrames.RIFE will create only the fluidified video \n" + ] - messageBox_text = """This widget allows you to choose to save frames generated by the AI. + CTkMessageBox(messageType = "info", + title = "Save frames", + subtitle = "This widget allows to choose to save frames generated by the AI", + default_value = "Enabled", + option_list = option_list) - Default: Enabled - - ENABLED - A video called eating_pizza.mp4 will create - • the fluidified video eating_pizza_RIFExxx.mp4 - • the folder eating_pizza_RIFExxx containing all original and interpolated frames +def open_info_cpu(): + option_list = [ + " When possible the app will use the number of cpus selected", + " Currently this value is only used for the video encoding phase", + ] - DISABLED - A video called eating_pizza.mp4 will create only the fluidified video eating_pizza_RIFExxx.mp4""" + default_cpus = str(int(os_cpu_count()/2)) - CTkMessageBox(text = messageBox_text, title = messageBox_title) + CTkMessageBox(messageType = "info", + title = "Cpu number", + subtitle = "This widget allows to choose how many cpus to devote to the app", + default_value = default_cpus, + option_list = option_list) @@ -1348,22 +1786,22 @@ def open_files_action(): print("> Uploaded files: " + str(uploaded_files_counter) + " => Supported files: " + str(supported_files_counter)) if supported_files_counter > 0: + global scrollable_frame_file_list - scrollable_frame_file_list = ScrollableImagesTextFrame(master = window, fg_color = dark_color, bg_color = dark_color) - scrollable_frame_file_list.place(relx = 0.0, - rely = 0.0, - relwidth = 1.0, - relheight = 0.45) + scrollable_frame_file_list = ScrollableImagesTextFrame( + master = window, + selected_file_list = supported_files_list, + fg_color = dark_color, + bg_color = dark_color + ) + + scrollable_frame_file_list.place( + relx = 0.0, + rely = 0.0, + relwidth = 1.0, + relheight = 0.45 + ) - scrollable_frame_file_list.add_clean_button() - - for index in range(supported_files_counter): - actual_file = supported_files_list[index] - if check_if_file_is_video(actual_file): - video_label, ctkimage = extract_video_info(actual_file) - scrollable_frame_file_list.add_item(text_to_show = video_label, image = ctkimage, file_element = actual_file) - remove_file("temp.jpg") - info_message.set("Ready") else: @@ -1465,17 +1903,17 @@ def place_AI_menu(): AI_menu_button.place(relx = column0_x, rely = row1_y - 0.053, anchor = "center") AI_menu.place(relx = column0_x, rely = row1_y, anchor = "center") -def place_fluidity_option_menu(): +def place_framegeneration_option_menu(): - fluidity_option_button = create_info_button(open_info_fluidity_option, "AI fluidity") - fluidity_option_menu = create_option_menu(select_fluidity_option_from_menu, fluidity_options_list) + fluidity_option_button = create_info_button(open_info_fluidity_option, "AI frame generation") + fluidity_option_menu = create_option_menu(select_framegeneration_option_from_menu, fluidity_options_list) fluidity_option_button.place(relx = column0_x, rely = row2_y - 0.05, anchor = "center") fluidity_option_menu.place(relx = column0_x, rely = row2_y, anchor = "center") def place_gpu_menu(): - gpu_button = create_info_button(open_info_device, "GPU") + gpu_button = create_info_button(open_info_gpu, "GPU") gpu_menu = create_option_menu(select_AI_device_from_menu, gpu_list_names) gpu_button.place(relx = column0_x, rely = row3_y - 0.053, anchor = "center") @@ -1526,7 +1964,7 @@ def place_message_label(): message_label = CTkLabel(master = window, textvariable = info_message, height = 25, - font = bold10, + font = bold11, fg_color = "#ffbf00", text_color = "#000000", anchor = "center", @@ -1581,221 +2019,6 @@ def __init__( self.memory: any = memory self.float64: bool = float64 -class ScrollableImagesTextFrame(CTkScrollableFrame): - def __init__( - self, - master, - **kwargs - ) -> None: - super().__init__(master, **kwargs) - self.grid_columnconfigure(0, weight = 1) - self.file_list = [] - self.label_list = [] - self.button_list = [] - - def get_selected_file_list( - self: any - ) -> list: - - return self.file_list - - def add_clean_button( - self: any - ) -> None: - - label = CTkLabel(self, text = "") - button = CTkButton(self, - image = clear_icon, - font = bold11, - text = "CLEAN", - compound = "left", - width = 100, - height = 28, - border_width = 1, - fg_color = "#282828", - text_color = "#E0E0E0", - border_color = "#0096FF") - - button.configure(command=lambda: self.clean_all_items()) - button.grid(row = len(self.button_list), column=1, pady=(0, 10), padx = 5) - self.label_list.append(label) - self.button_list.append(button) - - def add_item( - self: any, - text_to_show: str, - file_element: str, - image: CTkImage = None - ) -> None: - - label = CTkLabel(self, - text = text_to_show, - font = bold11, - image = image, - text_color = "#E0E0E0", - compound = "left", - padx = 10, - pady = 5, - anchor = "center") - - label.grid(row = len(self.label_list), column = 0, pady = (3, 3), padx = (3, 3), sticky = "w") - self.label_list.append(label) - self.file_list.append(file_element) - - def clean_all_items( - self: any - ) -> None: - - self.label_list = [] - self.button_list = [] - self.file_list = [] - self.destroy() - place_loadFile_section() - -class CTkMessageBox(CTkToplevel): - def __init__( - self, - title: str = "title", - text: str = "text", - type: str = "info" - ) -> None: - - super().__init__() - - self._running: bool = False - - self.type = type - self._title = title - self._text = text - - self.title('') - self.lift() # lift window on top - self.attributes("-topmost", True) # stay on top - self.protocol("WM_DELETE_WINDOW", self._on_closing) - self.after(10, self._create_widgets) # create widgets with slight delay, to avoid white flickering of background - self.resizable(False, False) - self.grab_set() # make other windows not clickable - - def _create_widgets( - self - ) -> None: - - self.grid_columnconfigure((0, 1), weight=1) - self.rowconfigure(0, weight=1) - - self._text = '\n' + self._text +'\n' - - if self.type == "info": color_for_messagebox_title = "#0096FF" - elif self.type == "error": color_for_messagebox_title = "#ff1a1a" - - self._titleLabel = CTkLabel( - master = self, - width = 500, - anchor = 'w', - justify = "left", - fg_color = "transparent", - text_color = color_for_messagebox_title, - font = bold24, - text = self._title - ) - - self._textLabel = CTkLabel(master = self, - width = 620, - wraplength = 620, - corner_radius = 6, - anchor = 'w', - justify = "left", - text_color = "#C0C0C0", - bg_color = "transparent", - fg_color = "#252525", - font = bold12, - text = self._text) - - - - - - - - self._ok_button = CTkButton( - master = self, - command = self._ok_event, - text = 'OK', - width = 125, - font = bold11, - border_width = 1, - fg_color = "#282828", - text_color = "#E0E0E0", - border_color = "#0096FF" - ) - - self._titleLabel.grid(row=0, column=0, columnspan=2, padx=30, pady=20, sticky="ew") - self._textLabel.grid(row=1, column=0, columnspan=2, padx=25, pady=5, sticky="ew") - self._ok_button.grid(row=2, column=1, columnspan=1, padx=(10, 20), pady=(10, 20), sticky="e") - - def _ok_event( - self, - event = None - ) -> None: - self.grab_release() - self.destroy() - - def _on_closing( - self - ) -> None: - self.grab_release() - self.destroy() - -def create_info_button( - command: any, - text: str - ) -> CTkButton: - - return CTkButton(master = window, - command = command, - text = text, - fg_color = "transparent", - text_color = "#C0C0C0", - anchor = "w", - height = 23, - width = 150, - corner_radius = 12, - font = bold12, - image = info_icon) - -def create_option_menu( - command: any, - values: list - ) -> CTkOptionMenu: - - return CTkOptionMenu(master = window, - command = command, - values = values, - width = 150, - height = 31, - anchor = "center", - dropdown_font = bold10, - font = bold11, - text_color = "#C0C0C0", - fg_color = "#000000", - button_color = "#000000", - button_hover_color = "#000000", - dropdown_fg_color = "#000000") - -def create_text_box( - textvariable: StringVar - ) -> CTkEntry: - - return CTkEntry(master = window, - textvariable = textvariable, - border_width = 1, - width = 150, - height = 30, - font = bold10, - justify = "center", - fg_color = "#000000", - border_color = "#404040") - def on_app_close(): window.grab_release() window.destroy() @@ -1820,7 +2043,7 @@ def __init__(self, window): place_telegram_button() place_AI_menu() - place_fluidity_option_menu() + place_framegeneration_option_menu() place_gpu_menu() place_AI_output_menu() @@ -1837,16 +2060,14 @@ def __init__(self, window): if __name__ == "__main__": multiprocessing_freeze_support() - processing_queue = multiprocessing_Queue() - - gpu_list_names = [] - gpu_list = [] + processing_queue = multiprocessing_Queue(maxsize=1) set_appearance_mode("Dark") set_default_color_theme("dark-blue") + gpu_list_names = [] + gpu_list = [] how_many_gpus = directml_device_count() - for index in range(how_many_gpus): gpu_index = index gpu_name = directml_device_name(index) @@ -1899,10 +2120,16 @@ def __init__(self, window): bold10 = CTkFont(family = font, size = 10, weight = "bold") bold11 = CTkFont(family = font, size = 11, weight = "bold") bold12 = CTkFont(family = font, size = 12, weight = "bold") + bold13 = CTkFont(family = font, size = 13, weight = "bold") + bold14 = CTkFont(family = font, size = 14, weight = "bold") + bold16 = CTkFont(family = font, size = 16, weight = "bold") + bold17 = CTkFont(family = font, size = 17, weight = "bold") bold18 = CTkFont(family = font, size = 18, weight = "bold") bold19 = CTkFont(family = font, size = 19, weight = "bold") bold20 = CTkFont(family = font, size = 20, weight = "bold") bold21 = CTkFont(family = font, size = 21, weight = "bold") + bold22 = CTkFont(family = font, size = 22, weight = "bold") + bold23 = CTkFont(family = font, size = 23, weight = "bold") bold24 = CTkFont(family = font, size = 24, weight = "bold") diff --git a/requirements.txt b/requirements.txt index d250f21..cff30df 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,6 @@ torch-directml==0.1.13.1.dev230413 #GUI customtkinter -packaging #UTILS nuitka