467 lines
19 KiB
JavaScript
Executable File
467 lines
19 KiB
JavaScript
Executable File
// 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) {}
|
|
}
|
|
}
|
|
} |