2025-06-09 20:40:31 +02:00

182 lines
6.5 KiB
Python

import os
import sys
import glob
from pymkv import MKVFile, MKVTrack
from pathlib import Path
#: 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',
}
def ligne_bleu():
"""Affiche une ligne bleue pleine largeur dans le terminal."""
cols = os.get_terminal_size().columns
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é.
: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
"""
pattern = f"*S0{saison}E{episode}*.mkv"
files = glob.glob(os.path.join(source_dir, pattern))
return files[0] if files else 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.
La vidéo provient de source_1_file.
Les pistes audio et sous-titres sont priorisées depuis source_2_file.
Les pistes audio/sous-titres en français sont réorganisées, annotées et une seule est définie par défaut.
: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
"""
# Affiche une ligne bleue pour la lisibilité dans le terminal
ligne_bleu()
# 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 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)
# Crée un objet MKVFile pour le fichier 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:
mkv.add_track(MKVTrack(source_1_file, track_id=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 track in subtitle_tracks_fr + subtitle_tracks_other:
track.default_track = False
# Renommage des pistes FR si exactement deux
if len(subtitle_tracks_fr) == 2:
for track in subtitle_tracks_fr:
codec = track.codec_id.upper()
if track.forced_track:
track.track_name = f"FR Forcé [{codec}]"
else:
track.track_name = f"FR Complet [{codec}]"
# Ajout de toutes les pistes audio et sous-titres
for track in audio_tracks_fr + audio_tracks_other + subtitle_tracks_fr + subtitle_tracks_other:
mkv.add_track(track)
# Traitement des chapitres : choix du fichier avec le plus de chapitres valides
chapters_1 = source_1_mkv.chapters or []
chapters_2 = source_2_mkv.chapters or []
if len(chapters_1) >= 4 and has_named_chapters(chapters_1) and len(chapters_1) >= len(chapters_2):
mkv.chapters = chapters_1
elif len(chapters_2) >= 4 and has_named_chapters(chapters_2):
mkv.chapters = chapters_2
else:
mkv.chapters = None
# Supprime le titre et génère le fichier final
mkv.title = ""
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("Usage: script.py <SOURCE_DIR_1> <SOURCE_DIR_2> <SERIE_NAME> <SAISON>")
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()