root / pykota / trunk / bin / pkpgcounter @ 1449

Revision 1449, 6.8 kB (checked in by jalet, 20 years ago)

Now uses mmap in PCL mode

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1#! /usr/bin/env python
2# -*- coding: ISO-8859-15 -*-
3
4# pkpgcounter, a smart software page counter
5#
6# PyKota - Print Quotas for CUPS and LPRng
7#
8# (c) 2003-2004 Jerome Alet <alet@librelogiciel.com>
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation; either version 2 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program; if not, write to the Free Software
21# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
22#
23# $Id$
24#
25# $Log$
26# Revision 1.4  2004/05/04 12:21:55  jalet
27# Now uses mmap in PCL mode
28#
29# Revision 1.3  2004/05/04 04:39:26  jalet
30# Better PCL support
31#
32# Revision 1.2  2004/05/04 03:14:26  jalet
33# fixed copy&paste problem in pkpgcounter
34#
35# Revision 1.1  2004/04/08 17:07:42  jalet
36# pkpgcounter added
37#
38#
39
40import sys
41import os
42import mmap
43import tempfile
44
45def ispostscript(data) :   
46    """Returns 1 if data is PostScript, else 0."""
47    if data.startswith("%!") or \
48       data.startswith("\004%!") or \
49       data.startswith("\033%-12345X%!PS") or \
50       ((data[:128].find("\033%-12345X") != -1) and \
51         ((data.find("LANGUAGE=POSTSCRIPT") != -1) or \
52          (data.find("LANGUAGE = POSTSCRIPT") != -1) or \
53          (data.find("LANGUAGE = Postscript") != -1))) :
54        return 1
55    else :   
56        return 0
57   
58def postscript(infile) :
59    """Count pages in a PostScript document."""
60    pagecount = 0
61    pagenum = None
62    while 1 :
63        line = infile.readline()
64        if not line :
65            break
66        if line.startswith("%%Page: ") :
67            pagecount += 1
68    return pagecount
69   
70def ispcl(data) :   
71    """Returns 1 if data is PCL, else 0."""
72    if data.startswith("\033E\033") or \
73       ((data[:128].find("\033%-12345X") != -1) and \
74         ((data.find("LANGUAGE=PCL") != -1) or \
75          (data.find("LANGUAGE = PCL") != -1) or \
76          (data.find("LANGUAGE = Pcl") != -1))) :
77        return 1
78    else :   
79        return 0
80   
81def pcl(infile) :   
82    """Count pages in a PostScript document."""
83    #
84    # Algorithm from pclcount
85    # (c) 2003, by Eduardo Gielamo Oliveira & Rodolfo Broco Manin
86    # published under the terms of the GNU General Public Licence v2.
87    #
88    # Backported from C to Python by Jerome Alet, then enhanced
89    # with more PCL tags detected. I think all the necessary PCL tags
90    # are recognized to correctly handle PCL5e files wrt their number
91    # of pages
92    #
93    infileno = infile.fileno()
94    infile = mmap.mmap(infileno, os.fstat(infileno).st_size, access=mmap.ACCESS_READ)
95    tagsends = { "&n" : "W", "&b": "W", "*i" : "W", "*l" : "W", "*m" : "W", "*v": "W", "*c" : "W", "(f" : "W", "*b" : "VW", "(s" : "W", ")s" : "W", "&p" : "X", "&l" : "X" } 
96    copies = 1
97    pagecount = 0
98    tag = None
99    position = 0
100    while 1 :
101        try :
102            char = infile[position]
103        except IndexError :     # EOF   
104            break
105        position += 1
106        if not char :
107            break
108        if char == "\014" :   
109            pagecount += 1
110        elif char == "\033" :   
111            #
112            #     <ESC>*b###W -> Start of a raster data row/block
113            #     <ESC>*b###V -> Start of a raster data plane
114            #     <ESC>*c###W -> Start of a user defined pattern
115            #     <ESC>*i###W -> Start of a viewing illuminant block
116            #     <ESC>*l###W -> Start of a color lookup table
117            #     <ESC>*m###W -> Start of a download dither matrix block
118            #     <ESC>*v###W -> Start of a configure image data block
119            #     <ESC>(s###W -> Start of a characters description block
120            #     <ESC>)s###W -> Start of a fonts description block
121            #     <ESC>(f###W -> Start of a symbol set block
122            #     <ESC>&b###W -> Start of configuration data block
123            #     <ESC>&l###X -> Number of copies
124            #     <ESC>&n###W -> Starts an alphanumeric string ID block
125            #     <ESC>&p###X -> Start of a non printable characters block
126            #
127            tagstart = infile[position]
128            position += 1
129            if tagstart in "E9=YZ" : # one byte PCL tag
130                continue             # skip to next tag
131            tag = tagstart + infile[position]
132            position += 1
133            try :
134                tagend = tagsends[tag]
135            except KeyError :   
136                pass    # Unsupported PCL tag
137            else :   
138                # Now read the numeric argument
139                size = 0
140                while 1 :
141                    char = infile[position]
142                    position += 1
143                    if not char.isdigit() :
144                        break
145                    size = (size * 10) + int(char)   
146                if char in tagend :   
147                    if tag == "&l" :
148                        copies = size
149                    else :   
150                        # doing a read will prevent the seek
151                        # for unseekable streams.
152                        # we just ignore the block anyway.
153                        if tag == "&n" : 
154                            # we have to take care of the operation id byte
155                            # which is before the string itself
156                            size += 1
157                        position += size   
158    return copies * pagecount       
159
160def smartpagecounter(filename) :
161    """Autodetects file format and returns number of pages."""
162    if filename == "-" :
163        # we must read from stdin
164        # but since stdin is not seekable, we have to use a temporary
165        # file instead.
166        infile = tempfile.TemporaryFile()
167        while 1 :
168            data = sys.stdin.read(256 * 1024) 
169            if not data :
170                break
171            infile.write(data)
172        infile.flush()   
173        infile.seek(0)
174    else :   
175        # normal file
176        infile = open(filename, "rb")
177       
178    # Try to detect file type by reading first block of datas   
179    firstblock = infile.read(1024)
180    infile.seek(0)
181    if ispostscript(firstblock) :
182        size = postscript(infile)
183    elif ispcl(firstblock) :   
184        size = pcl(infile)
185    else :   
186        sys.stderr.write("ERROR : Unknown file format for %s\n" % filename)
187        size = 0
188    infile.close()   
189    return size
190   
191if __name__ == "__main__" :   
192    if len(sys.argv) < 2 :
193        sys.argv.append("-")
194       
195    totalsize = 0   
196    for arg in sys.argv[1:] :
197        totalsize += smartpagecounter(arg)
198    print "%s" % totalsize
Note: See TracBrowser for help on using the browser.