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,272 @@
|
|
|
1
|
+
"""Put every EDL edge in a real pause, measured on the waveform (RMS 10 ms).
|
|
2
|
+
|
|
3
|
+
Whisper word times drift 0.1-0.3 s and the speech regions are too coarse for a tight cut, so an
|
|
4
|
+
edge written from them lands inside a word whenever the speaker runs words together. This helper
|
|
5
|
+
measures the source audio itself and moves each range edge:
|
|
6
|
+
|
|
7
|
+
1. Edge on top of speech (the source is still sounding there): it goes to the nearest pause of
|
|
8
|
+
at least PAUSE_MIN_S, finishing the word (up to REACH_OUT_S outward) or dropping the unfinished
|
|
9
|
+
one (up to REACH_IN_S inward), whichever is closer. With no pause in reach (speech really runs
|
|
10
|
+
on) it goes to the quietest 10 ms point nearby and is reported as SEM PAUSA: listen to it,
|
|
11
|
+
or move the edge to the end of the sentence.
|
|
12
|
+
2. Edge in silence: it is pulled in to 0.08 s before the first sound and 0.12 s after the last
|
|
13
|
+
one (floor + 14 dB starts speech, floor + 10 dB ends it, 80 ms hysteresis), then walked back
|
|
14
|
+
out up to 0.12 s while there is still sound above floor + 6 dB, so a weak consonant (the S of
|
|
15
|
+
"Só") is not bitten.
|
|
16
|
+
|
|
17
|
+
Two ranges of one source that nearly touch (the next starts less than CONTIGUOUS_S after the
|
|
18
|
+
previous ends) are one continuous take split in two: the gap is closed and that junction is left
|
|
19
|
+
alone, because any pause search there would delete words from the middle of the sentence.
|
|
20
|
+
|
|
21
|
+
Never crosses the previous or next range of the same source, never goes more than REACH_OUT_S
|
|
22
|
+
past the original edge (stretching further brings back the flub the cut removed).
|
|
23
|
+
Recipe validated on the Vitória VSL (18/09/2026): 73 s of dead air out, zero words cut.
|
|
24
|
+
|
|
25
|
+
Usage:
|
|
26
|
+
uv run python helpers/bordas_precisas.py <edit>/edl.json [--apply]
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import argparse
|
|
32
|
+
import math
|
|
33
|
+
import shutil
|
|
34
|
+
import sys
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
|
|
37
|
+
import numpy as np
|
|
38
|
+
|
|
39
|
+
from common import load_json, save_json
|
|
40
|
+
from speech_regions import load_mono
|
|
41
|
+
|
|
42
|
+
SR = 16000
|
|
43
|
+
HOP = 0.010
|
|
44
|
+
ON_DB, OFF_DB, WEAK_DB = 14.0, 10.0, 6.0 # above the source floor
|
|
45
|
+
HYST = 8 # 80 ms
|
|
46
|
+
PAD_IN, PAD_OUT = 0.08, 0.12 # silence kept before / after the speech
|
|
47
|
+
WALK = 12 # up to 0.12 s outward for weak consonants
|
|
48
|
+
PAUSE_MIN_S = 0.07 # shorter dips are stop consonants or run-on speech
|
|
49
|
+
REACH_OUT_S = 0.25 # an edge on speech may grow this much to finish a word
|
|
50
|
+
REACH_IN_S = 0.40 # ... or shrink this much to drop the unfinished word
|
|
51
|
+
EDGE_PROBE_S = 0.03 # how close to the edge sound counts as "on speech"
|
|
52
|
+
MIN_DYNAMIC_DB = 20.0 # p90 - floor below this: no silence to measure against
|
|
53
|
+
CLICK_S = 0.03 # a blip this short inside a pause does not split it
|
|
54
|
+
CONTIGUOUS_S = 0.15 # next range of the same source starting this close: one take
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
RUMBLE_AF = "highpass=f=300,highpass=f=300" # car, road, AC: rumble lives below the voice
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def envelope(path: str, af: str | None = None) -> np.ndarray:
|
|
61
|
+
mono, _ = load_mono(path, sr=SR, af=af)
|
|
62
|
+
n = int(HOP * SR)
|
|
63
|
+
frames = mono[: (len(mono) // n) * n].reshape(-1, n).astype(np.float64)
|
|
64
|
+
return 10.0 * np.log10(np.mean(frames * frames, axis=1) + 1e-12)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def idx(t: float) -> int:
|
|
68
|
+
return int(round(t / HOP))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def pauses(db: np.ndarray, floor: float, i0: int, i1: int) -> list[tuple[int, int]]:
|
|
72
|
+
"""Runs below floor+OFF_DB of at least PAUSE_MIN_S inside [i0, i1), as (first, last+1)."""
|
|
73
|
+
i0, i1 = max(0, i0), min(len(db), i1)
|
|
74
|
+
quiet = db[i0:i1] < floor + OFF_DB
|
|
75
|
+
# a blip of CLICK_S or less inside a pause (lip click, breath) does not split it
|
|
76
|
+
k, short = 0, int(round(CLICK_S / HOP))
|
|
77
|
+
while k < quiet.size:
|
|
78
|
+
if not quiet[k]:
|
|
79
|
+
j = k
|
|
80
|
+
while j < quiet.size and not quiet[j]:
|
|
81
|
+
j += 1
|
|
82
|
+
if j - k <= short and k > 0 and j < quiet.size:
|
|
83
|
+
quiet[k:j] = True
|
|
84
|
+
k = j
|
|
85
|
+
else:
|
|
86
|
+
k += 1
|
|
87
|
+
out, k, need = [], 0, int(round(PAUSE_MIN_S / HOP))
|
|
88
|
+
while k < quiet.size:
|
|
89
|
+
if quiet[k]:
|
|
90
|
+
j = k
|
|
91
|
+
while j < quiet.size and quiet[j]:
|
|
92
|
+
j += 1
|
|
93
|
+
if j - k >= need:
|
|
94
|
+
out.append((i0 + k, i0 + j))
|
|
95
|
+
k = j
|
|
96
|
+
else:
|
|
97
|
+
k += 1
|
|
98
|
+
return out
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def on_speech(db: np.ndarray, floor: float, t: float) -> bool:
|
|
102
|
+
a, b = max(0, idx(t - EDGE_PROBE_S)), min(len(db), idx(t + EDGE_PROBE_S) + 1)
|
|
103
|
+
return b > a and float(db[a:b].max()) >= floor + OFF_DB
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def fix_edge_on_speech(db, floor, t, side, lo, hi):
|
|
107
|
+
"""(new_time, how) for an edge that sits on speech."""
|
|
108
|
+
out_dir = 1 if side == "end" else -1
|
|
109
|
+
w0 = max(idx(lo), idx(t - (REACH_OUT_S if side == "start" else REACH_IN_S)))
|
|
110
|
+
w1 = min(idx(hi), idx(t + (REACH_OUT_S if side == "end" else REACH_IN_S)))
|
|
111
|
+
best = None
|
|
112
|
+
for a, b in pauses(db, floor, w0, w1 + 1):
|
|
113
|
+
# the edge keeps PAD_OUT / PAD_IN of the pause, or all of it when the pause is shorter:
|
|
114
|
+
# stopping mid-pause left 0.04 s after the word and the 30 ms fade ate its end
|
|
115
|
+
if side == "end": # end goes where the pause begins
|
|
116
|
+
new = min(a * HOP + PAD_OUT, b * HOP)
|
|
117
|
+
dist = abs(a * HOP - t)
|
|
118
|
+
else: # start goes where the pause ends
|
|
119
|
+
new = max(b * HOP - PAD_IN, a * HOP)
|
|
120
|
+
dist = abs(b * HOP - t)
|
|
121
|
+
grows = (new - t) * out_dir > 0
|
|
122
|
+
if (dist <= REACH_OUT_S if grows else dist <= REACH_IN_S):
|
|
123
|
+
# finishing the word wins a near tie over dropping it
|
|
124
|
+
score = dist - (0.05 if grows else 0.0)
|
|
125
|
+
if best is None or score < best[0]:
|
|
126
|
+
best = (score, new, "termina a palavra" if grows else "tira a palavra pela metade")
|
|
127
|
+
if best:
|
|
128
|
+
return min(max(best[1], lo), hi), best[2]
|
|
129
|
+
a, b = max(idx(lo), idx(t - 0.08)), min(idx(hi), idx(t + 0.08)) + 1
|
|
130
|
+
if b <= a:
|
|
131
|
+
return t, "SEM PAUSA"
|
|
132
|
+
k = a + int(np.argmin(db[a:b]))
|
|
133
|
+
return k * HOP, "SEM PAUSA"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def tighten(db, floor, s, e, lo, hi, fix_start, fix_end):
|
|
137
|
+
"""Pull edges in silence toward the speech, walking out for weak consonants."""
|
|
138
|
+
i0, i1 = idx(s), min(int(math.ceil(e / HOP)), len(db) - 1)
|
|
139
|
+
if i1 <= i0:
|
|
140
|
+
return s, e
|
|
141
|
+
win = db[i0:i1 + 1]
|
|
142
|
+
on = np.where(win >= floor + ON_DB)[0]
|
|
143
|
+
off = np.where(win >= floor + OFF_DB)[0]
|
|
144
|
+
if len(on) == 0 or len(off) == 0:
|
|
145
|
+
return s, e
|
|
146
|
+
ns, ne = s, e
|
|
147
|
+
if fix_start:
|
|
148
|
+
start_i = on[0]
|
|
149
|
+
for i in on:
|
|
150
|
+
if (win[i:i + HYST] >= floor + OFF_DB).mean() > 0.6:
|
|
151
|
+
start_i = i
|
|
152
|
+
break
|
|
153
|
+
k, steps = i0 + start_i, 0
|
|
154
|
+
while steps < WALK and k - 1 >= 0 and db[k - 1] >= floor + WEAK_DB:
|
|
155
|
+
k, steps = k - 1, steps + 1
|
|
156
|
+
ns = min((i0 + start_i) * HOP - PAD_IN, k * HOP - 0.05)
|
|
157
|
+
if fix_end:
|
|
158
|
+
k, steps = i0 + off[-1], 0
|
|
159
|
+
while steps < WALK and k + 1 < len(db) and db[k + 1] >= floor + WEAK_DB:
|
|
160
|
+
k, steps = k + 1, steps + 1
|
|
161
|
+
ne = max((i0 + off[-1] + 1) * HOP + PAD_OUT, (k + 1) * HOP + 0.06)
|
|
162
|
+
return max(ns, lo), min(ne, hi)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def source_levels(path: str, log=None) -> tuple[np.ndarray, float] | None:
|
|
166
|
+
"""(10 ms level, noise floor) of a source, or None when it has no silence to measure against.
|
|
167
|
+
A recording with rumble (car, road, air conditioning) is measured again without the lows: the voice
|
|
168
|
+
stands 25 to 30 dB over what is left, while the high consonants (the S) stay in."""
|
|
169
|
+
for af in (None, RUMBLE_AF):
|
|
170
|
+
db = envelope(path, af)
|
|
171
|
+
floor, top = float(np.percentile(db, 10)), float(np.percentile(db, 90))
|
|
172
|
+
if top - floor >= MIN_DYNAMIC_DB:
|
|
173
|
+
if af and log:
|
|
174
|
+
log(f"{Path(path).stem}: ronco de fundo, medi a voz sem os graves (faixa {top - floor:.0f} dB)")
|
|
175
|
+
return db, floor
|
|
176
|
+
if log:
|
|
177
|
+
log(f"{Path(path).stem}: sem silêncio pra medir (chão {floor:.0f} dB, fala {top:.0f} dB; trilha ou "
|
|
178
|
+
f"vídeo já cortado?). Bordas dessa fonte ficam como estão.")
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def refine_edl(edl: dict, envs: dict | None = None, log=print) -> list[str]:
|
|
183
|
+
"""Move every edge of edl["ranges"] in place. Returns the SEM PAUSA alerts."""
|
|
184
|
+
envs = {} if envs is None else envs
|
|
185
|
+
for name, path in edl["sources"].items():
|
|
186
|
+
if name in envs:
|
|
187
|
+
continue
|
|
188
|
+
envs[name] = source_levels(path, log)
|
|
189
|
+
if envs[name]:
|
|
190
|
+
db, floor = envs[name]
|
|
191
|
+
log(f"{name}: chão {floor:.0f} dB, {len(db) * HOP:.1f} s")
|
|
192
|
+
|
|
193
|
+
rs = edl["ranges"]
|
|
194
|
+
tirado, alerts, moved = 0.0, [], 0
|
|
195
|
+
frozen_start, frozen_end = set(), set()
|
|
196
|
+
for a, b in zip(rs, rs[1:]):
|
|
197
|
+
if a["source"] == b["source"] and 0 <= float(b["start"]) - float(a["end"]) < CONTIGUOUS_S:
|
|
198
|
+
frozen_end.add(a["id"])
|
|
199
|
+
frozen_start.add(b["id"])
|
|
200
|
+
b["start"] = a["end"]
|
|
201
|
+
for n, r in enumerate(rs):
|
|
202
|
+
env = envs.get(r["source"])
|
|
203
|
+
if env is None:
|
|
204
|
+
continue
|
|
205
|
+
db, floor = env
|
|
206
|
+
s, e = float(r["start"]), float(r["end"])
|
|
207
|
+
lo, hi = max(0.0, s - REACH_OUT_S), min(len(db) * HOP, e + REACH_OUT_S)
|
|
208
|
+
prev = next((p for p in rs[:n][::-1] if p["source"] == r["source"]), None)
|
|
209
|
+
nxt = next((p for p in rs[n + 1:] if p["source"] == r["source"]), None)
|
|
210
|
+
if prev and prev["end"] <= s + 1e-6: # only a neighbour that plays before this one
|
|
211
|
+
lo = max(lo, float(prev["end"]) + 0.02)
|
|
212
|
+
if nxt and nxt["start"] >= e - 1e-6:
|
|
213
|
+
hi = min(hi, float(nxt["start"]) - 0.02)
|
|
214
|
+
|
|
215
|
+
notes = []
|
|
216
|
+
ns, ne = s, e
|
|
217
|
+
keep_s, keep_e = r["id"] in frozen_start, r["id"] in frozen_end
|
|
218
|
+
if keep_s:
|
|
219
|
+
notes.append("início emendado no trecho anterior")
|
|
220
|
+
if keep_e:
|
|
221
|
+
notes.append("fim emendado no próximo trecho")
|
|
222
|
+
start_hot = not keep_s and on_speech(db, floor, s)
|
|
223
|
+
end_hot = not keep_e and on_speech(db, floor, e)
|
|
224
|
+
if start_hot:
|
|
225
|
+
ns, how = fix_edge_on_speech(db, floor, s, "start", lo, hi)
|
|
226
|
+
notes.append(f"início na fala: {how}")
|
|
227
|
+
if end_hot:
|
|
228
|
+
ne, how = fix_edge_on_speech(db, floor, e, "end", lo, hi)
|
|
229
|
+
notes.append(f"fim na fala: {how}")
|
|
230
|
+
ts, te = tighten(db, floor, ns, ne, lo, hi, not (start_hot or keep_s), not (end_hot or keep_e))
|
|
231
|
+
ns, ne = (s if keep_s else round(ts, 3)), (e if keep_e else round(te, 3))
|
|
232
|
+
if ne - ns < 0.1:
|
|
233
|
+
notes.append("ficou curto demais, mantido")
|
|
234
|
+
ns, ne = s, e
|
|
235
|
+
if any("SEM PAUSA" in x for x in notes):
|
|
236
|
+
alerts.append(f"{r['id']} ({r['source']} {s:.2f}-{e:.2f}) {r.get('quote', '')[:50]}")
|
|
237
|
+
d0, d1 = ns - s, ne - e
|
|
238
|
+
mark = "" if abs(d0) < 0.005 and abs(d1) < 0.005 else " <-"
|
|
239
|
+
moved += bool(mark)
|
|
240
|
+
tirado += (e - s) - (ne - ns)
|
|
241
|
+
log(f"{r['id']} {r['source']} {s:7.2f}->{ns:7.2f} ({d0:+.2f}) {e:7.2f}->{ne:7.2f} ({d1:+.2f})"
|
|
242
|
+
f"{mark}{' · ' + '; '.join(notes) if notes else ''}")
|
|
243
|
+
r["start"], r["end"] = ns, ne
|
|
244
|
+
|
|
245
|
+
log(f"\n{moved} de {len(rs)} trechos mexidos · silêncio tirado {tirado:+.2f} s")
|
|
246
|
+
if alerts:
|
|
247
|
+
log("SEM PAUSA (a fala corre direto nessa borda; ouça ou mude o corte pro fim da frase):")
|
|
248
|
+
for a in alerts:
|
|
249
|
+
log(" " + a)
|
|
250
|
+
return alerts
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def main() -> int:
|
|
254
|
+
ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
|
|
255
|
+
ap.add_argument("edl", type=Path)
|
|
256
|
+
ap.add_argument("--apply", action="store_true", help="grava no edl.json (backup em edl.json.bak)")
|
|
257
|
+
args = ap.parse_args()
|
|
258
|
+
|
|
259
|
+
edl = load_json(args.edl)
|
|
260
|
+
if not isinstance(edl, dict) or not edl.get("ranges"):
|
|
261
|
+
print(f"EDL sem trechos: {args.edl}", file=sys.stderr)
|
|
262
|
+
return 2
|
|
263
|
+
refine_edl(edl)
|
|
264
|
+
if args.apply:
|
|
265
|
+
shutil.copy(args.edl, args.edl.with_name(args.edl.name + ".bak"))
|
|
266
|
+
save_json(args.edl, edl)
|
|
267
|
+
print(f"gravado em {args.edl} (backup {args.edl.name}.bak)")
|
|
268
|
+
return 0
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
if __name__ == "__main__":
|
|
272
|
+
sys.exit(main())
|