我已经有一段时间没有使用
make
了,所以请耐心等待...
我有一个目录,
flac
,其中包含 .FLAC 文件。我有一个相应的目录,mp3
,包含 MP3 文件。如果 FLAC 文件比相应的 MP3 文件新(或者相应的 MP3 文件不存在),那么我想运行一堆命令将 FLAC 文件转换为 MP3 文件,并复制标签。
更关键的是:我需要递归搜索
flac
目录,并在 mp3
目录中创建相应的子目录。目录和文件的名称中可以有空格,并以 UTF-8 命名。
我想用
make
来驱动它。
我会尝试一些类似的事情
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 "$@"
给
make
初学者的一些快速说明:
@
可防止 make
在实际运行命令之前打印命令。$(@D)
是目标文件名的目录部分 ($@
) 即使这应该处理所有 UTF-8 字符和内容,它也会在文件或目录名中的空格处失败,因为
make
使用空格来分隔 makefile 中的内容,我不知道有什么方法可以解决这个问题。恐怕只剩下一个 shell 脚本了:-/
您可以像这样定义自己的递归通配符函数:
rwildcard=$(foreach d,$(wildcard $(1:=/*)),$(call rwildcard,$d,$2) $(filter $(subst *,%,$2),$d))
第一个参数(
$1
)是目录列表,第二个参数($2
)是要匹配的模式列表。
查找当前目录下的所有C文件:
$(call rwildcard,.,*.c)
要查找
.c
中的所有 .h
和 src
文件:
$(call rwildcard,src,*.c *.h)
此功能基于本文的实现,并进行了一些改进。
如果您使用的是 Bash 4.x,则可以使用 新的 globbing 选项,例如:
SHELL:=/bin/bash -O globstar
list:
@echo Flac: $(shell ls flac/**/*.flac)
@echo MP3: $(shell ls mp3/**/*.mp3)
这种递归通配符可以找到您感兴趣的所有文件(.flac,.mp3或其他)。
FWIW,我在 Makefile 中使用了类似的东西:
RECURSIVE_MANIFEST = `find . -type f -print`
上面的示例将从当前目录 ('.') 搜索所有“普通文件”('-type f'),并将
RECURSIVE_MANIFEST
make 变量设置为它找到的每个文件。然后,您可以使用模式替换来减少此列表,或者向 find 提供更多参数以缩小它返回的范围。请参阅 find 的手册页。
我的解决方案基于上面的解决方案,使用
sed
而不是 patsubst
来破坏 find
的输出并转义空格。
从flac/到ogg/
OGGS = $(shell find flac -type f -name "*.flac" | sed 's/ /\\ /g;s/flac\//ogg\//;s/\.flac/\.ogg/' )
注意事项:
这是我快速编写的一个 Python 脚本,用于解决最初的问题:保留音乐库的压缩副本。该脚本会将 .m4a 文件(假定为 ALAC)转换为 AAC 格式,除非 AAC 文件已存在并且比 ALAC 文件新。库中的 MP3 文件将被链接,因为它们已经被压缩。
请注意,中止脚本(ctrl-c)将留下半转换的文件。
我最初也想编写一个 Makefile 来处理这个问题,但由于它无法处理文件名中的空格(请参阅已接受的答案),而且编写 bash 脚本肯定会让我陷入痛苦的世界,所以 Python 就是这样。它相当简单且简短,因此应该很容易根据您的需求进行调整。
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])
当然,这可以增强以并行执行编码。这留给读者作为练习;-)
要递归查找文件而不诉诸外部依赖项(如
find
),您可以使用函数。然后使用另一个答案中的结果来转换文件。
rwildcard=$(wildcard $1) $(foreach d,$1,$(call rwildcard,$(addsuffix /$(notdir $d),$(wildcard $(dir $d)*))))
FLAC_FILES = $(call rwildcard,flac/*.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 "$@"