
EPUB 제작은 “문서를 변환해 .epub 확장자를 붙이는 일”이 아니다. 원고, 이미지, 목차, 메타데이터, 읽기 순서, 압축 구조가 서로 맞아야 하는 작은 웹사이트 패키징 작업에 가깝다. 변환 도구가 만든 첫 결과물은 초안이고, 검증과 수동 점검을 통과한 파일이 최종본이다.
얼마전 송도에서 열렸던 Vibe!Dive! 컨퍼런스에서 한빛 에디터님의 발표를 듣고 이전에 나 역시 책을 쓰고 epub 를 만들며 레퍼런스가 거의 없고 옛날 방식의 툴을 사용하는 방식이라 시행착오를 거쳤던 과정들이 생각나 이번 기회에 한번 정리해본다.
이 글은 특정 책의 제목·저자·출판사·ISBN·로컬 경로를 쓰지 않는다. 아래의 도서명, 저자명, 출판사명, ISBN은 모두 채워 넣을 자리표시자다. 공공기관 지원사업이나 공모전 제출 전에는 반드시 해당 연도 공고문과 검수 안내가 이 글보다 우선한다.
원고·권리·표지 준비
↓
초안 EPUB 변환 또는 XHTML 패키지 생성
↓
Codex 구조 점검과 최소 수정
↓
EPUBCheck 자동 검증
↓
Sigil 수동 점검 + 실제 리더 확인
↓
공고문 제출 규격·권리·증빙 대조
↓
읽기 전용 최종 EPUB 보관·제출
중요한 원칙은 하나다. 생성용 작업 폴더, 검수용 복사본, 제출용 최종 파일을 분리한다. 자동 수정과 Sigil의 복구 기능을 원본에 바로 적용하면 원인을 추적하기 어렵고, 나중에 재제작하기도 힘들어진다.
EPUBCheck는 EPUB의 패키지 구조, XHTML, OPF, 링크, 목차, 미디어 타입 등을 검사하는 표준 검증 도구다. macOS에서 먼저 Java가 있는지 확인한다.
java --version
명령을 찾지 못하면 Java 런타임을 설치한다. Homebrew를 이미 쓰는 경우에는 다음처럼 설치할 수 있다.
brew install --cask temurin
java --version
EPUBCheck는 Homebrew로 설치하는 방법이 가장 간단하다.
brew install epubcheck
epubcheck --version
Homebrew를 쓰지 않는다면 EPUBCheck 공식 배포 ZIP을 내려받아 압축을 풀고, 그 폴더의 epubcheck.jar와 lib/ 디렉터리를 함께 보관한다.
mkdir -p "$HOME/Tools/epubcheck"
cd "$HOME/Tools/epubcheck"
# 공식 Releases에서 내려받은 epubcheck-버전.zip 파일을 이 폴더에 둔 뒤
unzip epubcheck-버전.zip
java -jar epubcheck.jar --version
공식 문서는 macOS Homebrew 설치와 Java 기반 실행 방법을 함께 안내한다. EPUBCheck 설치 안내
Sigil은 EPUB을 열어 목차, XHTML, CSS, 이미지, 메타데이터를 사람이 직접 보는 편집기다. 자동 검사를 대체하지 않고, 자동 검사가 못 잡는 화면·목차·읽기 흐름 문제를 잡는 마지막 관문으로 쓴다.
Applications로 옮긴다.Sigil의 macOS 지원 버전은 릴리스마다 바뀔 수 있으므로 설치 전 공식 다운로드 페이지를 확인한다. 현재 공식 페이지는 macOS용 Intel/Arm64 설치 파일과 서명 확인 방법을 제공한다.
DOCX·HTML·ODT 같은 원고를 초안 EPUB으로 바꾸거나, 생성한 EPUB을 별도 리더에서 열어 볼 때 쓴다. Calibre 공식 macOS 다운로드에서 앱을 설치한 뒤 응용 프로그램 폴더로 옮긴다.
macOS의 ebook-convert는 보통 앱 번들 안에 있다. 그래서 다음처럼 절대 경로로 먼저 실행하면 PATH 설정 없이도 된다.
"/Applications/calibre.app/Contents/MacOS/ebook-convert" \
"원고.docx" "초안.epub" \
--title "도서명" \
--authors "저자명" \
--language ko \
--cover "cover.jpg"
위 명령은 초안을 얻기 위한 시작점이다. 변환 결과의 제목 구조, CSS, 목차가 그대로 제출 기준을 만족한다고 가정하면 안 된다. Calibre의 macOS CLI 위치는 공식 문서에서 확인할 수 있다.
Markdown에서 HTML/XHTML 초안을 만들고 싶을 때만 설치한다.
brew install pandoc
pandoc --version
Pandoc은 원고 변환 도구일 뿐 EPUB 최종 검수 도구가 아니다. 변환 뒤의 EPUBCheck와 수동 검수는 생략하지 않는다.
my-book/
├── source/ # 원고·이미지 원본, 수정 금지 백업
├── work/ # 압축을 풀어 실제로 편집하는 EPUB 구조
├── review/ # Sigil이 수정해도 되는 검수 복사본
├── dist/ # 최종 EPUB만 보관
├── evidence/ # EPUBCheck 결과·스크린샷·체크리스트
└── README.md # 버전, 변경 내역, 검수 결과
source/는 손대지 않는다. work/에서 고치고, 패키징 결과를 dist/에 새 파일명으로 만들며, Sigil은 review/의 복사본만 연다. 그래야 “Sigil이 자동으로 고친 뒤 무엇이 바뀌었는지 모르는” 사고를 피할 수 있다.
기존 EPUB을 작업 폴더로 풀 때는 다음처럼 한다.
mkdir -p work
unzip "source/초안.epub" -d work
압축을 풀었을 때 가장 바깥에는 보통 다음이 있어야 한다.
mimetype
META-INF/container.xml
OEBPS/ # EPUB 3 계열에서 흔한 콘텐츠 폴더 예시
일부 EPUB 2 프로젝트는 OEBPS/ 없이 XHTML·OPF·NCX가 최상위에 놓인다. 기존 구조를 억지로 섞거나 폴더명을 바꾸지 말고, META-INF/container.xml이 가리키는 OPF 위치를 기준으로 유지한다.
Codex는 원고를 읽고, 파일 관계를 대조하고, 반복 수정을 정확히 처리하는 데 좋다. 하지만 한 번에 “공모전 통과용 EPUB 만들어줘”라고 던지면 원본 훼손과 과도한 자동 수정이 생길 수 있다. 아래처럼 읽기 → 계획 → 최소 수정 → 검증 순서로 시킨다.
이 EPUB 작업 폴더를 읽기 전용으로 점검해줘.
- META-INF/container.xml이 가리키는 OPF를 찾고
- OPF manifest/spine, 목차(NCX 또는 nav), XHTML 내부 링크를 서로 대조하고
- 이미지·CSS·폰트의 실제 파일과 manifest 누락을 목록화하고
- 수정하지 말고, 오류·위험도·수정 순서만 제안해줘.
특정 도서의 제목, 저자, 출판사, ISBN은 출력하거나 로그에 쓰지 마.
앞서 확인한 목록 중 다음 항목만 수정해줘.
1. 모든 XHTML의 xml:lang="ko"와 lang="ko" 누락
2. 목차가 가리키는 앵커와 실제 id 불일치
3. manifest에 없는 실제 패키지 파일 또는 manifest만 있고 없는 파일
원고 문장·표지 이미지·메타데이터 값은 바꾸지 말고,
수정 파일 목록과 각 변경 이유를 보고해줘.
수정 뒤 EPUBCheck를 실행하되, 실패하면 패키지 재생성 대신 오류 원인을 먼저 요약해줘.
Codex가 파일을 고쳐도 이 판단의 책임이 사라지지는 않는다.
mimetype: 첫 번째이며 압축하지 않는다EPUB ZIP의 첫 항목은 정확히 mimetype이어야 하고 압축하지 않는다. 내용은 아래 한 줄이다.
application/epub+zip
META-INF/container.xml: OPF의 안내판이 파일이 패키지의 OPF 위치를 가리킨다. OPF를 다른 폴더로 옮겼다면 여기와 함께 고쳐야 한다.
OPF는 세 역할을 함께 한다.
공모·유통 제출이라면 공고문이 요구하는 메타데이터 항목을 빠짐없이 채운다. 다만 연도별 공고가 ISBN·출판사명·표지 크기·EPUB 버전을 다르게 정할 수 있으므로, 이 글의 자리표시자 예시를 그대로 제출하지 않는다.
<dc:title>도서명</dc:title>
<dc:creator>저자명</dc:creator>
<dc:language>ko</dc:language>
<dc:identifier>식별자 또는 ISBN</dc:identifier>
EPUB 2 호환을 요구하는 경우 특히 XHTML 문법이 중요하다.
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ko" lang="ko">
<head>
<title>장 제목</title>
<link rel="stylesheet" type="text/css" href="../Styles/stylesheet.css"/>
</head>
<body>
<h1 id="chapter-01">1장. 장 제목</h1>
<p>본문은 문단 태그로 작성합니다.</p>
</body>
</html>
자주 깨지는 부분은 다음과 같다.
&를 &로 이스케이프하지 않음<h1>을 열고 </h2>로 닫음<br/> 연속으로 처리함h1~h6 제목이 없음lang 또는 xml:lang 누락현재 공고가 EPUB 2를 요구하면 toc.ncx, EPUB 3을 요구하면 XHTML navigation document(nav)가 기준이 된다. 변환기가 두 파일을 함께 만들었다고 해도 실제 spine 순서, 제목, 앵커가 일치하는지 확인한다.
목차 항목 → XHTML 파일 → #id 앵커 → 실제 h1~h6 제목
이 네 단계 중 하나라도 끊기면 자동 검사에 통과해도 독자에게는 깨진 목차가 된다.
패키징 전에는 반드시 작업 폴더에서 테스트하고, 제출용 파일은 새 이름으로 만든다. 아래 예시는 mimetype, META-INF, OEBPS 구조를 쓰는 EPUB 3형 프로젝트다.
cd work
mkdir -p ../dist
OUT="../dist/제출전_검수본.epub"
# 실수로 기존 최종본을 덮어쓰지 않게 막는다.
test ! -e "$OUT" || { echo "이미 결과 파일이 있습니다: $OUT"; exit 1; }
# 1) mimetype을 첫 항목·무압축으로 넣는다.
zip -X0 "$OUT" mimetype
# 2) 나머지 파일을 압축한다.
zip -Xr9 "$OUT" META-INF OEBPS
최상위에 XHTML·CSS·OPF·NCX가 있는 EPUB 2형 프로젝트라면 기존 구조를 유지한다.
cd work
mkdir -p ../dist
OUT="../dist/제출전_검수본.epub"
test ! -e "$OUT" || { echo "이미 결과 파일이 있습니다: $OUT"; exit 1; }
zip -X0 "$OUT" mimetype
zip -Xr9 "$OUT" META-INF *.xhtml *.css *.opf *.ncx images
두 명령을 섞어 쓰지 않는다. 특히 mimetype을 나중에 넣거나 일반 압축으로 넣는 실수가 많다.
패키지 첫 항목은 바로 확인한다.
zipinfo -1 "$OUT" | head -n 1
# 기대값: mimetype
epubcheck "dist/제출전_검수본.epub"
Homebrew 설치가 아니라 JAR을 쓰는 경우에는 다음처럼 실행한다.
java -jar "$HOME/Tools/epubcheck/epubcheck.jar" "dist/제출전_검수본.epub"
제출용 목표는 오류 0건, 경고 0건이다. “리더에서 열린다”는 말은 검증 결과가 아니다.
수정이 필요할 때는 다음 순서를 지킨다.
EPUBCheck 메시지 확인
↓
원본 EPUB이 아니라 work/의 원인 파일 수정
↓
새 파일명으로 재패키징
↓
EPUBCheck 재실행
↓
결과를 evidence/에 날짜와 함께 저장
오류 메시지를 무작정 정규식으로 일괄 치환하지 않는다. 한 번의 자동 치환으로 제목 닫는 태그나 내부 앵커를 넓게 망가뜨리는 일이 흔하다. 수정 전후의 파일 목록과 EPUBCheck 결과를 남기면 Codex도 다음 오류를 훨씬 정확하게 고칠 수 있다.
# XHTML과 OPF·NCX가 XML로 잘 닫혔는지 확인
xmllint --noout work/OEBPS/Text/*.xhtml
xmllint --noout work/OEBPS/package.opf
# 작업 구조가 최상위 EPUB 2형이라면 경로를 그 구조에 맞춘다.
xmllint --noout work/*.xhtml work/*.opf work/*.ncx
이 검사는 모든 EPUB 규격을 대체하지 않는다. XML 문법 실수를 빨리 찾는 용도이고, 최종 판정은 EPUBCheck다.
EPUBCheck가 통과해도 독서 화면은 망가질 수 있다. 그래서 최종본을 Sigil용 복사본으로 만들고 다음 순서로 확인한다.
cp "dist/제출전_검수본.epub" "review/수동점검본.epub"
review/수동점검본.epub만 Sigil로 연다. source/나 dist/ 원본을 바로 열지 않는다.| 확인 항목 | 통과 기준 |
|---|---|
| 표지 | 잘리지 않고, 세로·가로 화면에서 제목이 읽힘 |
| 목차 | 모든 항목이 실제 장·절의 제목 위치로 이동 |
| 제목 | 장·절의 읽기 계층이 자연스럽고 이미지 제목도 텍스트 제목을 가짐 |
| 본문 | 긴 빈 페이지, 문단 겹침, 과도한 줄바꿈이 없음 |
| 이미지·표 | 작은 화면에서 핵심 글자가 읽히고 잘리지 않음 |
| 링크 | 외부 링크·각주·내부 링크가 의도한 위치로 이동 |
| 스타일 | 한 장만 폰트·여백·정렬이 깨지지 않음 |
Sigil에서 수정했다면 그 파일을 곧바로 제출하지 않는다. 다시 work/에 변경점을 반영하고, 새 EPUB를 패키징한 뒤 EPUBCheck를 다시 실행한다. Sigil은 화면 점검과 구조 보조 도구이지, 검증 기록을 대신 만드는 도구가 아니다.
가능하면 Apple Books, Calibre Viewer처럼 서로 다른 리더 한 곳에서도 열어 본다. 리더별 CSS 지원이 달라서 한 프로그램에서만 보던 문제를 발견할 수 있다.
기술 검수 통과는 선정·통과를 보장하지 않는다. 공모전은 파일 규격 외에도 공고 적합성, 권리, 제출 서류, 납기, 품질을 본다. 따라서 제출 직전에 아래 세 묶음을 분리해 체크한다.
이 항목은 해마다 바뀌므로 과거 공고나 다른 기관의 체크리스트를 복사해 쓰지 않는다. 현재 공고문을 체크리스트의 첫 줄에 붙인다.
mimetype이 첫 항목이며 무압축인지 확인했는가shasum -a 256 "dist/제출전_검수본.epub" > evidence/제출전_검수본.sha256
epubcheck "dist/제출전_검수본.epub" > evidence/epubcheck_결과.txt 2>&1
해시는 “내가 검증한 파일”과 “제출한 파일”이 같은지 나중에 확인하게 해 준다. 제목이나 저자명 같은 서지 정보가 노출될 수 있는 공용 스크린샷을 만들 필요는 없다.
다른 에이전트에게 무작정 전체를 고치게 하지 말고, 다음처럼 읽기 전용 최종 검토를 요청한다.
dist/ 안의 EPUB을 읽기 전용으로 최종 검토해줘.
- EPUBCheck 실행 결과를 요약하고
- zip 첫 항목이 mimetype인지 확인하고
- container.xml → OPF → manifest/spine → 목차 → XHTML 앵커 연결을 점검하고
- 개인정보·비밀값·로컬 절대 경로가 EPUB 본문이나 메타데이터에 들어갔는지 확인해줘.
파일을 수정·재패키징·외부 업로드하지 말고,
통과/보류와 보류 사유만 짧게 보고해줘.
이 검토가 끝나면 제출 파일을 다시 만들지 않는다. 수정이 필요할 때만 work/로 돌아가 새 버전을 만들고, EPUBCheck와 Sigil 점검을 처음부터 반복한다.
[ ] 원고·이미지·폰트의 권리와 공고 적합성 확인
[ ] 작업용·검수용·제출용 파일 분리
[ ] container.xml이 올바른 OPF를 가리킴
[ ] OPF의 metadata / manifest / spine 정합
[ ] XHTML의 XML 문법, lang, 제목 계층, 내부 링크 확인
[ ] 목차(NCX 또는 nav)와 실제 앵커·읽기 순서 일치
[ ] mimetype 첫 항목·무압축 패키징
[ ] EPUBCheck 0 오류·0 경고
[ ] Sigil에서 표지·목차·본문·이미지 수동 점검
[ ] 다른 리더 한 곳에서 재확인
[ ] 공고문 제출 규격·권리·서류 대조
[ ] EPUBCheck 결과와 최종 파일 SHA-256 보관
EPUB은 자동화하기 좋은 형식이지만, 제출 품질은 자동화만으로 나오지 않는다. Codex는 반복 검사와 파일 관계 정리에 쓰고, EPUBCheck는 규격에 쓰고, Sigil과 실제 리더는 사람 눈의 최종 검수에 쓰면 된다. 이 세 역할을 분리하면 “열리기는 하는 EPUB”이 아니라, 재현 가능하게 검증한 제출용 EPUB을 만들 수 있다.