最近呢,买了一个 iPad,虽然家里笔记本台式都有,显示器都是 2个,比较方便看代码(边打游戏边追剧)。
但是在床上拿笔记本始终还是不方便,手机在家看还是小了点,自从有 iPad 之后,拿个大屏在家里用着确实舒服不少。
能追剧,能玩玩其他应用,那还得听听音乐不是,但是懂的都懂,苹果里导入文件是个麻烦事,更别说音乐播放。
所以这件事就得研究研究,因为在电脑上已经把音乐都按照文件夹整理好了。
在 Android 中很简单,adb 一推到 Music 目录中,更新一下就行。
但是 iOS 搜了一圈发现还真不好弄,基本都是要通过 Apple 这个音乐导入到资料库,接着再通过 iTunes(新版已经合并在 Finder 中) 进行同步。
我试了下音乐这个应用,确实是可以通过新建歌单后,把需要添加的音乐文件夹直接拖入到歌单中,这样一次就可以添加多首,这个虽然要操作一下,但是也还可以接受。
正当我发现这个方案可行的时候,我看了一眼歌单,发现只有部分歌,就有点纳闷为啥部分歌曲没有导入。
在网上一搜,发现原来不支持无损,就是 FLAC 格式的文件。
这不是尴尬了么,所以看来还需要一个操作把 FLAC 文件转为 mp3 格式再导入才可以。
如果选第三方的工具,比如格式工厂或者狸窝,文件夹太多的情况,都要自己动手就太折腾了,比如我这里有几十个文件夹。(别问为啥这么多,强迫症就是歌手区分,各种风格也要区分)
用过 shell 的朋友都知道,这种批量的工作最好就交给脚本来做,遍历文件夹批量转化所有文件就行。
批量转化音乐
当然这里还有一些其他的逻辑,比如歌曲中已经是 mp3 的格式了,那应该就直接复制,除了 mp3 还有 wav 格式,同理针对 lrc 歌词文件也应该是直接复制。
所以和 ChatGPT "对线"几轮后,终于得到了一个满意的脚本,就不卖关子了。(对线真的考验心态和血压,最好自己能懂部分,可以自己动手改一下 shell)
#!/bin/bash
# 检查是否安装了 ffmpeg
if ! command -v ffmpeg &> /dev/null
then
echo "Error: ffmpeg 未安装。请先安装 ffmpeg。"
exit 1
fi
# 检查参数是否足够
if [ "$#" -ne 2 ]; then
echo "Usage: $0 <import_directory> <export_directory>"
exit 1
fi
# 输入和输出目录
import_dir="$1"
export_dir="$2"
error_log="$export_dir/error_log.txt"
# 检查导入目录是否存在
if [ ! -d "$import_dir" ]; then
echo "Error: 导入目录 $import_dir 不存在。"
exit 1
fi
# 创建导出目录(如果不存在)
mkdir -p "$export_dir"
# 清空错误日志文件
: > "$error_log"
# 查找所有文件并计算文件总数
total_files=$(find "$import_dir" -type f | wc -l)
if [ "$total_files" -eq 0 ]; then
echo "Error: 未找到文件。"
exit 1
fi
echo "共找到 $total_files 个文件,开始处理..."
# 初始化计数器
counter=1
# 遍历所有歌手目录
for artist_dir in "$import_dir"/*; do
if [ -d "$artist_dir" ]; then
# 遍历每个歌手目录下的所有文件
for song in "$artist_dir"/*; do
if [ -f "$song" ]; then
# 确定输出文件夹结构
rel_path="${song#$import_dir/}"
output_dir="$export_dir/$(dirname "$rel_path")"
mkdir -p "$output_dir"
# 获取文件扩展名
ext="${song##*.}"
# 处理不同文件类型
if [ "$ext" = "flac" ]; then
# 转换 FLAC 文件为 MP3,指定比特率 320k,并显式指定编码器
output_file="$output_dir/$(basename "${song%.flac}.mp3")"
echo "正在转换文件 ($counter/$total_files): $song -> $output_file"
# 使用 libmp3lame 编码器,忽略非音频流,并增加 analyzeduration 和 probesize
ffmpeg -analyzeduration 100M -probesize 50M -i "$song" -vn -c:a libmp3lame -b:a 320k "$output_file" > /dev/null 2> ffmpeg_errors.txt
if [ $? -ne 0 ]; then
echo "Error: 转换 $song 失败。" | tee -a "$error_log"
else
echo "转换成功: $output_file"
fi
elif [ "$ext" = "lrc" ] || [ "$ext" = "mp3" ] || [ "$ext" = "wav" ]; then
# 直接复制 LRC 和 MP3 文件
echo "正在复制文件 ($counter/$total_files): $song -> $output_dir/"
cp "$song" "$output_dir/"
if [ $? -ne 0 ]; then
echo "Error: 复制 $song 失败。" | tee -a "$error_log"
else
echo "复制成功: $song"
fi
else
echo "跳过不支持的文件 ($counter/$total_files): $song"
fi
# 更新计数器
counter=$((counter + 1))
fi
done
fi
done
echo "所有文件已处理完成。"
总结几个点:
- 这里是通过 ffmpeg 进行转换,毕竟这个开源工具很强大,视频都能随便处理,音频处理不是手到擒来么。
- 加入了处理进度,会在控制台输出,这样我们比较好知道处理到哪了,大概还有多久的时间。
- 第三是加入了错误日志导出,这样知道哪些歌曲出错了,没有处理。
因为脚本上也有对应的注释,如果知道一点编程的朋友应该能知道怎么修改一下。但是呢,考虑到可能会有非程序员的朋友看到该文章,还是简单讲一下这里的操作的流程。
打开终端应用。
在里面输入下面的语句,这个是通过 brew 命令安装 ffmpeg 库。
brew install ffmpeg
当命令行自己运行一会,光标重新开始闪烁时一般就是安装完毕。可以通过查看下 ffmpeg 版本看下是否安装好了。
ffmpeg --version
这样就完成了第一步 ffmpeg 安装。接着我们通过命令要新建一个普通文件,命名为 cvt.sh ,意思就是 converte 缩写,当然可以换个任意你喜欢的名字。
touch cvt.sh
一般来说命令行首次打开会在自己的 home 目录下,那么新建也是在这里。
如果会用命令修改的话可以直接通过 vi 打开复制,不会的朋友找到这个文件,然后用文本编辑应用打开。把刚刚那一长串代码复制进去,就像这样,记得保存一下。(Command + s)
第二部脚本文件可以说准备好了,但这里还差一点,就是新建的脚本文件需要加上可执行的权限。
在命令行中输入,这样我们一会才能执行这个转化的脚本。
chmod 711 cvt.sh
万事具备,讲一讲这个用法。(输出的文件夹可以不用存在,会自动创建)
#这里需要把对应的文件夹名字换一下。
bash cvt.sh <输入的文件夹> <输出的文件夹>
这里还要说明一下,脚本扫描的路径层级是这样:
输入的文件夹 - 二级目录(一般是歌手或者歌曲风格) - 该目录下所有歌曲
如果二级目录这个位置是歌曲是不会处理的,因为这么设计是为了方便后续导入 Apple 歌单.
我这里示范一下,假如我的音乐 testMusic 和脚本在一个地方,都在 home 目录下。
bash cvt.sh testMusic outputMusic
这样就开始了,可以看到有复制的,有转换的,也有对应进度。
需要注意的是,因为这里把错误信息导出到文件了,所以当第一次跑脚本,中途取消了,重新跑会发现,命令行卡着不动,实际上可以在当前目录中看到有错误日志,这里提示问是否覆盖。
所以建议如果用这个脚本,就一次性跑完,或者需要重新跑的时候把目标文件夹清除一下。
当然更优秀的朋友应该知道根据自己需求改下脚本,比如文件是直接强行覆盖不用询问么,或者还是需要手动对比。当然每个人的想法不一样,这里就是抛砖引玉。
这样的话,音乐的转换就完成了。
如果只有几个歌单需要添加的朋友,那么手动拖一下到 音乐 中就可以解决问题了。
批量导入歌单
接着就是到歌曲导入为 Apple 的歌单了。
从我前面的强迫症发言来看,就知道我需要导入的歌单不少,那这么多都需要操作一遍岂不是很麻烦,所得想个招,比如有没有办法用脚本来做,所以懒惰才是人类的第一生产力。
问了下 gpt ,好消息-有方案,坏消息-是其他脚本。
Gpt 提到可以用 Mac 自带的脚本编辑器来做,虽然我不会它这个脚本的语法,但是我有 gpt 呀,它会≈我会。😎
把导入的诉求告诉了它,又是一顿 battle 。
算是最后拿出了一个脚本,你还别说,shell 都算语法奇怪的了,苹果这个更奇怪,不过 ...... 反正能跑就是好代码不是。
照例加入进度打印,错误输出。
on run argv
-- 确保传入的参数数量正确
if (count of argv) is not 1 then
error "请提供一个参数:音乐文件夹的根路径。"
end if
-- 获取传入路径
set inputPath to item 1 of argv
-- 检查是否为相对路径,若是则转换为绝对路径
if inputPath does not start with "/" then
set currentDirectory to (POSIX path of (do shell script "pwd"))
set rootFolderPathString to currentDirectory & "/" & inputPath
else
set rootFolderPathString to inputPath
end if
-- 转换路径为 POSIX file 类型
set rootFolderPath to POSIX file rootFolderPathString
-- 设置日志文件路径
set logFilePath to POSIX file (rootFolderPathString & "/import_log.txt")
-- 强制启动音乐应用
tell application "Music"
launch -- 确保 Music 应用已启动
end tell
-- 获取根文件夹下的所有文件夹
tell application "Finder"
set musicFolders to every folder of folder rootFolderPath
end tell
-- 清空日志文件
try
set logFile to open for access logFilePath with write permission
set eof of logFile to 0 -- 清空文件
close access logFile
on error
-- 如果日志文件不存在,则创建它
set logFile to open for access logFilePath with write permission
close access logFile
end try
-- 总文件夹数量
set totalFolders to count of musicFolders
-- 遍历每个文件夹
repeat with musicFolder in musicFolders
set playlistName to name of musicFolder -- 使用文件夹名作为播放列表名称
set musicFolderPath to (musicFolder as alias)
tell application "Music"
-- 检查是否已经存在同名播放列表
set playlistExists to false
repeat with aPlaylist in (get user playlists)
if (name of aPlaylist) is equal to playlistName then
set playlistExists to true
set existingPlaylist to aPlaylist
exit repeat
end if
end repeat
-- 如果不存在同名播放列表,则创建新的播放列表
if playlistExists then
set targetPlaylist to existingPlaylist
else
set targetPlaylist to make new user playlist with properties {name:playlistName}
log "Created new playlist: " & playlistName
end if
-- 获取该文件夹中的所有音乐文件
tell application "Finder"
set musicFiles to every file of musicFolder
end tell
-- 当前文件夹的已处理文件计数
set processedFilesInFolder to 0 -- 初始化当前文件夹处理计数
-- 将每个文件导入到音乐应用并添加到播放列表
repeat with aFile in musicFiles
set fileName to name of aFile
set fileExtension to (name extension of aFile)
log "Checking fileName " & fileName & " ;fileExtension: " & fileExtension
-- 只处理 .mp3 和 .wav 文件
if fileExtension is "mp3" or fileExtension is "wav" then
try
-- 检查文件是否已经在播放列表中
set songAlreadyInPlaylist to false
log "File name: " & (name of aFile) -- 查看文件名
repeat with aTrack in (get tracks of targetPlaylist)
if (name of aTrack) is equal to fileName then
set songAlreadyInPlaylist to true
log "Found existing song in playlist: " & fileName
exit repeat
end if
end repeat
-- 如果歌曲尚未在播放列表中,才导入
if not songAlreadyInPlaylist then
log "Importing song to playlist: " & fileName
-- 确保 aFile 以 alias 形式导入
set importedTrack to add (aFile as alias) to targetPlaylist
log "Successfully added: " & fileName
--delay 1 -- 添加 1 秒的延迟
else
log "Skipping already existing song: " & fileName
--delay 1 -- 添加 1 秒的延迟
end if
on error errorMsg
-- 处理可能的错误,记录详细信息
set logMessage to "Error importing file: " & fileName & return & errorMsg
my appendToLog(logMessage, logFilePath)
end try
else
-- 记录不支持的文件到日志文件
log "Skipping unsupported file: " & fileName
end if
-- 增加当前文件夹的已处理文件数量
set processedFilesInFolder to processedFilesInFolder + 1
-- 打印当前进度
my displayProgress(processedFilesInFolder, (count of musicFiles), playlistName)
end repeat
end tell
end repeat
-- 打印总的处理完成信息
display dialog "所有歌曲已处理完成!" buttons {"OK"} default button 1
end run
-- 函数:将消息附加到日志文件
on appendToLog(logMessage, logFilePath)
set logFile to open for access logFilePath with write permission
write logMessage to logFile starting at eof
close access logFile
end appendToLog
-- 函数:显示处理进度
on displayProgress(folderProcessed, totalInFolder, playlistName)
set progressPercent to (folderProcessed / totalInFolder) * 100
set formattedProgress to round progressPercent * 10 / 10.0 -- 保留一位小数
-- 在终端输出进度
log "Processing " & playlistName & ": " & (folderProcessed as string) & "/" & (totalInFolder as string) & " (" & (formattedProgress as string) & "%)"
end displayProgress
关于这个脚本的用法,简单讲一下,估计大部分朋友都没有接触过,毕竟不通用。
打开这个编辑器,把刚才的脚本拷贝上,然后保存为 脚本格式。
我这里文件名用的是 importMusic.scpt ,说一下用法。
osascript <脚本名称> <导入的文件夹>
和刚才一样,我的命令行在 home 目录下,新建的 importMusic.scpt 也挪到这个目录,处理后的音乐还在刚才的位置。
那我就可以这么用。
osascript importMusic.scpt outputMusic
接下来就是见证奇迹的时刻。
轻轻松松导入,真是省大心。
当然最后可以把这一段执行代码再组合在前面的 shell 文档中,不过分开一下也好,各个朋友有各自的需求,需求什么用什么。
脚本真是提升效率的利器。
后续计划录个视频把操作和代码上传一下,如果有看视频来的朋友用起来就比较方便了。
如果对你有帮助请点赞收藏支持一下,感谢 ~