Seitennummern in pdf einfügen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
mathi
User
Beiträge: 314
Registriert: Dienstag 27. November 2007, 14:30

Hallo zusammen,

ich erstelle aus verschiedenen Programmen pdf- Dateien. Diese werden dann zusammengefvügt zu einer großen .pdf

Jetzt möchte ich diese nummerieren (Seitennummern). Meine Lösung ist folgende: mit pypdf die Seitenanzahl des org. Dokumentes auslesen, dann mit reportlab eine temp.pdf erstellen, die nur die Seitennummern enthält und dann beide pdf-Dateien mit pypdf zusammenfügen.
Das liefert das gewünschte Ergebnis, aber:

1. kommt mir das sehr umständlich vor
2. dauert dieser Weg eine halbe Ewigkeit, zumal es durchaus 1000 Seiten in dem .pdf Dokument sein können ....

Fals es keinen anderen (nichtkommerziellen) Weg gibt, die Seitennummern einzufügen hätte ich noch eine Idee:

kann ich nicht bei einem 4-kern Prozessor jedem Kern 1/4 der Seiten zuweisen, damit ginge die sache doch schonmal 4x schneller oder (multithreading)

Gruß Mathi
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

Performancevorteile mit Mehrkernprozessoren bekommst du mit Threading keine (wegen dem GIL). Dafür gibt es aber dann das Modul [mod]multiprocessing[/mod]. Die "Pool"-Klasse dürfte genau das richtige sein, da musst du nicht mal etwas manuell aufteilen!

Allerdings bringt das auch nur bei CPU-lastigen Aufgaben etwas, aber in deinem Fall ist vermutlich eher die HD das Bottleneck. Aber das musst du einfach mal testen.
mathi
User
Beiträge: 314
Registriert: Dienstag 27. November 2007, 14:30

Danke für die Antwort,

ich habe mich mal mit der "Pool"-Klasse beschäftigt, aber ich komm damit nicht zu Rande. Hier mal die nicht-lauffähige Struktur meines Programmes:

Code: Alles auswählen

class pdfPage(wx.Frame): 
    
    def __init__(self, parent = None,id=-1, title = "pdfPage"): 
        #wx.Frame.__init__(self, parent,id, title, size = size)
        #wx.Panel.....
        
    def onOutput(self):
        for pagenum in range(pages):
            """ Hier läuft die Arbeit ab"""
            # nimm pdf1.Seite(pagenum) und addiere pdf2.Seite(pagenum)

def main(): 
    """Main""" 
    app = wx.App() 
    f = pdfPage() 
    f.Center() 
    f.Show() 
    app.MainLoop() 

if __name__ == "__main__": 
    pool = Pool(processes=4)
    pool.apply_async(main()) # <----  ????
Wie muß ich das hier einbauen??
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

Du musst die Funktion als Objekt übergeben und nicht den Rückgabewert der Funktion, indem du diese gleich aufrufst. (Sprich Klammern weg hinter `main`).
mathi
User
Beiträge: 314
Registriert: Dienstag 27. November 2007, 14:30

Danke für Deine Antwort, aber das funktioniert so nicht, da das Programm dann nicht startet.
( ich habe also

Code: Alles auswählen

 if __name__ == "__main__": 
    pool = Pool(processes=4)
    pool.apply_async(main)
stehen. Muß ich nicht auch pool.map() aufrufen??
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

http://docs.python.org/dev/library/mult ... of-workers
Hier ist doch ein Minimalbeispiel. Map brauchst du nicht, dafür das "result.get"
mathi
User
Beiträge: 314
Registriert: Dienstag 27. November 2007, 14:30

Code: Alles auswählen

 if __name__ == "__main__":    
    pool = Pool(processes=4)
    result = pool.apply_async(main) 
    result.get(timeout=None)
So hab ich es gemacht, damit startet zwar das Programm, aber es wird nur ein Kern genutzt (Taskmanager zeigt nur 25% auslastung ) :(
BlackJack

@mathi: Da stelle ich mir jetzt die Frage warum Du irgendetwas anderes erwartet hast. Nicht nur weil das die Dokumentation auch hergegeben hätte. Was hättest Du denn sonst erwartet? Das vier Kerne genutzt werden? Wofür denn? Viermal `main` starten? Und die Funktion soll dann was machen? Viermal die gleiche GUI starten, welche dann die gleichen PDF-Seiten umwandelt? Das ist irgendwie sinnlos.

Du wolltest doch das *Umwandeln* aufteilen auf Prozessoren. Dann musst Du auch genau *die* Funktion, die einzelne Seiten umwandelt auf mehrere Prozessoren verteilen, und die dann auf ein "iterable" mit Seiten anwenden lassen. Dazu ist die `Pool.map()`-Methode gedacht.
mathi
User
Beiträge: 314
Registriert: Dienstag 27. November 2007, 14:30

@BlackJack
Ich hatte gedacht, dass dann das gesamte Programm und damit alle seine Funktionen über 4 Kerne verteilt werden, war wohl nicht so...

Jetzt habe ich überlegt, wie ich an die Funktion und das "iterable" rankomme:

Code: Alles auswählen

class pdfPage(wx.Frame): 
    
    def __init__(self, parent = None,id=-1, title = "pdfPage"): 
        #wx.Frame.__init__(self, parent,id, title, size = size)
        #wx.Panel.....

        pool = Pool(processes=cpu_count())
        f=self.onOutput
        result = pool.apply_async(f) 
        result.get(timeout=None)
        pool.map(f, range(self.pages))
     
   
    def onOutput(self):
        self.pages=pages
        for pagenum in range(pages):
            """ Hier läuft die Arbeit ab"""
            # nimm pdf1.Seite(pagenum) und addiere pdf2.Seite(pagenum)

def main(): 
    """Main""" 
    app = wx.App() 
    f = pdfPage() 
    f.Center() 
    f.Show() 
    app.MainLoop() 

if __name__ == "__main__": 
    main()
Jetzt starten zwar 4 instanzen von python im Taskmanager, aber das Programm startet nicht, irgendwie verstehe ich das ganze nicht richtig...
In den Beispielen wird pool beim Programmstart (

Code: Alles auswählen

if __name__ == "__main__":
) aufgerufen, nur da komme ich doch garnicht an die entspr. "iterable" ran... :oops:

edit: ich find auch leider über Google keine Beispiele, die mich weiterbringen :cry:

edit2:

Code: Alles auswählen

if __name__ == "__main__": 
    pool = Pool(processes=cpu_count())
    main()
    f=pdfPage.onOutput 
    result = pool.apply_async(f)
    result.get(timeout=None) 
    pool.map(f, range(100))
jetzt habe ich mal testweise range( 100) eingegeben (die Datei hat mehr als 100 Seiten) , aber es wird trotzdem nur ein Kern benutzt, map scheint nicht richtig zu funktionieren. Ich dachte map "nimmt" sich einfach alles was in onOutput steht undteilt die arbeit selbst auf.... Muß ich die for-Schleife als separate funktion und außerhalb der Klasse definieren??

Ich bitte Euch um Hilfe, da ich das ganze nicht verstehe...(die Beispiele der Python doku bringen mich leider auch nicht weiter)
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

- onOutput ist KEINE Klassenmethod, deshalb kannst du sie ohne Instanz gar nicht aufrufen.
- "apply" brauchst du gar nicht aufrufen, sondern nur das "map".
- Wenn du keine Argument für processes angibst, wird automatische cpu_count() genommen, ``Pool()`` ist also dasselbe wie ``Pool(processes=cpu_count())``
- Schau dir mal PEP8 an: Funktions- und Variablennamen werden klein_mit_unterstrich geschrieben, Klassen CamelCase, Leerzeichen bei den Funktionsparametern sind so auch nicht korrekt, ...
mathi
User
Beiträge: 314
Registriert: Dienstag 27. November 2007, 14:30

erstmal Danke für die Hinweise, ich hoffe es gelingt noch die Lösung zu finden... dann müßte doch ein :

Code: Alles auswählen

class pdfpage(wx.Frame): 
    
    def __init__(self, parent = None,id=-1, title = "pdfPage"): 
        #wx.Frame.__init__(self, parent,id, title, size = size)
        #wx.Panel.....  
   
    def output(self):
        self.pages=pages
        for pagenum in range(pages):
            """ Hier läuft die Arbeit ab"""
            # nimm pdf1.Seite(pagenum) und addiere pdf2.Seite(pagenum)

f=pdfpage.output 
            
def main(): 
    """Main""" 
    app = wx.App() 
    f = pdfpage() 
    f.Center() 
    f.Show() 
    app.MainLoop() 

if __name__ == "__main__": 
    pool = Pool()
    main()
    pool.map(f, range(100))
funktionieren, aber es läuft immernoch nur in einem Kern :-(
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

ice2k3 hat geschrieben:- onOutput ist KEINE Klassenmethod, deshalb kannst du sie ohne Instanz gar nicht aufrufen.
1. das. Und 2. rufst du die "pool.map" bei diesem Code gar nie auf, weil du schon im GUI-Loop steckst.

Mein Tipp: Lass die ganze GUI Sache erstmal komplett weg und bring das mit multiprocessing zum Laufen, und dann baust du die GUI darum herum. Dein ganzes Programmdesign ist nicht wirklich gelungen und passt überhaupt nicht zusammen (vor allem sollte GUI und Logik getrennt sein).
mathi
User
Beiträge: 314
Registriert: Dienstag 27. November 2007, 14:30

so ich hab den Rat befolgt, un eine GUI-freie version geschrieben, diese habe ich mal als lauffähige Version http://paste.pocoo.org/show/UoYQjvV5uhCBee9jPDKs/

gepastet.

Ich scheitere immer an der selben Stelle. Könnt Ihr mir nicht diese Version so korrigieren, dass es funktioniert??

Oder eine Hilfestellung geben, wo mein Denkfehler liegt??
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

Die Funktion, die du dem "map" übergibst, darf nur 1 Seite verarbeiten. Die for-Schleife muss also raus und du solltest einen (viel) günstigeren Namen für diese Funktion wählen.

Dateipfade sollte man außerdem immer mit os.path.join zusammensetzen und nie mit Stringkonkatenation!

Edit: Der Code auf Modul-Ebene ist natürlich auch nix. Aber du kennst ja schon if __name__ == '__main__'. Warum verwendest du das nicht konsequent?!

Edit2: Die "map" Funktion darf auch nur einen Parameter haben. (In dem Fall die Nummer der Seite). Willst du noch weitere (Default)-Parameter an die Funktion binden, verwende functools.partial.
mathi
User
Beiträge: 314
Registriert: Dienstag 27. November 2007, 14:30

Grundsätzlich ist doch das Problem, dass man in map keine Variablen übergeben kann, d.h. merge() falsch oder?? edit: vgl. Dein edit2

Code: Alles auswählen

    
    with open (tempdir+"\\temp.pdf", "rb") as (tmp_file):
        watermark = PdfFileReader(tmp_file)        
        for pagenum in range(pages):
            merge(output,input1,watermark,pagenum)
            
        temp_file = NamedTemporaryFile(suffix = '.pdf', prefix = 'temp',delete=False,dir =tempdir)
        output.write(temp_file)
        os.startfile(temp_file.name)
        temp_file.close()

def merge(output,input1,watermark,pagenum):
    page=input1.getPage(pagenum)
    page.mergePage(watermark.getPage(pagenum))
    output.addPage(page)
         

if __name__ == "__main__":
    pool = Pool()                      #?????
    pool.map(merge,range(1))
    output(input,pages)
mathi
User
Beiträge: 314
Registriert: Dienstag 27. November 2007, 14:30

also ich gebe auf, das ist mir zu hoch :cry: So siehts jetzt aus, aber es läuft nicht, weil watermark für merge() nicht vorhanden ist...und das ist bestimmt nicht der letzte Bug

http://paste.pocoo.org/show/5IuzHLRXltdZXhLrVPZV/
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

Hab doch beschrieben, wie man das Problem lösen kann:
[mod]functools[/mod].partial
mathi
User
Beiträge: 314
Registriert: Dienstag 27. November 2007, 14:30

jetzt habe ich versucht, auch dieses problem zu vertagen und mich auf das multiprocessing zu konzentrieren,

ich bin also noch einen Schritt zurück gegangen, aber jetzt ist irgendwie eine schleife entstanden, denn es werden immer mehr temp- Ordner angelegt in denen sich jeweils eine temp.pdf befindet .......

Code: Alles auswählen

# -*- coding: iso-8859-15 -*- 

from __future__ import division,unicode_literals 
from pyPdf import PdfFileWriter, PdfFileReader 
from tempfile import mkdtemp 
from shutil import rmtree 
from reportlab.pdfgen import canvas 
from reportlab.lib.units import cm 
from reportlab.lib.colors import black 
from tempfile import NamedTemporaryFile 
from reportlab.lib.pagesizes import A3, A4, landscape 
from thread import start_new_thread 
from multiprocessing import Pool 
import  os 

tempdir=mkdtemp(suffix='TemppP') 

#def output():
def merge(pagenum): 
    page=input1.getPage(pagenum) 
    page.mergePage(watermark.getPage(pagenum)) 
    output.addPage(page)
        
    #def output(input1,pages):    
        #global watermark            
output = PdfFileWriter()  
input1 = PdfFileReader(file("test_lang.pdf", "rb"))    
pages=input1.getNumPages() 
currentSeitenformat=0 
if currentSeitenformat==0: #A4-hoch 
    rx=19*cm 
    ry=28.3*cm 
    rdx=4*cm 
    rdy=4*cm 
    x=19.3*cm 
    y=28.6*cm 
    c = canvas.Canvas(tempdir+"\\temp.pdf",pagesize=A4) 
    rot=0 
                  
c.setFont("Helvetica",11) 
page=54 
pre="N" 
post="c" 
                      
for pagenum in range(pages): 
    c.setFillGray(0.75) 
            # draw a rectangle 
    c.rotate(rot) 
    c.rect(rx,ry,rdx,rdy, stroke=0, fill=1) 
    c.setStrokeColor(black) 
    c.setFillColor(black) 
    c.drawString(x,y,(pre+str(page+pagenum)+post)) 
    c.showPage() 
    c.save() 
        
with open (tempdir+"\\temp.pdf", "rb") as (tmp_file): 
    watermark = PdfFileReader(tmp_file) 
    
    for pagenum in range(pages): 
        pool = Pool()                      #????? 
        pool.map(merge,range(pages)) 
                
temp_file = NamedTemporaryFile(suffix = '.pdf', prefix = 'temp',delete=False,dir =tempdir) 
output.write(temp_file) 
os.startfile(temp_file.name) 
temp_file.close() 
        
 
    
        
#if __name__ == "__main__": 
#    output()
BlackJack

@mathi: `Pool.map()` startet n Prozesse (n = Anzahl der Prozessoren/Kerne) mit der angegeben Funktion und wendet diese auf alle Argumente in dem "iterable" an. Und Du baust da jetzt eine Schleife drumherum, die soviele von diesen Pools erzeugt wie Seiten da sind. Wieviele Prozesse hättest Du denn da jetzt erwartet und warum? Ich habe so langsam das Gefühl Du probierst nur wild herum und hoffst, dass da irgendwann schon die richtige Kombination von Schleifen und Aufrufen bei herauskommt. So funktioniert Programmieren nicht wirklich.
mathi
User
Beiträge: 314
Registriert: Dienstag 27. November 2007, 14:30

@BlackJack
ich komme mit der doku nicht wirklich zurecht, Beispiele finden sich kaum (ich habe keine für mich verwertbaren gefunden), so versuche ich halt so gut es geht. Natürlich hast Du recht, aber das sagt sich leicht wenn man weiß wie es geht...
Antworten