summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ar/.config/ncmpcpp/config8
-rwxr-xr-xar/.config/ncmpcpp/genius-lyrics.py175
-rw-r--r--global/Music/.music.txt2
-rw-r--r--global/Music/.music_titles.txt56
4 files changed, 209 insertions, 32 deletions
diff --git a/ar/.config/ncmpcpp/config b/ar/.config/ncmpcpp/config
index 6cea647..5e90e43 100644
--- a/ar/.config/ncmpcpp/config
+++ b/ar/.config/ncmpcpp/config
@@ -335,7 +335,7 @@ song_columns_list_format = (20)[cyan]{a} (40)[yellow]{t} (30)[blue]{b} (10)[mage
## Note: Custom command that will be executed each time song changes. Useful for
## notifications etc.
##
-execute_on_song_change="pkill -RTMIN+2 dwmblocks"
+execute_on_song_change="pkill -RTMIN+2 dwmblocks; (~/.config/ncmpcpp/genius-lyrics.py >/dev/null 2>&1 &)"
#
##
## Note: Custom command that will be executed each time player state
@@ -421,7 +421,11 @@ media_library_albums_split_by_date = no
#
#cyclic_scrolling = no
#
-#lyrics_fetchers = tags, genius, tekstowo, plyrics, justsomelyrics, jahlyrics, zeneszoveg, internet
+## Only `tags` (embedded lyrics) is kept here. The broken built-in `genius`
+## scraper is removed on purpose; lyrics downloading is handled by
+## ~/.config/ncmpcpp/genius-lyrics.py (run from execute_on_song_change).
+## To restore ncmpcpp's own fetchers, re-add: genius, tekstowo, plyrics, ...
+lyrics_fetchers = tags
#
#follow_now_playing_lyrics = no
#
diff --git a/ar/.config/ncmpcpp/genius-lyrics.py b/ar/.config/ncmpcpp/genius-lyrics.py
new file mode 100755
index 0000000..98a0292
--- /dev/null
+++ b/ar/.config/ncmpcpp/genius-lyrics.py
@@ -0,0 +1,175 @@
+#!/usr/bin/env python3
+"""Fetch lyrics from Genius for the current (or given) song and write them
+in the flat `<artist> - <title>.txt` form that ncmpcpp reads.
+
+Why this exists: ncmpcpp 0.10.1's built-in Genius scraper stops at the first
+</div> inside the lyrics container. Genius now nests a LyricsHeader div there,
+so ncmpcpp only captures the header ("N Contributors<title> Lyrics") and drops
+the actual lyrics. This script parses the current Genius structure correctly.
+
+Usage:
+ genius-lyrics.py # uses `mpc current` for artist/title
+ genius-lyrics.py "Artist" "Title"
+ genius-lyrics.py --force ... # refetch even if a good file already exists
+
+Designed to be safe to call from ncmpcpp's execute_on_song_change: it never
+raises to the caller, runs quietly, and skips songs that already have lyrics.
+"""
+from __future__ import annotations
+
+import html
+import json
+import os
+import re
+import subprocess
+import sys
+import urllib.parse
+import urllib.request
+
+LYRICS_DIR = os.path.expanduser(
+ os.environ.get("NCMPCPP_LYRICS_DIR", "~/.local/share/lyrics")
+)
+UA = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36"
+TIMEOUT = 12
+
+# Content that means a previous (broken) scrape, so we should refetch:
+# e.g. "2 Contributorsgestalt Lyrics"
+BROKEN_RE = re.compile(r"^\s*\d+\s+Contributors?.*Lyrics\s*$", re.IGNORECASE | re.DOTALL)
+MIN_VALID_LEN = 60 # real lyrics are always longer than this
+
+
+def get(url: str) -> str:
+ req = urllib.request.Request(url, headers={"User-Agent": UA})
+ with urllib.request.urlopen(req, timeout=TIMEOUT) as r:
+ return r.read().decode("utf-8", "replace")
+
+
+def current_song():
+ try:
+ out = subprocess.run(
+ ["mpc", "current", "-f", "%artist%\t%title%"],
+ capture_output=True, text=True, timeout=5,
+ ).stdout.strip()
+ except Exception:
+ return None
+ if not out or "\t" not in out:
+ return None
+ artist, title = out.split("\t", 1)
+ if not artist or not title:
+ return None
+ return artist, title
+
+
+def lyrics_path(artist: str, title: str) -> str:
+ # ncmpcpp stores lyrics flat as "<artist> - <title>.txt"; '/' is unsafe.
+ name = f"{artist} - {title}.txt".replace("/", "_")
+ return os.path.join(LYRICS_DIR, name)
+
+
+def needs_fetch(path: str, force: bool) -> bool:
+ if force or not os.path.exists(path):
+ return True
+ try:
+ with open(path, encoding="utf-8") as f:
+ content = f.read()
+ except OSError:
+ return True
+ return len(content.strip()) < MIN_VALID_LEN or bool(BROKEN_RE.match(content))
+
+
+def _norm(s: str) -> str:
+ # Keep only alphanumerics (Unicode-aware: Korean stays), casefolded.
+ return "".join(ch for ch in s.casefold() if ch.isalnum())
+
+
+def _artist_matches(query_artist: str, hit_artist: str) -> bool:
+ a, b = _norm(query_artist), _norm(hit_artist)
+ if not a or not b:
+ return False
+ return a in b or b in a
+
+
+def genius_url(artist: str, title: str):
+ q = urllib.parse.quote(f"{artist} {title}")
+ try:
+ data = json.loads(get(f"https://genius.com/api/search/multi?q={q}"))
+ except Exception:
+ return None
+ # Only accept a hit whose artist matches the song's artist. Genius search
+ # happily returns unrelated songs when it lacks the track; writing those
+ # would be worse than writing nothing.
+ for section in data.get("response", {}).get("sections", []):
+ for hit in section.get("hits", []):
+ if hit.get("type") != "song":
+ continue
+ result = hit.get("result", {})
+ hit_artist = result.get("primary_artist", {}).get("name", "")
+ if _artist_matches(artist, hit_artist):
+ return result.get("url")
+ return None
+
+
+def extract_lyrics(page: str) -> str:
+ # Every verse lives in a data-lyrics-container; concatenate them all.
+ parts = re.findall(
+ r'data-lyrics-container="true"[^>]*>(.*?)</div>'
+ r'(?=\s*<div data-lyrics-container|\s*<div[^>]*class="[^"]*RightSidebar'
+ r'|\s*</div>\s*<div[^>]*StyledLink|$)',
+ page, re.S,
+ )
+ if not parts:
+ parts = re.findall(r'data-lyrics-container="true"[^>]*>(.*)', page, re.S)
+ text = "".join(parts)
+ # Drop the nested header block (Contributors / "<song> Lyrics") that broke ncmpcpp.
+ text = re.sub(
+ r'<div[^>]*data-exclude-from-selection="true".*?</div>\s*</div>\s*</div>',
+ "", text, flags=re.S,
+ )
+ text = re.sub(r"<br\s*/?>", "\n", text) # line breaks
+ text = re.sub(r"<[^>]+>", "", text) # strip remaining tags
+ return html.unescape(text).strip()
+
+
+def write_atomic(path: str, content: str) -> None:
+ os.makedirs(os.path.dirname(path), exist_ok=True)
+ tmp = path + ".tmp"
+ with open(tmp, "w", encoding="utf-8") as f:
+ f.write(content + "\n")
+ os.replace(tmp, path)
+
+
+def main(argv) -> int:
+ force = "--force" in argv
+ argv = [a for a in argv if a != "--force"]
+
+ if len(argv) >= 2:
+ artist, title = argv[0], argv[1]
+ else:
+ song = current_song()
+ if not song:
+ return 0 # nothing playing; nothing to do
+ artist, title = song
+
+ path = lyrics_path(artist, title)
+ if not needs_fetch(path, force):
+ return 0
+
+ url = genius_url(artist, title)
+ if not url:
+ return 0
+ try:
+ lyrics = extract_lyrics(get(url))
+ except Exception:
+ return 0
+ if len(lyrics) < MIN_VALID_LEN or BROKEN_RE.match(lyrics):
+ return 0 # don't overwrite with garbage
+
+ write_atomic(path, lyrics)
+ return 0
+
+
+if __name__ == "__main__":
+ try:
+ sys.exit(main(sys.argv[1:]))
+ except Exception:
+ sys.exit(0) # never disrupt ncmpcpp's song-change command
diff --git a/global/Music/.music.txt b/global/Music/.music.txt
index ed47e01..61cc35e 100644
--- a/global/Music/.music.txt
+++ b/global/Music/.music.txt
@@ -222,7 +222,6 @@ youtube hxLXdCvLHfs
youtube jJzw1h5CR-I
youtube pOnXgF4wTmc
youtube b_bjtIeqIR4
-youtube uMeR2W19wT0
youtube vGoMNejaSy0
youtube dGZqpVCJP3k
youtube 64ftDYJXcTo
@@ -373,7 +372,6 @@ youtube G1ej5up7JG0
youtube kPlSyYtE63M
youtube IMWT6937uUs
youtube -kcZNuFYnns
-youtube Kdl3erX8kA4
youtube q-I62ICVEeE
youtube 1kHNU5KsH5g
youtube Yc4gp6oeN7k
diff --git a/global/Music/.music_titles.txt b/global/Music/.music_titles.txt
index 191ec90..d528ed3 100644
--- a/global/Music/.music_titles.txt
+++ b/global/Music/.music_titles.txt
@@ -155,7 +155,7 @@ kcx0a2OAhN0 IU - Love poem
ybiINVA_gLc IU - Ending Scene (이런 엔딩)
1MnzUWjBQog IU - The shower (푸르던)
EQzqq0LNlto IU - Rain Drop
-5CXTwUrotf4 라따뚜이 - 레오 Leo - 콧시 CozySea cover (원곡:優里 Yuuri - Leo)
+5CXTwUrotf4 콧시 CozySea - Leo (cover)
cvQQEs4TDGM BIBI - Bam Yang Gang (밤양갱)
9TiGuWyGlXc 10CM - Tell Me It's Not a Dream
zFpoaELOrTo Crush - Love You With All My Heart
@@ -173,23 +173,23 @@ yuTesDwv6uA DAOKO & Kenshi Yonezu - 打上花火
TQ8WlA2GXbk OFFICIAL HIGE DANDISM - Pretender
m9SMT5ipbxk YOASOBI - アイドル
_PNFMuT6e4o King Gnu - Hakujitsu
-iuJDhFRDx9M Master ZX - Tokyo Drift - Teriyaki Boyz [ MUSIC VIDEO ] HD
+iuJDhFRDx9M Teriyaki Boyz - Tokyo Drift
1tk1pqwrOys Eve - Kaikai Kitan
4r6L3FUhbIo YOASOBI - Monster
--EKxzId_Sj4 Kenshi Yonezu - 米津玄師 - アイネクライネ , Kenshi Yonezu - Eine Kleine
+-EKxzId_Sj4 Kenshi Yonezu - アイネクライネ
0xSiBpUdW4E あいみょん - Marigold
-Dx_fKPBPYUI Kenshi Yonezu - 米津玄師 - LOSER , Kenshi Yonezu
+Dx_fKPBPYUI Kenshi Yonezu - LOSER
GtPLYvYeZ_4 Ado - Usseewa
diH9qVVUSNo NiziU - Make you happy
NinLIDs9qmU LiSA - homura
qp0AktOIAag RADWIMPS - Zenzenzense - movie ver.
zqKrpw36xnU KANA-BOON - Silhouette
-9aJVr5tTTWk Kenshi Yonezu - 米津玄師 - ピースサイン , Kenshi Yonezu - Peace Sign
+9aJVr5tTTWk Kenshi Yonezu - ピースサイン
-Jget2NBi30 紗倉ひびき(CV:ファイルーズあい)&街雄鳴造(CV:石川界人) - お願いマッスル
x8VYWazR5mE YOASOBI - Yoru ni Kakeru
wNjvuRZtQeI Mrs. GREEN APPLE - Inferno
RjDEFe8dw0A DA PUMP - U.S.A.
-gJX2iy6nhHc Masaki Suda & Kenshi Yonezu - 米津玄師 MV「 灰色と青( +菅田将暉 )」
+gJX2iy6nhHc Kenshi Yonezu - 灰色と青 (+菅田将暉)
by4SYYWlhEs YOASOBI - 夜に駆ける
KMdTrqzEI0I Gen Hoshino - Koi
b9dBi-Sd4SI Kenshi Yonezu - Paprika
@@ -208,16 +208,16 @@ gxp3R7l1iSk minami - Kawakiwoameku
pdvnR6cmI5U Masaki Suda - Machigaisagashi
0t0HtFYjcU8 imase - NIGHT DANCER
C7YQq-nup3A Ado - Odo
-ptnYBctoexk Kenshi Yonezu - 米津玄師 - 馬と鹿 Kenshi Yonezu - Uma to Shika
-bt8wNQJaKAk OFFICIAL HIGE DANDISM - Official髭男dism - I LOVE...[Official Video]
+ptnYBctoexk Kenshi Yonezu - 馬と鹿
+bt8wNQJaKAk Official髭男dism - I LOVE...
yzC4hFK5P3g Kyary Pamyu Pamyu - Pon Pon Pon
anOu7GvEKpI BABYMETAL - Gimme Chocolate!!
-J9FuvPmMoI RADWIMPS - Sparkle - movie ver.
-DuMqFknYHBs Official髭男dism - Official髭男dism - イエスタデイ[Official Video]
+DuMqFknYHBs Official髭男dism - イエスタデイ
CVe_AuaoQJk Kenshi Yonezu - Flamingo
3wu_p2s3h1g Columbia Music Box - Orion (Kenshi Yonezu)
3zh9Wb1KuW8 Fujii Kaze - Shinunoga E-Wa
-J5Z7tIq7bco スキマスイッチ - 「奏(かなで)」Music Video : SUKIMASWITCH / KANADE Music Video
+J5Z7tIq7bco スキマスイッチ - 奏(かなで)
hxLXdCvLHfs AKB48 - Heavy Rotation
jJzw1h5CR-I Eve - Dramaturgy
pOnXgF4wTmc J SOUL BROTHERS III from EXILE TRIBE - R.Y.U.S.E.I.
@@ -228,7 +228,7 @@ dGZqpVCJP3k YOASOBI - 群青
qT2ZeIWhcno Masaki Suda - Sayonara Elegy
yOAwvRmVIyo あいみょん - Naked Heart
IHaUGQVrBa8 THE ORAL CIGARETTES - Kyouran Hey Kids!!
-CID-sYQNCew animelab - Attack on Titan Season 2 - Official Opening Song - Shinzou wo Sasageyo by Linked Horizon
+CID-sYQNCew Linked Horizon - Shinzou wo Sasageyo
96IPIQjohZ4 WANIMA - Tomoni
1jaK9CT_omc Eito - Kousui
Dt0CYNAYfNY Ado - Gira Gira
@@ -236,7 +236,7 @@ VJDbgtc692k Ado - Show
qhmrt3ZNgcs Ado - Dried Flowers
QnAUQvrjuP0 King & Prince - Tsukiyomi
ViOzYSYWCMM Ado - New Genesis (UTA from ONE PIECE FILM RED)
-zkNzxsaCunU Kenshi Yonezu - 米津玄師 - 春雷 Kenshi Yonezu - Shunrai
+zkNzxsaCunU Kenshi Yonezu - 春雷
wJuef4IAt4k WagakkiBand - Senbonzakura
mMlFlUZDZVQ Superfly - Aiwo Komete Hanatabawo (From THE FIRST TAKE)
F8xpmTmdf6o MONOGATARI Series - Renai Circulation
@@ -246,25 +246,25 @@ WHdL2vstvL8 ヨルシカ - 言って。
YL9_wPbbvQo Masaki Suda - Niji
6gMBmZ28Xg4 Creepy Nuts - Bling-Bang-Bang-Born
-QxMzUEJH4Q Mrs. GREEN APPLE - Ao To Natsu
--kgOFJG881I OFFICIAL HIGE DANDISM - Official髭男dism - 宿命[Official Video]
+-kgOFJG881I Official髭男dism - 宿命
mp2-w15SXms Tani Yuuki - W / X / Y
EwpKUV0ECvQ yama - Haru wo Tsugeru
-O1bhZgkC4Gw OFFICIAL HIGE DANDISM - Official髭男dism - Cry Baby[Official Video]
+O1bhZgkC4Gw Official髭男dism - Cry Baby
N2A2VZTl9X0 Iori Kanzaki - Hated by Life
9mWbCPJuoIo frederic - Oddloop
-EHw005ZqCXk OFFICIAL HIGE DANDISM - Official髭男dism - ノーダウト[Official Video]
+EHw005ZqCXk Official髭男dism - ノーダウト
waYhTInn7GU 神山 羊 - YELLOW
-s582L3gujnw Kenshi Yonezu - 米津玄師 - パプリカ Kenshi Yonezu - PAPRIKA
+s582L3gujnw Kenshi Yonezu - パプリカ
oGWXgMa5vdE HIKAKIN & SEIKIN - YouTube Theme Song
6f6J7bvnceU Taro Urashima - Umino Koe (Instrumental)
xiuOSWPsIyM OFFICIAL HIGE DANDISM - Subtitle
TqyfmFaZlqw LiSA - Gurenge
VEe_yIbW64w Miki Matsubara - Mayonaka no Door 〜 Stay with Me
-xOYe7PN9u5E Kenshi Yonezu - KICK BACK (Tomggg Remix)
+xOYe7PN9u5E Kenshi Yonezu - KICK BACK (Tomggg Remix)
0ad7f0H5wmY ZUTOMAYO - Byoushinwo Kamu
-nROvY9uiYYk Eve - お気に召すまま - Eve MV
+nROvY9uiYYk Eve - お気に召すまま
tkjALBLAF6o SixTONES - kokkara
-Woorod1gJ_w Amatsuki - 【オリジナルPV】 小さな恋のうた / MONGOL800(cover) by天月
+Woorod1gJ_w 天月 - 小さな恋のうた (cover)
vfLfonEeoyM Chinozo - Good-bye Declaration
bWVYjgsJUUc ONE OK ROCK - Clock Strikes
M_Uz5SKGxs4 BUMP OF CHICKEN - Tentaikansoku
@@ -292,9 +292,9 @@ Hx_7LvLjNmc AAA - KoiotoToAmazora
tgNIeWvXKNM Yoko Oginome - Dancing Hero(Eat You Up)
7lcxt2tRyGQ AAA - さよならの前に
6ZyZRFChIPU Uru - Anataga Iru Kotode
-clU8c2fpk2s kobasolo - 【女性が歌う】Lemon/米津玄師(Full Covered by コバソロ & 春茶)
+clU8c2fpk2s kobasolo - Lemon (cover)
uDsEVMjwNb0 back number - Takaneno Hanakosan
-1s84rIhPuhk Kenshi Yonezu - 米津玄師 - 海の幽霊 Kenshi Yonezu - Spirits of the Sea
+1s84rIhPuhk Kenshi Yonezu - 海の幽霊
e7u2aPzWmU4 back number - Christmas Song
ENQANfv2ANQ Vaundy - Kaiju no Hanauta
Po2Ujyn8SXE aimyon - Harunohi
@@ -308,7 +308,7 @@ oMeFtedQS0E King Gnu - Ichizu (ALBUM ver.)
8GfDq1osBLM YOASOBI - ハルジオン
vJ2AhTaQO10 Saucy Dog - Cinderella Boy
kMzcG30g8OQ なにわ男子 - 初心LOVE(うぶらぶ)YouTube ver.
-jOegTv3a2h4 high_note Music Lounge - 糸 - 中島みゆき(フル)
+jOegTv3a2h4 中島みゆき - 糸
csVeFCLhmAM FLOW - Sign
FVaAp4dhK0Q gesunokiwamiotome - watashi igai watashi ja naino
IGldkA5dG8o Hikaru Utada - First Love
@@ -349,7 +349,7 @@ MYZXUJ-BG-Q Paul Blanco - Summer
kp-gVJ7XJV4 KyoungSeo - 첫 키스에 내 심장은 120BPM
8kQH_bBMrMQ 최유리 - Forest
7eiQHEJ-Sbo 안예은 - Magic Lily
-EK18vAp9VVc CHiCORY - 비몽 - 작야 Live COVER (240201) │ 원곡 조선블루스
+EK18vAp9VVc 비몽 - 작야 (cover)
2El3ZfWbehw OOHYO - Youth (DAY)
yPCi20P1QFs YEEUN AHN - Night Flower (야화)
KBKzIDdAO1s 하시토 - [한글자막] 원피스 필름 레드 삽입곡 Full - 나는 최강(I'm invincible) │ Ado
@@ -358,7 +358,7 @@ hza4uQGZexA 이예준 - miss you more, I'm sorry
Bi87zvvexLo majiko - Kokoronashi (2017 Live Version)
em0lGANmAUU GUMMY - Memory Loss
RGPpBDFFqDg Lucia(심규선) - 달과 6펜스
-i6rXYiwuJwM Philopon Music - 리무카이쵸 - 너에게 마지막 입맞춤을(君に最後の口づけを) [자막/가사]
+i6rXYiwuJwM 리무카이쵸 - 너에게 마지막 입맞춤을
R89tf2GbxTM 위도 Music Official - Gravity | 위도(W2RDO) Live Cover
a1IuJLebHgM Adele - When We Were Young
8CNnNGgAHig Lucia - Narrow Road (소로 小路)
@@ -505,7 +505,7 @@ Dmy0CkieM6s CAMO - Shawty
CLNQ_qWdEeA Gyeong Je Hwan - a remote star and satellite
h0ccMK0U9nY Coogie - Alone (Feat. LeeHi) (Alone
5TgTPH2b6nM Jehwwn - No Hope For Your Return
-JvQhJTM9OJU JUSTHIS - Do Not Go Gentle Into That Good Night (Feat. KWAII, DON MALIK, GongGongGoo009) (Prod. Humbert)
+JvQhJTM9OJU JUSTHIS - Do Not Go Gentle Into That Good Night
pRosoqfxJY4 GIRIBOY - That's Why I Can't Talk About Love (Feat. Woo) (That's Why I Can't Talk About Love
vMrI9LXohYo 2NE1 - 그리워해요(Missing You)
iTPkkKRKGrQ 2NE1 - Go Away
@@ -850,7 +850,7 @@ _0Yp2Wtvpeo Primary - Drama
jXgc1FTjMd4 hard life - dead celebrities
pwjj1eInTSU Zion.T - Click Me (2013)
sobPzRxbmwE 스트레이 - Thing You Are (Live Demo)
-OejyNpxR-DM The S6 - Vardy's on Fire - The S6 [Official Lyric Video]
+OejyNpxR-DM The S6 - Vardy's on Fire
vw3k_jWa-gI Yeah Yeah Yeahs - Heads Will Roll
pRIZohFFOMo Lewis Capaldi - Someone You Loved
NqLB6ub1BOw VIVIZ - MANIAC
@@ -900,9 +900,9 @@ PiHCuVnvBkU Shin Giwon Piano - In Your Time
IwQGnXpskoY Rokudenashi - One Voice
dgZlLjfh6q4 Damons year - yours
BctgR3JqGug Hebi - 늘 (EVER)
-2ThVc_x9_y0 비몽 Beemong - 김필선 - 마마 live (cover by 비몽)
+2ThVc_x9_y0 비몽 - 마마 (cover)
29lj9jFfJvY Woody - Sadder Than Yesterday (어제보다 슬픈 오늘)
-iTF-zVkrtZk 비몽 Beemong - 비몽 - 야수 live cover [돌발서버 콘서트]
+iTF-zVkrtZk 비몽 - 야수 (cover)
eNiPuDEUQpQ AKMU - Joy, Sorrow, A Beautiful Heart
7kCDaxIjZFY Park Hyo Shin - AE
QmyrqNY9RPU 하데스(HADES) - Planet B