| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- 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
-
- # Configuration
- PDF_PATH = "TaniaBorecMemoir(Ukr).pdf" # Fichier original
- OLLAMA_MODEL = "traductionUkrainienVersFrancais:latest"
- OLLAMA_URL = "http://localhost:11434/api/generate" # URL par défaut d'Ollama
- TARGET_LANGUAGE = "français" # Langue cible (ex: "français", "anglais", "allemand", "espagnol", etc.)
- OUTPUT_PDF_PATH = PDF_PATH.replace(".pdf", f" ({TARGET_LANGUAGE.upper()[:2]}).pdf") # Chemin du PDF de sortie
-
- 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 extract_text_from_pdf(pdf_path):
- """Extrait le texte page par page d'un PDF sans les numéros de pages."""
- import re
- text_by_page = []
- with open(pdf_path, "rb") as file:
- reader = PyPDF2.PdfReader(file)
- for page in reader.pages:
- text = page.extract_text()
- # Supprime les numéros de pages (nombres seuls en début/fin de ligne)
- text = re.sub(r'^\d+\s*\n', '', text, flags=re.MULTILINE)
- text = re.sub(r'\n\s*\d+\s*$', '', text, flags=re.MULTILINE)
- text_by_page.append(text)
- return text_by_page
-
- def split_pages_in_paragraphs(pages_text):
- """
- Divise le texte en paragraphes en détectant un point suivi d'un saut de ligne ou d'un retour à la ligne.
- Conserve les sauts de ligne à l'intérieur des paragraphes.
- """
- import re
-
- # Concatène tout le texte
- full_text = "\n".join(pages_text)
-
- # Remplace les sauts de ligne à l'intérieur des paragraphes par des espaces
- # (pour éviter les sauts de ligne intempestifs dans un même paragraphe)
- full_text = re.sub(r'(?<![.!?])\n+(?![.!?])', ' ', full_text)
-
- # Divise le texte en paragraphes : un point suivi d'un saut de ligne
- paragraphs = re.split(r'(?<=[.!?])\s*\n+', full_text.strip())
-
- # Nettoie chaque paragraphe : remplace les sauts de ligne restants par des espaces
- paragraphs = [re.sub(r'\s+', ' ', p).strip() for p in paragraphs if p.strip()]
-
- return paragraphs
-
- def send_to_ollama(text, target_lang=TARGET_LANGUAGE, model=OLLAMA_MODEL, context_size=128000):
- """Envoie une requête à Ollama et retourne la réponse traduite."""
- # Construit le prompt avec les instructions système et la demande de traduction
- 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,
- "options": {"num_ctx": context_size}
- }
- 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}")
-
- 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'
-
- 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
- title_text = f"Traduction - Ukrainien vers {TARGET_LANGUAGE.capitalize()}"
- story.append(Paragraph(title_text, 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 main():
- # Affiche les informations du modèle LLM
- display_llm_info()
-
- # Extraction du texte page par page
- pages = extract_text_from_pdf(PDF_PATH)
- print(f"Nombre de pages extraites : {len(pages)}")
-
- # Fusion des paragraphes qui s'étendent sur plusieurs pages
- paragraphs = split_pages_in_paragraphs(pages)
- print(f"Nombre de paragraphes complets extraits : {len(paragraphs)}")
-
- # Dictionnaire pour stocker les résultats
- results = {}
-
- # Traitement des paragraphes complets
- for i, paragraph_text in enumerate(paragraphs, start=1):
- print(f"{15 * '-'} Traduction du paragraphe {i}/{len(paragraphs)}...")
- try:
- result = send_to_ollama(paragraph_text, target_lang=TARGET_LANGUAGE)
- print(f"{result}.")
- results[i] = result
- except Exception as e:
- print(f"Erreur lors du traitement du paragraphe {i} : {e}")
- results[i] = f"Erreur lors du traitement du paragraphe {i} : {e}"
-
- # Création du PDF avec tous les résultats
- create_pdf_from_results(results, OUTPUT_PDF_PATH)
-
- if __name__ == "__main__":
- main()
|