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

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
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 "所有文件已处理完成。"

总结几个点:

  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 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 文档中,不过分开一下也好,各个朋友有各自的需求,需求什么用什么。

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

在这里插入图片描述

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

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


http://www.kler.cn/news/366800.html

相关文章:

  • 【NodeJS】NodeJS+mongoDB在线版开发简单RestfulAPI (七):MongoDB的设置
  • Jetpack架构组件_LiveData组件
  • vue3 树型视图,利用自定义SFC来定义一个TreeItem,然后进行渲染出一个树形。
  • JavaScript网页设计案例教程:从零开始构建一个响应式网页
  • C#与C++交互开发系列(十):数组传递的几种形式
  • 【Linux系统编程】冯诺依曼体系结构与操作系统
  • 【力扣 + 牛客 | SQL题 | 每日4题】牛客大厂面试真题W3,W10
  • Protues中51单片机按键无法复位(已解决)
  • 【多态案例】电脑组装
  • 如何使用python seaborn进行复杂的数据可视化操作?
  • 使用API有效率地管理Dynadot域名,通过域名命令删除域名服务器(NS)
  • canvas-editor首行缩进
  • Python爬虫,初识xpath(1)
  • leetcode day1 910+16
  • 【文献及模型、制图分享】长江中游经济区“水—能源—粮食”系统与城市绿色转型适配性研究
  • java中常见集合,非常重要!!!
  • 基于SSM农业信息管理系统的设计
  • LeetCode Hot 100:回溯
  • 基于微信小程序的智能社区服务管理系统
  • 阻塞队列——Java
  • SQL SERVER 2005/2008/2012/2016/2020 数据库状态为“可疑”的解决方法(亲测可用)
  • LeetCode - #127 单词接龙
  • 在 MySQL 中,添加索引后,插入、更新和删除操作的性能通常会变慢的原因
  • 2.插入排序(斗地主起牌)
  • 强化学习数学原理学习(一)
  • golang开发alertmanagerWebhook,实现prometheus+alertmanagerWebhook告警