feat(api): add video generation endpoint

Add POST /sdapi/v1/video plus GET /sdapi/v1/video/models and
GET /sdapi/v1/video/file. The generation body is extracted from the
gradio handler into a keyword-only core, video_run.run, which returns a
structured result and raises typed errors; the positional generate
signature is unchanged and now adapts to the core. Omitting engine and
model drives the currently loaded checkpoint when it is video-capable,
which covers models loaded from local folders without a registry entry.

- registry helpers in models_def (find, engines, pipeline_classes,
  workflow_for_class); validate_pipeline reuses the shared class set
- modular pipes stamp their workflow so out-of-registry loads dispatch
  onto the modular branch
- disk switches (mp4_*) and wire switches (send_*) are independent;
  artifacts above the base64 cap fall back to path plus the file route,
  which is jailed to the video output directory and serves video/mp4
  with range support
- always-on video scripts get bootstrapped default args, matching the
  txt2img handler; missing bootstrap raised a TypeError per frame
- checkpoint overrides are rejected with a pointer to the checkpoint
  endpoint; unknown engine, model and sampler names return 404 with the
  valid choices
- cli/api-video.py client, test/test-video-api.py suite and a
  full-test.sh entry; video mimetypes registered; rate-limit cost set
- remove the unreferenced video_ui.run_video dispatcher
This commit is contained in:
CalamitousFelicitousness
2026-08-08 14:19:12 +01:00
parent 815d47f0c1
commit ff42f1631c
13 changed files with 972 additions and 102 deletions
+2 -12
View File
@@ -564,18 +564,8 @@ def update_pipeline(sd_model, p: processing.StableDiffusionProcessing):
def validate_pipeline(p: processing.StableDiffusionProcessing):
from modules.video_models.models_def import models as video_models
models_cls = []
for family in video_models:
for m in video_models[family]:
if m.repo_cls is not None:
if isinstance(m.repo_cls, str):
models_cls.append(m.repo_cls)
else:
models_cls.append(m.repo_cls.__name__)
if m.custom is not None:
models_cls.append(m.custom)
is_video_model = shared.sd_model.__class__.__name__ in models_cls
from modules.video_models import models_def
is_video_model = shared.sd_model.__class__.__name__ in models_def.pipeline_classes()
override_video_pipelines = ['WanPipeline', 'WanImageToVideoPipeline', 'WanVACEPipeline', 'MiniMaxH3ModularPipeline']
is_video_pipeline = ('video' in p.__class__.__name__.lower()) or (shared.sd_model.__class__.__name__ in override_video_pipelines)
if is_video_model and not is_video_pipeline: