认识我的朋友都知道,我是个热爱音乐的博主,值此国庆小长假来临之际,自当整理一下我的Car Music
,确保旅途中动次打次不停歇。
以前都是手动整理,从网易云、QQ 音乐下载歌曲,手动解密,然后拷贝到 U 盘。于是,我面临以下重复工作:
- 下载歌曲,下载歌词;
- 拷贝到 U 盘;
- 痛苦的肉眼翻一遍 U 盘里的歌曲,确保“过时”(emm……就是突然不喜欢听的歌,懂得都懂)歌曲被手动移除;
尤其在第三步,很繁琐。于是就有了本篇分享。
我为什么建议你在电脑另存一份 Car Music
诚然,现在车联网很方便,在线听歌很便捷。当仍然无法解决付费歌曲、隧道网络差、自由歌单这些痛点。同时,一般车机上,偶尔会蹦一个“热插拔”异常,导致 U 盘歌曲丢失。(我那尊贵的 CoffeeOS
车机,已经让我体验过 2 次了,真好)
所以我真诚建议你,在电脑备份一份文件。尤其是现在固态硬盘白菜价的时候,别犹豫了!
教程开始
本篇分享脚本基于node.js
实现,歌词下载使用开源工具ZonyLrcToolsX
。因此开始之前,请先下载安装node.js
(过于简单,直接放个链接吧(记得添加到环境变量,不懂 Google 下):Node.js 安装配置)
ZonyLrcToolsX 下载地址:ZonyLrcToolsX/releases
将 ZonyLrcToolsX 包解压到电脑本地某个目录,记下这个目录地址。在电脑一个你记得住的位置,创建一个 car_music.txt
文件,内容是下面这些:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
| const fs = require('fs').promises; const readline = require('readline'); const path = require('path');
const sourceDirectory = 'E:/car music'; const targetDirectory = 'F:/car music';
const addedList = []; const deleteList = [];
function printSeparator() { console.log('---------------------------'); }
function getFormattedTimestamp(info) { const now = new Date(); const hours = now.getHours().toString().padStart(2, '0'); const minutes = now.getMinutes().toString().padStart(2, '0'); const seconds = now.getSeconds().toString().padStart(2, '0'); if (info) { return `[${hours}:${minutes}:${seconds} INF]`; } else { return `[${hours}:${minutes}:${seconds} WRN]`; } }
async function copyFiles() { for (const file of addedList) { try { await fs.copyFile(file, `${targetDirectory}/${path.basename(file)}`); console.log(`${getFormattedTimestamp(1)} 拷贝成功:${file}`); } catch (err) { console.error(`${getFormattedTimestamp(0)} 拷贝失败:${file}`); } } }
async function deleteFiles() { for (const file of deleteList) { try { await fs.unlink(file); console.log(`${getFormattedTimestamp(1)} 删除成功:${file}`); } catch (err) { console.error(`${getFormattedTimestamp(0)} 删除失败:${file}`); } } }
async function confirmCopy() { printSeparator(); console.log(`${getFormattedTimestamp(1)} 扫描完成,待添加文件列表如下:`); addedList.forEach((file) => { console.log(file); }); printSeparator();
const rl1 = readline.createInterface({ input: process.stdin, output: process.stdout, });
return new Promise((resolve) => { rl1.question('确认帮您拷贝添加到 U 盘吗? (no/yes): ', async (confirmAdd) => { if (confirmAdd.toLowerCase() === 'n' || confirmAdd.toLowerCase() === 'no') { printSeparator(); console.log(`${getFormattedTimestamp(1)} 您拒绝了添加文件。`); resolve(); } else if (confirmAdd.toLowerCase() === 'y' || confirmAdd.toLowerCase() === 'yes') { printSeparator(); await copyFiles(); resolve(); } else { printSeparator(); console.log(`${getFormattedTimestamp(0)} 您键入的指令不正确,已跳过本次操作。`); resolve(); }
rl1.close(); }); }); }
async function confirmDelete() { if (deleteList.length > 0) { printSeparator(); console.log(`${getFormattedTimestamp(1)} 待删除文件列表如下:`); deleteList.forEach((file) => { console.log(file); });
const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout, });
printSeparator();
return new Promise((resolve) => { rl2.question('确认帮您删除 U 盘上述文件吗? (no/yes): ', async (confirmDelete) => { if (confirmDelete.toLowerCase() === 'n' || confirmDelete.toLowerCase() === 'no') { printSeparator(); console.log(`${getFormattedTimestamp(1)} 您拒绝了删除文件。`); } else if (confirmDelete.toLowerCase() === 'y' || confirmDelete.toLowerCase() === 'yes') { printSeparator(); await deleteFiles(); printSeparator(); console.log(`${getFormattedTimestamp(1)} 删除操作已完成。`); } else { printSeparator(); console.log(`${getFormattedTimestamp(0)} 您键入的指令不正确,已跳过本次操作。`); }
rl2.close(); resolve(); }); }); } else { printSeparator(); console.log(`${getFormattedTimestamp(1)} 没有待删除的文件。`); } }
async function main() { try { const sourceFiles = await fs.readdir(sourceDirectory); const targetFiles = await fs.readdir(targetDirectory);
for (const fileName of sourceFiles) { const sourceFilePath = `${sourceDirectory}/${fileName}`; const targetFilePath = `${targetDirectory}/${fileName}`;
if (!targetFiles.includes(fileName)) { addedList.push(sourceFilePath); } }
for (const fileName of targetFiles) { const sourceFilePath = `${sourceDirectory}/${fileName}`; const targetFilePath = `${targetDirectory}/${fileName}`;
if (!sourceFiles.includes(fileName)) { deleteList.push(targetFilePath); } }
await confirmDelete(); if (addedList.length > 0) { await confirmCopy(); } else { printSeparator(); console.log(`${getFormattedTimestamp(1)} 没有待添加的文件。`); } } catch (err) { console.error(err); } }
main();
|
将其中开头的这两行路径,修改为你自己的。第一个“E:/car music”为“电脑本地备份的音乐目录”,第二个“F:/car music”为“U盘上的音乐目录,注意盘符”:
1 2
| const sourceDirectory = 'E:/car music'; const targetDirectory = 'F:/car music';
|
将 car_music.txt
文件,重命名为 car_music.js
文件。再创建一个 music_processor.txt
文件,实现鼠标双击执行的功能,可以放在任何位置,比如桌面方便找也可以。内容为:
1 2 3 4 5 6 7 8 9 10 11
| #!/bin/bash
cd d:/home/tools/zonyLrcTools ./ZonyLrcTools.Cli.exe download -d "e:\car music" -l -n 2
cd e:/ node ./car_music.js
echo "---------------------------" echo "脚本执行完成,按 Enter 键退出" read
|
将其中的 d:/home/tools/zonyLrcTools
修改为你刚才下载的 zonyLrcToolsX 解压缩的目录。同样的,将其中的e:\car music
改为你电脑本地备份的歌曲目录。保存后,将 music_processor.txt
文件重命名为 music_processor.sh
文件。
1 2
| cd e:/ node ./car_music.js
|
这两部分是进入你刚才保存 car_music.js
的目录,并执行它。因此地址请按你保存的目录修改,我这里放在了 E 盘根目录。
歌曲解密
在网易云、QQ 音乐下载的歌曲,有些是加密的(.ncm / .mflac 之类的格式),可以来这里解密(原作者牛皮):
大功告成
现在插上 U 盘,鼠标双击 music_processor.sh
文件,试试看吧。没插入 U 盘执行会报错,因为找不到对应地址。也可以本地弄两个文件夹,自己测试。

推荐下寸铁 《近人可读》
专辑,最近好上头。最后,预祝大家假期愉快,下次见~~