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

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

Renamed module.

  • 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, mscrap, \
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 readFirstAndLastBlocks(self, inputfile) :
129        """Reads the first and last blocks of data."""
130        # Now read first and last block of the input file
131        # to be able to detect the real file format and the parser to use.
132        firstblock = inputfile.read(pdlparser.FIRSTBLOCKSIZE)
133        try :
134            inputfile.seek(-pdlparser.LASTBLOCKSIZE, 2)
135            lastblock = inputfile.read(pdlparser.LASTBLOCKSIZE)
136        except IOError :   
137            lastblock = ""
138        return (firstblock, lastblock)     
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        if not os.stat(self.filename).st_size :
146            raise pdlparser.PDLParserError, "input file %s is empty !" % str(self.filename)
147        (firstblock, lastblock) = self.readFirstAndLastBlocks(self.workfile)
148           
149        # IMPORTANT : the order is important below. FIXME.
150        for module in (postscript, \
151                       pclxl, \
152                       pdf, \
153                       qpdl, \
154                       spl1, \
155                       dvi, \
156                       tiff, \
157                       zjstream, \
158                       ooo, \
159                       hbp, \
160                       lidil, \
161                       pcl345, \
162                       escp2, \
163                       escpages03, \
164                       pil, \
165                       mscrap, \
166                       plain) :     # IMPORTANT : don't move this one up !
167            try :               
168                return module.Parser(self, (firstblock, lastblock))
169            except pdlparser.PDLParserError :
170                pass # try next parser
171        raise pdlparser.PDLParserError, "Analysis of first data block failed."
172           
173def main() :   
174    """Entry point for PDL Analyzer."""
175    import optparse
176    from copy import copy
177   
178    def check_cichoice(option, opt, value) :
179        """To add a CaseIgnore Choice option type."""
180        valower = value.lower()
181        if valower in [v.lower() for v in option.cichoices] :
182            return valower
183        else :   
184            choices = ", ".join([repr(o) for o in option.cichoices])
185            raise optparse.OptionValueError(
186                "option %s: invalid choice: %r (choose from %s)"
187                % (opt, value, choices))
188   
189    class MyOption(optparse.Option) :
190        """New Option class, with CaseIgnore Choice type."""
191        TYPES = optparse.Option.TYPES + ("cichoice",)
192        ATTRS = optparse.Option.ATTRS + ["cichoices"]
193        TYPE_CHECKER = copy(optparse.Option.TYPE_CHECKER)
194        TYPE_CHECKER["cichoice"] = check_cichoice
195       
196    parser = optparse.OptionParser(option_class=MyOption, 
197                                   usage="python analyzer.py [options] file1 [file2 ...]")
198    parser.add_option("-v", "--version", 
199                            action="store_true", 
200                            dest="version",
201                            help="Show pkpgcounter's version number and exit.")
202    parser.add_option("-d", "--debug", 
203                            action="store_true", 
204                            dest="debug",
205                            help="Activate debug mode.")
206    parser.add_option("-c", "--colorspace", 
207                            dest="colorspace",
208                            type="cichoice",
209                            cichoices=["bw", "rgb", "cmyk", "cmy", "gc"],
210                            help="Activate the computation of ink usage, and defines the colorspace to use. Supported values are 'BW', 'RGB', 'CMYK', 'CMY', and 'GC'.")
211    parser.add_option("-r", "--resolution", 
212                            type="int", 
213                            default=72, 
214                            dest="resolution",
215                            help="The resolution in DPI to use when checking ink usage. Lower resolution is faster but less accurate. Default is 72 dpi.")
216    (options, arguments) = parser.parse_args()
217    if options.version :
218        print "%s" % version.__version__
219    elif not (72 <= options.resolution <= 1200) :   
220        sys.stderr.write("ERROR: the argument to the --resolution command line option must be between 72 and 1200.\n")
221        sys.stderr.flush()
222    else :
223        if (not arguments) or ((not sys.stdin.isatty()) and ("-" not in arguments)) :
224            arguments.append("-")
225        totalsize = 0   
226        lines = []
227        try :
228            for arg in arguments :
229                try :
230                    parser = PDLAnalyzer(arg, options)
231                    if not options.colorspace :
232                        totalsize += parser.getJobSize()
233                    else :
234                        (cspace, pages) = parser.getInkCoverage()
235                        for page in pages :
236                            lineparts = []
237                            for k in cspace : # NB : this way we preserve the order of the planes
238                                try :
239                                    lineparts.append("%s : %s%%" % (k, ("%f" % page[k]).rjust(10)))
240                                except KeyError :
241                                    pass
242                            lines.append("      ".join(lineparts))     
243                except (IOError, pdlparser.PDLParserError), msg :   
244                    sys.stderr.write("ERROR: %s\n" % msg)
245                    sys.stderr.flush()
246        except KeyboardInterrupt :           
247            sys.stderr.write("WARN: Aborted at user's request.\n")
248            sys.stderr.flush()
249        if not options.colorspace :   
250            print "%i" % totalsize
251        else :   
252            print "\n".join(lines)
253   
254if __name__ == "__main__" :   
255    main()
Note: See TracBrowser for help on using the browser.