이 페이지의 이전 버전을 보고 있습니다. 현재 버전 보기.

현재와 비교 페이지 이력 보기

« 이전 버전 6 다음 »

Amazon Polly 소개

일반적으로 텍스트를 읽는 것은 쉽지 않습니다. 애플리케이션이 문장에 있는 각각의 문자를 읽는 것만으로는 의미가 있다고 볼 수 없습니다. 예를 들어, 텍스트를 음성으로 변환하는(text-to-speech) 애플리케이션은 다음과 같은 공통적인 문제가 있습니다:

  • 동일하게 쓰여졌으나 다르게 발음되는 단어: I live in Las Vegas. vs. This presentation broadcasts live from Las Vegas.
  • 텍스트 정규화. 애매모호한 약어, 두문자어 및 단위: St.는 street 또는 saint 로 쓰일 수 있습니다.
  • 복잡한 매핑을 사용하는 언어로 된 음소로의 텍스트 변환: tough, through, though. 스펠링은 유사하지만, 단어나 문맥에 따라 다르게 발음 합니다.
  • 외래어(déjàvu), 고유 이름(François Hollande), 속어(ASAP LOL) 등에서 발음 문제가 있습니다.

Amazon Polly는 이러한 문제를 해결하는 음성 합성 기능을 제공합니다. 따라서 위와 같이 텍스트를 음성으로 변환하는 어려움을 해결하기 위한 노력 대신 애플리케이션 개발 자체에 주력할 수 있습니다.

Amazon Polly는 텍스트를 생생한 음성으로 변환시킵니다. 자연스럽게 말하는 애플리케이션을 만들 수 있으므로 완전히 새로운 범주의 음성을 사용하는 서비스를 만들 수 있습니다. Amazon Polly는 딥 러닝 학습 기술을 사용하여 사람 목소리처럼 들리는 음성을 합성하는 Amazon AI 서비스입니다. 28 개국 언어(한국어 포함)로 57 가지(한국어 서연 목소리 포함)의 목소리가 포함되어 있어 이상적인 목소리를 선택하여 다양한 국가에서 사용할 수 있는 음성을 제공하는 애플리케이션을 제작할 수 있습니다. (2019년 03월 06일 기준)

또한 Amazon Polly는 실시간 대화를 지원하는 데 요구되는 일관성 있는 빠른 응답 시간을 제공합니다. 오프라인 재생 또는 재배포를 위해 Polly의 오디오 파일을 캐시하고 저장할 수 있습니다. (즉, 재활용이 가능하므로 추가 과금이 발생하지 않습니다.) 그리고 Polly는 사용하기 쉽습니다. 음성으로 변환하려는 텍스트를 Amazon Polly API에 실어 보내기만 하면 됩니다. Amazon Polly는 오디오 스트림을 애플리케이션에 즉시 반환하므로 애플리케이션에서 직접 재생하거나 MP3와 같은 표준 오디오 파일 형식으로 저장할 수 있습니다.

이 블로그는 Amazon Polly를 사용하여 텍스트를 음성으로 변환하는 서버리스 서비스 기반 애플리케이션을 구축하는 방법을 소개합니다. 이 애플리케이션은 텍스트를 여러 언어로 받아 들인 다음 웹 브라우저에서 재생할 수있는 오디오 파일로 변환하는 간단한 사용자 인터페이스를 제공합니다. 여기서는 블로그 게시물을 사용하지만 모든 유형의 텍스트를 사용할 수 있습니다. 예를 들어, 차를 운전하거나 자전거를 타는 동안 뉴스 기사 또는 책을 읽거나, 식사를 준비하는 동안 조리법을 읽어주는 애플리케이션에 사용할 수 있습니다.

실습 소개

기존 Amazon Polly를 통한 음성 읽기 서버리스 앱 개발하기 블로그와 동일한 기능을 수행하는 실습입니다
Amazon Polly(TTS) 서비스가 비동기 방식을 지원하고, 한자를 한글로 변환하는 기능이 개선되어 해당 실습에서는 삭제 하였습니다.

애플리케이션 아키텍처

아래 다이어그램은 이 애플리케이션의 아키텍처를 보여 줍니다. 프로비저닝, 패치, 확장에 대해서 고민할 필요가 없는, 즉 서버 작업이 필요하지 않는 서버리스 서비스로 구성합니다. 클라우드가 자동으로 이를 관리하기 때문에 우리는 애플리케이션 개발에만 집중할 수 있습니다.

이 애플리케이션은 크게 세 가지 서비스를 제공합니다.

  • 첫 번째는 HTML, CSS, Javascript 및 이미지를 정적 웹 페이지로 호스팅 합니다.
  • 두 번째는 새로운 게시물에 대한 텍스트 정보를 MP3 파일로 변환하는 방법을 제공합니다.
  • 세 번째는 게시물에 대한 정보(S3 버킷에 저장된 MP3 파일에 대한 링크 포함)를 검색하고 삭제하는 기능 입니다.

게시물 등록, 검색, 삭제는 Amazon API Gateway를 통해 RESTful 웹 서비스로 제공됩니다. 로직은 Lambda 서비스를 통해서 구현되며 애플리케이션이 어떻게 상호 작용 하는지 살펴 보겠습니다.

정적 웹페이지 구현

  1. 이 시나리오는 Amazon S3(Simple Storage Service)에서 호스팅되는 정적 웹 페이지를 기반으로 실행합니다.

새로운 게시물의 텍스트를 MP3로 변환

  1. MP3로 생성할 텍스트 정보는 Amazon API Gateway에 의해 노출 된 RESTful 웹 서비스에 의해 수신됩니다.
  2. Amazon API Gateway는 텍스트를 MP3로 변환 요청하는 Lambda 함수인 "PostNews"를 호출합니다.
  3. "PostNews" Lambda 함수는 Amazon Polly 서비스에 요청받은 Text를 Speech로 변환하는 작업을 비동기로 호출합니다. 
  4. "PostNews" Lambda 함수는 Amazon Polly 서비스에 받은 Task ID와 게시물에 대한 메타 정보를 받아서 DynamoDB에 등록합니다. 
  5. Amazon Polly가 텍스트를 음성으로 변환을 완료하면, 지정된 S3에 mp3 파일을 업로드 합니다.
  6. Amazon Polly가 작업을 완료하면 SNS NewsTopic을 호출합니다.
  7. NewsTopic을 구독하고 있는 UpdateNews Lambda 함수가 트리거 되어 동작합니다.
  8. UpdateNews Lambda 함수는 Amazon Polly가 완료한 작업 상태 값을 DynamoDB에 업데이트 합니다.

등록된 게시물 정보 검색

  1. RESTful 웹 서비스는 Amazon API Gateway를 사용하여 배포합니다. Amazon API Gateway는 게시물에 대한 정보를 검색하는 방법을 제공합니다. 이 방법은 게시물의 텍스트와 MP3 파일이 저장되는 S3 버킷에 대한 링크가 포함됩니다. 이 시나리오에서 이 웹 서비스는 Amazon S3에서 호스팅되는 정적 웹 페이지에서 호출됩니다.
  2. Amazon API Gateway는 게시 데이터를 검색하기 위한 로직을 배포한 GetNews Lambda 함수를 GET 메서드로 호출합니다.
  3. GetNews Lambda 함수는 DynamoDB 테이블에서 게시물에 대한 정보(Amazon S3에 대한 참조 URL을 포함)를 검색합니다.
  4. Amazon API Gateway는 게시 데이터를 삭제하기 위한 로직을 배포한 DeleteNews Lambda 함수를 DELETE 메서드로 호출합니다.
  5. DeleteNews Lambda 함수는 DynamoDB 테이블에서 게시물에 대한 정보(Amazon S3에 대한 참조 URL을 포함)와 S3의 mp3 파일을 삭제합니다.


아래 실습을 직접 수행하려면 Amazon Polly 서비스가 제공되는 지역을 선택하십시오. (이 예제는 Seoul 리전에서 English 언어 설정 상태에서 진행합니다.)


실습

실습은 다음과 같은 순서로 진행됩니다.

  1. 콘솔 로그인
  2. DynamoDB 테이블 만들기
  3. SNS Topic 만들기
  4. mp3 저장을 위한 S3 버킷 만들기
  5. IAM 역할 만들기
  6. PostNews Lambda 함수 만들기
  7. GetNews Lambda 함수 만들기
  8. UpdateNews Lambda 함수 만들기
  9. DeleteNews Lambda 함수 만들기
  10. Lambda 함수를 RESTful 웹 서비스로 만들기
  11. 정적 웹 서비스를 위한 S3 버킷 만들기
  12. 최종 테스트


1. 콘솔 로그인

리전을 서울(우측 상단)로 선택하고, 언어는 영어로 진행(좌측 하단)


1. DynamoDB 테이블 만들기

DynamoDB는 게시물 정보와 생성된 MP3의 URL 메타 정보를 저장합니다.

  1. DynamoDB 서비스로 이동합니다.
  2. Create Table 버튼을 클릭합니다.
  3. DynamoDB 콘솔에서 NewsTable 라고 하는 단일 테이블을 만듭니다. 기본 키 id 는 문자열이고, PostNews Lambda 함수에 의해 새 레코드는 NewsTable 에 자동으로 추가 생성합니다.
  4. NoSQL인 DynamoDB를 사용하므로 스키마를 사전에 정의하지는 않겠지만, 사용하게 될 어트리뷰트가 어떤 것이 있는지 살펴 보겠습니다.

    keyvalue
    id게시물 ID (UUID로 자동 생성)
    voice오디오 파일을 생성하는데 사용된 Amazon Polly 음성
    pollyStatus처리 상태에 따라서 PROCESSING 또는 UPDATED로 구분
    originText원문 텍스트
    RequestCharacters요청한 문자 개수
    mp3UrlPolly에 의해서 생성된 mp3 접근 URL
    updateDate수정된 시간
    timbre음색 (소리의 파형)
    pitch음조 (소리의 높낮이)

    5. 테이블 생성 확인


2. SNS Topic 만들기

아키텍처 다이어그램에서 알 수 있듯이 텍스트에 대한 MP3 생성 요청을 비동기 적으로 수행할 수 있도록 Amazon Polly를 호출하고, 완료 처리를 UpdateNews Lambda 함수가 처리하도록 분할했습니다. 몇 가지 이유로 이 작업을 수행했습니다.

첫째, 비동기 호출을 사용하여 PostNews Lambda 함수가 UpdateNews Lambda 함수를 실행할 때까지 기다리는 것을 피할 수 있습니다. 그래서 나중에 수행해야 할 부분을 id를 통해서 공유 할 수 있습니다. 즉, 새로운 게시물 등록 작업에 대한 응답을 빠르게 클라이언트로 응답할 수 있습니다. 작은 게시물의 경우 오디오 파일로 변환하는 데 수백 밀리 초가 걸릴 수 있지만 글이 길어지면(10만 단어 이상) 텍스트를 변환하는 데 추가적인 시간이 필요할 수 있습니다. 다만, 실시간 스트리밍을 원할 때에는 문제가 되지 않습니다. Amazon Polly는 첫 번째 바이트를 사용할 수 있다면 곧바로 읽기를 시작하기 때문입니다.

두 번째 이유는 Lambda 함수는 15분 동안 실행할 수 있습니다. 이는 게시물을 변환하기 위해 충분한 시간입니다. 미래에 더 큰 것을 변화시키고 자 할 경우 Lambda 대신 AWS Batch를 사용하고자 할 수 있습니다. 애플리케이션의 이 두 부분을 분리하면 이 변경 작업이 훨씬 쉬워집니다. 위와 같이 두 개의 컴포넌트(여기서는 Lambda 함수 두 개)가 있을 때 이를 통합할 수 있습니다. 즉, 두 번째 컴포넌트가 언제 시작할지 알아야 합니다. 여러 가지 방법으로 이 작업을 수행 할 수 있습니다. 이 경우 Amazon Polly가 작업이 완료되면, Amazon SNS를 트리거 하도록 합니다. 

  1. 그렇기에 간단한 SNS 주제를 만들어 보겠습니다. SNS 서비스로 이동합니다.
  2. Create topic 메뉴를 눌러서 Topic을 생성합니다.
  3. 아래와 같이 NewsTopic 이라는 새로운 Topic을 생성합니다.
  4. 생성된 SNS는 Amazon Polly가 작업이 완료되면 알려주기 위해서 해당 Topic을 호출해야 합니다. 따라서 Topic에 대한 아마존 고유 리소스 이름을 알아야 합니다.

    아래와 같이 생성된 Topic을 클릭하여 Topic의 ARN을 복사하여 메모장에 기록해 둡니다. (사용자 별로 다르게 나옵니다. 본인의 ARN을 복사합니다.)
    Topic ARN 복사 하기: 

3. mp3 저장을 위한 S3 버킷 만들기

응용 프로그램에서 생성한 모든 오디오 파일을 저장하는 S3 버킷을 만들어야합니다.

  1. S3 콘솔로 이동하여 새로운 버킷을 생성합니다. 예제에서는 polly-mp3.studydev.com 이라는 버킷 이름으로 작성하지만, 실습에서는 전 세계적으로 고유한 다른 이름으로 작성해야 합니다.
  2. (옵션) Tags 정보에 Key에는 Name을 Value에는 WebApp 을 추가합니다.
  3. 외부에서 퍼블릭하게 접속해서 mp3 재생이 되도록 체크 박스를 모두 해지 합니다. (기본은 체크되어 안전하게 막혀 있음)
  4. 모든 설정이 완료되면 Create Bucket 버튼을 클릭하고 S3 버킷을 생성합니다.


4. IAM 역할 만들기

Lambda 함수를 만들기 전에 함수에 대한 IAM 역할을 만들어야 합니다. 역할은 함수가 상호 작용할 수 있는 AWS 서비스(API)를 지정합니다. 세 가지 Lambda 함수 모두에 대해 하나의 역할을 만듭니다. (원래 기능별로 역할을 만들지만, 예제 구현을 위해 하나로 만듭니다.)
역할을 생성하기 위해서 역할에 부여될 정책을 JSON 포맷으로 생성하고, 생성한 정책을 해당 역할에 부여합니다.

  1. 콘솔의 서비스에서 IAM을 찾은 다음 역할 메뉴를 선택하고, 역할 만들기 버튼을 눌러 새 역할을 만들기 위한 마법사를 엽니다. 이 역할을 사용할 서비스인  AWS 서비스 의  Lambda 를 선택하고 다음:권한 버튼을 클릭합니다.

Lambda 4개를 위한 Role을 한 개 생성(가능하면 다른 Lambda의 용도에 맞추어서 따로 만드는걸 추천하지만 실습에서는 1개로 진행)

IAM 서비스로 이동

Lambda를 위한 Role을 생성하기 위해 이동

Role을 생성

이 역할을 사용하는 주체는 AWS Service의 Lambda입니다.


정책을 새로 만듭니다.

Visual editor에서 JSON으로 변경합니다.

아래 리소스에 대한 접근 정책을 JSON으로 추가합니다.

{
  "Version":"2012-10-17",
  "Statement":[ 
    { 
      "Effect":"Allow",
      "Action":[ 
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents",
        "polly:StartSpeechSynthesisTask",
        "dynamodb:Query",
        "dynamodb:Scan",
        "dynamodb:PutItem",
        "dynamodb:UpdateItem",
        "dynamodb:DeleteItem",
        "sns:Publish",
        "s3:PutObject",
        "s3:PutObjectAcl",
        "s3:DeleteObject",
        "s3:GetBucketLocation"
      ],
      "Resource":[ 
        "*"
      ]
    }
  ]
}


정책 이름은 NewsPolicy로 지정하고, 하단 우측읜 Create policy 버튼을 클릭합니다.

정책이 만들어 진것을 알 수 있습니다.

원래 창에서 NewsPolicy를 검색합니다.

 나오지 않는다면, 우측 상단의 새로 고침 버튼을 클릭합니다.

Role의 Tag를 입력합니다. NewsApp을 만들기 때문에 Name을 NewsApp으로 정의합니다.

News Role을 생성합니다.

NewsRole이 생성된 것을 확인할 수 있습니다. 클릭해서 ARN 정보를 기록합니다.

arn:aws:iam::551374826607:role/NewsRole


S3 폴더 만들기

S3 서비스 이동

현재 만들어진 버킷을 볼 수 있습니다.


S3 정적 웹 호스팅용 버킷 만들기




S3 버킷 정적 웹 호스팅 기능 활성화

정적 웹 호스팅을 제공할 버킷을 클릭합니다.

2번째 Properties 탭에서 Static Web Hosting 기능을 클릭합니다.

정적 웹 호스팅 기능이 비활성화 되어 있습니다.

Static Website hosting 기능을 켜 주고, index와 error 문서를 지정하고 저장합니다.

정적 웹 호스팅 기능이 활성화 된 것을 볼 수 있습니다.

버켓 접근에 대한 정책을 설정 할 수 있습니다.

S3 버켓에 대한 접근 권한 설정
{ 
  "Version":"2012-10-17",
  "Statement":[ 
    { 
      "Sid":"PublicReadGetObject",
      "Effect":"Allow",
      "Principal":"*",
      "Action":[ 
        "s3:GetObject"
      ],
      "Resource":[ 
        "arn:aws:s3:::BUCKET_NAME/*"
      ]
    }
  ]
}


버켓 정책을 설정하고 저장 합니다.

S3 Bucket이 Public 접근이 가능하다는 메시지가 출력됩니다.


S3 버켓 컨텐츠 업로드 (API 수정)


Lambda 4개 생성 (환경변수 고려)

Lambda 서비스로 이동


Lambda 서비스에서 Function을 하나 생성합니다.


Scratch로 PostNews 이름으로 Lambda 함수를 생성합니다. Runtime은  Python 2.7이고 Role은 이미 만들어둔 NewsRole을 선택하고 Create function 버튼을 클릭 합니다.


Lambda 함수가 정상적으로 만들어진 것을 확인할 수 있습니다.

함수 코드를 입력합니다.

# -*- coding: utf-8 -*-
from __future__ import print_function
  
import boto3
import os
import json
import uuid
import datetime
import re
 
def lambda_handler(event, context):
    if "body" in event:
        parmas = json.loads(event['body'])
     
    voice = parmas["voice"]
    originText = parmas["text"]
    timbre = parmas["timbre"]
    pitch = parmas["pitch"]
    updateDate = datetime.datetime.now().strftime("%Y%m%d")
  
    polly = boto3.client('polly')
    removeBrackets = re.sub(r'\([^)]*\)', '', originText)
    repTextBlock = re.sub('[·…]', '<break time="100ms"/>', removeBrackets)
    ssmlBlock = "<speak><amazon:effect vocal-tract-length=\"" + timbre + "\"><prosody pitch=\"" + pitch + "\">" + repTextBlock + "</prosody></amazon:effect></speak>"
    ssmlBlock = ssmlBlock.replace('철수', '<amazon:effect vocal-tract-length="+80%"><prosody pitch="-70%">안녕하세요? 저는 서연 친구 철수에요.</prosody></amazon:effect>')
    ssmlBlock = ssmlBlock.replace('귀신', '<amazon:effect name="whispered"><amazon:effect vocal-tract-length="-30%"><prosody volume="loud">나 꿍꼬또, 기싱꿍꼬또</prosody></amazon:effect></amazon:effect>')
    print (ssmlBlock)
      
    response = polly.start_speech_synthesis_task(
        OutputFormat= 'mp3',
        OutputS3BucketName= os.environ['BUCKET_NAME'],
        # OutputS3KeyPrefix='polly/',
        SampleRate='22050',
        SnsTopicArn=os.environ['SNS_TOPIC'],
        Text=ssmlBlock,
        TextType='ssml',
        VoiceId=voice
    )
    print (response)
    data = response['SynthesisTask']
      
    # Creating new record in DynamoDB table
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table(os.environ['DB_TABLE_NAME'])
    table.put_item(
        Item = {
            'id' : data['TaskId'],
            'updateDate': data['CreationTime'].strftime("%Y-%m-%d %H:%M:%S"),
            'voice' : voice,
            'originText': originText,
            'pollyStatus' : data['TaskStatus'],
            'timbre': timbre,
            'pitch': pitch,
            'mp3Url': data['OutputUri'],
            'RequestCharacters': data['RequestCharacters']
        }
    )
     
    result = {
        'statusCode': 200,
        'body': json.dumps({'recordId': data['TaskId']}),
        'headers': {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        }
    }
      
    return result


함수 환경 변수, 실행 시간, Tag를 설정합니다.

아래 Function code 영역에 코드를 다음과 같이 수정합니다.


두 번째 함수 만들기

Create function 버튼을 클릭해서 새로운 함수를 생성합니다.

GetNews 함수를 생성합니다.

from __future__ import print_function
  
import boto3
import os
import json
import decimal
from boto3.dynamodb.conditions import Key, Attr
 
# https://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/GettingStarted.Python.04.html
# Helper class to convert a DynamoDB item to JSON.
class DecimalEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, decimal.Decimal):
            if o % 1 > 0:
                return float(o)
            else:
                return int(o)
        return super(DecimalEncoder, self).default(o)
  
def lambda_handler(event, context):
    if "queryStringParameters" in event:
        parmas = event['queryStringParameters']
    print (parmas)
      
    postId = parmas["postId"]
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table(os.environ['DB_TABLE_NAME'])
   
    if postId == "*":
        items = table.scan()
    else:
        items = table.query(KeyConditionExpression=Key('id').eq(postId))
          
    response = {
        'statusCode': 200,
        'body': json.dumps(items["Items"], cls=DecimalEncoder),
        'headers': {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        }
    }
  
    return response


UpdateNews 함수 생성

코드

from __future__ import print_function
 
import boto3
import os
import json
from contextlib import closing
from boto3.dynamodb.conditions import Key, Attr
import re
 
def lambda_handler(event, context):
    polly_message = event["Records"][0]["Sns"]["Message"]
    print (polly_message)
    polly_response = json.loads(polly_message)
 
    # Updating the item in DynamoDB
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table(os.environ['DB_TABLE_NAME'])
  
    response = table.update_item(
        Key={
            'id': polly_response["taskId"]
        },
        UpdateExpression="set pollyStatus = :s",
        ExpressionAttributeValues={
            ':s': polly_response['taskStatus']
        }
    )
     
    print(response)
     
    s3 = boto3.client('s3')
    s3.put_object_acl(
        ACL = 'public-read',
        Bucket = os.environ['BUCKET_NAME'],
        Key = polly_response["taskId"] + ".mp3"
    )
     
    return

코드 및 환경 변수 설정

Lambda 함수가 기동되는 이벤트 등록

저장하고 나면 NewsTopic에 의해서 트리거가 활성화 된 것을 볼 수 있습니다.


DeleteNews 함수 등록

함수 생성

4개 람다 함수.


API Gateway 생성 및 배포

API Gateway 


생성 화면














S3 버켓 컨텐츠 업로드 (API 수정)


테스트

  • 레이블 없음