primocode 10.15.2 → 10.17.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.
- package/LICENSES/edit-ia-pro.txt +32 -0
- package/LICENSES/fontes-OFL.txt +476 -0
- package/LICENSES/hyperframes-Apache-2.0.txt +205 -0
- package/LICENSES/permanent-marker-Apache-2.0.txt +204 -0
- package/LICENSES/selfie-multiclass-Apache-2.0.txt +227 -0
- package/LICENSES/three-MIT.txt +23 -0
- package/LICENSES/video-use-MIT.txt +24 -0
- package/LICENSES/woven-sfx-CC0.txt +16 -0
- package/LICENSES/yunet-MIT.txt +21 -0
- package/README.md +2 -2
- package/bin/primocode.js +74 -9
- package/editor/LICENSE.md +11 -0
- package/editor/README.md +321 -0
- package/editor/SKILL.md +307 -0
- package/editor/THIRD_PARTY_NOTICES.md +228 -0
- package/editor/assets/baixar.json +591 -0
- package/editor/assets/fonts/anton/OFL.txt +93 -0
- package/editor/assets/fonts/caveat/OFL.txt +93 -0
- package/editor/assets/fonts/figtree/OFL.txt +93 -0
- package/editor/assets/fonts/fonts.json +10 -0
- package/editor/assets/fonts/montserrat/OFL.txt +93 -0
- package/editor/assets/fonts/permanentmarker/LICENSE.txt +202 -0
- package/editor/assets/models/SELFIE_MULTICLASS_LICENSE.txt +227 -0
- package/editor/assets/models/YUNET_LICENSE.txt +21 -0
- package/editor/assets/sfx/licencas/bass_drop.txt +32 -0
- package/editor/assets/sfx/licencas/cash.txt +76 -0
- package/editor/assets/sfx/licencas/charge.txt +31 -0
- package/editor/assets/sfx/licencas/click.txt +46 -0
- package/editor/assets/sfx/licencas/coin.txt +50 -0
- package/editor/assets/sfx/licencas/count.txt +47 -0
- package/editor/assets/sfx/licencas/data.txt +31 -0
- package/editor/assets/sfx/licencas/error.txt +32 -0
- package/editor/assets/sfx/licencas/flash.txt +31 -0
- package/editor/assets/sfx/licencas/gears.txt +82 -0
- package/editor/assets/sfx/licencas/keyboard.txt +48 -0
- package/editor/assets/sfx/licencas/metal_hit.txt +62 -0
- package/editor/assets/sfx/licencas/metal_riser.txt +61 -0
- package/editor/assets/sfx/licencas/mouse_click.txt +50 -0
- package/editor/assets/sfx/licencas/notification.txt +54 -0
- package/editor/assets/sfx/licencas/pop.txt +38 -0
- package/editor/assets/sfx/licencas/rewind.txt +32 -0
- package/editor/assets/sfx/licencas/rise.txt +46 -0
- package/editor/assets/sfx/licencas/sinteticos.txt +21 -0
- package/editor/assets/sfx/licencas/snap.txt +50 -0
- package/editor/assets/sfx/licencas/success.txt +40 -0
- package/editor/assets/sfx/licencas/synth_hit.txt +32 -0
- package/editor/assets/sfx/licencas/tick.txt +14 -0
- package/editor/assets/sfx/licencas/whoosh.txt +47 -0
- package/editor/assets/sfx/licencas/wind.txt +31 -0
- package/editor/assets/sfx/sfx.json +832 -0
- package/editor/assets/sfx/sfx_sinteticos.json +317 -0
- package/editor/assets/sfx/sfx_woven.json +387 -0
- package/editor/assets/sfx/woven/LICENCA.md +16 -0
- package/editor/fichas/briefing-do-video.md +45 -0
- package/editor/fichas/conferencia-da-amostra.md +56 -0
- package/editor/fichas/matriz-de-ganchos.csv +3 -0
- package/editor/fichas/padrao.json +16 -0
- package/editor/fichas/planilha-de-cenas.csv +4 -0
- package/editor/fichas/planilha-de-versoes.csv +4 -0
- package/editor/fichas/plano-em-blocos.md +32 -0
- package/editor/fichas/registro-de-materiais.json +27 -0
- package/editor/helpers/assets_ocr/ocr_vision.swift +41 -0
- package/editor/helpers/bordas_precisas.py +272 -0
- package/editor/helpers/camera_viva.py +426 -0
- package/editor/helpers/captions.py +601 -0
- package/editor/helpers/common.py +308 -0
- package/editor/helpers/compose_final.py +3555 -0
- package/editor/helpers/conferir_corte.py +234 -0
- package/editor/helpers/conferir_dados.py +405 -0
- package/editor/helpers/contact_sheet.py +282 -0
- package/editor/helpers/corte_por_onda.py +433 -0
- package/editor/helpers/corte_seco.py +595 -0
- package/editor/helpers/denoise.py +971 -0
- package/editor/helpers/efeitos.py +408 -0
- package/editor/helpers/efeitos_gerar.py +159 -0
- package/editor/helpers/elevenlabs_api.py +442 -0
- package/editor/helpers/face_layout.py +570 -0
- package/editor/helpers/fetch_images.py +423 -0
- package/editor/helpers/ganho_por_trecho.py +214 -0
- package/editor/helpers/gen_image.py +312 -0
- package/editor/helpers/gen_music.py +316 -0
- package/editor/helpers/gen_sfx.py +148 -0
- package/editor/helpers/grade.py +307 -0
- package/editor/helpers/icloud.py +119 -0
- package/editor/helpers/make_sfx.py +940 -0
- package/editor/helpers/matte.py +1342 -0
- package/editor/helpers/medir_referencia.py +2232 -0
- package/editor/helpers/mixar_trilha.py +354 -0
- package/editor/helpers/montar_corte.py +295 -0
- package/editor/helpers/motion/__init__.py +13 -0
- package/editor/helpers/motion/engine.py +1915 -0
- package/editor/helpers/motion/registry.py +510 -0
- package/editor/helpers/motion/render.py +665 -0
- package/editor/helpers/motion/templates/barra_progresso.py +370 -0
- package/editor/helpers/motion/templates/carimbo.py +132 -0
- package/editor/helpers/motion/templates/checklist.py +271 -0
- package/editor/helpers/motion/templates/comentario_cta.py +380 -0
- package/editor/helpers/motion/templates/contador.py +222 -0
- package/editor/helpers/motion/templates/destaque.py +263 -0
- package/editor/helpers/motion/templates/etiqueta_preco.py +461 -0
- package/editor/helpers/motion/templates/grafico_barras.py +510 -0
- package/editor/helpers/motion/templates/grafico_linha.py +462 -0
- package/editor/helpers/motion/templates/notificacao.py +266 -0
- package/editor/helpers/motion/templates/palavra_impacto.py +410 -0
- package/editor/helpers/motion/templates/passos.py +321 -0
- package/editor/helpers/motion/templates/riscado.py +298 -0
- package/editor/helpers/motion/templates/selo_garantia.py +150 -0
- package/editor/helpers/motion_render.py +241 -0
- package/editor/helpers/motion_sugerir.py +961 -0
- package/editor/helpers/noise_check.py +134 -0
- package/editor/helpers/pack_transcripts.py +223 -0
- package/editor/helpers/palavras_na_onda.py +176 -0
- package/editor/helpers/plano_sfx.py +839 -0
- package/editor/helpers/portao.py +427 -0
- package/editor/helpers/preview_server.py +1177 -0
- package/editor/helpers/rastrear_rosto.py +298 -0
- package/editor/helpers/render_cut.py +1256 -0
- package/editor/helpers/respiros.py +89 -0
- package/editor/helpers/sincronizar_cameras.py +571 -0
- package/editor/helpers/speech_regions.py +423 -0
- package/editor/helpers/style_catalog.json +231 -0
- package/editor/helpers/style_samples.py +246 -0
- package/editor/helpers/tela_verde.py +2113 -0
- package/editor/helpers/text_render.py +2059 -0
- package/editor/helpers/texto_na_tela.py +366 -0
- package/editor/helpers/timeline_view.py +292 -0
- package/editor/helpers/tira_de_quadros.py +113 -0
- package/editor/helpers/transcribe.py +734 -0
- package/editor/helpers/transcribe_batch.py +129 -0
- package/editor/helpers/trilhas.py +1146 -0
- package/editor/helpers/trilhas_humores.json +66 -0
- package/editor/helpers/valores.py +187 -0
- package/editor/helpers/verify_cut.py +820 -0
- package/editor/helpers/voice_levels.py +233 -0
- package/editor/helpers/voz_check.py +386 -0
- package/editor/helpers/voz_limpa.py +81 -0
- package/editor/helpers/watch_preview.py +372 -0
- package/editor/modelo-projeto/LEIA.md +402 -0
- package/editor/modelo-projeto/assets/fontes/OFL-Caveat.txt +93 -0
- package/editor/modelo-projeto/assets/fontes/OFL-JetBrainsMono.txt +93 -0
- package/editor/modelo-projeto/assets/fontes/OFL-Montserrat.txt +93 -0
- package/editor/modelo-projeto/assets/fontes/larguras.json +1 -0
- package/editor/modelo-projeto/assets/three/BufferGeometryUtils.js +1438 -0
- package/editor/modelo-projeto/assets/three/GLTFLoader.js +4895 -0
- package/editor/modelo-projeto/assets/three/LICENSE +21 -0
- package/editor/modelo-projeto/assets/three/ORIGEM.txt +17 -0
- package/editor/modelo-projeto/assets/three/RoomEnvironment.js +185 -0
- package/editor/modelo-projeto/assets/three/three.core.min.js +6 -0
- package/editor/modelo-projeto/assets/three/three.module.min.js +6 -0
- package/editor/modelo-projeto/build.py +4828 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_antes_depois.html +199 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_arquivo.html +232 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_busca.html +315 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_comando.html +304 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_conversa.html +217 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_esquema.html +289 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_medidor.html +249 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_notas.html +233 -0
- package/editor/modelo-projeto/compositions/cenas/3d/k3d_caminho.html +197 -0
- package/editor/modelo-projeto/compositions/cenas/3d/k3d_explodida.html +152 -0
- package/editor/modelo-projeto/compositions/cenas/3d/k3d_tela.html +167 -0
- package/editor/modelo-projeto/compositions/cenas/3d/k3d_volume.html +124 -0
- package/editor/modelo-projeto/compositions/cenas/3d/motor-three.js +728 -0
- package/editor/modelo-projeto/compositions/cenas/LEIA.md +177 -0
- package/editor/modelo-projeto/ferramentas/medir_fontes.py +65 -0
- package/editor/modelo-projeto/ferramentas/pessoa_recortada.py +176 -0
- package/editor/modelo-projeto/ferramentas/rastrear_maos.py +189 -0
- package/editor/modelo-projeto/hyperframes.json +12 -0
- package/editor/modelo-projeto/package.json +11 -0
- package/editor/modelo-projeto/projeto.json +16 -0
- package/editor/preview/app.css +1009 -0
- package/editor/preview/app.js +2122 -0
- package/editor/preview/index.html +307 -0
- package/editor/pyproject.toml +23 -0
- package/editor/references/3d.md +236 -0
- package/editor/references/animacao-catalogo.md +672 -0
- package/editor/references/animacao.md +498 -0
- package/editor/references/comandos.md +92 -0
- package/editor/references/corte-seco-como-funciona.md +50 -0
- package/editor/references/corte-seco.md +88 -0
- package/editor/references/corte.md +290 -0
- package/editor/references/direcao-por-blocos.md +218 -0
- package/editor/references/enquadramento.md +654 -0
- package/editor/references/estilos-prontos.md +355 -0
- package/editor/references/estudio.md +100 -0
- package/editor/references/exemplo-eventos.json +67 -0
- package/editor/references/lettering.md +252 -0
- package/editor/references/modelos.md +494 -0
- package/editor/references/phase2.md +1007 -0
- package/editor/references/referencia-aplicar.md +203 -0
- package/editor/references/referencia-medir.md +375 -0
- package/editor/references/revisao.md +335 -0
- package/editor/references/som-e-trilha.md +146 -0
- package/editor/references/tabela-de-sons.md +130 -0
- package/editor/references/variacoes.md +234 -0
- package/editor/uv.lock +1141 -0
- package/lib/act.js +38 -8
- package/lib/app-chat.js +97 -0
- package/lib/catalogo.js +7 -1
- package/lib/chamada-torta.js +81 -0
- package/lib/editor-pro.js +786 -0
- package/lib/entrega.js +58 -0
- package/lib/estudio/conferencia-visual.js +76 -0
- package/lib/estudio/diretor.js +6 -1
- package/lib/estudio/exportar.js +3 -1
- package/lib/estudio/gosto/tela.html +1349 -0
- package/lib/estudio/gosto.js +305 -0
- package/lib/estudio/index.js +12 -1
- package/lib/estudio/kit-ae/base.jsx +119 -13
- package/lib/estudio/kit-ae/cenas.jsx +47 -15
- package/lib/estudio/kit-ae/guarda.jsx +274 -0
- package/lib/estudio/kit-ae/primitivas.jsx +180 -24
- package/lib/estudio/maquina-diretor.js +70 -12
- package/lib/estudio/maquina-foto.js +177 -0
- package/lib/estudio/maquina-guarda.js +207 -0
- package/lib/estudio/maquina-layout.js +22 -1
- package/lib/estudio/maquina.js +64 -11
- package/lib/estudio/referencias.js +1 -1
- package/lib/estudio/visao/visao.swift +97 -0
- package/lib/estudio/visao.js +60 -0
- package/lib/headless.js +15 -3
- package/lib/local/cerebro.json +285 -7
- package/lib/local/motor.js +5 -2
- package/lib/memoria-usuario.js +164 -0
- package/lib/nuvem.js +33 -2
- package/lib/passagem.js +50 -0
- package/lib/perigo.js +19 -11
- package/lib/protecao.js +134 -0
- package/lib/repeticao.js +1 -1
- package/lib/sandbox.js +57 -9
- package/lib/titulo-conversa.js +46 -0
- package/lib/tools.js +23 -4
- package/lib/ui.js +2 -2
- package/package.json +3 -2
- package/skills/editar-meu-video/SKILL.md +6 -2
- package/skills/editar-video/SKILL.md +113 -0
- package/studio/pessoa.py +52 -0
- package/voz/primo_voz/__main__.py +55 -40
- package/voz/primo_voz/app.py +500 -4
- package/voz/primo_voz/chat.html +1773 -0
- package/voz/primo_voz/conversa.py +0 -26
- package/voz/primo_voz/hud.html +8 -2
- package/voz/primo_voz/hud.py +129 -5
- package/voz/primo_voz/identidade.py +1 -1
- package/voz/primo_voz/painel_dados.py +134 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"about": "Humores da biblioteca de trilhas (helpers/trilhas.py) e o pedido pronto de cada um para o gen_music.py --humor. Só texto: nenhuma música vai junto com a skill.",
|
|
4
|
+
"moods": [
|
|
5
|
+
{
|
|
6
|
+
"id": "animada",
|
|
7
|
+
"label": "Animada",
|
|
8
|
+
"when": "energia, oferta, novidade",
|
|
9
|
+
"prompt": "Upbeat energetic instrumental for a social media ad, bright electronic pop with punchy drums, around 118 bpm, happy and exciting, steady energy from the first second, no vocals, leaves space for voiceover"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"id": "inspiradora",
|
|
13
|
+
"label": "Inspiradora",
|
|
14
|
+
"when": "conquista, virada de vida, motivação",
|
|
15
|
+
"prompt": "Inspiring motivational instrumental, uplifting piano and soft strings over a steady modern beat, around 95 bpm, hopeful and determined, builds gently, no vocals, leaves space for voiceover"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "triste",
|
|
19
|
+
"label": "Triste",
|
|
20
|
+
"when": "dor, problema, desabafo",
|
|
21
|
+
"prompt": "Sad emotional instrumental, gentle solo piano with soft ambient pads, slow around 70 bpm, melancholic and reflective, sparse, no drums, no vocals, leaves space for voiceover"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": "tensa",
|
|
25
|
+
"label": "Tensa",
|
|
26
|
+
"when": "segredo, alerta, erro que as pessoas cometem",
|
|
27
|
+
"prompt": "Tense suspenseful instrumental, low pulsing synth bass and ticking percussion, dark cinematic atmosphere, around 90 bpm, mysterious and urgent, no vocals, leaves space for voiceover"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": "calma",
|
|
31
|
+
"label": "Calma",
|
|
32
|
+
"when": "explicação, tutorial, confiança",
|
|
33
|
+
"prompt": "Calm confident instrumental, soft lo-fi beat with warm electric piano, around 85 bpm, relaxed and trustworthy, steady, no vocals, leaves space for voiceover"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "epica",
|
|
37
|
+
"label": "Épica",
|
|
38
|
+
"when": "revelação, resultado, grande virada",
|
|
39
|
+
"prompt": "Epic cinematic instrumental, big drums and rising orchestral strings, powerful and triumphant, around 100 bpm, dramatic build, no vocals, leaves space for voiceover"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "divertida",
|
|
43
|
+
"label": "Divertida",
|
|
44
|
+
"when": "humor, bastidor, conteúdo leve",
|
|
45
|
+
"prompt": "Playful fun instrumental, quirky ukulele and light percussion with claps, around 110 bpm, cheerful and light, no vocals, leaves space for voiceover"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "luxo",
|
|
49
|
+
"label": "Luxo",
|
|
50
|
+
"when": "produto premium, sofisticação",
|
|
51
|
+
"prompt": "Elegant sophisticated instrumental, smooth deep house groove with soft piano chords, around 120 bpm, premium and stylish, no vocals, leaves space for voiceover"
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
"beats": {
|
|
55
|
+
"GANCHO": ["tensa", "animada"],
|
|
56
|
+
"PROBLEMA": ["triste", "tensa"],
|
|
57
|
+
"VIRADA": ["inspiradora", "epica"],
|
|
58
|
+
"PROVA": ["epica", "calma"],
|
|
59
|
+
"CHAMADA": ["animada", "inspiradora"],
|
|
60
|
+
"PASSO": ["calma"],
|
|
61
|
+
"RESULTADO": ["epica", "inspiradora"],
|
|
62
|
+
"CONTEXTO": ["calma", "triste"],
|
|
63
|
+
"EXPERIENCIA": ["inspiradora", "divertida"],
|
|
64
|
+
"RECOMENDACAO": ["animada", "calma"]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""valores.py: reads numbers and money said in Portuguese (PT-BR speech and on-screen text).
|
|
2
|
+
|
|
3
|
+
Library used by motion_sugerir.py. Normalizes accents and case (`norm`) and parses numbers written or spoken
|
|
4
|
+
("1.200", "vinte e cinco") and money ("R$ 5 mil", "10k", "cinco mil reais", "meio milhão", "dois mil e
|
|
5
|
+
quinhentos", the slang "milão", "seis dígitos"). Counts are not money: "10 mil seguidores" is a count.
|
|
6
|
+
|
|
7
|
+
norm(text) -> str lower case, no accents
|
|
8
|
+
parse_number("1.200") -> 1200.0 a plain number (digits or words)
|
|
9
|
+
parse_money("R$ 5 mil") -> 5000.0 the value of a money expression
|
|
10
|
+
find_money(norm(text)) -> [Money] money spans in normalized text (start, end, value, bare)
|
|
11
|
+
|
|
12
|
+
The regex pieces (DIGITS_RE, WORDNUM_RE, MULT_RE, CUR_RE) are public so detectors can build on them.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import re
|
|
18
|
+
import unicodedata
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
|
|
21
|
+
# ------------------------------------------------------------------ normalization
|
|
22
|
+
|
|
23
|
+
_PUNCT_MAP = {
|
|
24
|
+
"‘": "'", "’": "'", "´": "'", "`": "'",
|
|
25
|
+
"“": '"', "”": '"', "–": "-", "—": "-",
|
|
26
|
+
" ": " ", " ": " ", "…": "...", "*": "",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def normalize(text: str) -> tuple[str, list[int]]:
|
|
31
|
+
"""Lowercase + strip accents. Returns (normalized, index map norm_pos -> original_pos)."""
|
|
32
|
+
out: list[str] = []
|
|
33
|
+
index: list[int] = []
|
|
34
|
+
for i, ch in enumerate(text):
|
|
35
|
+
mapped = _PUNCT_MAP.get(ch)
|
|
36
|
+
pieces = mapped if mapped is not None else unicodedata.normalize("NFKD", ch)
|
|
37
|
+
for piece in pieces:
|
|
38
|
+
if unicodedata.combining(piece):
|
|
39
|
+
continue
|
|
40
|
+
for c in piece.lower():
|
|
41
|
+
out.append(c)
|
|
42
|
+
index.append(i)
|
|
43
|
+
return "".join(out), index
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def norm(text: str) -> str:
|
|
47
|
+
return normalize(text)[0]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ------------------------------------------------------------------ numbers & money
|
|
51
|
+
|
|
52
|
+
WORD_NUMBERS = {
|
|
53
|
+
"um": 1, "uma": 1, "dois": 2, "duas": 2, "tres": 3, "quatro": 4, "cinco": 5, "seis": 6,
|
|
54
|
+
"sete": 7, "oito": 8, "nove": 9, "dez": 10, "onze": 11, "doze": 12, "treze": 13,
|
|
55
|
+
"quatorze": 14, "catorze": 14, "quinze": 15, "dezesseis": 16, "dezessete": 17,
|
|
56
|
+
"dezoito": 18, "dezenove": 19, "vinte": 20, "trinta": 30, "quarenta": 40,
|
|
57
|
+
"cinquenta": 50, "sessenta": 60, "setenta": 70, "oitenta": 80, "noventa": 90,
|
|
58
|
+
"cem": 100, "cento": 100, "duzentos": 200, "duzentas": 200, "trezentos": 300,
|
|
59
|
+
"quatrocentos": 400, "quinhentos": 500, "seiscentos": 600, "setecentos": 700,
|
|
60
|
+
"oitocentos": 800, "novecentos": 900, "meio": 0.5, "meia": 0.5,
|
|
61
|
+
}
|
|
62
|
+
# "milão" is spoken slang for one million ("tá fazendo milão")
|
|
63
|
+
MULTIPLIERS = {"mil": 1_000, "k": 1_000, "milhao": 1_000_000, "milhoes": 1_000_000, "milao": 1_000_000,
|
|
64
|
+
"miloes": 1_000_000, "mi": 1_000_000, "bilhao": 1_000_000_000, "bilhoes": 1_000_000_000,
|
|
65
|
+
"bi": 1_000_000_000}
|
|
66
|
+
|
|
67
|
+
_WORDNUM_ALT = "|".join(sorted(WORD_NUMBERS, key=len, reverse=True))
|
|
68
|
+
WORDNUM_RE = rf"(?:{_WORDNUM_ALT})(?:\s+e\s+(?:{_WORDNUM_ALT}))*"
|
|
69
|
+
DIGITS_RE = r"\d{1,3}(?:\.\d{3})+(?:,\d{1,2})?|\d+(?:[.,]\d{1,2})?"
|
|
70
|
+
NUM_RE = rf"(?:{DIGITS_RE}|\b{WORDNUM_RE})"
|
|
71
|
+
MULT_RE = r"(?:mil|k|milhao|milhoes|milao|miloes|mi|bilhao|bilhoes|bi)"
|
|
72
|
+
CUR_RE = r"(?:reais|real|contos?|pilas?|dolares|dolar|euros?|usd|brl)"
|
|
73
|
+
# spoken compound amounts: "dois mil e quinhentos", "um milhão e meio", "um milhão e 200 mil"
|
|
74
|
+
_COMPOUND_TAIL = rf"(?:\s+e\s+(?:meio|meia|(?:{WORDNUM_RE}|\d{{1,3}})(?:\s+mil\b)?))?"
|
|
75
|
+
# "seis dígitos de faturamento": the number of digits of an amount
|
|
76
|
+
DIGIT_COUNT_RE = r"(?:cinco|seis|sete|oito|nove|5|6|7|8|9)\s+digitos\b"
|
|
77
|
+
|
|
78
|
+
MONEY_RE = (
|
|
79
|
+
rf"(?:(?:r\$|us\$|u\$|\$|€)\s*(?:{DIGITS_RE})(?:\s*{MULT_RE}\b)?(?:\s+(?:de\s+)?{CUR_RE}\b)?"
|
|
80
|
+
rf"|(?<![\w$]){NUM_RE}\s*{MULT_RE}\b{_COMPOUND_TAIL}(?:\s+(?:de\s+)?{CUR_RE}\b)?"
|
|
81
|
+
rf"|(?<![\w$]){NUM_RE}\s+(?:de\s+)?{CUR_RE}\b"
|
|
82
|
+
rf"|\bmil\s+e\s+(?:{WORDNUM_RE}|\d{{1,3}})\b(?:\s+(?:de\s+)?{CUR_RE}\b)?"
|
|
83
|
+
rf"|\bmil\s+(?:de\s+)?{CUR_RE}\b"
|
|
84
|
+
rf"|(?<![\w$]){DIGIT_COUNT_RE}"
|
|
85
|
+
rf"|\b(?:mil\s+)?milh?(?:ao|oes)\b{_COMPOUND_TAIL}(?:\s+(?:de\s+)?{CUR_RE}\b)?)"
|
|
86
|
+
)
|
|
87
|
+
_MONEY_COMPILED = re.compile(MONEY_RE)
|
|
88
|
+
|
|
89
|
+
# Nouns that make a bare "10 mil" a count, not money.
|
|
90
|
+
NON_MONEY_NOUNS = re.compile(
|
|
91
|
+
r"\s*(?:de\s+)?(?:seguidor\w*|inscrit\w*|visualiza\w*|views?|curtidas?|likes?|pessoas?|alun\w*|"
|
|
92
|
+
r"clientes?|leads?|vendas?|pedidos?|unidades?|produtos?|pecas?|metros?|km|quilos?|kg|horas?|"
|
|
93
|
+
r"minutos?|dias?|anos?|mensage\w*|comentarios?|compartilhament\w*|downloads?|acessos?|cliques?|"
|
|
94
|
+
r"impress\w*|membros?|usuari\w*|contatos?|numeros?|videos?|posts?|lojas?|fornecedor\w*|itens|"
|
|
95
|
+
r"copias?|livros?|pacotes?|caixas?|vezes|passos|reels|stories|grupos?|mulheres|homens|familias?)\b"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass
|
|
100
|
+
class Money:
|
|
101
|
+
start: int
|
|
102
|
+
end: int
|
|
103
|
+
value: float | None
|
|
104
|
+
bare: bool # no currency marker (R$, reais...): needs context to count as money
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def parse_number(token: str) -> float | None:
|
|
108
|
+
token = token.strip()
|
|
109
|
+
if not token:
|
|
110
|
+
return None
|
|
111
|
+
if re.fullmatch(r"\d{1,3}(?:\.\d{3})+(?:,\d{1,2})?", token):
|
|
112
|
+
return float(token.replace(".", "").replace(",", "."))
|
|
113
|
+
if re.fullmatch(r"\d+(?:[.,]\d{1,2})?", token):
|
|
114
|
+
return float(token.replace(",", "."))
|
|
115
|
+
words = [w for w in re.split(r"\s+e\s+|\s+", token) if w]
|
|
116
|
+
if words and all(w in WORD_NUMBERS for w in words):
|
|
117
|
+
return float(sum(WORD_NUMBERS[w] for w in words))
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _parse_compound(t: str) -> float | None:
|
|
122
|
+
"""'dois mil e quinhentos' -> 2500, 'mil e quinhentos' -> 1500, 'um milhão e meio' -> 1.5e6."""
|
|
123
|
+
m = re.fullmatch(r"(?:(.+?)\s+)?(mil|milhao|milhoes|milao|miloes)\s+e\s+(meio|meia|.+?)(?:\s+(mil))?", t)
|
|
124
|
+
if not m:
|
|
125
|
+
return None
|
|
126
|
+
unit = float(MULTIPLIERS[m.group(2)])
|
|
127
|
+
head = parse_number(m.group(1)) if m.group(1) else 1.0
|
|
128
|
+
if head is None:
|
|
129
|
+
return None
|
|
130
|
+
if m.group(3) in ("meio", "meia"):
|
|
131
|
+
return head * unit + unit / 2
|
|
132
|
+
tail = parse_number(m.group(3))
|
|
133
|
+
if tail is None:
|
|
134
|
+
return None
|
|
135
|
+
tail *= 1000.0 if m.group(4) else 1.0
|
|
136
|
+
return head * unit + tail if tail < unit else None
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def parse_money(text: str) -> float | None:
|
|
140
|
+
"""Value in currency units of a money expression. '5 mil' -> 5000, 'R$ 1.500,00' -> 1500."""
|
|
141
|
+
t = norm(text)
|
|
142
|
+
t = re.sub(r"(?:r\$|us\$|u\$|\$|€)", " ", t)
|
|
143
|
+
t = re.sub(rf"\b(?:de\s+)?{CUR_RE}\b", " ", t).strip()
|
|
144
|
+
digits = re.fullmatch(DIGIT_COUNT_RE, t)
|
|
145
|
+
if digits:
|
|
146
|
+
n = WORD_NUMBERS.get(t.split()[0]) or float(t.split()[0])
|
|
147
|
+
return float(10 ** (int(n) - 1))
|
|
148
|
+
compound = _parse_compound(" ".join(t.split()))
|
|
149
|
+
if compound is not None:
|
|
150
|
+
return compound
|
|
151
|
+
mult = 1.0
|
|
152
|
+
m = re.search(rf"({MULT_RE})\s*$", t)
|
|
153
|
+
if m and (m.start(1) == 0 or not t[m.start(1) - 1].isalpha()):
|
|
154
|
+
mult = float(MULTIPLIERS[m.group(1)])
|
|
155
|
+
t = t[: m.start(1)].strip()
|
|
156
|
+
if not t:
|
|
157
|
+
return mult if mult > 1 else None
|
|
158
|
+
if t == "mil": # "mil reais" -> 1000 · "mil milhão" (speech slip) -> 1 milhão
|
|
159
|
+
return 1_000.0 if mult == 1.0 else mult
|
|
160
|
+
base = parse_number(t)
|
|
161
|
+
if base is None:
|
|
162
|
+
return None
|
|
163
|
+
return base * mult
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def find_money(ntext: str) -> list[Money]:
|
|
167
|
+
"""Money spans in normalized text."""
|
|
168
|
+
found: list[Money] = []
|
|
169
|
+
for m in _MONEY_COMPILED.finditer(ntext):
|
|
170
|
+
s = m.group(0).rstrip()
|
|
171
|
+
start, end = m.start(), m.start() + len(s)
|
|
172
|
+
bare = not re.search(rf"(?:r\$|\$|€|\b{CUR_RE}\b)", s)
|
|
173
|
+
if re.search(r"digitos$", s):
|
|
174
|
+
if re.search(r"\b(?:senha|codigo|numero|pin|token|cep|cpf|cnpj|telefone|celular|chave)\s+(?:\w+\s+)?(?:de|com)\s+$",
|
|
175
|
+
ntext[max(0, start - 30):start]):
|
|
176
|
+
continue # "senha de 6 dígitos"
|
|
177
|
+
bare = False
|
|
178
|
+
counted = NON_MONEY_NOUNS.match(ntext, end) if bare else None
|
|
179
|
+
if counted and not re.match(r"\s+(?:passad[oa]|retrasad[oa]|atras)\b", ntext[counted.end():]):
|
|
180
|
+
continue # "10 mil seguidores" is a count ("1 milhão ano passado" is still money)
|
|
181
|
+
if bare and re.fullmatch(r"\d+\s*(?:mi|bi)", s): # "5 mi" is too ambiguous alone
|
|
182
|
+
continue
|
|
183
|
+
if s in ("milao", "miloes") and re.search(r"\b(?:em|de|para|pra|pro|a|ao|da|do|na|no|via|ate)\s+$",
|
|
184
|
+
ntext[max(0, start - 8):start]):
|
|
185
|
+
continue # "fui pra Milão": the city, not the slang
|
|
186
|
+
found.append(Money(start, end, parse_money(s), bare))
|
|
187
|
+
return found
|