diff --git a/main.py b/main.py index a89e9bc..2957d7a 100644 --- a/main.py +++ b/main.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 """ - # "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']}
\nDate de modification: {formatted_date}
\nSystem: {info['system']}
\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'(?") - 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(): - # 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) - 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()