盒子
盒子
文章目录
  1. 缘起
  2. 说干就干
  3. FootNotes

支持LRC歌词的终端播放器

最终效果可以看这里:

来自asciinema的终端录像

缘起

自从降频之后,电脑特别卡……

降频是因为总是自动关机

自动关机据几个月观察是因为cpu过热

cpu过热猜测是因为散热有问题了

散热有问题猜测是该清灰换硅胶了

但三星……非常麻烦,自从去年毕业自己把本拆了一遍后就再懒得拆机了。

于是,想听歌时懒得打开vlc界面,就用命令行版本的cvlc来播放

可是……没有歌词?

某天想干脆自己做个支持lrc的终端播放器算了。

作为一个脚本小子,我只习惯python……

说干就干

谷歌搜寻了下:

  • python如何播放mp3音乐
  • lrc文件的格式,考虑下如何在终端打印歌词
  • vlc有没有python绑定

于是,有了以下版本,仅仅100行的多彩歌词显示终端播放器(使用pygame来播放音乐):

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
#!/usr/bin/env python
# -*- coding:utf-8 -*-

from pygame import mixer
from mutagen.mp3 import MP3
import re
import time
import sys
import random

f_mp3 = sys.argv[1]
f_lrc = sys.argv[2]


def lrc2dict(lrc):
lrc_dict = {}
remove = lambda x: x.strip('[|]')
for line in lrc.split('\n'):
time_stamps = re.findall(r'\[[^\]]+\]', line)
if time_stamps:
# 截取歌词
lyric = line
for tplus in time_stamps:
lyric = lyric.replace(tplus, '')
# 解析时间
# tplus: [02:31.79]
# t 02:31.79
for tplus in time_stamps:
t = remove(tplus)
tag_flag = t.split(':')[0]
# 跳过: [ar: 逃跑计划]
if not tag_flag.isdigit():
continue
time_lrc = int(tag_flag) * 60000
time_lrc += int(t.split(':')[1].split('.')[0]) * 1000
# ms也许没有
try:
time_lrc += int(t.split('.')[1])
except:
pass
# 截取到0.1s精度,降低cpu占用
time_lrc = time_lrc / 100 * 100
lrc_dict[time_lrc] = lyric
return lrc_dict


def print_lrc(mixer, lrc_d, color, wholetime):
mixer.music.play()
# 防止开头歌词来不及播放
mixer.music.pause()
time.sleep(0.001)
mixer.music.play()
while 1:
# 截取到0.1s精度,降低cpu占用
t = mixer.music.get_pos() / 100 * 100
sys.stdout.write('[' +
time.strftime("%M:%S", time.localtime(t / 1000)) +
'/' +
time.strftime("%M:%S",
time.localtime(wholetime)) +
'] ')
if t in lrc_d:
sys.stdout.write(color + lrc_d[t] + '\033[0m')
sys.stdout.flush()
# 向后清除
sys.stdout.write("\033[K")
else:
sys.stdout.flush()
# 向后清除
# sys.stdout.write("\033[K")
sys.stdout.write('\r')
# 播放停止时退出
if t < 0:
sys.exit(0)
# 0.05s循环
time.sleep(0.05)

with open(f_lrc) as f:
lrc = f.read()

lrc_d = lrc2dict(lrc)

mixer.init()
mixer.music.load(f_mp3)

try:
wholetime = MP3(f_mp3).info.length
except:
wholetime = 0

colors = [
'\x1B[31m', # 红色
'\x1B[32m', # 绿色
'\x1B[33m', # 黄色
'\x1B[34m', # 蓝色
'\x1B[35m', # 紫色
'\x1B[36m', # 青色
'\x1B[37m' # 灰白
]
color = random.choice(colors)
print_lrc(mixer, lrc_d, color, wholetime)

那个不断循环查看播放进度打印对应歌词的实现真够丧心病狂的……精确到0.1s算了。

Be aware that MP3 support is limited. On some systems an unsupported format can crash the program, e.g. Debian Linux. Consider using OGG instead.

开始在pygame的文档中看到这句话还没在意后来,播放某个叫in my life的mp3时发现完全不对劲……而且没有获取音频长度的功能[^1]

后来想想,还是用vlc的py绑定吧。

vlc的py绑定相当赞:

完整覆盖libvlc功能,纯python,支持各个vlc版本和完整的文档。[^2]

但开始我以为pip可以直接下载……后来发现pip搜索到的是macos独有的vlc python wrapper……和这个绑定是两码事……真给跪了……

自行下载vlc.py到当前目录或者扔到python搜索路径。

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
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import vlc
import re
import time
import sys
import random

f_mp3 = sys.argv[1]
f_lrc = sys.argv[2]


def lrc2dict(lrc):
lrc_dict = {}
remove = lambda x: x.strip('[|]')
for line in lrc.split('\n'):
time_stamps = re.findall(r'\[[^\]]+\]', line)
if time_stamps:
# 截取歌词
lyric = line
for tplus in time_stamps:
lyric = lyric.replace(tplus, '')
# 解析时间
# tplus: [02:31.79]
# t 02:31.79
for tplus in time_stamps:
t = remove(tplus)
tag_flag = t.split(':')[0]
# 跳过: [ar: 逃跑计划]
if not tag_flag.isdigit():
continue
time_lrc = int(tag_flag) * 60000
time_lrc += int(t.split(':')[1].split('.')[0]) * 1000
# ms也许没有
try:
time_lrc += int(t.split('.')[1])
except:
pass
# 截取到0.1s精度,降低cpu占用
time_lrc = time_lrc / 1000 * 1000
lrc_dict[time_lrc] = lyric
return lrc_dict


def print_lrc(player, lrc_d, color):
player.play()
player.pause()
# 防止无法获取整个音频时长
# 音频时长必须加载才能读取
time.sleep(0.1)
player.play()
wholetime = mediaObject.get_duration() / 1000 * 1000
while 1:
# 截取到0.1s精度,降低cpu占用, notwork fine for vlc
# FIXME: get_time NOT WORK WELL, 只能精确到0.3s
# t = player.get_position() * wholetime
t = player.get_time() / 1000 * 1000
# sys.stdout.write(str(t) + '\r')
sys.stdout.write('[' +
time.strftime("%M:%S", time.localtime(t / 1000)) +
'/' +
time.strftime("%M:%S",
time.localtime(wholetime / 1000)) +
'] ')
if t not in lrc_d:
sys.stdout.flush()
# 向后清除
# sys.stdout.write("\033[K")
else:
sys.stdout.write(color + lrc_d[t] + '\033[0m')
sys.stdout.flush()
# 向后清除
sys.stdout.write("\033[K")
sys.stdout.write('\r')
# 播放停止时退出
if t == wholetime:
sys.exit(0)
# 0.05s循环
time.sleep(0.05)

with open(f_lrc) as f:
lrc = f.read()

lrc_d = lrc2dict(lrc)

vlcInstance = vlc.Instance()
player = vlcInstance.media_player_new()
mediaObject = vlcInstance.media_new(f_mp3)
player.set_media(mediaObject)

colors = [
'\x1B[31m', # 红色
'\x1B[32m', # 绿色
'\x1B[33m', # 黄色
'\x1B[34m', # 蓝色
'\x1B[35m', # 紫色
'\x1B[36m', # 青色
'\x1B[37m' # 灰白
]
color = random.choice(colors)
print_lrc(player, lrc_d, color)

奇葩的还是那个丧心病狂的循环,查询播放进度最短间隔只有0.3s。只好把lrc歌词显示精度降到1s了

反正对我是能用了。

也许有空可以让它支持播放列表的,用vlc完全不是问题。

嗯,完工。我擦嘞我竟然一天写了俩!

FootNotes

[^1]: Find the Length of a Song with Pygame
[^2]: Python bindings