io4it 0.0.0.12__tar.gz → 0.0.0.12.2__tar.gz

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 (35) hide show
  1. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/PKG-INFO +2 -1
  2. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/io4it.egg-info/PKG-INFO +2 -1
  3. io4it-0.0.0.12.2/orangecontrib/IO4IT/ocr_function/word_converter.py +327 -0
  4. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/setup.py +44 -5
  5. io4it-0.0.0.12/orangecontrib/IO4IT/ocr_function/word_converter.py +0 -654
  6. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/License.txt +0 -0
  7. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/io4it.egg-info/SOURCES.txt +0 -0
  8. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/io4it.egg-info/dependency_links.txt +0 -0
  9. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/io4it.egg-info/entry_points.txt +0 -0
  10. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/io4it.egg-info/namespace_packages.txt +0 -0
  11. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/io4it.egg-info/requires.txt +0 -0
  12. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/io4it.egg-info/top_level.txt +0 -0
  13. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/orangecontrib/IO4IT/__init__.py +0 -0
  14. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/orangecontrib/IO4IT/ocr_function/__init__.py +0 -0
  15. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/orangecontrib/IO4IT/widgets/OWMarkdownizer.py +0 -0
  16. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/orangecontrib/IO4IT/widgets/OWPathPropagator.py +0 -0
  17. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/orangecontrib/IO4IT/widgets/OWS3Uploader.py +0 -0
  18. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/orangecontrib/IO4IT/widgets/OWS3downloader.py +0 -0
  19. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/orangecontrib/IO4IT/widgets/OWS3list.py +0 -0
  20. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/orangecontrib/IO4IT/widgets/OWSpeechToText.py +0 -0
  21. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/orangecontrib/IO4IT/widgets/OWwordpdf2docx.py +0 -0
  22. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/orangecontrib/IO4IT/widgets/__init__.py +0 -0
  23. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/orangecontrib/IO4IT/widgets/designer/ow_in_or_out_path.ui +0 -0
  24. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/orangecontrib/IO4IT/widgets/designer/owspeechtotext.ui +0 -0
  25. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/orangecontrib/IO4IT/widgets/designer/wordpdf2docx.ui +0 -0
  26. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/orangecontrib/IO4IT/widgets/icons/category.svg +0 -0
  27. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/orangecontrib/IO4IT/widgets/icons/download.png +0 -0
  28. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/orangecontrib/IO4IT/widgets/icons/in_or_out.png +0 -0
  29. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/orangecontrib/IO4IT/widgets/icons/list_aws.png +0 -0
  30. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/orangecontrib/IO4IT/widgets/icons/md.png +0 -0
  31. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/orangecontrib/IO4IT/widgets/icons/speech_to_text.png +0 -0
  32. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/orangecontrib/IO4IT/widgets/icons/upload.png +0 -0
  33. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/orangecontrib/IO4IT/widgets/icons/wordpdf2docx.png +0 -0
  34. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/orangecontrib/__init__.py +0 -0
  35. {io4it-0.0.0.12 → io4it-0.0.0.12.2}/setup.cfg +0 -0
@@ -1,6 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: io4it
3
- Version: 0.0.0.12
3
+ Version: 0.0.0.12.2
4
+ Summary: Package nécessitant PyTorch CUDA. Après installation, exécutez:
4
5
  Home-page:
5
6
  Author:
6
7
  Author-email:
@@ -1,6 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: io4it
3
- Version: 0.0.0.12
3
+ Version: 0.0.0.12.2
4
+ Summary: Package nécessitant PyTorch CUDA. Après installation, exécutez:
4
5
  Home-page:
5
6
  Author:
6
7
  Author-email:
@@ -0,0 +1,327 @@
1
+ import os
2
+ import win32com.client
3
+ from pathlib import Path
4
+ import pathlib
5
+ import tempfile
6
+ import shutil
7
+ import time
8
+ import pythoncom
9
+ import fitz
10
+
11
+ if "site-packages/Orange/widgets" in os.path.dirname(os.path.abspath(__file__)).replace("\\", "/"):
12
+ from Orange.widgets.orangecontrib.AAIT.utils.MetManagement import get_local_store_path,reset_folder
13
+ else:
14
+ from orangecontrib.AAIT.utils.MetManagement import get_local_store_path,reset_folder
15
+
16
+ def enable_long_path(path):
17
+ """Simplifie la gestion des chemins longs sous Windows."""
18
+ return pathlib.Path(r"\\?\\" + str(path))
19
+
20
+ def convert_pdf_structure(input_dir: str, output_dir: str,ignore_exsting_out_put=False,progress_callback=None):
21
+ """
22
+ return a string with log in case of error
23
+ Recursively lists all .pdf and .PDF files in the input directory,
24
+ replicates the folder structure in the output directory, and
25
+ creates empty .docx files with the same names.
26
+
27
+ Parameters:
28
+ input_dir (str): Path to the input directory containing PDF files.
29
+ output_dir (str): Path to the output directory where DOCX files will be created.
30
+ """
31
+ error_log=""
32
+ if os.name != 'nt':
33
+ error_log="version developped for windows computer "
34
+ return error_log
35
+
36
+ nbre_file = 0
37
+ for i, data in enumerate(input_dir):
38
+ input_path = Path(str(input_dir[i]))
39
+ for pdf_file in input_path.rglob("*.pdf"):
40
+ nbre_file += 1
41
+
42
+ k = 1
43
+ for i, data in enumerate(input_dir):
44
+ input_path = Path(str(input_dir[i]))
45
+ output_path = Path(str(output_dir[i]))
46
+
47
+ if not input_path.exists() or not input_path.is_dir():
48
+ print(f"Error: The input directory '{input_dir}' does not exist or is not a directory.")
49
+ return f"Error: The input directory '{input_dir}' does not exist or is not a directory. "
50
+
51
+ for pdf_file in input_path.rglob("*.pdf"): # Recursively search for .pdf and .PDF files
52
+ relative_path = pdf_file.relative_to(input_path) # Get relative path from input root
53
+ new_file_path = output_path / relative_path.with_suffix(".docx") # Change extension to .docx
54
+
55
+
56
+
57
+ if ignore_exsting_out_put:
58
+ if os.path.exists(enable_long_path(str(new_file_path))):
59
+ print("ignoring",pdf_file)
60
+ continue
61
+
62
+
63
+ if 0!= convert_pdf_with_temp(str(pdf_file),str(new_file_path)):#convert_pdf_with_temp #convert_pdf_to_docx
64
+ if error_log!="":
65
+ error_log+="\n"
66
+ error_log+="error -> "+str(pdf_file)
67
+ return error_log # a supprimer
68
+ if progress_callback is not None:
69
+ progress_value = float(100 * (k) / nbre_file)
70
+ k += 1
71
+ progress_callback(progress_value)
72
+ # purge temp dir if everithing is ok
73
+ if error_log=="":
74
+ reset_folder(get_local_store_path() + "temp_word_conversion/", attempts=10, delay=0.05, recreate=False)
75
+ return error_log
76
+
77
+
78
+
79
+
80
+ def convert_pdf_to_docx(pdf_path, docx_path):
81
+ """
82
+ Convertit un fichier PDF en DOCX en utilisant Microsoft Word.
83
+
84
+ Args:
85
+ pdf_path (str): Chemin du fichier PDF source.
86
+ docx_path (str): Chemin du fichier DOCX de destination.
87
+
88
+ Returns:
89
+ int: 0 si la conversion a réussi, 1 en cas d'échec.
90
+ """
91
+ if not os.path.exists(pdf_path):
92
+ print(f"Erreur : Le fichier {pdf_path} n'existe pas.")
93
+ return 1
94
+
95
+ try:
96
+ # Initialiser COM
97
+ pythoncom.CoInitialize()
98
+
99
+ # Lancer Word
100
+ word = win32com.client.Dispatch("Word.Application")
101
+ word.DisplayAlerts = 0 # Désactiver les alertes
102
+ word.Visible = True # Mettre à True pour voir Word en action
103
+ print(f"Conversion de {pdf_path} en {docx_path}...")
104
+
105
+ # Ouvrir le PDF en lecture seule
106
+ doc = word.Documents.Open(pdf_path, ReadOnly=True, ConfirmConversions=False)
107
+
108
+ # Sauvegarder en DOCX
109
+ doc.SaveAs(docx_path, FileFormat=16) # 16 = wdFormatDocumentDefault
110
+ doc.Close(False)
111
+
112
+ print(f"Conversion réussie : {docx_path}")
113
+ return 0
114
+
115
+ except Exception as e:
116
+ print(f"Erreur lors de la conversion : {e}")
117
+ return 1
118
+
119
+ finally:
120
+ if 'word' in locals():
121
+ word.Quit()
122
+
123
+ # Libérer COM
124
+ pythoncom.CoUninitialize()
125
+
126
+
127
+ def wait_for_file_access(file_path, timeout=10, interval=0.5):
128
+ """
129
+ Attendre que le fichier soit accessible en lecture/écriture.
130
+
131
+ Args:
132
+ file_path (str): Chemin du fichier à vérifier.
133
+ timeout (int): Temps max en secondes avant d'abandonner.
134
+ interval (float): Temps d'attente entre chaque vérification.
135
+
136
+ Returns:
137
+ bool: True si le fichier est accessible, False sinon.
138
+ """
139
+ start_time = time.time()
140
+
141
+ while time.time() - start_time < timeout:
142
+ if os.path.exists(file_path) and os.access(file_path, os.R_OK | os.W_OK):
143
+ try:
144
+ with open(file_path, "a"):
145
+ pass # Test d'ouverture en écriture
146
+ return True
147
+ except IOError:
148
+ pass
149
+
150
+ time.sleep(interval) # Attendre avant de réessayer
151
+
152
+ print(f"Erreur : Le fichier {file_path} est verrouillé ou inaccessible.")
153
+ return False
154
+
155
+
156
+
157
+
158
+ def is_pdf_a4(pdf_path: str) -> bool:
159
+ """
160
+ Vérifie si toutes les pages du PDF sont au format A4.
161
+ Retourne True si toutes les pages sont en A4, False sinon.
162
+ """
163
+ A4_WIDTH_PTS = 595 # Largeur A4 en points (approx. 210mm)
164
+ A4_HEIGHT_PTS = 842 # Hauteur A4 en points (approx. 297mm)
165
+ TOLERANCE = 5 # Marge de tolérance en points
166
+
167
+ try:
168
+ doc = fitz.open(pdf_path)
169
+ if len(doc) == 0:
170
+ return False
171
+
172
+ for page in doc:
173
+ width, height = page.rect.width, page.rect.height
174
+ if not (
175
+ (abs(width - A4_WIDTH_PTS) <= TOLERANCE and abs(height - A4_HEIGHT_PTS) <= TOLERANCE) or
176
+ (abs(width - A4_HEIGHT_PTS) <= TOLERANCE and abs(height - A4_WIDTH_PTS) <= TOLERANCE)
177
+ ):
178
+ return False
179
+ except Exception as e:
180
+ print("is A4?",e)
181
+ return False
182
+ return True
183
+
184
+ def convert_pdf_to_a4(input_pdf, output_pdf):
185
+ try:
186
+ # Dimensions A4 en points
187
+ a4_width, a4_height = fitz.paper_size("a4")
188
+ doc = fitz.open(input_pdf)
189
+ new_doc = fitz.open()
190
+
191
+ for page in doc:
192
+ page_w, page_h = page.rect.width, page.rect.height
193
+
194
+ # Si la page est déjà en A4 (tolérance de 1 point)
195
+ if abs(page_w - a4_width) < 1 and abs(page_h - a4_height) < 1:
196
+ new_doc.insert_pdf(doc, from_page=page.number, to_page=page.number)
197
+ continue
198
+
199
+ # Définition de la transformation selon l'orientation de la page
200
+ if page_w > page_h: # Paysage
201
+ # Après rotation, les dimensions seront inversées (largeur <-> hauteur)
202
+ effective_scale = min(a4_width / page_h, a4_height / page_w)
203
+ matrix = fitz.Matrix(effective_scale, effective_scale)
204
+ # Rotation de 90° et translation pour repositionner le contenu
205
+ matrix = matrix.prerotate(90).pretranslate(page_h * effective_scale, 0)
206
+ else: # Portrait
207
+ effective_scale = min(a4_width / page_w, a4_height / page_h)
208
+ matrix = fitz.Matrix(effective_scale, effective_scale)
209
+
210
+ # Générer le pixmap à la résolution finale souhaitée
211
+ pix = page.get_pixmap(matrix=matrix)
212
+
213
+ # Calcul du centrage sur la page A4
214
+ new_img_w, new_img_h = pix.width, pix.height
215
+ x_offset = (a4_width - new_img_w) / 2
216
+ y_offset = (a4_height - new_img_h) / 2
217
+
218
+ # Créer la nouvelle page et y insérer l'image
219
+ new_page = new_doc.new_page(width=a4_width, height=a4_height)
220
+ new_page.insert_image(
221
+ fitz.Rect(x_offset, y_offset, x_offset + new_img_w, y_offset + new_img_h),
222
+ pixmap=pix
223
+ )
224
+ new_doc.save(output_pdf)
225
+ new_doc.close()
226
+ doc.close()
227
+ return 0
228
+ except:
229
+ return 1
230
+
231
+
232
+ def write_two_strings_to_file(file_path: str,string1: str, string2: str):
233
+ """
234
+ Writes two strings to a file, one per line, handling errors gracefully.
235
+
236
+ :param string1: The first string to write.
237
+ :param string2: The second string to write.
238
+ :param file_path: The path where the file should be saved.
239
+ """
240
+ try:
241
+ file = open(file_path, 'w', encoding='utf-8')
242
+ file.write(string1 + "\n")
243
+ file.write(string2 )
244
+ print(f"Successfully written to {file_path}")
245
+ except IOError as e:
246
+ print(f"Error writing to file: {e}")
247
+ return 1
248
+ except Exception as e:
249
+ print(f"An unexpected error occurred: {e}")
250
+ return 1
251
+ finally:
252
+ file.close()
253
+ return 0
254
+
255
+
256
+ def convert_pdf_with_temp(temp_pdf, output_path):
257
+ """
258
+ Copie le PDF source dans un dossier temporaire, le convertit en DOCX,
259
+ puis copie le fichier résultant vers le chemin de sortie spécifié,
260
+ en gérant les chemins longs.
261
+ """
262
+ pdf_path = enable_long_path(os.path.abspath(temp_pdf))
263
+ output_path = enable_long_path(os.path.abspath(output_path))
264
+ output_dir = output_path.parent
265
+
266
+ if not pdf_path.exists():
267
+ print(f"Le fichier {pdf_path} n'existe pas.")
268
+ return 1
269
+
270
+ # Créer le dossier de sortie s'il n'existe pas
271
+ if not output_dir.exists():
272
+ output_dir.mkdir(parents=True, exist_ok=True)
273
+
274
+ try:
275
+ dest_dir = get_local_store_path() + "temp_word_conversion/"
276
+ if 0 != reset_folder(dest_dir, attempts=10, delay=0.05):
277
+ print("impossible to reset " + dest_dir)
278
+ return 1
279
+ # Création du dossier temporaire
280
+ temp_pdf = os.path.join(dest_dir, "input_toto.pdf")
281
+ temp_docx = os.path.join(dest_dir, "input_toto.docx")
282
+
283
+ print(dest_dir+"conversion_en_cours.txt")
284
+ print("######################################")
285
+ if 0!=write_two_strings_to_file(dest_dir+"conversion_en_cours.txt",str(pdf_path),str(output_path)):
286
+ print("error writing ",dest_dir+"conversion_en_cours.txt")
287
+ return 1
288
+ # Copie du fichier source vers le dossier temporaire
289
+ shutil.copy2(pdf_path, temp_pdf)
290
+ wait_for_file_access(temp_pdf)
291
+
292
+ if is_pdf_a4(temp_pdf)==False:
293
+ temp_pdf2 = os.path.join(dest_dir, "input_totoA4.pdf")
294
+ if 0!=convert_pdf_to_a4(temp_pdf,temp_pdf2):
295
+ print("erreur au resize du pdf")
296
+ return 1
297
+ temp_pdf=temp_pdf2
298
+ wait_for_file_access(temp_pdf)
299
+ time.sleep(1)
300
+ result=0
301
+ # Conversion du PDF en DOCX
302
+ for _ in range(4):
303
+ time.sleep(1)
304
+ result = convert_pdf_to_docx(str(temp_pdf), str(temp_docx))
305
+ if result==0:
306
+ break
307
+ if result == 0:
308
+ # Copie du fichier converti vers la destination finale
309
+ shutil.copy2(temp_docx, output_path)
310
+ print(f"recopie réussie : {output_path}")
311
+
312
+ # Supprimer les fichiers temporaires après le déplacement
313
+ # if temp_docx.exists():
314
+ # temp_docx.unlink()
315
+ # if temp_pdf.exists():
316
+ # temp_pdf.unlink()
317
+ return 0
318
+ else:
319
+ print("Erreur lors de la conversion.")
320
+ return 1
321
+
322
+ except Exception as e:
323
+ print(f"Erreur : {e}")
324
+ return 1
325
+
326
+
327
+
@@ -1,12 +1,35 @@
1
1
  from setuptools import setup, find_packages
2
2
  import platform
3
3
  import warnings
4
+ # orangecontrib/IO4IT/__init__.py
5
+ from setuptools import setup, find_packages
6
+ import sys
7
+ import subprocess
8
+ from setuptools.command.install import install
9
+
10
+ class PostInstallCommand(install):
11
+ """Post-installation pour l'installation GPU."""
12
+ def run(self):
13
+ install.run(self) # Installation normale
14
+
15
+ # Tentative d'installation PyTorch CUDA après l'installation principale
16
+ try:
17
+ subprocess.check_call([
18
+ sys.executable, '-m', 'pip', 'install',
19
+ 'torch', 'torchvision', 'torchaudio',
20
+ '--index-url', 'https://download.pytorch.org/whl/cu126'
21
+ ])
22
+ print("\n\n✅ PyTorch CUDA installé avec succès")
23
+ except subprocess.CalledProcessError:
24
+ print("\n\n⚠️ IMPORTANT : Pour l'accélération GPU, exécutez MANUELLEMENT:")
25
+ print("pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126")
26
+
4
27
 
5
28
  # Nom du package PyPI ('pip install NAME')
6
29
  NAME = "io4it"
7
30
 
8
31
  # Version du package PyPI
9
- VERSION = "0.0.0.12" # la version doit être supérieure à la précédente sinon la publication sera refusée
32
+ VERSION = "0.0.0.12.2" # la version doit être supérieure à la précédente sinon la publication sera refusée
10
33
 
11
34
  # Facultatif / Adaptable à souhait
12
35
  AUTHOR = ""
@@ -33,9 +56,20 @@ PACKAGE_DATA = {
33
56
  # /!\ les noms de fichier 'orangecontrib.hkh_bot.widgets' doivent correspondre à l'arborescence
34
57
 
35
58
  # Dépendances
36
- INSTALL_REQUIRES = ["boto3", "docling", "docling-core", "speechbrain", "whisper", "whisper-openai", "pyannote.audio", "pyannote.core", "wave", "scikit-learn"]
37
-
38
-
59
+ # Dans votre setup.py principal
60
+ INSTALL_REQUIRES = [
61
+ "boto3",
62
+ "docling",
63
+ "docling-core",
64
+ "speechbrain",
65
+ "whisper",
66
+ "whisper-openai",
67
+ "pyannote.audio",
68
+ "pyannote.core",
69
+ "wave",
70
+ "scikit-learn",
71
+ # NE PAS inclure torch ici
72
+ ]
39
73
 
40
74
  # Extras optionnels pour CUDA
41
75
  EXTRAS_REQUIRE = {
@@ -72,7 +106,6 @@ setup(
72
106
  author=AUTHOR,
73
107
  author_email=AUTHOR_EMAIL,
74
108
  url=URL,
75
- description=DESCRIPTION,
76
109
  license=LICENSE,
77
110
  keywords=KEYWORDS,
78
111
  packages=PACKAGES,
@@ -81,5 +114,11 @@ setup(
81
114
  extras_require=EXTRAS_REQUIRE,
82
115
  entry_points=ENTRY_POINTS,
83
116
  namespace_packages=NAMESPACE_PACKAGES,
117
+ description="Package nécessitant PyTorch CUDA. Après installation, exécutez:\n"
118
+ "pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126",
119
+
120
+ cmdclass={
121
+ 'install': PostInstallCommand,
122
+ },
84
123
  )
85
124
 
@@ -1,654 +0,0 @@
1
- import os
2
- import win32com.client
3
- from pathlib import Path
4
- import pathlib
5
- import tempfile
6
- import shutil
7
- import time
8
- import pythoncom
9
- import fitz
10
-
11
- if "site-packages/Orange/widgets" in os.path.dirname(os.path.abspath(__file__)).replace("\\", "/"):
12
- from Orange.widgets.orangecontrib.AAIT.utils.MetManagement import get_local_store_path,reset_folder
13
- else:
14
- from orangecontrib.AAIT.utils.MetManagement import get_local_store_path,reset_folder
15
-
16
- def enable_long_path(path):
17
- """Simplifie la gestion des chemins longs sous Windows."""
18
- return pathlib.Path(r"\\?\\" + str(path))
19
-
20
- def convert_pdf_structure(input_dir: str, output_dir: str,ignore_exsting_out_put=False,progress_callback=None):
21
- """
22
- return a string with log in case of error
23
- Recursively lists all .pdf and .PDF files in the input directory,
24
- replicates the folder structure in the output directory, and
25
- creates empty .docx files with the same names.
26
-
27
- Parameters:
28
- input_dir (str): Path to the input directory containing PDF files.
29
- output_dir (str): Path to the output directory where DOCX files will be created.
30
- """
31
- error_log=""
32
- if os.name != 'nt':
33
- error_log="version developped for windows computer "
34
- return error_log
35
-
36
- nbre_file = 0
37
- for i, data in enumerate(input_dir):
38
- input_path = Path(str(input_dir[i]))
39
- for pdf_file in input_path.rglob("*.pdf"):
40
- nbre_file += 1
41
-
42
- k = 1
43
- for i, data in enumerate(input_dir):
44
- input_path = Path(str(input_dir[i]))
45
- output_path = Path(str(output_dir[i]))
46
-
47
- if not input_path.exists() or not input_path.is_dir():
48
- print(f"Error: The input directory '{input_dir}' does not exist or is not a directory.")
49
- return f"Error: The input directory '{input_dir}' does not exist or is not a directory. "
50
-
51
- for pdf_file in input_path.rglob("*.pdf"): # Recursively search for .pdf and .PDF files
52
- relative_path = pdf_file.relative_to(input_path) # Get relative path from input root
53
- new_file_path = output_path / relative_path.with_suffix(".docx") # Change extension to .docx
54
-
55
-
56
-
57
- if ignore_exsting_out_put:
58
- if os.path.exists(enable_long_path(str(new_file_path))):
59
- print("ignoring",pdf_file)
60
- continue
61
-
62
-
63
- if 0!= convert_pdf_with_temp(str(pdf_file),str(new_file_path)):#convert_pdf_with_temp #convert_pdf_to_docx
64
- if error_log!="":
65
- error_log+="\n"
66
- error_log+="error -> "+str(pdf_file)
67
- return error_log # a supprimer
68
- if progress_callback is not None:
69
- progress_value = float(100 * (k) / nbre_file)
70
- k += 1
71
- progress_callback(progress_value)
72
- # purge temp dir if everithing is ok
73
- if error_log=="":
74
- reset_folder(get_local_store_path() + "temp_word_conversion/", attempts=10, delay=0.05, recreate=False)
75
- return error_log
76
-
77
-
78
-
79
-
80
- def convert_pdf_to_docx(pdf_path, docx_path):
81
- """
82
- Convertit un fichier PDF en DOCX en utilisant Microsoft Word.
83
-
84
- Args:
85
- pdf_path (str): Chemin du fichier PDF source.
86
- docx_path (str): Chemin du fichier DOCX de destination.
87
-
88
- Returns:
89
- int: 0 si la conversion a réussi, 1 en cas d'échec.
90
- """
91
- if not os.path.exists(pdf_path):
92
- print(f"Erreur : Le fichier {pdf_path} n'existe pas.")
93
- return 1
94
-
95
- try:
96
- # Initialiser COM
97
- pythoncom.CoInitialize()
98
-
99
- # Lancer Word
100
- word = win32com.client.Dispatch("Word.Application")
101
- word.DisplayAlerts = 0 # Désactiver les alertes
102
- word.Visible = True # Mettre à True pour voir Word en action
103
- print(f"Conversion de {pdf_path} en {docx_path}...")
104
-
105
- # Ouvrir le PDF en lecture seule
106
- doc = word.Documents.Open(pdf_path, ReadOnly=True, ConfirmConversions=False)
107
-
108
- # Sauvegarder en DOCX
109
- doc.SaveAs(docx_path, FileFormat=16) # 16 = wdFormatDocumentDefault
110
- doc.Close(False)
111
-
112
- print(f"Conversion réussie : {docx_path}")
113
- return 0
114
-
115
- except Exception as e:
116
- print(f"Erreur lors de la conversion : {e}")
117
- return 1
118
-
119
- finally:
120
- if 'word' in locals():
121
- word.Quit()
122
-
123
- # Libérer COM
124
- pythoncom.CoUninitialize()
125
-
126
-
127
- def wait_for_file_access(file_path, timeout=10, interval=0.5):
128
- """
129
- Attendre que le fichier soit accessible en lecture/écriture.
130
-
131
- Args:
132
- file_path (str): Chemin du fichier à vérifier.
133
- timeout (int): Temps max en secondes avant d'abandonner.
134
- interval (float): Temps d'attente entre chaque vérification.
135
-
136
- Returns:
137
- bool: True si le fichier est accessible, False sinon.
138
- """
139
- start_time = time.time()
140
-
141
- while time.time() - start_time < timeout:
142
- if os.path.exists(file_path) and os.access(file_path, os.R_OK | os.W_OK):
143
- try:
144
- with open(file_path, "a"):
145
- pass # Test d'ouverture en écriture
146
- return True
147
- except IOError:
148
- pass
149
-
150
- time.sleep(interval) # Attendre avant de réessayer
151
-
152
- print(f"Erreur : Le fichier {file_path} est verrouillé ou inaccessible.")
153
- return False
154
-
155
-
156
-
157
-
158
- def is_pdf_a4(pdf_path: str) -> bool:
159
- """
160
- Vérifie si toutes les pages du PDF sont au format A4.
161
- Retourne True si toutes les pages sont en A4, False sinon.
162
- """
163
- A4_WIDTH_PTS = 595 # Largeur A4 en points (approx. 210mm)
164
- A4_HEIGHT_PTS = 842 # Hauteur A4 en points (approx. 297mm)
165
- TOLERANCE = 5 # Marge de tolérance en points
166
-
167
- try:
168
- doc = fitz.open(pdf_path)
169
- if len(doc) == 0:
170
- return False
171
-
172
- for page in doc:
173
- width, height = page.rect.width, page.rect.height
174
- if not (
175
- (abs(width - A4_WIDTH_PTS) <= TOLERANCE and abs(height - A4_HEIGHT_PTS) <= TOLERANCE) or
176
- (abs(width - A4_HEIGHT_PTS) <= TOLERANCE and abs(height - A4_WIDTH_PTS) <= TOLERANCE)
177
- ):
178
- return False
179
- except Exception as e:
180
- print("is A4?",e)
181
- return False
182
- return True
183
-
184
- def convert_pdf_to_a4(input_pdf, output_pdf):
185
- try:
186
- # Dimensions A4 en points
187
- a4_width, a4_height = fitz.paper_size("a4")
188
- doc = fitz.open(input_pdf)
189
- new_doc = fitz.open()
190
-
191
- for page in doc:
192
- page_w, page_h = page.rect.width, page.rect.height
193
-
194
- # Si la page est déjà en A4 (tolérance de 1 point)
195
- if abs(page_w - a4_width) < 1 and abs(page_h - a4_height) < 1:
196
- new_doc.insert_pdf(doc, from_page=page.number, to_page=page.number)
197
- continue
198
-
199
- # Définition de la transformation selon l'orientation de la page
200
- if page_w > page_h: # Paysage
201
- # Après rotation, les dimensions seront inversées (largeur <-> hauteur)
202
- effective_scale = min(a4_width / page_h, a4_height / page_w)
203
- matrix = fitz.Matrix(effective_scale, effective_scale)
204
- # Rotation de 90° et translation pour repositionner le contenu
205
- matrix = matrix.prerotate(90).pretranslate(page_h * effective_scale, 0)
206
- else: # Portrait
207
- effective_scale = min(a4_width / page_w, a4_height / page_h)
208
- matrix = fitz.Matrix(effective_scale, effective_scale)
209
-
210
- # Générer le pixmap à la résolution finale souhaitée
211
- pix = page.get_pixmap(matrix=matrix)
212
-
213
- # Calcul du centrage sur la page A4
214
- new_img_w, new_img_h = pix.width, pix.height
215
- x_offset = (a4_width - new_img_w) / 2
216
- y_offset = (a4_height - new_img_h) / 2
217
-
218
- # Créer la nouvelle page et y insérer l'image
219
- new_page = new_doc.new_page(width=a4_width, height=a4_height)
220
- new_page.insert_image(
221
- fitz.Rect(x_offset, y_offset, x_offset + new_img_w, y_offset + new_img_h),
222
- pixmap=pix
223
- )
224
- new_doc.save(output_pdf)
225
- new_doc.close()
226
- doc.close()
227
- return 0
228
- except:
229
- return 1
230
-
231
-
232
- def write_two_strings_to_file(file_path: str,string1: str, string2: str):
233
- """
234
- Writes two strings to a file, one per line, handling errors gracefully.
235
-
236
- :param string1: The first string to write.
237
- :param string2: The second string to write.
238
- :param file_path: The path where the file should be saved.
239
- """
240
- try:
241
- file = open(file_path, 'w', encoding='utf-8')
242
- file.write(string1 + "\n")
243
- file.write(string2 )
244
- print(f"Successfully written to {file_path}")
245
- except IOError as e:
246
- print(f"Error writing to file: {e}")
247
- return 1
248
- except Exception as e:
249
- print(f"An unexpected error occurred: {e}")
250
- return 1
251
- finally:
252
- file.close()
253
- return 0
254
-
255
-
256
- def convert_pdf_with_temp(temp_pdf, output_path):
257
- """
258
- Copie le PDF source dans un dossier temporaire, le convertit en DOCX,
259
- puis copie le fichier résultant vers le chemin de sortie spécifié,
260
- en gérant les chemins longs.
261
- """
262
- pdf_path = enable_long_path(os.path.abspath(temp_pdf))
263
- output_path = enable_long_path(os.path.abspath(output_path))
264
- output_dir = output_path.parent
265
-
266
- if not pdf_path.exists():
267
- print(f"Le fichier {pdf_path} n'existe pas.")
268
- return 1
269
-
270
- # Créer le dossier de sortie s'il n'existe pas
271
- if not output_dir.exists():
272
- output_dir.mkdir(parents=True, exist_ok=True)
273
-
274
- try:
275
- dest_dir = get_local_store_path() + "temp_word_conversion/"
276
- if 0 != reset_folder(dest_dir, attempts=10, delay=0.05):
277
- print("impossible to reset " + dest_dir)
278
- return 1
279
- # Création du dossier temporaire
280
- temp_pdf = os.path.join(dest_dir, "input_toto.pdf")
281
- temp_docx = os.path.join(dest_dir, "input_toto.docx")
282
-
283
- print(dest_dir+"conversion_en_cours.txt")
284
- print("######################################")
285
- if 0!=write_two_strings_to_file(dest_dir+"conversion_en_cours.txt",str(pdf_path),str(output_path)):
286
- print("error writing ",dest_dir+"conversion_en_cours.txt")
287
- return 1
288
- # Copie du fichier source vers le dossier temporaire
289
- shutil.copy2(pdf_path, temp_pdf)
290
- wait_for_file_access(temp_pdf)
291
-
292
- if is_pdf_a4(temp_pdf)==False:
293
- temp_pdf2 = os.path.join(dest_dir, "input_totoA4.pdf")
294
- if 0!=convert_pdf_to_a4(temp_pdf,temp_pdf2):
295
- print("erreur au resize du pdf")
296
- return 1
297
- temp_pdf=temp_pdf2
298
- wait_for_file_access(temp_pdf)
299
- time.sleep(1)
300
- result=0
301
- # Conversion du PDF en DOCX
302
- for _ in range(4):
303
- time.sleep(1)
304
- result = convert_pdf_to_docx(str(temp_pdf), str(temp_docx))
305
- if result==0:
306
- break
307
- if result == 0:
308
- # Copie du fichier converti vers la destination finale
309
- shutil.copy2(temp_docx, output_path)
310
- print(f"recopie réussie : {output_path}")
311
-
312
- # Supprimer les fichiers temporaires après le déplacement
313
- # if temp_docx.exists():
314
- # temp_docx.unlink()
315
- # if temp_pdf.exists():
316
- # temp_pdf.unlink()
317
- return 0
318
- else:
319
- print("Erreur lors de la conversion.")
320
- return 1
321
-
322
- except Exception as e:
323
- print(f"Erreur : {e}")
324
- return 1
325
-
326
-
327
-
328
- import os
329
- import win32com.client
330
- from pathlib import Path
331
- import pathlib
332
- import tempfile
333
- import shutil
334
- import time
335
- import pythoncom
336
- import fitz
337
-
338
- if "site-packages/Orange/widgets" in os.path.dirname(os.path.abspath(__file__)).replace("\\", "/"):
339
- from Orange.widgets.orangecontrib.AAIT.utils.MetManagement import get_local_store_path,reset_folder
340
- else:
341
- from orangecontrib.AAIT.utils.MetManagement import get_local_store_path,reset_folder
342
-
343
- def enable_long_path(path):
344
- """Simplifie la gestion des chemins longs sous Windows."""
345
- return pathlib.Path(r"\\?\\" + str(path))
346
-
347
- def convert_pdf_structure(input_dir: str, output_dir: str,ignore_exsting_out_put=False,progress_callback=None):
348
- """
349
- return a string with log in case of error
350
- Recursively lists all .pdf and .PDF files in the input directory,
351
- replicates the folder structure in the output directory, and
352
- creates empty .docx files with the same names.
353
-
354
- Parameters:
355
- input_dir (str): Path to the input directory containing PDF files.
356
- output_dir (str): Path to the output directory where DOCX files will be created.
357
- """
358
- error_log=""
359
- if os.name != 'nt':
360
- error_log="version developped for windows computer "
361
- return error_log
362
-
363
- nbre_file = 0
364
- for i, data in enumerate(input_dir):
365
- input_path = Path(str(input_dir[i]))
366
- for pdf_file in input_path.rglob("*.pdf"):
367
- nbre_file += 1
368
-
369
- k = 1
370
- for i, data in enumerate(input_dir):
371
- input_path = Path(str(input_dir[i]))
372
- output_path = Path(str(output_dir[i]))
373
-
374
- if not input_path.exists() or not input_path.is_dir():
375
- print(f"Error: The input directory '{input_dir}' does not exist or is not a directory.")
376
- return f"Error: The input directory '{input_dir}' does not exist or is not a directory. "
377
-
378
- for pdf_file in input_path.rglob("*.pdf"): # Recursively search for .pdf and .PDF files
379
- relative_path = pdf_file.relative_to(input_path) # Get relative path from input root
380
- new_file_path = output_path / relative_path.with_suffix(".docx") # Change extension to .docx
381
-
382
-
383
-
384
- if ignore_exsting_out_put:
385
- if os.path.exists(enable_long_path(str(new_file_path))):
386
- print("ignoring",pdf_file)
387
- continue
388
-
389
-
390
- if 0!= convert_pdf_with_temp(str(pdf_file),str(new_file_path)):#convert_pdf_with_temp #convert_pdf_to_docx
391
- if error_log!="":
392
- error_log+="\n"
393
- error_log+="error -> "+str(pdf_file)
394
- return error_log # a supprimer
395
- if progress_callback is not None:
396
- progress_value = float(100 * (k) / nbre_file)
397
- k += 1
398
- progress_callback(progress_value)
399
- # purge temp dir if everithing is ok
400
- if error_log=="":
401
- reset_folder(get_local_store_path() + "temp_word_conversion/", attempts=10, delay=0.05, recreate=False)
402
- return error_log
403
-
404
-
405
-
406
-
407
- def convert_pdf_to_docx(pdf_path, docx_path):
408
- """
409
- Convertit un fichier PDF en DOCX en utilisant Microsoft Word.
410
-
411
- Args:
412
- pdf_path (str): Chemin du fichier PDF source.
413
- docx_path (str): Chemin du fichier DOCX de destination.
414
-
415
- Returns:
416
- int: 0 si la conversion a réussi, 1 en cas d'échec.
417
- """
418
- if not os.path.exists(pdf_path):
419
- print(f"Erreur : Le fichier {pdf_path} n'existe pas.")
420
- return 1
421
-
422
- try:
423
- # Initialiser COM
424
- pythoncom.CoInitialize()
425
-
426
- # Lancer Word
427
- word = win32com.client.Dispatch("Word.Application")
428
- word.DisplayAlerts = 0 # Désactiver les alertes
429
- word.Visible = True # Mettre à True pour voir Word en action
430
- print(f"Conversion de {pdf_path} en {docx_path}...")
431
-
432
- # Ouvrir le PDF en lecture seule
433
- doc = word.Documents.Open(pdf_path, ReadOnly=True, ConfirmConversions=False)
434
-
435
- # Sauvegarder en DOCX
436
- doc.SaveAs(docx_path, FileFormat=16) # 16 = wdFormatDocumentDefault
437
- doc.Close(False)
438
-
439
- print(f"Conversion réussie : {docx_path}")
440
- return 0
441
-
442
- except Exception as e:
443
- print(f"Erreur lors de la conversion : {e}")
444
- return 1
445
-
446
- finally:
447
- if 'word' in locals():
448
- word.Quit()
449
-
450
- # Libérer COM
451
- pythoncom.CoUninitialize()
452
-
453
-
454
- def wait_for_file_access(file_path, timeout=10, interval=0.5):
455
- """
456
- Attendre que le fichier soit accessible en lecture/écriture.
457
-
458
- Args:
459
- file_path (str): Chemin du fichier à vérifier.
460
- timeout (int): Temps max en secondes avant d'abandonner.
461
- interval (float): Temps d'attente entre chaque vérification.
462
-
463
- Returns:
464
- bool: True si le fichier est accessible, False sinon.
465
- """
466
- start_time = time.time()
467
-
468
- while time.time() - start_time < timeout:
469
- if os.path.exists(file_path) and os.access(file_path, os.R_OK | os.W_OK):
470
- try:
471
- with open(file_path, "a"):
472
- pass # Test d'ouverture en écriture
473
- return True
474
- except IOError:
475
- pass
476
-
477
- time.sleep(interval) # Attendre avant de réessayer
478
-
479
- print(f"Erreur : Le fichier {file_path} est verrouillé ou inaccessible.")
480
- return False
481
-
482
-
483
-
484
-
485
- def is_pdf_a4(pdf_path: str) -> bool:
486
- """
487
- Vérifie si toutes les pages du PDF sont au format A4.
488
- Retourne True si toutes les pages sont en A4, False sinon.
489
- """
490
- A4_WIDTH_PTS = 595 # Largeur A4 en points (approx. 210mm)
491
- A4_HEIGHT_PTS = 842 # Hauteur A4 en points (approx. 297mm)
492
- TOLERANCE = 5 # Marge de tolérance en points
493
-
494
- try:
495
- doc = fitz.open(pdf_path)
496
- if len(doc) == 0:
497
- return False
498
-
499
- for page in doc:
500
- width, height = page.rect.width, page.rect.height
501
- if not (
502
- (abs(width - A4_WIDTH_PTS) <= TOLERANCE and abs(height - A4_HEIGHT_PTS) <= TOLERANCE) or
503
- (abs(width - A4_HEIGHT_PTS) <= TOLERANCE and abs(height - A4_WIDTH_PTS) <= TOLERANCE)
504
- ):
505
- return False
506
- except Exception as e:
507
- print("is A4?",e)
508
- return False
509
- return True
510
-
511
- def convert_pdf_to_a4(input_pdf, output_pdf):
512
- try:
513
- # Dimensions A4 en points
514
- a4_width, a4_height = fitz.paper_size("a4")
515
- doc = fitz.open(input_pdf)
516
- new_doc = fitz.open()
517
-
518
- for page in doc:
519
- page_w, page_h = page.rect.width, page.rect.height
520
-
521
- # Si la page est déjà en A4 (tolérance de 1 point)
522
- if abs(page_w - a4_width) < 1 and abs(page_h - a4_height) < 1:
523
- new_doc.insert_pdf(doc, from_page=page.number, to_page=page.number)
524
- continue
525
-
526
- # Définition de la transformation selon l'orientation de la page
527
- if page_w > page_h: # Paysage
528
- # Après rotation, les dimensions seront inversées (largeur <-> hauteur)
529
- effective_scale = min(a4_width / page_h, a4_height / page_w)
530
- matrix = fitz.Matrix(effective_scale, effective_scale)
531
- # Rotation de 90° et translation pour repositionner le contenu
532
- matrix = matrix.prerotate(90).pretranslate(page_h * effective_scale, 0)
533
- else: # Portrait
534
- effective_scale = min(a4_width / page_w, a4_height / page_h)
535
- matrix = fitz.Matrix(effective_scale, effective_scale)
536
-
537
- # Générer le pixmap à la résolution finale souhaitée
538
- pix = page.get_pixmap(matrix=matrix)
539
-
540
- # Calcul du centrage sur la page A4
541
- new_img_w, new_img_h = pix.width, pix.height
542
- x_offset = (a4_width - new_img_w) / 2
543
- y_offset = (a4_height - new_img_h) / 2
544
-
545
- # Créer la nouvelle page et y insérer l'image
546
- new_page = new_doc.new_page(width=a4_width, height=a4_height)
547
- new_page.insert_image(
548
- fitz.Rect(x_offset, y_offset, x_offset + new_img_w, y_offset + new_img_h),
549
- pixmap=pix
550
- )
551
- new_doc.save(output_pdf)
552
- new_doc.close()
553
- doc.close()
554
- return 0
555
- except:
556
- return 1
557
-
558
-
559
- def write_two_strings_to_file(file_path: str,string1: str, string2: str):
560
- """
561
- Writes two strings to a file, one per line, handling errors gracefully.
562
-
563
- :param string1: The first string to write.
564
- :param string2: The second string to write.
565
- :param file_path: The path where the file should be saved.
566
- """
567
- try:
568
- file = open(file_path, 'w', encoding='utf-8')
569
- file.write(string1 + "\n")
570
- file.write(string2 )
571
- print(f"Successfully written to {file_path}")
572
- except IOError as e:
573
- print(f"Error writing to file: {e}")
574
- return 1
575
- except Exception as e:
576
- print(f"An unexpected error occurred: {e}")
577
- return 1
578
- finally:
579
- file.close()
580
- return 0
581
-
582
-
583
- def convert_pdf_with_temp(temp_pdf, output_path):
584
- """
585
- Copie le PDF source dans un dossier temporaire, le convertit en DOCX,
586
- puis copie le fichier résultant vers le chemin de sortie spécifié,
587
- en gérant les chemins longs.
588
- """
589
- pdf_path = enable_long_path(os.path.abspath(temp_pdf))
590
- output_path = enable_long_path(os.path.abspath(output_path))
591
- output_dir = output_path.parent
592
-
593
- if not pdf_path.exists():
594
- print(f"Le fichier {pdf_path} n'existe pas.")
595
- return 1
596
-
597
- # Créer le dossier de sortie s'il n'existe pas
598
- if not output_dir.exists():
599
- output_dir.mkdir(parents=True, exist_ok=True)
600
-
601
- try:
602
- dest_dir = get_local_store_path() + "temp_word_conversion/"
603
- if 0 != reset_folder(dest_dir, attempts=10, delay=0.05):
604
- print("impossible to reset " + dest_dir)
605
- return 1
606
- # Création du dossier temporaire
607
- temp_pdf = os.path.join(dest_dir, "input_toto.pdf")
608
- temp_docx = os.path.join(dest_dir, "input_toto.docx")
609
-
610
- print(dest_dir+"conversion_en_cours.txt")
611
- print("######################################")
612
- if 0!=write_two_strings_to_file(dest_dir+"conversion_en_cours.txt",str(pdf_path),str(output_path)):
613
- print("error writing ",dest_dir+"conversion_en_cours.txt")
614
- return 1
615
- # Copie du fichier source vers le dossier temporaire
616
- shutil.copy2(pdf_path, temp_pdf)
617
- wait_for_file_access(temp_pdf)
618
-
619
- if is_pdf_a4(temp_pdf)==False:
620
- temp_pdf2 = os.path.join(dest_dir, "input_totoA4.pdf")
621
- if 0!=convert_pdf_to_a4(temp_pdf,temp_pdf2):
622
- print("erreur au resize du pdf")
623
- return 1
624
- temp_pdf=temp_pdf2
625
- wait_for_file_access(temp_pdf)
626
- time.sleep(1)
627
- result=0
628
- # Conversion du PDF en DOCX
629
- for _ in range(4):
630
- time.sleep(1)
631
- result = convert_pdf_to_docx(str(temp_pdf), str(temp_docx))
632
- if result==0:
633
- break
634
- if result == 0:
635
- # Copie du fichier converti vers la destination finale
636
- shutil.copy2(temp_docx, output_path)
637
- print(f"recopie réussie : {output_path}")
638
-
639
- # Supprimer les fichiers temporaires après le déplacement
640
- # if temp_docx.exists():
641
- # temp_docx.unlink()
642
- # if temp_pdf.exists():
643
- # temp_pdf.unlink()
644
- return 0
645
- else:
646
- print("Erreur lors de la conversion.")
647
- return 1
648
-
649
- except Exception as e:
650
- print(f"Erreur : {e}")
651
- return 1
652
-
653
-
654
-
File without changes
File without changes