diff -r ae9fb85ab4e0 Doc/library/zipfile.rst --- a/Doc/library/zipfile.rst Tue Dec 03 21:49:30 2013 -0600 +++ b/Doc/library/zipfile.rst Thu Dec 05 10:16:39 2013 +0100 @@ -423,7 +423,8 @@ .. attribute:: ZipInfo.compress_type - Type of compression for the archive member. + Type of compression for the archive member. Can only be either of + :const:`ZIP_STORED` or :const:`ZIP_DEFLATED`. .. attribute:: ZipInfo.comment @@ -460,7 +461,14 @@ .. attribute:: ZipInfo.flag_bits - ZIP flag bits. + ZIP flag bits. With the :const:`ZIP_DEFLATED` compression method, bits 1 + and 2 of this flag control the compression level. The four levels, in + increasing order of compression, are: :const:`DEF_S` (Store only), + :const:`DEF_F` (Fast), :const:`DEF_N` (Normal) and :const:`DEF_X` (Maximum). + + To set the level, you need to remember to bit-shift these constants:: + + zipinfo.flag_bits = (zipfile.DEF_X << 1) .. attribute:: ZipInfo.volume diff -r ae9fb85ab4e0 Lib/test/test_zipfile.py --- a/Lib/test/test_zipfile.py Tue Dec 03 21:49:30 2013 -0600 +++ b/Lib/test/test_zipfile.py Thu Dec 05 10:16:39 2013 +0100 @@ -13,11 +13,14 @@ import zipfile import unittest +from copy import copy from StringIO import StringIO from tempfile import TemporaryFile from random import randint, random from unittest import skipUnless +from zipfile import DEF_N, DEF_X, DEF_F, DEF_S + from test.test_support import TESTFN, TESTFN_UNICODE, TESTFN_ENCODING, \ run_unittest, findfile, unlink try: @@ -1316,6 +1319,48 @@ for f in (TESTFN2, TemporaryFile(), io.BytesIO()): self.zip_random_open_test(f, zipfile.ZIP_DEFLATED) + @skipUnless(zlib, "requires zlib") + def test_compression_level_from_zipinfo(self): + default_info = zipfile.ZipInfo() + default_info.compress_type = zipfile.ZIP_DEFLATED + default_info.internal_attr = 0 # Set: Ascii Unset: Binary + + deflate_lvls = [DEF_S, DEF_F, DEF_N, DEF_X] + # Arranged in increasing order of compression + + f = StringIO() + with zipfile.ZipFile(f, "w") as zipfp: + for i, level in enumerate(deflate_lvls): + fname = 'test%d_lvl%d.txt' % (i, level) + # fnames.append(fname) + zipinfo = copy(default_info) + zipinfo.flag_bits = (level << 1) + zipinfo.filename = fname + + zipfp.writestr(zipinfo, self.data) + + info_objs = zipfp.infolist() + info_objs.sort(key = lambda i: i.filename) + + # names = [i.filename for i in info_objs] + # self.assertEqual(fnames, names) + + last_compress_size = len(self.data) * 10 + for i, info_obj in enumerate(info_objs): + level = (info_obj.flag_bits & 0b110) >> 1 + self.assertEqual(level, deflate_lvls[i]) + + compress_size = info_obj.compress_size + if level == DEF_X: + self.assertTrue(compress_size <= last_compress_size) + # This test should also always be strictly less, too, but + # it's not easy to come up with a simple byte string where + # this is the case... + else: + self.assertTrue(compress_size < last_compress_size) + # print info_obj.filename, level, compress_size + last_compress_size = compress_size + @skipUnless(zlib, "requires zlib") class TestsWithMultipleOpens(unittest.TestCase): diff -r ae9fb85ab4e0 Lib/zipfile.py --- a/Lib/zipfile.py Tue Dec 03 21:49:30 2013 -0600 +++ b/Lib/zipfile.py Thu Dec 05 10:16:39 2013 +0100 @@ -131,6 +131,16 @@ _CD64_DIRECTORY_SIZE = 8 _CD64_OFFSET_START_CENTDIR = 9 +# bit flag enums for Deflate compression levels (section 4.4.4 in APPNOTE.TXT), +# and their mapping to zlib parameters +DEF_N, DEF_X, DEF_F, DEF_S = 0b00, 0b01, 0b10, 0b11 +deflate_opts = { + DEF_S: (zlib.Z_DEFAULT_STRATEGY, 'defS'), # 0 Super Fast. Store only + DEF_F: (zlib.Z_BEST_SPEED, 'defF'), # 1 Fast + DEF_X: (zlib.Z_BEST_COMPRESSION, 'defX'), # 9 Maximum + DEF_N: (zlib.Z_DEFAULT_COMPRESSION, 'defN'), # 6 Normal (-1 in zlib module def) +} + def _check_zipfile(fp): try: if _EndRecData(fp): @@ -879,10 +889,16 @@ def printdir(self): """Print a table of contents for the zip file.""" - print "%-46s %19s %12s" % ("File Name", "Modified ", "Size") + print "%-38s %s %18s %12s" % ("File Name", "Deflate", + "Modified ", "Size") for zinfo in self.filelist: + def_type = ' ' + if zinfo.compress_type == ZIP_DEFLATED: + def_lvl = (zinfo.flag_bits & 0b110) >> 1 + _, def_type = deflate_opts[def_lvl] date = "%d-%02d-%02d %02d:%02d:%02d" % zinfo.date_time[:6] - print "%-46s %s %12d" % (zinfo.filename, date, zinfo.file_size) + print "%-40s %s %s %12d" % (zinfo.filename, def_type, + date, zinfo.file_size) def testzip(self): """Read all the files and check the CRC.""" @@ -1219,8 +1235,12 @@ self._didModify = True zinfo.CRC = crc32(bytes) & 0xffffffff # CRC-32 checksum if zinfo.compress_type == ZIP_DEFLATED: - co = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, - zlib.DEFLATED, -15) + level_index = (zinfo.flag_bits & 0b110) >> 1 + level, _ = _deflate_opts[level_index] + co = zlib.compressobj(level, zlib.DEFLATED, -15) + # The zlib docs say the WBITS value should be an int from 8 to 15, + # and that it's the base 2 log of the size of the window buffer. + # So how does the above wbits value of -15 work then? bytes = co.compress(bytes) + co.flush() zinfo.compress_size = len(bytes) # Compressed size else: