1 changed files with 71 additions and 291 deletions
-
356main.py
@ -1,341 +1,121 @@ |
|||
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" # Fichier original |
|||
PDF_PATH = "TaniaBorecMemoir(Ukr).pdf" |
|||
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): |
|||
"""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 |
|||
|
|||
# Découpage en paragraphes (inchangé) |
|||
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 |
|||
# 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, |
|||
"options": {"num_ctx": context_size} |
|||
} |
|||
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}") |
|||
|
|||
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): |
|||
"""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 = [] |
|||
|
|||
# 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 = styles["BodyText"] |
|||
|
|||
body_style = ParagraphStyle( |
|||
'CustomBody', |
|||
parent=styles['BodyText'], |
|||
fontSize=11, |
|||
alignment=TA_JUSTIFY, |
|||
spaceAfter=0.2*inch, |
|||
fontName=font_name |
|||
) |
|||
for idx, translation in results.items(): |
|||
story.append(Paragraph(translation, body_style)) |
|||
|
|||
# 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") |
|||
print(f"PDF final généré : {output_path}") |
|||
|
|||
# Fonction principale |
|||
def main(): |
|||
# Affiche les informations du modèle LLM |
|||
display_llm_info() |
|||
# Charge le checkpoint |
|||
checkpoint = load_checkpoint() |
|||
last_index = checkpoint["last_processed_index"] |
|||
results = checkpoint["results"] |
|||
|
|||
# Extraction du texte page par page |
|||
# Extraction des paragraphes |
|||
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): |
|||
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: |
|||
result = send_to_ollama(paragraph_cumul, target_lang=TARGET_LANGUAGE) |
|||
result = send_to_ollama(paragraph_cumul) |
|||
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: |
|||
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__": |
|||
main() |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue