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

Revision 520, 10.4 kB (checked in by jerome, 16 years ago)

Code cleaning.

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