PDF Split & Merge

Du hast eine Idee für ein Projekt?
Antworten
fbuchinger
User
Beiträge: 29
Registriert: Donnerstag 7. September 2006, 21:30

Da ich mit vielen FOSS-PDF-Split&Merge-Tools unzufrieden bin, möchte ich mit PyPDF als Grundlage ein eigenes, leistungsfähigeres Tool implementieren.

Neben den Standardfunktionen hab ich mir ein paar produktivitätssteigernde Schmankerl einfallen lassen...

Aber lest mal selbst:

Preliminary Feature List *hüstel*:

SPLIT:

- Alle Seiten eines PDFs als Einzel-PDFs ausgeben (burst)
- Seitenbereiche wie [5:10] oder [6:] in ein PDF ausgeben
- Alle n Seiten ein neues Teil-PDF erstellen (45 Seiten Quelldokument -> 3 Zieldokumente à 15 Seiten)
- Nur gerade/ungerade Seiten in PDF ausgeben
- nur jede n-te Seite in das Ziel-PDF aufnehmen

CHUNK:
- beim Chunking wird ein Größenlimit für die Zieldokumente festgelegt, die Seitenzahl pro Teil-PDF richtet sich danach. Nützlich für den E-Mail-Versand von großen PDFs.

MERGE:
- Zwei oder mehrere PDFs zu einem Gesamt-PDF verschmelzen
- Alle PDFs eines Verzeichnisses -> Gesamt-PDF
- Inhaltsbasiertes Merging: nur wenn Begriff x im PDF vorkommt, wird das PDF berücksichtigt.
- Piped Merging: Mit einem ls/dir-Befehl gefundene PDFs können per Pipe an das Merging-Programm weitergegeben werden. Beispiel: alle PDFs, die heute erstellt wurden, mergen.
- Merging mit Globbing-Support:

Code: Alles auswählen

merge *rechnung*.pdf kassabuch.pdf
würde rechnung 1.pdf Rechnung_Installateur.pdf etc. in Kassabuch.pdf mergen

- CSV-Merge: Ich habe eine in Excel editierbare Tabelle, die sich wie folgt aufbaut:

Code: Alles auswählen

                    Quelle1.pdf    Quelle2.pdf    Quelle3.pdf   Quelle4.pdf
Ausgabe1.pdf         1               4               3               2
Ausgabe2.pdf         4               3               2               1
Ausgabe3.pdf         1                              2
In der ersten Spalte sind die auszugebenden PDFs, in der ersten Zeile die Quell-PDFs, die Ziffern geben die Reihenfolge an, in der die Quelldateien in die jeweilige Ausgabedatei kommen. Leeres Feld= Quelldatei wird ignoriert.

Das CSV-Merge-Feature ist ganz nützlich, um z.B. für Kunden angepasste Unterlagen aus mehreren "Baustein-PDFs" zu basteln.

ALIASE:

Aliase sind Kürzel-Verweise auf häufig verwendete PDF-Dokumente. Sie erleichtern die Arbeit mit "verstreuten" PDFs (in unterschiedlichen Verzeichnissen abgelegt). Beispielsweise könnte sich der Alias "AGB" auf K:\Offerte\AGBs\AGB_2007.pdf beziehen, der Alias "cover-offert" auf P:\Designs\Cover\Offert-Cover.pdf. Mit dem Befehl

Code: Alles auswählen

merge cover-offert angebot-huber.pdf AGB > resultat.pdf 
könnte ich die drei auf verschiedenen Verzeichnissen liegenden Dokumente einfach in resultat.pdf verschmelzen.

Folgende Alias-Funktionen sind geplant:
- dauerhaftes Speichern von Aliasen
- Auflisten aller Aliase inkl. Globbing-Support
- Validierung von Aliasen (existiert das referenzierte PDF noch?)
- Unterstützung von Aliasen in allen Modi des Tools
- Auto-Alias-Funktion: mit einem Befehl Aliase für ein ganzes PDF-Dir erstellen (ein solcher Auto-Alias würde sich z.B. aus den drei ersten Buchstaben des Dateinamens plus einer fortlaufenden Nummer zusammensetzen). -> Diese Aliase würden dann z.B. komplexere Splitting/Merging Operationen erleichtern.

Ich sehe das Tool weniger als Python-Bibliothek, sondern viel mehr als Kommandozeilen-Werkzeug, das natürlich unter Windows als EXE daherkommen soll.

Von den Features her ist das Teil zu 50% fertig, allerdings verträgt der Code noch ordentlich Politur. Falls Interesse besteht, werde ich ihn bald hier posten/verlinken.
    Zuletzt geändert von fbuchinger am Samstag 30. Juni 2007, 09:55, insgesamt 2-mal geändert.
    Benutzeravatar
    gerold
    Python-Forum Veteran
    Beiträge: 5555
    Registriert: Samstag 28. Februar 2004, 22:04
    Wohnort: Oberhofen im Inntal (Tirol)
    Kontaktdaten:

    fbuchinger hat geschrieben:mit PyPDF als Grundlage ein eigenes, leistungsfähigeres Tool implementieren.
    [...]
    allerdings verträgt der Code noch ordentlich Politur.
    Hallo fbuchinger!

    Das ist eine tolle Sache. Je früher du den Code veröffentlichst, desto besser. :D So etwas kann man immer brauchen.

    Falls du mit "Politur" evt. die Übergabe von Parametern und Einstellungen meinst, dann könnte dich das hier http://www.python-forum.de/topic-10936.html vielleicht ein wenig inspirieren.

    mfg
    Gerold
    :-)
    http://halvar.at | Kleiner Bascom AVR Kurs
    Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
    fbuchinger
    User
    Beiträge: 29
    Registriert: Donnerstag 7. September 2006, 21:30

    Hallo Gerold,

    danke für den nützlichen Hinweis auf dein interessantes Parsing-Modul!

    Es ist sicher nützlich, sobald ich die nötigen Konfigurationsparameter definiert habe.

    Momentan bin ich noch mit den Merging-Funktionen beschäftigt, außerdem sind die Funktionen derzeit noch auf einige lose def's verstreut. Möchte noch eine vernünftige Klassenarchitektur reinbringen, bevor ich das ganze release.

    Habe übrigens die Funktionsbeschreibung um ein paar nützliche Features mehr erweitert.

    lg,

    Franz
    Andy
    User
    Beiträge: 196
    Registriert: Sonntag 1. Januar 2006, 20:12
    Wohnort: aus dem hohen Norden....

    gerold hat geschrieben:Das ist eine tolle Sache. Je früher du den Code veröffentlichst, desto besser. :D So etwas kann man immer brauchen.
    Das sehe ich auch so! Ich denke das würde bei mir sehr oft Verwendung finden. 8)
    fbuchinger hat geschrieben:Falls Interesse besteht, werde ich ihn bald hier posten/verlinken.
    Melde mich dann schon mal. :wink:
    fbuchinger
    User
    Beiträge: 29
    Registriert: Donnerstag 7. September 2006, 21:30

    Hallo,

    ich habe meine Drohung wahr gemacht ;-) und den bisher erzeugten Code unter http://code.google.com/p/smpdf publiziert.

    Ihr könnt das Web-Interface des SVN-Stores nutzen um auf das Skript zuzugreifen und mir Verbesserungsvorschläge zu machen.

    Ich weiß, es liegt noch einiges im Argen, aber wie heißt es so schön "release early, release often".

    Nähere Infos zum Featureset gibts auf der Projectpage.

    lg,

    Franz
    Benutzeravatar
    phxx
    User
    Beiträge: 31
    Registriert: Freitag 5. Mai 2006, 19:47

    Sinnvoll wäre es vllt auch das ganze Plugin basiert zu machen.

    Ich meine so:

    Code: Alles auswählen

    smpdf newfeature file1.pdf file2.pdf
    ruft die Methode "execute" der Klasse Command aus der Datei /path/to/smpdf/plugins/newfeature.py auf.

    So könnte man das ganz einfach erweitern und hilft den eigentlich Code übersichtlich zu halten.

    Fänd ich schön =) Und toll das du an sowas bastelst. Die Features hören sich ziemlich super an.

    phxx
    lunar

    gerold hat geschrieben:Falls du mit "Politur" evt. die Übergabe von Parametern und Einstellungen meinst, dann könnte dich das hier http://www.python-forum.de/topic-10936.html vielleicht ein wenig inspirieren.
    Das kann man mit ConfigObj und ein paar kleinen optparse-Hacks auch leichter hinkriegen:

    Abgeleitete Option-Klasse, die das neue Keyword-Argument "configkey" korrekt behandelt. Configkey wird als Unix-Path angegeben, d.h. Sektionen und Schlüssel sind durch / getrennt.

    Code: Alles auswählen

    class ConfigObjOption(Option):
        ATTRS = Option.ATTRS[:]
        ATTRS.append('configkey')
    
        def _parse_configuration_key(self, config, key):
            """Parses configuration and returns ``(section, key)``,
            where ``section`` is the section, in which key has to be stored"""
            parts = key.split('/')
            if len(parts) == 1:
                # no subsection found
                return config, key
            # last part is the key, the other denote section
            key = parts[-1]
            section_names = parts[:-1]
            # current section
            section = config
            for name in section_names:
                if name not in section:
                    # create non-existing sections
                    section[name] = {}
                section = section[name]
            return section, key
    
        def _ensure_config_value(self, section, key, value):
            """Sets ``section[key]`` to value, if `key is not contained in
            `section` or ``section[key]`` is ``None``.
            Returns ``section[key]``"""
    
        def take_action(self, action, dest, opt, value, values, parser):
            if self.configkey is not None:
                # parse configkey attribute and store results in this
                # instance. This allows callbacks to access the results too
                self.section, self.key = self._parse_configuration_key(
                    values.configuration_object, self.configkey)
            Option.take_action(self, action, dest, opt, value, values, parser)
            if self.configkey is None:
                # don't continue if not configkey was set _or_ if no value was
                # given. Latter is done in respect to merging
                return
            config = values.configuration_object
            if action == "store":
                if value is not None:
                    self.section[self.key] = value
            elif action == "store_const":
                self.section[self.key] = self.const
            elif action == "store_true":
                self.section[self.key] = True
            elif action == "store_false":
                self.section[self.key] = False
            elif action == "append":
                if value is not None:
                    l = self._ensure_config_value(self.section, self.key, [])
                    l.append(value)
            elif action == "append_const":
                self._ensure_config_value(self.section, self.key, []).append(
                    self.const)
            elif action == "count":
                self.section[self.key] = self._ensure_config_value(
                    self.section, self.key, 0) + 1
    
    Erzeugung des OptionParsers und Füllen mit Argumenten

    Code: Alles auswählen

    parser = OptionParser(option_class=ConfigObjOption)
    parser.add_option('-n', '--cover-namebase', metavar='name',
                               help='The filename of the cover file. Defaults to '
                              '"cover". Do not supply extension here, it will be '
                              'determined automatically',
                              configkey='cover/namebase'),
    # wichtig, um das ConfigObj zu erzeugen, welches die Optionen enthält
    parser.set_defaults(configuration_object=ConfigObj())
    [...]
    
    Auslesen der Konfiguration. Man sollte/muss mit configspecs arbeiten, wenn man Standardwerte angeben will, da die Standardwerte aus optparse ignoriert werden, um eben nicht mit configspec ins Gehege zu kommen. Außerdem ist es sowieso sinnvoll, Default-Werte nur an einer Stelle anzugeben.

    Code: Alles auswählen

    config = ConfigObj(configuration_file, configspec=CONFIG_SPEC)
    config.validate(Validator())
    
    # Optionen parsen und mit der Konfiguration mergen

    Code: Alles auswählen

    options, args = parser.parse_args()
    config.merge(options.configuration_object)
    
    Jetzt ist die Konfiguration in config entsprechend den Kommandozeilenoptionen aktualisiert.
    Antworten