259 lines
10 KiB
Python
259 lines
10 KiB
Python
import argparse
|
|
import datetime
|
|
import os
|
|
import shutil
|
|
import sys
|
|
from urllib import request as ulreq
|
|
|
|
from bs4 import BeautifulSoup
|
|
import requests
|
|
from huggingface_hub import HfApi
|
|
from PIL import ImageFile
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('out_file', nargs='?', help='file to save to', default='stable-diffusion-textual-inversion-models.html')
|
|
args = parser.parse_args()
|
|
|
|
print('Will save to file:', args.out_file)
|
|
|
|
# Init some stuff before saving the time
|
|
api = HfApi()
|
|
models_list = []
|
|
|
|
# Save the time now before we do the hard work
|
|
dt = datetime.datetime.now()
|
|
tz = dt.astimezone().tzname()
|
|
|
|
# Get list of models under the sd-concepts-library organization
|
|
for model in api.list_models(author="sd-concepts-library"):
|
|
models_list.append(model.modelId.replace('sd-concepts-library/', ''))
|
|
models_list.sort()
|
|
|
|
html_struct = f"""
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Stable Diffusion Textual Inversion Models</title>
|
|
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
|
<meta http-equiv="Pragma" content="no-cache" />
|
|
<meta http-equiv="Expires" content="0" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-u1OknCvxWvY5kfmNBILK2hRnQC3Pr17a+RTT6rIHI7NnikvbZlHgTPOOmMi466C8" crossorigin="anonymous"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js" integrity="sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg==" crossorigin="anonymous"></script>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" crossorigin="anonymous">
|
|
|
|
<link rel="apple-touch-icon" sizes="180x180" href="/stable-diffusion-textual-inversion-models/apple-touch-icon.png">
|
|
<link rel="icon" type="image/png" sizes="32x32" href="/stable-diffusion-textual-inversion-models/favicon-32x32.png">
|
|
<link rel="icon" type="image/png" sizes="16x16" href="/stable-diffusion-textual-inversion-models/favicon-16x16.png">
|
|
<link rel="manifest" href="/stable-diffusion-textual-inversion-models/site.webmanifest">
|
|
<link rel="mask-icon" href="/stable-diffusion-textual-inversion-models/safari-pinned-tab.svg" color="#ee9321">
|
|
<link rel="shortcut icon" href="favicon.ico">
|
|
<meta name="msapplication-TileColor" content="#ee9321">
|
|
<meta name="msapplication-config" content="/stable-diffusion-textual-inversion-models/browserconfig.xml">
|
|
<meta name="theme-color" content="#ee9321">
|
|
|
|
<!-- Matomo -->
|
|
<script>
|
|
var _paq = window._paq = window._paq || [];
|
|
_paq.push(['trackPageView']);
|
|
_paq.push(['trackVisibleContentImpressions']);
|
|
_paq.push(['enableLinkTracking']);
|
|
(function() {{
|
|
var u = "https://mato.evulid.cc/";
|
|
_paq.push(['setTrackerUrl', u + 'matomo.php']);
|
|
_paq.push(['setSiteId', '1']);
|
|
var d = document,
|
|
g = d.createElement('script'),
|
|
s = d.getElementsByTagName('script')[0];
|
|
g.async = true;
|
|
g.src = u + 'matomo.js';
|
|
s.parentNode.insertBefore(g, s);
|
|
}})();
|
|
</script>
|
|
<!-- End Matomo Code -->
|
|
</head>
|
|
|
|
<body>
|
|
<style>
|
|
.thumbnail {{
|
|
max-width: 185px;
|
|
display: block;
|
|
padding-top: 5px;
|
|
padding-bottom: 5px;
|
|
}}
|
|
|
|
.model-title {{
|
|
margin-top: 100px;
|
|
}}
|
|
</style>
|
|
<div class="container" style="margin-bottom: 180px;">
|
|
<div class="jumbotron text-center" style="margin-top: 45px;margin-right: 45px;margin-bottom: 0px;margin-left: 45px;">
|
|
<h1>Stable Diffusion Textual Inversion Models</h1>
|
|
</div>
|
|
<div style="text-align: center;margin-bottom: 45px;font-size: 8pt;">
|
|
<p>
|
|
<i>Page updates automatically daily. Last updated <a class="btn-link" style="cursor: pointer;text-decoration: none;" data-toggle="tooltip" data-placement="bottom" title="{dt.strftime(f"%m-%d-%Y %H:%M:%S {tz}")}">{datetime.datetime.now().strftime("%A %B %d, %Y")}</a>.</i>
|
|
</p>
|
|
</div>
|
|
|
|
<p>
|
|
Browser for the <a href="https://huggingface.co/sd-concepts-library">HuggingFace textual inversion library</a>. There are currently {len(models_list)} textual inversion models in sd-concepts-library.
|
|
</p>
|
|
|
|
<p>
|
|
Models are downloaded straight from the HuggingFace repositories. The images displayed are the inputs, not the outputs. Want to quickly test concepts? Try the <a href="https://huggingface.co/spaces/sd-concepts-library/stable-diffusion-conceptualizer">Stable Diffusion Conceptualizer</a> on HuggingFace.<a href="https://huggingface.co/docs/diffusers/main/en/training/text_inversion">More info on textual inversion.</a>
|
|
</p>
|
|
|
|
<center>
|
|
<a href="https://github.com/Cyberes/stable-diffusion-textual-inversion-models/actions/workflows/generate_static_html.yml"><img src="https://github.com/Cyberes/stable-diffusion-textual-inversion-models/actions/workflows/generate_static_html.yml/badge.svg"></a>
|
|
</center>
|
|
<br>
|
|
<hr>
|
|
<noscript><p><img src="https://mato.evulid.cc/matomo.php?idsite=1&rec=1&url=https://cyberes.github.io/stable-diffusion-textual-inversion-models" style="border:0;" alt="" /></p></noscript>
|
|
"""
|
|
|
|
i = 1
|
|
for model_name in models_list:
|
|
# For testing
|
|
# if i == 4:
|
|
# break
|
|
|
|
print(f'{i}/{len(models_list)} -> {model_name}')
|
|
|
|
html_struct = html_struct + f'<h3 class="model-title" data-track-content data-content-name="{model_name}" data-content-piece="Model Title">{model_name}</h3>'
|
|
|
|
# Get the concept images from the huggingface repo
|
|
restricted = False
|
|
try:
|
|
files = api.list_repo_files(
|
|
repo_id=f'sd-concepts-library/{model_name}')
|
|
concept_images = [i for i in files if i.startswith('concept_images/')]
|
|
except requests.exceptions.HTTPError:
|
|
# Sometimes an author will require you to share your contact info to gain access
|
|
restricted = True
|
|
|
|
if restricted:
|
|
html_struct = html_struct + f"""
|
|
<p>
|
|
{model_name} is restricted and you must share your contact information to view this repository.
|
|
<a type="button" class="btn btn-link" href="https://huggingface.co/sd-concepts-library/{model_name}/">View Repository</a>
|
|
</p>
|
|
"""
|
|
else:
|
|
html_struct = html_struct + f"""
|
|
<p>
|
|
<button type="button" class="btn btn-primary" onclick="downloadAs('https://huggingface.co/sd-concepts-library/{model_name}/resolve/main/learned_embeds.bin', '{model_name}.pt')">Download {model_name}.pt</button>
|
|
<a type="button" class="btn btn-link" href="https://huggingface.co/sd-concepts-library/{model_name}/">View Repository</a>
|
|
</p>
|
|
<div class="row">
|
|
"""
|
|
|
|
# Most repos have 3 concept images but some have more or less
|
|
# We gotta make sure only 3 are shown
|
|
img_count = 3
|
|
if len(concept_images) < 3:
|
|
img_count = len(concept_images)
|
|
|
|
for x in range(img_count):
|
|
html_struct = html_struct + f"""
|
|
<div class="col-sm">
|
|
<!-- <img class="thumbnail mx-auto lazy-load img-fluid" data-src="https://huggingface.co/sd-concepts-library/{model_name}/resolve/main/{concept_images[x]}">-->
|
|
<img class="thumbnail mx-auto img-fluid" loading="lazy" src="https://huggingface.co/sd-concepts-library/{model_name}/resolve/main/{concept_images[x]}">
|
|
</div>
|
|
"""
|
|
html_struct = html_struct + '</div>'
|
|
i = i + 1
|
|
|
|
html_struct = html_struct + """
|
|
</div>
|
|
<script>
|
|
// Lazy-loading images
|
|
// Not using since the browser implementation seems pretty ok
|
|
/*
|
|
document.addEventListener("DOMContentLoaded", function() {
|
|
let lazyloadImages;
|
|
if ("IntersectionObserver" in window) {
|
|
lazyloadImages = document.querySelectorAll(".lazy-load");
|
|
let imageObserver = new IntersectionObserver(function(entries, observer) {
|
|
entries.forEach(function(entry) {
|
|
if (entry.isIntersecting) {
|
|
let image = entry.target;
|
|
image.src = image.dataset.src;
|
|
image.classList.remove("lazy-load");
|
|
imageObserver.unobserve(image);
|
|
}
|
|
});
|
|
});
|
|
lazyloadImages.forEach(function(image) {
|
|
imageObserver.observe(image);
|
|
});
|
|
} else {
|
|
let lazyloadThrottleTimeout;
|
|
lazyloadImages = document.querySelectorAll(".lazy-load");
|
|
|
|
function lazyload() {
|
|
if (lazyloadThrottleTimeout) {
|
|
clearTimeout(lazyloadThrottleTimeout);
|
|
}
|
|
lazyloadThrottleTimeout = setTimeout(function() {
|
|
let scrollTop = window.pageYOffset;
|
|
lazyloadImages.forEach(function(img) {
|
|
if (img.offsetTop < (window.innerHeight + scrollTop)) {
|
|
img.src = img.dataset.src;
|
|
img.classList.remove('lazy-load');
|
|
}
|
|
});
|
|
if (lazyloadImages.length == 0) {
|
|
document.removeEventListener("scroll", lazyload);
|
|
window.removeEventListener("resize", lazyload);
|
|
window.removeEventListener("orientationChange", lazyload);
|
|
}
|
|
}, 20);
|
|
}
|
|
document.addEventListener("scroll", lazyload);
|
|
window.addEventListener("resize", lazyload);
|
|
window.addEventListener("orientationChange", lazyload);
|
|
}
|
|
})
|
|
*/
|
|
|
|
// Enable tooltips
|
|
$(function() {
|
|
$('[data-toggle="tooltip"]').tooltip({
|
|
placement: "bottom"
|
|
})
|
|
});
|
|
|
|
// Download the file under a different name
|
|
const downloadAs = (url, name) => {
|
|
axios.get(url, {
|
|
headers: {
|
|
"Content-Type": "application/octet-stream"
|
|
},
|
|
responseType: "blob"
|
|
})
|
|
.then(response => {
|
|
const a = document.createElement("a");
|
|
const url = window.URL.createObjectURL(response.data);
|
|
a.href = url;
|
|
a.download = name;
|
|
a.click();
|
|
})
|
|
.catch(err => {
|
|
console.log("error", err);
|
|
});
|
|
_paq.push(['trackLink', url, 'download']);
|
|
};
|
|
</script>
|
|
</body>
|
|
"""
|
|
|
|
# Load the HTML into bs4 so we can format it
|
|
soup = BeautifulSoup(html_struct, "html.parser")
|
|
|
|
f = open(args.out_file, 'w', encoding='utf-8')
|
|
f.write(str(soup.prettify()))
|
|
f.close()
|