아주 간단하고 단촐한 GUI 기반의 프로그램을 만들어 보겠습니다. 이름은 'pySubRenamer'라고 지었습니다. 파이썬 기반의 'SubRenamer'입니다. SubRenamer란 프로그램은 제가 예전에 MFC 기반으로 작성해 클리앙에 올린 적이 있습니다. 제가 개인적인 목적으로 제작한 툴이며, 동영상 파일과 자막 파일의 이름을 쉽게 맞추기 위한 프로그램입니다. pySubRenamer는 이 SubRenamer의 프로토타입 버전을 파이썬으로 구현한 것입니다.
pySubRenamer의 완성 형태는 제가 클리앙에 공개한 MFC 기반의 SubRenamer가 아닌 프로토타입 버전과 거의 동일합니다. 오랜 기간 사용해보니 오히려 프로토타입의 기능 이상을 사용할 일이 없는데다 GUI가 매우 간단해 파이썬의 GUI 프로그래밍 연습용으로 쓰기 좋다고 생각해 이번에 다시 한번 작성해 보았습니다.
사실 파이썬을 사용하려 했던 몇 가지 이유가 있었는데, 그 중의 하나로 'GUI 활용'이 있었습니다. 간결하고 쉬운 파이썬 스크립트를 이용해 빠르게 GUI를 구성하고, 필요에 따라 심각한 부분은 다른 언어를 이용해 작성하는 것은 어떨까 생각했었습니다.
비주얼 프로그래밍 라이브러리, GUI 툴킷으로 아마 가장 유명한 것 중 하나는 Microsoft의 MFC일 것입니다. MFC는 사실 Microsoft의 API를 C++ 기반으로 래핑(wrapping)한 것이라 GUI 툴킷만을 위한 것은 아닙니다만, 여기서는 그냥 넘어가도록 하죠. 요새는 어떤지 모르지만 저 때만 하더라도 학교 정규 강의 과목 중 하나였습니다. 특정 회사의 독점 라이브러리를 학교 강의로 쓰는 것은 약간 문제라는 이야기도 가끔씩 들었지만, C++를 겨우 익힌 학생들에게 C++의 고급 기법 및 비주얼 프로그래밍의 개념을 가르치기 위한 용도로는 이만한 게 없다는 것이 중론이었습니다. /*사실 따지면 마인크래프트나 심시티 같은 엄연한 상용 소프트웨어가 강의에 쓰이기도 합니다. 마이크로소프트 오피스군 제품을 안 쓰고 보고서 작성이나 팀 프로젝트 진행하기가 얼마나 어려운데요.*/ MFC는 마이크로소프트 윈도우 운영 체제가 아니면 동작하지 않고 소스 공개도 되어 있지 않습니다. 이런 것들은 MFC의 단점입니다. 반면 좋은 점도 있습니다. 제가 생각하는 MFC의 장점과 단점은 대략 이렇습니다.
한편 MFC 이외에 대안으로 쓸 수 있는, 크로스-플랫폼(Cross-Platform) 기반의 GUI 툴킷들이 많이 있습니다. 이 곳에 잘 정리되어 있습니다. 제가 쓸만한 툴킷으로는 Qt, GTK+, wxWidgets 이렇게 세 가지를 꼽을 수 있었습니다. 일단 셋 다 파이썬 환경에서 사용할 수 있도록 바인딩이 지원됩니다. 각각 pyQt, pyGTK, wxPython이라 합니다.
GTK+는 양호하지만 각각의 OS 환경에 네이티브1)한 모습이 들지 않아 제외하기로 했습니다, 라이브러리 자체의 성능과는 관계 없는 저만의 트집입니다만… Qt는 매우 방대하고 훌륭한 라이브러리입니다. 많은 프로젝트들이 이를 증명하지요. 저도 살짝 건드려 보기도 했습니다. 하지만 결국 저는 wxWidget을 선택했습니다. wxWidgets는 C++ 기반으로 프로그래밍할 때 코드가 MFC와 매우 유사하다는 점이 마음에 들었습니다. 상당히 친숙한 형태이므로 빠르게 이해하고 사용할 수 있으리라 생각하기 때문이었습니다. 그리고 결과물이 각 OS에 상당히 네이티브합니다. 사실 파이썬 자체도 GUI 툴킷2)을 제공합니다. 자체 표준 라이브러리 만으로도 GUI를 만드는 데 큰 무리가 없습니다. 하지만 wxWidgets의 네이티브한 생김새는 정말 매력적이더군요.
/*C/C++로 핵심 모듈을 만들고, UI 부분은 파이썬의 스크립트를 활용한다는 시나리오를 생각해 보았습니다. 왠지 굉장한 일처럼 느껴집니다. 그리고 서로 다른 언어가 하나로 똘똘 뭉쳐 일하는 모습도 멋져 보입니다. 적어도 저는 그렇습니다. 단일 언어, 일례로 C++로 UI와 핵심 모듈을 모두 제작한다고 생각하면 당연히 같은 언어에서 모든 사항이 구현되므로 UI와 다른 모듈이 매우 자연스럽게 융합될 것입니다. 속도도 빠르구요. 그러나 편의성은 조금 하락할 것입니다. UI 부분은 다소 제작하기 까다로운 단점도 있습니다. 물론 숙련된 분 앞에 무엇이 어렵겠습니까, 절대적으로 딱 잘라 말할 수는 없습니다.*/ 파이썬을 이용해 UI를 제작했을 때 기존에 사용하던 MFC(혹은 C++ 기반의 GUI 툴킷)와 대비했을 때 생각나는 장점은 다음과 같습니다.
또한 생각나는 단점은 다음과 같습니다. 첫 항목은 이 문서에서 다루지 않기 때문에 '예상한다'고 표현했습니다.
앞서 언급했던 단점 중 두번째 항목인 '비주얼 툴의 부재'는 반드시 극복해야 합니다. 직관적이며 쓰기 편리한 툴은 반드시 필요합니다. 그렇지 않으면 UI 코딩을 바닥부터 생짜로 해야 하는데, 그럴 바엔 그냥 MFC를 계속 쓰는 것이 낫습니다. 물론 이미 wxWidget 기반의 UI 설계를 도와주는 편리한 툴이 몇 가지 있습니다. 저는 그 중 'wxGlade'를 선택했습니다. 윈도우 OS에서는 SAE, Standalone Edition을 받아 설치하면 됩니다. wxGlade를 실행하면 아래 그림과 같은 UI를 만날 수 있습니다.
좌측의 창은 wxGlade의 메인 메뉴 창입니다. wxGlade 프로그램 메뉴 및 위젯을 추가할 수 있는 메뉴가 구비되어 있습니다. 중앙의 창은 디자인하고 있는 UI의 구조를 트리로 보여줍니다. 각 항목을 선택하면 선택의 결과가 우측의 창에 반영됩니다. 우측 창에는 선택된 위젯에 대한 자세한 속성이 출력되고 사용자는 마우스 클릭으로 편하게 위젯에 대한 설정을 할 수 있습니다.
보통 한 동영상의 자막 파일과 영상 파일의 이름을 똑같이 해 두면 동영상이 재생될 때 자막도 자동으로 출력됩니다. 대개 이 한 쌍의 파일은 잘 맞춰진 상태로 되어 있지만, 필시 누군가가 미리 둘의 이름을 맞추어 놓았을 것입니다. 저는 자막 파일을 자막 제작자들의 블로그나 홈페이지에서 직접 구하는 편이라 영상 파일과 자막 파일의 이름이 서로 다를 때가 대부분입니다.
처음에는 둘의 이름을 맞추기 위해 탐색기를 열고, 영상 파일의 이름을 찾아, 이름을 복사해(F2→Ctrl+C), 자막 파일도 찾아, 붙여넣습니다(F2→Ctrl+V). 상당히 번거로웠습니다. 많은 목록 중 한 파일을 찾아는 것도 헷갈립니다. Ctrl+V를 눌러야 하는데, Ctrl+C를 한 번 더 누른다든지 실수도 있습니다. 파일을 선택해서 F2키를 눌렀을 때 선택된 파일 이름의 범위를 다시 조정하는 것도 귀찮았습니다. 비스타나 7부터는 그나마 아래 그림처럼 확장자 부분을 선택하지 않으니 좀 낫지만요, XP 때는 파일의 이름과 확장자가 전부 선택되었죠.
하루에 1~2회정도 뿐이니 조금 불편해도 그냥 이대로 써도 되지만, 프로그래밍에 대해 눈을 뜨니 이런 불편을 계속 감수하고 쓰기 싫어지더군요. 게다가 별로 어려운 작업도 아닌 듯하니 직접 만들어보면 어떨까 하는 생각이 들었습니다. 그래서 처음엔 MFC 기반으로 아주 간결하게 프로토타입을 만들게 되었습니다.
사용해본 결과 프로그램 자체는 매우 간단하지만 개인적으로 매우 요긴하고 유용한 툴이었습니다. 동영상과 자막의 이름을 맞출 때마다 사용하니 한 번에 오랜 시간을 이용하는 것은 아니지만, 매우 빈번히 이용하게 되더군요. 결국엔 프로토타입에 자잘한 기능을 좀 더 넣어본 SubRenamer를 다시 제작해보기도 하고, 근 3년간 계속 사용 중입니다.
단순하기 짝이 없습니다.
궁극적으로 이 프로그램은 도스 명령어
ren a.smi b.smi
를 하는 겁니다. 단지 a, b 부분을 일일이 찾아 입력하기 귀찮으니 부려 보는 하찮은 꼼수입니다. 대량의 파일에 대한 이름 변경은 전문적인 툴이 따로 있으니, 한 시즌이나 전 에피소드 전부에 대해 작업을 하려면 그쪽을 사용하면 됩니다. 이 프로그램은 단지 한번에 1~2개의 동영상, 자막 쌍을 맞추는 용도로 사용할 것입니다. 결코 복잡하거나 어려운 일을 하지 않습니다.
다음 그림은 SubRenamer의 UI 그림입니다. 편의상 파이썬 기반으로 완성된 것으로 삽입하였습니다.
이제 wxGlade를 이용해 UI 디자인을 시작해 보겠습니다. wxGlade를 열어 아무 것도 없는 초기 상태로 만듭니다. pySubRenamer는 단순하게 다이얼로그 창으로 되어 있습니다. 트리 창에서 'Application'을 선택하고 메인 창에서 1열 2행에 있는 'Add a Dialog/Panel' 아이콘()을 클릭합니다. 아래와 같은 창이 나오는데, dialog를 선택하고 클래스 이름은 적당히 지어 줍니다.
이렇게 하면 다이얼로그에 대한 디자인 창이 하나 더 생성됩니다. Properties 창의 내용이 바뀐 것에 주목하세요. Properties 창에 'common' 탭을 클릭한 후 'Name'을 'mainDialog'로 변경합니다. 이제부터는 여기에 WYSIWYG 방식으로 위젯을 채워 넣기만 하면 됩니다. 우선 필요한 위젯을 차곡차곡 채워넣는 작업부터 시작하도록 하지요.
프로그램은 5개의 StaticText 위젯(, 3행 4열), 3개의 TextCtrl 위젯(, 2행 5열), 2개의 ListCtrl 위젯(, 5행 5열), 4개의 ButtonCtrl 위젯(, 2행 1열)과 1개의 CheckboxCtrl(, 3행 5열)이면 충분해 보입니다. 그러나 이들 위젯만으로는 기대한 결과가 나오지 않습니다. 완성된 UI 모습처럼 나오려면 위젯들의 레이아웃(layout)이 설정되어야 합니다. 레이아웃을 결정하는 위젯은 눈에 보이지 않지만 따로 존재합니다. 바로 BoxSizer 위젯(, 7행 2열)입니다. GridSizer 위젯(, 7행 3열)도 있지만, 이것은 현재 UI에 적합하지 않습니다.
BoxSizer는 사각형의 레이아웃 장치로, 수직/수평 중 한 방향으로 위젯을 나열할 때 사용합니다. Sizer를 이용하면 위젯의 위치를 편하게 맞출 수 있습니다. 그리고 Sizer는 창의 크기에 알맞게 위젯의 크기를 관리해줍니다. 보통 상대적인 순서대로 위젯이 나열됩니다. 물론 좌표값으로 위젯을 나열하는 것도 가능합니다만 특별한 이유가 아니라면 상대적으로 나열하는 것이 낫습니다. 눈에 보이지는 않지만 우리가 원하는 대로 UI를 나열하려면 다음과 같이 BoxSizer를 우선 나열하고, 그 안에 위젯을 적절히 삽입해야 합니다.
그러면 우선 그림의 붉은 색과 푸른 색 사각형으로 그린 수평/수직 BoxSizer를 삽입하여 레이아웃을 맞추어 봅니다.
BoxSizer 아이콘을 클릭하고 Design 창으로 커서를 가져다대면 모양이 십자(+)로 변합니다. 'Select sizer type'이란 창에서 Sizer의 기본 속성을 지정해 줄 수 있습니다. 설계안대로 Sizer를 삽입합니다.
Sizer만 나열하고 나면 Tree와 Design은 아래 그림과 같이 될 것입니다.
각 슬롯에 정해진 나머지 위젯들을 배열합니다. 위젯 아이콘을 클릭한 후 지정된 Sizer의 슬롯에 마우스 커서를 가져가면 커서가 십자로 변합니다. 그 상태에서 클릭하면 슬롯마다 위젯이 삽입됩니다. 모든 위젯의 삽입이 끝나면 아래 그림처럼 됩니다. 아직은 뒤죽박죽입니다. 그럴듯한 모습이 되기 위해서는 각 위젯마다 알맞은 속성을 지정해 주어야 합니다. 우선은 각 위젯의 이름을 그림과 같이 변경해둡니다.
이제 각 위젯마다 세세하게 속성을 지정하여 맵시있게 모양을 다듬습니다. 아래 표는 각 위젯의 수정된 속성을 정리한 것입니다.
위젯 이름 | 속성 탭 | 키워드 | 값 |
---|---|---|---|
mainDialog (MyDialog) | Common | Name | mainDialog |
Size | 800, 600 | ||
Widget | Title | pySubRenamer | |
pathSizer | Common | Border | 2, wxALL |
Alignment | wxEXPAND, wxALIGN_CENTER_VERTICAL | ||
labelPath | Layout | Alignment | wxALIGN_CENTER_VERTICAL |
Widget | Label | 루트 경로: | |
textCtrlPath | Layout | Proportion | 1 |
Border | 3, wxLEFT, wxRIGHT | ||
Alignment | wxEXPAND | ||
Widget | Style | wxTE_READONLY | |
buttonPath | Widget | Label | 경로 지정... |
Events | Event | EVT_BUTTON, OnButtonPath | |
labelMovie | Layout | Border | 4, wxALL |
Widget | Label | \n자막이 없는 영상 파일: | |
listCtrlMovie | Layout | Proportion | 7 |
Border | 2 wxALL | ||
Alignment | wxEXPAND | ||
Widget | Style | wxLC_REPORT | |
Events | Event | EVT_LIST_ITEM_SELECTED, OnListCtrlMovie | |
labelSubtitle | Layout | Border | 4, wxALL |
Widget | Label | \n영상이 없는 자막 파일: | |
listCtrlSubtitle | Layout | Proportion | 7 |
Border | 2, wxALL | ||
Alignment | wxEXPAND | ||
Widget | Style | wxLC_REPORT | |
Events | Event | EVT_LIST_ITEM_SELECTED, OnListCtrlSubtitle | |
movieSizer | Layout | Proportion | 1 |
Border | 2, wxALL | ||
Alignment | wxEXPAND | ||
labelChosenMovie | Layout | Alignment | wxALIGN_CENTER_VERTICAL |
Widget | Label | 선택된 영상 파일: | |
textCtrlChosenMovie | Layout | Proportion | 1 |
Alignment | wxEXPAND | ||
Widget | Style | wxTE_READONLY | |
subtitleSizer | Layout | Proportion | 1 |
Border | 2, wxALL | ||
Alignment | wxEXPAND | ||
labelChosenSubtitle | Layout | Alignment | wxALIGN_CENTER_VERTICAL |
Widget | Label | 선택된 자막 파일: | |
textCtrlChosenSubtitle | Layout | Proportion | 1 |
Alignment | wxEXPAND | ||
Widget | Style | wxTE_READONLY | |
footerSizer | Layout | Proportion | 1 |
Border | 2, wxALL | ||
Alignment | wxEXPAND, wxALIGN_RIGHT | ||
checkboxRecursive | Layout | Proportion | 1 |
Border | 2, wxALL | ||
Alignment | wxALIGN_CENTER_VERTICAL | ||
Widget | Label | 하위 폴더도 검색 | |
Events | Event | EVT_CHECKBOX, OnCheckBoxRecursive | |
buttonScan | Widget | Label | 영상/자막 검사 |
Events | Event | EVT_BUTTON, OnButtonScan | |
buttonAccept | Widget | Label | 자막 파일 변환 |
Events | Event | EVT_BUTTON, OnButtonAccept | |
buttonExit | Widget | Label | 종료 |
Events | Event | EVT_BUTTON, OnButtonExit |
모든 속성을 입력하고 나면 mainDialog를 선택합니다. Common 속성 아래에 'Preview' 버튼이 있습니다. 클릭하면 지금까지 설계한 다이얼로그의 미리 보기가 가능합니다. 앞서 나온 SubRenamer의 UI 그림과 비슷한 창이 출력될 것입니다.
모든 위젯의 배치와 속성이 알맞게 수정되었다면 이제는 파이썬 코드로 결과를 출력해야 합니다. Tree 창에서 Application을 선택한 후 Properties 창 안의 내용을 다음과 같이 수정합니다.
탭 | 키워드 | 값 |
---|---|---|
Application | Name | pySubRenamer |
Encoding | cp949 (적절히 변경) | |
Top | mainDialog | |
Language | python | |
Output path | 적절히 변경 |
'Generate code' 버튼을 눌러 파이썬 코드를 출력해냅니다.
출력한 후 바로 코드를 실행해 올바르게 창이 동작하는지 확인해 봅니다. 핸들러 함수는 지정했지만, 함수 내부를 작성하지 않았기 때문에 콘솔 창을 통해 미리 지정된 에러 메시지가 출력될 것입니다. 어쨌든 이벤트 핸들러가 동작하는 것만은 확인할 수 있습니다. 이제부터는 원하는 동작을 하도록 핸들러 함수의 내부를 직접 수정해야 합니다. wxGlade가 함수 내부까지 작성해 줄 수는 없습니다. 이 부분부터는 wxPython, wxWidgets API에 대해 어느 정도 이해를 하고 있어야 합니다. 이것은 이 문서에서 다루기 어려운 부분입니다. 각자 wxWidgets의 문서를 찾아가면서 해결해야 하는 부분입니다.
또한 각 핸들러의 역할은 자명하므로 세세한 설명은 생략하도록 하겠습니다. 핸들러의 상세한 구현은 최종 소스를 참고하기 바랍니다. 완성되어 안정적으로 동작하는 소스는 확장자를 pyw로 변경해도 됩니다. 이렇게 하면 콘솔 윈도우가 생성되지 않습니다.
하는 일이 단순하니 코어라고 해 봐야 그리 길지 않습니다. 이 부분은 MFC 기반으로 작성할 때 MFC로도, Boost 라이브러리를 이용해서도 작성해 보았습니다. 속도는 역시 C++가 더 낫지만 코드의 간결함은 파이썬을 따라올 수 없었습니다. 코어 함수가 하는 일은 간단합니다. 지정된 디렉토리의 파일의 이름을 보고 동영상 파일이면 같은 이름의 자막 파일이 있는지 확인하고, 자막 파일이면 같은 이름의 동영상 파일이 있는지를 확인합니다. 함수는 2개의 리스트를 반환하는데, 하나는 짝이 되는 자막이 없는 동영상 파일의 리스트, 다른 하나는 짝이 되는 동영상이 없는 자막 파일의 리스트입니다. 최종 구현 파일을 참고하기 바랍니다.
본 문서에서는 다이얼로그 창 형태의 동영상과 자막의 쌍을 맞추는 아주 단순한 GUI 툴을 디자인하고 제작해 보았습니다.
파이썬의 간결함에 다시 한 번 놀랬습니다. 같은 프로그램을 C++ MFC로도, 파이썬으로도 작성해 보았습니다. 코드 양이 이 정도로 적어질줄은 상상도 하지 못했습니다. 코어 부분은 주석까지 포함해 100줄이 되지 않았고, wxGlade가 생성해준 UI 코드 또한 핸들러와 추가 코드를 포함하여 230줄 내외로 작성되었습니다. C/C++ MFC GUI 코딩보다 훨씬 간결합니다. wxGlade를 처음 사용할 때는 매우 어색해서 이걸로 어떻게 결과를 만들어낼지 막막했는데, 어느 정도 요령이 생기니 꽤 쓰기 편했고 수월하게 결과물을 만들 수 있었습니다.
물론 일정 수준 이상의 GUI 프로그래밍을 하려면 여러 지식이 필요합니다만, 대개의 GUI 프로그램은 복잡한 알고리즘이나 매우 어려운 테크닉을 요구하지는 않습니다. 보통은 API를 어떻게 사용해야 하는지 이해하는 것이 먼저입니다. 그러나 수많은 사항을 일일이 기억하기는 어려우므로 기본이 되는 사항만 일단 잘 숙지한 후 세부적인 사항은 문서를 참고하면서 익숙해지는 것이 요령이라면 요령입니다. 본 문서가 그 시작에 도움이 되었으면 하는 바람입니다.
최종 결과 코드를 ZIP 파일로 압축하였습니다. 총 3개의 파일이 안에 있습니다. 프로그램을 실행하려면 wxPython이 필요합니다.