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

Revision 3474, 10.7 kB (checked in by jerome, 16 years ago)

Changed copyright years.

  • Property svn:keywords set to Auth Date Id Rev
Line 
1# -*- coding: utf-8 -*-
2#
3# pkpgcounter : a generic Page Description Language parser
4#
5# (c) 2003-2009 Jerome Alet <alet@librelogiciel.com>
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program.  If not, see <http://www.gnu.org/licenses/>.
18#
19# $Id$
20#
21
22"""This is the main module of pkpgcounter.
23
24It defines the PDLAnalyzer class, which provides a generic way to parse
25input files, by automatically detecting the best parser to use."""
26
27import sys
28import os
29import tempfile
30
31import version, pdlparser, postscript, pdf, pcl345, pclxl, hbp, \
32       pil, mscrap, cfax, lidil, escp2, dvi, tiff, ooo, zjstream, \
33       pnmascii, bj, qpdl, spl1, escpages03, plain
34import inkcoverage
35
36class AnalyzerOptions :
37    """A class for use as the options parameter to PDLAnalyzer's constructor."""
38    def __init__(self, debug=None,
39                       colorspace=None,
40                       resolution=None) :
41        """Sets initial attributes."""
42        self.debug = debug
43        self.colorspace = colorspace
44        self.resolution = resolution
45
46
47class PDLAnalyzer :
48    """Class for PDL autodetection."""
49    def __init__(self, filename, options=AnalyzerOptions()) :
50        """Initializes the PDL analyzer.
51
52           filename is the name of the file or '-' for stdin.
53           filename can also be a file-like object which
54           supports read() and seek().
55        """
56        self.options = options
57        self.filename = filename
58        self.workfile = None
59
60    def getJobSize(self) :
61        """Returns the job's size."""
62        size = 0
63        self.openFile()
64        try :
65            try :
66                pdlhandler = self.detectPDLHandler()
67                size = pdlhandler.getJobSize()
68            except pdlparser.PDLParserError, msg :
69                raise pdlparser.PDLParserError, "Unsupported file format for %s (%s)" % (self.filename, msg)
70        finally :
71            self.closeFile()
72        return size
73
74    def getInkCoverage(self, colorspace=None, resolution=None) :
75        """Extracts the percents of ink coverage from the input file."""
76        result = None
77        cspace = colorspace or self.options.colorspace
78        res = resolution or self.options.resolution
79        if (not cspace) or (not res) :
80            raise ValueError, "Invalid colorspace (%s) or resolution (%s)" % (cspace, res)
81        self.openFile()
82        try :
83            try :
84                pdlhandler = self.detectPDLHandler()
85                dummyfile = tempfile.NamedTemporaryFile(mode="w+b",
86                                                        prefix="pkpgcounter_",
87                                                        suffix=".tiff",
88                                                        dir=os.environ.get("PYKOTADIRECTORY") or tempfile.gettempdir())
89                filename = dummyfile.name
90                try :
91                    pdlhandler.convertToTiffMultiPage24NC(filename, self.options.resolution)
92                    result = inkcoverage.getInkCoverage(filename, cspace)
93                finally :
94                    dummyfile.close()
95            except pdlparser.PDLParserError, msg :
96                raise pdlparser.PDLParserError, "Unsupported file format for %s (%s)" % (self.filename, msg)
97        finally :
98            self.closeFile()
99        return result
100
101    def openFile(self) :
102        """Opens the job's data stream for reading."""
103        if hasattr(self.filename, "read") and hasattr(self.filename, "seek") :
104            # filename is in fact a file-like object
105            infile = self.filename
106        elif self.filename == "-" :
107            # we must read from stdin
108            infile = sys.stdin
109        else :
110            # normal file
111            self.workfile = open(self.filename, "rb")
112            return
113
114        # Use a temporary file, always seekable contrary to standard input.
115        self.workfile = tempfile.NamedTemporaryFile(mode="w+b",
116                                                    prefix="pkpgcounter_",
117                                                    suffix=".prn",
118                                                    dir=os.environ.get("PYKOTADIRECTORY") or tempfile.gettempdir())
119        self.filename = self.workfile.name
120        while True :
121            data = infile.read(pdlparser.MEGABYTE)
122            if not data :
123                break
124            self.workfile.write(data)
125        self.workfile.flush()
126        self.workfile.seek(0)
127
128    def closeFile(self) :
129        """Closes the job's data stream if we have to."""
130        self.workfile.close()
131
132    def readFirstAndLastBlocks(self, inputfile) :
133        """Reads the first and last blocks of data."""
134        # Now read first and last block of the input file
135        # to be able to detect the real file format and the parser to use.
136        firstblock = inputfile.read(pdlparser.FIRSTBLOCKSIZE)
137        try :
138            inputfile.seek(-pdlparser.LASTBLOCKSIZE, 2)
139            lastblock = inputfile.read(pdlparser.LASTBLOCKSIZE)
140        except IOError :
141            lastblock = ""
142        return (firstblock, lastblock)
143
144    def detectPDLHandler(self) :
145        """Tries to autodetect the document format.
146
147           Returns the correct PDL handler class or None if format is unknown
148        """
149        if not os.stat(self.filename).st_size :
150            raise pdlparser.PDLParserError, "input file %s is empty !" % str(self.filename)
151        (firstblock, lastblock) = self.readFirstAndLastBlocks(self.workfile)
152
153        # IMPORTANT : the order is important below. FIXME.
154        for module in (postscript, \
155                       pclxl, \
156                       pdf, \
157                       qpdl, \
158                       spl1, \
159                       dvi, \
160                       tiff, \
161                       cfax, \
162                       zjstream, \
163                       ooo, \
164                       hbp, \
165                       lidil, \
166                       pcl345, \
167                       escp2, \
168                       escpages03, \
169                       bj, \
170                       pnmascii, \
171                       pil, \
172                       mscrap, \
173                       plain) :     # IMPORTANT : don't move this one up !
174            try :
175                return module.Parser(self, self.filename,
176                                           (firstblock, lastblock))
177            except pdlparser.PDLParserError :
178                pass # try next parser
179        raise pdlparser.PDLParserError, "Analysis of first data block failed."
180
181def main() :
182    """Entry point for PDL Analyzer."""
183    import optparse
184    from copy import copy
185
186    def check_cichoice(option, opt, value) :
187        """To add a CaseIgnore Choice option type."""
188        valower = value.lower()
189        if valower in [v.lower() for v in option.cichoices] :
190            return valower
191        else :
192            choices = ", ".join([repr(o) for o in option.cichoices])
193            raise optparse.OptionValueError(
194                "option %s: invalid choice: %r (choose from %s)"
195                % (opt, value, choices))
196
197    class MyOption(optparse.Option) :
198        """New Option class, with CaseIgnore Choice type."""
199        TYPES = optparse.Option.TYPES + ("cichoice",)
200        ATTRS = optparse.Option.ATTRS + ["cichoices"]
201        TYPE_CHECKER = copy(optparse.Option.TYPE_CHECKER)
202        TYPE_CHECKER["cichoice"] = check_cichoice
203
204    parser = optparse.OptionParser(option_class=MyOption,
205                                   usage="python analyzer.py [options] file1 [file2 ...]")
206    parser.add_option("-v", "--version",
207                            action="store_true",
208                            dest="version",
209                            help="Show pkpgcounter's version number and exit.")
210    parser.add_option("-d", "--debug",
211                            action="store_true",
212                            dest="debug",
213                            help="Activate debug mode.")
214    parser.add_option("-c", "--colorspace",
215                            dest="colorspace",
216                            type="cichoice",
217                            cichoices=["bw", "rgb", "cmyk", "cmy", "gc"],
218                            help="Activate the computation of ink usage, and defines the colorspace to use. Supported values are 'BW', 'RGB', 'CMYK', 'CMY', and 'GC'.")
219    parser.add_option("-r", "--resolution",
220                            type="int",
221                            default=72,
222                            dest="resolution",
223                            help="The resolution in DPI to use when checking ink usage. Lower resolution is faster but less accurate. Default is 72 dpi.")
224    (options, arguments) = parser.parse_args()
225    if options.version :
226        sys.stdout.write("%s\n" % version.__version__)
227    elif not (72 <= options.resolution <= 1200) :
228        sys.stderr.write("ERROR: the argument to the --resolution command line option must be between 72 and 1200.\n")
229        sys.stderr.flush()
230    else :
231        if (not arguments) or ((not sys.stdin.isatty()) and ("-" not in arguments)) :
232            arguments.append("-")
233        totalsize = 0
234        lines = []
235        try :
236            for arg in arguments :
237                try :
238                    parser = PDLAnalyzer(arg, options)
239                    if not options.colorspace :
240                        totalsize += parser.getJobSize()
241                    else :
242                        (cspace, pages) = parser.getInkCoverage()
243                        for page in pages :
244                            lineparts = []
245                            for k in cspace : # NB : this way we preserve the order of the planes
246                                try :
247                                    lineparts.append("%s : %s%%" % (k, ("%f" % page[k]).rjust(10)))
248                                except KeyError :
249                                    pass
250                            lines.append("      ".join(lineparts))
251                except (IOError, pdlparser.PDLParserError), msg :
252                    sys.stderr.write("ERROR: %s\n" % msg)
253                    sys.stderr.flush()
254        except KeyboardInterrupt :
255            sys.stderr.write("WARN: Aborted at user's request.\n")
256            sys.stderr.flush()
257        if not options.colorspace :
258            sys.stdout.write("%i\n" % totalsize)
259        else :
260            sys.stdout.write("%s\n" % ("\n".join(lines)))
261
262if __name__ == "__main__" :
263    main()
Note: See TracBrowser for help on using the browser.