일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- Python
- ERP
- pyside6
- 장고
- django erp
- qpa_plugin
- 중량 관리
- pip 오류
- uiload
- channels
- django test
- test drive development
- 파이썬
- django
- 장고로 ERP
- django rank
- django drf
- qwindows.dll
- 페이지 최적화
- django role based
- orm 최적화
- QApplication
- materialized
- Self ERP
- tensorflow
- optimization page
- query 최적화
- pyside6 ui
- 재고 관리
- pip 설치
- Today
- Total
취미삼아 배우는 프로그래밍
PDF파일 다루는 프로그램 만들어보기. 본문
PyQt5는 강의하는 강좌도 되게 많고, 좋은 내용도 상당히 많습니다.
그냥 PyQt기존 강좌처럼 따라가려 하다가,
생각해 보니까 PDF를 다루는 프로그램들이 죄다 유료여서 회사에서 쓰기 불편한게 생각나더라구요
그래서 만들어 보려고 합니다.
회사에서 안 사주니 더러워서 직접 만들어 쓰는 PDF툴
시작합니다.
일단 커다란 틀 정도는 잡고 가야겠죠
메인 기능은
1. PDF 나누기 : 선택한 파일을 그림 파일로 쪼갠다.
2. PDF 합치기 : 선택한 파일 pdf 파일들을 모두 한 파일로 합친다.
3. 페이지 추출 : 선택한 파일(들)의 한 페이지를 일괄 추출한다.
정도로 생각해야겠네요.
필요한 기능이 있으면 추가를 해보구요.
1. 틀 만들기
# 자 오늘 사용해볼 Pdf 라이브러리는 PyPDF2입니다.
# 설치는 pip install PyPDF2를 해주세요.
from PyPDF2 import PdfFileReader, PdfFileWriter
class HandlePdfFile:
def __init__(self, file_path):
if type(file_path) != list:
raise Exception('파일 경로의 입력 형태가 리스트가 아닙니다.')
@classmethod
def merge_pdf(cls, file_path_list):
pass
@classmethod
def split_pdf(cls, file_path_list):
pass
@classmethod
def extract_pageFromPdf(cls, file_path_list, target_page):
pass
# 테스트 코드
if __name__ == "__main__":
HandlePdfFile.merge_pdf(['asdfa'])
비교적 형태는 간단하게 잡았습니다.
그냥 함수형으로 써도 되겠지만, 임포트를 한 번에 하기 위해서 전부 클래스 안에 넣었습니다.
일단 저는 모든 파일들에 대해서 리스트 형식으로 받을 계획입니다.
그래서 부모 클래스에게 파일경로 변수가 리스트로 오는건지를 먼저 체크하는걸 넣었죠.
이렇게 되면
cls(file_path_list)를 호출함과 동시에 변수의 타입등을 한 번에 체크가 가능해져요.
이번엔 파일이 실제 있는지의 여부와
아웃풋 폴더가 존재하는지에 대해 예외체크를 추가해 보도록 할게요.
# 자 오늘 사용해볼 Pdf 라이브러리는 PyPDF2입니다.
# 설치는 pip install PyPDF2를 해주세요.
from PyPDF2 import PdfFileReader, PdfFileWriter
import os
class HandlePdfFile:
def __init__(self, file_path):
self.output_folder = 'output'
# 예외 확인
# 타입 체크
if type(file_path) != list:
raise Exception('파일 경로의 입력 형태가 리스트가 아닙니다.')
# 파일 경로 체크
for f in file_path:
if not os.path.exists(f):
raise Exception('파일 경로에 파일이 없습니다.')
# 아웃풋 폴더 체크
if not os.path.exists(self.output_folder):
os.makedirs(self.output_folder)
@classmethod
def merge_pdf(cls, file_path_list, output_path='output.pdf'):
cls(file_path_list)
@classmethod
def split_pdf(cls, file_path_list):
pass
@classmethod
def extract_pageFromPdf(cls, file_path_list, target_page):
pass
@classmethod
def create_pdfFromImages(cls, file_path_list):
pass
# 테스트 코드
if __name__ == "__main__":
HandlePdfFile.merge_pdf(['asdfa'])
저는 보통 예외체크를 제일 마지막에 넣거나 귀찮으니 패스하긴 하는데,
이번엔 한 번 먼저 만들어 봤습니다.
이리저리 생각을 하다가 classmethod를 처음 구조적으로 사용해봤는데,
제 주관적으로 상당히 재밌는 구조로 틀을 만들었네요.
자 그럼 이제 각 함수에 내용을 채워보도록 하겠습니다.
각 함수는
https://blog.naver.com/goglkms/221817310571
제가 쓴 이 글을 참조했습니다.
먼저,
* PDF 페이지 나누기
@classmethod
def split_pdf(cls, file_path_list):
this = cls(file_path_list)
output_path = os.path.join(os.getcwd(), this.output_folder)
print(output_path)
for file_path in file_path_list:
basename = os.path.basename(file_path)
fname = os.path.splitext(basename)[0]
# 경로로부터 파일 읽기
pdf = PdfFileReader(file_path)
for page in range(pdf.getNumPages()):
pdf_writer = PdfFileWriter()
pdf_writer.addPage(pdf.getPage(page))
output_filename = f'{fname}_page_{page + 1}.pdf'
with open(os.path.join(output_path, output_filename),
'wb') as out:
pdf_writer.write(out)
* PDF 합치기
@classmethod
def merge_pdf(cls, file_path_list, output_path='output.pdf'):
this = cls(file_path_list)
pdf_writer = PdfFileWriter()
for f in file_path_list:
pdf_reader = PdfFileReader(f)
for page in range(pdf_reader.getNumPages()):
pdf_writer.addPage(pdf_reader.getPage(page))
with open(output_path, 'wb') as complete:
pdf_writer.write(complete)
* PDF 페이지 추출
@classmethod
def extract_pageFromPdf(cls, file_path_list, target_page):
this = cls(file_path_list)
for file_path in file_path_list:
basename = os.path.basename(file_path)
fname = os.path.splitext(basename)[0]
print(fname)
pdf = PdfFileReader(file_path)
# 예외 처리 : 페이지가 존재하지 않는 경우
if pdf.getNumPages() < target_page:
raise Exception('타겟 페이지가 없습니다.')
pdf_writer = PdfFileWriter()
pdf_writer.addPage(pdf.getPage(target_page))
output_filename = f'extracted_{fname}_page_{target_page + 1}.pdf'
with open(os.path.join(os.getcwd(), this.output_folder, output_filename),
'wb') as out:
pdf_writer.write(out)
* 전체 코드
# pdf_handle.py
# 자 오늘 사용해볼 Pdf 라이브러리는 PyPDF2입니다.
# 설치는 pip install PyPDF2를 해주세요.
from PyPDF2 import PdfFileReader, PdfFileWriter
import os
class HandlePdfFile:
def __init__(self, file_path):
self.output_folder = 'output'
# 예외 확인
# 타입 체크
if type(file_path) != list:
raise Exception('파일 경로의 입력 형태가 리스트가 아닙니다.')
# 파일 경로 체크
for f in file_path:
if not os.path.exists(f):
raise Exception('파일 경로에 파일이 없습니다.')
# 아웃풋 폴더 체크
if not os.path.exists(self.output_folder):
os.makedirs(self.output_folder)
@classmethod
def merge_pdf(cls, file_path_list, output_path='output.pdf'):
this = cls(file_path_list)
pdf_writer = PdfFileWriter()
for f in file_path_list:
pdf_reader = PdfFileReader(f)
for page in range(pdf_reader.getNumPages()):
pdf_writer.addPage(pdf_reader.getPage(page))
with open(output_path, 'wb') as complete:
pdf_writer.write(complete)
@classmethod
def split_pdf(cls, file_path_list):
this = cls(file_path_list)
output_path = os.path.join(os.getcwd(), this.output_folder)
print(output_path)
for file_path in file_path_list:
basename = os.path.basename(file_path)
fname = os.path.splitext(basename)[0]
# 경로로부터 파일 읽기
pdf = PdfFileReader(file_path)
for page in range(pdf.getNumPages()):
pdf_writer = PdfFileWriter()
pdf_writer.addPage(pdf.getPage(page))
output_filename = f'{fname}_page_{page + 1}.pdf'
with open(os.path.join(output_path, output_filename),
'wb') as out:
pdf_writer.write(out)
@classmethod
def extract_pageFromPdf(cls, file_path_list, target_page):
this = cls(file_path_list)
for file_path in file_path_list:
basename = os.path.basename(file_path)
fname = os.path.splitext(basename)[0]
print(fname)
pdf = PdfFileReader(file_path)
# 예외 처리 : 페이지가 존재하지 않는 경우
if pdf.getNumPages() < target_page:
raise Exception('타겟 페이지가 없습니다.')
pdf_writer = PdfFileWriter()
pdf_writer.addPage(pdf.getPage(target_page))
output_filename = f'extracted_{fname}_page_{target_page + 1}.pdf'
with open(os.path.join(os.getcwd(), this.output_folder, output_filename),
'wb') as out:
pdf_writer.write(out)
# 테스트 코드
if __name__ == "__main__":
#
HandlePdfFile.split_pdf(['pdf_sample.pdf'])
HandlePdfFile.merge_pdf(['pdf_sample.pdf', 'pdf_sample2.pdf'])
HandlePdfFile.extract_pageFromPdf(['pdf_sample.pdf', 'pdf_sample2.pdf'],2)
# HandlePdfFile.extract_pageFromPdf(
# [r'C:/Users/Nadure/Desktop/pyqt_example/pdf_sample.pdf', r'C:/Users/Nadure/Desktop/pyqt_example/pdf_sample2.pdf']
# , 1)
그 다음 해야할 거는,
파일을 선택하는 화면을 띄우고, gui와 연결해줘야할 듯 합니다.
한 번, 파일을 선택하는 함수를 버튼들과 연결 시켜 봅시다.
그치만 파일을 선택하는 함수가 총 세 개고, 세 개는 모두 다른 버튼입니다.
파일을 선택하는 코드는 모두 같지만, 저장되는 영역은 각기 다르기 때문에
복붙해서 코드를 작성해야 하는 것 처럼 보입니다.
그치만 유지보수를 위해서라면 손대기 쉬울 만큼 최대한 간결하게 쓰는게 좋다고 생각합니다.
그러니, 중복코드를 줄이려면 무언가 다른 수를 써야 합니다.
여러 방법들이 많겠지만, 저는 따로 action이라는 변수를 전달받고,
이 변수의 값에 따라 분기점을 주는 방식을 택했습니다.
그리고 버튼들을 연결하는 부분은 그냥 모아서 진행하려 합니다.
import sys, os
from PyQt5.QtWidgets import QMainWindow, QApplication, QFileDialog
from PyQt5 import uic
form_class = uic.loadUiType("main.ui")[0]
class MyWindow(QMainWindow, form_class):
def __init__(self):
super().__init__()
self.setupUi(self)
# 결과물을 저장할 경로 리스트
self.mergePathList = []
self.splitPathList = []
self.extractPathList = []
self.connect_Btn()
def connect_Btn(self):
'''
파일을 선택하는 함수를 실행.
lambda는 변수를 입력하기 위해 추가됐다.
원래의 형태는 .connect(self.fileSelect) 로 작성해야 정상적이다.
그치만 변수를 넣어줄 수 없기 때문에 아래와 같은 형식을 취했다.
그 말인즉, select를 연결 시킬 건데, 이 때의 select는
self.fileSelect(변수) 를 넣은 것을 select로 하여 연결시키게 한다.
'''
self.mergeSelectBtn.clicked.connect( lambda select: self.fileSelect('merge') )
self.splitSelectBtn.clicked.connect( lambda select: self.fileSelect('split') )
self.extractSelectBtn.clicked.connect( lambda select: self.fileSelect('extract') )
def fileSelect(self, action):
# 들어오는 action에 따라 조금씩 다르게 작동하도록 분기점을 넣었다.
options = QFileDialog.Options()
options |= QFileDialog.DontUseNativeDialog
fileNames, _ = QFileDialog.getOpenFileNames(self,
f"Open {action}",
"","Python Files (*.pdf);;All Files (*)", options=options)
if fileNames:
print( fileNames )
for f in fileNames :
name = os.path.split(f)[-1]
if action == 'merge' and f not in self.mergePathList:
self.mergePathList.append(f)
self.mergeListWidget.addItem(name)
elif action == 'split' and f not in self.splitPathList:
self.splitPathList.append(f)
self.splitListWidget.addItem(name)
elif action == 'extract' and f not in self.extractPathList:
self.extractPathList.append(f)
self.extractListWidget.addItem(name)
if __name__ == "__main__":
app = QApplication(sys.argv)
myWindow = MyWindow()
myWindow.show()
app.exec_()
* 목록 지우는 함수 추가
# ... 중략
# 내용 추가
def clear_list(self, action):
if action == 'merge':
self.mergeListWidget.clear()
self.mergePathList = []
if action == 'split':
self.splitListWidget.clear()
self.splitPathList = []
if action == 'extract':
self.extractListWidget.clear()
self.extractPathList = []
* 버튼 등록
def connect_Btn(self):
'''
파일을 선택하는 함수를 실행.
lambda는 변수를 입력하기 위해 추가됐다.
원래의 형태는 .connect(self.fileSelect) 로 작성해야 정상적이다.
그치만 변수를 넣어줄 수 없기 때문에 아래와 같은 형식을 취했다.
그 말인즉, select를 연결 시킬 건데, 이 때의 select는
self.fileSelect(변수) 를 넣은 것을 select로 하여 연결시키게 한다.
'''
self.mergeSelectBtn.clicked.connect( lambda select: self.fileSelect('merge') )
self.splitSelectBtn.clicked.connect( lambda select: self.fileSelect('split') )
self.extractSelectBtn.clicked.connect( lambda select: self.fileSelect('extract') )
self.mergeClearBtn.clicked.connect( lambda select: self.clear_list('merge') )
self.splitClearBtn.clicked.connect( lambda select: self.clear_list('split') )
self.extractClearBtn.clicked.connect( lambda select: self.clear_list('extract') )
마찬가지로, 메인 함수를 실행시키는 부분을 추가시키고
버튼에 연결합니다.
아, 아까 만든 pdf 다루는 걸 임포트하는것도 추가하구요.
def do_startMainFunction(self, action):
# HandlePdfFile.split_pdf(['pdf_sample.pdf'])
# HandlePdfFile.merge_pdf(['pdf_sample.pdf', 'pdf_sample2.pdf'])
# HandlePdfFile.extract_pageFromPdf(['pdf_sample.pdf', 'pdf_sample2.pdf'], 1)
if action == 'merge':
HandlePdfFile.merge_pdf(self.mergePathList)
print('merged')
if action == 'split':
HandlePdfFile.split_pdf(self.splitPathList)
print('splited')
if action == 'extract':
target_page = self.extractPageNo.value()
HandlePdfFile.extract_pageFromPdf(self.extractPathList, target_page-1)
print('extracted')
버튼 추가
def connect_Btn(self):
'''
파일을 선택하는 함수를 실행.
lambda는 변수를 입력하기 위해 추가됐다.
원래의 형태는 .connect(self.fileSelect) 로 작성해야 정상적이다.
그치만 변수를 넣어줄 수 없기 때문에 아래와 같은 형식을 취했다.
그 말인즉, select를 연결 시킬 건데, 이 때의 select는
self.fileSelect(변수) 를 넣은 것을 select로 하여 연결시키게 한다.
'''
self.mergeSelectBtn.clicked.connect( lambda select: self.fileSelect('merge') )
self.splitSelectBtn.clicked.connect( lambda select: self.fileSelect('split') )
self.extractSelectBtn.clicked.connect( lambda select: self.fileSelect('extract') )
self.mergeClearBtn.clicked.connect( lambda select: self.clear_list('merge') )
self.splitClearBtn.clicked.connect( lambda select: self.clear_list('split') )
self.extractClearBtn.clicked.connect( lambda select: self.clear_list('extract') )
self.mergeStartBtn.clicked.connect( lambda select: self.do_startMainFunction('merge') )
self.splitStartBtn.clicked.connect( lambda select: self.do_startMainFunction('split') )
self.extractStartBtn.clicked.connect( lambda select: self.do_startMainFunction('extract') )
구
전체코드입니다.
import sys, os
from PyQt5.QtWidgets import QMainWindow, QApplication, QFileDialog
from PyQt5 import uic
from pdf_handle import HandlePdfFile
form_class = uic.loadUiType("main.ui")[0]
class MyWindow(QMainWindow, form_class):
def __init__(self):
super().__init__()
self.setupUi(self)
# 결과물을 저장할 경로 리스트
self.mergePathList = []
self.splitPathList = []
self.extractPathList = []
self.connect_Btn()
def connect_Btn(self):
'''
파일을 선택하는 함수를 실행.
lambda는 변수를 입력하기 위해 추가됐다.
원래의 형태는 .connect(self.fileSelect) 로 작성해야 정상적이다.
그치만 변수를 넣어줄 수 없기 때문에 아래와 같은 형식을 취했다.
그 말인즉, select를 연결 시킬 건데, 이 때의 select는
self.fileSelect(변수) 를 넣은 것을 select로 하여 연결시키게 한다.
'''
self.mergeSelectBtn.clicked.connect( lambda select: self.fileSelect('merge') )
self.splitSelectBtn.clicked.connect( lambda select: self.fileSelect('split') )
self.extractSelectBtn.clicked.connect( lambda select: self.fileSelect('extract') )
self.mergeClearBtn.clicked.connect( lambda select: self.clear_list('merge') )
self.splitClearBtn.clicked.connect( lambda select: self.clear_list('split') )
self.extractClearBtn.clicked.connect( lambda select: self.clear_list('extract') )
self.mergeStartBtn.clicked.connect( lambda select: self.do_startMainFunction('merge') )
self.splitStartBtn.clicked.connect( lambda select: self.do_startMainFunction('split') )
self.extractStartBtn.clicked.connect( lambda select: self.do_startMainFunction('extract') )
def fileSelect(self, action):
# 들어오는 action에 따라 조금씩 다르게 작동하도록 분기점을 넣었다.
options = QFileDialog.Options()
options |= QFileDialog.DontUseNativeDialog
fileNames, _ = QFileDialog.getOpenFileNames(self,
f"Open {action}",
"","Python Files (*.pdf);;All Files (*)", options=options)
if fileNames:
print( fileNames )
for f in fileNames :
name = os.path.split(f)[-1]
if action == 'merge' and f not in self.mergePathList:
self.mergePathList.append(f)
self.mergeListWidget.addItem(name)
elif action == 'split' and f not in self.splitPathList:
self.splitPathList.append(f)
self.splitListWidget.addItem(name)
elif action == 'extract' and f not in self.extractPathList:
self.extractPathList.append(f)
self.extractListWidget.addItem(name)
def clear_list(self, action):
if action == 'merge':
self.mergeListWidget.clear()
self.mergePathList = []
if action == 'split':
self.splitListWidget.clear()
self.splitPathList = []
if action == 'extract':
self.extractListWidget.clear()
self.extractPathList = []
def do_startMainFunction(self, action):
# HandlePdfFile.split_pdf(['pdf_sample.pdf'])
# HandlePdfFile.merge_pdf(['pdf_sample.pdf', 'pdf_sample2.pdf'])
# HandlePdfFile.extract_pageFromPdf(['pdf_sample.pdf', 'pdf_sample2.pdf'], 1)
if action == 'merge':
HandlePdfFile.merge_pdf(self.mergePathList)
print('merged to output.pdf')
if action == 'split':
HandlePdfFile.split_pdf(self.splitPathList)
print('splited')
if action == 'extract':
target_page = self.extractPageNo.value()
HandlePdfFile.extract_pageFromPdf(self.extractPathList, target_page-1)
print('extracted')
if __name__ == "__main__":
app = QApplication(sys.argv)
myWindow = MyWindow()
myWindow.show()
app.exec_()
작동모습
* blog.naver.com/goglkms
이거 제 블로그임미다 !
'파이썬' 카테고리의 다른 글
PySide / Pyqt qpa_plugin error solved(Or not working QApplication) (0) | 2022.03.21 |
---|---|
PySide6 load .ui without convert to .py (0) | 2022.03.21 |
잡무 기강 잡기 -1(원자재 관리) (0) | 2021.09.28 |
pip > permission denied (0) | 2020.09.15 |
파이썬 환경변수 설정 꼬였을 때, (0) | 2020.07.19 |