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,329 @@
1
+ import pathlib
2
+ import os
3
+ import datetime
4
+
5
+ from PySide6.QtWidgets import QHBoxLayout, QPushButton, QWidget, QSizePolicy, QLabel, QSpacerItem, QGraphicsOpacityEffect, QFileDialog, QDialog, QCheckBox, QRadioButton, QButtonGroup, QApplication
6
+ from PySide6.QtCore import Qt, QPropertyAnimation, QEasingCurve, QSize, QTimer
7
+
8
+ from subtitld.interface import utils
9
+ from subtitld.interface.translation import _
10
+
11
+ from subtitld.modules import session
12
+ from subtitld.modules import file_io
13
+ from subtitld.modules import utils as modules_utils
14
+
15
+
16
+ def load(self):
17
+ self.titleBar_left_container = QWidget(self)
18
+ self.titleBar_left_container.setObjectName('titleBar_left_container')
19
+ self.titleBar_left_container.setLayout(QHBoxLayout())
20
+ self.titleBar_left_container.layout().setContentsMargins(0, 0, 0, 0)
21
+ self.titleBar_left_container.setSizePolicy(QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding))
22
+ self.titleBar_left_container.setAttribute(Qt.WA_StyledBackground, True)
23
+ self.titleBar_left_container.opacity = QGraphicsOpacityEffect()
24
+ self.titleBar_left_container.opacity.setOpacity(0)
25
+ self.titleBar_left_container.setGraphicsEffect(self.titleBar_left_container.opacity)
26
+ self.titleBar_left_container.animation = QPropertyAnimation(self.titleBar_left_container, b'pos')
27
+ self.titleBar_left_container.animation.setEasingCurve(QEasingCurve.OutQuint)
28
+
29
+ self.titleBar.layout().setAlignment(Qt.AlignTop)
30
+ self.titleBar.setFixedHeight(50)
31
+ self.titleBar.setObjectName('titleBar')
32
+ self.titleBar.setAttribute(Qt.WA_StyledBackground, True)
33
+
34
+ # Rearrange icons to top
35
+ self.titleBar.layout().setAlignment(self.titleBar.minBtn, Qt.AlignTop)
36
+ self.titleBar.layout().setAlignment(self.titleBar.maxBtn, Qt.AlignTop)
37
+ self.titleBar.layout().setAlignment(self.titleBar.closeBtn, Qt.AlignTop)
38
+
39
+ # Remove spacer
40
+ for i in range(self.titleBar.layout().count()):
41
+ item = self.titleBar.layout().itemAt(i)
42
+ if isinstance(item, QSpacerItem):
43
+ self.titleBar.layout().removeItem(item)
44
+
45
+ # Add our bar
46
+ self.titleBar.layout().insertWidget(0, self.titleBar_left_container)
47
+
48
+ class titleBar_left_save_button(QPushButton):
49
+ def __init__(widget, parent=None):
50
+ super().__init__(parent)
51
+ widget.key_modifiers = []
52
+
53
+ def keyPressEvent(widget, event):
54
+ widget.key_modifiers = event.modifiers()
55
+ event.accept()
56
+
57
+ def keyReleaseEvent(widget, event):
58
+ widget.key_modifiers = []
59
+ event.accept()
60
+
61
+ def update_state(widget):
62
+ widget.setProperty(
63
+ 'class',
64
+ 'unsaved' if session.UNSAVED else 'saved'
65
+ )
66
+ widget.style().unpolish(widget)
67
+ widget.style().polish(widget)
68
+ super().update()
69
+
70
+ self.titleBar_left_save_button = titleBar_left_save_button(self)
71
+ self.titleBar_left_save_button.setObjectName('titleBar_left_save_button')
72
+ self.titleBar_left_save_button.setProperty('class', 'saved')
73
+ self.titleBar_left_save_button.setIconSize(QSize(16, 16))
74
+ self.titleBar_left_save_button.setSizePolicy(QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum))
75
+ self.titleBar_left_save_button.clicked.connect(lambda: toppanel_save_button_clicked(self))
76
+ session._unsaved_change_callbacks.append(self.titleBar_left_save_button.update_state)
77
+ self.titleBar_left_container.layout().addWidget(self.titleBar_left_save_button, alignment=Qt.AlignLeft | Qt.AlignVCenter)
78
+
79
+ self.titleBar_left_export_button = QPushButton(self)
80
+ self.titleBar_left_export_button.setObjectName('titleBar_left_export_button')
81
+ self.titleBar_left_export_button.setIconSize(QSize(16, 16))
82
+ self.titleBar_left_export_button.setSizePolicy(QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum))
83
+ self.titleBar_left_export_button.clicked.connect(lambda: toppanel_export_button_clicked(self))
84
+ self.titleBar_left_container.layout().addWidget(self.titleBar_left_export_button, alignment=Qt.AlignLeft | Qt.AlignVCenter)
85
+
86
+ self.titleBar_left_information_container = QWidget(self)
87
+ self.titleBar_left_information_container.setObjectName('titleBar_left_information_container')
88
+ self.titleBar_left_information_container.setSizePolicy(QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding))
89
+ self.titleBar_left_information_container.setLayout(QHBoxLayout())
90
+ self.titleBar_left_information_container.layout().setContentsMargins(0, 0, 0, 0)
91
+ self.titleBar_left_container.layout().addWidget(self.titleBar_left_information_container)
92
+
93
+ self.titleBar_left_information_label = QLabel(self)
94
+ self.titleBar_left_information_label.setObjectName('titleBar_left_information_label')
95
+ self.titleBar_left_information_label.setSizePolicy(QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding))
96
+ self.titleBar_left_information_container.layout().addWidget(self.titleBar_left_information_label, alignment=Qt.AlignLeft | Qt.AlignVCenter)
97
+ self.titleBar_left_information_label.setText('<b>filename.usf</b><br /><small>subtitle format</small>')
98
+
99
+ self.tilteBar_subtitld_label = QLabel(self)
100
+ self.tilteBar_subtitld_label.setObjectName('tilteBar_subtitld_label')
101
+ self.tilteBar_subtitld_label.setAttribute(Qt.WA_StyledBackground, True)
102
+ self.titleBar.layout().insertWidget(1, self.tilteBar_subtitld_label, alignment=Qt.AlignRight | Qt.AlignTop)
103
+ self.tilteBar_subtitld_label.setText('<b>SUBTITLD</b> v25.08.01.01')
104
+
105
+
106
+ def update(self):
107
+ filename = _('top_bar.untitled_file')
108
+ filepath = pathlib.Path(session.VIDEO.get('filepath', '')).parent
109
+ if session.SUBTITLE.get('filepath', ''):
110
+ filename = pathlib.Path(session.SUBTITLE.get('filepath', '')).name
111
+ filepath = pathlib.Path(session.SUBTITLE.get('filepath', '')).parent
112
+
113
+ self.titleBar_left_information_label.setText(f'<b>{filename}</b><br /><small>{filepath}</small>')
114
+
115
+
116
+ def show(self):
117
+ update(self)
118
+ QTimer().singleShot(200, lambda: self.titleBar_left_container.opacity.setOpacity(1.0))
119
+ utils.animate_element(self.titleBar_left_container.animation, duration=1000, effect='slide_from_left')
120
+
121
+
122
+ def translate(self):
123
+ self.titleBar_left_save_button.setToolTip(_('top_bar.save'))
124
+ self.titleBar_left_export_button.setToolTip(_('top_bar.export'))
125
+
126
+
127
+ def toppanel_save_button_clicked(self):
128
+ """Save the project as USFX. Shift+click forces Save As. Ctrl+click
129
+ forwards to the Export button for backwards compatibility."""
130
+ live_modifiers = QApplication.keyboardModifiers()
131
+ button_modifiers = self.titleBar_left_save_button.key_modifiers or Qt.KeyboardModifier.NoModifier
132
+ ctrl_pressed = bool(live_modifiers & Qt.ControlModifier) or bool(button_modifiers & Qt.ControlModifier)
133
+ shift_pressed = bool(live_modifiers & Qt.ShiftModifier) or bool(button_modifiers & Qt.ShiftModifier)
134
+
135
+ if ctrl_pressed:
136
+ toppanel_export_button_clicked(self)
137
+ return
138
+
139
+ usfx_filter = session.LIST_OF_SUPPORTED_SUBTITLE_EXTENSIONS['USFX']['description'] + ' (*.usfx)'
140
+
141
+ current_filepath = session.SUBTITLE.get('filepath') or ''
142
+ needs_dialog = (
143
+ shift_pressed
144
+ or not current_filepath
145
+ or not current_filepath.lower().endswith('.usfx')
146
+ )
147
+
148
+ if needs_dialog:
149
+ suggested_dir = os.path.dirname(current_filepath) if current_filepath else os.path.dirname(session.VIDEO.get('filepath', ''))
150
+ base_source = current_filepath or session.VIDEO.get('filepath', '')
151
+ suggested_filename = (os.path.splitext(os.path.basename(base_source))[0] or 'subtitle') + '.usfx'
152
+ suggested = os.path.join(suggested_dir, suggested_filename)
153
+ title = 'Save subtitle as' if shift_pressed else 'Save subtitle'
154
+
155
+ filedialog = QFileDialog.getSaveFileName(parent=self, caption=title, dir=suggested, filter=usfx_filter)
156
+ if not filedialog[0]:
157
+ return
158
+ filepath = filedialog[0]
159
+ if not filepath.lower().endswith('.usfx'):
160
+ filepath += '.usfx'
161
+ session.SUBTITLE['filepath'] = filepath
162
+
163
+ session.set_unsaved(False)
164
+ session.FORMAT['format'] = 'USFX'
165
+ file_io.save_file(session.SUBTITLE['filepath'], 'USFX', session.CONFIG['selected_language'])
166
+
167
+
168
+ def toppanel_export_button_clicked(self):
169
+ """Export to any format other than the project's native USFX."""
170
+ filter_entries = []
171
+ for fmt, info in session.LIST_OF_SUPPORTED_SUBTITLE_EXTENSIONS.items():
172
+ if fmt == 'USFX':
173
+ continue
174
+ filter_entries.append(info['description'] + ' (' + ' '.join(f'*.{e}' for e in info['extensions']) + ')')
175
+ for fmt, info in session.LIST_OF_SUPPORTED_AUDIO_EXPORT_EXTENSIONS.items():
176
+ filter_entries.append(info['description'] + ' (' + ' '.join(f'*.{e}' for e in info['extensions']) + ')')
177
+ export_filter = ';;'.join(filter_entries)
178
+
179
+ suggested_dir = os.path.dirname(session.SUBTITLE.get('filepath', '') or session.VIDEO.get('filepath', ''))
180
+ base = os.path.basename(session.SUBTITLE.get('filepath', '') or session.VIDEO.get('filepath', '')) or 'subtitle'
181
+ suggested = os.path.join(suggested_dir, os.path.splitext(base)[0] + '.srt')
182
+
183
+ filedialog = QFileDialog.getSaveFileName(parent=self, caption='Export as', dir=suggested, filter=export_filter)
184
+ if not filedialog[0] or not filedialog[1]:
185
+ return
186
+
187
+ filepath = filedialog[0]
188
+ selected_extensions = filedialog[1].rsplit('(', 1)[-1].split(')', 1)[0].replace('*.', '').split(' ')
189
+ if not filepath.rsplit('.', 1)[-1].lower() in selected_extensions:
190
+ filepath += f'.{selected_extensions[0]}'
191
+ selected_extension = selected_extensions[0]
192
+ else:
193
+ selected_extension = filepath.rsplit('.', 1)[-1].lower()
194
+
195
+ audio_format = None
196
+ for fmt, info in session.LIST_OF_SUPPORTED_AUDIO_EXPORT_EXTENSIONS.items():
197
+ if selected_extension in info['extensions']:
198
+ audio_format = fmt
199
+ break
200
+
201
+ if audio_format is not None:
202
+ from subtitld.modules import bounce as _bounce
203
+ has_background = _bounce._background_audio_path() is not None
204
+ has_vocals = _bounce._vocals_audio_path() is not None
205
+ audio_dialog = export_audio_dialog(parent=self, title=f'Export {audio_format}',
206
+ is_mp4=(audio_format == 'MP4'),
207
+ has_background=has_background,
208
+ has_vocals=has_vocals)
209
+ config = audio_dialog.exec_and_get_values()
210
+ if config:
211
+ engine = getattr(self.preview_panel_player, '_audio_device', None)
212
+ _bounce.bounce(filepath, audio_format, config['mode'],
213
+ audio_engine=engine,
214
+ include_background=config['include_background'])
215
+ return
216
+
217
+ selected_format = modules_utils.get_format_from_extension(selected_extension)
218
+
219
+ if selected_format == 'JSON' and 'options' not in session.FORMAT:
220
+ json_dialog = export_json_dialog(parent=self, title='JSON options')
221
+ config = json_dialog.exec_and_get_values()
222
+ if config:
223
+ session.FORMAT['options'] = config
224
+ elif selected_format == 'USF' and 'options' not in session.FORMAT:
225
+ usf_dialog = export_usf_dialog(parent=self, title='USF options')
226
+ config = usf_dialog.exec_and_get_values()
227
+ if config:
228
+ session.FORMAT['options'] = config
229
+
230
+ file_io.save_file(filepath, selected_format, session.CONFIG['selected_language'])
231
+
232
+
233
+ class export_json_dialog(utils.SimpleDialog):
234
+ def __init__(self, parent=None, title=''):
235
+ super().__init__(parent, title)
236
+
237
+ self.format_combobox = utils.LabeledComboBox()
238
+ self.format_combobox.setLabel(_('export_json_dialog.format'))
239
+ self.format_combobox.addItems(['Whisper', 'AD'])
240
+ self.format_combobox.setCurrentText('Whisper')
241
+ # self.format_combobox.activated.connect(lambda: self.format_combobox_activated())
242
+
243
+ self.content.layout().addWidget(self.format_combobox)
244
+
245
+ def exec_and_get_values(self):
246
+ if self.exec() == QDialog.Accepted:
247
+ return {
248
+ 'standard': self.format_combobox.currentText()
249
+ }
250
+ return None
251
+
252
+
253
+ class export_audio_dialog(utils.SimpleDialog):
254
+ def __init__(self, parent=None, title='', is_mp4=False, has_background=False, has_vocals=False):
255
+ super().__init__(parent, title)
256
+
257
+ self.mode_group = QButtonGroup(self)
258
+ self.mode_mixdown = QRadioButton(_('export_audio_dialog.mode_mixdown'))
259
+ self.mode_stems = QRadioButton(_('export_audio_dialog.mode_stems'))
260
+ self.mode_clips = QRadioButton(_('export_audio_dialog.mode_clips'))
261
+ self.mode_background_only = QRadioButton(_('export_audio_dialog.mode_background_only'))
262
+ self.mode_vocals_only = QRadioButton(_('export_audio_dialog.mode_vocals_only'))
263
+ self.mode_mixdown.setChecked(True)
264
+ for btn in (self.mode_mixdown, self.mode_stems, self.mode_clips,
265
+ self.mode_background_only, self.mode_vocals_only):
266
+ self.mode_group.addButton(btn)
267
+ self.content.layout().addWidget(btn)
268
+
269
+ if is_mp4:
270
+ self.mode_clips.setEnabled(False)
271
+
272
+ self.mode_background_only.setEnabled(has_background)
273
+ if not has_background:
274
+ self.mode_background_only.setToolTip(_('export_audio_dialog.background_unavailable'))
275
+
276
+ self.mode_vocals_only.setEnabled(has_vocals)
277
+ if not has_vocals:
278
+ self.mode_vocals_only.setToolTip(_('export_audio_dialog.vocals_unavailable'))
279
+
280
+ self.include_background_checkbox = QCheckBox(_('export_audio_dialog.include_background'))
281
+ self.include_background_checkbox.setEnabled(has_background)
282
+ if not has_background:
283
+ self.include_background_checkbox.setToolTip(_('export_audio_dialog.background_unavailable'))
284
+ self.content.layout().addWidget(self.include_background_checkbox)
285
+
286
+ # Hide the "include background" checkbox for the two pass-through modes
287
+ # since mixing doesn't make sense when exporting a single track.
288
+ def _refresh_checkbox_state():
289
+ is_passthrough = self.mode_background_only.isChecked() or self.mode_vocals_only.isChecked()
290
+ self.include_background_checkbox.setDisabled(is_passthrough or not has_background)
291
+ for btn in (self.mode_mixdown, self.mode_stems, self.mode_clips,
292
+ self.mode_background_only, self.mode_vocals_only):
293
+ btn.toggled.connect(_refresh_checkbox_state)
294
+
295
+ def exec_and_get_values(self):
296
+ if self.exec() == QDialog.Accepted:
297
+ mode = 'mixdown'
298
+ if self.mode_stems.isChecked():
299
+ mode = 'stems'
300
+ elif self.mode_clips.isChecked():
301
+ mode = 'clips'
302
+ elif self.mode_background_only.isChecked():
303
+ mode = 'background_only'
304
+ elif self.mode_vocals_only.isChecked():
305
+ mode = 'vocals_only'
306
+ return {
307
+ 'mode': mode,
308
+ 'include_background': self.include_background_checkbox.isChecked() and self.include_background_checkbox.isEnabled(),
309
+ }
310
+ return None
311
+
312
+
313
+ class export_usf_dialog(utils.SimpleDialog):
314
+ def __init__(self, parent=None, title=''):
315
+ super().__init__(parent, title)
316
+
317
+ self.embed_images_checkbox = QCheckBox(_('export_usf_dialog.embed_speaker_images'))
318
+ self.content.layout().addWidget(self.embed_images_checkbox)
319
+
320
+ self.embed_dubs_checkbox = QCheckBox(_('export_usf_dialog.embed_audio_clips'))
321
+ self.content.layout().addWidget(self.embed_dubs_checkbox)
322
+
323
+ def exec_and_get_values(self):
324
+ if self.exec() == QDialog.Accepted:
325
+ return {
326
+ 'embed_speaker_images': self.embed_images_checkbox.isChecked(),
327
+ 'embed_audio_clips': self.embed_dubs_checkbox.isChecked(),
328
+ }
329
+ return None
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import os
4
+ import i18n
5
+
6
+ from subtitld.modules.session import PATH_SUBTITLD
7
+
8
+ i18n.set('file_format', 'json')
9
+ i18n.load_path.append(os.path.join(PATH_SUBTITLD, 'locale'))
10
+ i18n.set('filename_format', '{locale}.{format}')
11
+ i18n.set('skip_locale_root_data', True)
12
+ i18n.set('fallback', 'en_US')
13
+
14
+
15
+ def _(text):
16
+ return i18n.t(text)
17
+
18
+
19
+ # LIST_OF_MONTHS = ['', _('January'), _('February'), _('March'), _('April'), _('May'), _('June'), _('July'), _('August'), _('September'), _('October'), _('November'), _('December')]
20
+ # LIST_OF_WEEKDAYS = [_('Monday'), _('Tuesday'), _('Wednesday'), _('Thursday'), _('Friday'), _('Saturday'), _('Sunday')]
21
+
22
+ # def translate_month_names():
23
+ # global LIST_OF_MONTHS
24
+ # LIST_OF_MONTHS = []
25
+ # for item in ['', _('January'), _('February'), _('March'), _('April'), _('May'), _('June'), _('July'), _('August'), _('September'), _('October'), _('November'), _('December')]:
26
+ # LIST_OF_MONTHS.append(_(item))
27
+
28
+
29
+ def load_translation_files():
30
+ for lp in i18n.load_path:
31
+ for f in os.listdir(lp):
32
+ path = os.path.join(lp, f)
33
+ if os.path.isfile(path) and path.endswith(i18n.config.get('file_format')):
34
+ locale = f.split(i18n.config.get('namespace_delimiter'))[0]
35
+ if '{locale}' in i18n.config.get('filename_format') and locale not in i18n.config.get('available_locales'):
36
+ i18n.resource_loader.load_translation_file(f, lp, locale)
37
+
38
+
39
+ # def get_list_of_months(language):
40
+ # month_list = ['']
41
+ # for month in LIST_OF_MONTHS:
42
+ # if month:
43
+ # month_list.append(i18n.t(month, locale=language))
44
+ # return month_list
45
+
46
+
47
+ # def get_list_of_weekdays(language):
48
+ # weekdays = []
49
+ # for month in LIST_OF_WEEKDAYS:
50
+ # if month:
51
+ # weekdays.append(i18n.t(month, locale=language))
52
+ # return weekdays
53
+
54
+
55
+ def set_language(language):
56
+ i18n.set('locale', language)
57
+
58
+
59
+ def get_language_pairs(language):
60
+ result = i18n.translations.container.get(language, {})
61
+ return result
62
+
63
+
64
+ def get_available_language_names():
65
+ final_dict = {}
66
+ for language in i18n.translations.container:
67
+ final_dict[language] = i18n.translations.container[language].get('language_name', '')
68
+ return final_dict
@@ -0,0 +1,286 @@
1
+ from PySide6.QtWidgets import QVBoxLayout, QWidget, QComboBox, QHBoxLayout, QDialog, QPushButton, QLabel, QLineEdit, QListWidgetItem, QSizePolicy
2
+ from PySide6.QtGui import QImage, QPixmap, QPainter, QBrush, QPen, QPainterPath, QColor
3
+ from PySide6.QtCore import QThread, Signal, Qt, QRect, QPoint, QSize
4
+
5
+
6
+ from subtitld.interface.translation import _
7
+
8
+
9
+ def animate_element(animation, duration=1000, effect='fadein'):
10
+ widget = animation.targetObject()
11
+ animation.setDuration(duration)
12
+ if effect.startswith('slide_'):
13
+ original_position = widget.pos()
14
+ if effect == 'slide_from_left':
15
+ animation.setStartValue(QPoint(-widget.parent().width(), 0))
16
+ animation.setEndValue(original_position)
17
+ elif effect == 'slide_from_right':
18
+ animation.setStartValue(QPoint(widget.parent().width(), 0))
19
+ animation.setEndValue(original_position)
20
+ elif effect == 'slide_from_bottom':
21
+ animation.setStartValue(QPoint(0, widget.parent().height()))
22
+ animation.setEndValue(original_position)
23
+ elif effect == 'slide_from_top':
24
+ animation.setStartValue(QPoint(0, -widget.parent().height()))
25
+ animation.setEndValue(original_position)
26
+ elif effect == 'slide_to_bottom':
27
+ animation.setStartValue(original_position)
28
+ animation.setEndValue(QPoint(0, widget.parent().height()))
29
+ elif effect == 'fadein':
30
+ animation.setStartValue(widget.opacity() if hasattr(widget, 'opacity') else 0)
31
+ animation.setEndValue(1)
32
+ elif effect == 'fadeout':
33
+ animation.setStartValue(widget.opacity() if hasattr(widget, 'opacity') else 1)
34
+ animation.setEndValue(0)
35
+ animation.start()
36
+
37
+ from datetime import datetime, timedelta
38
+
39
+ def friendly_time(dt):
40
+ """
41
+ Returns a human-friendly string showing the time difference between `dt` and now,
42
+ using `_()` for translatable strings.
43
+ """
44
+ now = datetime.now()
45
+
46
+ # Convert timestamp to datetime
47
+ if isinstance(dt, (int, float)):
48
+ dt = datetime.fromtimestamp(dt)
49
+
50
+ delta = now - dt
51
+ seconds = int(delta.total_seconds())
52
+
53
+ if seconds < 0: # Future
54
+ seconds = abs(seconds)
55
+ if seconds < 60:
56
+ return _("in a few seconds") if seconds < 5 else _("in {seconds} seconds").format(seconds=seconds)
57
+ elif seconds < 3600:
58
+ minutes = seconds // 60
59
+ return _("in {minutes} minute{plural}").format(minutes=minutes, plural='' if minutes == 1 else 's')
60
+ elif seconds < 86400:
61
+ hours = seconds // 3600
62
+ return _("in {hours} hour{plural}").format(hours=hours, plural='' if hours == 1 else 's')
63
+ elif seconds < 604800:
64
+ days = seconds // 86400
65
+ return _("in {days} day{plural}").format(days=days, plural='' if days == 1 else 's')
66
+ elif seconds < 2419200:
67
+ weeks = seconds // 604800
68
+ return _("in {weeks} week{plural}").format(weeks=weeks, plural='' if weeks == 1 else 's')
69
+ else:
70
+ months = seconds // 2419200
71
+ return _("in {months} month{plural}").format(months=months, plural='' if months == 1 else 's')
72
+ else: # Past
73
+ if seconds < 60:
74
+ return _("just now") if seconds < 5 else _("{seconds} seconds ago").format(seconds=seconds)
75
+ elif seconds < 3600:
76
+ minutes = seconds // 60
77
+ return _("{minutes} minute{plural} ago").format(minutes=minutes, plural='' if minutes == 1 else 's')
78
+ elif seconds < 86400:
79
+ hours = seconds // 3600
80
+ return _("{hours} hour{plural} ago").format(hours=hours, plural='' if hours == 1 else 's')
81
+ elif seconds < 604800:
82
+ days = seconds // 86400
83
+ return _("{days} day{plural} ago").format(days=days, plural='' if days == 1 else 's')
84
+ elif seconds < 2419200:
85
+ weeks = seconds // 604800
86
+ return _("{weeks} week{plural} ago").format(weeks=weeks, plural='' if weeks == 1 else 's')
87
+ else:
88
+ months = seconds // 2419200
89
+ return _("{months} month{plural} ago").format(months=months, plural='' if months == 1 else 's')
90
+
91
+
92
+ class SimpleDialog(QDialog):
93
+ def __init__(self, parent=None, title='', *args, **kwargs):
94
+ super().__init__(parent, *args, **kwargs)
95
+ self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint)
96
+ self.setAttribute(Qt.WA_TranslucentBackground)
97
+ self.setLayout(QVBoxLayout())
98
+ self.layout().setContentsMargins(0, 0, 0, 0)
99
+ self.layout().setSpacing(0)
100
+ self.setMinimumWidth(400)
101
+
102
+ self.title_line = QWidget()
103
+ self.title_line.setObjectName('dialog_title')
104
+ self.title_line.setLayout(QHBoxLayout())
105
+ self.title_line.layout().setContentsMargins(10, 0, 0, 1)
106
+
107
+ self.title_line.label = QLabel(title)
108
+ self.title_line.label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum)
109
+ self.title_line.layout().addWidget(self.title_line.label)
110
+
111
+ close_button = QPushButton()
112
+ close_button.setFixedSize(QSize(32, 32))
113
+ close_button.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
114
+ close_button.setObjectName('dialog_close_button')
115
+ close_button.clicked.connect(lambda: self.reject())
116
+ self.title_line.layout().addWidget(close_button)
117
+
118
+ self.layout().addWidget(self.title_line)
119
+
120
+ # Enable mouse tracking for dragging functionality
121
+ self.title_line.setMouseTracking(True)
122
+ self.title_line.mousePressEvent = self.title_mouse_press_event
123
+ self.title_line.mouseMoveEvent = self.title_mouse_move_event
124
+ self.title_line.mouseReleaseEvent = self.title_mouse_release_event
125
+
126
+ self.content = QWidget()
127
+ self.content.setObjectName('dialog_content')
128
+ self.content.setLayout(QVBoxLayout())
129
+ self.content.layout().setContentsMargins(10, 10, 10, 10)
130
+ self.layout().addWidget(self.content)
131
+
132
+ bottom_line = QWidget()
133
+ bottom_line.setObjectName('dialog_bottom')
134
+ bottom_line.setLayout(QHBoxLayout())
135
+ bottom_line.layout().setContentsMargins(0, 1, 0, 0)
136
+ bottom_line.layout().setSpacing(0)
137
+
138
+ self.accept_button = QPushButton("OK")
139
+ self.accept_button.setProperty('class', 'accept_button')
140
+
141
+ self.reject_button = QPushButton("Cancel")
142
+ self.reject_button.setProperty('class', 'reject_button')
143
+
144
+ bottom_line.layout().addStretch()
145
+ bottom_line.layout().addWidget(self.reject_button)
146
+ bottom_line.layout().addWidget(self.accept_button)
147
+
148
+ self.accept_button.clicked.connect(self.accept)
149
+ self.reject_button.clicked.connect(self.reject)
150
+
151
+ self.layout().addWidget(bottom_line)
152
+
153
+ def title_mouse_press_event(self, event):
154
+ if event.button() == Qt.LeftButton:
155
+ self.drag_position = event.globalPos() - self.frameGeometry().topLeft()
156
+ event.accept()
157
+
158
+ def title_mouse_move_event(self, event):
159
+ if event.buttons() == Qt.LeftButton:
160
+ if hasattr(self, 'drag_position'):
161
+ new_pos = event.globalPos() - self.drag_position
162
+ self.move(new_pos)
163
+ event.accept()
164
+
165
+ def title_mouse_release_event(self, event):
166
+ if hasattr(self, 'drag_position'):
167
+ delattr(self, 'drag_position')
168
+
169
+ def set_title(self, title):
170
+ self.title_line.label.setText(title)
171
+
172
+ def accept(self):
173
+ super().accept()
174
+
175
+
176
+ class LabeledComboBox(QWidget):
177
+ activated = Signal()
178
+ def __init__(widget, parent=None):
179
+ super().__init__(parent=parent)
180
+ widget.setObjectName('labeled_combobox')
181
+ widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum)
182
+ widget.setAttribute(Qt.WA_StyledBackground)
183
+
184
+ widget.setLayout(QVBoxLayout())
185
+ widget.layout().setContentsMargins(0, 0, 0, 0)
186
+ widget.layout().setSpacing(0)
187
+
188
+ widget.label = QLabel()
189
+ widget.layout().addWidget(widget.label, 1)
190
+
191
+ widget.bottom_line = QHBoxLayout()
192
+ widget.bottom_line.setContentsMargins(0, 0, 0, 0)
193
+ widget.bottom_line.setSpacing(0)
194
+ widget.layout().addLayout(widget.bottom_line)
195
+
196
+ widget.combobox = QComboBox()
197
+ widget.bottom_line.addWidget(widget.combobox, 1)
198
+
199
+ widget.combobox.activated.connect(widget.activated)
200
+
201
+ def clear(widget):
202
+ widget.combobox.clear()
203
+
204
+ def addItems(widget, items):
205
+ widget.combobox.addItems(items)
206
+
207
+ def addItem(widget, item):
208
+ widget.combobox.addItem(item)
209
+
210
+ def setLabel(widget, label):
211
+ widget.label.setText(label)
212
+
213
+ def setCurrentText(widget, text):
214
+ widget.combobox.setCurrentText(text)
215
+
216
+ def currentText(widget):
217
+ return widget.combobox.currentText()
218
+
219
+
220
+ class LabeledLabel(QWidget):
221
+ def __init__(widget, parent=None):
222
+ super().__init__(parent=parent)
223
+ widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum)
224
+ widget.setAttribute(Qt.WA_StyledBackground)
225
+
226
+ widget.setLayout(QVBoxLayout())
227
+ widget.layout().setContentsMargins(0, 0, 0, 0)
228
+ widget.layout().setSpacing(0)
229
+
230
+ widget.top_label = QLabel()
231
+ widget.top_label.setObjectName('top_label')
232
+ widget.layout().addWidget(widget.top_label, 1)
233
+
234
+ widget.bottom_line = QHBoxLayout()
235
+ widget.bottom_line.setContentsMargins(0, 0, 0, 0)
236
+ widget.bottom_line.setSpacing(0)
237
+ widget.layout().addLayout(widget.bottom_line)
238
+
239
+ widget.label = QLabel()
240
+ widget.label.setObjectName('label')
241
+ widget.label.setWordWrap(True)
242
+ widget.bottom_line.addWidget(widget.label, 1)
243
+
244
+ def setText(widget, label):
245
+ widget.label.setText(label)
246
+
247
+ def setLabel(widget, text):
248
+ widget.top_label.setText(text)
249
+
250
+
251
+ class LabeledLineEdit(QWidget):
252
+ editingFinished = Signal(str)
253
+ def __init__(widget, parent=None):
254
+ super().__init__(parent=parent)
255
+ widget.setObjectName('labeled_lineedit')
256
+ widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum)
257
+ widget.setAttribute(Qt.WA_StyledBackground)
258
+
259
+ widget.setLayout(QVBoxLayout())
260
+ widget.layout().setContentsMargins(0, 0, 0, 0)
261
+ widget.layout().setSpacing(0)
262
+
263
+ widget.label = QLabel()
264
+ widget.label.setObjectName('top_label')
265
+ widget.layout().addWidget(widget.label, 1)
266
+
267
+ widget.bottom_line = QHBoxLayout()
268
+ widget.bottom_line.setContentsMargins(0, 0, 0, 0)
269
+ widget.bottom_line.setSpacing(0)
270
+ widget.layout().addLayout(widget.bottom_line)
271
+
272
+ widget.lineedit = QLineEdit()
273
+ widget.lineedit.editingFinished.connect(lambda: widget.editing_finished())
274
+ widget.bottom_line.addWidget(widget.lineedit, 1)
275
+
276
+ def setLabel(widget, label):
277
+ widget.label.setText(label)
278
+
279
+ def setText(widget, text):
280
+ widget.lineedit.setText(text)
281
+
282
+ def text(widget):
283
+ return widget.lineedit.text()
284
+
285
+ def editing_finished(widget):
286
+ widget.editingFinished.emit(widget.text())