Jump to content
과거의 기술자료(읽기 전용): https://tech.devgear.co.kr ×
과거의 기술자료(읽기 전용): https://tech.devgear.co.kr

OpenAI를 사용해 사실적인 텍스트 음성 변환을 여러분의 앱에 추가하는 방법


Recommended Posts

Softacom Information"How To Use OpenAI To Add Realistic Text-To-Speech To Your Apps" 을 번역했습니다. (원문 작성: 2024년 5월, 최종 번역: 2024년 5월)

(옮긴이: 이 기고는 상당히 자세하고 유익합니다. 이 기고를 통해 충분히 학습을 할 수 있습니다.

다만, 이 기고와 소스 코드는 10.4 버전에서 구현되었습니다. 따라서 현재 최신 버전인 12.x에서는 컴파일 오류가 발생합니다. 혹시 최신 버전인 12.x 버전에서 작동되는 전체 소스 코드가 필요하다면, https://welcome.devgear.co.kr/contact 으로 요청하세요. 개별적으로 전달드리겠습니다)

OpenAI를 사용해 사실적인 음성을 텍스트로부터 생성하는 크로스 플랫폼 앱을 만들고 싶은가? 이를 수행하는 방법에 대한 모든 내용을 정리한 기술 파트너 Softacom의 이 기고를 읽어보기 바란다.

OpenAI를 사용해 사실적인 텍스트 음성 변환을 여러분의 앱에 추가하는 방법 개요

최신 인공지능-기반 서비스들은 음성 생성 및 음성-텍스트 변환을 가능하게 해준다. 게다가, 다양한 언어를 지원한다. 우리는 그 서비스에 쉽게 텍스트를 전달하고 나서 합성된 음성 출력을 받을 수 있다. 또한 다양한 설정들이 제공되므로, 우리는 생성되는 음성에 반영될 음성 유형을 선택할 수도 있다.

게다가, 음성을 텍스트로 변환하는 것도 가능하다. 예를 들어, 우리가 좋아하는 아티스트의 노래가 담긴 MP3 트랙으로부터 그 가사를 텍스트로 뽑아낼 수 있다.

이 기고에서는,  OpenAI API의 기능들 중 텍스트 설명을 바탕으로 음성을 생성하는 기능 그리고, 그 반대 방향 변환, 즉 음성을 바탕으로 텍스트를 생성하는 기능을 분석한다. 그리고 그것을 우리의 엠바카데로 델파이 FMX 애플리케이션 안에서 구현한다.

OpenAI API의 음성 생성 기능 그리고 텍스트 추출 기능을 사용하기 위해서는, 먼저 비밀 키를 등록하고 획득해야 한다. 텍스트 생성하기에만 집중한 기고에서, 우리는 비밀 키(API key) 등록 및 획득 절차를 설명했었다.

spacer.png
사용자 요청에 따라 음성을 생성하기 위해서, 우리는 OpenAI API("Create speech" 탭)를 활용할 것이다.

OpenAI API는 광범위한 기능을 음성 생성을 위해 제공한다. 여기서, 우리는 음성 유형, 출력 미디어 파일 형식(예: mp3, wav 등), 생성된 미디어 파일의 음성 속도, 생성된 음성에 대한 텍스트 설명, 사용할 기계 학습 모델(tts- 1 또는 tts-1-hd) 등을 구성할 수 있다.

spacer.png

음성에서 텍스트를 추출하기 위해서, 우리는 OpenAI API("Create transcription" 탭)도 사용한다.

spacer.png

OpenAI API에는 음성을 바탕으로 텍스트를 생성하는 기능도 풍부하다.

여기에서, 우리는 입력 미디어 파일 유형(mp3, wav 등)과 OpenAI의 응답 형식(json, text, srt, verbose_json 또는 vtt)을 구성할 수 있다.

spacer.png

OpenAI API 활용을 강화해서, 사용자 요청에 따라 음성을 생성하고 음성에서 텍스트를 생성하도록 하기 위해서, 우리는 이전에 개발했던 TChatGPT 클래스의 기능을 확장할 것이다. 그 클래스에 대한 설명은 예전에 텍스트 생성에 집중해서 게시했던 기고를 보면 된다.

이 클래스에 메서드들을 추가하자. GetGeneratedSpeechAsStream, GetGeneratedTextFromSpeech 등을 추가할 것이다. 또한 우리는 오버로드된 생성자 메서드 즉 Create를 추가할 것이다. 그래서 미디어 파일의 음성에서 텍스트를 추출할 수 있도록 만들 것이다.

spacer.png

TChatGPT 클래스의 생성자 메서드들 중에서 우리가 취하는 버전은 다음 오브젝트들을  입력 파라미터로 받는다: HttpBasicAuthenticator(THTTPBasicAuthenticator 클래스), RESTClient(TRESTClient 클래스), RESTRequest(TRESTRequest 클래스), 문자열 상수인 OpenAIApiKey(여기에는 우리의 비밀 키를 담는다).

spacer.png

이 ChatGPT 클래스를 사용하면, 우리는 TMemoryStream 오브젝트를 얻을 수 있다. 이 오브젝트에는 텍스트 설명을 기반으로 생성된 음성이 들어 간다. 그 음성은 OpenAI API를 사용해 얻는다.

그것을 위해 입력 파라미터로 전달하는 것들이 있다. 문자열 상수인 Input과 Voice다. 그것들은 우리의 텍스트 설명 그리고 생성되는 음성 유형(alloy, echo, fable, onyx, nova, shimmer)을 가리킨다.

사용되는 기계 학습 모델(이 경우 tts-1)에 대한 세부 정보와 음성 생성(입력) 및 음성 유형(음성)에 대한 텍스트 설명은 JObj 안에 포함된다. 이것은 TJSONObject 클래스의 오브젝트다.

문자열 변수인 Request는 JObj 오브젝트로부터 데이터를 꺼내 문자열로 저장한다. 또한, Request 안에 문자열 형식으로 담긴 내용은 StringStream 오브젝트(TStringStream 클래스)에게 전달된다.

그 다음, StringStream으로부터 꺼낸 문자열 데이터는 MultipartFormData 오브젝트(TMultipartFormData 클래스)에게 전송된다.

OpenAI API의 음성 생성용 URL은 FNetHttpClient 오브젝트의 Post 메서드에게 입력 파라미터 형태로 전달된다.

또한, MultipartFormData 오브젝트는 우리의  모델 데이터, 음성 생성을 위한 텍스트 설명, 음성 유형을 담은 채 전달된다.

텍스트 및 이미지 생성 프로젝트들과 비슷하게, 우리는 헤더(Authorization와 Content-Type)도 포함해야 한다. FNetHttpClient.Post 메서드를 실행하면, 우리는 OpenAI에서 생성 음성을 TMemoryStream 형식으로 획득하게 된다.

spacer.png

GetGeneratedTextFromSpeech 메서드를 사용하면, 우리는 음성을 텍스트로 변환할 수 있다. 이 메서드는 문자열 상수 InputAudioFilePath를 입력 받는다. 거기에는 해당 미디어 파일에 대한 경로가 들어 있다. FRESTClient 오브젝트의 BaseURL 프로퍼티에는 미디어 파일의 음성을 바탕으로 텍스트를 생성하는 OpenAI API의 URL이 들어 있다. FRESTRequest 오브젝트에는 OpenAI의 응답 유형(text, json, srt, verbose_json 또는 vtt), 사용되는 기계 학습 모델(이 경우 Whisper-1), 녹음된 음성이 담긴 미디어 파일의 경로 등의 정보가 들어 있다.

인증 처리는 FHTTPBasicAuthenticator 오브젝트(THTTPBasicAuthenticator 클래스)를 사용해 수행된다. 우리는 우리의 비밀 키를 Password 필드에 할당해야 한다(FHTTPBasicAuthenticator.Password:= FOpenAIApiKey).

FRESTRequest.Execute 메서드는 POST 요청을 수행한다. 그래서 미디어 파일을 전달해 OpenAI를 통해 텍스트를 추출한다. 그 결과로, 우리는 변환된 음성 텍스트를 담은 문자열(Result:= FRESTRequest.Response.Content)을 받게 된다.

spacer.png

TChatGPT 클래스의 전체 소스 코드는 다음과 같다.

unit ChatGPTHelper;
 
interface
 
uses
  System.SysUtils, System.Types, System.UITypes, System.Classes,
  System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics,
  FMX.Dialogs, FMX.Memo.Types, FMX.ScrollBox, FMX.Memo, FMX.StdCtrls,
  FMX.Controls.Presentation, System.Net.URLClient, System.Net.HttpClient,
  System.Net.HttpClientComponent, JSON, System.Threading,
  System.Net.Mime, System.Generics.Collections,
  REST.Client, REST.Types, REST.Authenticator.Basic;
 
type
  IChatGPTHelper = interface
    function SendTextToChatGPT(const Text: string): string;
    function GetJSONWithImage(const Prompt: string; ResponseFormat: Integer): string;
    function GetImageURLFromJSON(const JsonResponse: string): string;
    function GetImageAsStream(const ImageURL: string): TMemoryStream;
    function GetImageBASE64FromJSON(const JsonResponse: string): string;
    function GetGeneratedSpeechAsStream(const Input: string; const Voice: string): TMemoryStream;
    function GetGeneratedTextFromSpeech(const InputAudioFilePath: string): string;
  end;
 
  TChatGPT = class(TInterfacedObject, IChatGPTHelper)
  private
    FNetHttpClient: TNetHTTPClient;
    FHttpBasicAuthenticator: THTTPBasicAuthenticator;
    FRestRequest: TRESTRequest;
    FRestClient: TRESTClient;
    FOpenAIApiKey: string;
    FText: string;
    function FormatJSON(const JSON: string): string;
    function SendTextToChatGPT(const Text: string): string;
    function GetJSONWithImage(const Prompt: string; ResponseFormat: Integer): string;
    function GetImageURLFromJSON(const JsonResponse: string): string;
    function GetImageAsStream(const ImageURL: string): TMemoryStream;
    function GetImageBASE64FromJSON(const JsonResponse: string): string;
    function GetGeneratedSpeechAsStream(const Input: string; const Voice: string): TMemoryStream;
    function GetGeneratedTextFromSpeech(const InputAudioFilePath: string): string;
  public
    constructor Create(const NetHttpClient: TNetHTTPClient;
      const OpenAIApiKey: string); overload;
    constructor Create(const HttpBasicAuthentificator: THTTPBasicAuthenticator;
      const RESTClient: TRESTClient; const RESTRequest: TRESTRequest;
      const OpenAIApiKey: string); overload;
    class function MessageContentFromChatGPT(const JsonAnswer: string): string;
  end;
 
implementation
 
{ TFirebaseAuth }
 
constructor TChatGPT.Create(const NetHttpClient: TNetHTTPClient;
  const OpenAIApiKey: string);
begin
  FNetHttpClient := NetHttpClient;
  if OpenAIApiKey <> '' then
    FOpenAIApiKey := OpenAIApiKey
  else
  begin
    ShowMessage('OpenAI API key is empty!');
    Exit;
  end;
end;
 
constructor TChatGPT.Create(const HttpBasicAuthentificator: THTTPBasicAuthenticator;
  const RESTClient: TRESTClient; const RESTRequest: TRESTRequest;
  const OpenAIApiKey: string);
begin
  FHttpBasicAuthenticator := HttpBasicAuthentificator;
  FRestRequest :=  RESTRequest;
  FRestClient := RESTClient;
  if OpenAIApiKey <> '' then
    FOpenAIApiKey := OpenAIApiKey
  else
  begin
    ShowMessage('OpenAI API key is empty!');
    Exit;
  end;
end;
 
function TChatGPT.FormatJSON(const JSON: string): string;
var
  JsonObject: TJsonObject;
begin
  JsonObject := TJsonObject.ParseJSONValue(JSON) as TJsonObject;
  try
    if Assigned(JsonObject) then
      Result := JsonObject.Format()
    else
      Result := JSON;
  finally
    JsonObject.Free;
  end;
end;
 
function TChatGPT.GetGeneratedSpeechAsStream(const Input, Voice: string): TMemoryStream;
var
  JObj: TJsonObject;
  Request: string;
  MultipartFormData: TMultipartFormData;
  MyHeaders: TArray<TNameValuePair>;
  StringStream: TStringStream;
begin
  JObj := nil;
  MultipartFormData := nil;
  StringStream := nil;
  try
    Result := TMemoryStream.Create;
    SetLength(MyHeaders, 2);
    MyHeaders[0] := TNameValuePair.Create('Authorization', FOpenAIApiKey);
    MyHeaders[1] := TNameValuePair.Create('Content-Type', 'application/json');
    JObj := TJSONObject.Create;
    JObj.AddPair('model', 'tts-1');
    JObj.AddPair('input', Input);
    JObj.AddPair('voice', Voice);
    Request := JObj.ToString;
    MultipartFormData := TMultipartFormData.Create;
    StringStream := TStringStream.Create(Request, TEncoding.UTF8);
    MultipartFormData.Stream.LoadFromStream(StringStream);
    FNetHttpClient.Post('https://api.openai.com/v1/audio/speech',
    MultiPartFormData, Result, MyHeaders);
  finally
    JObj.Free;
    MultipartFormData.Free;
    StringStream.Free;
  end;
end;
 
function TChatGPT.GetGeneratedTextFromSpeech(const InputAudioFilePath: string): string;
begin
  FRESTClient.Authenticator := FHTTPBasicAuthenticator;
  FRESTRequest.Method := TRESTRequestMethod.rmPOST;
  FHTTPBasicAuthenticator.Password := FOpenAIApiKey;
  FRESTClient.BaseURL := 'https://api.openai.com/v1/audio/transcriptions';
  FRESTRequest.AddParameter('response_format', 'text',
    TRESTRequestParameterKind.pkREQUESTBODY);
  FRESTRequest.AddParameter('model', 'whisper-1',
    TRESTRequestParameterKind.pkREQUESTBODY);
  FRESTRequest.AddFile('file', InputAudioFilePath,
    TRESTContentType.ctAPPLICATION_OCTET_STREAM);
  FRESTRequest.Client := FRESTClient;
  FRESTRequest.Execute;
  Result := FRESTRequest.Response.Content;
end;
 
function TChatGPT.GetImageAsStream(const ImageURL: string): TMemoryStream;
begin
  Result := TMemoryStream.Create;
  FNetHTTPClient.Get(ImageURL, Result);
end;
 
function TChatGPT.GetImageURLFromJSON(const JsonResponse: string): string;
var
  Json: TJsonObject;
  DataArr: TJsonArray;
begin
  Json := TJsonObject.ParseJSONValue(JsonResponse) as TJsonObject;
  try
    if Assigned(Json) then
    begin
      DataArr := TJsonArray(Json.Get('data').JsonValue);
      Result := TJSONPair(TJSONObject(DataArr.Items[0]).Get('url')).JsonValue.Value;
    end
    else
      Result := '';
  finally
    Json.Free;
  end;
end;
 
function TChatGPT.GetImageBASE64FromJSON(const JsonResponse: string): string;
var
  Json: TJsonObject;
  DataArr: TJsonArray;
begin
  Json := TJsonObject.ParseJSONValue(JsonResponse) as TJsonObject;
  try
    if Assigned(Json) then
    begin
      DataArr := TJsonArray(Json.Get('data').JsonValue);
      Result := TJSONPair(TJSONObject(DataArr.Items[0]).Get('b64_json')).JsonValue.Value;
    end
    else
      Result := '';
  finally
    Json.Free;
  end;
end;
 
 
function TChatGPT.GetJSONWithImage(const Prompt: string; ResponseFormat: Integer): string;
var
  JObj: TJsonObject;
  Request: string;
  ResponseContent, StringStream: TStringStream;
  MultipartFormData: TMultipartFormData;
  MyHeaders: TArray<TNameValuePair>;
begin
  JObj := nil;
  MultipartFormData := nil;
  ResponseContent := nil;
  StringStream := nil;
  try
    SetLength(MyHeaders, 2);
    MyHeaders[0] := TNameValuePair.Create('Authorization', FOpenAIApiKey);
    MyHeaders[1] := TNameValuePair.Create('Content-Type', 'application/json');
    JObj := TJSONObject.Create;
    with JObj do
    begin
      Owned := False;
      AddPair('model', 'dall-e-2');
      if ResponseFormat = 1 then
        AddPair('response_format','b64_json')
      else
        AddPair('response_format','url');
      AddPair('prompt', Prompt);
      AddPair('n', TJSONNumber.Create(1));
      AddPair('size', '1024x1024');
    end;
    Request := Jobj.ToString;
    MultipartFormData := TMultipartFormData.Create;
    StringStream := TStringStream.Create(Request, TEncoding.UTF8);
    MultipartFormData.Stream.LoadFromStream(StringStream);
    ResponseContent := TStringStream.Create;
    FNetHttpClient.Post('https://api.openai.com/v1/images/generations',
      MultiPartFormData, ResponseContent, MyHeaders);
    Result := ResponseContent.DataString;
  finally
    JObj.Free;
    MultipartFormData.Free;
    ResponseContent.Free;
    StringStream.Free;
  end;
end;
 
class function TChatGPT.MessageContentFromChatGPT(const JsonAnswer: string): string;
var
  Mes: TJsonArray;
  JsonResp: TJsonObject;
begin
  JsonResp := nil;
  try
    JsonResp := TJsonObject.ParseJSONValue(JsonAnswer) as TJsonObject;
    if Assigned(JsonResp) then
    begin
      Mes := TJsonArray(JsonResp.Get('choices').JsonValue);
      Result := TJsonObject(TJsonObject(Mes.Get(0)).Get('message').JsonValue).GetValue('content').Value;
    end
    else
      Result := '';
  finally
    JsonResp.Free;
  end;
end;
 
function TChatGPT.SendTextToChatGPT(const Text: string): string;
var
  JArr: TJsonArray;
  JObj, JObjOut: TJsonObject;
  Request: string;
  ResponseContent, StringStream: TStringStream;
  MultipartFormData: TMultipartFormData;
  Headers: TArray<TNameValuePair>;
  I: Integer;
begin
  JArr := nil;
  JObj := nil;
  JObjOut := nil;
  MultipartFormData := nil;
  ResponseContent := nil;
  StringStream := nil;
  try
    SetLength(Headers, 2);
    Headers[0] := TNameValuePair.Create('Authorization', FOpenAIApiKey);
    Headers[1] := TNameValuePair.Create('Content-Type', 'application/json');
    JObj := TJsonObject.Create;
    JObj.Owned := False;
    JObj.AddPair('role', 'user');
    JArr := TJsonArray.Create;
    JArr.AddElement(JObj);
    Self.FText := Text;
    JObj.AddPair('content', FText);
    JObjOut := TJsonObject.Create;
    JObjOut.AddPair('model', 'gpt-3.5-turbo');
    JObjOut.AddPair('messages', Trim(JArr.ToString));
    JObjOut.AddPair('temperature', TJSONNumber.Create(0.7));
    Request := JObjOut.ToString.Replace('', '');
    for I := 0 to Length(Request) - 1 do
    begin
      if ((Request[I] = '"') and (Request[I + 1] = '[')) or
        ((Request[I] = '"') and (Request[I - 1] = ']')) then
      begin
        Request[I] := ' ';
      end;
    end;
    ResponseContent := TStringStream.Create;
    MultipartFormData := TMultipartFormData.Create;
    StringStream := TStringStream.Create(Request, TEncoding.UTF8);
    MultipartFormData.Stream.LoadFromStream(StringStream);
    FNetHttpClient.Post('https://api.openai.com/v1/chat/completions',
      MultipartFormData, ResponseContent, Headers);
    Result := FormatJSON(ResponseContent.DataString);
  finally
    StringStream.Free;
    ResponseContent.Free;
    MultipartFormData.Free;
    JObjOut.Free;
    JArr.Free;
    JObj.Free;
  end;
end;
 
end.

텍스트 설명을 바탕으로 음성 생성하기 그리고 미디어 파일에 있는 음성에서 텍스트 추출하기를 엠바카데로 델파이 FMX 애플리케이션 안에서 구현했다. 

우리의 델파이 FMX 애플리케이션 안에서, 우리는 TNetHttpClient 컴포넌트를 사용해 OpenAI API를 다룬다. 특히 OpenAI에 POST 요청을 보내는데 사용한다.

spacer.png

OpenAI에 의해 생성되어 미디어 파일 안에 (MP3 형식으로) 저장된 음성을 재생하기 위해 우리의 엠바카데로 델파이 FMX 애플리케이션안에서는 TMediaPlayer 컴포넌트를 사용한다.

spacer.png

OpenAI에게 음성이 저장된 미디어 파일을 전송해 거기에서 텍스트를 추출하도록 요청하기 위해서, 우리가 사용하는 컴포넌트는 세 가지다: TRESTClient, TRESTRequest, THTTPBasicAuthenticator.

spacer.png

이 컴포넌트들에 추가로 더 설정할 것은 없다. TRESTClient와 TRESTRequest는 POST 요청을 만든다. 그리고 OpenAI에서 오는 데이터를 추출한다. 그 데이터에는 우리가 전달한 미디어 파일에서 추출된 음성 텍스트가 담겨있다. THTTPBasicAuthenticator는 비밀 키를 사용하는 인증에 사용된다.

음성 생성을 위한 텍스트 설명을 입력하기 위해, 우리는 TMemo 컴포넌트를 사용한다.

spacer.png

또한 이 TMemo 컴포넌트는 미디어 파일의 음성에서 추출된 텍스트를 표시하는데도 사용한다.

메인 폼(form)의 onCreate 메서드 안에서, 우리는 OpenAI에서 생성한 음성이 미디어 파일 형태로 저장될  경로를 대입해야 한다. FAudioFilePath 필드에 그 경로를 대입한다. 또한 FOpenAIApiKey 필드에는 비밀 키 값을 대입한다.

spacer.png

우리의 엠바카데로 델파이 FMX 애플리케이션의 기능들 중 텍스트 설명을 기반으로 음성을 생성하기, 그것을 미디어 파일에 저장하기, 그 음성 파일을 재생하기 등의 기능을 구현하는 곳은 "Send Request For Speech Generation" 버튼의 onClick 핸들러 안이다. 이 핸들러 안에서, 우리는 GPTHelper(IChatGPTHelper 타입) 오브젝트들을 선언하고, 텍스트 설명을 OpenAI에 전달해 음성을 생성하도록 한다. 그리고 ImageStream(TMemoryStream 클래스) 오브젝트를 선언한다. 생성되는 음성이 그 안에 TMemoryStream으로 담기도록 하기 위해서다.

spacer.png

그 다음으로, 우리는 TChatGPT 클래스의 생성자를 호출한다. 그래서 NetHttpClient1 그리고 우리의 비밀 키(FOpenAIApiKey) 등의 오브젝트들을 전달한다. 그런 다음, GetGeneratedSpeechAsStream 메서드를 불러낸다. 그때, 제공하는 파라미터들이 있다. 음성으로 생성된 텍스트 설명(Memo2.Text) 그리고 음성 유형(이 예에서는 문자열로 'onyx'라고 적었음)이다. 요청 실행 도중에 애플리케이션 화면 인터페이스가 방해 받는 것을 방지하기 위해, 우리는 TTask.Run을 사용한다. GetGeneratedSpeechAsStream 메서드를 실행한 결과, 생성된 음성이 ImageStream 안에 저장된다.

spacer.png

애플리케이션의 메인 쓰레드(thread) 안에서는, TThread.Synchronize를 사용해, 음성을 MP3 미디어 파일에 저장한다. 이를 위해 ImageStream의 SaveToFile 메서드를 사용한다. 이 과정에서, 우리는 명시된 경로에 파일이 존재하는지 확인하기 위해  FileExists 함수를 사용한다. 파일이 존재하는 경우에는, 그 파일을 삭제해야 한다. 그럴 때는 DeleteFile 함수를 사용한다. 저장한 후, 그 미디어 파일을 우리의 엠바카데로 델파이 FMX 애플리케이션에서 재생하기 위해 TMediaPlayer(MediaPlayer1.Play)를 사용한다. 미디어를 재생하려면, 그 미디어 파일에 대한 경로(MediaPlayer1.FileName)를 제공해야 한다.

spacer.png

"Send Request For Speech Generation" 버튼 핸들러에 대한 코드는 아래에 있다.

procedure TForm1.Button1Click(Sender: TObject);
var
  GPTHelper: IChatGPTHelper;
  ImageStream: TMemoryStream;
begin
  TTask.Run(
    procedure
    begin
      GPTHelper := TChatGPT.Create(NetHTTPClient1, 'Bearer ' + FOpenAIApiKey);
      ImageStream := GPTHelper.GetGeneratedSpeechAsStream(Memo2.Text, 'onyx');
      try
        TThread.Synchronize(nil,
          procedure
          begin
            if FileExists(FAudioFilePath) then
            begin
              DeleteFile(FAudioFilePath);
              ImageStream.SaveToFile(FAudioFilePath);
            end
            else
              ImageStream.SaveToFile(FAudioFilePath);
            MediaPlayer1.FileName := FAudioFilePath;
            MediaPlayer1.Play;
            ShowMessage('All is done!!!');
          end);
      finally
        ImageStream.Free;
      end;
    end);
end;

이제 저장된 미디어 파일의 음성으로부터 텍스트를 추출해 보자. 우리가 이 기능들을 구현하는 곳은 "Speech From Audio File To Text" 버튼의 onClick 핸들러 안이다. 이 핸들러 안에서, 우리는 GPTHelper(IChatGPTHelper) 타입을 선언하고, 미디어 파일을 OpenAI에 전달해 텍스트를 추출하도록 한다. 또한 우리는 문자열 변수인 Text를 선언한다. 그 안에는 그 미디어로부터 추출되는 텍스트가 담기도록 하기 위해서다.

spacer.png

다음으로, 우리는 이 생성자의 두 번째 변형을 호출해야 한다. 여기에서는 4개의 입력 파라미터(HTTPBasicAuthenticator1, RESTClient1, RESTRequest1, FOpenAIApiKey)를 전달한다. 그런 다음, GetGeneratedTextFromSpeech 메서드를 불러낸다. 그때 전달하는 파라미터는 미디어 파일의 경로다. 이 메서드가 반환하는 것은 그 미디어 파일의 음성으로부터 추출된 텍스트다. 마지막으로, 우리는 수신된 텍스트를  TMemo(Memo1.Text)를 사용해 표시한다.

spacer.png

"Speech From Audio File To Text" 버튼 핸들러에 대한 코드는 아래에 있다.

procedure TForm1.Button4Click(Sender: TObject);
var
  GPTHelper: IChatGPTHelper;
  Text: string;
begin
  TTask.Run(
    procedure
    begin
      GPTHelper := TChatGPT.Create(HTTPBasicAuthenticator1,
        RESTClient1, RESTRequest1, FOpenAIApiKey);
      Text := GPTHelper.GetGeneratedTextFromSpeech(FAudioFilePath);
      TThread.Synchronize(nil,
        procedure
        begin
          Memo1.Text := Text;
          ShowMessage('All is done!!!');
        end);
    end);
end;

우리가 만든 엠바카데로 델파이 FMX 애플리케이션을 테스트해 보자. 먼저, 텍스트 설명을 기반으로 음성을 생성해보자. 음성은 TMediaPlayer를 사용하여 재생된다. 그리고 그 음성은 mp3 확장자를 가진 미디어 파일 안에 저장된다.

spacer.png

우리가 만든 엠바카데로 델파이 FMX 애플리케이션에서 저장하는 미디어 파일은 "Documents" 디렉터리에 저장된다.

spacer.png

이제, 이 애플리케이션을 사용해 미디어 파일에 저장된 음성을 다시 텍스트로 변환해 보자.

spacer.png

이 예시들 중 몇 가지를 직접 해보고 싶은가? RAD 스튜디오 최신 버전의 무료 평가판을 다운로드 할 수 있다.

이 기고는 엠바카데로 기술 파트너인 Softacom에서 작성했음. Softacom의 전문 분야는 모든 종류의 소프트웨어 개발을 델파이에 집중하여 수행하는 것이다. Softacom의 서비스에 대해 자세히 알아보려면 웹사이트에 방문하면 된다.

이 댓글 링크
다른 사이트에 공유하기

(2024년 6월 20일 업데이트)

이 기고와 소스 코드는 10.4 버전에서 구현되었습니다. 따라서 현재 최신 버전인 12.x에서는 컴파일 오류가 발생합니다. 혹시 최신 버전인 12.x 버전에서 작동되는 전체 소스 코드가 필요하다면, https://welcome.devgear.co.kr/contact 으로 요청하세요. 개별적으로 전달하겠습니다.

---

저도 같은 에러가 발생하네요. 원문에 Comment를 남겼으니, 기다려보려고 합니다.

답변이 오기 전이라도 혹시라도 해결책을 아시는 분이 있으시면, 답글로 남겨주시면 고맙겠습니다.

이 댓글 링크
다른 사이트에 공유하기

이 토의에 참여하세요

지금 바로 의견을 남길 수 있습니다. 그리고 나서 가입해도 됩니다. 이미 회원이라면, 지금 로그인하고 본인 계정으로 의견을 남기세요.

Guest
이 토픽(기고/질문)에 답하기

×   서식있는 텍스트로 붙여넣기.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   이전에 작성한 콘텐츠가 복원되었습니다..   편집창 비우기

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

중요한 정보

이용약관 개인정보보호정책 이용규칙 이 사이트가 더 잘 작동하기 위해 방문자의 컴퓨터에 쿠키가 배치됩니다. 쿠키 설정 변경에서 원하는 설정을 할 수 있습니다. 변경하지 않으면 쿠키를 허용하는 것으로 이해합니다.