Some refactoring, and added a script to regenerate variants
This commit is contained in:
2
.idea/kakigoori.iml
generated
2
.idea/kakigoori.iml
generated
@@ -16,7 +16,7 @@
|
|||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<excludeFolder url="file://$MODULE_DIR$/env" />
|
<excludeFolder url="file://$MODULE_DIR$/env" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="jdk" jdkName="Python 3.11 virtualenv at ~/kakigoori/.venv" jdkType="Python SDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
<component name="TemplatesService">
|
<component name="TemplatesService">
|
||||||
|
4
.idea/misc.xml
generated
4
.idea/misc.xml
generated
@@ -3,7 +3,7 @@
|
|||||||
<component name="Black">
|
<component name="Black">
|
||||||
<option name="enabledOnReformat" value="true" />
|
<option name="enabledOnReformat" value="true" />
|
||||||
<option name="enabledOnSave" value="true" />
|
<option name="enabledOnSave" value="true" />
|
||||||
<option name="sdkName" value="Python 3.13 (kakigoori)" />
|
<option name="sdkName" value="Python 3.11 virtualenv at ~/kakigoori/.venv" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (kakigoori)" project-jdk-type="Python SDK" />
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 virtualenv at ~/kakigoori/.venv" project-jdk-type="Python SDK" />
|
||||||
</project>
|
</project>
|
55
images/management/commands/regenerate_variants.py
Normal file
55
images/management/commands/regenerate_variants.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from images.models import ImageVariant
|
||||||
|
from images.utils import get_b2_resource
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument("variants", nargs="+", type=str)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
if options["variants"] and len(options["variants"]) > 0:
|
||||||
|
variants = options["variants"]
|
||||||
|
else:
|
||||||
|
variants = ImageVariant.objects.all()
|
||||||
|
|
||||||
|
bucket = get_b2_resource()
|
||||||
|
|
||||||
|
for variant_id in variants:
|
||||||
|
print("Regenerating variant %s" % variant_id)
|
||||||
|
|
||||||
|
image_variant = ImageVariant.objects.filter(id=variant_id).first()
|
||||||
|
|
||||||
|
if image_variant.is_full_size and (
|
||||||
|
image_variant.file_type == "jpg" or image_variant.file_type == "png"
|
||||||
|
):
|
||||||
|
print("Can't regenerate original image")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if image_variant.file_type == "webp" or image_variant.file_type == "avif":
|
||||||
|
image_variant.regenerate = True
|
||||||
|
image_variant.save()
|
||||||
|
continue
|
||||||
|
|
||||||
|
image, file_extension = image_variant.image.create_resized_image(
|
||||||
|
image_variant.height,
|
||||||
|
image_variant.width,
|
||||||
|
image_variant.gaussian_blur,
|
||||||
|
image_variant.brightness,
|
||||||
|
)
|
||||||
|
|
||||||
|
if file_extension == "jpg":
|
||||||
|
content_type = "image/jpeg"
|
||||||
|
elif file_extension == "png":
|
||||||
|
content_type = "image/png"
|
||||||
|
else:
|
||||||
|
content_type = "binary/octet-stream"
|
||||||
|
|
||||||
|
image_variant.file_type = file_extension
|
||||||
|
|
||||||
|
bucket.upload_fileobj(
|
||||||
|
image,
|
||||||
|
image_variant.backblaze_filepath,
|
||||||
|
ExtraArgs={"ContentType": content_type},
|
||||||
|
)
|
18
images/migrations/0008_imagevariant_regenerate.py
Normal file
18
images/migrations/0008_imagevariant_regenerate.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2 on 2025-05-03 10:57
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("images", "0007_image_version_imagevariant_available_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="imagevariant",
|
||||||
|
name="regenerate",
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
@@ -58,18 +58,7 @@ class Image(models.Model):
|
|||||||
available=False,
|
available=False,
|
||||||
).save()
|
).save()
|
||||||
|
|
||||||
ImageVariant(
|
def download_original_variant(self):
|
||||||
image=variant.image,
|
|
||||||
height=variant.height,
|
|
||||||
width=variant.width,
|
|
||||||
is_full_size=variant.is_full_size,
|
|
||||||
file_type="jpegli",
|
|
||||||
gaussian_blur=variant.gaussian_blur,
|
|
||||||
brightness=variant.brightness,
|
|
||||||
available=False,
|
|
||||||
).save()
|
|
||||||
|
|
||||||
def create_variant(self, width, height, gaussian_blur, brightness):
|
|
||||||
bucket = get_b2_resource()
|
bucket = get_b2_resource()
|
||||||
|
|
||||||
original_image = BytesIO()
|
original_image = BytesIO()
|
||||||
@@ -85,8 +74,46 @@ class Image(models.Model):
|
|||||||
original_image,
|
original_image,
|
||||||
)
|
)
|
||||||
original_image.seek(0)
|
original_image.seek(0)
|
||||||
|
return original_image
|
||||||
|
|
||||||
|
def create_resized_image(
|
||||||
|
self,
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
gaussian_blur,
|
||||||
|
brightness,
|
||||||
|
):
|
||||||
|
original_image = self.download_original_variant()
|
||||||
resized_image = BytesIO()
|
resized_image = BytesIO()
|
||||||
|
file_extension = "jpg"
|
||||||
|
|
||||||
|
with PILImage.open(original_image) as im:
|
||||||
|
ImageOps.exif_transpose(im, in_place=True)
|
||||||
|
im = im.filter(ImageFilter.GaussianBlur(gaussian_blur))
|
||||||
|
enhancer = ImageEnhance.Brightness(im)
|
||||||
|
im = enhancer.enhance(brightness)
|
||||||
|
im.thumbnail(
|
||||||
|
(width, height), resample=PILImage.Resampling.LANCZOS, reducing_gap=3.0
|
||||||
|
)
|
||||||
|
|
||||||
|
if im.has_transparency_data:
|
||||||
|
try:
|
||||||
|
im.save(resized_image, "PNG", quality=90)
|
||||||
|
file_extension = "png"
|
||||||
|
except OSError:
|
||||||
|
im.convert("RGB").save(resized_image, "JPEG", quality=90)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
im.save(resized_image, "JPEG", quality=90)
|
||||||
|
except OSError:
|
||||||
|
im.convert("RGB").save(resized_image, "JPEG", quality=90)
|
||||||
|
|
||||||
|
resized_image.seek(0)
|
||||||
|
|
||||||
|
return resized_image, file_extension
|
||||||
|
|
||||||
|
def create_variant(self, width, height, gaussian_blur, brightness):
|
||||||
|
bucket = get_b2_resource()
|
||||||
|
|
||||||
image_variant = ImageVariant(
|
image_variant = ImageVariant(
|
||||||
image=self,
|
image=self,
|
||||||
@@ -99,29 +126,18 @@ class Image(models.Model):
|
|||||||
available=True,
|
available=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
resized_image, file_extension = self.create_resized_image(
|
||||||
|
height, width, gaussian_blur, brightness
|
||||||
|
)
|
||||||
|
|
||||||
|
if file_extension == "jpg":
|
||||||
content_type = "image/jpeg"
|
content_type = "image/jpeg"
|
||||||
|
elif file_extension == "png":
|
||||||
with PILImage.open(original_image) as im:
|
|
||||||
ImageOps.exif_transpose(im, in_place=True)
|
|
||||||
im = im.filter(ImageFilter.GaussianBlur(gaussian_blur))
|
|
||||||
enhancer = ImageEnhance.Brightness(im)
|
|
||||||
im = enhancer.enhance(brightness)
|
|
||||||
im.thumbnail((width, height))
|
|
||||||
|
|
||||||
if im.has_transparency_data:
|
|
||||||
try:
|
|
||||||
im.save(resized_image, "PNG")
|
|
||||||
image_variant.file_extension = "png"
|
|
||||||
content_type = "image/png"
|
content_type = "image/png"
|
||||||
except OSError:
|
|
||||||
im.convert("RGB").save(resized_image, "JPEG")
|
|
||||||
else:
|
else:
|
||||||
try:
|
content_type = "binary/octet-stream"
|
||||||
im.save(resized_image, "JPEG")
|
|
||||||
except OSError:
|
|
||||||
im.convert("RGB").save(resized_image, "JPEG")
|
|
||||||
|
|
||||||
resized_image.seek(0)
|
image_variant.file_type = file_extension
|
||||||
|
|
||||||
bucket.upload_fileobj(
|
bucket.upload_fileobj(
|
||||||
resized_image,
|
resized_image,
|
||||||
@@ -146,6 +162,7 @@ class ImageVariant(models.Model):
|
|||||||
is_full_size = models.BooleanField(default=False)
|
is_full_size = models.BooleanField(default=False)
|
||||||
file_type = models.CharField(max_length=10)
|
file_type = models.CharField(max_length=10)
|
||||||
available = models.BooleanField(default=False)
|
available = models.BooleanField(default=False)
|
||||||
|
regenerate = models.BooleanField(default=False)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def backblaze_filepath(self):
|
def backblaze_filepath(self):
|
||||||
|
@@ -13,6 +13,7 @@ dependencies = [
|
|||||||
"charset-normalizer==3.3.2",
|
"charset-normalizer==3.3.2",
|
||||||
"click==8.1.7",
|
"click==8.1.7",
|
||||||
"django==5.2",
|
"django==5.2",
|
||||||
|
"django-stubs>=5.2.0",
|
||||||
"gunicorn==23.0.0",
|
"gunicorn==23.0.0",
|
||||||
"idna==3.8",
|
"idna==3.8",
|
||||||
"jmespath==1.0.1",
|
"jmespath==1.0.1",
|
||||||
|
49
uv.lock
generated
49
uv.lock
generated
@@ -146,6 +146,35 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/63/e0/6a5b5ea350c5bd63fe94b05e4c146c18facb51229d9dee42aa39f9fc2214/Django-5.2-py3-none-any.whl", hash = "sha256:91ceed4e3a6db5aedced65e3c8f963118ea9ba753fc620831c77074e620e7d83", size = 8301361 },
|
{ url = "https://files.pythonhosted.org/packages/63/e0/6a5b5ea350c5bd63fe94b05e4c146c18facb51229d9dee42aa39f9fc2214/Django-5.2-py3-none-any.whl", hash = "sha256:91ceed4e3a6db5aedced65e3c8f963118ea9ba753fc620831c77074e620e7d83", size = 8301361 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django-stubs"
|
||||||
|
version = "5.2.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "asgiref" },
|
||||||
|
{ name = "django" },
|
||||||
|
{ name = "django-stubs-ext" },
|
||||||
|
{ name = "types-pyyaml" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/87/79/7e7ad8b4bac545c8e608fa8db0bd061977f93035a112be78a7f3ffc6ff66/django_stubs-5.2.0.tar.gz", hash = "sha256:07e25c2d3cbff5be540227ff37719cc89f215dfaaaa5eb038a75b01bbfbb2722", size = 276297 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/01/5913ba5514337f3896c7bcbff6075808184dd303cd0fc3ecc289ec7e0c96/django_stubs-5.2.0-py3-none-any.whl", hash = "sha256:cd52da033489afc1357d6245f49e3cc57bf49015877253fb8efc6722ea3d2d2b", size = 481836 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django-stubs-ext"
|
||||||
|
version = "5.2.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "django" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/7c/7a/84338605817960942c1ea9d852685923ccccd0d91ba0d49532605973491f/django_stubs_ext-5.2.0.tar.gz", hash = "sha256:00c4ae307b538f5643af761a914c3f8e4e3f25f4e7c6d7098f1906c0d8f2aac9", size = 9618 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e2/65/9f5ca467d84a67c0c547f10b0ece9fd9c26c5efc818a01bf6a3d306c2a0c/django_stubs_ext-5.2.0-py3-none-any.whl", hash = "sha256:b27ae0aab970af4894ba4e9b3fcd3e03421dc8731516669659ee56122d148b23", size = 9066 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gunicorn"
|
name = "gunicorn"
|
||||||
version = "23.0.0"
|
version = "23.0.0"
|
||||||
@@ -189,6 +218,7 @@ dependencies = [
|
|||||||
{ name = "charset-normalizer" },
|
{ name = "charset-normalizer" },
|
||||||
{ name = "click" },
|
{ name = "click" },
|
||||||
{ name = "django" },
|
{ name = "django" },
|
||||||
|
{ name = "django-stubs" },
|
||||||
{ name = "gunicorn" },
|
{ name = "gunicorn" },
|
||||||
{ name = "idna" },
|
{ name = "idna" },
|
||||||
{ name = "jmespath" },
|
{ name = "jmespath" },
|
||||||
@@ -217,6 +247,7 @@ requires-dist = [
|
|||||||
{ name = "charset-normalizer", specifier = "==3.3.2" },
|
{ name = "charset-normalizer", specifier = "==3.3.2" },
|
||||||
{ name = "click", specifier = "==8.1.7" },
|
{ name = "click", specifier = "==8.1.7" },
|
||||||
{ name = "django", specifier = "==5.2" },
|
{ name = "django", specifier = "==5.2" },
|
||||||
|
{ name = "django-stubs", specifier = ">=5.2.0" },
|
||||||
{ name = "gunicorn", specifier = "==23.0.0" },
|
{ name = "gunicorn", specifier = "==23.0.0" },
|
||||||
{ name = "idna", specifier = "==3.8" },
|
{ name = "idna", specifier = "==3.8" },
|
||||||
{ name = "jmespath", specifier = "==1.0.1" },
|
{ name = "jmespath", specifier = "==1.0.1" },
|
||||||
@@ -382,6 +413,24 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/5d/a5/b2860373aa8de1e626b2bdfdd6df4355f0565b47e51f7d0c54fe70faf8fe/sqlparse-0.5.1-py3-none-any.whl", hash = "sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4", size = 44156 },
|
{ url = "https://files.pythonhosted.org/packages/5d/a5/b2860373aa8de1e626b2bdfdd6df4355f0565b47e51f7d0c54fe70faf8fe/sqlparse-0.5.1-py3-none-any.whl", hash = "sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4", size = 44156 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "types-pyyaml"
|
||||||
|
version = "6.0.12.20250402"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/2d/68/609eed7402f87c9874af39d35942744e39646d1ea9011765ec87b01b2a3c/types_pyyaml-6.0.12.20250402.tar.gz", hash = "sha256:d7c13c3e6d335b6af4b0122a01ff1d270aba84ab96d1a1a1063ecba3e13ec075", size = 17282 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ed/56/1fe61db05685fbb512c07ea9323f06ea727125951f1eb4dff110b3311da3/types_pyyaml-6.0.12.20250402-py3-none-any.whl", hash = "sha256:652348fa9e7a203d4b0d21066dfb00760d3cbd5a15ebb7cf8d33c88a49546681", size = 20329 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.13.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tzdata"
|
name = "tzdata"
|
||||||
version = "2025.2"
|
version = "2025.2"
|
||||||
|
Reference in New Issue
Block a user