### Example fuorc configuration file Source: https://feeluown.readthedocs.io/en/latest/fuorc.html Example fuorc configuration file. Sets theme, collections directory, and audio select policy, and registers a function to send a system notification when the song changes. Uses the `when` alias for `add_hook`. ```python import os # 自定义配置 config.THEME = 'dark' config.COLLECTIONS_DIR = '~/Dropbox/public/music' config.AUDIO_SELECT_POLICY = '>>>' # 一个小功能:切换歌曲时,发送系统通知 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}"') when('app.playlist.song_changed', notify_song_changed) # 让编辑器识别这是一个 Python 文件 # # Local Variables: # mode: python # End: # # vim: ft=python ``` -------------------------------- ### Request options examples Source: https://feeluown.readthedocs.io/en/latest/protocol.html Examples of request options: less (shorthand for less=true) and format=json. These options are not yet implemented. ```text # 搜索纵观线关键字,结果可以分多次返回(设置了请求选项) # 这里 less 请求选项是 less=true 的简写 search 纵贯线 #: less # 使用 JSON 格式返回 search 纵贯线 #: format=json,less ``` -------------------------------- ### exec command example Source: https://feeluown.readthedocs.io/en/latest/protocol.html Shows the exec command using a multi-line request and its equivalent single-line form. ```text # 让服务端执行代码 exec < nc localhost 23333 OK feeluown 1.0.0 ``` -------------------------------- ### 安装 libmpv 和 pipx(macOS/Ubuntu) Source: https://feeluown.readthedocs.io/en/latest/_sources/quickstart.rst.txt 使用系统包管理器安装 libmpv 和 pipx,为后续安装 FeelUOwn 做准备。 ```sh # macOS brew install mpv pipx # Ubuntu sudo apt-get install libmpv1 pipx ``` -------------------------------- ### 以 debug 模式启动 feeluown Source: https://feeluown.readthedocs.io/en/latest/_sources/dev_quickstart.rst.txt 使用 --debug 选项启动 feeluown,以便进行调试。运行 feeluown -h 可查看更多选项。 ```bash # 运行 feeluown -h 查看更多选项 feeluown --debug ``` -------------------------------- ### feeluown.fuoexec.source Source: https://feeluown.readthedocs.io/en/latest/_sources/fuorc.rst.txt Executes a source string in the fuorc context. ```APIDOC ## feeluown.fuoexec.source ### Description Executes a source string in the fuorc context. ### Method Function ### Signature `feeluown.fuoexec.source(source)` ### Parameters - **source** (str) - Required - The source code to execute. ### Returns None ``` -------------------------------- ### 克隆 FeelUOwn 仓库 Source: https://feeluown.readthedocs.io/en/latest/_sources/dev_quickstart.rst.txt 使用 SSH 方式克隆自己 fork 的 FeelUOwn 仓库到本地。 ```bash git clone git@github.com:username/FeelUOwn.git ``` -------------------------------- ### 使用 NetCat 连接 FeelUOwn Daemon Source: https://feeluown.readthedocs.io/en/latest/_sources/quickstart.rst.txt 通过 NetCat 工具连接 FeelUOwn Daemon 端口,输入命令查看播放器状态或播放音乐。 ```sh nc localhost 23333 # 输入 `status` 命令,可以查看播放器状态 # 输入 `fuo play fuo://netease/songs/470302665` 可以播放音乐 ``` -------------------------------- ### openSUSE Tumbleweed 安装 FeelUOwn Source: https://feeluown.readthedocs.io/en/latest/_sources/quickstart.rst.txt 以 root 用户运行命令,添加仓库并安装 feeluown。 ```sh zypper addrepo https://download.opensuse.org/repositories/home:weearcm/openSUSE_Tumbleweed/home:weearcm.repo zypper refresh zypper install --recommends feeluown ``` -------------------------------- ### FeelUOwn 协议多行文本格式 Source: https://feeluown.readthedocs.io/en/latest/_sources/protocol.rst.txt 展示多行文本的格式,类似于 bash here document。document 是命令的参数,这种命令只能接收一个参数。 ```text {cmd} [{options}] #: {req_options} < raise NotSupportedError 2. XiamiSongModel.list -> AttributeError # 好像不太优雅 3. XiamiSongModel.allow_batch -> 加一个标记字段 ``` -------------------------------- ### 使用 pipx 安装 FeelUOwn(Linux/macOS/Windows) Source: https://feeluown.readthedocs.io/en/latest/_sources/quickstart.rst.txt 使用 pipx 安装 FeelUOwn 及其依赖的 Python 包。macOS 和 Windows 用户建议使用 Python 3.10/3.11 版本,因为部分依赖包未适配 3.12 和 3.13。 ```sh # Linux 用户可以使用如下命令 pipx install feeluown[battery,ai,cookies,webengine,qt] -v # macOS 和 Windows 用户可以分别使用如下命令 # 建议优先使用 Python 3.10/3.11 版本(部分依赖包没有适配 3.12 和 3.13 版本) pipx install feeluown[battery,ai,cookies,webengine,qt,macos] --python `which python3.11` -v pipx install feeluown[battery,ai,cookies,webengine,qt,win32] --python `which python3.11` -v ``` -------------------------------- ### 使用 fuo 命令行工具(Daemon 模式) Source: https://feeluown.readthedocs.io/en/latest/quickstart.html 启动 Daemon 模式后,使用 fuo 命令查看播放器状态、搜索歌曲和播放歌曲。 ```bash feeluown -nw # 使用 Daemon 模式启动 feeluown fuo status # 查看播放器状态 fuo search 周杰伦 # 搜索歌曲 fuo play fuo://netease/songs/470302665 # 播放:(世界が终るまでは…)《灌篮高手》 ``` -------------------------------- ### Song URI with description Source: https://feeluown.readthedocs.io/en/latest/protocol.html Shows how to attach a short description to a song URI. The description fields are optional but must follow the given order. ```text {song uri}{ }# {title}-{artists_name}-{album_name}-{duration_ms} | | | 空格或者\t 分割符# 描述(所有字段均为可选,但必须按照顺序) ``` -------------------------------- ### NeteaseSongModel.list batch retrieval Source: https://feeluown.readthedocs.io/en/latest/research.html Illustrates a batch song retrieval method that NetEase Cloud Music can implement. This is contrasted with Xiami and QQ Music, which cannot provide the same functionality. ```python NeteaseSongModel.list(song_ids): -> list ``` -------------------------------- ### 使用 fuo exec 注入多行 Python 代码调试应用 Source: https://feeluown.readthedocs.io/en/latest/_sources/dev_best_practice.rst.txt 在 tmux 中启动 GUI 并打开 server,然后在另一个 shell 中使用 fuo exec 从 stdin 注入多行 Python 代码,避免 shell 参数中的引号和转义问题。注意启动应用时不要使用 -ns,否则 fuo exec 无法连接。 ```bash # 在 tmux 中启动 GUI,并打开 server uv run fuo -vv ``` ```bash # 在另一个 shell 中注入多行 Python 代码 uv run fuo exec <<'EOF' print(type(app).__name__) print(app.ui) EOF ``` -------------------------------- ### User, artist, album URI with description Source: https://feeluown.readthedocs.io/en/latest/protocol.html Shows the format for user, artist, and album URIs with descriptions. ```text {user uri} # {name} {artist uri} # {name} {album uri} # {album_name}-{artist_name} ``` -------------------------------- ### 安装 OpenAI Python 包 Source: https://feeluown.readthedocs.io/en/latest/_sources/fuorc.rst.txt 使用 pipx 安装 feeluown 时,通过 pipx inject 命令安装 openai 包,以便使用 AI 助手功能。 ```bash pipx inject feeluown openai ``` -------------------------------- ### macOS 安装 FeelUOwn Source: https://feeluown.readthedocs.io/en/latest/_sources/quickstart.rst.txt 通过 Homebrew 安装 FeelUOwn,并生成桌面图标。实测 macOS Monterey(版本12)可以安装,版本 11 可能不能正常安装。 ```sh # macOS Monterey(版本12)实测可以安装,版本 11 可能不能正常安装 # (https://github.com/feeluown/FeelUOwn/issues/421) brew tap feeluown/feeluown brew install feeluown --with-battery # 更多选项见 `brew info feeluown` feeluown genicon # 在桌面会生成一个 FeelUOwn 图标 ``` -------------------------------- ### TaskManager.run_afn_preemptive(afn, *args, name='') Source: https://feeluown.readthedocs.io/en/latest/_modules/feeluown/task.html Runs an async function as a preemptive task. If no name is provided, it generates one from the function. Returns the bound asyncio task. ```APIDOC ## TaskManager.run_afn_preemptive(afn, *args, name='') ### Description Runs an async function as a preemptive task. If no name is provided, it generates one from the function. ### Method Method ### Signature `run_afn_preemptive(self, afn, *args, name='')` ### Parameters - **afn** (async function) - Required - The async function to run. - **args** - Optional - Arguments to pass to the function. - **name** (str) - Optional - The task name. If empty, it is generated from the function. ### Returns - **asyncio.Task** - The bound task. ### Example ```python task = task_mgr.run_afn_preemptive(fetch_song, name='fetch-song') ``` ``` -------------------------------- ### Daemon 模式基本使用命令 Source: https://feeluown.readthedocs.io/en/latest/_sources/quickstart.rst.txt 使用 Daemon 模式启动 FeelUOwn,并查看播放器状态、搜索歌曲、播放歌曲。 ```sh feeluown -nw # 使用 Daemon 模式启动 feeluown fuo status # 查看播放器状态 fuo search 周杰伦 # 搜索歌曲 fuo play fuo://netease/songs/470302665 # 播放:(世界が终るまでは…)《灌篮高手》 ``` -------------------------------- ### 网易云音乐批量获取歌曲功能的实现方式 Source: https://feeluown.readthedocs.io/en/latest/_sources/research.rst.txt 展示了网易云音乐批量获取歌曲功能的实现方式,以及针对不支持该功能的平台提出的三种处理方案,最终选择了添加标记字段 allow_get 和 allow_batch 的方案。 ```python NeteaseSongModel.list(song_ids): -> list ``` ```python 1. XiamiSongModel.list(song_ids): -> raise NotSupportedError 2. XiamiSongModel.list -> AttributeError # 好åƒä¸å¤ªä¼˜é›… 3. XiamiSongModel.allow_batch -> 加一个标记字段 ``` -------------------------------- ### Control FeelUOwn via TCP commands Source: https://feeluown.readthedocs.io/en/latest/_sources/features.rst.txt Commands to control FeelUOwn over TCP on localhost port 23333. The first checks player status, the second pauses playback (note: the source shows the same command for both), and the third searches for a song. ```bash echo status | nc localhost 23333 ``` ```bash echo status | nc localhost 23333 ``` ```bash echo "search 周杰伦" | nc localhost 23333 ``` -------------------------------- ### feeluown Task and Exception API Source: https://feeluown.readthedocs.io/en/latest/api.html This code snippet contains the API documentation for the feeluown task module and exception classes. ```APIDOC ## feeluown.task.is_in_loop_thread ### Description Check if current thread has event loop. ### Method Function ### Signature `feeluown.task.is_in_loop_thread()` ### Returns Boolean indicating whether the current thread has an event loop. ``` ```APIDOC ## class feeluown.task.TaskKind ### Description Enumeration of task kinds. ### Members - `preemptive = 'preemptive'` - Preemptive task. - `cooperative = 'cooperative'` - Cooperative task. ``` ```APIDOC ## class feeluown.task.PreemptiveTaskSpec ### Description Preemptive task specification (threadsafe). ### Methods #### bind_coro(coro) -> Task Run the coroutine and bind the task. It will cancel the previous task if exists. **Returns:** `asyncio.Task` #### bind_blocking_io(func, *args) -> Task Run blocking io func in a thread executor, and bind the task. It will cancel the previous task if exists. **Returns:** `asyncio.Task` ``` ```APIDOC ## class feeluown.task.TaskManager ### Description Named task manager. ### Usage ``` async def fetch_song(): pass task_name = 'unique-name' task_spec = task_mgr.get_or_create(task_name, TaskType.preemptive) task = task_spec.bind_coro(fetch_song()) ``` ### Methods #### get_or_create(name, kind=TaskKind.preemptive) Get task spec, it will be created if not exists. **Parameters:** - `name` (str) - Task identifier (name). - `kind` (TaskKind) - The kind of task. ``` ```APIDOC ## exception feeluown.excs.FuoException ### Description Base exception class for feeluown. ``` ```APIDOC ## exception feeluown.excs.LibraryException ### Description Exception related to library operations. ``` ```APIDOC ## exception feeluown.excs.ProviderIOError ### Description Read/write data from/to provider failed. Currently, all providers use requests to send HTTP requests, and many Request exceptions are not caught, so ProviderIOError inherits RequestException. ### Parameters - `message` (str) - Error message. - `provider` (optional) - The provider instance. ``` ```APIDOC ## exception feeluown.excs.CreateReaderFailed ### Description (DEPRECATED) Use ProviderIOError instead. ### Parameters - `message` (str) - Error message. - `provider` (optional) - The provider instance. ``` ```APIDOC ## exception feeluown.excs.ReaderException ### Description (DEPRECATED) Use ProviderIOError instead. ### Parameters - `message` (str) - Error message. - `provider` (optional) - The provider instance. ``` ```APIDOC ## exception feeluown.excs.ReadFailed ### Description (DEPRECATED) Use ProviderIOError instead. ### Parameters - `message` (str) - Error message. - `provider` (optional) - The provider instance. ``` ```APIDOC ## exception feeluown.excs.ResourceNotFound ### Description Exception raised when a resource is not found. ### Parameters - `args` - Additional arguments. - `reason` (Reason) - Reason for not found. - `kwargs` - Additional keyword arguments. ``` ```APIDOC ## class feeluown.excs.Reason ### Description Enumeration of reasons for exceptions. Added in version v4.0. ``` ```APIDOC ## exception feeluown.excs.ProviderAlreadyRegistered ### Description Exception raised when a provider is already registered. ``` ```APIDOC ## exception feeluown.excs.ModelNotFound ### Description Model is not found. For example, a model identifier is invalid. Maybe ResourceNotFound is enough and this exception should be removed. Added in version 3.7.7. ### Parameters - `args` - Additional arguments. - `reason` (Reason) - Reason for not found. - `kwargs` - Additional keyword arguments. ``` ```APIDOC ## exception feeluown.excs.MediaNotFound ### Description Exception raised when media is not found. ### Parameters - `args` - Additional arguments. - `reason` (Reason) - Reason for not found. - `kwargs` - Additional keyword arguments. ``` ```APIDOC ## class feeluown.excs.MediaNotFound.Reason ### Description Enumeration of reasons for media not found. ``` ```APIDOC ## exception feeluown.excs.NoUserLoggedIn ### Description (DEPRECATED) Return None when there is no user logged in. ``` -------------------------------- ### FeelUOwn 协议 exec 命令示例 Source: https://feeluown.readthedocs.io/en/latest/_sources/protocol.rst.txt 展示 exec 命令的两种用法:多行文本和单行文本。多行文本使用 here document 语法,单行文本直接提供参数。 ```text # 让服务端执行代码 exec < asyncio.Task: """run the coroutine and bind the task it will cancel the previous task if exists :return: :class:`asyncio.Task` """ self._before_bind() if is_in_loop_thread(): self._task = aio.create_task(coro) else: self._task = asyncio.run_coroutine_threadsafe(coro, loop=self._mgr.loop) if self._use_default_cb: self._task.add_done_callback(self._cb) return self._task [docs] def bind_blocking_io(self, func, *args) -> asyncio.Task: """run blocking io func in a thread executor, and bind the task it will cancel the previous task if exists :return: :class:`asyncio.Task` """ self._before_bind() self._task = self._mgr.loop.run_in_executor(None, func, *args) if self._use_default_cb: self._task.add_done_callback(self._cb) return self._task def disable_default_cb(self): self._use_default_cb = False def _cb(self, future): try: future.result() except asyncio.CancelledError: logger.warning(f'Task {self.name} is cancelled') except Exception as e: # noqa logger.exception(f'Task {self.name} failed') [docs] class TaskManager: """named task manager Usage:: async def fetch_song(): pass task_name = 'unique-name' task_spec = task_mgr.get_or_create(task_name, TaskType.preemptive) task = task_spec.bind_coro(fetch_song()) """ def __init__(self, *_): # only accessible for task instance self.loop = asyncio.get_running_loop() # store the name:taskspec mapping self._store = {} [docs] def get_or_create(self, name, kind=TaskKind.preemptive): """get task spec, it will be created if not exists :param name: task identifier(name) :param kind: :class:`TaskKind` TODO: client should register first, then get by name """ if name not in self._store: task_spec = self._create(name, kind) else: task_spec = self._store[name] return task_spec def _create(self, name, kind): kind = TaskKind(kind) task_spec = PreemptiveTaskSpec(self, name) self._store[name] = task_spec return task_spec def run_afn_preemptive(self, afn, *args, name=''): if not name: name = get_fn_name(afn) task_spec = self.get_or_create(name) return task_spec.bind_coro(afn(*args)) def run_fn_preemptive(self, fn, *args, name=''): if not name: name = get_fn_name(fn) task_spec = self.get_or_create(name) return task_spec.bind_blocking_io(fn, *args) def get_fn_name(fn): if hasattr(fn, '__self__') and hasattr(fn, '__func__'): this = fn.__self__ return f'{this.__module__}.{this.__class__.__name__}.{fn.__name__}' return f'{fn.__module__}.{fn.__name__}' ``` -------------------------------- ### 音乐库架构图 Source: https://feeluown.readthedocs.io/en/latest/_sources/media_assets_management/index.rst.txt 展示音乐库(Library)与提供方(如 netease、xiami)之间的交互,以及提供方暴露的方法(如 song_get、search)和返回的模型(如 BriefSongModel、SongModel)。 ```text +---------------------------------------------------------------------------+ | +---------+ | | | Library | | | +---------+ +-------------+ | | | | song_get | | | | +-------------------+ | ... | | | |--| provider(netease) |--| aritst_get |-----+ | | | +-------------------+ | search | | +----------------+ | | | | ... | | | BriefSongModel | | | | +-------------+ | | ... | | | | +-------------+ +-----| | | | | | song_get_mv | | | SongModel | | | | +-----------------+ | ... | | | ArtistModel | | | |--| provider(xiami) |--| album_get |-------+ | ... | | | | +-----------------+ | search | +----------------+ | | | | ... | | | | +-------------+ | | |--... | | | +---------------------------------------------------------------------------+ ``` -------------------------------- ### FeelUOwn 协议响应格式 Source: https://feeluown.readthedocs.io/en/latest/_sources/protocol.rst.txt 展示响应格式,包括成功和失败的 ACK 格式。响应体分为头和内容,以 \r\n 为结束。 ```text # 成功 ACK ok {length} #: more,json {body} # 失败 ACK oops {length} {err_type}: {err_msg} # 示例 ACK ok 0 ``` -------------------------------- ### fuo URI format Source: https://feeluown.readthedocs.io/en/latest/protocol.html Shows the general format of a fuo URI, which consists of scheme, provider, type, and identifier. The scheme is always fuo. ```text fuo://{res_provider}/{res_type}/{res_identifier} | | | 资源提供方 id 资源类型 资源id ``` -------------------------------- ### TaskManager.run_fn_preemptive(fn, *args, name='') Source: https://feeluown.readthedocs.io/en/latest/_modules/feeluown/task.html Runs a blocking function as a preemptive task using a thread executor. If no name is provided, it generates one from the function. Returns the bound asyncio task. ```APIDOC ## TaskManager.run_fn_preemptive(fn, *args, name='') ### Description Runs a blocking function as a preemptive task using a thread executor. If no name is provided, it generates one from the function. ### Method Method ### Signature `run_fn_preemptive(self, fn, *args, name='')` ### Parameters - **fn** (callable) - Required - The blocking function to run. - **args** - Optional - Arguments to pass to the function. - **name** (str) - Optional - The task name. If empty, it is generated from the function. ### Returns - **asyncio.Task** - The bound task. ### Example ```python task = task_mgr.run_fn_preemptive(blocking_io_func, arg1, name='blocking-task') ``` ``` -------------------------------- ### feeluown.fuoexec.add_hook Source: https://feeluown.readthedocs.io/en/latest/_sources/fuorc.rst.txt Adds a hook to the fuorc execution context. ```APIDOC ## feeluown.fuoexec.add_hook ### Description Adds a hook to the fuorc execution context. ### Method Function ### Signature `feeluown.fuoexec.add_hook(hook)` ### Parameters - **hook** (callable) - Required - The hook function to add. ### Returns None ```