Ajout de HandBrake_recursive proxmox_export_disk tree_stream

This commit is contained in:
lionel
2025-05-12 13:28:19 +02:00
commit 534b6d9455
32 changed files with 1247 additions and 0 deletions

3
tree_stream/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
build
tree_stream.egg-info/
.venv/

3
tree_stream/.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
</profile>
</component>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
tree_stream/.idea/misc.xml generated Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.11 (tree_stream)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (tree_stream)" project-jdk-type="Python SDK" />
</project>

8
tree_stream/.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/tree_stream.iml" filepath="$PROJECT_DIR$/.idea/tree_stream.iml" />
</modules>
</component>
</project>

10
tree_stream/.idea/tree_stream.iml generated Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.11 (tree_stream)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -0,0 +1,3 @@
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"

View File

@ -0,0 +1 @@
ffmpeg-python

22
tree_stream/setup.py Normal file
View File

@ -0,0 +1,22 @@
from setuptools import setup, find_packages
setup(
name="tree_stream",
version="0.1.0",
description="Affiche une arborescence enrichie des fichiers vidéo avec chapitres et flux",
author="TonNom",
packages=find_packages(),
install_requires=["ffmpeg-python"],
entry_points={
"console_scripts": [
"tree_stream=tree_stream.main:main",
]
},
classifiers=[
"Programming Language :: Python :: 3",
"Environment :: Console",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
python_requires='>=3.6',
)

View File

@ -0,0 +1 @@
from .main import main

View File

@ -0,0 +1,162 @@
#!/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 larborescence 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 daffichage 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()