콘티온 ContiOn TIL 2
자막 PPT를 뽑는 기능을 붙이면서 판단이 필요했던 것들을 모았다. PPT 용량, 콘티 안의 구간 묶기와 드래그, 링크 공유 카드, 글꼴 관리, 미리보기 순서다.
뽑은 PPT가 샘플보다 수십 배 컸다
가사 슬라이드마다 배경 그라데이션과 자막 바 이미지를 깔아 PPT를 뽑아 받아 보니 80장짜리 파일이 77.5MB였다. 원래 쓰던 샘플 템플릿은 1MB도 안 됐다.
추측으로 고치지 않고 같은 이미지를 두 방식으로 넣어 크기부터 쟀다.
| 방식 | 80장 기준 |
|---|---|
| 슬라이드마다 삽입 | 77.5MB |
| 슬라이드 마스터에 한 번 | 1.38MB |
PPT를 만드는 라이브러리(pptxgenjs)는 같은 이미지라도 슬라이드마다 따로 저장한다. 80장이면 배경 80벌, 바 80벌이다. 슬라이드 마스터(모든 슬라이드가 공유하는 밑판)에 한 번만 두면 슬라이드는 그걸 가리키기만 하므로 한 벌만 들어간다. 샘플 템플릿이 가벼웠던 것도 같은 이유였다.
확인도 눈으로 할 수 있었다. pptx는 사실 zip 파일이라 풀어 보면 안에 이미지가 몇 개 들었는지 보인다. 고친 뒤 80장짜리 안에 이미지는 두 개뿐이었다.
곁들여 두 가지를 더 줄였다. 가로로만 색이 변하는 그라데이션은 세로로는 변화가 없으니 높이 2px짜리 띠로 만들어 늘려 써도 화면은 같다. 그리고 곡 사이 빈 슬라이드처럼 자막 바가 없어야 하는 장은 마스터를 하나 더 두어 골라 쓰게 했다.
“같은 그림이니 알아서 합쳐지겠지”는 도구마다 다르다. 용량 문제는 원인을 짐작하기 전에 두 방식을 나란히 재 보면 숫자가 답을 준다.
뽑은 파일만 보고는 최종본인지 알 수 없었다
뽑은 PPT는 앱 밖에서 메신저로 돌아다닌다. 받은 사람은 앱을 열지 않으니, 그 파일이 승인된 콘티에서 나온 것인지 아직 고치는 중인 것인지 알 길이 없다. 그래서 승인되지 않은 콘티를 뽑으면 파일명 끝에 「(미확정)」을 붙이게 했다.
앱 안의 상태는 파일이 앱을 떠나는 순간 사라진다. 밖으로 나가는 결과물에는 알아야 할 상태를 같이 실어 보낸다.
곡을 구간별로 묶고 싶었다
콘티(곡 순서표) 안에서 곡을 앞부분·본 순서 같은 구간으로 묶어 보여 주고 싶었다. 이 묶음(그룹)마다 PPT에는 구간 이름이 적힌 안내 슬라이드가 한 장 들어간다.
묶음이라고 하면 “그룹 안에 곡들이 들어 있는” 중첩 구조가 먼저 떠오른다. 그런데 최종 결과물은 한 줄로 늘어선 슬라이드다. 중첩으로 들고 있으면 저장하든 내보내든 매번 한 줄로 펴야 한다.
그래서 저장은 항목들의 순서 목록 하나로 두고, 그중 일부를 구분자(그룹 머리글 역할을 하는 항목)로 두기로 했다. 그룹의 범위는 규칙으로 읽는다 — 구분자부터 다음 구분자 직전까지.
원본 : [앞부분] a b [본순서] c d
그룹 본순서 → 맨앞 : [본순서] c d [앞부분] a b ← 소속 곡이 따라옴
곡 c → 앞부분 : [앞부분] a b c [본순서] d ← 그룹 사이 이동
평면으로 두니 까다로울 줄 알았던 경우들이 저절로 풀렸다. 첫 구분자 앞에 있는 곡은 그냥 “그룹 없는 곡”이고, 머리글만 지우면 그 곡들은 앞 그룹에 흡수된다. 중첩이었다면 부모 잃은 곡을 따로 처리해야 했다. 곡 하나를 옮기는 것과 그룹을 통째로 옮기는 것도 “길이가 1인 덩어리냐 여럿인 덩어리냐”의 차이일 뿐이다.
결과물의 모양이 저장 구조를 정한다. 끝에 한 줄로 나갈 것이면 처음부터 한 줄로 들고, 묶음은 규칙으로 읽는 편이 단순하다.
끌어서 옮기는데 원하는 자리에 안 들어갔다
그룹을 붙인 뒤 직접 드래그로 순서를 바꿔 보니 세 가지가 불편했다.
- 항목 위에 커서를 올리면 무조건 그 항목 앞에 들어갔다. 원하는 자리를 정확히 집을 수 없었다.
- 맨 끝으로 옮길 방법이 없었다. 마지막 항목에 놓아도 그 앞에 들어갔다.
- 끄는 동안 항목들이 벌어지며 밀려서, 커서 아래 항목이 계속 바뀌어 조준이 흔들렸다.
1번과 2번은 같은 뿌리였다. “어느 항목 위에 있나”만 보면 앞인지 뒤인지 알 수 없다. 항목의 위쪽 절반이면 앞, 아래쪽 절반이면 뒤로 보면 앞뒤가 갈리고, 마지막 항목의 아래쪽 절반은 자연스럽게 “맨 끝”이 된다. 맨 끝 전용 놓기 영역을 따로 만들 필요가 없었다.
3번은 표시가 자리를 차지해서 생겼다. 놓일 자리를 보여 주려고 틈을 벌리면 그만큼 항목이 밀리고, 커서 아래 항목이 바뀌고, 다시 다른 틈이 벌어진다. 표시는 레이아웃을 밀지 않고 항목 사이에 선만 겹쳐 그려야 한다.
자막 편집 화면에서도 같은 일이 있었다. 글씨 크기를 바꾸면 “기본값” 버튼이 새로 나타나면서 아래 버튼들이 밀렸다. 버튼을 없앴다 만들었다 하지 않고 자리는 늘 지키되 바꿀 게 없을 때만 눌리지 않게 두니 흔들림이 사라졌다.
조작하는 도중에 화면이 움직이면 손이 겨냥하던 곳이 바뀐다. 상태를 보여 주는 표시는 자리를 차지하지 않게 한다.
곁들여, 목록이 길면 화면 밖으로는 끌고 갈 수가 없어서 커서가 가장자리에 오면 저절로 스크롤되게 했다. 이때 목록 자체가 스크롤되는 영역이면 창이 아니라 목록을 굴려야 한다.
링크를 보내면 카드가 떠야 했다
메신저에 콘티온 링크를 붙이면 제목과 그림이 든 미리보기 카드가 뜨게 하고 싶었다. 메신저는 링크를 받으면 그 페이지를 한 번 가져가 og 태그(링크를 미리보기 카드로 만들 때 읽어 가는 제목·설명·이미지 정보)를 읽는다. 아무것도 없으면 카드도 없다.
카드 이미지는 앱 화면을 캡처하지 않고 디자인 카드로 따로 만들었다. 실제 화면에는 곡 목록과 사람 이름이 나오는데, 미리보기 카드는 로그인하지 않은 사람에게도 보이기 때문이다. 로그인 벽이 있는 도구라도 카드는 벽 바깥에 있다.
이미지 주소는 전체 주소로 적어야 한다. 카드를 만드는 쪽은 남의 서버라 우리 사이트 기준의 상대경로를 풀어 주지 않을 수 있다. 크기는 1200×630이 사실상 표준이다.
검색에는 안 잡히고 카드는 뜨게
닫힌 팀이 쓰는 도구라 검색 결과에 주소가 뜨는 건 원하지 않았다. 그런데 막는 방법에 따라 방금 만든 카드까지 같이 사라질 수 있었다.
- noindex(페이지 안에 “검색 결과에서 빼 달라”고 적어 두는 표시)는 부탁이다. 규칙을 지키는 검색엔진이 알아서 결과에서 뺀다. 어차피 로그인 벽이라 내용은 못 보니, 막고 싶은 건 주소가 결과에 뜨는 것뿐이었고 이걸로 충분했다.
- robots.txt(사이트 전체에 대해 “가져가지 마라”를 적는 파일)로 막으면 일부 미리보기 봇도 이 규칙을 따라 페이지를 아예 안 읽는다. 그러면 카드도 안 뜬다.
| 읽는 것 | noindex를 따르나 | |
|---|---|---|
| 검색엔진 봇 | 페이지 전체 | 따른다 → 결과에서 빠짐 |
| 링크 미리보기 봇 | og 태그 | 안 따른다 → 카드는 그대로 |
그래서 noindex 쪽을 골랐다. 더 세게 막는 게 늘 나은 게 아니라, 막고 싶은 것만 막는 방법을 골라야 원하는 것이 같이 안 막힌다.
미리보기 봇은 결과를 한동안 저장해 둔다. 이미 공유했던 링크는 예전 카드가 뜰 수 있다.
글꼴 목록을 코드에 둘까 DB에 둘까
PPT에 쓸 글꼴을 고르는 목록이 필요했다. 목록을 코드에 적어 둘지, DB에 두고 화면에서 고치게 할지 정해야 했다. 앞선 TIL 1에서 곡 구분 목록을 DB로 옮긴 것과 같은 갈림길이라, 이번에는 그 기준을 말로 정리해 뒀다.
기준은 “얼마나 자주 바뀌나”가 아니라 “바뀔 때 누가 붙어야 하나”로 잡았다. 코드에 있으면 바꿀 때마다 누군가 코드를 고치고 다시 배포해야 한다. DB에 있으면 쓰는 사람이 화면에서 직접 바꾼다.
1년에 한 번 바뀌어도 그때마다 만든 쪽이 불려 와야 한다면 DB로 뺄 이유가 된다. 반대로 매주 바뀌어도 그게 동작 규칙의 일부라면 코드에 두는 게 맞다.
이번 건은 명확했다. 콘티온은 팀에 넘기고 손을 뗄 도구다. 코드에 남은 목록 하나가 나중에 다시 불려 오는 고리가 된다. 그래서 DB로 뺐다.
글꼴 파일을 통째로 올리고 싶었다
목록만이 아니라 글꼴 파일 자체를 올려 두고 싶었다. 두 가지에 막혔다.
- PPT에는 글꼴 파일이 들어가지 않는다. 쓰는 라이브러리가 글꼴 이름만 적어 둔다. 파일을 아무리 잘 보관해도 PPT는 달라지지 않고, 글꼴은 PPT를 띄우는 PC에 설치돼 있어야 한다.
- 둘 곳이 마땅치 않았다. 쓰고 있는 D1(Cloudflare가 제공하는 SQLite 기반 데이터베이스)은 값 하나가 2MB를 넘지 못하는데 한글 글꼴 파일은 보통 4~8MB다.
그래서 파일 대신 글꼴 이름과 받는 곳 주소만 저장하기로 했다. 실제로 필요한 일은 “PPT를 띄우는 PC 담당자가 글꼴을 구해 설치하는 것”이고, 그건 받는 곳만 알면 된다.
원래 하려던 방법이 막히면 그 방법이 무엇을 위한 것이었는지로 돌아가 본다. 파일을 보관하는 대신 어디서 받는지를 보관해도 필요한 동작은 다 된다.
쓰고 있는 글꼴을 지울 수 있었다
목록에 삭제 버튼을 달고 나니 구멍이 보였다. 지금 기본 템플릿이 쓰고 있는 글꼴을 지워 버리면, 템플릿에는 이름만 남고 그게 어디서 받는 글꼴인지 아무도 모르게 된다.
DB에는 쓰이는 항목을 함부로 못 지우게 묶는 장치(외래 키)가 있지만 여기서는 못 쓴다. 템플릿은 글꼴을 번호가 아니라 이름으로 들고 있기 때문이다(PPT에 들어가는 게 이름이라 그렇게 뒀다). 그래서 지우기 전에 쓰이고 있는지 따로 확인하고, 쓰이면 거절하게 했다.
화면에서는 그 글꼴의 삭제 버튼을 아예 누를 수 없게 두고 이유를 적었다. 그런데 서버에서도 똑같이 막는다. 화면은 사람이 보는 입구일 뿐이고, 요청은 화면을 거치지 않고도 보낼 수 있다. 화면만 막으면 그건 안내이지 방어가 아니다. 막아야 하는 일은 데이터를 받는 서버가 막는다.
받는 곳 주소는 누구나 넣는 값이다
같은 원리가 받는 곳 주소에도 걸렸다. 이 주소는 쓰는 사람이 입력하고, 화면에 “받기” 링크로 나가 다른 사람이 누른다. 누구든 넣을 수 있는 값이 남이 누를 링크가 되는 자리라, 어떤 주소를 받을지 서버가 걸러야 한다. 입력칸은 어디든 공격이 들어오는 입구가 될 수 있고, 그 값이 다른 사람 화면에 나가는 자리라면 특히 그렇다.
이 PC에 글꼴이 없다고 알려 주고 싶었다
PPT에는 글꼴 이름만 들어가니, 고른 글꼴이 지금 쓰는 PC에 없으면 PPT가 다른 글꼴로 뜬다. 그래서 템플릿을 고칠 때 “이 PC에는 이 글꼴이 없다”는 경고를 띄우기로 했다.
브라우저에 글꼴을 확인하는 기능이 있어 그대로 쓰면 될 줄 알았는데, 없는 이름을 넣어도 “있다”고 답했다. 그 기능이 답하는 질문은 “설치돼 있나”가 아니라 “이 지정으로 글자를 그릴 수 있나”였다. 글꼴이 없으면 브라우저는 폴백 글꼴(지정한 글꼴이 없을 때 대신 쓰는 기본 글꼴)로 그리니, 그릴 수는 늘 있다. 이름이 비슷해 보여도 묻는 것이 다른 질문이었다.
결국 결과를 보는 쪽으로 판정한다. 그 글꼴을 지정했을 때와 안 했을 때 글자 폭이 다르면 실제로 그 글꼴이 쓰인 것이고, 똑같으면 폴백된 것이다. 이때 재는 시점도 중요했다. 사이트가 함께 싣고 오는 글꼴은 필요해질 때 받아 오므로, 받기 전에 재면 없다고 나온다. 받은 뒤에 재야 한다.
그리고 확인이 끝나기 전에는 경고를 띄우지 않기로 했다. 두 방향의 오류는 비용이 다르다. 있는데 없다고 뜨면 멀쩡한 설정을 의심하고 뭘 잘못했나 찾게 된다. 없는데 잠깐 안 뜨는 건 곧 바로잡힌다. 경고의 기본값은 조용한 쪽에 둔다.
미리보기와 뽑힌 PPT가 달랐다
배경 그라데이션은 콘티온 안의 전용 편집기에서 고른다. 색 점을 막대 위에서 끌어 위치를 정하는 방식이다. 미리보기는 CSS로 그리고, 실제 PPT 배경은 캔버스(브라우저가 그림을 픽셀로 직접 그려 넣는 영역)에 구워 넣는다. 같은 값을 쓰니 같게 나올 줄 알았다.
색 점을 끌어 서로 지나치게 만들면 둘이 갈렸다. CSS는 뒤에 오는 점이 앞 점보다 왼쪽에 있으면 앞 점 자리로 눌러 버린다. 캔버스는 그런 보정 없이 받은 자리에 그대로 그린다. 같은 데이터가 두 곳에서 다른 그림이 된다. 미리보기가 결과와 다르면 미리보기가 있을 이유가 없다.
해결은 위치순 정렬인데 정렬하는 시점이 중요했다. 끄는 도중에 목록을 정렬하면 손에 잡고 있던 점의 순번이 바뀌어 드래그가 끊긴다. 그래서 손이 만지는 목록은 그대로 두고, 그릴 때와 저장할 때만 정렬한 사본을 쓴다. 조작 중인 상태와 결과로 나가는 상태를 나눈 것이다.
글씨 위치에서도 같은 일이 있었다. 크기와 좌표를 계산해 맞췄는데도 뽑은 PPT와 미리보기를 나란히 놓고 보니 미리보기 글씨만 살짝 위에 있었다. PPT 글상자는 줄 간격 1.5인데 미리보기는 사이트 전체의 줄 간격 1.6을 물려받고 있었다. 줄 높이가 다르면 세로 가운데 정렬의 기준이 달라진다.
미리보기를 결과와 맞춘다는 건 좌표와 크기만이 아니라 그리는 규칙까지 같게 두는 일이다. 결과를 뽑아 미리보기와 나란히 놓고 봐야 잡힌다.
기본 슬라이더를 버리니 키보드가 사라졌다
색 점을 원하는 모양으로 만들려고 브라우저가 원래 주는 슬라이더 대신 직접 만든 손잡이를 썼다. 모양은 원하는 대로 됐지만 공짜로 따라오던 것이 같이 사라졌다. 키보드로 조작할 수 없고, 화면 낭독기(화면의 내용을 소리로 읽어 주는 보조 도구)가 이게 무엇인지 모른다.
그래서 슬라이더라는 표시와 방향키 조작을 따로 붙였다. 붙이고 보니 방향키가 마우스보다 정확해서 88%처럼 딱 떨어지는 값을 맞출 때는 오히려 이쪽이 편했다.
기본 요소를 버리고 직접 만들면 모양만 가져오는 게 아니라 기본 요소가 하던 일까지 다시 만들어야 한다. 직접 만들지 정할 때 그 비용까지 같이 센다.
규격대로 된 원본을 주니 자르는 단계가 사라졌다
자막 바 이미지는 1920×1080짜리 그림에서 필요한 띠만 잘라 쓰고 있었다. 자를 위치를 비율로 계산하고, 잘라낸 것을 다시 그림으로 굽는 단계가 있었다.
자막 담당자에게 정확히 규격대로(1920×173) 만든 자막 바 원본을 받아 그대로 넘겼더니 이 과정이 통째로 필요 없어졌다. 자르지 않아도 되면 다시 구울 이유도 없고, 파일을 받은 그대로 넣으면 된다. 다시 구우면 원본과 미세하게 달라지는 문제도 같이 사라졌다.
재료를 결과물 규격에 맞춰 주면 그걸 맞추려고 만든 변환 단계가 없어진다. 코드로 보정하는 것보다 재료를 고치는 편이 짧을 때가 있다.
요약
- 같은 이미지라도 도구가 슬라이드마다 따로 저장할 수 있다. 반복되는 배경은 슬라이드 마스터에 한 번만 두고, 용량 문제는 짐작 전에 두 방식을 나란히 재 본다.
- 앱 밖으로 나가는 파일에는 상태(미확정)를 파일명으로 실어 보낸다.
- 결과물이 한 줄로 나가면 저장도 한 줄로 들고, 묶음은 “구분자부터 다음 구분자 직전까지”라는 규칙으로 읽는 편이 단순하다. 그룹 없는 곡, 머리글만 지우기가 저절로 풀린다.
- 드래그는 항목의 위·아래 절반으로 앞뒤를 가른다. 그러면 맨 끝이 특별한 경우가 아니게 된다. 조작 중에 뜨는 표시와 버튼은 자리를 차지하지 않게 한다.
- 로그인 벽이 있어도 미리보기 카드는 벽 바깥에 있다. 화면 캡처 대신 디자인 카드로 만든다.
- 검색만 막고 싶으면 noindex, robots.txt로 막으면 공유 카드까지 사라질 수 있다. 막고 싶은 것만 막는 방법을 고른다.
- 코드냐 DB냐는 얼마나 자주 바뀌나가 아니라 바뀔 때 누가 붙어야 하나로 가른다. 넘기고 손 뗄 도구라면 코드에 남은 목록 하나가 다시 불려 오는 고리가 된다.
- 원래 방법이 막히면 그 방법이 무엇을 위한 것이었는지로 돌아간다. 글꼴 파일 대신 받는 곳을 보관해도 필요한 동작은 된다.
- 화면만 막으면 안내일 뿐이고 방어는 서버에서 한다. 쓰이는 항목 삭제도, 남이 누를 링크가 될 주소 검사도 서버가 막는다.
- 확인 기능이 답하는 질문이 내가 묻는 질문과 같은지 먼저 본다. 경고의 기본값은 조용한 쪽이다.
- 미리보기와 결과는 좌표뿐 아니라 그리는 규칙까지 같아야 한다. 손이 만지는 상태와 결과로 나가는 상태는 나눠도 된다.
- 기본 요소를 버리고 직접 만들면 그 요소가 하던 일(키보드·화면 낭독기)까지 다시 만들어야 한다.
- 재료를 결과물 규격에 맞춰 주면 변환 단계가 사라진다.