summarylogtreecommitdiffstats
path: root/lyricify
blob: 7087cdae8eef3e1d17053e7019b8364e6f284c2b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#!/usr/bin/env python3

import requests, subprocess, time, sys, re, os
from subprocess import check_output, CalledProcessError

RESET = "\033[0m"
HIGHLIGHT = "\033[48;5;27m"

def get_current_track():
    try:
        artist = check_output(["playerctl", "metadata", "xesam:artist", "-p", "spotify"]).decode().strip()
        title = check_output(["playerctl", "metadata", "xesam:title", "-p", "spotify"]).decode().strip()
        return artist, title
    except CalledProcessError:
        print("❌ Failed to get current song from playerctl.")
        return None, None

def get_synced_lyrics(artist, title):
    query = f"{artist} {title}"
    url = f"https://lrclib.net/api/search?q={requests.utils.quote(query)}"
    
    try:
        response = requests.get(url, timeout=5)
        data = response.json()
    except Exception as e:
        print(f"❌ Error while querying LRCLIB: {e}")
        return None

    if not data:
        return None

    track = data[0]

    synced = track.get("syncedLyrics", None)
    if synced:
        if isinstance(synced, str) and synced.startswith("http"):
            try:
                return requests.get(synced, timeout=5).text
            except Exception as e:
                print(f"❌ Couldn't download .lrc file: {e}")
        else:
            return synced

    plain = track.get("plainLyrics", None)
    if plain:
        return plain

    return None

def get_current_position():
    try:
        pos = subprocess.check_output(["playerctl", "position", "-p", "spotify"]).decode().strip()
        return float(pos)
    except Exception:
        return 0.0

def parse_lrc(lrc_text):
    pattern = re.compile(r"(\[(\d+):(\d+\.\d+)\])+(.+)")
    lines = []
    for line in lrc_text.splitlines():
        matches = list(re.finditer(r"\[(\d+):(\d+\.\d+)\]", line))
        if matches:
            lyric = line[matches[-1].end():].strip()
            for m in matches:
                minutes = int(m.group(1))
                seconds = float(m.group(2))
                timestamp = minutes * 60 + seconds
                lines.append((timestamp, lyric))
    return sorted(lines, key=lambda x: x[0])

def clear_terminal():
    sys.stdout.write("\033[2J\033[H")
    sys.stdout.flush()

def find_current_line(lines, current_time):
    for i in range(len(lines)-1):
        if lines[i][0] <= current_time < lines[i+1][0]:
            return i
    return len(lines) - 1

def display_lyrics(lines, current_time, window=10, last_index=None):
    current_index = find_current_line(lines, current_time)
    if last_index == current_index:
        return last_index

    start = max(0, current_index - window // 2)
    end = min(len(lines), start + window)

    clear_terminal()

    for i in range(start, end):
        ts, lyric = lines[i]
        if i == current_index:
            print(f"{HIGHLIGHT}{lyric}{RESET}")
        else:
            print(lyric)
    return current_index

#Gonna be useful in the future
def is_kitty_by_term():
    return os.environ.get("TERM", "").lower() == "xterm-kitty"

def main():
    last_track = None
    last_index = None
    lines = []
    no_lyrics_msg = "❌ No lyrics found."

    try:
        while True:
            artist, title = get_current_track()
            if not artist or not title:
                time.sleep(1)
                continue

            current_track = f"{artist} - {title}"

            if current_track != last_track:
                print(f"🎧 Fetching lyrics for: {current_track}")
                lrc_text = get_synced_lyrics(artist, title)

                if lrc_text:
                    lines = parse_lrc(lrc_text)
                    last_index = None
                else:
                    lines = [(0, no_lyrics_msg)]
                    last_index = None

                last_track = current_track

            pos = get_current_position()
            last_index = display_lyrics(lines, pos, last_index=last_index)
            time.sleep(0.2)
    except KeyboardInterrupt:
        clear_terminal()
        print("Exited synced lyrics display.")

if __name__ == "__main__":
    main()