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

Revision 480, 10.8 kB (checked in by jerome, 17 years ago)

v3.20 is out with support for Brother HBP and improved PCLXL
parser wrt the inclusion of Canon ImageRunner? commands.

  • 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, 2007 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 3 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, see <http://www.gnu.org/licenses/>.
17#
18# $Id$
19#
20
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
26import sys
27import os
28import tempfile
29
30import version, pdlparser, postscript, pdf, pcl345, pclxl, hbp, \
31       escp2, dvi, tiff, ooo, zjstream, qpdl, spl1, escpages03, plain
32import inkcoverage
33
34class AnalyzerOptions :
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
43   
44   
45class PDLAnalyzer :   
46    """Class for PDL autodetection."""
47    def __init__(self, filename, options=AnalyzerOptions()) :
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        """
54        self.options = options
55        self.filename = filename
56        self.infile = None
57        self.mustclose = None
58       
59    def getJobSize(self) :   
60        """Returns the job's size."""
61        size = 0
62        self.openFile()
63        try :
64            try :
65                pdlhandler = self.detectPDLHandler()
66                size = pdlhandler.getJobSize()
67            except pdlparser.PDLParserError, msg :   
68                raise pdlparser.PDLParserError, "Unknown file format for %s (%s)" % (self.filename, msg)
69        finally :   
70            self.closeFile()
71        return size
72           
73    def getInkCoverage(self, colorspace=None, resolution=None) :
74        """Extracts the percents of ink coverage from the input file."""
75        result = None
76        cspace = colorspace or self.options.colorspace
77        res = resolution or self.options.resolution
78        if (not cspace) or (not res) :
79            raise ValueError, "Invalid colorspace (%s) or resolution (%s)" % (cspace, res)
80        self.openFile()
81        try :
82            try :
83                pdlhandler = self.detectPDLHandler()
84                (handle, filename) = tempfile.mkstemp(".tmp", "pkpgcounter")   
85                os.close(handle)
86                try :
87                    pdlhandler.convertToTiffMultiPage24NC(filename, self.options.resolution)
88                    result = inkcoverage.getInkCoverage(filename, cspace)
89                finally :   
90                    try :
91                        os.remove(filename)
92                    except OSError :
93                        sys.stderr.write("Problem when trying to remove temporary file %s\n" % filename)
94            except pdlparser.PDLParserError, msg :   
95                raise pdlparser.PDLParserError, "Unknown file format for %s (%s)" % (self.filename, msg)
96        finally :   
97            self.closeFile()
98        return result
99       
100    def openFile(self) :   
101        """Opens the job's data stream for reading."""
102        self.mustclose = 0  # by default we don't want to close the file when finished
103        if hasattr(self.filename, "read") and hasattr(self.filename, "seek") :
104            # filename is in fact a file-like object
105            infile = self.filename
106        elif self.filename == "-" :
107            # we must read from stdin
108            infile = sys.stdin
109        else :   
110            # normal file
111            self.infile = open(self.filename, "rbU")
112            self.mustclose = 1
113            return
114           
115        # Use a temporary file, always seekable contrary to standard input.
116        self.infile = tempfile.TemporaryFile(mode="w+b") # TODO : not opened in universal newline mode, Python 2.5 refuses.
117        while 1 :
118            data = infile.read(pdlparser.MEGABYTE) 
119            if not data :
120                break
121            self.infile.write(data)
122        self.infile.flush()   
123        self.infile.seek(0)
124           
125    def closeFile(self) :       
126        """Closes the job's data stream if we can close it."""
127        if self.mustclose :
128            self.infile.close()   
129        else :   
130            # if we don't have to close the file, then
131            # ensure the file pointer is reset to the
132            # start of the file in case the process wants
133            # to read the file again.
134            try :
135                self.infile.seek(0)
136            except IOError :   
137                pass    # probably stdin, which is not seekable
138       
139    def detectPDLHandler(self) :   
140        """Tries to autodetect the document format.
141       
142           Returns the correct PDL handler class or None if format is unknown
143        """   
144        # Try to detect file type by reading first and last blocks of datas   
145        # Each parser can read them automatically, but here we do this only once.
146        self.infile.seek(0)
147        firstblock = self.infile.read(pdlparser.FIRSTBLOCKSIZE)
148        try :
149            self.infile.seek(-pdlparser.LASTBLOCKSIZE, 2)
150            lastblock = self.infile.read(pdlparser.LASTBLOCKSIZE)
151        except IOError :   
152            lastblock = ""
153        self.infile.seek(0)
154        if not firstblock :
155            raise pdlparser.PDLParserError, "input file %s is empty !" % str(self.filename)
156        else :   
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                           pcl345, \
169                           escp2, \
170                           escpages03, \
171                           plain) :     # IMPORTANT : don't move this one up !
172                try :               
173                    return module.Parser(self.infile, self.options.debug, firstblock, lastblock)
174                except pdlparser.PDLParserError :
175                    pass # try next parser
176        raise pdlparser.PDLParserError, "Analysis of first data block failed."
177           
178def main() :   
179    """Entry point for PDL Analyzer."""
180    import optparse
181    from copy import copy
182   
183    def check_cichoice(option, opt, value) :
184        """To add a CaseIgnore Choice option type."""
185        valower = value.lower()
186        if valower in [v.lower() for v in option.cichoices] :
187            return valower
188        else :   
189            choices = ", ".join([repr(o) for o in option.cichoices])
190            raise optparse.OptionValueError(
191                "option %s: invalid choice: %r (choose from %s)"
192                % (opt, value, choices))
193   
194    class MyOption(optparse.Option) :
195        """New Option class, with CaseIgnore Choice type."""
196        TYPES = optparse.Option.TYPES + ("cichoice",)
197        ATTRS = optparse.Option.ATTRS + ["cichoices"]
198        TYPE_CHECKER = copy(optparse.Option.TYPE_CHECKER)
199        TYPE_CHECKER["cichoice"] = check_cichoice
200       
201    parser = optparse.OptionParser(option_class=MyOption, 
202                                   usage="python analyzer.py [options] file1 [file2 ...]")
203    parser.add_option("-v", "--version", 
204                            action="store_true", 
205                            dest="version",
206                            help="Show pkpgcounter's version number and exit.")
207    parser.add_option("-d", "--debug", 
208                            action="store_true", 
209                            dest="debug",
210                            help="Activate debug mode.")
211    parser.add_option("-c", "--colorspace", 
212                            dest="colorspace",
213                            type="cichoice",
214                            cichoices=["bw", "rgb", "cmyk", "cmy", "gc"],
215                            help="Activate the computation of ink usage, and defines the colorspace to use. Supported values are 'BW', 'RGB', 'CMYK', 'CMY', and 'GC'.")
216    parser.add_option("-r", "--resolution", 
217                            type="int", 
218                            default=72, 
219                            dest="resolution",
220                            help="The resolution in DPI to use when checking ink usage. Lower resolution is faster but less accurate. Default is 72 dpi.")
221    (options, arguments) = parser.parse_args()
222    if options.version :
223        print "%s" % version.__version__
224    elif not (72 <= options.resolution <= 1200) :   
225        sys.stderr.write("ERROR: the argument to the --resolution command line option must be between 72 and 1200.\n")
226        sys.stderr.flush()
227    else :
228        if (not arguments) or ((not sys.stdin.isatty()) and ("-" not in arguments)) :
229            arguments.append("-")
230        totalsize = 0   
231        lines = []
232        try :
233            for arg in arguments :
234                try :
235                    parser = PDLAnalyzer(arg, options)
236                    if not options.colorspace :
237                        totalsize += parser.getJobSize()
238                    else :
239                        (cspace, pages) = parser.getInkCoverage()
240                        for page in pages :
241                            lineparts = []
242                            for k in cspace : # NB : this way we preserve the order of the planes
243                                try :
244                                    lineparts.append("%s : %s%%" % (k, ("%f" % page[k]).rjust(10)))
245                                except KeyError :
246                                    pass
247                            lines.append("      ".join(lineparts))     
248                except (IOError, pdlparser.PDLParserError), msg :   
249                    sys.stderr.write("ERROR: %s\n" % msg)
250                    sys.stderr.flush()
251        except KeyboardInterrupt :           
252            sys.stderr.write("WARN: Aborted at user's request.\n")
253            sys.stderr.flush()
254        if not options.colorspace :   
255            print "%s" % totalsize
256        else :   
257            print "\n".join(lines)
258   
259if __name__ == "__main__" :   
260    main()
Note: See TracBrowser for help on using the browser.