music-lens 0.1.2 → 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 CHANGED
@@ -23,10 +23,14 @@ music-lens analyze song.mp3
23
23
  ```powershell
24
24
  $node = "C:\Users\86173\Documents\deepseek-harness\default-workspace\snowluma\SnowLuma-v2.2.1\node.exe"
25
25
  & $node .\bin\music-lens.mjs doctor # 体检:python / 依赖 / 模型
26
+ & $node .\bin\music-lens.mjs spectrogram "song.mp3" # 10 秒出图,只要 ffmpeg
26
27
  & $node .\bin\music-lens.mjs separate "song.mp3" # 分轨
27
28
  & $node .\bin\music-lens.mjs analyze "song.mp3" --model medium
28
29
  ```
29
30
 
31
+ `spectrogram` 是**零依赖**的快速预览:频谱图 + 波形图,不装 Python、不下模型,
32
+ 只要机器上有 ffmpeg(`MUSIC_LENS_FFMPEG` 可指定路径)。
33
+
30
34
  `setup` 会自己装 python 依赖并从 hf-mirror 下模型,**默认只下 small(461 MB)**:
31
35
 
32
36
  ```powershell
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "music-lens",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Let a text-only model read a song: lyrics + timestamps, loudness, brightness, key, tempo, sections. Local, no API keys.",
5
5
  "type": "module",
6
6
  "bin": {