io4it 0.0.0.12.10__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 (30) hide show
  1. io4it-0.0.0.12.10-py3.11-nspkg.pth +1 -0
  2. io4it-0.0.0.12.10.dist-info/License.txt +6 -0
  3. io4it-0.0.0.12.10.dist-info/METADATA +23 -0
  4. io4it-0.0.0.12.10.dist-info/RECORD +30 -0
  5. io4it-0.0.0.12.10.dist-info/WHEEL +5 -0
  6. io4it-0.0.0.12.10.dist-info/entry_points.txt +2 -0
  7. io4it-0.0.0.12.10.dist-info/namespace_packages.txt +1 -0
  8. io4it-0.0.0.12.10.dist-info/top_level.txt +1 -0
  9. orangecontrib/IO4IT/__init__.py +0 -0
  10. orangecontrib/IO4IT/ocr_function/__init__.py +0 -0
  11. orangecontrib/IO4IT/ocr_function/word_converter.py +327 -0
  12. orangecontrib/IO4IT/widgets/OWMarkdownizer.py +202 -0
  13. orangecontrib/IO4IT/widgets/OWPathPropagator.py +123 -0
  14. orangecontrib/IO4IT/widgets/OWS3Uploader.py +92 -0
  15. orangecontrib/IO4IT/widgets/OWS3downloader.py +94 -0
  16. orangecontrib/IO4IT/widgets/OWS3list.py +107 -0
  17. orangecontrib/IO4IT/widgets/OWSpeechToText.py +362 -0
  18. orangecontrib/IO4IT/widgets/OWwordpdf2docx.py +129 -0
  19. orangecontrib/IO4IT/widgets/__init__.py +19 -0
  20. orangecontrib/IO4IT/widgets/designer/ow_in_or_out_path.ui +85 -0
  21. orangecontrib/IO4IT/widgets/designer/owspeechtotext.ui +104 -0
  22. orangecontrib/IO4IT/widgets/designer/wordpdf2docx.ui +57 -0
  23. orangecontrib/IO4IT/widgets/icons/category.svg +50 -0
  24. orangecontrib/IO4IT/widgets/icons/download.png +0 -0
  25. orangecontrib/IO4IT/widgets/icons/in_or_out.png +0 -0
  26. orangecontrib/IO4IT/widgets/icons/list_aws.png +0 -0
  27. orangecontrib/IO4IT/widgets/icons/md.png +0 -0
  28. orangecontrib/IO4IT/widgets/icons/speech_to_text.png +0 -0
  29. orangecontrib/IO4IT/widgets/icons/upload.png +0 -0
  30. orangecontrib/IO4IT/widgets/icons/wordpdf2docx.png +0 -0
@@ -0,0 +1,362 @@
1
+ import datetime
2
+ import ntpath
3
+ import os
4
+ import wave
5
+ import tempfile
6
+ import shutil
7
+ import os
8
+ from PyQt5.QtCore import QThread, pyqtSignal
9
+ from PyQt5.QtWidgets import QApplication, QTextEdit, QPushButton, QSpinBox
10
+ from pyannote.audio import Audio
11
+ from pyannote.core import Segment
12
+
13
+ import numpy as np
14
+ import torch
15
+ import whisper
16
+ from Orange.data import Table, Domain, StringVariable
17
+ from Orange.widgets import widget
18
+ from Orange.widgets.utils.signals import Output
19
+ from sklearn.cluster import KMeans
20
+ from speechbrain.inference.speaker import EncoderClassifier
21
+
22
+ if "site-packages/Orange/widgets" in os.path.dirname(os.path.abspath(__file__)).replace("\\", "/"):
23
+ from orangecontrib.AAIT.utils.import_uic import uic
24
+ from orangecontrib.AAIT.utils import SimpleDialogQt
25
+ from orangecontrib.AAIT.utils.MetManagement import get_local_store_path, GetFromRemote
26
+ else:
27
+ from orangecontrib.AAIT.utils.import_uic import uic
28
+ from orangecontrib.AAIT.utils import SimpleDialogQt
29
+ from orangecontrib.AAIT.utils.MetManagement import get_local_store_path, GetFromRemote
30
+
31
+ import subprocess
32
+
33
+ def convert_audio_to_pcm(file_path, ffmpeg_path):
34
+ try:
35
+ import subprocess
36
+
37
+ ext = os.path.splitext(file_path)[1].lower()
38
+ if ext not in [".mp3", ".wav"]:
39
+ print(f"[ERREUR] Type de fichier non supporté : {ext}")
40
+ return None
41
+
42
+ pcm_wav_path = file_path.replace(ext, "_pcm.wav") # pas de nom compliqué ici
43
+
44
+ ffmpeg_cmd = [
45
+ ffmpeg_path, "-y",
46
+ "-i", file_path,
47
+ "-acodec", "pcm_s16le",
48
+ "-ar", "16000",
49
+ "-ac", "1",
50
+ pcm_wav_path
51
+ ]
52
+
53
+ print(f"[INFO] Lancement de ffmpeg : {' '.join(ffmpeg_cmd)}")
54
+ result = subprocess.run(ffmpeg_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
55
+
56
+ if result.returncode != 0:
57
+ print(f"[ERREUR] ffmpeg a échoué avec le code {result.returncode}")
58
+ print("[STDERR]", result.stderr)
59
+ return None
60
+
61
+ if os.path.exists(pcm_wav_path):
62
+ print(f"[INFO] Fichier converti : {pcm_wav_path}")
63
+ return pcm_wav_path
64
+ else:
65
+ print("[ERREUR] Le fichier converti n’a pas été trouvé.")
66
+ return None
67
+
68
+ except Exception as e:
69
+ print("❌ Exception pendant la conversion audio :", e)
70
+ return None
71
+
72
+
73
+ def get_wav_duration(wav_path):
74
+ try:
75
+ with wave.open(wav_path, "rb") as wav_file:
76
+ frames = wav_file.getnframes()
77
+ rate = wav_file.getframerate()
78
+ return frames / float(rate)
79
+ except Exception as e:
80
+ # and print the error message
81
+ print("An error occurred when getting the wav duration : ", e)
82
+ return
83
+
84
+
85
+ class TranscriptionThread(QThread):
86
+ result_signal = pyqtSignal(str, list, float)
87
+
88
+ def __init__(self, file_path, model, embedding_model, audio_helper, num_speakers=2):
89
+ super().__init__()
90
+ self.file_path = file_path
91
+ self.model = model
92
+ self.embedding_model = embedding_model
93
+ self.audio_helper = audio_helper
94
+ self.num_speakers = num_speakers
95
+
96
+ def run(self):
97
+ try:
98
+ print("[THREAD] Démarrage du thread de transcription")
99
+ print(f"[THREAD] Fichier à traiter : {self.file_path}")
100
+
101
+ if not os.path.exists(self.file_path):
102
+ print(f"[ERREUR] Le fichier n'existe pas : {self.file_path}")
103
+ self.result_signal.emit("Erreur : fichier introuvable.", [], 0.0)
104
+ return
105
+
106
+ file_duration = get_wav_duration(self.file_path)
107
+ print(f"[INFO] Durée du fichier : {file_duration:.2f} sec" if file_duration else "[ERREUR] Durée inconnue")
108
+
109
+ if file_duration is None:
110
+ self.result_signal.emit("Error: File duration unknown.", [], 0.0)
111
+ return
112
+
113
+ print("[INFO] Début de la transcription avec Whisper")
114
+ start_time = datetime.datetime.now()
115
+
116
+ result = self.model.transcribe(
117
+ self.file_path,
118
+ language="fr",
119
+ without_timestamps=False,
120
+ temperature=0
121
+ )
122
+
123
+ transcription_time = (datetime.datetime.now() - start_time).total_seconds() / 60
124
+ print(f"[INFO] Transcription terminée en {transcription_time:.2f} min")
125
+
126
+ if not result or "segments" not in result or not result["segments"]:
127
+ print("[ERREUR] Aucun segment détecté")
128
+ self.result_signal.emit("Error: No speech detected.", [], transcription_time)
129
+ return
130
+
131
+ segments = result["segments"]
132
+ print(f"[INFO] Nombre de segments détectés : {len(segments)}")
133
+
134
+ embeddings = None
135
+
136
+ for i, segment in enumerate(segments):
137
+ start, end = segment["start"], segment["end"]
138
+
139
+ if end > file_duration:
140
+ print(f"[AVERTISSEMENT] Segment {i} ignoré (fin hors durée)")
141
+ continue
142
+
143
+ try:
144
+ waveform, _ = self.audio_helper.crop(self.file_path, Segment(start, end))
145
+ if waveform.ndim == 1:
146
+ waveform = waveform.unsqueeze(0)
147
+ with torch.no_grad():
148
+ embedding = self.embedding_model.encode_batch(waveform).squeeze().cpu().numpy()
149
+ if embeddings is None:
150
+ embeddings = np.zeros((len(segments), embedding.shape[0]))
151
+ embeddings[i] = embedding
152
+ except Exception as crop_err:
153
+ print(f"[ERREUR] Erreur lors du crop ou de l'embedding pour le segment {i} : {crop_err}")
154
+ continue
155
+
156
+ print("[INFO] Clustering des embeddings avec KMeans")
157
+ clustering = KMeans(n_clusters=min(self.num_speakers, len(segments)), random_state=42).fit(embeddings)
158
+ labels = clustering.labels_
159
+
160
+ speaker_map = {}
161
+ merged_segments = []
162
+ current_speaker = None
163
+ current_text = ""
164
+ current_start = None
165
+ table_output = []
166
+
167
+ print("[INFO] Regroupement par locuteur")
168
+ for i, segment in enumerate(segments):
169
+ speaker_id = labels[i]
170
+ if speaker_id not in speaker_map:
171
+ speaker_map[speaker_id] = f"SPEAKER {len(speaker_map) + 1}"
172
+ speaker = speaker_map[speaker_id]
173
+
174
+ if current_speaker == speaker:
175
+ current_text += f" {segment['text']}"
176
+ else:
177
+ if current_speaker is not None:
178
+ timestamp = str(datetime.timedelta(seconds=round(current_start)))
179
+ merged_segments.append(f"{current_speaker} {timestamp}: {current_text}")
180
+ table_output.append([current_speaker, timestamp, current_text])
181
+ current_speaker = speaker
182
+ current_text = segment["text"]
183
+ current_start = segment["start"]
184
+
185
+ print("[INFO] Finalisation des résultats")
186
+ speaker_text_output = "\n".join(merged_segments)
187
+ self.result_signal.emit(speaker_text_output, table_output, transcription_time)
188
+ print("[THREAD] Transcription terminée et signal émis")
189
+
190
+ except Exception as e:
191
+ print("❌ An error occurred during transcription:", e)
192
+ import traceback
193
+ traceback.print_exc()
194
+ return
195
+
196
+
197
+ class OWSpeech_To_Text(widget.OWWidget):
198
+ name = "Speech To Text"
199
+ description = "Convert audio to text with speaker recognition"
200
+ priority = 1111
201
+ category = "Advanced Artificial Intelligence Tools"
202
+ icon = "icons/speech_to_text.png"
203
+ if "site-packages/Orange/widgets" in os.path.dirname(os.path.abspath(__file__)).replace("\\", "/"):
204
+ icon = "icons_dev/speech_to_text.png"
205
+ gui = os.path.join(os.path.dirname(os.path.abspath(__file__)), "designer/owspeechtotext.ui")
206
+
207
+ class Outputs:
208
+ data = Output("Data", Table)
209
+ global_transcription = Output("Global Transcription", Table)
210
+
211
+ def __init__(self):
212
+ super().__init__()
213
+ self.file_path = ""
214
+ self.num_speakers = 4 # spin box a defaut 4
215
+
216
+ self.local_store_path = get_local_store_path()
217
+ model_name = "large-v3-turbo.pt"
218
+ self.embedding_model_name = "spkrec-ecapa-voxceleb"
219
+ self.model_path = os.path.join(self.local_store_path, "Models", "S2T", model_name)
220
+ self.embedding_model_path = os.path.join(self.local_store_path, "Models", "S2T", self.embedding_model_name)
221
+
222
+ self.ffmpeg_path = os.path.join(self.local_store_path, "Models", "S2T", "ffmpeg", "bin", "ffmpeg.exe")
223
+ # Extraire le dossier de ffmpeg.exe
224
+ ffmpeg_bin_dir = os.path.dirname(self.ffmpeg_path)
225
+
226
+ # Ajouter ffmpeg au PATH pour whisper/ffmpeg-python
227
+ if ffmpeg_bin_dir not in os.environ["PATH"]:
228
+ os.environ["PATH"] = ffmpeg_bin_dir + os.pathsep + os.environ["PATH"]
229
+ print(f"[INFO] Dossier ffmpeg ajouté au PATH Python : {ffmpeg_bin_dir}")
230
+
231
+
232
+ if not os.path.exists(self.model_path):
233
+ if not SimpleDialogQt.BoxYesNo("Whisper turbo Transcription Model isn't in your computer. Do you want to download it from AAIT store?"):
234
+ return
235
+ try:
236
+ if 0 != GetFromRemote("Whisper turbo"):
237
+ return
238
+ except Exception as e:
239
+ print(e)
240
+ SimpleDialogQt.BoxError("Unable to get the Whisper turbo.")
241
+ return
242
+
243
+ if not os.path.exists(self.embedding_model_path):
244
+ if not SimpleDialogQt.BoxYesNo("Voxceleb Embedding Model isn't in your computer. Do you want to download it from AAIT store?"):
245
+ return
246
+ try:
247
+ if 0 != GetFromRemote("Voxceleb"):
248
+ return
249
+ except Exception as e:
250
+ print(e)
251
+ SimpleDialogQt.BoxError("Unable to get the Voxceleb.")
252
+ return
253
+ if not os.path.exists(self.ffmpeg_path):
254
+ if not SimpleDialogQt.BoxYesNo("FFMPEG isn't in your computer. Do you want to download it from AAIT store?"):
255
+ return
256
+ try:
257
+ if 0 != GetFromRemote("FFMPEG"):
258
+ return
259
+ except Exception as e:
260
+ print(e)
261
+ SimpleDialogQt.BoxError("Unable to get the ffmpeg.")
262
+ return
263
+
264
+ self.model = whisper.load_model(self.model_path)
265
+ print("Version of PyTorch :", torch.__version__)
266
+ print("Used cuda version :", torch.version.cuda)
267
+ print("CUDA available ? :", torch.cuda.is_available())
268
+ if torch.cuda.is_available():
269
+ print("Number of GPU :", torch.cuda.device_count())
270
+ print("GPU's name :", torch.cuda.get_device_name(0))
271
+ # Définition d'un model d'embedding (hard codé pour l'instant)
272
+
273
+ self.embedding_model = EncoderClassifier.from_hparams(source=self.embedding_model_path)
274
+ self.audio_helper = Audio()
275
+ uic.loadUi(self.gui, self)
276
+
277
+ self.file_button = self.findChild(QPushButton,
278
+ 'fileButton')
279
+ self.file_button.clicked.connect(self.select_file)
280
+ self.process_button = self.findChild(QPushButton,
281
+ 'processButton')
282
+ self.process_button.clicked.connect(self.process_recording)
283
+ self.text_area = self.findChild(QTextEdit, 'textArea')
284
+
285
+ self.spinBox_nb_people=self.findChild(QSpinBox,'spinBox_nb_people')
286
+ self.spinBox_nb_people.setValue(int(self.num_speakers))
287
+ self.spinBox_nb_people.valueChanged.connect(self.spinbox_value_changed)
288
+ self.process_button.setEnabled(False)
289
+
290
+ def spinbox_value_changed(self, value):
291
+ self.num_speakers = value
292
+
293
+ def select_file(self):
294
+ file_path = SimpleDialogQt.BoxSelectExistingFile(self, extention="Audio files (*.wav *.mp3)")
295
+
296
+ if file_path:
297
+ # 🔃 Copie dans un chemin sans accents ni caractères spéciaux
298
+ temp_dir = tempfile.gettempdir()
299
+ base_ext = os.path.splitext(file_path)[1]
300
+ clean_copy = os.path.join(temp_dir, "input_audio" + base_ext)
301
+ shutil.copy(file_path, clean_copy)
302
+ print(f"[INFO] Copie vers fichier temporaire sans accents : {clean_copy}")
303
+
304
+ # 🔁 Conversion dans ce dossier temporaire
305
+ pcm_path = convert_audio_to_pcm(clean_copy, self.ffmpeg_path)
306
+ print(f"[DEBUG] pcm_path: {pcm_path}")
307
+
308
+ if pcm_path:
309
+ self.file_path = pcm_path
310
+ self.temp_pcm_path = pcm_path
311
+ self.process_button.setEnabled(True)
312
+ else:
313
+ SimpleDialogQt.BoxError("Erreur : La conversion audio a échoué.")
314
+ else:
315
+ print("[ERREUR] Aucun fichier sélectionné.")
316
+
317
+
318
+ def process_recording(self):
319
+ self.process_button.setEnabled(False)
320
+ if not self.file_path:
321
+ SimpleDialogQt.BoxError(
322
+ "Aucun fichier sélectionné. Veuillez choisir un fichier audio avant de lancer la transcription.")
323
+ return
324
+ self.num_speakers = self.spinBox_nb_people.value()
325
+ self.text_area.setText("Transcription in progress...")
326
+ self.thread = TranscriptionThread(
327
+ self.file_path, self.model, self.embedding_model, self.audio_helper, self.num_speakers
328
+ )
329
+ self.thread.result_signal.connect(self.display_text)
330
+ self.thread.start()
331
+ self.progressBarInit() # Ajout de la barre de progression
332
+
333
+ def display_text(self, text, table_output, transcription_time):
334
+ self.text_area.setText(f"{text}\n\n⏳ Temps de transcription: {transcription_time:.2f} minutes")
335
+
336
+ # Sortie 1 : tableau par speaker
337
+ domain = Domain([],
338
+ metas=[StringVariable("Speaker"), StringVariable("Timestamp"), StringVariable("Transcription")])
339
+ metas = [[row[0], row[1], row[2]] for row in table_output] if table_output else [["", "", ""]]
340
+ out_data = Table(domain, [[] for _ in metas])
341
+ for i, meta in enumerate(metas):
342
+ out_data.metas[i] = meta
343
+ self.Outputs.data.send(out_data)
344
+
345
+ # Sortie 2 : une seule ligne avec toutes les infos
346
+ global_domain = Domain([],
347
+ metas=[StringVariable("Nom du fichier"),
348
+ StringVariable("Transcription"),
349
+ StringVariable("Temps de transcription (min)")])
350
+ filename = ntpath.basename(self.file_path) # Pour ne garder que le nom du fichier
351
+ global_metas = [[filename, text, f"{transcription_time:.2f}"]]
352
+ global_table = Table(global_domain, [[]])
353
+ global_table.metas[0] = global_metas[0]
354
+ self.Outputs.global_transcription.send(global_table)
355
+ self.progressBarFinished()
356
+
357
+ if __name__ == "__main__":
358
+ import sys
359
+ app = QApplication(sys.argv)
360
+ window = OWSpeech_To_Text()
361
+ window.show()
362
+ sys.exit(app.exec_())
@@ -0,0 +1,129 @@
1
+ import os
2
+ import sys
3
+
4
+ import Orange.data
5
+ from AnyQt.QtWidgets import QApplication,QCheckBox
6
+
7
+ from Orange.widgets import widget
8
+ from Orange.widgets.utils.signals import Input, Output
9
+
10
+
11
+ if "site-packages/Orange/widgets" in os.path.dirname(os.path.abspath(__file__)).replace("\\", "/"):
12
+ from Orange.widgets.orangecontrib.AAIT.utils import thread_management
13
+ from Orange.widgets.orangecontrib.AAIT.utils.import_uic import uic
14
+ from Orange.widgets.orangecontrib.IO4IT.ocr_function import word_converter
15
+ else:
16
+ from orangecontrib.AAIT.utils import thread_management
17
+ from orangecontrib.AAIT.utils.import_uic import uic
18
+ from orangecontrib.IO4IT.ocr_function import word_converter
19
+
20
+ class OWwordpdf2docx(widget.OWWidget):
21
+ name = "WordPdf2Docx"
22
+ description = "Convert pdf from a directory to docx using word"
23
+ icon = "icons/wordpdf2docx.png"
24
+ if "site-packages/Orange/widgets" in os.path.dirname(os.path.abspath(__file__)).replace("\\", "/"):
25
+ icon = "icons_dev/wordpdf2docx.png"
26
+ gui = os.path.join(os.path.dirname(os.path.abspath(__file__)), "designer/wordpdf2docx.ui")
27
+ want_control_area = False
28
+ priority = 3000
29
+
30
+ class Inputs:
31
+ data = Input("Data", Orange.data.Table)
32
+
33
+ class Outputs:
34
+ data = Output("Data", Orange.data.Table)
35
+
36
+ @Inputs.data
37
+ def set_data(self, in_data):
38
+ self.data = in_data
39
+ if self.autorun:
40
+ self.run()
41
+
42
+
43
+ def __init__(self):
44
+ super().__init__()
45
+ # Qt Management
46
+ self.setFixedWidth(470)
47
+ self.setFixedHeight(300)
48
+ uic.loadUi(self.gui, self)
49
+ self.check_box= self.findChild(QCheckBox, 'checkBox')
50
+ # Data Management
51
+ self.data = None
52
+ self.thread = None
53
+ self.autorun = True
54
+ self.result = None
55
+ self.post_initialized()
56
+
57
+ def run(self):
58
+ self.error("")
59
+ # if thread is running quit
60
+ if self.thread is not None:
61
+ self.thread.safe_quit()
62
+
63
+ if self.data is None:
64
+ return
65
+
66
+
67
+ # Verification of in_data
68
+ self.error("")
69
+ try:
70
+ self.data.domain["input_dir"]
71
+ except KeyError:
72
+ self.error('You need a "input_dir" column in input data')
73
+ return
74
+
75
+ if type(self.data.domain["input_dir"]).__name__ != 'StringVariable':
76
+ self.error('"input_dir" column needs to be a Text')
77
+ return
78
+ try:
79
+ self.data.domain["output_dir"]
80
+ except KeyError:
81
+ self.error('You need a "output_dir" column in input data')
82
+ return
83
+
84
+ if type(self.data.domain["output_dir"]).__name__ != 'StringVariable':
85
+ self.error('"output_dir" column needs to be a Text')
86
+ return
87
+ input_dir = self.data.get_column("input_dir")
88
+ output_dir = self.data.get_column("output_dir")
89
+
90
+ # Start progress bar
91
+ self.progressBarInit()
92
+ # Connect and start thread : main function, progress, result and finish
93
+ # --> progress is used in the main function to track progress (with a callback)
94
+ # --> result is used to collect the result from main function
95
+ # --> finish is just an empty signal to indicate that the thread is finished
96
+ ignore_existing_docx=False
97
+ if self.check_box.isChecked():
98
+ ignore_existing_docx=True
99
+ self.thread = thread_management.Thread(word_converter.convert_pdf_structure, input_dir, output_dir,ignore_exsting_out_put=ignore_existing_docx)
100
+ self.thread.progress.connect(self.handle_progress)
101
+ self.thread.result.connect(self.handle_result)
102
+ self.thread.finish.connect(self.handle_finish)
103
+ self.thread.start()
104
+
105
+ def handle_progress(self, value: float) -> None:
106
+ self.progressBarSet(value)
107
+
108
+ def handle_result(self, result):
109
+ try:
110
+ self.result = result
111
+ self.error(result)
112
+ self.Outputs.data.send(self.data)
113
+ except Exception as e:
114
+ print("An error occurred when sending out_data:", e)
115
+ self.Outputs.data.send(None)
116
+ return
117
+
118
+ def handle_finish(self):
119
+ print("conversion finished")
120
+ self.progressBarFinished()
121
+
122
+ def post_initialized(self):
123
+ pass
124
+
125
+ if __name__ == "__main__":
126
+ app = QApplication(sys.argv)
127
+ my_widget = OWwordpdf2docx()
128
+ my_widget.show()
129
+ app.exec_()
@@ -0,0 +1,19 @@
1
+ """
2
+ Widgets from Development workflow category
3
+
4
+ """
5
+ # ID = "orangecontrib.AAIT"
6
+
7
+ NAME = "Advanced Artificial Intelligence Tools"
8
+
9
+ # Category icon show in the menu
10
+ ICON = "icons/category.svg"
11
+
12
+ BACKGROUND = "light-green"
13
+
14
+ DESCRIPTION = ("Advanced Artificial Intelligence Tools is a package meant to develop and enable advanced AI "
15
+ "functionalities in Orange Data Mining.")
16
+
17
+
18
+
19
+ # PRIORITY = 6
@@ -0,0 +1,85 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <ui version="4.0">
3
+ <class>Form</class>
4
+ <widget class="QWidget" name="Form">
5
+ <property name="geometry">
6
+ <rect>
7
+ <x>0</x>
8
+ <y>0</y>
9
+ <width>727</width>
10
+ <height>151</height>
11
+ </rect>
12
+ </property>
13
+ <property name="windowTitle">
14
+ <string>Input_dir or Output_dir</string>
15
+ </property>
16
+ <widget class="QLabel" name="Description">
17
+ <property name="geometry">
18
+ <rect>
19
+ <x>40</x>
20
+ <y>10</y>
21
+ <width>641</width>
22
+ <height>41</height>
23
+ </rect>
24
+ </property>
25
+ <property name="sizePolicy">
26
+ <sizepolicy hsizetype="Maximum" vsizetype="Expanding">
27
+ <horstretch>0</horstretch>
28
+ <verstretch>0</verstretch>
29
+ </sizepolicy>
30
+ </property>
31
+ <property name="font">
32
+ <font>
33
+ <pointsize>8</pointsize>
34
+ </font>
35
+ </property>
36
+ <property name="text">
37
+ <string>This widget get a folder path to propagate it as a path , either as input or output path</string>
38
+ </property>
39
+ <property name="wordWrap">
40
+ <bool>true</bool>
41
+ </property>
42
+ </widget>
43
+ <widget class="QPushButton" name="fileButton">
44
+ <property name="geometry">
45
+ <rect>
46
+ <x>60</x>
47
+ <y>70</y>
48
+ <width>291</width>
49
+ <height>51</height>
50
+ </rect>
51
+ </property>
52
+ <property name="text">
53
+ <string>📂 Select a fodler</string>
54
+ </property>
55
+ </widget>
56
+ <widget class="QRadioButton" name="input_dir">
57
+ <property name="geometry">
58
+ <rect>
59
+ <x>470</x>
60
+ <y>60</y>
61
+ <width>131</width>
62
+ <height>31</height>
63
+ </rect>
64
+ </property>
65
+ <property name="text">
66
+ <string>input_dir</string>
67
+ </property>
68
+ </widget>
69
+ <widget class="QRadioButton" name="output_dir">
70
+ <property name="geometry">
71
+ <rect>
72
+ <x>470</x>
73
+ <y>100</y>
74
+ <width>131</width>
75
+ <height>31</height>
76
+ </rect>
77
+ </property>
78
+ <property name="text">
79
+ <string>output_dir</string>
80
+ </property>
81
+ </widget>
82
+ </widget>
83
+ <resources/>
84
+ <connections/>
85
+ </ui>