├── 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 --------------------------------------------------------------------------------