web/barf: init, including sapi
sapi sapi sapi http://totoro:11316/sam?text=OLE%20Apartments%20are%20Very%20Complicated%20and%20cannot%20be%20understood%20by%20Mere%20Mortals.
This commit is contained in:
parent
aad93631ca
commit
6522ddba8c
11 changed files with 388 additions and 2 deletions
7
go.work
7
go.work
|
@ -1,3 +1,6 @@
|
|||
go 1.20
|
||||
go 1.21.7
|
||||
|
||||
use ./go
|
||||
use (
|
||||
./go
|
||||
./web/barf/sapi
|
||||
)
|
||||
|
|
11
go.work.sum
11
go.work.sum
|
@ -1,4 +1,15 @@
|
|||
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||
github.com/Azure/go-autorest/autorest v0.11.20 h1:s8H1PbCZSqg/DH7JMlOz6YMig6htWLNPsjDdlLqCx3M=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.15 h1:X+p2GF0GWyOiSmqohIaEeuNFNDY4I4EOlVuUQvFdWMk=
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
|
||||
github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c=
|
||||
github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o=
|
||||
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
|
||||
github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
|
|
40
ops/nixos/totoro/barf.nix
Normal file
40
ops/nixos/totoro/barf.nix
Normal file
|
@ -0,0 +1,40 @@
|
|||
# SPDX-FileCopyrightText: 2024 Luke Granger-Brown <depot@lukegb.com>
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
{ depot, pkgs, ... }:
|
||||
|
||||
{
|
||||
systemd.targets.barf = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
systemd.services.barf-sapid = {
|
||||
wantedBy = [ "barf.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${depot.web.barf.sapi.sapid-wrapper}/bin/sapid-wrapper";
|
||||
CacheDirectory = "barf-sapid";
|
||||
User = "barf-sapid";
|
||||
KillMode = "mixed";
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = true;
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHostname = true;
|
||||
ProtectHome = true;
|
||||
ProtectProc = "invisible";
|
||||
ProcSubset = "pid";
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectClock = true;
|
||||
CapabilityBoundingSet = "";
|
||||
LockPersonality = true;
|
||||
PrivateUsers = true;
|
||||
RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
|
||||
|
||||
DynamicUser = true;
|
||||
Restart = "always";
|
||||
};
|
||||
};
|
||||
}
|
|
@ -21,6 +21,7 @@ in {
|
|||
./home-assistant.nix
|
||||
./authentik.nix
|
||||
./adsb.nix
|
||||
./barf.nix
|
||||
];
|
||||
|
||||
boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "nvme" "usb_storage" "usbhid" "sd_mod" ];
|
||||
|
|
1
web/barf/README.md
Normal file
1
web/barf/README.md
Normal file
|
@ -0,0 +1 @@
|
|||
# Birthday Activities Reply Frontend (BARF)
|
9
web/barf/default.nix
Normal file
9
web/barf/default.nix
Normal file
|
@ -0,0 +1,9 @@
|
|||
# SPDX-FileCopyrightText: 2024 Luke Granger-Brown <depot@lukegb.com>
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
{ ... }@args:
|
||||
|
||||
{
|
||||
sapi = import ./sapi args;
|
||||
}
|
137
web/barf/sapi/default.nix
Normal file
137
web/barf/sapi/default.nix
Normal file
|
@ -0,0 +1,137 @@
|
|||
# SPDX-FileCopyrightText: 2024 Luke Granger-Brown <depot@lukegb.com>
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
{ pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
spchapi = pkgs.fetchurl {
|
||||
url = "https://www.htmlbible.com/MicrosoftSpeechComponents/SAPI4point0aRuntimeBinaries/spchapi.exe";
|
||||
hash = "sha256:1pb3wcv80g0kk3q94rfr29spxa8asd8qzrc05yg57hkv441ywmc9";
|
||||
};
|
||||
tv_enua = pkgs.fetchurl {
|
||||
url = "https://www.htmlbible.com/MicrosoftSpeechComponents/TextToSpeechEngines/AmericanEnglish/tv_enua.exe";
|
||||
hash = "sha256:0cckcpmndnq9d0r0vmpch5pmw76lgzxn3983196niysi5683d6bh";
|
||||
};
|
||||
msam = pkgs.fetchurl {
|
||||
url = "https://archive.org/download/Sam_mike_and_mary/microsoft_sam%2C_mike%2C_and_mary.exe";
|
||||
hash = "sha256:0pixa50v5lyhv7i6d8p2b6lb2pf38gr4lmym9v5m1ljkkr49lzy8";
|
||||
};
|
||||
|
||||
wineprefix = pkgs.runCommand "sapi-wineprefix" {
|
||||
nativeBuildInputs = [ pkgs.wine pkgs.xvfb-run ];
|
||||
stampVersion = "1";
|
||||
} ''
|
||||
mkdir $out
|
||||
echo "$stampVersion" > $out/version
|
||||
mkdir $out/home
|
||||
export HOME=$out/home
|
||||
export WINEPREFIX=$out/wineprefix
|
||||
xvfb-run -a wine ${spchapi} || true
|
||||
xvfb-run -a wine ${tv_enua} /q || true
|
||||
xvfb-run -a wine ${msam} /q || true
|
||||
xvfb-run -a wine ${sapi4src}/SAPI4SDK.exe /q || true
|
||||
'';
|
||||
|
||||
sapi4src = pkgs.fetchFromGitHub {
|
||||
owner = "TETYYS";
|
||||
repo = "SAPI4";
|
||||
rev = "23b0dd17083cc2fd7d5656d2996640a90cb58ea0";
|
||||
hash = "sha256:1yjjfhbs6d29vyvi2bs96mhr0zmjlyf8m8snrxiq0c5z5f4imfzb";
|
||||
};
|
||||
w32pkgs = pkgs.pkgsCross.mingw32;
|
||||
w32BuildPackages = w32pkgs.buildPackages;
|
||||
gcc = w32BuildPackages.wrapCC (w32BuildPackages.gcc-unwrapped.override ({
|
||||
threadsCross = {
|
||||
model = "win32";
|
||||
package = null;
|
||||
};
|
||||
}));
|
||||
stdenv' = w32pkgs.overrideCC w32pkgs.stdenv gcc;
|
||||
sapi4 = stdenv'.mkDerivation {
|
||||
pname = "sapi4";
|
||||
version = "0.0.1";
|
||||
src = sapi4src;
|
||||
|
||||
buildPhase = ''
|
||||
export CC=i686-w64-mingw32-g++
|
||||
export MSSPEECH="${wineprefix}/wineprefix/drive_c/Program Files/Microsoft Speech SDK/Include/"
|
||||
|
||||
cat ${./mysapi4.cpp} >> sapi4.cpp
|
||||
|
||||
echo sapi4
|
||||
"$CC" -shared -Wl,--out-implib,sapi4.lib -o sapi4.dll sapi4.cpp -I "$MSSPEECH" -lole32 -luser32 -luuid
|
||||
# echo sapi4limits
|
||||
# "$CC" -o sapi4limits.exe sapi4limits.cpp -I "$MSSPEECH" -lole32 -luser32 -luuid -L. -lsapi4
|
||||
# echo sapi4out
|
||||
# "$CC" -o sapi4out.exe sapi4out.cpp -I "$MSSPEECH" -lole32 -luser32 -luuid -L. -lsapi4
|
||||
|
||||
mkdir -p $out/bin
|
||||
cp sapi4.dll $out/bin
|
||||
'';
|
||||
};
|
||||
sayit = pkgs.writeShellApplication {
|
||||
name = "sayit-sapi";
|
||||
|
||||
runtimeInputs = [ pkgs.wine ];
|
||||
|
||||
text = ''
|
||||
prefixStampVersion="0"
|
||||
export WINEPREFIXBASE="''${CACHE_DIRECTORY:-$HOME/.cache}/sayit-sapi-wineprefix"
|
||||
if [[ -f "$WINEPREFIXBASE/version" ]]; then
|
||||
prefixStampVersion="$(cat "$WINEPREFIXBASE/version")"
|
||||
fi
|
||||
if [[ "$prefixStampVersion" != "${wineprefix.stampVersion}" ]]; then
|
||||
rm -rf "$WINEPREFIXBASE"
|
||||
cp -R "${wineprefix}" "$WINEPREFIXBASE"
|
||||
chmod -R u+w "$WINEPREFIXBASE"
|
||||
fi
|
||||
|
||||
export WINEPREFIX="$WINEPREFIXBASE/wineprefix"
|
||||
export WINEDEBUG="''${WINEDEBUG:--all}"
|
||||
exec wine ${sapi4}/bin/sapi4out.exe Sam 100 150 "$1"
|
||||
'';
|
||||
};
|
||||
sapid = pkgs.buildGoModule {
|
||||
name = "sapid";
|
||||
src = lib.sourceByRegex ./. [".*\.go$" "go.mod"];
|
||||
vendorHash = null;
|
||||
|
||||
postInstall = ''
|
||||
for f in ${sapi4}/bin/*.dll; do
|
||||
ln -s "$f" $out/bin
|
||||
done
|
||||
mv $out/bin/windows_386/*.exe $out/bin
|
||||
rmdir $out/bin/windows_386
|
||||
'';
|
||||
|
||||
preBuild = ''
|
||||
export GOOS=windows GOARCH=386
|
||||
'';
|
||||
};
|
||||
sapid-wrapper = pkgs.writeShellApplication {
|
||||
name = "sapid-wrapper";
|
||||
|
||||
runtimeInputs = [ pkgs.xvfb-run pkgs.wine ];
|
||||
|
||||
text = ''
|
||||
prefixStampVersion="0"
|
||||
export WINEPREFIXBASE="''${CACHE_DIRECTORY:-$HOME/.cache}/sayit-sapi-wineprefix"
|
||||
if [[ -f "$WINEPREFIXBASE/version" ]]; then
|
||||
prefixStampVersion="$(cat "$WINEPREFIXBASE/version")"
|
||||
fi
|
||||
if [[ "$prefixStampVersion" != "${wineprefix.stampVersion}" ]]; then
|
||||
rm -rf "$WINEPREFIXBASE"
|
||||
cp -R "${wineprefix}" "$WINEPREFIXBASE"
|
||||
chmod -R u+w "$WINEPREFIXBASE"
|
||||
fi
|
||||
|
||||
export WINEPREFIX="$WINEPREFIXBASE/wineprefix"
|
||||
export WINEDEBUG="''${WINEDEBUG:--all}"
|
||||
exec xvfb-run -a wine "${sapid}/bin/sapi.exe" "$@"
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
inherit spchapi tv_enua msam wineprefix sapi4src sapi4 sayit sapid sapid-wrapper;
|
||||
}
|
3
web/barf/sapi/go.mod
Normal file
3
web/barf/sapi/go.mod
Normal file
|
@ -0,0 +1,3 @@
|
|||
module hg.lukegb.com/lukegb/depot/web/barf/sapi
|
||||
|
||||
go 1.21
|
20
web/barf/sapi/mysapi4.cpp
Normal file
20
web/barf/sapi/mysapi4.cpp
Normal file
|
@ -0,0 +1,20 @@
|
|||
extern "C" {
|
||||
|
||||
extern __declspec(dllexport) BOOL MakeSamSay(LPCSTR text, LPSTR* OutFile) {
|
||||
VOICE_INFO voiceInfo;
|
||||
bool ret = false;
|
||||
if (!InitializeForVoice("Sam", &voiceInfo)) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
UINT64 len;
|
||||
if (GetTTS(&voiceInfo, 100, 150, text, &len, OutFile)) {
|
||||
ret = true;
|
||||
}
|
||||
|
||||
DeinitializeForVoice(&voiceInfo);
|
||||
end:
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
160
web/barf/sapi/sapidwin32.go
Normal file
160
web/barf/sapi/sapidwin32.go
Normal file
|
@ -0,0 +1,160 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
serve = flag.String("serve", ":11316", "Port number.")
|
||||
cacheDir = flag.String("cache_dir", "c:\\temp\\sapid-cache", "Cache dir.")
|
||||
|
||||
sapi = syscall.NewLazyDLL("sapi4.dll")
|
||||
procMakeSamSay = sapi.NewProc("MakeSamSay")
|
||||
|
||||
sayitMutex sync.Mutex
|
||||
)
|
||||
|
||||
type sayResponse struct {
|
||||
Bytes []byte
|
||||
Err error
|
||||
}
|
||||
|
||||
type sayRequest struct {
|
||||
Text string
|
||||
ResponseCh chan sayResponse
|
||||
}
|
||||
|
||||
func sayRoutine(reqCh chan sayRequest) {
|
||||
//defer os.Exit(1)
|
||||
//defer log.Printf("sayRoutine is exiting!!!")
|
||||
runtime.LockOSThread()
|
||||
for req := range reqCh {
|
||||
respBytes, err := say(req.Text)
|
||||
req.ResponseCh <- sayResponse{respBytes, err}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
sayChan chan sayRequest
|
||||
)
|
||||
|
||||
func say(str string) ([]byte, error) {
|
||||
td, err := os.MkdirTemp("", "sayit-sapi")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer os.RemoveAll(td)
|
||||
|
||||
if err := os.Chdir(td); err != nil {
|
||||
return nil, fmt.Errorf("chdir %v: %w", td, err)
|
||||
}
|
||||
|
||||
inp := make([]byte, len(str)+1)
|
||||
copy(inp, str)
|
||||
inpP := &inp[0]
|
||||
|
||||
outfile := make([]byte, 17)
|
||||
outfileP := &outfile[0]
|
||||
outfilePP := &outfileP
|
||||
r1, _, _ := syscall.SyscallN(procMakeSamSay.Addr(), uintptr(unsafe.Pointer(inpP)), uintptr(unsafe.Pointer(outfilePP)))
|
||||
if r1 != 1 {
|
||||
return nil, fmt.Errorf("MakeSamSay failed")
|
||||
}
|
||||
|
||||
wavPath := path.Join(td, strings.TrimSpace(string(outfile[:16])))
|
||||
log.Printf("input %q: reading from %v", str, wavPath)
|
||||
return os.ReadFile(wavPath)
|
||||
}
|
||||
|
||||
func sayUncached(ctx context.Context, str string) ([]byte, error) {
|
||||
respCh := make(chan sayResponse, 1)
|
||||
sayChan <- sayRequest{Text: str, ResponseCh: respCh}
|
||||
resp := <-respCh
|
||||
if resp.Err != nil {
|
||||
return nil, resp.Err
|
||||
}
|
||||
return resp.Bytes, nil
|
||||
}
|
||||
|
||||
func sayCached(ctx context.Context, str string) ([]byte, error) {
|
||||
str = strings.TrimSpace(str)
|
||||
hb := sha256.Sum256([]byte(str))
|
||||
hbHex := hex.EncodeToString(hb[:])
|
||||
|
||||
cachedStr, err := os.ReadFile(path.Join(*cacheDir, hbHex+".txt"))
|
||||
if err == nil {
|
||||
if strings.TrimSpace(string(cachedStr)) == str {
|
||||
return os.ReadFile(path.Join(*cacheDir, hbHex+".wav"))
|
||||
}
|
||||
} else if !errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, fmt.Errorf("checking cache %v/%v: %w", *cacheDir, hbHex, err)
|
||||
}
|
||||
|
||||
out, err := sayUncached(ctx, str)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := os.WriteFile(path.Join(*cacheDir, hbHex+".txt"), []byte(str), 0644); err != nil {
|
||||
log.Printf("failed writing cache key to %v/%v.txt: %w", *cacheDir, hbHex, err)
|
||||
return out, nil
|
||||
}
|
||||
if err := os.WriteFile(path.Join(*cacheDir, hbHex+".wav"), out, 0644); err != nil {
|
||||
log.Printf("failed writing cache data to %v/%v.wav: %w", *cacheDir, hbHex, err)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func makeNoise(rw http.ResponseWriter, r *http.Request) {
|
||||
text := r.FormValue("text")
|
||||
if text == "" {
|
||||
http.Error(rw, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
sayFunc := sayCached
|
||||
if *cacheDir == "" {
|
||||
sayFunc = sayUncached
|
||||
}
|
||||
respBytes, err := sayFunc(r.Context(), text)
|
||||
if err != nil {
|
||||
log.Printf("rendering %q: %v", text, err)
|
||||
http.Error(rw, "failed rendering", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-type", "audio/x-wav")
|
||||
rw.Write(respBytes)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
if *cacheDir != "" {
|
||||
if err := os.MkdirAll(*cacheDir, 0700); err != nil {
|
||||
log.Fatalf("creating cache dir %v: %v", *cacheDir, err)
|
||||
}
|
||||
}
|
||||
|
||||
sayChan = make(chan sayRequest)
|
||||
go sayRoutine(sayChan)
|
||||
|
||||
http.HandleFunc("/sam", makeNoise)
|
||||
log.Printf("Listening on %s", *serve)
|
||||
log.Fatal(http.ListenAndServe(*serve, nil))
|
||||
}
|
|
@ -23,4 +23,5 @@
|
|||
'';
|
||||
|
||||
lukegbcom = import ./lukegbcom args;
|
||||
barf = import ./barf args;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue