vibeeditor-render 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/sistema.js +158 -0
- package/cli/vibeeditor-render.js +32 -6
- package/core/nucleo.js +1 -0
- package/core/servidor.js +1 -1
- package/core/voz.js +97 -9
- package/package.json +1 -1
package/cli/sistema.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// Integração com o sistema, para ninguém precisar abrir o Vibe Render na mão a cada vez que liga o computador:
|
|
2
|
+
// · início automático: sobe junto com o login (Windows: pasta Inicializar; Mac: LaunchAgent; Linux: autostart),
|
|
3
|
+
// minimizado e sem perguntar nada — o pareamento já fica salvo na pasta de dados;
|
|
4
|
+
// · link vibe-render://abrir: o botão "Abrir o Vibe Render" do site chama este link e o navegador pergunta
|
|
5
|
+
// "Abrir Vibe Render?" (Windows: registro do usuário; Linux: x-scheme-handler; Mac: um app pequeno em
|
|
6
|
+
// ~/Applications feito com o osacompile).
|
|
7
|
+
// Tudo fica na conta do usuário (nada de administrador) e cada passo que falhar só é pulado.
|
|
8
|
+
"use strict";
|
|
9
|
+
const fs = require("node:fs");
|
|
10
|
+
const os = require("node:os");
|
|
11
|
+
const path = require("node:path");
|
|
12
|
+
const {spawnSync} = require("node:child_process");
|
|
13
|
+
|
|
14
|
+
const PROTOCOLO = "vibe-render";
|
|
15
|
+
const COMANDO = "npx -y vibeeditor-render@latest --sem-atalho --segundo-plano";
|
|
16
|
+
|
|
17
|
+
const rodar = (cmd, args) => spawnSync(cmd, args, {encoding: "utf8", windowsHide: true});
|
|
18
|
+
|
|
19
|
+
/** pasta especial do Windows (Startup, Desktop…), já com OneDrive e nome traduzido */
|
|
20
|
+
function pastaWindows(qual) {
|
|
21
|
+
const r = rodar("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", `[Environment]::GetFolderPath('${qual}')`]);
|
|
22
|
+
const p = String(r.stdout || "").trim();
|
|
23
|
+
return r.status === 0 && p ? p : null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** onde fica o arquivo do início automático em cada sistema */
|
|
27
|
+
function arquivoInicio(plataforma = process.platform, casa = os.homedir(), env = process.env) {
|
|
28
|
+
if (plataforma === "win32") {
|
|
29
|
+
const startup = pastaWindows("Startup") || path.join(env.APPDATA || path.join(casa, "AppData", "Roaming"), "Microsoft", "Windows", "Start Menu", "Programs", "Startup");
|
|
30
|
+
return path.join(startup, "Vibe Render.cmd");
|
|
31
|
+
}
|
|
32
|
+
if (plataforma === "darwin") return path.join(casa, "Library", "LaunchAgents", "br.com.vibeeditor.render.plist");
|
|
33
|
+
return path.join(env.XDG_CONFIG_HOME || path.join(casa, ".config"), "autostart", "vibeeditor-render.desktop");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** conteúdo do arquivo do início automático (minimizado, sem pausa no fim) */
|
|
37
|
+
function conteudoInicio(plataforma = process.platform, dirDados = "") {
|
|
38
|
+
if (plataforma === "win32") return `@echo off\r\nstart "Vibe Render" /min cmd /c "${COMANDO}"\r\n`;
|
|
39
|
+
if (plataforma === "darwin") {
|
|
40
|
+
const log = path.join(dirDados, "inicio.log");
|
|
41
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
42
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
43
|
+
<plist version="1.0">
|
|
44
|
+
<dict>
|
|
45
|
+
<key>Label</key><string>br.com.vibeeditor.render</string>
|
|
46
|
+
<key>ProgramArguments</key>
|
|
47
|
+
<array><string>/bin/bash</string><string>-lc</string><string>${COMANDO}</string></array>
|
|
48
|
+
<key>RunAtLoad</key><true/>
|
|
49
|
+
<key>StandardOutPath</key><string>${log}</string>
|
|
50
|
+
<key>StandardErrorPath</key><string>${log}</string>
|
|
51
|
+
</dict>
|
|
52
|
+
</plist>
|
|
53
|
+
`;
|
|
54
|
+
}
|
|
55
|
+
return `[Desktop Entry]
|
|
56
|
+
Type=Application
|
|
57
|
+
Name=Vibe Render
|
|
58
|
+
Comment=Exporta os vídeos do Editor IA neste computador
|
|
59
|
+
Exec=bash -lc "${COMANDO}"
|
|
60
|
+
Terminal=false
|
|
61
|
+
X-GNOME-Autostart-enabled=true
|
|
62
|
+
`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function ligarInicio({dirDados}) {
|
|
66
|
+
const arq = arquivoInicio();
|
|
67
|
+
fs.mkdirSync(path.dirname(arq), {recursive: true});
|
|
68
|
+
fs.writeFileSync(arq, conteudoInicio(process.platform, dirDados), {mode: 0o644});
|
|
69
|
+
return arq;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function desligarInicio() {
|
|
73
|
+
const arq = arquivoInicio();
|
|
74
|
+
const tinha = fs.existsSync(arq);
|
|
75
|
+
fs.rmSync(arq, {force: true});
|
|
76
|
+
return tinha;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const inicioLigado = () => fs.existsSync(arquivoInicio());
|
|
80
|
+
|
|
81
|
+
/** o link vibe-render://abrir chama este iniciador (Windows/Linux); abre visível, mas sem segurar a janela */
|
|
82
|
+
function registrarProtocolo({dirDados}) {
|
|
83
|
+
if (process.platform === "win32") {
|
|
84
|
+
const iniciador = path.join(dirDados, "abrir.cmd");
|
|
85
|
+
fs.writeFileSync(iniciador, `@echo off\r\nstart "Vibe Render" cmd /c "npx -y vibeeditor-render@latest --sem-atalho"\r\n`);
|
|
86
|
+
const chave = `HKCU\\Software\\Classes\\${PROTOCOLO}`;
|
|
87
|
+
const passos = [
|
|
88
|
+
["add", chave, "/ve", "/d", "URL:Vibe Render", "/f"],
|
|
89
|
+
["add", chave, "/v", "URL Protocol", "/d", "", "/f"],
|
|
90
|
+
["add", `${chave}\\shell\\open\\command`, "/ve", "/d", `"${iniciador}" "%1"`, "/f"],
|
|
91
|
+
];
|
|
92
|
+
for (const p of passos) {
|
|
93
|
+
const r = rodar("reg.exe", p);
|
|
94
|
+
if (r.status !== 0) throw new Error(`não consegui registrar o link (${String(r.stderr || r.stdout).trim()})`);
|
|
95
|
+
}
|
|
96
|
+
return `${PROTOCOLO}:// → ${iniciador}`;
|
|
97
|
+
}
|
|
98
|
+
if (process.platform === "darwin") {
|
|
99
|
+
// app de AppleScript que recebe o link e sobe o Vibe Render em segundo plano
|
|
100
|
+
const app = path.join(os.homedir(), "Applications", "Vibe Render.app");
|
|
101
|
+
fs.mkdirSync(path.dirname(app), {recursive: true});
|
|
102
|
+
const log = path.join(dirDados, "abrir.log");
|
|
103
|
+
const script = [
|
|
104
|
+
"on open location u",
|
|
105
|
+
` do shell script "/bin/bash -lc 'nohup ${COMANDO.replace("--segundo-plano", "")} > \\"${log}\\" 2>&1 &'"`,
|
|
106
|
+
"end open location",
|
|
107
|
+
"on run",
|
|
108
|
+
` do shell script "/bin/bash -lc 'nohup ${COMANDO.replace("--segundo-plano", "")} > \\"${log}\\" 2>&1 &'"`,
|
|
109
|
+
"end run",
|
|
110
|
+
];
|
|
111
|
+
fs.rmSync(app, {recursive: true, force: true});
|
|
112
|
+
const r = rodar("osacompile", ["-o", app, ...script.flatMap((l) => ["-e", l])]);
|
|
113
|
+
if (r.status !== 0) throw new Error(`osacompile falhou (${String(r.stderr).trim()})`);
|
|
114
|
+
const plist = path.join(app, "Contents", "Info.plist");
|
|
115
|
+
for (const a of [
|
|
116
|
+
"Add :CFBundleIdentifier string br.com.vibeeditor.render.link",
|
|
117
|
+
"Add :CFBundleURLTypes array", "Add :CFBundleURLTypes:0 dict",
|
|
118
|
+
"Add :CFBundleURLTypes:0:CFBundleURLName string Vibe Render",
|
|
119
|
+
"Add :CFBundleURLTypes:0:CFBundleURLSchemes array",
|
|
120
|
+
`Add :CFBundleURLTypes:0:CFBundleURLSchemes:0 string ${PROTOCOLO}`,
|
|
121
|
+
]) rodar("/usr/libexec/PlistBuddy", ["-c", a, plist]);
|
|
122
|
+
rodar("/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister", ["-f", app]);
|
|
123
|
+
return `${PROTOCOLO}:// → ${app}`;
|
|
124
|
+
}
|
|
125
|
+
const apps = path.join(process.env.XDG_DATA_HOME || path.join(os.homedir(), ".local", "share"), "applications");
|
|
126
|
+
fs.mkdirSync(apps, {recursive: true});
|
|
127
|
+
const desk = path.join(apps, "vibeeditor-render-link.desktop");
|
|
128
|
+
fs.writeFileSync(desk, `[Desktop Entry]
|
|
129
|
+
Type=Application
|
|
130
|
+
Name=Vibe Render
|
|
131
|
+
Exec=bash -lc "npx -y vibeeditor-render@latest --sem-atalho" %u
|
|
132
|
+
Terminal=true
|
|
133
|
+
NoDisplay=true
|
|
134
|
+
MimeType=x-scheme-handler/${PROTOCOLO};
|
|
135
|
+
`);
|
|
136
|
+
rodar("xdg-mime", ["default", "vibeeditor-render-link.desktop", `x-scheme-handler/${PROTOCOLO}`]);
|
|
137
|
+
return `${PROTOCOLO}:// → ${desk}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** liga tudo de uma vez; devolve o que deu certo e o que foi pulado (com o motivo) */
|
|
141
|
+
function integrar({dirDados, inicio = true}) {
|
|
142
|
+
const feito = [], pulado = [];
|
|
143
|
+
try {
|
|
144
|
+
feito.push(`link ${registrarProtocolo({dirDados})}`);
|
|
145
|
+
} catch (e) {
|
|
146
|
+
pulado.push(`link vibe-render:// (${e.message})`);
|
|
147
|
+
}
|
|
148
|
+
if (inicio) {
|
|
149
|
+
try {
|
|
150
|
+
feito.push(`início automático em ${ligarInicio({dirDados})}`);
|
|
151
|
+
} catch (e) {
|
|
152
|
+
pulado.push(`início automático (${e.message})`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return {feito, pulado};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = {integrar, ligarInicio, desligarInicio, inicioLigado, registrarProtocolo, arquivoInicio, conteudoInicio, PROTOCOLO, COMANDO};
|
package/cli/vibeeditor-render.js
CHANGED
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
// vibeeditor-render --pasta <dir> muda a pasta onde os vídeos são salvos (fica guardada)
|
|
9
9
|
// vibeeditor-render --voicestudio <url> endereço do VoiceStudio deste computador (fica guardado)
|
|
10
10
|
// vibeeditor-render desparear desliga este computador da conta
|
|
11
|
-
// vibeeditor-render atalho (re)cria o atalho "Vibe Render"
|
|
11
|
+
// vibeeditor-render atalho (re)cria o atalho "Vibe Render", o link vibe-render:// e o início automático
|
|
12
|
+
// vibeeditor-render sem-inicio-automatico não abre mais junto com o computador
|
|
13
|
+
// vibeeditor-render --segundo-plano como o início automático chama: minimizado, sem perguntar nada
|
|
12
14
|
"use strict";
|
|
13
15
|
const fs = require("node:fs");
|
|
14
16
|
const os = require("node:os");
|
|
@@ -20,6 +22,8 @@ const PACOTE = require("../package.json");
|
|
|
20
22
|
const VERSAO = PACOTE.version;
|
|
21
23
|
const RAIZ = path.join(__dirname, "..");
|
|
22
24
|
const SITE = "https://plat.vibeeditor.com.br/editor/render";
|
|
25
|
+
const sistema = require("./sistema");
|
|
26
|
+
const VERSAO_INTEGRACAO = 1; // sobe quando o que sistema.integrar() faz mudar
|
|
23
27
|
|
|
24
28
|
// ------------------------------------------------------------------ terminal
|
|
25
29
|
|
|
@@ -38,7 +42,8 @@ ${negrito("Vibe Render")} ${cinza(`v${VERSAO}`)} · exporta os vídeos do Editor
|
|
|
38
42
|
${azul("npx vibeeditor-render@latest --pasta \"C:\\Videos\"")} muda a pasta dos vídeos
|
|
39
43
|
${azul("npx vibeeditor-render@latest --voicestudio http://127.0.0.1:3900")} endereço do VoiceStudio (voz clonada)
|
|
40
44
|
${azul("npx vibeeditor-render@latest desparear")} desliga este computador da sua conta
|
|
41
|
-
${azul("npx vibeeditor-render@latest atalho")}
|
|
45
|
+
${azul("npx vibeeditor-render@latest atalho")} atalho, botão "Abrir" do site e início junto com o computador
|
|
46
|
+
${azul("npx vibeeditor-render@latest sem-inicio-automatico")} não abre mais junto com o computador
|
|
42
47
|
|
|
43
48
|
Deixe a janela aberta enquanto exporta. Para fechar: Ctrl+C.
|
|
44
49
|
Ajuda: ${SITE}
|
|
@@ -46,7 +51,7 @@ Ajuda: ${SITE}
|
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
function lerArgs(argv) {
|
|
49
|
-
const a = {comando: null, codigo: null, pasta: null, voicestudio: null, semAtalho: false};
|
|
54
|
+
const a = {comando: null, codigo: null, pasta: null, voicestudio: null, semAtalho: false, segundoPlano: false};
|
|
50
55
|
for (let i = 0; i < argv.length; i++) {
|
|
51
56
|
const v = argv[i];
|
|
52
57
|
if (v === "--codigo" || v === "-c") a.codigo = argv[++i] || "";
|
|
@@ -56,6 +61,7 @@ function lerArgs(argv) {
|
|
|
56
61
|
else if (v === "--voicestudio") a.voicestudio = argv[++i] || "";
|
|
57
62
|
else if (v.startsWith("--voicestudio=")) a.voicestudio = v.slice(14);
|
|
58
63
|
else if (v === "--sem-atalho") a.semAtalho = true;
|
|
64
|
+
else if (v === "--segundo-plano") a.segundoPlano = true;
|
|
59
65
|
else if (v === "--versao" || v === "-v" || v === "--version") a.comando = "versao";
|
|
60
66
|
else if (v === "--ajuda" || v === "-h" || v === "--help") a.comando = "ajuda";
|
|
61
67
|
else if (!v.startsWith("-") && !a.comando) a.comando = v;
|
|
@@ -204,7 +210,7 @@ async function principal() {
|
|
|
204
210
|
const args = lerArgs(process.argv.slice(2));
|
|
205
211
|
if (args.comando === "versao") return dizer(VERSAO);
|
|
206
212
|
if (args.comando === "ajuda") return ajuda();
|
|
207
|
-
if (args.comando && !["desparear", "atalho"].includes(args.comando)) throw new Error(`não conheço o comando "${args.comando}". Veja: npx vibeeditor-render --ajuda`);
|
|
213
|
+
if (args.comando && !["desparear", "atalho", "sem-inicio-automatico"].includes(args.comando)) throw new Error(`não conheço o comando "${args.comando}". Veja: npx vibeeditor-render --ajuda`);
|
|
208
214
|
|
|
209
215
|
const [maior] = process.versions.node.split(".").map(Number);
|
|
210
216
|
if (maior < 20) throw new Error(`o Vibe Render precisa do Node.js 20 ou mais novo (este computador tem ${process.version}). Atualize em https://nodejs.org`);
|
|
@@ -228,7 +234,18 @@ async function principal() {
|
|
|
228
234
|
}
|
|
229
235
|
if (args.comando === "atalho") {
|
|
230
236
|
const a = criarAtalho();
|
|
231
|
-
|
|
237
|
+
dizer(a ? `${verde("✓")} Atalho criado: ${a}` : "Atalho na área de trabalho só no Windows e no Mac.");
|
|
238
|
+
config = {...config, semInicio: false};
|
|
239
|
+
const r = sistema.integrar({dirDados: dados});
|
|
240
|
+
for (const f of r.feito) dizer(`${verde("✓")} ${f}`);
|
|
241
|
+
for (const p of r.pulado) dizer(`${ambar("!")} Pulei: ${p}`);
|
|
242
|
+
config = {...config, atalhoCriado: true, integrado: VERSAO_INTEGRACAO}; gravarConfig();
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (args.comando === "sem-inicio-automatico") {
|
|
246
|
+
const tinha = sistema.desligarInicio();
|
|
247
|
+
config = {...config, semInicio: true, integrado: VERSAO_INTEGRACAO}; gravarConfig();
|
|
248
|
+
return dizer(tinha ? `${verde("✓")} O Vibe Render não abre mais junto com o computador. Para voltar: npx vibeeditor-render@latest atalho` : "O início automático já estava desligado.");
|
|
232
249
|
}
|
|
233
250
|
if (args.pasta) {
|
|
234
251
|
config = {...config, dirSaida: path.resolve(args.pasta)}; gravarConfig();
|
|
@@ -243,7 +260,7 @@ async function principal() {
|
|
|
243
260
|
const {PORTAS} = require("../core/config");
|
|
244
261
|
const outra = await jaAberto(PORTAS);
|
|
245
262
|
if (outra) {
|
|
246
|
-
dizer(`${ambar("!")} O Vibe Render já está aberto em outra janela (porta ${outra}). Use aquela.`);
|
|
263
|
+
if (!args.segundoPlano) dizer(`${ambar("!")} O Vibe Render já está aberto em outra janela (porta ${outra}). Use aquela.`);
|
|
247
264
|
return;
|
|
248
265
|
}
|
|
249
266
|
|
|
@@ -262,6 +279,15 @@ async function principal() {
|
|
|
262
279
|
mostrarArquivo, log, voiceStudio: args.voicestudio || null,
|
|
263
280
|
});
|
|
264
281
|
|
|
282
|
+
// link vibe-render:// (botão "Abrir" do site) e início junto com o computador: uma vez por versão da
|
|
283
|
+
// integração, também para quem já abria pelo atalho antigo (que passa --sem-atalho)
|
|
284
|
+
if (config.integrado !== VERSAO_INTEGRACAO) {
|
|
285
|
+
const r = sistema.integrar({dirDados: dados, inicio: !config.semInicio});
|
|
286
|
+
if (r.feito.length) dizer(`${verde("✓")} Daqui para a frente o Vibe Render abre sozinho junto com o computador, e o site consegue abri-lo pelo botão. ${cinza("(para desligar: npx vibeeditor-render@latest sem-inicio-automatico)")}`);
|
|
287
|
+
for (const p of r.pulado) log("integração pulada:", p);
|
|
288
|
+
config = {...config, integrado: VERSAO_INTEGRACAO}; gravarConfig();
|
|
289
|
+
}
|
|
290
|
+
|
|
265
291
|
// atalho na área de trabalho, uma vez (quem abriu pelo atalho passa --sem-atalho)
|
|
266
292
|
if (!args.semAtalho && !config.atalhoCriado) {
|
|
267
293
|
try {
|
package/core/nucleo.js
CHANGED
|
@@ -28,6 +28,7 @@ async function criarNucleo(opc) {
|
|
|
28
28
|
servidor = criarServidor({versao: opc.versao, origens: origensPermitidas(env), cache, jobs, transcricoes, voz, pareamento, mostrarArquivo: opc.mostrarArquivo, log: opc.log});
|
|
29
29
|
const porta = await servidor.iniciar(opc.portas);
|
|
30
30
|
pareamento.conferir();
|
|
31
|
+
voz.aoIniciar(); // abre o VoiceStudio junto, se a pessoa pediu (Configurações → VoiceStudio)
|
|
31
32
|
const relogio = setInterval(() => pareamento.conferir(), STATUS_INTERVALO);
|
|
32
33
|
relogio.unref();
|
|
33
34
|
return {
|
package/core/servidor.js
CHANGED
|
@@ -116,7 +116,7 @@ function criarServidor(opc) {
|
|
|
116
116
|
const p = opc.pareamento.visao();
|
|
117
117
|
return json(200, {app: "vibe-render", versao: opc.versao, pareado: p.pareado, dispositivoId: p.dispositivoId, email: p.email,
|
|
118
118
|
plataforma: process.platform, porta, acessoPlataforma: p.plataforma,
|
|
119
|
-
recursos: [...(opc.transcricoes ? ["transcricao"] : []), ...(opc.voz ? ["voz"] : [])]}, cors);
|
|
119
|
+
recursos: [...(opc.transcricoes ? ["transcricao"] : []), ...(opc.voz ? ["voz", "voz-abrir"] : [])]}, cors);
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
// daqui para baixo: sessão válida deste aparelho
|
package/core/voz.js
CHANGED
|
@@ -8,10 +8,14 @@
|
|
|
8
8
|
// POST /v1/voz/perfis multipart → POST /profiles (clonar: name, kind, ref_audio, ref_text, language)
|
|
9
9
|
// POST /v1/voz/perfis/<id>/excluir → DELETE /profiles/<id> (a voz que já não existe conta como excluída)
|
|
10
10
|
// POST /v1/voz/gerar multipart → POST /generate (text, profile_id ou ref_audio… → WAV)
|
|
11
|
+
// POST /v1/voz/abrir abre o app do VoiceStudio instalado neste computador (o site espera ele subir)
|
|
12
|
+
// POST /v1/voz/preferencias {abrirJunto} abre o VoiceStudio junto com o Vibe Render (que sobe com o computador)
|
|
11
13
|
// Nada de IA aqui: gerar voz é determinístico. O formato das respostas do VoiceStudio muda entre versões, então
|
|
12
14
|
// o status vai cru e quem interpreta é o site.
|
|
13
15
|
const fs = require("node:fs");
|
|
16
|
+
const os = require("node:os");
|
|
14
17
|
const path = require("node:path");
|
|
18
|
+
const {spawn} = require("node:child_process");
|
|
15
19
|
|
|
16
20
|
const URL_PADRAO = "http://127.0.0.1:3900";
|
|
17
21
|
const HOSTS_LOCAIS = new Set(["127.0.0.1", "localhost", "[::1]"]);
|
|
@@ -35,6 +39,43 @@ function urlValida(bruta) {
|
|
|
35
39
|
|
|
36
40
|
const erro = (status, mensagem, extra = {}) => Object.assign(new Error(mensagem), {status, ...extra});
|
|
37
41
|
|
|
42
|
+
/** onde o instalador do VoiceStudio costuma pôr o app (Electron), em cada sistema */
|
|
43
|
+
function lugaresDoApp(plataforma = process.platform, env = process.env, casa = os.homedir()) {
|
|
44
|
+
if (plataforma === "win32") {
|
|
45
|
+
const local = env.LOCALAPPDATA || path.join(casa, "AppData", "Local");
|
|
46
|
+
return [path.join(local, "Programs", "VoiceStudio", "VoiceStudio.exe"),
|
|
47
|
+
...[env.ProgramFiles, env["ProgramFiles(x86)"]].filter(Boolean).map((p) => path.join(p, "VoiceStudio", "VoiceStudio.exe"))];
|
|
48
|
+
}
|
|
49
|
+
if (plataforma === "darwin") return ["/Applications/VoiceStudio.app", path.join(casa, "Applications", "VoiceStudio.app")];
|
|
50
|
+
const appimages = [path.join(casa, "Applications"), path.join(casa, "Downloads")].flatMap((d) => {
|
|
51
|
+
try {
|
|
52
|
+
return fs.readdirSync(d).filter((f) => /^voicestudio.*\.appimage$/i.test(f)).map((f) => path.join(d, f));
|
|
53
|
+
} catch {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
return [...appimages, "/opt/VoiceStudio/voicestudio", "/usr/bin/voicestudio", "/usr/local/bin/voicestudio"];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** só abre um arquivo que existe e se chama VoiceStudio: o site manda o pedido, nunca o caminho de outro programa */
|
|
61
|
+
function appValido(caminho) {
|
|
62
|
+
if (!caminho || !path.isAbsolute(caminho) || !/voicestudio/i.test(path.basename(caminho))) return false;
|
|
63
|
+
try {
|
|
64
|
+
return fs.existsSync(caminho);
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** abre o app sem prender o Vibe Render a ele (fecha um, o outro continua) */
|
|
71
|
+
function abrirApp(caminho, plataforma = process.platform) {
|
|
72
|
+
const filho = plataforma === "darwin"
|
|
73
|
+
? spawn("open", ["-a", caminho], {detached: true, stdio: "ignore"})
|
|
74
|
+
: spawn(caminho, [], {detached: true, stdio: "ignore", windowsHide: false, cwd: path.dirname(caminho)});
|
|
75
|
+
filho.on("error", () => {});
|
|
76
|
+
filho.unref();
|
|
77
|
+
}
|
|
78
|
+
|
|
38
79
|
/** lê o corpo inteiro até `max` bytes (413 acima disso) */
|
|
39
80
|
function lerCorpo(req, max) {
|
|
40
81
|
return new Promise((ok, falha) => {
|
|
@@ -76,13 +117,20 @@ function criarVoz(opc) {
|
|
|
76
117
|
const log = opc.log || (() => {});
|
|
77
118
|
const arq = path.join(opc.dirDados, ARQUIVO);
|
|
78
119
|
|
|
79
|
-
|
|
120
|
+
// voicestudio.json: {url, abrirJunto, app} — o endereço, se abre junto com o Vibe Render e onde está o app
|
|
121
|
+
const lerArq = () => {
|
|
80
122
|
try {
|
|
81
|
-
|
|
123
|
+
const d = JSON.parse(fs.readFileSync(arq, "utf8"));
|
|
124
|
+
return d && typeof d === "object" ? d : {};
|
|
82
125
|
} catch {
|
|
83
|
-
return
|
|
126
|
+
return {};
|
|
84
127
|
}
|
|
85
128
|
};
|
|
129
|
+
const gravarArq = (patch) => {
|
|
130
|
+
fs.mkdirSync(opc.dirDados, {recursive: true});
|
|
131
|
+
fs.writeFileSync(arq, JSON.stringify({...lerArq(), ...patch}, null, 2));
|
|
132
|
+
};
|
|
133
|
+
const salva = () => urlValida(lerArq().url);
|
|
86
134
|
/** a variável de ambiente manda (fixo); senão o endereço guardado; senão o padrão do VoiceStudio */
|
|
87
135
|
function config() {
|
|
88
136
|
const daEnv = urlValida(env.VIBE_RENDER_VOICESTUDIO);
|
|
@@ -93,12 +141,40 @@ function criarVoz(opc) {
|
|
|
93
141
|
function definir(bruta) {
|
|
94
142
|
const url = urlValida(bruta);
|
|
95
143
|
if (!url) throw erro(400, "Endereço inválido: use o do VoiceStudio neste computador, como http://127.0.0.1:3900");
|
|
96
|
-
|
|
97
|
-
fs.writeFileSync(arq, JSON.stringify({url}, null, 2));
|
|
144
|
+
gravarArq({url});
|
|
98
145
|
return config();
|
|
99
146
|
}
|
|
100
147
|
if (opc.urlInicial) definir(opc.urlInicial);
|
|
101
148
|
|
|
149
|
+
/** o app instalado: o caminho guardado (se ainda existe) ou o primeiro lugar padrão onde ele está */
|
|
150
|
+
function acharApp() {
|
|
151
|
+
const guardado = lerArq().app;
|
|
152
|
+
if (appValido(guardado)) return guardado;
|
|
153
|
+
return (opc.lugares || lugaresDoApp(process.platform, env)).find(appValido) || null;
|
|
154
|
+
}
|
|
155
|
+
const preferencias = () => ({abrirJunto: lerArq().abrirJunto === true, app: acharApp()});
|
|
156
|
+
|
|
157
|
+
/** abre o VoiceStudio se estiver fechado; o site espera ele subir conferindo o status */
|
|
158
|
+
async function abrir() {
|
|
159
|
+
const st = await status();
|
|
160
|
+
if (st.online) return {jaAberto: true, app: acharApp()};
|
|
161
|
+
const app = acharApp();
|
|
162
|
+
if (!app) throw erro(404, "Não encontrei o VoiceStudio instalado neste computador. Instale uma vez (github.com/debpalash/VoiceStudio) e tente de novo.", {semApp: true});
|
|
163
|
+
(opc.abrirApp || abrirApp)(app);
|
|
164
|
+
gravarArq({app});
|
|
165
|
+
log("voz", `abrindo o VoiceStudio: ${app}`);
|
|
166
|
+
return {abrindo: true, app};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** chamado quando o Vibe Render sobe: abre o VoiceStudio junto, se a pessoa pediu */
|
|
170
|
+
async function aoIniciar() {
|
|
171
|
+
if (!preferencias().abrirJunto) return null;
|
|
172
|
+
return abrir().catch((e) => {
|
|
173
|
+
log("voz", `não abri o VoiceStudio junto: ${e.message}`);
|
|
174
|
+
return null;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
102
178
|
/** um pedido ao VoiceStudio; erro com status 503 (fechado), 504 (demorou) ou o status dele */
|
|
103
179
|
async function chamar(caminho, {metodo = "GET", corpo, tipo, timeout = 8000, sinal} = {}) {
|
|
104
180
|
const {url} = config();
|
|
@@ -177,7 +253,19 @@ function criarVoz(opc) {
|
|
|
177
253
|
async function rota(req, res, resto, {json, cors}) {
|
|
178
254
|
const [a, b, c] = resto;
|
|
179
255
|
try {
|
|
180
|
-
if (a === "config" && !b && req.method === "GET") return json(200, config(), cors), true;
|
|
256
|
+
if (a === "config" && !b && req.method === "GET") return json(200, {...config(), ...preferencias()}, cors), true;
|
|
257
|
+
if (a === "abrir" && !b && req.method === "POST") return json(200, await abrir(), cors), true;
|
|
258
|
+
if (a === "preferencias" && !b && req.method === "POST") {
|
|
259
|
+
let d;
|
|
260
|
+
try {
|
|
261
|
+
d = JSON.parse((await lerCorpo(req, 4096)).toString("utf8") || "{}");
|
|
262
|
+
} catch (e) {
|
|
263
|
+
throw e.status ? e : erro(400, "JSON inválido");
|
|
264
|
+
}
|
|
265
|
+
if (typeof (d && d.abrirJunto) !== "boolean") throw erro(400, "abrirJunto precisa ser true ou false");
|
|
266
|
+
gravarArq({abrirJunto: d.abrirJunto});
|
|
267
|
+
return json(200, preferencias(), cors), true;
|
|
268
|
+
}
|
|
181
269
|
if (a === "config" && !b && req.method === "POST") {
|
|
182
270
|
let d;
|
|
183
271
|
try {
|
|
@@ -215,13 +303,13 @@ function criarVoz(opc) {
|
|
|
215
303
|
if (e.status === 499) return res.destroy(), true;
|
|
216
304
|
// 401/403 do VoiceStudio não podem parecer sessão inválida do Vibe Render (o site renovaria a sessão)
|
|
217
305
|
const st = e.status === 401 || e.status === 403 ? 502 : e.status || 500;
|
|
218
|
-
json(st, {erro: e.status ? e.message : "erro interno", ...(e.offline ? {offline: true} : {}), ...(e.doVoiceStudio ? {voiceStudio: e.status} : {})}, cors);
|
|
306
|
+
json(st, {erro: e.status ? e.message : "erro interno", ...(e.offline ? {offline: true} : {}), ...(e.semApp ? {semApp: true} : {}), ...(e.doVoiceStudio ? {voiceStudio: e.status} : {})}, cors);
|
|
219
307
|
if (!e.status) log("erro", e.message);
|
|
220
308
|
return true;
|
|
221
309
|
}
|
|
222
310
|
}
|
|
223
311
|
|
|
224
|
-
return {config, definir, status, chamar, rota};
|
|
312
|
+
return {config, definir, status, chamar, rota, abrir, aoIniciar, preferencias, acharApp};
|
|
225
313
|
}
|
|
226
314
|
|
|
227
|
-
module.exports = {criarVoz, urlValida, URL_PADRAO, LIMITE_CORPO};
|
|
315
|
+
module.exports = {criarVoz, urlValida, lugaresDoApp, appValido, URL_PADRAO, LIMITE_CORPO};
|
package/package.json
CHANGED