Skip to content

Instantly share code, notes, and snippets.

@mariocesar
Last active November 6, 2024 16:57
Show Gist options
  • Select an option

  • Save mariocesar/4747598 to your computer and use it in GitHub Desktop.

Select an option

Save mariocesar/4747598 to your computer and use it in GitHub Desktop.
Django custom storage backend that applies optimizations for png and jpg images
from django.contrib.staticfiles.storage import CachedFilesMixin, StaticFilesStorage
from pipeline.storage import PipelineMixin
from functools import wraps
import shutil
import re
import subprocess
def coroutine(func):
@wraps(func)
def start(*args, **kwargs):
cr = func(*args, **kwargs)
next(cr)
return cr
return start
def runlocal(cmd: str):
"""Executes a command locally."""
return subprocess.run(
cmd,
shell=True,
check=True,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
class PipelineStorage(PipelineMixin, CachedFilesMixin, StaticFilesStorage):
def post_process(self, paths, dry_run=False, **options):
@coroutine
def optimize(targets):
while True:
filename = (yield)
# Do not optimize files that are less than 5Kb
if 1024 * 4 < self.size(filename):
for target in targets:
target.send(filename)
@coroutine
def grep(pattern, target, inverse=False):
pattern = re.compile(pattern)
while True:
filename = (yield)
if (pattern.match(filename) and not inverse) or (not pattern.match(filename) and inverse):
print(filename)
target.send(filename)
@coroutine
def pngcrush():
while True:
filename = (yield)
runlocal('pngcrush -q '
'-rem gAMA -rem cHRM '
'-rem iCCP -rem sRGB '
'-rem alla -rem text '
'-reduce -brute "%s" "%s"' % (
self.path(filename),
'%s.min' % self.path(filename)
)
)
self.delete(filename)
shutil.move('%s.min' % self.path(filename), self.path(filename))
@coroutine
def jpegtran():
while True:
filename = (yield)
runlocal('jpegtran '
'-outfile "%s" '
'-optimize '
'-copy none "%s"' % (
'%s.min' % self.path(filename),
self.path(filename)
)
)
self.delete(filename)
shutil.move('%s.min' % self.path(filename), self.path(filename))
def process(post_paths, target):
for old_path, new_path, processed in post_paths:
target.send(new_path)
yield (old_path, new_path, processed)
self.delete(old_path)
target.close()
post_paths = list(super(PipelineStorage, self).post_process(paths, dry_run, **options))
new_paths = (process(post_paths, optimize([
grep(r'.+\.png$', pngcrush()),
grep(r'.+\.jpg$', jpegtran()),
])
))
for old_path, new_path, processed in new_paths:
new_path = self.hashed_name(new_path)
yield (old_path, new_path, processed)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment