mirror of
https://github.com/vladmandic/automatic
synced 2026-09-06 21:10:45 +02:00
refactor video file create and save
Signed-off-by: Vladimir Mandic <mandic00@live.com>
This commit is contained in:
+1
-70
@@ -15,6 +15,7 @@ from modules import sd_samplers, shared, script_callbacks, errors, paths
|
||||
from modules.images_grid import image_grid, get_grid_size, split_grid, combine_grid, check_grid_size, get_font, draw_grid_annotations, draw_prompt_matrix, GridAnnotation, Grid # pylint: disable=unused-import
|
||||
from modules.images_resize import resize_image # pylint: disable=unused-import
|
||||
from modules.images_namegen import FilenameGenerator, get_next_sequence_number # pylint: disable=unused-import
|
||||
from modules.video import save_video # pylint: disable=unused-import
|
||||
|
||||
|
||||
debug = errors.log.trace if os.environ.get('SD_PATH_DEBUG', None) is not None else lambda *args, **kwargs: None
|
||||
@@ -190,76 +191,6 @@ def save_image(image,
|
||||
return params.filename, filename_txt, exifinfo
|
||||
|
||||
|
||||
def save_video_atomic(images, filename, video_type: str = 'none', duration: float = 2.0, loop: bool = False, interpolate: int = 0, scale: float = 1.0, pad: int = 1, change: float = 0.3):
|
||||
try:
|
||||
import cv2
|
||||
except Exception as e:
|
||||
shared.log.error(f'Save video: cv2: {e}')
|
||||
return
|
||||
os.makedirs(os.path.dirname(filename), exist_ok=True)
|
||||
if video_type.lower() == 'mp4':
|
||||
frames = images
|
||||
if interpolate > 0:
|
||||
try:
|
||||
import modules.rife
|
||||
frames = modules.rife.interpolate(images, count=interpolate, scale=scale, pad=pad, change=change)
|
||||
except Exception as e:
|
||||
shared.log.error(f'RIFE interpolation: {e}')
|
||||
errors.display(e, 'RIFE interpolation')
|
||||
video_frames = [np.array(frame) for frame in frames]
|
||||
fourcc = "mp4v"
|
||||
h, w, _c = video_frames[0].shape
|
||||
video_writer = cv2.VideoWriter(filename, fourcc=cv2.VideoWriter_fourcc(*fourcc), fps=len(frames)/duration, frameSize=(w, h))
|
||||
for i in range(len(video_frames)):
|
||||
img = cv2.cvtColor(video_frames[i], cv2.COLOR_RGB2BGR)
|
||||
video_writer.write(img)
|
||||
size = os.path.getsize(filename)
|
||||
shared.log.info(f'Save video: file="{filename}" frames={len(frames)} duration={duration} fourcc={fourcc} size={size}')
|
||||
if video_type.lower() == 'gif' or video_type.lower() == 'png':
|
||||
append = images.copy()
|
||||
image = append.pop(0)
|
||||
if loop:
|
||||
append += append[::-1]
|
||||
frames=len(append) + 1
|
||||
image.save(
|
||||
filename,
|
||||
save_all = True,
|
||||
append_images = append,
|
||||
optimize = False,
|
||||
duration = 1000.0 * duration / frames,
|
||||
loop = 0 if loop else 1,
|
||||
)
|
||||
size = os.path.getsize(filename)
|
||||
shared.log.info(f'Save video: file="{filename}" frames={len(append) + 1} duration={duration} loop={loop} size={size}')
|
||||
|
||||
|
||||
def save_video(p, images, filename = None, video_type: str = 'none', duration: float = 2.0, loop: bool = False, interpolate: int = 0, scale: float = 1.0, pad: int = 1, change: float = 0.3, sync: bool = False):
|
||||
if images is None or len(images) < 2 or video_type is None or video_type.lower() == 'none':
|
||||
return None
|
||||
image = images[0]
|
||||
if p is not None:
|
||||
seed = p.all_seeds[0] if getattr(p, 'all_seeds', None) is not None else p.seed
|
||||
prompt = p.all_prompts[0] if getattr(p, 'all_prompts', None) is not None else p.prompt
|
||||
namegen = FilenameGenerator(p, seed=seed, prompt=prompt, image=image)
|
||||
else:
|
||||
namegen = FilenameGenerator(None, seed=0, prompt='', image=image)
|
||||
if filename is None and p is not None:
|
||||
filename = namegen.apply(shared.opts.samples_filename_pattern if shared.opts.samples_filename_pattern and len(shared.opts.samples_filename_pattern) > 0 else "[seq]-[prompt_words]")
|
||||
filename = os.path.join(shared.opts.outdir_video, filename)
|
||||
filename = namegen.sequence(filename, shared.opts.outdir_video, '')
|
||||
else:
|
||||
if os.pathsep not in filename:
|
||||
filename = os.path.join(shared.opts.outdir_video, filename)
|
||||
if not filename.lower().endswith(video_type.lower()):
|
||||
filename += f'.{video_type.lower()}'
|
||||
filename = namegen.sanitize(filename)
|
||||
if not sync:
|
||||
threading.Thread(target=save_video_atomic, args=(images, filename, video_type, duration, loop, interpolate, scale, pad, change)).start()
|
||||
else:
|
||||
save_video_atomic(images, filename, video_type, duration, loop, interpolate, scale, pad, change)
|
||||
return filename
|
||||
|
||||
|
||||
def safe_decode_string(s: bytes):
|
||||
remove_prefix = lambda text, prefix: text[len(prefix):] if text.startswith(prefix) else text # pylint: disable=unnecessary-lambda-assignment
|
||||
for encoding in ['utf-8', 'utf-16', 'ascii', 'latin_1', 'cp1252', 'cp437']: # try different encodings
|
||||
|
||||
@@ -179,6 +179,9 @@ def set_pipeline_args(p, model, prompts:list, negative_prompts:list, prompts_2:t
|
||||
p.extra_generation_params["CHI"] = chi
|
||||
if not chi:
|
||||
args['complex_human_instruction'] = None
|
||||
if 'use_resolution_binning' in possible:
|
||||
args['use_resolution_binning'] = True
|
||||
p.extra_generation_params["Binning"] = True
|
||||
if prompt_parser_diffusers.embedder is not None and not prompt_parser_diffusers.embedder.scheduled_prompt: # not scheduled so we dont need it anymore
|
||||
prompt_parser_diffusers.embedder = None
|
||||
|
||||
|
||||
+1
-1
@@ -768,6 +768,7 @@ options_templates.update(options_section(('ui', "User Interface"), {
|
||||
"autolaunch": OptionInfo(False, "Autolaunch browser upon startup"),
|
||||
"font_size": OptionInfo(14, "Font size", gr.Slider, {"minimum": 8, "maximum": 32, "step": 1, "visible": True}),
|
||||
"aspect_ratios": OptionInfo("1:1, 4:3, 3:2, 16:9, 16:10, 21:9, 2:3, 3:4, 9:16, 10:16, 9:21", "Allowed aspect ratios"),
|
||||
"logmonitor_show": OptionInfo(True, "Show log view"),
|
||||
"motd": OptionInfo(False, "Show MOTD"),
|
||||
"compact_view": OptionInfo(False, "Compact view"),
|
||||
"return_grid": OptionInfo(True, "Show grid in results"),
|
||||
@@ -787,7 +788,6 @@ options_templates.update(options_section(('live-preview', "Live Previews"), {
|
||||
"taesd_layers": OptionInfo(3, "TAESD decode layers", gr.Slider, {"minimum": 1, "maximum": 3, "step": 1}),
|
||||
"live_preview_downscale": OptionInfo(True, "Downscale high resolution live previews"),
|
||||
|
||||
"logmonitor_show": OptionInfo(True, "Show log view"),
|
||||
"logmonitor_refresh_period": OptionInfo(5000, "Log view update period", gr.Slider, {"minimum": 0, "maximum": 30000, "step": 25}),
|
||||
"notification_audio_enable": OptionInfo(False, "Play a notification upon completion"),
|
||||
"notification_audio_path": OptionInfo("html/notification.mp3","Path to notification sound", component_args=hide_dirs, folder=True),
|
||||
|
||||
@@ -168,13 +168,8 @@ def create_ui(_blocks: gr.Blocks=None):
|
||||
with gr.Row():
|
||||
video_skip_frames = gr.Slider(minimum=0, maximum=100, step=1, label='Skip input frames', value=0, elem_id="control_video_skip_frames")
|
||||
with gr.Row():
|
||||
video_type = gr.Dropdown(label='Video file', choices=['None', 'GIF', 'PNG', 'MP4'], value='None', elem_id="control_video_type")
|
||||
video_duration = gr.Slider(label='Duration', minimum=0.25, maximum=300, step=0.25, value=2, visible=False, elem_id="control_video_duration")
|
||||
with gr.Row():
|
||||
video_loop = gr.Checkbox(label='Loop', value=True, visible=False, elem_id="control_video_loop")
|
||||
video_pad = gr.Slider(label='Pad frames', minimum=0, maximum=24, step=1, value=1, visible=False, elem_id="control_video_pad")
|
||||
video_interpolate = gr.Slider(label='Interpolate frames', minimum=0, maximum=24, step=1, value=0, visible=False, elem_id="control_video_interpolate")
|
||||
video_type.change(fn=helpers.video_type_change, inputs=[video_type], outputs=[video_duration, video_loop, video_pad, video_interpolate])
|
||||
from modules.ui_sections import create_video_inputs
|
||||
video_type, video_duration, video_loop, video_pad, video_interpolate = create_video_inputs()
|
||||
|
||||
enable_hr, hr_sampler_index, hr_denoising_strength, hr_resize_mode, hr_resize_context, hr_upscaler, hr_force, hr_second_pass_steps, hr_scale, hr_resize_x, hr_resize_y, refiner_steps, refiner_start, refiner_prompt, refiner_negative = ui_sections.create_hires_inputs('control')
|
||||
detailer_enabled, detailer_prompt, detailer_negative, detailer_steps, detailer_strength = shared.yolo.ui('control')
|
||||
|
||||
@@ -181,15 +181,6 @@ def select_input(input_mode, input_image, init_image, init_type, input_resize, i
|
||||
return res
|
||||
|
||||
|
||||
def video_type_change(video_type):
|
||||
return [
|
||||
gr.update(visible=video_type != 'None'),
|
||||
gr.update(visible=video_type == 'GIF' or video_type == 'PNG'),
|
||||
gr.update(visible=video_type == 'MP4'),
|
||||
gr.update(visible=video_type == 'MP4'),
|
||||
]
|
||||
|
||||
|
||||
def copy_input(mode_from, mode_to, input_image, input_resize, input_inpaint):
|
||||
debug_log(f'Control transfter input: from={mode_from} to={mode_to} image={input_image} resize={input_resize} inpaint={input_inpaint}')
|
||||
def getimg(ctrl):
|
||||
|
||||
@@ -131,6 +131,26 @@ def create_seed_inputs(tab, reuse_visible=True):
|
||||
return seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w
|
||||
|
||||
|
||||
def create_video_inputs():
|
||||
def video_type_change(video_type):
|
||||
return [
|
||||
gr.update(visible=video_type != 'None'),
|
||||
gr.update(visible=video_type in ['GIF', 'PNG']),
|
||||
gr.update(visible=video_type not in ['None', 'GIF', 'PNG']),
|
||||
gr.update(visible=video_type not in ['None', 'GIF', 'PNG']),
|
||||
]
|
||||
with gr.Column():
|
||||
video_codecs = ['None', 'GIF', 'PNG', 'MP4/MP4V', 'MP4/AVC1', 'MP4/JVT3', 'MKV/H264', 'AVI/DIVX', 'AVI/RGBA', 'MJPEG/MJPG', 'MPG/MPG1', 'AVR/AVR1']
|
||||
video_type = gr.Dropdown(label='Video type', choices=video_codecs, value='None')
|
||||
with gr.Column():
|
||||
video_duration = gr.Slider(label='Duration', minimum=0.25, maximum=300, step=0.25, value=2, visible=False)
|
||||
video_loop = gr.Checkbox(label='Loop', value=True, visible=False, elem_id="control_video_loop")
|
||||
video_pad = gr.Slider(label='Pad frames', minimum=0, maximum=24, step=1, value=1, visible=False)
|
||||
video_interpolate = gr.Slider(label='Interpolate frames', minimum=0, maximum=24, step=1, value=0, visible=False)
|
||||
video_type.change(fn=video_type_change, inputs=[video_type], outputs=[video_duration, video_loop, video_pad, video_interpolate])
|
||||
return video_type, video_duration, video_loop, video_pad, video_interpolate
|
||||
|
||||
|
||||
def create_cfg_inputs(tab):
|
||||
with gr.Row():
|
||||
cfg_scale = gr.Slider(minimum=0.0, maximum=30.0, step=0.1, label='Guidance scale', value=6.0, elem_id=f"{tab}_cfg_scale")
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import os
|
||||
import threading
|
||||
import numpy as np
|
||||
from modules import shared, errors
|
||||
from modules.images_namegen import FilenameGenerator # pylint: disable=unused-import
|
||||
|
||||
|
||||
def interpolate_frames(images, count: int = 0, scale: float = 1.0, pad: int = 1, change: float = 0.3):
|
||||
if images is None:
|
||||
return []
|
||||
if not isinstance(images, list):
|
||||
images = [images]
|
||||
if count > 0:
|
||||
try:
|
||||
import modules.rife
|
||||
frames = modules.rife.interpolate(images, count=count, scale=scale, pad=pad, change=change)
|
||||
if len(frames) > 0:
|
||||
images = frames
|
||||
except Exception as e:
|
||||
shared.log.error(f'RIFE interpolation: {e}')
|
||||
errors.display(e, 'RIFE interpolation')
|
||||
return [np.array(image) for image in images]
|
||||
|
||||
|
||||
def save_video_atomic(images, filename, video_type: str = 'none', duration: float = 2.0, loop: bool = False, interpolate: int = 0, scale: float = 1.0, pad: int = 1, change: float = 0.3):
|
||||
try:
|
||||
import cv2
|
||||
except Exception as e:
|
||||
shared.log.error(f'Save video: cv2: {e}')
|
||||
return
|
||||
os.makedirs(os.path.dirname(filename), exist_ok=True)
|
||||
if video_type.lower() in ['gif', 'png']:
|
||||
append = images.copy()
|
||||
image = append.pop(0)
|
||||
if loop:
|
||||
append += append[::-1]
|
||||
frames=len(append) + 1
|
||||
image.save(
|
||||
filename,
|
||||
save_all = True,
|
||||
append_images = append,
|
||||
optimize = False,
|
||||
duration = 1000.0 * duration / frames,
|
||||
loop = 0 if loop else 1,
|
||||
)
|
||||
size = os.path.getsize(filename)
|
||||
shared.log.info(f'Save video: file="{filename}" frames={len(append) + 1} duration={duration} loop={loop} size={size}')
|
||||
elif video_type.lower() != 'none':
|
||||
frames = interpolate_frames(images, count=interpolate, scale=scale, pad=pad, change=change)
|
||||
fourcc = "mp4v"
|
||||
h, w, _c = frames[0].shape
|
||||
video_writer = cv2.VideoWriter(filename, fourcc=cv2.VideoWriter_fourcc(*fourcc), fps=len(frames)/duration, frameSize=(w, h))
|
||||
for i in range(len(frames)):
|
||||
img = cv2.cvtColor(frames[i], cv2.COLOR_RGB2BGR)
|
||||
video_writer.write(img)
|
||||
size = os.path.getsize(filename)
|
||||
shared.log.info(f'Save video: file="{filename}" frames={len(frames)} duration={duration} fourcc={fourcc} size={size}')
|
||||
|
||||
|
||||
def save_video(p, images, filename = None, video_type: str = 'none', duration: float = 2.0, loop: bool = False, interpolate: int = 0, scale: float = 1.0, pad: int = 1, change: float = 0.3, sync: bool = False):
|
||||
if images is None or len(images) < 2 or video_type is None or video_type.lower() == 'none':
|
||||
return None
|
||||
image = images[0]
|
||||
if p is not None:
|
||||
seed = p.all_seeds[0] if getattr(p, 'all_seeds', None) is not None else p.seed
|
||||
prompt = p.all_prompts[0] if getattr(p, 'all_prompts', None) is not None else p.prompt
|
||||
namegen = FilenameGenerator(p, seed=seed, prompt=prompt, image=image)
|
||||
else:
|
||||
namegen = FilenameGenerator(None, seed=0, prompt='', image=image)
|
||||
if filename is None and p is not None:
|
||||
filename = namegen.apply(shared.opts.samples_filename_pattern if shared.opts.samples_filename_pattern and len(shared.opts.samples_filename_pattern) > 0 else "[seq]-[prompt_words]")
|
||||
filename = os.path.join(shared.opts.outdir_video, filename)
|
||||
filename = namegen.sequence(filename, shared.opts.outdir_video, '')
|
||||
else:
|
||||
if os.pathsep not in filename:
|
||||
filename = os.path.join(shared.opts.outdir_video, filename)
|
||||
ext = video_type.lower().split('/')[0] if '/' in video_type else video_type.lower()
|
||||
if not filename.lower().endswith(ext):
|
||||
filename += f'.{ext}'
|
||||
filename = namegen.sanitize(filename)
|
||||
if not sync:
|
||||
threading.Thread(target=save_video_atomic, args=(images, filename, video_type, duration, loop, interpolate, scale, pad, change)).start()
|
||||
else:
|
||||
save_video_atomic(images, filename, video_type, duration, loop, interpolate, scale, pad, change)
|
||||
return filename
|
||||
Reference in New Issue
Block a user