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

Revision 491, 9.5 kB (checked in by jerome, 16 years ago)

Major code cleaning. Now clearer, although probably a bit slower since
a file can be opened several times.
Now universal line opening mode is only used when needed (PS, PDF and plain
text), and binary opening mode is used for the other formats.
This mean we will be able to remove mmap calls wherever possible, finally.

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