AliJabbarpour commited on
Commit
2cf7da7
·
verified ·
1 Parent(s): b02233b

Update pipeline/postprocess.py

Browse files
Files changed (1) hide show
  1. pipeline/postprocess.py +92 -58
pipeline/postprocess.py CHANGED
@@ -1,61 +1,60 @@
1
- # ─── Post-Processing Engine v3 ─────────────────────────────────────────────────
 
 
 
 
 
 
2
 
3
  def format_timestamp(seconds: float) -> str:
4
  ms = round((seconds % 1) * 1000)
5
- s = int(seconds) % 60
6
- m = int(seconds // 60) % 60
7
- h = int(seconds // 3600)
8
  return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}"
9
 
10
 
11
  def merge_segments(segments: list) -> list:
12
- """
13
- ادغام segment‌های کوتاه و تک‌کلمه‌ای به جملات کامل.
14
- هر جمله حداکثر ۸۰ کاراکتر و حداکثر ۷ ثانیه.
15
- """
16
  if not segments:
17
  return []
18
 
19
- MAX_CHARS = 80
20
- MAX_DURATION = 7.0
21
- MAX_GAP = 1.5 # اگه gap بین دو segment بیشتر از این بود، جدا بمون
22
-
23
- merged = []
24
  current = None
25
 
26
  for seg in segments:
27
  if current is None:
28
  current = {
29
- "text": seg["text"].strip(),
30
  "start": seg["start"],
31
- "end": seg["end"],
32
- "words": seg.get("words", []),
33
  }
34
  continue
35
 
36
- gap = seg["start"] - current["end"]
37
- combined_text = current["text"] + " " + seg["text"].strip()
38
  combined_duration = seg["end"] - current["start"]
39
- ends_sentence = current["text"].rstrip().endswith((".", "!", "?"))
40
 
41
  should_merge = (
42
  len(combined_text.replace(" ", "")) <= MAX_CHARS
43
- and combined_duration <= MAX_DURATION
44
  and gap <= MAX_GAP
45
  and not ends_sentence
46
  )
47
 
48
  if should_merge:
49
- current["text"] = combined_text
50
- current["end"] = seg["end"]
51
- current["words"] = current["words"] + seg.get("words", [])
52
  else:
53
  merged.append(current)
54
  current = {
55
- "text": seg["text"].strip(),
56
  "start": seg["start"],
57
- "end": seg["end"],
58
- "words": seg.get("words", []),
59
  }
60
 
61
  if current:
@@ -64,40 +63,69 @@ def merge_segments(segments: list) -> list:
64
  return merged
65
 
66
 
 
 
 
 
 
 
 
 
 
 
 
67
  def fix_overlaps(segments: list) -> list:
68
- """
69
- رفع timestamp‌های overlapping.
70
- هیچ subtitle نباید قبل از شروع subtitle قبلی تموم بشه.
71
- """
72
  fixed = []
73
- for i, seg in enumerate(segments):
 
74
  if fixed and seg["start"] < fixed[-1]["end"]:
75
- # overlap داریم — start رو تنظیم کن
76
- seg = dict(seg)
77
  seg["start"] = fixed[-1]["end"] + 0.001
78
- if seg["start"] >= seg["end"]:
79
- seg["end"] = seg["start"] + 0.5
80
  fixed.append(seg)
81
  return fixed
82
 
83
 
84
- def split_long_segment(seg: dict, max_chars: int = 80) -> list:
85
  """
86
- شکستن segment‌های خیلی طولانی به دو بخش.
 
 
87
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  words = seg.get("words", [])
89
- text = seg["text"].strip()
90
 
91
- if len(text.replace(" ", "")) <= max_chars or len(words) < 4:
92
  return [seg]
93
 
94
- mid = len(words) // 2
95
-
96
- # پیدا کردن بهترین نقطه split
97
- best_idx = mid
98
  best_gap = -1
99
  for i in range(1, len(words)):
100
- gap = words[i]["start"] - words[i-1]["end"]
101
  if gap > best_gap:
102
  best_gap = gap
103
  best_idx = i
@@ -109,22 +137,22 @@ def split_long_segment(seg: dict, max_chars: int = 80) -> list:
109
  return [seg]
110
 
111
  start2 = words2[0]["start"]
112
- end1 = max(start2 - 0.08, words1[-1]["end"])
113
 
114
  seg1 = {
115
- "text": " ".join(w["word"].strip() for w in words1),
116
  "start": words1[0]["start"],
117
- "end": end1,
118
  "words": words1,
119
  }
120
  seg2 = {
121
- "text": " ".join(w["word"].strip() for w in words2),
122
  "start": start2,
123
- "end": words2[-1]["end"],
124
  "words": words2,
125
  }
126
 
127
- return split_long_segment(seg1, max_chars) + split_long_segment(seg2, max_chars)
128
 
129
 
130
  def build_srt(segments: list) -> str:
@@ -135,7 +163,7 @@ def build_srt(segments: list) -> str:
135
  if not text:
136
  continue
137
  start_tc = format_timestamp(seg["start"])
138
- end_tc = format_timestamp(seg["end"])
139
  lines.append(f"{index}\n{start_tc} --> {end_tc}\n{text}")
140
  index += 1
141
  return "\n\n".join(lines) + "\n"
@@ -145,18 +173,24 @@ def process(segments: list) -> str:
145
  if not segments:
146
  return ""
147
 
148
- # مرحله ۱ — ادغام segment‌های کوتاه
149
  merged = merge_segments(segments)
150
 
151
- # مرحله ۲ — رفع overlap
 
 
 
152
  fixed = fix_overlaps(merged)
153
 
154
- # مرحله ۳ — شکستن segment‌های خیلی طولانی
155
- final = []
156
  for seg in fixed:
157
- final.extend(split_long_segment(seg))
 
 
 
158
 
159
- # مرحله ۴ — رفع overlap بعد از split
160
- final = fix_overlaps(final)
161
 
162
  return build_srt(final)
 
1
+ # ─── Post-Processing Engine v4 ─────────────────────────────────────────────────
2
+
3
+ MIN_DISPLAY = 1.5 # حداقل زمان نمایش هر subtitle (ثانیه)
4
+ MAX_DISPLAY = 7.0 # حداکثر زمان نمایش
5
+ MAX_CHARS = 80 # حداکثر کاراکتر برای merge
6
+ MAX_GAP = 1.5 # حداکثر فاصله بین دو segment برای merge
7
+
8
 
9
  def format_timestamp(seconds: float) -> str:
10
  ms = round((seconds % 1) * 1000)
11
+ s = int(seconds) % 60
12
+ m = int(seconds // 60) % 60
13
+ h = int(seconds // 3600)
14
  return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}"
15
 
16
 
17
  def merge_segments(segments: list) -> list:
18
+ """ادغام segment‌های کوتاه به جملات کامل"""
 
 
 
19
  if not segments:
20
  return []
21
 
22
+ merged = []
 
 
 
 
23
  current = None
24
 
25
  for seg in segments:
26
  if current is None:
27
  current = {
28
+ "text": seg["text"].strip(),
29
  "start": seg["start"],
30
+ "end": seg["end"],
31
+ "words": list(seg.get("words", [])),
32
  }
33
  continue
34
 
35
+ gap = seg["start"] - current["end"]
36
+ combined_text = current["text"] + " " + seg["text"].strip()
37
  combined_duration = seg["end"] - current["start"]
38
+ ends_sentence = current["text"].rstrip().endswith((".", "!", "?"))
39
 
40
  should_merge = (
41
  len(combined_text.replace(" ", "")) <= MAX_CHARS
42
+ and combined_duration <= MAX_DISPLAY
43
  and gap <= MAX_GAP
44
  and not ends_sentence
45
  )
46
 
47
  if should_merge:
48
+ current["text"] = combined_text
49
+ current["end"] = seg["end"]
50
+ current["words"] += list(seg.get("words", []))
51
  else:
52
  merged.append(current)
53
  current = {
54
+ "text": seg["text"].strip(),
55
  "start": seg["start"],
56
+ "end": seg["end"],
57
+ "words": list(seg.get("words", [])),
58
  }
59
 
60
  if current:
 
63
  return merged
64
 
65
 
66
+ def remove_duplicates(segments: list) -> list:
67
+ """حذف subtitle‌های تکراری متوالی"""
68
+ if not segments:
69
+ return []
70
+ result = [segments[0]]
71
+ for seg in segments[1:]:
72
+ if seg["text"].strip() != result[-1]["text"].strip():
73
+ result.append(seg)
74
+ return result
75
+
76
+
77
  def fix_overlaps(segments: list) -> list:
78
+ """رفع timestamp‌های overlapping"""
 
 
 
79
  fixed = []
80
+ for seg in segments:
81
+ seg = dict(seg)
82
  if fixed and seg["start"] < fixed[-1]["end"]:
 
 
83
  seg["start"] = fixed[-1]["end"] + 0.001
84
+ if seg["start"] >= seg["end"]:
85
+ seg["end"] = seg["start"] + MIN_DISPLAY
86
  fixed.append(seg)
87
  return fixed
88
 
89
 
90
+ def apply_min_display(segments: list) -> list:
91
  """
92
+ اعمال حداقل زمان نمایش.
93
+ اگه فاصله تا subtitle بعدی کافی باشه، end رو افزایش میدیم.
94
+ اگه subtitle بعدی نزدیکه، حداکثر تا ۸۰ms قبلش امتداد میدیم.
95
  """
96
+ result = []
97
+ for i, seg in enumerate(segments):
98
+ seg = dict(seg)
99
+ duration = seg["end"] - seg["start"]
100
+
101
+ if duration < MIN_DISPLAY:
102
+ # حداکثر تا کجا میتونیم end رو ببریم؟
103
+ if i + 1 < len(segments):
104
+ next_start = segments[i + 1]["start"]
105
+ max_end = next_start - 0.080 # ۸۰ms قبل از subtitle بعدی
106
+ else:
107
+ max_end = seg["end"] + MAX_DISPLAY
108
+
109
+ new_end = seg["start"] + MIN_DISPLAY
110
+ seg["end"] = min(new_end, max(max_end, seg["end"]))
111
+
112
+ result.append(seg)
113
+ return result
114
+
115
+
116
+ def split_long_segment(seg: dict) -> list:
117
+ """شکستن segment‌های خیلی طولانی"""
118
  words = seg.get("words", [])
119
+ text = seg["text"].strip()
120
 
121
+ if len(text.replace(" ", "")) <= MAX_CHARS or len(words) < 4:
122
  return [seg]
123
 
124
+ # پیدا کردن بهترین نقطه split (بزرگ‌ترین gap)
125
+ best_idx = len(words) // 2
 
 
126
  best_gap = -1
127
  for i in range(1, len(words)):
128
+ gap = words[i]["start"] - words[i - 1]["end"]
129
  if gap > best_gap:
130
  best_gap = gap
131
  best_idx = i
 
137
  return [seg]
138
 
139
  start2 = words2[0]["start"]
140
+ end1 = max(start2 - 0.080, words1[-1]["end"])
141
 
142
  seg1 = {
143
+ "text": " ".join(w["word"].strip() for w in words1),
144
  "start": words1[0]["start"],
145
+ "end": end1,
146
  "words": words1,
147
  }
148
  seg2 = {
149
+ "text": " ".join(w["word"].strip() for w in words2),
150
  "start": start2,
151
+ "end": words2[-1]["end"],
152
  "words": words2,
153
  }
154
 
155
+ return split_long_segment(seg1) + split_long_segment(seg2)
156
 
157
 
158
  def build_srt(segments: list) -> str:
 
163
  if not text:
164
  continue
165
  start_tc = format_timestamp(seg["start"])
166
+ end_tc = format_timestamp(seg["end"])
167
  lines.append(f"{index}\n{start_tc} --> {end_tc}\n{text}")
168
  index += 1
169
  return "\n\n".join(lines) + "\n"
 
173
  if not segments:
174
  return ""
175
 
176
+ # ۱ — ادغام segment‌های کوتاه
177
  merged = merge_segments(segments)
178
 
179
+ # ۲ — حذف duplicate
180
+ merged = remove_duplicates(merged)
181
+
182
+ # ۳ — رفع overlap
183
  fixed = fix_overlaps(merged)
184
 
185
+ # ۴ — شکستن segment‌های خیلی طولانی
186
+ split = []
187
  for seg in fixed:
188
+ split.extend(split_long_segment(seg))
189
+
190
+ # ۵ — رفع overlap بعد از split
191
+ split = fix_overlaps(split)
192
 
193
+ # ۶ — اعمال حداقل زمان نمایش
194
+ final = apply_min_display(split)
195
 
196
  return build_srt(final)