AWS Polly

  • polly란, Amazon에서 제공하는 TTS 서비스입니다.
  • 생성된 음성을 S3 버킷에 저장할 수도 있고, 파일째로 다운받을 수도 있습니다.
  • 별도 구축도 필요 없고, 단순히 Tool 정도로만 기능이 제공되고 있습니다.
  • 그러나 제공되는 aws-sdk를 이용하면 이런 polly의 기능을 API 형태로 활용할 수 있게 되는데요, 오늘은 이를 이용해 다음과 같은 간단한 서비스(?)를 만들어 보려고 합니다.

구조도

  1. 클라이언트가 어떤 텍스트를 요청하면
  2. 람다가 작동해 Polly 서비스를 통해 TTS처리된 음성파일을 얻고
  3. 다시 람다는 해당 파일을 S3버킷에 저장한 뒤
  4. 클라이언트에게 그 파일을 재생시켜 준다.
  • 물론 S3 버킷에 구지 저장 하지 않고, AudioStream을 바로 전달해서 클라이언트측에서 audio를 재생하도록 할 수도 있겠지만, 이것저것 연계해서 써보면서 배울 수 있는 점을 찾기 위해 S3에 파일을 저장하는 것 까지 넣어 보았습니다.

Polly 서비스 간단히 사용해 보기

  • 먼저 Polly를 aws 웹상에서 간단히 사용해 보는 것 부터 시작하는 게 좋겠네요.
  • 아래 polly 콘솔에 접속하여 간단히 TTS 프로그램을 이용해 봅시다.
  • 요기

Polly

* Polly 요금제
5백만 자 무료
AWS 프리 티어를 12개월간 달마다 제공
  • 일반 텍스트에 원하는 문자열을 입력하고
  • 음성 듣기를 누르면 바로 재생이,
  • MP3 다운로드를 누르면 말 그대로 다운로드가,
  • S3에 합성 버튼을 누르면 mp3파일이 지정된 S3로 전송 됩니다.

Lambda 서비스를 활성화해 Polly API 호출해 보기

  • Lambda 설정에 관한 좋은 블로그 글이 있어 소개 드릴게요
  • 요기
  • 간단히 람다 함수를 하나 생성한 뒤, 다음과 같이 작성해 봅니다
  • 저는 javascript로 구성하였구요..
  • 자세한 사용법에 대해서는 제 주석과 AWS 공식 문서를 참고해 주세요!
  • 공식 문서는 요기
const AWS = require("aws-sdk");
AWS.config.update({ region: "ap-northeast-2" });

// Polly API를 사용하기 위한 객체 생성
var polly = new AWS.Polly();

// S3 API를 사용하기 위한 객체 생성
var s3 = new AWS.S3({
  params: {
    Bucket: "...", // 본인이 생성한 S3 버킷명을 입력해주세요.
  },
});

exports.handler = (event, context, callback) => {
  var pollyParams = {
    OutputFormat: "mp3",
    Text: "TEST", // polly가 읽게 될 문자열입니다.
    TextType: "text",
    VoiceId: "Seoyeon", // polly가 지원하는 성우(?) 종류 입니다.
  };

  polly.synthesizeSpeech(pollyParams, function (err, data) {
    if (err) {
      // Polly API 콜에 실패하는 경우, Lambda의 callback을 통해 처리합니다.
      // 당연하게도 Polly API는 비동기로 진행 되며 3번째 인자인 callback 설정을 통해 처리할 수 있도록 되어 있습니다.
      callback("polly failed: ", err);
    } else {
      var s3param = {
        Key: "test.mp3",
        ContentType: "audio/mpeg",
        // polly.synthesizeSpeech의 결과로 떨어지는 data의 AudioStream key에 스트림이 담겨져 옵니다.
        Body: data.AudioStream,
        ACL: "public-read",
      };
      s3.putObject(s3param, function (err, data) {
        // S3 API를 사용해 파일을 저장해 볼게요.
        if (err) {
          callback("s3 failed: ", err);
        } else {
          // 모두 성공한 경우 200 응답과 함께 OK 문자열을 전송합니다.
          callback(null, {
            statusCode: 200,
            body: "OK",
          });
        }
      });
    }
  });
};
  • 위와 같은 람다를 구성한 뒤 실행하면
  • Seoyeon 님이 읽어주시는 “테스트” 라는 오디오파일(mp3)이 지정한 S3 버킷에 ‘test.mp3’ 라는 이름으로 저장되는 것을 확인 하실 수 있을 거에요.

  • 저의 경우는 이때 조금 애먹었던게 AWS 서비스간의 권한 설정 부분이었는데, 테스트용 장난감이니 쿨하게 모든 권한을 오픈해 주었습니다.
    • lambda - s3
    • lambda - polly
    • lambda - api gateway

TTS 변환할 문자를 동적으로 지정해 보자

  • 여러 가지 방법이 있겠지만…
  • 저는 람다에 연결된 API Gateway를 이용해 queryString으로 전달하기로 하였습니다.

  • lambda가 실행될 때, 함수 파라미터로 들어오는 event 라는 객체가 뭔가 값이 들어 있을 것 같아 알아보니..
  • event.queryStringParameters 이라는 객체에 담겨 들어오는걸 알게 되었습니다.
  • 그렇다면 이제 동적으로 세팅할 수 있겠네요 !
  • 기존 코드에서 poly API에 사용하는 파라미터 부분을 다음과 같이 수정해 줍니다.
let message = "지정된 문자가 없습니다.";

if (event.queryStringParameters && event.queryStringParameters.text) {
  message = event.queryStringParameters.text;
}

var pollyParams = {
  OutputFormat: "mp3",
  Text: message,
  TextType: "text",
  VoiceId: "Seoyeon",
};
  • 이제 동적으로 얻어낼 수 있겠군요 !
  • 이왕이면 S3에 적재하는 파일명도 동적으로 생성하도록 리팩토링 해 봅시다
let itemName =
  Math.floor(Math.random() * 100) + "_" + new Date().getTime() + ".mp3";
var s3param = {
  Key: itemName,
  ContentType: "audio/mpeg",
  Body: data.AudioStream,
  ACL: "public-read",
};
  • 자, 이제 이 람다는 다음과 같은 작업을 수행합니다.

    • gateway가 호출될 때 들어오는 querystring 파라미터중 ‘text’ 값을 얻습니다.
    • 없는 경우는 ‘지정된 문자가 없습니다.’ 가 기본값이 되구요.
    • 대상 문자열을 Polly의 synthesizeSpeech 기능을 사용해 Seoyeon님이 읽어주는 오디오 파일로 변환 합니다.
    • 응답받은 AudioStream을 mp3 포맷으로 S3에 적재하는데,
    • 그때 저장되는 파일명은 “랜덤정수_unixtime().mp3” 가 됩니다.
  • S3 버킷에 직접 접속해 확인해 보면, 요청을 줄 때마다 파일 하나가 생성되고, 실행시켜보면 text 파라미터로 전달했던 문자열을 읽어주고 있습니다.

S3에 적재된 mp3 파일들..

이왕이면…

  • 작업을 하다 보니 이왕이면 API Gateway를 호출하면 생성된 mp3파일을 바로 재생 시키면 더 재밌겠다는 생각이 들었습니다.
  • 그래서 해 봤습니다.

S3에 적재된 mp3 파일들..

  • 이 때 새로 알게 된 사실이… http response에 status를 302로 주고,
  • 헤더의 Location에 위치를 지정하면 브라우저에서 응답을 받음과 동시에 해당 위치로 redirect 하게 되더군요..

  • s3버킷에 putObject함수의 성공 콜백을 다음과 같이 수정해 주었습니다.
callback(null, {
  statusCode: 302,
  headers: {
    Location: `https://나의_s3_엔드포인트/${itemName}`,
  },
  body: "OK",
});
  • callback 인자의 1번째 파라미터로는 에러가 들어가기 때문에 , 에러가 아니므로 null을 지정한 부분을 참고해주세요.
  • 짜잔. 이제 이 람다를 배포하고 API gateway로 접근해보면 실제로 생성된 mp3 파일이 재생되는 모습을 볼 수 있습니다.
  • 데모 서버를 띄워서 보여드리고 싶지만.. 직접 구현해 보는 즐거움을 뺏는 것 같아 별도로 제공하지는 않을게요!

무엇을 더 할 수 있을까?

  • AWS에 있는 제품들을 이것저것 들여다보고 있으면 재밌는 것들이 참 많은데요.
  • 이 Polly도 그 중 하나였던 것 같습니다.
  • 그렇다면 이런 장난감 말고 이걸로 어떤 일들을 할 수 있을까 한번 생각 해 봤습니다.

    • 비설치형 TTS 웹툴 제공 (은 이미 과포화 상태인것 같군요.)
    • 언론사 등지에서 뉴스 내용을 간단히 읽어주는 TTS기능 도입 (웹 편의성 제공 목적)
    • 챗봇 등에 적용해서 응답해주고자 하는 음성을 읽어주는 스트리밍 서비스 접목
  • 등을 생각 해 보며 이만 글을 마쳐 보겠습니다…

참고 사이트 & 함께 봐야만 하는 사이트

firepizza's profile image

firepizza

2021-04-14 18:00

firepizza 님이 작성하신 글 더 보기