Ajout de HandBrake_recursive proxmox_export_disk tree_stream
This commit is contained in:
3
tree_stream/.gitignore
vendored
Normal file
3
tree_stream/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
build
|
||||
tree_stream.egg-info/
|
||||
.venv/
|
3
tree_stream/.idea/.gitignore
generated
vendored
Normal file
3
tree_stream/.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
6
tree_stream/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
tree_stream/.idea/inspectionProfiles/Project_Default.xml
generated
Normal 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>
|
6
tree_stream/.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
tree_stream/.idea/inspectionProfiles/profiles_settings.xml
generated
Normal 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
7
tree_stream/.idea/misc.xml
generated
Normal 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
8
tree_stream/.idea/modules.xml
generated
Normal 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
10
tree_stream/.idea/tree_stream.iml
generated
Normal 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>
|
3
tree_stream/pyproject.toml
Normal file
3
tree_stream/pyproject.toml
Normal file
@ -0,0 +1,3 @@
|
||||
[build-system]
|
||||
requires = ["setuptools", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
1
tree_stream/requirements.txt
Normal file
1
tree_stream/requirements.txt
Normal file
@ -0,0 +1 @@
|
||||
ffmpeg-python
|
22
tree_stream/setup.py
Normal file
22
tree_stream/setup.py
Normal 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',
|
||||
)
|
1
tree_stream/tree_stream/__init__.py
Normal file
1
tree_stream/tree_stream/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .main import main
|
162
tree_stream/tree_stream/main.py
Normal file
162
tree_stream/tree_stream/main.py
Normal 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 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()
|
Reference in New Issue
Block a user