#!/usr/bin/env bash set -euo pipefail LIBRARY_DIR="${1:-/vol3/1000/音视频/MusicWork/Library}" BASE_URL="${MANGTOOL_LYRICS_BASE_URL:-http://192.168.10.159:28883}" AUTH="${MANGTOOL_LYRICS_AUTH:-}" [[ -d "$LIBRARY_DIR" ]] || { echo "Library 不存在: $LIBRARY_DIR" >&2; exit 1; } export BASE_URL AUTH find "$LIBRARY_DIR" -type f \( -iname '*.mp3' -o -iname '*.flac' -o -iname '*.m4a' -o -iname '*.aac' -o -iname '*.ogg' -o -iname '*.opus' -o -iname '*.wma' \) -print0 | while IFS= read -r -d '' audio; do lrc="${audio%.*}.lrc" if [[ -s "$lrc" ]] && python3 - "$lrc" <<'PY' import re,sys text=open(sys.argv[1],encoding='utf-8-sig',errors='replace').read().replace('\r','') meta=re.compile(r'^\[(?:ti|ar|al|by|re|ve|offset):',re.I) timed=re.compile(r'^\s*\[(?:\d{1,2}:)?\d{1,2}:\d{2}(?:\.\d+)?\]\s*(.*)$') labels=re.compile(r'^(?:作词|作曲|词曲|编曲|制作人?|混音(?:师|助理)?|录音(?:师)?|工程师|弦乐|鼓和钢琴|电贝斯|额外编程|数字编辑|原唱|演唱|出品|发行|版权|produced|written|composer|arranged|lyrics?|music|artist|title)\s*[::\-].*$',re.I) body=[] for raw in text.split('\n'): line=raw.strip() if not line or meta.match(line): continue match=timed.match(line); line=(match.group(1).strip() if match else line) if line and not labels.match(line): body.append(line) sys.exit(0 if len(body)>=2 and len(''.join(body).replace(' ',''))>=8 else 1) PY then echo "SKIP $audio" continue fi meta=$(ffprobe -v error -show_entries format_tags=title,artist -of json "$audio" 2>/dev/null || true) result=$(AUDIO_META="$meta" python3 - <<'PY' import json,os,sys,urllib.parse,urllib.request m=json.loads(os.environ.get('AUDIO_META','{}')).get('format',{}).get('tags',{}); t=m.get('title','').strip(); a=m.get('artist','').strip() if not t or not a: sys.exit(0) req=urllib.request.Request(os.environ['BASE_URL'].rstrip('/')+'/lrc?'+urllib.parse.urlencode({'title':t,'artist':a})) if os.environ.get('AUTH'): req.add_header('Authorization',os.environ['AUTH']) try: with urllib.request.urlopen(req,timeout=15) as r: rows=json.load(r) except Exception: sys.exit(0) def n(x): return ''.join(c.lower() for c in x if c.isalnum()) def meaningful(text): import re meta=re.compile(r'^\[(?:ti|ar|al|by|re|ve|offset):',re.I) timed=re.compile(r'^\s*\[(?:\d{1,2}:)?\d{1,2}:\d{2}(?:\.\d+)?\]\s*(.*)$') labels=re.compile(r'^(?:作词|作曲|词曲|编曲|制作人?|混音(?:师|助理)?|录音(?:师)?|工程师|弦乐|鼓和钢琴|电贝斯|额外编程|数字编辑|原唱|演唱|出品|发行|版权|produced|written|composer|arranged|lyrics?|music|artist|title)\s*[::\-].*$',re.I) body=[] for raw in text.replace('\r','').split('\n'): line=raw.strip() if not line or meta.match(line): continue match=timed.match(line); line=(match.group(1).strip() if match else line) if line and not labels.match(line): body.append(line) return len(body)>=2 and len(''.join(body).replace(' ',''))>=8 best=None for x in rows if isinstance(rows,list) else []: l=x.get('lyrics','').strip(); ct=x.get('title',''); ca=x.get('artist','') if not meaningful(l): continue s=(45 if n(t)==n(ct) else 25 if n(t) in n(ct) or n(ct) in n(t) else 0)+(45 if n(a)==n(ca) else 20 if n(a) in n(ca) or n(ca) in n(a) else -40) s+=10 if '[' in l and ':' in l else 0 if s>=60 and (best is None or s>best[0]): best=(s,l) if best: sys.stdout.write(best[1]) PY ) [[ -n "$result" ]] || { echo "MISS $audio"; continue; } printf '%s\n' "$result" > "${lrc}.tmp" && mv -f "${lrc}.tmp" "$lrc"; echo "OK $lrc" done