Fix first word being cut at clip start ('Žena' problem)

Real-world failure: 'Ansambel Saša Avsenika - ŽENA ME TEPE'
- Refren starts with 'Žena me tepe' at 78.0s
- Scribe's segment boundary: word 'Žena' was end of previous segment (73.9-78.2s)
  while new segment 'tepe, mi prazni žepe' started at 78.3s
- Claude picked clip start = 78.3s (segment boundary)
- Fade-in 0.4s on vocal start = inaudible 'Že-'
- User hears: '...na me tepe' (cut)

Three-part fix:

1. PROMPT: instruct Claude to start clip ~0.3s BEFORE first chorus word
   (not exactly at it). Concrete example with timing math.

2. POST-LLM EXTENSION: scan corrected_segments for boundary cases:
   - If clip start falls MID-segment → extend back to segment start - 0.2s
   - If a previous segment ended within 0.5s of clip start → check if its
     last word might actually be the first chorus word, extend back to it
   - Uses word-level timestamps when available (Scribe provides these)

3. FADE-IN: was 0.4s when starting on vocal — too long, audibly cuts first
   word. Reduced to 0.05s (just click prevention, not audible). Still 0.2s
   for instrumental intros where fade is musically appropriate.

Now 'Žena' will be heard fully — clip starts at ~77.5-77.7s, word starts
at 78.0s, plenty of buffer.
This commit is contained in:
Sebastjan Artič 2026-04-29 14:47:07 +00:00
parent 1cc8e8be35
commit a5097c5acc

View File

@ -742,9 +742,12 @@ def detect_audio_fade(clip_range, transcript, video_duration=None):
if video_duration is not None:
extended_end = min(extended_end, video_duration)
fade_in = 0.4 if starts_in_vocal else 0.2
# Fade-in: če clip začne MED vokalom, fade-in mora biti zelo kratek
# da ne odreže prve besede. Pri vokalnem začetku samo 0.05s "smooth click prevention",
# ne pravi audible fade. Pri instrumentalnem intro lahko 0.2-0.3s.
fade_in = 0.05 if starts_in_vocal else 0.2
# Krajši fade out (0.5s) ker zdaj clip konča po koncu vokala
fade_out = 0.5 if ends_in_vocal else 0.3
fade_out = 0.3 if ends_in_vocal else 0.4
return {
"fade_in": fade_in,
@ -838,7 +841,8 @@ PROSIM:
## ⚠️ ABSOLUTNO PRAVILO: clip se ZAČNE na PRVI BESEDI prvega refrena
- **NE** vključuj kateri koli verz, pre-chorus, build-up, ali intro
- **NE** začni "tik pred" refrenom
- **Začetek = točno tam, kjer prva vrstica refrena prvič začne**
- **Začetek = ~0.3s PRED prvo besedo refrena** (npr. če prva beseda refrena "Žena" začne pri 78.0s, izberi start = 77.7s)
- **Razlog**: 0.3s buffer da ne odrežeš prve besede zaradi audio fade-in efekta
## Identifikacija refrena (univerzalno čez jezike):
- Najdi del, ki se v pesmi **ponavlja vsaj 2-krat** (običajno 3-4x)
@ -1350,6 +1354,57 @@ def main():
duration # ne čez celoten audio
)
# ── EXTEND clip START nazaj če Claude začne sredi besede/segmenta ──
# Refren se pogosto začne na isti besedi kot v transkriptu, ampak Scribe
# lahko zazna mejo med segmenti **PO** prvi besedi refrena (npr.
# "Žena me tepe" — beseda "Žena" v prejšnjem segmentu pri 78.0s,
# nov segment začne pri 78.3s s "tepe"). To pomeni Claude reže
# PRED besedo "Žena" → odrezana.
#
# Strategija: če clip start pade SREDI segmenta (ne tik na začetku),
# razširi nazaj na začetek tega segmenta + 0.2s buffer.
current_start = clip_range["start"]
for seg in corrected_segs:
seg_start = float(seg.get("start", 0))
seg_end = float(seg.get("end", 0))
# Segment ki se prekriva s clip start (start je MED njim)
if seg_start < current_start < seg_end:
# Razširi nazaj — vendar samo če je segment kratek (<3s) in
# se "naslanja" na clip (zadnja beseda lahko vodi v refren)
if (seg_end - seg_start) < 3.0:
new_start = max(0, seg_start - 0.2) # 0.2s buffer pred prvo besedo
if new_start < current_start:
print(f" 🎵 Razširim clip začetek {current_start:.1f}s → {new_start:.1f}s "
f"(prva beseda refrena je v prejšnjem segmentu)", file=sys.stderr)
current_start = new_start
break
# Segment ki se konča TOČNO ali tik pred clip start (lahko zadnja
# beseda refrena = "Žena" se konča na 78.2 ko clip začne 78.3)
elif current_start - 0.5 <= seg_end <= current_start + 0.1:
# Preveri ali zadnja beseda v segmentu morda **začne refren**
seg_text = seg.get("text", "").strip()
# Če segment kaže novo frazo (z veliko začetnico po pavzi) ali
# vsebuje znake interpunkcije, morda zadnja beseda res začne refren
if seg_text and (seg_end - seg_start) < 4.0:
# Razširi nazaj na začetek POSLEDJE besede tega segmenta
words = seg.get("words", [])
if words:
last_word = words[-1]
new_start = max(0, float(last_word.get("start", seg_start)) - 0.15)
else:
# Brez word-level: vzemi 0.5s nazaj
new_start = max(0, current_start - 0.5)
if new_start < current_start:
print(f" 🎵 Razširim clip začetek {current_start:.1f}s → {new_start:.1f}s "
f"(zadnja beseda prejšnjega segmenta morda začne refren)", file=sys.stderr)
current_start = new_start
break
if current_start < clip_range["start"]:
clip_range["start"] = round(current_start, 2)
clip_range["duration"] = round(clip_range["end"] - current_start, 2)
clip_range["reason"] += f" (start extended back)"
# Najdi vse segmente ki se začnejo PO trenutnem clip end
for seg in corrected_segs:
seg_start = float(seg.get("start", 0))