Compare commits

..

4 Commits

Author SHA1 Message Date
5d7975cf45 Fix pipx commande pour installer les dépendances depuis pypi.org
All checks were successful
Build & Publish Python Packages / build-and-publish (push) Successful in 11s
2025-07-08 22:39:10 +02:00
a88a3203d8 Modification de README.md pour prendre en charge les paquets PyPi
All checks were successful
Build & Publish Python Packages / build-and-publish (push) Successful in 12s
2025-07-08 17:06:19 +02:00
5fa468d849 Fix nom des projets pour les paquets PyPi
All checks were successful
Build & Publish Python Packages / build-and-publish (push) Successful in 13s
2025-07-08 17:00:33 +02:00
477be5a30a Add CI workflow 2025-07-08 17:00:18 +02:00
4 changed files with 80 additions and 93 deletions

View File

@ -1,3 +1,5 @@
name: Build & Publish Python Packages
on: on:
push: push:
branches: branches:
@ -6,84 +8,87 @@ on:
jobs: jobs:
build-and-publish: build-and-publish:
name: 🐍 Build & Publish Python Packages runs-on: docker
runs-on: ubuntu-latest
steps: steps:
- name: 📦 Cloner le dépôt - name: 📦 Cloner le dépôt
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: 🔧 Installer les outils Python
run: |
python3 -m pip install --break-system-packages setuptools wheel twine
- name: 🔍 Sélectionner les projets à publier - name: 🔍 Sélectionner les projets à publier
id: detect id: detect
env: env:
DEVPI_URL: ${{ vars.DEVPI_URL }} REGISTRY_URL: ${{ vars.REGISTRY_URL }}
DEVPI_USERNAME: ${{ secrets.DEVPI_USERNAME }} REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
DEBUG: true
run: | run: |
set +e # Ne pas interrompre le script en cas d'erreur set +e # Ne pas interrompre le script en cas d'erreur
git fetch origin master --depth=2 || true git fetch origin master --depth=2 || true
CHANGED=$(git diff --name-only HEAD~1 HEAD || echo "") CHANGED=$(git diff --name-only HEAD~1 HEAD || echo "")
#echo "🔍 Fichiers modifiés: $CHANGED" [ "$DEBUG" = true ] && echo "🔍 Fichiers modifiés: $CHANGED"
SELECTED="" SELECTED=""
for dir in */; do for dir in */; do
if [ ! -f "$dir/setup.py" ]; then if [ ! -f "$dir/setup.py" ]; then
echo "⏭️ $dir ignoré (pas de setup.py)" [ "$DEBUG" = true ] && echo "⏭️ $dir ignoré (pas de setup.py)"
continue continue
fi fi
NAME=$(cd "$dir" && python3 setup.py --name 2>/dev/null) NAME=$(cd "$dir" && python3 setup.py --name 2>/dev/null)
VERSION=$(cd "$dir" && python3 setup.py --version 2>/dev/null)
if [ -z "$NAME" ]; then if [ -z "$NAME" ]; then
echo "⚠️ Aucun nom récupéré pour $dir" [ "$DEBUG" = true ] && echo "⚠️ Aucun nom récupéré pour $dir"
continue continue
fi fi
VERSION=$(cd "$dir" && python3 setup.py --version 2>/dev/null) SHOULD_INCLUDE=0
if [ -z "$VERSION" ]; then
echo "⚠️ Aucune version récupéré pour $dir"
continue
fi
for f in $CHANGED; do for f in $CHANGED; do
if [[ "$f" == "$dir"* ]]; then if [[ "$f" == "$dir"* ]]; then
echo "🟡 Modification détectée dans $dir" [ "$DEBUG" = true ] && echo "🟡 Modification détectée dans $dir"
SHOULD_INCLUDE=$((SHOULD_INCLUDE + 1))
fi fi
done done
API_URL="https://$DEVPI_URL/$DEVPI_USERNAME/dev/$NAME/$VERSION" API_URL="https://$REGISTRY_USERNAME:$REGISTRY_PASSWORD@$REGISTRY_URL/api/v1/packages/$REGISTRY_USERNAME/pypi/$NAME/$VERSION"
STATUS_CODE=$(curl -L -s -o /dev/null -w "%{http_code}" "$API_URL") STATUS_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$API_URL")
if [ "$STATUS_CODE" = "404" ]; then if [ "$STATUS_CODE" = "404" ]; then
echo "📦 $NAME version $VERSION n'existe pas (HTTP $STATUS_CODE)" [ "$DEBUG" = true ] && echo "📦 $NAME version $VERSION n'existe pas (HTTP $STATUS_CODE)"
SELECTED+="$dir " SHOULD_INCLUDE=$((SHOULD_INCLUDE + 1))
else else
echo "🔄 $NAME version $VERSION déjà présente (HTTP $STATUS_CODE)" [ "$DEBUG" = true ] && echo "🔄 $NAME version $VERSION déjà présente (HTTP $STATUS_CODE)"
fi
if [ "$SHOULD_INCLUDE" -eq 2 ]; then
SELECTED+="$dir "
fi fi
done done
echo "selected_projects=$SELECTED" >> $GITHUB_OUTPUT echo "selected_projects=$SELECTED" >> $GITHUB_OUTPUT
echo "✅ Projets sélectionnés: $SELECTED" echo "✅ Projets sélectionnés: $SELECTED"
- name: 🔧 Installer les outils Python
if: steps.detect.outputs.selected_projects != ''
run: |
python3 -m pip install --break-system-packages setuptools wheel twine
- name: 🚀 Publier les projets sélectionnés - name: 🚀 Publier les projets sélectionnés
if: steps.detect.outputs.selected_projects != '' if: steps.detect.outputs.selected_projects != ''
env: env:
DEVPI_URL: ${{ vars.DEVPI_URL }} REGISTRY_URL: ${{ vars.REGISTRY_URL }}
DEVPI_USERNAME: ${{ secrets.DEVPI_USERNAME }} REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
DEVPI_PASSWORD: ${{ secrets.DEVPI_PASSWORD }} REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
run: | run: |
for dir in ${{ steps.detect.outputs.selected_projects }}; do for dir in ${{ steps.detect.outputs.selected_projects }}; do
echo "🚀 Publication de $dir" echo "🚀 Publication de $dir"
cd "$dir" cd "$dir"
python3 setup.py sdist python3 setup.py sdist
twine upload \ twine upload \
--repository-url https://$DEVPI_URL/$DEVPI_USERNAME/dev \ --repository-url https://$REGISTRY_URL/api/packages/$REGISTRY_USERNAME/pypi \
-u "$DEVPI_USERNAME" \ -u "$REGISTRY_USERNAME" \
-p "$DEVPI_PASSWORD" \ -p "$REGISTRY_PASSWORD" \
dist/* || exit 1 dist/* || exit 1
cd - cd -
done done

View File

@ -2,8 +2,6 @@
Ce dépôt contient plusieurs projets Python que vous pouvez installer et utiliser avec `pipx`. Ce dépôt contient plusieurs projets Python que vous pouvez installer et utiliser avec `pipx`.
Dépôt PyPi → [🔗 devpi.netdldata.net](https://devpi.netdldata.net/lionel/dev)
## Installation de pipx ## Installation de pipx
Si `pipx` n'est pas installé, vous pouvez l'installer en utilisant les commandes suivantes selon votre distribution : Si `pipx` n'est pas installé, vous pouvez l'installer en utilisant les commandes suivantes selon votre distribution :
@ -37,7 +35,7 @@ pipx ensurepath
Un outil pour encoder des vidéos de manière récursive en utilisant HandBrake. Il permet de traiter des fichiers vidéo dans un répertoire donné, en utilisant des presets définis pour le transcodage. Il génère également un résumé des opérations effectuées dans un fichier Excel. Un outil pour encoder des vidéos de manière récursive en utilisant HandBrake. Il permet de traiter des fichiers vidéo dans un répertoire donné, en utilisant des presets définis pour le transcodage. Il génère également un résumé des opérations effectuées dans un fichier Excel.
```bash ```bash
pipx install --index-url https://devpi.netdldata.net/lionel/dev/+simple HandBrake-recursive pipx install --pip-args="--extra-index-url https://git.netdldata.net/api/packages/lionel/pypi/simple/" HandBrake-recursive
``` ```
### proxmox_export_disk ### proxmox_export_disk
@ -45,7 +43,7 @@ pipx install --index-url https://devpi.netdldata.net/lionel/dev/+simple HandBrak
Un script pour exporter des informations sur les disques depuis Proxmox. Il récupère les détails des disques des machines virtuelles et des conteneurs sur un serveur Proxmox, et exporte ces informations dans un fichier Excel ou ODS. Un script pour exporter des informations sur les disques depuis Proxmox. Il récupère les détails des disques des machines virtuelles et des conteneurs sur un serveur Proxmox, et exporte ces informations dans un fichier Excel ou ODS.
```bash ```bash
pipx install --index-url https://devpi.netdldata.net/lionel/dev/+simple proxmox-export-disk pipx install --pip-args="--extra-index-url https://git.netdldata.net/api/packages/lionel/pypi/simple/" proxmox-export-disk
``` ```
### tree_stream ### tree_stream
@ -53,7 +51,7 @@ pipx install --index-url https://devpi.netdldata.net/lionel/dev/+simple proxmox-
Un utilitaire pour afficher la structure des répertoires sous forme de flux. Il permet de visualiser l'arborescence des fichiers et des dossiers avec des informations supplémentaires sur les fichiers vidéo, comme les flux et les chapitres. Un utilitaire pour afficher la structure des répertoires sous forme de flux. Il permet de visualiser l'arborescence des fichiers et des dossiers avec des informations supplémentaires sur les fichiers vidéo, comme les flux et les chapitres.
```bash ```bash
pipx install --index-url https://devpi.netdldata.net/lionel/dev/+simple tree-stream pipx install --pip-args="--extra-index-url https://git.netdldata.net/api/packages/lionel/pypi/simple/" tree-stream
``` ```
### multiplex ### multiplex
@ -62,5 +60,5 @@ Un script Python pour assembler des pistes vidéo, audio et sous-titres issues d
Il gère la priorité des pistes françaises, la définition des pistes par défaut, la gestion des chapitres et le renommage intelligent des pistes. Il gère la priorité des pistes françaises, la définition des pistes par défaut, la gestion des chapitres et le renommage intelligent des pistes.
```bash ```bash
pipx install --index-url https://devpi.netdldata.net/lionel/dev/+simple multiplex pipx install --pip-args="--extra-index-url https://git.netdldata.net/api/packages/lionel/pypi/simple/" multiplex
``` ```

View File

@ -2,7 +2,6 @@ import os
import sys import sys
import glob import glob
import re import re
import argparse
from pathlib import Path from pathlib import Path
from pymkv import MKVFile, MKVTrack from pymkv import MKVFile, MKVTrack
@ -51,13 +50,9 @@ def find_episode_file(source_dir, saison, episode):
:param episode: Numéro de l'épisode :param episode: Numéro de l'épisode
:return: Chemin du fichier trouvé ou None :return: Chemin du fichier trouvé ou None
""" """
pattern = re.compile(rf"s{int(saison):02d}\s*[ex]{int(episode):02d}", re.IGNORECASE) target = f"s0{saison}e{episode}".lower()
p = Path(source_dir) for file in glob.glob(os.path.join(source_dir, "*.mkv")):
# On récupère tous les fichiers mkv ou mp4 en une seule liste if target in os.path.basename(file).lower():
files = list(p.glob("*.mkv")) + list(p.glob("*.mp4"))
for file in files:
if pattern.search(os.path.basename(file)):
return file return file
return None return None
@ -73,7 +68,15 @@ def find_first_video_track_index(mkv_file):
return idx return idx
return None return None
def process_episode(episode, source_dir_1, source_dir_2, saison, serie_name, dest_dir, chapitre): 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. Traite un épisode en combinant les pistes vidéo et audio/sous-titres de deux sources.
@ -83,7 +86,6 @@ def process_episode(episode, source_dir_1, source_dir_2, saison, serie_name, des
:param saison: Numéro de saison :param saison: Numéro de saison
:param serie_name: Nom de la série :param serie_name: Nom de la série
:param dest_dir: Répertoire de sortie :param dest_dir: Répertoire de sortie
:param chapitre: Indique quels chapitres inclure dans le fichier final
:return: None :return: None
""" """
# Affiche une ligne bleue pour la lisibilité dans le terminal # Affiche une ligne bleue pour la lisibilité dans le terminal
@ -143,7 +145,7 @@ def process_episode(episode, source_dir_1, source_dir_2, saison, serie_name, des
subtitle_tracks_other = [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): for idx, track in enumerate(subtitle_tracks_fr):
track.default_track = (idx == 0) and track.default_track track.default_track = (idx == 0)
if not track.track_name: if not track.track_name:
codec = track._track_codec codec = track._track_codec
if track.forced_track: if track.forced_track:
@ -161,14 +163,27 @@ def process_episode(episode, source_dir_1, source_dir_2, saison, serie_name, des
final_mkv.add_track(track) final_mkv.add_track(track)
# Traitement des chapitres # Traitement des chapitres
if chapitre in ('v', 'video'): 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() source_2_mkv.no_chapters()
elif chapitre in ('a', 'audio'): elif valid_2:
source_1_mkv.no_chapters() source_1_mkv.no_chapters()
else: else:
source_1_mkv.no_chapters() source_1_mkv.no_chapters()
source_2_mkv.no_chapters() source_2_mkv.no_chapters()
# Éffacer tous les tags # Éffacer tous les tags
source_1_mkv.no_global_tags() source_1_mkv.no_global_tags()
source_2_mkv.no_global_tags() source_2_mkv.no_global_tags()
@ -186,57 +201,26 @@ def process_episode(episode, source_dir_1, source_dir_2, saison, serie_name, des
def main(): def main():
""" """
@brief Point d'entrée du script. Point d'entrée principal du script.
@details Parse les arguments en ligne de commande et lance le traitement
des épisodes de la série. Lit les arguments de ligne de commande, prépare les répertoires,
@arg -v, --video Répertoire source vidéo et lance le traitement de chaque épisode (de 01 à 30).
@arg -a, --audio Répertoire source audio/sous-titres
@arg -va Répertoire source commun pour vidéo et audio
@arg -n, --nom Nom de la série
@arg -s, --saison Numéro de la saison
@arg -d, --dest Répertoire de sortie
@return None
""" """
parser = argparse.ArgumentParser( if len(sys.argv) != 5:
description="Combine des épisodes MKV depuis deux sources (vidéo + audio/sous-titres)." print(f'Usage: {sys.argv[0]} <SOURCE_DIR_1> <SOURCE_DIR_2> <SERIE_NAME> <SAISON>')
) sys.exit(1)
parser.add_argument("-v", "--video", help="Répertoire source vidéo" ) source_dir_1 = sys.argv[1]
parser.add_argument("-a", "--audio", help="Répertoire source audio/sous-titres" ) source_dir_2 = sys.argv[2]
parser.add_argument("-va", help="Répertoire source commun pour vidéo et audio" ) serie_name = sys.argv[3]
parser.add_argument("-n", "--nom", required=True, help="Nom de la série") saison = sys.argv[4]
parser.add_argument("-s", "--saison", required=True, help="Numéro de la saison") dest_dir = f"/media/data/reencoded/{serie_name}/Saison {saison}"
parser.add_argument("-d", "--dest", required=True, help="Répertoire de sortie")
parser.add_argument("-c", "--chapitre", choices=['n', 'non', 'v', 'video', 'a', 'audio'], default='a', help="Gestion des chapitres : 'n'/'non' pour aucun, 'v'/'video' pour source vidéo, 'a'/'audio' pour source audio (par défaut 'a')")
args = parser.parse_args()
# Gestion de l'option commune -va
if args.va:
source_dir_1 = source_dir_2 = args.va
else:
source_dir_1 = args.video
source_dir_2 = args.audio
# Vérification que les deux dossiers sont renseignés
if not source_dir_1 or not source_dir_2:
parser.error("Vous devez fournir soit -va, soit à la fois -v et -a.")
serie_name = args.nom
saison = args.saison
chapitre = args.chapitre
# Chemin de sortie
dest_dir = f"{args.dest}/{serie_name}/Saison {saison}"
Path(dest_dir).mkdir(parents=True, exist_ok=True) Path(dest_dir).mkdir(parents=True, exist_ok=True)
# Compte le nombre de fichiers dans chaque répertoire source for i in range(1, 31):
count1 = sum(1 for _ in Path(source_dir_1).iterdir() if _.is_file())
count2 = sum(1 for _ in Path(source_dir_2).iterdir() if _.is_file())
for i in range(1, max(count1, count2) + 1):
episode = f"{i:02}" episode = f"{i:02}"
process_episode(episode, source_dir_1, source_dir_2, saison, serie_name, dest_dir, chapitre) process_episode(episode, source_dir_1, source_dir_2, saison, serie_name, dest_dir)
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -2,7 +2,7 @@ from setuptools import setup
setup( setup(
name='multiplex', name='multiplex',
version='0.1.2', version='0.1.0',
description='Un script pour multiplexer les fichiers vidéo avec mkvmerge.', description='Un script pour multiplexer les fichiers vidéo avec mkvmerge.',
py_modules=['multiplex'], py_modules=['multiplex'],
install_requires=[ install_requires=[