mirror of
https://github.com/TriM-Organization/Musicreater.git
synced 2026-04-17 06:08:00 +00:00
临时上传,仍在开发过程中
This commit is contained in:
@@ -1,19 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""一个简单的我的世界音频转换库
|
||||
音·创 (Musicreater)
|
||||
|
||||
"""
|
||||
音·创
|
||||
是一款免费开源的《我的世界》数字音频支持库。
|
||||
Musicreater(音·创)
|
||||
A free open source library used for dealing with **Minecraft** digital musics.
|
||||
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
Musicreater (音·创)
|
||||
A free and open-source library for handling with **Minecraft** digital music.
|
||||
|
||||
音·创(“本项目”)的协议颁发者为 金羿、诸葛亮与八卦阵
|
||||
The Licensor of Musicreater("this project") is Eilles, bgArray.
|
||||
版权所有 © 2026 睿乐组织
|
||||
Copyright © 2026 TriM-Organization
|
||||
|
||||
音·创(“本项目”)的协议颁发者为 金羿、玉衡Alioth
|
||||
The Licensor of Musicreater("this project") is Eilles, YuhengAlioth
|
||||
|
||||
本项目根据 第一版 汉钰律许可协议(“本协议”)授权。
|
||||
任何人皆可从以下地址获得本协议副本:https://gitee.com/EillesWan/YulvLicenses。
|
||||
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,不予提供任何形式的担保、任何明示、任何暗示或类似承诺。也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。
|
||||
任何人皆可从以下地址获得本协议副本:
|
||||
https://gitee.com/TriM-Organization/Musicreater/blob/master/LICENSE.md。
|
||||
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,
|
||||
不予提供任何形式的担保、任何明示、任何暗示或类似承诺。
|
||||
也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。
|
||||
详细的准许和限制条款请见原协议文本。
|
||||
"""
|
||||
|
||||
@@ -22,123 +27,12 @@ The Licensor of Musicreater("this project") is Eilles, bgArray.
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
|
||||
__version__ = "2.4.2.3"
|
||||
__vername__ = "音符附加信息升级"
|
||||
|
||||
__version__ = "3.0.0-alpha"
|
||||
|
||||
__author__ = (
|
||||
("金羿", "Eilles"),
|
||||
("诸葛亮与八卦阵", "bgArray"),
|
||||
("玉衡Alioth", "YuhengAlioth"),
|
||||
("鱼旧梦", "ElapsingDreams"),
|
||||
("偷吃不是Touch", "Touch"),
|
||||
)
|
||||
__all__ = [
|
||||
# 主要类
|
||||
"MusicSequence",
|
||||
"MidiConvert",
|
||||
# 附加类
|
||||
# "SingleNote",
|
||||
"MineNote",
|
||||
"MineCommand",
|
||||
"SingleNoteBox",
|
||||
"ProgressBarStyle",
|
||||
# "TimeStamp", 未来功能
|
||||
# 字典键
|
||||
"MIDI_PROGRAM",
|
||||
"MIDI_VOLUME",
|
||||
"MIDI_PAN",
|
||||
# 默认值
|
||||
"MIDI_DEFAULT_PROGRAM_VALUE",
|
||||
"MIDI_DEFAULT_VOLUME_VALUE",
|
||||
"DEFAULT_PROGRESSBAR_STYLE",
|
||||
# Midi 自己的对照表
|
||||
"MIDI_PITCH_NAME_TABLE",
|
||||
"MIDI_PITCHED_NOTE_NAME_GROUP",
|
||||
"MIDI_PITCHED_NOTE_NAME_TABLE",
|
||||
"MIDI_PERCUSSION_NOTE_NAME_TABLE",
|
||||
# Minecraft 自己的对照表
|
||||
"MC_PERCUSSION_INSTRUMENT_LIST",
|
||||
"MC_PITCHED_INSTRUMENT_LIST",
|
||||
"MC_INSTRUMENT_BLOCKS_TABLE",
|
||||
"MC_EILLES_RTJE12_INSTRUMENT_REPLACE_TABLE",
|
||||
"MC_EILLES_RTBETA_INSTRUMENT_REPLACE_TABLE",
|
||||
# Midi 与 游戏 的对照表
|
||||
"MM_INSTRUMENT_RANGE_TABLE",
|
||||
"MM_INSTRUMENT_DEVIATION_TABLE",
|
||||
"MM_CLASSIC_PITCHED_INSTRUMENT_TABLE",
|
||||
"MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE",
|
||||
"MM_TOUCH_PITCHED_INSTRUMENT_TABLE",
|
||||
"MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE",
|
||||
"MM_DISLINK_PITCHED_INSTRUMENT_TABLE",
|
||||
"MM_DISLINK_PERCUSSION_INSTRUMENT_TABLE",
|
||||
"MM_NBS_PITCHED_INSTRUMENT_TABLE",
|
||||
"MM_NBS_PERCUSSION_INSTRUMENT_TABLE",
|
||||
# 操作性函数
|
||||
"velocity_2_distance_natural",
|
||||
"velocity_2_distance_straight",
|
||||
"panning_2_rotation_linear",
|
||||
"panning_2_rotation_trigonometric",
|
||||
# 工具函数
|
||||
"load_decode_musicsequence_metainfo",
|
||||
"load_decode_msq_flush_release",
|
||||
"load_decode_fsq_flush_release",
|
||||
"guess_deviation",
|
||||
"mctick2timestr",
|
||||
"midi_inst_to_mc_sound",
|
||||
]
|
||||
|
||||
from .old_main import MusicSequence, MidiConvert
|
||||
|
||||
from .subclass import (
|
||||
MineNote,
|
||||
MineCommand,
|
||||
SingleNoteBox,
|
||||
ProgressBarStyle,
|
||||
mctick2timestr,
|
||||
DEFAULT_PROGRESSBAR_STYLE,
|
||||
)
|
||||
|
||||
from .utils import (
|
||||
# 兼容性函数
|
||||
load_decode_musicsequence_metainfo,
|
||||
load_decode_msq_flush_release,
|
||||
load_decode_fsq_flush_release,
|
||||
# 工具函数
|
||||
guess_deviation,
|
||||
midi_inst_to_mc_sound,
|
||||
# 处理用函数
|
||||
velocity_2_distance_natural,
|
||||
velocity_2_distance_straight,
|
||||
panning_2_rotation_linear,
|
||||
panning_2_rotation_trigonometric,
|
||||
)
|
||||
|
||||
from .constants import (
|
||||
# 字典键
|
||||
MIDI_PROGRAM,
|
||||
MIDI_PAN,
|
||||
MIDI_VOLUME,
|
||||
# 默认值
|
||||
MIDI_DEFAULT_PROGRAM_VALUE,
|
||||
MIDI_DEFAULT_VOLUME_VALUE,
|
||||
# MIDI 表
|
||||
MIDI_PITCH_NAME_TABLE,
|
||||
MIDI_PITCHED_NOTE_NAME_GROUP,
|
||||
MIDI_PITCHED_NOTE_NAME_TABLE,
|
||||
MIDI_PERCUSSION_NOTE_NAME_TABLE,
|
||||
# 我的世界 表
|
||||
MC_EILLES_RTBETA_INSTRUMENT_REPLACE_TABLE,
|
||||
MC_EILLES_RTJE12_INSTRUMENT_REPLACE_TABLE,
|
||||
MC_INSTRUMENT_BLOCKS_TABLE,
|
||||
MC_PERCUSSION_INSTRUMENT_LIST,
|
||||
MC_PITCHED_INSTRUMENT_LIST,
|
||||
# MIDI 到 我的世界 表
|
||||
MM_INSTRUMENT_RANGE_TABLE,
|
||||
MM_INSTRUMENT_DEVIATION_TABLE,
|
||||
MM_CLASSIC_PITCHED_INSTRUMENT_TABLE,
|
||||
MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE,
|
||||
MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
|
||||
MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
||||
MM_DISLINK_PITCHED_INSTRUMENT_TABLE,
|
||||
MM_DISLINK_PERCUSSION_INSTRUMENT_TABLE,
|
||||
MM_NBS_PITCHED_INSTRUMENT_TABLE,
|
||||
MM_NBS_PERCUSSION_INSTRUMENT_TABLE,
|
||||
)
|
||||
|
||||
412
Musicreater/_plugin_abc.py
Normal file
412
Musicreater/_plugin_abc.py
Normal file
@@ -0,0 +1,412 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
存储 音·创 v3 的插件基类,提供抽象接口以供实际插件使用
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2025 金羿
|
||||
Copyright © 2025 Eilles
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
# =====================
|
||||
# NOTE: [WARNING]
|
||||
# 这个文件是一坨屎山代码
|
||||
# 请勿模仿,请多包容
|
||||
# =====================
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import (
|
||||
Dict,
|
||||
Any,
|
||||
Optional,
|
||||
List,
|
||||
Tuple,
|
||||
Union,
|
||||
Sequence,
|
||||
BinaryIO,
|
||||
Generator,
|
||||
Iterator,
|
||||
Set,
|
||||
)
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
import tomllib
|
||||
import tomli_w
|
||||
else:
|
||||
import tomli as tomllib # 第三方包
|
||||
import tomli_w
|
||||
|
||||
from .exceptions import (
|
||||
PluginConfigDumpError,
|
||||
PluginConfigLoadError,
|
||||
PluginMetainfoNotFoundError,
|
||||
PluginMetainfoTypeError,
|
||||
PluginMetainfoValueError,
|
||||
PluginAttributeNotFoundError,
|
||||
ParameterTypeError,
|
||||
PluginInstanceNotFoundError,
|
||||
)
|
||||
from .data import SingleMusic, SingleTrack
|
||||
|
||||
# 已经全部由 plugins.py 提供接口
|
||||
# 请用户从 plugins.py 导入
|
||||
# 不要在这里导,会坏掉的
|
||||
|
||||
# __all__ = [
|
||||
# # 枚举类
|
||||
# "PluginType",
|
||||
# # 抽象基类/数据类(插件参数定义)
|
||||
# "PluginConfig",
|
||||
# "PluginMetaInformation",
|
||||
# # 抽象基类(插件定义)
|
||||
# "MusicInputPlugin",
|
||||
# "TrackInputPlugin",
|
||||
# "MusicOperatePlugin",
|
||||
# "TrackOperatePlugin",
|
||||
# "MusicOutputPlugin",
|
||||
# "TrackOutputPlugin",
|
||||
# "ServicePlugin",
|
||||
# "LibraryPlugin",
|
||||
# # 插件注册用装饰函数
|
||||
# "music_input_plugin",
|
||||
# "track_input_plugin",
|
||||
# "music_operate_plugin",
|
||||
# "track_operate_plugin",
|
||||
# "music_output_plugin",
|
||||
# "track_output_plugin",
|
||||
# "service_plugin",
|
||||
# "library_plugin",
|
||||
# ]
|
||||
|
||||
|
||||
@dataclass
|
||||
class PluginConfig(ABC):
|
||||
"""插件配置基类"""
|
||||
|
||||
def to_dict(self) -> 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":
|
||||
"""从字典创建配置实例"""
|
||||
# 只保留类中定义的字段
|
||||
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:
|
||||
"""保存配置到文件"""
|
||||
if file_path.suffix.upper() == ".TOML":
|
||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
else:
|
||||
raise PluginConfigDumpError(
|
||||
"插件配置文件类型不应为`{}`,须为`TOML`格式。".format(file_path.suffix)
|
||||
)
|
||||
|
||||
try:
|
||||
with file_path.open("wb") as f:
|
||||
tomli_w.dump(self.to_dict(), f, multiline_strings=False, indent=4)
|
||||
except Exception as e:
|
||||
raise PluginConfigDumpError(e)
|
||||
|
||||
@classmethod
|
||||
def load_from_file(cls, file_path: Path) -> "PluginConfig":
|
||||
"""从文件加载配置"""
|
||||
try:
|
||||
with file_path.open("rb") as f:
|
||||
return cls.from_dict(tomllib.load(f))
|
||||
except Exception as e:
|
||||
raise PluginConfigLoadError(e)
|
||||
|
||||
|
||||
class PluginType(str, Enum):
|
||||
"""插件类型枚举"""
|
||||
|
||||
FUNCTION_IMPORT = "import_data"
|
||||
FUNCTION_EXPORT = "export_data"
|
||||
FUNCTION_OPERATE = "data_operate"
|
||||
SERVICE = "service"
|
||||
LIBRARY = "library"
|
||||
|
||||
|
||||
@dataclass
|
||||
class PluginMetaInformation(ABC):
|
||||
"""插件元信息"""
|
||||
|
||||
name: str
|
||||
"""插件名称,应为惟一之名"""
|
||||
author: str
|
||||
"""插件作者"""
|
||||
description: str
|
||||
"""插件简介"""
|
||||
version: Tuple[int, ...]
|
||||
"""插件版本号"""
|
||||
type: PluginType
|
||||
"""插件类型"""
|
||||
license: str = "MIT License"
|
||||
"""插件发布时采用的许可协议"""
|
||||
dependencies: Sequence[str] = []
|
||||
"""插件是否对其他插件存在依赖"""
|
||||
|
||||
|
||||
class TopBasePlugin(ABC):
|
||||
"""所有插件的抽象基类"""
|
||||
|
||||
metainfo: PluginMetaInformation
|
||||
"""插件元信息"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
if hasattr(cls, "metainfo"):
|
||||
if not isinstance(cls.metainfo, PluginMetaInformation):
|
||||
raise PluginMetainfoTypeError(
|
||||
"类`{cls_name}`之属性`metainfo`的类型,必须为`PluginMetaInformation`".format(
|
||||
cls_name=cls.__name__
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise PluginMetainfoNotFoundError(
|
||||
"类`{cls_name}`必须定义一个`metainfo`属性。".format(
|
||||
cls_name=cls.__name__
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class TopInOutBasePlugin(TopBasePlugin, ABC):
|
||||
"""导入导出用抽象基类"""
|
||||
|
||||
supported_formats: Tuple[str, ...] = tuple()
|
||||
"""支持的格式"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if hasattr(cls, "supported_formats"):
|
||||
if cls.supported_formats:
|
||||
# 强制转换为大写,并使用元组
|
||||
cls.supported_formats = tuple(map(str.upper, cls.supported_formats))
|
||||
else:
|
||||
cls.supported_formats = tuple()
|
||||
else:
|
||||
raise PluginAttributeNotFoundError(
|
||||
"用于导入导出数据的类`{cls_name}`必须定义一个`supported_formats`属性。".format(
|
||||
cls_name=cls.__name__
|
||||
)
|
||||
)
|
||||
|
||||
def can_handle_file(self, file_path: Path) -> bool:
|
||||
"""判断是否可处理某个文件"""
|
||||
return file_path.suffix.upper().endswith(self.supported_formats)
|
||||
|
||||
def can_handle_format(self, format_name: str) -> bool:
|
||||
"""判断是否可处理某个格式"""
|
||||
return format_name.upper().endswith(self.supported_formats)
|
||||
|
||||
|
||||
class MusicInputPlugin(TopInOutBasePlugin, ABC):
|
||||
"""导入用插件抽象基类-完整曲目"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginType.FUNCTION_IMPORT:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`MusicInputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_IMPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
cls_type=cls.metainfo.type.name,
|
||||
)
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def loadbytes(
|
||||
self, bytes_buffer_in: BinaryIO, config: Optional[PluginConfig]
|
||||
) -> "SingleMusic":
|
||||
"""从字节流加载数据到完整曲目"""
|
||||
pass
|
||||
|
||||
def load(self, file_path: Path, config: Optional[PluginConfig]) -> "SingleMusic":
|
||||
"""从文件加载数据到完整曲目"""
|
||||
with file_path.open("rb") as f:
|
||||
return self.loadbytes(f, config)
|
||||
|
||||
|
||||
class TrackInputPlugin(TopInOutBasePlugin, ABC):
|
||||
"""导入用插件抽象基类-单个音轨"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginType.FUNCTION_IMPORT:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`TrackInputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_IMPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
cls_type=cls.metainfo.type.name,
|
||||
)
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def loadbytes(
|
||||
self, bytes_buffer_in: BinaryIO, config: Optional[PluginConfig]
|
||||
) -> "SingleTrack":
|
||||
"""从字节流加载音符数据到单个音轨"""
|
||||
pass
|
||||
|
||||
def load(self, file_path: Path, config: Optional[PluginConfig]) -> "SingleTrack":
|
||||
"""从文件加载音符数据到单个音轨"""
|
||||
with file_path.open("rb") as f:
|
||||
return self.loadbytes(f, config)
|
||||
|
||||
|
||||
class MusicOperatePlugin(TopBasePlugin, ABC):
|
||||
"""音乐处理用插件抽象基类-完整曲目"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginType.FUNCTION_OPERATE:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`MusicOperatePlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_OPERATE`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
cls_type=cls.metainfo.type.name,
|
||||
)
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def process(
|
||||
self, data: "SingleMusic", config: Optional[PluginConfig]
|
||||
) -> "SingleMusic":
|
||||
"""处理完整曲目的数据"""
|
||||
pass
|
||||
|
||||
|
||||
class TrackOperatePlugin(TopBasePlugin, ABC):
|
||||
"""音乐处理用插件抽象基类-单个音轨"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginType.FUNCTION_OPERATE:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`TrackOperatePlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_OPERATE`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
cls_type=cls.metainfo.type.name,
|
||||
)
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def process(
|
||||
self, data: "SingleTrack", config: Optional[PluginConfig]
|
||||
) -> "SingleTrack":
|
||||
"""处理单个音轨的音符数据"""
|
||||
pass
|
||||
|
||||
|
||||
class MusicOutputPlugin(TopInOutBasePlugin, ABC):
|
||||
"""导出用插件的抽象基类-完整曲目"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginType.FUNCTION_EXPORT:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`MusicOutputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_EXPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
cls_type=cls.metainfo.type.name,
|
||||
)
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def dumpbytes(
|
||||
self, data: "SingleMusic", config: Optional[PluginConfig]
|
||||
) -> BinaryIO:
|
||||
"""将完整曲目导出为对应格式的字节流"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def dump(
|
||||
self, data: "SingleMusic", file_path: Path, config: Optional[PluginConfig]
|
||||
):
|
||||
"""将完整曲目导出为对应格式的文件"""
|
||||
pass
|
||||
|
||||
|
||||
class TrackOutputPlugin(TopInOutBasePlugin, ABC):
|
||||
"""导出用插件的抽象基类-单个音轨"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginType.FUNCTION_EXPORT:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`TrackOutputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_EXPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
cls_type=cls.metainfo.type.name,
|
||||
)
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def dumpbytes(
|
||||
self, data: "SingleTrack", config: Optional[PluginConfig]
|
||||
) -> BinaryIO:
|
||||
"""将单个音轨导出为对应格式的字节流"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def dump(
|
||||
self, data: "SingleTrack", file_path: Path, config: Optional[PluginConfig]
|
||||
):
|
||||
"""将单个音轨导出为对应格式的文件"""
|
||||
pass
|
||||
|
||||
|
||||
class ServicePlugin(TopBasePlugin, ABC):
|
||||
"""服务插件抽象基类"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginType.SERVICE:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`ServicePlugin`继承的,该类的子类应当为一个`PluginType.SERVICE`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
cls_type=cls.metainfo.type.name,
|
||||
)
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def serve(self, config: Optional[PluginConfig], *args) -> None:
|
||||
"""服务插件的运行逻辑"""
|
||||
pass
|
||||
|
||||
|
||||
class LibraryPlugin(TopBasePlugin, ABC):
|
||||
"""插件依赖库的抽象基类"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginType.LIBRARY:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`LibraryPlugin`继承的,该类的子类应当为一个`PluginType.LIBRARY`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
cls_type=cls.metainfo.type.name,
|
||||
)
|
||||
)
|
||||
|
||||
# 怎么?
|
||||
# 插件的彼此依赖就不需要什么调用了吧
|
||||
@@ -44,6 +44,9 @@ MIDI_PAN = "pan"
|
||||
"""Midi通道立体声场偏移"""
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Midi用对照表
|
||||
|
||||
MIDI_DEFAULT_VOLUME_VALUE: int = (
|
||||
|
||||
@@ -16,7 +16,13 @@ Terms & Conditions: License.md in the root directory
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
# “
|
||||
# 把代码 洒落在这里
|
||||
# 和音符 留下的沙砾
|
||||
# 一点一点爬进你类定义的缝隙
|
||||
# ” —— 乐曲访问 by resnah
|
||||
|
||||
import heapq
|
||||
from math import sin, cos, asin, radians, degrees, sqrt, atan, inf, ceil
|
||||
from dataclasses import dataclass
|
||||
from typing import (
|
||||
@@ -33,12 +39,16 @@ from typing import (
|
||||
Iterable,
|
||||
Iterator,
|
||||
Literal,
|
||||
Hashable,
|
||||
TypeVar,
|
||||
)
|
||||
from enum import Enum
|
||||
|
||||
from .exceptions import SingleNoteDecodeError, ParameterTypeError
|
||||
from .exceptions import SingleNoteDecodeError, ParameterTypeError, ParameterValueError
|
||||
from .paramcurve import ParamCurve
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class SoundAtmos:
|
||||
"""声源方位类"""
|
||||
@@ -411,6 +421,9 @@ class SingleTrack(List[SingleNote]):
|
||||
track_name: str
|
||||
"""轨道之名称"""
|
||||
|
||||
is_enabled: bool
|
||||
"""该音轨是否启用"""
|
||||
|
||||
track_instrument: str
|
||||
"""乐器ID"""
|
||||
|
||||
@@ -440,12 +453,16 @@ class SingleTrack(List[SingleNote]):
|
||||
precise_time: bool = True,
|
||||
percussion: bool = False,
|
||||
sound_direction: SoundAtmos = SoundAtmos(),
|
||||
enabled: bool = True,
|
||||
extra_information: Dict[str, Any] = {},
|
||||
*args: SingleNote,
|
||||
):
|
||||
self.track_name = name
|
||||
"""音轨名称"""
|
||||
|
||||
self.is_enabled = enabled
|
||||
"""音轨启用情况"""
|
||||
|
||||
self.track_instrument = instrument
|
||||
"""乐器ID"""
|
||||
|
||||
@@ -494,27 +511,40 @@ class SingleTrack(List[SingleNote]):
|
||||
)
|
||||
)
|
||||
super().append(item)
|
||||
super().sort()
|
||||
super().sort() # =========================== TODO 需要优化
|
||||
|
||||
def update(self, items: Iterable[SingleNote]):
|
||||
"""
|
||||
拼接两个音轨
|
||||
"""
|
||||
super().extend(items)
|
||||
super().sort()
|
||||
super().sort() # =========================== TODO 需要优化
|
||||
|
||||
def get(self, time: int) -> Iterator[SingleNote]:
|
||||
def get(self, time: int) -> Generator[SingleNote, None, None]:
|
||||
"""通过开始时间来获取音符"""
|
||||
|
||||
return filter(lambda x: x.start_time == time, self)
|
||||
return (x for x in self if x.start_time == time)
|
||||
|
||||
def get_range(
|
||||
def get_notes(
|
||||
self, start_time: float, end_time: float = inf
|
||||
) -> Iterator[SingleNote]:
|
||||
) -> Generator[SingleNote, None, None]:
|
||||
"""通过开始时间和结束时间来获取音符"""
|
||||
|
||||
return filter(
|
||||
lambda x: (x.start_time >= start_time) and (x.start_time <= end_time), self
|
||||
if end_time < start_time:
|
||||
raise ParameterValueError(
|
||||
"获取音符的时间范围有误,终止时间`{}`早于起始时间`{}`".format(
|
||||
end_time, start_time
|
||||
)
|
||||
)
|
||||
elif start_time < 0 or end_time < 0:
|
||||
raise ParameterValueError(
|
||||
"获取音符的时间范围有误,终止时间`{}`和起始时间`{}`皆不可为负数".format(
|
||||
end_time, start_time
|
||||
)
|
||||
)
|
||||
return (
|
||||
x
|
||||
for x in self
|
||||
if (x.start_time >= start_time) and (x.start_time <= end_time)
|
||||
)
|
||||
|
||||
def get_minenotes(
|
||||
@@ -522,7 +552,7 @@ class SingleTrack(List[SingleNote]):
|
||||
) -> Generator[MineNote, Any, None]:
|
||||
"""获取能够用以在我的世界播放的音符数据类"""
|
||||
|
||||
for _note in self.get_range(range_start_time, range_end_time):
|
||||
for _note in self.get_notes(range_start_time, range_end_time):
|
||||
yield MineNote.from_single_note(
|
||||
note=_note,
|
||||
note_instrument=self.track_instrument,
|
||||
@@ -626,9 +656,107 @@ class SingleMusic(List[SingleTrack]):
|
||||
return len(self)
|
||||
|
||||
@property
|
||||
def music_tracks(self) -> List[SingleTrack]:
|
||||
"""音轨列表"""
|
||||
return self
|
||||
def music_tracks(self) -> Iterator[SingleTrack]:
|
||||
"""音轨列表,不包含被禁用的音轨"""
|
||||
return (track for track in self if track.is_enabled)
|
||||
|
||||
@staticmethod
|
||||
def yield_from_tracks(
|
||||
tracks: Sequence[Iterator[T]],
|
||||
sort_key: Callable[[T], Any],
|
||||
is_subseq_sorted: bool = True,
|
||||
) -> Iterator[T]:
|
||||
"""从任意迭代器列表迭代符合顺序的元素
|
||||
(惰性多路归并多个迭代器,按 sort_key 排序)
|
||||
|
||||
参数
|
||||
----
|
||||
tracks: Sequence[Iterator[T]]
|
||||
迭代器列表
|
||||
sort_key: Callable[[T], Any]
|
||||
接受 T 元素,返回可比较的键
|
||||
is_subseq_sorted: bool = True
|
||||
子序列是否已排序
|
||||
|
||||
迭代
|
||||
----
|
||||
归并后的每个元素,按 sort_key 升序
|
||||
"""
|
||||
if is_subseq_sorted:
|
||||
return heapq.merge(*tracks, key=sort_key)
|
||||
else:
|
||||
# 初始化堆
|
||||
heap_pool: List[Tuple[Any, int, T]] = []
|
||||
for _index, _track in enumerate(tracks):
|
||||
try:
|
||||
item = next(_track)
|
||||
heapq.heappush(heap_pool, (sort_key(item), _index, item))
|
||||
except StopIteration:
|
||||
continue
|
||||
|
||||
# 归并主循环
|
||||
while heap_pool:
|
||||
_key, _index, item = heapq.heappop(heap_pool)
|
||||
yield item
|
||||
try:
|
||||
next_item = next(tracks[_index])
|
||||
heapq.heappush(heap_pool, (sort_key(next_item), _index, next_item))
|
||||
except StopIteration:
|
||||
pass
|
||||
# NEVER REACH:
|
||||
# pool: List[Tuple[str, T]] = []
|
||||
# remove_track: List[str] = []
|
||||
# for _name, _track in tracks.items():
|
||||
# try:
|
||||
# pool.append((_name, next(_track)))
|
||||
# except StopIteration:
|
||||
# remove_track.append(_name)
|
||||
# for _x in remove_track:
|
||||
# tracks.pop(_x)
|
||||
# del remove_track
|
||||
# while tracks and pool:
|
||||
# yield (_x := min(pool, key=sort_key))[1]
|
||||
# try:
|
||||
# pool.append((_x[0], next(tracks[_x[0]])))
|
||||
# except StopIteration:
|
||||
# tracks.pop(_x[0])
|
||||
# pool.sort(key=sort_key)
|
||||
# for _remain in pool:
|
||||
# yield _remain[1]
|
||||
|
||||
def get_tracked_notes(
|
||||
self, start_time: float, end_time: float = inf
|
||||
) -> Generator[Iterator[SingleNote], Any, None]:
|
||||
"""获取指定时间段的各个音轨的音符数据"""
|
||||
return (track.get_notes(start_time, end_time) for track in self)
|
||||
|
||||
def get_tracked_minenotes(
|
||||
self, start_time: float, end_time: float = inf
|
||||
) -> Generator[Iterator[MineNote], Any, None]:
|
||||
"""获取指定时间段的各个音轨的,供我的世界播放的音符数据类"""
|
||||
return (track.get_minenotes(start_time, end_time) for track in self)
|
||||
|
||||
def get_notes(
|
||||
self, start_time: float, end_time: float = inf
|
||||
) -> Iterator[SingleNote]:
|
||||
"""获取指定时间段的所有音符数据,按照时间顺序"""
|
||||
if self.track_amount == 0:
|
||||
return iter(())
|
||||
return self.yield_from_tracks(
|
||||
[track.get_notes(start_time, end_time) for track in self],
|
||||
sort_key=lambda x: x.start_time,
|
||||
)
|
||||
|
||||
def get_minenotes(
|
||||
self, start_time: float, end_time: float = inf
|
||||
) -> Generator[MineNote, Any, None]:
|
||||
"""获取指定时间段所有的,供我的世界播放的音符数据类,按照时间顺序"""
|
||||
if self.track_amount == 0:
|
||||
return
|
||||
yield from self.yield_from_tracks(
|
||||
[track.get_minenotes(start_time, end_time) for track in self],
|
||||
sort_key=lambda x: x.start_tick,
|
||||
)
|
||||
|
||||
def set_info(self, key: Union[str, Sequence[str]], value: Any):
|
||||
"""设置附加信息"""
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2025 金羿 & 玉衡
|
||||
Copyright © 2025 Eilles & Alioth
|
||||
版权所有 © 2025 金羿 & 玉衡Alioth
|
||||
Copyright © 2025 Eilles & YuhengAlioth
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
@@ -16,6 +16,10 @@ Terms & Conditions: License.md in the root directory
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
# “
|
||||
# There are planty of "exception"s in this library
|
||||
# for I know I will always go with my heart.
|
||||
# ” —— Cyberdevil by resnah
|
||||
|
||||
|
||||
class MusicreaterBaseException(Exception):
|
||||
|
||||
80
Musicreater/main.py
Normal file
80
Musicreater/main.py
Normal file
@@ -0,0 +1,80 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
"""
|
||||
音·创
|
||||
是一款免费开源的《我的世界》数字音频支持库。
|
||||
|
||||
Musicreater (音·创)
|
||||
A free and open-source library for handling with **Minecraft** digital music.
|
||||
|
||||
版权所有 © 2026 睿乐组织
|
||||
Copyright © 2026 TriM-Organization
|
||||
|
||||
音·创(“本项目”)的协议颁发者为 金羿、玉衡Alioth
|
||||
The Licensor of Musicreater("this project") is Eilles, YuhengAlioth
|
||||
|
||||
本项目根据 第一版 汉钰律许可协议(“本协议”)授权。
|
||||
任何人皆可从以下地址获得本协议副本:
|
||||
https://gitee.com/TriM-Organization/Musicreater/blob/master/LICENSE.md。
|
||||
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,
|
||||
不予提供任何形式的担保、任何明示、任何暗示或类似承诺。
|
||||
也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。
|
||||
详细的准许和限制条款请见原协议文本。
|
||||
"""
|
||||
|
||||
# 音·创 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库根目录下的 License.md
|
||||
|
||||
|
||||
# BUG退散!BUG退散!
|
||||
# 异常与错误作乱之时
|
||||
# 二六字组!万国码合!二六字组!万国码合!
|
||||
# 赶快呼叫 程序员!Let's Go!
|
||||
|
||||
# BUG退散!BUG退散!
|
||||
# 異常、誤りが、困った時は
|
||||
# パラメータ メソッド!パラメータ メソッド!
|
||||
# 助けてもらおう、開発者!レッツゴー!
|
||||
|
||||
# Bug retreat! Bug retreat!
|
||||
# Exceptions and errors are causing chaos
|
||||
# Words combine! Codes unite!
|
||||
# Hurry to call the programmer! Let's Go!
|
||||
|
||||
|
||||
from typing import Dict, Generator, List, Optional, Tuple, Union
|
||||
from pathlib import Path
|
||||
|
||||
from .data import SingleMusic, SingleTrack
|
||||
from ._plugin_abc import TopBasePlugin
|
||||
from .plugins import __global_plugin_registry, PluginRegistry
|
||||
|
||||
|
||||
class MusiCreater:
|
||||
"""
|
||||
音·创 v3 主要控制类
|
||||
另:“创建者”一词的英文应该是“Creator”
|
||||
"""
|
||||
|
||||
__plugin_registry: PluginRegistry
|
||||
"""插件注册表实例"""
|
||||
_plugin_cache: Dict[str, TopBasePlugin]
|
||||
"""插件缓存字典,插件名为键、插件实例为值"""
|
||||
music: SingleMusic
|
||||
"""当前曲目实例"""
|
||||
|
||||
def __init__(self, whole_music: SingleMusic) -> None:
|
||||
global __global_plugin_registry
|
||||
|
||||
self.__plugin_registry = __global_plugin_registry
|
||||
|
||||
self._plugin_cache = {}
|
||||
|
||||
self.music = whole_music
|
||||
|
||||
|
||||
|
||||
|
||||
def import_music(self, file_path: Path, plugin_name: Optional[str] = None) -> SingleMusic:
|
||||
144
Musicreater/old_init.py
Normal file
144
Musicreater/old_init.py
Normal file
@@ -0,0 +1,144 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""一个简单的我的世界音频转换库
|
||||
音·创 (Musicreater)
|
||||
是一款免费开源的《我的世界》数字音频支持库。
|
||||
Musicreater(音·创)
|
||||
A free open source library used for dealing with **Minecraft** digital musics.
|
||||
|
||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
||||
Copyright © 2025 Eilles & bgArray
|
||||
|
||||
音·创(“本项目”)的协议颁发者为 金羿、诸葛亮与八卦阵
|
||||
The Licensor of Musicreater("this project") is Eilles, bgArray.
|
||||
|
||||
本项目根据 第一版 汉钰律许可协议(“本协议”)授权。
|
||||
任何人皆可从以下地址获得本协议副本:https://gitee.com/EillesWan/YulvLicenses。
|
||||
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,不予提供任何形式的担保、任何明示、任何暗示或类似承诺。也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。
|
||||
详细的准许和限制条款请见原协议文本。
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
|
||||
__version__ = "2.4.2.3"
|
||||
__vername__ = "音符附加信息升级"
|
||||
__author__ = (
|
||||
("金羿", "Eilles"),
|
||||
("诸葛亮与八卦阵", "bgArray"),
|
||||
("鱼旧梦", "ElapsingDreams"),
|
||||
("偷吃不是Touch", "Touch"),
|
||||
)
|
||||
__all__ = [
|
||||
# 主要类
|
||||
"MusicSequence",
|
||||
"MidiConvert",
|
||||
# 附加类
|
||||
# "SingleNote",
|
||||
"MineNote",
|
||||
"MineCommand",
|
||||
"SingleNoteBox",
|
||||
"ProgressBarStyle",
|
||||
# "TimeStamp", 未来功能
|
||||
# 字典键
|
||||
"MIDI_PROGRAM",
|
||||
"MIDI_VOLUME",
|
||||
"MIDI_PAN",
|
||||
# 默认值
|
||||
"MIDI_DEFAULT_PROGRAM_VALUE",
|
||||
"MIDI_DEFAULT_VOLUME_VALUE",
|
||||
"DEFAULT_PROGRESSBAR_STYLE",
|
||||
# Midi 自己的对照表
|
||||
"MIDI_PITCH_NAME_TABLE",
|
||||
"MIDI_PITCHED_NOTE_NAME_GROUP",
|
||||
"MIDI_PITCHED_NOTE_NAME_TABLE",
|
||||
"MIDI_PERCUSSION_NOTE_NAME_TABLE",
|
||||
# Minecraft 自己的对照表
|
||||
"MC_PERCUSSION_INSTRUMENT_LIST",
|
||||
"MC_PITCHED_INSTRUMENT_LIST",
|
||||
"MC_INSTRUMENT_BLOCKS_TABLE",
|
||||
"MC_EILLES_RTJE12_INSTRUMENT_REPLACE_TABLE",
|
||||
"MC_EILLES_RTBETA_INSTRUMENT_REPLACE_TABLE",
|
||||
# Midi 与 游戏 的对照表
|
||||
"MM_INSTRUMENT_RANGE_TABLE",
|
||||
"MM_INSTRUMENT_DEVIATION_TABLE",
|
||||
"MM_CLASSIC_PITCHED_INSTRUMENT_TABLE",
|
||||
"MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE",
|
||||
"MM_TOUCH_PITCHED_INSTRUMENT_TABLE",
|
||||
"MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE",
|
||||
"MM_DISLINK_PITCHED_INSTRUMENT_TABLE",
|
||||
"MM_DISLINK_PERCUSSION_INSTRUMENT_TABLE",
|
||||
"MM_NBS_PITCHED_INSTRUMENT_TABLE",
|
||||
"MM_NBS_PERCUSSION_INSTRUMENT_TABLE",
|
||||
# 操作性函数
|
||||
"velocity_2_distance_natural",
|
||||
"velocity_2_distance_straight",
|
||||
"panning_2_rotation_linear",
|
||||
"panning_2_rotation_trigonometric",
|
||||
# 工具函数
|
||||
"load_decode_musicsequence_metainfo",
|
||||
"load_decode_msq_flush_release",
|
||||
"load_decode_fsq_flush_release",
|
||||
"guess_deviation",
|
||||
"mctick2timestr",
|
||||
"midi_inst_to_mc_sound",
|
||||
]
|
||||
|
||||
from .old_main import MusicSequence, MidiConvert
|
||||
|
||||
from .subclass import (
|
||||
MineNote,
|
||||
MineCommand,
|
||||
SingleNoteBox,
|
||||
ProgressBarStyle,
|
||||
mctick2timestr,
|
||||
DEFAULT_PROGRESSBAR_STYLE,
|
||||
)
|
||||
|
||||
from .utils import (
|
||||
# 兼容性函数
|
||||
load_decode_musicsequence_metainfo,
|
||||
load_decode_msq_flush_release,
|
||||
load_decode_fsq_flush_release,
|
||||
# 工具函数
|
||||
guess_deviation,
|
||||
midi_inst_to_mc_sound,
|
||||
# 处理用函数
|
||||
velocity_2_distance_natural,
|
||||
velocity_2_distance_straight,
|
||||
panning_2_rotation_linear,
|
||||
panning_2_rotation_trigonometric,
|
||||
)
|
||||
|
||||
from .constants import (
|
||||
# 字典键
|
||||
MIDI_PROGRAM,
|
||||
MIDI_PAN,
|
||||
MIDI_VOLUME,
|
||||
# 默认值
|
||||
MIDI_DEFAULT_PROGRAM_VALUE,
|
||||
MIDI_DEFAULT_VOLUME_VALUE,
|
||||
# MIDI 表
|
||||
MIDI_PITCH_NAME_TABLE,
|
||||
MIDI_PITCHED_NOTE_NAME_GROUP,
|
||||
MIDI_PITCHED_NOTE_NAME_TABLE,
|
||||
MIDI_PERCUSSION_NOTE_NAME_TABLE,
|
||||
# 我的世界 表
|
||||
MC_EILLES_RTBETA_INSTRUMENT_REPLACE_TABLE,
|
||||
MC_EILLES_RTJE12_INSTRUMENT_REPLACE_TABLE,
|
||||
MC_INSTRUMENT_BLOCKS_TABLE,
|
||||
MC_PERCUSSION_INSTRUMENT_LIST,
|
||||
MC_PITCHED_INSTRUMENT_LIST,
|
||||
# MIDI 到 我的世界 表
|
||||
MM_INSTRUMENT_RANGE_TABLE,
|
||||
MM_INSTRUMENT_DEVIATION_TABLE,
|
||||
MM_CLASSIC_PITCHED_INSTRUMENT_TABLE,
|
||||
MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE,
|
||||
MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
|
||||
MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
||||
MM_DISLINK_PITCHED_INSTRUMENT_TABLE,
|
||||
MM_DISLINK_PERCUSSION_INSTRUMENT_TABLE,
|
||||
MM_NBS_PITCHED_INSTRUMENT_TABLE,
|
||||
MM_NBS_PERCUSSION_INSTRUMENT_TABLE,
|
||||
)
|
||||
@@ -1,783 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
存储 音·创 v3 的插件接口与管理相关,提供抽象基类以供其他插件使用
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2025 金羿
|
||||
Copyright © 2025 Eilles
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
"""
|
||||
|
||||
# 睿乐组织 开发交流群 861684859
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
# =====================
|
||||
# NOTE: [WARNING]
|
||||
# 这个文件是一坨屎山代码
|
||||
# 请勿模仿,请多包容
|
||||
# =====================
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
from abc import ABC, abstractmethod, ABCMeta
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import (
|
||||
Dict,
|
||||
Any,
|
||||
Optional,
|
||||
List,
|
||||
Tuple,
|
||||
Union,
|
||||
Sequence,
|
||||
BinaryIO,
|
||||
Generator,
|
||||
Iterator,
|
||||
Set,
|
||||
)
|
||||
from itertools import chain
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
import tomllib
|
||||
import tomli_w
|
||||
else:
|
||||
import tomli as tomllib # 第三方包
|
||||
import tomli_w
|
||||
|
||||
from .exceptions import (
|
||||
PluginConfigDumpError,
|
||||
PluginConfigLoadError,
|
||||
PluginMetainfoNotFoundError,
|
||||
PluginMetainfoTypeError,
|
||||
PluginMetainfoValueError,
|
||||
PluginAttributeNotFoundError,
|
||||
ParameterTypeError,
|
||||
PluginInstanceNotFoundError,
|
||||
)
|
||||
from .data import SingleMusic, SingleTrack
|
||||
|
||||
__all__ = [
|
||||
# 枚举类
|
||||
"PluginType",
|
||||
# 抽象基类/数据类(插件参数定义)
|
||||
"PluginConfig",
|
||||
"PluginMetaInformation",
|
||||
# 抽象基类(插件定义)
|
||||
"MusicInputPlugin",
|
||||
"TrackInputPlugin",
|
||||
"MusicOperatePlugin",
|
||||
"TrackOperatePlugin",
|
||||
"MusicOutputPlugin",
|
||||
"TrackOutputPlugin",
|
||||
"ServicePlugin",
|
||||
"LibraryPlugin",
|
||||
# 插件注册用装饰函数
|
||||
"music_input_plugin",
|
||||
"track_input_plugin",
|
||||
"music_operate_plugin",
|
||||
"track_operate_plugin",
|
||||
"music_output_plugin",
|
||||
"track_output_plugin",
|
||||
"service_plugin",
|
||||
"library_plugin",
|
||||
# 全局插件注册表
|
||||
"plugin_registry",
|
||||
]
|
||||
|
||||
|
||||
@dataclass
|
||||
class PluginConfig(ABC):
|
||||
"""插件配置基类"""
|
||||
|
||||
def to_dict(self) -> 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":
|
||||
"""从字典创建配置实例"""
|
||||
# 只保留类中定义的字段
|
||||
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:
|
||||
"""保存配置到文件"""
|
||||
if file_path.suffix.upper() == ".TOML":
|
||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
else:
|
||||
raise PluginConfigDumpError(
|
||||
"插件配置文件类型不应为`{}`,须为`TOML`格式。".format(file_path.suffix)
|
||||
)
|
||||
|
||||
try:
|
||||
with file_path.open("wb") as f:
|
||||
tomli_w.dump(self.to_dict(), f, multiline_strings=False, indent=4)
|
||||
except Exception as e:
|
||||
raise PluginConfigDumpError(e)
|
||||
|
||||
@classmethod
|
||||
def load_from_file(cls, file_path: Path) -> "PluginConfig":
|
||||
"""从文件加载配置"""
|
||||
try:
|
||||
with file_path.open("rb") as f:
|
||||
return cls.from_dict(tomllib.load(f))
|
||||
except Exception as e:
|
||||
raise PluginConfigLoadError(e)
|
||||
|
||||
|
||||
class PluginType(str, Enum):
|
||||
"""插件类型枚举"""
|
||||
|
||||
FUNCTION_IMPORT = "import_data"
|
||||
FUNCTION_EXPORT = "export_data"
|
||||
FUNCTION_OPERATE = "data_operate"
|
||||
SERVICE = "service"
|
||||
LIBRARY = "library"
|
||||
|
||||
|
||||
@dataclass
|
||||
class PluginMetaInformation(ABC):
|
||||
"""插件元信息"""
|
||||
|
||||
name: str
|
||||
"""插件名称,应为惟一之名"""
|
||||
author: str
|
||||
"""插件作者"""
|
||||
description: str
|
||||
"""插件简介"""
|
||||
version: Tuple[int, ...]
|
||||
"""插件版本号"""
|
||||
type: PluginType
|
||||
"""插件类型"""
|
||||
license: str = "MIT License"
|
||||
"""插件发布时采用的许可协议"""
|
||||
dependencies: Sequence[str] = []
|
||||
"""插件是否对其他插件存在依赖"""
|
||||
|
||||
|
||||
class TopBasePlugin(ABC):
|
||||
"""所有插件的抽象基类"""
|
||||
|
||||
metainfo: PluginMetaInformation
|
||||
"""插件元信息"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
if hasattr(cls, "metainfo"):
|
||||
if not isinstance(cls.metainfo, PluginMetaInformation):
|
||||
raise PluginMetainfoTypeError(
|
||||
"类`{cls_name}`之属性`metainfo`的类型,必须为`PluginMetaInformation`".format(
|
||||
cls_name=cls.__name__
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise PluginMetainfoNotFoundError(
|
||||
"类`{cls_name}`必须定义一个`metainfo`属性。".format(
|
||||
cls_name=cls.__name__
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class TopInOutBasePlugin(TopBasePlugin, ABC):
|
||||
"""导入导出用抽象基类"""
|
||||
|
||||
supported_formats: Tuple[str, ...] = tuple()
|
||||
"""支持的格式"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if hasattr(cls, "supported_formats"):
|
||||
if cls.supported_formats:
|
||||
# 强制转换为大写,并使用元组
|
||||
cls.supported_formats = tuple(map(str.upper, cls.supported_formats))
|
||||
else:
|
||||
cls.supported_formats = tuple()
|
||||
else:
|
||||
raise PluginAttributeNotFoundError(
|
||||
"用于导入导出数据的类`{cls_name}`必须定义一个`supported_formats`属性。".format(
|
||||
cls_name=cls.__name__
|
||||
)
|
||||
)
|
||||
|
||||
def can_handle_file(self, file_path: Path) -> bool:
|
||||
"""判断是否可处理某个文件"""
|
||||
return file_path.suffix.upper().endswith(self.supported_formats)
|
||||
|
||||
def can_handle_format(self, format_name: str) -> bool:
|
||||
"""判断是否可处理某个格式"""
|
||||
return format_name.upper().endswith(self.supported_formats)
|
||||
|
||||
|
||||
class MusicInputPlugin(TopInOutBasePlugin, ABC):
|
||||
"""导入用插件抽象基类-完整曲目"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginType.FUNCTION_IMPORT:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`MusicInputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_IMPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
cls_type=cls.metainfo.type.name,
|
||||
)
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def loadbytes(
|
||||
self, bytes_buffer_in: BinaryIO, config: Optional[PluginConfig]
|
||||
) -> "SingleMusic":
|
||||
"""从字节流加载数据到完整曲目"""
|
||||
pass
|
||||
|
||||
def load(self, file_path: Path, config: Optional[PluginConfig]) -> "SingleMusic":
|
||||
"""从文件加载数据到完整曲目"""
|
||||
with file_path.open("rb") as f:
|
||||
return self.loadbytes(f, config)
|
||||
|
||||
|
||||
class TrackInputPlugin(TopInOutBasePlugin, ABC):
|
||||
"""导入用插件抽象基类-单个音轨"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginType.FUNCTION_IMPORT:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`TrackInputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_IMPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
cls_type=cls.metainfo.type.name,
|
||||
)
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def loadbytes(
|
||||
self, bytes_buffer_in: BinaryIO, config: Optional[PluginConfig]
|
||||
) -> "SingleTrack":
|
||||
"""从字节流加载音符数据到单个音轨"""
|
||||
pass
|
||||
|
||||
def load(self, file_path: Path, config: Optional[PluginConfig]) -> "SingleTrack":
|
||||
"""从文件加载音符数据到单个音轨"""
|
||||
with file_path.open("rb") as f:
|
||||
return self.loadbytes(f, config)
|
||||
|
||||
|
||||
class MusicOperatePlugin(TopBasePlugin, ABC):
|
||||
"""音乐处理用插件抽象基类-完整曲目"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginType.FUNCTION_OPERATE:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`MusicOperatePlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_OPERATE`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
cls_type=cls.metainfo.type.name,
|
||||
)
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def process(
|
||||
self, data: "SingleMusic", config: Optional[PluginConfig]
|
||||
) -> "SingleMusic":
|
||||
"""处理完整曲目的数据"""
|
||||
pass
|
||||
|
||||
|
||||
class TrackOperatePlugin(TopBasePlugin, ABC):
|
||||
"""音乐处理用插件抽象基类-单个音轨"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginType.FUNCTION_OPERATE:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`TrackOperatePlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_OPERATE`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
cls_type=cls.metainfo.type.name,
|
||||
)
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def process(
|
||||
self, data: "SingleTrack", config: Optional[PluginConfig]
|
||||
) -> "SingleTrack":
|
||||
"""处理单个音轨的音符数据"""
|
||||
pass
|
||||
|
||||
|
||||
class MusicOutputPlugin(TopInOutBasePlugin, ABC):
|
||||
"""导出用插件的抽象基类-完整曲目"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginType.FUNCTION_EXPORT:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`MusicOutputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_EXPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
cls_type=cls.metainfo.type.name,
|
||||
)
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def dumpbytes(
|
||||
self, data: "SingleMusic", config: Optional[PluginConfig]
|
||||
) -> BinaryIO:
|
||||
"""将完整曲目导出为对应格式的字节流"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def dump(
|
||||
self, data: "SingleMusic", file_path: Path, config: Optional[PluginConfig]
|
||||
):
|
||||
"""将完整曲目导出为对应格式的文件"""
|
||||
pass
|
||||
|
||||
|
||||
class TrackOutputPlugin(TopInOutBasePlugin, ABC):
|
||||
"""导出用插件的抽象基类-单个音轨"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginType.FUNCTION_EXPORT:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`TrackOutputPlugin`继承的,该类的子类应当为一个`PluginType.FUNCTION_EXPORT`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
cls_type=cls.metainfo.type.name,
|
||||
)
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def dumpbytes(
|
||||
self, data: "SingleTrack", config: Optional[PluginConfig]
|
||||
) -> BinaryIO:
|
||||
"""将单个音轨导出为对应格式的字节流"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def dump(
|
||||
self, data: "SingleTrack", file_path: Path, config: Optional[PluginConfig]
|
||||
):
|
||||
"""将单个音轨导出为对应格式的文件"""
|
||||
pass
|
||||
|
||||
|
||||
class ServicePlugin(TopBasePlugin, ABC):
|
||||
"""服务插件抽象基类"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginType.SERVICE:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`ServicePlugin`继承的,该类的子类应当为一个`PluginType.SERVICE`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
cls_type=cls.metainfo.type.name,
|
||||
)
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def serve(self, config: Optional[PluginConfig], *args) -> None:
|
||||
"""服务插件的运行逻辑"""
|
||||
pass
|
||||
|
||||
|
||||
class LibraryPlugin(TopBasePlugin, ABC):
|
||||
"""插件依赖库的抽象基类"""
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
if cls.metainfo.type != PluginType.LIBRARY:
|
||||
raise PluginMetainfoValueError(
|
||||
"插件类`{cls_name}`是从`LibraryPlugin`继承的,该类的子类应当为一个`PluginType.LIBRARY`类型的插件,而不是`PluginType.{cls_type}`".format(
|
||||
cls_name=cls.__name__,
|
||||
cls_type=cls.metainfo.type.name,
|
||||
)
|
||||
)
|
||||
|
||||
# 怎么?
|
||||
# 插件的彼此依赖就不需要什么调用了吧
|
||||
|
||||
|
||||
class PluginRegistry:
|
||||
"""插件注册管理器"""
|
||||
|
||||
def __init__(self):
|
||||
self._music_input_plugins: List[MusicInputPlugin] = []
|
||||
self._track_input_plugins: List[TrackInputPlugin] = []
|
||||
self._music_operate_plugins: List[MusicOperatePlugin] = []
|
||||
self._track_operate_plugins: List[TrackOperatePlugin] = []
|
||||
self._music_output_plugins: List[MusicOutputPlugin] = []
|
||||
self._track_output_plugins: List[TrackOutputPlugin] = []
|
||||
self._service_plugins: List[ServicePlugin] = []
|
||||
self._library_plugins: List[LibraryPlugin] = []
|
||||
|
||||
def register_music_input_plugin(self, plugin_class: type) -> None:
|
||||
"""注册输入插件-整首曲目"""
|
||||
plugin_instance = plugin_class()
|
||||
self._music_input_plugins.append(plugin_instance)
|
||||
|
||||
def register_track_input_plugin(self, plugin_class: type) -> None:
|
||||
"""注册输入插件-单个音轨"""
|
||||
plugin_instance = plugin_class()
|
||||
self._track_input_plugins.append(plugin_instance)
|
||||
|
||||
def register_music_operate_plugin(self, plugin_class: type) -> None:
|
||||
"""注册曲目处理插件"""
|
||||
plugin_instance = plugin_class()
|
||||
self._music_operate_plugins.append(plugin_instance)
|
||||
|
||||
def register_track_operate_plugin(self, plugin_class: type) -> None:
|
||||
"""注册音轨处理插件"""
|
||||
plugin_instance = plugin_class()
|
||||
self._track_operate_plugins.append(plugin_instance)
|
||||
|
||||
def register_music_output_plugin(self, plugin_class: type) -> None:
|
||||
"""注册输出插件-整首曲目"""
|
||||
plugin_instance = plugin_class()
|
||||
self._music_output_plugins.append(plugin_instance)
|
||||
|
||||
def register_track_output_plugin(self, plugin_class: type) -> None:
|
||||
"""注册输出插件-单个音轨"""
|
||||
plugin_instance = plugin_class()
|
||||
self._track_output_plugins.append(plugin_instance)
|
||||
|
||||
def register_service_plugin(self, plugin_class: type) -> None:
|
||||
"""注册服务插件"""
|
||||
plugin_instance = plugin_class()
|
||||
self._service_plugins.append(plugin_instance)
|
||||
|
||||
def register_library_plugin(self, plugin_class: type) -> None:
|
||||
"""注册支持库插件"""
|
||||
plugin_instance = plugin_class()
|
||||
self._library_plugins.append(plugin_instance)
|
||||
|
||||
def get_music_input_plugin_by_format(
|
||||
self, filepath_or_format: Union[Path, str]
|
||||
) -> Generator[MusicInputPlugin, None, None]:
|
||||
"""通过指定输入的文件或格式,以获取对应的全曲导入用插件"""
|
||||
if isinstance(filepath_or_format, str):
|
||||
for plugin in self._music_input_plugins:
|
||||
if plugin.can_handle_format(filepath_or_format):
|
||||
yield plugin
|
||||
elif isinstance(filepath_or_format, Path):
|
||||
for plugin in self._music_input_plugins:
|
||||
if plugin.can_handle_file(filepath_or_format):
|
||||
yield plugin
|
||||
else:
|
||||
raise ParameterTypeError(
|
||||
"用于指定“导入全曲的数据之类型”的参数,其类型须为`Path`路径或字符串,而非`{}`类型的`{}`值".format(
|
||||
type(filepath_or_format), filepath_or_format
|
||||
)
|
||||
)
|
||||
|
||||
def get_track_input_plugin_by_format(
|
||||
self, filepath_or_format: Union[Path, str]
|
||||
) -> Generator[TrackInputPlugin, None, None]:
|
||||
"""通过指定输入的文件或格式,以获取对应的单音轨导入用插件"""
|
||||
if isinstance(filepath_or_format, str):
|
||||
for plugin in self._track_input_plugins:
|
||||
if plugin.can_handle_format(filepath_or_format):
|
||||
yield plugin
|
||||
elif isinstance(filepath_or_format, Path):
|
||||
for plugin in self._track_input_plugins:
|
||||
if plugin.can_handle_file(filepath_or_format):
|
||||
yield plugin
|
||||
else:
|
||||
raise ParameterTypeError(
|
||||
"用于指定“导入单个音轨的数据之类型”的参数,其类型须为`Path`路径或字符串,而非`{}`类型的`{}`值".format(
|
||||
type(filepath_or_format), filepath_or_format
|
||||
)
|
||||
)
|
||||
|
||||
def get_music_output_plugin_by_format(
|
||||
self, filepath_or_format: Union[Path, str]
|
||||
) -> Generator[MusicOutputPlugin, None, None]:
|
||||
"""通过指定输出的文件或格式,以获取对应的导出全曲用插件"""
|
||||
if isinstance(filepath_or_format, str):
|
||||
for plugin in self._music_output_plugins:
|
||||
if plugin.can_handle_format(filepath_or_format):
|
||||
yield plugin
|
||||
elif isinstance(filepath_or_format, Path):
|
||||
for plugin in self._music_output_plugins:
|
||||
if plugin.can_handle_file(filepath_or_format):
|
||||
yield plugin
|
||||
else:
|
||||
raise ParameterTypeError(
|
||||
"用于指定“全曲数据导出的类型”的参数,其类型须为`Path`路径或字符串,而非`{}`类型的`{}`值".format(
|
||||
type(filepath_or_format), filepath_or_format
|
||||
)
|
||||
)
|
||||
|
||||
def get_track_output_plugin_by_format(
|
||||
self, filepath_or_format: Union[Path, str]
|
||||
) -> Generator[TrackOutputPlugin, None, None]:
|
||||
"""通过指定输出的文件或格式,以获取对应的导出单个音轨用插件"""
|
||||
if isinstance(filepath_or_format, str):
|
||||
for plugin in self._track_output_plugins:
|
||||
if plugin.can_handle_format(filepath_or_format):
|
||||
yield plugin
|
||||
elif isinstance(filepath_or_format, Path):
|
||||
for plugin in self._track_output_plugins:
|
||||
if plugin.can_handle_file(filepath_or_format):
|
||||
yield plugin
|
||||
else:
|
||||
raise ParameterTypeError(
|
||||
"用于指定“单音轨数据导出的类型”的参数,其类型须为`Path`路径或字符串,而非`{}`类型的`{}`值".format(
|
||||
type(filepath_or_format), filepath_or_format
|
||||
)
|
||||
)
|
||||
|
||||
def get_music_input_plugin(self, plugin_name: str) -> MusicInputPlugin:
|
||||
"""获取指定名称的全曲导入用插件,当名称重叠时,取版本号最大的"""
|
||||
try:
|
||||
return max(
|
||||
filter(
|
||||
lambda plugin: plugin.metainfo.name == plugin_name,
|
||||
self._music_input_plugins,
|
||||
),
|
||||
key=lambda plugin: plugin.metainfo.version,
|
||||
)
|
||||
except ValueError:
|
||||
raise PluginInstanceNotFoundError(
|
||||
"未找到“用于导入曲目、名为`{}`”的插件".format(plugin_name)
|
||||
)
|
||||
|
||||
def get_track_input_plugin(self, plugin_name: str) -> TrackInputPlugin:
|
||||
"""获取指定名称的单音轨导入用插件,当名称重叠时,取版本号最大的"""
|
||||
try:
|
||||
return max(
|
||||
filter(
|
||||
lambda plugin: plugin.metainfo.name == plugin_name,
|
||||
self._track_input_plugins,
|
||||
),
|
||||
key=lambda plugin: plugin.metainfo.version,
|
||||
)
|
||||
except ValueError:
|
||||
raise PluginInstanceNotFoundError(
|
||||
"未找到“用于导入单个音轨、名为`{}`”的插件".format(plugin_name)
|
||||
)
|
||||
|
||||
def get_music_operate_plugin(self, plugin_name: str) -> MusicOperatePlugin:
|
||||
"""获取指定名称的全曲处理用插件,当名称重叠时,取版本号最大的"""
|
||||
try:
|
||||
return max(
|
||||
filter(
|
||||
lambda plugin: plugin.metainfo.name == plugin_name,
|
||||
self._music_operate_plugins,
|
||||
),
|
||||
key=lambda plugin: plugin.metainfo.version,
|
||||
)
|
||||
except ValueError:
|
||||
raise PluginInstanceNotFoundError(
|
||||
"未找到“用于处理整个曲目、名为`{}`”的插件".format(plugin_name)
|
||||
)
|
||||
|
||||
def get_track_operate_plugin(self, plugin_name: str) -> TrackOperatePlugin:
|
||||
"""获取指定名称的单音轨处理用插件,当名称重叠时,取版本号最大的"""
|
||||
try:
|
||||
return max(
|
||||
filter(
|
||||
lambda plugin: plugin.metainfo.name == plugin_name,
|
||||
self._track_operate_plugins,
|
||||
),
|
||||
key=lambda plugin: plugin.metainfo.version,
|
||||
)
|
||||
except ValueError:
|
||||
raise PluginInstanceNotFoundError(
|
||||
"未找到“用于处理单个音轨、名为`{}`”的插件".format(plugin_name)
|
||||
)
|
||||
|
||||
def get_music_output_plugin(self, plugin_name: str) -> MusicOutputPlugin:
|
||||
"""获取指定名称的导出全曲用插件,当名称重叠时,取版本号最大的"""
|
||||
try:
|
||||
return max(
|
||||
filter(
|
||||
lambda plugin: plugin.metainfo.name == plugin_name,
|
||||
self._music_output_plugins,
|
||||
),
|
||||
key=lambda plugin: plugin.metainfo.version,
|
||||
)
|
||||
except ValueError:
|
||||
raise PluginMetainfoNotFoundError(
|
||||
"未找到“用于导出完整曲目、名为`{}`”的插件".format(plugin_name)
|
||||
)
|
||||
|
||||
def get_track_output_plugin(self, plugin_name: str) -> TrackOutputPlugin:
|
||||
"""获取指定名称的导出单音轨用插件,当名称重叠时,取版本号最大的"""
|
||||
try:
|
||||
return max(
|
||||
filter(
|
||||
lambda plugin: plugin.metainfo.name == plugin_name,
|
||||
self._track_output_plugins,
|
||||
),
|
||||
key=lambda plugin: plugin.metainfo.version,
|
||||
)
|
||||
except ValueError:
|
||||
raise PluginMetainfoNotFoundError(
|
||||
"未找到“用于导出单个音轨、名为`{}`”的插件".format(plugin_name)
|
||||
)
|
||||
|
||||
def get_service_plugin(self, plugin_name: str) -> ServicePlugin:
|
||||
"""获取服务用插件,当名称重叠时,取版本号最大的"""
|
||||
try:
|
||||
return max(
|
||||
filter(
|
||||
lambda plugin: plugin.metainfo.name == plugin_name,
|
||||
self._service_plugins,
|
||||
),
|
||||
key=lambda plugin: plugin.metainfo.version,
|
||||
)
|
||||
except ValueError:
|
||||
raise PluginInstanceNotFoundError(
|
||||
"未找到名为`{}`的服务用插件".format(plugin_name)
|
||||
)
|
||||
|
||||
def get_library_plugin(self, plugin_name: str) -> LibraryPlugin:
|
||||
"""获取依赖库类插件,当名称重叠时,取版本号最高的"""
|
||||
try:
|
||||
return max(
|
||||
filter(
|
||||
lambda plugin: plugin.metainfo.name == plugin_name,
|
||||
self._library_plugins,
|
||||
),
|
||||
key=lambda plugin: plugin.metainfo.version,
|
||||
)
|
||||
except ValueError:
|
||||
raise PluginInstanceNotFoundError(
|
||||
"未找到名为`{}`的依赖库插件".format(plugin_name)
|
||||
)
|
||||
|
||||
def supported_input_formats(self) -> Set[str]:
|
||||
"""所有支持的导入格式"""
|
||||
return set(
|
||||
chain.from_iterable(
|
||||
plugin.supported_formats
|
||||
for plugin in chain(
|
||||
self._music_input_plugins, self._track_input_plugins
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def supported_output_formats(self) -> Set[str]:
|
||||
"""所有支持的导出格式"""
|
||||
return set(
|
||||
chain.from_iterable(
|
||||
plugin.supported_formats
|
||||
for plugin in chain(
|
||||
self._music_output_plugins, self._track_output_plugins
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
plugin_registry = PluginRegistry()
|
||||
"""全局插件注册表实例"""
|
||||
|
||||
|
||||
def music_input_plugin(metainfo: PluginMetaInformation):
|
||||
"""全曲输入用插件装饰器"""
|
||||
|
||||
def decorator(cls):
|
||||
global plugin_registry
|
||||
cls.metainfo = metainfo
|
||||
plugin_registry.register_music_input_plugin(cls)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def track_input_plugin(metainfo: PluginMetaInformation):
|
||||
"""单轨输入用插件装饰器"""
|
||||
|
||||
def decorator(cls):
|
||||
global plugin_registry
|
||||
cls.metainfo = metainfo
|
||||
plugin_registry.register_track_input_plugin(cls)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def music_operate_plugin(metainfo: PluginMetaInformation):
|
||||
"""全曲处理用插件装饰器"""
|
||||
|
||||
def decorator(cls):
|
||||
global plugin_registry
|
||||
cls.metainfo = metainfo
|
||||
plugin_registry.register_music_operate_plugin(cls)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def track_operate_plugin(metainfo: PluginMetaInformation):
|
||||
"""音轨处理插件装饰器"""
|
||||
|
||||
def decorator(cls):
|
||||
global plugin_registry
|
||||
cls.metainfo = metainfo
|
||||
plugin_registry.register_track_operate_plugin(cls)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def music_output_plugin(metainfo: PluginMetaInformation):
|
||||
"""乐曲输出用插件装饰器"""
|
||||
|
||||
def decorator(cls):
|
||||
global plugin_registry
|
||||
cls.metainfo = metainfo
|
||||
plugin_registry.register_music_output_plugin(cls)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def track_output_plugin(metainfo: PluginMetaInformation):
|
||||
"""音轨输出用插件装饰器"""
|
||||
|
||||
def decorator(cls):
|
||||
global plugin_registry
|
||||
cls.metainfo = metainfo
|
||||
plugin_registry.register_track_output_plugin(cls)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def service_plugin(metainfo: PluginMetaInformation):
|
||||
"""服务插件装饰器"""
|
||||
|
||||
def decorator(cls):
|
||||
global plugin_registry
|
||||
cls.metainfo = metainfo
|
||||
plugin_registry.register_service_plugin(cls)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def library_plugin(metainfo: PluginMetaInformation):
|
||||
"""支持库插件装饰器"""
|
||||
|
||||
def decorator(cls):
|
||||
global plugin_registry
|
||||
cls.metainfo = metainfo
|
||||
plugin_registry.register_library_plugin(cls)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
存储 音·创 v3 的插件管理和上层设计内容
|
||||
存储 音·创 v3 的插件接口与管理相关内容
|
||||
"""
|
||||
|
||||
"""
|
||||
@@ -16,7 +16,471 @@ Terms & Conditions: License.md in the root directory
|
||||
# Email TriM-Organization@hotmail.com
|
||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||
|
||||
from typing import List, Optional, Dict, Generator, Any
|
||||
from pathlib import Path
|
||||
|
||||
from .plugin import MusicInputPlugin, MusicOperatePlugin, MusicOutputPlugin, TrackInputPlugin, TrackOperatePlugin, TrackOutputPlugin, ServicePlugin, LibraryPlugin
|
||||
import importlib
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, Optional, List, Tuple, Union, Generator, Set
|
||||
from itertools import chain
|
||||
|
||||
|
||||
from ._plugin_abc import (
|
||||
# 枚举类
|
||||
PluginType,
|
||||
# 抽象基类/数据类(插件参数定义)
|
||||
PluginConfig,
|
||||
PluginMetaInformation,
|
||||
# 抽象基类(插件定义)
|
||||
MusicInputPlugin,
|
||||
TrackInputPlugin,
|
||||
MusicOperatePlugin,
|
||||
TrackOperatePlugin,
|
||||
MusicOutputPlugin,
|
||||
TrackOutputPlugin,
|
||||
ServicePlugin,
|
||||
LibraryPlugin,
|
||||
)
|
||||
from .exceptions import (
|
||||
PluginMetainfoNotFoundError,
|
||||
ParameterTypeError,
|
||||
PluginInstanceNotFoundError,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
# 枚举类
|
||||
"PluginType",
|
||||
# 抽象基类/数据类(插件参数定义)
|
||||
"PluginConfig",
|
||||
"PluginMetaInformation",
|
||||
# 抽象基类(插件定义)
|
||||
"MusicInputPlugin",
|
||||
"TrackInputPlugin",
|
||||
"MusicOperatePlugin",
|
||||
"TrackOperatePlugin",
|
||||
"MusicOutputPlugin",
|
||||
"TrackOutputPlugin",
|
||||
"ServicePlugin",
|
||||
"LibraryPlugin",
|
||||
# 插件注册用装饰函数
|
||||
"music_input_plugin",
|
||||
"track_input_plugin",
|
||||
"music_operate_plugin",
|
||||
"track_operate_plugin",
|
||||
"music_output_plugin",
|
||||
"track_output_plugin",
|
||||
"service_plugin",
|
||||
"library_plugin",
|
||||
]
|
||||
|
||||
|
||||
def load_plugin_module(package: Union[Path, str]):
|
||||
"""自动发现并加载插件包中的插件
|
||||
|
||||
参数:
|
||||
=====
|
||||
package: Path | str, 可选
|
||||
插件包路径或名称,当为 Path 类时为路径,为 str 时为包名,切勿混淆。
|
||||
"""
|
||||
|
||||
if isinstance(package, Path):
|
||||
relative_path = package.resolve().relative_to(Path.cwd().resolve())
|
||||
if relative_path.stem == "__init__":
|
||||
return importlib.import_module(".".join(relative_path.parts[:-1]))
|
||||
else:
|
||||
return importlib.import_module(
|
||||
".".join(relative_path.parts[:-1] + (relative_path.stem,))
|
||||
)
|
||||
else:
|
||||
return importlib.import_module(package)
|
||||
|
||||
|
||||
class PluginRegistry:
|
||||
"""插件注册管理器(注册表)"""
|
||||
|
||||
def __init__(self):
|
||||
# 实际上在纵容那些有着同样名称的插件……
|
||||
# (不用 Dict[str`plugin name`, PluginClass`] 的形式)
|
||||
# 啊,我真的很高尚
|
||||
# 你真的不会把插件注册两遍吧……对吧?
|
||||
self._music_input_plugins: Set[MusicInputPlugin] = set()
|
||||
self._track_input_plugins: Set[TrackInputPlugin] = set()
|
||||
self._music_operate_plugins: Set[MusicOperatePlugin] = set()
|
||||
self._track_operate_plugins: Set[TrackOperatePlugin] = set()
|
||||
self._music_output_plugins: Set[MusicOutputPlugin] = set()
|
||||
self._track_output_plugins: Set[TrackOutputPlugin] = set()
|
||||
self._service_plugins: Set[ServicePlugin] = set()
|
||||
self._library_plugins: Set[LibraryPlugin] = set()
|
||||
|
||||
def register_music_input_plugin(self, plugin_class: type) -> None:
|
||||
"""注册输入插件-整首曲目"""
|
||||
self._music_input_plugins.add(plugin_class())
|
||||
|
||||
def register_track_input_plugin(self, plugin_class: type) -> None:
|
||||
"""注册输入插件-单个音轨"""
|
||||
self._track_input_plugins.add(plugin_class())
|
||||
|
||||
def register_music_operate_plugin(self, plugin_class: type) -> None:
|
||||
"""注册曲目处理插件"""
|
||||
self._music_operate_plugins.add(plugin_class())
|
||||
|
||||
def register_track_operate_plugin(self, plugin_class: type) -> None:
|
||||
"""注册音轨处理插件"""
|
||||
self._track_operate_plugins.add(plugin_class())
|
||||
|
||||
def register_music_output_plugin(self, plugin_class: type) -> None:
|
||||
"""注册输出插件-整首曲目"""
|
||||
self._music_output_plugins.add(plugin_class())
|
||||
|
||||
def register_track_output_plugin(self, plugin_class: type) -> None:
|
||||
"""注册输出插件-单个音轨"""
|
||||
self._track_output_plugins.add(plugin_class())
|
||||
|
||||
def register_service_plugin(self, plugin_class: type) -> None:
|
||||
"""注册服务插件"""
|
||||
self._service_plugins.add(plugin_class())
|
||||
|
||||
def register_library_plugin(self, plugin_class: type) -> None:
|
||||
"""注册支持库插件"""
|
||||
self._library_plugins.add(plugin_class())
|
||||
|
||||
def get_music_input_plugin_by_format(
|
||||
self, filepath_or_format: Union[Path, str]
|
||||
) -> Generator[MusicInputPlugin, 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
|
||||
)
|
||||
)
|
||||
|
||||
def get_track_input_plugin_by_format(
|
||||
self, filepath_or_format: Union[Path, str]
|
||||
) -> Generator[TrackInputPlugin, 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
|
||||
)
|
||||
)
|
||||
|
||||
def get_music_output_plugin_by_format(
|
||||
self, filepath_or_format: Union[Path, str]
|
||||
) -> Generator[MusicOutputPlugin, 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
|
||||
)
|
||||
)
|
||||
|
||||
def get_track_output_plugin_by_format(
|
||||
self, filepath_or_format: Union[Path, str]
|
||||
) -> Generator[TrackOutputPlugin, 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)
|
||||
)
|
||||
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
|
||||
)
|
||||
)
|
||||
|
||||
def get_music_input_plugin(self, plugin_name: str) -> MusicInputPlugin:
|
||||
"""获取指定名称的全曲导入用插件,当名称重叠时,取版本号最大的"""
|
||||
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)
|
||||
)
|
||||
|
||||
def get_track_input_plugin(self, plugin_name: str) -> TrackInputPlugin:
|
||||
"""获取指定名称的单音轨导入用插件,当名称重叠时,取版本号最大的"""
|
||||
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)
|
||||
)
|
||||
|
||||
def get_music_operate_plugin(self, plugin_name: str) -> MusicOperatePlugin:
|
||||
"""获取指定名称的全曲处理用插件,当名称重叠时,取版本号最大的"""
|
||||
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)
|
||||
)
|
||||
|
||||
def get_track_operate_plugin(self, plugin_name: str) -> TrackOperatePlugin:
|
||||
"""获取指定名称的单音轨处理用插件,当名称重叠时,取版本号最大的"""
|
||||
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)
|
||||
)
|
||||
|
||||
def get_music_output_plugin(self, plugin_name: str) -> MusicOutputPlugin:
|
||||
"""获取指定名称的导出全曲用插件,当名称重叠时,取版本号最大的"""
|
||||
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)
|
||||
)
|
||||
|
||||
def get_track_output_plugin(self, plugin_name: str) -> TrackOutputPlugin:
|
||||
"""获取指定名称的导出单音轨用插件,当名称重叠时,取版本号最大的"""
|
||||
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)
|
||||
)
|
||||
|
||||
def get_service_plugin(self, plugin_name: str) -> ServicePlugin:
|
||||
"""获取服务用插件,当名称重叠时,取版本号最大的"""
|
||||
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)
|
||||
)
|
||||
|
||||
def get_library_plugin(self, plugin_name: str) -> LibraryPlugin:
|
||||
"""获取依赖库类插件,当名称重叠时,取版本号最高的"""
|
||||
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)
|
||||
)
|
||||
|
||||
def supported_input_formats(self) -> Set[str]:
|
||||
"""所有支持的导入格式"""
|
||||
return set(
|
||||
chain.from_iterable(
|
||||
plugin.supported_formats
|
||||
for plugin in chain(
|
||||
self._music_input_plugins, self._track_input_plugins
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def supported_output_formats(self) -> Set[str]:
|
||||
"""所有支持的导出格式"""
|
||||
return set(
|
||||
chain.from_iterable(
|
||||
plugin.supported_formats
|
||||
for plugin in chain(
|
||||
self._music_output_plugins, self._track_output_plugins
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
__global_plugin_registry = PluginRegistry()
|
||||
"""全局插件注册表实例"""
|
||||
|
||||
|
||||
def music_input_plugin(metainfo: PluginMetaInformation):
|
||||
"""全曲输入用插件装饰器"""
|
||||
|
||||
def decorator(cls):
|
||||
global __global_plugin_registry
|
||||
cls.metainfo = metainfo
|
||||
__global_plugin_registry.register_music_input_plugin(cls)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def track_input_plugin(metainfo: PluginMetaInformation):
|
||||
"""单轨输入用插件装饰器"""
|
||||
|
||||
def decorator(cls):
|
||||
global __global_plugin_registry
|
||||
cls.metainfo = metainfo
|
||||
__global_plugin_registry.register_track_input_plugin(cls)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def music_operate_plugin(metainfo: PluginMetaInformation):
|
||||
"""全曲处理用插件装饰器"""
|
||||
|
||||
def decorator(cls):
|
||||
global __global_plugin_registry
|
||||
cls.metainfo = metainfo
|
||||
__global_plugin_registry.register_music_operate_plugin(cls)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def track_operate_plugin(metainfo: PluginMetaInformation):
|
||||
"""音轨处理插件装饰器"""
|
||||
|
||||
def decorator(cls):
|
||||
global __global_plugin_registry
|
||||
cls.metainfo = metainfo
|
||||
__global_plugin_registry.register_track_operate_plugin(cls)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def music_output_plugin(metainfo: PluginMetaInformation):
|
||||
"""乐曲输出用插件装饰器"""
|
||||
|
||||
def decorator(cls):
|
||||
global __global_plugin_registry
|
||||
cls.metainfo = metainfo
|
||||
__global_plugin_registry.register_music_output_plugin(cls)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def track_output_plugin(metainfo: PluginMetaInformation):
|
||||
"""音轨输出用插件装饰器"""
|
||||
|
||||
def decorator(cls):
|
||||
global __global_plugin_registry
|
||||
cls.metainfo = metainfo
|
||||
__global_plugin_registry.register_track_output_plugin(cls)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def service_plugin(metainfo: PluginMetaInformation):
|
||||
"""服务插件装饰器"""
|
||||
|
||||
def decorator(cls):
|
||||
global __global_plugin_registry
|
||||
cls.metainfo = metainfo
|
||||
__global_plugin_registry.register_service_plugin(cls)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def library_plugin(metainfo: PluginMetaInformation):
|
||||
"""支持库插件装饰器"""
|
||||
|
||||
def decorator(cls):
|
||||
global __global_plugin_registry
|
||||
cls.metainfo = metainfo
|
||||
__global_plugin_registry.register_library_plugin(cls)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
"""
|
||||
|
||||
"""
|
||||
版权所有 © 2025 金羿 & 玉衡
|
||||
Copyright © 2025 Eilles & Alioth
|
||||
版权所有 © 2025 金羿 & 玉衡Alioth
|
||||
Copyright © 2025 Eilles & YuhengAlioth
|
||||
|
||||
开源相关声明请见 仓库根目录下的 License.md
|
||||
Terms & Conditions: License.md in the root directory
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Musicreater
|
||||
import Musicreater.old_init as old_init
|
||||
import Musicreater.experiment
|
||||
import Musicreater.old_plugin
|
||||
|
||||
@@ -16,12 +16,12 @@ from Musicreater.old_plugin.mcstructfile import (
|
||||
)
|
||||
|
||||
MSCT_MAIN = (
|
||||
Musicreater,
|
||||
Musicreater.experiment,
|
||||
old_init,
|
||||
old_init.experiment,
|
||||
# Musicreater.previous,
|
||||
)
|
||||
|
||||
MSCT_PLUGIN = (Musicreater.old_plugin,)
|
||||
MSCT_PLUGIN = (old_init.old_plugin,)
|
||||
|
||||
MSCT_PLUGIN_FUNCTION = (
|
||||
to_addon_pack_in_delay,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[Bilibili: 金羿ELS]: https://img.shields.io/badge/Bilibili-%E9%87%91%E7%BE%BFELS-00A1E7?style=for-the-badge
|
||||
[Bilibili: 诸葛亮与八卦阵]: https://img.shields.io/badge/Bilibili-%E8%AF%B8%E8%91%9B%E4%BA%AE%E4%B8%8E%E5%85%AB%E5%8D%A6%E9%98%B5-00A1E7?style=for-the-badge
|
||||
[Bilibili: 玉衡Alioth]: https://img.shields.io/badge/Bilibili-%E7%8E%89%E8%A1%A1Alioth-00A1E7?style=for-the-badge
|
||||
[CodeStyle: black]: https://img.shields.io/badge/code%20style-black-121110.svg?style=for-the-badge
|
||||
[python]: https://img.shields.io/badge/python-3.8-AB70FF?style=for-the-badge
|
||||
[release]: https://img.shields.io/github/v/release/EillesWan/Musicreater?style=for-the-badge
|
||||
@@ -23,7 +23,7 @@
|
||||
<p>
|
||||
|
||||
[![][Bilibili: 金羿ELS]](https://space.bilibili.com/397369002/)
|
||||
[![][Bilibili: 诸葛亮与八卦阵]](https://space.bilibili.com/604072474)
|
||||
[![][Bilibili: 玉衡Alioth]](https://space.bilibili.com/604072474)
|
||||
[![CodeStyle: black]](https://github.com/psf/black)
|
||||
[![][python]](https://www.python.org/)
|
||||
[![][license]](LICENSE)
|
||||
@@ -83,7 +83,7 @@
|
||||
|
||||
**金羿 Eilles**:我的世界基岩版指令作者,个人开发者,B 站不知名 UP 主。
|
||||
|
||||
**诸葛亮与八卦阵 bgArray**:我的世界基岩版玩家,喜欢编程和音乐,深圳学生。
|
||||
**玉衡Alioth Alioth**:我的世界基岩版玩家,喜欢编程和音乐,学生。
|
||||
|
||||
**偷吃不是Touch Touch**:我的世界基岩版指令制作者,提供测试支持
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
- 感谢由 **[Dislink Sforza](https://github.com/Dislink) “断联·斯福尔扎”**\<QQ1600515314\> 带来的 midi 音色解析以及转换指令的算法,我们将其改编并应用;同时,感谢他的[网页版转换器](https://dislink.github.io/midi2bdx/)给我们的开发与更新带来巨大的压力和动力,让我们在原本一骑绝尘的摸鱼道路上转向开发。
|
||||
- 感谢 **Mono**\<QQ738893087\> 反馈安装时的问题,辅助我们找到了视窗操作系统下的兼容性问题;感谢其反馈延迟播放器出现的重大问题,让我们得以修改全部延迟播放错误;尤其感谢他对于我们的软件的大力宣传
|
||||
- 感谢 **Ammelia “艾米利亚”**\<QQ2838334637\> 敦促我们进行新的功能开发,并为新功能提出了非常优秀的大量建议,以及提供的 BDX 导入测试支持,为我们的新结构生成算法提供了大量的实际理论支持
|
||||
- 感谢 **[神羽](https://gitee.com/snowykami) “[SnowyKami](https://github.com/snowyfirefly)”** 对我们项目的支持与宣传,非常感谢他为我们提供的服务器!
|
||||
- 感谢 **[神羽 “SnowyKami”](https://www.sfkm.me/)** 对我们项目的支持与宣传,非常感谢他为我们提供的服务器!
|
||||
- 感谢 **指令师\_苦力怕 playjuice123**\<QQ240667197\> 为我们的程序找出错误,并提醒我们修复一个一直存在的大 bug。
|
||||
- 感谢 **雷霆**\<QQ3555268519\> 用他那令所有开发者都大为光火的操作方法为我们的程序找出错误,并提醒修复 bug。
|
||||
- 感谢 **小埋**\<QQ2039310975\> 反馈附加包生成时缺少描述和标题的问题。
|
||||
|
||||
12
README_EN.md
12
README_EN.md
@@ -1,5 +1,5 @@
|
||||
[Bilibili: Eilles]: https://img.shields.io/badge/Bilibili-%E9%87%91%E7%BE%BFELS-00A1E7?style=for-the-badge
|
||||
[Bilibili: bgArray]: https://img.shields.io/badge/Bilibili-%E8%AF%B8%E8%91%9B%E4%BA%AE%E4%B8%8E%E5%85%AB%E5%8D%A6%E9%98%B5-00A1E7?style=for-the-badge
|
||||
[Bilibili: Eilles]: https://img.shields.io/badge/Bilibili-%E9%87%91%E7%BE%BFEilles-00A1E7?style=for-the-badge
|
||||
[Bilibili: Alioth]: https://img.shields.io/badge/Bilibili-%E7%8E%89%E8%A1%A1Alioth-00A1E7?style=for-the-badge
|
||||
[CodeStyle: black]: https://img.shields.io/badge/code%20style-black-121110.svg?style=for-the-badge
|
||||
[python]: https://img.shields.io/badge/python-3.8-AB70FF?style=for-the-badge
|
||||
[release]: https://img.shields.io/github/v/release/EillesWan/Musicreater?style=for-the-badge
|
||||
@@ -14,7 +14,7 @@
|
||||
</img>
|
||||
</p>
|
||||
|
||||
<h3 align="center">A free open-source library of <i>Minecraft</i> digital music.</h3>
|
||||
<h3 align="center">A free and open-source library for handling with <i>Minecraft</i> digital music.</h3>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/BUILD%20WITH%20LOVE-FF3432?style=for-the-badge">
|
||||
@@ -22,7 +22,7 @@
|
||||
<p>
|
||||
|
||||
[![][Bilibili: Eilles]](https://space.bilibili.com/397369002/)
|
||||
[![][Bilibili: bgArray]](https://space.bilibili.com/604072474)
|
||||
[![][Bilibili: Alioth]](https://space.bilibili.com/604072474)
|
||||
[![CodeStyle: black]](https://github.com/psf/black)
|
||||
[![][python]](https://www.python.org/)
|
||||
[![][license]](LICENSE)
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
## Introduction🚀
|
||||
|
||||
Musicreater is a free open-source library used for digital music that being played in _Minecraft_.
|
||||
Musicreater is a free open-source library used for handling with digital musics that being able to be played in _Minecraft_.
|
||||
|
||||
Welcome to join our QQ group: [861684859](https://jq.qq.com/?_wv=1027&k=hpeRxrYr)
|
||||
|
||||
@@ -85,7 +85,7 @@ Commands such as `python`、`pip` could be changed to some like `python3` or `pi
|
||||
|
||||
**Eilles (金羿)**:A student, individual developer, unfamous Bilibili UPer, which knows a little about commands in _Minecraft: Bedrock Edition_
|
||||
|
||||
**bgArray (诸葛亮与八卦阵)**: A student, player of _Minecraft: Bedrock Edition_, which is a fan of music and programming.
|
||||
**Alioth (玉衡Alioth)**: A student, player of _Minecraft: Bedrock Edition_, which is a fan of music and programming.
|
||||
|
||||
**Touch (偷吃不是 Touch)**: A man who is good at using command(s) in _Minecraft: Bedrock Edition_, who supported us of debugging and testing program and algorithm
|
||||
|
||||
|
||||
10
example.py
10
example.py
@@ -18,7 +18,7 @@ Terms & Conditions: ./License.md
|
||||
|
||||
import os
|
||||
|
||||
import Musicreater
|
||||
import Musicreater.old_init as old_init
|
||||
from Musicreater.old_plugin.addonpack import (
|
||||
to_addon_pack_in_delay,
|
||||
to_addon_pack_in_repeater,
|
||||
@@ -156,7 +156,7 @@ else:
|
||||
|
||||
|
||||
print(f"正在处理 {midi_path} :")
|
||||
cvt_mid = Musicreater.MidiConvert.from_midi_file(
|
||||
cvt_mid = old_init.MidiConvert.from_midi_file(
|
||||
midi_path, old_exe_format=False, min_volume=prompts[0], play_speed=prompts[1]
|
||||
)
|
||||
|
||||
@@ -187,7 +187,7 @@ print(
|
||||
cvt_method(
|
||||
cvt_mid,
|
||||
out_path,
|
||||
Musicreater.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None, # type: ignore
|
||||
old_init.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None, # type: ignore
|
||||
*prompts[3:],
|
||||
)
|
||||
)
|
||||
@@ -199,14 +199,14 @@ print(
|
||||
to_BDX_file_in_score(
|
||||
cvt_mid,
|
||||
out_path,
|
||||
Musicreater.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None,
|
||||
old_init.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None,
|
||||
*prompts[3:],
|
||||
)
|
||||
if playerFormat == 1
|
||||
else to_BDX_file_in_delay(
|
||||
cvt_mid,
|
||||
out_path,
|
||||
Musicreater.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None,
|
||||
old_init.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None,
|
||||
*prompts[3:],
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import Musicreater
|
||||
import Musicreater.old_init as old_init
|
||||
import Musicreater.old_plugin
|
||||
import Musicreater.old_plugin.mcstructfile
|
||||
|
||||
print(
|
||||
Musicreater.old_plugin.mcstructfile.to_mcstructure_file_in_delay(
|
||||
Musicreater.MidiConvert.from_midi_file(
|
||||
old_init.old_plugin.mcstructfile.to_mcstructure_file_in_delay(
|
||||
old_init.MidiConvert.from_midi_file(
|
||||
input("midi路径:"),
|
||||
old_exe_format=False,
|
||||
# note_table_replacement={"note.harp": "note.flute"},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Musicreater
|
||||
import Musicreater.old_init as old_init
|
||||
import Musicreater.old_plugin
|
||||
import Musicreater.old_plugin.websocket
|
||||
|
||||
@@ -7,9 +7,9 @@ import os
|
||||
dire = input("midi目录:")
|
||||
|
||||
print(
|
||||
Musicreater.old_plugin.websocket.to_websocket_server(
|
||||
old_init.old_plugin.websocket.to_websocket_server(
|
||||
[
|
||||
Musicreater.MidiConvert.from_midi_file(
|
||||
old_init.MidiConvert.from_midi_file(
|
||||
os.path.join(dire, names), old_exe_format=False
|
||||
)
|
||||
for names in os.listdir(
|
||||
@@ -19,6 +19,6 @@ print(
|
||||
],
|
||||
input("服务器地址:"),
|
||||
int(input("服务器端口:")),
|
||||
Musicreater.DEFAULT_PROGRESSBAR_STYLE,
|
||||
old_init.DEFAULT_PROGRESSBAR_STYLE,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -123,7 +123,7 @@ msc_cvt = Musicreater.experiment.FutureMidiConvertJavaE.from_midi_file(
|
||||
input("midi路径:"),
|
||||
play_speed=float(input("播放速度:")),
|
||||
old_exe_format=True,
|
||||
note_table_replacement=Musicreater.MC_EILLES_RTJE12_INSTRUMENT_REPLACE_TABLE,
|
||||
note_table_replacement=Musicreater.old_init.MC_EILLES_RTJE12_INSTRUMENT_REPLACE_TABLE,
|
||||
# pitched_note_table=Musicreater.MM_NBS_PITCHED_INSTRUMENT_TABLE,
|
||||
)
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
from rich.pretty import pprint
|
||||
|
||||
import Musicreater
|
||||
import Musicreater.old_init as old_init
|
||||
from Musicreater.utils import (
|
||||
load_decode_fsq_flush_release,
|
||||
load_decode_musicsequence_metainfo,
|
||||
)
|
||||
|
||||
msc_seq = Musicreater.MusicSequence.from_mido(
|
||||
Musicreater.mido.MidiFile(
|
||||
msc_seq = old_init.MusicSequence.from_mido(
|
||||
old_init.mido.MidiFile(
|
||||
"./resources/测试片段.mid",
|
||||
),
|
||||
"TEST-测试片段",
|
||||
@@ -20,7 +20,7 @@ with open("test.fsq", "wb") as f:
|
||||
f.write(fsq_bytes := msc_seq.encode_dump(flowing_codec_support=True))
|
||||
|
||||
with open("test.fsq", "rb") as f:
|
||||
msc_seq_r = Musicreater.MusicSequence.load_decode(f.read(), verify=True)
|
||||
msc_seq_r = old_init.MusicSequence.load_decode(f.read(), verify=True)
|
||||
|
||||
pprint("FSQ 传入类成功:")
|
||||
pprint(msc_seq_r)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
from rich.pretty import pprint
|
||||
|
||||
import Musicreater
|
||||
import Musicreater.old_init as old_init
|
||||
from Musicreater.utils import (
|
||||
load_decode_msq_flush_release,
|
||||
load_decode_musicsequence_metainfo,
|
||||
)
|
||||
|
||||
msc_seq = Musicreater.MusicSequence.from_mido(
|
||||
Musicreater.mido.MidiFile(
|
||||
msc_seq = old_init.MusicSequence.from_mido(
|
||||
old_init.mido.MidiFile(
|
||||
"./resources/测试片段.mid",
|
||||
),
|
||||
"TEST-测试片段",
|
||||
@@ -20,7 +20,7 @@ with open("test.msq", "wb") as f:
|
||||
f.write(msq_bytes := msc_seq.encode_dump())
|
||||
|
||||
with open("test.msq", "rb") as f:
|
||||
msc_seq_r = Musicreater.MusicSequence.load_decode(f.read())
|
||||
msc_seq_r = old_init.MusicSequence.load_decode(f.read())
|
||||
|
||||
pprint("常规 MSQ 读取成功:")
|
||||
pprint(msc_seq_r)
|
||||
|
||||
21
tests/genexpr_vs_yieldfrom.py
Normal file
21
tests/genexpr_vs_yieldfrom.py
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
# 模拟两种写法
|
||||
def method_A(self, start, end):
|
||||
yield from (f"{track}.get_range(start, end)" for track in self)
|
||||
|
||||
def method_B(self, start, end):
|
||||
return (f"{track}.get_range(start, end)" for track in self)
|
||||
|
||||
|
||||
|
||||
tracks = ["A", "B"]
|
||||
|
||||
gen_a = method_A(tracks, 0, 10)
|
||||
print(list(gen_a))
|
||||
|
||||
|
||||
|
||||
gen_b = method_B(tracks, 0, 10)
|
||||
print(list(gen_b))
|
||||
|
||||
# they are the same output
|
||||
Reference in New Issue
Block a user