From a5097c5accf67d46626eb9738987bed541a3993d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastjan=20Arti=C4=8D?= Date: Wed, 29 Apr 2026 14:47:07 +0000 Subject: [PATCH] =?UTF-8?q?Fix=20first=20word=20being=20cut=20at=20clip=20?= =?UTF-8?q?start=20('=C5=BDena'=20problem)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- scripts/analyze.py | 61 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/scripts/analyze.py b/scripts/analyze.py index 496d936..3b8b320 100644 --- a/scripts/analyze.py +++ b/scripts/analyze.py @@ -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))