163 lines
5.6 KiB
Python
163 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
||
|
||
import os
|
||
import sys
|
||
import argparse
|
||
import ffmpeg
|
||
|
||
# Codes des couleurs ANSI
|
||
NOIR = "\033[30m"
|
||
ROUGE = "\033[31m"
|
||
VERT = "\033[32m"
|
||
ORANGE = "\033[33m" # On considère le marron comme orange
|
||
BLEU = "\033[34m"
|
||
VIOLET = "\033[35m"
|
||
CYAN = "\033[36m"
|
||
GRIS_CLAIR = "\033[37m"
|
||
|
||
GRIS_FONCE = "\033[90m"
|
||
ROUGE_CLAIR = "\033[91m"
|
||
VERT_CLAIR = "\033[92m"
|
||
JAUNE = "\033[93m"
|
||
BLEU_CLAIR = "\033[94m"
|
||
VIOLET_CLAIR = "\033[95m"
|
||
CYAN_CLAIR = "\033[96m"
|
||
BLANC = "\033[97m"
|
||
|
||
# Styles de texte
|
||
NORMAL = "\033[0m" # Normal (pas de style)
|
||
GRAS = "\033[1m" # Gras
|
||
SOULIGNE = "\033[4m" # Souligné
|
||
CLIGNOTANT = "\033[5m" # Clignotant
|
||
INVERSE = "\033[7m" # Inversé (texte clair sur fond sombre)
|
||
|
||
|
||
def couleur(nom_fichier, est_dossier):
|
||
if est_dossier:
|
||
return BLEU + nom_fichier + NORMAL
|
||
elif nom_fichier.lower().endswith(('.mp4', '.mkv', '.avi')):
|
||
return VIOLET + nom_fichier + NORMAL # 💜 fichier vidéo
|
||
else:
|
||
return nom_fichier
|
||
|
||
def analyser_fichier_video(chemin):
|
||
try:
|
||
probe = ffmpeg.probe(chemin, show_chapters=None) # Contrairement à ce que l'on pourrait croire, on demande les chapitres, c'est l'option show_chapters qui ne prend pas de paramètre.
|
||
info = []
|
||
max_index_len = len(str(len(probe.get("streams", [])) - 1))
|
||
streams = probe.get('streams', [])
|
||
|
||
# Filtrer pour ne garder que les flux pertinents (video, audio, subtitle)
|
||
flux_valides = [stream for stream in streams if stream.get('codec_type') in ['video', 'audio', 'subtitle']]
|
||
|
||
# Si on n'a pas de flux valide à afficher, on s'arrête
|
||
if not flux_valides:
|
||
return []
|
||
|
||
for i, stream in enumerate(flux_valides):
|
||
codec_type = stream.get('codec_type', 'und')
|
||
codec_name = stream.get('codec_name', 'und')
|
||
langue = stream.get('tags', {}).get('language', 'und')
|
||
titre = stream.get('tags', {}).get('title', '')
|
||
disposition = stream.get('disposition', {})
|
||
defaut = f"{ROUGE}défaut{NORMAL}" if disposition.get('default') == 1 else ""
|
||
force = f"{ROUGE}forcé{NORMAL}" if disposition.get('forced') == 1 else ""
|
||
|
||
# Déterminer les informations à afficher
|
||
ligne = (
|
||
f"{ORANGE}Stream{NORMAL} "
|
||
f"{ROUGE}{i}{NORMAL}"
|
||
f"{BLANC}:{NORMAL} "
|
||
f"{BLEU}{codec_type:<8}{NORMAL} "
|
||
f"{BLANC}({codec_name}, {langue}){NORMAL}"
|
||
)
|
||
|
||
# Ajouter les flags défaut/forcé s'ils existent
|
||
flags = []
|
||
if defaut:
|
||
flags.append(defaut)
|
||
if force:
|
||
flags.append(force)
|
||
if flags:
|
||
ligne += f" {' '.join(flags)}"
|
||
|
||
if titre:
|
||
ligne += f" {VERT}\"{titre}\"{NORMAL}"
|
||
|
||
info.append(ligne)
|
||
|
||
nb_chapitres = len(probe.get('chapters', []))
|
||
info.append(
|
||
f"{ORANGE}Chapitres{NORMAL} "
|
||
f"{BLANC}:{NORMAL} "
|
||
f"{VERT}{nb_chapitres}{NORMAL}")
|
||
|
||
return info
|
||
|
||
except ffmpeg.Error as e:
|
||
return [f"{ROUGE}Erreur ffmpeg : {e}{NORMAL}"]
|
||
except Exception as e:
|
||
return [f"{ROUGE}Erreur : {e}{NORMAL}"]
|
||
|
||
def afficher_arborescence(dossier, prefixe="", tout_afficher=False, niveau_max=None, niveau_actuel=0):
|
||
if niveau_max is not None and niveau_actuel > niveau_max:
|
||
return
|
||
|
||
try:
|
||
elements = sorted(
|
||
os.listdir(dossier),
|
||
key=lambda x: (not os.path.isdir(os.path.join(dossier, x)), x)
|
||
)
|
||
except PermissionError:
|
||
print(prefixe + "└── [Permission refusée]")
|
||
return
|
||
|
||
if not tout_afficher:
|
||
elements = [e for e in elements if not e.startswith('.')]
|
||
for index, nom in enumerate(elements):
|
||
chemin_complet = os.path.join(dossier, nom)
|
||
est_dernier = (index == len(elements) - 1)
|
||
branche = "└── " if est_dernier else "├── "
|
||
sous_prefixe = " " if est_dernier else "│ "
|
||
est_dossier = os.path.isdir(chemin_complet)
|
||
print(prefixe + branche + couleur(nom, est_dossier))
|
||
|
||
if est_dossier:
|
||
afficher_arborescence(
|
||
chemin_complet,
|
||
prefixe + sous_prefixe,
|
||
tout_afficher=tout_afficher,
|
||
niveau_max=niveau_max,
|
||
niveau_actuel=niveau_actuel + 1
|
||
)
|
||
elif nom.lower().endswith(('.mp4', '.mkv', '.avi')):
|
||
infos = analyser_fichier_video(chemin_complet)
|
||
for i, ligne in enumerate(infos):
|
||
print(prefixe + sous_prefixe + ("└── " if i == len(infos) - 1 else "├── ") + ligne)
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(description="Affiche l’arborescence avec informations sur les fichiers vidéo.")
|
||
parser.add_argument("dossiers", nargs="*", default=["."], help="Un ou plusieurs dossiers à analyser")
|
||
parser.add_argument("-a", "--tout", action="store_true", help="Afficher les fichiers masqués")
|
||
parser.add_argument("-L", "--niveau", type=int, help="Niveau d’affichage maximale")
|
||
|
||
args = parser.parse_args()
|
||
|
||
try:
|
||
for dossier in args.dossiers:
|
||
if not os.path.exists(dossier):
|
||
print(f"{ROUGE}Erreur : le dossier '{dossier}' n'existe pas.{NORMAL}")
|
||
continue
|
||
|
||
print(couleur(dossier, est_dossier=True))
|
||
afficher_arborescence(
|
||
dossier,
|
||
tout_afficher=args.tout,
|
||
niveau_max=args.niveau
|
||
)
|
||
except KeyboardInterrupt:
|
||
sys.exit(0)
|
||
|
||
if __name__ == "__main__":
|
||
main()
|