mirror of
https://github.com/TriM-Organization/Musicreater.git
synced 2026-04-30 21:26:00 +00:00
临时上传,仍在开发过程中
This commit is contained in:
@@ -1,19 +1,24 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""一个简单的我的世界音频转换库
|
|
||||||
音·创 (Musicreater)
|
"""
|
||||||
|
音·创
|
||||||
是一款免费开源的《我的世界》数字音频支持库。
|
是一款免费开源的《我的世界》数字音频支持库。
|
||||||
|
|
||||||
Musicreater (音·创)
|
Musicreater (音·创)
|
||||||
A free open source library used for dealing with **Minecraft** digital musics.
|
A free and open-source library for handling with **Minecraft** digital music.
|
||||||
|
|
||||||
版权所有 © 2025 金羿 & 诸葛亮与八卦阵
|
版权所有 © 2026 睿乐组织
|
||||||
Copyright © 2025 Eilles & bgArray
|
Copyright © 2026 TriM-Organization
|
||||||
|
|
||||||
音·创(“本项目”)的协议颁发者为 金羿、诸葛亮与八卦阵
|
音·创(“本项目”)的协议颁发者为 金羿、玉衡Alioth
|
||||||
The Licensor of Musicreater("this project") is Eilles, bgArray.
|
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
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
|
||||||
__version__ = "2.4.2.3"
|
|
||||||
__vername__ = "音符附加信息升级"
|
__version__ = "3.0.0-alpha"
|
||||||
|
|
||||||
__author__ = (
|
__author__ = (
|
||||||
("金羿", "Eilles"),
|
("金羿", "Eilles"),
|
||||||
("诸葛亮与八卦阵", "bgArray"),
|
("玉衡Alioth", "YuhengAlioth"),
|
||||||
("鱼旧梦", "ElapsingDreams"),
|
("鱼旧梦", "ElapsingDreams"),
|
||||||
("偷吃不是Touch", "Touch"),
|
("偷吃不是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用对照表
|
# Midi用对照表
|
||||||
|
|
||||||
MIDI_DEFAULT_VOLUME_VALUE: int = (
|
MIDI_DEFAULT_VOLUME_VALUE: int = (
|
||||||
|
|||||||
@@ -16,7 +16,13 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# Email TriM-Organization@hotmail.com
|
# Email TriM-Organization@hotmail.com
|
||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
||||||
|
|
||||||
|
# “
|
||||||
|
# 把代码 洒落在这里
|
||||||
|
# 和音符 留下的沙砾
|
||||||
|
# 一点一点爬进你类定义的缝隙
|
||||||
|
# ” —— 乐曲访问 by resnah
|
||||||
|
|
||||||
|
import heapq
|
||||||
from math import sin, cos, asin, radians, degrees, sqrt, atan, inf, ceil
|
from math import sin, cos, asin, radians, degrees, sqrt, atan, inf, ceil
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import (
|
from typing import (
|
||||||
@@ -33,12 +39,16 @@ from typing import (
|
|||||||
Iterable,
|
Iterable,
|
||||||
Iterator,
|
Iterator,
|
||||||
Literal,
|
Literal,
|
||||||
|
Hashable,
|
||||||
|
TypeVar,
|
||||||
)
|
)
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from .exceptions import SingleNoteDecodeError, ParameterTypeError
|
from .exceptions import SingleNoteDecodeError, ParameterTypeError, ParameterValueError
|
||||||
from .paramcurve import ParamCurve
|
from .paramcurve import ParamCurve
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
class SoundAtmos:
|
class SoundAtmos:
|
||||||
"""声源方位类"""
|
"""声源方位类"""
|
||||||
@@ -411,6 +421,9 @@ class SingleTrack(List[SingleNote]):
|
|||||||
track_name: str
|
track_name: str
|
||||||
"""轨道之名称"""
|
"""轨道之名称"""
|
||||||
|
|
||||||
|
is_enabled: bool
|
||||||
|
"""该音轨是否启用"""
|
||||||
|
|
||||||
track_instrument: str
|
track_instrument: str
|
||||||
"""乐器ID"""
|
"""乐器ID"""
|
||||||
|
|
||||||
@@ -440,12 +453,16 @@ class SingleTrack(List[SingleNote]):
|
|||||||
precise_time: bool = True,
|
precise_time: bool = True,
|
||||||
percussion: bool = False,
|
percussion: bool = False,
|
||||||
sound_direction: SoundAtmos = SoundAtmos(),
|
sound_direction: SoundAtmos = SoundAtmos(),
|
||||||
|
enabled: bool = True,
|
||||||
extra_information: Dict[str, Any] = {},
|
extra_information: Dict[str, Any] = {},
|
||||||
*args: SingleNote,
|
*args: SingleNote,
|
||||||
):
|
):
|
||||||
self.track_name = name
|
self.track_name = name
|
||||||
"""音轨名称"""
|
"""音轨名称"""
|
||||||
|
|
||||||
|
self.is_enabled = enabled
|
||||||
|
"""音轨启用情况"""
|
||||||
|
|
||||||
self.track_instrument = instrument
|
self.track_instrument = instrument
|
||||||
"""乐器ID"""
|
"""乐器ID"""
|
||||||
|
|
||||||
@@ -494,27 +511,40 @@ class SingleTrack(List[SingleNote]):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
super().append(item)
|
super().append(item)
|
||||||
super().sort()
|
super().sort() # =========================== TODO 需要优化
|
||||||
|
|
||||||
def update(self, items: Iterable[SingleNote]):
|
def update(self, items: Iterable[SingleNote]):
|
||||||
"""
|
"""
|
||||||
拼接两个音轨
|
拼接两个音轨
|
||||||
"""
|
"""
|
||||||
super().extend(items)
|
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
|
self, start_time: float, end_time: float = inf
|
||||||
) -> Iterator[SingleNote]:
|
) -> Generator[SingleNote, None, None]:
|
||||||
"""通过开始时间和结束时间来获取音符"""
|
"""通过开始时间和结束时间来获取音符"""
|
||||||
|
if end_time < start_time:
|
||||||
return filter(
|
raise ParameterValueError(
|
||||||
lambda x: (x.start_time >= start_time) and (x.start_time <= end_time), self
|
"获取音符的时间范围有误,终止时间`{}`早于起始时间`{}`".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(
|
def get_minenotes(
|
||||||
@@ -522,7 +552,7 @@ class SingleTrack(List[SingleNote]):
|
|||||||
) -> Generator[MineNote, Any, None]:
|
) -> 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(
|
yield MineNote.from_single_note(
|
||||||
note=_note,
|
note=_note,
|
||||||
note_instrument=self.track_instrument,
|
note_instrument=self.track_instrument,
|
||||||
@@ -626,9 +656,107 @@ class SingleMusic(List[SingleTrack]):
|
|||||||
return len(self)
|
return len(self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def music_tracks(self) -> List[SingleTrack]:
|
def music_tracks(self) -> Iterator[SingleTrack]:
|
||||||
"""音轨列表"""
|
"""音轨列表,不包含被禁用的音轨"""
|
||||||
return self
|
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):
|
def set_info(self, key: Union[str, Sequence[str]], value: Any):
|
||||||
"""设置附加信息"""
|
"""设置附加信息"""
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
版权所有 © 2025 金羿 & 玉衡
|
版权所有 © 2025 金羿 & 玉衡Alioth
|
||||||
Copyright © 2025 Eilles & Alioth
|
Copyright © 2025 Eilles & YuhengAlioth
|
||||||
|
|
||||||
开源相关声明请见 仓库根目录下的 License.md
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
Terms & Conditions: License.md in the root directory
|
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
|
# Email TriM-Organization@hotmail.com
|
||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 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):
|
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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
存储 音·创 v3 的插件管理和上层设计内容
|
存储 音·创 v3 的插件接口与管理相关内容
|
||||||
"""
|
"""
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -16,7 +16,471 @@ Terms & Conditions: License.md in the root directory
|
|||||||
# Email TriM-Organization@hotmail.com
|
# Email TriM-Organization@hotmail.com
|
||||||
# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md
|
# 若需转载或借鉴 许可声明请查看仓库目录下的 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 金羿 & 玉衡
|
版权所有 © 2025 金羿 & 玉衡Alioth
|
||||||
Copyright © 2025 Eilles & Alioth
|
Copyright © 2025 Eilles & YuhengAlioth
|
||||||
|
|
||||||
开源相关声明请见 仓库根目录下的 License.md
|
开源相关声明请见 仓库根目录下的 License.md
|
||||||
Terms & Conditions: License.md in the root directory
|
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.experiment
|
||||||
import Musicreater.old_plugin
|
import Musicreater.old_plugin
|
||||||
|
|
||||||
@@ -16,12 +16,12 @@ from Musicreater.old_plugin.mcstructfile import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
MSCT_MAIN = (
|
MSCT_MAIN = (
|
||||||
Musicreater,
|
old_init,
|
||||||
Musicreater.experiment,
|
old_init.experiment,
|
||||||
# Musicreater.previous,
|
# Musicreater.previous,
|
||||||
)
|
)
|
||||||
|
|
||||||
MSCT_PLUGIN = (Musicreater.old_plugin,)
|
MSCT_PLUGIN = (old_init.old_plugin,)
|
||||||
|
|
||||||
MSCT_PLUGIN_FUNCTION = (
|
MSCT_PLUGIN_FUNCTION = (
|
||||||
to_addon_pack_in_delay,
|
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: 金羿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
|
[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
|
[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
|
[release]: https://img.shields.io/github/v/release/EillesWan/Musicreater?style=for-the-badge
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
<p>
|
<p>
|
||||||
|
|
||||||
[![][Bilibili: 金羿ELS]](https://space.bilibili.com/397369002/)
|
[![][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)
|
[![CodeStyle: black]](https://github.com/psf/black)
|
||||||
[![][python]](https://www.python.org/)
|
[![][python]](https://www.python.org/)
|
||||||
[![][license]](LICENSE)
|
[![][license]](LICENSE)
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
|
|
||||||
**金羿 Eilles**:我的世界基岩版指令作者,个人开发者,B 站不知名 UP 主。
|
**金羿 Eilles**:我的世界基岩版指令作者,个人开发者,B 站不知名 UP 主。
|
||||||
|
|
||||||
**诸葛亮与八卦阵 bgArray**:我的世界基岩版玩家,喜欢编程和音乐,深圳学生。
|
**玉衡Alioth Alioth**:我的世界基岩版玩家,喜欢编程和音乐,学生。
|
||||||
|
|
||||||
**偷吃不是Touch Touch**:我的世界基岩版指令制作者,提供测试支持
|
**偷吃不是Touch Touch**:我的世界基岩版指令制作者,提供测试支持
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
- 感谢由 **[Dislink Sforza](https://github.com/Dislink) “断联·斯福尔扎”**\<QQ1600515314\> 带来的 midi 音色解析以及转换指令的算法,我们将其改编并应用;同时,感谢他的[网页版转换器](https://dislink.github.io/midi2bdx/)给我们的开发与更新带来巨大的压力和动力,让我们在原本一骑绝尘的摸鱼道路上转向开发。
|
- 感谢由 **[Dislink Sforza](https://github.com/Dislink) “断联·斯福尔扎”**\<QQ1600515314\> 带来的 midi 音色解析以及转换指令的算法,我们将其改编并应用;同时,感谢他的[网页版转换器](https://dislink.github.io/midi2bdx/)给我们的开发与更新带来巨大的压力和动力,让我们在原本一骑绝尘的摸鱼道路上转向开发。
|
||||||
- 感谢 **Mono**\<QQ738893087\> 反馈安装时的问题,辅助我们找到了视窗操作系统下的兼容性问题;感谢其反馈延迟播放器出现的重大问题,让我们得以修改全部延迟播放错误;尤其感谢他对于我们的软件的大力宣传
|
- 感谢 **Mono**\<QQ738893087\> 反馈安装时的问题,辅助我们找到了视窗操作系统下的兼容性问题;感谢其反馈延迟播放器出现的重大问题,让我们得以修改全部延迟播放错误;尤其感谢他对于我们的软件的大力宣传
|
||||||
- 感谢 **Ammelia “艾米利亚”**\<QQ2838334637\> 敦促我们进行新的功能开发,并为新功能提出了非常优秀的大量建议,以及提供的 BDX 导入测试支持,为我们的新结构生成算法提供了大量的实际理论支持
|
- 感谢 **Ammelia “艾米利亚”**\<QQ2838334637\> 敦促我们进行新的功能开发,并为新功能提出了非常优秀的大量建议,以及提供的 BDX 导入测试支持,为我们的新结构生成算法提供了大量的实际理论支持
|
||||||
- 感谢 **[神羽](https://gitee.com/snowykami) “[SnowyKami](https://github.com/snowyfirefly)”** 对我们项目的支持与宣传,非常感谢他为我们提供的服务器!
|
- 感谢 **[神羽 “SnowyKami”](https://www.sfkm.me/)** 对我们项目的支持与宣传,非常感谢他为我们提供的服务器!
|
||||||
- 感谢 **指令师\_苦力怕 playjuice123**\<QQ240667197\> 为我们的程序找出错误,并提醒我们修复一个一直存在的大 bug。
|
- 感谢 **指令师\_苦力怕 playjuice123**\<QQ240667197\> 为我们的程序找出错误,并提醒我们修复一个一直存在的大 bug。
|
||||||
- 感谢 **雷霆**\<QQ3555268519\> 用他那令所有开发者都大为光火的操作方法为我们的程序找出错误,并提醒修复 bug。
|
- 感谢 **雷霆**\<QQ3555268519\> 用他那令所有开发者都大为光火的操作方法为我们的程序找出错误,并提醒修复 bug。
|
||||||
- 感谢 **小埋**\<QQ2039310975\> 反馈附加包生成时缺少描述和标题的问题。
|
- 感谢 **小埋**\<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: Eilles]: https://img.shields.io/badge/Bilibili-%E9%87%91%E7%BE%BFEilles-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: 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
|
[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
|
[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
|
[release]: https://img.shields.io/github/v/release/EillesWan/Musicreater?style=for-the-badge
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
</img>
|
</img>
|
||||||
</p>
|
</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">
|
<p align="center">
|
||||||
<img src="https://img.shields.io/badge/BUILD%20WITH%20LOVE-FF3432?style=for-the-badge">
|
<img src="https://img.shields.io/badge/BUILD%20WITH%20LOVE-FF3432?style=for-the-badge">
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<p>
|
<p>
|
||||||
|
|
||||||
[![][Bilibili: Eilles]](https://space.bilibili.com/397369002/)
|
[![][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)
|
[![CodeStyle: black]](https://github.com/psf/black)
|
||||||
[![][python]](https://www.python.org/)
|
[![][python]](https://www.python.org/)
|
||||||
[![][license]](LICENSE)
|
[![][license]](LICENSE)
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
|
|
||||||
## Introduction🚀
|
## 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)
|
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_
|
**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
|
**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 os
|
||||||
|
|
||||||
import Musicreater
|
import Musicreater.old_init as old_init
|
||||||
from Musicreater.old_plugin.addonpack import (
|
from Musicreater.old_plugin.addonpack import (
|
||||||
to_addon_pack_in_delay,
|
to_addon_pack_in_delay,
|
||||||
to_addon_pack_in_repeater,
|
to_addon_pack_in_repeater,
|
||||||
@@ -156,7 +156,7 @@ else:
|
|||||||
|
|
||||||
|
|
||||||
print(f"正在处理 {midi_path} :")
|
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]
|
midi_path, old_exe_format=False, min_volume=prompts[0], play_speed=prompts[1]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -187,7 +187,7 @@ print(
|
|||||||
cvt_method(
|
cvt_method(
|
||||||
cvt_mid,
|
cvt_mid,
|
||||||
out_path,
|
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:],
|
*prompts[3:],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -199,14 +199,14 @@ print(
|
|||||||
to_BDX_file_in_score(
|
to_BDX_file_in_score(
|
||||||
cvt_mid,
|
cvt_mid,
|
||||||
out_path,
|
out_path,
|
||||||
Musicreater.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None,
|
old_init.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None,
|
||||||
*prompts[3:],
|
*prompts[3:],
|
||||||
)
|
)
|
||||||
if playerFormat == 1
|
if playerFormat == 1
|
||||||
else to_BDX_file_in_delay(
|
else to_BDX_file_in_delay(
|
||||||
cvt_mid,
|
cvt_mid,
|
||||||
out_path,
|
out_path,
|
||||||
Musicreater.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None,
|
old_init.DEFAULT_PROGRESSBAR_STYLE if prompts[2] else None,
|
||||||
*prompts[3:],
|
*prompts[3:],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import Musicreater
|
import Musicreater.old_init as old_init
|
||||||
import Musicreater.old_plugin
|
import Musicreater.old_plugin
|
||||||
import Musicreater.old_plugin.mcstructfile
|
import Musicreater.old_plugin.mcstructfile
|
||||||
|
|
||||||
print(
|
print(
|
||||||
Musicreater.old_plugin.mcstructfile.to_mcstructure_file_in_delay(
|
old_init.old_plugin.mcstructfile.to_mcstructure_file_in_delay(
|
||||||
Musicreater.MidiConvert.from_midi_file(
|
old_init.MidiConvert.from_midi_file(
|
||||||
input("midi路径:"),
|
input("midi路径:"),
|
||||||
old_exe_format=False,
|
old_exe_format=False,
|
||||||
# note_table_replacement={"note.harp": "note.flute"},
|
# 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
|
||||||
import Musicreater.old_plugin.websocket
|
import Musicreater.old_plugin.websocket
|
||||||
|
|
||||||
@@ -7,9 +7,9 @@ import os
|
|||||||
dire = input("midi目录:")
|
dire = input("midi目录:")
|
||||||
|
|
||||||
print(
|
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
|
os.path.join(dire, names), old_exe_format=False
|
||||||
)
|
)
|
||||||
for names in os.listdir(
|
for names in os.listdir(
|
||||||
@@ -19,6 +19,6 @@ print(
|
|||||||
],
|
],
|
||||||
input("服务器地址:"),
|
input("服务器地址:"),
|
||||||
int(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路径:"),
|
input("midi路径:"),
|
||||||
play_speed=float(input("播放速度:")),
|
play_speed=float(input("播放速度:")),
|
||||||
old_exe_format=True,
|
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,
|
# pitched_note_table=Musicreater.MM_NBS_PITCHED_INSTRUMENT_TABLE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
from rich.pretty import pprint
|
from rich.pretty import pprint
|
||||||
|
|
||||||
import Musicreater
|
import Musicreater.old_init as old_init
|
||||||
from Musicreater.utils import (
|
from Musicreater.utils import (
|
||||||
load_decode_fsq_flush_release,
|
load_decode_fsq_flush_release,
|
||||||
load_decode_musicsequence_metainfo,
|
load_decode_musicsequence_metainfo,
|
||||||
)
|
)
|
||||||
|
|
||||||
msc_seq = Musicreater.MusicSequence.from_mido(
|
msc_seq = old_init.MusicSequence.from_mido(
|
||||||
Musicreater.mido.MidiFile(
|
old_init.mido.MidiFile(
|
||||||
"./resources/测试片段.mid",
|
"./resources/测试片段.mid",
|
||||||
),
|
),
|
||||||
"TEST-测试片段",
|
"TEST-测试片段",
|
||||||
@@ -20,7 +20,7 @@ with open("test.fsq", "wb") as f:
|
|||||||
f.write(fsq_bytes := msc_seq.encode_dump(flowing_codec_support=True))
|
f.write(fsq_bytes := msc_seq.encode_dump(flowing_codec_support=True))
|
||||||
|
|
||||||
with open("test.fsq", "rb") as f:
|
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("FSQ 传入类成功:")
|
||||||
pprint(msc_seq_r)
|
pprint(msc_seq_r)
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
from rich.pretty import pprint
|
from rich.pretty import pprint
|
||||||
|
|
||||||
import Musicreater
|
import Musicreater.old_init as old_init
|
||||||
from Musicreater.utils import (
|
from Musicreater.utils import (
|
||||||
load_decode_msq_flush_release,
|
load_decode_msq_flush_release,
|
||||||
load_decode_musicsequence_metainfo,
|
load_decode_musicsequence_metainfo,
|
||||||
)
|
)
|
||||||
|
|
||||||
msc_seq = Musicreater.MusicSequence.from_mido(
|
msc_seq = old_init.MusicSequence.from_mido(
|
||||||
Musicreater.mido.MidiFile(
|
old_init.mido.MidiFile(
|
||||||
"./resources/测试片段.mid",
|
"./resources/测试片段.mid",
|
||||||
),
|
),
|
||||||
"TEST-测试片段",
|
"TEST-测试片段",
|
||||||
@@ -20,7 +20,7 @@ with open("test.msq", "wb") as f:
|
|||||||
f.write(msq_bytes := msc_seq.encode_dump())
|
f.write(msq_bytes := msc_seq.encode_dump())
|
||||||
|
|
||||||
with open("test.msq", "rb") as f:
|
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("常规 MSQ 读取成功:")
|
||||||
pprint(msc_seq_r)
|
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