Upload 124 files
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitattributes +6 -0
- infinite-zoom-automatic1111-webui/.github/ISSUE_TEMPLATE/bug_report.md +29 -0
- infinite-zoom-automatic1111-webui/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- infinite-zoom-automatic1111-webui/.gitignore +132 -0
- infinite-zoom-automatic1111-webui/CODE_OF_CONDUCT.md +128 -0
- infinite-zoom-automatic1111-webui/LICENSE +21 -0
- infinite-zoom-automatic1111-webui/README.md +115 -0
- infinite-zoom-automatic1111-webui/install.py +6 -0
- infinite-zoom-automatic1111-webui/iz_helpers/__init__.py +2 -0
- infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/__init__.cpython-310.pyc +0 -0
- infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/helpers.cpython-310.pyc +0 -0
- infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/image.cpython-310.pyc +0 -0
- infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/prompt_util.cpython-310.pyc +0 -0
- infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/run.cpython-310.pyc +0 -0
- infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/sd_helpers.cpython-310.pyc +0 -0
- infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/settings.cpython-310.pyc +0 -0
- infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/static_variables.cpython-310.pyc +0 -0
- infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/ui.cpython-310.pyc +0 -0
- infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/video.cpython-310.pyc +0 -0
- infinite-zoom-automatic1111-webui/iz_helpers/extra.py +0 -0
- infinite-zoom-automatic1111-webui/iz_helpers/helpers.py +126 -0
- infinite-zoom-automatic1111-webui/iz_helpers/image.py +23 -0
- infinite-zoom-automatic1111-webui/iz_helpers/prompt_util.py +67 -0
- infinite-zoom-automatic1111-webui/iz_helpers/promptschema.json +60 -0
- infinite-zoom-automatic1111-webui/iz_helpers/run.py +512 -0
- infinite-zoom-automatic1111-webui/iz_helpers/sd_helpers.py +81 -0
- infinite-zoom-automatic1111-webui/iz_helpers/settings.py +108 -0
- infinite-zoom-automatic1111-webui/iz_helpers/static_variables.py +51 -0
- infinite-zoom-automatic1111-webui/iz_helpers/ui.py +307 -0
- infinite-zoom-automatic1111-webui/iz_helpers/video.py +43 -0
- infinite-zoom-automatic1111-webui/javascript/infinite-zoom-hints.js +49 -0
- infinite-zoom-automatic1111-webui/javascript/infinite-zoom.js +54 -0
- infinite-zoom-automatic1111-webui/scripts/__pycache__/infinite-zoom.cpython-310.pyc +0 -0
- infinite-zoom-automatic1111-webui/scripts/infinite-zoom.py +5 -0
- infinite-zoom-automatic1111-webui/style.css +10 -0
- sd-webui-cutoff/.gitignore +1 -0
- sd-webui-cutoff/LICENSE +1 -0
- sd-webui-cutoff/README.md +146 -0
- sd-webui-cutoff/images/cover.jpg +3 -0
- sd-webui-cutoff/images/idea.png +0 -0
- sd-webui-cutoff/images/sample-1.png +3 -0
- sd-webui-cutoff/images/sample-2.png +3 -0
- sd-webui-cutoff/images/sample-3.png +3 -0
- sd-webui-cutoff/scripts/__pycache__/cutoff.cpython-310.pyc +0 -0
- sd-webui-cutoff/scripts/cutoff.py +257 -0
- sd-webui-cutoff/scripts/cutofflib/__pycache__/embedding.cpython-310.pyc +0 -0
- sd-webui-cutoff/scripts/cutofflib/__pycache__/sdhook.cpython-310.pyc +0 -0
- sd-webui-cutoff/scripts/cutofflib/__pycache__/utils.cpython-310.pyc +0 -0
- sd-webui-cutoff/scripts/cutofflib/__pycache__/xyz.cpython-310.pyc +0 -0
- sd-webui-cutoff/scripts/cutofflib/embedding.py +214 -0
.gitattributes
CHANGED
@@ -32,3 +32,9 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
32 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
33 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
34 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
32 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
33 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
34 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
35 |
+
sd-webui-cutoff/images/cover.jpg filter=lfs diff=lfs merge=lfs -text
|
36 |
+
sd-webui-cutoff/images/sample-1.png filter=lfs diff=lfs merge=lfs -text
|
37 |
+
sd-webui-cutoff/images/sample-2.png filter=lfs diff=lfs merge=lfs -text
|
38 |
+
sd-webui-cutoff/images/sample-3.png filter=lfs diff=lfs merge=lfs -text
|
39 |
+
sd-webui-llul/images/llul_yuv420p.mp4 filter=lfs diff=lfs merge=lfs -text
|
40 |
+
sd-webui-llul/images/mask_effect.jpg filter=lfs diff=lfs merge=lfs -text
|
infinite-zoom-automatic1111-webui/.github/ISSUE_TEMPLATE/bug_report.md
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
---
|
2 |
+
name: Bug report
|
3 |
+
about: Create a report to help us improve
|
4 |
+
title: "[BUG]"
|
5 |
+
labels: bug
|
6 |
+
assignees: ''
|
7 |
+
|
8 |
+
---
|
9 |
+
|
10 |
+
**Describe the bug**
|
11 |
+
A clear and concise description of what the bug is.
|
12 |
+
|
13 |
+
**To Reproduce**
|
14 |
+
Steps to reproduce the behavior:
|
15 |
+
1. Enter these inputs '....'
|
16 |
+
2. Click on '....'
|
17 |
+
|
18 |
+
**Expected behavior**
|
19 |
+
A clear and concise description of what you expected to happen.
|
20 |
+
|
21 |
+
**Screenshots of error**
|
22 |
+
If applicable, add screenshots to help explain your problem. Or just dump the error.
|
23 |
+
|
24 |
+
**Desktop (please complete the following information):**
|
25 |
+
- OS: [e.g. Windows]
|
26 |
+
- Version [e.g. 22]
|
27 |
+
|
28 |
+
**Additional context**
|
29 |
+
Add any other context about the problem here.
|
infinite-zoom-automatic1111-webui/.github/ISSUE_TEMPLATE/feature_request.md
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
---
|
2 |
+
name: Feature request
|
3 |
+
about: Suggest an idea for this project
|
4 |
+
title: "[FEATURE]"
|
5 |
+
labels: enhancement
|
6 |
+
assignees: ''
|
7 |
+
|
8 |
+
---
|
9 |
+
|
10 |
+
**Is your feature request related to a problem? Please describe.**
|
11 |
+
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
12 |
+
|
13 |
+
**Describe the solution you'd like**
|
14 |
+
A clear and concise description of what you want to happen.
|
15 |
+
|
16 |
+
**Describe alternatives you've considered**
|
17 |
+
A clear and concise description of any alternative solutions or features you've considered.
|
18 |
+
|
19 |
+
**Additional context**
|
20 |
+
Add any other context or screenshots about the feature request here.
|
infinite-zoom-automatic1111-webui/.gitignore
ADDED
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Byte-compiled / optimized / DLL files
|
2 |
+
__pycache__/
|
3 |
+
*.py[cod]
|
4 |
+
*$py.class
|
5 |
+
|
6 |
+
# C extensions
|
7 |
+
*.so
|
8 |
+
|
9 |
+
# Distribution / packaging
|
10 |
+
.Python
|
11 |
+
build/
|
12 |
+
develop-eggs/
|
13 |
+
dist/
|
14 |
+
downloads/
|
15 |
+
eggs/
|
16 |
+
.eggs/
|
17 |
+
lib/
|
18 |
+
lib64/
|
19 |
+
parts/
|
20 |
+
sdist/
|
21 |
+
var/
|
22 |
+
wheels/
|
23 |
+
pip-wheel-metadata/
|
24 |
+
share/python-wheels/
|
25 |
+
*.egg-info/
|
26 |
+
.installed.cfg
|
27 |
+
*.egg
|
28 |
+
MANIFEST
|
29 |
+
|
30 |
+
# PyInstaller
|
31 |
+
# Usually these files are written by a python script from a template
|
32 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
33 |
+
*.manifest
|
34 |
+
*.spec
|
35 |
+
|
36 |
+
# Installer logs
|
37 |
+
pip-log.txt
|
38 |
+
pip-delete-this-directory.txt
|
39 |
+
|
40 |
+
# Unit test / coverage reports
|
41 |
+
htmlcov/
|
42 |
+
.tox/
|
43 |
+
.nox/
|
44 |
+
.coverage
|
45 |
+
.coverage.*
|
46 |
+
.cache
|
47 |
+
nosetests.xml
|
48 |
+
coverage.xml
|
49 |
+
*.cover
|
50 |
+
*.py,cover
|
51 |
+
.hypothesis/
|
52 |
+
.pytest_cache/
|
53 |
+
|
54 |
+
# Translations
|
55 |
+
*.mo
|
56 |
+
*.pot
|
57 |
+
|
58 |
+
# Django stuff:
|
59 |
+
*.log
|
60 |
+
local_settings.py
|
61 |
+
db.sqlite3
|
62 |
+
db.sqlite3-journal
|
63 |
+
|
64 |
+
# Flask stuff:
|
65 |
+
instance/
|
66 |
+
.webassets-cache
|
67 |
+
|
68 |
+
# Scrapy stuff:
|
69 |
+
.scrapy
|
70 |
+
|
71 |
+
# Sphinx documentation
|
72 |
+
docs/_build/
|
73 |
+
|
74 |
+
# PyBuilder
|
75 |
+
target/
|
76 |
+
|
77 |
+
# Jupyter Notebook
|
78 |
+
.ipynb_checkpoints
|
79 |
+
|
80 |
+
# IPython
|
81 |
+
profile_default/
|
82 |
+
ipython_config.py
|
83 |
+
|
84 |
+
# pyenv
|
85 |
+
.python-version
|
86 |
+
|
87 |
+
# pipenv
|
88 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
89 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
90 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
91 |
+
# install all needed dependencies.
|
92 |
+
#Pipfile.lock
|
93 |
+
|
94 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
95 |
+
__pypackages__/
|
96 |
+
|
97 |
+
# Celery stuff
|
98 |
+
celerybeat-schedule
|
99 |
+
celerybeat.pid
|
100 |
+
|
101 |
+
# SageMath parsed files
|
102 |
+
*.sage.py
|
103 |
+
|
104 |
+
# Environments
|
105 |
+
.env
|
106 |
+
.venv
|
107 |
+
env/
|
108 |
+
venv/
|
109 |
+
ENV/
|
110 |
+
env.bak/
|
111 |
+
venv.bak/
|
112 |
+
|
113 |
+
# Spyder project settings
|
114 |
+
.spyderproject
|
115 |
+
.spyproject
|
116 |
+
|
117 |
+
# Rope project settings
|
118 |
+
.ropeproject
|
119 |
+
|
120 |
+
# mkdocs documentation
|
121 |
+
/site
|
122 |
+
|
123 |
+
# mypy
|
124 |
+
.mypy_cache/
|
125 |
+
.dmypy.json
|
126 |
+
dmypy.json
|
127 |
+
|
128 |
+
# Pyre type checker
|
129 |
+
.pyre/
|
130 |
+
.vscode/settings.json
|
131 |
+
.DS_Store
|
132 |
+
/.vs
|
infinite-zoom-automatic1111-webui/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Contributor Covenant Code of Conduct
|
2 |
+
|
3 |
+
## Our Pledge
|
4 |
+
|
5 |
+
We as members, contributors, and leaders pledge to make participation in our
|
6 |
+
community a harassment-free experience for everyone, regardless of age, body
|
7 |
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
8 |
+
identity and expression, level of experience, education, socio-economic status,
|
9 |
+
nationality, personal appearance, race, religion, or sexual identity
|
10 |
+
and orientation.
|
11 |
+
|
12 |
+
We pledge to act and interact in ways that contribute to an open, welcoming,
|
13 |
+
diverse, inclusive, and healthy community.
|
14 |
+
|
15 |
+
## Our Standards
|
16 |
+
|
17 |
+
Examples of behavior that contributes to a positive environment for our
|
18 |
+
community include:
|
19 |
+
|
20 |
+
* Demonstrating empathy and kindness toward other people
|
21 |
+
* Being respectful of differing opinions, viewpoints, and experiences
|
22 |
+
* Giving and gracefully accepting constructive feedback
|
23 |
+
* Accepting responsibility and apologizing to those affected by our mistakes,
|
24 |
+
and learning from the experience
|
25 |
+
* Focusing on what is best not just for us as individuals, but for the
|
26 |
+
overall community
|
27 |
+
|
28 |
+
Examples of unacceptable behavior include:
|
29 |
+
|
30 |
+
* The use of sexualized language or imagery, and sexual attention or
|
31 |
+
advances of any kind
|
32 |
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
33 |
+
* Public or private harassment
|
34 |
+
* Publishing others' private information, such as a physical or email
|
35 |
+
address, without their explicit permission
|
36 |
+
* Other conduct which could reasonably be considered inappropriate in a
|
37 |
+
professional setting
|
38 |
+
|
39 |
+
## Enforcement Responsibilities
|
40 |
+
|
41 |
+
Community leaders are responsible for clarifying and enforcing our standards of
|
42 |
+
acceptable behavior and will take appropriate and fair corrective action in
|
43 |
+
response to any behavior that they deem inappropriate, threatening, offensive,
|
44 |
+
or harmful.
|
45 |
+
|
46 |
+
Community leaders have the right and responsibility to remove, edit, or reject
|
47 |
+
comments, commits, code, wiki edits, issues, and other contributions that are
|
48 |
+
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
49 |
+
decisions when appropriate.
|
50 |
+
|
51 |
+
## Scope
|
52 |
+
|
53 |
+
This Code of Conduct applies within all community spaces, and also applies when
|
54 |
+
an individual is officially representing the community in public spaces.
|
55 |
+
Examples of representing our community include using an official e-mail address,
|
56 |
+
posting via an official social media account, or acting as an appointed
|
57 |
+
representative at an online or offline event.
|
58 |
+
|
59 |
+
## Enforcement
|
60 |
+
|
61 |
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
62 |
+
reported to the community leaders responsible for enforcement at
|
63 |
+
me@vahid.cloud.
|
64 |
+
All complaints will be reviewed and investigated promptly and fairly.
|
65 |
+
|
66 |
+
All community leaders are obligated to respect the privacy and security of the
|
67 |
+
reporter of any incident.
|
68 |
+
|
69 |
+
## Enforcement Guidelines
|
70 |
+
|
71 |
+
Community leaders will follow these Community Impact Guidelines in determining
|
72 |
+
the consequences for any action they deem in violation of this Code of Conduct:
|
73 |
+
|
74 |
+
### 1. Correction
|
75 |
+
|
76 |
+
**Community Impact**: Use of inappropriate language or other behavior deemed
|
77 |
+
unprofessional or unwelcome in the community.
|
78 |
+
|
79 |
+
**Consequence**: A private, written warning from community leaders, providing
|
80 |
+
clarity around the nature of the violation and an explanation of why the
|
81 |
+
behavior was inappropriate. A public apology may be requested.
|
82 |
+
|
83 |
+
### 2. Warning
|
84 |
+
|
85 |
+
**Community Impact**: A violation through a single incident or series
|
86 |
+
of actions.
|
87 |
+
|
88 |
+
**Consequence**: A warning with consequences for continued behavior. No
|
89 |
+
interaction with the people involved, including unsolicited interaction with
|
90 |
+
those enforcing the Code of Conduct, for a specified period of time. This
|
91 |
+
includes avoiding interactions in community spaces as well as external channels
|
92 |
+
like social media. Violating these terms may lead to a temporary or
|
93 |
+
permanent ban.
|
94 |
+
|
95 |
+
### 3. Temporary Ban
|
96 |
+
|
97 |
+
**Community Impact**: A serious violation of community standards, including
|
98 |
+
sustained inappropriate behavior.
|
99 |
+
|
100 |
+
**Consequence**: A temporary ban from any sort of interaction or public
|
101 |
+
communication with the community for a specified period of time. No public or
|
102 |
+
private interaction with the people involved, including unsolicited interaction
|
103 |
+
with those enforcing the Code of Conduct, is allowed during this period.
|
104 |
+
Violating these terms may lead to a permanent ban.
|
105 |
+
|
106 |
+
### 4. Permanent Ban
|
107 |
+
|
108 |
+
**Community Impact**: Demonstrating a pattern of violation of community
|
109 |
+
standards, including sustained inappropriate behavior, harassment of an
|
110 |
+
individual, or aggression toward or disparagement of classes of individuals.
|
111 |
+
|
112 |
+
**Consequence**: A permanent ban from any sort of public interaction within
|
113 |
+
the community.
|
114 |
+
|
115 |
+
## Attribution
|
116 |
+
|
117 |
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
118 |
+
version 2.0, available at
|
119 |
+
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
120 |
+
|
121 |
+
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
122 |
+
enforcement ladder](https://github.com/mozilla/diversity).
|
123 |
+
|
124 |
+
[homepage]: https://www.contributor-covenant.org
|
125 |
+
|
126 |
+
For answers to common questions about this code of conduct, see the FAQ at
|
127 |
+
https://www.contributor-covenant.org/faq. Translations are available at
|
128 |
+
https://www.contributor-covenant.org/translations.
|
infinite-zoom-automatic1111-webui/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
MIT License
|
2 |
+
|
3 |
+
Copyright (c) 2023 vahid khroasani
|
4 |
+
|
5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6 |
+
of this software and associated documentation files (the "Software"), to deal
|
7 |
+
in the Software without restriction, including without limitation the rights
|
8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9 |
+
copies of the Software, and to permit persons to whom the Software is
|
10 |
+
furnished to do so, subject to the following conditions:
|
11 |
+
|
12 |
+
The above copyright notice and this permission notice shall be included in all
|
13 |
+
copies or substantial portions of the Software.
|
14 |
+
|
15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21 |
+
SOFTWARE.
|
infinite-zoom-automatic1111-webui/README.md
ADDED
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Infinite Zoom extension for [Stable Diffusion WebUI](https://github.com/AUTOMATIC1111/stable-diffusion-webui/).
|
2 |
+
|
3 |
+
<p align="center">
|
4 |
+
<a href="https://discord.gg/v2nHqSrWdW">
|
5 |
+
<img src="https://img.shields.io/discord/1095469311830806630?color=blue&label=discord&logo=discord&logoColor=white" alt="build status">
|
6 |
+
</a>
|
7 |
+
</p>
|
8 |
+
|
9 |
+
This is an extension for the AUTOMATIC1111's (and Vladmandic´s) webui that allows users to create infinite zoom effect videos using stable diffusion outpainting method.
|
10 |
+
<p align="center">
|
11 |
+
<img src="https://user-images.githubusercontent.com/62482657/233385585-82d7157e-1438-4cf8-b805-220d96bbbe31.gif" width="332" height="188" />
|
12 |
+
</p>
|
13 |
+
|
14 |
+
## How to install?
|
15 |
+
<details>
|
16 |
+
<summary> Click to expand </summary>
|
17 |
+
|
18 |
+
1. Open [Stable Diffusion WebUI](https://github.com/AUTOMATIC1111/stable-diffusion-webui/).
|
19 |
+
|
20 |
+
2. Go to the `Extensions tab` > `Install from URL`.
|
21 |
+
|
22 |
+
3. Enter `https://github.com/v8hid/infinite-zoom-automatic1111-webui.git` for the URL and leave the second field empty and wait for it to be installed.
|
23 |
+
> <img width="587" alt="Screenshot" src="https://user-images.githubusercontent.com/62482657/231554653-16484c48-582e-489d-8191-bafc4cccbd3b.png">
|
24 |
+
|
25 |
+
4. Go to the Installed tab and press Apply, wait for installation, and restart.
|
26 |
+
|
27 |
+
> <img width="616" alt="Screenshot" src="https://user-images.githubusercontent.com/62482657/231554793-4a54ae94-51d2-408e-9908-2eed73cde9c0.png">
|
28 |
+
|
29 |
+
5. Wait for the Stable Diffusion WebUI to restart and now you can try the Infinite Zoom extension.
|
30 |
+
|
31 |
+
</details>
|
32 |
+
|
33 |
+
## How to use?
|
34 |
+
|
35 |
+
<details>
|
36 |
+
<summary> Click to expand </summary>
|
37 |
+
|
38 |
+
1. Click on the Infinite Zoom tab <img width="1431" alt="Screenshot 2023-04-12 at 10 14 50 PM" src="https://user-images.githubusercontent.com/62482657/231571341-92767f0d-af36-4b94-8ba9-c40a63c209ba.png">
|
39 |
+
|
40 |
+
2. Modify the parameters as you wish and click Generate video, the video will appear as soon as it generates
|
41 |
+
|
42 |
+
</details>
|
43 |
+
|
44 |
+
**To learn more about the parameters, please refer to our [WIKI](https://github.com/v8hid/infinite-zoom-automatic1111-webui/wiki).**
|
45 |
+
## Effective Friendly Tips for Optimal Outcomes
|
46 |
+
|
47 |
+
<details>
|
48 |
+
<summary> Click to expand </summary>
|
49 |
+
|
50 |
+
* You're only as good as your model, so level up with an <ins>Inpainting model</ins> for killer results.
|
51 |
+
|
52 |
+
* Heads up: Setting <ins>Mask Blur</ins> parameter above 0 will give you results that look like they've been hit by the ugly stick.
|
53 |
+
|
54 |
+
* Just between us - don't forget to uncheck <ins> Apply color correction to img2img results to match original colors</ins> in the Stable Diffusion tab of the WebUI settings. You don't want your results looking like a bad Instagram filter.
|
55 |
+
|
56 |
+
</details>
|
57 |
+
|
58 |
+
## Examples
|
59 |
+
|
60 |
+
<details>
|
61 |
+
<summary> Click to expand </summary>
|
62 |
+
|
63 |
+
|
64 |
+
|
65 |
+
https://user-images.githubusercontent.com/62482657/232369614-e112d17a-db12-47b2-9795-5be4037fa9fe.mp4
|
66 |
+
|
67 |
+
|
68 |
+
https://user-images.githubusercontent.com/62482657/231573289-2db85c57-540d-4c7d-859f-3c3ddfcd2c8a.mp4
|
69 |
+
|
70 |
+
|
71 |
+
https://user-images.githubusercontent.com/62482657/231574588-3196beda-7237-407f-bc76-eae10599b5eb.mp4
|
72 |
+
|
73 |
+
|
74 |
+
https://user-images.githubusercontent.com/62482657/231574839-9d3aab52-7a87-4658-88d0-46b8dd7f4b60.mp4
|
75 |
+
|
76 |
+
</details>
|
77 |
+
|
78 |
+
## How it works?
|
79 |
+
<details>
|
80 |
+
<summary> Click to expand </summary>
|
81 |
+
|
82 |
+
To start, let's break down the workflow of the extension into three main steps:
|
83 |
+
|
84 |
+
- **Step 1: Choose an image to start with**
|
85 |
+
The program either generates an initial image using the first prompt you provide or you can upload your own image in the `custom initial image` field. This initial image will be the basis for the outpainting process.
|
86 |
+
|
87 |
+
- **Step 2: Generate outpaint steps**
|
88 |
+
Once you have your initial image, the program will start generating outpaint steps. The number of outpaint steps is determined by the `Total Outpaint Steps` input. In each outpaint step, the program makes the initial image smaller in the center of the canvas and generates a new image in the empty space that is created. This process is repeated for each outpaint step until the desired number is reached.
|
89 |
+
|
90 |
+
- **Step 3: Create a gradual zoom effect**
|
91 |
+
After all outpaint steps have been generated, the program creates an interpolation between each outpaint step to create a gradual zoom effect. The number of frames created between each outpaint step is determined by the `Zoom Speed` parameter and the `Frames per second` parameter.
|
92 |
+
|
93 |
+
Number of frames for each outpaint step = `Zoom Speed` $\times$ `Frames per second`
|
94 |
+
|
95 |
+
Length of each outpaint step in second = `Number of frames` $\div$ `Frames per second`
|
96 |
+
|
97 |
+
</details>
|
98 |
+
|
99 |
+
## Google Colab version
|
100 |
+
It works on free colab plan
|
101 |
+
|
102 |
+
<a target="_blank" href="https://colab.research.google.com/github/v8hid/infinite-zoom-stable-diffusion/blob/main/infinite_zoom_gradio.ipynb">
|
103 |
+
<img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/>
|
104 |
+
</a>
|
105 |
+
<a target="_blank" href="https://github.com/v8hid/infinite-zoom-stable-diffusion">
|
106 |
+
<img src="https://img.shields.io/static/v1?label=github&message=repository&color=blue&style=flat&logo=github&logoColor=white" alt="GitHub Repo"/>
|
107 |
+
</a>
|
108 |
+
|
109 |
+
## Contributing
|
110 |
+
|
111 |
+
Contributions are welcome! Please follow these guidelines:
|
112 |
+
|
113 |
+
1. Fork the repository.
|
114 |
+
2. Make your changes and commit them.
|
115 |
+
3. Make sure to submit the pull request to the develop repository.
|
infinite-zoom-automatic1111-webui/install.py
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import launch
|
2 |
+
|
3 |
+
if not launch.is_installed("imageio"):
|
4 |
+
launch.run_pip("install imageio", "requirements 0 for Infinite-Zoom")
|
5 |
+
if not launch.is_installed("imageio-ffmpeg"):
|
6 |
+
launch.run_pip("install imageio-ffmpeg", "requirements 1 for Infinite-Zoom")
|
infinite-zoom-automatic1111-webui/iz_helpers/__init__.py
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
# from .ui import on_ui_tabs
|
2 |
+
# from .settings import on_ui_settings
|
infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/__init__.cpython-310.pyc
ADDED
Binary file (202 Bytes). View file
|
|
infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/helpers.cpython-310.pyc
ADDED
Binary file (3.34 kB). View file
|
|
infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/image.cpython-310.pyc
ADDED
Binary file (996 Bytes). View file
|
|
infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/prompt_util.cpython-310.pyc
ADDED
Binary file (1.55 kB). View file
|
|
infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/run.cpython-310.pyc
ADDED
Binary file (7.99 kB). View file
|
|
infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/sd_helpers.cpython-310.pyc
ADDED
Binary file (1.81 kB). View file
|
|
infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/settings.cpython-310.pyc
ADDED
Binary file (2.67 kB). View file
|
|
infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/static_variables.cpython-310.pyc
ADDED
Binary file (1.94 kB). View file
|
|
infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/ui.cpython-310.pyc
ADDED
Binary file (7.33 kB). View file
|
|
infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/video.cpython-310.pyc
ADDED
Binary file (1.25 kB). View file
|
|
infinite-zoom-automatic1111-webui/iz_helpers/extra.py
ADDED
File without changes
|
infinite-zoom-automatic1111-webui/iz_helpers/helpers.py
ADDED
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import math
|
2 |
+
import os
|
3 |
+
import modules.shared as shared
|
4 |
+
import modules.sd_models
|
5 |
+
import gradio as gr
|
6 |
+
from scripts import postprocessing_upscale
|
7 |
+
from .prompt_util import readJsonPrompt
|
8 |
+
import asyncio
|
9 |
+
|
10 |
+
|
11 |
+
def fix_env_Path_ffprobe():
|
12 |
+
envpath = os.environ["PATH"]
|
13 |
+
ffppath = shared.opts.data.get("infzoom_ffprobepath", "")
|
14 |
+
|
15 |
+
if ffppath and not ffppath in envpath:
|
16 |
+
path_sep = ";" if os.name == "nt" else ":"
|
17 |
+
os.environ["PATH"] = envpath + path_sep + ffppath
|
18 |
+
|
19 |
+
|
20 |
+
def closest_upper_divisible_by_eight(num):
|
21 |
+
if num % 8 == 0:
|
22 |
+
return num
|
23 |
+
else:
|
24 |
+
return math.ceil(num / 8) * 8
|
25 |
+
|
26 |
+
|
27 |
+
def load_model_from_setting(model_field_name, progress, progress_desc):
|
28 |
+
# fix typo in Automatic1111 vs Vlad111
|
29 |
+
if hasattr(modules.sd_models, "checkpoint_alisases"):
|
30 |
+
checkPList = modules.sd_models.checkpoint_alisases
|
31 |
+
elif hasattr(modules.sd_models, "checkpoint_aliases"):
|
32 |
+
checkPList = modules.sd_models.checkpoint_aliases
|
33 |
+
else:
|
34 |
+
raise Exception(
|
35 |
+
"This is not a compatible StableDiffusion Platform, can not access checkpoints"
|
36 |
+
)
|
37 |
+
|
38 |
+
model_name = shared.opts.data.get(model_field_name)
|
39 |
+
if model_name is not None and model_name != "":
|
40 |
+
checkinfo = checkPList[model_name]
|
41 |
+
|
42 |
+
if not checkinfo:
|
43 |
+
raise NameError(model_field_name + " Does not exist in your models.")
|
44 |
+
|
45 |
+
if progress:
|
46 |
+
progress(0, desc=progress_desc + checkinfo.name)
|
47 |
+
|
48 |
+
modules.sd_models.load_model(checkinfo)
|
49 |
+
|
50 |
+
|
51 |
+
def do_upscaleImg(curImg, upscale_do, upscaler_name, upscale_by):
|
52 |
+
if not upscale_do:
|
53 |
+
return curImg
|
54 |
+
|
55 |
+
# ensure even width and even height for ffmpeg
|
56 |
+
# if odd, switch to scale to mode
|
57 |
+
rwidth = round(curImg.width * upscale_by)
|
58 |
+
rheight = round(curImg.height * upscale_by)
|
59 |
+
|
60 |
+
ups_mode = 2 # upscale_by
|
61 |
+
if (rwidth % 2) == 1:
|
62 |
+
ups_mode = 1
|
63 |
+
rwidth += 1
|
64 |
+
if (rheight % 2) == 1:
|
65 |
+
ups_mode = 1
|
66 |
+
rheight += 1
|
67 |
+
|
68 |
+
if 1 == ups_mode:
|
69 |
+
print(
|
70 |
+
"Infinite Zoom: aligning output size to even width and height: "
|
71 |
+
+ str(rwidth)
|
72 |
+
+ " x "
|
73 |
+
+ str(rheight),
|
74 |
+
end="\r",
|
75 |
+
)
|
76 |
+
|
77 |
+
pp = postprocessing_upscale.scripts_postprocessing.PostprocessedImage(curImg)
|
78 |
+
ups = postprocessing_upscale.ScriptPostprocessingUpscale()
|
79 |
+
ups.process(
|
80 |
+
pp,
|
81 |
+
upscale_mode=ups_mode,
|
82 |
+
upscale_by=upscale_by,
|
83 |
+
upscale_to_width=rwidth,
|
84 |
+
upscale_to_height=rheight,
|
85 |
+
upscale_crop=False,
|
86 |
+
upscaler_1_name=upscaler_name,
|
87 |
+
upscaler_2_name=None,
|
88 |
+
upscaler_2_visibility=0.0,
|
89 |
+
)
|
90 |
+
return pp.image
|
91 |
+
|
92 |
+
async def showGradioErrorAsync(txt, delay=1):
|
93 |
+
await asyncio.sleep(delay) # sleep for 1 second
|
94 |
+
raise gr.Error(txt)
|
95 |
+
|
96 |
+
def putPrompts(files):
|
97 |
+
try:
|
98 |
+
with open(files.name, "r") as f:
|
99 |
+
file_contents = f.read()
|
100 |
+
|
101 |
+
data = readJsonPrompt(file_contents,False)
|
102 |
+
return [
|
103 |
+
gr.Textbox.update(data["prePrompt"]),
|
104 |
+
gr.DataFrame.update(data["prompts"]),
|
105 |
+
gr.Textbox.update(data["postPrompt"]),
|
106 |
+
gr.Textbox.update(data["negPrompt"])
|
107 |
+
]
|
108 |
+
|
109 |
+
except Exception:
|
110 |
+
print(
|
111 |
+
"[InfiniteZoom:] Loading your prompt failed. It seems to be invalid. Your prompt table is preserved."
|
112 |
+
)
|
113 |
+
|
114 |
+
# error only be shown with raise, so ui gets broken.
|
115 |
+
#asyncio.run(showGradioErrorAsync("Loading your prompts failed. It seems to be invalid. Your prompt table has been preserved.",5))
|
116 |
+
|
117 |
+
return [gr.Textbox.update(), gr.DataFrame.update(), gr.Textbox.update(),gr.Textbox.update()]
|
118 |
+
|
119 |
+
|
120 |
+
def clearPrompts():
|
121 |
+
return [
|
122 |
+
gr.DataFrame.update(value=[[0, "Infinite Zoom. Start over"]]),
|
123 |
+
gr.Textbox.update(""),
|
124 |
+
gr.Textbox.update(""),
|
125 |
+
gr.Textbox.update("")
|
126 |
+
]
|
infinite-zoom-automatic1111-webui/iz_helpers/image.py
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from PIL import Image
|
2 |
+
|
3 |
+
def shrink_and_paste_on_blank(current_image, mask_width, mask_height):
|
4 |
+
"""
|
5 |
+
Decreases size of current_image by mask_width pixels from each side,
|
6 |
+
then adds a mask_width width transparent frame,
|
7 |
+
so that the image the function returns is the same size as the input.
|
8 |
+
:param current_image: input image to transform
|
9 |
+
:param mask_width: width in pixels to shrink from each side
|
10 |
+
:param mask_height: height in pixels to shrink from each side
|
11 |
+
"""
|
12 |
+
|
13 |
+
# calculate new dimensions
|
14 |
+
width, height = current_image.size
|
15 |
+
new_width = width - 2 * mask_width
|
16 |
+
new_height = height - 2 * mask_height
|
17 |
+
|
18 |
+
# resize and paste onto blank image
|
19 |
+
prev_image = current_image.resize((new_width, new_height))
|
20 |
+
blank_image = Image.new("RGBA", (width, height), (0, 0, 0, 1))
|
21 |
+
blank_image.paste(prev_image, (mask_width, mask_height))
|
22 |
+
|
23 |
+
return blank_image
|
infinite-zoom-automatic1111-webui/iz_helpers/prompt_util.py
ADDED
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
from jsonschema import validate
|
3 |
+
|
4 |
+
from .static_variables import (
|
5 |
+
empty_prompt,
|
6 |
+
invalid_prompt,
|
7 |
+
jsonprompt_schemafile,
|
8 |
+
promptTableHeaders
|
9 |
+
)
|
10 |
+
|
11 |
+
def completeOptionals(j):
|
12 |
+
if isinstance(j, dict):
|
13 |
+
# Remove header information, user dont pimp our ui
|
14 |
+
if "prompts" in j:
|
15 |
+
if "headers" in j["prompts"]:
|
16 |
+
del j["prompts"]["headers"]
|
17 |
+
j["prompts"]["headers"]=promptTableHeaders
|
18 |
+
|
19 |
+
if "negPrompt" not in j:
|
20 |
+
j["negPrompt"]=""
|
21 |
+
|
22 |
+
if "prePrompt" not in j:
|
23 |
+
if "commonPromptPrefix" in j:
|
24 |
+
j["prePrompt"]=j["commonPromptPrefix"]
|
25 |
+
else:
|
26 |
+
j["prePrompt"]=""
|
27 |
+
|
28 |
+
if "postPrompt" not in j:
|
29 |
+
if "commonPromptSuffix" in j:
|
30 |
+
j["postPrompt"]=j["commonPromptSuffix"]
|
31 |
+
else:
|
32 |
+
j["postPrompt"]=""
|
33 |
+
|
34 |
+
return j
|
35 |
+
|
36 |
+
|
37 |
+
def validatePromptJson_throws(data):
|
38 |
+
with open(jsonprompt_schemafile, "r") as s:
|
39 |
+
schema = json.load(s)
|
40 |
+
try:
|
41 |
+
validate(instance=data, schema=schema)
|
42 |
+
|
43 |
+
except Exception:
|
44 |
+
raise Exception("Your prompts are not schema valid.")
|
45 |
+
|
46 |
+
return completeOptionals(data)
|
47 |
+
|
48 |
+
|
49 |
+
def readJsonPrompt(txt, returnFailPrompt=False):
|
50 |
+
if not txt:
|
51 |
+
return empty_prompt
|
52 |
+
|
53 |
+
try:
|
54 |
+
jpr = json.loads(txt)
|
55 |
+
except Exception:
|
56 |
+
if returnFailPrompt:
|
57 |
+
print (f"Infinite Zoom: Corrupted Json structure: {txt[:24]} ...")
|
58 |
+
return invalid_prompt
|
59 |
+
raise (f"Infinite Zoom: Corrupted Json structure: {txt[:24]} ...")
|
60 |
+
|
61 |
+
try:
|
62 |
+
return validatePromptJson_throws(jpr)
|
63 |
+
except Exception:
|
64 |
+
if returnFailPrompt:
|
65 |
+
return invalid_prompt
|
66 |
+
pass
|
67 |
+
|
infinite-zoom-automatic1111-webui/iz_helpers/promptschema.json
ADDED
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
3 |
+
"$id": "1.1",
|
4 |
+
"type": "object",
|
5 |
+
"properties": {
|
6 |
+
"prompts": {
|
7 |
+
"type": "object",
|
8 |
+
"properties": {
|
9 |
+
"data": {
|
10 |
+
"type": "array",
|
11 |
+
"items": {
|
12 |
+
"type": "array",
|
13 |
+
"items": [
|
14 |
+
{
|
15 |
+
"oneOf": [
|
16 |
+
{
|
17 |
+
"type": "integer",
|
18 |
+
"minimum": 0
|
19 |
+
},
|
20 |
+
{
|
21 |
+
"type": "string"
|
22 |
+
}
|
23 |
+
]
|
24 |
+
},
|
25 |
+
{
|
26 |
+
"type": "string"
|
27 |
+
}
|
28 |
+
],
|
29 |
+
"minItems": 0,
|
30 |
+
"maxItems": 999,
|
31 |
+
"uniqueItems": false
|
32 |
+
},
|
33 |
+
"minItems": 0
|
34 |
+
},
|
35 |
+
"headers": {
|
36 |
+
"type": "array",
|
37 |
+
"items": {
|
38 |
+
"type": "string"
|
39 |
+
},
|
40 |
+
"minItems": 2
|
41 |
+
}
|
42 |
+
},
|
43 |
+
"required": [
|
44 |
+
"data"
|
45 |
+
]
|
46 |
+
},
|
47 |
+
"negPrompt": {
|
48 |
+
"type": "string"
|
49 |
+
},
|
50 |
+
"prePrompt": {
|
51 |
+
"type": "string"
|
52 |
+
},
|
53 |
+
"postPrompt": {
|
54 |
+
"type": "string"
|
55 |
+
}
|
56 |
+
},
|
57 |
+
"required": [
|
58 |
+
"prompts"
|
59 |
+
]
|
60 |
+
}
|
infinite-zoom-automatic1111-webui/iz_helpers/run.py
ADDED
@@ -0,0 +1,512 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import math, time, os
|
2 |
+
import numpy as np
|
3 |
+
from PIL import Image, ImageFilter, ImageDraw
|
4 |
+
from modules.ui import plaintext_to_html
|
5 |
+
import modules.shared as shared
|
6 |
+
from modules.paths_internal import script_path
|
7 |
+
from .helpers import (
|
8 |
+
fix_env_Path_ffprobe,
|
9 |
+
closest_upper_divisible_by_eight,
|
10 |
+
load_model_from_setting,
|
11 |
+
do_upscaleImg,
|
12 |
+
)
|
13 |
+
from .sd_helpers import renderImg2Img, renderTxt2Img
|
14 |
+
from .image import shrink_and_paste_on_blank
|
15 |
+
from .video import write_video
|
16 |
+
|
17 |
+
|
18 |
+
def crop_fethear_ellipse(image, feather_margin=30, width_offset=0, height_offset=0):
|
19 |
+
# Create a blank mask image with the same size as the original image
|
20 |
+
mask = Image.new("L", image.size, 0)
|
21 |
+
draw = ImageDraw.Draw(mask)
|
22 |
+
|
23 |
+
# Calculate the ellipse's bounding box
|
24 |
+
ellipse_box = (
|
25 |
+
width_offset,
|
26 |
+
height_offset,
|
27 |
+
image.width - width_offset,
|
28 |
+
image.height - height_offset,
|
29 |
+
)
|
30 |
+
|
31 |
+
# Draw the ellipse on the mask
|
32 |
+
draw.ellipse(ellipse_box, fill=255)
|
33 |
+
|
34 |
+
# Apply the mask to the original image
|
35 |
+
result = Image.new("RGBA", image.size)
|
36 |
+
result.paste(image, mask=mask)
|
37 |
+
|
38 |
+
# Crop the resulting image to the ellipse's bounding box
|
39 |
+
cropped_image = result.crop(ellipse_box)
|
40 |
+
|
41 |
+
# Create a new mask image with a black background (0)
|
42 |
+
mask = Image.new("L", cropped_image.size, 0)
|
43 |
+
draw = ImageDraw.Draw(mask)
|
44 |
+
|
45 |
+
# Draw an ellipse on the mask image
|
46 |
+
draw.ellipse(
|
47 |
+
(
|
48 |
+
0 + feather_margin,
|
49 |
+
0 + feather_margin,
|
50 |
+
cropped_image.width - feather_margin,
|
51 |
+
cropped_image.height - feather_margin,
|
52 |
+
),
|
53 |
+
fill=255,
|
54 |
+
outline=0,
|
55 |
+
)
|
56 |
+
|
57 |
+
# Apply a Gaussian blur to the mask image
|
58 |
+
mask = mask.filter(ImageFilter.GaussianBlur(radius=feather_margin / 2))
|
59 |
+
cropped_image.putalpha(mask)
|
60 |
+
res = Image.new(cropped_image.mode, (image.width, image.height))
|
61 |
+
paste_pos = (
|
62 |
+
int((res.width - cropped_image.width) / 2),
|
63 |
+
int((res.height - cropped_image.height) / 2),
|
64 |
+
)
|
65 |
+
res.paste(cropped_image, paste_pos)
|
66 |
+
|
67 |
+
return res
|
68 |
+
|
69 |
+
|
70 |
+
def outpaint_steps(
|
71 |
+
width,
|
72 |
+
height,
|
73 |
+
common_prompt_pre,
|
74 |
+
common_prompt_suf,
|
75 |
+
prompts,
|
76 |
+
negative_prompt,
|
77 |
+
seed,
|
78 |
+
sampler,
|
79 |
+
num_inference_steps,
|
80 |
+
guidance_scale,
|
81 |
+
inpainting_denoising_strength,
|
82 |
+
inpainting_mask_blur,
|
83 |
+
inpainting_fill_mode,
|
84 |
+
inpainting_full_res,
|
85 |
+
inpainting_padding,
|
86 |
+
init_img,
|
87 |
+
outpaint_steps,
|
88 |
+
out_config,
|
89 |
+
mask_width,
|
90 |
+
mask_height,
|
91 |
+
custom_exit_image,
|
92 |
+
frame_correction=True, # TODO: add frame_Correction in UI
|
93 |
+
):
|
94 |
+
main_frames = [init_img.convert("RGB")]
|
95 |
+
|
96 |
+
for i in range(outpaint_steps):
|
97 |
+
print_out = (
|
98 |
+
"Outpaint step: "
|
99 |
+
+ str(i + 1)
|
100 |
+
+ " / "
|
101 |
+
+ str(outpaint_steps)
|
102 |
+
+ " Seed: "
|
103 |
+
+ str(seed)
|
104 |
+
)
|
105 |
+
print(print_out)
|
106 |
+
current_image = main_frames[-1]
|
107 |
+
current_image = shrink_and_paste_on_blank(
|
108 |
+
current_image, mask_width, mask_height
|
109 |
+
)
|
110 |
+
|
111 |
+
mask_image = np.array(current_image)[:, :, 3]
|
112 |
+
mask_image = Image.fromarray(255 - mask_image).convert("RGB")
|
113 |
+
# create mask (black image with white mask_width width edges)
|
114 |
+
|
115 |
+
if custom_exit_image and ((i + 1) == outpaint_steps):
|
116 |
+
current_image = custom_exit_image.resize(
|
117 |
+
(width, height), resample=Image.LANCZOS
|
118 |
+
)
|
119 |
+
main_frames.append(current_image.convert("RGB"))
|
120 |
+
# print("using Custom Exit Image")
|
121 |
+
save2Collect(current_image, out_config, f"exit_img.png")
|
122 |
+
else:
|
123 |
+
pr = prompts[max(k for k in prompts.keys() if k <= i)]
|
124 |
+
processed, newseed = renderImg2Img(
|
125 |
+
f"{common_prompt_pre}\n{pr}\n{common_prompt_suf}".strip(),
|
126 |
+
negative_prompt,
|
127 |
+
sampler,
|
128 |
+
num_inference_steps,
|
129 |
+
guidance_scale,
|
130 |
+
seed,
|
131 |
+
width,
|
132 |
+
height,
|
133 |
+
current_image,
|
134 |
+
mask_image,
|
135 |
+
inpainting_denoising_strength,
|
136 |
+
inpainting_mask_blur,
|
137 |
+
inpainting_fill_mode,
|
138 |
+
inpainting_full_res,
|
139 |
+
inpainting_padding,
|
140 |
+
)
|
141 |
+
|
142 |
+
if len(processed.images) > 0:
|
143 |
+
main_frames.append(processed.images[0].convert("RGB"))
|
144 |
+
save2Collect(processed.images[0], out_config, f"outpain_step_{i}.png")
|
145 |
+
seed = newseed
|
146 |
+
# TODO: seed behavior
|
147 |
+
|
148 |
+
if frame_correction and inpainting_mask_blur > 0:
|
149 |
+
corrected_frame = crop_inner_image(
|
150 |
+
main_frames[i + 1], mask_width, mask_height
|
151 |
+
)
|
152 |
+
|
153 |
+
enhanced_img = crop_fethear_ellipse(
|
154 |
+
main_frames[i],
|
155 |
+
30,
|
156 |
+
inpainting_mask_blur / 3 // 2,
|
157 |
+
inpainting_mask_blur / 3 // 2,
|
158 |
+
)
|
159 |
+
save2Collect(main_frames[i], out_config, f"main_frame_{i}")
|
160 |
+
save2Collect(enhanced_img, out_config, f"main_frame_enhanced_{i}")
|
161 |
+
corrected_frame.paste(enhanced_img, mask=enhanced_img)
|
162 |
+
main_frames[i] = corrected_frame
|
163 |
+
# else :TEST
|
164 |
+
# current_image.paste(prev_image, mask=prev_image)
|
165 |
+
return main_frames, processed
|
166 |
+
|
167 |
+
|
168 |
+
def create_zoom(
|
169 |
+
common_prompt_pre,
|
170 |
+
prompts_array,
|
171 |
+
common_prompt_suf,
|
172 |
+
negative_prompt,
|
173 |
+
num_outpainting_steps,
|
174 |
+
guidance_scale,
|
175 |
+
num_inference_steps,
|
176 |
+
custom_init_image,
|
177 |
+
custom_exit_image,
|
178 |
+
video_frame_rate,
|
179 |
+
video_zoom_mode,
|
180 |
+
video_start_frame_dupe_amount,
|
181 |
+
video_last_frame_dupe_amount,
|
182 |
+
inpainting_mask_blur,
|
183 |
+
inpainting_fill_mode,
|
184 |
+
zoom_speed,
|
185 |
+
seed,
|
186 |
+
outputsizeW,
|
187 |
+
outputsizeH,
|
188 |
+
batchcount,
|
189 |
+
sampler,
|
190 |
+
upscale_do,
|
191 |
+
upscaler_name,
|
192 |
+
upscale_by,
|
193 |
+
inpainting_denoising_strength=1,
|
194 |
+
inpainting_full_res=0,
|
195 |
+
inpainting_padding=0,
|
196 |
+
progress=None,
|
197 |
+
):
|
198 |
+
for i in range(batchcount):
|
199 |
+
print(f"Batch {i+1}/{batchcount}")
|
200 |
+
result = create_zoom_single(
|
201 |
+
common_prompt_pre,
|
202 |
+
prompts_array,
|
203 |
+
common_prompt_suf,
|
204 |
+
negative_prompt,
|
205 |
+
num_outpainting_steps,
|
206 |
+
guidance_scale,
|
207 |
+
num_inference_steps,
|
208 |
+
custom_init_image,
|
209 |
+
custom_exit_image,
|
210 |
+
video_frame_rate,
|
211 |
+
video_zoom_mode,
|
212 |
+
video_start_frame_dupe_amount,
|
213 |
+
video_last_frame_dupe_amount,
|
214 |
+
inpainting_mask_blur,
|
215 |
+
inpainting_fill_mode,
|
216 |
+
zoom_speed,
|
217 |
+
seed,
|
218 |
+
outputsizeW,
|
219 |
+
outputsizeH,
|
220 |
+
sampler,
|
221 |
+
upscale_do,
|
222 |
+
upscaler_name,
|
223 |
+
upscale_by,
|
224 |
+
inpainting_denoising_strength,
|
225 |
+
inpainting_full_res,
|
226 |
+
inpainting_padding,
|
227 |
+
progress,
|
228 |
+
)
|
229 |
+
return result
|
230 |
+
|
231 |
+
|
232 |
+
def prepare_output_path():
|
233 |
+
isCollect = shared.opts.data.get("infzoom_collectAllResources", False)
|
234 |
+
output_path = shared.opts.data.get("infzoom_outpath", "outputs")
|
235 |
+
|
236 |
+
save_path = os.path.join(
|
237 |
+
output_path, shared.opts.data.get("infzoom_outSUBpath", "infinite-zooms")
|
238 |
+
)
|
239 |
+
|
240 |
+
if isCollect:
|
241 |
+
save_path = os.path.join(save_path, "iz_collect" + str(int(time.time())))
|
242 |
+
|
243 |
+
if not os.path.exists(save_path):
|
244 |
+
os.makedirs(save_path)
|
245 |
+
|
246 |
+
video_filename = os.path.join(
|
247 |
+
save_path, "infinite_zoom_" + str(int(time.time())) + ".mp4"
|
248 |
+
)
|
249 |
+
|
250 |
+
return {
|
251 |
+
"isCollect": isCollect,
|
252 |
+
"save_path": save_path,
|
253 |
+
"video_filename": video_filename,
|
254 |
+
}
|
255 |
+
|
256 |
+
|
257 |
+
def save2Collect(img, out_config, name):
|
258 |
+
if out_config["isCollect"]:
|
259 |
+
img.save(f'{out_config["save_path"]}/{name}.png')
|
260 |
+
|
261 |
+
|
262 |
+
def frame2Collect(all_frames, out_config):
|
263 |
+
save2Collect(all_frames[-1], out_config, f"frame_{len(all_frames)}")
|
264 |
+
|
265 |
+
|
266 |
+
def frames2Collect(all_frames, out_config):
|
267 |
+
for i, f in enumerate(all_frames):
|
268 |
+
save2Collect(f, out_config, f"frame_{i}")
|
269 |
+
|
270 |
+
|
271 |
+
def crop_inner_image(outpainted_img, width_offset, height_offset):
|
272 |
+
width, height = outpainted_img.size
|
273 |
+
|
274 |
+
center_x, center_y = int(width / 2), int(height / 2)
|
275 |
+
|
276 |
+
# Crop the image to the center
|
277 |
+
cropped_img = outpainted_img.crop(
|
278 |
+
(
|
279 |
+
center_x - width_offset,
|
280 |
+
center_y - height_offset,
|
281 |
+
center_x + width_offset,
|
282 |
+
center_y + height_offset,
|
283 |
+
)
|
284 |
+
)
|
285 |
+
prev_step_img = cropped_img.resize((width, height), resample=Image.LANCZOS)
|
286 |
+
# resized_img = resized_img.filter(ImageFilter.SHARPEN)
|
287 |
+
|
288 |
+
return prev_step_img
|
289 |
+
|
290 |
+
|
291 |
+
def create_zoom_single(
|
292 |
+
common_prompt_pre,
|
293 |
+
prompts_array,
|
294 |
+
common_prompt_suf,
|
295 |
+
negative_prompt,
|
296 |
+
num_outpainting_steps,
|
297 |
+
guidance_scale,
|
298 |
+
num_inference_steps,
|
299 |
+
custom_init_image,
|
300 |
+
custom_exit_image,
|
301 |
+
video_frame_rate,
|
302 |
+
video_zoom_mode,
|
303 |
+
video_start_frame_dupe_amount,
|
304 |
+
video_last_frame_dupe_amount,
|
305 |
+
inpainting_mask_blur,
|
306 |
+
inpainting_fill_mode,
|
307 |
+
zoom_speed,
|
308 |
+
seed,
|
309 |
+
outputsizeW,
|
310 |
+
outputsizeH,
|
311 |
+
sampler,
|
312 |
+
upscale_do,
|
313 |
+
upscaler_name,
|
314 |
+
upscale_by,
|
315 |
+
inpainting_denoising_strength,
|
316 |
+
inpainting_full_res,
|
317 |
+
inpainting_padding,
|
318 |
+
progress,
|
319 |
+
):
|
320 |
+
# try:
|
321 |
+
# if gr.Progress() is not None:
|
322 |
+
# progress = gr.Progress()
|
323 |
+
# progress(0, desc="Preparing Initial Image")
|
324 |
+
# except Exception:
|
325 |
+
# pass
|
326 |
+
fix_env_Path_ffprobe()
|
327 |
+
out_config = prepare_output_path()
|
328 |
+
|
329 |
+
prompts = {}
|
330 |
+
|
331 |
+
for x in prompts_array:
|
332 |
+
try:
|
333 |
+
key = int(x[0])
|
334 |
+
value = str(x[1])
|
335 |
+
prompts[key] = value
|
336 |
+
except ValueError:
|
337 |
+
pass
|
338 |
+
|
339 |
+
assert len(prompts_array) > 0, "prompts is empty"
|
340 |
+
|
341 |
+
width = closest_upper_divisible_by_eight(outputsizeW)
|
342 |
+
height = closest_upper_divisible_by_eight(outputsizeH)
|
343 |
+
|
344 |
+
current_image = Image.new(mode="RGBA", size=(width, height))
|
345 |
+
mask_image = np.array(current_image)[:, :, 3]
|
346 |
+
mask_image = Image.fromarray(255 - mask_image).convert("RGB")
|
347 |
+
current_image = current_image.convert("RGB")
|
348 |
+
current_seed = seed
|
349 |
+
|
350 |
+
if custom_init_image:
|
351 |
+
current_image = custom_init_image.resize(
|
352 |
+
(width, height), resample=Image.LANCZOS
|
353 |
+
)
|
354 |
+
save2Collect(current_image, out_config, f"init_custom.png")
|
355 |
+
|
356 |
+
else:
|
357 |
+
load_model_from_setting(
|
358 |
+
"infzoom_txt2img_model", progress, "Loading Model for txt2img: "
|
359 |
+
)
|
360 |
+
|
361 |
+
pr = prompts[min(k for k in prompts.keys() if k >= 0)]
|
362 |
+
processed, newseed = renderTxt2Img(
|
363 |
+
f"{common_prompt_pre}\n{pr}\n{common_prompt_suf}".strip(),
|
364 |
+
negative_prompt,
|
365 |
+
sampler,
|
366 |
+
num_inference_steps,
|
367 |
+
guidance_scale,
|
368 |
+
current_seed,
|
369 |
+
width,
|
370 |
+
height,
|
371 |
+
)
|
372 |
+
if len(processed.images) > 0:
|
373 |
+
current_image = processed.images[0]
|
374 |
+
save2Collect(current_image, out_config, f"init_txt2img.png")
|
375 |
+
current_seed = newseed
|
376 |
+
|
377 |
+
mask_width = math.trunc(width / 4) # was initially 512px => 128px
|
378 |
+
mask_height = math.trunc(height / 4) # was initially 512px => 128px
|
379 |
+
|
380 |
+
num_interpol_frames = round(video_frame_rate * zoom_speed)
|
381 |
+
|
382 |
+
all_frames = []
|
383 |
+
|
384 |
+
if upscale_do and progress:
|
385 |
+
progress(0, desc="upscaling inital image")
|
386 |
+
|
387 |
+
load_model_from_setting(
|
388 |
+
"infzoom_inpainting_model", progress, "Loading Model for inpainting/img2img: "
|
389 |
+
)
|
390 |
+
main_frames, processed = outpaint_steps(
|
391 |
+
width,
|
392 |
+
height,
|
393 |
+
common_prompt_pre,
|
394 |
+
common_prompt_suf,
|
395 |
+
prompts,
|
396 |
+
negative_prompt,
|
397 |
+
seed,
|
398 |
+
sampler,
|
399 |
+
num_inference_steps,
|
400 |
+
guidance_scale,
|
401 |
+
inpainting_denoising_strength,
|
402 |
+
inpainting_mask_blur,
|
403 |
+
inpainting_fill_mode,
|
404 |
+
inpainting_full_res,
|
405 |
+
inpainting_padding,
|
406 |
+
current_image,
|
407 |
+
num_outpainting_steps,
|
408 |
+
out_config,
|
409 |
+
mask_width,
|
410 |
+
mask_height,
|
411 |
+
custom_exit_image,
|
412 |
+
)
|
413 |
+
all_frames.append(
|
414 |
+
do_upscaleImg(main_frames[0], upscale_do, upscaler_name, upscale_by)
|
415 |
+
if upscale_do
|
416 |
+
else main_frames[0]
|
417 |
+
)
|
418 |
+
for i in range(len(main_frames) - 1):
|
419 |
+
# interpolation steps between 2 inpainted images (=sequential zoom and crop)
|
420 |
+
for j in range(num_interpol_frames - 1):
|
421 |
+
current_image = main_frames[i + 1]
|
422 |
+
interpol_image = current_image
|
423 |
+
save2Collect(interpol_image, out_config, f"interpol_img_{i}_{j}].png")
|
424 |
+
|
425 |
+
interpol_width = math.ceil(
|
426 |
+
(
|
427 |
+
1
|
428 |
+
- (1 - 2 * mask_width / width)
|
429 |
+
** (1 - (j + 1) / num_interpol_frames)
|
430 |
+
)
|
431 |
+
* width
|
432 |
+
/ 2
|
433 |
+
)
|
434 |
+
|
435 |
+
interpol_height = math.ceil(
|
436 |
+
(
|
437 |
+
1
|
438 |
+
- (1 - 2 * mask_height / height)
|
439 |
+
** (1 - (j + 1) / num_interpol_frames)
|
440 |
+
)
|
441 |
+
* height
|
442 |
+
/ 2
|
443 |
+
)
|
444 |
+
|
445 |
+
interpol_image = interpol_image.crop(
|
446 |
+
(
|
447 |
+
interpol_width,
|
448 |
+
interpol_height,
|
449 |
+
width - interpol_width,
|
450 |
+
height - interpol_height,
|
451 |
+
)
|
452 |
+
)
|
453 |
+
|
454 |
+
interpol_image = interpol_image.resize((width, height))
|
455 |
+
save2Collect(interpol_image, out_config, f"interpol_resize_{i}_{j}.png")
|
456 |
+
|
457 |
+
# paste the higher resolution previous image in the middle to avoid drop in quality caused by zooming
|
458 |
+
interpol_width2 = math.ceil(
|
459 |
+
(1 - (width - 2 * mask_width) / (width - 2 * interpol_width))
|
460 |
+
/ 2
|
461 |
+
* width
|
462 |
+
)
|
463 |
+
|
464 |
+
interpol_height2 = math.ceil(
|
465 |
+
(1 - (height - 2 * mask_height) / (height - 2 * interpol_height))
|
466 |
+
/ 2
|
467 |
+
* height
|
468 |
+
)
|
469 |
+
|
470 |
+
prev_image_fix_crop = shrink_and_paste_on_blank(
|
471 |
+
main_frames[i], interpol_width2, interpol_height2
|
472 |
+
)
|
473 |
+
|
474 |
+
interpol_image.paste(prev_image_fix_crop, mask=prev_image_fix_crop)
|
475 |
+
save2Collect(interpol_image, out_config, f"interpol_prevcrop_{i}_{j}.png")
|
476 |
+
|
477 |
+
if upscale_do and progress:
|
478 |
+
progress(((i + 1) / num_outpainting_steps), desc="upscaling interpol")
|
479 |
+
|
480 |
+
all_frames.append(
|
481 |
+
do_upscaleImg(interpol_image, upscale_do, upscaler_name, upscale_by)
|
482 |
+
if upscale_do
|
483 |
+
else interpol_image
|
484 |
+
)
|
485 |
+
|
486 |
+
if upscale_do and progress:
|
487 |
+
progress(((i + 1) / num_outpainting_steps), desc="upscaling current")
|
488 |
+
|
489 |
+
all_frames.append(
|
490 |
+
do_upscaleImg(current_image, upscale_do, upscaler_name, upscale_by)
|
491 |
+
if upscale_do
|
492 |
+
else current_image
|
493 |
+
)
|
494 |
+
|
495 |
+
frames2Collect(all_frames, out_config)
|
496 |
+
|
497 |
+
write_video(
|
498 |
+
out_config["video_filename"],
|
499 |
+
all_frames,
|
500 |
+
video_frame_rate,
|
501 |
+
video_zoom_mode,
|
502 |
+
int(video_start_frame_dupe_amount),
|
503 |
+
int(video_last_frame_dupe_amount),
|
504 |
+
)
|
505 |
+
print("Video saved in: " + os.path.join(script_path, out_config["video_filename"]))
|
506 |
+
return (
|
507 |
+
out_config["video_filename"],
|
508 |
+
main_frames,
|
509 |
+
processed.js(),
|
510 |
+
plaintext_to_html(processed.info),
|
511 |
+
plaintext_to_html(""),
|
512 |
+
)
|
infinite-zoom-automatic1111-webui/iz_helpers/sd_helpers.py
ADDED
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from modules.processing import (
|
2 |
+
process_images,
|
3 |
+
StableDiffusionProcessingTxt2Img,
|
4 |
+
StableDiffusionProcessingImg2Img,
|
5 |
+
)
|
6 |
+
import modules.shared as shared
|
7 |
+
|
8 |
+
|
9 |
+
def renderTxt2Img(
|
10 |
+
prompt, negative_prompt, sampler, steps, cfg_scale, seed, width, height
|
11 |
+
):
|
12 |
+
processed = None
|
13 |
+
p = StableDiffusionProcessingTxt2Img(
|
14 |
+
sd_model=shared.sd_model,
|
15 |
+
outpath_samples=shared.opts.outdir_txt2img_samples,
|
16 |
+
outpath_grids=shared.opts.outdir_txt2img_grids,
|
17 |
+
prompt=prompt,
|
18 |
+
negative_prompt=negative_prompt,
|
19 |
+
seed=seed,
|
20 |
+
sampler_name=sampler,
|
21 |
+
n_iter=1,
|
22 |
+
steps=steps,
|
23 |
+
cfg_scale=cfg_scale,
|
24 |
+
width=width,
|
25 |
+
height=height,
|
26 |
+
)
|
27 |
+
processed = process_images(p)
|
28 |
+
newseed = p.seed
|
29 |
+
return processed, newseed
|
30 |
+
|
31 |
+
|
32 |
+
def renderImg2Img(
|
33 |
+
prompt,
|
34 |
+
negative_prompt,
|
35 |
+
sampler,
|
36 |
+
steps,
|
37 |
+
cfg_scale,
|
38 |
+
seed,
|
39 |
+
width,
|
40 |
+
height,
|
41 |
+
init_image,
|
42 |
+
mask_image,
|
43 |
+
inpainting_denoising_strength,
|
44 |
+
inpainting_mask_blur,
|
45 |
+
inpainting_fill_mode,
|
46 |
+
inpainting_full_res,
|
47 |
+
inpainting_padding,
|
48 |
+
):
|
49 |
+
processed = None
|
50 |
+
|
51 |
+
p = StableDiffusionProcessingImg2Img(
|
52 |
+
sd_model=shared.sd_model,
|
53 |
+
outpath_samples=shared.opts.outdir_img2img_samples,
|
54 |
+
outpath_grids=shared.opts.outdir_img2img_grids,
|
55 |
+
prompt=prompt,
|
56 |
+
negative_prompt=negative_prompt,
|
57 |
+
seed=seed,
|
58 |
+
sampler_name=sampler,
|
59 |
+
n_iter=1,
|
60 |
+
steps=steps,
|
61 |
+
cfg_scale=cfg_scale,
|
62 |
+
width=width,
|
63 |
+
height=height,
|
64 |
+
init_images=[init_image],
|
65 |
+
denoising_strength=inpainting_denoising_strength,
|
66 |
+
mask_blur=inpainting_mask_blur,
|
67 |
+
inpainting_fill=inpainting_fill_mode,
|
68 |
+
inpaint_full_res=inpainting_full_res,
|
69 |
+
inpaint_full_res_padding=inpainting_padding,
|
70 |
+
mask=mask_image,
|
71 |
+
)
|
72 |
+
# p.latent_mask = Image.new("RGB", (p.width, p.height), "white")
|
73 |
+
|
74 |
+
processed = process_images(p)
|
75 |
+
# For those that use Image grids this will make sure that ffmpeg does not crash out
|
76 |
+
if (len(processed.images) > 1) and (processed.images[0].size[0] != processed.images[-1].size[0]):
|
77 |
+
processed.images.pop(0)
|
78 |
+
print("\nGrid image detected applying patch")
|
79 |
+
|
80 |
+
newseed = p.seed
|
81 |
+
return processed, newseed
|
infinite-zoom-automatic1111-webui/iz_helpers/settings.py
ADDED
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import modules.shared as shared
|
3 |
+
from .static_variables import default_prompt
|
4 |
+
|
5 |
+
|
6 |
+
def on_ui_settings():
|
7 |
+
section = ("infinite-zoom", "Infinite Zoom")
|
8 |
+
|
9 |
+
shared.opts.add_option(
|
10 |
+
"infzoom_outpath",
|
11 |
+
shared.OptionInfo(
|
12 |
+
"outputs",
|
13 |
+
"Path where to store your infinite video. Default is Outputs",
|
14 |
+
gr.Textbox,
|
15 |
+
{"interactive": True},
|
16 |
+
section=section,
|
17 |
+
),
|
18 |
+
)
|
19 |
+
|
20 |
+
shared.opts.add_option(
|
21 |
+
"infzoom_outSUBpath",
|
22 |
+
shared.OptionInfo(
|
23 |
+
"infinite-zooms",
|
24 |
+
"Which subfolder name to be created in the outpath. Default is 'infinite-zooms'",
|
25 |
+
gr.Textbox,
|
26 |
+
{"interactive": True},
|
27 |
+
section=section,
|
28 |
+
),
|
29 |
+
)
|
30 |
+
|
31 |
+
shared.opts.add_option(
|
32 |
+
"infzoom_outsizeW",
|
33 |
+
shared.OptionInfo(
|
34 |
+
512,
|
35 |
+
"Default width of your video",
|
36 |
+
gr.Slider,
|
37 |
+
{"minimum": 16, "maximum": 2048, "step": 16},
|
38 |
+
section=section,
|
39 |
+
),
|
40 |
+
)
|
41 |
+
|
42 |
+
shared.opts.add_option(
|
43 |
+
"infzoom_outsizeH",
|
44 |
+
shared.OptionInfo(
|
45 |
+
512,
|
46 |
+
"Default height your video",
|
47 |
+
gr.Slider,
|
48 |
+
{"minimum": 16, "maximum": 2048, "step": 16},
|
49 |
+
section=section,
|
50 |
+
),
|
51 |
+
)
|
52 |
+
|
53 |
+
shared.opts.add_option(
|
54 |
+
"infzoom_ffprobepath",
|
55 |
+
shared.OptionInfo(
|
56 |
+
"",
|
57 |
+
"Writing videos has dependency to an existing FFPROBE executable on your machine. D/L here (https://github.com/BtbN/FFmpeg-Builds/releases) your OS variant and point to your installation path",
|
58 |
+
gr.Textbox,
|
59 |
+
{"interactive": True},
|
60 |
+
section=section,
|
61 |
+
),
|
62 |
+
)
|
63 |
+
|
64 |
+
shared.opts.add_option(
|
65 |
+
"infzoom_txt2img_model",
|
66 |
+
shared.OptionInfo(
|
67 |
+
None,
|
68 |
+
"Name of your desired model to render keyframes (txt2img)",
|
69 |
+
gr.Dropdown,
|
70 |
+
lambda: {"choices": [x for x in list(shared.list_checkpoint_tiles()) if "inpainting" not in x]},
|
71 |
+
section=section,
|
72 |
+
),
|
73 |
+
)
|
74 |
+
|
75 |
+
shared.opts.add_option(
|
76 |
+
"infzoom_inpainting_model",
|
77 |
+
shared.OptionInfo(
|
78 |
+
None,
|
79 |
+
"Name of your desired inpaint model (img2img-inpaint). Default is vanilla sd-v1-5-inpainting.ckpt ",
|
80 |
+
gr.Dropdown,
|
81 |
+
lambda: {"choices": [x for x in list(shared.list_checkpoint_tiles()) if "inpainting" in x]},
|
82 |
+
section=section,
|
83 |
+
),
|
84 |
+
)
|
85 |
+
|
86 |
+
shared.opts.add_option(
|
87 |
+
"infzoom_defPrompt",
|
88 |
+
shared.OptionInfo(
|
89 |
+
default_prompt,
|
90 |
+
"Default prompt-setup to start with'",
|
91 |
+
gr.Code,
|
92 |
+
{"interactive": True, "language": "json"},
|
93 |
+
section=section,
|
94 |
+
),
|
95 |
+
)
|
96 |
+
|
97 |
+
|
98 |
+
shared.opts.add_option(
|
99 |
+
"infzoom_collectAllResources",
|
100 |
+
shared.OptionInfo(
|
101 |
+
False,
|
102 |
+
"!!! Store all images (txt2img, init_image,exit_image, inpainting, interpolation) into one folder in your OUTPUT Path. Very slow, a lot of data. Dont do this on long runs !!!",
|
103 |
+
gr.Checkbox,
|
104 |
+
{"interactive": True},
|
105 |
+
section=section,
|
106 |
+
),
|
107 |
+
)
|
108 |
+
|
infinite-zoom-automatic1111-webui/iz_helpers/static_variables.py
ADDED
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from modules import scripts
|
3 |
+
import modules.sd_samplers
|
4 |
+
|
5 |
+
default_sampling_steps = 35
|
6 |
+
default_sampler = "DDIM"
|
7 |
+
default_cfg_scale = 8
|
8 |
+
default_mask_blur = 48
|
9 |
+
default_total_outpaints = 5
|
10 |
+
promptTableHeaders = ["Start at second [0,1,...]", "Prompt"]
|
11 |
+
|
12 |
+
default_prompt = """
|
13 |
+
{
|
14 |
+
"prePrompt": "Huge spectacular Waterfall in ",
|
15 |
+
"prompts": {
|
16 |
+
"data": [
|
17 |
+
[0, "a dense tropical forest"],
|
18 |
+
[2, "a Lush jungle"],
|
19 |
+
[3, "a Thick rainforest"],
|
20 |
+
[5, "a Verdant canopy"]
|
21 |
+
]
|
22 |
+
},
|
23 |
+
"postPrompt": "epic perspective,(vegetation overgrowth:1.3)(intricate, ornamentation:1.1),(baroque:1.1), fantasy, (realistic:1) digital painting , (magical,mystical:1.2) , (wide angle shot:1.4), (landscape composed:1.2)(medieval:1.1),(tropical forest:1.4),(river:1.3) volumetric lighting ,epic, style by Alex Horley Wenjun Lin greg rutkowski Ruan Jia (Wayne Barlowe:1.2)",
|
24 |
+
"negPrompt": "frames, border, edges, borderline, text, character, duplicate, error, out of frame, watermark, low quality, ugly, deformed, blur, bad-artist"
|
25 |
+
}
|
26 |
+
"""
|
27 |
+
|
28 |
+
empty_prompt = '{"prompts":{"data":[],"negPrompt":"", prePrompt:"", postPrompt:""}'
|
29 |
+
|
30 |
+
invalid_prompt = {
|
31 |
+
"prompts": {
|
32 |
+
"data": [[0, "Your prompt-json is invalid, please check Settings"]],
|
33 |
+
},
|
34 |
+
"negPrompt": "Invalid prompt-json",
|
35 |
+
"prePrompt": "Invalid prompt",
|
36 |
+
"postPrompt": "Invalid prompt",
|
37 |
+
}
|
38 |
+
|
39 |
+
available_samplers = [
|
40 |
+
s.name for s in modules.sd_samplers.samplers if "UniPc" not in s.name
|
41 |
+
]
|
42 |
+
|
43 |
+
current_script_dir = scripts.basedir().split(os.sep)[
|
44 |
+
-2:
|
45 |
+
] # contains install and our extension foldername
|
46 |
+
jsonprompt_schemafile = (
|
47 |
+
current_script_dir[0]
|
48 |
+
+ "/"
|
49 |
+
+ current_script_dir[1]
|
50 |
+
+ "/iz_helpers/promptschema.json"
|
51 |
+
)
|
infinite-zoom-automatic1111-webui/iz_helpers/ui.py
ADDED
@@ -0,0 +1,307 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
from .run import create_zoom
|
3 |
+
import modules.shared as shared
|
4 |
+
from webui import wrap_gradio_gpu_call
|
5 |
+
from modules.ui import create_output_panel
|
6 |
+
|
7 |
+
from .static_variables import (
|
8 |
+
default_prompt,
|
9 |
+
available_samplers,
|
10 |
+
default_total_outpaints,
|
11 |
+
default_sampling_steps,
|
12 |
+
default_cfg_scale,
|
13 |
+
default_mask_blur,
|
14 |
+
default_sampler,
|
15 |
+
)
|
16 |
+
from .helpers import putPrompts, clearPrompts
|
17 |
+
from .prompt_util import readJsonPrompt
|
18 |
+
from .static_variables import promptTableHeaders
|
19 |
+
|
20 |
+
|
21 |
+
def on_ui_tabs():
|
22 |
+
with gr.Blocks(analytics_enabled=False) as infinite_zoom_interface:
|
23 |
+
gr.HTML(
|
24 |
+
"""
|
25 |
+
<p style="text-align: center;">
|
26 |
+
<a target="_blank" href="https://github.com/v8hid/infinite-zoom-automatic1111-webui"><img src="https://img.shields.io/static/v1?label=github&message=repository&color=blue&style=flat&logo=github&logoColor=white" style="display: inline;" alt="GitHub Repo"/></a>
|
27 |
+
<a href="https://discord.gg/v2nHqSrWdW"><img src="https://img.shields.io/discord/1095469311830806630?color=blue&label=discord&logo=discord&logoColor=white" style="display: inline;" alt="Discord server"></a>
|
28 |
+
</p>
|
29 |
+
|
30 |
+
"""
|
31 |
+
)
|
32 |
+
with gr.Row():
|
33 |
+
generate_btn = gr.Button(value="Generate video", variant="primary")
|
34 |
+
interrupt = gr.Button(value="Interrupt", elem_id="interrupt_training")
|
35 |
+
with gr.Row():
|
36 |
+
with gr.Column(scale=1, variant="panel"):
|
37 |
+
with gr.Tab("Main"):
|
38 |
+
with gr.Row():
|
39 |
+
batchcount_slider = gr.Slider(
|
40 |
+
minimum=1,
|
41 |
+
maximum=25,
|
42 |
+
value=shared.opts.data.get("infzoom_batchcount", 1),
|
43 |
+
step=1,
|
44 |
+
label="Batch Count",
|
45 |
+
)
|
46 |
+
main_outpaint_steps = gr.Number(
|
47 |
+
label="Total video length [s]",
|
48 |
+
value=default_total_outpaints,
|
49 |
+
precision=0,
|
50 |
+
interactive=True,
|
51 |
+
)
|
52 |
+
|
53 |
+
# safe reading json prompt
|
54 |
+
pr = shared.opts.data.get("infzoom_defPrompt", default_prompt)
|
55 |
+
jpr = readJsonPrompt(pr, True)
|
56 |
+
|
57 |
+
main_common_prompt_pre = gr.Textbox(
|
58 |
+
value=jpr["prePrompt"], label="Common Prompt Prefix"
|
59 |
+
)
|
60 |
+
|
61 |
+
main_prompts = gr.Dataframe(
|
62 |
+
type="array",
|
63 |
+
headers=promptTableHeaders,
|
64 |
+
datatype=["number", "str"],
|
65 |
+
row_count=1,
|
66 |
+
col_count=(2, "fixed"),
|
67 |
+
value=jpr["prompts"],
|
68 |
+
wrap=True,
|
69 |
+
)
|
70 |
+
|
71 |
+
main_common_prompt_suf = gr.Textbox(
|
72 |
+
value=jpr["postPrompt"], label="Common Prompt Suffix"
|
73 |
+
)
|
74 |
+
|
75 |
+
main_negative_prompt = gr.Textbox(
|
76 |
+
value=jpr["negPrompt"], label="Negative Prompt"
|
77 |
+
)
|
78 |
+
|
79 |
+
# these button will be moved using JS under the dataframe view as small ones
|
80 |
+
exportPrompts_button = gr.Button(
|
81 |
+
value="Export prompts",
|
82 |
+
variant="secondary",
|
83 |
+
elem_classes="sm infzoom_tab_butt",
|
84 |
+
elem_id="infzoom_exP_butt",
|
85 |
+
)
|
86 |
+
importPrompts_button = gr.UploadButton(
|
87 |
+
label="Import prompts",
|
88 |
+
variant="secondary",
|
89 |
+
elem_classes="sm infzoom_tab_butt",
|
90 |
+
elem_id="infzoom_imP_butt",
|
91 |
+
)
|
92 |
+
exportPrompts_button.click(
|
93 |
+
None,
|
94 |
+
_js="exportPrompts",
|
95 |
+
inputs=[
|
96 |
+
main_common_prompt_pre,
|
97 |
+
main_prompts,
|
98 |
+
main_common_prompt_suf,
|
99 |
+
main_negative_prompt,
|
100 |
+
],
|
101 |
+
outputs=None,
|
102 |
+
)
|
103 |
+
importPrompts_button.upload(
|
104 |
+
fn=putPrompts,
|
105 |
+
outputs=[
|
106 |
+
main_common_prompt_pre,
|
107 |
+
main_prompts,
|
108 |
+
main_common_prompt_suf,
|
109 |
+
main_negative_prompt,
|
110 |
+
],
|
111 |
+
inputs=[importPrompts_button],
|
112 |
+
)
|
113 |
+
|
114 |
+
clearPrompts_button = gr.Button(
|
115 |
+
value="Clear prompts",
|
116 |
+
variant="secondary",
|
117 |
+
elem_classes="sm infzoom_tab_butt",
|
118 |
+
elem_id="infzoom_clP_butt",
|
119 |
+
)
|
120 |
+
clearPrompts_button.click(
|
121 |
+
fn=clearPrompts,
|
122 |
+
inputs=[],
|
123 |
+
outputs=[
|
124 |
+
main_prompts,
|
125 |
+
main_negative_prompt,
|
126 |
+
main_common_prompt_pre,
|
127 |
+
main_common_prompt_suf,
|
128 |
+
],
|
129 |
+
)
|
130 |
+
|
131 |
+
with gr.Accordion("Render settings"):
|
132 |
+
with gr.Row():
|
133 |
+
seed = gr.Number(
|
134 |
+
label="Seed", value=-1, precision=0, interactive=True
|
135 |
+
)
|
136 |
+
main_sampler = gr.Dropdown(
|
137 |
+
label="Sampler",
|
138 |
+
choices=available_samplers,
|
139 |
+
value=default_sampler,
|
140 |
+
type="value",
|
141 |
+
)
|
142 |
+
with gr.Row():
|
143 |
+
main_width = gr.Slider(
|
144 |
+
minimum=16,
|
145 |
+
maximum=2048,
|
146 |
+
value=shared.opts.data.get("infzoom_outsizeW", 512),
|
147 |
+
step=16,
|
148 |
+
label="Output Width",
|
149 |
+
)
|
150 |
+
main_height = gr.Slider(
|
151 |
+
minimum=16,
|
152 |
+
maximum=2048,
|
153 |
+
value=shared.opts.data.get("infzoom_outsizeH", 512),
|
154 |
+
step=16,
|
155 |
+
label="Output Height",
|
156 |
+
)
|
157 |
+
with gr.Row():
|
158 |
+
main_guidance_scale = gr.Slider(
|
159 |
+
minimum=0.1,
|
160 |
+
maximum=15,
|
161 |
+
step=0.1,
|
162 |
+
value=default_cfg_scale,
|
163 |
+
label="Guidance Scale",
|
164 |
+
)
|
165 |
+
sampling_step = gr.Slider(
|
166 |
+
minimum=1,
|
167 |
+
maximum=150,
|
168 |
+
step=1,
|
169 |
+
value=default_sampling_steps,
|
170 |
+
label="Sampling Steps for each outpaint",
|
171 |
+
)
|
172 |
+
with gr.Row():
|
173 |
+
init_image = gr.Image(
|
174 |
+
type="pil", label="Custom initial image"
|
175 |
+
)
|
176 |
+
exit_image = gr.Image(
|
177 |
+
type="pil", label="Custom exit image", visible=False
|
178 |
+
)
|
179 |
+
with gr.Tab("Video"):
|
180 |
+
video_frame_rate = gr.Slider(
|
181 |
+
label="Frames per second",
|
182 |
+
value=30,
|
183 |
+
minimum=1,
|
184 |
+
maximum=60,
|
185 |
+
)
|
186 |
+
video_zoom_mode = gr.Radio(
|
187 |
+
label="Zoom mode",
|
188 |
+
choices=["Zoom-out", "Zoom-in"],
|
189 |
+
value="Zoom-out",
|
190 |
+
type="index",
|
191 |
+
)
|
192 |
+
video_start_frame_dupe_amount = gr.Slider(
|
193 |
+
label="number of start frame dupe",
|
194 |
+
info="Frames to freeze at the start of the video",
|
195 |
+
value=0,
|
196 |
+
minimum=1,
|
197 |
+
maximum=60,
|
198 |
+
)
|
199 |
+
video_last_frame_dupe_amount = gr.Slider(
|
200 |
+
label="number of last frame dupe",
|
201 |
+
info="Frames to freeze at the end of the video",
|
202 |
+
value=0,
|
203 |
+
minimum=1,
|
204 |
+
maximum=60,
|
205 |
+
)
|
206 |
+
video_zoom_speed = gr.Slider(
|
207 |
+
label="Zoom Speed",
|
208 |
+
value=1.0,
|
209 |
+
minimum=0.1,
|
210 |
+
maximum=20.0,
|
211 |
+
step=0.1,
|
212 |
+
info="Zoom speed in seconds (higher values create slower zoom)",
|
213 |
+
)
|
214 |
+
|
215 |
+
with gr.Tab("Outpaint"):
|
216 |
+
inpainting_mask_blur = gr.Slider(
|
217 |
+
label="Mask Blur",
|
218 |
+
minimum=0,
|
219 |
+
maximum=64,
|
220 |
+
value=default_mask_blur,
|
221 |
+
)
|
222 |
+
inpainting_fill_mode = gr.Radio(
|
223 |
+
label="Masked content",
|
224 |
+
choices=["fill", "original", "latent noise", "latent nothing"],
|
225 |
+
value="latent noise",
|
226 |
+
type="index",
|
227 |
+
)
|
228 |
+
|
229 |
+
with gr.Tab("Post proccess"):
|
230 |
+
upscale_do = gr.Checkbox(False, label="Enable Upscale")
|
231 |
+
upscaler_name = gr.Dropdown(
|
232 |
+
label="Upscaler",
|
233 |
+
elem_id="infZ_upscaler",
|
234 |
+
choices=[x.name for x in shared.sd_upscalers],
|
235 |
+
value=shared.sd_upscalers[0].name,
|
236 |
+
)
|
237 |
+
upscale_by = gr.Slider(
|
238 |
+
label="Upscale by factor",
|
239 |
+
minimum=1,
|
240 |
+
maximum=8,
|
241 |
+
step=0.5,
|
242 |
+
value=2,
|
243 |
+
)
|
244 |
+
with gr.Accordion("Help", open=False):
|
245 |
+
gr.Markdown(
|
246 |
+
"""# Performance critical
|
247 |
+
Depending on amount of frames and which upscaler you choose it might took a long time to render.
|
248 |
+
Our best experience and trade-off is the R-ERSGAn4x upscaler.
|
249 |
+
"""
|
250 |
+
)
|
251 |
+
|
252 |
+
with gr.Column(scale=1, variant="compact"):
|
253 |
+
output_video = gr.Video(label="Output").style(width=512, height=512)
|
254 |
+
(
|
255 |
+
out_image,
|
256 |
+
generation_info,
|
257 |
+
html_info,
|
258 |
+
html_log,
|
259 |
+
) = create_output_panel(
|
260 |
+
"infinite-zoom", shared.opts.outdir_img2img_samples
|
261 |
+
)
|
262 |
+
|
263 |
+
generate_btn.click(
|
264 |
+
fn=wrap_gradio_gpu_call(create_zoom, extra_outputs=[None, "", ""]),
|
265 |
+
inputs=[
|
266 |
+
main_common_prompt_pre,
|
267 |
+
main_prompts,
|
268 |
+
main_common_prompt_suf,
|
269 |
+
main_negative_prompt,
|
270 |
+
main_outpaint_steps,
|
271 |
+
main_guidance_scale,
|
272 |
+
sampling_step,
|
273 |
+
init_image,
|
274 |
+
exit_image,
|
275 |
+
video_frame_rate,
|
276 |
+
video_zoom_mode,
|
277 |
+
video_start_frame_dupe_amount,
|
278 |
+
video_last_frame_dupe_amount,
|
279 |
+
inpainting_mask_blur,
|
280 |
+
inpainting_fill_mode,
|
281 |
+
video_zoom_speed,
|
282 |
+
seed,
|
283 |
+
main_width,
|
284 |
+
main_height,
|
285 |
+
batchcount_slider,
|
286 |
+
main_sampler,
|
287 |
+
upscale_do,
|
288 |
+
upscaler_name,
|
289 |
+
upscale_by,
|
290 |
+
],
|
291 |
+
outputs=[output_video, out_image, generation_info, html_info, html_log],
|
292 |
+
)
|
293 |
+
|
294 |
+
main_prompts.change(
|
295 |
+
fn=checkPrompts, inputs=[main_prompts], outputs=[generate_btn]
|
296 |
+
)
|
297 |
+
|
298 |
+
interrupt.click(fn=lambda: shared.state.interrupt(), inputs=[], outputs=[])
|
299 |
+
infinite_zoom_interface.queue()
|
300 |
+
return [(infinite_zoom_interface, "Infinite Zoom", "iz_interface")]
|
301 |
+
|
302 |
+
|
303 |
+
def checkPrompts(p):
|
304 |
+
return gr.Button.update(
|
305 |
+
interactive=any(0 in sublist for sublist in p)
|
306 |
+
or any("0" in sublist for sublist in p)
|
307 |
+
)
|
infinite-zoom-automatic1111-webui/iz_helpers/video.py
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
import imageio
|
3 |
+
from PIL import Image
|
4 |
+
|
5 |
+
def write_video(file_path, frames, fps, reversed=True, start_frame_dupe_amount=15, last_frame_dupe_amount=30):
|
6 |
+
"""
|
7 |
+
Writes frames to an mp4 video file
|
8 |
+
:param file_path: Path to output video, must end with .mp4
|
9 |
+
:param frames: List of PIL.Image objects
|
10 |
+
:param fps: Desired frame rate
|
11 |
+
:param reversed: if order of images to be reversed (default = True)
|
12 |
+
"""
|
13 |
+
if reversed == True:
|
14 |
+
frames = frames[::-1]
|
15 |
+
|
16 |
+
# Drop missformed frames
|
17 |
+
frames = [frame for frame in frames if frame.size == frames[0].size]
|
18 |
+
|
19 |
+
# Create an imageio video writer, avoid block size of 512.
|
20 |
+
writer = imageio.get_writer(file_path, fps=fps, macro_block_size=None)
|
21 |
+
|
22 |
+
# Duplicate the start and end frames
|
23 |
+
start_frames = [frames[0]] * start_frame_dupe_amount
|
24 |
+
end_frames = [frames[-1]] * last_frame_dupe_amount
|
25 |
+
|
26 |
+
# Write the duplicated frames to the video writer
|
27 |
+
for frame in start_frames:
|
28 |
+
# Convert PIL image to numpy array
|
29 |
+
np_frame = np.array(frame)
|
30 |
+
writer.append_data(np_frame)
|
31 |
+
|
32 |
+
# Write the frames to the video writer
|
33 |
+
for frame in frames:
|
34 |
+
np_frame = np.array(frame)
|
35 |
+
writer.append_data(np_frame)
|
36 |
+
|
37 |
+
# Write the duplicated frames to the video writer
|
38 |
+
for frame in end_frames:
|
39 |
+
np_frame = np.array(frame)
|
40 |
+
writer.append_data(np_frame)
|
41 |
+
|
42 |
+
# Close the video writer
|
43 |
+
writer.close()
|
infinite-zoom-automatic1111-webui/javascript/infinite-zoom-hints.js
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// mouseover tooltips for various UI elements
|
2 |
+
|
3 |
+
infzoom_titles = {
|
4 |
+
"Batch Count":"How many separate videos to create",
|
5 |
+
"Total video length [s]":"For each seconds frame (FPS) will be generated. Define prompts at which time they should start wihtin this duration.",
|
6 |
+
"Common Prompt Prefix":"Prompt inserted before each step",
|
7 |
+
"Common Prompt Suffix":"Prompt inserted after each step",
|
8 |
+
"Negative Prompt":"What your model shall avoid",
|
9 |
+
"Export prompts": "Downloads a JSON file to save all prompts",
|
10 |
+
"Import prompts": "Restore Prompts table from a specific JSON file",
|
11 |
+
"Clear prompts": "Start over, remove all entries from prompt table, prefix, suffix, negative",
|
12 |
+
"Custom initial image":"An image at the end resp. begin of your movie, depending or ZoomIn or Out",
|
13 |
+
"Custom exit image":"An image at the end resp. begin of your movie, depending or ZoomIn or Out",
|
14 |
+
"Zoom Speed":"Varies additional frames per second",
|
15 |
+
"Start at second [0,1,...]": "At which time the prompt has to be occure. We need at least one prompt starting at time 0",
|
16 |
+
"Generate video": "Start rendering. If it´s disabled the prompt table is invalid, check we have a start prompt at time 0"
|
17 |
+
}
|
18 |
+
|
19 |
+
|
20 |
+
onUiUpdate(function(){
|
21 |
+
gradioApp().querySelectorAll('span, button, select, p').forEach(function(span){
|
22 |
+
tooltip = infzoom_titles[span.textContent];
|
23 |
+
|
24 |
+
if(!tooltip){
|
25 |
+
tooltip = infzoom_titles[span.value];
|
26 |
+
}
|
27 |
+
|
28 |
+
if(!tooltip){
|
29 |
+
for (const c of span.classList) {
|
30 |
+
if (c in infzoom_titles) {
|
31 |
+
tooltip = infzoom_titles[c];
|
32 |
+
break;
|
33 |
+
}
|
34 |
+
}
|
35 |
+
}
|
36 |
+
|
37 |
+
if(tooltip){
|
38 |
+
span.title = tooltip;
|
39 |
+
}
|
40 |
+
})
|
41 |
+
|
42 |
+
gradioApp().querySelectorAll('select').forEach(function(select){
|
43 |
+
if (select.onchange != null) return;
|
44 |
+
|
45 |
+
select.onchange = function(){
|
46 |
+
select.title = infzoom_titles[select.value] || "";
|
47 |
+
}
|
48 |
+
})
|
49 |
+
})
|
infinite-zoom-automatic1111-webui/javascript/infinite-zoom.js
ADDED
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// Function to download data to a file
|
2 |
+
function exportPrompts(cppre,p, cpsuf,np, filename = "infinite-zoom-prompts.json") {
|
3 |
+
|
4 |
+
let J = { prompts: p, negPrompt: np, prePrompt: cppre, postPrompt: cpsuf }
|
5 |
+
|
6 |
+
var file = new Blob([JSON.stringify(J,null,2)], { type: "text/csv" });
|
7 |
+
if (window.navigator.msSaveOrOpenBlob) // IE10+
|
8 |
+
window.navigator.msSaveOrOpenBlob(file, filename);
|
9 |
+
else { // Others
|
10 |
+
var a = document.createElement("a"),
|
11 |
+
url = URL.createObjectURL(file);
|
12 |
+
a.href = url;
|
13 |
+
a.download = filename;
|
14 |
+
document.body.appendChild(a);
|
15 |
+
a.click();
|
16 |
+
setTimeout(function () {
|
17 |
+
document.body.removeChild(a);
|
18 |
+
window.URL.revokeObjectURL(url);
|
19 |
+
}, 0);
|
20 |
+
}
|
21 |
+
}
|
22 |
+
|
23 |
+
document.addEventListener("DOMContentLoaded", () => {
|
24 |
+
const onload = () => {
|
25 |
+
|
26 |
+
if (typeof gradioApp === "function") {
|
27 |
+
/* move big buttons directly under the tabl of prompts as SMall ones */
|
28 |
+
const wrap = gradioApp().querySelector("#tab_iz_interface .gradio-dataframe .controls-wrap")
|
29 |
+
|
30 |
+
if (wrap) {
|
31 |
+
let butts = gradioApp().querySelectorAll("#tab_iz_interface .infzoom_tab_butt")
|
32 |
+
butts.forEach(b => {
|
33 |
+
wrap.appendChild(b)
|
34 |
+
b.classList.replace("lg", "sm") // smallest
|
35 |
+
});
|
36 |
+
}
|
37 |
+
else {
|
38 |
+
setTimeout(onload, 2000);
|
39 |
+
}
|
40 |
+
}
|
41 |
+
else {
|
42 |
+
setTimeout(onload, 2000);
|
43 |
+
}
|
44 |
+
};
|
45 |
+
onload();
|
46 |
+
});
|
47 |
+
|
48 |
+
|
49 |
+
|
50 |
+
|
51 |
+
|
52 |
+
|
53 |
+
|
54 |
+
|
infinite-zoom-automatic1111-webui/scripts/__pycache__/infinite-zoom.cpython-310.pyc
ADDED
Binary file (382 Bytes). View file
|
|
infinite-zoom-automatic1111-webui/scripts/infinite-zoom.py
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from modules import script_callbacks
|
2 |
+
from iz_helpers.ui import on_ui_tabs
|
3 |
+
from iz_helpers.settings import on_ui_settings
|
4 |
+
script_callbacks.on_ui_tabs(on_ui_tabs)
|
5 |
+
script_callbacks.on_ui_settings(on_ui_settings)
|
infinite-zoom-automatic1111-webui/style.css
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#tab_iz_interface .gradio-dataframe .controls-wrap {
|
2 |
+
flex-direction: row-reverse;
|
3 |
+
justify-content: space-between;
|
4 |
+
}
|
5 |
+
|
6 |
+
/* first column min width */
|
7 |
+
#tab_iz_interface th:first-child {
|
8 |
+
flex: 0 0 0%;
|
9 |
+
width: 0;
|
10 |
+
}
|
sd-webui-cutoff/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
__pycache__
|
sd-webui-cutoff/LICENSE
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
MIT License
|
sd-webui-cutoff/README.md
ADDED
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Cutoff - Cutting Off Prompt Effect
|
2 |
+
|
3 |
+
![cover](./images/cover.jpg)
|
4 |
+
|
5 |
+
<details>
|
6 |
+
<summary>Update Info</summary>
|
7 |
+
|
8 |
+
Upper is newer.
|
9 |
+
|
10 |
+
<dl>
|
11 |
+
<dt>20e87ce264338b824296b7559679ed1bb0bdacd7</dt>
|
12 |
+
<dd>Skip empty targets.</dd>
|
13 |
+
<dt>03bfe60162ba418e18dbaf8f1b9711fd62195ef3</dt>
|
14 |
+
<dd>Add <code>Disable for Negative prompt</code> option. Default is <code>True</code>.</dd>
|
15 |
+
<dt>f0990088fed0f5013a659cacedb194313a398860</dt>
|
16 |
+
<dd>Accept an empty prompt.</dd>
|
17 |
+
</dl>
|
18 |
+
</details>
|
19 |
+
|
20 |
+
## What is this?
|
21 |
+
|
22 |
+
This is an extension for [stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui) which limits the tokens' influence scope.
|
23 |
+
|
24 |
+
## Usage
|
25 |
+
|
26 |
+
1. Select `Enabled` checkbox.
|
27 |
+
2. Input words which you want to limit scope in `Target tokens`.
|
28 |
+
3. Generate images.
|
29 |
+
|
30 |
+
## Note
|
31 |
+
|
32 |
+
If the generated image was corrupted or something like that, try to change the `Weight` value or change the interpolation method to `SLerp`. Interpolation method can be found in `Details`.
|
33 |
+
|
34 |
+
### `Details` section
|
35 |
+
|
36 |
+
<dl>
|
37 |
+
<dt>Disable for Negative prompt.</dt>
|
38 |
+
<dd>If enabled, <b>Cutoff</b> will not work for the negative prompt. Default is <code>true</code>.</dd>
|
39 |
+
<dt>Cutoff strongly.</dt>
|
40 |
+
<dd>See <a href="#how-it-works">description below</a>. Default is <code>false</code>.</dd>
|
41 |
+
<dt>Interpolation method</dt>
|
42 |
+
<dd>How "padded" and "original" vectors will be interpolated. Default is <code>Lerp</code>.</dd>
|
43 |
+
<dt>Padding token</dt>
|
44 |
+
<dd>What token will be padded instead of <code>Target tokens</code>. Default is <code>_</code> (underbar).</dd>
|
45 |
+
</dl>
|
46 |
+
|
47 |
+
## Examples
|
48 |
+
|
49 |
+
```
|
50 |
+
7th_anime_v3_A-fp16 / kl-f8-anime2 / DPM++ 2M Karras / 15 steps / 512x768
|
51 |
+
Prompt: a cute girl, white shirt with green tie, red shoes, blue hair, yellow eyes, pink skirt
|
52 |
+
Negative Prompt: (low quality, worst quality:1.4), nsfw
|
53 |
+
Target tokens: white, green, red, blue, yellow, pink
|
54 |
+
```
|
55 |
+
|
56 |
+
Sample 1.
|
57 |
+
|
58 |
+
![sample 1](./images/sample-1.png)
|
59 |
+
|
60 |
+
Sample 2. (use `SLerp` for interpolation)
|
61 |
+
|
62 |
+
![sample 2](./images/sample-2.png)
|
63 |
+
|
64 |
+
Sample 3.
|
65 |
+
|
66 |
+
![sample 3](./images/sample-3.png)
|
67 |
+
|
68 |
+
## How it works
|
69 |
+
|
70 |
+
- [Japanese](#japanese)
|
71 |
+
- [English](#english)
|
72 |
+
|
73 |
+
or see [#5](https://github.com/hnmr293/sd-webui-cutoff/issues/5).
|
74 |
+
|
75 |
+
![idea](./images/idea.png)
|
76 |
+
|
77 |
+
### Japanese
|
78 |
+
|
79 |
+
プロンプトをCLIPに通して得られる (77, 768) 次元の埋め込み表現(?正式な用語は分かりません)について、
|
80 |
+
ごく単純には、77個の行ベクトルはプロンプト中の75個のトークン(+開始トークン+終了トークン)に対応していると考えられる。
|
81 |
+
|
82 |
+
※上図は作図上、この説明とは行と列を入れ替えて描いている。
|
83 |
+
|
84 |
+
このベクトルには単語単体の意味だけではなく、文章全体の、例えば係り結びなどの情報を集約したものが入っているはずである。
|
85 |
+
|
86 |
+
ここで `a cute girl, pink hair, red shoes` というプロンプトを考える。
|
87 |
+
普通、こういったプロンプトの意図は
|
88 |
+
|
89 |
+
1. `pink` は `hair` だけに係っており `shoes` には係っていない。
|
90 |
+
2. 同様に `red` も `hair` には係っていない。
|
91 |
+
3. `a cute girl` は全体に係っていて欲しい。`hair` や `shoes` は女の子に合うものが出て欲しい。
|
92 |
+
|
93 |
+
……というもののはずである。
|
94 |
+
|
95 |
+
しかしながら、[EvViz2](https://github.com/hnmr293/sd-webui-evviz2) などでトークン間の関係を見ると、そううまくはいっていないことが多い。
|
96 |
+
つまり、`shoes` の位置のベクトルに `pink` の影響が出てしまっていたりする。
|
97 |
+
|
98 |
+
一方で上述の通り `a cute girl` の影響は乗っていて欲しいわけで、どうにかして、特定のトークンの影響を取り除けるようにしたい。
|
99 |
+
|
100 |
+
この拡張では、指定されたトークンを *padding token* に書き換えることでそれを実現している。
|
101 |
+
|
102 |
+
たとえば `red shoes` の部分に対応して `a cute girl, _ hair, red shoes` というプロンプトを生成する。`red` と `shoes` に対応する位置のベクトルをここから生成したもので上書きしてやることで、`pink` の影響を除外している。
|
103 |
+
|
104 |
+
これを `pink` の側から見ると、自分の影響が `pink hair` の範囲内に制限されているように見える。What is this? の "limits the tokens' influence scope" はそういう意味。
|
105 |
+
|
106 |
+
ところで `a cute girl` の方は、`pink hair, red shoes` の影響を受けていてもいいし受けなくてもいいような気がする。
|
107 |
+
そこでこの拡張では、こういうどちらでもいいプロンプトに対して
|
108 |
+
|
109 |
+
1. `a cute girl, pink hair, red shoes`
|
110 |
+
2. `a cute girl, _ hair, _ shoes`
|
111 |
+
|
112 |
+
のどちらを適用するか選べるようにしている。`Details` の `Cutoff strongly` がそれで、オフのとき1.を、オンのとき2.を、それぞれ選ぶようになっている。
|
113 |
+
元絵に近いのが出るのはオフのとき。デフォルトもこちらにしてある。
|
114 |
+
|
115 |
+
### English
|
116 |
+
|
117 |
+
NB. The following text is a translation of the Japanese text above by [DeepL](https://www.deepl.com/translator).
|
118 |
+
|
119 |
+
For the (77, 768) dimensional embedded representation (I don't know the formal terminology), one could simply assume that the 77 row vectors correspond to the 75 tokens (+ start token and end token) in the prompt.
|
120 |
+
|
121 |
+
Note: The above figure is drawn with the rows and columns interchanged from this explanation.
|
122 |
+
|
123 |
+
This vector should contain not only the meanings of individual words, but also the aggregate information of the whole sentence, for example, the connection between words.
|
124 |
+
|
125 |
+
Consider the prompt `a cute girl, pink hair, red shoes`. Normally, the intent of such a prompt would be
|
126 |
+
|
127 |
+
- `pink` is only for `hair`, not `shoes`.
|
128 |
+
- Similarly, `red` does not refer to `hair`.
|
129 |
+
- We want `a cute girl` to be about the whole thing, and we want the `hair` and `shoes` to match the girl.
|
130 |
+
|
131 |
+
However, when we look at the relationship between tokens in [EvViz2](https://github.com/hnmr293/sd-webui-evviz2) and other tools, we see that it is not always that way. In other words, the position vector of the `shoes` may be affected by `pink`.
|
132 |
+
|
133 |
+
On the other hand, as mentioned above, we want the influence of `a cute girl` to be present, so we want to be able to somehow remove the influence of a specific token.
|
134 |
+
|
135 |
+
This extension achieves this by rewriting the specified tokens as a *padding token*.
|
136 |
+
|
137 |
+
For example, for the `red shoes` part, we generate the prompt `a cute girl, _ hair, red shoes`, and by overwriting the position vectors corresponding to `red` and `shoes` with those generated from this prompt, we remove the influence of `pink`.
|
138 |
+
|
139 |
+
From `pink`'s point of view, it appears that its influence is limited to the `pink hair`'s scope.
|
140 |
+
|
141 |
+
By the way, `a cute girl` may or may not be influenced by `pink hair` and `red shoes`. So, in this extension, for such a prompt that can be either
|
142 |
+
|
143 |
+
1. `a cute girl, pink hair, red shoes`
|
144 |
+
2. `a cute girl, _ hair, _ shoes`
|
145 |
+
|
146 |
+
The `Cutoff strongly` in the `Details` section allows you to select 1 when it is off and 2 when it is on. The one that comes out closer to the original image is "off". The default is also set this way.
|
sd-webui-cutoff/images/cover.jpg
ADDED
![]() |
Git LFS Details
|
sd-webui-cutoff/images/idea.png
ADDED
![]() |
sd-webui-cutoff/images/sample-1.png
ADDED
![]() |
Git LFS Details
|
sd-webui-cutoff/images/sample-2.png
ADDED
![]() |
Git LFS Details
|
sd-webui-cutoff/images/sample-3.png
ADDED
![]() |
Git LFS Details
|
sd-webui-cutoff/scripts/__pycache__/cutoff.cpython-310.pyc
ADDED
Binary file (6.86 kB). View file
|
|
sd-webui-cutoff/scripts/cutoff.py
ADDED
@@ -0,0 +1,257 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from collections import defaultdict
|
2 |
+
from typing import Union, List, Tuple
|
3 |
+
|
4 |
+
import numpy as np
|
5 |
+
import torch
|
6 |
+
from torch import Tensor, nn
|
7 |
+
import gradio as gr
|
8 |
+
|
9 |
+
from modules.processing import StableDiffusionProcessing
|
10 |
+
from modules import scripts
|
11 |
+
|
12 |
+
from scripts.cutofflib.sdhook import SDHook
|
13 |
+
from scripts.cutofflib.embedding import CLIP, generate_prompts, token_to_block
|
14 |
+
from scripts.cutofflib.utils import log, set_debug
|
15 |
+
from scripts.cutofflib.xyz import init_xyz
|
16 |
+
|
17 |
+
NAME = 'Cutoff'
|
18 |
+
PAD = '_</w>'
|
19 |
+
|
20 |
+
def check_neg(s: str, negative_prompt: str, all_negative_prompts: Union[List[str],None]):
|
21 |
+
if s == negative_prompt:
|
22 |
+
return True
|
23 |
+
|
24 |
+
if all_negative_prompts is not None:
|
25 |
+
return s in all_negative_prompts
|
26 |
+
|
27 |
+
return False
|
28 |
+
|
29 |
+
def slerp(t, v0, v1, DOT_THRESHOLD=0.9995):
|
30 |
+
# cf. https://memo.sugyan.com/entry/2022/09/09/230645
|
31 |
+
|
32 |
+
inputs_are_torch = False
|
33 |
+
input_device = v0.device
|
34 |
+
if not isinstance(v0, np.ndarray):
|
35 |
+
inputs_are_torch = True
|
36 |
+
v0 = v0.cpu().numpy()
|
37 |
+
v1 = v1.cpu().numpy()
|
38 |
+
|
39 |
+
dot = np.sum(v0 * v1 / (np.linalg.norm(v0) * np.linalg.norm(v1)))
|
40 |
+
if np.abs(dot) > DOT_THRESHOLD:
|
41 |
+
v2 = (1 - t) * v0 + t * v1
|
42 |
+
else:
|
43 |
+
theta_0 = np.arccos(dot)
|
44 |
+
sin_theta_0 = np.sin(theta_0)
|
45 |
+
theta_t = theta_0 * t
|
46 |
+
sin_theta_t = np.sin(theta_t)
|
47 |
+
s0 = np.sin(theta_0 - theta_t) / sin_theta_0
|
48 |
+
s1 = sin_theta_t / sin_theta_0
|
49 |
+
v2 = s0 * v0 + s1 * v1
|
50 |
+
|
51 |
+
if inputs_are_torch:
|
52 |
+
v2 = torch.from_numpy(v2).to(input_device)
|
53 |
+
|
54 |
+
return v2
|
55 |
+
|
56 |
+
|
57 |
+
class Hook(SDHook):
|
58 |
+
|
59 |
+
def __init__(
|
60 |
+
self,
|
61 |
+
enabled: bool,
|
62 |
+
targets: List[str],
|
63 |
+
padding: Union[str,int],
|
64 |
+
weight: float,
|
65 |
+
disable_neg: bool,
|
66 |
+
strong: bool,
|
67 |
+
interpolate: str,
|
68 |
+
):
|
69 |
+
super().__init__(enabled)
|
70 |
+
self.targets = targets
|
71 |
+
self.padding = padding
|
72 |
+
self.weight = float(weight)
|
73 |
+
self.disable_neg = disable_neg
|
74 |
+
self.strong = strong
|
75 |
+
self.intp = interpolate
|
76 |
+
|
77 |
+
def interpolate(self, t1: Tensor, t2: Tensor, w):
|
78 |
+
if self.intp == 'lerp':
|
79 |
+
return torch.lerp(t1, t2, w)
|
80 |
+
else:
|
81 |
+
return slerp(w, t1, t2)
|
82 |
+
|
83 |
+
def hook_clip(self, p: StableDiffusionProcessing, clip: nn.Module):
|
84 |
+
|
85 |
+
skip = False
|
86 |
+
|
87 |
+
def hook(mod: nn.Module, inputs: Tuple[List[str]], output: Tensor):
|
88 |
+
nonlocal skip
|
89 |
+
|
90 |
+
if skip:
|
91 |
+
# called from <A> below
|
92 |
+
return
|
93 |
+
|
94 |
+
assert isinstance(mod, CLIP)
|
95 |
+
|
96 |
+
prompts, *rest = inputs
|
97 |
+
assert len(prompts) == output.shape[0]
|
98 |
+
|
99 |
+
# Check wether we are processing Negative prompt or not.
|
100 |
+
# I firmly believe there is no one who uses a negative prompt
|
101 |
+
# exactly identical to a prompt.
|
102 |
+
if self.disable_neg:
|
103 |
+
if all(check_neg(x, p.negative_prompt, p.all_negative_prompts) for x in prompts):
|
104 |
+
# Now we are processing Negative prompt and skip it.
|
105 |
+
return
|
106 |
+
|
107 |
+
output = output.clone()
|
108 |
+
for pidx, prompt in enumerate(prompts):
|
109 |
+
tt = token_to_block(mod, prompt)
|
110 |
+
|
111 |
+
cutoff = generate_prompts(mod, prompt, self.targets, self.padding)
|
112 |
+
switch_base = np.full_like(cutoff.sw, self.strong)
|
113 |
+
switch = np.full_like(cutoff.sw, True)
|
114 |
+
active = cutoff.active_blocks()
|
115 |
+
|
116 |
+
prompt_to_tokens = defaultdict(lambda: [])
|
117 |
+
for tidx, (token, block_index) in enumerate(tt):
|
118 |
+
if block_index in active:
|
119 |
+
sw = switch.copy()
|
120 |
+
sw[block_index] = False
|
121 |
+
prompt = cutoff.text(sw)
|
122 |
+
else:
|
123 |
+
prompt = cutoff.text(switch_base)
|
124 |
+
prompt_to_tokens[prompt].append((tidx, token))
|
125 |
+
|
126 |
+
#log(prompt_to_tokens)
|
127 |
+
|
128 |
+
ks = list(prompt_to_tokens.keys())
|
129 |
+
if len(ks) == 0:
|
130 |
+
# without any (negative) prompts
|
131 |
+
ks.append('')
|
132 |
+
|
133 |
+
try:
|
134 |
+
# <A>
|
135 |
+
skip = True
|
136 |
+
vs = mod(ks)
|
137 |
+
finally:
|
138 |
+
skip = False
|
139 |
+
|
140 |
+
tensor = output[pidx, :, :] # e.g. (77, 768)
|
141 |
+
for k, t in zip(ks, vs):
|
142 |
+
assert tensor.shape == t.shape
|
143 |
+
for tidx, token in prompt_to_tokens[k]:
|
144 |
+
log(f'{tidx:03} {token.token:<16} {k}')
|
145 |
+
tensor[tidx, :] = self.interpolate(tensor[tidx,:], t[tidx,:], self.weight)
|
146 |
+
|
147 |
+
return output
|
148 |
+
|
149 |
+
self.hook_layer(clip, hook)
|
150 |
+
|
151 |
+
|
152 |
+
class Script(scripts.Script):
|
153 |
+
|
154 |
+
def __init__(self):
|
155 |
+
super().__init__()
|
156 |
+
self.last_hooker: Union[SDHook,None] = None
|
157 |
+
|
158 |
+
def title(self):
|
159 |
+
return NAME
|
160 |
+
|
161 |
+
def show(self, is_img2img):
|
162 |
+
return scripts.AlwaysVisible
|
163 |
+
|
164 |
+
def ui(self, is_img2img):
|
165 |
+
with gr.Accordion(NAME, open=False):
|
166 |
+
enabled = gr.Checkbox(label='Enabled', value=False)
|
167 |
+
targets = gr.Textbox(label='Target tokens (comma separated)', placeholder='red, blue')
|
168 |
+
weight = gr.Slider(minimum=-1.0, maximum=2.0, step=0.01, value=0.5, label='Weight')
|
169 |
+
with gr.Accordion('Details', open=False):
|
170 |
+
disable_neg = gr.Checkbox(value=True, label='Disable for Negative prompt.')
|
171 |
+
strong = gr.Checkbox(value=False, label='Cutoff strongly.')
|
172 |
+
padding = gr.Textbox(label='Padding token (ID or single token)')
|
173 |
+
lerp = gr.Radio(choices=['Lerp', 'SLerp'], value='Lerp', label='Interpolation method')
|
174 |
+
|
175 |
+
debug = gr.Checkbox(value=False, label='Debug log')
|
176 |
+
debug.change(fn=set_debug, inputs=[debug], outputs=[])
|
177 |
+
|
178 |
+
return [
|
179 |
+
enabled,
|
180 |
+
targets,
|
181 |
+
weight,
|
182 |
+
disable_neg,
|
183 |
+
strong,
|
184 |
+
padding,
|
185 |
+
lerp,
|
186 |
+
debug,
|
187 |
+
]
|
188 |
+
|
189 |
+
def process(
|
190 |
+
self,
|
191 |
+
p: StableDiffusionProcessing,
|
192 |
+
enabled: bool,
|
193 |
+
targets_: str,
|
194 |
+
weight: Union[float,int],
|
195 |
+
disable_neg: bool,
|
196 |
+
strong: bool,
|
197 |
+
padding: Union[str,int],
|
198 |
+
intp: str,
|
199 |
+
debug: bool,
|
200 |
+
):
|
201 |
+
set_debug(debug)
|
202 |
+
|
203 |
+
if self.last_hooker is not None:
|
204 |
+
self.last_hooker.__exit__(None, None, None)
|
205 |
+
self.last_hooker = None
|
206 |
+
|
207 |
+
if not enabled:
|
208 |
+
return
|
209 |
+
|
210 |
+
if targets_ is None or len(targets_) == 0:
|
211 |
+
return
|
212 |
+
|
213 |
+
targets = [x.strip() for x in targets_.split(',')]
|
214 |
+
targets = [x for x in targets if len(x) != 0]
|
215 |
+
|
216 |
+
if len(targets) == 0:
|
217 |
+
return
|
218 |
+
|
219 |
+
if padding is None:
|
220 |
+
padding = PAD
|
221 |
+
elif isinstance(padding, str):
|
222 |
+
if len(padding) == 0:
|
223 |
+
padding = PAD
|
224 |
+
else:
|
225 |
+
try:
|
226 |
+
padding = int(padding)
|
227 |
+
except:
|
228 |
+
if not padding.endswith('</w>'):
|
229 |
+
padding += '</w>'
|
230 |
+
|
231 |
+
weight = float(weight)
|
232 |
+
intp = intp.lower()
|
233 |
+
|
234 |
+
self.last_hooker = Hook(
|
235 |
+
enabled=True,
|
236 |
+
targets=targets,
|
237 |
+
padding=padding,
|
238 |
+
weight=weight,
|
239 |
+
disable_neg=disable_neg,
|
240 |
+
strong=strong,
|
241 |
+
interpolate=intp,
|
242 |
+
)
|
243 |
+
|
244 |
+
self.last_hooker.setup(p)
|
245 |
+
self.last_hooker.__enter__()
|
246 |
+
|
247 |
+
p.extra_generation_params.update({
|
248 |
+
f'{NAME} enabled': enabled,
|
249 |
+
f'{NAME} targets': targets,
|
250 |
+
f'{NAME} padding': padding,
|
251 |
+
f'{NAME} weight': weight,
|
252 |
+
f'{NAME} disable_for_neg': disable_neg,
|
253 |
+
f'{NAME} strong': strong,
|
254 |
+
f'{NAME} interpolation': intp,
|
255 |
+
})
|
256 |
+
|
257 |
+
init_xyz(Script, NAME)
|
sd-webui-cutoff/scripts/cutofflib/__pycache__/embedding.cpython-310.pyc
ADDED
Binary file (7.23 kB). View file
|
|
sd-webui-cutoff/scripts/cutofflib/__pycache__/sdhook.cpython-310.pyc
ADDED
Binary file (8.34 kB). View file
|
|
sd-webui-cutoff/scripts/cutofflib/__pycache__/utils.cpython-310.pyc
ADDED
Binary file (522 Bytes). View file
|
|
sd-webui-cutoff/scripts/cutofflib/__pycache__/xyz.cpython-310.pyc
ADDED
Binary file (4.36 kB). View file
|
|
sd-webui-cutoff/scripts/cutofflib/embedding.py
ADDED
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from dataclasses import dataclass
|
2 |
+
from itertools import product
|
3 |
+
import re
|
4 |
+
from typing import Union, List, Tuple
|
5 |
+
import numpy as np
|
6 |
+
import open_clip
|
7 |
+
from modules.sd_hijack_clip import FrozenCLIPEmbedderWithCustomWordsBase as CLIP
|
8 |
+
from modules import prompt_parser, shared
|
9 |
+
from scripts.cutofflib.utils import log
|
10 |
+
|
11 |
+
class ClipWrapper:
|
12 |
+
def __init__(self, te: CLIP):
|
13 |
+
self.te = te
|
14 |
+
self.v1 = hasattr(te.wrapped, 'tokenizer')
|
15 |
+
self.t = (
|
16 |
+
te.wrapped.tokenizer if self.v1
|
17 |
+
else open_clip.tokenizer._tokenizer
|
18 |
+
)
|
19 |
+
|
20 |
+
def token_to_id(self, token: str) -> int:
|
21 |
+
if self.v1:
|
22 |
+
return self.t._convert_token_to_id(token) # type: ignore
|
23 |
+
else:
|
24 |
+
return self.t.encoder[token]
|
25 |
+
|
26 |
+
def id_to_token(self, id: int) -> str:
|
27 |
+
if self.v1:
|
28 |
+
return self.t.convert_ids_to_tokens(id) # type: ignore
|
29 |
+
else:
|
30 |
+
return self.t.decoder[id]
|
31 |
+
|
32 |
+
def ids_to_tokens(self, ids: List[int]) -> List[str]:
|
33 |
+
if self.v1:
|
34 |
+
return self.t.convert_ids_to_tokens(ids) # type: ignore
|
35 |
+
else:
|
36 |
+
return [self.t.decoder[id] for id in ids]
|
37 |
+
|
38 |
+
def token(self, token: Union[int,str]):
|
39 |
+
if isinstance(token, int):
|
40 |
+
return Token(token, self.id_to_token(token))
|
41 |
+
else:
|
42 |
+
return Token(self.token_to_id(token), token)
|
43 |
+
|
44 |
+
|
45 |
+
@dataclass
|
46 |
+
class Token:
|
47 |
+
id: int
|
48 |
+
token: str
|
49 |
+
|
50 |
+
class CutoffPrompt:
|
51 |
+
|
52 |
+
@staticmethod
|
53 |
+
def _cutoff(prompt: str, clip: CLIP, tokens: List[str], padding: str):
|
54 |
+
def token_count(text: str):
|
55 |
+
tt = token_to_block(clip, text)
|
56 |
+
# tt[0] == clip.id_start (<|startoftext|>)
|
57 |
+
for index, (t, _) in enumerate(tt):
|
58 |
+
if t.id == clip.id_end: # <|endoftext|>
|
59 |
+
return index - 1
|
60 |
+
return 0 # must not happen...
|
61 |
+
|
62 |
+
re_targets = [ re.compile(r'\b' + re.escape(x) + r'\b') for x in tokens ]
|
63 |
+
replacer = [ ' ' + ' '.join([padding] * token_count(x)) + ' ' for x in tokens ]
|
64 |
+
|
65 |
+
rows: List[Tuple[str,str]] = []
|
66 |
+
for block in prompt.split(','):
|
67 |
+
b0 = block
|
68 |
+
for r, p in zip(re_targets, replacer):
|
69 |
+
block = r.sub(p, block)
|
70 |
+
b1 = block
|
71 |
+
rows.append((b0, b1))
|
72 |
+
|
73 |
+
return rows
|
74 |
+
|
75 |
+
def __init__(self, prompt: str, clip: CLIP, tokens: List[str], padding: str):
|
76 |
+
self.prompt = prompt
|
77 |
+
rows = CutoffPrompt._cutoff(prompt, clip, tokens, padding)
|
78 |
+
self.base = np.array([x[0] for x in rows])
|
79 |
+
self.cut = np.array([x[1] for x in rows])
|
80 |
+
self.sw = np.array([False] * len(rows))
|
81 |
+
|
82 |
+
@property
|
83 |
+
def block_count(self):
|
84 |
+
return self.base.shape[0]
|
85 |
+
|
86 |
+
def switch(self, block_index: int, to: Union[bool,None] = None):
|
87 |
+
if to is None:
|
88 |
+
to = not self.sw[block_index]
|
89 |
+
self.sw[block_index] = to
|
90 |
+
return to
|
91 |
+
|
92 |
+
def text(self, sw=None):
|
93 |
+
if sw is None:
|
94 |
+
sw = self.sw
|
95 |
+
blocks = np.where(sw, self.cut, self.base)
|
96 |
+
return ','.join(blocks)
|
97 |
+
|
98 |
+
def active_blocks(self) -> np.ndarray:
|
99 |
+
indices, = (self.base != self.cut).nonzero()
|
100 |
+
return indices
|
101 |
+
|
102 |
+
def generate(self):
|
103 |
+
indices = self.active_blocks()
|
104 |
+
for diff_sw in product([False, True], repeat=indices.shape[0]):
|
105 |
+
sw = np.full_like(self.sw, False)
|
106 |
+
sw[indices] = diff_sw
|
107 |
+
yield diff_sw, self.text(sw)
|
108 |
+
|
109 |
+
|
110 |
+
def generate_prompts(
|
111 |
+
clip: CLIP,
|
112 |
+
prompt: str,
|
113 |
+
targets: List[str],
|
114 |
+
padding: Union[str,int,Token],
|
115 |
+
) -> CutoffPrompt:
|
116 |
+
|
117 |
+
te = ClipWrapper(clip)
|
118 |
+
|
119 |
+
if not isinstance(padding, Token):
|
120 |
+
o_pad = padding
|
121 |
+
padding = te.token(padding)
|
122 |
+
if padding.id == clip.id_end:
|
123 |
+
raise ValueError(f'`{o_pad}` is not a valid token.')
|
124 |
+
|
125 |
+
result = CutoffPrompt(prompt, clip, targets, padding.token.replace('</w>', ''))
|
126 |
+
|
127 |
+
log(f'[Cutoff] replace: {", ".join(targets)}')
|
128 |
+
log(f'[Cutoff] to: {padding.token} ({padding.id})')
|
129 |
+
log(f'[Cutoff] original: {prompt}')
|
130 |
+
for i, (_, pp) in enumerate(result.generate()):
|
131 |
+
log(f'[Cutoff] #{i}: {pp}')
|
132 |
+
|
133 |
+
return result
|
134 |
+
|
135 |
+
|
136 |
+
def token_to_block(clip: CLIP, prompt: str):
|
137 |
+
te = ClipWrapper(clip)
|
138 |
+
|
139 |
+
# cf. sd_hijack_clip.py
|
140 |
+
|
141 |
+
parsed = prompt_parser.parse_prompt_attention(prompt)
|
142 |
+
tokenized: List[List[int]] = clip.tokenize([text for text, _ in parsed])
|
143 |
+
|
144 |
+
CHUNK_LENGTH = 75
|
145 |
+
id_start = te.token(clip.id_start) # type: ignore
|
146 |
+
id_end = te.token(clip.id_end) # type: ignore
|
147 |
+
comma = te.token(',</w>')
|
148 |
+
|
149 |
+
last_comma = -1
|
150 |
+
current_block = 0
|
151 |
+
current_tokens: List[Tuple[Token,int]] = []
|
152 |
+
result: List[Tuple[Token,int]] = []
|
153 |
+
|
154 |
+
def next_chunk():
|
155 |
+
nonlocal current_tokens, last_comma
|
156 |
+
|
157 |
+
to_add = CHUNK_LENGTH - len(current_tokens)
|
158 |
+
if 0 < to_add:
|
159 |
+
current_tokens += [(id_end, -1)] * to_add
|
160 |
+
|
161 |
+
current_tokens = [(id_start, -1)] + current_tokens + [(id_end, -1)]
|
162 |
+
|
163 |
+
last_comma = -1
|
164 |
+
result.extend(current_tokens)
|
165 |
+
current_tokens = []
|
166 |
+
|
167 |
+
for tokens, (text, weight) in zip(tokenized, parsed):
|
168 |
+
if text == 'BREAK' and weight == -1:
|
169 |
+
next_chunk()
|
170 |
+
continue
|
171 |
+
|
172 |
+
p = 0
|
173 |
+
while p < len(tokens):
|
174 |
+
token = tokens[p]
|
175 |
+
|
176 |
+
if token == comma.id:
|
177 |
+
last_comma = len(current_tokens)
|
178 |
+
current_block += 1
|
179 |
+
|
180 |
+
elif (
|
181 |
+
shared.opts.comma_padding_backtrack != 0
|
182 |
+
and len(current_tokens) == CHUNK_LENGTH
|
183 |
+
and last_comma != -1
|
184 |
+
and len(current_tokens) - last_comma <= shared.opts.comma_padding_backtrack
|
185 |
+
):
|
186 |
+
break_location = last_comma + 1
|
187 |
+
reloc_tokens = current_tokens[break_location:]
|
188 |
+
current_tokens = current_tokens[:break_location]
|
189 |
+
next_chunk()
|
190 |
+
current_tokens = reloc_tokens
|
191 |
+
|
192 |
+
if len(current_tokens) == CHUNK_LENGTH:
|
193 |
+
next_chunk()
|
194 |
+
|
195 |
+
embedding, embedding_length_in_tokens = clip.hijack.embedding_db.find_embedding_at_position(tokens, p)
|
196 |
+
if embedding is None:
|
197 |
+
if token == comma.id:
|
198 |
+
current_tokens.append((te.token(token), -1))
|
199 |
+
else:
|
200 |
+
current_tokens.append((te.token(token), current_block))
|
201 |
+
p += 1
|
202 |
+
continue
|
203 |
+
|
204 |
+
emb_len = int(embedding.vec.shape[0])
|
205 |
+
if len(current_tokens) + emb_len > CHUNK_LENGTH:
|
206 |
+
next_chunk()
|
207 |
+
|
208 |
+
current_tokens += [(te.token(0), current_block)] * emb_len
|
209 |
+
p += embedding_length_in_tokens
|
210 |
+
|
211 |
+
if len(current_tokens) > 0:
|
212 |
+
next_chunk()
|
213 |
+
|
214 |
+
return result
|