fix(voz): sobra de fatia interior no corte, e legenda comum poluindo com clipe desativado

Dois problemas reais vistos no projeto Mastopexia:

1. cut_clip_ranges só absorvia um keep-segment curto no INÍCIO/FIM do
   clipe (a lógica já existente do #6). Um keep curto no MEIO (entre dois
   cuts, sem nenhum vizinho mantido pra herdar) nunca era absorvido —
   sobrava como clipe de vídeo de 0,07-0,23s na timeline. Generalizado
   pra qualquer posição, com limiar maior (6 frames / 0,3s, medido no
   material real) — no meio, o pedacinho é descartado (vira parte do
   corte ao redor), nas bordas continua sendo herdado pelo vizinho.

2. generate_subtitles_by_emphasis gerava a legenda comum inteira e
   desativava (enabled="0") onde a dinâmica cobre. Título desativado
   continua aparecendo como clipe riscado na timeline do Final Cut mesmo
   sem renderizar — um corte com bastante ênfase virava dezenas de clipes
   mortos poluindo a trilha (visto ao vivo pelo usuário: "ficou uma
   bosta"). Trocado por não gerar o bloco comum ali, em vez de gerar e
   desativar. Custo: reativar ênfase manualmente depois exige regenerar a
   legenda comum daquele trecho, não só reabilitar.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
João Henrique
2026-08-21 18:59:21 -04:00
co-authored by Claude Sonnet 5
parent 8257155fd3
commit 2ad5854570
4 changed files with 97 additions and 45 deletions
+33 -16
View File
@@ -195,23 +195,40 @@ class CutMixin:
if cursor < clip_duration:
keeps.append((cursor, clip_duration))
# A keep segment shorter than a couple frames at the very start or
# end of the clip is just leftover cut padding with no neighboring
# kept audio on its outer side (the silence butts against the clip's
# own edge) — not a real clip. Rather than emit it as its own
# near-invisible micro-clip, fold it into the adjacent real segment,
# which simply starts earlier / ends later to absorb it.
min_keep_seconds = 2 * float(self.frame_duration_fraction())
if len(keeps) > 1:
first_start, first_end = keeps[0]
if (first_end - first_start).to_seconds() < min_keep_seconds:
keeps[1] = (first_start, keeps[1][1])
# A keep segment shorter than MIN_KEEP_SECONDS is leftover between
# two cuts, not a real clip — at the very start/end of the clip it's
# cut padding with no kept audio on the outer side; in the interior
# it's the pause BETWEEN two things that were both cut (e.g. two
# consecutive deactivated phrases in the voice-editing flow), which
# belongs to neither side by construction. At the edges we fold it
# into the one neighboring KEEP segment there is, which simply starts
# earlier / ends later to absorb it. In the interior both neighbors
# are CUT, not keep, so there is nothing to fold into — it is just
# dropped, extending the surrounding cut across it instead of
# surviving as a third near-invisible micro-clip.
#
# The threshold is bigger than one frame on purpose: measured on a
# real voice-edit (0.07-0.23s residues), a single frame did not catch
# them — this is pause/padding leftover, not intentional short
# content, so treating anything under a third of a second this way
# is safe for this cut path.
min_keep_seconds = max(6 * float(self.frame_duration_fraction()), 0.3)
i = 0
while len(keeps) > 1 and i < len(keeps):
start, end = keeps[i]
if (end - start).to_seconds() >= min_keep_seconds:
i += 1
continue
if i == 0:
keeps[1] = (start, keeps[1][1])
keeps.pop(0)
if len(keeps) > 1:
last_start, last_end = keeps[-1]
if (last_end - last_start).to_seconds() < min_keep_seconds:
keeps[-2] = (keeps[-2][0], last_end)
keeps.pop()
elif i == len(keeps) - 1:
keeps[i - 1] = (keeps[i - 1][0], end)
keeps.pop(i)
else:
keeps.pop(i)
# Re-check the same index: the segment now there might itself be
# short enough to absorb again (two short keeps in a row).
spine.remove(clip)
new_clips: List[ET.Element] = []