primocode 8.42.1 → 9.0.0

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.
Files changed (102) hide show
  1. package/README.md +41 -37
  2. package/bin/primocode.js +599 -327
  3. package/lib/acervo.js +8 -1
  4. package/lib/act.js +205 -114
  5. package/lib/api.js +35 -4
  6. package/lib/boas-vindas.js +31 -192
  7. package/lib/catalogo.js +60 -60
  8. package/lib/config.js +1 -1
  9. package/lib/conta.js +3 -24
  10. package/lib/diff.js +161 -0
  11. package/lib/diretor.js +37 -47
  12. package/lib/entrada.js +545 -0
  13. package/lib/especialistas.js +2 -2
  14. package/lib/estudio/assets.js +343 -0
  15. package/lib/estudio/identidade.js +560 -0
  16. package/lib/estudio/index.js +565 -0
  17. package/lib/estudio/plano.js +327 -0
  18. package/lib/estudio/projeto.js +246 -0
  19. package/lib/estudio/remotion.js +606 -0
  20. package/lib/estudio/slides.js +190 -0
  21. package/lib/estudio/video.js +296 -0
  22. package/lib/fala.js +13 -1
  23. package/lib/marca.js +4 -1
  24. package/lib/marketplace.js +189 -0
  25. package/lib/permissao.js +137 -0
  26. package/lib/registro.js +229 -0
  27. package/lib/skills.js +329 -0
  28. package/lib/tools.js +51 -181
  29. package/lib/ui.js +7 -3
  30. package/lib/voz.js +239 -0
  31. package/package.json +12 -3
  32. package/skills/acessibilidade/SKILL.md +28 -0
  33. package/skills/api-que-nao-quebra/SKILL.md +27 -0
  34. package/skills/depurar-de-verdade/SKILL.md +29 -0
  35. package/skills/editar-meu-video/SKILL.md +59 -0
  36. package/skills/escrever-em-portugues/SKILL.md +28 -0
  37. package/skills/git-limpo/SKILL.md +28 -0
  38. package/skills/motion-de-primeira/SKILL.md +62 -0
  39. package/skills/pagina-que-converte/SKILL.md +28 -0
  40. package/skills/performance-web/SKILL.md +28 -0
  41. package/skills/revisao-antes-de-entregar/SKILL.md +27 -0
  42. package/skills/slides-que-prendem/SKILL.md +40 -0
  43. package/studio/edge.py +16 -66
  44. package/studio/narrar.py +274 -0
  45. package/studio/pessoa.py +167 -0
  46. package/voz/primo_voz/__init__.py +0 -0
  47. package/voz/primo_voz/__main__.py +325 -0
  48. package/voz/primo_voz/escuta.py +235 -0
  49. package/voz/primo_voz/orbe.py +216 -0
  50. package/lib/audio-livre.js +0 -177
  51. package/lib/chaves.js +0 -149
  52. package/lib/claude-engine.js +0 -631
  53. package/lib/codex-engine.js +0 -208
  54. package/lib/remotion.js +0 -663
  55. package/lib/studio.js +0 -960
  56. package/studio/PRIMOCODE.md +0 -58
  57. package/studio/README.md +0 -154
  58. package/studio/__pycache__/edge.cpython-311.pyc +0 -0
  59. package/studio/__pycache__/elevenlabs.cpython-311.pyc +0 -0
  60. package/studio/__pycache__/voz.cpython-311.pyc +0 -0
  61. package/studio/arquivos/corte-teste-de-camera-fb823.json +0 -57
  62. package/studio/ferramentas/00-protocolo.md +0 -125
  63. package/studio/ferramentas/01-modelos.md +0 -270
  64. package/studio/ferramentas/README.md +0 -32
  65. package/studio/ferramentas/corte.md +0 -919
  66. package/studio/ferramentas/grade.md +0 -92
  67. package/studio/ferramentas/prisma.md +0 -107
  68. package/studio/ferramentas/prosa.md +0 -86
  69. package/studio/ferramentas/tela.md +0 -93
  70. package/studio/ferramentas/traco.md +0 -79
  71. package/studio/server.py +0 -2254
  72. package/studio/studio.log +0 -57
  73. package/studio/voz.py +0 -388
  74. package/studio/web/analise.html +0 -52
  75. package/studio/web/core.css +0 -1167
  76. package/studio/web/fontes/bebas.woff2 +0 -0
  77. package/studio/web/fontes/grotesk-700.woff2 +0 -0
  78. package/studio/web/fontes/inter-400.woff2 +0 -0
  79. package/studio/web/fontes/inter-700.woff2 +0 -0
  80. package/studio/web/fontes/jetbrains-400.woff2 +0 -0
  81. package/studio/web/fontes/playfair-700.woff2 +0 -0
  82. package/studio/web/fontes.css +0 -23
  83. package/studio/web/home.html +0 -217
  84. package/studio/web/js/analise.js +0 -510
  85. package/studio/web/js/audio.js +0 -607
  86. package/studio/web/js/editor.js +0 -1802
  87. package/studio/web/js/exportar.js +0 -222
  88. package/studio/web/js/ferramentas.js +0 -108
  89. package/studio/web/js/icones.js +0 -120
  90. package/studio/web/js/midia.js +0 -157
  91. package/studio/web/js/narracao.js +0 -325
  92. package/studio/web/js/player.js +0 -903
  93. package/studio/web/js/render-grade.js +0 -243
  94. package/studio/web/js/render-prosa.js +0 -104
  95. package/studio/web/js/render.js +0 -1852
  96. package/studio/web/js/sfx.js +0 -233
  97. package/studio/web/js/video.js +0 -464
  98. package/studio/web/js/voz-neural.js +0 -152
  99. package/studio/web/render.css +0 -1352
  100. package/studio/web/tf.html +0 -15
  101. package/studio/web/tool.html +0 -78
  102. package/studio/web/view.html +0 -27
package/studio/studio.log DELETED
@@ -1,57 +0,0 @@
1
- [studio] 2026-09-11T18:38:32+00:00 — iniciando na porta 7777
2
-
3
- ██ PrimoCode Studio
4
- ────────────────────────────────────────────────────────
5
- Estúdio http://localhost:7777
6
- Manual da IA http://localhost:7777/api/manual
7
- Ferramentas Prisma, Tela, Prosa, Grade, Corte, Traço
8
- ────────────────────────────────────────────────────────
9
- Ctrl+C para parar
10
-
11
- GET /api/ferramentas
12
- GET /api/ferramentas
13
- GET /api/arquivos
14
- GET /api/ferramentas
15
- GET /api/arquivos
16
- GET /api/ferramentas
17
- GET /api/arquivos
18
- POST /api/arquivos
19
- GET /api/arquivos/corte-teste-de-camera-fb823
20
- GET /api/voz/status
21
- GET /api/voz/status
22
- GET /api/arquivos/corte-teste-de-camera-fb823
23
- GET /api/voz/status
24
- GET /api/arquivos/corte-teste-de-camera-fb823
25
- GET /api/voz/status
26
- [studio] 2026-09-11T18:40:34+00:00 — iniciando na porta 7777
27
-
28
- ██ PrimoCode Studio
29
- ────────────────────────────────────────────────────────
30
- Estúdio http://localhost:7777
31
- Manual da IA http://localhost:7777/api/manual
32
- Ferramentas Prisma, Tela, Prosa, Grade, Corte, Traço
33
- ────────────────────────────────────────────────────────
34
- Ctrl+C para parar
35
-
36
- GET /api/arquivos/corte-teste-de-camera-fb823
37
- GET /api/voz/status
38
- GET /api/voz/status
39
- GET /api/voz/status
40
- GET /api/voz/status
41
- GET /api/arquivos/corte-teste-de-camera-fb823
42
- GET /api/voz/status
43
- GET /api/voz/status
44
- GET /api/arquivos/corte-teste-de-camera-fb823
45
- GET /api/voz/status
46
- GET /api/voz/status
47
- GET /api/arquivos/corte-teste-de-camera-fb823
48
- GET /api/voz/status
49
- GET /api/voz/status
50
- GET /api/arquivos/corte-teste-de-camera-fb823
51
- GET /api/voz/status
52
- GET /api/voz/status
53
- GET /api/arquivos/corte-teste-de-camera-fb823
54
- GET /api/voz/status
55
- GET /api/ferramentas
56
- GET /api/versao
57
- POST /api/sair
package/studio/voz.py DELETED
@@ -1,388 +0,0 @@
1
- """Narração de verdade, gerada aqui em Python.
2
-
3
- ── O PROBLEMA ────────────────────────────────────────────────────────────
4
- A narração era feita no NAVEGADOR. Havia três caminhos lá: a voz neural do
5
- Edge por WebSocket, o TTS do Google por este servidor, e o speechSynthesis
6
- da própria máquina. O primeiro morreu — a Microsoft passou a responder 403
7
- na síntese (a lista de vozes ainda responde; a síntese, não), e isso é uma
8
- trava que eles impõem de propósito, não um bug para contornar. O terceiro é
9
- o TTS do sistema: existe em todo computador e soa a computador. Resultado
10
- prático: o usuário pedia vídeo institucional e ouvia voz de robô.
11
-
12
- ── A SAÍDA ───────────────────────────────────────────────────────────────
13
- Trazer a síntese para cá, para o Python, onde ela não depende de navegador
14
- nem de serviço de ninguém. O motor é o Piper (MIT, do projeto Rhasspy):
15
- rede neural que roda na máquina, offline, sem chave, sem cota e sem limite.
16
- Ele não vem no pacote — são ~90 MB entre programa e modelo — então é
17
- baixado uma vez, sob demanda, e daí em diante a narração é instantânea e
18
- funciona sem internet.
19
-
20
- ── AS VOZES ──────────────────────────────────────────────────────────────
21
- As quatro vozes pt-BR do Piper foram sintetizadas e MEDIDAS: a frequência
22
- fundamental de cada uma está na tabela abaixo, apurada por autocorrelação
23
- sobre os quadros com tom. Não é adjetivo de catálogo, é medida. Por isso a
24
- padrão é a Jeff, de 139 Hz — a mais grave, que é o que sustenta abertura de
25
- vídeo. E por isso não há voz feminina listada: o Piper não tem nenhuma em
26
- pt-BR, e inventar rótulo seria pior que admitir a falta.
27
-
28
- ── A CORRENTE ────────────────────────────────────────────────────────────
29
- 1. PIPER neural, offline, ilimitado, com escolha de voz. O bom.
30
- 2. VOICEBOX se o usuário tiver o app instalado — voz clonável.
31
- 3. GOOGLE uma voz só, robótica, mas responde na hora e sem instalar nada.
32
-
33
- Cada um cai no seguinte quando falha. A narração nunca fica muda por falta
34
- de motor: enquanto o Piper baixa, o Google cobre.
35
- """
36
-
37
- import json
38
- import os
39
- import platform
40
- import re
41
- import shutil
42
- import subprocess
43
- import sys
44
- import tarfile
45
- import tempfile
46
- import threading
47
- import urllib.parse
48
- import urllib.request
49
- import wave
50
- import zipfile
51
-
52
- CASA = os.path.join(os.path.expanduser("~"), ".primocode", "vozes")
53
-
54
- RELEASE = "https://github.com/rhasspy/piper/releases/download/2023.11.14-2/"
55
- VOZES_URL = "https://huggingface.co/rhasspy/piper-voices/resolve/main/"
56
-
57
- # O pacote do Piper para cada sistema. Os cinco nomes foram conferidos no ar.
58
- PACOTES = {
59
- ("Linux", "x86_64"): "piper_linux_x86_64.tar.gz",
60
- ("Linux", "aarch64"): "piper_linux_aarch64.tar.gz",
61
- ("Linux", "arm64"): "piper_linux_aarch64.tar.gz",
62
- ("Darwin", "x86_64"): "piper_macos_x64.tar.gz",
63
- ("Darwin", "arm64"): "piper_macos_aarch64.tar.gz",
64
- ("Windows", "AMD64"): "piper_windows_amd64.zip",
65
- ("Windows", "x86_64"): "piper_windows_amd64.zip",
66
- }
67
-
68
- # hz: fundamental medida, em Hz. Quanto menor, mais grave.
69
- CATALOGO = [
70
- {"id": "jeff", "nome": "Jeff", "hz": 139, "grave": True, "padrao": True,
71
- "tom": "grave, de locução", "caminho": "pt/pt_BR/jeff/medium/pt_BR-jeff-medium", "mb": 60},
72
- {"id": "cadu", "nome": "Cadu", "hz": 140, "grave": True,
73
- "tom": "grave, mais solta", "caminho": "pt/pt_BR/cadu/medium/pt_BR-cadu-medium", "mb": 60},
74
- {"id": "faber", "nome": "Faber", "hz": 167,
75
- "tom": "clara, de leitura", "caminho": "pt/pt_BR/faber/medium/pt_BR-faber-medium", "mb": 60},
76
- {"id": "edresson", "nome": "Edresson", "hz": 151,
77
- "tom": "média, mais leve de baixar", "caminho": "pt/pt_BR/edresson/low/pt_BR-edresson-low", "mb": 21},
78
- {"id": "tugao", "nome": "Tugão", "hz": 0,
79
- "tom": "português de Portugal", "caminho": "pt/pt_PT/tugão/medium/pt_PT-tugão-medium", "mb": 60},
80
- ]
81
-
82
- POR_ID = {v["id"]: v for v in CATALOGO}
83
- PADRAO = next(v["id"] for v in CATALOGO if v.get("padrao"))
84
-
85
- # Estado do download, para a interface poder mostrar em vez de fingir que
86
- # está tudo pronto. Um dicionário simples, protegido por tranca porque quem
87
- # escreve é a thread do download e quem lê são as requisições.
88
- _tranca = threading.Lock()
89
- _baixando = {"ativo": False, "voz": None, "pct": 0, "etapa": "", "erro": None}
90
- _thread = None
91
-
92
-
93
- def _versao_pacote():
94
- return PACOTES.get((platform.system(), platform.machine()))
95
-
96
-
97
- def pasta_piper():
98
- return os.path.join(CASA, "piper")
99
-
100
-
101
- def binario():
102
- """O executável do Piper, se já foi baixado."""
103
- nome = "piper.exe" if platform.system() == "Windows" else "piper"
104
- p = os.path.join(pasta_piper(), nome)
105
- return p if os.path.exists(p) else None
106
-
107
-
108
- def modelo(voz_id):
109
- v = POR_ID.get(voz_id or PADRAO)
110
- if not v:
111
- return None
112
- base = os.path.join(CASA, os.path.basename(v["caminho"]))
113
- return base if os.path.exists(base + ".onnx") and os.path.exists(base + ".onnx.json") else None
114
-
115
-
116
- def instaladas():
117
- return [v["id"] for v in CATALOGO if modelo(v["id"])]
118
-
119
-
120
- # O binário EXISTIR não prova que ele RODA. O pacote macos_aarch64 do Piper
121
- # 2023.11.14-2 veio com executável x86_64 dentro — num Mac Apple Silicon sem
122
- # Rosetta, spawná-lo morre com "Bad CPU type" (errno 86). Sem esta checagem,
123
- # `pronta()` dizia sim, o servidor esperava a voz que nunca viria, e cada
124
- # pedido de narração travava a corrente inteira de motores por minutos.
125
- # A prova roda UMA vez e o veredito fica em arquivo: reiniciar não re-testa,
126
- # e reinstalar (apagar()) limpa o veredito junto.
127
- _MARCA_INCOMPATIVEL = os.path.join(CASA, "binario-incompativel")
128
- _roda_cache = None
129
-
130
-
131
- def marcar_incompativel(motivo):
132
- global _roda_cache
133
- _roda_cache = False
134
- try:
135
- os.makedirs(CASA, exist_ok=True)
136
- with open(_MARCA_INCOMPATIVEL, "w") as fh:
137
- fh.write(str(motivo))
138
- except Exception:
139
- pass
140
-
141
-
142
- def bloqueado():
143
- """Por que o Piper nunca vai funcionar nesta máquina — ou None."""
144
- try:
145
- if os.path.exists(_MARCA_INCOMPATIVEL):
146
- with open(_MARCA_INCOMPATIVEL) as fh:
147
- return fh.read().strip() or "binário incompatível com esta máquina"
148
- except Exception:
149
- pass
150
- return None
151
-
152
-
153
- def _binario_roda():
154
- global _roda_cache
155
- if _roda_cache is not None:
156
- return _roda_cache
157
- if bloqueado():
158
- _roda_cache = False
159
- return False
160
- exe = binario()
161
- if not exe:
162
- return None # ainda não baixou: sem veredito
163
- try:
164
- subprocess.run([exe, "--version"], stdout=subprocess.DEVNULL,
165
- stderr=subprocess.DEVNULL, timeout=10)
166
- _roda_cache = True
167
- except OSError as e:
168
- if getattr(e, "errno", None) == 86: # Bad CPU type (macOS)
169
- marcar_incompativel("o pacote do Piper veio com binário de outra "
170
- "arquitetura (x86_64 num Mac ARM sem Rosetta)")
171
- else:
172
- marcar_incompativel("o binário do Piper não executa: %s" % e)
173
- except Exception:
174
- _roda_cache = True # demorou mas executa: não é errno 86
175
- return _roda_cache
176
-
177
-
178
- def pronta(voz_id=None):
179
- return bool(binario() and modelo(voz_id or PADRAO) and _binario_roda())
180
-
181
-
182
- # ------------------------------------------------------------------ baixar --
183
-
184
- def _baixar(url, destino, fatia=(0, 100)):
185
- """Baixa mostrando progresso dentro da fatia que lhe cabe do total."""
186
- ini, fim = fatia
187
- with urllib.request.urlopen(url, timeout=60) as r:
188
- total = int(r.headers.get("Content-Length") or 0)
189
- feito = 0
190
- tmp = destino + ".parcial"
191
- with open(tmp, "wb") as fh:
192
- while True:
193
- p = r.read(262144)
194
- if not p:
195
- break
196
- fh.write(p)
197
- feito += len(p)
198
- if total:
199
- with _tranca:
200
- _baixando["pct"] = int(ini + (fim - ini) * feito / total)
201
- # Só vira o arquivo de verdade quando terminou inteiro: assim uma
202
- # queda de rede não deixa um modelo pela metade que "existe".
203
- os.replace(tmp, destino)
204
-
205
-
206
- def _instalar_piper():
207
- pacote = _versao_pacote()
208
- if not pacote:
209
- raise RuntimeError("não há Piper para %s/%s" % (platform.system(), platform.machine()))
210
- os.makedirs(CASA, exist_ok=True)
211
- with _tranca:
212
- _baixando["etapa"] = "baixando o motor de voz"
213
- alvo = os.path.join(CASA, pacote)
214
- _baixar(RELEASE + pacote, alvo, (0, 35))
215
-
216
- with _tranca:
217
- _baixando["etapa"] = "instalando o motor"
218
- if pacote.endswith(".zip"):
219
- with zipfile.ZipFile(alvo) as z:
220
- z.extractall(CASA)
221
- else:
222
- with tarfile.open(alvo) as t:
223
- t.extractall(CASA)
224
- os.remove(alvo)
225
-
226
- exe = binario()
227
- if not exe:
228
- raise RuntimeError("o pacote do Piper não trouxe o executável")
229
- if platform.system() != "Windows":
230
- os.chmod(exe, 0o755)
231
-
232
-
233
- def _instalar_voz(voz_id):
234
- v = POR_ID[voz_id]
235
- os.makedirs(CASA, exist_ok=True)
236
- base = os.path.join(CASA, os.path.basename(v["caminho"]))
237
- with _tranca:
238
- _baixando["etapa"] = "baixando a voz %s" % v["nome"]
239
- # O nome tem "ã" no caso do tugão: a URL precisa disso escapado.
240
- url = VOZES_URL + urllib.parse.quote(v["caminho"])
241
- _baixar(url + ".onnx.json", base + ".onnx.json", (35, 40))
242
- _baixar(url + ".onnx", base + ".onnx", (40, 100))
243
-
244
-
245
- def _rotina(voz_id):
246
- global _thread
247
- try:
248
- if not binario():
249
- _instalar_piper()
250
- if not modelo(voz_id):
251
- _instalar_voz(voz_id)
252
- with _tranca:
253
- _baixando.update(ativo=False, pct=100, etapa="pronta", erro=None)
254
- except Exception as e:
255
- sys.stderr.write("[voz] instalação falhou: %s\n" % e)
256
- with _tranca:
257
- _baixando.update(ativo=False, etapa="", erro=str(e))
258
- finally:
259
- _thread = None
260
-
261
-
262
- def instalar(voz_id=None, esperar=0):
263
- """Começa (ou acompanha) a instalação. Não bloqueia, por padrão.
264
-
265
- Quem chama é a interface e o próprio servidor quando vê um documento com
266
- narração: preparar a voz enquanto o agente ainda escreve o roteiro é o
267
- que faz ela estar pronta na hora de tocar."""
268
- global _thread
269
- voz_id = voz_id if voz_id in POR_ID else PADRAO
270
- # Baixar de novo não conserta binário de arquitetura errada: viria o
271
- # mesmo pacote. Sem esta guarda, cada pedido de narração re-disparava
272
- # os ~90 MB de download para nada.
273
- if bloqueado():
274
- return estado()
275
- with _tranca:
276
- if _thread is None and not pronta(voz_id):
277
- _baixando.update(ativo=True, voz=voz_id, pct=0, etapa="começando", erro=None)
278
- _thread = threading.Thread(target=_rotina, args=(voz_id,), daemon=True)
279
- _thread.start()
280
- t = _thread
281
- if t and esperar:
282
- t.join(esperar)
283
- return estado()
284
-
285
-
286
- def estado():
287
- with _tranca:
288
- b = dict(_baixando)
289
- return {
290
- "motor": "piper",
291
- "pronta": pronta(),
292
- "instaladas": instaladas(),
293
- # "suportado" responde à pergunta de verdade: o Piper VAI funcionar
294
- # aqui? Pacote inexistente para a plataforma e binário que não roda
295
- # são o mesmo "não" — e `bloqueado` explica o porquê.
296
- "suportado": bool(_versao_pacote()) and not bloqueado(),
297
- "bloqueado": bloqueado(),
298
- "baixando": b,
299
- "vozes": [{
300
- "id": v["id"], "nome": v["nome"], "tom": v["tom"], "hz": v["hz"], "mb": v["mb"],
301
- "instalada": bool(modelo(v["id"])),
302
- "grave": bool(v.get("grave")),
303
- "padrao": bool(v.get("padrao")),
304
- } for v in CATALOGO],
305
- }
306
-
307
-
308
- # --------------------------------------------------------------- sintetizar --
309
-
310
- def _mais_grave(bruto, razao):
311
- """Abaixa o tom reescrevendo a taxa declarada do WAV.
312
-
313
- Não há reamostragem: os mesmos quadros, lidos mais devagar, soam mais
314
- graves na mesma proporção. Como isso também alonga o áudio, a síntese já
315
- foi feita acelerada pelo mesmo fator, e a duração volta ao lugar.
316
- Medido: a voz Jeff sai de 139 Hz para 125 Hz com razão 0.9."""
317
- with wave.open(bruto, "rb") as w:
318
- p = w.getparams()
319
- quadros = w.readframes(p.nframes)
320
- saida = bruto + ".grave.wav"
321
- with wave.open(saida, "wb") as s:
322
- s.setnchannels(p.nchannels)
323
- s.setsampwidth(p.sampwidth)
324
- s.setframerate(max(8000, int(p.framerate * razao)))
325
- s.writeframes(quadros)
326
- return saida
327
-
328
-
329
- def sintetizar(texto, voz_id=None, grave=False, velocidade=1.0, timeout=120):
330
- """O texto vira WAV. None quando o Piper não está pronto — aí quem chamou
331
- cai para o próximo motor da corrente."""
332
- exe = binario()
333
- voz_id = voz_id if voz_id in POR_ID else PADRAO
334
- mod = modelo(voz_id)
335
- if not exe or not mod or not _binario_roda():
336
- return None
337
-
338
- # Sem isto, um texto com quebra de linha vira várias falas e o Piper
339
- # devolve só a última.
340
- limpo = re.sub(r"\s+", " ", str(texto or "")).strip()
341
- if not limpo:
342
- return None
343
-
344
- razao = 0.9 if grave else 1.0
345
- # length_scale > 1 é mais devagar. A razão do grave entra aqui para a
346
- # duração não crescer quando a taxa for reescrita lá embaixo.
347
- escala = (1.0 / max(0.5, min(2.0, velocidade))) * razao
348
-
349
- pasta = tempfile.mkdtemp(prefix="primo-voz-")
350
- try:
351
- saida = os.path.join(pasta, "voz.wav")
352
- try:
353
- r = subprocess.run(
354
- [exe, "--model", mod + ".onnx", "--config", mod + ".onnx.json",
355
- "--length_scale", "%.3f" % escala, "--sentence_silence", "0.25",
356
- "--output_file", saida],
357
- input=limpo.encode("utf-8"),
358
- stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, timeout=timeout,
359
- )
360
- except OSError as e:
361
- # errno 86 = binário de outra arquitetura (macOS). Marca de vez:
362
- # a corrente cai para o próximo motor e ninguém trava de novo.
363
- if getattr(e, "errno", None) == 86:
364
- marcar_incompativel("o pacote do Piper veio com binário de "
365
- "outra arquitetura (x86_64 num Mac ARM sem Rosetta)")
366
- return None
367
- raise
368
- if r.returncode != 0 or not os.path.exists(saida):
369
- raise RuntimeError((r.stderr or b"").decode("utf-8", "replace")[-300:] or "piper falhou")
370
- if grave:
371
- saida = _mais_grave(saida, razao)
372
- with open(saida, "rb") as fh:
373
- return fh.read()
374
- finally:
375
- shutil.rmtree(pasta, ignore_errors=True)
376
-
377
-
378
- def apagar():
379
- """Some com tudo que foi baixado. Existe para o usuário poder desfazer:
380
- são 90 MB na máquina dele, e ele tem direito de recuperá-los.
381
- O veredito de incompatibilidade vai junto (mora dentro de CASA): quem
382
- instalar de novo — num Mac com Rosetta, num pacote corrigido — merece
383
- uma prova nova, não a sentença antiga."""
384
- global _roda_cache
385
- _roda_cache = None
386
- if os.path.isdir(CASA):
387
- shutil.rmtree(CASA, ignore_errors=True)
388
- return {"ok": True}
@@ -1,52 +0,0 @@
1
- <!doctype html>
2
- <html lang="pt-BR">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width,initial-scale=1">
6
- <title>PrimoCode — ouvindo o vídeo</title>
7
- <link rel="stylesheet" href="/core.css">
8
- <link rel="stylesheet" href="/fontes.css">
9
- <style>
10
- /* Uma aba que abre sozinha, trabalha e some. Ela não pede nada da pessoa —
11
- só conta o que está fazendo, para o segundo de espera não parecer travado. */
12
- body {
13
- margin: 0; min-height: 100vh;
14
- display: flex; align-items: center; justify-content: center;
15
- background: var(--c-fundo, #0b0e14); color: var(--c-texto, #e7ecf3);
16
- font-family: var(--f-corpo, system-ui, sans-serif);
17
- }
18
- .caixa { width: min(560px, 92vw); padding: 34px 32px; }
19
- h1 { font-size: 21px; font-weight: 640; margin: 0 0 4px; letter-spacing: -.01em; }
20
- #qual {
21
- display: block; font-size: 13px; opacity: .62;
22
- font-family: var(--f-mono, ui-monospace, monospace);
23
- margin-bottom: 22px; word-break: break-all;
24
- }
25
- .trilho { height: 4px; border-radius: 4px; background: rgba(255,255,255,.1); overflow: hidden; }
26
- #barra {
27
- height: 100%; width: 0;
28
- background: linear-gradient(90deg, #2f6df6, #59a0ff);
29
- transition: width .45s cubic-bezier(.22,1,.36,1);
30
- }
31
- [data-estado="erro"] #barra { background: #d9584f; }
32
- [data-estado="pronto"] #barra { background: linear-gradient(90deg, #1f9d63, #35c584); }
33
- #painel { margin-top: 20px; max-height: 40vh; overflow: auto; }
34
- #painel p {
35
- margin: 0 0 7px; font-size: 14px; line-height: 1.5; opacity: .88;
36
- font-family: var(--f-mono, ui-monospace, monospace);
37
- }
38
- #painel p:last-child { opacity: 1; }
39
- </style>
40
- </head>
41
- <body>
42
-
43
- <div class="caixa">
44
- <h1>Ouvindo o vídeo para achar as pausas</h1>
45
- <code id="qual"></code>
46
- <div class="trilho"><div id="barra"></div></div>
47
- <div id="painel"></div>
48
- </div>
49
-
50
- <script src="/js/analise.js"></script>
51
- </body>
52
- </html>