CK-Explorer commited on
Commit
25c5a9d
·
1 Parent(s): 13d6531

feat: add web ui code

Browse files
Files changed (7) hide show
  1. app.py +5 -0
  2. assets/title.html +26 -0
  3. ui/__init__.py +0 -0
  4. ui/constants.py +25 -0
  5. ui/events.py +224 -0
  6. ui/layout.py +355 -0
  7. ui/utils.py +44 -0
app.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ from ui.layout import create_main_gr_blocks_ui as duosubs_gr_blocks
2
+
3
+ if __name__ == "__main__":
4
+ webui = duosubs_gr_blocks()
5
+ webui.launch()
assets/title.html ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <style>
2
+ .hover-badge:hover {
3
+ transform: scale(1.075);
4
+ opacity: 0.9;
5
+ transition: 0.15s;
6
+ }
7
+ </style>
8
+
9
+ <div style="text-align: center;">
10
+ <h1>DuoSubs</h1>
11
+ <p>A semantic subtitle aligner and merger for bilingual subtitle syncing</p>
12
+ <div style="display: flex; justify-content: center; gap: 5px; flex-wrap: wrap; margin-top: 1em;">
13
+ <a href="https://github.com/CK-Explorer/DuoSubs" target="_blank">
14
+ <img class="hover-badge" src="https://img.shields.io/github/stars/CK-Explorer/DuoSubs?style=social" />
15
+ </a>
16
+ <a href="https://pypi.org/project/duosubs/" target="_blank">
17
+ <img class="hover-badge" src="https://img.shields.io/pypi/v/duosubs.svg" />
18
+ </a>
19
+ <a href="https://duosubs.readthedocs.io/en/latest/" target="_blank">
20
+ <img class="hover-badge" src="https://img.shields.io/badge/docs-available-blue?logo=readthedocs" />
21
+ </a>
22
+ <a href="https://github.com/CK-Explorer/DuoSubs/blob/main/LICENSE" target="_blank">
23
+ <img class="hover-badge" src="https://img.shields.io/badge/license-Apache--2.0-blueviolet.svg" />
24
+ </a>
25
+ </div>
26
+ </div>
ui/__init__.py ADDED
File without changes
ui/constants.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Constants used in the DuoSubs Hugging Face Web UI.
3
+
4
+ This module defines file paths, supported subtitle formats, model precision options,
5
+ and default values for use in the UI and other components.
6
+ """
7
+
8
+ from pathlib import Path
9
+
10
+ from duosubs import ModelPrecision, SubtitleFormat
11
+
12
+ TITLE_HTML = Path(__file__).parent.parent / "assets" / "title.html"
13
+
14
+ SUB_EXT_LIST: list[str] = [f.value for f in SubtitleFormat]
15
+ SUB_EXT_LIST_WITH_DOT: list[str] = [f".{ext}" for ext in SUB_EXT_LIST]
16
+ PRECISION_LIST: list[str] = [f.value for f in ModelPrecision]
17
+
18
+ DEFAULT_PRECISION = ModelPrecision.FLOAT32.value
19
+ DEFAULT_SUB_EXT = SubtitleFormat.ASS.value
20
+
21
+ MODEL_NAME_LIST = [
22
+ "sentence-transformers/LaBSE",
23
+ "BAAI/bge-m3",
24
+ "Qwen/Qwen3-Embedding-0.6B",
25
+ ]
ui/events.py ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ """
3
+ Event handlers and UI logic for subtitle merging in DuoSubs Hugging Face Web UI.
4
+
5
+ This module contains functions to handle merging of subtitles, and update UI elements
6
+ based on user interactions.
7
+ """
8
+
9
+ from pathlib import Path
10
+
11
+ import gradio as gr
12
+ from duosubs import (
13
+ LoadSubsError,
14
+ MergeArgs,
15
+ MergeSubsError,
16
+ ModelPrecision,
17
+ OmitFile,
18
+ SaveSubsError,
19
+ SubtitleFormat,
20
+ load_subtitles,
21
+ merge_subtitles,
22
+ save_subtitles_in_zip,
23
+ )
24
+ from sentence_transformers import SentenceTransformer
25
+
26
+
27
+ def start_merging(
28
+ model_pool: dict[str, SentenceTransformer],
29
+ primary_subtitles: str,
30
+ secondary_subtitles: str,
31
+ model_name: str,
32
+ batch_size: int,
33
+ model_precision: str,
34
+ ignore_non_overlap: bool,
35
+ retain_newline: bool,
36
+ secondary_above_primary: bool,
37
+ omit_subtitles: list[str],
38
+ combined_format: str,
39
+ primary_format:str,
40
+ secondary_format: str,
41
+ cancel_state: list[bool],
42
+ progress: gr.Progress | None = None
43
+ ) -> str | None:
44
+ """
45
+ The main function to handle the merging process of subtitles, which starts from
46
+ loading subtitles, merging subtitles, and saving the output in a ZIP file.
47
+
48
+ Args:
49
+ model_pool (dict[str, SentenceTransformer]): Mapping of model names to shared
50
+ SentenceTransformer instances.
51
+ primary_subtitles (str): Path to primary subtitle file.
52
+ secondary_subtitles (str): Path to secondary subtitle file.
53
+ model_name (str): Name of the model to use.
54
+ batch_size (int): Batch size for inference.
55
+ model_precision (str): Precision mode for inference.
56
+ ignore_non_overlap (bool): Whether to ignore non-overlapping subtitles.
57
+ retain_newline (bool): Whether to retain newlines in output.
58
+ secondary_above_primary (bool): Whether to place secondary subtitle above
59
+ primary.
60
+ omit_subtitles (list[str]): List of subtitle types to omit from output.
61
+ combined_format (str): Format for combined subtitles.
62
+ primary_format (str): Format for primary subtitles.
63
+ secondary_format (str): Format for secondary subtitles.
64
+ cancel_state (list[bool]): List tracking cancellation state.
65
+ progress (gradio.Progress) : Gradio progress object (optional).
66
+ Defaults to None.
67
+
68
+ Returns:
69
+ str | None: Path to the output ZIP file, or None if cancelled.
70
+
71
+ Raises:
72
+ gradio.Error: If any error occurs during loading, merging, or saving subtitles.
73
+ """
74
+ if progress is None:
75
+ progress = gr.Progress()
76
+
77
+ args = MergeArgs(
78
+ primary=primary_subtitles,
79
+ secondary=secondary_subtitles,
80
+ model_precision=ModelPrecision(model_precision),
81
+ batch_size=int(batch_size),
82
+ ignore_non_overlap_filter=ignore_non_overlap,
83
+ retain_newline=retain_newline,
84
+ secondary_above=secondary_above_primary,
85
+ omit=[OmitFile.EDIT],
86
+ format_combined=SubtitleFormat(combined_format),
87
+ format_primary=SubtitleFormat(primary_format),
88
+ format_secondary=SubtitleFormat(secondary_format)
89
+ )
90
+
91
+ if "Combined" in omit_subtitles:
92
+ args.omit.append(OmitFile.COMBINED)
93
+ if "Primary" in omit_subtitles:
94
+ args.omit.append(OmitFile.PRIMARY)
95
+ if "Secondary" in omit_subtitles:
96
+ args.omit.append(OmitFile.SECONDARY)
97
+
98
+ zip_name_with_path: str | None = None
99
+
100
+ if len(args.omit) == 4:
101
+ gr.Warning(
102
+ (
103
+ "Nothing to merge — Please adjust "
104
+ "<strong><em>Excluded Subtitle Files</em></strong> options "
105
+ "in <strong><em>File Exports</em></strong>"
106
+ ),
107
+ duration=7
108
+ )
109
+ return zip_name_with_path
110
+
111
+ try:
112
+ if not cancel_state[0]:
113
+ progress(progress=0, desc= "Stage 1 → Loading subtitles", total=1)
114
+ primary_subs_data, secondary_subs_data = load_subtitles(args)
115
+ progress(progress=1, desc= "Stage 1 → Loading subtitles", total=1)
116
+
117
+ if not cancel_state[0]:
118
+ def update_progress(current: int) -> None:
119
+ progress(
120
+ progress=current/100,
121
+ desc= f"Stage 2 → Merging subtitles using {model_name}",
122
+ total=100
123
+ )
124
+ merged_subs = merge_subtitles(
125
+ args,
126
+ model_pool[model_name],
127
+ primary_subs_data,
128
+ secondary_subs_data,
129
+ cancel_state,
130
+ progress_callback=update_progress
131
+ )
132
+
133
+ if not cancel_state[0]:
134
+ full_zip_path_without_ext = str(Path(args.primary).with_suffix(""))
135
+ zip_name_with_path = f"{full_zip_path_without_ext}.zip"
136
+ zip_name = Path(zip_name_with_path).name
137
+ progress(
138
+ progress=0,
139
+ desc= f"Stage 3 → Compressing files into {zip_name}",
140
+ total=1
141
+ )
142
+ save_subtitles_in_zip(
143
+ args,
144
+ merged_subs,
145
+ primary_subs_data.styles,
146
+ secondary_subs_data.styles
147
+ )
148
+ progress(
149
+ progress=1,
150
+ desc= f"Stage 3 → Compressing files into {zip_name}",
151
+ total=1
152
+ )
153
+
154
+ if cancel_state[0]:
155
+ gr.Info("The merging process is stopped.", duration=7)
156
+
157
+ except LoadSubsError as e1:
158
+ raise gr.Error(str(e1)) from e1
159
+ except MergeSubsError as e2:
160
+ raise gr.Error(str(e2)) from e2
161
+ except SaveSubsError as e3:
162
+ raise gr.Error(str(e3)) from e3
163
+
164
+ return zip_name_with_path
165
+
166
+ def cancel_merging(cancel_state: list[bool]) -> gr.Button:
167
+ """
168
+ Cancels the merging process and updates the UI state.
169
+
170
+ Args:
171
+ cancel_state (list[bool]): List tracking cancellation state.
172
+
173
+ Returns:
174
+ gradio.Button: Cancel button with updated interactivity.
175
+ """
176
+ cancel_state[0] = True
177
+ gr.Info("Cancelling merge process...", duration=7)
178
+ return gr.Button("Cancel", interactive=False)
179
+
180
+ def states_during_merging(cancel_state: list[bool]) -> tuple[gr.Button, gr.Button]:
181
+ """
182
+ Sets UI state for buttons during the merging process, which disables the Merge
183
+ button and enables the Cancel button.
184
+
185
+ Args:
186
+ cancel_state (list[bool]): List tracking cancellation state.
187
+
188
+ Returns:
189
+ tuple[gradio.Button, gradio.Button]: Updated Merge and Cancel buttons.
190
+ """
191
+ cancel_state[0] = False
192
+ return gr.Button("Merge", interactive=False), gr.Button("Cancel", interactive=True)
193
+
194
+ def states_after_merging(cancel_state: list[bool]) -> tuple[gr.Button, gr.Button]:
195
+ """
196
+ Sets UI state for buttons after the merging process, which enables the Merge button
197
+ and disables the Cancel button.
198
+
199
+ Args:
200
+ cancel_state (list[bool]): List tracking cancellation state.
201
+
202
+ Returns:
203
+ tuple[gradio.Button, gradio.Button]: Updated Merge and Cancel buttons.
204
+ """
205
+ cancel_state[0] = False
206
+ return gr.Button("Merge", interactive=True), gr.Button("Cancel", interactive=False)
207
+
208
+ def validate_excluded_subtitle_file(selected: list[str]) -> None:
209
+ """
210
+ Validates the selected options for excluded subtitle files.
211
+ If all options are selected, it shows a warning message.
212
+
213
+ Args:
214
+ selected (list[str]): List of selected options.
215
+ """
216
+ if len(selected) == 3:
217
+ gr.Warning(
218
+ (
219
+ "Nothing to merge — Please adjust "
220
+ "<strong><em>Excluded Subtitle Files</em></strong> options "
221
+ "in <strong><em>File Exports</em></strong>"
222
+ ),
223
+ duration=7
224
+ )
ui/layout.py ADDED
@@ -0,0 +1,355 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Defines the main Gradio UI layout and configuration for DuoSubs subtitle merging
3
+ Hugging Face web app.
4
+
5
+ This module sets up the UI components, event handlers, and manages the model loading
6
+ and merging process. It includes device selection, model configuration, and subtitle
7
+ file handling.
8
+ """
9
+
10
+ import gradio as gr
11
+
12
+ from .constants import (
13
+ DEFAULT_PRECISION,
14
+ DEFAULT_SUB_EXT,
15
+ MODEL_NAME_LIST,
16
+ PRECISION_LIST,
17
+ SUB_EXT_LIST,
18
+ SUB_EXT_LIST_WITH_DOT,
19
+ TITLE_HTML,
20
+ )
21
+ from .events import (
22
+ cancel_merging,
23
+ start_merging,
24
+ states_after_merging,
25
+ states_during_merging,
26
+ validate_excluded_subtitle_file,
27
+ )
28
+ from .utils import create_model_pools, open_html
29
+
30
+ model_pool = create_model_pools(MODEL_NAME_LIST)
31
+
32
+ def create_main_gr_blocks_ui(
33
+ cache_delete_frequency: int = 3600,
34
+ cache_delete_age: int = 7200
35
+ ) -> gr.Blocks:
36
+ """
37
+ Builds and returns the main Gradio Blocks UI for DuoSubs.
38
+
39
+ Args:
40
+ cache_delete_frequency (int): How often to delete cache (seconds).
41
+ cache_delete_age (int): Age threshold for cache deletion (seconds).
42
+
43
+ Returns:
44
+ gradio.Blocks: The constructed Gradio UI.
45
+ """
46
+ main_block = gr.Blocks(
47
+ title="DuoSubs",
48
+ theme=gr.themes.Ocean(),
49
+ delete_cache=(cache_delete_frequency, cache_delete_age)
50
+ )
51
+ ui: gr.Blocks
52
+ with main_block as ui:
53
+ global model_pool
54
+ cancel_state = gr.State([False])
55
+
56
+ title_content = open_html(TITLE_HTML)
57
+ gr.HTML(title_content)
58
+ with gr.Row():
59
+ with gr.Column():
60
+ (
61
+ primary_file,
62
+ secondary_file,
63
+ merged_file,
64
+ merge_button,
65
+ cancel_button
66
+ ) = _create_subtitles_io_block()
67
+ with gr.Column():
68
+ gr.Markdown("### 🔧 Configurations")
69
+ with gr.Tab("Model & Device"):
70
+ (
71
+ model_name,
72
+ batch_size,
73
+ model_precision
74
+ ) = _create_model_configurations_block()
75
+ with gr.Tab("Alignment Behavior"):
76
+ ignore_non_overlap = _create_alignment_behaviour_block()
77
+ with gr.Tab("Output Styling"):
78
+ (
79
+ retain_newline,
80
+ secondary_above_primary
81
+ ) = _create_output_styling_block()
82
+ with gr.Tab("File Exports"):
83
+ (
84
+ omit_subtitles,
85
+ combined_format,
86
+ primary_format,
87
+ secondary_format
88
+ ) = _create_file_exports_block()
89
+
90
+ omit_subtitles.change(
91
+ fn=validate_excluded_subtitle_file,
92
+ inputs=omit_subtitles
93
+ )
94
+
95
+ merge_button.click(
96
+ fn=states_during_merging,
97
+ inputs=cancel_state,
98
+ outputs=[merge_button, cancel_button]
99
+ ).then(
100
+ fn=wrapped_start_merging,
101
+ inputs=[
102
+ primary_file,
103
+ secondary_file,
104
+ model_name,
105
+ batch_size,
106
+ model_precision,
107
+ ignore_non_overlap,
108
+ retain_newline,
109
+ secondary_above_primary,
110
+ omit_subtitles,
111
+ combined_format,
112
+ primary_format,
113
+ secondary_format,
114
+ cancel_state
115
+ ],
116
+ outputs=merged_file
117
+ ).then(
118
+ fn=states_after_merging,
119
+ inputs=cancel_state,
120
+ outputs=[merge_button, cancel_button]
121
+ )
122
+
123
+ cancel_button.click(
124
+ fn=cancel_merging,
125
+ inputs=cancel_state,
126
+ outputs=cancel_button,
127
+ concurrency_limit=None
128
+ )
129
+
130
+ return ui
131
+
132
+ def _create_subtitles_io_block(
133
+ ) -> tuple[gr.File, gr.File, gr.File, gr.Button, gr.Button]:
134
+ """
135
+ Creates subtitle file input/output UI components.
136
+
137
+ This function sets up the UI for uploading primary and secondary subtitle files,
138
+ buttons to initiate and cancel the merging process, and creates the merged output
139
+ file.
140
+
141
+ Returns:
142
+ tuple[gradio.File, gradio.File, gradio.File, gradio.Button, gradio.Button]:
143
+ - primary_file
144
+ - secondary_file
145
+ - merged_file
146
+ - merge_button
147
+ - cancel_button
148
+ """
149
+ gr.Markdown("### 📄 Input Subtitles")
150
+ with gr.Row():
151
+ primary_file = gr.File(
152
+ label="Primary Subtitle File",
153
+ file_types=SUB_EXT_LIST_WITH_DOT
154
+ )
155
+ secondary_file = gr.File(
156
+ label="Secondary Subtitle File",
157
+ file_types=SUB_EXT_LIST_WITH_DOT
158
+ )
159
+
160
+ gr.Markdown("### 📦 Output Zip")
161
+ merged_file = gr.File(label="Processed Subtitles (in zip)")
162
+
163
+ with gr.Row():
164
+ merge_button = gr.Button("Merge")
165
+ cancel_button = gr.Button("Cancel", interactive=False)
166
+
167
+ return primary_file, secondary_file, merged_file, merge_button, cancel_button
168
+
169
+ def _create_model_configurations_block() -> tuple[gr.Dropdown, gr.Slider, gr.Dropdown]:
170
+ """
171
+ Creates model and device configuration UI components.
172
+
173
+ This function sets up the UI for selecting the model name, batch size, and model
174
+ precision.
175
+
176
+ Returns:
177
+ tuple[gradio.Dropdown, gradio.Slider, gradio.Dropdown]:
178
+ - model_name
179
+ - batch_size
180
+ - model_precision
181
+ """
182
+ with gr.Column():
183
+ model_name = gr.Dropdown(
184
+ choices=MODEL_NAME_LIST,
185
+ label="Sentence Transformer Model",
186
+ value=MODEL_NAME_LIST[0],
187
+ info="Model for computing subtitle sentence similarity."
188
+ )
189
+
190
+ with gr.Row():
191
+ batch_size = gr.Slider(
192
+ label="Batch Size",
193
+ minimum=8,
194
+ maximum=256,
195
+ value=256,
196
+ step=1,
197
+ info="Number of sentences to process in parallel"
198
+ )
199
+ model_precision = gr.Dropdown(
200
+ choices=PRECISION_LIST,
201
+ label="Model Precision",
202
+ value=DEFAULT_PRECISION,
203
+ info="Precision mode for inference"
204
+ )
205
+ return model_name, batch_size, model_precision
206
+
207
+ def _create_alignment_behaviour_block() -> gr.Checkbox:
208
+ """
209
+ Creates alignment behavior UI components.
210
+
211
+ This function sets up a checkbox for ignoring non-overlapping subtitles, in the
212
+ merging process.
213
+
214
+ Returns:
215
+ gradio.Checkbox: Checkbox for alignment behavior.
216
+ """
217
+ with gr.Column():
218
+ ignore_non_overlap = gr.Checkbox(
219
+ label="Ignore Non-Overlap Filter",
220
+ value=False,
221
+ info=(
222
+ "💡 Use only if both subtitles are **semantically identical** "
223
+ "and contain **no added scenes or annotations**"
224
+ )
225
+ )
226
+ return ignore_non_overlap
227
+
228
+ def _create_output_styling_block() -> tuple[gr.Checkbox, gr.Checkbox]:
229
+ """
230
+ Creates output styling UI components.
231
+
232
+ This function sets up checkboxes for retaining newlines in the original subtitles
233
+ and placing secondary subtitles above primary subtitles in the merged output.
234
+
235
+ Returns:
236
+ tuple[gradio.Checkbox, gradio.Checkbox]:
237
+ - retain_newline
238
+ - secondary_above_primary
239
+ """
240
+ with gr.Column():
241
+ retain_newline = gr.Checkbox(
242
+ label="Retain Newlines",
243
+ value=False,
244
+ info="**Retain line breaks** from the original subtitles"
245
+ )
246
+ secondary_above_primary = gr.Checkbox(
247
+ label="Secondary subtitle above primary subtitle",
248
+ value=False,
249
+ info="Place **secondary** subtitle **above** the **primary**"
250
+ )
251
+
252
+ return retain_newline, secondary_above_primary
253
+
254
+ def _create_file_exports_block(
255
+ ) -> tuple[gr.CheckboxGroup, gr.Dropdown, gr.Dropdown, gr.Dropdown]:
256
+ """
257
+ Creates file export UI components.
258
+
259
+ This function sets up checkboxes for excluding certain subtitle files from the ZIP
260
+ output, and dropdowns for selecting the format of combined, primary, and secondary
261
+ subtitles.
262
+
263
+ Returns:
264
+ tuple[gradio.CheckboxGroup, gradio.Dropdown, gradio.Dropdown, gradio.Dropdown]:
265
+ - omit_subtitles
266
+ - combined_format
267
+ - primary_format
268
+ - secondary_format
269
+ """
270
+ with gr.Column():
271
+ omit_subtitles = gr.CheckboxGroup(
272
+ ["Combined", "Primary", "Secondary"],
273
+ type="value",
274
+ label="Excluded Subtitle Files from ZIP"
275
+ )
276
+ with gr.Column():
277
+ gr.Markdown("Subtitle Format")
278
+ combined_format = gr.Dropdown(
279
+ choices=SUB_EXT_LIST,
280
+ value=DEFAULT_SUB_EXT,
281
+ label="Combined"
282
+ )
283
+ primary_format = gr.Dropdown(
284
+ choices=SUB_EXT_LIST,
285
+ value=DEFAULT_SUB_EXT,
286
+ label="Primary"
287
+ )
288
+ secondary_format = gr.Dropdown(
289
+ choices=SUB_EXT_LIST,
290
+ value=DEFAULT_SUB_EXT,
291
+ label="Secondary"
292
+ )
293
+ return omit_subtitles, combined_format, primary_format, secondary_format
294
+
295
+ def wrapped_start_merging(
296
+ primary_subtitles: str,
297
+ secondary_subtitles: str,
298
+ model_name: str,
299
+ batch_size: int,
300
+ model_precision: str,
301
+ ignore_non_overlap: bool,
302
+ retain_newline: bool,
303
+ secondary_above_primary: bool,
304
+ omit_subtitles: list[str],
305
+ combined_format: str,
306
+ primary_format:str,
307
+ secondary_format: str,
308
+ cancel_state: list[bool],
309
+ progress: gr.Progress | None = None
310
+ ) -> str | None:
311
+ """
312
+ Wrapper for starting the merging process with all required arguments.
313
+
314
+ Args:
315
+ primary_subtitles (str): Path to primary subtitle file.
316
+ secondary_subtitles (str): Path to secondary subtitle file.
317
+ model_name (str): Name of the model to use.
318
+ batch_size (int): Batch size for inference.
319
+ model_precision (str): Precision mode for inference.
320
+ ignore_non_overlap (bool): Whether to ignore non-overlapping subtitles.
321
+ retain_newline (bool): Whether to retain newlines in output.
322
+ secondary_above_primary (bool): Whether to place secondary subtitle above
323
+ primary.
324
+ omit_subtitles (list[str]): List of subtitle types to omit from output.
325
+ combined_format (str): Format for combined subtitles.
326
+ primary_format (str): Format for primary subtitles.
327
+ secondary_format (str): Format for secondary subtitles.
328
+ gpu_list (list[str]): List of available GPU names.
329
+ loaded_model_device (list[str]): List tracking loaded model device.
330
+ loaded_model_name (list[str]): List tracking loaded model name.
331
+ cancel_state (list[bool]): List tracking cancellation state.
332
+ request (gradio.Request): Gradio request object.
333
+ progress (gradio.Progress) : Gradio progress object (optional).
334
+ Defaults to None.
335
+
336
+ Returns:
337
+ str | None: Path to the output ZIP file, or None if cancelled.
338
+ """
339
+ return start_merging(
340
+ model_pool,
341
+ primary_subtitles,
342
+ secondary_subtitles,
343
+ model_name,
344
+ batch_size,
345
+ model_precision,
346
+ ignore_non_overlap,
347
+ retain_newline,
348
+ secondary_above_primary,
349
+ omit_subtitles,
350
+ combined_format,
351
+ primary_format,
352
+ secondary_format,
353
+ cancel_state,
354
+ progress
355
+ )
ui/utils.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Utility functions for device detection, GPU listing, and HTML file reading for the web
3
+ UI.
4
+
5
+ These functions help in determining available devices for model inference, listing GPU
6
+ names, and reading HTML content for the UI.
7
+ """
8
+
9
+ from pathlib import Path
10
+
11
+ from sentence_transformers import SentenceTransformer
12
+
13
+
14
+ def open_html(file: str | Path) -> str:
15
+ """
16
+ Reads and returns the content of an HTML file.
17
+
18
+ Args:
19
+ file (str | Path): Path to the HTML file (str or Path).
20
+
21
+ Returns:
22
+ str: The content of the HTML file as a string.
23
+ """
24
+ content = ""
25
+ with open(file, "r", encoding="utf-8") as f:
26
+ content = f.read()
27
+ return content
28
+
29
+ def create_model_pools(name_list: list[str]) -> dict[str, SentenceTransformer]:
30
+ """
31
+ Creates a pool of SentenceTransformer models based on the provided model names.
32
+
33
+ Args:
34
+ name_list (list[str]): List of model names to create SentenceTransformer
35
+ instances.
36
+
37
+ Returns:
38
+ dict[str, SentenceTransformer]: A dictionary mapping model names to their
39
+ corresponding SentenceTransformer instances.
40
+ """
41
+ model_pool: dict[str, SentenceTransformer] = {}
42
+ for name in name_list:
43
+ model_pool[name] = SentenceTransformer(name, device="cuda")
44
+ return model_pool