L4 FeelUOwn音乐播放器
这是python结课作业的第四题(Level 4),选择一个python项目,迁移到自己的机器上(哎就是安装),各路大神都在搞深度学习、人工智能之类,我学习进度没他们快、还没自学到这些,所以选择很受限。很感激遇到这个项目,挺有意思的,让我学到不少。也对着源码发呆没少耗费时光。这是这学期自己探索的最有意思的事了,所以记录一下。
前言:为什么选择这个项目
为什么没有选择实验文档给出的kannan-anbu/python-music-player呢?
一开始选题的时候,我真的不知道该做什么(人工智能、深度学习、数据分析什么都不会),头很大,还没决定要不要做这个播放器的题目,所以只是在github上草率地搜了搜,竟然没找到这个项目,如下图。我还尝试了在github上搜索作者,调整搜索过滤器等方法,都无果。
随机逛了逛,发现了FeelUOwn这个项目,还是国人写的,3k多星,有自己的项目网站,还配备了开发者指南(当时还不知道不太这个指南不太详细),并公开了开发者交流群,看作者的个人网站(cosven),觉得他是很有哲思的一个人。总而言之,在我为了不知道选什么题目而抓狂之中,这个项目就像一颗启明星。同时,作者是中国人也让我很激动,怎么说呢,看Vue的纪录片时,作者尤雨溪,一个黄皮肤的中国人,做出了风靡全球的开源项目,无论对于web应用从业人员,还是我这种普通学生,这都是一种鼓舞,有一种让人血液里的脉搏更加跳动的力量,而看到FeelUOwn这个国人做的项目,想到我也要和它多少产生点关系,我就不由得激动。
这就是起因。
而直到我准备完了level5的代码,写报告这会儿,才想起来用搜索引擎搜一下那个没搜到的音乐播放器项目,就搜到了…
现在想来,当时搜不到,大概率是项目及作者名字中的-
触发了github搜索的语法,搜索引擎以另外一种方式解析我的搜索内容,才导致搜索不成功。
对这个软件的介绍
这个软件集成了多个音乐平台的的乐库,使用mpv作为默认播放引擎,qt做为gui框架,提供不仅可用而且美观的搜索、播放、歌词等功能,除此之外,还有类似vimrc
的配置文件、无GUI启动、与emacs集成等极客的使用内容,有趣也值得探索。
如何安装并使用
安装
这是官方安装指南。
我使用的操作系统是archlinux,所以直接使用命令sudo pacman -S feeluown
即可。
而对于其他的linux发行版,安装指南中的pip安装方法应该是比较通用的,我也尝试了,没有问题。
使用
feeluwon既提供gui启动,也支持命令行使用。
安装好后,可以通过命令行启动,也可以通过桌面图标(调用feeluown-genicon
生成桌面图标)启动。
阅读源码(link)可以发现,feeluown为自己注册了两个命令行启动别名:fuo
和feeluown
,所以下面我将以方便为主,任意使用两个别名。
gui启动
先尝试启用gui界面:(更新失败是因为垃圾校园网)
在这个gui界面的上侧可以切换出搜索栏:
尝试搜索王心凌的爱你, 如下,没有结果:
这是因为我们还没有安装音乐提供方的插件,作者开发了若干比较流行的音乐提供方插件:网易云、QQ、虾米等(甚至还有集成在vim和emacs中的插件),我在level5中只会涉及网易云插件,所以我们就先安装网易云插件:pip install fuo-netease
安装完成后,重启fuo。
可以看到,现在:
-
左上角出现了网易云的图标
-
歌曲搜索也有了结果,歌曲最右侧表明音乐来源都是网易云
其他基本的播放,歌词,搜索专辑、歌手等功能,feeluown都有提供,助教不妨多玩一玩。
命令行(daemon)
我们可以在一个端口以纯daemon模式打开feeluown服务,另一个端口使用netcat与其交互,现在的feeluown使用端口23333,以后可能会实现用户指定端口的功能。
下面是fuo的帮助文本,主要的功能是
play,show,search,remove,add,exec,pause,resume,toggle,stop,next,previous,list,clear,status,genicon
usage: feeluown [-h] [-V] [-ns] [-nw] [-d] [-v] [--log-to-file]
[--mpv-audio-device MPV_AUDIO_DEVICE]
{play,show,search,remove,add,exec,pause,resume,toggle,stop,next,previous,list,clear,status,genicon}
...
FeelUOwn - modern music player (daemon).
Example:
- fuo # start fuo server
- fuo status # lookup server status
- fuo play 晴天-周杰伦 # search and play
positional arguments:
{play,show,search,remove,add,exec,pause,resume,toggle,stop,next,previous,list,clear,status,genicon}
show 显示资源详细信息
remove 从播放列表移除歌曲
add 添加歌曲到播放列表
pause 暂停播放
resume 回复播放
toggle
options:
-h, --help show this help message and exit
-V, --version show program's version number and exit
-ns, --no-server 不运行 server
-nw, --no-window 不显示 GUI
-d, --debug 开启调试模式
-v, --verbose 输出详细的日志
--log-to-file 将日志打到文件中
--mpv-audio-device MPV_AUDIO_DEVICE
(高级选项)指定播放设备
现在我们尝试通过命令行交互,让feeluown播放张杰唱的夜空中最亮的星。
先看一看当前的播放列表:
list
ACK OK 103
fuo://netease/songs/1474411443 # 爱你 - 王心凌
然后搜索夜空中最亮的星这首歌:
search 夜空中最亮的星-张杰
ACK OK 620
fuo://netease/songs/28427772 # 夜空中最亮的星 (L… - 张杰
fuo://netease/songs/28059417 # 他不懂 - 张杰
fuo://netease/songs/25706282 # 夜空中最亮的星 - 逃跑计划
fuo://netease/songs/574919767 # 只要平凡 - 张杰 & 张碧晨
fuo://netease/songs/191254 # 天下 - 张杰
fuo://netease/songs/1386887580 # 夜空中最亮的星 ( … - 小朱吃了几公斤
fuo://netease/songs/450222627 # 夜空中最亮的星 (l… - 张杰
fuo://netease/songs/570296467 # 粉红色的回忆 + Be… - 张杰 & 六位素人
fuo://netease/songs/1335012479 # 夜空中最亮的星( … - 九艾伦
feeluown的音乐标识符都遵从fuo协议(应该是作者自定义的),也就是这一串:fuo://netease/songs/1405753158
,这个代表网易云提供的歌曲,编号为28427772
,也就是我们要找的这首歌。我们现在把它添加到播放列表并播放:
add fuo://netease/songs/28427772
ACK OK 0
list
ACK OK 88
fuo://netease/songs/1474411443 # 爱你 - 王心凌
fuo://netease/songs/28427772 # -
next
ACK OK 0
status
ACK OK 204
repeat: true
random: false
volume: 23
state: playing
duration: 372.532245
position: 4.320552399092971
song: fuo://netease/songs/28427772 # -
lyric-s: 作曲 : 逃跑计划
这里看到程序的一个小bug,添加之后没有歌曲信息。因为现在播放的还是爱你 这首歌,所以我们使用next
命令播放下一首的夜空中最亮的星。随着音乐响起,我们可以用status
查看,播放的是fuo://netease/songs/28427772
,无误。
在server的终端Ctrl-C
关闭服务端。关闭不当会导致端口23333仍被占用,不能重新打开feeluown,linux下需要使用kill -s KILL {fuo/feeluown}
杀掉后台进程。
配置文件
在用户目录下的.fuorc
就是配置文件,这是一个py文件。feeluown会暴露config对象和若干函数到这个文件中,并在启动时以python的exec
方法执行它。
一些可配置项可以在这里找到。
对于自定义函数这项,add_hook
的源码是这样的:
@expose_to_rcfile(aliases='when')
def add_hook(signal_symbol: str, func: Callable, use_symbol: bool = False, **kwargs):
"""add hook on signal
:param signal_symbol: app.{object}.{signal_name} .
:param func: Signal receiver.
:param use_symbol: Whether to connect the signal to the symbol of the receiver.
If this is true, the real receiver is lazy found by the symbol, and
the signal connects to a symbol instead of a function object.
If this is false, problem may occur when the rcfile is reloaded.
because there the signal connects to two *same* receivers.
:param kwargs: This is directly passed to Signal.connect.
>>> def func(): pass
>>> add_hook('app.initialized', func)
.. versionadded:: 3.8
The *kwargs* keyword argument.
"""
signal_mgr.add(signal_symbol, func, use_symbol, **kwargs)
暴露到配置文件是由一个装饰器实现的,有个alias:when
,比较重要的是signal_symbol
这个参数的格式:app.{object}.{signal_name}
。
以作者提供的一个示例为例:
when('app.playlist.song_changed', notify_song_changed)
notify_song_changed
就是自定义的函数,我们来看这个'app.playlist.song_changed'
,这个代表的是当播放列表歌曲发生切换时的signal。这种信号可以在源码中找到,我的意思是,作者没有提供所有信号的列表。以这个信号为例,在这里找到,类似的,也可以找到playerlist的若干其他信号(考虑到继承后的实现,这个列表不一定全):
Signal | Meaning |
---|---|
eof_reached | playlist have no enough songs |
mode_changed | playlist mode changed signal |
playback_mode_changed | playback mode changed signal |
songs_added | add a song |
songs_removed | reomove a song |
song_changed | change a song |
song_changed_v2 | don’t know what it’s |
自定义函数先讨论到这里,再看config对象,这个可定义项我拷贝了下来,是这样的:
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
DEBUG | bool | False | 是否为调试模式 |
MODE | str | 0x0000 | CLI or GUI 模式 |
THEME | str | auto | auto/light/dark |
COLLECTIONS_DIR | str | '' | 本地收藏所在目录 |
LOG_TO_FILE | bool | True | 将日志输出到文件中 |
AUDIO_SELECT_POLICY | str | hq<> | feeluown.media.Quality.SortPolicy |
VIDEO_SELECT_POLICY | str | hd<> | feeluown.media.Quality.SortPolicy |
我想配置的只有主题,其实主题还有一个选项:`macos_dark’,可以在这里找到。
为什选用这个主题呢?因为dark
主题只有底端是黑色的,主面板还是白的💢
所有的都介绍完了,更新一下配置文件:
config.THEME = 'macos_dark'
# 一个小功能:切换歌曲时,发送系统通知
def notify_song_changed(song):
if song is not None:
title = song.title_display
artists_name = song.artists_name_display
song_str = f'{title}-{artists_name}'
# os.system(f'notify-send "{song_str}"')
print(f"{song_str}")
when('app.playlist.song_changed', notify_song_changed)
# 让编辑器识别这是一个 Python 文件
#
# Local Variables:
# mode: python
# End:
#
# vim: ft=python
这时通过命令行启动fuo,看到黑色主题来了,在切换歌曲时,启动终端会发出切换歌曲通知:
更多配置:
本应该可以像notify_song_changed一样,定义添加、删除歌曲的hook函数,但是这并不现实。拿
songs_added
为例,注意这个Signal的名称中是songs而不是song,因为可能批量增加,这时hook函数的参数不再是song
而是:(idx, cnt),代表增加在列表中的起始位置和增加个数。有这些信息,可是我没有运行时的app实例呀!如果考虑修改源码,那不是不行,但是没必要。但是其他的配置函数,有需要还是可以考虑一下的,比如
eof_reached
就不要参数,这个好,我们可以在播放列表到达末尾时给发送一条消息。就简单地给终端发送一条信息,可以参考这样的写法:when('app.playlist.eof_reached', notify_eof) def notify_eof(): print("EOF reached")
我担心助教说我做无用功,所以解释一下为啥费这么大功夫搞这玩意:
这些配置文件都是简化版的模型,我想,像fuorc这样比较贴近源码的配置文件,配置好了是可以有很多实用、高度定制化的玩法的。
就拿这个eof来说,顺序播放在到达列表末端时会停止播放,这时提醒一下播放结束了,不用让听的人纠结:是不是还在加载新歌曲呢?我的naive实现只在daemon模式下有效,但是完全可以将这个hook函数写复杂一点,比如:向窗口管理器发送通知,使标签页显红闪烁,像这种:
所以我做的这些工作/研究还是有实用价值的。