Compare commits
2 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
1bc79f2515 | |
|
|
b6a578468d |
|
|
@ -0,0 +1,4 @@
|
|||
output.pdf
|
||||
captures/
|
||||
dist/
|
||||
build/
|
||||
|
|
@ -0,0 +1 @@
|
|||
3.11.9
|
||||
26
README.md
26
README.md
|
|
@ -1,23 +1,21 @@
|
|||
# Template
|
||||
This project is a code for ~
|
||||
# Ebook Snipping Tool
|
||||
This project is a code for Ebook snipping and PDF converting
|
||||
|
||||
## Prerequisites
|
||||
- foo
|
||||
- bar
|
||||
- ...
|
||||
- Python 3.11.9
|
||||
- pip
|
||||
- mss
|
||||
- pyautogui
|
||||
- opencv-python
|
||||
- pyqt5
|
||||
|
||||
## Installation
|
||||
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
|
||||
```
|
||||
## Warn
|
||||
- Keep `optimize = 0` in the `.spec` file to avoid conflicts with OpenCV when using PyInstaller.
|
||||
- Setting `optimize = 2` may cause runtime errors related to `numpy` and `opencv-python` due to compatibility issues with compiled C extensions.
|
||||
- This ensures better stability, especially when working with image processing libraries like OpenCV and NumPy.
|
||||
|
||||
## Contributing
|
||||
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