| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298 |
- 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
-
- # Prompt système personnalisé (instructions pour le LLM)
- SYSTEM_PROMPT = """"""
-
- 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."""
- try:
- # 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 des informations principales
- info = {
- "temperature": parsed_params.get('temperature', model_data.get("temperature", "Not available")),
- "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: {OLLAMA_MODEL}<br//>\nSystem: {info['system']}<br//>\nTemperature: {info['temperature']}<br//>\nDate de modification: {formatted_date}"
- 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 merge_paragraphs_across_pages(pages_text):
- """Divise le texte en chunks raisonnables pour la traduction."""
- import re
-
- # Concatène tout le texte
- full_text = "\n".join(pages_text)
-
- # Essaie d'abord de diviser par les doubles sauts de ligne
- paragraphs = re.split(r'\n\s*\n+', full_text.strip())
-
- # Si on obtient seulement un paragraphe, on divise par une taille maximale
- if len(paragraphs) == 1:
- print("Aucune séparation par double saut de ligne détectée. Division par taille...")
- # Divise par les phrases (points suivis d'un espace)
- sentences = re.split(r'(?<=[.!?])\s+', full_text.strip())
-
- # Regroupe les phrases en chunks d'environ 1500 caractères
- max_chunk_size = 1500
- paragraphs = []
- current_chunk = ""
-
- for sentence in sentences:
- if len(current_chunk) + len(sentence) < max_chunk_size:
- current_chunk += (" " + sentence) if current_chunk else sentence
- else:
- if current_chunk:
- paragraphs.append(current_chunk)
- current_chunk = sentence
-
- if current_chunk:
- paragraphs.append(current_chunk)
- else:
- # Normalise les sauts de ligne internes
- paragraphs = [re.sub(r'\n+', ' ', 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, system_prompt=SYSTEM_PROMPT):
- """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"{system_prompt}\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 = merge_paragraphs_across_pages(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()
|