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

Revision 547, 10.6 kB (checked in by jerome, 17 years ago)

Renamed parser in a more consistent and informational way.

  • 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       pil, mscrap, cfax, lidil, escp2, dvi, tiff, ooo, zjstream, \
32       pnmascii, bj, qpdl, spl1, escpages03, 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.workfile = 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, "Unsupported 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                dummyfile = tempfile.NamedTemporaryFile(mode="w+b")
86                filename = dummyfile.name
87                try :
88                    pdlhandler.convertToTiffMultiPage24NC(filename, self.options.resolution)
89                    result = inkcoverage.getInkCoverage(filename, cspace)
90                finally :   
91                    dummyfile.close()
92            except pdlparser.PDLParserError, msg :   
93                raise pdlparser.PDLParserError, "Unsupported file format for %s (%s)" % (self.filename, msg)
94        finally :
95            self.closeFile()
96        return result
97       
98    def openFile(self) :   
99        """Opens the job's data stream for reading."""
100        self.mustclose = False  # by default we don't want to close the file when finished
101        if hasattr(self.filename, "read") and hasattr(self.filename, "seek") :
102            # filename is in fact a file-like object
103            infile = self.filename
104        elif self.filename == "-" :
105            # we must read from stdin
106            infile = sys.stdin
107        else :   
108            # normal file
109            self.workfile = open(self.filename, "rb")
110            self.mustclose = True
111            return
112           
113        # Use a temporary file, always seekable contrary to standard input.
114        self.workfile = tempfile.NamedTemporaryFile(mode="w+b")
115        self.filename = self.workfile.name
116        while True :
117            data = infile.read(pdlparser.MEGABYTE) 
118            if not data :
119                break
120            self.workfile.write(data)
121        self.workfile.flush()   
122        self.workfile.seek(0)
123           
124    def closeFile(self) :       
125        """Closes the job's data stream if we have to."""
126        if self.mustclose :
127            self.workfile.close()   
128       
129    def readFirstAndLastBlocks(self, inputfile) :
130        """Reads the first and last blocks of data."""
131        # Now read first and last block of the input file
132        # to be able to detect the real file format and the parser to use.
133        firstblock = inputfile.read(pdlparser.FIRSTBLOCKSIZE)
134        try :
135            inputfile.seek(-pdlparser.LASTBLOCKSIZE, 2)
136            lastblock = inputfile.read(pdlparser.LASTBLOCKSIZE)
137        except IOError :   
138            lastblock = ""
139        return (firstblock, 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        (firstblock, lastblock) = self.readFirstAndLastBlocks(self.workfile)
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                       cfax, \
159                       zjstream, \
160                       ooo, \
161                       hbp, \
162                       lidil, \
163                       pcl345, \
164                       escp2, \
165                       escpages03, \
166                       bj, \
167                       pnmascii, \
168                       pil, \
169                       mscrap, \
170                       plain) :     # IMPORTANT : don't move this one up !
171            try :               
172                return module.Parser(self, self.filename, 
173                                           (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 "%i" % totalsize
256        else :   
257            print "\n".join(lines)
258   
259if __name__ == "__main__" :   
260    main()
Note: See TracBrowser for help on using the browser.