├── LICENSE
├── README.md
├── dist
└── ebook_to_pdf.exe
├── eBookToPdf.py
├── favicon.ico
├── gui_image.png
└── gui_image_win.png
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 DongHyun Kim
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # eBookToPdf
2 | E-Book PDF 추출 프로그램
3 | 제작 과정 : https://eastshine12.tistory.com/55
4 |
5 |
6 |
7 | # 프로그램 화면
8 |
9 |
10 |
11 | # 실행파일 위치
12 | eBookToPdf/dist/ebook_to_pdf.exe
13 |
14 |
15 | # 사용법
16 | 1. '좌표 위치 클릭' 버튼 클릭 후 캡처할 영역의 좌측상단, 우측하단 좌표를 구한다.
17 | 2. 총 페이지 수와 생성할 PDF 이름을 작성한다.
18 | 3. 다음 페이지를 넘겨보며 화면이 완전히 랜더링되는 시간을 참고하여 캡처 속도를 조절한다.
19 | 4. PDF로 만들기 클릭!
20 | 5. 캡처 이미지가 많아지면, PDF 변환 시간이 길어질 수 있으므로 잠시 기다린다.
21 | 6. 'PDF 변환 완료!'라는 문구가 뜨면 PDF 생성이 완료된 것이다.
22 | 7. PDF 파일은 실행 파일과 같은 경로에 생성된다.
23 |
24 |
25 | # 사용 시 유의사항
26 | 1. 해당 프로그램은 windows 전용 app이다.
27 | 2. 이미지 좌표 영역이 뷰어 영역을 벗어나면 안된다.
28 | 3. 반드시 키보드 오른쪽 방향키를 통해 다음 페이지 전환이 되어야 한다.
29 | 4. 페이지 수가 많을 경우 PDF 용량이 꽤 되므로 HDD 용량이 여유가 있어야 한다.
30 | 5. PDF 재생성 오류 시 프로그램을 재실행한다.
31 |
--------------------------------------------------------------------------------
/dist/ebook_to_pdf.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eastshine12/eBookToPdf/a0be6a9d427558da722ffb90b17bd3d632f70f56/dist/ebook_to_pdf.exe
--------------------------------------------------------------------------------
/eBookToPdf.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import time
4 | import mss
5 | import mss.tools
6 | import pyautogui
7 | import natsort
8 | import shutil
9 |
10 | from pynput import mouse
11 | from pynput.keyboard import Key, Controller
12 | from PIL import Image
13 |
14 | from PySide6.QtCore import QSize, Qt
15 | from PySide6.QtWidgets import QApplication, QWidget, QLabel, QLineEdit, QPushButton, QMainWindow, QVBoxLayout, \
16 | QHBoxLayout, QSlider
17 |
18 |
19 | class MainWindow(QMainWindow):
20 | def __init__(self) -> None:
21 | super().__init__()
22 |
23 | self.num = 1
24 | self.posX1 = 0
25 | self.posY1 = 0
26 | self.posX2 = 0
27 | self.posY2 = 0
28 | self.total_page = 1
29 | self.speed = 0.1
30 | self.region = {}
31 | self.file_list = []
32 |
33 | # 앱 타이틀
34 | self.setWindowTitle("eBookToPdf")
35 |
36 | # 버튼 생성
37 | self.button1 = QPushButton("좌표 위치 클릭")
38 | self.button2 = QPushButton("좌표 위치 클릭")
39 | self.button3 = QPushButton("PDF로 만들기")
40 | self.button3.setFixedSize(QSize(430, 60))
41 | self.button4 = QPushButton("초기화")
42 |
43 | # 버튼 클릭 이벤트
44 | self.button1.clicked.connect(self.좌측상단_좌표_클릭)
45 | self.button2.clicked.connect(self.우측하단_좌표_클릭)
46 | self.button3.clicked.connect(self.btn_click)
47 | self.button4.clicked.connect(self.초기화)
48 |
49 | # 속도 slider
50 | self.speed_slider = QSlider(Qt.Orientation.Horizontal)
51 | self.speed_slider.setMinimum(1)
52 | self.speed_slider.setMaximum(20)
53 | self.speed_slider.setValue(1)
54 | self.speed_slider.valueChanged.connect(self.속도_변경)
55 |
56 | self.speed_label = QLabel(f'캡쳐 속도: {self.speed:.1f}초')
57 | self.speed_label.setAlignment(Qt.AlignmentFlag.AlignRight)
58 | font_speed = self.speed_label.font()
59 | font_speed.setPointSize(10)
60 | self.speed_label.setFont(font_speed)
61 |
62 | self.title = QLabel('E-Book PDF 생성기', self)
63 | self.title.setAlignment(Qt.AlignmentFlag.AlignCenter)
64 | font_title = self.title.font()
65 | font_title.setPointSize(20)
66 | self.title.setFont(font_title)
67 |
68 | self.stat = QLabel('', self)
69 | self.stat.setAlignment(Qt.AlignmentFlag.AlignCenter)
70 | font_stat = self.stat.font()
71 | font_stat.setPointSize(18)
72 | font_stat.setBold(True)
73 | self.stat.setFont(font_stat)
74 |
75 | self.sign = QLabel('Made By EastShine', self)
76 | self.sign.setAlignment(Qt.AlignmentFlag.AlignRight)
77 | font_sign = self.stat.font()
78 | font_sign.setPointSize(10)
79 | font_sign.setItalic(True)
80 | self.sign.setFont(font_sign)
81 |
82 | self.label1 = QLabel('이미지 좌측상단 좌표 ==> ', self)
83 | self.label1_1 = QLabel('(0, 0)', self)
84 | self.label2 = QLabel('이미지 우측하단 좌표 ==> ', self)
85 | self.label2_1 = QLabel('(0, 0)', self)
86 | self.label3 = QLabel('총 페이지 수 ', self)
87 | self.label4 = QLabel('PDF 이름 ', self)
88 |
89 | self.input1 = QLineEdit()
90 | self.input1.setPlaceholderText("총 페이지 수를 입력하세요.")
91 |
92 | self.input2 = QLineEdit()
93 | self.input2.setPlaceholderText("생성할 PDF의 이름을 입력하세요.")
94 |
95 | # Box 설정
96 | box1 = QHBoxLayout()
97 | box1.addWidget(self.label1)
98 | box1.addWidget(self.label1_1)
99 | box1.addWidget(self.button1)
100 |
101 | box2 = QHBoxLayout()
102 | box2.addWidget(self.label2)
103 | box2.addWidget(self.label2_1)
104 | box2.addWidget(self.button2)
105 |
106 | box3 = QHBoxLayout()
107 | box3.addWidget(self.label3)
108 | box3.addWidget(self.input1)
109 |
110 | box4 = QHBoxLayout()
111 | box4.addWidget(self.label4)
112 | box4.addWidget(self.input2)
113 |
114 | box5 = QHBoxLayout()
115 | box5.addWidget(self.speed_label)
116 | box5.addWidget(self.speed_slider)
117 |
118 |
119 | box6 = QHBoxLayout()
120 | box6.addWidget(self.stat)
121 | box6.addWidget(self.button4)
122 | box6.addWidget(self.sign)
123 |
124 | # 레이아웃 설정
125 | layout = QVBoxLayout()
126 | layout.addWidget(self.title)
127 | layout.addStretch(2)
128 | layout.addLayout(box1)
129 | layout.addStretch(1)
130 | layout.addLayout(box2)
131 | layout.addStretch(1)
132 | layout.addLayout(box3)
133 | layout.addStretch(1)
134 | layout.addLayout(box4)
135 | layout.addStretch(4)
136 | layout.addLayout(box5)
137 | layout.addLayout(box6)
138 | layout.addWidget(self.button3)
139 |
140 | container = QWidget()
141 | container.setLayout(layout)
142 |
143 | self.setCentralWidget(container)
144 |
145 | # 창 크기 고정
146 | self.setFixedSize(QSize(450, 320))
147 |
148 | def 초기화(self):
149 | self.num = 1
150 | self.posX1 = 0
151 | self.posY1 = 0
152 | self.posX2 = 0
153 | self.posY2 = 0
154 | self.speed = 0.1
155 | self.total_page = 1
156 | self.region = {}
157 | self.label1_1.setText('(0, 0)')
158 | self.label2_1.setText('(0, 0)')
159 | self.input1.clear()
160 | self.input2.clear()
161 | self.stat.clear()
162 | self.speed_slider.setValue(1)
163 |
164 | def 좌측상단_좌표_클릭(self):
165 | def on_click(x, y, button, pressed):
166 | self.posX1 = int(x)
167 | self.posY1 = int(y)
168 | self.label1_1.setText(str(f'({int(x)}, {int(y)})'))
169 | print('Button: %s, Position: (%s, %s), Pressed: %s ' % (button, x, y, pressed))
170 | if not pressed:
171 | return False
172 |
173 | with mouse.Listener(on_click=on_click) as listener:
174 | listener.join()
175 |
176 | def 우측하단_좌표_클릭(self):
177 | def on_click(x, y, button, pressed):
178 | self.posX2 = int(x)
179 | self.posY2 = int(y)
180 | self.label2_1.setText(str(f'({int(x)}, {int(y)})'))
181 | print('Button: %s, Position: (%s, %s), Pressed: %s ' % (button, x, y, pressed))
182 | if not pressed:
183 | return False
184 |
185 | with mouse.Listener(on_click=on_click) as listener:
186 | listener.join()
187 | def 속도_변경(self):
188 | self.speed = self.speed_slider.value() / 10.0
189 | self.speed_label.setText(f'캡쳐 속도: {self.speed:.1f}초')
190 |
191 | def btn_click(self):
192 |
193 | if self.input1.text() == '':
194 | self.stat.setText('페이지 수를 입력하세요.')
195 | self.input1.setFocus()
196 | return
197 |
198 | if self.input2.text() == '':
199 | self.stat.setText('PDF 제목을 입력하세요.')
200 | self.input2.setFocus()
201 | return
202 |
203 | pos_x, pos_y = pyautogui.position()
204 |
205 | if not(os.path.isdir('pdf_images')):
206 | os.mkdir(os.path.join('pdf_images'))
207 |
208 | self.total_page = int(self.input1.text())
209 |
210 | # The screen part to capture
211 | self.region = {'top': self.posY1, 'left': self.posX1, 'width': self.posX2 - self.posX1,
212 | 'height': self.posY2 - self.posY1}
213 |
214 | m = mouse.Controller()
215 | mouse_left = mouse.Button.left
216 | kb_control = Controller()
217 |
218 | try:
219 | # 화면 전환 위해 한번 클릭
220 | time.sleep(2)
221 | m.position = (self.posX1, self.posY1)
222 |
223 | time.sleep(2)
224 | m.click(mouse_left)
225 | time.sleep(2)
226 | m.position = (pos_x, pos_y)
227 |
228 | # 파일 저장
229 | while self.num <= self.total_page:
230 |
231 | time.sleep(self.speed)
232 |
233 | # 캡쳐하기
234 | with mss.mss() as sct:
235 | # Grab the data
236 | img = sct.grab(self.region)
237 | # Save to the picture file
238 | mss.tools.to_png(img.rgb, img.size, output=f'pdf_images/img_{str(self.num).zfill(4)}.png')
239 |
240 | # 페이지 넘기기
241 | kb_control.press(Key.right)
242 | kb_control.release(Key.right)
243 |
244 | self.num += 1
245 |
246 | print("캡쳐 완료!")
247 | self.stat.setText('PDF 변환 중..')
248 | path = 'pdf_images'
249 | # 이미지 파일 리스트
250 | self.file_list = os.listdir(path)
251 | self.file_list = natsort.natsorted(self.file_list)
252 |
253 | # .DS_Store 파일이름 삭제
254 | if '.DS_Store' in self.file_list:
255 | del self.file_list[0]
256 |
257 | img_list = []
258 |
259 | # PDF 첫 페이지 만들어두기
260 | img_path = 'pdf_images/' + self.file_list[0]
261 | im_buf = Image.open(img_path)
262 | cvt_rgb_0 = im_buf.convert('RGB')
263 |
264 | for i in self.file_list:
265 | img_path = 'pdf_images/' + i
266 | im_buf = Image.open(img_path)
267 | cvt_rgb = im_buf.convert('RGB')
268 | img_list.append(cvt_rgb)
269 |
270 | del img_list[0]
271 |
272 | pdf_name = self.input2.text()
273 | if pdf_name == '':
274 | pdf_name = 'default'
275 |
276 | cvt_rgb_0.save(pdf_name+'.pdf', save_all=True, append_images=img_list, quality=100)
277 | print("PDF 변환 완료!")
278 | self.stat.setText('PDF 변환 완료!')
279 | shutil.rmtree('pdf_images/')
280 |
281 | except Exception as e:
282 | print('예외 발생. ', e)
283 | self.stat.setText('오류 발생. 종료 후 다시 시도해주세요.')
284 |
285 | finally:
286 | self.num = 1
287 | self.file_list = []
288 |
289 |
290 | app = QApplication(sys.argv)
291 |
292 | window = MainWindow()
293 | window.show()
294 |
295 | # 이벤트 루프 시작
296 | app.exec()
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eastshine12/eBookToPdf/a0be6a9d427558da722ffb90b17bd3d632f70f56/favicon.ico
--------------------------------------------------------------------------------
/gui_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eastshine12/eBookToPdf/a0be6a9d427558da722ffb90b17bd3d632f70f56/gui_image.png
--------------------------------------------------------------------------------
/gui_image_win.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eastshine12/eBookToPdf/a0be6a9d427558da722ffb90b17bd3d632f70f56/gui_image_win.png
--------------------------------------------------------------------------------