// 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) {} } } }