mirror of
https://github.com/vladmandic/sdnext.git
synced 2026-10-12 02:24:13 +00:00
@@ -34,10 +34,15 @@
|
||||
- log long torch autotune operations
|
||||
- utilize `torch.accelerator` where available
|
||||
- add `SD_DIFFUSERS_DEBUG` and `SD_TRANSFORMERS_DEBUG` env variables to trace diffusers and transformers internal operations
|
||||
- **Video**
|
||||
- reorganized *video* tab
|
||||
- better support for video codeces and formats
|
||||
- **API**
|
||||
- full support for video generation using api
|
||||
new endpoints: `/sdapi/v1/video`, `/sdapi/v1/video/models`, `/sdapi/v1/video/file`
|
||||
*note*: video api uses async workflow where you submit request and then later download the result
|
||||
- **Other**
|
||||
- ar display ration on manual resolution change
|
||||
- **Removed**
|
||||
- remove DirectML support
|
||||
latest release was over 2 years ago and is not compatible with modern frameworks
|
||||
|
||||
@@ -31,11 +31,20 @@
|
||||
- [ImageDB](https://github.com/vladmandic/imagedb)
|
||||
- Unify *huggingface* and *diffusers* model folders
|
||||
|
||||
### Roadmap
|
||||
|
||||
- Object clear remover for Kanvas
|
||||
- OpenAI API interface for image generation
|
||||
- Lightweight scheduler/queue manager
|
||||
- Distraction-free UI mode with prompt-only
|
||||
- Revisit transformer caching for modular pipelines
|
||||
- Revisit guidance for modular pipelines
|
||||
- Implement modular for some image models
|
||||
|
||||
### OnHold
|
||||
|
||||
- [nVidia-VFX](https://pypi.org/project/nvidia-vfx/): not compatible with latest nVidia drivers
|
||||
- [QuantFunc](https://huggingface.co/QuantFunc/Klein-9B-Series): once its released as sdk
|
||||
- [nVidia LocateAnything](https://huggingface.co/nvidia/LocateAnything-3B) detection for Detailer, once its compatible with transformers==5
|
||||
- LoRA add OMI format support for SD35/FLUX.1
|
||||
- Remote Text-Encoder support, sidelined for the moment
|
||||
- Multi-user support
|
||||
|
||||
@@ -25,8 +25,8 @@ def create_ui(prompt, _negative, styles, overrides, script_inputs, mp4_fps, mp4_
|
||||
with gr.Row():
|
||||
width, height = ui_sections.create_resolution_inputs('minimax', default_width=1024, default_height=576, step=32)
|
||||
with gr.Row():
|
||||
steps = gr.Slider(minimum=2, maximum=100, step=1, label="Steps", elem_id='minimax_steps', value=30)
|
||||
frames = gr.Slider(label='Frames', minimum=22, maximum=345, step=17, value=107, elem_id='minimax_frames')
|
||||
steps = gr.Slider(minimum=2, maximum=100, step=1, label="MiniMax Steps", elem_id='minimax_steps', value=30)
|
||||
frames = gr.Slider(label='MiniMax Frames', minimum=22, maximum=345, step=17, value=107, elem_id='minimax_frames')
|
||||
with gr.Row():
|
||||
seed = gr.Number(label='Seed', value=-1, elem_id='minimax_seed', container=True)
|
||||
random_seed = ToolButton(ui_symbols.random, elem_id='minimax_seed_random')
|
||||
|
||||
@@ -155,6 +155,18 @@ def generate(task_id, _ui_state,
|
||||
if pixels is None:
|
||||
return None, "MiniMax: No frames generated"
|
||||
|
||||
if mp4_interpolate > 0:
|
||||
p.video_interpolate = mp4_interpolate
|
||||
from modules.processing_video import apply_video_interpolation
|
||||
print('HERE1', pixels.shape, pixels.min(), pixels.max())
|
||||
# pixels is 5-D (N,C,T,H,W) in [-1,1]; RIFE needs 4-D (T,C,H,W) in [0,1]
|
||||
x = pixels.squeeze(0).permute(1, 0, 2, 3)
|
||||
x = (x.clamp(-1., 1.) + 1.0) * 0.5
|
||||
x = apply_video_interpolation(p, x, count=mp4_interpolate) # sets p.video_interpolated otherwise main save_video would do it also
|
||||
x = x * 2.0 - 1.0
|
||||
pixels = x.permute(1, 0, 2, 3).unsqueeze(0)
|
||||
print('HERE2', pixels.shape, pixels.min(), pixels.max())
|
||||
|
||||
save_fps = mp4_fps * processing_video.interpolation_factor(p)
|
||||
num_frames, video_file, _thumb = video_save.save_video(
|
||||
p=p,
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
codecs_config = {
|
||||
# --- Modern / Standard Distribution (CPU) ---
|
||||
'libx264': {
|
||||
'name': 'H.264 / AVC',
|
||||
'desc': 'Standard for web and streaming compatibility.',
|
||||
'ext': 'mp4',
|
||||
'allowed_exts': ['mp4', 'mkv', 'mov', 'ts'],
|
||||
'options': 'crf=18:preset=p4',
|
||||
},
|
||||
'libx264rgb': {
|
||||
'name': 'H.264 Lossless RGB',
|
||||
'desc': 'Lossless recording for screen capture.',
|
||||
'ext': 'mp4',
|
||||
'allowed_exts': ['mp4', 'mkv', 'mov'],
|
||||
'options': 'crf=0:preset=p4',
|
||||
},
|
||||
'libx265': {
|
||||
'name': 'HEVC / H.265',
|
||||
'desc': 'High efficiency compression for 4K video.',
|
||||
'ext': 'mp4',
|
||||
'allowed_exts': ['mp4', 'mkv', 'mov', 'ts'],
|
||||
'options': 'crf=22:preset=medium',
|
||||
},
|
||||
'libvpx': {
|
||||
'name': 'VP8 Video',
|
||||
'desc': 'Legacy open video format for WebM.',
|
||||
'ext': 'webm',
|
||||
'allowed_exts': ['webm', 'mkv'],
|
||||
'options': 'crf=10:b=0',
|
||||
},
|
||||
'libvpx-vp9': {
|
||||
'name': 'VP9 Video',
|
||||
'desc': 'Royalty-free web video format.',
|
||||
'ext': 'webm',
|
||||
'allowed_exts': ['webm', 'mkv'],
|
||||
'options': 'crf=23:b=0',
|
||||
},
|
||||
'libsvtav1': {
|
||||
'name': 'AV1',
|
||||
'desc': 'Next-gen open codec with high compression.',
|
||||
'ext': 'mp4',
|
||||
'allowed_exts': ['mp4', 'mkv', 'webm'],
|
||||
'options': 'crf=26:preset=5',
|
||||
},
|
||||
'mpeg4': {
|
||||
'name': 'MPEG-4 Part 2',
|
||||
'desc': 'Legacy format for old media players.',
|
||||
'ext': 'mp4',
|
||||
'allowed_exts': ['mp4', 'avi', 'mkv'],
|
||||
'options': 'qscale:v=3',
|
||||
},
|
||||
'mjpeg': {
|
||||
'name': 'Motion JPEG',
|
||||
'desc': 'Sequence of JPEG frames; minimal CPU load.',
|
||||
'ext': 'avi',
|
||||
'allowed_exts': ['avi', 'mov', 'mkv'],
|
||||
'options': 'qscale:v=3',
|
||||
},
|
||||
|
||||
# --- Hardware Accelerated: NVIDIA NVENC ---
|
||||
'h264_nvenc': {
|
||||
'name': 'H.264 (NVIDIA)',
|
||||
'desc': 'Fast NVIDIA GPU encoding.',
|
||||
'ext': 'mp4',
|
||||
'allowed_exts': ['mp4', 'mkv', 'mov', 'ts'],
|
||||
'options': 'rc=vbr:cq=19:preset=p5',
|
||||
},
|
||||
'hevc_nvenc': {
|
||||
'name': 'HEVC / H.265 (NVIDIA)',
|
||||
'desc': 'Fast high-efficiency NVIDIA GPU encoding.',
|
||||
'ext': 'mp4',
|
||||
'allowed_exts': ['mp4', 'mkv', 'mov', 'ts'],
|
||||
'options': 'rc=vbr:cq=23:preset=p5',
|
||||
},
|
||||
'av1_nvenc': {
|
||||
'name': 'AV1 (NVIDIA)',
|
||||
'desc': 'Fast AV1 encoding for NVIDIA RTX 40+.',
|
||||
'ext': 'mp4',
|
||||
'allowed_exts': ['mp4', 'mkv', 'webm'],
|
||||
'options': 'rc=vbr:cq=26:preset=p5',
|
||||
},
|
||||
|
||||
# --- Hardware Accelerated: Intel QSV ---
|
||||
'h264_qsv': {
|
||||
'name': 'H.264 (Intel QuickSync)',
|
||||
'desc': 'Hardware encoding via Intel GPU.',
|
||||
'ext': 'mp4',
|
||||
'allowed_exts': ['mp4', 'mkv', 'mov', 'ts'],
|
||||
'options': 'global_quality=20:preset=medium',
|
||||
},
|
||||
'hevc_qsv': {
|
||||
'name': 'HEVC / H.265 (Intel QuickSync)',
|
||||
'desc': 'Hardware HEVC encoding via Intel GPU.',
|
||||
'ext': 'mp4',
|
||||
'allowed_exts': ['mp4', 'mkv', 'mov', 'ts'],
|
||||
'options': 'global_quality=23:preset=medium',
|
||||
},
|
||||
'av1_qsv': {
|
||||
'name': 'AV1 (Intel QuickSync)',
|
||||
'desc': 'Hardware AV1 encoding via Intel Arc/iGPU.',
|
||||
'ext': 'mp4',
|
||||
'allowed_exts': ['mp4', 'mkv', 'webm'],
|
||||
'options': 'global_quality=26:preset=medium',
|
||||
},
|
||||
'vp9_qsv': {
|
||||
'name': 'VP9 (Intel QuickSync)',
|
||||
'desc': 'Hardware VP9 encoding via Intel GPU.',
|
||||
'ext': 'webm',
|
||||
'allowed_exts': ['webm', 'mkv'],
|
||||
'options': 'global_quality=24',
|
||||
},
|
||||
|
||||
# --- Hardware Accelerated: AMD AMF (Windows) ---
|
||||
'h264_amf': {
|
||||
'name': 'H.264 (AMD AMF)',
|
||||
'desc': 'Hardware encoding for AMD GPUs on Windows.',
|
||||
'ext': 'mp4',
|
||||
'allowed_exts': ['mp4', 'mkv', 'mov', 'ts'],
|
||||
'options': 'rc=cqp:qp_i=18:qp_p=18:quality=quality',
|
||||
},
|
||||
'hevc_amf': {
|
||||
'name': 'HEVC / H.265 (AMD AMF)',
|
||||
'desc': 'Hardware HEVC encoding for AMD GPUs on Windows.',
|
||||
'ext': 'mp4',
|
||||
'allowed_exts': ['mp4', 'mkv', 'mov', 'ts'],
|
||||
'options': 'rc=cqp:qp_i=22:qp_p=22:quality=quality',
|
||||
},
|
||||
'av1_amf': {
|
||||
'name': 'AV1 (AMD AMF)',
|
||||
'desc': 'Hardware AV1 encoding for AMD RX 7000+.',
|
||||
'ext': 'mp4',
|
||||
'allowed_exts': ['mp4', 'mkv', 'webm'],
|
||||
'options': 'rc=cqp:qp_i=26:qp_p=26:quality=quality',
|
||||
},
|
||||
|
||||
# --- Hardware Accelerated: AMD / Linux (VAAPI) ---
|
||||
'h264_vaapi': {
|
||||
'name': 'H.264 (VAAPI Linux)',
|
||||
'desc': 'Hardware encoding via Linux VAAPI.',
|
||||
'ext': 'mp4',
|
||||
'allowed_exts': ['mp4', 'mkv', 'mov'],
|
||||
'options': 'qp=20',
|
||||
},
|
||||
'hevc_vaapi': {
|
||||
'name': 'HEVC / H.265 (VAAPI Linux)',
|
||||
'desc': 'Hardware HEVC encoding via Linux VAAPI.',
|
||||
'ext': 'mp4',
|
||||
'allowed_exts': ['mp4', 'mkv', 'mov'],
|
||||
'options': 'qp=25',
|
||||
},
|
||||
'av1_vaapi': {
|
||||
'name': 'AV1 (VAAPI Linux)',
|
||||
'desc': 'Hardware AV1 encoding via Linux VAAPI.',
|
||||
'ext': 'mp4',
|
||||
'allowed_exts': ['mp4', 'mkv', 'webm'],
|
||||
'options': 'qp=28',
|
||||
},
|
||||
|
||||
# --- Professional / Editing / Archival ---
|
||||
'prores': {
|
||||
'name': 'Apple ProRes',
|
||||
'desc': 'High-performance video editing standard.',
|
||||
'ext': 'mov',
|
||||
'allowed_exts': ['mov', 'mkv'],
|
||||
'options': 'profile=3',
|
||||
},
|
||||
'prores_ks': {
|
||||
'name': 'Apple ProRes (iCodec)',
|
||||
'desc': 'Open ProRes implementation for editing.',
|
||||
'ext': 'mov',
|
||||
'allowed_exts': ['mov', 'mkv'],
|
||||
'options': 'profile=3',
|
||||
},
|
||||
'dnxhd': {
|
||||
'name': 'Avid DNxHD / DNxHR',
|
||||
'desc': 'Broadcast editing format for Avid workflows.',
|
||||
'ext': 'mov',
|
||||
'allowed_exts': ['mov', 'mkv', 'mxf'],
|
||||
'options': 'b=185M',
|
||||
},
|
||||
'ffv1': {
|
||||
'name': 'Lossless Archival (FFV1)',
|
||||
'desc': 'Lossless codec for long-term storage.',
|
||||
'ext': 'mkv',
|
||||
'allowed_exts': ['mkv', 'avi'],
|
||||
'options': 'level=3:coder=1:context=1:slices=16',
|
||||
},
|
||||
'huffyuv': {
|
||||
'name': 'HuffYUV Lossless',
|
||||
'desc': 'Fast, simple intra-frame lossless codec.',
|
||||
'ext': 'avi',
|
||||
'allowed_exts': ['avi', 'mkv'],
|
||||
'options': '',
|
||||
},
|
||||
'utvideo': {
|
||||
'name': 'Ut Video Lossless',
|
||||
'desc': 'Efficient lossless format for editing.',
|
||||
'ext': 'avi',
|
||||
'allowed_exts': ['avi', 'mkv', 'mov'],
|
||||
'options': 'pred=left',
|
||||
},
|
||||
'magicyuv': {
|
||||
'name': 'MagicYUV Lossless',
|
||||
'desc': 'Real-time lossless recording codec.',
|
||||
'ext': 'avi',
|
||||
'allowed_exts': ['avi', 'mkv'],
|
||||
'options': '',
|
||||
},
|
||||
}
|
||||
|
||||
def get_codec_list():
|
||||
return ['none'] + list(codecs_config.keys())
|
||||
|
||||
def get_codec_name(codec_name):
|
||||
codec_info = codecs_config.get(codec_name)
|
||||
if codec_info is None:
|
||||
return ''
|
||||
return codec_info['name']
|
||||
|
||||
def get_codec_ext(codec_name):
|
||||
codec_info = codecs_config.get(codec_name)
|
||||
if codec_info is None:
|
||||
return ''
|
||||
return codec_info['ext']
|
||||
|
||||
def get_codec_options(codec_name):
|
||||
codec_info = codecs_config.get(codec_name)
|
||||
if codec_info is None:
|
||||
return ''
|
||||
return codec_info['options']
|
||||
|
||||
def get_codec_allowed_exts(codec_name):
|
||||
codec_info = codecs_config.get(codec_name)
|
||||
if codec_info is None:
|
||||
return []
|
||||
return codec_info['allowed_exts']
|
||||
|
||||
def get_codec_dict(codec_name):
|
||||
codec_info = codecs_config.get(codec_name)
|
||||
if codec_info is None:
|
||||
return {}
|
||||
return codec_info
|
||||
@@ -171,6 +171,25 @@ def add_audio_tensor(container, audio_stream, audio: torch.Tensor, sample_rate:
|
||||
add_audio_packets(container, audio_stream, {"sr": sample_rate, "layout": layout, "frames": [audio_frame]})
|
||||
|
||||
|
||||
def parse_options(options):
|
||||
if isinstance(options, dict):
|
||||
return options
|
||||
if not isinstance(options, str) or not options.strip():
|
||||
return {}
|
||||
parsed_options = {}
|
||||
normalized = options.replace(',', ':') # Standardize delimiters by replacing commas with colons
|
||||
for item in normalized.split(':'):
|
||||
item = item.strip()
|
||||
if not item:
|
||||
continue
|
||||
if '=' in item:
|
||||
key, value = item.split('=', 1)
|
||||
parsed_options[key.strip()] = value.strip()
|
||||
else:
|
||||
parsed_options[item] = '1' # Handle flag options without explicit '=' (e.g., 'fastseek')
|
||||
return parsed_options
|
||||
|
||||
|
||||
def atomic_save_video(
|
||||
filename: str,
|
||||
tensor: torch.Tensor,
|
||||
@@ -192,18 +211,7 @@ def atomic_save_video(
|
||||
savejob = shared.state.begin('Save video')
|
||||
frames, height, width, _channels = tensor.shape
|
||||
rate = round(fps)
|
||||
parsed_options = {}
|
||||
if isinstance(options, str):
|
||||
for option in [opt.strip() for opt in options.split(',')]:
|
||||
if '=' in option:
|
||||
key, value = option.split('=', 1)
|
||||
elif ':' in option:
|
||||
key, value = option.split(':', 1)
|
||||
else:
|
||||
continue
|
||||
parsed_options[key.strip()] = value.strip()
|
||||
elif isinstance(options, dict):
|
||||
parsed_options = options
|
||||
parsed_options = parse_options(options)
|
||||
log.info(f'Video: file="{filename}" codec={codec} frames={frames} width={width} height={height} fps={rate} audio={audio is not None} sample_rate={sample_rate} options={parsed_options}')
|
||||
video_array = torch.as_tensor(tensor, dtype=torch.uint8).numpy(force=True)
|
||||
task = pbar.add_task('encoding', total=frames) if pbar is not None else None
|
||||
|
||||
@@ -59,21 +59,33 @@ def model_load(engine, model):
|
||||
|
||||
|
||||
def create_ui_outputs():
|
||||
from modules.video_models import video_codecs
|
||||
default_codec = 'libx264'
|
||||
def on_codec_change(codec):
|
||||
cfg = video_codecs.get_codec_dict(codec)
|
||||
if not cfg:
|
||||
return gr.update(value='unknown codec'), gr.update(value='mp4'), gr.update(value='')
|
||||
return gr.update(value=cfg['name']), gr.update(value=cfg['ext'], choices=cfg['allowed_exts']), gr.update(value=cfg['options'])
|
||||
|
||||
with gr.Row():
|
||||
with gr.Column(variant='compact', elem_id="video_outputs", elem_classes=['settings-column'], scale=1):
|
||||
with gr.Row():
|
||||
mp4_fps = gr.Slider(label="FPS", minimum=1, maximum=60, value=24, step=1)
|
||||
mp4_fps = gr.Slider(label="Target FPS", minimum=1, maximum=60, value=24, step=1)
|
||||
mp4_interpolate = gr.Slider(label="Video interpolation", minimum=0, maximum=10, value=0, step=1)
|
||||
with gr.Row():
|
||||
mp4_codec = gr.Dropdown(label="Video codec", choices=['none', 'libx264'], value='libx264', type='value')
|
||||
mp4_codec = gr.Dropdown(label="Video codec", choices=video_codecs.get_codec_list(), value=default_codec, type='value')
|
||||
ui_common.create_refresh_button(mp4_codec, video_utils.get_codecs, elem_id="video_mp4_codec_refresh")
|
||||
mp4_ext = gr.Textbox(label="Video format", value='mp4', elem_id="video_mp4_ext")
|
||||
mp4_opt = gr.Textbox(label="Video options", value='crf:16', elem_id="video_mp4_opt")
|
||||
mp4_info = gr.Label(value=video_codecs.get_codec_name(default_codec), label='Codec info', elem_id='video_mp4_codec_label', show_label=False, elem_classes=['codec-label'])
|
||||
with gr.Row():
|
||||
mp4_video = gr.Checkbox(label='Video save video', value=True, elem_id="video_mp4_video")
|
||||
mp4_frames = gr.Checkbox(label='Video save frames', value=False, elem_id="video_mp4_frames")
|
||||
mp4_sf = gr.Checkbox(label='Video save safetensors', value=False, elem_id="video_mp4_sf")
|
||||
mp4_thumb = gr.Checkbox(label='Video save thumbnail', value=True, elem_id="video_mp4_thumb")
|
||||
# mp4_ext = gr.Textbox(label="Video format", value='mp4', elem_id="video_mp4_ext")
|
||||
mp4_ext = gr.Dropdown(label="Video format", choices=video_codecs.get_codec_allowed_exts(default_codec), value=video_codecs.get_codec_ext(default_codec), elem_id="video_mp4_ext")
|
||||
mp4_opt = gr.Textbox(label="FFmpeg options", value=video_codecs.get_codec_options(default_codec), elem_id="video_mp4_opt")
|
||||
with gr.Row():
|
||||
mp4_video = gr.Checkbox(label='Save: video', value=True, elem_id="video_mp4_video")
|
||||
mp4_frames = gr.Checkbox(label='Save: frames', value=False, elem_id="video_mp4_frames")
|
||||
mp4_sf = gr.Checkbox(label='Save: safetensors', value=False, elem_id="video_mp4_sf")
|
||||
mp4_thumb = gr.Checkbox(label='Save: thumbnail', value=True, elem_id="video_mp4_thumb")
|
||||
mp4_codec.change(fn=on_codec_change, inputs=[mp4_codec], outputs=[mp4_info, mp4_ext, mp4_opt], show_progress='hidden')
|
||||
return mp4_fps, mp4_interpolate, mp4_codec, mp4_ext, mp4_opt, mp4_video, mp4_frames, mp4_sf, mp4_thumb
|
||||
|
||||
|
||||
@@ -87,16 +99,7 @@ def create_ui(prompt, negative, styles, overrides, script_inputs, mp4_fps, mp4_i
|
||||
model = gr.Dropdown(label='Video model', choices=[''], value='None', elem_id="video_model")
|
||||
btn_load = ToolButton(ui_symbols.loading, elem_id="video_model_load")
|
||||
url = gr.HTML(label='Model URL', elem_id='video_model_url', value='<br><br>')
|
||||
with gr.Accordion(open=False, label="Parameters", elem_id='video_parameters_accordion'):
|
||||
steps, sampler_index = ui_sections.create_sampler_and_steps_selection(None, "video", default_steps=50)
|
||||
with gr.Row():
|
||||
sampler_shift = gr.Slider(label='Sampler shift', minimum=-1.0, maximum=20.0, step=0.1, value=-1.0, elem_id="video_scheduler_shift")
|
||||
dynamic_shift = gr.Checkbox(label='Dynamic shift', value=False, elem_id="video_dynamic_shift")
|
||||
audio = gr.Checkbox(label='Audio', value=True, elem_id="video_audio")
|
||||
with gr.Row():
|
||||
guidance_scale = gr.Slider(label='Guidance scale', minimum=-1.0, maximum=14.0, step=0.1, value=-1.0, elem_id="video_guidance_scale")
|
||||
guidance_true = gr.Slider(label='True guidance', minimum=-1.0, maximum=14.0, step=0.1, value=-1.0, elem_id="video_guidance_true")
|
||||
with gr.Accordion(open=False, label="Size", elem_id='video_size_accordion'):
|
||||
with gr.Accordion(open=False, label="Parameters", elem_id='video_params_accordion'):
|
||||
with gr.Row():
|
||||
width, height = ui_sections.create_resolution_inputs('video', default_width=1024, default_height=576, step=16)
|
||||
with gr.Row():
|
||||
@@ -105,6 +108,16 @@ def create_ui(prompt, negative, styles, overrides, script_inputs, mp4_fps, mp4_i
|
||||
random_seed = ToolButton(ui_symbols.random, elem_id="video_seed_random")
|
||||
reuse_seed = ToolButton(ui_symbols.reuse, elem_id="video_seed_reuse")
|
||||
random_seed.click(fn=lambda: -1, show_progress='hidden', inputs=[], outputs=[seed])
|
||||
with gr.Row():
|
||||
audio = gr.Checkbox(label='Audio Enabled', value=True, elem_id="video_audio")
|
||||
with gr.Accordion(open=False, label="Advanced", elem_id='video_advanced_accordion'):
|
||||
steps, sampler_index = ui_sections.create_sampler_and_steps_selection(None, "video", default_steps=30)
|
||||
with gr.Row():
|
||||
sampler_shift = gr.Slider(label='Sampler shift', minimum=-1.0, maximum=20.0, step=0.1, value=-1.0, elem_id="video_scheduler_shift")
|
||||
dynamic_shift = gr.Checkbox(label='Dynamic shift', value=False, elem_id="video_dynamic_shift")
|
||||
with gr.Row():
|
||||
guidance_scale = gr.Slider(label='Guidance scale', minimum=-1.0, maximum=14.0, step=0.1, value=-1.0, elem_id="video_guidance_scale")
|
||||
guidance_true = gr.Slider(label='True guidance', minimum=-1.0, maximum=14.0, step=0.1, value=-1.0, elem_id="video_guidance_true")
|
||||
with gr.Accordion(open=False, label="Inputs", elem_id='video_inputs_accordion'):
|
||||
init_strength = gr.Slider(label='Init strength', minimum=0.0, maximum=1.0, step=0.01, value=0.8, elem_id="video_denoising_strength")
|
||||
gr.HTML("<br>  Init image")
|
||||
|
||||
@@ -6,6 +6,7 @@ from PIL import Image
|
||||
from installer import install
|
||||
from modules import shared, sd_models, timer, errors, devices
|
||||
from modules.logger import log
|
||||
from modules.video_models.video_codecs import codecs_config
|
||||
|
||||
|
||||
debug = log.trace if os.environ.get('SD_VIDEO_DEBUG', None) is not None else lambda *args, **kwargs: None
|
||||
@@ -64,7 +65,12 @@ def get_codecs():
|
||||
if av is None:
|
||||
return []
|
||||
codecs = []
|
||||
practical_codecs = codecs_config.keys()
|
||||
rejected = 0
|
||||
for codec in av.codecs_available:
|
||||
if codec not in practical_codecs:
|
||||
rejected += 1
|
||||
continue
|
||||
try:
|
||||
c = av.Codec(codec, mode='w')
|
||||
if c.type == 'video' and c.is_encoder and len(c.video_formats) > 0:
|
||||
@@ -74,11 +80,13 @@ def get_codecs():
|
||||
pass
|
||||
hw_codecs = [c for c in codecs if (c.capabilities & 0x40000 > 0) or (c.capabilities & 0x80000 > 0)]
|
||||
sw_codecs = [c for c in codecs if c not in hw_codecs]
|
||||
log.debug(f'Video codecs: hardware={len(hw_codecs)} software={len(sw_codecs)}')
|
||||
# for c in hw_codecs:
|
||||
# log.trace(f'codec={c.name} cname="{c.canonical_name}" decs="{c.long_name}" intra={c.intra_only} lossy={c.lossy} lossless={c.lossless} capabilities={c.capabilities} hw=True')
|
||||
# for c in sw_codecs:
|
||||
# log.trace(f'codec={c.name} cname="{c.canonical_name}" decs="{c.long_name}" intra={c.intra_only} lossy={c.lossy} lossless={c.lossless} capabilities={c.capabilities} hw=False')
|
||||
log.debug(f'Video codecs enum: hardware={len(hw_codecs)} software={len(sw_codecs)} rejected={rejected}')
|
||||
"""
|
||||
for c in hw_codecs:
|
||||
log.trace(f'codec={c.name} cname="{c.canonical_name}" decs="{c.long_name}" intra={c.intra_only} lossy={c.lossy} lossless={c.lossless} capabilities={c.capabilities} hw=True')
|
||||
for c in sw_codecs:
|
||||
log.trace(f'codec={c.name} cname="{c.canonical_name}" decs="{c.long_name}" intra={c.intra_only} lossy={c.lossy} lossless={c.lossless} capabilities={c.capabilities} hw=False')
|
||||
"""
|
||||
return ['none'] + [c.name for c in hw_codecs + sw_codecs]
|
||||
|
||||
|
||||
|
||||
Vendored
+36
-5
@@ -16795,15 +16795,43 @@ function pairOf(arEl) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function getNearestAspectRatio(width, height, maxPixelTolerance = 8) {
|
||||
const STANDARD_RATIOS = [
|
||||
{ label: "1:1", ratio: 1 / 1 },
|
||||
{ label: "4:3", ratio: 4 / 3 },
|
||||
{ label: "3:2", ratio: 3 / 2 },
|
||||
{ label: "16:9", ratio: 16 / 9 },
|
||||
{ label: "16:10", ratio: 16 / 10 },
|
||||
{ label: "21:9", ratio: 64 / 27 },
|
||||
// standard cinematic 21:9 ratio is actually 64/27
|
||||
{ label: "2:3", ratio: 2 / 3 },
|
||||
{ label: "3:4", ratio: 3 / 4 },
|
||||
{ label: "9:16", ratio: 9 / 16 },
|
||||
{ label: "10:16", ratio: 10 / 16 },
|
||||
{ label: "9:21", ratio: 27 / 64 }
|
||||
];
|
||||
const targetRatio = width / height;
|
||||
const closest = STANDARD_RATIOS.reduce((prev, curr) => Math.abs(curr.ratio - targetRatio) < Math.abs(prev.ratio - targetRatio) ? curr : prev);
|
||||
const expectedWidth = height * closest.ratio;
|
||||
const expectedHeight = width / closest.ratio;
|
||||
const widthDiff = Math.abs(width - expectedWidth);
|
||||
const heightDiff = Math.abs(height - expectedHeight);
|
||||
if (widthDiff <= maxPixelTolerance || heightDiff <= maxPixelTolerance) return closest.label;
|
||||
const ratio = (width / height).toFixed(2);
|
||||
return `${ratio}:1`;
|
||||
}
|
||||
function settle(arEl, source) {
|
||||
const ar = parseAR(arValue(arEl));
|
||||
if (!ar) return;
|
||||
const pair = pairOf(arEl);
|
||||
if (!pair) return;
|
||||
const [rw, rh] = ar;
|
||||
busy.add(arEl);
|
||||
if (source === "height") writeValue(pair.width, readValue(pair.height) * rw / rh);
|
||||
else writeValue(pair.height, readValue(pair.width) * rh / rw);
|
||||
if (ar) {
|
||||
const [rw, rh] = ar;
|
||||
busy.add(arEl);
|
||||
if (source === "height") writeValue(pair.width, readValue(pair.height) * rw / rh);
|
||||
else writeValue(pair.height, readValue(pair.width) * rh / rw);
|
||||
}
|
||||
const span = arEl.querySelector("span");
|
||||
if (span) span.innerText = getNearestAspectRatio(readValue(pair.width), readValue(pair.height));
|
||||
busy.delete(arEl);
|
||||
}
|
||||
function schedule(arEl, source, delay2) {
|
||||
@@ -16830,6 +16858,9 @@ function setupResolutionLock() {
|
||||
if (!pair) return;
|
||||
bind(arEl, pair.width, "width");
|
||||
bind(arEl, pair.height, "height");
|
||||
arEl.querySelectorAll("span").forEach((el2) => {
|
||||
if (el2) el2.innerText = "";
|
||||
});
|
||||
arEl.querySelectorAll("input").forEach((el2) => {
|
||||
if (!(el2 instanceof HTMLInputElement) || el2.classList.contains("ar-lock-bound")) return;
|
||||
el2.classList.add("ar-lock-bound");
|
||||
|
||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
+36
-5
@@ -63,15 +63,43 @@ function pairOf(arEl: Element): { width: Element; height: Element } | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
function getNearestAspectRatio(width: number, height: number, maxPixelTolerance = 8): string | null {
|
||||
const STANDARD_RATIOS = [
|
||||
{ label: '1:1', ratio: 1 / 1 },
|
||||
{ label: '4:3', ratio: 4 / 3 },
|
||||
{ label: '3:2', ratio: 3 / 2 },
|
||||
{ label: '16:9', ratio: 16 / 9 },
|
||||
{ label: '16:10', ratio: 16 / 10 },
|
||||
{ label: '21:9', ratio: 64 / 27 }, // standard cinematic 21:9 ratio is actually 64/27
|
||||
{ label: '2:3', ratio: 2 / 3 },
|
||||
{ label: '3:4', ratio: 3 / 4 },
|
||||
{ label: '9:16', ratio: 9 / 16 },
|
||||
{ label: '10:16', ratio: 10 / 16 },
|
||||
{ label: '9:21', ratio: 27 / 64 },
|
||||
];
|
||||
const targetRatio = width / height;
|
||||
const closest = STANDARD_RATIOS.reduce((prev, curr) => (Math.abs(curr.ratio - targetRatio) < Math.abs(prev.ratio - targetRatio) ? curr : prev));
|
||||
const expectedWidth = height * closest.ratio;
|
||||
const expectedHeight = width / closest.ratio;
|
||||
const widthDiff = Math.abs(width - expectedWidth);
|
||||
const heightDiff = Math.abs(height - expectedHeight);
|
||||
if (widthDiff <= maxPixelTolerance || heightDiff <= maxPixelTolerance) return closest.label;
|
||||
const ratio = (width / height).toFixed(2);
|
||||
return `${ratio}:1`;
|
||||
}
|
||||
|
||||
function settle(arEl: Element, source: 'width' | 'height'): void {
|
||||
const ar = parseAR(arValue(arEl));
|
||||
if (!ar) return;
|
||||
const pair = pairOf(arEl);
|
||||
if (!pair) return;
|
||||
const [rw, rh] = ar;
|
||||
busy.add(arEl);
|
||||
if (source === 'height') writeValue(pair.width, (readValue(pair.height) * rw) / rh);
|
||||
else writeValue(pair.height, (readValue(pair.width) * rh) / rw);
|
||||
if (ar) { // ar is set
|
||||
const [rw, rh] = ar;
|
||||
busy.add(arEl);
|
||||
if (source === 'height') writeValue(pair.width, (readValue(pair.height) * rw) / rh);
|
||||
else writeValue(pair.height, (readValue(pair.width) * rh) / rw);
|
||||
}
|
||||
const span = arEl.querySelector('span');
|
||||
if (span) span.innerText = getNearestAspectRatio(readValue(pair.width), readValue(pair.height));
|
||||
busy.delete(arEl);
|
||||
}
|
||||
|
||||
@@ -102,6 +130,9 @@ export function setupResolutionLock(): void {
|
||||
if (!pair) return;
|
||||
bind(arEl, pair.width, 'width');
|
||||
bind(arEl, pair.height, 'height');
|
||||
arEl.querySelectorAll('span').forEach((el) => {
|
||||
if (el) el.innerText = '';
|
||||
});
|
||||
arEl.querySelectorAll('input').forEach((el) => {
|
||||
if (!(el instanceof HTMLInputElement) || el.classList.contains('ar-lock-bound')) return;
|
||||
el.classList.add('ar-lock-bound');
|
||||
|
||||
Reference in New Issue
Block a user