music-lens 0.1.3 → 0.1.5
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 +4 -1
- package/package.json +1 -1
- package/scripts/analyze.py +56 -1
package/README.md
CHANGED
|
@@ -127,7 +127,10 @@ $base = "C:\Users\86173\Documents\deepseek-harness\default-workspace\music-lens"
|
|
|
127
127
|
4. **`initial_prompt` 会被复述成歌词**(模型对着音乐段"念提示词"),所以不要传提示词。
|
|
128
128
|
5. **`--lang zh` 会把英文歌硬翻成中文**,出乱码。默认改成了 `auto`。
|
|
129
129
|
6. VAD 在音乐上会把人声整段滤掉,所以默认关闭(语音场景再加 `--vad`)。
|
|
130
|
-
7.
|
|
130
|
+
7. **编造署名**:音乐最空的位置(前奏/间奏/尾奏)常冒出 `作词 李宗盛 作曲 李宗盛`、
|
|
131
|
+
`中文字幕组`、`字幕by索兰娅`、`Zither Harp`、`优优独播剧场` —— 全是训练数据里的高频碎片,
|
|
132
|
+
不是它"认识这首歌"。0.1.4 起内置 18 条黑名单自动过滤,`--keep-hallucinations` 可关闭。
|
|
133
|
+
8. **Windows 上 npm 生成的 `music-lens.ps1` 会被执行策略拦住**,敲 `music-lens` 直接报
|
|
131
134
|
`禁止运行脚本`。两个办法:删掉那个 `.ps1`(PowerShell 会回落到 `.cmd`),或者
|
|
132
135
|
`Set-ExecutionPolicy -Scope CurrentUser RemoteSigned`。**每次 `npm i -g` 之后都要处理一次。**
|
|
133
136
|
|
package/package.json
CHANGED
package/scripts/analyze.py
CHANGED
|
@@ -16,6 +16,7 @@ Usage:
|
|
|
16
16
|
import argparse
|
|
17
17
|
import json
|
|
18
18
|
import os
|
|
19
|
+
import re
|
|
19
20
|
import sys
|
|
20
21
|
import warnings
|
|
21
22
|
from pathlib import Path
|
|
@@ -47,6 +48,35 @@ MINOR = np.array([6.33, 2.68, 3.52, 5.38, 2.60, 3.53, 2.54, 4.75, 3.98, 2.69, 3.
|
|
|
47
48
|
NOTES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
|
|
48
49
|
NOTES_ZH = ["C", "升C", "D", "升D", "E", "F", "升F", "G", "升G", "A", "升A", "B"]
|
|
49
50
|
|
|
51
|
+
# Whisper famously fabricates credits/subtitles when the audio carries no speech
|
|
52
|
+
# (intros, outros, instrumental breaks). These are stock phrases from its training
|
|
53
|
+
# data -- e.g. "作词 李宗盛" is a near-universal Chinese lyric-video credit line.
|
|
54
|
+
HALLUCINATION_PATTERNS = [
|
|
55
|
+
r"作\s*[詞词]\s*[::]?\s*\S{2,4}\s*作曲", # "作词 X 作曲 Y"
|
|
56
|
+
r"作词曲", # "作词曲 李宗盛"
|
|
57
|
+
r"[詞词曲]\s*[::]\s*李宗盛",
|
|
58
|
+
r"李宗盛",
|
|
59
|
+
r"字幕\s*(by|BY|由|组|組|提供|制作|製作)",
|
|
60
|
+
r"中文字幕",
|
|
61
|
+
r"字幕组",
|
|
62
|
+
r"优优独播剧场",
|
|
63
|
+
r"YoYo Television",
|
|
64
|
+
r"Zither Harp",
|
|
65
|
+
r"请不吝点赞",
|
|
66
|
+
r"点赞.{0,4}订阅",
|
|
67
|
+
r"明镜与点点",
|
|
68
|
+
r"^\s*谢谢(观看|收看|收听)",
|
|
69
|
+
r"^\s*Thanks? for watching",
|
|
70
|
+
r"^\s*Subtitles?\s*by",
|
|
71
|
+
r"^\s*Amara\.org",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
_HALLUCINATION_RE = re.compile("|".join(HALLUCINATION_PATTERNS), re.IGNORECASE)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def is_hallucination(text: str) -> bool:
|
|
78
|
+
return bool(_HALLUCINATION_RE.search(text))
|
|
79
|
+
|
|
50
80
|
|
|
51
81
|
def estimate_key(chroma_mean):
|
|
52
82
|
best = (-2.0, None)
|
|
@@ -68,6 +98,8 @@ def main():
|
|
|
68
98
|
ap.add_argument("--device", default="cpu")
|
|
69
99
|
ap.add_argument("--skip-whisper", action="store_true")
|
|
70
100
|
ap.add_argument("--vad", action="store_true", help="enable VAD (good for speech, bad for singing)")
|
|
101
|
+
ap.add_argument("--keep-hallucinations", action="store_true",
|
|
102
|
+
help="keep fabricated credit/subtitle lines instead of dropping them")
|
|
71
103
|
args = ap.parse_args()
|
|
72
104
|
|
|
73
105
|
src = Path(args.audio).expanduser().resolve()
|
|
@@ -131,6 +163,7 @@ def main():
|
|
|
131
163
|
# ------------------------------------------------------------- transcription
|
|
132
164
|
segments = []
|
|
133
165
|
info = {}
|
|
166
|
+
dropped = []
|
|
134
167
|
if not args.skip_whisper:
|
|
135
168
|
print(f"[2/6] transcribing with faster-whisper '{args.model}' (lang={args.lang}) ...")
|
|
136
169
|
from faster_whisper import WhisperModel
|
|
@@ -153,7 +186,10 @@ def main():
|
|
|
153
186
|
for s in it:
|
|
154
187
|
text = s.text.strip()
|
|
155
188
|
if text:
|
|
156
|
-
|
|
189
|
+
# opencc t2s is for Chinese only -- it would mangle Japanese kanji
|
|
190
|
+
# (選->选, 関係->関系) and is pointless for other scripts.
|
|
191
|
+
detected = (getattr(info, "language", "") or "").lower()
|
|
192
|
+
if _CC is not None and ((lang or detected) == "zh"):
|
|
157
193
|
text = _CC.convert(text)
|
|
158
194
|
segments.append({"start": round(s.start, 2), "end": round(s.end, 2), "text": text})
|
|
159
195
|
print(f" {s.start:7.1f}s {text}")
|
|
@@ -170,6 +206,21 @@ def main():
|
|
|
170
206
|
if len(deduped) != len(segments):
|
|
171
207
|
print(f" collapsed duplicates: {len(segments)} -> {len(deduped)} lines")
|
|
172
208
|
segments = deduped
|
|
209
|
+
|
|
210
|
+
# drop fabricated credits / subtitles (whisper filler on instrumental parts)
|
|
211
|
+
dropped = []
|
|
212
|
+
if not args.keep_hallucinations:
|
|
213
|
+
kept = []
|
|
214
|
+
for seg in segments:
|
|
215
|
+
if is_hallucination(seg["text"]):
|
|
216
|
+
dropped.append(seg)
|
|
217
|
+
print(f" dropped hallucination @{seg['start']:6.1f}s {seg['text']}")
|
|
218
|
+
else:
|
|
219
|
+
kept.append(seg)
|
|
220
|
+
if kept:
|
|
221
|
+
segments = kept
|
|
222
|
+
elif dropped:
|
|
223
|
+
print(" (all segments looked like hallucinations -- keeping them)")
|
|
173
224
|
else:
|
|
174
225
|
print("[2/6] whisper skipped")
|
|
175
226
|
|
|
@@ -253,6 +304,7 @@ def main():
|
|
|
253
304
|
"vocal_time_ratio": round(sing_ratio, 2),
|
|
254
305
|
"sections": sections,
|
|
255
306
|
"segments_count": len(segments),
|
|
307
|
+
"dropped_hallucinations": len(dropped),
|
|
256
308
|
}
|
|
257
309
|
with open(out / "05_summary.json", "w", encoding="utf-8") as f:
|
|
258
310
|
json.dump(summary, f, ensure_ascii=False, indent=2)
|
|
@@ -269,6 +321,9 @@ def main():
|
|
|
269
321
|
L.append(f"- 动态范围:{lo_d} dB ~ {hi_d} dB(相对最响处)")
|
|
270
322
|
L.append(f"- 人声占比:约 **{sing_ratio*100:.0f}%** 时长检测到人声")
|
|
271
323
|
L.append(f"- 歌词字数:约 {total_lyrics} 字,{len(segments)} 句\n")
|
|
324
|
+
if dropped:
|
|
325
|
+
L.append(f"> 已过滤 {len(dropped)} 条疑似幻觉(片头/署名/字幕类)。"
|
|
326
|
+
f"想保留请加 `--keep-hallucinations`。\n")
|
|
272
327
|
|
|
273
328
|
L.append("## 段落结构(按响度自动切分)\n")
|
|
274
329
|
for s in sections:
|