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

Revision 529, 10.5 kB (checked in by jerome, 16 years ago)

Finalized support for MS documents.

  • 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, "Unsupported 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, "Unsupported 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, self.filename, 
169                                           (firstblock, lastblock))
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.