NZBGET_FIX_ENCODING/FixEncoding.py

227 lines
7.0 KiB
Python

#!/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()