init
This commit is contained in:
parent
3882a2607f
commit
b6a578468d
|
|
@ -0,0 +1,4 @@
|
||||||
|
output.pdf
|
||||||
|
captures/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
3.11.9
|
||||||
23
README.md
23
README.md
|
|
@ -1,23 +1,16 @@
|
||||||
# Template
|
# Ebook Snipping Tool
|
||||||
This project is a code for ~
|
This project is a code for Ebook snipping and PDF converting
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
- foo
|
- Python 3.11.9
|
||||||
- bar
|
- pip
|
||||||
- ...
|
- mss
|
||||||
|
- pyautogui
|
||||||
|
- opencv-python
|
||||||
|
- pyqt5
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
1. Clone this repository to your local machine.
|
1. Clone this repository to your local machine.
|
||||||
2. Install dependencies using ~
|
|
||||||
```batch
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
To run the script, execute the following command:
|
|
||||||
```batch
|
|
||||||
foobar
|
|
||||||
```
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
Feel free to contribute to this project by opening issues or pull requests. Any feedback or suggestions are welcome!
|
Feel free to contribute to this project by opening issues or pull requests. Any feedback or suggestions are welcome!
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
|
|
@ -0,0 +1,260 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from utils.utils import resource_path
|
||||||
|
from PyQt5.QtWidgets import *
|
||||||
|
from PyQt5 import uic, QtGui, QtCore
|
||||||
|
from widget import SnippingWidget
|
||||||
|
from functools import partial
|
||||||
|
import mss
|
||||||
|
from PIL import Image
|
||||||
|
import pyautogui
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
교보ebook 응용프로그램 전용
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class EbookSnipper(QMainWindow):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1"
|
||||||
|
self.capture_dir = "./captures"
|
||||||
|
|
||||||
|
uic.loadUi(resource_path("./ui/MainWindow.ui"), self)
|
||||||
|
self.coords = {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"w": 0,
|
||||||
|
"h": 0,
|
||||||
|
"btn_x": 0,
|
||||||
|
"btn_y": 0,
|
||||||
|
}
|
||||||
|
self.params = {"delay": 0, "pages": 0}
|
||||||
|
|
||||||
|
# Icon Setting
|
||||||
|
self.icon = QtGui.QIcon(resource_path("./assets/icon.ico"))
|
||||||
|
if self.icon.isNull():
|
||||||
|
print("Failed to load icon")
|
||||||
|
self.setWindowIcon(self.icon)
|
||||||
|
|
||||||
|
self.widget = SnippingWidget(self)
|
||||||
|
self.current_page = 0
|
||||||
|
self.is_snipping = False
|
||||||
|
|
||||||
|
# Initialize
|
||||||
|
self._initUi()
|
||||||
|
self.close_spash_screen()
|
||||||
|
|
||||||
|
def _initUi(self):
|
||||||
|
self.params["delay"] = self.spinBox_delay.value()
|
||||||
|
self.params["pages"] = self.spinBox_pages.value()
|
||||||
|
self.btn_coords.clicked.connect(lambda: self.start_setSnippingArea())
|
||||||
|
self.btn_nextbtn.clicked.connect(lambda: self.start_setNextBtnCoord())
|
||||||
|
for key, spinbox in {
|
||||||
|
"x": self.spinBox_x,
|
||||||
|
"y": self.spinBox_y,
|
||||||
|
"w": self.spinBox_w,
|
||||||
|
"h": self.spinBox_h,
|
||||||
|
"btn_x": self.spinBox_btn_x,
|
||||||
|
"btn_y": self.spinBox_btn_y,
|
||||||
|
}.items():
|
||||||
|
spinbox.valueChanged.connect(partial(self.coords_updownbutton_event, key))
|
||||||
|
for key, spinbox in {
|
||||||
|
"delay": self.spinBox_delay,
|
||||||
|
"pages": self.spinBox_pages,
|
||||||
|
}.items():
|
||||||
|
spinbox.valueChanged.connect(partial(self.params_updownbutton_event, key))
|
||||||
|
|
||||||
|
self.btn_start.clicked.connect(self.start_snipping)
|
||||||
|
self.btn_stop.clicked.connect(self.stop_snipping)
|
||||||
|
QShortcut(QtGui.QKeySequence("Alt+F10"), self, self.start_snipping)
|
||||||
|
QShortcut(QtGui.QKeySequence("Alt+F11"), self, self.stop_snipping)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Pyinstaller Splash Screen Terminator
|
||||||
|
"""
|
||||||
|
|
||||||
|
def close_spash_screen(self):
|
||||||
|
if hasattr(sys, "_MEIPASS"):
|
||||||
|
import pyi_splash # type: ignore
|
||||||
|
|
||||||
|
pyi_splash.close()
|
||||||
|
|
||||||
|
"""
|
||||||
|
Snipping Area Mode
|
||||||
|
"""
|
||||||
|
|
||||||
|
def start_setSnippingArea(self):
|
||||||
|
self.widget.rectMode = True
|
||||||
|
self.widget.showFullScreen()
|
||||||
|
|
||||||
|
def update_rect_coords(self, coords=None):
|
||||||
|
self.spinbox_blocksignal(True)
|
||||||
|
if coords == None:
|
||||||
|
coords = self.coords
|
||||||
|
if self.spinBox_x.value() != coords["x"]:
|
||||||
|
self.spinBox_x.setValue(coords["x"])
|
||||||
|
if self.spinBox_y.value() != coords["y"]:
|
||||||
|
self.spinBox_y.setValue(coords["y"])
|
||||||
|
if self.spinBox_w.value() != coords["w"]:
|
||||||
|
self.spinBox_w.setValue(coords["w"])
|
||||||
|
if self.spinBox_h.value() != coords["h"]:
|
||||||
|
self.spinBox_h.setValue(coords["h"])
|
||||||
|
self.spinbox_blocksignal(False)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Next Button Coord Mode
|
||||||
|
"""
|
||||||
|
|
||||||
|
def start_setNextBtnCoord(self):
|
||||||
|
self.widget.rectMode = False
|
||||||
|
self.widget.showFullScreen()
|
||||||
|
|
||||||
|
def update_btn_coord(self, coords=None):
|
||||||
|
self.spinbox_blocksignal(True)
|
||||||
|
if coords == None:
|
||||||
|
coords = self.coords
|
||||||
|
if self.spinBox_btn_x.value() != coords["btn_x"]:
|
||||||
|
self.spinBox_btn_x.setValue(coords["btn_x"])
|
||||||
|
if self.spinBox_btn_y.value() != coords["btn_y"]:
|
||||||
|
self.spinBox_btn_y.setValue(coords["btn_y"])
|
||||||
|
self.spinbox_blocksignal(True)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Event
|
||||||
|
"""
|
||||||
|
|
||||||
|
def coords_updownbutton_event(self, key, value):
|
||||||
|
self.coords[key] = value
|
||||||
|
print(self.coords)
|
||||||
|
|
||||||
|
def params_updownbutton_event(self, key, value):
|
||||||
|
self.params[key] = value
|
||||||
|
print(self.params)
|
||||||
|
|
||||||
|
def spinbox_blocksignal(self, boolean):
|
||||||
|
# if boolean == True: print(f"Before: {self.coords}")
|
||||||
|
# else: print(f"After: {self.coords}")
|
||||||
|
self.spinBox_x.blockSignals(boolean)
|
||||||
|
self.spinBox_y.blockSignals(boolean)
|
||||||
|
self.spinBox_w.blockSignals(boolean)
|
||||||
|
self.spinBox_h.blockSignals(boolean)
|
||||||
|
self.spinBox_btn_x.blockSignals(boolean)
|
||||||
|
self.spinBox_btn_y.blockSignals(boolean)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Snipping Process
|
||||||
|
"""
|
||||||
|
|
||||||
|
def stop_snipping(self):
|
||||||
|
if self.is_snipping:
|
||||||
|
self.is_snipping = False
|
||||||
|
print("Snipping has been stopped")
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
def start_snipping(self):
|
||||||
|
if self.is_snipping:
|
||||||
|
print("Already processing")
|
||||||
|
return
|
||||||
|
print("Start snipping")
|
||||||
|
self.is_snipping = True
|
||||||
|
self.current_page = 0
|
||||||
|
self.capture_next_page()
|
||||||
|
|
||||||
|
def capture_next_page(self):
|
||||||
|
if self.current_page >= self.params["pages"]:
|
||||||
|
self.current_page = 0
|
||||||
|
self.is_snipping = False
|
||||||
|
print("Snipping complete")
|
||||||
|
result = QMessageBox.question(
|
||||||
|
self,
|
||||||
|
"캡쳐 완료",
|
||||||
|
f"{self.params['pages']} 페이지 캡쳐가 완료되었습니다. PDF로 변환 하시겠습니까?",
|
||||||
|
QMessageBox.Yes | QMessageBox.No,
|
||||||
|
QMessageBox.No, # 기본 선택 버튼
|
||||||
|
)
|
||||||
|
|
||||||
|
if result == QMessageBox.Yes:
|
||||||
|
self.images_to_pdf()
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.is_snipping:
|
||||||
|
QMessageBox.information(
|
||||||
|
self,
|
||||||
|
"캡쳐 중단",
|
||||||
|
f"캡쳐가 중단되었습니다. [{self.current_page}/{self.params['pages']}]",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# capture
|
||||||
|
self.capture()
|
||||||
|
|
||||||
|
# click next button
|
||||||
|
pyautogui.click(x=self.coords["btn_x"], y=self.coords["btn_y"])
|
||||||
|
|
||||||
|
# next page process
|
||||||
|
self.current_page += 1
|
||||||
|
QtCore.QTimer.singleShot(self.params["delay"] * 1000, self.capture_next_page)
|
||||||
|
|
||||||
|
def capture(self):
|
||||||
|
with mss.mss() as sct:
|
||||||
|
monitor = {
|
||||||
|
"left": self.coords["x"],
|
||||||
|
"top": self.coords["y"],
|
||||||
|
"width": self.coords["w"],
|
||||||
|
"height": self.coords["h"],
|
||||||
|
}
|
||||||
|
|
||||||
|
screenshot = sct.grab(monitor)
|
||||||
|
img = np.array(screenshot)
|
||||||
|
img = cv2.cvtColor(img, cv2.COLOR_BGRA2BGR) # BGRA → BGR 변환
|
||||||
|
|
||||||
|
# ✅ 이미지 저장
|
||||||
|
if not os.path.exists(self.capture_dir):
|
||||||
|
os.makedirs(self.capture_dir)
|
||||||
|
cv2.imwrite(f"{self.capture_dir}/{self.current_page}.png", img)
|
||||||
|
print(f"> Page {self.current_page + 1} has been captured")
|
||||||
|
|
||||||
|
def images_to_pdf(self):
|
||||||
|
image_files = [
|
||||||
|
f
|
||||||
|
for f in os.listdir(self.capture_dir)
|
||||||
|
if f.lower().endswith((".png", ".jpg", ".jpeg"))
|
||||||
|
]
|
||||||
|
image_files.sort()
|
||||||
|
|
||||||
|
if not image_files:
|
||||||
|
QMessageBox.warning(
|
||||||
|
self,
|
||||||
|
"이미지 없음",
|
||||||
|
f"변환할 이미지가 없습니다.",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
first_image = Image.open(
|
||||||
|
os.path.join(self.capture_dir, image_files[0])
|
||||||
|
).convert("RGB")
|
||||||
|
|
||||||
|
image_list = []
|
||||||
|
for file in image_files[1:]:
|
||||||
|
img = Image.open(os.path.join(self.capture_dir, file)).convert("RGB")
|
||||||
|
image_list.append(img)
|
||||||
|
|
||||||
|
first_image.save("./output.pdf", save_all=True, append_images=image_list)
|
||||||
|
QMessageBox.information(
|
||||||
|
self,
|
||||||
|
"PDF 변환 완료",
|
||||||
|
f"PDF 변환이 완료되었습니다.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
main_window = EbookSnipper()
|
||||||
|
main_window.show()
|
||||||
|
sys.exit(app.exec_())
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
program_name = 'Ebook Snipper'
|
||||||
|
icon_path = os.path.abspath('./assets/icon.ico')
|
||||||
|
|
||||||
|
a = Analysis(
|
||||||
|
['main.py'],
|
||||||
|
pathex=['.'],
|
||||||
|
binaries=[],
|
||||||
|
datas=[
|
||||||
|
('ui/mainWindow.ui', 'ui'),
|
||||||
|
('assets/icon.ico', 'assets'),
|
||||||
|
],
|
||||||
|
hiddenimports=[],
|
||||||
|
hookspath=[],
|
||||||
|
hooksconfig={},
|
||||||
|
runtime_hooks=[],
|
||||||
|
excludes=[],
|
||||||
|
noarchive=False,
|
||||||
|
optimize=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
pyz = PYZ(a.pure)
|
||||||
|
|
||||||
|
exe = EXE(
|
||||||
|
pyz,
|
||||||
|
a.scripts,
|
||||||
|
a.binaries,
|
||||||
|
a.datas,
|
||||||
|
[],
|
||||||
|
name=program_name,
|
||||||
|
debug=True,
|
||||||
|
bootloader_ignore_signals=False,
|
||||||
|
strip=False,
|
||||||
|
upx=True,
|
||||||
|
upx_exclude=[],
|
||||||
|
runtime_tmpdir=None,
|
||||||
|
console=False,
|
||||||
|
disable_windowed_traceback=False,
|
||||||
|
argv_emulation=False,
|
||||||
|
target_arch=None,
|
||||||
|
codesign_identity=None,
|
||||||
|
entitlements_file=None,
|
||||||
|
icon=icon_path,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,438 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>MainWindow</class>
|
||||||
|
<widget class="QMainWindow" name="MainWindow">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>340</width>
|
||||||
|
<height>436</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>340</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Ebook Snipping Tool</string>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="centralwidget">
|
||||||
|
<layout class="QGridLayout" name="gridLayout_8">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QFrame" name="frame">
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::StyledPanel</enum>
|
||||||
|
</property>
|
||||||
|
<property name="frameShadow">
|
||||||
|
<enum>QFrame::Raised</enum>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_2">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QGroupBox" name="groupBox_1st">
|
||||||
|
<property name="title">
|
||||||
|
<string>1. 영역 지정</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_4">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="spacing">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QPushButton" name="btn_coords">
|
||||||
|
<property name="text">
|
||||||
|
<string>캡쳐 영역 설정 (취소: esc)</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QGroupBox" name="groupBox_2nd">
|
||||||
|
<property name="title">
|
||||||
|
<string>2. 페이지 넘김 버튼 위치 지정</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_7">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="spacing">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QPushButton" name="btn_nextbtn">
|
||||||
|
<property name="text">
|
||||||
|
<string>버튼 위치 설정</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QGroupBox" name="groupBox_3rd">
|
||||||
|
<property name="title">
|
||||||
|
<string>3. 파라미터 지정</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_10">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="spacing">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QGroupBox" name="groupBox_3rd_1">
|
||||||
|
<property name="title">
|
||||||
|
<string>캡쳐 영역</string>
|
||||||
|
</property>
|
||||||
|
<property name="flat">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_9">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="spacing">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<layout class="QGridLayout" name="gridLayout_3">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_x">
|
||||||
|
<property name="text">
|
||||||
|
<string>X:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="spinBox_x">
|
||||||
|
<property name="maximum">
|
||||||
|
<number>20000</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_y">
|
||||||
|
<property name="text">
|
||||||
|
<string>Y:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="spinBox_y">
|
||||||
|
<property name="maximum">
|
||||||
|
<number>20000</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_w">
|
||||||
|
<property name="text">
|
||||||
|
<string>Width:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="spinBox_w">
|
||||||
|
<property name="maximum">
|
||||||
|
<number>20000</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_h">
|
||||||
|
<property name="text">
|
||||||
|
<string>Height:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="spinBox_h">
|
||||||
|
<property name="maximum">
|
||||||
|
<number>20000</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QGroupBox" name="groupBox_3rd_2">
|
||||||
|
<property name="title">
|
||||||
|
<string>버튼 좌표</string>
|
||||||
|
</property>
|
||||||
|
<property name="flat">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_12">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="spacing">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<layout class="QGridLayout" name="gridLayout_11">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_btn_x">
|
||||||
|
<property name="text">
|
||||||
|
<string>X:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="spinBox_btn_x">
|
||||||
|
<property name="maximum">
|
||||||
|
<number>20000</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_8">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_btn_y">
|
||||||
|
<property name="text">
|
||||||
|
<string>Y:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="spinBox_btn_y">
|
||||||
|
<property name="maximum">
|
||||||
|
<number>20000</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QGroupBox" name="groupBox_3rd_3">
|
||||||
|
<property name="title">
|
||||||
|
<string>조건</string>
|
||||||
|
</property>
|
||||||
|
<property name="flat">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_6">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="spacing">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_delay">
|
||||||
|
<property name="text">
|
||||||
|
<string>딜레이 시간(sec)</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="spinBox_delay">
|
||||||
|
<property name="maximum">
|
||||||
|
<number>20000</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>2</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_page">
|
||||||
|
<property name="text">
|
||||||
|
<string>페이지 수</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="spinBox_pages">
|
||||||
|
<property name="maximum">
|
||||||
|
<number>20000</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QGroupBox" name="groupBox_4th">
|
||||||
|
<property name="title">
|
||||||
|
<string>4. 캡쳐</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="spacing">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<layout class="QGridLayout" name="gridLayout_5">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QPushButton" name="btn_start">
|
||||||
|
<property name="text">
|
||||||
|
<string>시작 (alt + F10)</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QPushButton" name="btn_stop">
|
||||||
|
<property name="text">
|
||||||
|
<string>중지 (alt + F11)</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QMenuBar" name="menubar">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>340</width>
|
||||||
|
<height>21</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="nativeMenuBar">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<tabstops>
|
||||||
|
<tabstop>spinBox_x</tabstop>
|
||||||
|
<tabstop>spinBox_y</tabstop>
|
||||||
|
<tabstop>spinBox_w</tabstop>
|
||||||
|
<tabstop>spinBox_h</tabstop>
|
||||||
|
<tabstop>spinBox_delay</tabstop>
|
||||||
|
<tabstop>spinBox_pages</tabstop>
|
||||||
|
<tabstop>btn_start</tabstop>
|
||||||
|
<tabstop>btn_stop</tabstop>
|
||||||
|
</tabstops>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,70 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
def resource_path(relative_path): # for Pyinstaller
|
||||||
|
if hasattr(sys, '_MEIPASS'):
|
||||||
|
return os.path.join(sys._MEIPASS, relative_path)
|
||||||
|
return os.path.join(os.path.abspath("."), relative_path)
|
||||||
|
|
||||||
|
def terminate_process(process_name):
|
||||||
|
try:
|
||||||
|
print(f"[INFO] Attempting to terminate process: {process_name}")
|
||||||
|
subprocess.run(f"taskkill /F /IM {process_name}", shell=True, check=True)
|
||||||
|
print(f"[INFO] Successfully terminated process: {process_name}")
|
||||||
|
return 0
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
if "128" in str(e): print(f"[WARN] Process '{process_name}' not found or already terminated.")
|
||||||
|
else: print(f"[ERROR] Failed to terminate process '{process_name}'. Error: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ERROR] Unexpected error while terminating '{process_name}'. Error: {e}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def start_process(process_name):
|
||||||
|
try:
|
||||||
|
print(f"[INFO] Attempting to start process: {process_name}")
|
||||||
|
subprocess.run(f"start {process_name}", shell=True, check=True)
|
||||||
|
print(f"[INFO] Successfully started process: {process_name}")
|
||||||
|
return 0
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"[ERROR] Process '{process_name}' not found. Please check the path or name.")
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"[ERROR] Failed to start process '{process_name}'. Error: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ERROR] Unexpected error while starting '{process_name}'. Error: {e}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
"""
|
||||||
|
Pyinstaller splash screen related
|
||||||
|
"""
|
||||||
|
def check_alpha(img):
|
||||||
|
"""Check if the image contains translucent pixels."""
|
||||||
|
alpha_channel = img.getchannel('A')
|
||||||
|
alpha_data = alpha_channel.load()
|
||||||
|
for y in range(img.height):
|
||||||
|
for x in range(img.width):
|
||||||
|
alpha_value = alpha_data[x, y]
|
||||||
|
if 0 < alpha_value < 255: # Check if translucent
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def processing_image(img_path):
|
||||||
|
img = Image.open(img_path).convert("RGBA") # Ensure image is in RGBA format
|
||||||
|
print("Contain translucency pixels (Before):", check_alpha(img))
|
||||||
|
|
||||||
|
# Remove translucency: Set all non-opaque pixels (alpha != 255) to transparent (alpha = 0)
|
||||||
|
pixels = img.load()
|
||||||
|
for y in range(img.height):
|
||||||
|
for x in range(img.width):
|
||||||
|
r, g, b, a = pixels[x, y]
|
||||||
|
if a != 255: # If alpha is not fully opaque
|
||||||
|
pixels[x, y] = (r, g, b, 0) # Make pixel fully transparent
|
||||||
|
|
||||||
|
print("Contain translucency pixels (After):", check_alpha(img))
|
||||||
|
|
||||||
|
# Save the modified image
|
||||||
|
output_path = img_path.replace(".png", "_transparent.png")
|
||||||
|
img.save(output_path, "PNG")
|
||||||
|
print(f"Processed image saved to {output_path}")
|
||||||
|
return output_path
|
||||||
|
|
@ -0,0 +1,126 @@
|
||||||
|
import sys
|
||||||
|
from utils.utils import resource_path
|
||||||
|
from PyQt5.QtWidgets import QApplication, QWidget, QRubberBand, QMessageBox
|
||||||
|
from PyQt5.QtCore import Qt, QRect, QPoint, QSize
|
||||||
|
from PyQt5.QtGui import QGuiApplication, QCursor
|
||||||
|
|
||||||
|
|
||||||
|
class SnippingWidget(QWidget):
|
||||||
|
def __init__(self, mainwindow):
|
||||||
|
super().__init__()
|
||||||
|
self.setWindowTitle("Screen Snipper")
|
||||||
|
self.parent = mainwindow
|
||||||
|
self.setWindowIcon(self.parent.icon)
|
||||||
|
self.rectMode = True
|
||||||
|
|
||||||
|
# ✅ 최상단 유지 및 입력 포커스 확보
|
||||||
|
self.setWindowFlags(
|
||||||
|
Qt.FramelessWindowHint
|
||||||
|
| Qt.WindowStaysOnTopHint
|
||||||
|
| Qt.SplashScreen # ✅ 최상단 유지 및 입력 포커스 확보
|
||||||
|
)
|
||||||
|
|
||||||
|
self.setWindowOpacity(0.3) # ✅ 창을 불투명하게 유지
|
||||||
|
self.setAttribute(Qt.WA_NoSystemBackground, False) # ✅ 배경 활성화
|
||||||
|
self.clipboard = QApplication.clipboard()
|
||||||
|
|
||||||
|
# ✅ 전체 화면 설정
|
||||||
|
screen_geometry = QGuiApplication.primaryScreen().geometry()
|
||||||
|
self.setGeometry(screen_geometry)
|
||||||
|
self.activateWindow()
|
||||||
|
self.raise_()
|
||||||
|
|
||||||
|
# ✅ 드래그 초기화
|
||||||
|
self.origin = QPoint()
|
||||||
|
self.rubberBand = QRubberBand(QRubberBand.Rectangle, self)
|
||||||
|
|
||||||
|
def showEvent(self, event):
|
||||||
|
self.setCursor(Qt.CrossCursor) # ✅ 십자 모양 커서로 변경
|
||||||
|
super().showEvent(event)
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
self.unsetCursor()
|
||||||
|
super().closeEvent(event)
|
||||||
|
|
||||||
|
def mousePressEvent(self, event):
|
||||||
|
if event.button() == Qt.LeftButton:
|
||||||
|
if self.rectMode == True:
|
||||||
|
self.origin = event.pos()
|
||||||
|
self.rubberBand.setGeometry(QRect(self.origin, QSize()))
|
||||||
|
self.rubberBand.show()
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
def mouseMoveEvent(self, event):
|
||||||
|
if self.rectMode == True:
|
||||||
|
if not self.origin.isNull():
|
||||||
|
rect = QRect(self.origin, event.pos()).normalized()
|
||||||
|
self.rubberBand.setGeometry(rect)
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
def mouseReleaseEvent(self, event):
|
||||||
|
if event.button() == Qt.LeftButton:
|
||||||
|
|
||||||
|
""" Button Coordinate Setting Mode """
|
||||||
|
if self.rectMode == False:
|
||||||
|
pos = event.pos()
|
||||||
|
btn_x, btn_y = pos.x(), pos.y()
|
||||||
|
self.parent.coords["btn_x"] = btn_x
|
||||||
|
self.parent.coords["btn_y"] = btn_y
|
||||||
|
self.parent.update_btn_coord()
|
||||||
|
|
||||||
|
self.close_overlay()
|
||||||
|
|
||||||
|
QMessageBox.information(
|
||||||
|
self,
|
||||||
|
"버튼 좌표 지정 완료",
|
||||||
|
f"x: {btn_x}, y: {btn_y}",
|
||||||
|
)
|
||||||
|
|
||||||
|
""" Snipping Area Setting Mode """
|
||||||
|
if self.rectMode == True:
|
||||||
|
selected_rect = self.rubberBand.geometry()
|
||||||
|
|
||||||
|
self.rubberBand.hide()
|
||||||
|
QApplication.processEvents()
|
||||||
|
self.close_overlay()
|
||||||
|
|
||||||
|
# 멀티 모니터 지원
|
||||||
|
screen = QGuiApplication.screenAt(event.globalPos())
|
||||||
|
if not screen:
|
||||||
|
screen = QGuiApplication.primaryScreen()
|
||||||
|
|
||||||
|
# screenshot = screen.grabWindow(
|
||||||
|
# 0,
|
||||||
|
# selected_rect.x(),
|
||||||
|
# selected_rect.y(),
|
||||||
|
# selected_rect.width(),
|
||||||
|
# selected_rect.height(),
|
||||||
|
# )
|
||||||
|
|
||||||
|
# # ✅ 캡처 이미지 저장 및 클립보드 복사
|
||||||
|
# screenshot.save("screenshot.png", "PNG")
|
||||||
|
# self.clipboard.setPixmap(screenshot)
|
||||||
|
|
||||||
|
x, y, w, h = selected_rect.getRect()
|
||||||
|
self.parent.coords["x"] = x
|
||||||
|
self.parent.coords["y"] = y
|
||||||
|
self.parent.coords["w"] = w
|
||||||
|
self.parent.coords["h"] = h
|
||||||
|
|
||||||
|
self.parent.update_rect_coords()
|
||||||
|
|
||||||
|
QMessageBox.information(
|
||||||
|
self,
|
||||||
|
"영역 지정 완료",
|
||||||
|
f"x: {x}, y: {y}, width: {w}, height: {h}",
|
||||||
|
)
|
||||||
|
def close_overlay(self):
|
||||||
|
self.origin = QPoint()
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def keyPressEvent(self, event):
|
||||||
|
if event.key() == Qt.Key_Escape:
|
||||||
|
self.rubberBand.hide()
|
||||||
|
self.origin = QPoint()
|
||||||
|
self.close()
|
||||||
|
event.accept()
|
||||||
Loading…
Reference in New Issue