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

Revision 524, 10.8 kB (checked in by jerome, 16 years ago)

Added helper function to later detect the presence of executable dependencies at runtime.

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