Script python permettant de traduire un long texte
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

main.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. import PyPDF2
  2. import requests
  3. import json
  4. from reportlab.lib.pagesizes import letter
  5. from reportlab.lib.units import inch
  6. from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
  7. from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
  8. from reportlab.lib.enums import TA_JUSTIFY
  9. from reportlab.pdfbase import pdfmetrics
  10. from reportlab.pdfbase.ttfonts import TTFont
  11. import os
  12. # Configuration
  13. PDF_PATH = "TaniaBorecMemoir(Ukr).pdf" # Fichier original
  14. OLLAMA_MODEL = "traductionUkrainienVersFrancais:latest"
  15. OLLAMA_URL = "http://localhost:11434/api/generate" # URL par défaut d'Ollama
  16. TARGET_LANGUAGE = "français" # Langue cible (ex: "français", "anglais", "allemand", "espagnol", etc.)
  17. OUTPUT_PDF_PATH = PDF_PATH.replace(".pdf", f" ({TARGET_LANGUAGE.upper()[:2]}).pdf") # Chemin du PDF de sortie
  18. # Prompt système personnalisé (instructions pour le LLM)
  19. SYSTEM_PROMPT = """"""
  20. def extract_parameters_from_template(template_str):
  21. """Extrait les paramètres du modèle à partir du template."""
  22. import re
  23. parameters = {}
  24. if not template_str or not isinstance(template_str, str):
  25. return parameters
  26. # Si la chaîne contient "parameters:", récupère ce qui suit
  27. if 'parameters:' in template_str:
  28. params_section = template_str.split('parameters:', 1)[1]
  29. else:
  30. # Sinon, utilise la chaîne directement (elle contient déjà les paramètres)
  31. params_section = template_str
  32. # Parse les lignes de paramètres
  33. # Format: "stop "<end_of_turn>""
  34. # "temperature 0.1"
  35. lines = params_section.split('\n')
  36. for line in lines:
  37. if not line.strip():
  38. continue
  39. # Divise par le premier groupe d'espaces blancs
  40. # Cela sépare la clé des valeurs avec leurs espaces
  41. parts = line.split(None, 1) # split() avec maxsplit=1 divise sur les espaces
  42. if len(parts) == 2:
  43. param_name = parts[0].strip()
  44. param_value = parts[1].strip()
  45. parameters[param_name] = param_value
  46. return parameters
  47. def get_llm_model_info(model=OLLAMA_MODEL):
  48. """Extrait les informations du modèle LLM depuis Ollama."""
  49. try:
  50. # URL pour obtenir les informations du modèle
  51. info_url = OLLAMA_URL.replace("/api/generate", "/api/show")
  52. payload = {"name": model}
  53. response = requests.post(info_url, json=payload)
  54. if response.status_code == 200:
  55. model_data = response.json()
  56. # Gère le cas où model_data est une chaîne
  57. if isinstance(model_data, str):
  58. model_data = json.loads(model_data)
  59. # Extrait les paramètres du template
  60. parameters = model_data.get('parameters', '')
  61. parsed_params = extract_parameters_from_template(parameters)
  62. # Extraction des informations principales
  63. info = {
  64. "temperature": parsed_params.get('temperature', model_data.get("temperature", "Not available")),
  65. "num_ctx": parsed_params.get('num_ctx', "Not available"),
  66. "top_k": parsed_params.get('top_k', "Not available"),
  67. "top_p": parsed_params.get('top_p', "Not available"),
  68. "system": model_data.get("system", "Not available"),
  69. "modified_at": model_data.get("modified_at", "Not available"),
  70. }
  71. return info
  72. else:
  73. print(f"Erreur lors de la récupération du modèle : {response.text}")
  74. return None
  75. except Exception as e:
  76. print(f"Erreur lors de l'accès aux informations du modèle : {e}")
  77. return None
  78. def display_llm_info():
  79. """Retourne les informations du modèle LLM formatées."""
  80. from datetime import datetime
  81. info = get_llm_model_info(OLLAMA_MODEL)
  82. if info:
  83. # Formate la date en jj/mm/AAAA
  84. modified_at = info.get('modified_at', 'Not available')
  85. if modified_at and modified_at != 'Not available':
  86. try:
  87. # Parse la date ISO
  88. date_obj = datetime.fromisoformat(modified_at.replace('Z', '+00:00'))
  89. # Formate en jj/mm/AAAA
  90. formatted_date = date_obj.strftime("%d/%m/%Y")
  91. except:
  92. formatted_date = modified_at
  93. else:
  94. formatted_date = 'Not available'
  95. return f"LLM Modèle: {OLLAMA_MODEL}<br//>\nSystem: {info['system']}<br//>\nTemperature: {info['temperature']}<br//>\nDate de modification: {formatted_date}"
  96. else:
  97. return "Informations du modèle non disponibles"
  98. def extract_text_from_pdf(pdf_path):
  99. """Extrait le texte page par page d'un PDF sans les numéros de pages."""
  100. import re
  101. text_by_page = []
  102. with open(pdf_path, "rb") as file:
  103. reader = PyPDF2.PdfReader(file)
  104. for page in reader.pages:
  105. text = page.extract_text()
  106. # Supprime les numéros de pages (nombres seuls en début/fin de ligne)
  107. text = re.sub(r'^\d+\s*\n', '', text, flags=re.MULTILINE)
  108. text = re.sub(r'\n\s*\d+\s*$', '', text, flags=re.MULTILINE)
  109. text_by_page.append(text)
  110. return text_by_page
  111. def merge_paragraphs_across_pages(pages_text):
  112. """Divise le texte en chunks raisonnables pour la traduction."""
  113. import re
  114. # Concatène tout le texte
  115. full_text = "\n".join(pages_text)
  116. # Essaie d'abord de diviser par les doubles sauts de ligne
  117. paragraphs = re.split(r'\n\s*\n+', full_text.strip())
  118. # Si on obtient seulement un paragraphe, on divise par une taille maximale
  119. if len(paragraphs) == 1:
  120. print("Aucune séparation par double saut de ligne détectée. Division par taille...")
  121. # Divise par les phrases (points suivis d'un espace)
  122. sentences = re.split(r'(?<=[.!?])\s+', full_text.strip())
  123. # Regroupe les phrases en chunks d'environ 1500 caractères
  124. max_chunk_size = 1500
  125. paragraphs = []
  126. current_chunk = ""
  127. for sentence in sentences:
  128. if len(current_chunk) + len(sentence) < max_chunk_size:
  129. current_chunk += (" " + sentence) if current_chunk else sentence
  130. else:
  131. if current_chunk:
  132. paragraphs.append(current_chunk)
  133. current_chunk = sentence
  134. if current_chunk:
  135. paragraphs.append(current_chunk)
  136. else:
  137. # Normalise les sauts de ligne internes
  138. paragraphs = [re.sub(r'\n+', ' ', p.strip()) for p in paragraphs if p.strip()]
  139. return paragraphs
  140. def send_to_ollama(text, target_lang=TARGET_LANGUAGE, model=OLLAMA_MODEL, context_size=128000, system_prompt=SYSTEM_PROMPT):
  141. """Envoie une requête à Ollama et retourne la réponse traduite."""
  142. # Construit le prompt avec les instructions système et la demande de traduction
  143. full_prompt = f"{system_prompt}\n\nTraduis le texte suivant de l'ukrainien vers le {target_lang} :\n{text}"
  144. payload = {
  145. "model": model,
  146. "prompt": full_prompt,
  147. "stream": False,
  148. "options": {"num_ctx": context_size}
  149. }
  150. response = requests.post(OLLAMA_URL, data=json.dumps(payload))
  151. if response.status_code == 200:
  152. return response.json()["response"]
  153. else:
  154. raise Exception(f"Erreur Ollama: {response.text}")
  155. def register_unicode_font():
  156. """Enregistre une police TrueType qui supporte le cyrilique."""
  157. # Recherche une police système qui supporte le cyrilique
  158. font_paths = [
  159. r"C:\Windows\Fonts\DejaVuSans.ttf",
  160. r"C:\Windows\Fonts\Calibri.ttf",
  161. r"C:\Windows\Fonts\arial.ttf",
  162. ]
  163. for font_path in font_paths:
  164. if os.path.exists(font_path):
  165. try:
  166. pdfmetrics.registerFont(TTFont('UnicodeFont', font_path))
  167. return 'UnicodeFont'
  168. except Exception as e:
  169. print(f"Erreur lors de l'enregistrement de {font_path}: {e}")
  170. # Si aucune police spéciale trouvée, utilise Helvetica par défaut
  171. print("Aucune police Unicode trouvée, utilisation d'Helvetica")
  172. return 'Helvetica'
  173. def create_pdf_from_results(results, output_path):
  174. """Crée un PDF à partir des résultats de traduction."""
  175. doc = SimpleDocTemplate(output_path, pagesize=letter, topMargin=inch, bottomMargin=inch)
  176. story = []
  177. # Enregistre une police qui supporte le cyrilique
  178. font_name = register_unicode_font()
  179. # Style personnalisé
  180. styles = getSampleStyleSheet()
  181. title_style = ParagraphStyle(
  182. 'CustomTitle',
  183. parent=styles['Heading1'],
  184. fontSize=16,
  185. textColor='#1f4788',
  186. spaceAfter=0.3*inch,
  187. alignment=TA_JUSTIFY,
  188. fontName=font_name
  189. )
  190. page_style = ParagraphStyle(
  191. 'PageHeading',
  192. parent=styles['Heading2'],
  193. fontSize=12,
  194. textColor='#1f4788',
  195. spaceAfter=0.2*inch,
  196. spaceBefore=0.2*inch,
  197. fontName=font_name
  198. )
  199. body_style = ParagraphStyle(
  200. 'CustomBody',
  201. parent=styles['BodyText'],
  202. fontSize=11,
  203. alignment=TA_JUSTIFY,
  204. spaceAfter=0.2*inch,
  205. fontName=font_name
  206. )
  207. # Titre avec la langue cible
  208. title_text = f"Traduction - Ukrainien vers {TARGET_LANGUAGE.capitalize()}"
  209. story.append(Paragraph(title_text, title_style))
  210. story.append(Spacer(1, 0.2*inch))
  211. # Contenu
  212. for page_num, translation in results.items():
  213. # Préserver la mise en page en convertissant les sauts de ligne
  214. formatted_text = translation.replace("\n", "<br/>")
  215. story.append(Paragraph(formatted_text, body_style))
  216. story.append(Spacer(1, 0.1*inch))
  217. # Infos sur le LLM
  218. story.append(Spacer(1, 0.2*inch))
  219. story.append(Paragraph(display_llm_info(), page_style))
  220. # Construction du PDF
  221. doc.build(story)
  222. print(f"PDF généré avec succès : {output_path}")
  223. def main():
  224. # Affiche les informations du modèle LLM
  225. display_llm_info()
  226. # Extraction du texte page par page
  227. pages = extract_text_from_pdf(PDF_PATH)
  228. print(f"Nombre de pages extraites : {len(pages)}")
  229. # Fusion des paragraphes qui s'étendent sur plusieurs pages
  230. paragraphs = merge_paragraphs_across_pages(pages)
  231. print(f"Nombre de paragraphes complets extraits : {len(paragraphs)}")
  232. # Dictionnaire pour stocker les résultats
  233. results = {}
  234. # Traitement des paragraphes complets
  235. for i, paragraph_text in enumerate(paragraphs, start=1):
  236. print(f"{15 * '-'} Traduction du paragraphe {i}/{len(paragraphs)}...")
  237. try:
  238. result = send_to_ollama(paragraph_text, target_lang=TARGET_LANGUAGE)
  239. print(f"{result}.")
  240. results[i] = result
  241. except Exception as e:
  242. print(f"Erreur lors du traitement du paragraphe {i} : {e}")
  243. results[i] = f"Erreur lors du traitement du paragraphe {i} : {e}"
  244. # Création du PDF avec tous les résultats
  245. create_pdf_from_results(results, OUTPUT_PDF_PATH)
  246. if __name__ == "__main__":
  247. main()