Skip to content

Commit

Permalink
library: use yt-dlp as a global standby-provider (#842)
Browse files Browse the repository at this point in the history
To use this feature, you should 
1. instance yt-dlp `pip install yt-dlp`
2. setup config in `~/.fuorc`, for example

```python
config.YTDL_RULES = [{'name': 'match_source',
                      'pattern': 'ytmusic',
                      'http_proxy': 'http://127.0.0.1:7890'},]
```
  • Loading branch information
cosven authored Jun 30, 2024
1 parent d3fe652 commit 4d1f4aa
Show file tree
Hide file tree
Showing 10 changed files with 257 additions and 55 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:
pip install --upgrade pip
pip install pyqt5
pip install "pytest<7.2"
pip install -e .[dev,cookies,webserver]
pip install -e .[dev,cookies,webserver,ytdl]
- name: Install Python(macOS) Dependencies
if: startsWith(matrix.os, 'macos')
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/win-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
pip install pyqt5
pip install pyinstaller
pip install pyinstaller-versionfile
pip install -e .[win32,battery,cookies]
pip install -e .[win32,battery,cookies,ytdl]
- name: Download mpv-1.dll
run: |
choco install curl
Expand Down
108 changes: 66 additions & 42 deletions docs/source/fuorc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@

类似 Vim 的 ``.vimrc`` 、Emacs 的 ``init.el`` ,feeluown 也有自己的配置文件 ``.fuorc`` 。

fuorc 文件是一个 Python 脚本,它位于 ``~/.fuorc`` 目录下,我们可以在其中使用任意 Python 语法。
feeluown 启动时,第一步就是加载并解析该配置文件。通常,我们可以在配置文件中作以下事情:
fuorc 文件是一个 Python 脚本,它位于 ``~/.fuorc`` ,我们可以在其中使用任意 Python 语法。
feeluown 启动时,第一步就是加载并解析该配置文件,结合 Python 的灵活性,意味着你可以对
feeluown 的行为进行完完全全的控制。通常,你可以在配置文件中作以下事情:

1. 配置部分选项
2. 定制一些小功能
1. 配置选项
2. 功能定制

一个 fuorc 文件示例::

一个🌰
-----------------

import os
一个 fuorc 文件示例::

import os

# 自定义配置
config.THEME = 'dark'
Expand All @@ -39,55 +42,76 @@ feeluown 启动时,第一步就是加载并解析该配置文件。通常,
#
# vim: ft=python

配置选项
-------------

原理简述
--------------

feeluown 使用 Python 的 exec 方法来加载(执行)配置文件。执行时,
会暴露 config 对象和部分函数到这个作用域中。

函数
~~~~~~~~

目前暴露到该作用域的函数有

.. autofunction:: feeluown.fuoexec.add_hook

.. autofunction:: feeluown.fuoexec.rm_hook

.. autofunction:: feeluown.fuoexec.source

config 对象
~~~~~~~~~~~~~

``config`` 是 :class:`feeluown.config.Config` 的实例,常见使用场景有两种::

>>> theme = config.THEME # 获取配置项的值
>>> config.THEME = 'dark' # 设置配置项的值

目前支持的配置项如下
通用配置项如下

**通用配置项**

==================== ========= ============ =========
======================= ========= ============ =========
名称 类型 默认值 描述
==================== ========= ============ =========
DEBUG ``bool`` ``False`` 是否为调试模式
MODE ``str`` ``0x0000`` CLI or GUI 模式
======================= ========= ============ =========
RPC_PORT ``int`` ``23333`` RPC 服务端口
PUBSUB_PORT ``int`` ``23334`` PUBSUB 服务端口
ALLOW_LAN_CONNECT ``bool`` ``False`` 是否可以从局域网连接 RPC 服务
THEME ``str`` ``auto`` auto/light/dark
COLLECTIONS_DIR ``str`` ``''`` 本地收藏所在目录
LOG_TO_FILE ``bool`` ``True`` 将日志输出到文件中
AUDIO_SELECT_POLICY ``str`` ``hq<>`` :class:`feeluown.media.Quality.SortPolicy`
VIDEO_SELECT_POLICY ``str`` ``hd<>`` :class:`feeluown.media.Quality.SortPolicy`
==================== ========= ============ =========

**MPV 播放器配置项** (使用 MPV 做为播放引擎时生效)
NOTIFY_ON_TRACK_CHANGED ``bool`` ``False`` 切换歌曲时显示桌面通知
NOTIFY_DURATION ``int`` ``3000`` 桌面通知保留时长(ms)
PROVIDERS_STANDBY ``list`` ``None`` 候选歌曲提供方(默认:所有提供方)
======================= ========= ============ =========

实验特性的配置项

============================= ========= ============ =========
名称 类型 默认值 描述
============================= ========= ============ =========
ENABLE_WEB_SERVER ``bool`` ``False`` 开启 WEB 服务
WEB_PORT ``int`` ``23332`` WEB 服务端口
ENABLE_NEW_HOMEPAGE ``bool`` ``False`` 开启新版主页
ENABLE_MV_AS_STANDBY ``bool`` ``True`` 使用 MV 作为歌曲资源
PLAYBACK_CROSSFADE ``bool`` ``False`` 开启淡入淡出
PLAYBACK_CROSSFADE_DURATION ``int`` ``500`` 淡入淡出持续时间
ENABLE_YTDL_AS_MEDIA_PROVIDER ``int`` ``False`` 使用 YTDL 作为媒体资源提供方
YTDL_RULES ``list`` ``None`` YTDL 的命中规则
============================= ========= ============ =========

MPV 播放器配置项 (使用 MPV 做为播放引擎时生效)

==================== ========= ============ =========
名称 类型 默认值 描述
==================== ========= ============ =========
MPV_AUDIO_DEVICE ``str`` ``auto`` MPV 播放设备
==================== ========= ============ =========

你可以查看下述函数的源代码来查看完整列表。

.. autofunction:: feeluown.app.config.create_config


功能定制
--------------

feeluown 使用 Python 的 exec 方法来加载(执行)配置文件。执行时,
会暴露 ``config`` 对象和部分函数(如 ``add_hook``, ``rm_hook`` 等)到这个作用域中。
通过 ``add_hook`` (这个函数有个别名是 ``when`` ),你可以对程序大部分事件进行监听。

.. autoclass:: feeluown.config.Config
常见的事件有

1. ``app.player.metadata_changed`` 歌曲元信息变化时(通常发生于歌曲切换时)
2. ``app.initialized`` 应用初始化完毕(未启动)
3. ``app.started`` 应用已经启动
4. ``app.ui.songs_table.about_to_show_menu`` 右键显示歌曲菜单

你可以参考示例 `cosven-fuorc <https://github.com/cosven/rcfiles/blob/master/fuorc>`_

函数
~~~~~~~~

函数的文档可以参考

.. autofunction:: feeluown.fuoexec.add_hook
.. autofunction:: feeluown.fuoexec.rm_hook
.. autofunction:: feeluown.fuoexec.source
8 changes: 8 additions & 0 deletions feeluown/app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ def __init__(self, args, config, **kwargs):

# Library.
self.library = Library(config.PROVIDERS_STANDBY)
if config.ENABLE_YTDL_AS_MEDIA_PROVIDER:
try:
self.library.setup_ytdl(rules=config.YTDL_RULES)
except ImportError as e:
logger.warning(f"can't enable ytdl as standby due to {e}")
else:
logger.info(f"enable ytdl as standby succeed"
f" with rules:{config.YTDL_RULES}")
# TODO: initialization should be moved into initialize
Resolver.library = self.library
# Player.
Expand Down
7 changes: 7 additions & 0 deletions feeluown/app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ def create_config() -> Config:
config.deffield('VIDEO_SELECT_POLICY', default='hd<>')
config.deffield('ALLOW_LAN_CONNECT', type_=bool, default=False, desc='是否可以从局域网连接服务器')
config.deffield('PROVIDERS_STANDBY', type_=list, default=None, desc='')
config.deffield('ENABLE_YTDL_AS_MEDIA_PROVIDER', type_=bool, default=True,
desc='YTDL 作为备用资源')
# For example::
# [{'name': 'match_by_source',
# 'source': 'xxx',
# 'http_proxy': 'http://127.0.0.1:7890'},]
config.deffield('YTDL_RULES', type_=list, default=None, desc='')
# TODO(cosven): maybe
# 1. when it is set to 2, find standby from other providers first.
# 2. when it is set to 3, play it's MV model instead of using MV's media.
Expand Down
51 changes: 41 additions & 10 deletions feeluown/library/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import logging
import warnings
from functools import partial
from typing import Optional, TypeVar, List
from typing import Optional, TypeVar, List, TYPE_CHECKING

from feeluown.media import Media
from feeluown.utils.aio import run_fn, as_completed
Expand All @@ -20,9 +20,12 @@
from .provider_protocol import (
check_flag as check_flag_impl,
SupportsSongLyric, SupportsSongMV, SupportsSongMultiQuality,
SupportsVideoMultiQuality,
SupportsVideoMultiQuality, SupportsSongWebUrl, SupportsVideoWebUrl,
)

if TYPE_CHECKING:
from .ytdl import Ytdl


logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -75,10 +78,16 @@ def __init__(self, providers_standby=None):
"""
self._providers_standby = providers_standby
self._providers = set()
self.ytdl: Optional['Ytdl'] = None

self.provider_added = Signal() # emit(AbstractProvider)
self.provider_removed = Signal() # emit(AbstractProvider)

def setup_ytdl(self, *args, **kwargs):
from .ytdl import Ytdl

self.ytdl = Ytdl(*args, **kwargs)

def register(self, provider):
"""register provider
Expand Down Expand Up @@ -271,9 +280,20 @@ def song_prepare_media(self, song: BriefSongModel, policy) -> Media:
raise MediaNotFound(f'provider({song.source}) not found')
media = None
if isinstance(provider, SupportsSongMultiQuality):
media, _ = provider.song_select_media(song, policy)
try:
media, _ = provider.song_select_media(song, policy)
except MediaNotFound as e:
if e.reason is MediaNotFound.Reason.check_children:
raise
else:
media = None
if not media:
raise MediaNotFound('provider returns empty media')
if self.ytdl is not None and isinstance(provider, SupportsSongWebUrl):
song_web_url = provider.song_get_web_url(song)
logger.info(f'use ytdl to get media for {song_web_url}')
media = self.ytdl.select_audio(song_web_url, policy, source=song.source)
if not media:
raise MediaNotFound('provider returns empty media')
return media

def song_prepare_mv_media(self, song: BriefSongModel, policy) -> Media:
Expand Down Expand Up @@ -405,10 +425,21 @@ def video_prepare_media(self, video: BriefVideoModel, policy) -> Media:
:param video: either a v1 MvModel or a v2 (Brief)VideoModel.
"""
provider = self.get(video.source)
if isinstance(provider, SupportsVideoMultiQuality):
media, _ = provider.video_select_media(video, policy)
else:
raise MediaNotFound('provider or video not found')
if not media:
raise MediaNotFound('provider returns empty media')

try:
if isinstance(provider, SupportsVideoMultiQuality):
media, _ = provider.video_select_media(video, policy)
if not media:
raise MediaNotFound('provider returns empty media')
else:
raise MediaNotFound('provider or video not found')
except MediaNotFound:
if self.ytdl is not None and isinstance(provider, SupportsVideoWebUrl):
video_web_url = provider.video_get_web_url(video)
logger.info(f'use ytdl to get media for {video_web_url}')
media = self.ytdl.select_video(video_web_url,
policy,
source=video.source)
if not media:
raise
return media
6 changes: 6 additions & 0 deletions feeluown/library/provider_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,12 @@ def video_get(self, identifier: ID) -> VideoModel:
raise NotImplementedError


@runtime_checkable
class SupportsVideoWebUrl(Protocol):
def video_get_web_url(self, video: BriefVideoModel) -> str:
raise NotImplementedError


@eq(ModelType.video, PF.multi_quality)
@runtime_checkable
class SupportsVideoMultiQuality(Protocol):
Expand Down
Loading

0 comments on commit 4d1f4aa

Please sign in to comment.