реструктуризация файлов, добавление вебчатов
This commit is contained in:
+212
@@ -0,0 +1,212 @@
|
||||
unit uTTS;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Windows, SysUtils, Classes, MMSystem;
|
||||
|
||||
type
|
||||
TPlayFinishedEvent = procedure(Sender: TObject) of object;
|
||||
|
||||
type
|
||||
TTTS = class
|
||||
private
|
||||
FPiperExePath: string;
|
||||
FVoicesFolder: string;
|
||||
FCurrentModel: string;
|
||||
FOnPlayFinished: TPlayFinishedEvent;
|
||||
procedure DoPlayFinished;
|
||||
function RunProcessCaptureOutput(const Exe, Args, InputText: string; out OutputText: string): Boolean;
|
||||
public
|
||||
constructor Create(const APiperExePath, AVoicesFolder: string);
|
||||
/// <summary>Ñïèñîê ìîäåëåé (ôàéëû .onnx) â ïàïêå voices</summary>
|
||||
function GetModelsList: TStringList;
|
||||
|
||||
/// <summary>Âûáðàòü ìîäåëü îçâó÷èâàíèÿ (èìÿ ôàéëà .onnx)</summary>
|
||||
procedure SetModel(const ModelFileName: string);
|
||||
|
||||
/// <summary>Îçâó÷èòü òåêñò âûáðàííîé ìîäåëüþ</summary>
|
||||
procedure TextToSpeech(const aText: string; isDeleteFile: Boolean = False);
|
||||
|
||||
property OnPlayFinished: TPlayFinishedEvent read FOnPlayFinished write FOnPlayFinished;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
procedure TTTS.DoPlayFinished;
|
||||
begin
|
||||
if Assigned(FOnPlayFinished) then
|
||||
FOnPlayFinished(Self);
|
||||
end;
|
||||
|
||||
constructor TTTS.Create(const APiperExePath, AVoicesFolder: string);
|
||||
begin
|
||||
inherited Create;
|
||||
FPiperExePath := APiperExePath;
|
||||
FVoicesFolder := AVoicesFolder;
|
||||
FCurrentModel := ''; // Ïîêà íå âûáðàíà ìîäåëü
|
||||
end;
|
||||
|
||||
function TTTS.GetModelsList: TStringList;
|
||||
var
|
||||
SR: TSearchRec;
|
||||
begin
|
||||
Result := TStringList.Create;
|
||||
if not DirectoryExists(FVoicesFolder) then Exit;
|
||||
if FindFirst(FVoicesFolder + PathDelim + '*.onnx', faAnyFile, SR) = 0 then
|
||||
begin
|
||||
repeat
|
||||
Result.Add(SR.Name);
|
||||
until FindNext(SR) <> 0;
|
||||
FindClose(SR);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TTTS.SetModel(const ModelFileName: string);
|
||||
var
|
||||
FullPath: string;
|
||||
begin
|
||||
FullPath := IncludeTrailingPathDelimiter(FVoicesFolder) + ModelFileName;
|
||||
if not FileExists(FullPath) then
|
||||
raise Exception.CreateFmt('Ìîäåëü íå íàéäåíà: %s', [FullPath]);
|
||||
FCurrentModel := FullPath;
|
||||
end;
|
||||
|
||||
function TTTS.RunProcessCaptureOutput(const Exe, Args, InputText: string; out OutputText: string): Boolean;
|
||||
var
|
||||
SecAttr: TSecurityAttributes;
|
||||
StdOutRead, StdOutWrite: THandle;
|
||||
StdInRead, StdInWrite: THandle;
|
||||
StartupInfo: TStartupInfo;
|
||||
ProcInfo: TProcessInformation;
|
||||
Buffer: array [0..4095] of AnsiChar;
|
||||
BytesRead: Cardinal;
|
||||
ReadOK: BOOL;
|
||||
Stream: TStringStream;
|
||||
InheritHandles: Boolean;
|
||||
BytesWritten: Cardinal;
|
||||
Utf8Bytes: TBytes;
|
||||
begin
|
||||
Result := False;
|
||||
OutputText := '';
|
||||
Stream := TStringStream.Create('', TEncoding.UTF8);
|
||||
try
|
||||
SecAttr.nLength := SizeOf(SecAttr);
|
||||
SecAttr.bInheritHandle := True;
|
||||
SecAttr.lpSecurityDescriptor := nil;
|
||||
|
||||
if not CreatePipe(StdOutRead, StdOutWrite, @SecAttr, 0) then Exit;
|
||||
try
|
||||
if not SetHandleInformation(StdOutRead, HANDLE_FLAG_INHERIT, 0) then Exit;
|
||||
|
||||
if not CreatePipe(StdInRead, StdInWrite, @SecAttr, 0) then Exit;
|
||||
try
|
||||
if not SetHandleInformation(StdInWrite, HANDLE_FLAG_INHERIT, 0) then Exit;
|
||||
|
||||
ZeroMemory(@StartupInfo, SizeOf(StartupInfo));
|
||||
StartupInfo.cb := SizeOf(StartupInfo);
|
||||
StartupInfo.hStdOutput := StdOutWrite;
|
||||
StartupInfo.hStdError := StdOutWrite;
|
||||
StartupInfo.hStdInput := StdInRead;
|
||||
StartupInfo.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
|
||||
StartupInfo.wShowWindow := SW_HIDE;
|
||||
|
||||
InheritHandles := True;
|
||||
|
||||
if not CreateProcess(PChar(Exe), PChar('"' + Exe + '" ' + Args), nil, nil,
|
||||
InheritHandles, CREATE_NO_WINDOW, nil, nil, StartupInfo, ProcInfo) then Exit;
|
||||
|
||||
CloseHandle(StdOutWrite);
|
||||
CloseHandle(StdInRead);
|
||||
|
||||
if InputText <> '' then
|
||||
begin
|
||||
Utf8Bytes := TEncoding.UTF8.GetBytes(InputText + #10);
|
||||
WriteFile(StdInWrite, Utf8Bytes[0], Length(Utf8Bytes), BytesWritten, nil);
|
||||
end;
|
||||
CloseHandle(StdInWrite);
|
||||
|
||||
repeat
|
||||
ReadOK := ReadFile(StdOutRead, Buffer, SizeOf(Buffer), BytesRead, nil);
|
||||
if ReadOK and (BytesRead > 0) then
|
||||
Stream.Write(Buffer, BytesRead);
|
||||
until not ReadOK or (BytesRead = 0);
|
||||
|
||||
WaitForSingleObject(ProcInfo.hProcess, INFINITE);
|
||||
|
||||
CloseHandle(ProcInfo.hThread);
|
||||
CloseHandle(ProcInfo.hProcess);
|
||||
|
||||
OutputText := Stream.DataString;
|
||||
Result := True;
|
||||
finally
|
||||
CloseHandle(StdInWrite);
|
||||
CloseHandle(StdInRead);
|
||||
end;
|
||||
finally
|
||||
CloseHandle(StdOutRead);
|
||||
CloseHandle(StdOutWrite);
|
||||
end;
|
||||
finally
|
||||
Stream.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TTTS.TextToSpeech(const aText: string; isDeleteFile: Boolean);
|
||||
var
|
||||
Args, Text, Output, WavFile: string;
|
||||
Lines: TStringList;
|
||||
i: Integer;
|
||||
begin
|
||||
if (FPiperExePath = '') or (FCurrentModel = '') then
|
||||
raise Exception.Create('Piper.exe èëè ìîäåëü íå óêàçàíû.');
|
||||
|
||||
Text := Trim(aText);
|
||||
if Text = '' then Exit;
|
||||
|
||||
// Ôîðìèðóåì àðãóìåíòû - ïóñòü Piper ñîõðàíÿåò wav â òåêóùóþ ïàïêó ñ óíèêàëüíûì èìåíåì
|
||||
Args := '--model "' + FCurrentModel + '" -f o.wav';
|
||||
|
||||
if not RunProcessCaptureOutput(FPiperExePath, Args, Text, Output) then
|
||||
begin
|
||||
|
||||
Exit;
|
||||
end;
|
||||
|
||||
Lines := TStringList.Create;
|
||||
try
|
||||
Lines.Text := Output;
|
||||
WavFile := '';
|
||||
for i := Lines.Count - 1 downto 0 do
|
||||
if (Pos('.wav', LowerCase(Lines[i])) > 0) and FileExists(Trim(Lines[i])) then
|
||||
begin
|
||||
WavFile := Trim(Lines[i]);
|
||||
Break;
|
||||
end;
|
||||
|
||||
if WavFile <> '' then
|
||||
begin
|
||||
// Çàïóñêàåì â îòäåëüíîì ïîòîêå, ÷òîáû îòñëåäèòü îêîí÷àíèå
|
||||
TThread.CreateAnonymousThread(
|
||||
procedure
|
||||
begin
|
||||
PlaySound(PChar(WavFile), 0, SND_FILENAME); // áåç SND_ASYNC — æä¸ì îêîí÷àíèÿ
|
||||
if isDeleteFile then
|
||||
DeleteFile(WavFile);
|
||||
TThread.Synchronize(nil,
|
||||
procedure
|
||||
begin
|
||||
DoPlayFinished;
|
||||
end
|
||||
);
|
||||
end
|
||||
).Start;
|
||||
end;
|
||||
|
||||
finally
|
||||
Lines.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
Reference in New Issue
Block a user