Cela fait longtemps que je n'ai pas utilisé make
, alors supporte-moi ...
J'ai un répertoire, flac
, contenant des fichiers .FLAC. J'ai un répertoire correspondant, mp3
contenant des fichiers MP3. Si un fichier FLAC est plus récent que le fichier MP3 correspondant (ou que le fichier MP3 correspondant n'existe pas), je souhaite exécuter plusieurs commandes pour convertir le fichier FLAC en un fichier MP3 et copier les balises.
Le kicker: Je dois effectuer une recherche récursive dans le répertoire flac
et créer les sous-répertoires correspondants dans le répertoire mp3
. Les répertoires et les fichiers peuvent avoir des espaces dans les noms et sont nommés dans UTF-8.
Et je veux utiliser make
pour conduire ceci.
Je voudrais essayer quelque chose dans ce sens
FLAC_FILES = $(Shell find flac/ -type f -name '*.flac')
MP3_FILES = $(patsubst flac/%.flac, mp3/%.mp3, $(FLAC_FILES))
.PHONY: all
all: $(MP3_FILES)
mp3/%.mp3: flac/%.flac
@mkdir -p "$(@D)"
@echo convert "$<" to "$@"
Quelques notes rapides pour les débutants de make
:
@
devant les commandes empêche make
d'imprimer la commande avant de l'exécuter.$(@D)
est la partie répertoire du nom du fichier cible ($@
) Même si cela doit gérer tous les caractères et tout le matériel UTF-8, les espaces dans les noms de fichiers ou de répertoires échoueront, car make
utilise des espaces pour séparer les éléments dans les fichiers makefiles et je ne sais pas comment résoudre ce problème. Donc, cela ne vous laisse qu'un script Shell, j'ai bien peur: - /
Vous pouvez définir votre propre fonction générique récursive comme ceci:
rwildcard=$(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst *,%,$2),$d))
Le premier paramètre ($1
) est le nom du répertoire et le second ($2
) est le modèle que vous souhaitez associer.
Pour trouver tous les fichiers C dans le répertoire actuel:
$(call rwildcard, , *.c)
Pour trouver tous les fichiers .c
et .h
dans src
:
$(call rwildcard, src/, *.c *.h)
FWIW, j'ai utilisé quelque chose comme ceci dans un Makefile :
RECURSIVE_MANIFEST = `find . -type f -print`
L'exemple ci-dessus recherche dans le répertoire en cours ( '.' ) tous les "fichiers ordinaires" ( "-type f ') et définit la variable RECURSIVE_MANIFEST
make sur chaque fichier trouvé. Vous pouvez ensuite utiliser des substitutions de modèle pour réduire cette liste ou vous pouvez également fournir davantage d'arguments dans find pour limiter ce qui est renvoyé. Voir la page de manuel pour find .
Ma solution est basée sur celle ci-dessus, utilise sed
au lieu de patsubst
pour modifier le résultat de find
ET échapper aux espaces.
Aller de flac/à ogg /
OGGS = $(Shell find flac -type f -name "*.flac" | sed 's/ /\\ /g;s/flac\//ogg\//;s/\.flac/\.ogg/' )
Mises en garde:
Si vous utilisez Bash 4.x, vous pouvez utiliser une nouvelle option de globbing , par exemple:
Shell:=/bin/bash -O globstar
list:
@echo Flac: $(Shell ls flac/**/*.flac)
@echo MP3: $(Shell ls mp3/**/*.mp3)
Ce type de joker récursif peut trouver tous les fichiers qui vous intéressent (.flac, .mp3 ou autre). O
Voici un script Python que j'ai rapidement piraté pour résoudre le problème initial: conserver une copie compressée d'une bibliothèque musicale. Le script convertira les fichiers .m4a (supposés être ALAC) au format AAC, sauf si le fichier AAC existe déjà et est plus récent que le fichier ALAC. Les fichiers MP3 de la bibliothèque seront liés, car ils sont déjà compressés.
Méfiez-vous simplement que l'avortement du script (ctrl-c) laissera un fichier à moitié converti.
Au départ, je voulais aussi écrire un Makefile pour gérer cela, mais comme il ne peut pas gérer les espaces dans les noms de fichiers (voir la réponse acceptée) et que l'écriture d'un script bash garantit de me mettre dans un monde de douleur, c'est Python. C'est assez simple et court, et devrait donc être facile à ajuster à vos besoins.
from __future__ import print_function
import glob
import os
import subprocess
UNCOMPRESSED_DIR = 'Music'
COMPRESSED = 'compressed_'
UNCOMPRESSED_EXTS = ('m4a', ) # files to convert to lossy format
LINK_EXTS = ('mp3', ) # files to link instead of convert
for root, dirs, files in os.walk(UNCOMPRESSED_DIR):
out_root = COMPRESSED + root
if not os.path.exists(out_root):
os.mkdir(out_root)
for file in files:
file_path = os.path.join(root, file)
file_root, ext = os.path.splitext(file_path)
if ext[1:] in LINK_EXTS:
if not os.path.exists(COMPRESSED + file_path):
print('Linking {}'.format(file_path))
link_source = os.path.relpath(file_path, out_root)
os.symlink(link_source, COMPRESSED + file_path)
continue
if ext[1:] not in UNCOMPRESSED_EXTS:
print('Skipping {}'.format(file_path))
continue
out_file_path = COMPRESSED + file_path
if (os.path.exists(out_file_path)
and os.path.getctime(out_file_path) > os.path.getctime(file_path)):
print('Up to date: {}'.format(file_path))
continue
print('Converting {}'.format(file_path))
subprocess.call(['ffmpeg', '-y', '-i', file_path,
'-c:a', 'libfdk_aac', '-vbr', '4',
out_file_path])
Bien entendu, cela peut être amélioré pour effectuer le codage en parallèle. Ceci est laissé comme exercice au lecteur ;-)