Compare commits
No commits in common. "master" and "main" have entirely different histories.
|
|
@ -1,4 +0,0 @@
|
||||||
output.pdf
|
|
||||||
captures/
|
|
||||||
dist/
|
|
||||||
build/
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
3.11.9
|
|
||||||
26
README.md
26
README.md
|
|
@ -1,21 +1,23 @@
|
||||||
# Ebook Snipping Tool
|
# Template
|
||||||
This project is a code for Ebook snipping and PDF converting
|
This project is a code for ~
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
- Python 3.11.9
|
- foo
|
||||||
- pip
|
- bar
|
||||||
- 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
|
||||||
|
```
|
||||||
|
|
||||||
## Warn
|
## Usage
|
||||||
- Keep `optimize = 0` in the `.spec` file to avoid conflicts with OpenCV when using PyInstaller.
|
To run the script, execute the following command:
|
||||||
- Setting `optimize = 2` may cause runtime errors related to `numpy` and `opencv-python` due to compatibility issues with compiled C extensions.
|
```batch
|
||||||
- This ensures better stability, especially when working with image processing libraries like OpenCV and NumPy.
|
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.
BIN
assets/icon.ico
BIN
assets/icon.ico
Binary file not shown.
|
Before Width: | Height: | Size: 19 KiB |
BIN
assets/icon.png
BIN
assets/icon.png
Binary file not shown.
|
Before Width: | Height: | Size: 29 KiB |
260
main.py
260
main.py
|
|
@ -1,260 +0,0 @@
|
||||||
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_())
|
|
||||||
47
main.spec
47
main.spec
|
|
@ -1,47 +0,0 @@
|
||||||
# -*- 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,
|
|
||||||
)
|
|
||||||
438
ui/mainwindow.ui
438
ui/mainwindow.ui
|
|
@ -1,438 +0,0 @@
|
||||||
<?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.
|
|
@ -1,70 +0,0 @@
|
||||||
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
|
|
||||||
126
widget.py
126
widget.py
|
|
@ -1,126 +0,0 @@
|
||||||
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