Update pipeline/postprocess.py
Browse files- pipeline/postprocess.py +32 -7
pipeline/postprocess.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# ─── Post-Processing Engine
|
| 2 |
|
| 3 |
MIN_DISPLAY = 1.5
|
| 4 |
MAX_DISPLAY = 7.0
|
|
@@ -6,6 +6,7 @@ MERGE_MAX_CHARS = 72
|
|
| 6 |
MAX_GAP = 1.5
|
| 7 |
MAX_LINE_CHARS = 60 # threshold برای split به دو subtitle جداگانه
|
| 8 |
WRAP_CHARS = 42 # استاندارد Netflix برای wrap به دو خط
|
|
|
|
| 9 |
|
| 10 |
|
| 11 |
def format_timestamp(seconds: float) -> str:
|
|
@@ -30,6 +31,7 @@ def merge_segments(segments: list) -> list:
|
|
| 30 |
"start": seg["start"],
|
| 31 |
"end": seg["end"],
|
| 32 |
"words": list(seg.get("words", [])),
|
|
|
|
| 33 |
}
|
| 34 |
continue
|
| 35 |
|
|
@@ -40,23 +42,37 @@ def merge_segments(segments: list) -> list:
|
|
| 40 |
current_duration = current["end"] - current["start"]
|
| 41 |
is_short = current_duration < 1.5
|
| 42 |
same_speaker = current.get("speaker") is None or current.get("speaker") == seg.get("speaker")
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
should_merge = (
|
| 45 |
not ends_sentence
|
| 46 |
and combined_duration <= MAX_DISPLAY
|
|
|
|
| 47 |
and (
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
is_short
|
| 51 |
-
or (len(combined_text) <= MERGE_MAX_CHARS and gap <= MAX_GAP)
|
| 52 |
-
)
|
| 53 |
)
|
| 54 |
)
|
| 55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
if should_merge:
|
| 57 |
current["text"] = combined_text
|
| 58 |
current["end"] = seg["end"]
|
| 59 |
current["words"] += list(seg.get("words", []))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
else:
|
| 61 |
merged.append(current)
|
| 62 |
current = {
|
|
@@ -64,6 +80,7 @@ def merge_segments(segments: list) -> list:
|
|
| 64 |
"start": seg["start"],
|
| 65 |
"end": seg["end"],
|
| 66 |
"words": list(seg.get("words", [])),
|
|
|
|
| 67 |
}
|
| 68 |
|
| 69 |
if current:
|
|
@@ -115,7 +132,11 @@ def split_long_segment(seg: dict) -> list:
|
|
| 115 |
"""
|
| 116 |
فقط اگه punctuation نزدیک وسط جمله بود، به دو subtitle جداگانه بشکن.
|
| 117 |
اگه نقطه مناسبی نبود، نشکن — wrap_text در build_srt دو خطش میکنه.
|
|
|
|
| 118 |
"""
|
|
|
|
|
|
|
|
|
|
| 119 |
words = seg.get("words", [])
|
| 120 |
text = seg["text"].strip()
|
| 121 |
|
|
@@ -170,7 +191,11 @@ def split_long_segment(seg: dict) -> list:
|
|
| 170 |
|
| 171 |
|
| 172 |
def wrap_text(text: str) -> str:
|
| 173 |
-
"""اگه متن بیشتر از WRAP_CHARS بود، به دو خط تقسیم کن
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
if len(text) <= WRAP_CHARS:
|
| 175 |
return text
|
| 176 |
|
|
|
|
| 1 |
+
# ─── Post-Processing Engine v9 ─────────────────────────────────────────────────
|
| 2 |
|
| 3 |
MIN_DISPLAY = 1.5
|
| 4 |
MAX_DISPLAY = 7.0
|
|
|
|
| 6 |
MAX_GAP = 1.5
|
| 7 |
MAX_LINE_CHARS = 60 # threshold برای split به دو subtitle جداگانه
|
| 8 |
WRAP_CHARS = 42 # استاندارد Netflix برای wrap به دو خط
|
| 9 |
+
CPS_LIMIT = 17 # حداکثر کاراکتر قابلخواندن در ثانیه - برای تشخیص جملهی «بهسرعت رد شده»
|
| 10 |
|
| 11 |
|
| 12 |
def format_timestamp(seconds: float) -> str:
|
|
|
|
| 31 |
"start": seg["start"],
|
| 32 |
"end": seg["end"],
|
| 33 |
"words": list(seg.get("words", [])),
|
| 34 |
+
"speaker": seg.get("speaker"),
|
| 35 |
}
|
| 36 |
continue
|
| 37 |
|
|
|
|
| 42 |
current_duration = current["end"] - current["start"]
|
| 43 |
is_short = current_duration < 1.5
|
| 44 |
same_speaker = current.get("speaker") is None or current.get("speaker") == seg.get("speaker")
|
| 45 |
+
too_fast = (len(current["text"]) / current_duration) > CPS_LIMIT if current_duration > 0 else True
|
| 46 |
+
fits_dialogue = len(current["text"]) <= WRAP_CHARS and len(seg["text"].strip()) <= WRAP_CHARS
|
| 47 |
+
already_dialogue = current.get("is_dialogue_block", False)
|
| 48 |
|
| 49 |
should_merge = (
|
| 50 |
not ends_sentence
|
| 51 |
and combined_duration <= MAX_DISPLAY
|
| 52 |
+
and same_speaker
|
| 53 |
and (
|
| 54 |
+
is_short
|
| 55 |
+
or (len(combined_text) <= MERGE_MAX_CHARS and gap <= MAX_GAP)
|
|
|
|
|
|
|
|
|
|
| 56 |
)
|
| 57 |
)
|
| 58 |
|
| 59 |
+
should_pair_as_dialogue = (
|
| 60 |
+
not already_dialogue
|
| 61 |
+
and not same_speaker
|
| 62 |
+
and too_fast
|
| 63 |
+
and gap <= MAX_GAP
|
| 64 |
+
and fits_dialogue
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
if should_merge:
|
| 68 |
current["text"] = combined_text
|
| 69 |
current["end"] = seg["end"]
|
| 70 |
current["words"] += list(seg.get("words", []))
|
| 71 |
+
elif should_pair_as_dialogue:
|
| 72 |
+
current["text"] = f"- {current['text']}\n- {seg['text'].strip()}"
|
| 73 |
+
current["end"] = seg["end"]
|
| 74 |
+
current["words"] += list(seg.get("words", []))
|
| 75 |
+
current["is_dialogue_block"] = True
|
| 76 |
else:
|
| 77 |
merged.append(current)
|
| 78 |
current = {
|
|
|
|
| 80 |
"start": seg["start"],
|
| 81 |
"end": seg["end"],
|
| 82 |
"words": list(seg.get("words", [])),
|
| 83 |
+
"speaker": seg.get("speaker"),
|
| 84 |
}
|
| 85 |
|
| 86 |
if current:
|
|
|
|
| 132 |
"""
|
| 133 |
فقط اگه punctuation نزدیک وسط جمله بود، به دو subtitle جداگانه بشکن.
|
| 134 |
اگه نقطه مناسبی نبود، نشکن — wrap_text در build_srt دو خطش میکنه.
|
| 135 |
+
بلوکهای دیالوگِ دو-گویندهای (is_dialogue_block) هیچوقت شکسته نمیشن.
|
| 136 |
"""
|
| 137 |
+
if seg.get("is_dialogue_block"):
|
| 138 |
+
return [seg]
|
| 139 |
+
|
| 140 |
words = seg.get("words", [])
|
| 141 |
text = seg["text"].strip()
|
| 142 |
|
|
|
|
| 191 |
|
| 192 |
|
| 193 |
def wrap_text(text: str) -> str:
|
| 194 |
+
"""اگه متن بیشتر از WRAP_CHARS بود، به دو خط تقسیم کن.
|
| 195 |
+
اگه متن از قبل یه بلوک دیالوگِ دو-گویندهای (با - و خط جدید) بود، دستنخورده برگردون."""
|
| 196 |
+
if text.startswith("- ") and "\n" in text:
|
| 197 |
+
return text
|
| 198 |
+
|
| 199 |
if len(text) <= WRAP_CHARS:
|
| 200 |
return text
|
| 201 |
|