mirror of
https://github.com/TriM-Organization/Musicreater.git
synced 2026-04-30 13:15:59 +00:00
优化插件基类、完善插件文档
This commit is contained in:
@@ -498,9 +498,9 @@ class MusicOutputPluginBase(TopInOutPluginBase, ABC):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def dumpbytes(
|
def stream_dump(
|
||||||
self, data: "SingleMusic", config: Optional[PluginConfig]
|
self, data: "SingleMusic", config: Optional[PluginConfig]
|
||||||
) -> BinaryIO:
|
) -> Iterator[bytes]:
|
||||||
"""将完整曲目导出为对应格式的字节流
|
"""将完整曲目导出为对应格式的字节流
|
||||||
|
|
||||||
参数
|
参数
|
||||||
@@ -512,12 +512,11 @@ class MusicOutputPluginBase(TopInOutPluginBase, ABC):
|
|||||||
|
|
||||||
返回
|
返回
|
||||||
====
|
====
|
||||||
BinaryIO
|
Iterator[bytes]
|
||||||
导出后的二进制字节流
|
分块导出的二进制字节串
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def dump(
|
def dump(
|
||||||
self, data: "SingleMusic", file_path: Path, config: Optional[PluginConfig]
|
self, data: "SingleMusic", file_path: Path, config: Optional[PluginConfig]
|
||||||
):
|
):
|
||||||
@@ -533,7 +532,9 @@ class MusicOutputPluginBase(TopInOutPluginBase, ABC):
|
|||||||
插件配置;**可选**
|
插件配置;**可选**
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
with file_path.open("wb") as f:
|
||||||
|
for _bytes in self.stream_dump(data, config):
|
||||||
|
f.write(_bytes)
|
||||||
|
|
||||||
|
|
||||||
class TrackOutputPluginBase(TopInOutPluginBase, ABC):
|
class TrackOutputPluginBase(TopInOutPluginBase, ABC):
|
||||||
@@ -551,9 +552,9 @@ class TrackOutputPluginBase(TopInOutPluginBase, ABC):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def dumpbytes(
|
def stream_dump(
|
||||||
self, data: "SingleTrack", config: Optional[PluginConfig]
|
self, data: "SingleTrack", config: Optional[PluginConfig]
|
||||||
) -> BinaryIO:
|
) -> Iterator[bytes]:
|
||||||
"""将单个音轨导出为对应格式的字节流
|
"""将单个音轨导出为对应格式的字节流
|
||||||
|
|
||||||
参数
|
参数
|
||||||
@@ -565,12 +566,11 @@ class TrackOutputPluginBase(TopInOutPluginBase, ABC):
|
|||||||
|
|
||||||
返回
|
返回
|
||||||
====
|
====
|
||||||
BinaryIO
|
Iterator[bytes]
|
||||||
导出后的二进制字节流
|
分块导出的二进制字节串
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def dump(
|
def dump(
|
||||||
self, data: "SingleTrack", file_path: Path, config: Optional[PluginConfig]
|
self, data: "SingleTrack", file_path: Path, config: Optional[PluginConfig]
|
||||||
):
|
):
|
||||||
@@ -585,7 +585,9 @@ class TrackOutputPluginBase(TopInOutPluginBase, ABC):
|
|||||||
config: Optional[PluginConfig]
|
config: Optional[PluginConfig]
|
||||||
插件配置;**可选**
|
插件配置;**可选**
|
||||||
"""
|
"""
|
||||||
pass
|
with file_path.open("wb") as f:
|
||||||
|
for _bytes in self.stream_dump(data, config):
|
||||||
|
f.write(_bytes)
|
||||||
|
|
||||||
|
|
||||||
class ServicePluginBase(TopPluginBase, ABC):
|
class ServicePluginBase(TopPluginBase, ABC):
|
||||||
@@ -603,15 +605,13 @@ class ServicePluginBase(TopPluginBase, ABC):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def serve(self, config: Optional[PluginConfig], *args) -> None:
|
def serve(self, config: Optional[PluginConfig]) -> None:
|
||||||
"""服务插件的运行逻辑
|
"""服务插件的运行逻辑
|
||||||
|
|
||||||
参数
|
参数
|
||||||
====
|
====
|
||||||
config: Optional[PluginConfig]
|
config: Optional[PluginConfig]
|
||||||
插件配置;**可选**
|
插件配置;**可选**
|
||||||
*args: Any
|
|
||||||
其他运行时参数
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
184
docs/编写插件.md
Normal file
184
docs/编写插件.md
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
|
||||||
|
# 示例插件:导入音符数据
|
||||||
|
|
||||||
|
> 版权所有 © 2026 金羿
|
||||||
|
> Copyright © 2026 Eilles
|
||||||
|
|
||||||
|
睿乐组织 开发交流群 [861684859](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=fxNYIX_zKMgaO8X6K7pP7tHtLB7JRvdX&noverify=0&group_code=861684859)
|
||||||
|
Email [TriM-Organization@hotmail.com](mailto:TriM-Organization@hotmail.com)
|
||||||
|
|
||||||
|
```license
|
||||||
|
本示例模块开放授权,同时,本教程文件已开放至公共领域。
|
||||||
|
请注意:
|
||||||
|
若是对本文件的直接转载(在形式上没有修改、增删、添加注释,或单纯修改排版、翻译、录屏、截图)
|
||||||
|
则该使用者需要在转载所及之处,明确在转载的内容开头标注本文之原始著作权人
|
||||||
|
在当前文件下,该原始著作权人为金羿(Eilles)
|
||||||
|
如果是对本文进行了一定程度上的修改和补充、或者以不同方式演绎本文件(如制成视频教程等)
|
||||||
|
则无需标注原作者,允许该使用者自行署名
|
||||||
|
|
||||||
|
本声明仅限于包含此声明的本文件,本声明与项目内其他文件无关。
|
||||||
|
```
|
||||||
|
|
||||||
|
本教程文档的关联文件是:
|
||||||
|
- 全曲导入、音轨导入插件示例:[exp_importdata_plugin.py](../examples/exp_importdata_plugin.py)
|
||||||
|
- 导出曲目、导出音轨插件示例:[exp_dataexport_plugin.py](../examples/exp_dataexport_plugin.py)
|
||||||
|
|
||||||
|
## 新建文件
|
||||||
|
|
||||||
|
### 基础模块知识
|
||||||
|
|
||||||
|
首先,一个 **音·创 v3** 的插件应当存储于一个 Python 模块之中,也就是插件存在于可以被 import 语句引入的 module 中。
|
||||||
|
|
||||||
|
这就意味着,承载插件的模块本质上可以是多个 Python 的 `.py` 文件组成的,带有 `__init__.py` 的一个文件夹;
|
||||||
|
或者是一个简单的 `.py` 文件。
|
||||||
|
|
||||||
|
我们有这种共识:大家已经知道了模块的相关知识,后面的教程中你已经理解 **音·创 v3** 插件和 Python 模块的区别。
|
||||||
|
|
||||||
|
## 开始动笔
|
||||||
|
|
||||||
|
### 插件配置
|
||||||
|
|
||||||
|
如果插件需要配置项,则需进行此节。
|
||||||
|
|
||||||
|
从 `Musicreater.plugins` 导入 `PluginConfig` 类,并从此继承一个类,且须用 dataclass 装饰器来注册之:这就成为了一个插件的配置类。
|
||||||
|
_对于这个 `dataclass` “数据类”的使用方式,可以阅读 dataclass 的官方文档,或者直接在实例后面打个 `.`,直接看看能干哈子_
|
||||||
|
|
||||||
|
```python
|
||||||
|
from Musicreater.plugins import PluginConfig
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ExampleImportConfig(PluginConfig):
|
||||||
|
example_config_item3: bool
|
||||||
|
example_config_item1: str = "example_config_item"
|
||||||
|
example_config_item2: int = 0
|
||||||
|
```
|
||||||
|
|
||||||
|
## 编写插件
|
||||||
|
|
||||||
|
### 导入所需项目
|
||||||
|
|
||||||
|
首先在代码开头导入插件所需的东西。
|
||||||
|
|
||||||
|
在此之前,我们明确:一个 **音·创 v3** 的插件应当是一个继承自我们已经准备好的插件基类的**类**(缩句:插件是类);
|
||||||
|
在 **音·创 v3** 中,任何对音乐的操作,包括**导入**、**处理**、**导出**,都分为对 **整首曲目** 的操作和对 **单个音轨** 的操作。
|
||||||
|
|
||||||
|
在这里我们首先要对插件的类型进行判别,根据以下表格,可以得出所用功能对应的插件类型:
|
||||||
|
|
||||||
|
| 功能\对象 | 完整曲目 | 单个音轨 |
|
||||||
|
|----------|----------|----------|
|
||||||
|
| 导入数据 | `PluginTypes.FUNCTION_MUSIC_IMPORT` | `PluginTypes.FUNCTION_TRACK_IMPORT` |
|
||||||
|
| 数据处理 | `PluginTypes.FUNCTION_MUSIC_OPERATE` | `PluginTypes.FUNCTION_TRACK_OPERATE` |
|
||||||
|
| 导出数据 | `PluginTypes.FUNCTION_MUSIC_EXPORT` | `PluginTypes.FUNCTION_TRACK_EXPORT` |
|
||||||
|
| 支持库 | `PluginTypes.LIBRARY` | 同左 |
|
||||||
|
| 提供服务 | `PluginTypes.SERVICE` | 同左 |
|
||||||
|
|
||||||
|
也就是说,除了 `PluginTypes.LIBRARY` 和 `PluginTypes.SERVICE` 是不按照处理对象做区分的外,其余的这些都是对数据进行处理的插件、因此是做了处理数据的类型区分的。
|
||||||
|
|
||||||
|
我们对每个不同类型的插件都做了专用的抽象基类和一个装饰器函数。因为插件本身就是类,所以对应类型的插件只需要继承我们提供的抽象基类,并通过装饰器函数注册即可。(具体写法在后面会说哦)
|
||||||
|
|
||||||
|
也就是说,如果我们要写的是一个用来导入音乐的、对整个曲目进行处理的插件,那么就需要导入 `MusicInputPluginBase` 类和 `music_input_plugin` 函数以便后续调用。
|
||||||
|
|
||||||
|
同时,既然要导入内容,那就一并把 `PluginMetaInformation` 类和 `PluginTypes` 类也导入了吧,这是定义插件的信息所需要的。也就是说,这样的话,我们在导入部分就应该这样写:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from Musicreater.plugins import (
|
||||||
|
music_input_plugin,
|
||||||
|
PluginMetaInformation,
|
||||||
|
PluginTypes,
|
||||||
|
MusicInputPluginBase,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 定义信息
|
||||||
|
|
||||||
|
接着我们来定义一个插件的信息并将其注册。
|
||||||
|
|
||||||
|
假设我们想要做一个对**整首曲目**进行**导入操作**的插件(参照前面举的例子),那么就需要继承 `MusicInputPluginBase` 类。
|
||||||
|
|
||||||
|
> 请注意:插件类的类名称不得以 `Base` 结尾,因为咱写的是插件,不是插件基类。
|
||||||
|
|
||||||
|
在插件的类的开头,需要用插件注册装饰函数来对插件类装饰。
|
||||||
|
|
||||||
|
```python
|
||||||
|
@music_input_plugin("example_import_plugin")
|
||||||
|
class xxx:
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
我们这里对应插件类型的注册器是 `music_input_plugin` 函数。
|
||||||
|
在注册器函数后的参数,是这个插件的惟一识别码。不应与其他任何插件混淆。
|
||||||
|
通常,这个惟一识别码可以是这个插件的功能描述或者就是插件名。
|
||||||
|
|
||||||
|
接着编写这个插件,也即是此类。
|
||||||
|
|
||||||
|
每个插件的类必须包含一个用于指定插件元信息的 `metainfo` 属性。
|
||||||
|
如果插件是导入数据或者导出数据的插件,则必须包含一个 `supported_formats` 属性,用以声明插件所支持的数据格式。
|
||||||
|
|
||||||
|
对于插件的元信息,我们规定为一个 `PluginMetaInformation` 实例,这个实例需要的参数如下:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 注册插件
|
||||||
|
@music_input_plugin("something_convert_to_music")
|
||||||
|
# 继承自对应类型的插件基类
|
||||||
|
class ExampleImportPlugin(MusicInputPluginBase):
|
||||||
|
|
||||||
|
# 插件元信息定义
|
||||||
|
metainfo = PluginMetaInformation(
|
||||||
|
name="示例导入插件", # 插件名称
|
||||||
|
author="金羿", # 插件作者
|
||||||
|
description="这是一个示例导入插件", # 插件描述
|
||||||
|
version=(0, 0, 1), # 插件版本
|
||||||
|
type=PluginTypes.FUNCTION_MUSIC_IMPORT, # 插件类型,需要和注册的类型与继承的基类相符合
|
||||||
|
license="The Unlicense", # 插件许可证(可缺省,默认为字符串 `MIT License`)
|
||||||
|
dependencies=("something_convertion_library") # 插件对于其他插件的依赖项(可缺省,默认为空元组)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
对于实现导入导出数据的功能的插件,`supported_formats` 属性应当是一个元组,其中最好以全字母大写的字符串形式列出支持的**文件格式**或者**数据格式**(如果定义的时候没有大写的话,内部会自动处理成大写的,所以插件类的实例后面也会变成大写,这个时候,因为原定义是小写,有可能造成混淆,所以尽量不要写小写)。例如一个处理 `.mp4` 文件格式的插件可以这样写:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@...
|
||||||
|
class ...:
|
||||||
|
...
|
||||||
|
|
||||||
|
supported_formats = ("MP4", "MPEG4", "MPEG-4")
|
||||||
|
```
|
||||||
|
|
||||||
|
至此,你已经完成了插件基本信息的定义。
|
||||||
|
|
||||||
|
### 实现功能
|
||||||
|
|
||||||
|
根据插件的类型不同,每个插件都需要实现至少一个指定的方法。如下表所示:
|
||||||
|
|
||||||
|
| 插件功能 | 必须实现的方法 | 类型描述 | 可选实现的方法| 可选方法类型描述 | 备注 |
|
||||||
|
| ------ | ------------ | - | ----------- | - |----|
|
||||||
|
| 导入数据 | `loadbytes` | `Callable[[BinaryIO, Optional[PluginConfig]], T@插件处理对象类型]` | `load` | `Callable[[Path, Optional[PluginConfig]], T@插件处理对象类型]` | 如果 `load` 方法不单独实现,则会自动在打开文件后将文件 IO 变量传入 `loadbytes` 中并返回之 |
|
||||||
|
| 数据处理 | `process` | `Callable[[T@插件处理对象类型, Optional[PluginConfig]], T@插件处理对象类型]` | 无 | 无 | 根据处理对象是完整曲目(`SingleMusic`)还是单个音轨(`SingleTrack`),返回也是一样的。导入导出数据相关的插件亦皆同此说。 |
|
||||||
|
| 导出数据 | `stream_dump` | `Callable[[T@插件处理对象类型, Optional[PluginConfig]], Iterator[bytes]]` | `dump` | `Callable[[T@插件处理对象类型, Path, Optional[PluginConfig]], None]` | 若未重写 `dump` 方法,基类已提供默认实现:逐块写入 `stream_dump` 的结果 |
|
||||||
|
| 支持库 | 无 | 无 | 无 | 无 | 无 |
|
||||||
|
| 服务 | `serve` | `Callable[[Optional[PluginConfig]], None]` | 无 | 无 | 用于提供后台服务或一次性任务,由运行时调用(暂无设计思路,相关讨论请见[项目待办清单](../TO-DO.md#讨论)) |
|
||||||
|
|
||||||
|
也就是说,举个例子:一个**用于导入**的插件类必须包含一个 `loadbytes` 方法,用于从字节流中导入数据。可选是否单独实现 `load` 方法,如果不单独实现,则在调用时,会直接通过打开文件后传参数给 `loadbytes` 来实现。
|
||||||
|
|
||||||
|
```python
|
||||||
|
@...
|
||||||
|
class ExampleImportPlugin(MusicInputPluginBase):
|
||||||
|
...
|
||||||
|
# 定义 loadbytes 方法,从字节流中导入数据
|
||||||
|
def loadbytes(
|
||||||
|
self, bytes_buffer_in: BinaryIO, config: Optional[ExampleImportConfig]
|
||||||
|
) -> "SingleMusic":
|
||||||
|
... # 这里写功能实现
|
||||||
|
|
||||||
|
# 插件可选地定义 load 方法,从文件导入数据。下面展示的是不定义 load 方法时候的实现方式
|
||||||
|
def load(
|
||||||
|
self, file_path: Path, config: Optional[ExampleImportConfig]
|
||||||
|
) -> "SingleMusic":
|
||||||
|
with file_path.open("rb") as f:
|
||||||
|
return self.loadbytes(f, config)
|
||||||
|
```
|
||||||
|
|
||||||
|
至此,一个插件的编写已经完成。
|
||||||
|
|
||||||
|
同时,如果有不清楚的地方,可以查看我们的[内置插件](../Musicreater/builtin_plugins/),说不定会给你一些启发。
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
# 示例插件:导入音符数据
|
|
||||||
|
|
||||||
> 版权所有 © 2026 金羿
|
|
||||||
> Copyright © 2026 Eilles
|
|
||||||
|
|
||||||
睿乐组织 开发交流群 [861684859](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=fxNYIX_zKMgaO8X6K7pP7tHtLB7JRvdX&noverify=0&group_code=861684859)
|
|
||||||
Email [TriM-Organization@hotmail.com](mailto:TriM-Organization@hotmail.com)
|
|
||||||
|
|
||||||
```license
|
|
||||||
本示例模块开放授权,同时,本教程文件已开放至公共领域。
|
|
||||||
请注意:
|
|
||||||
若是对本文件的直接转载(在形式上没有修改、增删、添加注释,或单纯修改排版、翻译、录屏、截图)
|
|
||||||
则该使用者需要在转载所及之处,明确在转载的内容开头标注本文之原始著作权人
|
|
||||||
在当前文件下,该原始著作权人为金羿(Eilles)
|
|
||||||
如果是对本文进行了一定程度上的修改和补充、或者以不同方式演绎本文件(如制成视频教程等)
|
|
||||||
则无需标注原作者,允许该使用者自行署名
|
|
||||||
|
|
||||||
本声明仅限于包含此声明的本文件,本声明与项目内其他文件无关。
|
|
||||||
```
|
|
||||||
|
|
||||||
本教程文档的关联文件是[exp_importdata_plugin.py](./exp_importdata_plugin.py)
|
|
||||||
|
|
||||||
## 新建文件夹 · 基础模块知识
|
|
||||||
|
|
||||||
|
|
||||||
首先,一个 **音·创 v3** 的插件应当存储于一个 Python 模块之中,也就是插件存在于可以被 import 语句引入的 module 中。
|
|
||||||
|
|
||||||
这就意味着,承载插件的模块本质上可以是多个 Python 的 `.py` 文件组成的,带有 `__init__.py` 的一个文件夹;
|
|
||||||
或者是一个简单的 `.py` 文件。
|
|
||||||
|
|
||||||
我们有这种共识:大家已经知道了模块的相关知识,后面的教程中你已经理解 **音·创 v3** 插件和 Python 模块的区别。
|
|
||||||
|
|
||||||
## 开始编写插件 · 插件基础
|
|
||||||
|
|
||||||
|
|
||||||
首先导入插件所需的类。
|
|
||||||
|
|
||||||
在这里我们是一个用来导入数据的插件。
|
|
||||||
|
|
||||||
所以就需要导入 `MusicInputPluginBase` 类和 `music_input_plugin` 函数。
|
|
||||||
|
|
||||||
同时,`PluginMetaInformation` 类和 `PluginTypes` 类也必须导入,这是插件的元信息所需要的。
|
|
||||||
|
|
||||||
```python
|
|
||||||
from Musicreater.plugins import (
|
|
||||||
music_input_plugin,
|
|
||||||
PluginMetaInformation,
|
|
||||||
PluginTypes,
|
|
||||||
MusicInputPluginBase,
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
如果插件需要配置,那么请再导入 `PluginConfig` 类,并从此继承一个类,且须用 dataclass 装饰器来注册之。
|
|
||||||
_对于这个类的使用方式,可以阅读 dataclass 的官方文档_
|
|
||||||
|
|
||||||
```python
|
|
||||||
from Musicreater.plugins import PluginConfig
|
|
||||||
from dataclasses import dataclass
|
|
||||||
@dataclass
|
|
||||||
class ExampleImportConfig(PluginConfig):
|
|
||||||
example_config_item3: bool
|
|
||||||
example_config_item1: str = "example_config_item"
|
|
||||||
example_config_item2: int = 0
|
|
||||||
```
|
|
||||||
|
|
||||||
## 编写插件 · 开始
|
|
||||||
|
|
||||||
接着我们来制作一个插件。
|
|
||||||
|
|
||||||
首先,一个 **音·创 v3** 的插件应当是一个继承自我们已经准备好的插件基类的**类**(缩句:插件是类);
|
|
||||||
在 **音·创 v3** 中,任何对音乐的操作,包括导入、导出、处理,都分为对 **整首曲目** 的操作和对 **单个音轨** 的操作。
|
|
||||||
|
|
||||||
我们的样例是一个对**整首曲目**进行**导入操作**的插件,因此需要继承 `MusicInputPluginBase` 类。
|
|
||||||
插件类的类名称不得以 `Base` 结尾,因为咱写的是插件,不是插件基类。
|
|
||||||
|
|
||||||
在插件的类的开头,需要用插件注册装饰函数来对插件类装饰。
|
|
||||||
```python
|
|
||||||
@music_input_plugin("example_import_plugin")
|
|
||||||
class xxx:
|
|
||||||
...
|
|
||||||
```
|
|
||||||
我们这里对应插件类型的注册器是 `music_input_plugin` 函数。
|
|
||||||
在注册器函数后的参数,是这个插件的惟一识别码。不应与其他插件混淆。
|
|
||||||
通常可以是这个插件的功能描述、或者就是插件名。
|
|
||||||
|
|
||||||
接着编写这个插件,也即是此类。
|
|
||||||
每个插件的类必须包含一个用于指定插件元信息的 `metainfo` 属性。
|
|
||||||
如果插件是导入数据或者导出数据的插件,则必须包含一个 `supported_formats` 属性,用以声明插件所支持的数据格式。
|
|
||||||
|
|
||||||
用于导入的插件类必须包含一个 `loadbytes` 方法,用于从字节流中导入数据。可选是否单独实现 `load` 方法,如果不单独实现,则在调用时,会直接通过打开文件后使用 `loadbytes` 的方式实现。
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 注册插件
|
|
||||||
@music_input_plugin("something_convert_to_music")
|
|
||||||
# 继承自对应类型的插件基类
|
|
||||||
class ExampleImportPlugin(MusicInputPluginBase):
|
|
||||||
|
|
||||||
# 插件元信息定义
|
|
||||||
metainfo = PluginMetaInformation(
|
|
||||||
name="示例导入插件", # 插件名称
|
|
||||||
author="金羿", # 插件作者
|
|
||||||
description="这是一个示例导入插件", # 插件描述
|
|
||||||
version=(0, 0, 1), # 插件版本
|
|
||||||
type=PluginTypes.FUNCTION_MUSIC_IMPORT, # 插件类型
|
|
||||||
license="The Unlicense", # 插件许可证
|
|
||||||
dependencies=("something_convertion_library") # 插件对于其他插件的依赖项(此功能尚未实现)
|
|
||||||
)
|
|
||||||
|
|
||||||
# 导入导出插件支持的数据格式,大小写皆可,无需加后缀名前的那个点
|
|
||||||
supported_formats = ("EXP", "example_format")
|
|
||||||
|
|
||||||
# 定义 loadbytes 方法,从字节流中导入数据
|
|
||||||
def loadbytes(
|
|
||||||
self, bytes_buffer_in: BinaryIO, config: Optional[ExampleImportConfig]
|
|
||||||
) -> "SingleMusic":
|
|
||||||
...
|
|
||||||
|
|
||||||
# 插件可选地定义 load 方法,从文件导入数据。下面展示的是不定义 load 方法时候的实现方式
|
|
||||||
def load(
|
|
||||||
self, file_path: Path, config: Optional[ExampleImportConfig]
|
|
||||||
) -> "SingleMusic":
|
|
||||||
with file_path.open("rb") as f:
|
|
||||||
return self.loadbytes(f, config)
|
|
||||||
```
|
|
||||||
|
|
||||||
至此,一个插件的编写已经完成。
|
|
||||||
|
|
||||||
同时,如果有不清楚的地方,可以查看我们的[内置插件](../Musicreater/builtin_plugins/),说不定会给你一些启发。
|
|
||||||
112
examples/exp_dataexport_plugin.py
Normal file
112
examples/exp_dataexport_plugin.py
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
示例插件:导出成其他文件
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
版权所有 © 2026 金羿
|
||||||
|
Copyright © 2026 Eilles
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 睿乐组织 开发交流群 861684859
|
||||||
|
# Email TriM-Organization@hotmail.com
|
||||||
|
|
||||||
|
"""
|
||||||
|
本示例模块开放授权,本文件已开放至公共领域。
|
||||||
|
请注意:
|
||||||
|
若是对本文件的直接转载(在形式上没有修改、增删、添加注释,或单纯修改排版、翻译、录屏、截图)
|
||||||
|
则该使用者需要在转载所及之处,明确在转载的内容开头标注本文之原始著作权人
|
||||||
|
在当前文件下,该原始著作权人为金羿(Eilles)
|
||||||
|
如果是对本文进行了一定程度上的修改和补充、或者以不同方式演绎本文件(如制成视频教程等)
|
||||||
|
则无需标注原作者,允许该使用者自行署名
|
||||||
|
|
||||||
|
本声明仅限于包含此声明的本文件,本声明与项目内其他文件无关。
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import BinaryIO, Optional, Iterator, Generator, Any, Tuple
|
||||||
|
from pathlib import Path
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from Musicreater import SingleMusic, SingleTrack
|
||||||
|
from Musicreater.plugins import (
|
||||||
|
PluginConfig,
|
||||||
|
PluginMetaInformation,
|
||||||
|
PluginTypes,
|
||||||
|
music_output_plugin,
|
||||||
|
MusicOutputPluginBase,
|
||||||
|
track_output_plugin,
|
||||||
|
TrackOutputPluginBase,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ExampleExportConfig(PluginConfig):
|
||||||
|
example_config_item3: bool
|
||||||
|
example_config_item1: str = "example_config_item"
|
||||||
|
example_config_item2: int = 0
|
||||||
|
|
||||||
|
def __iter__(self) -> Generator[Tuple[str, Any], None, None]:
|
||||||
|
for k, v in self.to_dict().items():
|
||||||
|
yield k, v
|
||||||
|
|
||||||
|
|
||||||
|
@music_output_plugin("convert_music_to_something")
|
||||||
|
class ExampleExportMusicPlugin(MusicOutputPluginBase):
|
||||||
|
metainfo = PluginMetaInformation(
|
||||||
|
name="示例导出插件·甲",
|
||||||
|
author="金羿",
|
||||||
|
description="这是一个示例导出插件,演示整首曲目导出到其他文件格式的插件的编写过程",
|
||||||
|
version=(0, 0, 1),
|
||||||
|
type=PluginTypes.FUNCTION_MUSIC_EXPORT,
|
||||||
|
license="The Unlicense",
|
||||||
|
dependencies=("something_convertion_library"),
|
||||||
|
)
|
||||||
|
|
||||||
|
supported_formats = ("EXP", "EXAMPLE_FORMAT")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def something_data_convert(*args) -> bytes:
|
||||||
|
return b"This is something wonderful"
|
||||||
|
|
||||||
|
def stream_dump(
|
||||||
|
self, data: SingleMusic, config: ExampleExportConfig | None
|
||||||
|
) -> Iterator[bytes]:
|
||||||
|
if not config:
|
||||||
|
config = ExampleExportConfig(True)
|
||||||
|
for cfg in config:
|
||||||
|
yield self.something_data_convert(cfg)
|
||||||
|
|
||||||
|
# 插件可选地定义 dump 方法,从文件导入数据。下面展示的是不定义 load 方法时候的实现方式
|
||||||
|
def dump(
|
||||||
|
self, data: SingleMusic, file_path: Path, config: ExampleExportConfig | None
|
||||||
|
):
|
||||||
|
|
||||||
|
with file_path.open("wb") as f:
|
||||||
|
for _bytes in self.stream_dump(data, config):
|
||||||
|
f.write(_bytes)
|
||||||
|
|
||||||
|
|
||||||
|
@track_output_plugin("convert_track_to_something")
|
||||||
|
class ExampleImportTrackPlugin(TrackOutputPluginBase):
|
||||||
|
metainfo = PluginMetaInformation(
|
||||||
|
name="示例导出插件·乙",
|
||||||
|
author="金羿",
|
||||||
|
description="这是一个示例导出插件,演示从音轨导出的其他格式的插件的编写过程",
|
||||||
|
version=(0, 0, 1),
|
||||||
|
type=PluginTypes.FUNCTION_TRACK_EXPORT,
|
||||||
|
license="The Unlicense",
|
||||||
|
# 可以缺省依赖,如果不需要的话
|
||||||
|
)
|
||||||
|
|
||||||
|
supported_formats = ("EXP", "example_format")
|
||||||
|
|
||||||
|
def stream_dump(
|
||||||
|
self, data: SingleTrack, config: ExampleExportConfig | None
|
||||||
|
) -> Iterator[bytes]:
|
||||||
|
if not config:
|
||||||
|
config = ExampleExportConfig(True)
|
||||||
|
for cfg in config:
|
||||||
|
yield ExampleExportMusicPlugin.something_data_convert(cfg)
|
||||||
|
|
||||||
|
# 可以缺省 dump 方法,会直接用上面展示过的方法输出
|
||||||
@@ -28,13 +28,15 @@ from typing import BinaryIO, Optional
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from Musicreater import SingleMusic
|
from Musicreater import SingleMusic, SingleTrack
|
||||||
from Musicreater.plugins import (
|
from Musicreater.plugins import (
|
||||||
music_input_plugin,
|
|
||||||
PluginConfig,
|
PluginConfig,
|
||||||
PluginMetaInformation,
|
PluginMetaInformation,
|
||||||
PluginTypes,
|
PluginTypes,
|
||||||
|
music_input_plugin,
|
||||||
MusicInputPluginBase,
|
MusicInputPluginBase,
|
||||||
|
track_input_plugin,
|
||||||
|
TrackInputPluginBase,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -46,18 +48,18 @@ class ExampleImportConfig(PluginConfig):
|
|||||||
|
|
||||||
|
|
||||||
@music_input_plugin("something_convert_to_music")
|
@music_input_plugin("something_convert_to_music")
|
||||||
class ExampleImportPlugin(MusicInputPluginBase):
|
class ExampleImportMusicPlugin(MusicInputPluginBase):
|
||||||
metainfo = PluginMetaInformation(
|
metainfo = PluginMetaInformation(
|
||||||
name="示例导入插件",
|
name="示例导入插件·甲",
|
||||||
author="金羿",
|
author="金羿",
|
||||||
description="这是一个示例导入插件",
|
description="这是一个示例导入插件,演示导入到全曲的插件编写过程",
|
||||||
version=(0, 0, 1),
|
version=(0, 0, 1),
|
||||||
type=PluginTypes.FUNCTION_MUSIC_IMPORT,
|
type=PluginTypes.FUNCTION_MUSIC_IMPORT,
|
||||||
license="The Unlicense",
|
license="The Unlicense",
|
||||||
dependencies=("something_convertion_library"),
|
dependencies=("something_convertion_library"),
|
||||||
)
|
)
|
||||||
|
|
||||||
supported_formats = ("EXP", "example_format")
|
supported_formats = ("EXP", "EXAMPLE_FORMAT")
|
||||||
|
|
||||||
def loadbytes(
|
def loadbytes(
|
||||||
self, bytes_buffer_in: BinaryIO, config: Optional[ExampleImportConfig]
|
self, bytes_buffer_in: BinaryIO, config: Optional[ExampleImportConfig]
|
||||||
@@ -70,3 +72,25 @@ class ExampleImportPlugin(MusicInputPluginBase):
|
|||||||
) -> "SingleMusic":
|
) -> "SingleMusic":
|
||||||
with file_path.open("rb") as f:
|
with file_path.open("rb") as f:
|
||||||
return self.loadbytes(f, config)
|
return self.loadbytes(f, config)
|
||||||
|
|
||||||
|
|
||||||
|
@track_input_plugin("something_convert_to_track")
|
||||||
|
class ExampleImportTrackPlugin(TrackInputPluginBase):
|
||||||
|
metainfo = PluginMetaInformation(
|
||||||
|
name="示例导入插件·乙",
|
||||||
|
author="金羿",
|
||||||
|
description="这是一个示例导入插件,演示导入到音轨的插件编写过程",
|
||||||
|
version=(0, 0, 1),
|
||||||
|
type=PluginTypes.FUNCTION_TRACK_IMPORT,
|
||||||
|
license="The Unlicense",
|
||||||
|
# 可以缺省依赖,如果不需要的话
|
||||||
|
)
|
||||||
|
|
||||||
|
supported_formats = ("EXP", "example_format")
|
||||||
|
|
||||||
|
def loadbytes(
|
||||||
|
self, bytes_buffer_in: BinaryIO, config: Optional[ExampleImportConfig]
|
||||||
|
) -> "SingleTrack":
|
||||||
|
return SingleTrack()
|
||||||
|
|
||||||
|
# 可以缺省 load 方法,会直接用上面展示过的方法读取数据
|
||||||
|
|||||||
10
test_read.py
10
test_read.py
@@ -14,9 +14,9 @@ print(msct := MusiCreater.import_music(Path("./resources/测试片段.mid")))
|
|||||||
|
|
||||||
print(msct.music)
|
print(msct.music)
|
||||||
|
|
||||||
|
# 如果要直接访问插件里面的函数:
|
||||||
# 为了确保类型安全,以下方法不建议使用,因为这本质上是越过了 MusiCreater 类而直接执行插件的函数
|
# 为了确保类型安全,以下方法不建议使用,因为这本质上是越过了 MusiCreater 类而直接执行插件的函数
|
||||||
print(t := msct.midi_2_music_plugin.load(Path("./resources/测试片段.mid"), None))
|
print(t := msct.midi_2_music_plugin.load(Path("./resources/测试片段.mid"), None)) # type: ignore
|
||||||
# 我们建议用这种方式来代替
|
# 我们建议用这种方式来代替
|
||||||
t = _global_plugin_registry._music_input_plugins["midi_2_music_plugin"].load(
|
t = _global_plugin_registry._music_input_plugins["midi_2_music_plugin"].load(
|
||||||
Path("./resources/测试片段.mid"),
|
Path("./resources/测试片段.mid"),
|
||||||
@@ -28,8 +28,12 @@ t = _global_plugin_registry._music_input_plugins["midi_2_music_plugin"].load(
|
|||||||
from Musicreater.plugins import MusicInputPluginBase
|
from Musicreater.plugins import MusicInputPluginBase
|
||||||
|
|
||||||
if isinstance((p := msct.midi_2_music_plugin), MusicInputPluginBase):
|
if isinstance((p := msct.midi_2_music_plugin), MusicInputPluginBase):
|
||||||
# 但是,我们不建议用这样的方式读取操作配置类,尽管这也是可以的
|
|
||||||
t = p.load(Path("./resources/测试片段.mid"), None)
|
t = p.load(Path("./resources/测试片段.mid"), None)
|
||||||
|
|
||||||
|
# 但是说实话,既然已经在 MusiCreater 类中提供了
|
||||||
|
# import_music、export_music、perform_operation_on_music 等方法,
|
||||||
|
# 那么我们不建议使用上面展示的调取插件的方式来执行插件内的函数。
|
||||||
|
msct.perform_operation_on_music
|
||||||
|
|
||||||
print(_global_plugin_registry)
|
print(_global_plugin_registry)
|
||||||
print(msct._plugin_cache)
|
print(msct._plugin_cache)
|
||||||
|
|||||||
Reference in New Issue
Block a user