본문 바로가기
카테고리 없음

[IT도서리뷰📘]n8n이 다 해줌(5/8)/CHAPTER 05. AI 회의록 서기 만들기_미션

by Kim성연 2026. 6. 8.

 

출처 : https://wordrow.kr/%EC%9E%90%EC%84%B8%ED%95%9C-%EC%9D%98%EB%AF%B8/%EC%84%9C%EA%B8%B0/5/

1. 전체 흐름 파악하기

음성 파일이 어디에서 와서 어디로 가는지, Gemini에게 어떻게 옮겨가는지 아는 게 Point!

음성 파일을 n8n으로 전달하면, 
Gemini가 음성을 전사하고 
AI가 교정·요약 후 
Google Drive 저장 및 
Notion 회의록을 자동 생성하는 
워크플로우

https://github.com/ggplab/n8n-playbook/tree/main/01-hanbit-n8n-guidebook/chap5

 

1-1. 음성 파일을 n8n으로 전달하면 ( On Form submission )

On Form submission
[Form Elements]
- Label // 이름 설정
- Element Type // 오디오 '파일'이니 'File'로 설정 ⭐

제출 시 Binary 파일이 생성되며 오디오 파일에 대한 정보를 카드로 볼 수 있음

1-2. Gemini가 음성을 전사하고 ( Gemini 의 Transcribe a recording)

Gemini 의 Transcribe a recording 노드 역할 : On Form submission 노드로 받은 음성 파일을 따로 텍스트로 정리

그리고 압정 핀을 사용하여 여러번 실행할 필요 없이 그냥 뒤에 있는 데이터를 고정시킬 수 있다.

신기한 점은 Input Type에서 파일말고 URL로 설정해도 된다는 점이었다. 유튜브 강의 내용 정리할 때 URL 방식이 편리할 것 같다.

1-3. AI가 교정·요약 후 (Gemini의 Message a model ×2)

교정, 요약은 이 친구만 이용한다.

더보기를 누르면 프롬프트를 볼 수 있다.

더보기
교정 프롬프트 요약 프롬프트
당신은 전문적인 교정 및 편집 에디터입니다.
아래 제공되는 [음성 인식 텍스트]를 바탕으로 다음의 [작업 지침]을 엄격히 준수하여 텍스트를 다듬어 주세요.

[작업 지침]
1. 교정 및 윤문: 맞춤법, 띄어쓰기, 문법 오류를 수정하고 문장의 흐름이 자연스럽게 이어지도록 매끄럽게 다듬으세요.
2. 길이 유지 (요약 금지): 내용을 요약하거나 축약하지 마세요. 원문의 정보량과 길이를 그대로 유지해야 합니다.
3. 누락 방지: 텍스트의 시작부터 끝까지, 어떤 문장도 누락되지 않도록 꼼꼼하게 검토하여 변환하세요.
4. 문맥 수정: 음성 인식(STT) 과정에서 잘못 인식된 것으로 보이는 단어나 문맥상 어색한 표현은 상황에 가장 적합한 단어로 수정하세요.
5. 출력 형식: 교정이 완료된 텍스트만 출력하세요. (인사말이나 부가 설명 생략)
당신은 비즈니스 문서 정리에 특화된 '수석 서기'입니다.
제공된 회의 스크립트(타임코드 포함)를 분석하여, 다음 JSON 포맷으로 정리된 회의록을 작성하세요.

[출력 포맷 - JSON]
{
  "meeting_date": "미팅 날짜 (YYYY-MM-DD 형식. 값이 없는 경우 {{ $now.setZone('Asia/Seoul').toFormat('yyyy-MM-dd') }} 를 기본값으로 설정)",
  "meeting_title": "회의 주제",
  "meeting_oneline": "한줄 요약",
  "meeting_attendee": ["참석자1", "참석자2"],
  "meeting_summary": "미팅 요약 (아래 작성 지침에 따른 마크다운 형식의 텍스트, 2000자 미만)"
}

[meeting_summary 작성 지침 - 엄격 준수]

1. 3줄 요약 (Executive Summary)
   - 회의의 핵심 목적과 결론을 가장 중요한 순서대로 딱 3문장으로 요약하세요.

2. 발언자별 핵심 발언 (Who Said What)
   - 담당 업무는 제외하고, 각 참여자가 회의에서 논의한 주요 의견만 간결하게 요약하세요.
   - 형식: **이름**: 주요 발언 요약

3. 담당자별 액션 아이템 (Action Items by Assignee)
   - 회의에서 도출된 할 일을 담당자별로 그룹화하여 정리하세요.
   - 공동 작업이거나 담당자가 불명확할 경우 '공통' 또는 '팀 전체'로 분류하세요.
   - 형식:
     - **담당자명**
       - [ ] 할 일 내용 (마감: 문맥상 날짜가 유추될 경우 기입, 아니면 빈칸)
   - [위험 고지] 액션 아이템의 마감 기한은 문맥상 명확하게 언급되었을 때만 기입하며, 유추된 마감일에는 더블 체크가 필요함을 상기하세요.

[주의사항]
- 응답은 오직 JSON 데이터만 출력하세요.
- meeting_summary 필드에 마크다운 줄바꿈(\n)을 포함하여 텍스트로 넣으세요.
결과
text:{\n  "meeting_date": "2026-06-04",\n  "meeting_title": "제1차 본회의: 국무총리 및 국무위원 출석 요구의 건",\n  "meeting_oneline": "교섭단체 대표연설을 위한 국무총리 및 국무위원 출석 요구안 가결 및 신규 의사봉 도입 보고",\n  "meeting_attendee": [\n    "우원식(국회의장)",\n    "유상범(의원)"\n  ],\n  "meeting_summary": "### 1. 3줄 요약 (Executive Summary)\n- 제1차 본회의를 개회하여 국정에 관한 교섭단체 대표연설을 위한 국무총리 및 국무위원 출석 요구의 건을 최종 가결하였습니다.\n- 파손된 기존 의사봉과 의사봉판을 역사 기록물로 보존하기로 결정하고, 금일부터 새로운 의사봉 세트를 도입하여 사용하기 시작했습니다.\n- 2월 3일과 4일 양일간 진행될 대표연설에 국무총리를 포함한 전체 국무위원이 출석할 것을 확정했습니다.\n\n### 2. 발언자별 핵심 발언 (Who Said What)\n- **우원식**: 파손된 기존 의사봉을 기록물로 보존하고 새 비품 사용을 안내했으며, 제안된 국무위원 출석 요구안에 대해 이의 없음을 확인하여 가결을 선포함.\n- **유상범**: 헌법 및 국회법에 의거하여 2월 3일~4일 예정된 교섭단체 대표연설을 위해 국무총리 및 모든 국무위원의 본회의 출석을 정식 요청함.\n\n### 3. 담당자별 액션 아이템 (Action Items by Assignee)\n- **우원식**\n  - [ ] 파손된 기존 의사봉 및 의사봉판의 전시 방안 검토 (마감: )\n- **공통**\n  - [ ] 2월 3일~4일 국정에 관한 교섭단체 대표연설 운영 준비 (마감: 2026-02-03)"\n}

 

1-3-1. 추가 설정

Output Content as Json으로 설정하면 좀 더 이쁘게 나온다고 하니 적용하자.

 

1-4. Google Drive 저장 및 (Convert to File)

Convert to File 노드의 역할 : Transcribe a recording 노드로 만든 텍스트를 다시 파일로 만듦.

{{ $('On form submission').item.json.data[0].filename }}_회의록.txt

 

파일로 만들어야 구글 드라이브에 저장하든 노션에 저장하든 할 수 있는 것일까?

유용한 점은 추가 옵션에서 File Name을 지정할 때, 전전 노드(On form submission)에서의 파일 자체의 이름을 드래그 앤 드롭으로 가져올 수 있다는 점이다. 그래서 이름 주소를 보면, 이 아이템이 json파일의 0번째 데이터의 filename을 가져오는 것을 확인할 수 있다. 음성 파일이 텍스트 파일이 되었다.

그리고 강의에서는 미리 백업하는 용도로 미리 Google Drive에 저장한다. 근데 여기서 기껏 convert to file에 저장해둔 파일 이름을 사용안하길래 나는 임의로 {{ $binary.data.fileName }} 라고 설정했다.

그리고 강의에서 요약하기 전에 Google Drive에 저장을 하는데, 약간 백업 용도로 생각하며 좋을 것 같다.

 

1-5. Notion 회의록을 자동 생성하는 워크플로우 (Notion)

삼점바 -> 연결 -> 연결 개발 또는 관리 선택 -> 신규 연결

좌측 연결 페이지 선택 -> 토큰 복사 및 노드에 붙여넣기

2. 결과 및 느낀 점

AI 교정, 요약 프롬프트 중 Json 포맷이 가장 중요한 것 같다. 음성 파일의 내용을 잘 나눠야 노션에 적절하게 배분할 수 있기 때문이다.

[기본 미션] ✅
CHAPTER 05 회의록 STT 워크플로우(오디오 파일 → Gemini STT → 교정 → 요약 → Google Drive + Notion 업로드)를 따라 워크플로우를 직접 만들고, 예제 오디오 파일(chap5_STT_example_audio.mp3)로 실행해 생성된 Notion 회의록 페이지와 Google Drive 전사 파일 링크를 블로그에 정리해 보세요.
※ iOS 사용자는 단축어, Android 사용자는 Google Drive 트리거 또는 Telegram 봇 중 편한 방식을 선택해 주세요.

[심화 미션]
본인의 실제 업무 또는 관심 분야에 맞게 음성 입력과 요약 포맷을 커스텀해 보세요.
- 트리거를 변경: 실제로 녹음한 회의/강의/인터뷰 음성을 입력✅
- 요약 프롬프트를 본인 업무 포맷에 맞게 재작성 (예: 영업 미팅 → 고객·니즈·다음 액션 / 강의 → 핵심 개념·예시·숙제)✅
- 출력 저장소 변경: Notion 대신 Obsidian, Google Docs, 사내 위키 등
- 한 발 더: 요약 결과에서 '액션 아이템'만 뽑아 별도 Task 관리 도구(Todoist, ClickUp 등)에 자동으로 쌓이도록 확장

 

기본 미션 결과

 

집중이 안되는 강의를 스크립트와 함께 봤을 때, 집중력이 올라간다는 것을 확인할 수 있었다. 그럼 내가 듣는 강의를 모두 텍스트 파일로 만들어서 분석하는 건 효율적일까? 굳이 다른 툴로 편하게 하고 있는데 이 방법을 이용할 필요는 없을 것 같다는 생각이 들었다. 어차피 할거면 매일 하는 걸 정리하는 게 좋지 않을까? 그럼 영어를 매일 하고 있으니 이걸 정리하는 편이 좋을 것 같다.

 

심화 미션 결과 (막힌 점 : 어떤 처리 결과를 내놓아 활용할지 고민 중)

여러 개의 오디오 파일을 받았을 때, 처리하도록 code 노드를 사용하였다.

 

더보기를 누르면 코드를 볼 수 있다.

더보기

Prepare Audio Files

// 1. 데이터 가져오기
const inputData = $input.first();
const files = inputData.json.data;
const binaryData = inputData.binary;

if (!files || !Array.isArray(files)) {
  return [];
}

// 2. 파일 정보에 원래 n8n 매핑 인덱스(원래 위치)를 기록해 둡니다.
const filesWithIndex = files.map((file, index) => ({
  ...file,
  originalIndex: index
}));

// 3. 파일 이름(filename)을 기준(01, 02, 03...)으로 정렬합니다.
filesWithIndex.sort((a, b) => {
  return a.filename.localeCompare(b.filename, undefined, { numeric: true, sensitivity: 'base' });
});

// 4. 정렬된 순서대로 JSON과 실제 오디오 바이너리를 매핑하여 내보냅니다.
const result = [];
filesWithIndex.forEach((file, sortedIndex) => {
  const binaryKey = `data_${file.originalIndex}`; // 원래 바이너리 키 찾기
  
  const newItem = {
    json: {
      // ⭐ [변경/추가] 명확한 구분을 위한 인덱스와 파일명 최상위 배치
      itemIndex: sortedIndex + 1,        // 1부터 시작하는 순번 (1, 2, 3...)
      displayTitle: `[파일 ${sortedIndex + 1}] ${file.filename}`, // 디스플레이용 이름
      fileName: file.filename,
      mimeType: file.mimetype,
      fileSize: file.size,
      submittedAt: inputData.json.submittedAt
    }
  };

  // 실제 오디오 파일 데이터가 존재하면 바이너리 필드도 함께 넘겨줍니다.
  if (binaryData && binaryData[binaryKey]) {
    newItem.binary = {
      data: binaryData[binaryKey] // Gemini 노드가 읽을 'data' 필드로 통일
    };
  }

  result.push(newItem);
});

return result;

 

Split

// Transcribe a recording 노드에서 넘어온 모든 아이템을 순회합니다.
return $input.all().map((item, index) => {
  
  // 1. Gemini 오디오 인식 결과에서 원문 텍스트 추출
  let fullText = "";
  if (item.json.content && item.json.content.parts && item.json.content.parts[0]) {
    fullText = item.json.content.parts[0].text || "";
  }

  let essayPart = fullText;
  let dialoguePart = "";

  // 2. 앞에 \n이나 공백이 붙어있어도 칼같이 식별하는 정규식
  const dialogueRegex = /\s*Dialogue\s+[Pp]ractice/;
  const match = fullText.match(dialogueRegex);

  if (match) {
    const dialogueIndex = match.index;

    // 키워드 앞부분(에세이) 자르고 깔끔하게 여백 정리
    essayPart = fullText.substring(0, dialogueIndex).trim();
    
    // 키워드부터 끝까지(대화문) 자르고 깔끔하게 여백 정리
    dialoguePart = fullText.substring(dialogueIndex).trim();
  }

  // ⭐ 3. 글자 형태의 '\\n'과 실제 엔터값('\n')을 모두 제거하고 한 칸 공백(' ')으로 치환
  // 연속된 공백이 생기지 않도록 깔끔하게 정돈합니다.
  essayPart = essayPart
    .replace(/\\n/g, ' ')   // 글자 모양 '\n' 제거
    .replace(/\n/g, ' ')    // 실제 줄바꿈 엔터 제거
    .replace(/\s+/g, ' ')   // 줄바꿈이 지워지면서 생긴 중복 공백들을 한 칸 띄어쓰기로 통일
    .trim();

  dialoguePart = dialoguePart
    .replace(/\\n/g, ' ')   // 글자 모양 '\n' 제거
    .replace(/\n/g, ' ')    // 실제 줄바꿈 엔터 제거
    .replace(/\s+/g, ' ')   // 중복 공백 통일
    .trim();

  // 4. 역사 속으로 사라진 파일명을 'Prepare Audio Files' 노드에서 강제로 추적해 오기
  let originalFileName = "";
  let itemIdx = index + 1; // 기본 순번
  
  try {
    const sourceNode = $("Prepare Audio Files");
    if (sourceNode && sourceNode.all && typeof sourceNode.all === 'function') {
      const allSourceItems = sourceNode.all();
      if (allSourceItems[index] && allSourceItems[index].json) {
        originalFileName = allSourceItems[index].json.fileName || "";
        itemIdx = allSourceItems[index].json.itemIndex || itemIdx;
      }
    }
  } catch (e) {
    try {
      const prevData = $item(index).node('Prepare Audio Files').json;
      originalFileName = prevData.fileName || "";
      itemIdx = prevData.itemIndex || itemIdx;
    } catch (err) {
      originalFileName = `입트영_오디오_파일_${itemIdx}.mp3`; 
    }
  }

  // 5. 정돈된 최종 알맹이 데이터만 반환
  return {
    json: {
      itemIndex: itemIdx,           // 1, 2, 3... 순번 확보
      fileName: originalFileName,   // 깨지지 않는 원래 파일 이름 확보
      essay: essayPart,             // 모든 줄바꿈이 제거된 매끄러운 통문장 에세이
      dialogue: dialoguePart        // 모든 줄바꿈이 제거된 매끄러운 통문장 대화문
    }
  };
});

 

사실 뒤에 어떤 AI를 붙여야 잘 사용할 수 있을지 고민 중이다. 공부를 하면서 덧붙여봐야겠다.