videopython 0.53.0__tar.gz → 0.54.1__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.1}/PKG-INFO +7 -4
- {videopython-0.53.0 → videopython-0.54.1}/pyproject.toml +49 -14
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/_revisions.py +8 -5
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/generation/__init__.py +1 -1
- videopython-0.54.1/src/videopython/ai/generation/image.py +93 -0
- videopython-0.54.1/src/videopython/ai/generation/video.py +207 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/understanding/faces.py +1 -1
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/audio/audio.py +5 -9
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/base/video.py +3 -2
- 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.1}/.gitignore +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/LICENSE +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/README.md +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/__init__.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/__init__.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/_device.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/_ollama.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/_optional.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/_predictor.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/auto_edit/__init__.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/auto_edit/backend.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/auto_edit/catalog.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/auto_edit/editor.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/auto_edit/local.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/auto_edit/models.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/auto_edit/resolve.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/dubbing/__init__.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/dubbing/_tts_backend.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/dubbing/audio_ops.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/dubbing/config.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/dubbing/dubber.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/dubbing/models.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/dubbing/pipeline.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/dubbing/quality.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/dubbing/remux.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/dubbing/separation.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/dubbing/timing.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/dubbing/translation.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/dubbing/voice_sample.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/effects.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/errors.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/generation/audio.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/keyframe.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/ops.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/transforms.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/understanding/__init__.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/understanding/_detector.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/understanding/audio.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/understanding/classification.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/understanding/image.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/understanding/objects.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/understanding/temporal.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/video_analysis/__init__.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/video_analysis/analyzer.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/video_analysis/detectors.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/video_analysis/models.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/video_analysis/sampling.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/ai/video_analysis/source_metadata.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/audio/__init__.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/audio/analysis.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/base/__init__.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/base/_dimensions.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/base/_ffmpeg.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/base/_video_io.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/base/description.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/base/draw_detections.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/base/exceptions.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/base/fonts/Anton-OFL.txt +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/base/fonts/Anton-Regular.ttf +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/base/fonts/BebasNeue-OFL.txt +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/base/fonts/BebasNeue-Regular.ttf +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/base/fonts/DejaVuSans.ttf +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/base/fonts/LICENSE_DEJAVU +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/base/fonts/Lato-Bold.ttf +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/base/fonts/Lato-OFL.txt +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/base/fonts/Poppins-Bold.ttf +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/base/fonts/Poppins-OFL.txt +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/base/fonts/__init__.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/base/transcription.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/editing/__init__.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/editing/_ass.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/editing/_easing.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/editing/_schema.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/editing/audio_ops.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/editing/effects.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/editing/operation.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/editing/streaming.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/editing/transcription_overlay.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/editing/transforms.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/editing/video_edit.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/mcp/__init__.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/src/videopython/mcp/server.py +0 -0
- {videopython-0.53.0 → videopython-0.54.1}/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.1
|
|
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/
|
|
@@ -24,10 +24,11 @@ Requires-Dist: resvg-py>=0.3.2
|
|
|
24
24
|
Requires-Dist: tqdm>=4.66.3
|
|
25
25
|
Provides-Extra: ai
|
|
26
26
|
Requires-Dist: accelerate>=0.29.2; extra == 'ai'
|
|
27
|
-
Requires-Dist: chatterbox-tts>=0.1.7; extra == 'ai'
|
|
28
27
|
Requires-Dist: demucs>=4.0.0; extra == 'ai'
|
|
29
|
-
Requires-Dist: diffusers>=0.
|
|
28
|
+
Requires-Dist: diffusers>=0.35.0; extra == 'ai'
|
|
29
|
+
Requires-Dist: ftfy>=6.1; extra == 'ai'
|
|
30
30
|
Requires-Dist: imagehash>=4.3; extra == 'ai'
|
|
31
|
+
Requires-Dist: numba>=0.62; extra == 'ai'
|
|
31
32
|
Requires-Dist: ollama>=0.5.0; extra == 'ai'
|
|
32
33
|
Requires-Dist: openai-whisper>=20240930; extra == 'ai'
|
|
33
34
|
Requires-Dist: pyannote-audio>=4.0.0; extra == 'ai'
|
|
@@ -35,9 +36,11 @@ Requires-Dist: pyloudnorm>=0.1.1; extra == 'ai'
|
|
|
35
36
|
Requires-Dist: silero-vad>=5.1; extra == 'ai'
|
|
36
37
|
Requires-Dist: torch>=2.8.0; extra == 'ai'
|
|
37
38
|
Requires-Dist: torchaudio>=2.8.0; extra == 'ai'
|
|
38
|
-
Requires-Dist:
|
|
39
|
+
Requires-Dist: torchcodec>=0.7.0; extra == 'ai'
|
|
40
|
+
Requires-Dist: torchvision>=0.23.0; extra == 'ai'
|
|
39
41
|
Requires-Dist: transformers>=5.2.0; extra == 'ai'
|
|
40
42
|
Requires-Dist: transnetv2-pytorch>=1.0.5; extra == 'ai'
|
|
43
|
+
Requires-Dist: videopython-chatterbox>=0.1.7.post1; extra == 'ai'
|
|
41
44
|
Provides-Extra: mcp
|
|
42
45
|
Requires-Dist: mcp<2,>=1.27; extra == 'mcp'
|
|
43
46
|
Description-Content-Type: text/markdown
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "videopython"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.54.1"
|
|
4
4
|
description = "Minimal video generation and processing library."
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Bartosz Wójtowicz", email = "bartoszwojtowicz@outlook.com" },
|
|
@@ -66,6 +66,13 @@ ai = ["videopython[ai]"]
|
|
|
66
66
|
ai = [
|
|
67
67
|
# Speech recognition / diarization (understanding/audio.py)
|
|
68
68
|
"openai-whisper>=20240930",
|
|
69
|
+
# whisper imports numba (timing.py), and numba is version-coupled to numpy:
|
|
70
|
+
# each release declares a `numpy<X` cap that moves forward (0.62 -> <2.4,
|
|
71
|
+
# 0.66 -> <2.5). Without a floor a resolver can satisfy a newer numpy by
|
|
72
|
+
# backtracking numba to an ancient release instead, which then fails at
|
|
73
|
+
# `import whisper`. Floor it so numba's own cap does the work; 0.62 is the
|
|
74
|
+
# oldest release covering our full Python range (3.11-3.13).
|
|
75
|
+
"numba>=0.62",
|
|
69
76
|
"pyannote-audio>=4.0.0",
|
|
70
77
|
"silero-vad>=5.1",
|
|
71
78
|
# Visual understanding: detection (D-FINE/YuNet via transformers + opencv core
|
|
@@ -75,11 +82,21 @@ ai = [
|
|
|
75
82
|
"imagehash>=4.3",
|
|
76
83
|
# Audio source separation (understanding/separation.py)
|
|
77
84
|
"demucs>=4.0.0",
|
|
78
|
-
# Voice cloning TTS (generation/audio.py — Chatterbox)
|
|
79
|
-
#
|
|
80
|
-
|
|
81
|
-
#
|
|
82
|
-
|
|
85
|
+
# Voice cloning TTS (generation/audio.py — Chatterbox). Upstream chatterbox-tts
|
|
86
|
+
# 0.1.7 pins diffusers==0.29.0 / torch==2.6.0 / transformers==5.2.0 with `==`,
|
|
87
|
+
# which is unsatisfiable against pyannote-audio (torch>=2.8) and our
|
|
88
|
+
# diffusers>=0.35 — it made `pip install "videopython[ai]"` impossible for
|
|
89
|
+
# consumers. videopython-chatterbox is upstream's source with corrected
|
|
90
|
+
# metadata (identical apart from its self-version lookup); the import name is
|
|
91
|
+
# still `chatterbox`. See https://github.com/BartWojtowicz/videopython-chatterbox
|
|
92
|
+
"videopython-chatterbox>=0.1.7.post1",
|
|
93
|
+
# Local media generation: Qwen-Image/Wan2.2 + MusicGen (generation/*).
|
|
94
|
+
# >=0.35 is the floor that ships QwenImagePipeline + Wan2.2 support (tested on 0.37.1).
|
|
95
|
+
"diffusers>=0.35.0",
|
|
96
|
+
# Wan2.2 i2v pipeline cleans prompts with ftfy via a hard import (diffusers'
|
|
97
|
+
# pipeline_wan_i2v); the t2v pipeline guards it with is_ftfy_available(), i2v
|
|
98
|
+
# does not, so it is a required dep for ImageToVideo.
|
|
99
|
+
"ftfy>=6.1",
|
|
83
100
|
"accelerate>=0.29.2",
|
|
84
101
|
# Dubbing loudness matching (dubbing/loudness.py)
|
|
85
102
|
"pyloudnorm>=0.1.1",
|
|
@@ -89,9 +106,17 @@ ai = [
|
|
|
89
106
|
# processor used by the D-FINE object detector (understanding/objects.py).
|
|
90
107
|
"torch>=2.8.0",
|
|
91
108
|
"torchaudio>=2.8.0",
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
|
|
109
|
+
# No upper bound: each torchvision release declares an exact `torch==X.Y.Z`
|
|
110
|
+
# pin, so resolvers co-select a valid pair unaided. Capping torchvision alone
|
|
111
|
+
# is actively harmful — it transitively pinned torch to 2.8 (cu12) while
|
|
112
|
+
# torchaudio floated to 2.11 (cu13), resolving cleanly and then dying at
|
|
113
|
+
# import with `OSError: libcudart.so.13`. Cap every member or none.
|
|
114
|
+
"torchvision>=0.23.0",
|
|
115
|
+
# torchaudio 2.11 routes save()/load() through torchcodec, and torchcodec is a
|
|
116
|
+
# native extension linked against libtorch that declares no torch requirement
|
|
117
|
+
# of its own. It arrives transitively via pyannote-audio; declare it so it is
|
|
118
|
+
# visible rather than incidental.
|
|
119
|
+
"torchcodec>=0.7.0",
|
|
95
120
|
]
|
|
96
121
|
# MCP server (videopython/mcp/). Pin <2 — v2 is pre-release/breaking.
|
|
97
122
|
mcp = ["mcp>=1.27,<2"]
|
|
@@ -131,12 +156,22 @@ module = [
|
|
|
131
156
|
ignore_missing_imports = true
|
|
132
157
|
|
|
133
158
|
[tool.uv]
|
|
134
|
-
#
|
|
135
|
-
#
|
|
136
|
-
#
|
|
137
|
-
#
|
|
159
|
+
# NOTE: overrides are a uv workspace feature — they do NOT ship in the built
|
|
160
|
+
# wheel. Anything reconciled only here is invisible to `pip install videopython`,
|
|
161
|
+
# so [ai] must be resolvable without this block. Keep it minimal.
|
|
162
|
+
#
|
|
163
|
+
# The torch/torchaudio/torchvision/diffusers overrides that used to live here
|
|
164
|
+
# existed solely to paper over chatterbox-tts's `==` pins. They masked the
|
|
165
|
+
# breakage from CI while every downstream consumer hit it, and an override
|
|
166
|
+
# replaces a requirement *everywhere* — including torchvision's own exact
|
|
167
|
+
# `torch==2.8.0` pin, which would have produced an ABI-invalid pair on the next
|
|
168
|
+
# `uv lock --upgrade`. [ai] now depends on videopython-chatterbox, whose metadata
|
|
169
|
+
# is correct, so none of them are needed.
|
|
170
|
+
# The numpy>=2.0.0 override is gone for the same reason: it existed to counter
|
|
171
|
+
# chatterbox-tts's `numpy<2.0.0` pin, and it also replaced numba's `numpy<2.5`
|
|
172
|
+
# cap, resolving numpy 2.5 into an environment where `import whisper` dies with
|
|
173
|
+
# "Numba needs NumPy 2.4 or less". numba IS version-coupled to numpy.
|
|
138
174
|
override-dependencies = [
|
|
139
|
-
"torch>=2.8.0", "torchaudio>=2.8.0", "torchvision>=0.23.0,<0.24.0", "numpy>=2.0.0", "diffusers>=0.30.0",
|
|
140
175
|
# Some transitive deps pull opencv-python, which conflicts with our
|
|
141
176
|
# opencv-python-headless (both provide cv2). Exclude opencv-python so
|
|
142
177
|
# 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)
|
|
@@ -236,7 +236,7 @@ class FaceSmoothingTracker(_FaceTrackerBase):
|
|
|
236
236
|
frame_center = (0.5, 0.5)
|
|
237
237
|
_, bbox = min(
|
|
238
238
|
faces_with_box,
|
|
239
|
-
key=lambda fb: (
|
|
239
|
+
key=lambda fb: (fb[1].center[0] - frame_center[0]) ** 2 + (fb[1].center[1] - frame_center[1]) ** 2,
|
|
240
240
|
)
|
|
241
241
|
elif self.selection_strategy == "index":
|
|
242
242
|
idx = self.face_index if self.face_index < len(faces_with_box) else 0
|
|
@@ -224,16 +224,12 @@ class Audio:
|
|
|
224
224
|
if dtype is None:
|
|
225
225
|
raise AudioLoadError(f"Unsupported sample width: {sample_width}")
|
|
226
226
|
|
|
227
|
-
|
|
227
|
+
# Explicitly annotated: numpy>=2.5 shape-types ndarray, so the
|
|
228
|
+
# 1-D frombuffer result cannot be rebound to a 2-D view below.
|
|
229
|
+
data: np.ndarray[Any, np.dtype[np.float32]]
|
|
230
|
+
data = np.frombuffer(raw_data, dtype=dtype).astype(np.float32)
|
|
228
231
|
|
|
229
|
-
# Reshape if stereo
|
|
230
|
-
if channels == 2:
|
|
231
|
-
data = data.reshape(-1, 2)
|
|
232
|
-
|
|
233
|
-
# Convert to float32
|
|
234
|
-
data = data.astype(np.float32)
|
|
235
|
-
|
|
236
|
-
# Reshape before normalization if stereo
|
|
232
|
+
# Reshape to (frames, channels) if stereo
|
|
237
233
|
if channels == 2:
|
|
238
234
|
data = data.reshape(-1, 2)
|
|
239
235
|
|
|
@@ -427,8 +427,9 @@ def extract_frames_at_indices(
|
|
|
427
427
|
# Truncate to complete frames only
|
|
428
428
|
raw_data = raw_data[: actual_frames * frame_size]
|
|
429
429
|
|
|
430
|
-
|
|
431
|
-
|
|
430
|
+
# Chained rather than reshaped in place: numpy>=2.5 shape-types ndarray, so
|
|
431
|
+
# rebinding a 1-D frombuffer result to a 4-D view is an assignment error.
|
|
432
|
+
frames = np.frombuffer(raw_data, dtype=np.uint8).copy().reshape(-1, metadata.height, metadata.width, 3)
|
|
432
433
|
|
|
433
434
|
# Reorder to match original frame_indices order if needed
|
|
434
435
|
if unique_sorted_indices != frame_indices:
|
|
@@ -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.1}/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
|
{videopython-0.53.0 → videopython-0.54.1}/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
|