mirror of
https://github.com/TriM-Organization/Musicreater.git
synced 2026-04-17 06:08:00 +00:00
完成插件系统的基本设计,接着需要优化一些内容,标记了 TODO
This commit is contained in:
@@ -93,24 +93,76 @@ from .data import SingleMusic, SingleTrack
|
||||
# ]
|
||||
|
||||
|
||||
# ========================
|
||||
# 枚举类
|
||||
# ========================
|
||||
|
||||
class PluginTypes(str, Enum):
|
||||
"""插件类型枚举"""
|
||||
|
||||
FUNCTION_MUSIC_IMPORT = "import_music_data"
|
||||
FUNCTION_TRACK_IMPORT = "import_track_data"
|
||||
FUNCTION_MUSIC_OPERATE = "music_data_operating"
|
||||
FUNCTION_TRACK_OPERATE = "track_data_operating"
|
||||
FUNCTION_MUSIC_EXPORT = "export_music_data"
|
||||
FUNCTION_TRACK_EXPORT = "export_track_data"
|
||||
SERVICE = "service"
|
||||
LIBRARY = "library"
|
||||
|
||||
|
||||
|
||||
# ========================
|
||||
# 数据类
|
||||
# ========================
|
||||
|
||||
|
||||
@dataclass
|
||||
class PluginConfig(ABC):
|
||||
"""插件配置基类"""
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""字典化配置文件"""
|
||||
"""将配置内容转换为字典
|
||||
|
||||
返回
|
||||
====
|
||||
Dict[str, Any]
|
||||
配置项的字典表示,不包含以下划线开头的私有属性
|
||||
"""
|
||||
return {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "PluginConfig":
|
||||
"""从字典创建配置实例"""
|
||||
"""从字典创建配置实例
|
||||
|
||||
参数
|
||||
====
|
||||
data: Dict[str, Any]
|
||||
包含配置字段的字典
|
||||
|
||||
返回
|
||||
====
|
||||
PluginConfig
|
||||
配置类的实例
|
||||
"""
|
||||
|
||||
# 只保留类中定义的字段
|
||||
field_names = {f.name for f in cls.__dataclass_fields__.values()}
|
||||
filtered_data = {k: v for k, v in data.items() if k in field_names}
|
||||
return cls(**filtered_data)
|
||||
|
||||
def save_to_file(self, file_path: Path) -> None:
|
||||
"""保存配置到文件"""
|
||||
"""保存配置到 TOML 文件
|
||||
|
||||
参数
|
||||
====
|
||||
file_path: Path
|
||||
目标文件路径;必须以 .toml 为后缀
|
||||
|
||||
异常
|
||||
====
|
||||
PluginConfigDumpError
|
||||
当文件后缀不是 .toml 或写入失败时抛出
|
||||
"""
|
||||
if file_path.suffix.upper() == ".TOML":
|
||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
else:
|
||||
@@ -126,7 +178,23 @@ class PluginConfig(ABC):
|
||||
|
||||
@classmethod
|
||||
def load_from_file(cls, file_path: Path) -> "PluginConfig":
|
||||
"""从文件加载配置"""
|
||||
"""从 TOML 文件加载配置
|
||||
|
||||
参数
|
||||
====
|
||||
file_path: Path
|
||||
源文件路径
|
||||
|
||||
返回
|
||||
====
|
||||
PluginConfig
|
||||
加载后的配置实例
|
||||
|
||||
异常
|
||||
====
|
||||
PluginConfigLoadError
|
||||
当读取或解析失败时抛出
|
||||
"""
|
||||
try:
|
||||
with file_path.open("rb") as f:
|
||||
return cls.from_dict(tomllib.load(f))
|
||||
@@ -134,19 +202,6 @@ class PluginConfig(ABC):
|
||||
raise PluginConfigLoadError(e)
|
||||
|
||||
|
||||
class PluginType(str, Enum):
|
||||
"""插件类型枚举"""
|
||||
|
||||
FUNCTION_MUSIC_IMPORT = "import_music_data"
|
||||
FUNCTION_TRACK_IMPORT = "import_track_data"
|
||||
FUNCTION_MUSIC_OPERATE = "music_data_operating"
|
||||
FUNCTION_TRACK_OPERATE = "track_data_operating"
|
||||
FUNCTION_MUSIC_EXPORT = "export_music_data"
|
||||
FUNCTION_TRACK_EXPORT = "export_track_data"
|
||||
SERVICE = "service"
|
||||
LIBRARY = "library"
|
||||
|
||||
|
||||
@dataclass
|
||||
class PluginMetaInformation(ABC):
|
||||
"""插件元信息"""
|
||||
@@ -159,7 +214,7 @@ class PluginMetaInformation(ABC):
|
||||
"""插件简介"""
|
||||
version: Tuple[int, ...]
|
||||
"""插件版本号"""
|
||||
type: PluginType
|
||||
type: PluginTypes
|
||||
"""插件类型"""
|
||||
license: str = "MIT License"
|
||||
"""插件发布时采用的许可协议"""
|
||||
@@ -167,6 +222,11 @@ class PluginMetaInformation(ABC):
|
||||
"""插件是否对其他插件存在依赖"""
|
||||
|
||||
|
||||
# ========================
|
||||
# 抽象基类
|
||||
# ========================
|
||||
|
||||
|
||||
class TopPluginBase(ABC):
|
||||
"""所有插件的抽象基类"""
|
||||
|
||||
@@ -195,7 +255,7 @@ class TopInOutPluginBase(TopPluginBase, ABC):
|
||||
"""导入导出用抽象基类"""
|
||||
|
||||
supported_formats: Tuple[str, ...] = tuple()
|
||||
"""支持的格式"""
|
||||
"""支持的格式(定义后会自动转大写)"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
@@ -214,11 +274,33 @@ class TopInOutPluginBase(TopPluginBase, ABC):
|
||||
)
|
||||
|
||||
def can_handle_file(self, file_path: Path) -> bool:
|
||||
"""判断是否可处理某个文件"""
|
||||
"""判断是否可处理某个文件
|
||||
|
||||
参数
|
||||
====
|
||||
file_path: Path
|
||||
待检测的文件路径
|
||||
|
||||
返回
|
||||
====
|
||||
bool
|
||||
若文件后缀已在本类中定义,则返回 True
|
||||
"""
|
||||
return file_path.suffix.upper().endswith(self.supported_formats)
|
||||
|
||||
def can_handle_format(self, format_name: str) -> bool:
|
||||
"""判断是否可处理某个格式"""
|
||||
"""判断是否可处理某个格式
|
||||
|
||||
参数
|
||||
====
|
||||
format_name: str
|
||||
格式名称(如 'MIDI', 'WAV')
|
||||
|
||||
返回
|
||||
====
|
||||
bool
|
||||
若格式名本类中已经定义,则返回 True
|
||||
"""
|
||||
return format_name.upper().endswith(self.supported_formats)
|
||||
|
||||
|
||||
@@ -228,7 +310,7 @@ class MusicInputPluginBase(TopInOutPluginBase, ABC):
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginType.FUNCTION_MUSIC_IMPORT:
|
||||
if cls.metainfo.type != PluginTypes.FUNCTION_MUSIC_IMPORT:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`MusicInputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_MUSIC_IMPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
@@ -240,11 +322,38 @@ class MusicInputPluginBase(TopInOutPluginBase, ABC):
|
||||
def loadbytes(
|
||||
self, bytes_buffer_in: BinaryIO, config: Optional[PluginConfig]
|
||||
) -> "SingleMusic":
|
||||
"""从字节流加载数据到完整曲目"""
|
||||
"""从字节流加载数据到完整曲目
|
||||
|
||||
参数
|
||||
====
|
||||
bytes_buffer_in: BinaryIO
|
||||
输入的二进制字节流
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
|
||||
返回
|
||||
====
|
||||
SingleMusic
|
||||
解析得到的完整曲目对象
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
def load(self, file_path: Path, config: Optional[PluginConfig]) -> "SingleMusic":
|
||||
"""从文件加载数据到完整曲目"""
|
||||
"""从文件加载数据到完整曲目
|
||||
|
||||
参数
|
||||
====
|
||||
file_path: Path
|
||||
输入文件路径
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
|
||||
返回
|
||||
====
|
||||
SingleMusic
|
||||
解析得到的完整曲目对象
|
||||
"""
|
||||
with file_path.open("rb") as f:
|
||||
return self.loadbytes(f, config)
|
||||
|
||||
@@ -255,7 +364,7 @@ class TrackInputPluginBase(TopInOutPluginBase, ABC):
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginType.FUNCTION_TRACK_IMPORT:
|
||||
if cls.metainfo.type != PluginTypes.FUNCTION_TRACK_IMPORT:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`TrackInputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_TRACK_IMPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
@@ -267,11 +376,37 @@ class TrackInputPluginBase(TopInOutPluginBase, ABC):
|
||||
def loadbytes(
|
||||
self, bytes_buffer_in: BinaryIO, config: Optional[PluginConfig]
|
||||
) -> "SingleTrack":
|
||||
"""从字节流加载音符数据到单个音轨"""
|
||||
"""从字节流加载音符数据到单个音轨
|
||||
|
||||
参数
|
||||
====
|
||||
bytes_buffer_in: BinaryIO
|
||||
输入的二进制字节流
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
|
||||
返回
|
||||
====
|
||||
SingleTrack
|
||||
解析得到的单个音轨对象
|
||||
"""
|
||||
pass
|
||||
|
||||
def load(self, file_path: Path, config: Optional[PluginConfig]) -> "SingleTrack":
|
||||
"""从文件加载音符数据到单个音轨"""
|
||||
"""从文件加载音符数据到单个音轨
|
||||
|
||||
参数
|
||||
====
|
||||
file_path: Path
|
||||
输入文件路径
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
|
||||
返回
|
||||
====
|
||||
SingleTrack
|
||||
解析得到的单个音轨对象
|
||||
"""
|
||||
with file_path.open("rb") as f:
|
||||
return self.loadbytes(f, config)
|
||||
|
||||
@@ -282,7 +417,7 @@ class MusicOperatePluginBase(TopPluginBase, ABC):
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginType.FUNCTION_MUSIC_OPERATE:
|
||||
if cls.metainfo.type != PluginTypes.FUNCTION_MUSIC_OPERATE:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`MusicOperatePlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_MUSIC_OPERATE`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
@@ -294,7 +429,20 @@ class MusicOperatePluginBase(TopPluginBase, ABC):
|
||||
def process(
|
||||
self, data: "SingleMusic", config: Optional[PluginConfig]
|
||||
) -> "SingleMusic":
|
||||
"""处理完整曲目的数据"""
|
||||
"""处理完整曲目的数据
|
||||
|
||||
参数
|
||||
====
|
||||
data: SingleMusic
|
||||
待处理的完整曲目
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
|
||||
返回
|
||||
====
|
||||
SingleMusic
|
||||
处理后的完整曲目
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@@ -304,7 +452,7 @@ class TrackOperatePluginBase(TopPluginBase, ABC):
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginType.FUNCTION_TRACK_OPERATE:
|
||||
if cls.metainfo.type != PluginTypes.FUNCTION_TRACK_OPERATE:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`TrackOperatePlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_TRACK_OPERATE`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
@@ -316,7 +464,20 @@ class TrackOperatePluginBase(TopPluginBase, ABC):
|
||||
def process(
|
||||
self, data: "SingleTrack", config: Optional[PluginConfig]
|
||||
) -> "SingleTrack":
|
||||
"""处理单个音轨的音符数据"""
|
||||
"""处理单个音轨的音符数据
|
||||
|
||||
参数
|
||||
====
|
||||
data: SingleTrack
|
||||
待处理的单个音轨
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
|
||||
返回
|
||||
====
|
||||
SingleTrack
|
||||
处理后的单个音轨
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@@ -326,7 +487,7 @@ class MusicOutputPluginBase(TopInOutPluginBase, ABC):
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginType.FUNCTION_MUSIC_EXPORT:
|
||||
if cls.metainfo.type != PluginTypes.FUNCTION_MUSIC_EXPORT:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`MusicOutputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_MUSIC_EXPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
@@ -338,14 +499,38 @@ class MusicOutputPluginBase(TopInOutPluginBase, ABC):
|
||||
def dumpbytes(
|
||||
self, data: "SingleMusic", config: Optional[PluginConfig]
|
||||
) -> BinaryIO:
|
||||
"""将完整曲目导出为对应格式的字节流"""
|
||||
"""将完整曲目导出为对应格式的字节流
|
||||
|
||||
参数
|
||||
====
|
||||
data: SingleMusic
|
||||
待导出的完整曲目
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
|
||||
返回
|
||||
====
|
||||
BinaryIO
|
||||
导出后的二进制字节流
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def dump(
|
||||
self, data: "SingleMusic", file_path: Path, config: Optional[PluginConfig]
|
||||
):
|
||||
"""将完整曲目导出为对应格式的文件"""
|
||||
"""将完整曲目导出为对应格式的文件
|
||||
|
||||
参数
|
||||
====
|
||||
data: SingleMusic
|
||||
待导出的完整曲目
|
||||
file_path: Path
|
||||
输出文件路径
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@@ -355,7 +540,7 @@ class TrackOutputPluginBase(TopInOutPluginBase, ABC):
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginType.FUNCTION_TRACK_EXPORT:
|
||||
if cls.metainfo.type != PluginTypes.FUNCTION_TRACK_EXPORT:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`TrackOutputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_TRACK_EXPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
@@ -367,14 +552,37 @@ class TrackOutputPluginBase(TopInOutPluginBase, ABC):
|
||||
def dumpbytes(
|
||||
self, data: "SingleTrack", config: Optional[PluginConfig]
|
||||
) -> BinaryIO:
|
||||
"""将单个音轨导出为对应格式的字节流"""
|
||||
"""将单个音轨导出为对应格式的字节流
|
||||
|
||||
参数
|
||||
====
|
||||
data: SingleTrack
|
||||
待导出的单个音轨
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
|
||||
返回
|
||||
====
|
||||
BinaryIO
|
||||
导出后的二进制字节流
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def dump(
|
||||
self, data: "SingleTrack", file_path: Path, config: Optional[PluginConfig]
|
||||
):
|
||||
"""将单个音轨导出为对应格式的文件"""
|
||||
"""将单个音轨导出为对应格式的文件
|
||||
|
||||
参数
|
||||
====
|
||||
data: SingleTrack
|
||||
待导出的单个音轨
|
||||
file_path: Path
|
||||
输出文件路径
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@@ -384,7 +592,7 @@ class ServicePluginBase(TopPluginBase, ABC):
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginType.SERVICE:
|
||||
if cls.metainfo.type != PluginTypes.SERVICE:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`ServicePlugin`继承的,该类的子类应当为一个`PluginType.SERVICE`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
@@ -394,7 +602,15 @@ class ServicePluginBase(TopPluginBase, ABC):
|
||||
|
||||
@abstractmethod
|
||||
def serve(self, config: Optional[PluginConfig], *args) -> None:
|
||||
"""服务插件的运行逻辑"""
|
||||
"""服务插件的运行逻辑
|
||||
|
||||
参数
|
||||
====
|
||||
config: Optional[PluginConfig]
|
||||
插件配置;**可选**
|
||||
*args: Any
|
||||
其他运行时参数
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@@ -404,7 +620,7 @@ class LibraryPluginBase(TopPluginBase, ABC):
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginType.LIBRARY:
|
||||
if cls.metainfo.type != PluginTypes.LIBRARY:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`LibraryPlugin`继承的,该类的子类应当为一个`PluginType.LIBRARY`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
|
||||
@@ -27,7 +27,7 @@ from Musicreater.plugins import (
|
||||
music_input_plugin,
|
||||
PluginConfig,
|
||||
PluginMetaInformation,
|
||||
PluginType,
|
||||
PluginTypes,
|
||||
MusicInputPluginBase,
|
||||
)
|
||||
|
||||
@@ -37,11 +37,11 @@ class MidiImport2MusicPlugin(MusicInputPluginBase):
|
||||
"""Midi 音乐数据导入插件"""
|
||||
|
||||
metainfo = PluginMetaInformation(
|
||||
name="midi_2_music_plugin",
|
||||
name="Midi 导入插件",
|
||||
author="金羿、玉衡Alioth",
|
||||
description="从 Midi 文件导入音乐数据",
|
||||
version=(0, 0, 1),
|
||||
type=PluginType.FUNCTION_MUSIC_IMPORT,
|
||||
type=PluginTypes.FUNCTION_MUSIC_IMPORT,
|
||||
license="Same as Musicreater",
|
||||
)
|
||||
|
||||
|
||||
@@ -476,14 +476,7 @@ class SingleTrack(List[SingleNote]):
|
||||
|
||||
self.extra_info = extra_information if extra_information else {}
|
||||
|
||||
self.argument_curves = {
|
||||
CurvableParam.PITCH: None,
|
||||
CurvableParam.VELOCITY: None,
|
||||
CurvableParam.VOLUME: None,
|
||||
CurvableParam.DISTANCE: None,
|
||||
CurvableParam.LR_PANNING: None,
|
||||
CurvableParam.UD_PANNING: None,
|
||||
}
|
||||
self.argument_curves = {item: None for item in CurvableParam}
|
||||
|
||||
super().__init__(*args)
|
||||
super().sort()
|
||||
@@ -572,9 +565,9 @@ class SingleTrack(List[SingleNote]):
|
||||
is_percussive_note=self.is_percussive,
|
||||
sound_position=self.sound_position,
|
||||
**{
|
||||
item.value: self.argument_curves[item].value_at(_note.start_time) # type: ignore
|
||||
item.value: argcrv.value_at(_note.start_time)
|
||||
for item in CurvableParam
|
||||
if self.argument_curves[item]
|
||||
if (argcrv := self.argument_curves[item])
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -225,6 +225,23 @@ class PluginLoadError(MusicreaterOuterlyError):
|
||||
super().__init__("插件加载错误 - ", *args)
|
||||
|
||||
|
||||
class PluginNotFoundError(PluginLoadError):
|
||||
"""插件未找到"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""插件未找到"""
|
||||
super().__init__("插件未找到:", *args)
|
||||
|
||||
|
||||
class PluginRegisteredError(PluginLoadError):
|
||||
"""插件重复注册"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""插件已被注册注册"""
|
||||
super().__init__("插件重复注册:", *args)
|
||||
|
||||
|
||||
|
||||
class PluginConfigRelatedError(MusicreaterOuterlyError):
|
||||
"""插件配置相关错误"""
|
||||
|
||||
|
||||
@@ -47,19 +47,25 @@ import re
|
||||
|
||||
|
||||
from difflib import get_close_matches
|
||||
from typing import Dict, Generator, List, Optional, Tuple, Union
|
||||
from typing import Dict, Generator, List, Optional, Tuple, Union, Mapping, Callable
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
from .data import SingleMusic, SingleTrack
|
||||
from .exceptions import FileFormatNotSupportedError, PluginNotSpecifiedError
|
||||
from .exceptions import (
|
||||
FileFormatNotSupportedError,
|
||||
PluginNotSpecifiedError,
|
||||
PluginNotFoundError,
|
||||
)
|
||||
from ._plugin_abc import TopPluginBase
|
||||
from .plugins import (
|
||||
_global_plugin_registry,
|
||||
PluginRegistry,
|
||||
PluginConfig,
|
||||
PluginType,
|
||||
PluginTypes,
|
||||
load_plugin_module,
|
||||
T_IOPlugin,
|
||||
T_Plugin,
|
||||
)
|
||||
|
||||
|
||||
@@ -72,7 +78,7 @@ class MusiCreater:
|
||||
__plugin_registry: PluginRegistry
|
||||
"""插件注册表实例"""
|
||||
_plugin_cache: Dict[str, TopPluginBase]
|
||||
"""插件缓存字典,插件名为键、插件实例为值"""
|
||||
"""插件缓存字典,插件id为键、插件实例为值"""
|
||||
music: SingleMusic
|
||||
"""当前曲目实例"""
|
||||
|
||||
@@ -86,149 +92,119 @@ class MusiCreater:
|
||||
|
||||
self.music = whole_music
|
||||
|
||||
@staticmethod
|
||||
def _get_plugin_within_iousage(
|
||||
get_func: Callable[[Union[Path, str]], Generator[T_IOPlugin, None, None]],
|
||||
fpath: Path,
|
||||
plg_regdict: Dict[str, T_IOPlugin],
|
||||
plg_id: Optional[str],
|
||||
) -> T_IOPlugin:
|
||||
|
||||
__plugin: Optional[T_IOPlugin] = None
|
||||
if plg_id:
|
||||
__plugin = plg_regdict.get(plg_id)
|
||||
|
||||
else:
|
||||
for __plg in get_func(fpath):
|
||||
if __plugin:
|
||||
raise PluginNotSpecifiedError(
|
||||
"文件类型`{}`可被多个插件处理,请在调用函数的参数中指定插件名称".format(
|
||||
fpath.suffix.upper()
|
||||
)
|
||||
)
|
||||
__plugin = __plg
|
||||
if __plugin:
|
||||
return __plugin
|
||||
else:
|
||||
raise FileFormatNotSupportedError(
|
||||
"无法找到处理`{}`类型文件的插件".format(fpath.suffix.upper())
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def import_music(
|
||||
cls,
|
||||
file_path: Path,
|
||||
plugin_name: Optional[str] = None,
|
||||
plugin_id: Optional[str] = None,
|
||||
plugin_config: Optional[PluginConfig] = None,
|
||||
):
|
||||
__music = None
|
||||
if plugin_name:
|
||||
__music = _global_plugin_registry.get_music_input_plugin(plugin_name).load(
|
||||
file_path, plugin_config
|
||||
)
|
||||
else:
|
||||
for plugin in _global_plugin_registry.get_music_input_plugin_by_format(
|
||||
file_path
|
||||
):
|
||||
if __music is not None:
|
||||
raise PluginNotSpecifiedError(
|
||||
"文件类型`{}`可被多个插件处理,请在导入函数的参数中指定插件名称".format(
|
||||
file_path.suffix.upper()
|
||||
)
|
||||
)
|
||||
__music = plugin.load(file_path, plugin_config)
|
||||
if __music is None:
|
||||
raise FileFormatNotSupportedError(
|
||||
"无法找到处理`{}`类型文件的插件".format(file_path.suffix.upper())
|
||||
)
|
||||
return cls(whole_music=__music)
|
||||
) -> "MusiCreater":
|
||||
return cls(
|
||||
whole_music=cls._get_plugin_within_iousage(
|
||||
_global_plugin_registry.get_music_input_plugin_by_format,
|
||||
file_path,
|
||||
_global_plugin_registry._music_input_plugins,
|
||||
plugin_id,
|
||||
).load(file_path, plugin_config)
|
||||
)
|
||||
|
||||
def import_track(
|
||||
self,
|
||||
file_path: Path,
|
||||
plugin_name: Optional[str] = None,
|
||||
plugin_id: Optional[str] = None,
|
||||
plugin_config: Optional[PluginConfig] = None,
|
||||
) -> SingleTrack:
|
||||
__track = None
|
||||
if plugin_name:
|
||||
__track = self.get_plugin_by_name(
|
||||
plugin_name
|
||||
).load( # pyright: ignore[reportAttributeAccessIssue]
|
||||
file_path, plugin_config
|
||||
)
|
||||
else:
|
||||
for plugin in self.__plugin_registry.get_track_input_plugin_by_format(
|
||||
file_path
|
||||
):
|
||||
if __track:
|
||||
raise PluginNotSpecifiedError(
|
||||
"文件类型`{}`可被多个插件处理,请在导入函数的参数中指定插件名称".format(
|
||||
file_path.suffix.upper()
|
||||
)
|
||||
)
|
||||
__track = plugin.load(file_path, plugin_config)
|
||||
if __track:
|
||||
self.music.append(__track)
|
||||
return __track
|
||||
raise FileFormatNotSupportedError(
|
||||
"无法找到处理`{}`类型文件的插件".format(file_path.suffix.upper())
|
||||
self.music.append(
|
||||
self._get_plugin_within_iousage(
|
||||
self.__plugin_registry.get_track_input_plugin_by_format,
|
||||
file_path,
|
||||
self.__plugin_registry._track_input_plugins,
|
||||
plugin_id,
|
||||
).load(file_path, plugin_config)
|
||||
)
|
||||
return self.music[-1]
|
||||
|
||||
def export_music(
|
||||
self,
|
||||
file_path: Path,
|
||||
plugin_name: Optional[str] = None,
|
||||
plugin_id: Optional[str] = None,
|
||||
plugin_config: Optional[PluginConfig] = None,
|
||||
) -> None:
|
||||
__plugin = None
|
||||
if plugin_name:
|
||||
__plugin = self.get_plugin_by_name(plugin_name)
|
||||
else:
|
||||
for plugin in self.__plugin_registry.get_music_output_plugin_by_format(
|
||||
file_path
|
||||
):
|
||||
if __plugin:
|
||||
raise PluginNotSpecifiedError(
|
||||
"文件类型`{}`可被多个插件处理,请在导出函数的参数中指定插件名称".format(
|
||||
file_path.suffix.upper()
|
||||
)
|
||||
)
|
||||
__plugin = plugin
|
||||
|
||||
if __plugin:
|
||||
__plugin.dump( # pyright: ignore[reportAttributeAccessIssue]
|
||||
self.music, file_path, plugin_config
|
||||
)
|
||||
else:
|
||||
raise FileFormatNotSupportedError(
|
||||
"无法找到处理`{}`类型文件的插件".format(file_path.suffix.upper())
|
||||
)
|
||||
self._get_plugin_within_iousage(
|
||||
self.__plugin_registry.get_music_output_plugin_by_format,
|
||||
file_path,
|
||||
self.__plugin_registry._music_output_plugins,
|
||||
plugin_id,
|
||||
).dump(self.music, file_path, plugin_config)
|
||||
|
||||
def export_track(
|
||||
self,
|
||||
track_index: int,
|
||||
file_path: Path,
|
||||
plugin_name: Optional[str] = None,
|
||||
plugin_id: Optional[str] = None,
|
||||
plugin_config: Optional[PluginConfig] = None,
|
||||
) -> None:
|
||||
__plugin = None
|
||||
if plugin_name:
|
||||
__plugin = self.get_plugin_by_name(plugin_name)
|
||||
else:
|
||||
for plugin in self.__plugin_registry.get_track_output_plugin_by_format(
|
||||
file_path
|
||||
):
|
||||
if __plugin:
|
||||
raise PluginNotSpecifiedError(
|
||||
"文件类型`{}`可被多个插件处理,请在导出函数的参数中指定插件名称".format(
|
||||
file_path.suffix.upper()
|
||||
)
|
||||
)
|
||||
__plugin = plugin
|
||||
|
||||
if __plugin:
|
||||
__plugin.dump( # pyright: ignore[reportAttributeAccessIssue]
|
||||
self.music[track_index], file_path, plugin_config
|
||||
)
|
||||
else:
|
||||
raise FileFormatNotSupportedError(
|
||||
"无法找到处理`{}`类型文件的插件".format(file_path.suffix.upper())
|
||||
)
|
||||
self._get_plugin_within_iousage(
|
||||
self.__plugin_registry.get_track_output_plugin_by_format,
|
||||
file_path,
|
||||
self.__plugin_registry._track_output_plugins,
|
||||
plugin_id,
|
||||
).dump(self.music[track_index], file_path, plugin_config)
|
||||
|
||||
def perform_operation_on_music(
|
||||
self, plugin_name: str, plugin_config: Optional[PluginConfig] = None
|
||||
self, plugin_id: str, plugin_config: Optional[PluginConfig] = None
|
||||
):
|
||||
# 这样做是为了兼容以后的*撤回/重做*功能
|
||||
self.music = self.get_plugin_by_name(
|
||||
plugin_name
|
||||
).process( # pyright: ignore[reportAttributeAccessIssue]
|
||||
self.music, plugin_config
|
||||
)
|
||||
if __plugin := self.__plugin_registry._music_operate_plugins.get(plugin_id):
|
||||
# 这样做是为了兼容以后的*撤回/重做*功能
|
||||
self.music = __plugin.process(self.music, plugin_config)
|
||||
else:
|
||||
raise PluginNotFoundError(
|
||||
"无法找到惟一识别码为`{}`的插件".format(plugin_id)
|
||||
)
|
||||
|
||||
def perform_operation_on_track(
|
||||
self,
|
||||
track_index: int,
|
||||
plugin_name: str,
|
||||
plugin_id: str,
|
||||
plugin_config: Optional[PluginConfig] = None,
|
||||
):
|
||||
# 这样做是为了兼容以后的*撤回/重做*功能
|
||||
self.music[track_index] = self.get_plugin_by_name(
|
||||
plugin_name
|
||||
).process( # pyright: ignore[reportAttributeAccessIssue]
|
||||
self.music[track_index], plugin_config
|
||||
)
|
||||
if __plugin := self.__plugin_registry._track_operate_plugins.get(plugin_id):
|
||||
# 这样做是为了兼容以后的*撤回/重做*功能
|
||||
self.music[track_index] = __plugin.process(
|
||||
self.music[track_index], plugin_config
|
||||
)
|
||||
else:
|
||||
raise PluginNotFoundError(
|
||||
"无法找到惟一识别码为`{}`的插件".format(plugin_id)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _camel_to_snake(name: str) -> str:
|
||||
@@ -240,8 +216,8 @@ class MusiCreater:
|
||||
"([a-z0-9])([A-Z])", r"\1_\2", re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
|
||||
).lower()
|
||||
|
||||
def _parse_plugin_name(self, attr_name: str) -> Optional[str]:
|
||||
"""解析属性名称为插件名称"""
|
||||
def _parse_plugin_id(self, attr_name: str) -> Optional[str]:
|
||||
"""解析属性名称为插件惟一识别码"""
|
||||
|
||||
# 尝试去除 _plugin 后缀
|
||||
if attr_name.endswith("_plugin"):
|
||||
@@ -256,35 +232,35 @@ class MusiCreater:
|
||||
if snake_case_name in self._plugin_cache: # 尝试转换后的插件名
|
||||
return snake_case_name
|
||||
else:
|
||||
return self._parse_plugin_name(snake_case_name)
|
||||
return self._parse_plugin_id(snake_case_name)
|
||||
|
||||
return None
|
||||
|
||||
def _get_closest_plugin_name(self, requested_name: str) -> Optional[str]:
|
||||
"""找到最接近的插件名称(用于更好的错误提示)"""
|
||||
def _get_closest_plugin_id(self, requested_id: str) -> Optional[str]:
|
||||
"""找到最接近的插件识别码(用于更好的错误提示)"""
|
||||
|
||||
matches = get_close_matches(
|
||||
requested_name, self._plugin_cache.keys(), n=1, cutoff=0.6
|
||||
requested_id, self._plugin_cache.keys(), n=1, cutoff=0.6
|
||||
)
|
||||
return matches[0] if matches else None
|
||||
|
||||
def get_plugin_by_name(self, name: str) -> TopPluginBase:
|
||||
def get_plugin_by_id(self, plg_id: str):
|
||||
"""获取插件实例,并缓存起来,提高性能"""
|
||||
if name.startswith("_"):
|
||||
raise AttributeError("属性`{}`不存在,不应访问类的私有属性".format(name))
|
||||
if plg_id.startswith("_"):
|
||||
raise AttributeError("属性`{}`不存在,不应访问类的私有属性".format(plg_id))
|
||||
|
||||
if name in self._plugin_cache:
|
||||
return self._plugin_cache[name]
|
||||
if plg_id in self._plugin_cache:
|
||||
return self._plugin_cache[plg_id]
|
||||
else:
|
||||
plugin_name = self._parse_plugin_name(name)
|
||||
plugin_name = self._parse_plugin_id(plg_id)
|
||||
if plugin_name:
|
||||
self._plugin_cache[name] = self._plugin_cache[plugin_name]
|
||||
return self._plugin_cache[name]
|
||||
self._plugin_cache[plg_id] = self._plugin_cache[plugin_name]
|
||||
return self._plugin_cache[plg_id]
|
||||
|
||||
closest = self._get_closest_plugin_name(name)
|
||||
closest = self._get_closest_plugin_id(plg_id)
|
||||
|
||||
raise AttributeError(
|
||||
"插件`{}`不存在,请检查插件名称是否正确".format(name)
|
||||
"插件`{}`不存在,请检查插件的惟一识别码是否正确".format(plg_id)
|
||||
+ (
|
||||
";或者阁下可能想要使用的是`{}`插件?".format(closest)
|
||||
if closest
|
||||
@@ -292,18 +268,18 @@ class MusiCreater:
|
||||
)
|
||||
)
|
||||
|
||||
def __getattr__(self, name: str):
|
||||
def __getattr__(self, plugin_id: str):
|
||||
"""动态属性访问,允许直接 实例.插件名 来访问插件"""
|
||||
return self.get_plugin_by_name(name)
|
||||
return self.get_plugin_by_id(plugin_id)
|
||||
|
||||
def _cache_all_plugins(self):
|
||||
"""获取所有已注册插件的名称"""
|
||||
for __plugin_type, __plugins_set in self.__plugin_registry:
|
||||
for __plugin in __plugins_set:
|
||||
if __plugin.metainfo.name in self._plugin_cache: # 避免重复缓存
|
||||
for __plugin_type, __plugins_map in self.__plugin_registry:
|
||||
for __plugin_id, __plugin in __plugins_map.items():
|
||||
if __plugin_id in self._plugin_cache: # 避免重复缓存
|
||||
if (
|
||||
__plugin.metainfo.version
|
||||
<= self._plugin_cache[__plugin.metainfo.name].metainfo.version
|
||||
<= self._plugin_cache[__plugin_id].metainfo.version
|
||||
): # 优先使用版本号最大的插件
|
||||
continue
|
||||
self._plugin_cache[__plugin.metainfo.name] = __plugin
|
||||
self._plugin_cache[__plugin_id] = __plugin
|
||||
|
||||
@@ -30,13 +30,16 @@ from typing import (
|
||||
Set,
|
||||
Iterable,
|
||||
Iterator,
|
||||
TypeVar,
|
||||
Mapping,
|
||||
Callable,
|
||||
)
|
||||
from itertools import chain
|
||||
|
||||
|
||||
from ._plugin_abc import (
|
||||
# 枚举类
|
||||
PluginType,
|
||||
PluginTypes,
|
||||
# 抽象基类/数据类(插件参数定义)
|
||||
PluginConfig,
|
||||
PluginMetaInformation,
|
||||
@@ -56,12 +59,13 @@ from .exceptions import (
|
||||
PluginMetainfoNotFoundError,
|
||||
ParameterTypeError,
|
||||
PluginInstanceNotFoundError,
|
||||
PluginRegisteredError,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
# 枚举类
|
||||
"PluginType",
|
||||
"PluginTypes",
|
||||
# 抽象基类/数据类(插件参数定义)
|
||||
"PluginConfig",
|
||||
"PluginMetaInformation",
|
||||
@@ -86,6 +90,26 @@ __all__ = [
|
||||
]
|
||||
|
||||
|
||||
T_IOPlugin = TypeVar(
|
||||
"T_IOPlugin",
|
||||
MusicInputPluginBase,
|
||||
TrackInputPluginBase,
|
||||
MusicOutputPluginBase,
|
||||
TrackOutputPluginBase,
|
||||
)
|
||||
T_Plugin = TypeVar(
|
||||
"T_Plugin",
|
||||
MusicInputPluginBase,
|
||||
TrackInputPluginBase,
|
||||
MusicOperatePluginBase,
|
||||
TrackOperatePluginBase,
|
||||
MusicOutputPluginBase,
|
||||
TrackOutputPluginBase,
|
||||
ServicePluginBase,
|
||||
LibraryPluginBase,
|
||||
)
|
||||
|
||||
|
||||
def load_plugin_module(package: Union[Path, str]):
|
||||
"""自动发现并加载插件包中的插件
|
||||
|
||||
@@ -111,290 +135,199 @@ class PluginRegistry:
|
||||
"""插件注册管理器(注册表)"""
|
||||
|
||||
def __init__(self):
|
||||
# 实际上在纵容那些有着同样名称的插件……
|
||||
# (不用 Dict[str`plugin name`, PluginClass`] 的形式)
|
||||
# 啊,我真的很高尚
|
||||
# 你真的不会把插件注册两遍吧……对吧?
|
||||
self._music_input_plugins: Dict[str, MusicInputPluginBase] = {}
|
||||
self._track_input_plugins: Dict[str, TrackInputPluginBase] = {}
|
||||
self._music_operate_plugins: Dict[str, MusicOperatePluginBase] = {}
|
||||
self._track_operate_plugins: Dict[str, TrackOperatePluginBase] = {}
|
||||
self._music_output_plugins: Dict[str, MusicOutputPluginBase] = {}
|
||||
self._track_output_plugins: Dict[str, TrackOutputPluginBase] = {}
|
||||
self._service_plugins: Dict[str, ServicePluginBase] = {}
|
||||
self._library_plugins: Dict[str, LibraryPluginBase] = {}
|
||||
|
||||
# EMERGENCY TODO ================================================ CRITICAL 紧急更改
|
||||
# 改成 Dict[str`plugin id`, PluginClass`]] 的形式吧
|
||||
# 刚刚才想起来,这个 name 是显示名称啊!草
|
||||
# 现在测试情况下就将错就错吧,先把 name 当成 id 来写吧
|
||||
self._music_input_plugins: Set[MusicInputPluginBase] = set()
|
||||
self._track_input_plugins: Set[TrackInputPluginBase] = set()
|
||||
self._music_operate_plugins: Set[MusicOperatePluginBase] = set()
|
||||
self._track_operate_plugins: Set[TrackOperatePluginBase] = set()
|
||||
self._music_output_plugins: Set[MusicOutputPluginBase] = set()
|
||||
self._track_output_plugins: Set[TrackOutputPluginBase] = set()
|
||||
self._service_plugins: Set[ServicePluginBase] = set()
|
||||
self._library_plugins: Set[LibraryPluginBase] = set()
|
||||
|
||||
def __iter__(self) -> Iterator[Tuple[PluginType, Set[TopPluginBase]]]:
|
||||
def __iter__(self) -> Iterator[Tuple[PluginTypes, Mapping[str, TopPluginBase]]]:
|
||||
"""迭代器,返回所有插件"""
|
||||
return iter(
|
||||
(
|
||||
(PluginType.FUNCTION_MUSIC_IMPORT, self._music_input_plugins),
|
||||
(PluginType.FUNCTION_TRACK_IMPORT, self._track_input_plugins),
|
||||
(PluginType.FUNCTION_MUSIC_OPERATE, self._music_operate_plugins),
|
||||
(PluginType.FUNCTION_TRACK_OPERATE, self._track_operate_plugins),
|
||||
(PluginType.FUNCTION_MUSIC_EXPORT, self._music_output_plugins),
|
||||
(PluginType.FUNCTION_TRACK_EXPORT, self._track_output_plugins),
|
||||
(PluginType.SERVICE, self._service_plugins),
|
||||
(PluginType.LIBRARY, self._library_plugins),
|
||||
(PluginTypes.FUNCTION_MUSIC_IMPORT, self._music_input_plugins),
|
||||
(PluginTypes.FUNCTION_TRACK_IMPORT, self._track_input_plugins),
|
||||
(PluginTypes.FUNCTION_MUSIC_OPERATE, self._music_operate_plugins),
|
||||
(PluginTypes.FUNCTION_TRACK_OPERATE, self._track_operate_plugins),
|
||||
(PluginTypes.FUNCTION_MUSIC_EXPORT, self._music_output_plugins),
|
||||
(PluginTypes.FUNCTION_TRACK_EXPORT, self._track_output_plugins),
|
||||
(PluginTypes.SERVICE, self._service_plugins),
|
||||
(PluginTypes.LIBRARY, self._library_plugins),
|
||||
)
|
||||
) # pyright: ignore[reportReturnType]
|
||||
)
|
||||
|
||||
def register_music_input_plugin(self, plugin_class: type) -> None:
|
||||
@staticmethod
|
||||
def _register_plugin(cls_dict: dict, plg_class: type, plg_id: str) -> None:
|
||||
"""注册插件"""
|
||||
if plg_id in cls_dict:
|
||||
if cls_dict[plg_id].metainfo.version >= plg_class.metainfo.version:
|
||||
raise PluginRegisteredError(
|
||||
"插件惟一识别码`{}`所对应的插件已存在更高版本`{}`,请勿重复注册同一插件!".format(
|
||||
plg_id, plg_class.metainfo
|
||||
)
|
||||
)
|
||||
cls_dict[plg_id] = plg_class()
|
||||
|
||||
def register_music_input_plugin(
|
||||
self,
|
||||
plugin_class: type,
|
||||
plugin_id: str,
|
||||
) -> None:
|
||||
"""注册输入插件-整首曲目"""
|
||||
self._music_input_plugins.add(plugin_class())
|
||||
self._register_plugin(self._music_input_plugins, plugin_class, plugin_id)
|
||||
|
||||
def register_track_input_plugin(self, plugin_class: type) -> None:
|
||||
def register_track_input_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||
"""注册输入插件-单个音轨"""
|
||||
self._track_input_plugins.add(plugin_class())
|
||||
self._register_plugin(self._track_input_plugins, plugin_class, plugin_id)
|
||||
|
||||
def register_music_operate_plugin(self, plugin_class: type) -> None:
|
||||
def register_music_operate_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||
"""注册曲目处理插件"""
|
||||
self._music_operate_plugins.add(plugin_class())
|
||||
self._register_plugin(self._music_operate_plugins, plugin_class, plugin_id)
|
||||
|
||||
def register_track_operate_plugin(self, plugin_class: type) -> None:
|
||||
def register_track_operate_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||
"""注册音轨处理插件"""
|
||||
self._track_operate_plugins.add(plugin_class())
|
||||
self._register_plugin(self._track_operate_plugins, plugin_class, plugin_id)
|
||||
|
||||
def register_music_output_plugin(self, plugin_class: type) -> None:
|
||||
def register_music_output_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||
"""注册输出插件-整首曲目"""
|
||||
self._music_output_plugins.add(plugin_class())
|
||||
self._register_plugin(self._music_output_plugins, plugin_class, plugin_id)
|
||||
|
||||
def register_track_output_plugin(self, plugin_class: type) -> None:
|
||||
def register_track_output_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||
"""注册输出插件-单个音轨"""
|
||||
self._track_output_plugins.add(plugin_class())
|
||||
self._register_plugin(self._track_output_plugins, plugin_class, plugin_id)
|
||||
|
||||
def register_service_plugin(self, plugin_class: type) -> None:
|
||||
def register_service_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||
"""注册服务插件"""
|
||||
self._service_plugins.add(plugin_class())
|
||||
self._register_plugin(self._service_plugins, plugin_class, plugin_id)
|
||||
|
||||
def register_library_plugin(self, plugin_class: type) -> None:
|
||||
def register_library_plugin(self, plugin_class: type, plugin_id: str) -> None:
|
||||
"""注册支持库插件"""
|
||||
self._library_plugins.add(plugin_class())
|
||||
self._register_plugin(self._library_plugins, plugin_class, plugin_id)
|
||||
|
||||
@staticmethod
|
||||
def _get_io_plugin_by_format(
|
||||
plugin_regdict: Dict[str, T_IOPlugin], fpath_or_format: Union[Path, str]
|
||||
) -> Generator[T_IOPlugin, None, None]:
|
||||
if isinstance(fpath_or_format, str):
|
||||
return (
|
||||
plugin
|
||||
for plugin in plugin_regdict.values()
|
||||
if plugin.can_handle_format(fpath_or_format)
|
||||
)
|
||||
elif isinstance(fpath_or_format, Path):
|
||||
return (
|
||||
plugin
|
||||
for plugin in plugin_regdict.values()
|
||||
if plugin.can_handle_file(fpath_or_format)
|
||||
)
|
||||
else:
|
||||
raise ParameterTypeError(
|
||||
"用于指定“导入全曲的数据之类型”的参数,其类型须为`Path`路径或字符串,而非`{}`类型的`{}`值".format(
|
||||
type(fpath_or_format), fpath_or_format
|
||||
)
|
||||
)
|
||||
|
||||
def get_music_input_plugin_by_format(
|
||||
self, filepath_or_format: Union[Path, str]
|
||||
) -> Generator[MusicInputPluginBase, None, None]:
|
||||
"""通过指定输入的文件或格式,以获取对应的全曲导入用插件"""
|
||||
if isinstance(filepath_or_format, str):
|
||||
return (
|
||||
plugin
|
||||
for plugin in self._music_input_plugins
|
||||
if plugin.can_handle_format(filepath_or_format)
|
||||
)
|
||||
elif isinstance(filepath_or_format, Path):
|
||||
return (
|
||||
plugin
|
||||
for plugin in self._music_input_plugins
|
||||
if plugin.can_handle_file(filepath_or_format)
|
||||
)
|
||||
else:
|
||||
raise ParameterTypeError(
|
||||
"用于指定“导入全曲的数据之类型”的参数,其类型须为`Path`路径或字符串,而非`{}`类型的`{}`值".format(
|
||||
type(filepath_or_format), filepath_or_format
|
||||
)
|
||||
)
|
||||
return self._get_io_plugin_by_format(
|
||||
self._music_input_plugins, filepath_or_format
|
||||
)
|
||||
|
||||
def get_track_input_plugin_by_format(
|
||||
self, filepath_or_format: Union[Path, str]
|
||||
) -> Generator[TrackInputPluginBase, None, None]:
|
||||
"""通过指定输入的文件或格式,以获取对应的单音轨导入用插件"""
|
||||
if isinstance(filepath_or_format, str):
|
||||
return (
|
||||
plugin
|
||||
for plugin in self._track_input_plugins
|
||||
if plugin.can_handle_format(filepath_or_format)
|
||||
)
|
||||
elif isinstance(filepath_or_format, Path):
|
||||
return (
|
||||
plugin
|
||||
for plugin in self._track_input_plugins
|
||||
if plugin.can_handle_file(filepath_or_format)
|
||||
)
|
||||
else:
|
||||
raise ParameterTypeError(
|
||||
"用于指定“导入单个音轨的数据之类型”的参数,其类型须为`Path`路径或字符串,而非`{}`类型的`{}`值".format(
|
||||
type(filepath_or_format), filepath_or_format
|
||||
)
|
||||
)
|
||||
return self._get_io_plugin_by_format(
|
||||
self._track_input_plugins, filepath_or_format
|
||||
)
|
||||
|
||||
def get_music_output_plugin_by_format(
|
||||
self, filepath_or_format: Union[Path, str]
|
||||
) -> Generator[MusicOutputPluginBase, None, None]:
|
||||
"""通过指定输出的文件或格式,以获取对应的导出全曲用插件"""
|
||||
if isinstance(filepath_or_format, str):
|
||||
return (
|
||||
plugin
|
||||
for plugin in self._music_output_plugins
|
||||
if plugin.can_handle_format(filepath_or_format)
|
||||
)
|
||||
elif isinstance(filepath_or_format, Path):
|
||||
return (
|
||||
plugin
|
||||
for plugin in self._music_output_plugins
|
||||
if plugin.can_handle_file(filepath_or_format)
|
||||
)
|
||||
else:
|
||||
raise ParameterTypeError(
|
||||
"用于指定“全曲数据导出的类型”的参数,其类型须为`Path`路径或字符串,而非`{}`类型的`{}`值".format(
|
||||
type(filepath_or_format), filepath_or_format
|
||||
)
|
||||
)
|
||||
return self._get_io_plugin_by_format(
|
||||
self._music_output_plugins, filepath_or_format
|
||||
)
|
||||
|
||||
def get_track_output_plugin_by_format(
|
||||
self, filepath_or_format: Union[Path, str]
|
||||
) -> Generator[TrackOutputPluginBase, None, None]:
|
||||
"""通过指定输出的文件或格式,以获取对应的导出单个音轨用插件"""
|
||||
if isinstance(filepath_or_format, str):
|
||||
return (
|
||||
plugin
|
||||
for plugin in self._track_output_plugins
|
||||
if plugin.can_handle_format(filepath_or_format)
|
||||
return self._get_io_plugin_by_format(
|
||||
self._track_output_plugins, filepath_or_format
|
||||
)
|
||||
|
||||
def _get_plugin_by_name(
|
||||
self,
|
||||
plugin_regdict: Mapping[str, T_Plugin],
|
||||
plugin_name: str,
|
||||
plugin_usage: str = "",
|
||||
) -> T_Plugin:
|
||||
"""通过指定名称,以获取对应的插件,当名称重叠时,取版本号最大的"""
|
||||
try:
|
||||
return max(
|
||||
[
|
||||
plugin
|
||||
for plugin in plugin_regdict.values()
|
||||
if plugin.metainfo.name == plugin_name
|
||||
],
|
||||
key=lambda plugin: plugin.metainfo.version,
|
||||
)
|
||||
elif isinstance(filepath_or_format, Path):
|
||||
return (
|
||||
plugin
|
||||
for plugin in self._track_output_plugins
|
||||
if plugin.can_handle_file(filepath_or_format)
|
||||
)
|
||||
else:
|
||||
raise ParameterTypeError(
|
||||
"用于指定“单音轨数据导出的类型”的参数,其类型须为`Path`路径或字符串,而非`{}`类型的`{}`值".format(
|
||||
type(filepath_or_format), filepath_or_format
|
||||
)
|
||||
except ValueError:
|
||||
raise PluginInstanceNotFoundError(
|
||||
"未找到“用于{}、名为`{}`”的插件".format(plugin_usage, plugin_name)
|
||||
)
|
||||
|
||||
def get_music_input_plugin(self, plugin_name: str) -> MusicInputPluginBase:
|
||||
"""获取指定名称的全曲导入用插件,当名称重叠时,取版本号最大的"""
|
||||
try:
|
||||
return max(
|
||||
[
|
||||
plugin
|
||||
for plugin in self._music_input_plugins
|
||||
if plugin.metainfo.name == plugin_name
|
||||
],
|
||||
key=lambda plugin: plugin.metainfo.version,
|
||||
)
|
||||
except ValueError:
|
||||
raise PluginInstanceNotFoundError(
|
||||
"未找到“用于导入曲目、名为`{}`”的插件".format(plugin_name)
|
||||
)
|
||||
return self._get_plugin_by_name(
|
||||
self._music_input_plugins, plugin_name, "导入全曲"
|
||||
)
|
||||
|
||||
def get_track_input_plugin(self, plugin_name: str) -> TrackInputPluginBase:
|
||||
"""获取指定名称的单音轨导入用插件,当名称重叠时,取版本号最大的"""
|
||||
try:
|
||||
return max(
|
||||
[
|
||||
plugin
|
||||
for plugin in self._track_input_plugins
|
||||
if plugin.metainfo.name == plugin_name
|
||||
],
|
||||
key=lambda plugin: plugin.metainfo.version,
|
||||
)
|
||||
except ValueError:
|
||||
raise PluginInstanceNotFoundError(
|
||||
"未找到“用于导入单个音轨、名为`{}`”的插件".format(plugin_name)
|
||||
)
|
||||
return self._get_plugin_by_name(
|
||||
self._track_input_plugins, plugin_name, "导入单轨"
|
||||
)
|
||||
|
||||
def get_music_operate_plugin(self, plugin_name: str) -> MusicOperatePluginBase:
|
||||
"""获取指定名称的全曲处理用插件,当名称重叠时,取版本号最大的"""
|
||||
try:
|
||||
return max(
|
||||
[
|
||||
plugin
|
||||
for plugin in self._music_operate_plugins
|
||||
if plugin.metainfo.name == plugin_name
|
||||
],
|
||||
key=lambda plugin: plugin.metainfo.version,
|
||||
)
|
||||
except ValueError:
|
||||
raise PluginInstanceNotFoundError(
|
||||
"未找到“用于处理整个曲目、名为`{}`”的插件".format(plugin_name)
|
||||
)
|
||||
return self._get_plugin_by_name(
|
||||
self._music_operate_plugins, plugin_name, "处理整个曲目"
|
||||
)
|
||||
|
||||
def get_track_operate_plugin(self, plugin_name: str) -> TrackOperatePluginBase:
|
||||
"""获取指定名称的单音轨处理用插件,当名称重叠时,取版本号最大的"""
|
||||
try:
|
||||
return max(
|
||||
[
|
||||
plugin
|
||||
for plugin in self._track_operate_plugins
|
||||
if plugin.metainfo.name == plugin_name
|
||||
],
|
||||
key=lambda plugin: plugin.metainfo.version,
|
||||
)
|
||||
except ValueError:
|
||||
raise PluginInstanceNotFoundError(
|
||||
"未找到“用于处理单个音轨、名为`{}`”的插件".format(plugin_name)
|
||||
)
|
||||
return self._get_plugin_by_name(
|
||||
self._track_operate_plugins, plugin_name, "处理单个音轨"
|
||||
)
|
||||
|
||||
def get_music_output_plugin(self, plugin_name: str) -> MusicOutputPluginBase:
|
||||
"""获取指定名称的导出全曲用插件,当名称重叠时,取版本号最大的"""
|
||||
try:
|
||||
return max(
|
||||
[
|
||||
plugin
|
||||
for plugin in self._music_output_plugins
|
||||
if plugin.metainfo.name == plugin_name
|
||||
],
|
||||
key=lambda plugin: plugin.metainfo.version,
|
||||
)
|
||||
except ValueError:
|
||||
raise PluginMetainfoNotFoundError(
|
||||
"未找到“用于导出完整曲目、名为`{}`”的插件".format(plugin_name)
|
||||
)
|
||||
return self._get_plugin_by_name(
|
||||
self._music_output_plugins, plugin_name, "导出完整曲目"
|
||||
)
|
||||
|
||||
def get_track_output_plugin(self, plugin_name: str) -> TrackOutputPluginBase:
|
||||
"""获取指定名称的导出单音轨用插件,当名称重叠时,取版本号最大的"""
|
||||
try:
|
||||
return max(
|
||||
[
|
||||
plugin
|
||||
for plugin in self._track_output_plugins
|
||||
if plugin.metainfo.name == plugin_name
|
||||
],
|
||||
key=lambda plugin: plugin.metainfo.version,
|
||||
)
|
||||
except ValueError:
|
||||
raise PluginMetainfoNotFoundError(
|
||||
"未找到“用于导出单个音轨、名为`{}`”的插件".format(plugin_name)
|
||||
)
|
||||
return self._get_plugin_by_name(
|
||||
self._track_output_plugins, plugin_name, "导出单个音轨"
|
||||
)
|
||||
|
||||
def get_service_plugin(self, plugin_name: str) -> ServicePluginBase:
|
||||
"""获取服务用插件,当名称重叠时,取版本号最大的"""
|
||||
try:
|
||||
return max(
|
||||
[
|
||||
plugin
|
||||
for plugin in self._service_plugins
|
||||
if plugin.metainfo.name == plugin_name
|
||||
],
|
||||
key=lambda plugin: plugin.metainfo.version,
|
||||
)
|
||||
except ValueError:
|
||||
raise PluginInstanceNotFoundError(
|
||||
"未找到名为`{}`的服务用插件".format(plugin_name)
|
||||
)
|
||||
return self._get_plugin_by_name(self._service_plugins, plugin_name, "提供服务")
|
||||
|
||||
def get_library_plugin(self, plugin_name: str) -> LibraryPluginBase:
|
||||
"""获取依赖库类插件,当名称重叠时,取版本号最高的"""
|
||||
try:
|
||||
return max(
|
||||
[
|
||||
plugin
|
||||
for plugin in self._library_plugins
|
||||
if plugin.metainfo.name == plugin_name
|
||||
],
|
||||
key=lambda plugin: plugin.metainfo.version,
|
||||
)
|
||||
except ValueError:
|
||||
raise PluginInstanceNotFoundError(
|
||||
"未找到名为`{}`的依赖库插件".format(plugin_name)
|
||||
)
|
||||
return self._get_plugin_by_name(
|
||||
self._library_plugins, plugin_name, "作为依赖库"
|
||||
)
|
||||
|
||||
def supported_input_formats(self) -> Set[str]:
|
||||
"""所有支持的导入格式"""
|
||||
@@ -402,7 +335,8 @@ class PluginRegistry:
|
||||
chain.from_iterable(
|
||||
plugin.supported_formats
|
||||
for plugin in chain(
|
||||
self._music_input_plugins, self._track_input_plugins
|
||||
self._music_input_plugins.values(),
|
||||
self._track_input_plugins.values(),
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -413,7 +347,8 @@ class PluginRegistry:
|
||||
chain.from_iterable(
|
||||
plugin.supported_formats
|
||||
for plugin in chain(
|
||||
self._music_output_plugins, self._track_output_plugins
|
||||
self._music_output_plugins.values(),
|
||||
self._track_output_plugins.values(),
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -423,97 +358,67 @@ _global_plugin_registry = PluginRegistry()
|
||||
"""全局插件注册表实例"""
|
||||
|
||||
|
||||
def music_input_plugin(plugin_id: str):
|
||||
"""全曲输入用插件装饰器"""
|
||||
def __plugin_regist_decorator(plg_id: str, rgst_func: Callable[[type, str], None]):
|
||||
|
||||
def decorator(cls):
|
||||
global _global_plugin_registry
|
||||
cls.id = plugin_id
|
||||
_global_plugin_registry.register_music_input_plugin(cls)
|
||||
cls.id = plg_id
|
||||
rgst_func(cls, plg_id)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def music_input_plugin(plugin_id: str):
|
||||
"""全曲输入用插件装饰器"""
|
||||
return __plugin_regist_decorator(
|
||||
plugin_id, _global_plugin_registry.register_music_input_plugin
|
||||
)
|
||||
|
||||
|
||||
def track_input_plugin(plugin_id: str):
|
||||
"""单轨输入用插件装饰器"""
|
||||
|
||||
def decorator(cls):
|
||||
global _global_plugin_registry
|
||||
cls.id = plugin_id
|
||||
_global_plugin_registry.register_track_input_plugin(cls)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
return __plugin_regist_decorator(
|
||||
plugin_id, _global_plugin_registry.register_track_input_plugin
|
||||
)
|
||||
|
||||
|
||||
def music_operate_plugin(plugin_id: str):
|
||||
"""全曲处理用插件装饰器"""
|
||||
|
||||
def decorator(cls):
|
||||
global _global_plugin_registry
|
||||
cls.id = plugin_id
|
||||
_global_plugin_registry.register_music_operate_plugin(cls)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
return __plugin_regist_decorator(
|
||||
plugin_id, _global_plugin_registry.register_music_operate_plugin
|
||||
)
|
||||
|
||||
def track_operate_plugin(plugin_id: str):
|
||||
"""音轨处理插件装饰器"""
|
||||
|
||||
def decorator(cls):
|
||||
global _global_plugin_registry
|
||||
cls.id = plugin_id
|
||||
_global_plugin_registry.register_track_operate_plugin(cls)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
return __plugin_regist_decorator(
|
||||
plugin_id, _global_plugin_registry.register_track_operate_plugin
|
||||
)
|
||||
|
||||
|
||||
def music_output_plugin(plugin_id: str):
|
||||
"""乐曲输出用插件装饰器"""
|
||||
|
||||
def decorator(cls):
|
||||
global _global_plugin_registry
|
||||
cls.id = plugin_id
|
||||
_global_plugin_registry.register_music_output_plugin(cls)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
return __plugin_regist_decorator(
|
||||
plugin_id, _global_plugin_registry.register_music_output_plugin
|
||||
)
|
||||
|
||||
|
||||
def track_output_plugin(plugin_id: str):
|
||||
"""音轨输出用插件装饰器"""
|
||||
|
||||
def decorator(cls):
|
||||
global _global_plugin_registry
|
||||
cls.id = plugin_id
|
||||
_global_plugin_registry.register_track_output_plugin(cls)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
return __plugin_regist_decorator(
|
||||
plugin_id, _global_plugin_registry.register_track_output_plugin
|
||||
)
|
||||
|
||||
|
||||
def service_plugin(plugin_id: str):
|
||||
"""服务插件装饰器"""
|
||||
|
||||
def decorator(cls):
|
||||
global _global_plugin_registry
|
||||
cls.id = plugin_id
|
||||
_global_plugin_registry.register_service_plugin(cls)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
return __plugin_regist_decorator(
|
||||
plugin_id, _global_plugin_registry.register_service_plugin
|
||||
)
|
||||
|
||||
def library_plugin(plugin_id: str):
|
||||
"""支持库插件装饰器"""
|
||||
|
||||
def decorator(cls):
|
||||
global _global_plugin_registry
|
||||
cls.id = plugin_id
|
||||
_global_plugin_registry.register_library_plugin(cls)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
return __plugin_regist_decorator(
|
||||
plugin_id, _global_plugin_registry.register_library_plugin
|
||||
)
|
||||
|
||||
@@ -14,5 +14,11 @@ print(msct:=MusiCreater.import_music(Path("./resources/测试片段.mid")))
|
||||
print(msct.music)
|
||||
|
||||
|
||||
# 为了让类型检查器满意,以下方法不建议使用,因为这本质上是越过了 MusiCreater 类而直接执行插件的函数
|
||||
print(t := msct.midi_2_music_plugin.load(Path("./resources/测试片段.mid"), None))
|
||||
# 我们建议用这种方式来代替
|
||||
t = _global_plugin_registry._music_input_plugins["midi_2_music_plugin"].load(Path("./resources/测试片段.mid"), None)
|
||||
|
||||
print(_global_plugin_registry)
|
||||
print(msct._plugin_cache)
|
||||
|
||||
print(msct.midi_2_music_plugin.load(Path("./resources/测试片段.mid"), None))
|
||||
Reference in New Issue
Block a user