| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- import PyPDF2
- import requests
- import json
- from reportlab.lib.pagesizes import letter
- from reportlab.lib.units import inch
- from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
- from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
- from reportlab.lib.enums import TA_JUSTIFY
- from reportlab.pdfbase import pdfmetrics
- from reportlab.pdfbase.ttfonts import TTFont
- import os
- from datetime import datetime
-
- # Configuration
- PDF_PATH = "TaniaBorecMemoir(Ukr).pdf"
- OLLAMA_MODEL = "traductionUkrainienVersFrancais:latest"
- OLLAMA_URL = "http://localhost:11434/api/generate"
- TARGET_LANGUAGE = "français"
- CHECKPOINT_FILE = "checkpoint.json"
- TEMP_OUTPUT_TXT = "output_temp.txt"
- FINAL_OUTPUT_PDF = PDF_PATH.replace(".pdf",f" ({TARGET_LANGUAGE.upper()[:2]})_V2.pdf")
- FINAL_OUTPUT_TXT = PDF_PATH.replace(".pdf",f" ({TARGET_LANGUAGE.upper()[:2]})_V2.txt")
-
- def extract_parameters_from_template(template_str):
- """Extrait les paramètres du modèle à partir du template."""
- import re
-
- parameters = {}
-
- if not template_str or not isinstance(template_str, str):
- return parameters
-
- # Si la chaîne contient "parameters:", récupère ce qui suit
- if 'parameters:' in template_str:
- params_section = template_str.split('parameters:', 1)[1]
- else:
- # Sinon, utilise la chaîne directement (elle contient déjà les paramètres)
- params_section = template_str
-
- # Parse les lignes de paramètres
- # Format: "stop "<end_of_turn>""
- # "temperature 0.1"
- lines = params_section.split('\n')
-
- for line in lines:
- if not line.strip():
- continue
-
- # Divise par le premier groupe d'espaces blancs
- # Cela sépare la clé des valeurs avec leurs espaces
- parts = line.split(None, 1) # split() avec maxsplit=1 divise sur les espaces
-
- if len(parts) == 2:
- param_name = parts[0].strip()
- param_value = parts[1].strip()
- parameters[param_name] = param_value
-
- return parameters
-
- def get_llm_model_info(model=OLLAMA_MODEL):
- """
- Extrait les informations du modèle LLM depuis Ollama, y compris le nom depuis la ligne FROM du Modelfile.
-
- @param model: Nom du modèle à interroger.
- @type model: str
- @return: Dictionnaire contenant les informations du modèle, ou None en cas d'erreur.
- @rtype: dict | None
- """
- try:
- # Chemin vers le fichier Modelfile (supposé être dans le même répertoire que le script)
- modelfile_path = os.path.join(os.path.dirname(__file__), "Modelfile")
-
- # Initialisation de model_name
- model_name = "none"
-
- # Lecture du fichier Modelfile pour extraire le nom du modèle
- if os.path.exists(modelfile_path):
- with open(modelfile_path, 'r', encoding='utf-8') as file:
- for line in file:
- if line.strip().startswith('FROM '):
- model_name = line.strip().split('FROM ')[1].strip()
- break
-
- # URL pour obtenir les informations du modèle
- info_url = OLLAMA_URL.replace("/api/generate", "/api/show")
- payload = {"name": model}
-
- response = requests.post(info_url, json=payload)
-
- if response.status_code == 200:
- model_data = response.json()
-
- # Gère le cas où model_data est une chaîne
- if isinstance(model_data, str):
- model_data = json.loads(model_data)
-
- # Extrait les paramètres du template
- parameters = model_data.get('parameters', '')
- parsed_params = extract_parameters_from_template(parameters)
-
- # Extraction du nom depuis la ligne FROM
- modelfile_content = model_data.get('Modelfile', '')
-
- # Extraction des informations principales
- info = {
- "temperature": parsed_params.get('temperature', model_data.get("temperature", "Not available")),
- "name": model_name,
- "num_ctx": parsed_params.get('num_ctx', "Not available"),
- "top_k": parsed_params.get('top_k', "Not available"),
- "top_p": parsed_params.get('top_p', "Not available"),
- "system": model_data.get("system", "Not available"),
- "modified_at": model_data.get("modified_at", "Not available"),
- }
- return info
- else:
- print(f"Erreur lors de la récupération du modèle : {response.text}")
- return None
- except Exception as e:
- print(f"Erreur lors de l'accès aux informations du modèle : {e}")
- return None
-
- def display_llm_info():
- """Retourne les informations du modèle LLM formatées."""
- from datetime import datetime
-
- info = get_llm_model_info(OLLAMA_MODEL)
-
- if info:
- # Formate la date en jj/mm/AAAA
- modified_at = info.get('modified_at', 'Not available')
- if modified_at and modified_at != 'Not available':
- try:
- # Parse la date ISO
- date_obj = datetime.fromisoformat(modified_at.replace('Z', '+00:00'))
- # Formate en jj/mm/AAAA
- formatted_date = date_obj.strftime("%d/%m/%Y")
- except:
- formatted_date = modified_at
- else:
- formatted_date = 'Not available'
-
- return f"LLM Modèle: {info['name']}<br//>\nDate de modification: {formatted_date}<br//>\nSystem: {info['system']}<br//>\nTemperature: {info['temperature']}"
- else:
- return "Informations du modèle non disponibles"
-
- def register_unicode_font():
- """Enregistre une police TrueType qui supporte le cyrilique."""
- # Recherche une police système qui supporte le cyrilique
- font_paths = [
- r"C:\Windows\Fonts\DejaVuSans.ttf",
- r"C:\Windows\Fonts\Calibri.ttf",
- r"C:\Windows\Fonts\arial.ttf",
- ]
-
- for font_path in font_paths:
- if os.path.exists(font_path):
- try:
- pdfmetrics.registerFont(TTFont('UnicodeFont', font_path))
- return 'UnicodeFont'
- except Exception as e:
- print(f"Erreur lors de l'enregistrement de {font_path}: {e}")
-
- # Si aucune police spéciale trouvée, utilise Helvetica par défaut
- print("Aucune police Unicode trouvée, utilisation d'Helvetica")
- return 'Helvetica'
-
- # Charge ou initialise le checkpoint
- def load_checkpoint():
- if os.path.exists(CHECKPOINT_FILE):
- with open(CHECKPOINT_FILE, "r") as f:
- return json.load(f)
- return {"last_processed_index": -1, "results": {}}
-
- # Sauvegarde le checkpoint
- def save_checkpoint(last_index, results):
- with open(CHECKPOINT_FILE, "w") as f:
- json.dump({"last_processed_index": last_index, "results": results}, f)
-
- # Sauvegarde les résultats temporaires dans un fichier TXT
- def save_temp_results(results):
- with open(TEMP_OUTPUT_TXT, "w", encoding="utf-8") as f:
- for idx, translation in results.items():
- f.write(f"Paragraphe {idx}:\n{translation}\n\n")
-
- # Extraction du texte du PDF (inchangée)
- def extract_text_from_pdf(pdf_path):
- text_by_page = []
- with open(pdf_path, "rb") as file:
- reader = PyPDF2.PdfReader(file)
- for page in reader.pages:
- text = page.extract_text()
- text_by_page.append(text)
- return text_by_page
-
- # Découpage en paragraphes (inchangé)
- def split_pages_in_paragraphs(pages_text):
- import re
- full_text = "\n".join(pages_text)
- full_text = re.sub(r'(?<![.!?])\n+(?![.!?])', ' ', full_text)
- paragraphs = re.split(r'(?<=[.!?])\s*\n+', full_text.strip())
- paragraphs = [re.sub(r'\s+', ' ', p).strip() for p in paragraphs if p.strip()]
- return paragraphs
-
- # Envoi à Ollama (inchangé)
- def send_to_ollama(text, target_lang=TARGET_LANGUAGE, model=OLLAMA_MODEL):
- full_prompt = f"\n\nTraduis le texte suivant de l'ukrainien vers le {target_lang} :\n{text}"
- payload = {"model": model, "prompt": full_prompt, "stream": False}
- response = requests.post(OLLAMA_URL, data=json.dumps(payload))
- if response.status_code == 200:
- return response.json()["response"]
- else:
- raise Exception(f"Erreur Ollama: {response.text}")
-
- # Création du PDF final (inchangée)
- def create_pdf_from_results(results, output_path):
- """Crée un PDF à partir des résultats de traduction."""
- doc = SimpleDocTemplate(output_path, pagesize=letter, topMargin=inch, bottomMargin=inch)
- story = []
-
- # Enregistre une police qui supporte le cyrilique
- font_name = register_unicode_font()
-
- # Style personnalisé
- styles = getSampleStyleSheet()
- title_style = ParagraphStyle(
- 'CustomTitle',
- parent=styles['Heading1'],
- fontSize=16,
- textColor='#1f4788',
- spaceAfter=0.3*inch,
- alignment=TA_JUSTIFY,
- fontName=font_name
- )
-
- page_style = ParagraphStyle(
- 'PageHeading',
- parent=styles['Heading2'],
- fontSize=12,
- textColor='#1f4788',
- spaceAfter=0.2*inch,
- spaceBefore=0.2*inch,
- fontName=font_name
- )
-
- body_style = ParagraphStyle(
- 'CustomBody',
- parent=styles['BodyText'],
- fontSize=11,
- alignment=TA_JUSTIFY,
- spaceAfter=0.2*inch,
- fontName=font_name
- )
-
- # Titre avec la langue cible
- story.append(Paragraph(f"Traduction - Ukrainien vers {TARGET_LANGUAGE.capitalize()}", title_style))
- story.append(Paragraph(f"Document : {PDF_PATH}", title_style))
- story.append(Spacer(1, 0.2*inch))
-
- # Contenu
- for page_num, translation in results.items():
- # Préserver la mise en page en convertissant les sauts de ligne
- formatted_text = translation.replace("\n", "<br/>")
- story.append(Paragraph(formatted_text, body_style))
- # story.append(Spacer(1, 0.1*inch))
-
- # Infos sur le LLM
- story.append(Spacer(1, 0.2*inch))
- story.append(Paragraph(display_llm_info(), page_style))
-
- # Construction du PDF
- doc.build(story)
- print(f"PDF généré avec succès : {output_path}")
-
- def create_txt_from_results(results, output_path):
- """Crée un fichier TXT à partir des résultats de traduction."""
- OUTPUT_TXT_PATH = output_path.replace(".pdf", f".txt") # Chemin du fichier TXT de sortie
-
- # Titre avec la langue cible
- title_text = f"Traduction - Ukrainien vers {TARGET_LANGUAGE.capitalize()}"
- with open(OUTPUT_TXT_PATH, 'w', encoding='utf-8') as txt_file:
- txt_file.write(title_text + "\n\n")
-
- # Contenu
- for page_num, translation in results.items():
- # Préserver la mise en page en convertissant les sauts de ligne
- txt_file.write(translation + "\n\n")
-
- # Infos sur le LLM
- txt_file.write("\n")
- txt_file.write(display_llm_info() + "\n")
-
- # Fonction principale
- def main():
- # Charge le checkpoint
- checkpoint = load_checkpoint()
- last_index = checkpoint["last_processed_index"]
- results = checkpoint["results"]
-
- # Extraction des paragraphes
- pages = extract_text_from_pdf(PDF_PATH)
- paragraphs = split_pages_in_paragraphs(pages)
-
- # Traitement des paragraphes
- batch_size = 3
- for i in range(last_index + 1, len(paragraphs), batch_size):
- batch = paragraphs[i:i + batch_size]
- paragraph_cumul = "\n".join(batch)
-
- print(f"{15 * '-'} Traduction des paragraphes {i+1} à {min(i + batch_size, len(paragraphs))} / {len(paragraphs)}")
-
- try:
- result = send_to_ollama(paragraph_cumul)
- print(f"{result}")
- results[i] = result
- save_checkpoint(i, results) # Sauvegarde le checkpoint
- save_temp_results(results) # Sauvegarde les résultats temporaires
- except Exception as e:
- print(f"Erreur : {e}")
- continue
-
- # Génération des fichiers finaux
- save_temp_results(results)
- create_pdf_from_results(results, FINAL_OUTPUT_PDF)
- create_txt_from_results(results, FINAL_OUTPUT_TXT)
- print("Traduction terminée !")
-
- if __name__ == "__main__":
- main()
|