가끔 가던 길을 가다가 발견.
![]() |
| JJ VELO |
전에 자전거 산 가게인데.
자전거 가게 문 닫았구나.
사진 그냥 보면 잘 안 보이지만 클릭해서 큰 그림으로 보면 잘 보인다.
간판은 JJ VELO 그대로 남아있지만 아래 유리에 임대 붙어있다.
그 가게는 다른 곳으로 이사 갔을까? 아닌 것 같다. 이사 갔다면 어디로 이사 갔다는 안내가 있을 듯한데 그런 안내는 안 보인다.
여기서 또 다른 가게가 문을 열겠지.
뽈찜.
가게 이름은? 소문난 뽈데기 찜.탕.아구찜 이라고 간판에 써 있는데, 어디까지 가게 이름인지 모르겠음.
서울 강서구청 길 건너 있는 식당. 주소는 서울 강서구 화곡로53길 11, 1층
서울 강서구 화곡로53길 11 1층 소문난뽈데기찜.탕.아구찜
![]() |
| 자체배달 즉 포장 집에서 뽈찜 먹음 |
식당에서 먹으면 기본 반찬 나오는데 포장에는 반찬 없음. 가서 먹으나 포장하나 가격은 같은데.
포장해 온 건 사진 찍으니 비쥬얼은 별로군. 그래서 난 인스타 체질은 아닌 듯.
![]() |
| 소문난 뽈 메뉴 2026. 5. 2. |
이 메뉴판에는 뽈테기, 간판에는 뽈데기. 어느 게 맞는 거?
메뉴는 이러함. 다른 블로그에 같은 식당 가격 다른 것 있는데, 가격 다른 건 아마도 지난 자료인 듯. 음식 가격은 어디가나 시간이 지나면 오름.
반면, 전자제품 가격은 시간이 지날 수록 성능은 좋아지고 가격은 내려가는데, 그렇다고 계속 가격 내려가길 기다리다가는 전자제품 평생 못 살 수 있음.
싸다헤어. 5호선 우장산역 4번 출구.
싸다. 그런데, 언제부터인가 일요일에 전화 안 받는다. 일요일엔 안 하는 듯. 그러면 간판에 써 놓던지.
02-323-9882 직접 전화 해 봄.
2026년 5월에는 목,금 만 영업 한다고 한다.
일정 봐서 다시 예약 잡아야 할 듯.
36년 전통, 명곡 아카데미
클래식의 품격, 최고 강사진과 함께
🎤 성악클래스(임종환 교수, 김정경 교수)
* 오페라 주역 및 세계 무대 활약 성악가 연주가
* 국내외 가곡, 건전가요, 발성법, 호흡법, 가창법, 시문학해석(노래가사), 보컬 테라피, 힐링, 마음건강
* 음악박사 피아니스트 반주, 지휘 및 건강 지도
왜 명곡아카데미인가?
성악과 음악치료, 심리상담, 심리치료 전문가
노래를 통해 정신건강과 품격 있는 삶 초대
힐링 명곡 교실
일시: 매주 금요일 15:00 ~ 16:30
장소: 서울시 강서구 우장산동 주민센터 3층
문의: 010-5416-4940, 010-8245-4931
이상은 임종환 교수님이 주신 자료.
그런데, 위 글만 보면 너무 엄숙하고 그래서 다가가기 어려워 보인다. 내 느낌은 좀 다르다.
동네 사랑방 같은 느낌? 교수님 말씀이 웃기다. 재밌다. 그런데, 그냥 단순히 웃긴 게 아니라, 그 속에 깊은 뜻이 있다. 그래서 더욱 재미있고 유익하게 노래 수업을 들을 수 있다.
장소가 서울 강서구 우장산동주민센터. 장소만 보면 "강서구 사람만 갈 수 있나?" 생각할 수도 있는데, 다른 곳에 사는 사람도 참가 가능. 더 자세한 것 궁금하면 위의 전화로 물어보시길.
강서구에 장애인을 위한 미용실이 생겼다.
위치는? 오래 전에 정보화진흥원 있던 자리. 정보화진흥원은 지방으로 내려간지 한참되었는데 그동안 서울 정보화진흥원 있던 자리 건물은 제대로 활용 안 되고 있었던 듯. 그런데, 이번에 어울림플라자 지은 것을 보고 깜짝 놀랐다. 매우 훌륭. 어울림플라자에는 도서관도 있고, 장애인전용미용실도 있다.
거기 있는 장애인 전용 미용실 이름은 헤어포유. 이름에서 유추해서 홈페이지 주소에 hairforyou 쳤더니... 강서구 아니고 다른 곳에 있는 헤어포유 나온다. 이런 일반적인 이름이 하나뿐이라면 오히려 이상한 듯. 강서구 헤어포유 주소
(우) 07564 서울 강서구 공항대로 489 어울림플라자 3층
인터넷 주소(homepage, 홈페이지)는?
http://gshairforyou.co.kr/
약간 아쉽다. 내가 하는 일이 그러하다 보니 그런 게 잘 보인다. 다른 사람들은 잘 발견하지 못할 것들을 내가 발견.
이 홈페이지 아쉬운 점.
이용료 안내 페이지와 이용안내 페이지 두 페이지가 자동으로 넘어간다. 이건 곤란하다.
한 페이지 읽고 있는데 다 읽지도 않았는데 다른 페이지로 넘어가버리면 당황한다. 장애인은 더더욱.
마이페이지 메뉴 클릭하면 로그인한 회원만 접근할 수 있다는 메시지가 나온다.
그게 무슨 문제냐고? 로그인한 회원만 접근할 수 있는 페이지라면 로그인 전에는 마이페이지 메뉴가 안 나오게 하는 게 좋다. 로그인 한 다음에는 마이페이지 메뉴가 나오고, 로그아웃하면 마이페이지 메뉴 사라지게. 기술적으로 어려울까? 아니다. 웹페이지 개발자가 그 정도도 못하면 웹페이지 개발 하지 말아야지. 아니면 배워서 하거나.
헤어포유 운영자를 탓하는 건 아니다. 어차피 운영자가 전산에 대해 잘 알고 있다는 보장 없으니. 그래도 아쉬운 건 그걸 홈페이지 개발 전문 업체에게 맡겼을 텐데, 그 업체는 무슨 생각으로 홈페이지를 만들었을까? 저거 만들고 개발비는 얼마나 받아간 걸까? 업체를 너무 나무런 것 같은데 여기서 내가 업체의 변명도 좀 하자면(병 주고 약 주고?) 업체는 고객의 요구사항에 따라 페이지를 만드는 거다. 그런데, 고객의 요구사항이 불분명하면 그에 따라 만든 홈페이지도 그리 되기 쉽다.
앞으로 많은 발전이 있기를. 강서헤어포유도. 그곳 홈페이지도.
http://gshairforyou.co.kr
BTS 공연이 별다른 사고 없이 끝난 것 같다. 이태원 같은 사고가 나지 않은 건 다행. 그러나, 너무 심한 거 아니었냐는 의견도 있었으니. 참 어렵다.
https://www.khan.co.kr/article/202603250700001
우리행복한교회 홈페이지 관리자 여러분께 알림. 관리자 홈페이지 설명서 아래 링크 참고.
http://help30.church-love.net/index_ch.html
LFS Book 한국어 번역 시도. 일단 github에서 받고, ko 디렉터리 만들어서 한국어 번역 관련 내용은 ko 디렉터리에 영어는 en 디렉터리에 공통사항은 메인 디렉터리에 넣으려고 함. 시행착오 중. 일단 현재 상황은 아래와 같음.
tu@ma:~/lfs.13$ ls -lF
합계 180
-rw-rw-r-- 1 tu tu 1776 3월 15일 08:34 INSTALL
-rw-rw-r-- 1 tu tu 7605 3월 15일 09:24 Makefile
-rw-rw-r-- 1 tu tu 920 3월 15일 08:34 README
lrwxrwxrwx 1 tu tu 13 3월 20일 03:19 appendices -> ko/appendices/
-rwxrwxr-x 1 tu tu 744 3월 15일 08:34 aux-file-data.sh*
lrwxrwxrwx 1 tu tu 14 3월 20일 03:18 bootscripts -> en/bootscripts/
lrwxrwxrwx 1 tu tu 12 3월 19일 21:44 chapter01 -> ko/chapter01/
lrwxrwxrwx 1 tu tu 12 3월 19일 21:45 chapter02 -> ko/chapter02/
lrwxrwxrwx 1 tu tu 12 3월 19일 21:45 chapter03 -> ko/chapter03/
lrwxrwxrwx 1 tu tu 12 3월 19일 21:45 chapter04 -> ko/chapter04/
lrwxrwxrwx 1 tu tu 12 3월 19일 21:45 chapter05 -> ko/chapter05/
lrwxrwxrwx 1 tu tu 12 3월 19일 21:45 chapter06 -> ko/chapter06/
lrwxrwxrwx 1 tu tu 12 3월 19일 21:45 chapter07 -> ko/chapter07/
lrwxrwxrwx 1 tu tu 12 3월 19일 21:45 chapter08 -> ko/chapter08/
lrwxrwxrwx 1 tu tu 12 3월 19일 21:45 chapter09 -> ko/chapter09/
lrwxrwxrwx 1 tu tu 12 3월 19일 21:53 chapter10 -> ko/chapter10/
lrwxrwxrwx 1 tu tu 12 3월 19일 21:46 chapter11 -> ko/chapter11/
-rw-rw-r-- 1 tu tu 60 3월 20일 04:13 conditional.ent
-rw-rw-r-- 1 tu tu 2812 3월 20일 04:53 dirlist.txt
drwxrwxr-x 19 tu tu 4096 3월 16일 02:04 en/
-rwxrwxr-x 1 tu tu 3317 3월 15일 08:34 gen-changelog.py*
-rw-rw-r-- 1 tu tu 1273 3월 15일 08:40 gen_conf.py
-rw-rw-r-- 1 tu tu 6815 3월 15일 08:34 general.ent
-rwxrwxr-x 1 tu tu 2463 3월 15일 08:34 git-version.sh*
drwxrwxr-x 2 tu tu 4096 3월 15일 21:12 images/
lrwxrwxrwx 1 tu tu 12 3월 15일 21:15 index.xml -> ko/index.xml
drwxr-xr-x 17 tu tu 4096 3월 20일 03:07 ko/
-rw-rw-r-- 1 tu tu 196 3월 20일 04:13 lfs-bootscripts-20250827.tar.xz
-rw-rw-r-- 1 tu tu 17000 3월 15일 08:34 lfs-latest-git.php
lrwxrwxrwx 1 tu tu 18 3월 15일 08:34 lfs-latest.php -> lfs-latest-git.php
-rwxrwxr-x 1 tu tu 682 3월 15일 08:34 make-aux-files.sh*
-rwxrwxr-x 1 tu tu 1342 3월 15일 08:34 obfuscate.sh*
-rw-rw-r-- 1 tu tu 35221 3월 15일 08:34 packages.ent
lrwxrwxrwx 1 tu tu 13 3월 20일 03:22 part3intro -> ko/part3intro/
-rw-rw-r-- 1 tu tu 2663 3월 15일 08:34 patches.ent
-rwxrwxr-x 1 tu tu 450 3월 15일 08:34 pdf-fixups.sh*
drwxrwxr-x 2 tu tu 4096 3월 20일 04:10 po/
-rw-rw-r-- 1 tu tu 17896 3월 16일 02:26 po4a.conf
-rwxrwxr-x 1 tu tu 822 3월 15일 08:34 process-scripts.sh*
lrwxrwxrwx 1 tu tu 11 3월 20일 03:21 prologue -> ko/prologue/
drwxrwxr-x 3 tu tu 4096 3월 15일 21:12 stylesheets/
-rw-rw-r-- 1 tu tu 194 3월 15일 08:34 tidy.conf
drwxrwxr-x 3 tu tu 4096 3월 15일 08:34 udev-lfs/
-rw-rw-r-- 1 tu tu 211 3월 20일 04:13 version.ent
상호 길빛미디어
등록신고번호 251002026000027
등록일 2026-02-06
소재지 서울특별시 강서구
전국츨판사인쇄사 검색시스템에 나타남.
발산동 포시즌 근처. 전에 카페동네 있던 자리. 동네카페 문 닫고. 역대급피자 문 열다.
2026. 3. 13.(금)
아래는 직원모집 공고.
https://www.albamon.com/jobs/detail/115301252?logpath=7&productCount=1
문 연지 얼마 안 되어서 그런가? 아래 링크에는 안 나옴.
http://yeokdaegeup.kr/franchise
교회 홈페이지에 서브 메뉴 추가 방법.
admin.wehappyc.org 로그인 (아이디 및 비번은 관리자에게 문의)
메뉴 설정 클릭.
1. 서브 메뉴 기본 정보
조직 및 기관 아래에 메뉴를 추가하는 예시.
조직 및 기관 클릭해서 나타난 팝업 메뉴 중 조직 및 기관 하위에 메뉴 추가
페이지명 입력.
2. 페이지 형식
콘텐츠 생성기, html, 게시판, 모듈, 링크 중에서 원하는 것을 클릭.
3. 기타 설정 열기 버튼 클릭하여 더 입력할 것 있으면 하면 됨.
서울시 강서구에 장애인 친화 미용실 헤어포유 문을 열다.
어디: 강서구 공항대로 489, 어울림플라자 3층
언제: 화~토 09:00-18:00 (일, 월, 공휴일 쉼)
이용대상: 서울시 강서구 거주 등록장애인
이용방법: 전화(02-6949-3491), 온라인 예약
커트: 6,000원, (기초수급자: 3,000원)
염색: 15,000원, (기초수급자:7,500원)
일반펌: 19,000원, (기초수급자: 9,500원)
열펌: 39,000원, (기초수급자: 19,500원)
강서헤어포유 홈페이지: http://www.gshairforyou.co.kr/
Linux From Scratch 설명서 한국어 번역. 아직 끝나지 않음. 언제 완성될 지 모르고, 누가 읽을지도 모르지만 아직도 진행 중. 아래 링크 맨 아래로 가면 링크 있다.
https://www.linuxfromscratch.org/lfs/read.html
내 LFS 저장소: https://github.com/sebuls/lfs-ko
Linux From Scratch 줄여서 LFS
설명서를 한국어로 번역 중.
저장소: https://github.com/sebuls/lfs-ko
한국어 설명서: 위 저장소에 있는 파일을 받아서 설명에 따라 make 하면 html 파일을 얻을 수 있다. 아주 가끔(실시간 아니라는 뜻) https://sebul.sarang.net/lfs/ 에 올리고 있다.
원본 파일: JesusLovesMe.m4a 이 파일 처음부터 32초까지만 자르기
결과파일: jesuslovesme32.m4a
d:\opt\ffmpeg-6.1-win-64\ffmpeg -i JesusLovesMe.m4a -ss 00:00:00 -t 00:00:32 -c copy jesuslovesme32.m4a
소리파일만 유튜브에 올릴 순 없잖아? 이미지 파일 추가
d:\opt\ffmpeg-6.1-win-64\ffmpeg -loop 1 -i imgJesuslovesme.jpg -i jesuslovesme32.m4a -c:v libx264 -tune stillimage -c:a copy -shortest jsmimg.mp4
이렇게 해서 만든 영상 jsmimg.mp4 파일을 유튜브에 올리면 됨.
○ 사업명: 장애학생 성장 자립 지원사업(정보통신보조기기 보급 자부담금 지원사업)
○ 지원대상: 과학기술정보통신부 ‘2025년 정보통신보조기기 보급사업’에 신청하여 최종 선정된 장애학생(초등학생, 중학생, 고등학생, 전공과 학생, 대학생, 대학원생)
○ 지원내용: 과학기술정보통신부 ‘2025년 정보통신보조기기 보급사업’ 선정자 대상 자부담금(개인부담금) 전액 지원
○ 신청기간: 2026년 1월 16일(금) ~ 2월 27일(금)
○ 제출서류
- 2025 정보통신보조기기 보급사업 기기 수령확인서(배송·설치업체 발급)
- 재학증명서(대학·대학원생의 경우 휴학증명서로 대체 가능)
- 통장사본(신청자 본인 통장)
- 자부담금 납부 증빙자료(이체확인증, 온라인 뱅킹 캡처 화면 등)
○ 제출방법
- 구글폼 접수 페이지에 첨부 후 제출(https://forms.gle/94Y21JpG7LTTP1DP7)
○ 문의처
- 담당자: 한국장애인단체총연맹 신우철 선임
- 대표 전화: 02-783-0067
- 대표 메일: mail@kofdo.kr
게임 이름:날목 (python3 으로 개발)
import sys
import math # 중앙 거리를 계산하기 위해 추가
from PyQt6.QtWidgets import QApplication, QMainWindow, QMessageBox
from PyQt6.QtGui import QPainter, QPen, QColor, QBrush
from PyQt6.QtCore import Qt, QPoint
class FixedNalGomoku(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("세벌의 날목 (Fixed AI)")
self.setFixedSize(640, 640)
self.grid_size = 30
self.margin = 50
self.board = [[0] * 19 for _ in range(19)]
self.current_player = 1 # 흑(컴퓨터) 선공
self.directions = [(2, 1), (1, 2), (-1, 2), (-2, 1)]
self.win_stones = []
# 첫 수는 무조건 중앙(천원)
self.board[9][9] = 1
self.current_player = 2
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
painter.fillRect(self.rect(), QColor("#E6C38A"))
# 바둑판 선 및 화점
painter.setPen(QPen(Qt.GlobalColor.black, 1))
for i in range(19):
painter.drawLine(self.margin, self.margin + i * self.grid_size, self.margin + 18 * self.grid_size, self.margin + i * self.grid_size)
painter.drawLine(self.margin + i * self.grid_size, self.margin, self.margin + i * self.grid_size, self.margin + 18 * self.grid_size)
painter.setBrush(QBrush(Qt.GlobalColor.black))
for r in [3, 9, 15]:
for c in [3, 9, 15]:
painter.drawEllipse(QPoint(self.margin + c * self.grid_size, self.margin + r * self.grid_size), 3, 3)
# 돌 그리기
for y in range(19):
for x in range(19):
if self.board[y][x] != 0:
center = QPoint(self.margin + x * self.grid_size, self.margin + y * self.grid_size)
color = Qt.GlobalColor.black if self.board[y][x] == 1 else Qt.GlobalColor.white
painter.setBrush(QBrush(color))
painter.setPen(QPen(QColor("#CCCCCC") if self.board[y][x] == 2 else Qt.GlobalColor.black, 1))
painter.drawEllipse(center, 13, 13)
if (y, x) in self.win_stones:
painter.setBrush(QBrush(QColor("lime")))
painter.setPen(Qt.PenStyle.NoPen)
painter.drawEllipse(center, 4, 4)
def mousePressEvent(self, event):
if self.current_player != 2 or self.win_stones: return
x = round((event.position().x() - self.margin) / self.grid_size)
y = round((event.position().y() - self.margin) / self.grid_size)
if 0 <= x < 19 and 0 <= y < 19 and self.board[y][x] == 0:
self.board[y][x] = 2
win, stones = self.check_win(y, x, 2)
if win:
self.win_stones = stones
self.update()
self.end_game("세벌 님 승리!")
else:
self.current_player = 1
self.ai_move()
self.update()
def ai_move(self):
best_score = -1
best_moves = [] # 동점일 경우를 위해 리스트로 관리
for y in range(19):
for x in range(19):
if self.board[y][x] == 0:
score = self.evaluate_move(y, x)
if score > best_score:
best_score = score
best_moves = [(y, x)]
elif score == best_score:
best_moves.append((y, x))
if best_moves:
# 동점일 경우 중앙에서 가장 가까운 곳 선택
best_move = min(best_moves, key=lambda pos: math.sqrt((pos[0]-9)**2 + (pos[1]-9)**2))
y, x = best_move
self.board[y][x] = 1
win, stones = self.check_win(y, x, 1)
if win:
self.win_stones = stones
self.update()
self.end_game("컴퓨터 승리!")
self.current_player = 2
self.update()
def evaluate_move(self, y, x):
score = 0
for dy, dx in self.directions:
# 공격 가중치와 방어 가중치를 전략적으로 배분
score += self.get_line_score(y, x, 1, dy, dx) * 1.5 # 공격 중시
score += self.get_line_score(y, x, 2, dy, dx) * 1.2 # 방어 필수
# 중앙 선호도 점수 (0~10점 사이의 아주 작은 보너스)
dist_from_center = math.sqrt((y-9)**2 + (x-9)**2)
score += (15 - dist_from_center)
return score
def get_line_score(self, y, x, player, dy, dx):
count = 1
open_ends = 0 # 양끝이 열려있는지 확인 (3-3 등 파악용)
for direction in [1, -1]:
ty, tx = y + dy*direction, x + dx*direction
while 0 <= ty < 19 and 0 <= tx < 19 and self.board[ty][tx] == player:
count += 1
ty, tx = ty + dy*direction, tx + dx*direction
if 0 <= ty < 19 and 0 <= tx < 19 and self.board[ty][tx] == 0:
open_ends += 1
# 가중치 대폭 상향
if count >= 5: return 100000
if count == 4: return 10000 if open_ends > 0 else 5000
if count == 3: return 2000 if open_ends == 2 else 500
if count == 2: return 100
return 0
def check_win(self, y, x, player):
for dy, dx in self.directions:
stones = [(y, x)]
for direction in [1, -1]:
ty, tx = y + dy*direction, x + dx*direction
while 0 <= ty < 19 and 0 <= tx < 19 and self.board[ty][tx] == player:
stones.append((ty, tx))
ty, tx = ty + dy*direction, tx + dx*direction
if len(stones) >= 5:
return True, stones[:5]
return False, []
def end_game(self, message):
QMessageBox.information(self, "게임 종료", message)
self.board = [[0] * 19 for _ in range(19)]
self.win_stones = []
self.board[9][9] = 1 # 리셋 후에도 흑(AI)이 중앙 선공
self.current_player = 2
if __name__ == "__main__":
app = QApplication(sys.argv)
window = FixedNalGomoku()
window.show()
sys.exit(app.exec())
Linux From Scratch Book https://github.com/lfs-book/lfs
L10n i18n 은 알아서 해야 된다. 그 사이트에서 기본 제공하지는 않는다는 뜻. 한국어 번역. 그 알아서 하는 것을 내가 시작.
한국어 번역. 사실 다른 사람이 한 게 있기는 하다. 현재 영어 원본은 버전 12까지 나왔는데, 한국어 버전 최근 버전은 9. 그런데, 버전 12와 버전 9 차이는 적지 않다. 한국어 버전 살펴보니 xml 파일을 직접 뜯어 고친 거라 유지관리 쉽지 않다. 기존 한국어 번역을 업데이트 하느니 최근 영어 버전 원본에서 새롭게 시작하는 게 낫다고 판단. 내가 한 작업은 .po 파일을 만들였고, 원문과 번역문 1대1로 쉽게 할 수 있도록 틀 만들었다. 또한 한국어 번역 중 영어 원본이 바뀌면 바뀐 부분을 추가적으로 한국어 버전에 반영하기 쉽게 했다. 그것을 모두 수작업으로 한 건 아니고, 관련 프로그램을 잘 활용하면 된다.
https://github.com/sebuls/lfs-ko/
이제 시작은 했다. 번역 업데이트는 틈틈이 해 나가면 된다. 다른 사람이 관심 있다면 내 작품 fork 해서 clone 해도 되고. 그게 오픈소스 좋은 점. 그런데 할 사람이 있을지는 모르겠다.
Linux From Scratch Book Korean Translation 현재 한국어 버전 9. 영문 Linux From Scratch Book은 최근 버전 12. 기존 한국어 번역을 업데이트 해서 12 버전 만드는 건 포기. 영문 최근 버전 12 갖고 와서 한국어 버전 만드는 중. 배우는 게 많다.
https://www.linuxfromscratch.org/lfs/download.html 에서 Current Development 보면서 진행 중. 아직은 잘 모르겠다. 내가 맞는 길로 가고 있는지. 그래도 좋다. 여기 저기 헤매면서 바른 길 찾아가는 과정에서 배우는 게 많다.
시작점은 어디? 바로 아래.
git clone git://git.linuxfromscratch.org/lfs.git
pdftk 참 좋은데 명령어 방식이라 좀 불편. PyQt 활용. 간단한 기능만 GUI 버전으로 만들어 봄.
gpdf.py
import sys
import subprocess
from PyQt6.QtWidgets import (QApplication, QWidget, QVBoxLayout, QPushButton,
QListWidget, QFileDialog, QHBoxLayout, QMessageBox)
from PyQt6.QtCore import Qt
class PDFJoiner(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('PDF 도우미 (pdftk GUI)')
self.setGeometry(300, 300, 500, 400)
layout = QVBoxLayout()
# 파일 목록 표시창
self.file_list = QListWidget()
layout.addWidget(self.file_list)
# 버튼 레이아웃
btn_layout = QHBoxLayout()
btn_add = QPushButton('파일 추가')
btn_add.clicked.connect(self.add_files)
btn_remove = QPushButton('삭제')
btn_remove.clicked.connect(self.remove_item)
btn_up = QPushButton('위로')
btn_up.clicked.connect(lambda: self.move_item(-1))
btn_down = QPushButton('아래로')
btn_down.clicked.connect(lambda: self.move_item(1))
btn_layout.addWidget(btn_add)
btn_layout.addWidget(btn_remove)
btn_layout.addWidget(btn_up)
btn_layout.addWidget(btn_down)
layout.addLayout(btn_layout)
# 실행 버튼
self.btn_run = QPushButton('PDF 합치기 실행')
self.btn_run.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold; height: 40px;")
self.btn_run.clicked.connect(self.merge_pdfs)
layout.addWidget(self.btn_run)
self.setLayout(layout)
def add_files(self):
files, _ = QFileDialog.getOpenFileNames(self, "PDF 파일 선택", "", "PDF Files (*.pdf)")
if files:
self.file_list.addItems(files)
def remove_item(self):
for item in self.file_list.selectedItems():
self.file_list.takeItem(self.file_list.row(item))
def move_item(self, direction):
current_row = self.file_list.currentRow()
if current_row < 0: return
target_row = current_row + direction
if 0 <= target_row < self.file_list.count():
item = self.file_list.takeItem(current_row)
self.file_list.insertItem(target_row, item)
self.file_list.setCurrentRow(target_row)
def merge_pdfs(self):
count = self.file_list.count()
if count < 2:
QMessageBox.warning(self, "알림", "합칠 파일이 2개 이상 필요합니다.")
return
save_path, _ = QFileDialog.getSaveFileName(self, "저장 경로 선택", "merged_output.pdf", "PDF Files (*.pdf)")
if save_path:
input_files = [self.file_list.item(i).text() for i in range(count)]
# pdftk 명령어 구성
# pdftk file1.pdf file2.pdf cat output combined.pdf
command = ["pdftk"] + input_files + ["cat", "output", save_path]
try:
subprocess.run(command, check=True)
QMessageBox.information(self, "성공", f"성공적으로 합쳐졌습니다!\n경로: {save_path}")
except Exception as e:
QMessageBox.critical(self, "오류", f"실행 중 오류가 발생했습니다: {e}")
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = PDFJoiner()
ex.show()
sys.exit(app.exec())