root / pkpgcounter / trunk / pkpgpdls / analyzer.py @ 3457

Revision 3457, 10.9 kB (checked in by jerome, 15 years ago)

Fixes #31 (will require a fix in PyKota as well, which references #32)
Added some stuff so we can be sure if the temporary files where created
by pkpgcounter and when/where.

  • Property svn:keywords set to Auth Date Id Rev
RevLine 
[3410]1# -*- coding: utf-8 -*-
[200]2#
3# pkpgcounter : a generic Page Description Language parser
4#
[564]5# (c) 2003, 2004, 2005, 2006, 2007, 2008 Jerome Alet <alet@librelogiciel.com>
[463]6# This program is free software: you can redistribute it and/or modify
[200]7# it under the terms of the GNU General Public License as published by
[463]8# the Free Software Foundation, either version 3 of the License, or
[200]9# (at your option) any later version.
[3436]10#
[200]11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
[3436]15#
[200]16# You should have received a copy of the GNU General Public License
[463]17# along with this program.  If not, see <http://www.gnu.org/licenses/>.
[200]18#
19# $Id$
20#
21
[353]22"""This is the main module of pkpgcounter.
23
24It defines the PDLAnalyzer class, which provides a generic way to parse
25input files, by automatically detecting the best parser to use."""
26
[200]27import sys
[363]28import os
[200]29import tempfile
30
[539]31import version, pdlparser, postscript, pdf, pcl345, pclxl, hbp, \
32       pil, mscrap, cfax, lidil, escp2, dvi, tiff, ooo, zjstream, \
[547]33       pnmascii, bj, qpdl, spl1, escpages03, plain
[363]34import inkcoverage
[200]35
[358]36class AnalyzerOptions :
[353]37    """A class for use as the options parameter to PDLAnalyzer's constructor."""
38    def __init__(self, debug=None,
39                       colorspace=None,
40                       resolution=None) :
41        """Sets initial attributes."""
42        self.debug = debug
43        self.colorspace = colorspace
44        self.resolution = resolution
[3436]45
46
47class PDLAnalyzer :
[200]48    """Class for PDL autodetection."""
[358]49    def __init__(self, filename, options=AnalyzerOptions()) :
[200]50        """Initializes the PDL analyzer.
[3436]51
[200]52           filename is the name of the file or '-' for stdin.
[3436]53           filename can also be a file-like object which
[200]54           supports read() and seek().
55        """
[351]56        self.options = options
[200]57        self.filename = filename
[3436]58        self.workfile = None
[353]59        self.mustclose = None
[3436]60
61    def getJobSize(self) :
[200]62        """Returns the job's size."""
[370]63        size = 0
[200]64        self.openFile()
65        try :
66            try :
[411]67                pdlhandler = self.detectPDLHandler()
[220]68                size = pdlhandler.getJobSize()
[3436]69            except pdlparser.PDLParserError, msg :
[527]70                raise pdlparser.PDLParserError, "Unsupported file format for %s (%s)" % (self.filename, msg)
[3436]71        finally :
[411]72            self.closeFile()
[370]73        return size
[3436]74
[363]75    def getInkCoverage(self, colorspace=None, resolution=None) :
76        """Extracts the percents of ink coverage from the input file."""
77        result = None
78        cspace = colorspace or self.options.colorspace
79        res = resolution or self.options.resolution
80        if (not cspace) or (not res) :
81            raise ValueError, "Invalid colorspace (%s) or resolution (%s)" % (cspace, res)
82        self.openFile()
83        try :
84            try :
[411]85                pdlhandler = self.detectPDLHandler()
[3457]86                dummyfile = tempfile.NamedTemporaryFile(mode="w+b",
87                                                        prefix="pkpgcounter_",
88                                                        suffix=".tiff",
89                                                        dir=os.environ.get("PYKOTADIRECTORY") or tempfile.gettempdir())
[501]90                filename = dummyfile.name
[492]91                try :
92                    pdlhandler.convertToTiffMultiPage24NC(filename, self.options.resolution)
93                    result = inkcoverage.getInkCoverage(filename, cspace)
[3436]94                finally :
[501]95                    dummyfile.close()
[3436]96            except pdlparser.PDLParserError, msg :
[527]97                raise pdlparser.PDLParserError, "Unsupported file format for %s (%s)" % (self.filename, msg)
[492]98        finally :
[411]99            self.closeFile()
[363]100        return result
[3436]101
102    def openFile(self) :
[200]103        """Opens the job's data stream for reading."""
[491]104        self.mustclose = False  # by default we don't want to close the file when finished
[200]105        if hasattr(self.filename, "read") and hasattr(self.filename, "seek") :
[3436]106            # filename is in fact a file-like object
[200]107            infile = self.filename
108        elif self.filename == "-" :
109            # we must read from stdin
110            infile = sys.stdin
[3436]111        else :
[200]112            # normal file
[491]113            self.workfile = open(self.filename, "rb")
114            self.mustclose = True
[200]115            return
[3436]116
[200]117        # Use a temporary file, always seekable contrary to standard input.
[3457]118        self.workfile = tempfile.NamedTemporaryFile(mode="w+b",
119                                                    prefix="pkpgcounter_",
120                                                    suffix=".prn",
121                                                    dir=os.environ.get("PYKOTADIRECTORY") or tempfile.gettempdir())
[491]122        self.filename = self.workfile.name
123        while True :
[3436]124            data = infile.read(pdlparser.MEGABYTE)
[200]125            if not data :
126                break
[491]127            self.workfile.write(data)
[3436]128        self.workfile.flush()
[491]129        self.workfile.seek(0)
[3436]130
131    def closeFile(self) :
[491]132        """Closes the job's data stream if we have to."""
[200]133        if self.mustclose :
[3436]134            self.workfile.close()
135
[522]136    def readFirstAndLastBlocks(self, inputfile) :
[520]137        """Reads the first and last blocks of data."""
138        # Now read first and last block of the input file
139        # to be able to detect the real file format and the parser to use.
[522]140        firstblock = inputfile.read(pdlparser.FIRSTBLOCKSIZE)
[520]141        try :
[522]142            inputfile.seek(-pdlparser.LASTBLOCKSIZE, 2)
143            lastblock = inputfile.read(pdlparser.LASTBLOCKSIZE)
[3436]144        except IOError :
[522]145            lastblock = ""
[3436]146        return (firstblock, lastblock)
147
148    def detectPDLHandler(self) :
[200]149        """Tries to autodetect the document format.
[3436]150
[200]151           Returns the correct PDL handler class or None if format is unknown
[3436]152        """
[491]153        if not os.stat(self.filename).st_size :
[220]154            raise pdlparser.PDLParserError, "input file %s is empty !" % str(self.filename)
[522]155        (firstblock, lastblock) = self.readFirstAndLastBlocks(self.workfile)
[3436]156
[491]157        # IMPORTANT : the order is important below. FIXME.
158        for module in (postscript, \
159                       pclxl, \
160                       pdf, \
161                       qpdl, \
162                       spl1, \
163                       dvi, \
164                       tiff, \
[539]165                       cfax, \
[491]166                       zjstream, \
167                       ooo, \
168                       hbp, \
169                       lidil, \
170                       pcl345, \
171                       escp2, \
172                       escpages03, \
[545]173                       bj, \
[547]174                       pnmascii, \
[501]175                       pil, \
[523]176                       mscrap, \
[491]177                       plain) :     # IMPORTANT : don't move this one up !
[3436]178            try :
179                return module.Parser(self, self.filename,
[529]180                                           (firstblock, lastblock))
[491]181            except pdlparser.PDLParserError :
182                pass # try next parser
[217]183        raise pdlparser.PDLParserError, "Analysis of first data block failed."
[3436]184
185def main() :
[200]186    """Entry point for PDL Analyzer."""
[350]187    import optparse
188    from copy import copy
[3436]189
[350]190    def check_cichoice(option, opt, value) :
191        """To add a CaseIgnore Choice option type."""
192        valower = value.lower()
193        if valower in [v.lower() for v in option.cichoices] :
194            return valower
[3436]195        else :
[353]196            choices = ", ".join([repr(o) for o in option.cichoices])
[350]197            raise optparse.OptionValueError(
198                "option %s: invalid choice: %r (choose from %s)"
199                % (opt, value, choices))
[3436]200
[350]201    class MyOption(optparse.Option) :
202        """New Option class, with CaseIgnore Choice type."""
203        TYPES = optparse.Option.TYPES + ("cichoice",)
204        ATTRS = optparse.Option.ATTRS + ["cichoices"]
205        TYPE_CHECKER = copy(optparse.Option.TYPE_CHECKER)
206        TYPE_CHECKER["cichoice"] = check_cichoice
[3436]207
208    parser = optparse.OptionParser(option_class=MyOption,
[350]209                                   usage="python analyzer.py [options] file1 [file2 ...]")
[3436]210    parser.add_option("-v", "--version",
211                            action="store_true",
[348]212                            dest="version",
[350]213                            help="Show pkpgcounter's version number and exit.")
[3436]214    parser.add_option("-d", "--debug",
215                            action="store_true",
[348]216                            dest="debug",
[350]217                            help="Activate debug mode.")
[3436]218    parser.add_option("-c", "--colorspace",
[350]219                            dest="colorspace",
220                            type="cichoice",
[439]221                            cichoices=["bw", "rgb", "cmyk", "cmy", "gc"],
222                            help="Activate the computation of ink usage, and defines the colorspace to use. Supported values are 'BW', 'RGB', 'CMYK', 'CMY', and 'GC'.")
[3436]223    parser.add_option("-r", "--resolution",
224                            type="int",
225                            default=72,
[350]226                            dest="resolution",
[367]227                            help="The resolution in DPI to use when checking ink usage. Lower resolution is faster but less accurate. Default is 72 dpi.")
[348]228    (options, arguments) = parser.parse_args()
229    if options.version :
[3456]230        sys.stdout.write("%s\n" % version.__version__)
[3436]231    elif not (72 <= options.resolution <= 1200) :
[367]232        sys.stderr.write("ERROR: the argument to the --resolution command line option must be between 72 and 1200.\n")
[358]233        sys.stderr.flush()
[200]234    else :
[348]235        if (not arguments) or ((not sys.stdin.isatty()) and ("-" not in arguments)) :
236            arguments.append("-")
[3436]237        totalsize = 0
[377]238        lines = []
[350]239        try :
240            for arg in arguments :
241                try :
[351]242                    parser = PDLAnalyzer(arg, options)
[363]243                    if not options.colorspace :
244                        totalsize += parser.getJobSize()
245                    else :
[376]246                        (cspace, pages) = parser.getInkCoverage()
247                        for page in pages :
[377]248                            lineparts = []
249                            for k in cspace : # NB : this way we preserve the order of the planes
[376]250                                try :
[383]251                                    lineparts.append("%s : %s%%" % (k, ("%f" % page[k]).rjust(10)))
[376]252                                except KeyError :
253                                    pass
[3436]254                            lines.append("      ".join(lineparts))
255                except (IOError, pdlparser.PDLParserError), msg :
[350]256                    sys.stderr.write("ERROR: %s\n" % msg)
257                    sys.stderr.flush()
[3436]258        except KeyboardInterrupt :
[350]259            sys.stderr.write("WARN: Aborted at user's request.\n")
260            sys.stderr.flush()
[3436]261        if not options.colorspace :
[3456]262            sys.stdout.write("%i\n" % totalsize)
[3436]263        else :
[3456]264            sys.stdout.write("%s\n" % ("\n".join(lines)))
[3436]265
266if __name__ == "__main__" :
[200]267    main()
Note: See TracBrowser for help on using the browser.