[kde-artists] PS: proposal to losslessly shrink jpeg files
Martin Walch <[email protected]>
| Newsgroups | gmane.comp.kde.artists |
|---|---|
| Message-ID | <1719186.K4Z2jDXumA@tacticalops> |
Sorry. Forgot to re-attach the file. ______________________________________________________________________________ [email protected] | https://mail.kde.org/mailman/listinfo/kde-artists
compress-jpg.py
(text/x-python, 5 KB)
#!/usr/bin/python
# -*- coding: utf_8 -*-
# version 0.1
# Copyright 2012 Martin Walch
# redistributable under the terms of the GNU GPL version 3
import argparse
import os
import signal
import stat
import subprocess
import sys
import tempfile
def clean_file(filename):
"""make sure the file filename is deleted"""
if os.path.exists(filename):
os.unlink(filename)
def new_file(filename):
"""make sure any old file is deleted and create a new file"""
clean_file(filename)
return open(filename, mode='wb')
def merge_new_file(path, new_file):
"""Replace the original file if the new file is smaller."""
new_file.close()
size_old = os.stat(path).st_size
size_new = os.stat(new_file.name).st_size
if size_new < size_old:
if arguments.keep_timestamp:
# touch from coreutils (http://www.gnu.org/software/coreutils/)
subprocess.call(["touch", "-r", path, new_file.name])
os.chmod(new_file.name, os.stat(path).st_mode)
os.rename(new_file.name, path)
if not arguments.quiet:
print(' ' + str(size_old) + ' -> ' + str(size_new))
else:
clean_file(new_file.name)
def jpegtran(path, output_file, progressive):
"""try optimization with jpegtran"""
# jpegtran from IJG's jpeg implementation (http://www.ijg.org/)
# or from libjpeg-turbo (http://libjpeg-turbo.virtualgl.org/)
argv = ["jpegtran", "-o", "-copy", "all", path]
if progressive:
argv.insert(1, "-progressive")
status = subprocess.call(argv, stdout=output_file)
if status == 0:
merge_new_file(path, output_file)
elif not arguments.quiet:
print('Warning: jpegtran failed. Maybe the input file is corrupt?')
def exif(path, output_file):
"""try removal of embedded thumbnails"""
sink = open('/dev/null', 'w')
# exif from libexif (http://libexif.sourceforge.net/)
status = subprocess.call(
["exif", "--no-fixup", "-r", "-o", output_file.name, path],
stdout=sink,
stderr=subprocess.STDOUT)
if status == 0:
merge_new_file(path, output_file)
def is_jpeg(path):
"""return True iff file that is located at path is a jpeg file"""
# the widely used file command (https://github.com/glensc/file)
filetype = subprocess.check_output(["file", "-bi", path])
if filetype[:11].decode(sys.stdout.encoding) == "image/jpeg;":
return True
else:
return False
def process_file(path):
"""process a single jpeg file"""
global name_temp_file
temp_file = tempfile.NamedTemporaryFile(
dir=os.path.dirname(path),
delete=True)
name_temp_file = temp_file.name
temp_file.close()
if not arguments.quiet:
print(path)
temp_file = new_file(name_temp_file)
jpegtran(path, temp_file, False)
if arguments.progressive:
temp_file = new_file(name_temp_file)
jpegtran(path, temp_file, True)
if arguments.drop_thumbnail:
temp_file = new_file(name_temp_file)
exif(path, temp_file)
clean_file(name_temp_file)
def process_dir(path):
"""traverse the file system hierarchy below the directory path"""
spinner = '|/-\\-/'
spinner_pos = 0
for directory, subdirectories, files in os.walk(path):
for next_file in files:
file_path = os.path.join(directory, next_file)
if not arguments.quiet:
sys.stdout.write('\r' + spinner[spinner_pos])
spinner_pos = (spinner_pos + 1) % len(spinner)
sys.stdout.flush()
if is_jpeg(file_path):
sys.stdout.write('\r')
process_file(file_path)
def signal_handler(signum, frame):
"""catch termination request to clean up before exiting, skip backtrace"""
print('\ntermination signal caught, cleaning up and exiting…')
if 'name_temp_file' in globals() and os.path.exists(name_temp_file):
os.unlink(name_temp_file)
sys.exit(-1)
def main():
global arguments
parser = argparse.ArgumentParser()
parser.add_argument(
'target',
type=str)
parser.add_argument(
'--no-progressive',
dest='progressive',
action="store_false")
parser.add_argument(
'--keep-thumbnail',
dest='drop_thumbnail',
action="store_false")
parser.add_argument(
'--new-timestamp',
dest='keep_timestamp',
action="store_true")
parser.add_argument(
'--quiet',
dest='quiet',
action="store_true")
arguments = parser.parse_args()
target = os.path.abspath(arguments.target)
mode = os.stat(target).st_mode
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler)
if stat.S_ISREG(mode):
if is_jpeg(target):
process_file(target)
else:
print('not a jpeg (the file command did not identify it as such)')
elif stat.S_ISDIR(mode):
process_dir(target)
main()
signature.asc
(application/pgp-signature, 198 B)
-----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.17 (GNU/Linux) iEYEABECAAYFAk/ACNsACgkQM6S4K8IXn9deFwCeIzWRRwYv6fbBoCMRBIDXLoWV MPQAn1YqaDazP7pbC2xRyj03zXjh/Sio =a8fC -----END PGP SIGNATURE-----