#!/usr/bin/env python3 # -*- coding: utf-8 -*- # ############################################################################## ### NZBGET POST-PROCESSING SCRIPT ### # # Corrige les problèmes d'encodage dans les noms de fichiers. # # Ce script détecte et corrige les noms de fichiers qui ont été mal encodés # (UTF-8 interprété comme ISO-8859-1/Windows-1252) et les renomme correctement. # # NOTE: Ce script utilise Python 3. # ############################################################################## ### OPTIONS ### # Activer le mode débogage (affiche plus d'informations dans les logs). # # yes, no. #Debug=no # Extensions de fichiers à traiter (séparées par des virgules). # Laisser vide pour traiter tous les fichiers. # # Exemples: .flac,.mp3,.mkv,.mp4 #FileExtensions= # Mode de simulation (ne renomme pas les fichiers, affiche seulement ce qui serait fait). # # yes, no. #DryRun=no ### NZBGET POST-PROCESSING SCRIPT ### ############################################################################## import os import sys import re # Exit codes utilisés par NZBGet POSTPROCESS_SUCCESS = 93 POSTPROCESS_ERROR = 94 POSTPROCESS_NONE = 95 def is_debug(): """Vérifie si le mode debug est activé.""" return os.environ.get('NZBPO_DEBUG', 'no').lower() == 'yes' def is_dry_run(): """Vérifie si le mode simulation est activé.""" return os.environ.get('NZBPO_DRYRUN', 'no').lower() == 'yes' def get_file_extensions(): """Récupère la liste des extensions de fichiers à traiter.""" extensions = os.environ.get('NZBPO_FILEEXTENSIONS', '') if not extensions: return None return [ext.strip().lower() for ext in extensions.split(',')] def print_log(message, level='INFO'): """Affiche un message dans les logs NZBGet.""" print(f'[{level}] {message}') sys.stdout.flush() def is_encoding_issue(filename): """ Détecte si un nom de fichier contient des problèmes d'encodage typiques. Recherche des motifs caractéristiques de l'UTF-8 mal interprété comme ISO-8859-1: - à suivi de caractères accentués (à, â, é, è, ê, ô, etc.) """ # Motifs caractéristiques d'un problème d'encodage UTF-8 -> ISO-8859-1 patterns = [ 'é', # é mal encodé 'è', # è mal encodé 'ê', # ê mal encodé 'ë', # ë mal encodé 'à ', # à mal encodé 'â', # â mal encodé 'ä', # ä mal encodé 'ç', # ç mal encodé 'ô', # ô mal encodé 'ö', # ö mal encodé 'ù', # ù mal encodé 'û', # û mal encodé 'ü', # ü mal encodé 'î', # î mal encodé 'ï', # ï mal encodé 'Å"', # œ mal encodé 'É', # É mal encodé 'À', # À mal encodé ] return any(pattern in filename for pattern in patterns) def fix_encoding(filename): """ Corrige le nom de fichier en supposant qu'il a été mal interprété. Le problème typique est: UTF-8 -> interprété comme ISO-8859-1 Solution: encoder en ISO-8859-1 (pour revenir aux octets originaux UTF-8) puis décoder en UTF-8 """ try: # Tente de corriger l'encodage # On encode d'abord en ISO-8859-1 pour récupérer les octets originaux # puis on décode en UTF-8 fixed = filename.encode('iso-8859-1').decode('utf-8') return fixed except (UnicodeDecodeError, UnicodeEncodeError): # Si la conversion échoue, on retourne le nom original return None def process_file(filepath, dirname, filename, extensions_filter, dry_run): """ Traite un fichier et le renomme si nécessaire. Returns: bool: True si le fichier a été renommé, False sinon """ # Vérifie l'extension si un filtre est défini if extensions_filter: file_ext = os.path.splitext(filename)[1].lower() if file_ext not in extensions_filter: return False # Vérifie si le nom contient des problèmes d'encodage if not is_encoding_issue(filename): return False # Tente de corriger l'encodage fixed_filename = fix_encoding(filename) if not fixed_filename or fixed_filename == filename: if is_debug(): print_log(f'Impossible de corriger: {filename}', 'DEBUG') return False # Construit les chemins complets old_path = os.path.join(dirname, filename) new_path = os.path.join(dirname, fixed_filename) # Vérifie que le nouveau nom n'existe pas déjà if os.path.exists(new_path): print_log(f'Le fichier de destination existe déjà: {fixed_filename}', 'WARNING') return False # Affiche l'action print_log(f'Renommage: {filename}') print_log(f' -> {fixed_filename}') # Renomme le fichier (sauf en mode simulation) if not dry_run: try: os.rename(old_path, new_path) return True except OSError as e: print_log(f'Erreur lors du renommage: {e}', 'ERROR') return False else: print_log('[MODE SIMULATION - Fichier non renommé]', 'INFO') return True def main(): """Fonction principale du script.""" # Récupère le répertoire de travail depuis les variables d'environnement NZBGet nzb_directory = os.environ.get('NZBPP_DIRECTORY') if not nzb_directory: print_log('Erreur: NZBPP_DIRECTORY non défini', 'ERROR') sys.exit(POSTPROCESS_ERROR) if not os.path.isdir(nzb_directory): print_log(f'Erreur: Le répertoire n\'existe pas: {nzb_directory}', 'ERROR') sys.exit(POSTPROCESS_ERROR) print_log('=== Début du traitement de correction d\'encodage ===') print_log(f'Répertoire: {nzb_directory}') # Récupère les options extensions_filter = get_file_extensions() dry_run = is_dry_run() if dry_run: print_log('MODE SIMULATION ACTIVÉ - Aucun fichier ne sera modifié', 'WARNING') if extensions_filter: print_log(f'Filtrage par extensions: {", ".join(extensions_filter)}') # Parcourt tous les fichiers récursivement files_renamed = 0 files_processed = 0 for dirpath, dirnames, filenames in os.walk(nzb_directory): for filename in filenames: files_processed += 1 if process_file(os.path.join(dirpath, filename), dirpath, filename, extensions_filter, dry_run): files_renamed += 1 # Affiche le résumé print_log('=== Résumé ===') print_log(f'Fichiers traités: {files_processed}') print_log(f'Fichiers renommés: {files_renamed}') if files_renamed > 0: print_log('Correction d\'encodage terminée avec succès') sys.exit(POSTPROCESS_SUCCESS) else: print_log('Aucun fichier à corriger') sys.exit(POSTPROCESS_NONE) if __name__ == '__main__': main()