본문 바로가기

Daum Developers

서비스

뉴톤(Newtone) - 모바일(Android) SDK

목차

소개

Android 앱 개발 시 Daum Developers에서 제공하는 뉴톤 API SDK를 활용하여 뉴톤 기능을 앱에 추가할 수 있도록 합니다. 웹검색어, 지역검색어 등 몇가지 특성에 맞게 최적화된 음성 인식을 수행할 수 있으며 기본 API 뿐 아니라 샘플로 제공되는 예제 코드를 통해 손쉬운 UI 구성도 지원합니다

요구 사항

  • minimum OS 버전 : android 3.0 (API Level 11)
  • target OS 버전 : android 6.0 (API Level 23)
  • 개발툴 : android studio (IntelliJ)
  • ver 2.2 부터 eclipse를 지원하지 않습니다.

APIKey 발급

이 API를 사용하기 위해서는 App을 생성하고 APIKey를 발급받아야 합니다. (콘솔)

콘솔에서 App 생성 후 플랫폼(android, iOS 등)마다 APIKey를 추가할 수 있습니다.

Android 앱의 package name은 AndroidManifest.xml 파일 내 manifest 엘리먼트의 package 속성을 통해 알 수 있습니다

뉴톤 API SDK 설치하기

Android용 뉴톤 API SDK는 라이브러리 버전별로 받을 수 있습니다.

제공되는 zip 압축파일을 해제하면 개발에 필요한 라이브러리(lib)와 레퍼런스(doc), 샘플코드가 포함되어 있습니다.

안드로이드 뉴톤톡 SDK는 aar 패키징된 파일로 제공됩니다. 해당 파일은 다운 받으신 압축파일을 푼 후, daum-speech-openapi-x.x 폴더 안에서 확인 할 수 있습니다.

Resize icon

제공된 SDK를 본인의 프로젝트 안에 추가하려면 다음의 순서대로 진행하면 됩니다.

  1. android studio에서 현재 작업 중인 프로젝트를 연 다음, File -> New -> New Module... 순으로 메뉴를 선택합니다.

Resize icon

  1. 나타나는 대화창의 다양한 옵션 중에서 Import .JAR/.AAR Package를 선택합니다.

Resize icon

  1. File -> project Structure... 에서 app module의 dependencies에 daum-speech-openapi-5.0을 추가합니다. Resize icon

이후 해당 aar 파일의 위치를 지정해주면 라이브러리 설치는 완료 됩니다.

permission 설정하기

뉴톤 기능을 이용하기 위해서는 몇가지 permission을 설정해야만 합니다. AndroidManifest.xml 파일에는 다음과 같은 permission 들을 설정합니다.

AndroidManifest.xml 파일에서 다음과 같은 permission 들을 설정합니다. (bluetooth 는 선택사항입니다.)
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.BLUETOOTH" />

라이브러리 초기화 및 해제

뉴톤 기능을 이용하려면 먼저 라이브러리 초기화를 해줘야 합니다. 뉴톤 API를 이용하는 Activity의 onCreate()에서 라이브러리를 초기화하는 함수인 SpeechRecognizerManager의 initializeLibrary() 를 호출합니다.

더불어 onDestroy()에서 해제를 위한 함수를 호출합니다.

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    SpeechRecognizerManager.getInstance().initializeLibrary(this);
}

// ...

public void onDestroy() {
    super.onDestroy();

       SpeechRecognizerManager.getInstance().finalizeLibrary();
}

뉴톤 기능 Client 인스턴스 생성하기

뉴톤 기능의 설정과 실행은 SpeechRecognizerClient 를 통해 이용할 수 있습니다.

SpeechRecognizerClient 인스턴스는 SpeechRecognizerClient.Builder 를 통해 생성합니다.

뉴톤 설정을 위한 builder setter는 아래 표를 참고하세요.

설정 Key 설명
setApiKey(String) [required] 발급받은 Api Key.
setServiceType(String) [optional] 음성 인식에서 지원하는 서비스 타입으로 웹검색/연속어/지도/고립어 인식을 제공.
- SpeechRecognizerClient.SERVICE_TYPE_WEB : 웹 검색 키워드 검색어 인식에 최적화. 포탈 검색에 많이 사용되는 텍스트들에 인식 최적화.(default)
- SpeechRecognizerClient.SERVICE_TYPE_LOCAL : 지도 검색으로 장소명 검색어 인식에 최적화.
- SpeechRecognizerClient.SERVICE_TYPE_DICTATION : 연속어 인식으로 문장 인식에 최적화. 15초 이내의 한 문장 정도 인식에 사용.
- ServiceRecognizerClient.SERVICE_TYPE_WORD : 고립어 인식으로 단어 인식에 최적화. UserDictionary 에 설정된 단어들 중에서만 인식.
setGlobalTimeOut(int) [optional] 녹음에서 인식까지의 전체 세션 타임 아웃으로 default는 30초(sec).
setUserDictionary(String) [optional] 사용자 사전을 정의. 구분자는 '\n'(newline). 예) "다음\n카카오\n음성인식"
String userdict = "다음\n카카오\n음성인식"
SpeechRecognizerClient.Builder builder = new SpeechRecognizerClient.Builder().
        setApiKey(apikey).     // 발급받은 api key
        setServiceType(SpeechRecognizerClient.SERVICE_TYPE_WEB).
        setUserDictionary(userdict);    // optional

SpeechRecognizerClient client = builder.build();

사용자 사전에 설정된 단어들은 고립어(Word) 서비스 모드 경우 설정된 단어들 중에서만 인식하고, 그 외 서비스 모드들에서는 그 단어들의 인식율을 더 높이는 효과가 있습니다.

뉴톤 기능 listener 설정하기

뉴톤 실행 중에 발생하는 다양한 이벤트를 callback 메서드를 구현하여 처리할 수 있습니다.

callback 메서드를 받기 위해서는 SpeechRecognizeListener 인터페이스를 구현해야 합니다.

SpeechRecognizeListener는 결과값을 받거나 에러가 발생하는 등의 이벤트가 발생했을 때 해당 상태에 맞는 callback 메서드를 호출해 줍니다.

SpeechRecognizeListener를 추가하는 방법은 SpeechRecognizerClient의 setSpeechRecognizeListener() 메서드를 이용하면 됩니다.

client.setSpeechRecognizeListener(new SpeechRecognizeListener() {
    @Override
    public void onReady() {
        //To change body of implemented methods use File | Settings | File Templates.
    }
     // ...
});

결과값 이용하기

SpeechRecognizeListener를 구현하려면 총 7개의 메서드를 구현해야 합니다.

모든 callback 함수는 UI thread가 아닌 background thread에서 호출될 수 있기 때문에, UI와 관련된 작업은 Activity.runOnUiThread(Runnable) 이나 Handler.post(Runnable)을 통해 비동기로 호출해야 합니다. 우선 startRecording()이 실행된 이후에 음성 입력을 감지하면 감지된 음성에 대한 결과를 경우에 따라 두가지 callback을 통해 얻을 수 있습니다.

onPartialResult

onPartialResult는 완전히 음성이 종료되기 이전에 현재까지 인식된 음성데이터 문자열을 알려줍니다.

이 데이터는 서버에 질의해 데이터를 보정하는 과정을 거치지 않으므로, 다소 부정확할 수 있습니다.

중간 인식 결과에 대한 결과가 발생할 때마다 호출되므로 여러번 호출될 수 있습니다.

@Override
public void onPartialResult(String text) {
    //TODO implement interface DaumSpeechRecognizeListener method
}

Resize icon

onResults

onResults는 음성 입력이 종료된 것으로 판단하거나 stopRecording()을 호출한 후에 서버에 질의하는 과정까지 마치고 나면 호출됩니다.

인식된 문자열은 신뢰도가 높은 값부터 순서대로 Bundle의 SpeechRecognizerClient.KEY_RECOGNITION_RESULTS 값을 통해 ArrayList로 얻을 수 있습니다.

신뢰도는 Bundle의 SpeechRecognizerClient.KEY_CONFIDENCE_VALUES 값을 통해 ArrayList로 얻을 수 있으며 높은 값부터 순서대로 입니다.

신뢰도값은 항상 0보다 크거나 같은 정수이며, 문자열 목록과 같은 개수입니다.

Resize icon

@Override
public void onResults(Bundle results) {
       //TODO implement interface DaumSpeechRecognizeListener method
    ArrayList<String/> texts =     results.getStringArrayList(SpeechRecognizerClient.KEY_RECOGNITION_RESULTS);
    ArrayList<Integer/> confs =     results.getIntegerArrayList(SpeechRecognizerClient.KEY_CONFIDENCE_VALUES);
}

오류 처리 및 기타 이벤트 처리

onError

결과를 얻기 위한 callback 말고도 다양한 callback 메서드가 존재합니다.

onError callback은 이름에서 알 수 있듯이 에러가 발생했을 때 호출됩니다.

SpeechRecognizerClient 에서 다양한 에러 코드에 대응하는 ERROR_ 로 시작하는 code 값들을 확인할 수 있습니다.

@Override
public void onError(int errorCode, String errorMsg) {
    //TODO implement interface DaumSpeechRecognizeListener method
}

뉴톤 에러코드

뉴톤 API는 아래와 같은 에러코드를 전달합니다. SpeechRecognizerClient에서 ERROR_ 로 시작하는 static int 값들입니다.

Error Code 설명
ERROR_AUDIO_FAIL 음성입력이 불가능하거나 마이크 접근이 허용되지 않았을 경우.
ERROR_AUTH_FAIL apikey 인증이 실패한 경우.
ERROR_AUTH_TROUBLE apikey 인증 과정 중 내부 문제가 발생한 경우.
ERROR_NETWORK_FAIL 네트워크 오류가 발생한 경우.
ERROR_NETWORK_TIMEOUT 네트워크 타임아웃이 발생한 경우.
ERROR_SERVER_FAIL 서버에서 오류가 발생한 경우.
ERROR_SERVER_TIMEOUT 서버 응답 시간이 초과한 경우.
ERROR_NO_RESULT 인식된 결과 목록이 없는 경우.
ERROR_CLIENT 클라이언트 내부 로직에서 오류가 발생한 경우.
ERROR_RECOGNITION_TIMEOUT 전체 소요시간에 대한 타임아웃이 발생한 경우.
ERROR_SERVER_UNSUPPORT_SERVICE 제공하지 않는 서비스 타입이 지정됐을 경우.
ERROR_SERVER_USERDICT_EMPTY 입력된 사용자 사전에 내용이 없는 경우.
ERROR_SERVER_ALLOWED_REQUESTS_EXCESS 요청 허용 횟수 초과.

기타 이벤트

onReady callback은 SpeechRecognizerClient의 startRecording이 수행된 후에 단말기의 마이크 하드웨어와 OS의 오디오 서비스 등에 대한 초기화 등이 모두 준비되었을 때 호출됩니다.

여러가지 초기화 작업을 성공적으로 마치고 나면 가장 먼저 호출되는 callback입니다.

onBeginningOfSpeech callback은 사용자가 말하기 시작하는 것으로 판단될 때 호출되는 callback입니다.

사용자가 말을 하지 않은 상태에서 에러가 발생하면 이 callback은 호출되지 않습니다.

onEndOfSpeech callback은 사용자가 말하는 것을 끝마친 것으로 판단될 때 호출되는 callback입니다.

사용자가 너무 오랜 시간을 말하여 앱이 지정한 시간 내에 음성을 인식하지 못하였거나 인식 중에 오류가 발생했을 때는 호출되지 않습니다.

onEndOfSpeech 이후에는 음성데이터를 서버에 전송하여 값을 보정하고 후보 인식 목록과 신뢰도를 조회하는 과정을 거칩니다.

Resize icon

onAudioLevel callback은 음성이 입력되는 도중에 입력되는 음성의 크기를 dB(데시벨)로 판별한 후 재가공을 거친 상대값을 알려줍니다. 0과 1 사이의 float 값입니다. onFinished callback은 인식 종료 후 사용하던 리소스를 모두 해제한 후에 호출됩니다. 음성인식이 성공적으로 종료되었을 때만 호출되며, onError가 발생했을 때는 호출되지 않습니다.

@Override
public void onReady() {
    //TODO implement interface DaumSpeechRecognizeListener method
}

@Override
public void onBeginningOfSpeech() {
    //TODO implement interface DaumSpeechRecognizeListener method
}

@Override
public void onEndOfSpeech() {
    //TODO implement interface DaumSpeechRecognizeListener method
}

@Override
public void onAudioLevel(float v) {
    //TODO implement interface DaumSpeechRecognizeListener method
}

@Override
public void onFinished() {
    //TODO implement interface DaumSpeechRecognizeListener method
}

뉴톤 시작하기

뉴톤을 시작하는 방법은 간단합니다.

아래와 같이 SpeechRecognizerClient의 startRecording() 메서드를 호출합니다.

파라미터로 입력하는 boolean 값은 현재 재생되고 있는 음악 등 배경 소리를 음소거(mute)할지 지정하는 값입니다. 값이 true면 음소거를 합니다.

음소거를 했을 경우 음성 인식이 종료되면 음소거가 종료됩니다.

client.startRecording(true);

뉴톤 기능 중지하기

음성 인식을 중지하는 방법은 cancelRecording() 메서드와 stopRecording() 메서드, 두가지가 있습니다.

cancelRecording() 은 음성 인식을 취소하며 이후로 진행되는 동작은 없습니다.

stopRecording() 은 음성 인식을 멈추지만, startRecording()을 실행한 이후부터 stopRecording()을 실행할 때까지의 음성데이터를 이용해 음성 인식을 진행합니다.

따라서, stopRecording() 은 실행된 이후에 SpeechRecognizeListener의 onResults 또는 onError가 호출되게 됩니다.

client.cancelRecording();

// or

client.stopRecording();

SDK에서 제공하는 기본 UI사용하기

SpeechRecognizerClient와 DaumSpeechRecognizeListener를 직접 이용하는 방법 말고도 제공되는 기본 UI를 통해 간편하게 사용자에게 UI를 제공할 수 있습니다.

다만 android에서는 리소스를 포함하는 jar 파일 이용을 지원하지 않으므로, 간결하게 UI를 구성할 수 있도록 샘플 프로젝트의 리소스와 예제 코드를 이용할 수 있습니다. 기본 UI에서 많은 커스터마이징이 필요하다면 별도로 제작하는 것이 바람직합니다.

UI와 관련된 파일 복사하기

기본 UI로 이용할 수 있는 뉴톤 기능은 압축파일에 있는 샘플 프로젝트의 VoiceRecoActivity를 이용합니다.

VoiceRecoActivity은 뉴톤 API SDK의 SpeechRecognizerActivity를 상속받아 구현하였습니다.

가장 먼저 할 작업은 압축파일에 포함된 기본 UI를 구현한 VoiceRecoActivity.java를 필요한 프로젝트에 복사합니다.

java 파일을 복사한 후에 package name은 그에 맞게 변경합니다.

Resize icon

그리고 activity에서 이용하는 리소스들을 프로젝트의 리소스 경로 위치로 복사합니다. 복사하는 것으로 인해 기존 프로젝트의 리소스를 덮어쓰지 않도록 파일 이름들이 설정되어 있습니다. res/ 밑에 anim, drawable, drawable-xhdpi 를 모두 복사합니다. res/layout/ 밑에 net_daum_mf_asr 로 시작하는 xml 파일들을 모두 복사합니다. res/values/ 밑에 net_daum_mf_asr_strings.xml 파일을 복사합니다.

Resize icon

AndroidManifest.xml 에 activity 등록하기

이제 AndroidManifest.xml에 activity를 등록만 하면 VoiceRecoActivity의 이용을 위한 준비는 끝납니다.

<!-- package 이름은 적절히 변경합니다. -->
<activity android:name="net.daum.speech.api.openapisample.VoiceRecoActivity" />

activity 호출하기

VoiceRecoActivity를 이용하여 호출하기 전에 Intent로 한가지 정보는 꼭 넘겨주어야 합니다. 바로 APIKey 입니다.

APIKey 이외에 설정할 수 있는 값이나, 자세한 내용은 javadoc을 참고하면 됩니다.

Intent i = new Intent(getApplicationContext(), VoiceRecoActivity.class);

i.putExtra(SpeechRecognizerActivity.EXTRA_KEY_API_KEY, apikey); 
// apiKey는 신청과정을 통해 package와 매치되도록 발급받은 APIKey 문자열 값.

startActivityForResult(i, 0);

이벤트 처리 - onActivityResult 이용

제공된 샘플의 SpeechSampleActivity의 onActivityResult method를 참고하여 성공과 실패에 대한 결과를 처리할 수 있습니다.

성공시에는 resultCode 가 RESULT_OK 이며, 에러가 발생하는 등의 실패가 발생했을 땐 RESULT_CANCELED 입니다.

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (resultCode == RESULT_OK) { // 성공
        ArrayList<String> results = data.getStringArrayListExtra(VoiceRecoActivity.EXTRA_KEY_RESULT_ARRAY);
        // ....
    }
    else if (requestCode == RESULT_CANCELED) { // 실패
        int errorCode = data.getIntExtra(VoiceRecoActivity.EXTRA_KEY_ERROR_CODE, -1);
        String errorMsg = data.getStringExtra(VoiceRecoActivity.EXTRA_KEY_ERROR_MESSAGE);
        // ....
    }
}

Intent의 extra 값은 VoiceRecoActivity.java의 상단에서 알 수 있듯이 다음과 같은 key 들로 조회할 수 있습니다.

public static String EXTRA_KEY_RESULT_ARRAY = "result_array"; // 결과값 목록.     ArrayList<String>
public static String EXTRA_KEY_MARKED = "marked"; // 첫번째 값의 신뢰도가 현저하게 높은 경우 true. 아니면 false. Boolean
public static String EXTRA_KEY_ERROR_CODE = "error_code"; // 에러가 발생했을 때 코드값. 코드값은     SpeechRecognizerClient를 참조. Integer
public static String EXTRA_KEY_ERROR_MESSAGE = "error_msg"; // 에러 메시지. String

이벤트 처리 - VoiceRecoActivity의 Override methods

onResourcesWillInitialize 메서드는 UI를 변경하지 않고 이용한다면 sample의 내용을 그대로 이용할 수 있습니다.

일부 리소스를 변경하고자 할 때는 SpeechRecognizerClient의 RES_STRINGS와 RES_VIEWID enum 과 관련된 문서를 참고하여 어떤 리소스의 ID가 어떤 상황에서 표시되는지 알 수 있습니다.

제공되는 기본 UI에서 리소스나 레이아웃을 변경할 때는 이미지 파일이나 strings 파일, layout xml을 직접 변경하는 것을 권장합니다.

@Override
protected void onResourcesWillInitialize() {
// ....
}

onRecognitionSuccess 메서드는 VoiceRecoActivity를 통해 음성 인식을 성공했을 때 호출되는 메서드입니다.

샘플 프로젝트에서 오버라이드한 onRecognitionSuccess 메서드는 결과 목록을 호출한 Activity로 값을 전달하도록 하였습니다.

메서드의 marked 파라미터는 결과목록의 첫번째 값이 신뢰도가 높은 값인지 알려줍니다. 결과 목록에서 첫번째 값의 신뢰도가 SpeechRecognizerClient.CONFIDENCE_PASSING_MARKED(int = 10) 보다 같거나 높으면 높은 신뢰도를 가지는 값으로 간주할 수 있습니다.

이와 별도로 결과 목록의 첫번째 값의 신뢰도가 낮으면 결과 목록을 UI에서 표시하고, 이 목록에서 사용자가 말한 단어를 선택할 수 있도록 도와줍니다.

이 단계를 거쳐서 값이 선택될 때도 marked 값은 true 입니다. marked 값이 false가 되는 경우는 후보단어 목록 표시 생략하기에서 알 수 있습니다.

@Override
protected void onRecognitionSuccess(List<String&gr; result, boolean marked) {
    // ....
}

onRecognitionFailed 메서드는 VoiceRecoActivity를 통해 수행한 음성 인식이 실패하거나 에러가 발생했을 때 호출되는 메서드입니다. sample에서의 기본 동작은 에러코드와 에러 메시지를 호출한 Activity로 전달합니다.

@Override
protected void onRecognitionFailed(int errorCode, String errorMsg) {
    // ....
}

후보단어 목록 표시 생략하기

기본 UI를 통한 구현은 기본적으로 높은 신뢰도를 가지지 못한 결과 목록을 얻었을 때 사용자에게 말한 내용을 판단하도록 거치는 과정이 있습니다.

이 과정을 생략하거나 다른 방법으로 안내를 하려면 SpeechRecognizerActivity 를 호출할 때 Intent의 extra 값으로 SpeechRecognizerActivity.EXTRA_KEY_SHOW_SUGGEST_LIST 키값을 설정하면 됩니다.

설정하지 않으면 기본값은 true이며, false면 후보단어 목록 표시를 생략하고 true면 후보단어 목록을 표시합니다.

후보단어 표시를 생략한 채로 onRecognitionSuccess callback 메서드가 호출되면 신뢰도가 높은 결과값인지 판단할 근거가 부족해집니다.

그래서 후보단어 표시를 생략하고 결과 목록의 신뢰도가 낮을 때만 onRecognitionSuccess 메서드의 marked 파라미터 값이 false가 됩니다.

Intent i = new Intent(getApplicationContext(), VoiceRecoActivity.class);

i.putExtra(SpeechRecognizerActivity.EXTRA_KEY_API_KEY, apikey);
i.putExtra(SpeechRecognizerActivity.EXTRA_KEY_SHOW_SUGGEST_LIST, false); // 후보단어 목록 표시 생략

startActivityForResult(i, 0);

추가 설명

서비스 모드 설명

서비스 모드란 음성인식 도메인이고, 각 도메인에 더 특화되어 있다.

서비스 모드 도메인 종류

서비스 모드 도메인 설명
Dictation 받아쓰기 모드이며 일반적인 문장, 대화체 인식에 사용
Local 주소, 지역명 인식에 사용(예 : 카카오맵 앱 음성 인식)
Web or Search 웹 검색에 사용 (예 : 다음 앱 음성 인식)
Word 명령어 인식에 사용

Word 모드에서 User Dict (사용자 사전) 설명

user dict (사용자 사전) 에 단어들을 넣어놓고 인식하면 그 단어들중에서만 인식 된다. user dict 에는 한단어 이상 반드시 들어가야 한다. (user dict 가 비워져있으면 에러 반환된다.) 그외 서비스 모드에서는 사용자 사전 안에 있는 텍스트들을 좀 더 가중치를 줘서 인식한다. 최대 허용 사이즈 : 약 3000 단어 정도

api 사용 방식

비동기적으로 사용해야 함

message queue, event handler 등을 사용해 비동기적으로 처리하고 onPartialResult, onError 등의 이벤트 호출 함수 안에서 또 다시 음성 api 함수를 재호출하지 않는다.

예 : onInactive (ios 라이브러리 경우 onFinished) 호출 안에서 startListening 재 호출 하는 경우 지양

stop (stopRecording 또는 stopListening) 과 cancel 호출 의 차이

stop : stop 이 호출된 시점까지 인식된다. (서버의 인식 결과를 기다림) cancel : cancel 이 호촐되면 바로 취소된다. (서버의 인식 결과를 기다리지 않고 취소됨)

종료시 마지막으로 호출되는 이벤트 함수

에러 발생시 : onError 정상 종료시 : onInacitive

이중 실행 금지

ios, android 음성 라이브러리는 기본적으로 이중 또는 다중 실행을 지원하지 않는다. 중복 실행시 여러 버그의 원인이 된다.

이용 가능 횟수

apikey 신청후 테스트 용도로 사용시 하루 100 회 까지 가능하다. 개인 실명 또는 회사 법인 확인 절차 이후 하루 20000 회 까지 가능하다. (인식 합성 모두 이용시 총 요청 횟수는 20000 회 까지 가능하다.) 20000회 이상 사용시 별도의 제휴 계약을 맺어야 한다.

카카오 이미지 표시 의무

개인 또는 법인이 상업적 용도로 사용시 아래 카카오(powered by kakao) 이미지를 음성 인식 라이브러리를 사용하고 있을때 표시하여야 한다. 폰 화면 상에서 위치는 상하 좌우측 또는 중간에 표시한다. 이미지 사이즈는 최소 가로 102 픽셀, 세로 15 픽셀 이상이어야 한다. (더 크게 표시할 경우 가로 세로 배율 유지)

Resize icon

이용사례

좋은 사례를 갤러리에 올려주시면, 검토 후 추가하겠습니다.