ShawnGGG commited on
Commit
1ae8021
1 Parent(s): 9592c66

Upload 124 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +6 -0
  2. infinite-zoom-automatic1111-webui/.github/ISSUE_TEMPLATE/bug_report.md +29 -0
  3. infinite-zoom-automatic1111-webui/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  4. infinite-zoom-automatic1111-webui/.gitignore +132 -0
  5. infinite-zoom-automatic1111-webui/CODE_OF_CONDUCT.md +128 -0
  6. infinite-zoom-automatic1111-webui/LICENSE +21 -0
  7. infinite-zoom-automatic1111-webui/README.md +115 -0
  8. infinite-zoom-automatic1111-webui/install.py +6 -0
  9. infinite-zoom-automatic1111-webui/iz_helpers/__init__.py +2 -0
  10. infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/__init__.cpython-310.pyc +0 -0
  11. infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/helpers.cpython-310.pyc +0 -0
  12. infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/image.cpython-310.pyc +0 -0
  13. infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/prompt_util.cpython-310.pyc +0 -0
  14. infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/run.cpython-310.pyc +0 -0
  15. infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/sd_helpers.cpython-310.pyc +0 -0
  16. infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/settings.cpython-310.pyc +0 -0
  17. infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/static_variables.cpython-310.pyc +0 -0
  18. infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/ui.cpython-310.pyc +0 -0
  19. infinite-zoom-automatic1111-webui/iz_helpers/__pycache__/video.cpython-310.pyc +0 -0
  20. infinite-zoom-automatic1111-webui/iz_helpers/extra.py +0 -0
  21. infinite-zoom-automatic1111-webui/iz_helpers/helpers.py +126 -0
  22. infinite-zoom-automatic1111-webui/iz_helpers/image.py +23 -0
  23. infinite-zoom-automatic1111-webui/iz_helpers/prompt_util.py +67 -0
  24. infinite-zoom-automatic1111-webui/iz_helpers/promptschema.json +60 -0
  25. infinite-zoom-automatic1111-webui/iz_helpers/run.py +512 -0
  26. infinite-zoom-automatic1111-webui/iz_helpers/sd_helpers.py +81 -0
  27. infinite-zoom-automatic1111-webui/iz_helpers/settings.py +108 -0
  28. infinite-zoom-automatic1111-webui/iz_helpers/static_variables.py +51 -0
  29. infinite-zoom-automatic1111-webui/iz_helpers/ui.py +307 -0
  30. infinite-zoom-automatic1111-webui/iz_helpers/video.py +43 -0
  31. infinite-zoom-automatic1111-webui/javascript/infinite-zoom-hints.js +49 -0
  32. infinite-zoom-automatic1111-webui/javascript/infinite-zoom.js +54 -0
  33. infinite-zoom-automatic1111-webui/scripts/__pycache__/infinite-zoom.cpython-310.pyc +0 -0
  34. infinite-zoom-automatic1111-webui/scripts/infinite-zoom.py +5 -0
  35. infinite-zoom-automatic1111-webui/style.css +10 -0
  36. sd-webui-cutoff/.gitignore +1 -0
  37. sd-webui-cutoff/LICENSE +1 -0
  38. sd-webui-cutoff/README.md +146 -0
  39. sd-webui-cutoff/images/cover.jpg +3 -0
  40. sd-webui-cutoff/images/idea.png +0 -0
  41. sd-webui-cutoff/images/sample-1.png +3 -0
  42. sd-webui-cutoff/images/sample-2.png +3 -0
  43. sd-webui-cutoff/images/sample-3.png +3 -0
  44. sd-webui-cutoff/scripts/__pycache__/cutoff.cpython-310.pyc +0 -0
  45. sd-webui-cutoff/scripts/cutoff.py +257 -0
  46. sd-webui-cutoff/scripts/cutofflib/__pycache__/embedding.cpython-310.pyc +0 -0
  47. sd-webui-cutoff/scripts/cutofflib/__pycache__/sdhook.cpython-310.pyc +0 -0
  48. sd-webui-cutoff/scripts/cutofflib/__pycache__/utils.cpython-310.pyc +0 -0
  49. sd-webui-cutoff/scripts/cutofflib/__pycache__/xyz.cpython-310.pyc +0 -0
  50. 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

  • SHA256: 129a1d562085cd9af3c46ad42ce2a3133e5306d03a7c7e37942b634d55de3666
  • Pointer size: 132 Bytes
  • Size of remote file: 1.96 MB
sd-webui-cutoff/images/idea.png ADDED
sd-webui-cutoff/images/sample-1.png ADDED

Git LFS Details

  • SHA256: e4e36a8555ed4818a166fe7c86c4b91ba67d5255c6ef51ad206b163f1aa779c6
  • Pointer size: 132 Bytes
  • Size of remote file: 5.93 MB
sd-webui-cutoff/images/sample-2.png ADDED

Git LFS Details

  • SHA256: 0546d2f8d3ea624b87839f8a0698a33db3465b3919540fc8c1f51b7467055455
  • Pointer size: 132 Bytes
  • Size of remote file: 1.02 MB
sd-webui-cutoff/images/sample-3.png ADDED

Git LFS Details

  • SHA256: e1d43f8e30d9078417a8ba22fc28dec5a16279adad189c917efb7a99b8706a4d
  • Pointer size: 132 Bytes
  • Size of remote file: 1.21 MB
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