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

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

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