
# 教程：编写插件

> 版权所有 © 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/)，说不定会给你一些启发。
