ge-utils/indesign_script/image_to_epub.jsx

467 lines
19 KiB
React
Raw Permalink Normal View History

2025-07-18 11:25:05 +00:00
// InDesign 이미지 처리 및 EPUB 내보내기 자동화 스크립트
// 설정 파일에서 경로를 읽고, 이미지 파일들을 스캔하여 InDesign 문서 생성 후 EPUB으로 내보내기
CONFIG_FILE_PATH = "/Users/jeon-yeongjae/dev/info.txt"
EPUB_FOLDER_NAME = "epub"
LOG_FOLDER_NAME = "log"
// 설정 파일에서 루트 경로 읽기
function getRootPaths() {
try {
var configFile = new File(CONFIG_FILE_PATH);
var paths = [];
if (configFile.exists) {
configFile.open("r");
while (!configFile.eof) {
var line = configFile.readln();
var cleanedPath = line.replace(/\r\n|\r|\n/g, "").replace(/^\s+|\s+$/g, "");
if (cleanedPath) {
paths.push(cleanedPath);
}
}
configFile.close();
return paths;
} else {
throw new Error("Config file not found: " + CONFIG_FILE_PATH);
}
} catch (error) {
throw new Error("Error reading config file: " + error.message);
}
}
// 단위 변환 함수들
function mmToPoints(mm) {
return mm * 2.834645669;
}
function pointsToMM(points) {
return points / 2.834645669;
}
// 실행 시간 포맷팅
function formatElapsedTime(milliseconds) {
var seconds = milliseconds / 1000;
if (seconds < 60) {
return seconds.toFixed(1) + "s";
} else {
var minutes = Math.floor(seconds / 60);
var remainingSeconds = (seconds % 60);
return minutes + "m " + remainingSeconds.toFixed(1) + "s";
}
}
// EPUB 내보내기 실행 (DPI 300 설정 추가)
// EPUB 내보내기 실행 (DPI 300 설정 추가)
function exportToEPUB(root_path, document, logMessages) {
try {
logMessages.push("\n=== Starting EPUB Export ===");
// 내보내기 폴더 생성
var exportFolder = new Folder(root_path + "/" + EPUB_FOLDER_NAME);
if (!exportFolder.exists) exportFolder.create();
// 타임스탬프로 고유 파일명 생성
var now = new Date();
var timestamp = now.getFullYear() + "-" +
(now.getMonth() + 1).toString().replace(/^(\d)$/, "0$1") + "-" +
now.getDate().toString().replace(/^(\d)$/, "0$1") + "_" +
now.getHours().toString().replace(/^(\d)$/, "0$1") + "-" +
now.getMinutes().toString().replace(/^(\d)$/, "0$1") + "-" +
now.getSeconds().toString().replace(/^(\d)$/, "0$1");
var epubFileName = "ImageBook_" + timestamp + ".epub";
var epubFile = new File(exportFolder.fsName + "/" + epubFileName);
// EPUB 내보내기 설정
try {
var epubPrefs = document.epubFixedLayoutExportPreferences;
// DPI 300 설정
epubPrefs.imageExportResolution = ImageResolution.PPI_300
logMessages.push("Image resolution set to 300 DPI");
epubPrefs.epubNavigationStyles = EpubNavigationStyle.FILENAME_NAVIGATION; // 파일명 기반 네비게이션
logMessages.push("FILENAME_NAVIGATION resolution set to 300 DPI");
// 추가 이미지 품질 설정
// epubPrefs.imageConversion = ImageConversion.JPEG;
// epubPrefs.jpegOptionsQuality = JPEGOptionsQuality.HIGH;
// epubPrefs.imageAlignment = ImageAlignmentType.LEFT;
// epubPrefs.imageSpaceBefore = 0;
// epubPrefs.imageSpaceAfter = 0;
// TOC Navigation 설정 (파일명 기반)
// epubPrefs.generateTOC = true; // TOC 생성 활성화
// epubPrefs.includeDocumentTOC = false; // 문서 내 TOC 포함하지 않음
// epubPrefs.tocStyleName = "Heading 1"; // 생성된 Heading 1 스타일 사용
// epubPrefs.navigationTOCStyle = TOCStyleType.EPUB_TOC_STYLE; // Navigation TOC 스타일
// epubPrefs.tocTitle = "Contents"; // TOC 제목
// epubPrefs.tocDepth = 1; // TOC 깊이 (Heading 1만 사용)
// 고정 레이아웃 관련 설정
// epubPrefs.epubVersion = EPubVersion.EPUB3;
// epubPrefs.includeDocumentMetadata = true;
// epubPrefs.preserveLocalOverrides = true;
} catch (e) {
logMessages.push("Warning: Could not access all EPUB preferences: " + e.message);
logMessages.push("Proceeding with default settings...");
}
// 고정 레이아웃 EPUB으로 내보내기 (대화상자 없이, 자동 열기 없이)
document.exportFile(ExportFormat.FIXED_LAYOUT_EPUB, epubFile, false);
// 내보내기 완료 대기
$.sleep(3000);
// 내보내기 성공 확인
if (epubFile.exists) {
var fileSizeKB = Math.round(epubFile.length / 1024);
logMessages.push("EPUB export successful: " + epubFile.fsName);
logMessages.push("File size: " + fileSizeKB + " KB");
logMessages.push("EPUB file saved but NOT automatically opened");
return { success: true, filePath: epubFile.fsName };
} else {
return { success: false, error: "EPUB not created" };
}
} catch (error) {
return { success: false, error: error.message };
}
}
// 로그 파일 작성
function writeLog(root_path, message) {
try {
var logFolder = new Folder(root_path + "/" + LOG_FOLDER_NAME);
if (!logFolder.exists) {
logFolder.create();
}
// 타임스탬프로 고유 로그 파일명 생성
var now = new Date();
var timestamp = now.getFullYear() + "-" +
(now.getMonth() + 1).toString().replace(/^(\d)$/, "0$1") + "-" +
now.getDate().toString().replace(/^(\d)$/, "0$1") + "_" +
now.getHours().toString().replace(/^(\d)$/, "0$1") + "-" +
now.getMinutes().toString().replace(/^(\d)$/, "0$1") + "-" +
now.getSeconds().toString().replace(/^(\d)$/, "0$1");
var logFile = new File(logFolder.fsName + "/indesign_script_" + timestamp + ".log");
logFile.encoding = "UTF-8";
logFile.open("w");
logFile.write(message);
logFile.close();
} catch (e) {
// 로그 작성 실패 시 스크립트 중단 방지
}
}
// 폴더에서 이미지 파일 검색
function getAllImageFiles(folder, imageFiles, logMessages) {
if (!folder.exists) return;
var supportedExtensions = /\.(jpg|jpeg|png|tif|tiff|psd|ai|eps|gif|bmp)$/i;
var files = folder.getFiles();
for (var i = 0; i < files.length; i++) {
var file = files[i];
if (file instanceof Folder) {
// 하위 폴더 검색은 주석 처리됨
} else if (file instanceof File && supportedExtensions.test(file.name)) {
imageFiles.push(file);
logMessages.push("Found image: " + file.name);
}
}
}
// 파일명에서 언더스코어 뒤 숫자 추출 (정렬용) - 수정된 버전
function extractNumberFromFilename(filename) {
// _숫자.확장자 패턴에서 숫자 추출
var match = filename.match(/_(\d+)\./);
return match ? parseInt(match[1], 10) : 999999;
}
// 파일명의 숫자 순서로 이미지 정렬
function sortImagesByNumber(imageFiles) {
return imageFiles.sort(function(a, b) {
var numA = extractNumberFromFilename(a.name);
var numB = extractNumberFromFilename(b.name);
return numA - numB;
});
}
// 이미지 파일의 실제 크기 측정
function getImageDimensions(imageFile, tempPage) {
try {
var tempFrame = tempPage.rectangles.add();
tempFrame.place(imageFile);
if (tempFrame.allGraphics.length > 0) {
var imageContent = tempFrame.allGraphics[0];
var bounds = imageContent.geometricBounds;
var w = bounds[3] - bounds[1]; // 너비 = x2 - x1
var h = bounds[2] - bounds[0]; // 높이 = y2 - y1
tempFrame.remove();
return { width: w, height: h };
} else {
tempFrame.remove();
return null;
}
} catch (error) {
try { tempFrame.remove(); } catch (e) {}
return null;
}
}
// 메인 실행 부분
var root_paths = getRootPaths();
for (var kk = 0; kk < root_paths.length; kk++) {
var root_path = root_paths[kk];
var workFolder = new Folder(root_path);
var logMessages = [];
var mainDoc = null;
// 시간 측정 변수
var scriptStartTime = new Date();
var sectionStartTime;
try {
// 초기화
sectionStartTime = new Date();
logMessages.push("=== InDesign Script Start ===");
logMessages.push("Script start time: " + scriptStartTime.toString());
logMessages.push("Root path: " + root_path);
if (!workFolder.exists) {
logMessages.push("ERROR: Work folder not found: " + workFolder.fsName);
writeLog(root_path, logMessages.join("\n"));
continue;
}
var initTime = new Date() - sectionStartTime;
logMessages.push("✓ Initialization completed in " + formatElapsedTime(initTime));
// 이미지 파일 검색
sectionStartTime = new Date();
logMessages.push("\n=== Starting Image Discovery ===");
var imageFiles = [];
getAllImageFiles(workFolder, imageFiles, logMessages);
if (imageFiles.length === 0) {
logMessages.push("ERROR: No images found");
writeLog(root_path, logMessages.join("\n"));
continue;
}
// 파일명 숫자 순서로 정렬
imageFiles = sortImagesByNumber(imageFiles);
logMessages.push("Images sorted by number in filename:");
for (var j = 0; j < imageFiles.length; j++) {
logMessages.push(" " + (j + 1) + ". " + imageFiles[j].name + " (number: " + extractNumberFromFilename(imageFiles[j].name) + ")");
}
var discoveryTime = new Date() - sectionStartTime;
logMessages.push("✓ Image discovery completed in " + formatElapsedTime(discoveryTime) + " - Found " + imageFiles.length + " images");
// InDesign 문서 생성
sectionStartTime = new Date();
logMessages.push("\n=== Creating InDesign Document ===");
mainDoc = app.documents.add();
mainDoc.documentPreferences.facingPages = false; // 단면 레이아웃
// 모든 여백을 0으로 설정 (전체 페이지 이미지용)
mainDoc.marginPreferences.top = 0;
mainDoc.marginPreferences.bottom = 0;
mainDoc.marginPreferences.left = 0;
mainDoc.marginPreferences.right = 0;
// 가이드와 격자 숨김
mainDoc.guidePreferences.guidesInBack = true;
mainDoc.guidePreferences.guidesShown = false;
mainDoc.gridPreferences.documentGridShown = false;
mainDoc.gridPreferences.baselineGridShown = false;
// 프레임 가장자리 숨김
try {
app.activeWindow.viewPreferences.showFrameEdges = false;
app.activeWindow.viewPreferences.showRulers = false;
} catch (e) {
logMessages.push("Note: Could not hide frame edges - will be visible in InDesign but not in export");
}
logMessages.push("Created new document with zero margins and hidden guides");
var docCreationTime = new Date() - sectionStartTime;
logMessages.push("✓ Document creation completed in " + formatElapsedTime(docCreationTime));
// 문서 크기 설정
sectionStartTime = new Date();
logMessages.push("\n=== Sizing Document ===");
var documentWidth, documentHeight;
// 첫 번째 이미지 크기에 맞춰 문서 크기 설정
if (imageFiles.length > 0) {
var firstImageFile = imageFiles[0];
var tempPage = mainDoc.pages.item(0);
var firstImageDimensions = getImageDimensions(firstImageFile, tempPage);
if (firstImageDimensions) {
documentWidth = firstImageDimensions.width;
documentHeight = firstImageDimensions.height;
mainDoc.documentPreferences.pageWidth = documentWidth;
mainDoc.documentPreferences.pageHeight = documentHeight;
logMessages.push("Document size set to first image: " + pointsToMM(documentWidth).toFixed(1) + "mm x " + pointsToMM(documentHeight).toFixed(1) + "mm");
} else {
logMessages.push("ERROR: Could not measure first image, using default size");
documentWidth = mainDoc.documentPreferences.pageWidth;
documentHeight = mainDoc.documentPreferences.pageHeight;
}
}
var sizingTime = new Date() - sectionStartTime;
logMessages.push("✓ Document sizing completed in " + formatElapsedTime(sizingTime));
// 이미지 배치 처리
sectionStartTime = new Date();
logMessages.push("\n=== Processing Images ===");
for (var i = 0; i < imageFiles.length; i++) {
var imageStartTime = new Date();
var imageFile = imageFiles[i];
logMessages.push("\nProcessing: " + imageFile.name + " (Image " + (i + 1) + "/" + imageFiles.length + ")");
// 페이지 가져오기 또는 생성
var currentPage;
if (i === 0) {
currentPage = mainDoc.pages.item(0);
} else {
currentPage = mainDoc.pages.add();
logMessages.push("Added page " + (i + 1) + " with document size: " + pointsToMM(documentWidth).toFixed(1) + "mm x " + pointsToMM(documentHeight).toFixed(1) + "mm");
}
// 전체 페이지를 채우는 이미지 프레임 생성
var imageFrame = currentPage.rectangles.add();
imageFrame.geometricBounds = [0, 0, documentHeight, documentWidth];
// 이미지 배치 및 맞춤
try {
imageFrame.place(imageFile);
if (imageFrame.allGraphics.length > 0) {
var img = imageFrame.allGraphics[0];
img.fit(FitOptions.FILL_PROPORTIONALLY); // 비율 유지하며 채우기
img.fit(FitOptions.CENTER_CONTENT); // 내용 중앙 정렬
var imageTime = new Date() - imageStartTime;
logMessages.push("✓ Image successfully placed on page " + (i + 1) + " (full page, no margins) - " + formatElapsedTime(imageTime));
} else {
logMessages.push("ERROR: Could not place image content: " + imageFile.name);
}
} catch (placeError) {
logMessages.push("ERROR placing image: " + placeError.message);
}
// 프레임 테두리 및 채우기 제거
imageFrame.strokeWeight = 0;
imageFrame.strokeColor = "None";
imageFrame.fillColor = "None";
// 투명도 100%로 설정
try {
imageFrame.transparencySettings.blendingSettings.opacity = 100;
} catch (e) {}
}
var imageProcessingTime = new Date() - sectionStartTime;
logMessages.push("✓ Image processing completed in " + formatElapsedTime(imageProcessingTime));
// 문서 마무리
sectionStartTime = new Date();
logMessages.push("\n=== Finalizing Document ===");
logMessages.push("Total pages created: " + mainDoc.pages.length);
logMessages.push("Total images processed: " + imageFiles.length);
// 깔끔한 문서 보기 설정
if (mainDoc.pages.length > 0) {
app.activeWindow.activePage = mainDoc.pages.item(0);
try {
app.activeWindow.viewPreferences.showFrameEdges = false;
app.activeWindow.viewPreferences.showRulers = false;
app.activeWindow.viewPreferences.showGuides = false;
app.activeWindow.viewPreferences.showBaselineGrid = false;
app.activeWindow.viewPreferences.showDocumentGrid = false;
} catch (e) {
logMessages.push("Note: Could not adjust all view preferences");
}
}
var finalizationTime = new Date() - sectionStartTime;
logMessages.push("✓ Document finalization completed in " + formatElapsedTime(finalizationTime));
// EPUB 내보내기
sectionStartTime = new Date();
var exportResult = exportToEPUB(root_path, mainDoc, logMessages);
var exportTime = new Date() - sectionStartTime;
if (exportResult.success) {
logMessages.push("✓ EPUB export complete: " + exportResult.filePath);
logMessages.push("✓ EPUB export completed in " + formatElapsedTime(exportTime));
} else {
logMessages.push("✗ EPUB export failed: " + exportResult.error);
logMessages.push("✗ EPUB export failed after " + formatElapsedTime(exportTime));
}
// 스크립트 실행 요약
var scriptEndTime = new Date();
var totalTime = scriptEndTime - scriptStartTime;
logMessages.push("\n=== Script Execution Summary ===");
logMessages.push("Script end time: " + scriptEndTime.toString());
logMessages.push("Total execution time: " + formatElapsedTime(totalTime));
logMessages.push("Average time per image: " + formatElapsedTime(totalTime / imageFiles.length));
logMessages.push("Images processed per minute: " + (imageFiles.length / (totalTime / 60000)).toFixed(1));
// 성능 분석
logMessages.push("\n=== Performance Breakdown ===");
logMessages.push("• Initialization: " + formatElapsedTime(initTime));
logMessages.push("• Image discovery: " + formatElapsedTime(discoveryTime));
logMessages.push("• Document creation: " + formatElapsedTime(docCreationTime));
logMessages.push("• Document sizing: " + formatElapsedTime(sizingTime));
logMessages.push("• Image processing: " + formatElapsedTime(imageProcessingTime));
logMessages.push("• Document finalization: " + formatElapsedTime(finalizationTime));
logMessages.push("• EPUB export: " + formatElapsedTime(exportTime));
writeLog(root_path, logMessages.join("\n"));
} catch (error) {
// 오류 처리
var scriptEndTime = new Date();
var totalTime = scriptEndTime - scriptStartTime;
var errorLog = "=== Script Error ===\n";
errorLog += "Error occurred after " + formatElapsedTime(totalTime) + "\n";
errorLog += "Error time: " + scriptEndTime.toString() + "\n";
errorLog += "Message: " + error.message + "\n";
if (error.line) errorLog += "Line: " + error.line + "\n";
errorLog += "Stack: " + error.toString() + "\n";
logMessages.push(errorLog);
writeLog(root_path, logMessages.join("\n"));
// 문서 정리
if (mainDoc && mainDoc.isValid) {
try { mainDoc.close(SaveOptions.NO); } catch (e) {}
}
}
}