root / pkpgcounter / trunk / pdlanalyzer / analyzer.py @ 217

Revision 217, 10.1 kB (checked in by jerome, 19 years ago)

Added basic support for the TIFF format (which can handle
multiple pages in the same file)

  • 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 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 2 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, write to the Free Software
17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18#
19# $Id$
20#
21
22import sys
23import tempfile
24
25from pdlanalyzer import version, pdlparser, postscript, pdf, pcl345, pclxl, escp2, dvi, tiff
26
27KILOBYTE = 1024   
28MEGABYTE = 1024 * KILOBYTE   
29LASTBLOCKSIZE = int(KILOBYTE / 4)
30
31class PDLAnalyzer :   
32    """Class for PDL autodetection."""
33    def __init__(self, filename, debug=0) :
34        """Initializes the PDL analyzer.
35       
36           filename is the name of the file or '-' for stdin.
37           filename can also be a file-like object which
38           supports read() and seek().
39        """
40        self.debug = debug
41        self.filename = filename
42        try :
43            import psyco 
44        except ImportError :   
45            sys.stderr.write("pkpgcounter : you should install psyco if possible, this would greatly speedup parsing.\n")
46            pass # Psyco is not installed
47        else :   
48            # Psyco is installed, tell it to compile
49            # the CPU intensive methods : PCL and PCLXL
50            # parsing will greatly benefit from this,
51            # for PostScript and PDF the difference is
52            # barely noticeable since they are already
53            # almost optimal, and much more speedy anyway.
54            psyco.bind(postscript.PostScriptParser.getJobSize)
55            psyco.bind(pdf.PDFParser.getJobSize)
56            psyco.bind(escp2.ESCP2Parser.getJobSize)
57            psyco.bind(pcl345.PCL345Parser.getJobSize)
58            psyco.bind(pclxl.PCLXLParser.getJobSize)
59            psyco.bind(dvi.DVIParser.getJobSize)
60            psyco.bind(tiff.TIFFParser.getJobSize)
61       
62    def getJobSize(self) :   
63        """Returns the job's size."""
64        self.openFile()
65        try :
66            pdlhandler = self.detectPDLHandler()
67        except pdlparser.PDLParserError, msg :   
68            self.closeFile()
69            raise pdlparser.PDLParserError, "ERROR : Unknown file format for %s (%s)" % (self.filename, msg)
70        else :
71            try :
72                size = pdlhandler(self.infile, self.debug).getJobSize()
73            finally :   
74                self.closeFile()
75            return size
76       
77    def openFile(self) :   
78        """Opens the job's data stream for reading."""
79        self.mustclose = 0  # by default we don't want to close the file when finished
80        if hasattr(self.filename, "read") and hasattr(self.filename, "seek") :
81            # filename is in fact a file-like object
82            infile = self.filename
83        elif self.filename == "-" :
84            # we must read from stdin
85            infile = sys.stdin
86        else :   
87            # normal file
88            self.infile = open(self.filename, "rb")
89            self.mustclose = 1
90            return
91           
92        # Use a temporary file, always seekable contrary to standard input.
93        self.infile = tempfile.TemporaryFile(mode="w+b")
94        while 1 :
95            data = infile.read(MEGABYTE) 
96            if not data :
97                break
98            self.infile.write(data)
99        self.infile.flush()   
100        self.infile.seek(0)
101           
102    def closeFile(self) :       
103        """Closes the job's data stream if we can close it."""
104        if self.mustclose :
105            self.infile.close()   
106        else :   
107            # if we don't have to close the file, then
108            # ensure the file pointer is reset to the
109            # start of the file in case the process wants
110            # to read the file again.
111            try :
112                self.infile.seek(0)
113            except :   
114                pass    # probably stdin, which is not seekable
115       
116    def isPostScript(self, sdata, edata) :   
117        """Returns 1 if data is PostScript, else 0."""
118        if sdata.startswith("%!") or \
119           sdata.startswith("\004%!") or \
120           sdata.startswith("\033%-12345X%!PS") or \
121           ((sdata[:128].find("\033%-12345X") != -1) and \
122             ((sdata.find("LANGUAGE=POSTSCRIPT") != -1) or \
123              (sdata.find("LANGUAGE = POSTSCRIPT") != -1) or \
124              (sdata.find("LANGUAGE = Postscript") != -1))) or \
125              (sdata.find("%!PS-Adobe") != -1) :
126            if self.debug : 
127                sys.stderr.write("%s is a PostScript file\n" % str(self.filename))
128            return 1
129        else :   
130            return 0
131       
132    def isPDF(self, sdata, edata) :   
133        """Returns 1 if data is PDF, else 0."""
134        if sdata.startswith("%PDF-") or \
135           sdata.startswith("\033%-12345X%PDF-") or \
136           ((sdata[:128].find("\033%-12345X") != -1) and (sdata.upper().find("LANGUAGE=PDF") != -1)) or \
137           (sdata.find("%PDF-") != -1) :
138            if self.debug : 
139                sys.stderr.write("%s is a PDF file\n" % str(self.filename))
140            return 1
141        else :   
142            return 0
143       
144    def isPCL(self, sdata, edata) :   
145        """Returns 1 if data is PCL, else 0."""
146        if sdata.startswith("\033E\033") or \
147           (sdata.startswith("\033*rbC") and (not edata[-3:] == "\f\033@")) or \
148           sdata.startswith("\033%8\033") or \
149           (sdata.find("\033%-12345X") != -1) :
150            if self.debug : 
151                sys.stderr.write("%s is a PCL3/4/5 file\n" % str(self.filename))
152            return 1
153        else :   
154            return 0
155       
156    def isPCLXL(self, sdata, edata) :   
157        """Returns 1 if data is PCLXL aka PCL6, else 0."""
158        if ((sdata[:128].find("\033%-12345X") != -1) and \
159             (sdata.find(" HP-PCL XL;") != -1) and \
160             ((sdata.find("LANGUAGE=PCLXL") != -1) or \
161              (sdata.find("LANGUAGE = PCLXL") != -1))) :
162            if self.debug : 
163                sys.stderr.write("%s is a PCLXL (aka PCL6) file\n" % str(self.filename))
164            return 1
165        else :   
166            return 0
167           
168    def isESCP2(self, sdata, edata) :       
169        """Returns 1 if data is ESC/P2, else 0."""
170        if sdata.startswith("\033@") or \
171           sdata.startswith("\033*") or \
172           sdata.startswith("\n\033@") or \
173           sdata.startswith("\0\0\0\033\1@EJL") : # ESC/P Raster ??? Seen on Stylus Photo 1284
174            if self.debug : 
175                sys.stderr.write("%s is an ESC/P2 file\n" % str(self.filename))
176            return 1
177        else :   
178            return 0
179           
180    def isDVI(self, sdata, edata) :       
181        """Returns 1 if data is DVI, else 0."""
182        if (ord(sdata[0]) == 0xf7) and (ord(edata[-1]) == 0xdf) :
183            if self.debug : 
184                sys.stderr.write("%s is a DVI file\n" % str(self.filename))
185            return 1
186        else :   
187            return 0
188           
189    def isTIFF(self, sdata, edata) :       
190        """Returns 1 if data is TIFF, else 0."""
191        littleendian = (chr(0x49)*2) + chr(0x2a) + chr(0)
192        bigendian = (chr(0x4d)*2) + chr(0) + chr(0x2a)
193        if sdata[:4] in (littleendian, bigendian) :
194            if self.debug : 
195                sys.stderr.write("%s is a TIFF file\n" % str(self.filename))
196            return 1
197        else :   
198            return 0
199   
200    def detectPDLHandler(self) :   
201        """Tries to autodetect the document format.
202       
203           Returns the correct PDL handler class or None if format is unknown
204        """   
205        # Try to detect file type by reading first block of datas   
206        self.infile.seek(0)
207        firstblock = self.infile.read(16 * KILOBYTE)
208        try :
209            self.infile.seek(-LASTBLOCKSIZE, 2)
210            lastblock = self.infile.read(LASTBLOCKSIZE)
211        except IOError :   
212            lastblock = ""
213        self.infile.seek(0)
214        if not firstblock :
215            sys.stderr.write("ERROR: input file %s is empty !\n" % str(self.filename))
216        else :   
217            if self.isPostScript(firstblock, lastblock) :
218                return postscript.PostScriptParser
219            elif self.isPCLXL(firstblock, lastblock) :   
220                return pclxl.PCLXLParser
221            elif self.isPDF(firstblock, lastblock) :   
222                return pdf.PDFParser
223            elif self.isPCL(firstblock, lastblock) :   
224                return pcl345.PCL345Parser
225            elif self.isESCP2(firstblock, lastblock) :   
226                return escp2.ESCP2Parser
227            elif self.isDVI(firstblock, lastblock) :   
228                return dvi.DVIParser
229            elif self.isTIFF(firstblock, lastblock) :
230                return tiff.TIFFParser
231        raise pdlparser.PDLParserError, "Analysis of first data block failed."
232           
233def main() :   
234    """Entry point for PDL Analyzer."""
235    if (len(sys.argv) < 2) or ((not sys.stdin.isatty()) and ("-" not in sys.argv[1:])) :
236        sys.argv.append("-")
237       
238    if ("-h" in sys.argv[1:]) or ("--help" in sys.argv[1:]) :
239        print "usage : pkpgcounter file1 file2 ... fileN"
240    elif ("-v" in sys.argv[1:]) or ("--version" in sys.argv[1:]) :
241        print "%s" % version.__version__
242    else :
243        totalsize = 0   
244        debug = 0
245        minindex = 1
246        if sys.argv[1] == "--debug" :
247            minindex = 2
248            debug = 1
249        for arg in sys.argv[minindex:] :
250            try :
251                parser = PDLAnalyzer(arg, debug)
252                totalsize += parser.getJobSize()
253            except pdlparser.PDLParserError, msg :   
254                sys.stderr.write("ERROR: %s\n" % msg)
255                sys.stderr.flush()
256        print "%s" % totalsize
257   
258if __name__ == "__main__" :   
259    main()
Note: See TracBrowser for help on using the browser.