구글 시트에 AI 이미지가 자동으로 생성되고 문서로 정리됨 Code.gs 구글 시트와 스테이블 디퓨전 AI를 연결하여, 프롬프트 입력만으로 이미지를 생성하고 구글 문서에 자동으로 정리해주는 스크립트입니다. 복사 다운로드 2 0 0 /** * 구글 시트와 스테이블 디퓨전 API를 연동하여 문서를 생성하는 스크립트 * * 기능: * 1. 시트의 B열(프롬프트)에 내용이 입력되면 자동으로 실행됩니다 (onEdit 트리거). * 2. 한글 프롬프트는 자동으로 영어로 번역되어 API에 전달됩니다. * 3. A열의 제목이 연속되면 같은 문서에, 다르면 새 문서에 이미지를 추가합니다. * 4. 생성된 문서 링크를 C열에 기록합니다. */ // ========================================== // [설정 영역] 사용자의 API 정보를 입력하세요 // ========================================== // 예시: Stability AI (DreamStudio) API 키 const API_KEY = 'Stability AI API 키 넣는 곳'; // 최신 SDXL 1.0 정식 버전 엔드포인트 (1024x1024 지원) const API_URL = 'https://api.stability.ai/v1/generation/stable-diffusion-xl-1024-v1-0/text-to-image'; // 작업할 시트 이름 (없으면 첫 번째 시트를 자동으로 사용합니다) const SHEET_NAME = '시트1'; // [요청 반영] 문서에 삽입할 이미지 너비 (높이는 비율에 맞게 자동 조절) const IMAGE_WIDTH = 800; // ========================================== // [트리거 함수] B열 수정 시 자동 실행되도록 설정 // ========================================== /** * 시트가 수정될 때 자동으로 실행되는 함수입니다. * B열에 내용이 입력되면 generateImageForSingleRow 함수를 호출합니다. * 이 함수를 사용하려면 Apps Script 에디터에서 트리거를 설정해야 합니다. * 설정 방법은 아래 설명을 참고해주세요. */ function onEdit(e) { // 이벤트 객체에서 수정된 셀 정보 추출 const range = e.range; const sheet = range.getSheet(); const sheetName = sheet.getName(); // 1. 설정된 시트 이름과 일치하는지 확인 // 2. 수정된 열이 B열(2번째 열)인지 확인 // 3. 행이 데이터 영역(2행 이상)인지 확인 if (sheetName === SHEET_NAME && range.getColumn() === 2 && range.getRow() > 1) { // B열이 수정되면 해당 행만 처리하는 함수를 호출 generateImageForSingleRow(sheet, range.getRow()); } } // ========================================== // [메인 함수] 단일 행을 처리하는 함수 // ========================================== function generateImageForSingleRow(sheet, rowIndex) { const rowData = sheet.getRange(rowIndex, 1, 1, 3).getValues()[0]; const bookTitle = rowData[0]; // A열: 책 제목 const prompt = rowData[1]; // B열: 이미지 설명 const existingLink = rowData[2]; // C열: 기존 링크 // 제목이나 프롬프트가 없으면 중단 if (!bookTitle || !prompt) return; // 이미 링크가 생성되어 있고(성공 케이스), 프롬프트가 변경되지 않은 경우 중복 방지 if (existingLink && !existingLink.toString().startsWith("실패") && !existingLink.toString().startsWith("에러")) { // 이미 처리가 완료된 경우 스킵할 수 있으나, onEdit은 프롬프트 변경 시 항상 실행됨. // 여기서는 프롬프트 변경 시 재작업을 허용함. } Logger.log(`처리 중(행 ${rowIndex}): ${bookTitle}`); sheet.getRange(rowIndex, 3).setValue("처리 중..."); SpreadsheetApp.flush(); // 1. 번역 로직 let translatedPrompt = prompt; try { if (/[가-힣]/.test(prompt)) { translatedPrompt = LanguageApp.translate(prompt, 'ko', 'en'); Logger.log(`번역됨: "${prompt}" -> "${translatedPrompt}"`); } } catch (e) { Logger.log(`번역 실패, 원본 사용: ${e.toString()}`); translatedPrompt = prompt; } try { // 2. 이미지 생성 API 호출 (번역된 프롬프트 사용) const apiResult = callStableDiffusionAPI(translatedPrompt); if (!apiResult.success) { const errorMsg = `실패: ${apiResult.error}`; Logger.log(errorMsg); sheet.getRange(rowIndex, 3).setValue(errorMsg); return; } const imageBlob = apiResult.blob; // 3. 문서 ID 및 URL 결정 (이전 행의 데이터 가져오기) let doc; let body; let currentDocUrl; let currentDocId; // 이전 행의 제목과 비교 (i는 1부터 시작하므로 rowIndex - 1) const prevRowIndex = rowIndex - 1; const prevRowData = prevRowIndex > 1 ? sheet.getRange(prevRowIndex, 1, 1, 3).getValues()[0] : null; const previousTitle = prevRowData ? prevRowData[0] : ""; const prevDocLink = prevRowData ? prevRowData[2] : ""; // 이전 행의 제목과 같고, 이전 행에 유효한 문서 링크가 있는 경우 if (bookTitle === previousTitle && prevDocLink && !prevDocLink.toString().startsWith("실패")) { try { currentDocId = DocumentApp.openByUrl(prevDocLink).getId(); currentDocUrl = prevDocLink; doc = DocumentApp.openById(currentDocId); body = doc.getBody(); } catch(e) { // 문서 열기 실패 시 새 문서 생성 doc = DocumentApp.create(bookTitle + " - 이미지 모음"); Utilities.sleep(500); // 안전장치 currentDocId = doc.getId(); currentDocUrl = doc.getUrl(); body = doc.getBody(); try { const titlePara = body.insertParagraph(0, bookTitle); titlePara.setHeading(DocumentApp.ParagraphHeading.HEADING_1); } catch (styleError) { Logger.log(`제목 스타일 적용 실패(내용은 유지): ${styleError}`); } } } else { // 제목이 다르거나 첫 시작이면 새 문서 생성 doc = DocumentApp.create(bookTitle + " - 이미지 모음"); Utilities.sleep(500); // 안전장치 currentDocId = doc.getId(); currentDocUrl = doc.getUrl(); body = doc.getBody(); try { const titlePara = body.insertParagraph(0, bookTitle); titlePara.setHeading(DocumentApp.ParagraphHeading.HEADING_1); } catch (styleError) { Logger.log(`제목 스타일 적용 실패(내용은 유지): ${styleError}`); } } // 4. 문서에 이미지 및 설명 추가 // [요청 반영] 프롬프트 텍스트 제거하고 이미지와 구분선만 추가 try { // 이미지 삽입 body.appendImage(imageBlob) .setWidth(IMAGE_WIDTH); } catch (imgError) { sheet.getRange(rowIndex, 3).setValue("실패: 이미지 포맷 오류"); return; } // 문서 구분을 위한 내용 추가 (선택 사항) body.appendParagraph("--------------------------------------------------"); doc.saveAndClose(); // 5. 시트 C열에 링크 기록 sheet.getRange(rowIndex, 3).setValue(currentDocUrl); } catch (e) { Logger.log(`에러 발생 (행 ${rowIndex}): ${e.toString()}`); sheet.getRange(rowIndex, 3).setValue(`에러: ${e.toString()}`); } } // ========================================== // [API 호출 함수] 스테이블 디퓨전 API 통신 // ========================================== function callStableDiffusionAPI(prompt) { const payload = { "text_prompts": [ { "text": prompt, "weight": 1 } ], "cfg_scale": 7, "height": 1024, "width": 1024, "samples": 1, "steps": 30 }; const options = { "method": "post", "headers": { "Content-Type": "application/json", "Accept": "image/png", "Authorization": `Bearer ${API_KEY}` }, "payload": JSON.stringify(payload), "muteHttpExceptions": true }; try { const response = UrlFetchApp.fetch(API_URL, options); const responseCode = response.getResponseCode(); if (responseCode === 200) { return { success: true, blob: response.getBlob() }; } else { const errorText = response.getContentText(); Logger.log(`API 오류(${responseCode}): ${errorText}`); try { const errorJson = JSON.parse(errorText); return { success: false, error: errorJson.message || `API 오류(${responseCode})` }; } catch (e) { return { success: false, error: `API 오류(${responseCode})` }; } } } catch (e) { Logger.log(`API 통신 실패: ${e}`); return { success: false, error: "통신 실패(인터넷/URL 확인)" }; } }
대량 QR코드 무료로 자동 생성하기 Code.gs 버튼 한 번으로 스프레드시트의 여러 링크를 QR코드로 만들고 구글 드라이브에 자동으로 저장하는 방법입니다. 복사 다운로드 21 1 2 ```javascript /***** 설정 *****/ const SHEET_NAME = "시트 이름"; // 시트 이름 const FOLDER_ID = "구글 폴더 ID"; // 드라이브 QR 저장 폴더 ID // 컬럼 위치 (1 = A열) const COL = { URL: 1, // A: link NAME: 2, // B: filename STATUS: 3, // C: status FILEID: 4 // D: fileId }; // 사용할 QR 생성 API (무료) const QR_BASE_URL = "https://api.qrserver.com/v1/create-qr-code/?size=500x500&data="; /***** 메인 함수: 시트 → QR 생성 → 드라이브 저장 *****/ function generateQrToDrive() { const ss = SpreadsheetApp.getActive(); const sheet = ss.getSheetByName(SHEET_NAME); const folder = DriveApp.getFolderById(FOLDER_ID); const lastRow = sheet.getLastRow(); if (lastRow < 2) return; // 데이터 없음 // 2행부터 마지막 행까지 읽기 const range = sheet.getRange(2, 1, lastRow - 1, 4); const values = range.getValues(); for (let i = 0; i < values.length; i++) { let row = values[i]; let url = row[COL.URL - 1]; // A let filename = row[COL.NAME - 1]; // B let status = row[COL.STATUS - 1]; // C // URL이 없거나 이미 완료된 경우 건너뛰기 if (!url) { row[COL.STATUS - 1] = "URL없음"; continue; } if (status === "완료") continue; try { // QR 생성 API 호출 const qrUrl = QR_BASE_URL + encodeURIComponent(url); const response = UrlFetchApp.fetch(qrUrl); let blob = response.getBlob(); // 파일 이름 지정 (비어 있으면 기본 이름 사용) const safeName = filename && String(filename).trim() !== "" ? String(filename).trim() : "qr_" + (i + 2); // 행 번호 기반 blob = blob.setName(safeName + ".png"); // 드라이브에 파일 생성 const file = folder.createFile(blob); // 상태 및 fileId 기록 row[COL.STATUS - 1] = "완료"; row[COL.FILEID - 1] = file.getId(); } catch (e) { row[COL.STATUS - 1] = "에러: " + e.message; } } // 수정된 값 다시 시트에 반영 range.setValues(values); } ```