Просмотр исходного кода

reprise du programme en cas d'erreur ou plantage

main
Alex 2 недель назад
Родитель
Сommit
71786d27c6
1 измененных файлов: 71 добавлений и 291 удалений
  1. 71
    291
      main.py

+ 71
- 291
main.py Просмотреть файл

import PyPDF2 import PyPDF2
import requests import requests
import json 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 import os
from datetime import datetime from datetime import datetime


# Configuration # Configuration
PDF_PATH = "TaniaBorecMemoir(Ukr).pdf" # Fichier original
PDF_PATH = "TaniaBorecMemoir(Ukr).pdf"
OLLAMA_MODEL = "traductionUkrainienVersFrancais:latest" 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.)

# Récupère la date et l'heure actuelles au format AAAMMJJ-HHMM
current_datetime = datetime.now().strftime("%Y%m%d-%H%M")
# Ajoute la date et la langue cible au nom du fichier PDF de sortie
# OUTPUT_PDF_PATH = PDF_PATH.replace(".pdf",f" ({TARGET_LANGUAGE.upper()[:2]})_{current_datetime}.pdf")
OUTPUT_PDF_PATH = PDF_PATH.replace(".pdf",f" ({TARGET_LANGUAGE.upper()[:2]})_V2.pdf")

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"

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")

# 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): 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 = [] text_by_page = []
with open(pdf_path, "rb") as file: with open(pdf_path, "rb") as file:
reader = PyPDF2.PdfReader(file) reader = PyPDF2.PdfReader(file)
for page in reader.pages: for page in reader.pages:
text = page.extract_text() 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) text_by_page.append(text)
return text_by_page return text_by_page


# Découpage en paragraphes (inchangé)
def split_pages_in_paragraphs(pages_text): 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 import re

# Concatène tout le texte
full_text = "\n".join(pages_text) 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) 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()) 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()] paragraphs = [re.sub(r'\s+', ' ', p).strip() for p in paragraphs if p.strip()]

return paragraphs 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
# 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}" 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}
}
payload = {"model": model, "prompt": full_prompt, "stream": False}
response = requests.post(OLLAMA_URL, data=json.dumps(payload)) response = requests.post(OLLAMA_URL, data=json.dumps(payload))
if response.status_code == 200: if response.status_code == 200:
return response.json()["response"] return response.json()["response"]
else: else:
raise Exception(f"Erreur Ollama: {response.text}") 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'

# Création du PDF final (inchangée)
def create_pdf_from_results(results, output_path): 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)
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

doc = SimpleDocTemplate(output_path, pagesize=letter)
story = [] story = []
# Enregistre une police qui supporte le cyrilique
font_name = register_unicode_font()
# Style personnalisé
styles = getSampleStyleSheet() 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}")
body_style = styles["BodyText"]


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")
for idx, translation in results.items():
story.append(Paragraph(translation, body_style))

doc.build(story)
print(f"PDF final généré : {output_path}")


# Fonction principale
def main(): def main():
# Affiche les informations du modèle LLM
display_llm_info()
# Extraction du texte page par page
# 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) 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) 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):
nb_paragraph_cumul = 7
for i in range(0, len(paragraphs), nb_paragraph_cumul):
batch = paragraphs[i:i + nb_paragraph_cumul]
paragraph_cumul = "\n".join(batch) # Concatène les paragraphes avec un saut de ligne
# 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)


start_idx = i + 1
end_idx = min(i + nb_paragraph_cumul, len(paragraphs))
print(f"{15 * '-'} Traduction des paragraphes {start_idx} à {end_idx} / {len(paragraphs)}...")
print(f"{15 * '-'} Traduction des paragraphes {i+1} à {min(i + batch_size, len(paragraphs))} / {len(paragraphs)}")


try: try:
result = send_to_ollama(paragraph_cumul, target_lang=TARGET_LANGUAGE)
result = send_to_ollama(paragraph_cumul)
print(f"{result}") print(f"{result}")
# Stocke le résultat pour chaque paragraphe du batch
results[i] = result # Ou `results[idx] = f"Résultat pour {idx}"` si tu veux les séparer
results[i] = result
save_checkpoint(i, results) # Sauvegarde le checkpoint
save_temp_results(results) # Sauvegarde les résultats temporaires
except Exception as e: except Exception as e:
print(f"Erreur lors du traitement des paragraphes {start_idx} à {end_idx} : {e}")
results[i] = f"Erreur lors du traitement des paragraphes {start_idx} à {end_idx} : {e}"

print(f"Erreur : {e}")
continue


# Création du PDF avec tous les résultats
create_pdf_from_results(results, OUTPUT_PDF_PATH)
create_txt_from_results(results, OUTPUT_PDF_PATH)
# Génération des fichiers finaux
save_temp_results(results)
create_pdf_from_results(results, FINAL_OUTPUT_PDF)
os.rename(TEMP_OUTPUT_TXT, FINAL_OUTPUT_TXT)
print("Traduction terminée !")


if __name__ == "__main__": if __name__ == "__main__":
main() main()

Загрузка…
Отмена
Сохранить