music-lens 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/bin/music-lens.mjs +57 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
不需要人耳,也不需要联网(模型已下到本地)。给纯文本的模型用:把声音变成它能读的数字和图。
|
|
6
6
|
|
|
7
|
+
> **第一次用?看 [`docs/新手操作指南.md`](docs/新手操作指南.md)** —— 面向完全没写过代码的人,
|
|
8
|
+
> 三步装好,七个坑都写在里面。
|
|
9
|
+
> 要做视频/图文分享的话,[`docs/发布文案.md`](docs/发布文案.md) 里有标题、简介和口播稿。
|
|
10
|
+
|
|
7
11
|
```bash
|
|
8
12
|
npm i -g music-lens # 或者 git clone 后 npm i -g .
|
|
9
13
|
music-lens setup # 装 python 依赖 + 下模型(约 2 GB)
|
|
@@ -19,10 +23,14 @@ music-lens analyze song.mp3
|
|
|
19
23
|
```powershell
|
|
20
24
|
$node = "C:\Users\86173\Documents\deepseek-harness\default-workspace\snowluma\SnowLuma-v2.2.1\node.exe"
|
|
21
25
|
& $node .\bin\music-lens.mjs doctor # 体检:python / 依赖 / 模型
|
|
26
|
+
& $node .\bin\music-lens.mjs spectrogram "song.mp3" # 10 秒出图,只要 ffmpeg
|
|
22
27
|
& $node .\bin\music-lens.mjs separate "song.mp3" # 分轨
|
|
23
28
|
& $node .\bin\music-lens.mjs analyze "song.mp3" --model medium
|
|
24
29
|
```
|
|
25
30
|
|
|
31
|
+
`spectrogram` 是**零依赖**的快速预览:频谱图 + 波形图,不装 Python、不下模型,
|
|
32
|
+
只要机器上有 ffmpeg(`MUSIC_LENS_FFMPEG` 可指定路径)。
|
|
33
|
+
|
|
26
34
|
`setup` 会自己装 python 依赖并从 hf-mirror 下模型,**默认只下 small(461 MB)**:
|
|
27
35
|
|
|
28
36
|
```powershell
|
|
@@ -133,3 +141,17 @@ $base = "C:\Users\86173\Documents\deepseek-harness\default-workspace\music-lens"
|
|
|
133
141
|
- [ ] 用 `librosa.pyin` 出逐帧基频,画"音高随时间的走向"
|
|
134
142
|
- [ ] 情绪的 arousal 维度(能量+亮度已经能算个大概)
|
|
135
143
|
- [ ] 换 `large-v3`(3 GB,CPU 上慢约 4 倍,中文应该更准)
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## 致谢
|
|
148
|
+
|
|
149
|
+
这个包的代码、文档、测试,以及下面那七条踩坑记录,全部由 **DSH(DeepSeek Harness)里的 AI 助手**
|
|
150
|
+
在 2026-11-01 的一次会话中完成(模型 `deepseek-flash`)。
|
|
151
|
+
|
|
152
|
+
它不能拥有账号、不能发布 npm 包、也不能处理 issue——所以仓库和包的所有权、维护责任在人类这边。
|
|
153
|
+
这份致谢记录的是“谁写的”,不是转移责任。
|
|
154
|
+
|
|
155
|
+
过程中被人类纠正过两次重要判断:先说“情绪和歌词做不出来”(错的,歌词能做),
|
|
156
|
+
又说“节点不在 PATH 就装不了”(也是错的)。所以文档里那些边界条件,有一半是吵出来的。
|
|
157
|
+
|
package/bin/music-lens.mjs
CHANGED
|
@@ -77,6 +77,22 @@ function findPython() {
|
|
|
77
77
|
return null
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
// ffmpeg is optional: only the zero-dependency `spectrogram` command needs it.
|
|
81
|
+
function findFfmpeg() {
|
|
82
|
+
const cands = []
|
|
83
|
+
if (process.env.MUSIC_LENS_FFMPEG) cands.push(process.env.MUSIC_LENS_FFMPEG)
|
|
84
|
+
cands.push('ffmpeg')
|
|
85
|
+
if (process.platform === 'win32') {
|
|
86
|
+
cands.push(path.join(os.homedir(), '.dsh', 'bili-whale', 'bin', 'ffmpeg.exe'))
|
|
87
|
+
cands.push('C:\\ffmpeg\\bin\\ffmpeg.exe')
|
|
88
|
+
}
|
|
89
|
+
for (const c of cands) {
|
|
90
|
+
const r = spawnSync(c, ['-version'], { encoding: 'utf8' })
|
|
91
|
+
if (r.status === 0) return c
|
|
92
|
+
}
|
|
93
|
+
return null
|
|
94
|
+
}
|
|
95
|
+
|
|
80
96
|
function run(cmd, args, opts = {}) {
|
|
81
97
|
const r = spawnSync(cmd, args, { stdio: 'inherit', env: { ...process.env, ...(opts.env || {}) } })
|
|
82
98
|
return r.status ?? 1
|
|
@@ -221,6 +237,44 @@ async function cmdAnalyze(args) {
|
|
|
221
237
|
return run(py, argv)
|
|
222
238
|
}
|
|
223
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Zero-dependency quick look: needs only ffmpeg, no python, no models.
|
|
242
|
+
* Produces a mel-ish spectrogram and a waveform picture.
|
|
243
|
+
*/
|
|
244
|
+
async function cmdSpectrogram(args) {
|
|
245
|
+
const audio = args._[0]
|
|
246
|
+
if (!audio) die('usage: music-lens spectrogram <audio> [--out DIR] [--size 1600x900]')
|
|
247
|
+
const src = path.resolve(audio)
|
|
248
|
+
if (!fs.existsSync(src)) die(`audio not found: ${src}`)
|
|
249
|
+
|
|
250
|
+
const ff = findFfmpeg()
|
|
251
|
+
if (!ff) {
|
|
252
|
+
die('ffmpeg not found.\n install: winget install Gyan.FFmpeg\n or set: MUSIC_LENS_FFMPEG=C:\\path\\to\\ffmpeg.exe')
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const outDir = path.resolve(args.out || path.join(path.dirname(src), path.parse(src).name + '_look'))
|
|
256
|
+
fs.mkdirSync(outDir, { recursive: true })
|
|
257
|
+
const size = args.size || '1600x900'
|
|
258
|
+
|
|
259
|
+
log(`ffmpeg : ${ff}`)
|
|
260
|
+
log(`output : ${outDir}`)
|
|
261
|
+
|
|
262
|
+
const spec = path.join(outDir, 'spectrum.png')
|
|
263
|
+
const wave = path.join(outDir, 'waveform.png')
|
|
264
|
+
|
|
265
|
+
const st1 = run(ff, ['-y', '-hide_banner', '-loglevel', 'error', '-i', src,
|
|
266
|
+
'-lavfi', `showspectrumpic=s=${size}:legend=1:scale=log`, '-frames:v', '1', spec])
|
|
267
|
+
log(` spectrum: ${st1 === 0 ? spec : 'FAILED'}`)
|
|
268
|
+
|
|
269
|
+
const st2 = run(ff, ['-y', '-hide_banner', '-loglevel', 'error', '-i', src,
|
|
270
|
+
'-lavfi', `showwavespic=s=${size}:split_channels=0:colors=#4a7ebb`, '-frames:v', '1', wave])
|
|
271
|
+
log(` waveform: ${st2 === 0 ? wave : 'FAILED'}`)
|
|
272
|
+
|
|
273
|
+
if (st1 !== 0 && st2 !== 0) return 1
|
|
274
|
+
log('\nquick look done. for lyrics / key / tempo / numbers, run: music-lens analyze "<same file>"')
|
|
275
|
+
return 0
|
|
276
|
+
}
|
|
277
|
+
|
|
224
278
|
async function cmdSeparate(args) {
|
|
225
279
|
const audio = args._[0]
|
|
226
280
|
if (!audio) die('usage: music-lens separate <audio> [--out DIR]')
|
|
@@ -242,6 +296,8 @@ function usage() {
|
|
|
242
296
|
[--model medium] small | medium | large-v3 | path
|
|
243
297
|
[--lang auto] language code or auto
|
|
244
298
|
[--out DIR] output directory
|
|
299
|
+
music-lens spectrogram <audio> quick look: spectrum + waveform (ffmpeg only,
|
|
300
|
+
[--size 1600x900] no python, no models, ~10 seconds)
|
|
245
301
|
music-lens separate <audio> split vocals / instrumental (demucs)
|
|
246
302
|
|
|
247
303
|
Outputs per song: 00_summary.md, 01_lyrics.txt, 02_transcript.json,
|
|
@@ -257,6 +313,7 @@ const map = {
|
|
|
257
313
|
setup: cmdSetup,
|
|
258
314
|
analyze: cmdAnalyze,
|
|
259
315
|
separate: cmdSeparate,
|
|
316
|
+
spectrogram: cmdSpectrogram,
|
|
260
317
|
}
|
|
261
318
|
|
|
262
319
|
if (cmd === 'help' || cmd === '--help' || cmd === '-h') { usage(); process.exit(0) }
|
package/package.json
CHANGED