videopython 0.53.0__tar.gz → 0.54.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {videopython-0.53.0 → videopython-0.54.0}/PKG-INFO +3 -2
- {videopython-0.53.0 → videopython-0.54.0}/pyproject.toml +9 -4
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/_revisions.py +8 -5
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/generation/__init__.py +1 -1
- videopython-0.54.0/src/videopython/ai/generation/image.py +93 -0
- videopython-0.54.0/src/videopython/ai/generation/video.py +207 -0
- videopython-0.53.0/src/videopython/ai/generation/image.py +0 -59
- videopython-0.53.0/src/videopython/ai/generation/video.py +0 -131
- {videopython-0.53.0 → videopython-0.54.0}/.gitignore +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/LICENSE +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/README.md +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/__init__.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/__init__.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/_device.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/_ollama.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/_optional.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/_predictor.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/auto_edit/__init__.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/auto_edit/backend.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/auto_edit/catalog.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/auto_edit/editor.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/auto_edit/local.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/auto_edit/models.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/auto_edit/resolve.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/dubbing/__init__.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/dubbing/_tts_backend.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/dubbing/audio_ops.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/dubbing/config.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/dubbing/dubber.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/dubbing/models.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/dubbing/pipeline.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/dubbing/quality.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/dubbing/remux.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/dubbing/separation.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/dubbing/timing.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/dubbing/translation.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/dubbing/voice_sample.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/effects.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/errors.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/generation/audio.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/keyframe.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/ops.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/transforms.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/understanding/__init__.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/understanding/_detector.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/understanding/audio.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/understanding/classification.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/understanding/faces.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/understanding/image.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/understanding/objects.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/understanding/temporal.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/video_analysis/__init__.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/video_analysis/analyzer.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/video_analysis/detectors.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/video_analysis/models.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/video_analysis/sampling.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/video_analysis/source_metadata.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/audio/__init__.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/audio/analysis.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/audio/audio.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/base/__init__.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/base/_dimensions.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/base/_ffmpeg.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/base/_video_io.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/base/description.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/base/draw_detections.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/base/exceptions.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/base/fonts/Anton-OFL.txt +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/base/fonts/Anton-Regular.ttf +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/base/fonts/BebasNeue-OFL.txt +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/base/fonts/BebasNeue-Regular.ttf +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/base/fonts/DejaVuSans.ttf +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/base/fonts/LICENSE_DEJAVU +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/base/fonts/Lato-Bold.ttf +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/base/fonts/Lato-OFL.txt +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/base/fonts/Poppins-Bold.ttf +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/base/fonts/Poppins-OFL.txt +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/base/fonts/__init__.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/base/transcription.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/base/video.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/editing/__init__.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/editing/_ass.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/editing/_easing.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/editing/_schema.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/editing/audio_ops.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/editing/effects.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/editing/operation.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/editing/streaming.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/editing/transcription_overlay.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/editing/transforms.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/editing/video_edit.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/mcp/__init__.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/mcp/server.py +0 -0
- {videopython-0.53.0 → videopython-0.54.0}/src/videopython/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: videopython
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.54.0
|
|
4
4
|
Summary: Minimal video generation and processing library.
|
|
5
5
|
Project-URL: Homepage, https://videopython.com
|
|
6
6
|
Project-URL: Repository, https://github.com/bartwojtowicz/videopython/
|
|
@@ -26,7 +26,8 @@ Provides-Extra: ai
|
|
|
26
26
|
Requires-Dist: accelerate>=0.29.2; extra == 'ai'
|
|
27
27
|
Requires-Dist: chatterbox-tts>=0.1.7; extra == 'ai'
|
|
28
28
|
Requires-Dist: demucs>=4.0.0; extra == 'ai'
|
|
29
|
-
Requires-Dist: diffusers>=0.
|
|
29
|
+
Requires-Dist: diffusers>=0.35.0; extra == 'ai'
|
|
30
|
+
Requires-Dist: ftfy>=6.1; extra == 'ai'
|
|
30
31
|
Requires-Dist: imagehash>=4.3; extra == 'ai'
|
|
31
32
|
Requires-Dist: ollama>=0.5.0; extra == 'ai'
|
|
32
33
|
Requires-Dist: openai-whisper>=20240930; extra == 'ai'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "videopython"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.54.0"
|
|
4
4
|
description = "Minimal video generation and processing library."
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Bartosz Wójtowicz", email = "bartoszwojtowicz@outlook.com" },
|
|
@@ -78,8 +78,13 @@ ai = [
|
|
|
78
78
|
# Voice cloning TTS (generation/audio.py — Chatterbox); its strict pins drive
|
|
79
79
|
# the [tool.uv].override-dependencies block below.
|
|
80
80
|
"chatterbox-tts>=0.1.7",
|
|
81
|
-
# Local media generation:
|
|
82
|
-
|
|
81
|
+
# Local media generation: Qwen-Image/Wan2.2 + MusicGen (generation/*).
|
|
82
|
+
# >=0.35 is the floor that ships QwenImagePipeline + Wan2.2 support (tested on 0.37.1).
|
|
83
|
+
"diffusers>=0.35.0",
|
|
84
|
+
# Wan2.2 i2v pipeline cleans prompts with ftfy via a hard import (diffusers'
|
|
85
|
+
# pipeline_wan_i2v); the t2v pipeline guards it with is_ftfy_available(), i2v
|
|
86
|
+
# does not, so it is a required dep for ImageToVideo.
|
|
87
|
+
"ftfy>=6.1",
|
|
83
88
|
"accelerate>=0.29.2",
|
|
84
89
|
# Dubbing loudness matching (dubbing/loudness.py)
|
|
85
90
|
"pyloudnorm>=0.1.1",
|
|
@@ -136,7 +141,7 @@ ignore_missing_imports = true
|
|
|
136
141
|
# These overrides let the combined [ai] resolve pick compatible versions; the
|
|
137
142
|
# floors in [ai] are aligned with them so pip and uv resolve similar versions.
|
|
138
143
|
override-dependencies = [
|
|
139
|
-
"torch>=2.8.0", "torchaudio>=2.8.0", "torchvision>=0.23.0,<0.24.0", "numpy>=2.0.0", "diffusers>=0.
|
|
144
|
+
"torch>=2.8.0", "torchaudio>=2.8.0", "torchvision>=0.23.0,<0.24.0", "numpy>=2.0.0", "diffusers>=0.35.0",
|
|
140
145
|
# Some transitive deps pull opencv-python, which conflicts with our
|
|
141
146
|
# opencv-python-headless (both provide cv2). Exclude opencv-python so
|
|
142
147
|
# only the headless variant is installed.
|
|
@@ -71,11 +71,14 @@ MODEL_REVISIONS: dict[str, str] = {
|
|
|
71
71
|
"opencv/face_detection_yunet": "3cc26e7f1014a5ee5d74a42acee58bafc9d0a310",
|
|
72
72
|
# MusicGen (ai/generation/audio.py: TextToMusic)
|
|
73
73
|
"facebook/musicgen-small": "4c8334b02c6ec4e8664a91979669a501ec497792",
|
|
74
|
-
#
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
74
|
+
# Text-to-image — Qwen-Image (ai/generation/image.py: TextToImage), Apache-2.0;
|
|
75
|
+
# replaced the OpenRAIL++-licensed SDXL.
|
|
76
|
+
"Qwen/Qwen-Image-2512": "25468b98e3276ca6700de15c6628e51b7de54a26",
|
|
77
|
+
# Text/image-to-video — Wan2.2 (ai/generation/video.py: TextToVideo / ImageToVideo),
|
|
78
|
+
# Apache-2.0; replaced the custom-licensed CogVideoX. The fp32 VAE is loaded from the
|
|
79
|
+
# same repo (subfolder="vae"), so revision=pinned(repo) covers both from_pretrained calls.
|
|
80
|
+
"Wan-AI/Wan2.2-T2V-A14B-Diffusers": "5be7df9619b54f4e2667b2755bc6a756675b5cd7",
|
|
81
|
+
"Wan-AI/Wan2.2-I2V-A14B-Diffusers": "596658fd9ca6b7b71d5057529bbf319ecbc61d74",
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Each symbol's backing leaf module is imported only on first access, so
|
|
4
4
|
``from videopython.ai.generation import TextToImage`` does not pull in
|
|
5
|
-
``audio`` (chatterbox/musicgen) or ``video`` (diffusers/
|
|
5
|
+
``audio`` (chatterbox/musicgen) or ``video`` (diffusers/Wan2.2). The
|
|
6
6
|
``TYPE_CHECKING`` block keeps the symbols visible to mypy and IDEs.
|
|
7
7
|
"""
|
|
8
8
|
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Image generation using local diffusion models (Qwen-Image)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from PIL import Image
|
|
8
|
+
|
|
9
|
+
from videopython.ai._device import log_device_initialization, select_device
|
|
10
|
+
from videopython.ai._predictor import ManagedPredictor
|
|
11
|
+
from videopython.ai._revisions import pinned
|
|
12
|
+
|
|
13
|
+
_MODEL_NAME = "Qwen/Qwen-Image-2512"
|
|
14
|
+
|
|
15
|
+
# Qwen-Image recommends appending a quality "magic" suffix to the prompt
|
|
16
|
+
# (verbatim from the model card).
|
|
17
|
+
_POSITIVE_MAGIC = ", Ultra HD, 4K, cinematic composition."
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TextToImage(ManagedPredictor):
|
|
21
|
+
"""Generates images from text descriptions using local models (Qwen-Image, Apache-2.0)."""
|
|
22
|
+
|
|
23
|
+
_model_attrs = ("_pipeline",)
|
|
24
|
+
|
|
25
|
+
def __init__(self, device: str | None = None):
|
|
26
|
+
self.device = device
|
|
27
|
+
self._pipeline: Any = None
|
|
28
|
+
|
|
29
|
+
def _init_local(self) -> None:
|
|
30
|
+
"""Initialize the local Qwen-Image diffusion pipeline (CUDA-only)."""
|
|
31
|
+
import torch
|
|
32
|
+
|
|
33
|
+
from videopython.ai._optional import require
|
|
34
|
+
|
|
35
|
+
requested_device = self.device
|
|
36
|
+
device = select_device(self.device, mps_allowed=False)
|
|
37
|
+
if device != "cuda":
|
|
38
|
+
raise RuntimeError("TextToImage requires a CUDA GPU; Qwen-Image (~20B) is impractical on CPU/MPS.")
|
|
39
|
+
|
|
40
|
+
QwenImagePipeline = require("diffusers", feature="TextToImage").QwenImagePipeline
|
|
41
|
+
self._pipeline = QwenImagePipeline.from_pretrained(
|
|
42
|
+
_MODEL_NAME,
|
|
43
|
+
revision=pinned(_MODEL_NAME),
|
|
44
|
+
torch_dtype=torch.bfloat16,
|
|
45
|
+
use_safetensors=True,
|
|
46
|
+
)
|
|
47
|
+
# ~20B params (Qwen2.5-VL text encoder + transformer + VAE). Offload submodules
|
|
48
|
+
# to the GPU on demand so it fits a single GPU; offload manages device placement,
|
|
49
|
+
# so we must NOT also call .to("cuda").
|
|
50
|
+
self._pipeline.enable_model_cpu_offload()
|
|
51
|
+
self._pipeline.enable_vae_tiling()
|
|
52
|
+
|
|
53
|
+
self.device = device
|
|
54
|
+
log_device_initialization(
|
|
55
|
+
"TextToImage",
|
|
56
|
+
requested_device=requested_device,
|
|
57
|
+
resolved_device=device,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def generate_image(
|
|
61
|
+
self,
|
|
62
|
+
prompt: str,
|
|
63
|
+
*,
|
|
64
|
+
negative_prompt: str = " ",
|
|
65
|
+
true_cfg_scale: float = 4.0,
|
|
66
|
+
num_inference_steps: int = 50,
|
|
67
|
+
width: int = 1328,
|
|
68
|
+
height: int = 1328,
|
|
69
|
+
add_magic: bool = True,
|
|
70
|
+
seed: int = 42,
|
|
71
|
+
) -> Image.Image:
|
|
72
|
+
"""Generate an image from a text prompt.
|
|
73
|
+
|
|
74
|
+
Qwen-Image uses ``true_cfg_scale`` (not ``guidance_scale``) for
|
|
75
|
+
classifier-free guidance; a non-empty ``negative_prompt`` (default a single
|
|
76
|
+
space) is required to enable it. ``add_magic`` appends the model's
|
|
77
|
+
recommended quality suffix to ``prompt``.
|
|
78
|
+
"""
|
|
79
|
+
import torch
|
|
80
|
+
|
|
81
|
+
if self._pipeline is None:
|
|
82
|
+
self._init_local()
|
|
83
|
+
|
|
84
|
+
full_prompt = prompt + _POSITIVE_MAGIC if add_magic else prompt
|
|
85
|
+
return self._pipeline(
|
|
86
|
+
prompt=full_prompt,
|
|
87
|
+
negative_prompt=negative_prompt,
|
|
88
|
+
true_cfg_scale=true_cfg_scale,
|
|
89
|
+
num_inference_steps=num_inference_steps,
|
|
90
|
+
width=width,
|
|
91
|
+
height=height,
|
|
92
|
+
generator=torch.Generator(device=self.device).manual_seed(seed),
|
|
93
|
+
).images[0]
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""Video generation using local diffusion models (Wan2.2)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from videopython.ai._device import log_device_initialization, select_device
|
|
10
|
+
from videopython.ai._predictor import ManagedPredictor
|
|
11
|
+
from videopython.ai._revisions import pinned
|
|
12
|
+
from videopython.base.video import Video
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from PIL.Image import Image
|
|
16
|
+
|
|
17
|
+
# Canonical Wan2.2 negative prompt (from the diffusers WanPipeline docstring example).
|
|
18
|
+
_WAN_NEGATIVE_PROMPT = (
|
|
19
|
+
"Bright tones, overexposed, static, blurred details, subtitles, style, works, "
|
|
20
|
+
"paintings, images, static, overall gray, worst quality, low quality, JPEG "
|
|
21
|
+
"compression residue, ugly, incomplete, extra fingers, poorly drawn hands, "
|
|
22
|
+
"poorly drawn faces, deformed, disfigured, misshapen limbs, fused fingers, "
|
|
23
|
+
"still picture, messy background, three legs, many people in the background, "
|
|
24
|
+
"walking backwards"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _retie_umt5_embeddings(pipeline: Any) -> None:
|
|
29
|
+
"""Restore Wan's text conditioning under transformers>=5.2.
|
|
30
|
+
|
|
31
|
+
Wan's checkpoint ships the UMT5 token embedding only as ``shared.weight``, but
|
|
32
|
+
transformers 5.2's ``UMT5EncoderModel`` has ``tie_word_embeddings=False`` and reads a
|
|
33
|
+
*separate* ``encoder.embed_tokens`` that the checkpoint never populates -- so it stays
|
|
34
|
+
zero-initialized. The encoder then emits an all-zero embedding for every prompt and
|
|
35
|
+
generation silently ignores the text (verified on GPU: prompt has no effect). Point the
|
|
36
|
+
embedding lookup back at the loaded ``shared`` weight to make conditioning live again.
|
|
37
|
+
"""
|
|
38
|
+
text_encoder = pipeline.text_encoder
|
|
39
|
+
text_encoder.encoder.embed_tokens = text_encoder.shared
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _require_cuda_device(device: str | None, component: str) -> str:
|
|
43
|
+
"""Resolve to a CUDA device or raise.
|
|
44
|
+
|
|
45
|
+
Wan2.2-A14B (~28B MoE) is too large to run on CPU/MPS, so generation requires
|
|
46
|
+
a CUDA GPU rather than silently falling back to an unusable device.
|
|
47
|
+
"""
|
|
48
|
+
selected_device = select_device(device, mps_allowed=False)
|
|
49
|
+
if selected_device != "cuda":
|
|
50
|
+
raise RuntimeError(f"{component} requires a CUDA GPU; Wan2.2-A14B (~28B) is impractical on CPU/MPS.")
|
|
51
|
+
return selected_device
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class TextToVideo(ManagedPredictor):
|
|
55
|
+
"""Generates videos from text descriptions using Wan2.2-T2V (Apache-2.0)."""
|
|
56
|
+
|
|
57
|
+
_model_attrs = ("_pipeline",)
|
|
58
|
+
|
|
59
|
+
def __init__(self, device: str | None = None):
|
|
60
|
+
self.device = device
|
|
61
|
+
self._pipeline: Any = None
|
|
62
|
+
|
|
63
|
+
def _init_local(self) -> None:
|
|
64
|
+
import torch
|
|
65
|
+
|
|
66
|
+
from videopython.ai._optional import require
|
|
67
|
+
|
|
68
|
+
diffusers = require("diffusers", feature="TextToVideo")
|
|
69
|
+
WanPipeline = diffusers.WanPipeline
|
|
70
|
+
AutoencoderKLWan = diffusers.AutoencoderKLWan
|
|
71
|
+
|
|
72
|
+
requested_device = self.device
|
|
73
|
+
device = _require_cuda_device(self.device, "TextToVideo")
|
|
74
|
+
|
|
75
|
+
model_name = "Wan-AI/Wan2.2-T2V-A14B-Diffusers"
|
|
76
|
+
revision = pinned(model_name)
|
|
77
|
+
# VAE stays float32 for decode quality; the transformer/pipeline use bf16.
|
|
78
|
+
vae = AutoencoderKLWan.from_pretrained(
|
|
79
|
+
model_name, subfolder="vae", revision=revision, torch_dtype=torch.float32
|
|
80
|
+
)
|
|
81
|
+
self._pipeline = WanPipeline.from_pretrained(model_name, vae=vae, revision=revision, torch_dtype=torch.bfloat16)
|
|
82
|
+
_retie_umt5_embeddings(self._pipeline)
|
|
83
|
+
# A14B is a MoE (high+low-noise experts), too large for a single-GPU .to("cuda");
|
|
84
|
+
# offload submodules on demand (offload manages placement, so do NOT also call .to).
|
|
85
|
+
self._pipeline.enable_model_cpu_offload()
|
|
86
|
+
|
|
87
|
+
self.device = device
|
|
88
|
+
log_device_initialization(
|
|
89
|
+
"TextToVideo",
|
|
90
|
+
requested_device=requested_device,
|
|
91
|
+
resolved_device=device,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def generate_video(
|
|
95
|
+
self,
|
|
96
|
+
prompt: str,
|
|
97
|
+
num_steps: int = 40,
|
|
98
|
+
num_frames: int = 81,
|
|
99
|
+
guidance_scale: float = 4.0,
|
|
100
|
+
) -> Video:
|
|
101
|
+
"""Generate video from text prompt."""
|
|
102
|
+
import torch
|
|
103
|
+
|
|
104
|
+
if self._pipeline is None:
|
|
105
|
+
self._init_local()
|
|
106
|
+
|
|
107
|
+
# output_type="pil" is required: the "np" default returns float32 in [0, 1],
|
|
108
|
+
# which the uint8 cast below would floor to an all-black video.
|
|
109
|
+
video_frames = self._pipeline(
|
|
110
|
+
prompt=prompt,
|
|
111
|
+
negative_prompt=_WAN_NEGATIVE_PROMPT,
|
|
112
|
+
height=720,
|
|
113
|
+
width=1280,
|
|
114
|
+
num_frames=num_frames,
|
|
115
|
+
num_inference_steps=num_steps,
|
|
116
|
+
guidance_scale=guidance_scale,
|
|
117
|
+
guidance_scale_2=3.0, # low-noise MoE expert; None would reuse guidance_scale
|
|
118
|
+
output_type="pil",
|
|
119
|
+
generator=torch.Generator(device=self.device).manual_seed(42),
|
|
120
|
+
).frames[0]
|
|
121
|
+
video_frames = np.asarray(video_frames, dtype=np.uint8)
|
|
122
|
+
return Video.from_frames(video_frames, fps=16.0)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class ImageToVideo(ManagedPredictor):
|
|
126
|
+
"""Generates videos from static images using Wan2.2-I2V (Apache-2.0)."""
|
|
127
|
+
|
|
128
|
+
_model_attrs = ("_pipeline",)
|
|
129
|
+
|
|
130
|
+
def __init__(self, device: str | None = None):
|
|
131
|
+
self.device = device
|
|
132
|
+
self._pipeline: Any = None
|
|
133
|
+
|
|
134
|
+
def _init_local(self) -> None:
|
|
135
|
+
import torch
|
|
136
|
+
|
|
137
|
+
from videopython.ai._optional import require
|
|
138
|
+
|
|
139
|
+
diffusers = require("diffusers", feature="ImageToVideo")
|
|
140
|
+
WanImageToVideoPipeline = diffusers.WanImageToVideoPipeline
|
|
141
|
+
AutoencoderKLWan = diffusers.AutoencoderKLWan
|
|
142
|
+
|
|
143
|
+
requested_device = self.device
|
|
144
|
+
device = _require_cuda_device(self.device, "ImageToVideo")
|
|
145
|
+
|
|
146
|
+
model_name = "Wan-AI/Wan2.2-I2V-A14B-Diffusers"
|
|
147
|
+
revision = pinned(model_name)
|
|
148
|
+
vae = AutoencoderKLWan.from_pretrained(
|
|
149
|
+
model_name, subfolder="vae", revision=revision, torch_dtype=torch.float32
|
|
150
|
+
)
|
|
151
|
+
self._pipeline = WanImageToVideoPipeline.from_pretrained(
|
|
152
|
+
model_name, vae=vae, revision=revision, torch_dtype=torch.bfloat16
|
|
153
|
+
)
|
|
154
|
+
_retie_umt5_embeddings(self._pipeline)
|
|
155
|
+
# A14B MoE is too large for a single-GPU .to("cuda"); offload submodules on demand.
|
|
156
|
+
self._pipeline.enable_model_cpu_offload()
|
|
157
|
+
|
|
158
|
+
self.device = device
|
|
159
|
+
log_device_initialization(
|
|
160
|
+
"ImageToVideo",
|
|
161
|
+
requested_device=requested_device,
|
|
162
|
+
resolved_device=device,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def _resize_to_model_grid(self, image: Image) -> tuple[Image, int, int]:
|
|
166
|
+
"""Resize ``image`` to Wan's area budget, snapped to the model's spatial grid.
|
|
167
|
+
|
|
168
|
+
Returns the resized image plus the ``(height, width)`` to request, derived
|
|
169
|
+
from the input aspect ratio against Wan's 480x832 area (per the model card).
|
|
170
|
+
"""
|
|
171
|
+
max_area = 480 * 832
|
|
172
|
+
aspect_ratio = image.height / image.width
|
|
173
|
+
mod_value = self._pipeline.vae_scale_factor_spatial * self._pipeline.transformer.config.patch_size[1]
|
|
174
|
+
height = round(np.sqrt(max_area * aspect_ratio)) // mod_value * mod_value
|
|
175
|
+
width = round(np.sqrt(max_area / aspect_ratio)) // mod_value * mod_value
|
|
176
|
+
return image.resize((width, height)), height, width
|
|
177
|
+
|
|
178
|
+
def generate_video(
|
|
179
|
+
self,
|
|
180
|
+
image: Image,
|
|
181
|
+
prompt: str = "",
|
|
182
|
+
num_steps: int = 40,
|
|
183
|
+
num_frames: int = 81,
|
|
184
|
+
guidance_scale: float = 3.5,
|
|
185
|
+
) -> Video:
|
|
186
|
+
"""Generate video animation from a static image."""
|
|
187
|
+
import torch
|
|
188
|
+
|
|
189
|
+
if self._pipeline is None:
|
|
190
|
+
self._init_local()
|
|
191
|
+
|
|
192
|
+
image, height, width = self._resize_to_model_grid(image)
|
|
193
|
+
video_frames = self._pipeline(
|
|
194
|
+
image=image,
|
|
195
|
+
prompt=prompt,
|
|
196
|
+
negative_prompt=_WAN_NEGATIVE_PROMPT,
|
|
197
|
+
height=height,
|
|
198
|
+
width=width,
|
|
199
|
+
num_frames=num_frames,
|
|
200
|
+
num_inference_steps=num_steps,
|
|
201
|
+
guidance_scale=guidance_scale,
|
|
202
|
+
# No guidance_scale_2: the low-noise MoE expert reuses guidance_scale (its None default).
|
|
203
|
+
output_type="pil",
|
|
204
|
+
generator=torch.Generator(device=self.device).manual_seed(42),
|
|
205
|
+
).frames[0]
|
|
206
|
+
video_frames = np.asarray(video_frames, dtype=np.uint8)
|
|
207
|
+
return Video.from_frames(video_frames, fps=16.0)
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
"""Image generation using local diffusion models."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import Any
|
|
6
|
-
|
|
7
|
-
from PIL import Image
|
|
8
|
-
|
|
9
|
-
from videopython.ai._device import log_device_initialization, select_device
|
|
10
|
-
from videopython.ai._predictor import ManagedPredictor
|
|
11
|
-
from videopython.ai._revisions import pinned
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class TextToImage(ManagedPredictor):
|
|
15
|
-
"""Generates images from text descriptions using local models."""
|
|
16
|
-
|
|
17
|
-
_model_attrs = ("_pipeline",)
|
|
18
|
-
|
|
19
|
-
def __init__(self, device: str | None = None):
|
|
20
|
-
self.device = device
|
|
21
|
-
self._pipeline: Any = None
|
|
22
|
-
|
|
23
|
-
def _init_local(self) -> None:
|
|
24
|
-
"""Initialize local diffusion pipeline."""
|
|
25
|
-
import torch
|
|
26
|
-
|
|
27
|
-
from videopython.ai._optional import require
|
|
28
|
-
|
|
29
|
-
DiffusionPipeline = require("diffusers", feature="TextToImage").DiffusionPipeline
|
|
30
|
-
|
|
31
|
-
requested_device = self.device
|
|
32
|
-
device = select_device(self.device, mps_allowed=True)
|
|
33
|
-
dtype = torch.float16 if device == "cuda" else torch.float32
|
|
34
|
-
variant = "fp16" if device == "cuda" else None
|
|
35
|
-
|
|
36
|
-
model_name = "stabilityai/stable-diffusion-xl-base-1.0"
|
|
37
|
-
self._pipeline = DiffusionPipeline.from_pretrained(
|
|
38
|
-
model_name,
|
|
39
|
-
revision=pinned(model_name),
|
|
40
|
-
torch_dtype=dtype,
|
|
41
|
-
variant=variant,
|
|
42
|
-
use_safetensors=True,
|
|
43
|
-
)
|
|
44
|
-
self._pipeline.to(device)
|
|
45
|
-
self.device = device
|
|
46
|
-
log_device_initialization(
|
|
47
|
-
"TextToImage",
|
|
48
|
-
requested_device=requested_device,
|
|
49
|
-
resolved_device=device,
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
if device == "mps":
|
|
53
|
-
self._pipeline.enable_attention_slicing()
|
|
54
|
-
|
|
55
|
-
def generate_image(self, prompt: str) -> Image.Image:
|
|
56
|
-
"""Generate an image from a text prompt."""
|
|
57
|
-
if self._pipeline is None:
|
|
58
|
-
self._init_local()
|
|
59
|
-
return self._pipeline(prompt=prompt).images[0]
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
"""Video generation using local diffusion models."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import TYPE_CHECKING, Any
|
|
6
|
-
|
|
7
|
-
import numpy as np
|
|
8
|
-
|
|
9
|
-
from videopython.ai._device import log_device_initialization, select_device
|
|
10
|
-
from videopython.ai._predictor import ManagedPredictor
|
|
11
|
-
from videopython.ai._revisions import pinned
|
|
12
|
-
from videopython.base.video import Video
|
|
13
|
-
|
|
14
|
-
if TYPE_CHECKING:
|
|
15
|
-
from PIL.Image import Image
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def _get_torch_device_and_dtype(device: str | None) -> tuple[str, Any]:
|
|
19
|
-
"""Get the best available torch device and dtype for CogVideoX."""
|
|
20
|
-
import torch
|
|
21
|
-
|
|
22
|
-
selected_device = select_device(device, mps_allowed=False)
|
|
23
|
-
if selected_device == "cuda":
|
|
24
|
-
return selected_device, torch.bfloat16
|
|
25
|
-
return selected_device, torch.float32
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class TextToVideo(ManagedPredictor):
|
|
29
|
-
"""Generates videos from text descriptions using local diffusion models."""
|
|
30
|
-
|
|
31
|
-
_model_attrs = ("_pipeline",)
|
|
32
|
-
|
|
33
|
-
def __init__(self, device: str | None = None):
|
|
34
|
-
self.device = device
|
|
35
|
-
self._pipeline: Any = None
|
|
36
|
-
|
|
37
|
-
def _init_local(self) -> None:
|
|
38
|
-
from videopython.ai._optional import require
|
|
39
|
-
|
|
40
|
-
CogVideoXPipeline = require("diffusers", feature="TextToVideo").CogVideoXPipeline
|
|
41
|
-
|
|
42
|
-
requested_device = self.device
|
|
43
|
-
device, dtype = _get_torch_device_and_dtype(self.device)
|
|
44
|
-
|
|
45
|
-
model_name = "THUDM/CogVideoX1.5-5B"
|
|
46
|
-
self._pipeline = CogVideoXPipeline.from_pretrained(model_name, revision=pinned(model_name), torch_dtype=dtype)
|
|
47
|
-
self._pipeline.to(device)
|
|
48
|
-
self.device = device
|
|
49
|
-
log_device_initialization(
|
|
50
|
-
"TextToVideo",
|
|
51
|
-
requested_device=requested_device,
|
|
52
|
-
resolved_device=device,
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
def generate_video(
|
|
56
|
-
self,
|
|
57
|
-
prompt: str,
|
|
58
|
-
num_steps: int = 50,
|
|
59
|
-
num_frames: int = 81,
|
|
60
|
-
guidance_scale: float = 6.0,
|
|
61
|
-
) -> Video:
|
|
62
|
-
"""Generate video from text prompt."""
|
|
63
|
-
import torch
|
|
64
|
-
|
|
65
|
-
if self._pipeline is None:
|
|
66
|
-
self._init_local()
|
|
67
|
-
|
|
68
|
-
video_frames = self._pipeline(
|
|
69
|
-
prompt=prompt,
|
|
70
|
-
num_inference_steps=num_steps,
|
|
71
|
-
num_frames=num_frames,
|
|
72
|
-
guidance_scale=guidance_scale,
|
|
73
|
-
generator=torch.Generator(device=self.device).manual_seed(42),
|
|
74
|
-
).frames[0]
|
|
75
|
-
video_frames = np.asarray(video_frames, dtype=np.uint8)
|
|
76
|
-
return Video.from_frames(video_frames, fps=16.0)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
class ImageToVideo(ManagedPredictor):
|
|
80
|
-
"""Generates videos from static images using local video diffusion."""
|
|
81
|
-
|
|
82
|
-
_model_attrs = ("_pipeline",)
|
|
83
|
-
|
|
84
|
-
def __init__(self, device: str | None = None):
|
|
85
|
-
self.device = device
|
|
86
|
-
self._pipeline: Any = None
|
|
87
|
-
|
|
88
|
-
def _init_local(self) -> None:
|
|
89
|
-
from videopython.ai._optional import require
|
|
90
|
-
|
|
91
|
-
CogVideoXImageToVideoPipeline = require("diffusers", feature="ImageToVideo").CogVideoXImageToVideoPipeline
|
|
92
|
-
|
|
93
|
-
requested_device = self.device
|
|
94
|
-
device, dtype = _get_torch_device_and_dtype(self.device)
|
|
95
|
-
|
|
96
|
-
model_name = "THUDM/CogVideoX1.5-5B-I2V"
|
|
97
|
-
self._pipeline = CogVideoXImageToVideoPipeline.from_pretrained(
|
|
98
|
-
model_name, revision=pinned(model_name), torch_dtype=dtype
|
|
99
|
-
)
|
|
100
|
-
self._pipeline.to(device)
|
|
101
|
-
self.device = device
|
|
102
|
-
log_device_initialization(
|
|
103
|
-
"ImageToVideo",
|
|
104
|
-
requested_device=requested_device,
|
|
105
|
-
resolved_device=device,
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
def generate_video(
|
|
109
|
-
self,
|
|
110
|
-
image: Image,
|
|
111
|
-
prompt: str = "",
|
|
112
|
-
num_steps: int = 50,
|
|
113
|
-
num_frames: int = 81,
|
|
114
|
-
guidance_scale: float = 6.0,
|
|
115
|
-
) -> Video:
|
|
116
|
-
"""Generate video animation from a static image."""
|
|
117
|
-
import torch
|
|
118
|
-
|
|
119
|
-
if self._pipeline is None:
|
|
120
|
-
self._init_local()
|
|
121
|
-
|
|
122
|
-
video_frames = self._pipeline(
|
|
123
|
-
prompt=prompt,
|
|
124
|
-
image=image,
|
|
125
|
-
num_inference_steps=num_steps,
|
|
126
|
-
num_frames=num_frames,
|
|
127
|
-
guidance_scale=guidance_scale,
|
|
128
|
-
generator=torch.Generator(device=self.device).manual_seed(42),
|
|
129
|
-
).frames[0]
|
|
130
|
-
video_frames = np.asarray(video_frames, dtype=np.uint8)
|
|
131
|
-
return Video.from_frames(video_frames, fps=16.0)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/understanding/classification.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{videopython-0.53.0 → videopython-0.54.0}/src/videopython/ai/video_analysis/source_metadata.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|