import os import sys import glob import re from pathlib import Path from pymkv import MKVFile, MKVTrack #: Dictionnaire de correspondance entre les codes de langue et leur nom complet en français LANG_MAP = { 'fre': 'Français', 'eng': 'Anglais', 'ita': 'Italien', 'spa': 'Espagnol', 'por': 'Portugais', 'rus': 'Russe', 'jpn': 'Japonais', 'chi': 'Chinois', 'kor': 'Coréen', 'nld': 'Néerlandais', } TRADUCTIONS = { "Forced": "Forcé", "Full": "Complet", "French": "Français", "English": "Anglais", "Japan": "Japonais", "subtitle": "Sous-titre", } def traduire(texte): for ancien, nouveau in TRADUCTIONS.items(): texte = re.sub(re.escape(ancien), nouveau, texte, flags=re.IGNORECASE) return texte def ligne_bleu(): """Affiche une ligne bleue pleine largeur dans le terminal.""" try: cols = os.get_terminal_size().columns except OSError: cols = 100 print("\033[0;34m" + "█" * cols + "\033[0m") def find_episode_file(source_dir, saison, episode): """ Recherche un fichier d'épisode dans un répertoire donné (insensible à la casse). :param source_dir: Répertoire source :param saison: Numéro de la saison :param episode: Numéro de l'épisode :return: Chemin du fichier trouvé ou None """ target = f"s0{saison}e{episode}".lower() for file in glob.glob(os.path.join(source_dir, "*.mkv")): if target in os.path.basename(file).lower(): return file return None def find_first_video_track_index(mkv_file): """ Recherche l'index de la première piste vidéo dans un fichier MKV. :param mkv_file: Objet MKVFile :return: Index de la piste vidéo ou None """ for idx, track in enumerate(mkv_file.tracks): if track.track_type == 'video': return idx return None def has_named_chapters(chapters): """ Détermine si une liste de chapitres contient des noms réels (autres que des génériques). """ import re pattern = re.compile(r'(?i)^\s*(chapter|chapitre)\s*\d+\s*$') return any(not pattern.match(c.name or '') for c in chapters) def process_episode(episode, source_dir_1, source_dir_2, saison, serie_name, dest_dir): """ Traite un épisode en combinant les pistes vidéo et audio/sous-titres de deux sources. :param episode: Numéro de l'épisode (format chaîne "01", "02", etc.) :param source_dir_1: Répertoire contenant la source vidéo :param source_dir_2: Répertoire contenant la source audio/sous-titres :param saison: Numéro de saison :param serie_name: Nom de la série :param dest_dir: Répertoire de sortie :return: None """ # Affiche une ligne bleue pour la lisibilité dans le terminal ligne_bleu() # Crée le chemin complet pour le fichier de sortie output_filename = f"{serie_name} S0{saison} E{episode}.mkv" output_path = os.path.join(dest_dir, output_filename) # Vérifie si le fichier final existe déjà if os.path.exists(output_path): print(f'\033[91m{output_filename} existe déjà.\033[0m') return # Recherche les fichiers source correspondants à l'épisode source_1_file = find_episode_file(source_dir_1, saison, episode) source_2_file = find_episode_file(source_dir_2, saison, episode) # Gère les cas où un fichier source est manquant if not source_1_file and source_2_file: print(f"Fichier source 1 manquant pour l'épisode {episode}") return if source_1_file and not source_2_file: print(f"Fichier source 2 manquant pour l'épisode {episode}") return if not source_1_file and not source_2_file: return # Crée un objet MKVFile pour le fichier final final_mkv = MKVFile() # Charge les fichiers source source_1_mkv = MKVFile(source_1_file) source_2_mkv = MKVFile(source_2_file) # Ajout de la piste vidéo principale video_track_index = find_first_video_track_index(source_1_mkv) if video_track_index is not None: final_mkv.add_track(source_1_mkv.tracks[video_track_index]) # Sélection des pistes audio (FR prioritaires) audio_tracks_fr = [t for t in source_2_mkv.tracks if t.track_type == 'audio' and t.language == 'fre'] audio_tracks_other = [t for t in source_2_mkv.tracks if t.track_type == 'audio' and t.language != 'fre'] for idx, track in enumerate(audio_tracks_fr): track.default_track = (idx == 0) if not track.track_name: track.track_name = LANG_MAP.get(track.language, track.language.title()) for track in audio_tracks_other: track.default_track = False if not track.track_name: track.track_name = LANG_MAP.get(track.language, track.language.title()) # Sélection des pistes sous-titres (FR prioritaires) subtitle_tracks_fr = [t for t in source_2_mkv.tracks if t.track_type == 'subtitles' and t.language == 'fre'] subtitle_tracks_other = [t for t in source_2_mkv.tracks if t.track_type == 'subtitles' and t.language != 'fre'] for idx, track in enumerate(subtitle_tracks_fr): track.default_track = (idx == 0) if not track.track_name: codec = track._track_codec if track.forced_track: track.track_name = f"FR Forcé [{codec}]" else: track.track_name = f"FR Complet [{codec}]" for track in subtitle_tracks_other: track.default_track = False # Ajout de toutes les pistes audio et sous-titres for track in audio_tracks_fr + audio_tracks_other + subtitle_tracks_fr + subtitle_tracks_other: # Traduction des noms des pistes track.track_name = traduire(track.track_name) final_mkv.add_track(track) # Traitement des chapitres try: chapters_1 = (source_1_mkv._info_json.get('chapters'))[0].get('num_entries') except (AttributeError, IndexError): chapters_1 = 0 try: chapters_2 = (source_2_mkv._info_json.get('chapters'))[0].get('num_entries') except (AttributeError, IndexError): chapters_2 = 0 valid_1 = chapters_1 >= 4 valid_2 = chapters_2 >= 4 if valid_1 and len(chapters_1) >= len(chapters_2): source_2_mkv.no_chapters() elif valid_2: source_1_mkv.no_chapters() else: source_1_mkv.no_chapters() source_2_mkv.no_chapters() # Éffacer tous les tags source_1_mkv.no_global_tags() source_2_mkv.no_global_tags() final_mkv.no_global_tags() source_1_mkv.no_track_tags() source_2_mkv.no_track_tags() final_mkv.no_track_tags() final_mkv.title = "" #print(final_mkv.command(output_path)) final_mkv.mux(output_path) print(f"✔ Fichier généré : {output_path}") def main(): """ Point d'entrée principal du script. Lit les arguments de ligne de commande, prépare les répertoires, et lance le traitement de chaque épisode (de 01 à 30). """ if len(sys.argv) != 5: print(f'Usage: {sys.argv[0]} ') sys.exit(1) source_dir_1 = sys.argv[1] source_dir_2 = sys.argv[2] serie_name = sys.argv[3] saison = sys.argv[4] dest_dir = f"/media/data/reencoded/{serie_name}/Saison {saison}" Path(dest_dir).mkdir(parents=True, exist_ok=True) for i in range(1, 31): episode = f"{i:02}" process_episode(episode, source_dir_1, source_dir_2, saison, serie_name, dest_dir) if __name__ == '__main__': main()