PDF-Dateien direkt aus Python heraus drucken

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.
Nobuddy
User
Beiträge: 997
Registriert: Montag 30. Januar 2012, 16:38

Hallo zusammen,

Ich suche eine Möglichkeit, PDF-Dateien direkt aus Python heraus zu drucken.
Mein Betriebssystem ist Linux Kubuntu 14.04 LTS.

Was benötige ich dazu, bzw. gibt es dafür schon etwas Fertiges?

Grüße Nobuddy
EmaNymton
User
Beiträge: 174
Registriert: Sonntag 30. Mai 2010, 14:07

Wenn es nur für dich unter Linux lauffähig sein soll, würde ich mit Hilfe des subprocess module das Dokument per lp bzw. lpr an den Drucker schicken.
Benutzeravatar
diesch
User
Beiträge: 80
Registriert: Dienstag 14. April 2009, 13:36
Wohnort: Brandenburg a.d. Havel
Kontaktdaten:

http://www.florian-diesch.de
Nobuddy
User
Beiträge: 997
Registriert: Montag 30. Januar 2012, 16:38

Hallo zusammen,

@EmaNymton, ja ist nur für Linux gedacht, ein kleiner Beispiel-Code, wäre nicht schlecht gewesen.

@diesch, das schaue ich mir mal genauer an, das könnte evtl. passen, DANKE :wink:

Grüße Nobuddy
EmaNymton
User
Beiträge: 174
Registriert: Sonntag 30. Mai 2010, 14:07

Entschuldige bitte, aber das ist ein Zweizeiler. Ich dachte du kannst mit den von mir genannten Begriffen was anfangen. Selbst wenn nicht, wenn du "lp subprocess" in eine Suchmaschine deiner Wahl eingibst, wirst du mit Sicherheit fündig werden. Ansonsten hilft auch ein "man lp" im Terminal weiter und die Dokumentation zum subprocess-Modul findest du in der offiziellen Python-Doku.

@pycups: AFAIK ist das nur zur Verwaltung von Druckern und nicht zum Drucken selbst gedacht.
BlackJack

@EmaNymton: Es gibt auf `cups.Connection`-Exemplaren eine `printFile()`-Methode wo man Druckername, lokalen Dateinamen, und Optionen angeben kann. Sieht also so aus als könnte man auch damit drucken.
EmaNymton
User
Beiträge: 174
Registriert: Sonntag 30. Mai 2010, 14:07

Sieht nicht nur so aus, geht auch, gerade getestet ;)
Nobuddy
User
Beiträge: 997
Registriert: Montag 30. Januar 2012, 16:38

test.py und cupstree.py funktionieren fehlerfrei, nur wie kann ich damit eine PDF-Datei mit meinem Drucker ausdrucken?

Vielleicht kann mir das Jemand verraten?

Grüße Nobuddy
Benutzeravatar
/me
User
Beiträge: 3556
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Nobuddy hat geschrieben:Vielleicht kann mir das Jemand verraten?
Um BlackJack zu zitieren: "Es gibt auf `cups.Connection`-Exemplaren eine `printFile()`-Methode wo man Druckername, lokalen Dateinamen, und Optionen angeben kann.".
Nobuddy
User
Beiträge: 997
Registriert: Montag 30. Januar 2012, 16:38

Hallo /me,

habe schon BlackJackś Info gelesen ... und stehe doch auf dem Schlauch. :K

Mittels cupstree.py habe ich Druckernamen und URI, meines LAN-Druckers erhalten.
Wenn ich nicht verkehrt liege, ist die test.py zum Drucken da.
Ich habe diese mal entsprechend editiert und sieht momentan so aus:

Code: Alles auswählen

#!/usr/bin/python
import cups

# Simple demonstration of cups module
PRINTER = 'MFC8860DN'
PRINTERNAME = 'MFC8860DN[@localhost]'
URI = 'socket://192.168.178.30:9100'
FILE = '/home/pfad/zu/meiner/pdf_datei/a.pdf'

def callback(prompt):
    print ("Password is required for this operation")
    password = raw_input(prompt)
    return password

def test_cups_module():
    cups.setUser("root")
    cups.setPasswordCB(callback)
    conn = cups.Connection()
    printers = list(conn.getPrinters().keys())
    pi = [i for i, printer in enumerate(printers)
        if printer == PRINTER][0]

    if 0:
        file(FILE, "w")
        conn.getFile(PRINTERNAME, FILE)
        conn.putFile(PRINTERNAME, FILE)

    print("Getting PPD for %s" % printers[pi])
    f = conn.getPPD(printers[pi])
    ppd = cups.PPD(f)
    ppd.markDefaults()
    print (ppd.conflicts())
    groups = ppd.optionGroups
    for group in groups:
        for opt in group.options:
            print(list(map(lambda x: x["text"], opt.choices)))
    # conn.printFile(PRINTERNAME, FILE, ??optionen??)

test_cups_module ()
Leider habe ich mit den Optionen erhebliche Schwierigkeiten.

Wäre schön, wenn Ihr mir da weiter helfen könntet!
Bin ich mit einem Teil des Codes auf dem richtigen Weg, oder total verkehrt?

Grüße Nobuddy
Nobuddy
User
Beiträge: 997
Registriert: Montag 30. Januar 2012, 16:38

Nun habe ich es doch noch geschafft, eine PDF-Datei auszudrucken. :D

Code: Alles auswählen

#!/usr/bin/python
import cups

# Simple demonstration of cups module
PRINTER = 'MFC8860DN'
PRINTERNAME = 'MFC8860DN[@localhost]'
URI = 'socket://192.168.178.30:9100'
FILE = '/home/pfad/zu/meiner/pdf_datei/a.pdf'

def callback(prompt):
    print ("Password is required for this operation")
    password = raw_input(prompt)
    return password

def test_cups_module():
    cups.setUser("root")
    cups.setPasswordCB(callback)
    conn = cups.Connection()
    printers = list(conn.getPrinters().keys())
    pi = [i for i, printer in enumerate(printers)
        if printer == PRINTER][0]

    if 0:
        file(FILE, "w")
        conn.getFile(PRINTERNAME, FILE)
        conn.putFile(PRINTERNAME, FILE)

    print("Getting PPD for %s" % printers[pi])
    f = conn.getPPD(printers[pi])
    ppd = cups.PPD(f)
    ppd.markDefaults()
    print (ppd.conflicts())
    groups = ppd.optionGroups
    title = FILE.split("/")[-1]
    conn.printFile(PRINTER, FILE, title=title, options={'copies': '1'})
Grüße Nobuddy
Benutzeravatar
darktrym
User
Beiträge: 784
Registriert: Freitag 24. April 2009, 09:26

Zum Verständnis wie die API funktioniert ok, aber die sonstige Codequalität wirft Fragen auf.
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Natürlich geht es auch kompliziert. Ich mache dies so:

Code: Alles auswählen

def print_invoice_pdf(path):
    """Print a given pdf-file. path must be absolute."""
    subprocess.call(['lp', path])
Solange dies nur lokal läuft, ist es für mich gut genug.
Benutzeravatar
diesch
User
Beiträge: 80
Registriert: Dienstag 14. April 2009, 13:36
Wohnort: Brandenburg a.d. Havel
Kontaktdaten:

Nobuddy hat geschrieben:Nun habe ich es doch noch geschafft, eine PDF-Datei auszudrucken. :D
Als Minimalvariante reicht sowas wie

Code: Alles auswählen

#!/usr/bin/python
# -*- coding: utf-8 -*-
import cups

PRINTER = 'PDF'
FILE = 'test.pdf'

conn = cups.Connection()
conn.printFile(PRINTER, FILE, title='test', options={})
http://www.florian-diesch.de
Nobuddy
User
Beiträge: 997
Registriert: Montag 30. Januar 2012, 16:38

Ja diesch, die Minimalvariante reicht da völlig aus. :wink:

Wie ist das bei den options, wenn ich ein 5-seitiges Dokument habe, bei dem ich nur Seite 2 bis 3 ausdrucken möchte?

Grüße Nobuddy
Benutzeravatar
diesch
User
Beiträge: 80
Registriert: Dienstag 14. April 2009, 13:36
Wohnort: Brandenburg a.d. Havel
Kontaktdaten:

kbr hat geschrieben:Natürlich geht es auch kompliziert. Ich mache dies so:

Code: Alles auswählen

def print_invoice_pdf(path):
    """Print a given pdf-file. path must be absolute."""
    subprocess.call(['lp', path])
Solange dies nur lokal läuft, ist es für mich gut genug.
Da musst du dafür sorgen, dass der Pfad nicht mit einem - anfängt, sonst interpretiert lp das als Option.

Die Lösung funktioniert nur, wenn der Benutzer kein Passwort zum Drucken benötigt. Und sie ist schlecht erweiterbar, wenn du mehr willst, als nur ein Datei mit festen Optionen zu drucken, z.B. auch eine Drucker-Auswahl anbieten.
http://www.florian-diesch.de
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

@diesch: Absolute Pfade fangen in der Regel nicht mit '-' an. Ansonsten hast Du Recht, dieser Einzeiler schiebt nur eine Datei an den Default-Drucker. Oftmals reicht das ja schon.
Benutzeravatar
diesch
User
Beiträge: 80
Registriert: Dienstag 14. April 2009, 13:36
Wohnort: Brandenburg a.d. Havel
Kontaktdaten:

Nobuddy hat geschrieben: Wie ist das bei den options, wenn ich ein 5-seitiges Dokument habe, bei dem ich nur Seite 2 bis 3 ausdrucken möchte?

Code: Alles auswählen

options={'page-ranges':'2-3'}
http://www.florian-diesch.de
Nobuddy
User
Beiträge: 997
Registriert: Montag 30. Januar 2012, 16:38

Hallo diesch,
Danke für Deine Info!

Ich habe einfach mal etwas herum gespielt und die cupstree.py editiert.
Zuerst habe ich eine Klasse erstellt und dann mit ein paar nützlichen Funktionen versehen.
Ich denke, daß sich mit dem Code von cupstree.py noch einiges mehr sich herausholen lässt.

Hier mal mein Änderungs-Code:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# For Python3.x


import cups

class CUPS_Pool(object):
    """
    Erstellung von System-Informationen zu CUPS-Drucker.
    """

    def __init__(self, host=None, depth=0):

        if host:
            cups.setServer(host)
        else:
            host = "localhost"
        c = cups.Connection()
        printers = c.getPrinters()
        classes = c.getClasses()
        self.default_printer = c.getDefault()
        self.indent = self.do_indent(depth)
        self.ident_number = 0
        self.conn = cups.Connection()
        self.printerpool = list()
        self.printersettings = dict()
        for name, queue in printers.items():
            self.getqueue(name, queue, host, depth, printers, classes)


    def do_indent(self, indent):
        return "  "*indent


    def getippqueue(self, dev, queue, depth):
        name = dev.rfind('/')
        name = dev[name + 1:]
        dev = dev[6:]
        e = dev.find(':')
        if e == -1:
            e = dev.find('/')
        host = dev[:e]
        cups.setServer(host)
        try:
            c = cups.Connection()
            printers = c.getPrinters()
            classes = c.getClasses()
        except RuntimeError:
            # Failed to connect.
            return
        except cups.IPPError as e:
            if e == cups.IPP_OPERATION_NOT_SUPPORTED:
                # CUPS-Get-Printers not supported so not a CUPS server.
                printers = {}
                classes = {}
            else:
                return

        queue = c.getPrinterAttributes(name)
        dev = queue['device-uri']
        self.getqueue(name, queue, host, depth + 1, printers, classes)


    def getqueue(self, name, queue, host, depth, printers, classes):
        self.indent = self.do_indent(depth)
        if queue['printer-type'] & cups.CUPS_PRINTER_CLASS:
            print("%s* Name:\t%s[@%s] (class)" % (self.indent, name, host))
            dev = queue['device-uri']
            if dev.startswith('ipp:'):
                self.getippqueue(dev, queue, depth)
            else:
                members = classes[name]
                depth += 1
                self.indent = self.do_indent(depth)
                for member in members:
                    self.getqueue(member, printers[member], host,
                        depth, printers, classes)
        else:
            print("%s* Name:\t%s[@%s]" % (self.indent, name, host))
            printer_name = name
            printer_host = "%s[@%s]" % (name, host)
            dev = queue['device-uri']
            info = queue['printer-info']
            print("%sURI:\t%s" % (self.indent, dev))
            printer_uri = dev
            print("%sInfo:\t%s" % (self.indent, info))
            printer_info = info
            if dev.startswith ('ipp:'):
                return self.getippqueue(dev, name, depth)

        if depth == 0:
            print

        master = ''
        if self.default_printer == printer_name:
            master = 'MASTER'
            self.master_printer = printer_name
        self.printerpool.append([master, printer_name, printer_host,
            printer_uri, printer_info])

        # Settings
        f = self.conn.getPPD(printer_name)
        ppd = cups.PPD(f)
        ppd.markDefaults()
        groups = ppd.optionGroups
        option2selection = dict()
        for group in groups:
            for opt in group.options:
                selection = list(map(lambda x: x["text"], opt.choices))
                option2selection[opt] = selection
        self.printersettings[printer_name] = option2selection


    def printer_pool(self):
        """
        Ausgabe von status, printer_name, printer_host, printer_uri
        und printer_info.
        """

        return sorted(self.printerpool, reverse=True)


    def printer_settings(self):
        """
        Ausgabe von Drucker-Einstellungen und Auswahl-Einstellungen.
        """

        return self.printersettings


    def print2document(self, print_file):
        """
        Ausführung von Druckauftrag.
        """

        title = print_file.split("/")[-1]
        try:
            self.conn.printFile(self.master_printer, print_file, 
                title=title, options={})
        except cups.IPPError as e:
            print('Fehler bei Druckauftrag: %s' % e)
        return


def main():
    ## Erledige Druckauftrag mit Standad-Drucker
    # CUPS_Pool().print2document('/pfad/zu/test.pdf')
    ## Ausgabe Aller CUPS-Drucker
    #printer_pool = CUPS_Pool().printer_pool()
    try:
        print(printer_pool)
    except NameError:
        pass
    ## Ausgabe der Drucker-Einstellungen und Auswahl-Einstellungen
    ## aller CUPS-Drucker.
    ## dict = (printer_name : {printer_setting : setting_selection})
    #printer_settings = CUPS_Pool().printer_settings()
    try:
        print(printer_settings['MFC8860DN'])
    except NameError:
        pass


if __name__ == '__main__':
    main()
Mit der Funktion 'print2document', übergebe ich nur noch das Druckdokument mit Pfad.
Der Standard-Drucker, wird automatisch übergeben.

Bei Zeile 92:

Code: Alles auswählen

        if depth == 0:
            print
bin ich mir nicht sicher, ob dies gebraucht wird und welche Aufgabe aus 'print' resultiert.

Grüße Nobuddy
Nobuddy
User
Beiträge: 997
Registriert: Montag 30. Januar 2012, 16:38

Was mich noch interessiert:
Gibt es die Möglichkeit über CUPS, die Info zu erhalten ob der betreffende Drucker überhaupt an und bereit ist?
Dazu habe ich bisher noch nichts finden können.

Grüße Nobuddy
Antworten