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

Revision 501, 10.3 kB (checked in by jerome, 16 years ago)

Added parser for all image formats supported by PIL.

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