jpgpdf

Ratgeber · Bildformate

JPG zu PDF im Code: Python, Node und Kommandozeile

Sobald JPGs nicht einmalig, sondern wiederholt oder in Masse zu PDF werden sollen, lohnt sich Code. Dieser Artikel zeigt die Wege in Python, Node und auf der Kommandozeile.

6 Min Lesezeit 1.293 Wörter 4 FAQs
Jan-Tristan Rudat
Jan-Tristan RudatRedakteur
Geprüft am

Warum Bilder im Code zu PDF bündeln

Ein einzelnes JPG in ein PDF zu verwandeln, erledigt ein Browser-Tool wie jpg-pdf.de in Sekunden. Sobald aber täglich Scans verarbeitet werden, ein Build-Schritt Belege zusammenfasst oder ein Backend pro Upload ein Dokument erzeugen muss, braucht es Code. Programmatisches Bündeln ist reproduzierbar, lässt sich in CI-Pipelines hängen und skaliert über tausende Dateien hinweg ohne manuelles Klicken.

Die zentrale Frage dabei ist fast immer dieselbe: Wird das Bild beim Einbetten neu kodiert oder nicht. Ein JPG ist bereits verlustbehaftet komprimiert. Wer es dekodiert und erneut als JPG kodiert, verliert ein zweites Mal Qualität und bekommt oft eine größere Datei. Wer die JPG-Bytes unverändert in den PDF-Container schiebt, behält die Originalqualität bei kleinerer Dateigröße. Diese Unterscheidung trennt die folgenden Werkzeuge.

Python mit Pillow: der naheliegende Weg

Pillow ist die De-facto-Bildbibliothek in Python und kann PDF direkt schreiben. Der Reiz liegt darin, dass Pillow ohnehin in vielen Projekten installiert ist und neben dem PDF-Export auch Skalierung, Drehung und Farbkonvertierung beherrscht.

from PIL import Image

# Ein einzelnes JPG zu PDF
img = Image.open("scan.jpg")
img.convert("RGB").save("scan.pdf", "PDF", resolution=100.0)

Der Aufruf von convert(“RGB”) ist wichtig, weil PDF kein Alpha kennt und Bilder im Modus RGBA oder P sonst einen Fehler werfen. Mehrere Bilder landen über append_images in einer einzigen mehrseitigen PDF:

from PIL import Image
from pathlib import Path

pfade = sorted(Path("bilder").glob("*.jpg"))
seiten = [Image.open(p).convert("RGB") for p in pfade]

erste, rest = seiten[0], seiten[1:]
erste.save(
    "album.pdf",
    "PDF",
    save_all=True,
    append_images=rest,
    resolution=150.0,
)

Der Haken: Pillow dekodiert jedes JPG vollständig zu einem Pixel-Array und kodiert es beim Speichern neu. Das kostet Qualität und Zeit. Für Screenshots, generierte Grafiken oder Fälle, in denen ohnehin skaliert oder bearbeitet wird, ist das egal. Für Originalscans, die unangetastet bleiben sollen, ist es der falsche Weg.

Python mit img2pdf: verlustfrei einbetten

Genau für diesen Fall gibt es img2pdf. Die Bibliothek dekodiert das JPG nicht, sondern legt die komprimierten Bytes direkt in das PDF. Das Ergebnis ist pixelgenau identisch zum Original, die Dateigröße entspricht ungefähr der Summe der Eingabebilder, und der Vorgang ist deutlich schneller als bei Pillow.

import img2pdf
from pathlib import Path

pfade = [str(p) for p in sorted(Path("bilder").glob("*.jpg"))]

with open("dokument.pdf", "wb") as f:
    f.write(img2pdf.convert(pfade))

Soll jede Seite ein festes Format wie A4 mit Rand bekommen, liefert img2pdf eine Layout-Funktion mit:

import img2pdf
from img2pdf import mm_to_pt

a4 = (mm_to_pt(210), mm_to_pt(297))
rand = (mm_to_pt(10), mm_to_pt(10))
layout = img2pdf.get_layout_fun(a4, border=rand)

with open("a4.pdf", "wb") as f:
    f.write(img2pdf.convert("bilder/seite1.jpg", "bilder/seite2.jpg", layout_fun=layout))

img2pdf akzeptiert ausschließlich Eingabeformate, die ohne Neukodierung in PDF passen, also vor allem JPG und PNG. Wer ein Bild vorher beschneiden oder drehen will, kombiniert beide Bibliotheken: Pillow für die Bearbeitung, img2pdf für das Einbetten. Bei JPG ist allerdings zu beachten, dass jede Pillow-Bearbeitung erneut neu kodiert. Wer wirklich verlustfrei bleiben will, sollte nur dort eingreifen, wo es unvermeidbar ist.

Node mit pdf-lib: PDF im JavaScript-Stack

Im JavaScript-Ökosystem ist pdf-lib die saubere Wahl, weil es ohne native Abhängigkeiten auskommt und im Browser wie in Node läuft. Es bettet JPGs über embedJpg ein, ohne sie neu zu kodieren, und gibt volle Kontrolle über Seitengröße und Positionierung.

import { PDFDocument } from "pdf-lib";
import { readFile, writeFile } from "node:fs/promises";
import { readdir } from "node:fs/promises";

const dir = "bilder";
const dateien = (await readdir(dir))
  .filter((n) => n.toLowerCase().endsWith(".jpg"))
  .sort();

const pdf = await PDFDocument.create();

for (const name of dateien) {
  const bytes = await readFile(`${dir}/${name}`);
  const bild = await pdf.embedJpg(bytes);
  const seite = pdf.addPage([bild.width, bild.height]);
  seite.drawImage(bild, { x: 0, y: 0, width: bild.width, height: bild.height });
}

const ergebnis = await pdf.save();
await writeFile("dokument.pdf", ergebnis);

Hier bestimmt jede Seite ihre Größe aus den Bildmaßen, das Bild füllt die Seite randlos. Für ein festes A4-Format setzt man die Seitengröße fix und skaliert das Bild proportional hinein:

import { PDFDocument } from "pdf-lib";
import { readFile, writeFile } from "node:fs/promises";

const A4 = { w: 595.28, h: 841.89 }; // Punkte
const pdf = await PDFDocument.create();
const bild = await pdf.embedJpg(await readFile("foto.jpg"));

const skala = Math.min(A4.w / bild.width, A4.h / bild.height);
const b = bild.width * skala;
const h = bild.height * skala;

const seite = pdf.addPage([A4.w, A4.h]);
seite.drawImage(bild, {
  x: (A4.w - b) / 2,
  y: (A4.h - h) / 2,
  width: b,
  height: h,
});

await writeFile("foto-a4.pdf", await pdf.save());

Wichtig ist embedJpg statt embedPng zu wählen, denn nur die JPG-Variante übernimmt die komprimierten Bytes direkt. pdf-lib eignet sich besonders, wenn die PDF-Erzeugung Teil einer Node-Anwendung ist, etwa als API-Endpunkt, der Uploads entgegennimmt und sofort ein PDF zurückgibt, oder im Browser ganz ohne Server.

ImageMagick auf der Kommandozeile

Wer keinen Code schreiben, sondern in einem Shell-Skript oder Makefile bündeln will, greift zu ImageMagick. Der Befehl ist kurz und seit Jahren stabil. In der Version 7 heißt das Werkzeug magick, in Version 6 convert:

# Alle JPGs eines Ordners in eine PDF, alphabetisch sortiert
magick *.jpg dokument.pdf

# Version 6
convert *.jpg dokument.pdf

Achtung beim Glob: Die Shell sortiert *.jpg in der Regel alphabetisch, was bei Dateinamen wie bild2.jpg und bild10.jpg zu falscher Reihenfolge führt. Für numerisch korrekte Sortierung hilft eine explizite Liste:

magick $(ls -v *.jpg) dokument.pdf

Festes A4-Format mit Zentrierung und weißem Hintergrund:

magick *.jpg -page A4 -gravity center -background white -extent A4 dokument.pdf

Standardmäßig kodiert ImageMagick die Bilder neu. Um die JPG-Daten unverändert einzubetten, gibt es die Option für JPEG-Passthrough:

magick *.jpg -define pdf:use-jpeg-quality=true dokument.pdf

In der Praxis ist die verlustfreie Garantie hier weniger streng als bei img2pdf. Wer auf Pixelgleichheit angewiesen ist, fährt mit img2pdf sicherer. ImageMagick spielt seine Stärke dort aus, wo ohnehin transformiert wird, etwa Skalieren, Komprimieren und Bündeln in einem Aufruf, oder wo eine Installation der Bibliothek einfacher ist als ein eigenes Skript.

Ein praktischer Hinweis zur Sicherheit: Viele Distributionen liefern ImageMagick mit einer restriktiven policy.xml, die PDF-Operationen aus historischen Sicherheitsgründen sperrt. Wenn der Befehl mit einer Meldung über eine nicht erlaubte Operation abbricht, muss in /etc/ImageMagick-7/policy.xml die entsprechende Zeile für das PDF-Coder-Recht angepasst werden.

Batch und Automatisierung

Für wiederkehrende Aufgaben zählt nicht der Einzelaufruf, sondern wie gut sich der Schritt einbetten lässt. Auf einem Server, der pro Upload ein PDF erzeugt, ist img2pdf oder pdf-lib die richtige Wahl, weil beide ohne externe Prozesse und ohne erneute Kodierung arbeiten. Ein nächtlicher Cronjob, der einen Scan-Ordner abräumt, ist mit einem ImageMagick-Einzeiler oder einem kleinen Python-Skript schnell erledigt.

Bei großen Mengen lohnt ein Blick auf den Speicher. Pillow hält jedes geöffnete Bild als Pixel-Array im RAM, was bei hochauflösenden Scans schnell mehrere hundert Megabyte pro Seite bedeutet. img2pdf und pdf-lib streamen die komprimierten Bytes und bleiben dabei sparsamer. Wer tausende Seiten in einem Durchlauf bündelt, sollte die Bilder einzeln öffnen und schließen, statt alle gleichzeitig im Speicher zu halten.

Ein typisches Python-Pattern für robuste Batch-Verarbeitung mit Fehlertoleranz:

import img2pdf
from pathlib import Path

quelle = Path("eingang")
ziel = Path("ausgang")
ziel.mkdir(exist_ok=True)

for ordner in sorted(p for p in quelle.iterdir() if p.is_dir()):
    jpgs = [str(p) for p in sorted(ordner.glob("*.jpg"))]
    if not jpgs:
        continue
    try:
        pdf_bytes = img2pdf.convert(jpgs)
        (ziel / f"{ordner.name}.pdf").write_bytes(pdf_bytes)
    except Exception as fehler:
        print(f"Übersprungen: {ordner.name} ({fehler})")

Dieses Muster verarbeitet pro Unterordner ein PDF, hält den Speicher klein und bricht nicht ab, wenn eine einzelne Datei defekt ist.

Welcher Weg wofür

Die Wahl hängt an drei Fragen: Soll die Qualität exakt erhalten bleiben, in welcher Sprache läuft das Projekt, und wird ohnehin transformiert. Wer in Python arbeitet und die Originalqualität braucht, nimmt img2pdf. Wer in Python ohnehin Bilder bearbeitet, kann bei Pillow bleiben und akzeptiert die Neukodierung. Im Node-Stack ist pdf-lib die saubere, abhängigkeitsarme Wahl, die zudem im Browser läuft. Für schnelle Skripte ohne eigenen Code ist ImageMagick auf der Kommandozeile unschlagbar bequem, solange die policy.xml mitspielt und die Sortierung stimmt. Für den einmaligen, manuellen Fall braucht es nichts davon, da genügt das Browser-Tool.

FAQ

Häufige Fragen

Warum erzeugt img2pdf kleinere Dateien als Pillow?

img2pdf bettet die JPG-Bytes ohne erneute Kodierung ein. Pillow dekodiert das Bild zu Pixeln und kodiert beim Speichern neu, was zusätzliche Artefakte und oft größere oder qualitativ schlechtere Dateien erzeugt.

Kann ich mehrere JPGs in eine einzige PDF mit je einer Seite legen?

Ja. Alle vier Wege unterstützen das. In Pillow übergibst du append_images, in img2pdf eine Liste von Pfaden, in pdf-lib fügst du pro Bild eine Seite hinzu, und ImageMagick verarbeitet mehrere Eingabedateien direkt zu mehrseitigen PDFs.

Wie steuere ich die Seitengröße und Ränder?

img2pdf bietet mit layout_fun feste Formate wie A4 inklusive Rand. In pdf-lib setzt du die Seitengröße beim addPage selbst. ImageMagick nutzt -page A4 plus -gravity und -extent für die Platzierung.

Welche Variante eignet sich für einen Server ohne Internet?

Alle vier laufen vollständig lokal ohne Netzwerkzugriff. img2pdf und pdf-lib haben die wenigsten Systemabhängigkeiten, ImageMagick benötigt das installierte Binary.

Anzeige

Quellen

Worauf dieser Ratgeber sich stützt

Veröffentlicht · zuletzt geprüft
Verantwortlich: Jan-Tristan Rudat
Anzeige
Anzeige
Anzeige
Anzeige