subtitld 0.0.0.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. subtitld/__init__.py +19 -0
  2. subtitld/__main__.py +240 -0
  3. subtitld/ftfy/char_classes.dat +0 -0
  4. subtitld/graphics/Montserrat-Black.ttf +0 -0
  5. subtitld/graphics/Montserrat-BlackItalic.ttf +0 -0
  6. subtitld/graphics/Montserrat-Bold.ttf +0 -0
  7. subtitld/graphics/Montserrat-BoldItalic.ttf +0 -0
  8. subtitld/graphics/Montserrat-ExtraBold.ttf +0 -0
  9. subtitld/graphics/Montserrat-ExtraBoldItalic.ttf +0 -0
  10. subtitld/graphics/Montserrat-ExtraLight.ttf +0 -0
  11. subtitld/graphics/Montserrat-ExtraLightItalic.ttf +0 -0
  12. subtitld/graphics/Montserrat-Italic.ttf +0 -0
  13. subtitld/graphics/Montserrat-Light.ttf +0 -0
  14. subtitld/graphics/Montserrat-LightItalic.ttf +0 -0
  15. subtitld/graphics/Montserrat-Medium.ttf +0 -0
  16. subtitld/graphics/Montserrat-MediumItalic.ttf +0 -0
  17. subtitld/graphics/Montserrat-Regular.ttf +0 -0
  18. subtitld/graphics/Montserrat-SemiBold.ttf +0 -0
  19. subtitld/graphics/Montserrat-SemiBoldItalic.ttf +0 -0
  20. subtitld/graphics/Montserrat-Thin.ttf +0 -0
  21. subtitld/graphics/Montserrat-ThinItalic.ttf +0 -0
  22. subtitld/graphics/Ubuntu-B.ttf +0 -0
  23. subtitld/graphics/Ubuntu-BI.ttf +0 -0
  24. subtitld/graphics/Ubuntu-C.ttf +0 -0
  25. subtitld/graphics/Ubuntu-L.ttf +0 -0
  26. subtitld/graphics/Ubuntu-LI.ttf +0 -0
  27. subtitld/graphics/Ubuntu-M.ttf +0 -0
  28. subtitld/graphics/Ubuntu-MI.ttf +0 -0
  29. subtitld/graphics/Ubuntu-R.ttf +0 -0
  30. subtitld/graphics/Ubuntu-RI.ttf +0 -0
  31. subtitld/graphics/Ubuntu-Th.ttf +0 -0
  32. subtitld/graphics/UbuntuMono-B.ttf +0 -0
  33. subtitld/graphics/UbuntuMono-BI.ttf +0 -0
  34. subtitld/graphics/UbuntuMono-R.ttf +0 -0
  35. subtitld/graphics/UbuntuMono-RI.ttf +0 -0
  36. subtitld/graphics/add_and_play.svg +4 -0
  37. subtitld/graphics/add_from_last.svg +4 -0
  38. subtitld/graphics/add_from_last_checked.svg +4 -0
  39. subtitld/graphics/add_subtitle_icon.svg +4 -0
  40. subtitld/graphics/add_subtitle_to_next_start.svg +4 -0
  41. subtitld/graphics/add_subtitle_to_next_start_checked.svg +4 -0
  42. subtitld/graphics/background_watermark.svg +9 -0
  43. subtitld/graphics/character_count_icon.svg +1 -0
  44. subtitld/graphics/checkbox_checked.svg +8 -0
  45. subtitld/graphics/checkbox_checked_hover.svg +8 -0
  46. subtitld/graphics/checkbox_disabled.svg +4 -0
  47. subtitld/graphics/checkbox_unchecked.svg +5 -0
  48. subtitld/graphics/checkbox_unchecked_hover.svg +5 -0
  49. subtitld/graphics/combobox_down_arrow.svg +4 -0
  50. subtitld/graphics/cps_icon.svg +1 -0
  51. subtitld/graphics/dialog_close_icon.svg +4 -0
  52. subtitld/graphics/dialog_close_icon_hover.svg +4 -0
  53. subtitld/graphics/dubbing_container_collapse_icon.svg +8 -0
  54. subtitld/graphics/dubbing_container_expand_icon.svg +4 -0
  55. subtitld/graphics/file.svg +4 -0
  56. subtitld/graphics/gap_add_icon.svg +4 -0
  57. subtitld/graphics/gap_remove_icon.svg +4 -0
  58. subtitld/graphics/grid_frames_icon.svg +4 -0
  59. subtitld/graphics/grid_frames_icon_checked.svg +4 -0
  60. subtitld/graphics/grid_frames_icon_disabled.svg +4 -0
  61. subtitld/graphics/grid_scenes_icon.svg +4 -0
  62. subtitld/graphics/grid_scenes_icon_checked.svg +4 -0
  63. subtitld/graphics/grid_scenes_icon_disabled.svg +4 -0
  64. subtitld/graphics/grid_seconds_icon.svg +4 -0
  65. subtitld/graphics/grid_seconds_icon_checked.svg +4 -0
  66. subtitld/graphics/grid_seconds_icon_disabled.svg +4 -0
  67. subtitld/graphics/last_end_to_current_position_icon.svg +4 -0
  68. subtitld/graphics/last_start_last_end_to_current_position_button.svg +9 -0
  69. subtitld/graphics/last_start_to_current_position_icon.svg +4 -0
  70. subtitld/graphics/left_panel_autosave.svg +4 -0
  71. subtitld/graphics/left_panel_dubbing.svg +4 -0
  72. subtitld/graphics/left_panel_export.svg +4 -0
  73. subtitld/graphics/left_panel_global.svg +4 -0
  74. subtitld/graphics/left_panel_import.svg +4 -0
  75. subtitld/graphics/left_panel_interface.svg +4 -0
  76. subtitld/graphics/left_panel_keyboard.svg +4 -0
  77. subtitld/graphics/left_panel_metadata.svg +4 -0
  78. subtitld/graphics/left_panel_navigation_background.svg +11 -0
  79. subtitld/graphics/left_panel_qualitycheck.svg +4 -0
  80. subtitld/graphics/left_panel_speakers.svg +4 -0
  81. subtitld/graphics/left_panel_subtitles.svg +1 -0
  82. subtitld/graphics/left_panel_transcription.svg +4 -0
  83. subtitld/graphics/left_panel_translation.svg +4 -0
  84. subtitld/graphics/merge_back_selected_subtitle_icon.svg +4 -0
  85. subtitld/graphics/merge_next_selected_subtitle_icon.svg +4 -0
  86. subtitld/graphics/move_backward_subtitle.svg +4 -0
  87. subtitld/graphics/move_end_back_subtitle.svg +4 -0
  88. subtitld/graphics/move_end_forward_subtitle.svg +4 -0
  89. subtitld/graphics/move_forward_subtitle.svg +4 -0
  90. subtitld/graphics/move_start_back_subtitle.svg +4 -0
  91. subtitld/graphics/move_start_forward_subtitle.svg +4 -0
  92. subtitld/graphics/music_voice_separation_music_icon.svg +4 -0
  93. subtitld/graphics/music_voice_separation_music_icon_disabled.svg +4 -0
  94. subtitld/graphics/music_voice_separation_voice_icon.svg +4 -0
  95. subtitld/graphics/music_voice_separation_voice_icon_disabled.svg +4 -0
  96. subtitld/graphics/new_line_icon.svg +1 -0
  97. subtitld/graphics/next_end_to_current_position_icon.svg +4 -0
  98. subtitld/graphics/next_start_next_end_to_current_position_button.svg +4 -0
  99. subtitld/graphics/next_start_to_current_position_icon.svg +4 -0
  100. subtitld/graphics/pause_icon.svg +12 -0
  101. subtitld/graphics/play_from_last_start_icon.svg +5 -0
  102. subtitld/graphics/play_from_next_start_icon.svg +7 -0
  103. subtitld/graphics/play_icon.svg +12 -0
  104. subtitld/graphics/play_icon_hover.svg +12 -0
  105. subtitld/graphics/play_icon_pressed.svg +12 -0
  106. subtitld/graphics/playback_repeat.svg +4 -0
  107. subtitld/graphics/playback_speed.svg +6 -0
  108. subtitld/graphics/playbackspeed_border.svg +5 -0
  109. subtitld/graphics/playbackspeed_border_checked.svg +5 -0
  110. subtitld/graphics/playbackspeed_border_hover.svg +5 -0
  111. subtitld/graphics/playercontrols_button_first.svg +5 -0
  112. subtitld/graphics/playercontrols_button_first_hover.svg +6 -0
  113. subtitld/graphics/playercontrols_button_first_pressed.svg +6 -0
  114. subtitld/graphics/playercontrols_button_last.svg +6 -0
  115. subtitld/graphics/playercontrols_button_last_hover.svg +6 -0
  116. subtitld/graphics/playercontrols_button_last_pressed.svg +6 -0
  117. subtitld/graphics/playercontrols_widget_center_bottom_line.svg +13 -0
  118. subtitld/graphics/playercontrols_widget_center_top_line.svg +17 -0
  119. subtitld/graphics/plus_icon.svg +4 -0
  120. subtitld/graphics/record_icon.svg +8 -0
  121. subtitld/graphics/record_icon_pressed.svg +5 -0
  122. subtitld/graphics/remove_selected_subtitle_icon.svg +6 -0
  123. subtitld/graphics/repeat_playback_border.svg +5 -0
  124. subtitld/graphics/repeat_playback_border_checked.svg +5 -0
  125. subtitld/graphics/repeat_playback_border_hover.svg +5 -0
  126. subtitld/graphics/save.svg +4 -0
  127. subtitld/graphics/send_text_to_last_subtitle_and_slice_button.svg +8 -0
  128. subtitld/graphics/send_text_to_last_subtitle_icon.svg +8 -0
  129. subtitld/graphics/send_text_to_next_subtitle_and_slice_button.svg +8 -0
  130. subtitld/graphics/send_text_to_next_subtitle_icon.svg +8 -0
  131. subtitld/graphics/slice_selected_subtitle_icon.svg +4 -0
  132. subtitld/graphics/snap_grid_icon.svg +4 -0
  133. subtitld/graphics/snap_grid_icon_checked.svg +4 -0
  134. subtitld/graphics/snap_grid_icon_disabled.svg +4 -0
  135. subtitld/graphics/snap_limits_icon.svg +4 -0
  136. subtitld/graphics/snap_limits_icon_checked.svg +4 -0
  137. subtitld/graphics/snap_limits_icon_disabled.svg +4 -0
  138. subtitld/graphics/snap_move_next.svg +4 -0
  139. subtitld/graphics/snap_move_next_checked.svg +4 -0
  140. subtitld/graphics/snap_move_next_disabled.svg +4 -0
  141. subtitld/graphics/snap_moving_icon.svg +4 -0
  142. subtitld/graphics/snap_moving_icon_checked.svg +4 -0
  143. subtitld/graphics/snap_moving_icon_disabled.svg +4 -0
  144. subtitld/graphics/speaker_list_item_change_color_icon.svg +4 -0
  145. subtitld/graphics/speaker_list_item_change_color_icon_hover.svg +4 -0
  146. subtitld/graphics/speaker_list_item_change_image_icon.svg +4 -0
  147. subtitld/graphics/speaker_list_item_change_image_icon_hover.svg +4 -0
  148. subtitld/graphics/speaker_list_item_export_icon.svg +4 -0
  149. subtitld/graphics/speaker_list_item_export_icon_hover.svg +4 -0
  150. subtitld/graphics/speaker_list_item_remove_icon.svg +4 -0
  151. subtitld/graphics/speaker_list_item_remove_icon_hover.svg +4 -0
  152. subtitld/graphics/speaker_list_item_rename_icon.svg +4 -0
  153. subtitld/graphics/speaker_list_item_rename_icon_hover.svg +4 -0
  154. subtitld/graphics/spinbox_arrow_down.svg +4 -0
  155. subtitld/graphics/spinbox_arrow_up.svg +4 -0
  156. subtitld/graphics/start_screen_top_shadow.svg +23 -0
  157. subtitld/graphics/stop_icon.svg +6 -0
  158. subtitld/graphics/stop_icon_hover.svg +6 -0
  159. subtitld/graphics/stop_icon_pressed.svg +6 -0
  160. subtitld/graphics/stylesheet.qss +1838 -0
  161. subtitld/graphics/subtitld.ico +0 -0
  162. subtitld/graphics/subtitld.png +0 -0
  163. subtitld/graphics/subtitles_panel_simplelist_properties_duration_timing_lock_closed_icon.svg +4 -0
  164. subtitld/graphics/subtitles_panel_simplelist_properties_duration_timing_lock_open_icon.svg +4 -0
  165. subtitld/graphics/subtitles_panel_simplelist_properties_duration_timing_lock_open_icon_hover.svg +4 -0
  166. subtitld/graphics/timeline_bottom_controls_background.svg +25 -0
  167. subtitld/graphics/timeline_cursor_back_frame.svg +4 -0
  168. subtitld/graphics/timeline_cursor_back_frame_hover.svg +4 -0
  169. subtitld/graphics/timeline_cursor_next_frame.svg +4 -0
  170. subtitld/graphics/timeline_cursor_next_frame_hover.svg +4 -0
  171. subtitld/graphics/timeline_show_speaker_color.svg +5 -0
  172. subtitld/graphics/timeline_show_speaker_color_checked.svg +5 -0
  173. subtitld/graphics/timeline_show_speaker_in_tracks.svg +4 -0
  174. subtitld/graphics/timeline_show_speaker_in_tracks_checked.svg +4 -0
  175. subtitld/graphics/timelinescrolling_follow_icon.svg +4 -0
  176. subtitld/graphics/timelinescrolling_follow_icon_checked.svg +4 -0
  177. subtitld/graphics/timelinescrolling_none_icon.svg +6 -0
  178. subtitld/graphics/timelinescrolling_none_icon_checked.svg +6 -0
  179. subtitld/graphics/timelinescrolling_page_icon.svg +4 -0
  180. subtitld/graphics/timelinescrolling_page_icon_checked.svg +4 -0
  181. subtitld/graphics/titleBar_left_container_border.svg +113 -0
  182. subtitld/graphics/unknown_speaker.svg +5 -0
  183. subtitld/graphics/vosk_transcription_download_model_icon.svg +4 -0
  184. subtitld/graphics/vosk_transcription_update_model_icon.svg +4 -0
  185. subtitld/graphics/word_count_icon.svg +1 -0
  186. subtitld/graphics/wpm_icon.svg +1 -0
  187. subtitld/graphics/zoom_in_icon.svg +4 -0
  188. subtitld/graphics/zoom_in_icon_disabled.svg +4 -0
  189. subtitld/graphics/zoom_out_icon.svg +4 -0
  190. subtitld/graphics/zoom_out_icon_disabled.svg +4 -0
  191. subtitld/interface/__init__.py +0 -0
  192. subtitld/interface/actionmanager.py +18 -0
  193. subtitld/interface/bottom_panel.py +43 -0
  194. subtitld/interface/left_panel.py +144 -0
  195. subtitld/interface/left_panel_autosave.py +149 -0
  196. subtitld/interface/left_panel_dubbing.py +469 -0
  197. subtitld/interface/left_panel_export.py +755 -0
  198. subtitld/interface/left_panel_global.py +358 -0
  199. subtitld/interface/left_panel_import.py +82 -0
  200. subtitld/interface/left_panel_interface.py +616 -0
  201. subtitld/interface/left_panel_keyboard.py +261 -0
  202. subtitld/interface/left_panel_metadata.py +74 -0
  203. subtitld/interface/left_panel_qualitycheck.py +262 -0
  204. subtitld/interface/left_panel_speakers.py +737 -0
  205. subtitld/interface/left_panel_subtitleslist.py +791 -0
  206. subtitld/interface/left_panel_transcription.py +763 -0
  207. subtitld/interface/left_panel_translation.py +294 -0
  208. subtitld/interface/playercontrols.py +2348 -0
  209. subtitld/interface/preview_panel.py +407 -0
  210. subtitld/interface/productionscreen.py +65 -0
  211. subtitld/interface/startscreen.py +273 -0
  212. subtitld/interface/timeline.py +1308 -0
  213. subtitld/interface/top_bar.py +329 -0
  214. subtitld/interface/translation.py +68 -0
  215. subtitld/interface/utils.py +286 -0
  216. subtitld/locale/en_US.json +301 -0
  217. subtitld/locale/pt_BR.json +269 -0
  218. subtitld/modules/__init__.py +0 -0
  219. subtitld/modules/audioengine.py +606 -0
  220. subtitld/modules/bounce.py +229 -0
  221. subtitld/modules/config.py +84 -0
  222. subtitld/modules/file_io.py +870 -0
  223. subtitld/modules/history.py +31 -0
  224. subtitld/modules/quality_check.py +53 -0
  225. subtitld/modules/session.py +270 -0
  226. subtitld/modules/shortcuts.py +84 -0
  227. subtitld/modules/subtitles.py +413 -0
  228. subtitld/modules/timecode.py +496 -0
  229. subtitld/modules/usf.py +366 -0
  230. subtitld/modules/utils.py +79 -0
  231. subtitld/modules/waveform.py +53 -0
  232. subtitld-0.0.0.dev0.dist-info/METADATA +97 -0
  233. subtitld-0.0.0.dev0.dist-info/RECORD +237 -0
  234. subtitld-0.0.0.dev0.dist-info/WHEEL +5 -0
  235. subtitld-0.0.0.dev0.dist-info/entry_points.txt +2 -0
  236. subtitld-0.0.0.dev0.dist-info/licenses/LICENSE +674 -0
  237. subtitld-0.0.0.dev0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,737 @@
1
+ import cv2
2
+ import mediapipe as mp
3
+ import numpy as np
4
+ from autohex import AutoHex
5
+
6
+ from PySide6.QtWidgets import QVBoxLayout, QWidget, QScrollArea, QHBoxLayout, QDialog, QPushButton, QLabel, QLineEdit, QSizePolicy, QColorDialog, QComboBox, QCheckBox, QStackedWidget
7
+ from PySide6.QtGui import QImage, QPixmap, QPainter, QPainterPath, QColor, QPolygonF, QCursor
8
+ from PySide6.QtCore import QThread, Signal, Qt, QSize, QRect, QPoint
9
+
10
+ from subtitld.interface import utils
11
+ from subtitld.interface import left_panel
12
+ from subtitld.interface.translation import _
13
+
14
+ from subtitld.modules import session
15
+ from subtitld.modules import subtitles
16
+
17
+
18
+ class FaceExtractorThread(QThread):
19
+ result = Signal(dict)
20
+ progress = Signal(float)
21
+ error = Signal(str)
22
+
23
+ def __init__(self, parent=None):
24
+ super().__init__(parent)
25
+ self.name = None
26
+ self.step = 0.3
27
+ self._running = True
28
+
29
+ def run(self):
30
+ intervals = subtitles.get_speaker_intervals(self.name)
31
+ if not self.name or not intervals:
32
+ return
33
+
34
+ cap = cv2.VideoCapture(session.VIDEO['filepath'])
35
+ if not cap.isOpened():
36
+ self.error.emit(f"Cannot open video: {session.VIDEO['filepath']}")
37
+ return
38
+
39
+ fps = cap.get(cv2.CAP_PROP_FPS)
40
+ if fps <= 0:
41
+ self.error.emit("Invalid FPS in video file.")
42
+ cap.release()
43
+ return
44
+
45
+ frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
46
+ total_duration = frame_count / fps if fps else 0
47
+
48
+ mp_face_detection = mp.solutions.face_detection
49
+
50
+ try:
51
+ with mp_face_detection.FaceDetection(
52
+ model_selection=1,
53
+ min_detection_confidence=0.5
54
+ ) as face_detector:
55
+
56
+ face_found = False
57
+
58
+ for start_time, end_time in intervals:
59
+ if not self._running or face_found:
60
+ return
61
+
62
+ if end_time <= start_time:
63
+ continue
64
+
65
+ start_time = max(0, start_time)
66
+ end_time = min(end_time, total_duration)
67
+ total_range = end_time - start_time
68
+ t = start_time
69
+
70
+ while t <= end_time and self._running and not face_found:
71
+ frame_idx = int(t * fps)
72
+ cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
73
+ ret, frame = cap.read()
74
+ if not ret:
75
+ break
76
+
77
+ rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
78
+ results = face_detector.process(rgb)
79
+
80
+ if results.detections:
81
+ det = results.detections[0]
82
+ box = det.location_data.relative_bounding_box
83
+ h, w, _ = rgb.shape
84
+ x1 = int(box.xmin * w)
85
+ y1 = int(box.ymin * h)
86
+ x2 = int((box.xmin + box.width) * w)
87
+ y2 = int((box.ymin + box.height) * h)
88
+ x1, y1 = max(0, x1), max(0, y1)
89
+ x2, y2 = min(w, x2), min(h, y2)
90
+
91
+ face_crop = rgb[y1:y2, x1:x2]
92
+ qimg = self._to_qimage(face_crop)
93
+ if qimg:
94
+ self.result.emit({
95
+ "name": self.name,
96
+ "image": qimg,
97
+ "timestamp": round(t, 3)
98
+ })
99
+ face_found = True
100
+ break
101
+
102
+ progress_value = (t - start_time) / total_range
103
+ self.progress.emit(min(max(progress_value, 0), 1))
104
+ t += self.step
105
+
106
+ if not face_found:
107
+ self.result.emit({
108
+ "name": self.name,
109
+ "image": None,
110
+ "timestamp": None
111
+ })
112
+
113
+ except Exception as e:
114
+ self.error.emit(str(e))
115
+ finally:
116
+ cap.release()
117
+
118
+ # ---------------------------------
119
+ # Public control methods
120
+ # ---------------------------------
121
+ def stop(self):
122
+ """Request thread stop gracefully."""
123
+ self._running = False
124
+
125
+ # ---------------------------------
126
+ # Internal utility
127
+ # ---------------------------------
128
+ @staticmethod
129
+ def _to_qimage(image: np.ndarray) -> QImage | None:
130
+ if image is None or image.size == 0 or image.ndim != 3:
131
+ return None
132
+ image = np.ascontiguousarray(image)
133
+ h, w, ch = image.shape
134
+ if ch != 3:
135
+ return None
136
+ bytes_per_line = ch * w
137
+ return QImage(image.data, w, h, bytes_per_line, QImage.Format_RGB888).copy()
138
+
139
+
140
+ class AccordionArrowWidget(QWidget):
141
+ """Custom widget that draws the accordion arrow icon."""
142
+
143
+ def __init__(self, expanded=True, parent=None):
144
+ super().__init__(parent)
145
+ self._expanded = expanded
146
+ self.setFixedSize(20, 20)
147
+
148
+ def set_expanded(self, expanded: bool):
149
+ self._expanded = expanded
150
+ self.update()
151
+
152
+ def paintEvent(self, event):
153
+ painter = QPainter(self)
154
+ painter.setRenderHint(QPainter.Antialiasing)
155
+ painter.setPen(Qt.NoPen)
156
+ painter.setBrush(QColor("#1e40af"))
157
+
158
+ if self._expanded:
159
+ arrow = QPolygonF([
160
+ QPoint(4, 12),
161
+ QPoint(16, 12),
162
+ QPoint(10, 6)
163
+ ])
164
+ else:
165
+ arrow = QPolygonF([
166
+ QPoint(4, 8),
167
+ QPoint(16, 8),
168
+ QPoint(10, 14)
169
+ ])
170
+
171
+ painter.drawPolygon(arrow)
172
+
173
+
174
+ class dubbing_container(QWidget):
175
+ def __init__(widget, parent=None):
176
+ super().__init__(parent)
177
+ widget.setLayout(QVBoxLayout())
178
+ widget.layout().setContentsMargins(10, 10, 10, 10)
179
+ widget.layout().setSpacing(0)
180
+
181
+ widget.setProperty('is_expanded', False)
182
+
183
+ header_line = QHBoxLayout()
184
+ header_line.setContentsMargins(0, 0, 0, 0)
185
+ header_line.setSpacing(0)
186
+ widget.layout().addLayout(header_line)
187
+
188
+ widget.combobox = utils.LabeledComboBox()
189
+ widget.combobox.activated.connect(lambda: widget.combobox_changed())
190
+ header_line.addWidget(widget.combobox, 1)
191
+
192
+ widget.hideexpand_button = QPushButton()
193
+ widget.hideexpand_button.setObjectName('dubbing_container_hideexpand_button')
194
+ widget.hideexpand_button.setCheckable(True)
195
+ widget.hideexpand_button.setFixedSize(24, 24)
196
+ widget.hideexpand_button.setIconSize(QSize(16, 16))
197
+ widget.combobox.bottom_line.addWidget(widget.hideexpand_button, 0)
198
+
199
+ widget.hideexpand_button.clicked.connect(lambda: widget.hideexpand_button_clicked())
200
+
201
+ widget.content = QStackedWidget()
202
+ widget.content.setObjectName('left_panel_speakers_panel_content_item_dubbing_content')
203
+ widget.content.setVisible(False)
204
+ widget.layout().addWidget(widget.content)
205
+
206
+ widget.update()
207
+
208
+ def combobox_changed(widget):
209
+ value = widget.combobox.currentText()
210
+ speaker_name = widget.property('speaker')
211
+
212
+ if speaker_name and speaker_name in session.SPEAKERS:
213
+ if not 'dubbing' in session.SPEAKERS[speaker_name]:
214
+ session.SPEAKERS[speaker_name]['dubbing'] = {}
215
+ session.SPEAKERS[speaker_name]['dubbing']['engine'] = value
216
+ widget.update()
217
+
218
+ def hideexpand_button_clicked(widget):
219
+ widget.setProperty('is_expanded', not widget.property('is_expanded'))
220
+ widget.update()
221
+
222
+ def update(widget):
223
+ has_engine = widget.combobox.combobox.currentIndex() >= 0
224
+ widget.hideexpand_button.setEnabled(has_engine)
225
+ if not has_engine:
226
+ widget.setProperty('is_expanded', False)
227
+
228
+ if widget.property('is_expanded'):
229
+ if not widget.hideexpand_button.isChecked():
230
+ widget.hideexpand_button.setChecked(True)
231
+ if not widget.content.isVisible():
232
+ widget.content.setVisible(True)
233
+
234
+ # widget.content.setCurrentWidget()
235
+ else:
236
+ if widget.hideexpand_button.isChecked():
237
+ widget.hideexpand_button.setChecked(False)
238
+ if widget.content.isVisible():
239
+ widget.content.setVisible(False)
240
+
241
+
242
+ class RoundedCornerLabel(QLabel):
243
+ def __init__(self, *args, **kwargs):
244
+ super().__init__(*args, **kwargs)
245
+ self.radius_top_left = 2 # 2px radius for top-left corner
246
+
247
+ def paintEvent(self, event):
248
+ pixmap = self.pixmap()
249
+ if pixmap:
250
+ painter = QPainter(self)
251
+ painter.setRenderHint(QPainter.Antialiasing)
252
+
253
+ # Create a rounded rectangle path with only top-left corner rounded
254
+ path = QPainterPath()
255
+ size = self.size()
256
+
257
+ # Start from top-left with rounded corner
258
+ path.moveTo(self.radius_top_left, 0)
259
+ path.lineTo(size.width(), 0) # Top edge
260
+ path.lineTo(size.width(), size.height()) # Right edge
261
+ path.lineTo(0, size.height()) # Bottom edge
262
+ path.lineTo(0, self.radius_top_left) # Left edge going up to the arc start
263
+ # Arc from left edge to top edge (counter-clockwise for proper curve)
264
+ path.arcTo(0, 0, self.radius_top_left * 2, self.radius_top_left * 2, 180, -90) # Top-left arc
265
+ path.closeSubpath()
266
+
267
+ # Clip the drawing area to the path
268
+ painter.setClipPath(path)
269
+
270
+ # Draw the pixmap
271
+ painter.drawPixmap(self.rect(), pixmap)
272
+ else:
273
+ # If no pixmap, just draw normally
274
+ super().paintEvent(event)
275
+
276
+
277
+ class speakers_list_item(QWidget):
278
+ def __init__(widget, speaker_name, speaker_data):
279
+ super().__init__()
280
+ widget.speaker_name = speaker_name
281
+ widget.speaker_data = speaker_data
282
+
283
+ widget.setAttribute(Qt.WA_StyledBackground, True)
284
+ widget.setObjectName('left_panel_speakers_panel_content_item')
285
+ widget.setProperty('class', '')
286
+ widget.setProperty('speaker_name', widget.speaker_name)
287
+ widget.setLayout(QVBoxLayout())
288
+ widget.layout().setContentsMargins(0, 0, 0, 0)
289
+ widget.layout().setSpacing(0)
290
+
291
+ up_line = QWidget()
292
+ up_line.setLayout(QHBoxLayout())
293
+ up_line.layout().setContentsMargins(0, 0, 10, 0)
294
+
295
+ widget.speaker_icon = RoundedCornerLabel()
296
+ widget.speaker_icon.setFixedSize(42, 42)
297
+ widget.speaker_icon.setScaledContents(True)
298
+ up_line.layout().addWidget(widget.speaker_icon)
299
+
300
+ widget.name_label = QLabel()
301
+ up_line.layout().addWidget(widget.name_label)
302
+
303
+ widget.rename_button = QPushButton()
304
+ widget.rename_button.setObjectName('left_panel_speakers_panel_content_item_rename_button')
305
+ widget.rename_button.setFixedSize(24, 24)
306
+ widget.rename_button.setIconSize(QSize(16, 16))
307
+ widget.rename_button.setVisible(False)
308
+ widget.rename_button.clicked.connect(lambda: rename_button_clicked(widget))
309
+ up_line.layout().addWidget(widget.rename_button)
310
+
311
+ widget.change_color_button = QPushButton()
312
+ widget.change_color_button.setObjectName('left_panel_speakers_panel_content_item_change_color_button')
313
+ widget.change_color_button.setFixedSize(24, 24)
314
+ widget.change_color_button.setIconSize(QSize(16, 16))
315
+ widget.change_color_button.setVisible(False)
316
+ widget.change_color_button.clicked.connect(lambda: change_color_button_clicked(widget))
317
+ up_line.layout().addWidget(widget.change_color_button)
318
+
319
+ widget.change_image_button = QPushButton()
320
+ widget.change_image_button.setObjectName('left_panel_speakers_panel_content_item_change_image_button')
321
+ widget.change_image_button.setFixedSize(24, 24)
322
+ widget.change_image_button.setIconSize(QSize(16, 16))
323
+ widget.change_image_button.setVisible(False)
324
+ widget.change_image_button.setToolTip(_('left_panel_speakers.change_image_tooltip'))
325
+ widget.change_image_button.clicked.connect(lambda: change_image_button_clicked(widget))
326
+ up_line.layout().addWidget(widget.change_image_button)
327
+
328
+ widget.export_button = QPushButton()
329
+ widget.export_button.setObjectName('left_panel_speakers_panel_content_item_export_button')
330
+ widget.export_button.setFixedSize(24, 24)
331
+ widget.export_button.setIconSize(QSize(16, 16))
332
+ widget.export_button.setVisible(False)
333
+ widget.export_button.clicked.connect(lambda: export_button_clicked(widget))
334
+ up_line.layout().addWidget(widget.export_button)
335
+
336
+ widget.remove_button = QPushButton()
337
+ widget.remove_button.setObjectName('left_panel_speakers_panel_content_item_remove_button')
338
+ widget.remove_button.setFixedSize(24, 24)
339
+ widget.remove_button.setIconSize(QSize(16, 16))
340
+ widget.remove_button.setVisible(False)
341
+ widget.remove_button.clicked.connect(lambda: remove_button_clicked(widget))
342
+ up_line.layout().addWidget(widget.remove_button)
343
+
344
+ widget.layout().addWidget(up_line)
345
+
346
+ widget.dubbing_line = QWidget()
347
+ widget.dubbing_line.setLayout(QHBoxLayout())
348
+ widget.dubbing_line.layout().setContentsMargins(0, 0, 0, 0)
349
+ widget.dubbing_line.layout().setSpacing(0)
350
+ widget.layout().addWidget(widget.dubbing_line)
351
+
352
+ widget.dubbing_container = dubbing_container(parent=widget)
353
+ widget.dubbing_container.setProperty('speaker', widget.speaker_name)
354
+ widget.dubbing_container.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum)
355
+ widget.dubbing_line.layout().addWidget(widget.dubbing_container)
356
+
357
+ class small_timeline(QLabel):
358
+ def __init__(widget, speaker_name=False, timeline=[], duration=30):
359
+ super().__init__()
360
+ widget.timeline = timeline
361
+ widget.duration = duration
362
+ widget.speaker_name = speaker_name
363
+
364
+ def paintEvent(widget, event):
365
+ if widget.speaker_name:
366
+ painter = QPainter(widget)
367
+ painter.setRenderHint(QPainter.Antialiasing)
368
+ painter.setPen(Qt.NoPen)
369
+ painter.setBrush(QColor(session.SPEAKERS[widget.speaker_name].get('color', '#b8cee0')))
370
+ painter.setOpacity(0.5)
371
+ for segment in widget.timeline:
372
+ start_x = (segment[0] / widget.duration) * widget.width()
373
+ end_x = (segment[1] / widget.duration) * widget.width()
374
+ painter.drawRect(start_x, 0, end_x - start_x, widget.height())
375
+
376
+ return super().paintEvent(event)
377
+
378
+ def update(widget, timeline=False, duration=False, color=False):
379
+ if timeline:
380
+ widget.timeline = timeline
381
+ if duration:
382
+ widget.duration = duration
383
+ if color:
384
+ widget.color = color
385
+
386
+ widget.bottom_line = small_timeline(speaker_name=widget.speaker_name)
387
+ widget.bottom_line.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
388
+ widget.bottom_line.setObjectName('left_panel_speakers_panel_content_item_bottom_line')
389
+ widget.bottom_line.setFixedHeight(5)
390
+ widget.layout().addWidget(widget.bottom_line)
391
+
392
+ widget.update()
393
+ widget.translate()
394
+
395
+ def enterEvent(widget, event):
396
+ widget.rename_button.setVisible(True)
397
+ widget.change_color_button.setVisible(True)
398
+ widget.change_image_button.setVisible(True)
399
+ widget.export_button.setVisible(False)
400
+ widget.remove_button.setVisible(True)
401
+ event.accept()
402
+
403
+ def leaveEvent(widget, event):
404
+ widget.rename_button.setVisible(False)
405
+ widget.change_color_button.setVisible(False)
406
+ widget.change_image_button.setVisible(False)
407
+ widget.export_button.setVisible(False)
408
+ widget.remove_button.setVisible(False)
409
+ event.accept()
410
+
411
+ def mousePressEvent(widget, event):
412
+ for sib_widget in widget.parent().children():
413
+ if sib_widget.objectName() == 'left_panel_speakers_panel_content_item':
414
+ if sib_widget.property('speaker_name') == widget.property('speaker_name'):
415
+ sib_widget.setProperty('selected', 'true')
416
+ else:
417
+ sib_widget.setProperty('selected', 'false')
418
+ sib_widget.style().unpolish(sib_widget)
419
+ sib_widget.style().polish(sib_widget)
420
+
421
+ return super().mousePressEvent(event)
422
+
423
+ def translate(widget):
424
+ widget.dubbing_container.combobox.setLabel(_('subtitles_panel_widget_dubbing.dubbing_engine'))
425
+ widget.dubbing_container.combobox.combobox.setPlaceholderText(_('subtitles_panel_widget_dubbing.no_engine_selected'))
426
+ for i in range(widget.dubbing_container.content.count()):
427
+ w = widget.dubbing_container.content.widget(i)
428
+ w.translate()
429
+
430
+ def update(widget):
431
+ widget.speaker_icon.setPixmap(
432
+ QPixmap(str(session.PATH_SUBTITLD_GRAPHICS / 'left_panel_speakers.svg')) if not widget.speaker_data.get('image', None)
433
+ else QPixmap.fromImage(widget.speaker_data['image']).scaled(36, 36)
434
+ )
435
+ widget.bottom_line.update(
436
+ timeline=[[segment['start'], segment['end']] for segment in session.SUBTITLE['segments'] if segment.get('speaker', 'A') == widget.speaker_name],
437
+ duration=session.VIDEO.get('duration', 60)
438
+ )
439
+ speaker_time = round(sum([segment['end'] - segment['start'] for segment in session.SUBTITLE['segments'] if segment.get('speaker', 'A') == widget.speaker_name]), 3)
440
+ total_speaking_time = sum([segment['end'] - segment['start'] for segment in session.SUBTITLE['segments']])
441
+ percentage = int(round((speaker_time / total_speaking_time) * 100, 0))
442
+ widget.name_label.setText('<b>' + widget.speaker_name + '</b><br><small>' + f'{speaker_time} sec. ({percentage}%)' + '</small>')
443
+
444
+ widget.dubbing_line.setVisible(session.CONFIG['dubbing'].get('enabled', False))
445
+
446
+ if session.CONFIG['dubbing'].get('enabled', False):
447
+ for i in range(widget.dubbing_container.content.count()):
448
+ w = widget.dubbing_container.content.widget(i)
449
+ w.update()
450
+
451
+ def update_dubbing_options(widget, options):
452
+ widget.dubbing_container.combobox.clear()
453
+ widget.dubbing_container.combobox.addItems(list(options.keys()))
454
+
455
+ while widget.dubbing_container.content.count():
456
+ w = widget.dubbing_container.content.widget(0)
457
+ widget.dubbing_container.content.removeWidget(w)
458
+ w.deleteLater()
459
+
460
+ for option, engine in options.items():
461
+ new_dubbing_panel = engine.speaker_panel()
462
+ new_dubbing_panel.setProperty('speaker', widget.speaker_name)
463
+ widget.dubbing_container.content.addWidget(new_dubbing_panel)
464
+ new_dubbing_panel.update()
465
+
466
+ saved_engine = session.SPEAKERS.get(widget.speaker_name, {}).get('dubbing', {}).get('engine')
467
+ if saved_engine and saved_engine in options:
468
+ widget.dubbing_container.combobox.setCurrentText(saved_engine)
469
+ else:
470
+ widget.dubbing_container.combobox.combobox.setCurrentIndex(-1)
471
+
472
+ widget.dubbing_container.update()
473
+ widget.update()
474
+ widget.translate()
475
+
476
+ class new_speaker_name_dialog(utils.SimpleDialog):
477
+ def __init__(self, parent=None, title=''):
478
+ super().__init__(parent, title)
479
+
480
+ self.input_line = QWidget()
481
+ self.input_line.setLayout(QHBoxLayout())
482
+ self.input_line.layout().setContentsMargins(0, 0, 0, 0)
483
+
484
+ self.input_label = QLabel('Please enter your name:')
485
+ self.input_line.layout().addWidget(self.input_label)
486
+
487
+ self.input = QLineEdit()
488
+ self.input_line.layout().addWidget(self.input)
489
+
490
+ self.content.layout().addWidget(self.input_line)
491
+
492
+
493
+ def exec_and_get_values(self):
494
+ if self.exec() == QDialog.Accepted:
495
+ return self.input.text()
496
+ return None
497
+
498
+
499
+ class rename_speaker_name_dialog(utils.SimpleDialog):
500
+ def __init__(self, parent=None, title=''):
501
+ super().__init__(parent, title)
502
+
503
+ self.input_line = QWidget()
504
+ self.input_line.setLayout(QHBoxLayout())
505
+ self.input_line.layout().setContentsMargins(0, 0, 0, 0)
506
+
507
+ self.input_label = QLabel('Please enter your name:')
508
+ self.input_line.layout().addWidget(self.input_label)
509
+
510
+ self.input = QLineEdit()
511
+ self.input_line.layout().addWidget(self.input)
512
+
513
+ self.content.layout().addWidget(self.input_line)
514
+
515
+
516
+ def exec_and_get_values(self):
517
+ if self.exec() == QDialog.Accepted:
518
+ return self.input.text()
519
+ return None
520
+
521
+
522
+ class remove_speaker_name_dialog(utils.SimpleDialog):
523
+ def __init__(self, parent=None, title=''):
524
+ super().__init__(parent, title)
525
+
526
+ self.input_line = QWidget()
527
+ self.input_line.setLayout(QHBoxLayout())
528
+ self.input_line.layout().setContentsMargins(0, 0, 0, 0)
529
+
530
+ self.input_label = QLabel('Please select the speaker to replace with the removed speaker:')
531
+ self.input_line.layout().addWidget(self.input_label)
532
+
533
+ self.select = QComboBox()
534
+ self.input_line.layout().addWidget(self.select)
535
+
536
+ self.content.layout().addWidget(self.input_line)
537
+
538
+
539
+ def exec_and_get_values(self):
540
+ if self.exec() == QDialog.Accepted:
541
+ return self.select.currentText()
542
+ return None
543
+
544
+
545
+ def load(self):
546
+ tab_name = 'speakers'
547
+
548
+ left_panel_speakers_panel = left_panel.left_panel(
549
+ parent=self,
550
+ tab_name=tab_name,
551
+ update_callback=update,
552
+ translate_callback=translate
553
+ )
554
+
555
+ left_panel_speakers_panel_scroll = QScrollArea()
556
+ left_panel_speakers_panel_scroll.setObjectName('left_panel_speakers_panel_scroll')
557
+ left_panel_speakers_panel_scroll.setWidgetResizable(True)
558
+ left_panel_speakers_panel_scroll.setFrameShape(QScrollArea.NoFrame)
559
+ left_panel_speakers_panel.layout().addWidget(left_panel_speakers_panel_scroll)
560
+
561
+ left_panel_speakers_panel_content = QWidget()
562
+ left_panel_speakers_panel_content.setProperty('class', 'transparent_panel')
563
+ left_panel_speakers_panel_content.setObjectName('left_panel_speakers_panel_content')
564
+ left_panel_speakers_panel_content.setLayout(QVBoxLayout())
565
+ left_panel_speakers_panel_content.layout().setContentsMargins(0, 0, 0, 0)
566
+ left_panel_speakers_panel_content.layout().setSpacing(10)
567
+ left_panel_speakers_panel_scroll.setWidget(left_panel_speakers_panel_content)
568
+
569
+ self.left_panel_speakers_list = QWidget()
570
+ self.left_panel_speakers_list.setObjectName('left_panel_speakers_list')
571
+ self.left_panel_speakers_list.setLayout(QVBoxLayout())
572
+ self.left_panel_speakers_list.layout().setContentsMargins(0, 0, 0, 0)
573
+ left_panel_speakers_panel_content.layout().addWidget(self.left_panel_speakers_list)
574
+
575
+ self.left_panel_speakers_add_button = QPushButton()
576
+ self.left_panel_speakers_add_button.clicked.connect(lambda: left_panel_speakers_add_speaker_button_clicked(self))
577
+ left_panel_speakers_panel_content.layout().addWidget(self.left_panel_speakers_add_button, 0, Qt.AlignmentFlag.AlignRight)
578
+
579
+ left_panel_speakers_panel_content.layout().addStretch()
580
+
581
+ def handle_face_result(data):
582
+ if data["image"] is not None:
583
+ if not data["name"] in session.SPEAKERS:
584
+ session.SPEAKERS[data["name"]] = {}
585
+ session.SPEAKERS[data["name"]]['image'] = data["image"]
586
+ update_speakers_list(self)
587
+
588
+ self.left_panel_speakers_image_test_thread = FaceExtractorThread(parent=self)
589
+ self.left_panel_speakers_image_test_thread.result.connect(handle_face_result)
590
+
591
+ self.left_panel_speakers_new_name_dialog = new_speaker_name_dialog(self, 'New speaker')
592
+
593
+ self.left_panel_speakers_rename_dialog = rename_speaker_name_dialog(parent=self)
594
+ self.left_panel_speakers_remove_dialog = remove_speaker_name_dialog(parent=self)
595
+
596
+ self.left_panel_speakers_list_of_available_dubbing_engine = {}
597
+
598
+ update(self)
599
+
600
+
601
+ def change_color_button_clicked(widget):
602
+ color_dialog = QColorDialog(session.SPEAKERS[widget.speaker_name]['color'], widget)
603
+ color = color_dialog.getColor()
604
+ if color.isValid():
605
+ session.SPEAKERS[widget.speaker_name]['color'] = f'{color.name()}'
606
+ widget.window().timeline_widget.update()
607
+ session.set_unsaved()
608
+
609
+
610
+ def change_image_button_clicked(widget):
611
+ window = widget.window()
612
+ preview = getattr(window, 'preview_panel_player', None)
613
+ if preview is None or not hasattr(preview, 'start_face_selection'):
614
+ return
615
+ preview.start_face_selection(widget.speaker_name)
616
+
617
+
618
+ def export_button_clicked(widget):
619
+ pass
620
+
621
+
622
+ def remove_button_clicked(widget):
623
+ if len(session.SPEAKERS) == 2:
624
+ remove_alert = utils.SimpleDialog(widget.window(), _('subtitles_panel_widget_speakers.remove_speaker'))
625
+ remove_alert.content.layout().addWidget(QLabel(_('subtitles_panel_widget_speakers.sure_to_remove_speaker')))
626
+ result = remove_alert.exec()
627
+ if result:
628
+ last_speaker = [speaker_name for speaker_name in session.SPEAKERS.keys() if speaker_name != widget.speaker_name][0]
629
+ for segment in session.SUBTITLE['segments']:
630
+ if segment['speaker'] == widget.speaker_name:
631
+ segment['speaker'] = last_speaker
632
+ del session.SPEAKERS[widget.speaker_name]
633
+ update_speakers_list(widget.window())
634
+
635
+ elif len(session.SPEAKERS) < 2:
636
+ no_remove_alert = utils.SimpleDialog(widget.window(), _('subtitles_panel_widget_speakers.remove_speaker'))
637
+ no_remove_alert.content.layout().addWidget(QLabel(_('subtitles_panel_widget_speakers.cannot_remove_last_speaker')))
638
+ no_remove_alert.reject_button.setVisible(False)
639
+ no_remove_alert.exec()
640
+ else:
641
+ widget.window().left_panel_speakers_remove_dialog.set_title(_('subtitles_panel_widget_speakers.remove_speaker'))
642
+ widget.window().left_panel_speakers_remove_dialog.select.clear()
643
+ widget.window().left_panel_speakers_remove_dialog.select.addItems([speaker_name for speaker_name in session.SPEAKERS.keys() if speaker_name != widget.speaker_name])
644
+
645
+ value = widget.window().left_panel_speakers_remove_dialog.exec_and_get_values()
646
+
647
+ if value:
648
+ for segment in session.SUBTITLE['segments']:
649
+ if segment['speaker'] == widget.speaker_name:
650
+ segment['speaker'] = value
651
+ del session.SPEAKERS[widget.speaker_name]
652
+ update_speakers_list(widget.window())
653
+
654
+
655
+ def rename_button_clicked(widget):
656
+ widget.window().left_panel_speakers_rename_dialog.set_title(_('subtitles_panel_widget_speakers.rename_speaker'))
657
+ widget.window().left_panel_speakers_rename_dialog.input_label.setText(_('subtitles_panel_widget_speakers.enter_speaker_name'))
658
+ widget.window().left_panel_speakers_rename_dialog.input.setText(widget.speaker_name)
659
+ widget.window().left_panel_speakers_rename_dialog.input.selectAll()
660
+
661
+ value = widget.window().left_panel_speakers_rename_dialog.exec_and_get_values()
662
+
663
+ if value:
664
+ new_name = value.strip()
665
+ else:
666
+ return
667
+
668
+ old_speaker = session.SPEAKERS[widget.speaker_name]
669
+ session.SPEAKERS[new_name] = old_speaker
670
+
671
+ for segment in session.SUBTITLE['segments']:
672
+ if segment['speaker'] == widget.speaker_name:
673
+ segment['speaker'] = new_name
674
+
675
+ del session.SPEAKERS[widget.speaker_name]
676
+
677
+ update_speakers_list(widget.window())
678
+
679
+
680
+ def update_speakers_list(self):
681
+ while self.left_panel_speakers_list.layout().count():
682
+ item = self.left_panel_speakers_list.layout().takeAt(0)
683
+ widget = item.widget()
684
+ if widget is not None:
685
+ widget.setParent(None)
686
+ widget.deleteLater()
687
+ continue
688
+
689
+ for speaker_name, speaker_data in session.SPEAKERS.items():
690
+ if not speaker_data.get('color', False):
691
+ gen = AutoHex()
692
+ speaker_data['color'] = f'{gen.gen(speaker_name)}'
693
+
694
+ widget = speakers_list_item(speaker_name, speaker_data)
695
+
696
+ if not speaker_data.get('image', None) and not self.left_panel_speakers_image_test_thread.isRunning():
697
+ self.left_panel_speakers_image_test_thread.name = speaker_name
698
+ self.left_panel_speakers_image_test_thread.start()
699
+
700
+ self.left_panel_speakers_list.layout().addWidget(widget)
701
+
702
+ widget.update_dubbing_options(self.left_panel_speakers_list_of_available_dubbing_engine)
703
+
704
+
705
+ def update(self):
706
+ update_speakers_list(self)
707
+
708
+
709
+ def left_panel_speakers_remove_speaker_button_clicked(self):
710
+ selected_items = self.left_panel_speakers_list.selectedItems()
711
+ if not selected_items:
712
+ return
713
+ for item in selected_items:
714
+ speaker_name = item.text()
715
+ if any(subtitle.get('speaker', 'A') == speaker_name for subtitle in session.SUBTITLE['segments']):
716
+ continue
717
+
718
+
719
+ def left_panel_speakers_add_speaker_button_clicked(self):
720
+ new_name = None
721
+ value = self.left_panel_speakers_new_name_dialog.exec_and_get_values()
722
+
723
+ if value:
724
+ new_name = value.strip()
725
+ else:
726
+ pass
727
+ subtitles_names = [subtitle.get('speaker', 'A') for subtitle in session.SUBTITLE['segments']]
728
+
729
+ if new_name and new_name not in session.SPEAKERS and not new_name in subtitles_names:
730
+ session.SPEAKERS.append(new_name)
731
+ update_speakers_list(self)
732
+
733
+
734
+ def translate(self):
735
+ self.left_panel_speakers_add_button.setText(_('subtitles_panel_widget_speakers.add_speaker'))
736
+ self.left_panel_speakers_new_name_dialog.set_title(_('subtitles_panel_widget_speakers.new_speaker'))
737
+ self.left_panel_speakers_new_name_dialog.input_label.setText(_('subtitles_panel_widget_speakers.enter_speaker_name'))