当前位置: 首页 > news >正文

Mac 使用脚本批量导入 Apple 歌曲

最近呢,买了一个 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
thenecho "Error: ffmpeg 未安装。请先安装 ffmpeg。"exit 1
fi# 检查参数是否足够
if [ "$#" -ne 2 ]; thenecho "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" ]; thenecho "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 ]; thenecho "Error: 未找到文件。"exit 1
fiecho "共找到 $total_files 个文件,开始处理..."# 初始化计数器
counter=1# 遍历所有歌手目录
for artist_dir in "$import_dir"/*; doif [ -d "$artist_dir" ]; then# 遍历每个歌手目录下的所有文件for song in "$artist_dir"/*; doif [ -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 和 probesizeffmpeg -analyzeduration 100M -probesize 50M -i "$song" -vn -c:a libmp3lame -b:a 320k "$output_file" > /dev/null 2> ffmpeg_errors.txtif [ $? -ne 0 ]; thenecho "Error: 转换 $song 失败。" | tee -a "$error_log"elseecho "转换成功: $output_file"fielif [ "$ext" = "lrc" ] || [ "$ext" = "mp3" ] || [ "$ext" = "wav" ]; then# 直接复制 LRC 和 MP3 文件echo "正在复制文件 ($counter/$total_files): $song -> $output_dir/"cp "$song" "$output_dir/"if [ $? -ne 0 ]; thenecho "Error: 复制 $song 失败。" | tee -a "$error_log"elseecho "复制成功: $song"fielseecho "跳过不支持的文件 ($counter/$total_files): $song"fi# 更新计数器counter=$((counter + 1))fidonefi
doneecho "所有文件已处理完成。"

总结几个点:

  1. 这里是通过 ffmpeg 进行转换,毕竟这个开源工具很强大,视频都能随便处理,音频处理不是手到擒来么。
  2. 加入了处理进度,会在控制台输出,这样我们比较好知道处理到哪了,大概还有多久的时间。
  3. 第三是加入了错误日志导出,这样知道哪些歌曲出错了,没有处理。

因为脚本上也有对应的注释,如果知道一点编程的朋友应该能知道怎么修改一下。但是呢,考虑到可能会有非程序员的朋友看到该文章,还是简单讲一下这里的操作的流程。


打开终端应用。

在这里插入图片描述

在里面输入下面的语句,这个是通过 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 thenerror "请提供一个参数:音乐文件夹的根路径。"end if-- 获取传入路径set inputPath to item 1 of argv-- 检查是否为相对路径,若是则转换为绝对路径if inputPath does not start with "/" thenset currentDirectory to (POSIX path of (do shell script "pwd"))set rootFolderPathString to currentDirectory & "/" & inputPathelseset rootFolderPathString to inputPathend 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 rootFolderPathend tell-- 清空日志文件tryset logFile to open for access logFilePath with write permissionset eof of logFile to 0 -- 清空文件close access logFileon error-- 如果日志文件不存在,则创建它set logFile to open for access logFilePath with write permissionclose access logFileend try-- 总文件夹数量set totalFolders to count of musicFolders-- 遍历每个文件夹repeat with musicFolder in musicFoldersset playlistName to name of musicFolder -- 使用文件夹名作为播放列表名称set musicFolderPath to (musicFolder as alias)tell application "Music"-- 检查是否已经存在同名播放列表set playlistExists to falserepeat with aPlaylist in (get user playlists)if (name of aPlaylist) is equal to playlistName thenset playlistExists to trueset existingPlaylist to aPlaylistexit repeatend ifend repeat-- 如果不存在同名播放列表,则创建新的播放列表if playlistExists thenset targetPlaylist to existingPlaylistelseset targetPlaylist to make new user playlist with properties {name:playlistName}log "Created new playlist: " & playlistNameend if-- 获取该文件夹中的所有音乐文件tell application "Finder"set musicFiles to every file of musicFolderend tell-- 当前文件夹的已处理文件计数set processedFilesInFolder to 0 -- 初始化当前文件夹处理计数-- 将每个文件导入到音乐应用并添加到播放列表repeat with aFile in musicFilesset fileName to name of aFileset fileExtension to (name extension of aFile)log "Checking fileName " & fileName & " ;fileExtension: " & fileExtension-- 只处理 .mp3 和 .wav 文件if fileExtension is "mp3" or fileExtension is "wav" thentry-- 检查文件是否已经在播放列表中set songAlreadyInPlaylist to falselog "File name: " & (name of aFile) -- 查看文件名repeat with aTrack in (get tracks of targetPlaylist)if (name of aTrack) is equal to fileName thenset songAlreadyInPlaylist to truelog "Found existing song in playlist: " & fileNameexit repeatend ifend repeat-- 如果歌曲尚未在播放列表中,才导入if not songAlreadyInPlaylist thenlog "Importing song to playlist: " & fileName-- 确保 aFile 以 alias 形式导入set importedTrack to add (aFile as alias) to targetPlaylistlog "Successfully added: " & fileName--delay 1 -- 添加 1 秒的延迟elselog "Skipping already existing song: " & fileName--delay 1 -- 添加 1 秒的延迟end ifon error errorMsg-- 处理可能的错误,记录详细信息set logMessage to "Error importing file: " & fileName & return & errorMsgmy appendToLog(logMessage, logFilePath)end tryelse-- 记录不支持的文件到日志文件log "Skipping unsupported file: " & fileNameend if-- 增加当前文件夹的已处理文件数量set processedFilesInFolder to processedFilesInFolder + 1-- 打印当前进度my displayProgress(processedFilesInFolder, (count of musicFiles), playlistName)end repeatend tellend repeat-- 打印总的处理完成信息display dialog "所有歌曲已处理完成!" buttons {"OK"} default button 1
end run-- 函数:将消息附加到日志文件
on appendToLog(logMessage, logFilePath)set logFile to open for access logFilePath with write permissionwrite logMessage to logFile starting at eofclose access logFile
end appendToLog-- 函数:显示处理进度
on displayProgress(folderProcessed, totalInFolder, playlistName)set progressPercent to (folderProcessed / totalInFolder) * 100set 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 文档中,不过分开一下也好,各个朋友有各自的需求,需求什么用什么。

脚本真是提升效率的利器。

在这里插入图片描述

后续计划录个视频把操作和代码上传一下,如果有看视频来的朋友用起来就比较方便了。

如果对你有帮助请点赞收藏支持一下,感谢 ~

http://www.lryc.cn/news/469411.html

相关文章:

  • 全桥PFC电路及MATLAB仿真
  • 【安当产品应用案例100集】025-确保数据安全传输——基于KMS与HSM的定期分发加密解决方案
  • 十 缺陷检测解决策略之三:频域+空域
  • 有望第一次走出慢牛
  • 计算机网络(十二) —— 高级IO
  • 电力行业 | 等保测评(网络安全等级保护)工作全解
  • 总裁主题CeoMax-Pro主题7.6开心版
  • 深入探讨编程的核心概念、学习路径、实际应用以及对未来的影响
  • IDEA如何将一个分支的代码合并到另一个分支(当前分支)
  • Python实现基于WebSocket的stomp协议调试助手工具
  • 基于neo4j的旅游知识图谱维护与问答系统
  • 竞赛学习路线推荐(编程基础)
  • webRTC搭建:STUN 和 TURN 服务器 链接google的有点慢,是不是可以自己搭建
  • 利用Pix4D和ArcGIS计算植被盖度
  • 用docker Desktop 下载使用thingsboard/tb-gateway
  • 从视频中学习的SeeDo:VLM解释视频并生成规划、代码(含通过RGB视频模仿的人形机器人OKAMI、DexMV)
  • 项目集群部署定时任务重复执行......怎么解决???
  • 使用JUC包的AtomicXxxFieldUpdater实现更新的原子性
  • vue3组件通信--props
  • leetcode-75-颜色分类
  • 【嵌入式原理设计】实验三:带报警功能的数字电压表设计
  • C#中的接口的使用
  • 记一次真实项目的性能问题诊断、优化(阿里云redis分片带宽限制问题)过程
  • LeetCode - 4. 寻找两个正序数组的中位数
  • 算法设计与分析——动态规划
  • 【实战篇】GEO是什么?还可以定义新的数据类型吗?
  • SpringBoot最佳实践之 - 项目中统一记录正常和异常日志
  • 【Flutter】状态管理:高级状态管理 (Riverpod, BLoC)
  • OAK相机的RGB-D彩色相机去畸变做对齐
  • smartctl硬盘检查工具