Script python permettant de traduire un long texte
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  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. from datetime import datetime
  13. # Configuration
  14. PDF_PATH = "TaniaBorecMemoir(Ukr).pdf"
  15. OLLAMA_MODEL = "traductionUkrainienVersFrancais:latest"
  16. OLLAMA_URL = "http://localhost:11434/api/generate"
  17. TARGET_LANGUAGE = "français"
  18. CHECKPOINT_FILE = "checkpoint.json"
  19. TEMP_OUTPUT_TXT = "output_temp.txt"
  20. FINAL_OUTPUT_PDF = PDF_PATH.replace(".pdf",f" ({TARGET_LANGUAGE.upper()[:2]})_V2.pdf")
  21. FINAL_OUTPUT_TXT = PDF_PATH.replace(".pdf",f" ({TARGET_LANGUAGE.upper()[:2]})_V2.txt")
  22. def extract_parameters_from_template(template_str):
  23. """Extrait les paramètres du modèle à partir du template."""
  24. import re
  25. parameters = {}
  26. if not template_str or not isinstance(template_str, str):
  27. return parameters
  28. # Si la chaîne contient "parameters:", récupère ce qui suit
  29. if 'parameters:' in template_str:
  30. params_section = template_str.split('parameters:', 1)[1]
  31. else:
  32. # Sinon, utilise la chaîne directement (elle contient déjà les paramètres)
  33. params_section = template_str
  34. # Parse les lignes de paramètres
  35. # Format: "stop "<end_of_turn>""
  36. # "temperature 0.1"
  37. lines = params_section.split('\n')
  38. for line in lines:
  39. if not line.strip():
  40. continue
  41. # Divise par le premier groupe d'espaces blancs
  42. # Cela sépare la clé des valeurs avec leurs espaces
  43. parts = line.split(None, 1) # split() avec maxsplit=1 divise sur les espaces
  44. if len(parts) == 2:
  45. param_name = parts[0].strip()
  46. param_value = parts[1].strip()
  47. parameters[param_name] = param_value
  48. return parameters
  49. def get_llm_model_info(model=OLLAMA_MODEL):
  50. """
  51. Extrait les informations du modèle LLM depuis Ollama, y compris le nom depuis la ligne FROM du Modelfile.
  52. @param model: Nom du modèle à interroger.
  53. @type model: str
  54. @return: Dictionnaire contenant les informations du modèle, ou None en cas d'erreur.
  55. @rtype: dict | None
  56. """
  57. try:
  58. # Chemin vers le fichier Modelfile (supposé être dans le même répertoire que le script)
  59. modelfile_path = os.path.join(os.path.dirname(__file__), "Modelfile")
  60. # Initialisation de model_name
  61. model_name = "none"
  62. # Lecture du fichier Modelfile pour extraire le nom du modèle
  63. if os.path.exists(modelfile_path):
  64. with open(modelfile_path, 'r', encoding='utf-8') as file:
  65. for line in file:
  66. if line.strip().startswith('FROM '):
  67. model_name = line.strip().split('FROM ')[1].strip()
  68. break
  69. # URL pour obtenir les informations du modèle
  70. info_url = OLLAMA_URL.replace("/api/generate", "/api/show")
  71. payload = {"name": model}
  72. response = requests.post(info_url, json=payload)
  73. if response.status_code == 200:
  74. model_data = response.json()
  75. # Gère le cas où model_data est une chaîne
  76. if isinstance(model_data, str):
  77. model_data = json.loads(model_data)
  78. # Extrait les paramètres du template
  79. parameters = model_data.get('parameters', '')
  80. parsed_params = extract_parameters_from_template(parameters)
  81. # Extraction du nom depuis la ligne FROM
  82. modelfile_content = model_data.get('Modelfile', '')
  83. # Extraction des informations principales
  84. info = {
  85. "temperature": parsed_params.get('temperature', model_data.get("temperature", "Not available")),
  86. "name": model_name,
  87. "num_ctx": parsed_params.get('num_ctx', "Not available"),
  88. "top_k": parsed_params.get('top_k', "Not available"),
  89. "top_p": parsed_params.get('top_p', "Not available"),
  90. "system": model_data.get("system", "Not available"),
  91. "modified_at": model_data.get("modified_at", "Not available"),
  92. }
  93. return info
  94. else:
  95. print(f"Erreur lors de la récupération du modèle : {response.text}")
  96. return None
  97. except Exception as e:
  98. print(f"Erreur lors de l'accès aux informations du modèle : {e}")
  99. return None
  100. def display_llm_info():
  101. """Retourne les informations du modèle LLM formatées."""
  102. from datetime import datetime
  103. info = get_llm_model_info(OLLAMA_MODEL)
  104. if info:
  105. # Formate la date en jj/mm/AAAA
  106. modified_at = info.get('modified_at', 'Not available')
  107. if modified_at and modified_at != 'Not available':
  108. try:
  109. # Parse la date ISO
  110. date_obj = datetime.fromisoformat(modified_at.replace('Z', '+00:00'))
  111. # Formate en jj/mm/AAAA
  112. formatted_date = date_obj.strftime("%d/%m/%Y")
  113. except:
  114. formatted_date = modified_at
  115. else:
  116. formatted_date = 'Not available'
  117. return f"LLM Modèle: {info['name']}<br//>\nDate de modification: {formatted_date}<br//>\nSystem: {info['system']}<br//>\nTemperature: {info['temperature']}"
  118. else:
  119. return "Informations du modèle non disponibles"
  120. def register_unicode_font():
  121. """Enregistre une police TrueType qui supporte le cyrilique."""
  122. # Recherche une police système qui supporte le cyrilique
  123. font_paths = [
  124. r"C:\Windows\Fonts\DejaVuSans.ttf",
  125. r"C:\Windows\Fonts\Calibri.ttf",
  126. r"C:\Windows\Fonts\arial.ttf",
  127. ]
  128. for font_path in font_paths:
  129. if os.path.exists(font_path):
  130. try:
  131. pdfmetrics.registerFont(TTFont('UnicodeFont', font_path))
  132. return 'UnicodeFont'
  133. except Exception as e:
  134. print(f"Erreur lors de l'enregistrement de {font_path}: {e}")
  135. # Si aucune police spéciale trouvée, utilise Helvetica par défaut
  136. print("Aucune police Unicode trouvée, utilisation d'Helvetica")
  137. return 'Helvetica'
  138. # Charge ou initialise le checkpoint
  139. def load_checkpoint():
  140. if os.path.exists(CHECKPOINT_FILE):
  141. with open(CHECKPOINT_FILE, "r") as f:
  142. return json.load(f)
  143. return {"last_processed_index": -1, "results": {}}
  144. # Sauvegarde le checkpoint
  145. def save_checkpoint(last_index, results):
  146. with open(CHECKPOINT_FILE, "w") as f:
  147. json.dump({"last_processed_index": last_index, "results": results}, f)
  148. # Sauvegarde les résultats temporaires dans un fichier TXT
  149. def save_temp_results(results):
  150. with open(TEMP_OUTPUT_TXT, "w", encoding="utf-8") as f:
  151. for idx, translation in results.items():
  152. f.write(f"Paragraphe {idx}:\n{translation}\n\n")
  153. # Extraction du texte du PDF (inchangée)
  154. def extract_text_from_pdf(pdf_path):
  155. text_by_page = []
  156. with open(pdf_path, "rb") as file:
  157. reader = PyPDF2.PdfReader(file)
  158. for page in reader.pages:
  159. text = page.extract_text()
  160. text_by_page.append(text)
  161. return text_by_page
  162. # Découpage en paragraphes (inchangé)
  163. def split_pages_in_paragraphs(pages_text):
  164. import re
  165. full_text = "\n".join(pages_text)
  166. full_text = re.sub(r'(?<![.!?])\n+(?![.!?])', ' ', full_text)
  167. paragraphs = re.split(r'(?<=[.!?])\s*\n+', full_text.strip())
  168. paragraphs = [re.sub(r'\s+', ' ', p).strip() for p in paragraphs if p.strip()]
  169. return paragraphs
  170. # Envoi à Ollama (inchangé)
  171. def send_to_ollama(text, target_lang=TARGET_LANGUAGE, model=OLLAMA_MODEL):
  172. full_prompt = f"\n\nTraduis le texte suivant de l'ukrainien vers le {target_lang} :\n{text}"
  173. payload = {"model": model, "prompt": full_prompt, "stream": False}
  174. response = requests.post(OLLAMA_URL, data=json.dumps(payload))
  175. if response.status_code == 200:
  176. return response.json()["response"]
  177. else:
  178. raise Exception(f"Erreur Ollama: {response.text}")
  179. # Création du PDF final (inchangée)
  180. def create_pdf_from_results(results, output_path):
  181. """Crée un PDF à partir des résultats de traduction."""
  182. doc = SimpleDocTemplate(output_path, pagesize=letter, topMargin=inch, bottomMargin=inch)
  183. story = []
  184. # Enregistre une police qui supporte le cyrilique
  185. font_name = register_unicode_font()
  186. # Style personnalisé
  187. styles = getSampleStyleSheet()
  188. title_style = ParagraphStyle(
  189. 'CustomTitle',
  190. parent=styles['Heading1'],
  191. fontSize=16,
  192. textColor='#1f4788',
  193. spaceAfter=0.3*inch,
  194. alignment=TA_JUSTIFY,
  195. fontName=font_name
  196. )
  197. page_style = ParagraphStyle(
  198. 'PageHeading',
  199. parent=styles['Heading2'],
  200. fontSize=12,
  201. textColor='#1f4788',
  202. spaceAfter=0.2*inch,
  203. spaceBefore=0.2*inch,
  204. fontName=font_name
  205. )
  206. body_style = ParagraphStyle(
  207. 'CustomBody',
  208. parent=styles['BodyText'],
  209. fontSize=11,
  210. alignment=TA_JUSTIFY,
  211. spaceAfter=0.2*inch,
  212. fontName=font_name
  213. )
  214. # Titre avec la langue cible
  215. story.append(Paragraph(f"Traduction - Ukrainien vers {TARGET_LANGUAGE.capitalize()}", title_style))
  216. story.append(Paragraph(f"Document : {PDF_PATH}", title_style))
  217. story.append(Spacer(1, 0.2*inch))
  218. # Contenu
  219. for page_num, translation in results.items():
  220. # Préserver la mise en page en convertissant les sauts de ligne
  221. formatted_text = translation.replace("\n", "<br/>")
  222. story.append(Paragraph(formatted_text, body_style))
  223. # story.append(Spacer(1, 0.1*inch))
  224. # Infos sur le LLM
  225. story.append(Spacer(1, 0.2*inch))
  226. story.append(Paragraph(display_llm_info(), page_style))
  227. # Construction du PDF
  228. doc.build(story)
  229. print(f"PDF généré avec succès : {output_path}")
  230. def create_txt_from_results(results, output_path):
  231. """Crée un fichier TXT à partir des résultats de traduction."""
  232. OUTPUT_TXT_PATH = output_path.replace(".pdf", f".txt") # Chemin du fichier TXT de sortie
  233. # Titre avec la langue cible
  234. title_text = f"Traduction - Ukrainien vers {TARGET_LANGUAGE.capitalize()}"
  235. with open(OUTPUT_TXT_PATH, 'w', encoding='utf-8') as txt_file:
  236. txt_file.write(title_text + "\n\n")
  237. # Contenu
  238. for page_num, translation in results.items():
  239. # Préserver la mise en page en convertissant les sauts de ligne
  240. txt_file.write(translation + "\n\n")
  241. # Infos sur le LLM
  242. txt_file.write("\n")
  243. txt_file.write(display_llm_info() + "\n")
  244. # Fonction principale
  245. def main():
  246. # Charge le checkpoint
  247. checkpoint = load_checkpoint()
  248. last_index = checkpoint["last_processed_index"]
  249. results = checkpoint["results"]
  250. # Extraction des paragraphes
  251. pages = extract_text_from_pdf(PDF_PATH)
  252. paragraphs = split_pages_in_paragraphs(pages)
  253. # Traitement des paragraphes
  254. batch_size = 3
  255. for i in range(last_index + 1, len(paragraphs), batch_size):
  256. batch = paragraphs[i:i + batch_size]
  257. paragraph_cumul = "\n".join(batch)
  258. print(f"{15 * '-'} Traduction des paragraphes {i+1} à {min(i + batch_size, len(paragraphs))} / {len(paragraphs)}")
  259. try:
  260. result = send_to_ollama(paragraph_cumul)
  261. print(f"{result}")
  262. results[i] = result
  263. save_checkpoint(i, results) # Sauvegarde le checkpoint
  264. save_temp_results(results) # Sauvegarde les résultats temporaires
  265. except Exception as e:
  266. print(f"Erreur : {e}")
  267. continue
  268. # Génération des fichiers finaux
  269. save_temp_results(results)
  270. create_pdf_from_results(results, FINAL_OUTPUT_PDF)
  271. create_txt_from_results(results, FINAL_OUTPUT_TXT)
  272. print("Traduction terminée !")
  273. if __name__ == "__main__":
  274. main()