
このツール(PDFTool
)は、PDFや画像ファイルをドラッグ&ドロップで操作できる多機能PDFユーティリティです。
主な特徴・機能を簡単にまとめます。
何ができるツール?
- PDFファイルや画像(JPEG, PNG)をウィンドウにドラッグ&ドロップしてリスト化
- ファイルの並べ替えや削除が簡単(リスト上で順番を変えたりDeleteキーで削除)
- 各ボタンでPDFファイルや画像ファイルに対し次の操作ができる:
主な機能
ボタン | 機能の説明 |
---|---|
🔻 圧縮 | 選択したPDFを**軽量化(圧縮)**して別フォルダに保存します。 |
📎 結合 | 複数のPDFを一つのPDFに結合+圧縮して保存します。 |
📂 分解 | 1つのPDFをページごとに分割し、それぞれ圧縮したPDFとして保存します。 |
🖼️→📄 画像PDF | 選択した画像(jpg/png)を PDF化して保存します。 |
使い方
- 起動するとウィンドウが表示されます。
- PDFや画像ファイルをウィンドウ内にドラッグ&ドロップ → ファイルリストに追加されます。
- ファイルリスト内で並べ替えや選択・削除が可能。
- 実行したい処理のボタンを押すと、出力先のフォルダまたは保存先を指定し、処理が始まります。
技術的な特徴
- GUI(ウィンドウ操作)は PyQt5 で作成
- PDF処理には PyPDF2、画像処理には Pillow(PIL) を利用
- PDFの圧縮処理は Ghostscript をコマンドラインで呼び出して実行
(Ghostscriptが必要、Windowsはgswin64c
を自動認識) - すべての操作がGUIで直感的に可能
代表的なユースケース
- スキャンした大量のPDFを軽くしたいとき
- バラバラのPDFや画像を一つのPDFにまとめたいとき
- 1つのPDFを1ページずつバラして管理したいとき
- JPEG/PNG画像をPDF化したいとき
注意点
- PDFの圧縮機能を使うにはGhostscriptのインストールが必要
- 圧縮・分解・結合は「PDFのみ」対象(画像はPDF変換だけ)
「PDFや画像ファイルの整理・圧縮・変換を直感的な操作で一括でこなしたい」人におすすめのツールです!
シンプル操作なので初心者でも迷いません。
import sys
import os
import subprocess
from PyQt5.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QListWidget, QPushButton,
QFileDialog, QGridLayout, QMessageBox, QLabel
)
from PyQt5.QtCore import Qt
from PyPDF2 import PdfMerger, PdfReader, PdfWriter
from PIL import Image
class PDFTool(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("PDFツール - ドラッグ&ドロップ対応")
self.setAcceptDrops(True)
self.resize(600, 400)
self.list_widget = QListWidget()
self.list_widget.setDragDropMode(QListWidget.InternalMove)
self.list_widget.setSelectionMode(QListWidget.SingleSelection)
self.list_widget.installEventFilter(self)
self.label = QLabel("PDFまたは画像をここにドラッグ&ドロップして並び替え可能\n選択してDeleteキーで削除できます")
self.label.setAlignment(Qt.AlignCenter)
self.btn_compress = QPushButton("🔻 圧縮")
self.btn_merge = QPushButton("📎 結合")
self.btn_split = QPushButton("📂 分解")
self.btn_image_to_pdf = QPushButton("🖼️→📄 画像PDF")
self.btn_compress.clicked.connect(self.compress_pdfs)
self.btn_merge.clicked.connect(self.merge_pdfs)
self.btn_split.clicked.connect(self.split_pdfs)
self.btn_image_to_pdf.clicked.connect(self.image_to_pdf_only)
layout = QVBoxLayout()
layout.addWidget(self.label)
layout.addWidget(self.list_widget)
grid = QGridLayout()
grid.addWidget(self.btn_compress, 0, 0, 1, 2)
grid.addWidget(self.btn_merge, 1, 0, 1, 2)
grid.addWidget(self.btn_split, 2, 0, 1, 2)
grid.addWidget(self.btn_image_to_pdf, 3, 0, 1, 2)
layout.addLayout(grid)
self.setLayout(layout)
def eventFilter(self, obj, event):
if obj == self.list_widget and event.type() == event.KeyPress:
if event.key() == Qt.Key_Delete:
for item in self.list_widget.selectedItems():
self.list_widget.takeItem(self.list_widget.row(item))
return True
return super().eventFilter(obj, event)
def dragEnterEvent(self, event):
if event.mimeData().hasUrls():
event.acceptProposedAction()
def dropEvent(self, event):
for url in event.mimeData().urls():
path = url.toLocalFile()
if path.lower().endswith((".pdf", ".jpg", ".jpeg", ".png")):
self.list_widget.addItem(path)
def get_output_dir(self):
output_dir = QFileDialog.getExistingDirectory(self, "出力フォルダを選択")
return output_dir if output_dir else None
def compress_pdf(self, input_path, output_path, quality="ebook"):
gs_command = "gswin64c" if sys.platform.startswith("win") else "gs"
cmd = [
gs_command, "-sDEVICE=pdfwrite", "-dCompatibilityLevel=1.4",
f"-dPDFSETTINGS=/{quality}", "-dNOPAUSE", "-dQUIET", "-dBATCH",
f"-sOutputFile={output_path}", input_path
]
try:
subprocess.run(cmd, check=True, creationflags=subprocess.CREATE_NO_WINDOW if sys.platform.startswith("win") else 0)
return True
except FileNotFoundError:
QMessageBox.critical(self, "Ghostscript エラー", "Ghostscript (gswin64c) が見つかりません。パスが通っているか確認してください。")
return False
except Exception as e:
QMessageBox.critical(self, "圧縮エラー", f"{input_path}\n{e}")
return False
def compress_pdfs(self):
output_dir = self.get_output_dir()
if not output_dir:
return
for i in range(self.list_widget.count()):
path = self.list_widget.item(i).text()
if path.lower().endswith(".pdf"):
name = os.path.splitext(os.path.basename(path))[0] + "_compressed.pdf"
out_path = os.path.join(output_dir, name)
self.compress_pdf(path, out_path)
QMessageBox.information(self, "完了", "圧縮完了")
def merge_pdfs(self):
paths = [self.list_widget.item(i).text() for i in range(self.list_widget.count()) if self.list_widget.item(i).text().lower().endswith(".pdf")]
if not paths:
return
save_path, _ = QFileDialog.getSaveFileName(self, "保存先(結合+圧縮)", "", "PDF Files (*.pdf)")
if not save_path:
return
temp_path = save_path + ".temp.pdf"
merger = PdfMerger()
for path in paths:
merger.append(path)
merger.write(temp_path)
merger.close()
success = self.compress_pdf(temp_path, save_path)
if os.path.exists(temp_path):
os.remove(temp_path)
if success:
QMessageBox.information(self, "完了", f"結合+圧縮完了: {save_path}")
else:
QMessageBox.warning(self, "失敗", "圧縮に失敗しました")
def split_pdfs(self):
output_dir = self.get_output_dir()
if not output_dir:
return
for i in range(self.list_widget.count()):
path = self.list_widget.item(i).text()
if path.lower().endswith(".pdf"):
base_name = os.path.splitext(os.path.basename(path))[0]
compressed_path = os.path.join(output_dir, f"{base_name}_compressed.pdf")
success = self.compress_pdf(path, compressed_path)
if not success or not os.path.exists(compressed_path):
continue
try:
reader = PdfReader(compressed_path)
for j, page in enumerate(reader.pages):
writer = PdfWriter()
writer.add_page(page)
name = f"{base_name}_p{j+1}.pdf"
out_path = os.path.join(output_dir, name)
with open(out_path, "wb") as f:
writer.write(f)
except Exception as e:
QMessageBox.warning(self, "分解エラー", f"{path} の分解に失敗しました。\n{e}")
finally:
if os.path.exists(compressed_path):
os.remove(compressed_path)
QMessageBox.information(self, "完了", "圧縮+分解完了")
def image_to_pdf_only(self):
output_dir = self.get_output_dir()
if not output_dir:
return
for i in range(self.list_widget.count()):
path = self.list_widget.item(i).text()
if path.lower().endswith((".jpg", ".jpeg", ".png")):
try:
image = Image.open(path).convert("RGB")
base_name = os.path.splitext(os.path.basename(path))[0]
pdf_path = os.path.join(output_dir, base_name + ".pdf")
image.save(pdf_path)
except Exception as e:
QMessageBox.warning(self, "画像変換エラー", f"{path} の変換に失敗しました。\n{e}")
QMessageBox.information(self, "完了", "画像をPDFに変換完了")
if __name__ == '__main__':
import ctypes
if sys.platform.startswith("win"):
ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 0)
app = QApplication(sys.argv)
tool = PDFTool()
tool.show()
sys.exit(app.exec_())
コメントを残す