支持LRC歌词的终端播放器
Published on Oct 09, 2014
最终效果可以看这里:
来自asciinema的终端录像
缘起
自从降频之后,电脑特别卡……
降频是因为总是自动关机
自动关机据几个月观察是因为cpu过热
cpu过热猜测是因为散热有问题了
散热有问题猜测是该清灰换硅胶了
但三星……非常麻烦,自从去年毕业自己把本拆了一遍后就再懒得拆机了。
于是,想听歌时懒得打开vlc界面,就用命令行版本的cvlc来播放
可是……没有歌词?
某天想干脆自己做个支持lrc的终端播放器算了。
作为一个脚本小子,我只习惯python……
说干就干
谷歌搜寻了下:
- python如何播放mp3音乐
- lrc文件的格式,考虑下如何在终端打印歌词
- vlc有没有python绑定
于是,有了以下版本,仅仅100行的多彩歌词显示终端播放器(使用=pygame=来播放音乐):
#!/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搜索路径。
#!/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完全不是问题。
嗯,完工。我擦嘞我竟然一天写了俩!