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

Revision 450, 10.7 kB (checked in by jerome, 17 years ago)

Now uses Python's universal newline detection to read input files,
and also uses file objects directly instead of calling their xreadlines()
method.
Fixed an accounting problem in the PDF parser for some type of files.

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