thumbnail-maker 0.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- thumbnail_maker/__init__.py +9 -0
- thumbnail_maker/__main__.py +112 -0
- thumbnail_maker/cli.py +140 -0
- thumbnail_maker/gui/__init__.py +10 -0
- thumbnail_maker/gui/dsl_manager.py +351 -0
- thumbnail_maker/gui/font_utils.py +39 -0
- thumbnail_maker/gui/handlers.py +299 -0
- thumbnail_maker/gui/main_window.py +177 -0
- thumbnail_maker/gui/preview_thread.py +28 -0
- thumbnail_maker/gui/widgets.py +386 -0
- thumbnail_maker/renderer.py +597 -0
- thumbnail_maker/upload.py +279 -0
- thumbnail_maker-0.1.6.dist-info/METADATA +159 -0
- thumbnail_maker-0.1.6.dist-info/RECORD +16 -0
- thumbnail_maker-0.1.6.dist-info/WHEEL +4 -0
- thumbnail_maker-0.1.6.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
이벤트 핸들러 모듈
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import json
|
|
9
|
+
import tempfile
|
|
10
|
+
import base64
|
|
11
|
+
from PySide6.QtWidgets import (QColorDialog, QFileDialog, QMessageBox,
|
|
12
|
+
QDialog, QVBoxLayout, QPlainTextEdit, QDialogButtonBox)
|
|
13
|
+
from PySide6.QtGui import QColor, QPixmap
|
|
14
|
+
from PySide6.QtCore import Qt
|
|
15
|
+
|
|
16
|
+
from ..renderer import ThumbnailRenderer
|
|
17
|
+
from .font_utils import infer_font_name_from_file
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class EventHandlers:
|
|
21
|
+
"""이벤트 핸들러 클래스"""
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def on_resolution_mode_changed(gui, mode):
|
|
25
|
+
"""해상도 모드 변경 시 처리"""
|
|
26
|
+
if mode == 'preset':
|
|
27
|
+
gui.aspect_ratio.setEnabled(True)
|
|
28
|
+
elif mode == 'fixedRatio':
|
|
29
|
+
gui.aspect_ratio.setEnabled(True)
|
|
30
|
+
# fixedRatio 모드로 변경 시 현재 너비를 기준으로 높이 재계산
|
|
31
|
+
EventHandlers.on_aspect_ratio_changed(gui, gui.aspect_ratio.currentText())
|
|
32
|
+
else: # custom
|
|
33
|
+
gui.aspect_ratio.setEnabled(False)
|
|
34
|
+
gui.update_preview()
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def on_aspect_ratio_changed(gui, ratio):
|
|
38
|
+
"""비율 변경 시 처리"""
|
|
39
|
+
# fixedRatio 모드일 때만 너비를 기준으로 높이 재계산
|
|
40
|
+
if gui.res_mode.currentText() == 'fixedRatio':
|
|
41
|
+
try:
|
|
42
|
+
parts = ratio.split(':')
|
|
43
|
+
if len(parts) == 2:
|
|
44
|
+
rw, rh = float(parts[0]), float(parts[1])
|
|
45
|
+
if rw > 0 and rh > 0:
|
|
46
|
+
aspect_ratio = rw / rh
|
|
47
|
+
current_width = gui.width_spin.value()
|
|
48
|
+
new_height = int(current_width / aspect_ratio)
|
|
49
|
+
if not gui._updating_dimension:
|
|
50
|
+
gui._updating_dimension = True
|
|
51
|
+
gui.height_spin.setValue(new_height)
|
|
52
|
+
gui._updating_dimension = False
|
|
53
|
+
except (ValueError, ZeroDivisionError):
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
gui.update_preview()
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def on_width_changed(gui, value):
|
|
60
|
+
"""너비 변경 시 처리"""
|
|
61
|
+
if gui._updating_dimension:
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
# fixedRatio 모드일 때 비율에 맞게 높이 조정
|
|
65
|
+
if gui.res_mode.currentText() == 'fixedRatio':
|
|
66
|
+
ratio_str = gui.aspect_ratio.currentText()
|
|
67
|
+
try:
|
|
68
|
+
parts = ratio_str.split(':')
|
|
69
|
+
if len(parts) == 2:
|
|
70
|
+
rw, rh = float(parts[0]), float(parts[1])
|
|
71
|
+
if rw > 0 and rh > 0:
|
|
72
|
+
aspect_ratio = rw / rh
|
|
73
|
+
new_height = int(value / aspect_ratio)
|
|
74
|
+
gui._updating_dimension = True
|
|
75
|
+
gui.height_spin.setValue(new_height)
|
|
76
|
+
gui._updating_dimension = False
|
|
77
|
+
except (ValueError, ZeroDivisionError):
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
gui.update_preview()
|
|
81
|
+
|
|
82
|
+
@staticmethod
|
|
83
|
+
def on_height_changed(gui, value):
|
|
84
|
+
"""높이 변경 시 처리"""
|
|
85
|
+
if gui._updating_dimension:
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
# fixedRatio 모드일 때 비율에 맞게 너비 조정
|
|
89
|
+
if gui.res_mode.currentText() == 'fixedRatio':
|
|
90
|
+
ratio_str = gui.aspect_ratio.currentText()
|
|
91
|
+
try:
|
|
92
|
+
parts = ratio_str.split(':')
|
|
93
|
+
if len(parts) == 2:
|
|
94
|
+
rw, rh = float(parts[0]), float(parts[1])
|
|
95
|
+
if rw > 0 and rh > 0:
|
|
96
|
+
aspect_ratio = rw / rh
|
|
97
|
+
new_width = int(value * aspect_ratio)
|
|
98
|
+
gui._updating_dimension = True
|
|
99
|
+
gui.width_spin.setValue(new_width)
|
|
100
|
+
gui._updating_dimension = False
|
|
101
|
+
except (ValueError, ZeroDivisionError):
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
gui.update_preview()
|
|
105
|
+
|
|
106
|
+
@staticmethod
|
|
107
|
+
def select_bg_color(gui):
|
|
108
|
+
"""배경 색상 선택"""
|
|
109
|
+
color = QColorDialog.getColor(QColor(gui.bg_color))
|
|
110
|
+
if color.isValid():
|
|
111
|
+
gui.bg_color = color.name()
|
|
112
|
+
gui.update_preview()
|
|
113
|
+
|
|
114
|
+
@staticmethod
|
|
115
|
+
def select_title_color(gui):
|
|
116
|
+
"""제목 색상 선택"""
|
|
117
|
+
color = QColorDialog.getColor(QColor(gui.title_color))
|
|
118
|
+
if color.isValid():
|
|
119
|
+
gui.title_color = color.name()
|
|
120
|
+
gui.update_preview()
|
|
121
|
+
|
|
122
|
+
@staticmethod
|
|
123
|
+
def select_subtitle_color(gui):
|
|
124
|
+
"""부제목 색상 선택"""
|
|
125
|
+
color = QColorDialog.getColor(QColor(gui.subtitle_color))
|
|
126
|
+
if color.isValid():
|
|
127
|
+
gui.subtitle_color = color.name()
|
|
128
|
+
gui.update_preview()
|
|
129
|
+
|
|
130
|
+
@staticmethod
|
|
131
|
+
def select_background_image(gui):
|
|
132
|
+
"""배경 이미지 선택"""
|
|
133
|
+
file_path, _ = QFileDialog.getOpenFileName(
|
|
134
|
+
gui, '배경 이미지 선택', '', 'Images (*.png *.jpg *.jpeg *.gif *.bmp)'
|
|
135
|
+
)
|
|
136
|
+
if file_path:
|
|
137
|
+
gui.bg_image_path.setText(file_path)
|
|
138
|
+
gui.update_preview()
|
|
139
|
+
|
|
140
|
+
@staticmethod
|
|
141
|
+
def set_title_font_name_from_path(gui, path: str):
|
|
142
|
+
"""제목 폰트 이름을 파일 경로에서 추출하여 설정"""
|
|
143
|
+
inferred = infer_font_name_from_file(path)
|
|
144
|
+
if inferred:
|
|
145
|
+
gui.title_font_name.setText(inferred)
|
|
146
|
+
gui.update_preview()
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def set_subtitle_font_name_from_path(gui, path: str):
|
|
150
|
+
"""부제목 폰트 이름을 파일 경로에서 추출하여 설정"""
|
|
151
|
+
inferred = infer_font_name_from_file(path)
|
|
152
|
+
if inferred:
|
|
153
|
+
gui.subtitle_font_name.setText(inferred)
|
|
154
|
+
gui.update_preview()
|
|
155
|
+
|
|
156
|
+
@staticmethod
|
|
157
|
+
def on_title_font_file_changed(gui):
|
|
158
|
+
"""제목 폰트 파일 경로 변경 시 처리"""
|
|
159
|
+
path = gui.title_font_file.text().strip()
|
|
160
|
+
if path and not gui.title_font_name.text().strip():
|
|
161
|
+
EventHandlers.set_title_font_name_from_path(gui, path)
|
|
162
|
+
else:
|
|
163
|
+
gui.update_preview()
|
|
164
|
+
|
|
165
|
+
@staticmethod
|
|
166
|
+
def on_subtitle_font_file_changed(gui):
|
|
167
|
+
"""부제목 폰트 파일 경로 변경 시 처리"""
|
|
168
|
+
path = gui.subtitle_font_file.text().strip()
|
|
169
|
+
if path and not gui.subtitle_font_name.text().strip():
|
|
170
|
+
EventHandlers.set_subtitle_font_name_from_path(gui, path)
|
|
171
|
+
else:
|
|
172
|
+
gui.update_preview()
|
|
173
|
+
|
|
174
|
+
@staticmethod
|
|
175
|
+
def generate_preview(gui):
|
|
176
|
+
"""미리보기 생성"""
|
|
177
|
+
if not hasattr(gui, 'current_dsl'):
|
|
178
|
+
gui.update_preview()
|
|
179
|
+
|
|
180
|
+
gui.preview_btn.setEnabled(False)
|
|
181
|
+
gui.preview_btn.setText('생성 중...')
|
|
182
|
+
|
|
183
|
+
# 스레드에서 생성
|
|
184
|
+
from .preview_thread import PreviewThread
|
|
185
|
+
gui.preview_thread = PreviewThread(gui.current_dsl)
|
|
186
|
+
gui.preview_thread.preview_ready.connect(lambda path: EventHandlers.on_preview_ready(gui, path))
|
|
187
|
+
gui.preview_thread.start()
|
|
188
|
+
|
|
189
|
+
@staticmethod
|
|
190
|
+
def on_preview_ready(gui, file_path):
|
|
191
|
+
"""미리보기 준비됨"""
|
|
192
|
+
if file_path and os.path.exists(file_path):
|
|
193
|
+
pixmap = QPixmap(file_path)
|
|
194
|
+
gui.preview_label.setPixmap(pixmap.scaled(
|
|
195
|
+
480, 270, Qt.KeepAspectRatio, Qt.SmoothTransformation
|
|
196
|
+
))
|
|
197
|
+
else:
|
|
198
|
+
msg = gui.preview_thread.error_message or '미리보기 생성 중 오류가 발생했습니다.'
|
|
199
|
+
QMessageBox.critical(gui, '에러', msg)
|
|
200
|
+
|
|
201
|
+
gui.preview_btn.setEnabled(True)
|
|
202
|
+
gui.preview_btn.setText('미리보기 생성')
|
|
203
|
+
|
|
204
|
+
@staticmethod
|
|
205
|
+
def save_thumbnail(gui):
|
|
206
|
+
"""썸네일 저장"""
|
|
207
|
+
if not hasattr(gui, 'current_dsl'):
|
|
208
|
+
QMessageBox.warning(gui, '경고', '먼저 미리보기를 생성해주세요.')
|
|
209
|
+
return
|
|
210
|
+
|
|
211
|
+
file_path, _ = QFileDialog.getSaveFileName(
|
|
212
|
+
gui, '썸네일 저장', 'thumbnail.png', 'Images (*.png)'
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
if file_path:
|
|
216
|
+
try:
|
|
217
|
+
ThumbnailRenderer.render_thumbnail(gui.current_dsl, file_path)
|
|
218
|
+
QMessageBox.information(gui, '완료', f'저장 완료: {file_path}')
|
|
219
|
+
except Exception as e:
|
|
220
|
+
QMessageBox.critical(gui, '에러', f'저장 실패: {e}')
|
|
221
|
+
|
|
222
|
+
@staticmethod
|
|
223
|
+
def show_dsl_dialog(gui):
|
|
224
|
+
"""현재 DSL을 JSON으로 출력하는 다이얼로그"""
|
|
225
|
+
if not hasattr(gui, 'current_dsl'):
|
|
226
|
+
gui.update_preview()
|
|
227
|
+
from .dsl_manager import DSLManager
|
|
228
|
+
dsl = getattr(gui, 'current_dsl', DSLManager.generate_dsl(gui))
|
|
229
|
+
try:
|
|
230
|
+
text = json.dumps(dsl, ensure_ascii=False, indent=2)
|
|
231
|
+
except Exception:
|
|
232
|
+
text = str(dsl)
|
|
233
|
+
|
|
234
|
+
dlg = QDialog(gui)
|
|
235
|
+
dlg.setWindowTitle('현재 DSL 보기')
|
|
236
|
+
v = QVBoxLayout(dlg)
|
|
237
|
+
editor = QPlainTextEdit()
|
|
238
|
+
editor.setPlainText(text)
|
|
239
|
+
editor.setReadOnly(True)
|
|
240
|
+
v.addWidget(editor)
|
|
241
|
+
btns = QDialogButtonBox(QDialogButtonBox.Close)
|
|
242
|
+
btns.rejected.connect(dlg.reject)
|
|
243
|
+
btns.accepted.connect(dlg.accept)
|
|
244
|
+
v.addWidget(btns)
|
|
245
|
+
dlg.resize(700, 500)
|
|
246
|
+
dlg.exec()
|
|
247
|
+
|
|
248
|
+
@staticmethod
|
|
249
|
+
def save_dsl(gui):
|
|
250
|
+
"""현재 DSL을 JSON 파일로 저장"""
|
|
251
|
+
if not hasattr(gui, 'current_dsl'):
|
|
252
|
+
gui.update_preview()
|
|
253
|
+
from .dsl_manager import DSLManager
|
|
254
|
+
dsl = getattr(gui, 'current_dsl', DSLManager.generate_dsl(gui))
|
|
255
|
+
|
|
256
|
+
file_path, _ = QFileDialog.getSaveFileName(
|
|
257
|
+
gui, 'DSL 저장', 'thumbnail.json', 'JSON (*.json)'
|
|
258
|
+
)
|
|
259
|
+
if not file_path:
|
|
260
|
+
return
|
|
261
|
+
try:
|
|
262
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
263
|
+
json.dump(dsl, f, ensure_ascii=False, indent=2)
|
|
264
|
+
QMessageBox.information(gui, '완료', f'DSL 저장 완료: {file_path}')
|
|
265
|
+
except Exception as e:
|
|
266
|
+
QMessageBox.critical(gui, '에러', f'DSL 저장 실패: {e}')
|
|
267
|
+
|
|
268
|
+
@staticmethod
|
|
269
|
+
def load_thl_package(gui):
|
|
270
|
+
""".thl 패키지를 로드하여 GUI에 적용"""
|
|
271
|
+
file_path, _ = QFileDialog.getOpenFileName(
|
|
272
|
+
gui, '패키지 로드', '', 'Thumbnail Package (*.thl)'
|
|
273
|
+
)
|
|
274
|
+
if not file_path:
|
|
275
|
+
return
|
|
276
|
+
|
|
277
|
+
try:
|
|
278
|
+
from .dsl_manager import DSLManager
|
|
279
|
+
dsl = DSLManager.load_thl_package(gui, file_path)
|
|
280
|
+
DSLManager.load_dsl_to_gui(gui, dsl)
|
|
281
|
+
QMessageBox.information(gui, '완료', f'패키지 로드 완료: {file_path}')
|
|
282
|
+
except Exception as e:
|
|
283
|
+
QMessageBox.critical(gui, '에러', f'패키지 로드 실패: {e}')
|
|
284
|
+
|
|
285
|
+
@staticmethod
|
|
286
|
+
def save_thl_package(gui):
|
|
287
|
+
"""현재 DSL과 사용 폰트를 묶어 .thl 패키지로 저장"""
|
|
288
|
+
file_path, _ = QFileDialog.getSaveFileName(
|
|
289
|
+
gui, '패키지 저장', 'thumbnail.thl', 'Thumbnail Package (*.thl)'
|
|
290
|
+
)
|
|
291
|
+
if not file_path:
|
|
292
|
+
return
|
|
293
|
+
try:
|
|
294
|
+
from .dsl_manager import DSLManager
|
|
295
|
+
DSLManager.save_thl_package(gui, file_path)
|
|
296
|
+
QMessageBox.information(gui, '완료', f'패키지 저장 완료: {file_path}')
|
|
297
|
+
except Exception as e:
|
|
298
|
+
QMessageBox.critical(gui, '에러', f'패키지 저장 실패: {e}')
|
|
299
|
+
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
메인 GUI 윈도우 클래스
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QTabWidget
|
|
9
|
+
|
|
10
|
+
from .widgets import WidgetFactory
|
|
11
|
+
from .handlers import EventHandlers
|
|
12
|
+
from .dsl_manager import DSLManager
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ThumbnailGUI(QMainWindow):
|
|
16
|
+
"""메인 GUI 클래스"""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
super().__init__()
|
|
20
|
+
self.setWindowTitle('썸네일 생성기')
|
|
21
|
+
self.setGeometry(100, 100, 1200, 800)
|
|
22
|
+
|
|
23
|
+
# 메인 위젯
|
|
24
|
+
main_widget = QWidget()
|
|
25
|
+
self.setCentralWidget(main_widget)
|
|
26
|
+
|
|
27
|
+
# 메인 레이아웃
|
|
28
|
+
main_layout = QHBoxLayout(main_widget)
|
|
29
|
+
|
|
30
|
+
# 왼쪽: 미리보기
|
|
31
|
+
preview_widget = WidgetFactory.create_preview_widget(self)
|
|
32
|
+
main_layout.addWidget(preview_widget, 2)
|
|
33
|
+
|
|
34
|
+
# 오른쪽: 설정 패널
|
|
35
|
+
settings_widget = self.create_settings_widget()
|
|
36
|
+
main_layout.addWidget(settings_widget, 1)
|
|
37
|
+
|
|
38
|
+
# 기본값 초기화
|
|
39
|
+
self.init_default_values()
|
|
40
|
+
|
|
41
|
+
def create_settings_widget(self):
|
|
42
|
+
"""설정 위젯 생성"""
|
|
43
|
+
scroll = QWidget()
|
|
44
|
+
layout = QVBoxLayout()
|
|
45
|
+
|
|
46
|
+
# 탭 위젯
|
|
47
|
+
tabs = QTabWidget()
|
|
48
|
+
|
|
49
|
+
# 해상도 탭
|
|
50
|
+
res_tab = WidgetFactory.create_resolution_tab(self)
|
|
51
|
+
tabs.addTab(res_tab, '해상도')
|
|
52
|
+
|
|
53
|
+
# 배경 탭
|
|
54
|
+
bg_tab = WidgetFactory.create_background_tab(self)
|
|
55
|
+
tabs.addTab(bg_tab, '배경')
|
|
56
|
+
|
|
57
|
+
# 제목 탭
|
|
58
|
+
title_tab = WidgetFactory.create_title_tab(self)
|
|
59
|
+
tabs.addTab(title_tab, '제목')
|
|
60
|
+
|
|
61
|
+
# 부제목 탭
|
|
62
|
+
subtitle_tab = WidgetFactory.create_subtitle_tab(self)
|
|
63
|
+
tabs.addTab(subtitle_tab, '부제목')
|
|
64
|
+
|
|
65
|
+
layout.addWidget(tabs)
|
|
66
|
+
scroll.setLayout(layout)
|
|
67
|
+
|
|
68
|
+
return scroll
|
|
69
|
+
|
|
70
|
+
def init_default_values(self):
|
|
71
|
+
"""기본값 초기화"""
|
|
72
|
+
self.title_text.setPlainText('10초만에\n썸네일 만드는 법')
|
|
73
|
+
self.subtitle_text.setPlainText('쉽고 빠르게 썸네일을 만드는 법\n= 퀵썸네일 쓰기')
|
|
74
|
+
# 기본 폰트 값 (노느늘 SBAggroB)
|
|
75
|
+
self.title_font_name.setText('SBAggroB')
|
|
76
|
+
self.title_font_url.setText('https://fastly.jsdelivr.net/gh/projectnoonnu/noonfonts_2108@1.1/SBAggroB.woff')
|
|
77
|
+
self.title_font_weight.setCurrentText('bold')
|
|
78
|
+
self.title_font_style.setCurrentText('normal')
|
|
79
|
+
|
|
80
|
+
self.subtitle_font_name.setText('SBAggroB')
|
|
81
|
+
self.subtitle_font_url.setText('https://fastly.jsdelivr.net/gh/projectnoonnu/noonfonts_2108@1.1/SBAggroB.woff')
|
|
82
|
+
self.subtitle_font_weight.setCurrentText('normal')
|
|
83
|
+
self.subtitle_font_style.setCurrentText('normal')
|
|
84
|
+
self.update_preview()
|
|
85
|
+
|
|
86
|
+
# 이벤트 핸들러 위임
|
|
87
|
+
def on_resolution_mode_changed(self, mode):
|
|
88
|
+
EventHandlers.on_resolution_mode_changed(self, mode)
|
|
89
|
+
|
|
90
|
+
def on_width_changed(self, value):
|
|
91
|
+
EventHandlers.on_width_changed(self, value)
|
|
92
|
+
|
|
93
|
+
def on_height_changed(self, value):
|
|
94
|
+
EventHandlers.on_height_changed(self, value)
|
|
95
|
+
|
|
96
|
+
def on_aspect_ratio_changed(self, ratio):
|
|
97
|
+
EventHandlers.on_aspect_ratio_changed(self, ratio)
|
|
98
|
+
|
|
99
|
+
def select_bg_color(self):
|
|
100
|
+
EventHandlers.select_bg_color(self)
|
|
101
|
+
|
|
102
|
+
def select_title_color(self):
|
|
103
|
+
EventHandlers.select_title_color(self)
|
|
104
|
+
|
|
105
|
+
def select_subtitle_color(self):
|
|
106
|
+
EventHandlers.select_subtitle_color(self)
|
|
107
|
+
|
|
108
|
+
def select_background_image(self):
|
|
109
|
+
EventHandlers.select_background_image(self)
|
|
110
|
+
|
|
111
|
+
def set_title_font_name_from_path(self, path: str):
|
|
112
|
+
EventHandlers.set_title_font_name_from_path(self, path)
|
|
113
|
+
|
|
114
|
+
def set_subtitle_font_name_from_path(self, path: str):
|
|
115
|
+
EventHandlers.set_subtitle_font_name_from_path(self, path)
|
|
116
|
+
|
|
117
|
+
def on_title_font_file_changed(self):
|
|
118
|
+
EventHandlers.on_title_font_file_changed(self)
|
|
119
|
+
|
|
120
|
+
def on_subtitle_font_file_changed(self):
|
|
121
|
+
EventHandlers.on_subtitle_font_file_changed(self)
|
|
122
|
+
|
|
123
|
+
def generate_preview(self):
|
|
124
|
+
EventHandlers.generate_preview(self)
|
|
125
|
+
|
|
126
|
+
def save_thumbnail(self):
|
|
127
|
+
EventHandlers.save_thumbnail(self)
|
|
128
|
+
|
|
129
|
+
def show_dsl_dialog(self):
|
|
130
|
+
EventHandlers.show_dsl_dialog(self)
|
|
131
|
+
|
|
132
|
+
def save_dsl(self):
|
|
133
|
+
EventHandlers.save_dsl(self)
|
|
134
|
+
|
|
135
|
+
def load_thl_package(self):
|
|
136
|
+
EventHandlers.load_thl_package(self)
|
|
137
|
+
|
|
138
|
+
def save_thl_package(self):
|
|
139
|
+
EventHandlers.save_thl_package(self)
|
|
140
|
+
|
|
141
|
+
# DSL 관련 메서드
|
|
142
|
+
def generate_dsl(self):
|
|
143
|
+
return DSLManager.generate_dsl(self)
|
|
144
|
+
|
|
145
|
+
def load_dsl_to_gui(self, dsl: dict):
|
|
146
|
+
DSLManager.load_dsl_to_gui(self, dsl)
|
|
147
|
+
|
|
148
|
+
def update_preview(self):
|
|
149
|
+
"""미리보기 업데이트"""
|
|
150
|
+
# URL/로컬 입력 영역 가시성 토글
|
|
151
|
+
is_title_local = self.title_font_source.currentText() == '로컬 폰트 파일'
|
|
152
|
+
self.label_title_font_url.setVisible(not is_title_local)
|
|
153
|
+
self.title_font_url.setVisible(not is_title_local)
|
|
154
|
+
self.label_title_font_file.setVisible(is_title_local)
|
|
155
|
+
self.title_font_file.setVisible(is_title_local)
|
|
156
|
+
|
|
157
|
+
is_sub_local = self.subtitle_font_source.currentText() == '로컬 폰트 파일'
|
|
158
|
+
self.label_subtitle_font_url.setVisible(not is_sub_local)
|
|
159
|
+
self.subtitle_font_url.setVisible(not is_sub_local)
|
|
160
|
+
self.label_subtitle_font_file.setVisible(is_sub_local)
|
|
161
|
+
self.subtitle_font_file.setVisible(is_sub_local)
|
|
162
|
+
|
|
163
|
+
dsl = self.generate_dsl()
|
|
164
|
+
self.current_dsl = dsl
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def main():
|
|
168
|
+
"""GUI 메인 함수"""
|
|
169
|
+
app = QApplication(sys.argv)
|
|
170
|
+
window = ThumbnailGUI()
|
|
171
|
+
window.show()
|
|
172
|
+
sys.exit(app.exec())
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
if __name__ == '__main__':
|
|
176
|
+
main()
|
|
177
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
미리보기 생성 스레드
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from PySide6.QtCore import QThread, Signal
|
|
8
|
+
from ..renderer import ThumbnailRenderer
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PreviewThread(QThread):
|
|
12
|
+
"""미리보기 생성 스레드"""
|
|
13
|
+
preview_ready = Signal(str) # preview file path
|
|
14
|
+
|
|
15
|
+
def __init__(self, dsl):
|
|
16
|
+
super().__init__()
|
|
17
|
+
self.dsl = dsl
|
|
18
|
+
self.error_message = None
|
|
19
|
+
|
|
20
|
+
def run(self):
|
|
21
|
+
preview_path = 'preview_temp.png'
|
|
22
|
+
try:
|
|
23
|
+
ThumbnailRenderer.render_thumbnail(self.dsl, preview_path)
|
|
24
|
+
self.preview_ready.emit(preview_path)
|
|
25
|
+
except Exception as e:
|
|
26
|
+
self.error_message = str(e)
|
|
27
|
+
self.preview_ready.emit('')
|
|
28
|
+
|