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

Revision 377, 10.9 kB (checked in by jerome, 16 years ago)

Don't output the number of pages anymore in ink coverage mode, the
number of pages is equal to the number of lines in the result anyway.

  • Property svn:keywords set to Auth Date Id Rev
Line 
1#
2# pkpgcounter : a generic Page Description Language parser
3#
4# (c) 2003, 2004, 2005, 2006 Jerome Alet <alet@librelogiciel.com>
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
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
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18#
19# $Id$
20#
21
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
27import sys
28import os
29import tempfile
30
31import version, pdlparser, postscript, pdf, pcl345, pclxl, \
32       escp2, dvi, tiff, ooo, zjstream
33import inkcoverage
34
35class AnalyzerOptions :
36    """A class for use as the options parameter to PDLAnalyzer's constructor."""
37    def __init__(self, debug=None,
38                       colorspace=None,
39                       resolution=None) :
40        """Sets initial attributes."""
41        self.debug = debug
42        self.colorspace = colorspace
43        self.resolution = resolution
44   
45   
46class PDLAnalyzer :   
47    """Class for PDL autodetection."""
48    def __init__(self, filename, options=AnalyzerOptions()) :
49        """Initializes the PDL analyzer.
50       
51           filename is the name of the file or '-' for stdin.
52           filename can also be a file-like object which
53           supports read() and seek().
54        """
55        self.options = options
56        self.filename = filename
57        self.infile = None
58        self.mustclose = None
59       
60    def getJobSize(self) :   
61        """Returns the job's size."""
62        size = 0
63        self.openFile()
64        try :
65            pdlhandler = self.detectPDLHandler()
66        except pdlparser.PDLParserError, msg :   
67            self.closeFile()
68            raise pdlparser.PDLParserError, "Unknown file format for %s (%s)" % (self.filename, msg)
69        else :
70            try :
71                size = pdlhandler.getJobSize()
72            finally :   
73                self.closeFile()
74        return size
75           
76    def getInkCoverage(self, colorspace=None, resolution=None) :
77        """Extracts the percents of ink coverage from the input file."""
78        result = None
79        cspace = colorspace or self.options.colorspace
80        res = resolution or self.options.resolution
81        if (not cspace) or (not res) :
82            raise ValueError, "Invalid colorspace (%s) or resolution (%s)" % (cspace, res)
83        self.openFile()
84        try :
85            pdlhandler = self.detectPDLHandler()
86        except pdlparser.PDLParserError, msg :   
87            self.closeFile()
88            raise pdlparser.PDLParserError, "Unknown file format for %s (%s)" % (self.filename, msg)
89        else :
90            try :
91                tiffname = self.convertToTiffMultiPage24NC(pdlhandler)
92                result = inkcoverage.getInkCoverage(tiffname, cspace)
93                try :
94                    os.remove(tiffname)
95                except OSError :
96                    sys.stderr.write("Problem when trying to remove temporary file %s\n" % tiffname)
97            finally :   
98                self.closeFile()
99        return result
100       
101    def convertToTiffMultiPage24NC(self, handler) :   
102        """Converts the input file to TIFF format, X dpi, 24 bits per pixel, uncompressed.
103           Returns a temporary filename which names a file containing the TIFF datas.
104           The temporary file has to be deleted by the caller.
105        """   
106        self.infile.seek(0)
107        (handle, filename) = tempfile.mkstemp(".tmp", "pkpgcounter")   
108        os.close(handle)
109        handler.convertToTiffMultiPage24NC(filename, self.options.resolution)
110        return filename
111       
112    def openFile(self) :   
113        """Opens the job's data stream for reading."""
114        self.mustclose = 0  # by default we don't want to close the file when finished
115        if hasattr(self.filename, "read") and hasattr(self.filename, "seek") :
116            # filename is in fact a file-like object
117            infile = self.filename
118        elif self.filename == "-" :
119            # we must read from stdin
120            infile = sys.stdin
121        else :   
122            # normal file
123            self.infile = open(self.filename, "rb")
124            self.mustclose = 1
125            return
126           
127        # Use a temporary file, always seekable contrary to standard input.
128        self.infile = tempfile.TemporaryFile(mode="w+b")
129        while 1 :
130            data = infile.read(pdlparser.MEGABYTE) 
131            if not data :
132                break
133            self.infile.write(data)
134        self.infile.flush()   
135        self.infile.seek(0)
136           
137    def closeFile(self) :       
138        """Closes the job's data stream if we can close it."""
139        if self.mustclose :
140            self.infile.close()   
141        else :   
142            # if we don't have to close the file, then
143            # ensure the file pointer is reset to the
144            # start of the file in case the process wants
145            # to read the file again.
146            try :
147                self.infile.seek(0)
148            except IOError :   
149                pass    # probably stdin, which is not seekable
150       
151    def detectPDLHandler(self) :   
152        """Tries to autodetect the document format.
153       
154           Returns the correct PDL handler class or None if format is unknown
155        """   
156        # Try to detect file type by reading first and last blocks of datas   
157        # Each parser can read them automatically, but here we do this only once.
158        self.infile.seek(0)
159        firstblock = self.infile.read(pdlparser.FIRSTBLOCKSIZE)
160        try :
161            self.infile.seek(-pdlparser.LASTBLOCKSIZE, 2)
162            lastblock = self.infile.read(pdlparser.LASTBLOCKSIZE)
163        except IOError :   
164            lastblock = ""
165        self.infile.seek(0)
166        if not firstblock :
167            raise pdlparser.PDLParserError, "input file %s is empty !" % str(self.filename)
168        else :   
169            for module in (postscript, \
170                           pclxl, \
171                           pdf, \
172                           pcl345, \
173                           escp2, \
174                           dvi, \
175                           tiff, \
176                           zjstream, \
177                           ooo) :
178                try :               
179                    return module.Parser(self.infile, self.options.debug, firstblock, lastblock)
180                except pdlparser.PDLParserError :
181                    pass # try next parser
182        raise pdlparser.PDLParserError, "Analysis of first data block failed."
183           
184def main() :   
185    """Entry point for PDL Analyzer."""
186    import optparse
187    from copy import copy
188   
189    def check_cichoice(option, opt, value) :
190        """To add a CaseIgnore Choice option type."""
191        valower = value.lower()
192        if valower in [v.lower() for v in option.cichoices] :
193            return valower
194        else :   
195            choices = ", ".join([repr(o) for o in option.cichoices])
196            raise optparse.OptionValueError(
197                "option %s: invalid choice: %r (choose from %s)"
198                % (opt, value, choices))
199   
200    class MyOption(optparse.Option) :
201        """New Option class, with CaseIgnore Choice type."""
202        TYPES = optparse.Option.TYPES + ("cichoice",)
203        ATTRS = optparse.Option.ATTRS + ["cichoices"]
204        TYPE_CHECKER = copy(optparse.Option.TYPE_CHECKER)
205        TYPE_CHECKER["cichoice"] = check_cichoice
206       
207    parser = optparse.OptionParser(option_class=MyOption, 
208                                   usage="python analyzer.py [options] file1 [file2 ...]")
209    parser.add_option("-v", "--version", 
210                            action="store_true", 
211                            dest="version",
212                            help="Show pkpgcounter's version number and exit.")
213    parser.add_option("-d", "--debug", 
214                            action="store_true", 
215                            dest="debug",
216                            help="Activate debug mode.")
217    parser.add_option("-c", "--colorspace", 
218                            dest="colorspace",
219                            type="cichoice",
220                            cichoices=["bw", "rgb", "cmyk", "cmy"],
221                            help="Activate the computation of ink usage, and defines the colorspace to use. Supported values are 'BW', 'RGB', 'CMYK', and 'CMY'.")
222    parser.add_option("-r", "--resolution", 
223                            type="int", 
224                            default=72, 
225                            dest="resolution",
226                            help="The resolution in DPI to use when checking ink usage. Lower resolution is faster but less accurate. Default is 72 dpi.")
227    (options, arguments) = parser.parse_args()
228    if options.version :
229        print "%s" % version.__version__
230    elif not (72 <= options.resolution <= 1200) :   
231        sys.stderr.write("ERROR: the argument to the --resolution command line option must be between 72 and 1200.\n")
232        sys.stderr.flush()
233    else :
234        if (not arguments) or ((not sys.stdin.isatty()) and ("-" not in arguments)) :
235            arguments.append("-")
236        totalsize = 0   
237        lines = []
238        try :
239            for arg in arguments :
240                try :
241                    parser = PDLAnalyzer(arg, options)
242                    if not options.colorspace :
243                        totalsize += parser.getJobSize()
244                    else :
245                        (cspace, pages) = parser.getInkCoverage()
246                        for page in pages :
247                            lineparts = []
248                            for k in cspace : # NB : this way we preserve the order of the planes
249                                try :
250                                    lineparts.append("%s : %f%%" % (k, page[k]))
251                                except KeyError :
252                                    pass
253                            lines.append("      ".join(lineparts))     
254                except (IOError, pdlparser.PDLParserError), msg :   
255                    sys.stderr.write("ERROR: %s\n" % msg)
256                    sys.stderr.flush()
257        except KeyboardInterrupt :           
258            sys.stderr.write("WARN: Aborted at user's request.\n")
259            sys.stderr.flush()
260        if not options.colorspace :   
261            print "%s" % totalsize
262        else :   
263            print "\n".join(lines)
264   
265if __name__ == "__main__" :   
266    main()
Note: See TracBrowser for help on using the browser.