-
Notifications
You must be signed in to change notification settings - Fork 581
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ImageCompressionBear is a bear which uses optimage, jpegoptim, jpegtran, pngcrush, optipng to see if the image can be compressed and how much bytes will be reduced if it's compressed. Closes #1259
- Loading branch information
1 parent
9dc6ef7
commit e8d5aea
Showing
13 changed files
with
182 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import os | ||
import subprocess | ||
import shutil | ||
import optimage | ||
|
||
from coalib.bears.GlobalBear import GlobalBear | ||
from coalib.results.Result import Result | ||
from dependency_management.requirements.PipRequirement import PipRequirement | ||
from dependency_management.requirements.DistributionRequirement \ | ||
import DistributionRequirement | ||
from coala_utils.FileUtils import create_tempfile | ||
|
||
|
||
class ImageCompressionBear(GlobalBear): | ||
LANGUAGES = {'Image'} | ||
REQUIREMENTS = {PipRequirement('optimage', '0.0.1'), | ||
DistributionRequirement(apt_get='jpegoptim'), | ||
DistributionRequirement(apt_get='libjpeg-progs', | ||
binary='jpegtran'), | ||
DistributionRequirement(apt_get='pngcrush'), | ||
DistributionRequirement(apt_get='optipng')} | ||
AUTHORS = {'The coala developers'} | ||
AUTHORS_EMAILS = {'The coala developers'} | ||
LICENSE = 'AGPL-3.0' | ||
CAN_DETECT = {'Compression', 'Useless metadata'} | ||
|
||
@classmethod | ||
def check_prerequisites(cls): | ||
for requirement in cls.REQUIREMENTS: | ||
if isinstance(requirement, DistributionRequirement): | ||
executable = requirement.package['binary'] or \ | ||
requirement.package['apt_get'] | ||
if shutil.which(executable) is None: | ||
return '{} is not installed'.format(executable) | ||
elif isinstance(requirement, PipRequirement): | ||
if not requirement.is_installed(): | ||
return '{} is not installed'.format( | ||
requirement.package) | ||
return True | ||
|
||
def run(self, image_files: list=()): | ||
""" | ||
Check for how much the image file size can be optimized | ||
:param image_files: The image files that this bear will use | ||
""" | ||
|
||
if isinstance(image_files, str): | ||
image_files = list(image_files) | ||
|
||
for filename in image_files: | ||
_, extension = os.path.splitext(filename) | ||
extension = extension.lower() | ||
compressor = optimage._EXTENSION_MAPPING.get(extension) | ||
|
||
if compressor is None: | ||
return | ||
|
||
output_filename = create_tempfile(suffix=extension) | ||
|
||
try: | ||
compressor(filename, output_filename) | ||
except optimage.InvalidExtension: | ||
return | ||
except optimage.MissingBinary: | ||
return | ||
except subprocess.CalledProcessError: | ||
return | ||
|
||
original_size = os.path.getsize(filename) | ||
new_size = os.path.getsize(output_filename) | ||
reduction = original_size - new_size | ||
reduction_percentage = reduction * 100 / original_size | ||
savings = 'savings: {} bytes = {:.2f}%'.format( | ||
reduction, reduction_percentage) | ||
|
||
if new_size < original_size: | ||
yield Result.from_values(origin=self, | ||
message=('This Image can be ' | ||
'losslessly compressed ' | ||
'to {} bytes ({})' | ||
.format(new_size, | ||
savings)), | ||
file=filename) | ||
|
||
os.remove(output_filename) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import os | ||
import unittest | ||
import shutil | ||
|
||
from bears.image.ImageCompressionBear import ImageCompressionBear | ||
from coalib.settings.Section import Section | ||
from queue import Queue | ||
|
||
|
||
def get_absolute_path(filename): | ||
return os.path.join(os.path.dirname(__file__), | ||
'test_files', | ||
filename) | ||
|
||
|
||
class ImageCompressionBearTest(unittest.TestCase): | ||
""" | ||
Tests for ImageCompressionBear | ||
""" | ||
|
||
MESSAGE_RE = ('This Image can be losslessly compressed to .* bytes ' | ||
'\(savings: .* .*%\)') | ||
|
||
NOT_INSTALLED_RE = ('(?:jpegoptim|jpegtran|pngcrush|optipng|optimage) ' | ||
'is not installed') | ||
|
||
def setUp(self): | ||
self.section = Section('') | ||
self.queue = Queue() | ||
self.icb = ImageCompressionBear(None, self.section, self.queue) | ||
|
||
def test_check_prerequisites(self): | ||
_shutil_which = shutil.which | ||
try: | ||
shutil.which = lambda *args, **kwargs: None | ||
self.assertRegex(ImageCompressionBear.check_prerequisites(), | ||
self.NOT_INSTALLED_RE) | ||
finally: | ||
shutil.which = _shutil_which | ||
|
||
def test_clean_jpeg(self): | ||
self.assertEqual([], list(self.icb.run( | ||
image_files=[get_absolute_path('gradient_clean.jpg')]))) | ||
|
||
def test_clean_png(self): | ||
self.assertEqual([], list(self.icb.run( | ||
image_files=[get_absolute_path('gradient_clean.png')]))) | ||
|
||
def test_bloat_jpeg(self): | ||
results = list(self.icb.run( | ||
image_files=[get_absolute_path('gradient_bloat.jpg')])) | ||
|
||
self.assertNotEqual([], results) | ||
self.assertRegex(results[0].message, self.MESSAGE_RE) | ||
|
||
def test_bloat_png(self): | ||
results = list(self.icb.run( | ||
image_files=[get_absolute_path('gradient_bloat.png')])) | ||
|
||
self.assertNotEqual([], results) | ||
self.assertRegex(results[0].message, self.MESSAGE_RE) | ||
|
||
def test_bloat_and_metadata_jpeg(self): | ||
results = list(self.icb.run( | ||
image_files=[ | ||
get_absolute_path('gradient_bloat_and_metadata.jpg')])) | ||
|
||
self.assertNotEqual([], results) | ||
self.assertRegex(results[0].message, self.MESSAGE_RE) | ||
|
||
def test_bloat_and_metadata_png(self): | ||
results = list(self.icb.run(image_files=[ | ||
get_absolute_path('gradient_bloat_and_metadata.png')])) | ||
|
||
self.assertNotEqual([], results) | ||
self.assertRegex(results[0].message, self.MESSAGE_RE) | ||
|
||
def test_metadata_jpeg(self): | ||
results = list(self.icb.run(image_files=[ | ||
get_absolute_path('gradient_metadata.jpg')])) | ||
|
||
self.assertNotEqual([], results) | ||
self.assertRegex(results[0].message, self.MESSAGE_RE) | ||
|
||
def test_metadata_png(self): | ||
results = list(self.icb.run(image_files=[ | ||
get_absolute_path('gradient_metadata.png')])) | ||
|
||
self.assertNotEqual([], results) | ||
self.assertRegex(results[0].message, self.MESSAGE_RE) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.