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.mzph.cn/diannao/57831.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Java】java 集合框架(详解)

&#x1f4c3;个人主页&#xff1a;island1314 ⛺️ 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f442;&#x1f3fd;留言 &#x1f60d;收藏 &#x1f49e; &#x1f49e; &#x1f49e; 1. 概述 &#x1f680; &#x1f525; Java集合框架 提供了一系列用于存储和操作…

实现uniapp天地图边界范围覆盖

在uniapp中&#xff0c;难免会遇到使用地图展示的功能&#xff0c;但是百度谷歌这些收费的显然对于大部分开源节流的开发者是不愿意接受的&#xff0c;所以天地图则是最佳选择。 此篇文章&#xff0c;详细的实现地图展示功能&#xff0c;并且可以自定义容器宽高&#xff0c;还可…

java项目之电影评论网站(springboot)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的电影评论网站。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 电影评论网站的主要使用者管…

UE5 源码学习 初始化

跟着 https://www.cnblogs.com/timlly/p/13877623.html 学习 入口函数 UnrealEngine\Engine\Source\Runtime\Launch\Private\Windows\LaunchWindows.cpp WinMain 入口 int32 WINAPI WinMain(_In_ HINSTANCE hInInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ char* p…

8.three.js相机详解

8.three.js相机详解 1、 认识相机 在Threejs中相机的表示是THREE.Camera&#xff0c;它是相机的抽象基类&#xff0c;其子类有两种相机&#xff0c;分别是正投影相机THREE.OrthographicCamera和透视投影相机THREE.PerspectiveCamera&#xff1a; 正投影和透视投影的区别是&am…

Excel:vba实现生成随机数

Sub 生成随机数字()Dim randomNumber As IntegerDim minValue As IntegerDim maxValue As Integer 设置随机数的范围(假入班级里面有43个学生&#xff0c;学号是从1→43)minValue 1maxValue 43 生成随机数(在1到43之间生成随机数)randomNumber Application.WorksheetFunctio…

element 按钮变形 el-button样式异常

什么都没动&#xff0c;element UI的按钮变形了&#xff0c;莫名其妙&#xff0c;连官网的也变形了&#xff0c;换了其它浏览器又正常&#xff0c; 难道这是element UI的问题&#xff1f;NO&#xff0c;是浏览器的插件影响到了&#xff01;去扩展插件里面一个个关闭扩展&#x…

时间序列预测(十)——长短期记忆网络(LSTM)

目录 一、LSTM结构 二、LSTM 核心思想 三、LSTM分步演练 &#xff08;一&#xff09;初始化 1、权重和偏置初始化 2、初始细胞状态和隐藏状态初始化 &#xff08;二&#xff09;前向传播 1、遗忘门计算&#xff08;决定从上一时刻隐状态中丢弃多少信息&#xff09; 2、…

CSS 样式 box-sizing: border-box; 用于控制元素的盒模型如何计算宽度和高度

文章目录 box-sizing: border-box; 的含义默认盒模型 (content-box)border-box 盒模型 在微信小程序中的应用示例 在微信小程序中&#xff0c;CSS 样式 box-sizing: border-box; 用于控制元素的盒模型如何计算宽度和高度。具体来说&#xff0c; box-sizing: border-box; 会改…

使用TimeShift备份和恢复Ubuntu Linux

您是否曾经想过如何备份和恢复您的Ubuntu或Debian系统&#xff1f;TimeShift是一个强大的备份和还原工具。TimeShift允许您创建系统快照&#xff0c;提供了一种在出现意外问题或系统故障时恢复到先前状态的简便方式。您可以使用RSYNC或BTRFS创建快照。 有了这个介绍&#xff0…

SSM 图书馆借还系统-计算机设计毕业源码24465

目 录 摘要 1 绪论 1.1 研究背景 1.2 研究意义 1.3论文章节安排 2相关技术介绍 2.1 B/S结构 2.2 SSM框架 2.3 MySQL数据库 3系统分析 3.1 可行性分析 3.2 系统功能性分析 3.3.非功能性分析 3.4 系统用例分析 3.5系统流程分析 3.5.1 用户登录流程 3.5.2 数据删…

中小企业设备资源优化:Spring Boot系统实现

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

ruoyi域名跳转缓存冲突问题(解决办法修改:session名修改session的JSESSIONID名称)

【版权所有&#xff0c;文章允许转载&#xff0c;但须以链接方式注明源地址&#xff0c;否则追究法律责任】【创作不易&#xff0c;点个赞就是对我最大的支持】 前言 仅作为学习笔记&#xff0c;供大家参考 总结的不错的话&#xff0c;记得点赞收藏关注哦&#xff01; 目录 前…

Prism 四事件聚合器

#1024程序员节&#xff5c;征文# 不废话&#xff0c;直接上代码一个简单的示例。 1、事件聚合 创建一个文件夹EventBLL&#xff0c;添加EventDemo.cs&#xff0c;代码如下。 using System; using System.Collections.Generic; using System.Linq; using System.Text; using …

linux中级wed服务器(https搭建加密服务器)

一。非对称加密算法&#xff1a; 公钥&#xff1a;公共密钥&#xff0c;开放 私钥&#xff1a;私有密钥&#xff0c;保密 1.发送方用自己的公钥加密&#xff0c;接受方用发送方的私钥解密&#xff1a;不可行 2.发送方用接受方的公钥加密&#xff0c;接受方用自己的私钥解密…

SpringMVC实战:构建高效表述层框架

文章目录 1. SpringMVC简介和体验1.1 介绍1.2 主要作用1.3 核心组件和调用流程1.4 快速体验 2. SpringMVC接收数据2.1 访问路径设置2.2 接收参数2.2.1 param和json参数比较2.2.2 param参数接收2.2.3 路径参数接收2.2.4 json参数接收 2.3 接收cookie数据2.4 接收请求头数据2.5 原…

【开源免费】基于SpringBoot+Vue.JS在线文档管理系统(JAVA毕业设计)

本文项目编号 T 038 &#xff0c;文末自助获取源码 \color{red}{T038&#xff0c;文末自助获取源码} T038&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…

CryoEM - 冷冻电镜 基于深度学习的 从头重构(Ab-initio Reconstruction) 开源项目 教程

欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/143162494 免责声明:本文来源于个人知识与公开资料,仅用于学术交流,欢迎讨论,不支持转载。 来源于 CryoDragon 算法 冷冻电镜(CryoEM) 是一种成像方式,为蛋白质和…

AOT漫谈专题(第六篇): C# AOT 的泛型,序列化,反射问题

一&#xff1a;背景 1. 讲故事 在 .NET AOT 编程中&#xff0c;难免会在 泛型&#xff0c;序列化&#xff0c;以及反射的问题上纠结和反复纠错尝试&#xff0c;这篇我们就来好好聊一聊相关的处理方案。 二&#xff1a;常见问题解决 1. 泛型问题 研究过泛型的朋友应该都知道…

论文笔记:通用世界模型WorldDreamer

整理了WorldDreamer: Towards General World Models for Video Generation via Predicting Masked Tokens 论文的阅读笔记 背景模型实验 背景 现有的世界模型仅限于游戏或驾驶等特定场景&#xff0c;限制了它们捕捉一般世界动态环境复杂性的能力。针对这一挑战&#xff0c;本文…