From 0de959c396d5bebec4316a73451fa77b64150caf Mon Sep 17 00:00:00 2001 From: EillesWan Date: Mon, 26 Jan 2026 09:39:22 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E7=9A=84=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E5=B7=B2=E7=BB=8F=E5=AE=8C=E6=88=90=EF=BC=8C=E4=BB=8A=E5=A4=A9?= =?UTF-8?q?=E4=BC=91=E6=81=AF=E4=B8=80=E4=B8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Musicreater/__init__.py | 2 +- Musicreater/data.py | 59 +- Musicreater/exceptions.py | 243 ++++-- Musicreater/experiment.py | 6 +- Musicreater/old_exceptions.py | 165 ++++ Musicreater/{main.py => old_main.py} | 4 +- .../{plugin => old_plugin}/__init__.py | 0 .../addonpack/__init__.py | 0 .../{plugin => old_plugin}/addonpack/main.py | 2 +- Musicreater/{plugin => old_plugin}/archive.py | 0 Musicreater/{plugin => old_plugin}/bdx.py | 0 .../bdxfile/__init__.py | 0 .../{plugin => old_plugin}/bdxfile/main.py | 2 +- Musicreater/{plugin => old_plugin}/common.py | 0 Musicreater/{plugin => old_plugin}/main.py | 0 .../mcstructfile/__init__.py | 0 .../mcstructfile/main.py | 2 +- .../{plugin => old_plugin}/mcstructure.py | 0 .../{plugin => old_plugin}/noteblock.py | 4 +- .../{plugin => old_plugin}/schematic.py | 0 .../schematic/__init__.py | 0 .../{plugin => old_plugin}/schematic/main.py | 0 .../websocket/__init__.py | 0 .../{plugin => old_plugin}/websocket/main.py | 2 +- Musicreater/old_types.py | 73 ++ Musicreater/paramcurve.py | 8 +- Musicreater/plugin.py | 783 ++++++++++++++++++ Musicreater/plugins.py | 22 + Musicreater/types.py | 58 +- Musicreater/utils.py | 4 +- Packer/MSCT_Packer.py | 10 +- example.py | 6 +- example_futureFunction.py | 6 +- example_singleConvert.py | 6 +- example_websocket.py | 6 +- let_future_java.py | 2 +- pyproject.toml | 4 +- test_future_kamires.py | 6 +- test_future_lyric.py | 6 +- uv.lock | 90 +- 40 files changed, 1372 insertions(+), 209 deletions(-) create mode 100644 Musicreater/old_exceptions.py rename Musicreater/{main.py => old_main.py} (99%) rename Musicreater/{plugin => old_plugin}/__init__.py (100%) rename Musicreater/{plugin => old_plugin}/addonpack/__init__.py (100%) rename Musicreater/{plugin => old_plugin}/addonpack/main.py (99%) rename Musicreater/{plugin => old_plugin}/archive.py (100%) rename Musicreater/{plugin => old_plugin}/bdx.py (100%) rename Musicreater/{plugin => old_plugin}/bdxfile/__init__.py (100%) rename Musicreater/{plugin => old_plugin}/bdxfile/main.py (99%) rename Musicreater/{plugin => old_plugin}/common.py (100%) rename Musicreater/{plugin => old_plugin}/main.py (100%) rename Musicreater/{plugin => old_plugin}/mcstructfile/__init__.py (100%) rename Musicreater/{plugin => old_plugin}/mcstructfile/main.py (99%) rename Musicreater/{plugin => old_plugin}/mcstructure.py (100%) rename Musicreater/{plugin => old_plugin}/noteblock.py (97%) rename Musicreater/{plugin => old_plugin}/schematic.py (100%) rename Musicreater/{plugin => old_plugin}/schematic/__init__.py (100%) rename Musicreater/{plugin => old_plugin}/schematic/main.py (100%) rename Musicreater/{plugin => old_plugin}/websocket/__init__.py (100%) rename Musicreater/{plugin => old_plugin}/websocket/main.py (99%) create mode 100644 Musicreater/old_types.py create mode 100644 Musicreater/plugin.py create mode 100644 Musicreater/plugins.py diff --git a/Musicreater/__init__.py b/Musicreater/__init__.py index f02fe9b..4f061c3 100644 --- a/Musicreater/__init__.py +++ b/Musicreater/__init__.py @@ -85,7 +85,7 @@ __all__ = [ "midi_inst_to_mc_sound", ] -from .main import MusicSequence, MidiConvert +from .old_main import MusicSequence, MidiConvert from .subclass import ( MineNote, diff --git a/Musicreater/data.py b/Musicreater/data.py index afb8932..f30d77d 100644 --- a/Musicreater/data.py +++ b/Musicreater/data.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- """ -存储音·创新数据存储类 +存储 音·创 v3 的内部数据类 """ """ -版权所有 © 2025 金羿 -Copyright © 2025 Eilles +版权所有 © 2026 金羿 +Copyright © 2026 Eilles 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory @@ -16,7 +16,6 @@ Terms & Conditions: License.md in the root directory # Email TriM-Organization@hotmail.com # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md -# WARNING 本文件中使用之功能尚未启用 from math import sin, cos, asin, radians, degrees, sqrt, atan, inf, ceil from dataclasses import dataclass @@ -37,9 +36,7 @@ from typing import ( ) from enum import Enum -from .types import FittingFunctionType -from .constants import MC_PITCHED_INSTRUMENT_LIST - +from .exceptions import SingleNoteDecodeError, ParameterTypeError from .paramcurve import ParamCurve @@ -205,13 +202,21 @@ class SingleNote: last_time=duration_, mass_precision_time=code_buffer[6] if is_high_time_precision else 0, ) - except: + except Exception as e: + # 我也不知道为什么这里要放一个异常处理 + # 之前用到过吗? + # —— 2026.01.25 金羿 print( - "[Error] 单音符解析错误,字节码`{}`,{}启用高精度时间偏移\n".format( + "[Exception] 单音符解析错误,字节码`{}`,{}启用高精度时间偏移\n".format( code_buffer, "已" if is_high_time_precision else "未" ) ) - raise + raise SingleNoteDecodeError( + e, + "技术信息:\nGROUP1\t`{}`\nCODE_BUFFER\t`{}`".format( + group_1, code_buffer + ), + ) def encode(self, is_high_time_precision: bool = True) -> bytes: """ @@ -267,7 +272,9 @@ class SingleNote: self.extra_info[key[i]] = value[i] else: # 提供简单报错就行了,如果放一堆 if 语句,降低处理速度 - raise TypeError("参数类型错误;键:`{}` 值:`{}`".format(key, value)) + raise ParameterTypeError( + "参数类型错误;键:`{}` 值:`{}`".format(key, value) + ) def get_info(self, key: str, default: Any = None) -> Any: """获取附加信息""" @@ -369,6 +376,7 @@ class MineNote: note: SingleNote, note_instrument: str, sound_volume: float, + is_persiced_time: bool, is_percussive_note: bool, sound_position: SoundAtmos, adjust_note_pitch: float = 0.0, @@ -391,7 +399,7 @@ class MineNote: volume=sound_volume + adjust_note_volume, start_tick=note.start_time, duration_tick=note.duration, - persiced_time=note.high_precision_start_time, + persiced_time=note.high_precision_start_time if is_persiced_time else 0, percussive=is_percussive_note, position=sound_position, ) @@ -480,8 +488,8 @@ class SingleTrack(List[SingleNote]): """ if not isinstance(item, SingleNote): - raise TypeError( - "单音轨类的元素类型须为单音符(SingleNote),不可为:{}".format( + raise ParameterTypeError( + "单音轨类的元素类型须为单音符(`SingleNote`),不可为:`{}`".format( type(item).__name__ ) ) @@ -514,15 +522,16 @@ class SingleTrack(List[SingleNote]): ) -> Generator[MineNote, Any, None]: """获取能够用以在我的世界播放的音符数据类""" - for note in self.get_range(range_start_time, range_end_time): + for _note in self.get_range(range_start_time, range_end_time): yield MineNote.from_single_note( - note, - self.track_instrument, - self.track_volume, - self.is_percussive, - self.sound_position, + note=_note, + note_instrument=self.track_instrument, + sound_volume=self.track_volume, + is_persiced_time=self.is_high_time_precision, + is_percussive_note=self.is_percussive, + sound_position=self.sound_position, **{ - item.value: self.argument_curves[item].value_at(note.start_time) # type: ignore + item.value: self.argument_curves[item].value_at(_note.start_time) # type: ignore for item in CurvableParam if self.argument_curves[item] }, @@ -551,7 +560,9 @@ class SingleTrack(List[SingleNote]): self.extra_info[key[i]] = value[i] else: # 提供简单报错就行了,如果放一堆 if 语句,降低处理速度 - raise TypeError("参数类型错误;键:`{}` 值:`{}`".format(key, value)) + raise ParameterTypeError( + "参数类型错误;键:`{}` 值:`{}`".format(key, value) + ) def get_info(self, key: str, default: Any = None) -> Any: """获取附加信息""" @@ -632,7 +643,9 @@ class SingleMusic(List[SingleTrack]): self.extra_info[key[i]] = value[i] else: # 提供简单报错就行了,如果放一堆 if 语句,降低处理速度 - raise TypeError("参数类型错误;键:`{}` 值:`{}`".format(key, value)) + raise ParameterTypeError( + "参数类型错误;键:`{}` 值:`{}`".format(key, value) + ) def get_info(self, key: str, default: Any = None) -> Any: """获取附加信息""" diff --git a/Musicreater/exceptions.py b/Musicreater/exceptions.py index 4f81e60..a9dd67a 100644 --- a/Musicreater/exceptions.py +++ b/Musicreater/exceptions.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- """ -存放一些报错类型 +存储 音·创 v3 用到的一些报错类型 """ """ -版权所有 © 2025 金羿 & 诸葛亮与八卦阵 -Copyright © 2025 Eilles & bgArray +版权所有 © 2025 金羿 & 玉衡 +Copyright © 2025 Eilles & Alioth 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory @@ -17,146 +17,211 @@ Terms & Conditions: License.md in the root directory # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md -class MSCTBaseException(Exception): - """音·创 的所有错误均继承于此""" + +class MusicreaterBaseException(Exception): + """音·创 v3 的所有错误均继承于此""" def __init__(self, *args): """音·创 的所有错误均继承于此""" - super().__init__("音·创", *args) + super().__init__("[音·创] - ", *args) - def meow( - self, - ): + def meow(self): for i in self.args: - print(i + "喵!") + print(i + "喵~", end=":") def crash_it(self): raise self + def __str__(self) -> str: + return "".join(self.args) -class MidiFormatException(MSCTBaseException): - """音·创 的所有MIDI格式错误均继承于此""" + +# ===================================== +# NOTE +# 面对用户时候爆出去的我们认为这就是“外部错误” +# 如果是在程序内部数据传输等情况下出现的就是“内部错误” +# 例如,无法读取文件,这就是一个外部错误 +# 某个参数的数据类型错误,这就是内部错误 +# ===================================== + + +class MusicreaterInnerlyError(MusicreaterBaseException): + """内部错误""" def __init__(self, *args): - """音·创 的所有MIDI格式错误均继承于此""" - super().__init__("MIDI 格式错误", *args) + """内部错误(面向开发者的报错信息)""" + super().__init__("内部错误 - ", *args) - -class MidiDestroyedError(MSCTBaseException): - """Midi文件损坏""" +class MusicreaterOuterlyError(MusicreaterBaseException): + """外部错误""" def __init__(self, *args): - """Midi文件损坏""" - super().__init__("MIDI文件损坏:无法读取 MIDI 文件", *args) + """外部错误(面向用户的报错信息)""" + super().__init__("外部错误 - ", *args) -# class MidiUnboundError(MSCTBaseException): -# """未定义Midi对象(无用)""" - -# def __init__(self, *args): -# """未绑定Midi对象""" -# super().__init__("未定义MidiFile对象:你甚至没有对象就想要生孩子?", *args) -# 此错误在本版本内已经不再使用 -class CommandFormatError(MSCTBaseException, RuntimeError): - """指令格式与目标格式不匹配而引起的错误""" +class InnerlyParameterError(MusicreaterInnerlyError): + """内部传参错误""" def __init__(self, *args): - """指令格式与目标格式不匹配而引起的错误""" - super().__init__("指令格式不匹配", *args) + """参数错误""" + super().__init__("传参错误 - ", *args) -# class CrossNoteError(MidiFormatException): -# """同通道下同音符交叉出现所产生的错误""" - -# def __init__(self, *args): -# """同通道下同音符交叉出现所产生的错误""" -# super().__init__("同通道下同音符交叉", *args) -# 这TM是什么错误? -# 我什么时候写的这玩意? -# 我哪知道这说的是啥? -# !!! -# 我知道这是什么了 —— 金羿 2025 0401 -# 两个其他属性相同的音符在同一个通道,出现连续两个开音信息和连续两个停止信息 -# 那么这两个音符的音长无法判断。这是个好问题,但是不是我现在能解决的,也不是我们现在想解决的问题 - - -class NotDefineTempoError(MidiFormatException): - """没有Tempo设定导致时间无法计算的错误""" +class ParameterTypeError(InnerlyParameterError, TypeError): + """参数类型错误""" def __init__(self, *args): - """没有Tempo设定导致时间无法计算的错误""" - super().__init__("在曲目开始时没有声明 Tempo(未指定拍长)", *args) + """参数类型错误""" + super().__init__("参数类型错误:", *args) -class ChannelOverFlowError(MidiFormatException): - """一个midi中含有过多的通道""" - - def __init__(self, max_channel=16, *args): - """一个midi中含有过多的通道""" - super().__init__("含有过多的通道(数量应≤{})".format(max_channel), *args) - - -class NotDefineProgramError(MidiFormatException): - """没有Program设定导致没有乐器可以选择的错误""" +class ParameterValueError(InnerlyParameterError, ValueError): + """参数值存在错误""" def __init__(self, *args): - """没有Program设定导致没有乐器可以选择的错误""" - super().__init__("未指定演奏乐器", *args) + """参数其值存在错误""" + super().__init__("参数数值错误:", *args) -class NoteOnOffMismatchError(MidiFormatException): - """音符开音和停止不匹配的错误""" + +class OuterlyParameterError(MusicreaterOuterlyError): + """外部参数错误""" def __init__(self, *args): - """音符开音和停止不匹配的错误""" - super().__init__("音符不匹配", *args) + """参数错误""" + super().__init__("参数错误 - ", *args) -class LyricMismatchError(MSCTBaseException): - """歌词匹配解析错误""" - - def __init__(self, *args): - """有可能产生了错误的歌词解析""" - super().__init__("歌词解析错误", *args) - - -class ZeroSpeedError(MSCTBaseException, ZeroDivisionError): +class ZeroSpeedError(OuterlyParameterError, ZeroDivisionError): """以0作为播放速度的错误""" def __init__(self, *args): """以0作为播放速度的错误""" - super().__init__("播放速度为零", *args) + super().__init__("播放速度为零:", *args) -class IllegalMinimumVolumeError(MSCTBaseException, ValueError): +class IllegalMinimumVolumeError(OuterlyParameterError, ValueError): """最小播放音量有误的错误""" def __init__(self, *args): """最小播放音量错误""" - super().__init__("最小播放音量超出范围", *args) + super().__init__("最小播放音量超出范围:", *args) -class MusicSequenceDecodeError(MSCTBaseException): - """音乐序列解码错误""" + + +class NoteBinaryDecodeError(MusicreaterOuterlyError): + """音乐存储二进制数据解码错误""" def __init__(self, *args): - """音乐序列无法正确解码的错误""" - super().__init__("解码音符序列文件时出现问题", *args) + """音乐存储二进制数据无法正确解码""" + super().__init__("解码音乐存储二进制数据时出现问题 - ", *args) -class MusicSequenceTypeError(MSCTBaseException): - """音乐序列类型错误""" +class SingleNoteDecodeError(NoteBinaryDecodeError): + """单个音符的二进制数据解码错误""" def __init__(self, *args): - """无法识别音符序列字节码的类型""" - super().__init__("错误的音符序列字节类型", *args) + """单个音符的二进制数据无法正确解码""" + super().__init__("音符解码出错:", *args) -class MusicSequenceVerificationFailed(MusicSequenceDecodeError): - """音乐序列校验失败""" +class NoteBinaryFileTypeError(NoteBinaryDecodeError): + """音乐存储二进制数据的文件类型错误""" def __init__(self, *args): - """音符序列文件与其校验值不一致""" - super().__init__("音符序列文件校验失败", *args) + """无法识别音乐存储文件的类型""" + super().__init__("无法识别音乐存储文件对应的类型:", *args) + + +class NoteBinaryFileVerificationFailed(NoteBinaryDecodeError): + """音乐存储二进制数据校验失败""" + + def __init__(self, *args): + """音乐存储文件与其校验值不一致""" + super().__init__("音乐存储文件校验失败:", *args) + +class PluginDefineError(MusicreaterInnerlyError): + """插件定义错误(内部相关)""" + + def __init__(self, *args): + """插件本身存在错误""" + super().__init__("插件内部错误 - ", *args) + +class PluginInstanceNotFoundError(PluginDefineError, LookupError): + """插件实例未找到""" + + def __init__(self, *args): + """插件实例未找到""" + super().__init__("插件实例未找到:", *args) + +class PluginAttributeNotFoundError(PluginDefineError, AttributeError): + """插件属性定义错误""" + + def __init__(self, *args): + """插件属性定义错误""" + super().__init__("插件类的必要属性不存在:", *args) + + +class PluginMetainfoError(PluginDefineError): + """插件元信息定义错误""" + + def __init__(self, *args): + """插件元信息定义错误""" + super().__init__("插件元信息定义错误 - ", *args) + +class PluginMetainfoTypeError(PluginMetainfoError, TypeError): + """插件元信息定义类型错误""" + + def __init__(self, *args): + """插件元信息定义类型错误""" + super().__init__("插件元信息类型错误:", *args) + +class PluginMetainfoValueError(PluginMetainfoError, ValueError): + """插件元信息定义值错误""" + + def __init__(self, *args): + """插件元信息定义值错误""" + super().__init__("插件元信息数值错误:", *args) + +class PluginMetainfoNotFoundError(PluginMetainfoError, PluginAttributeNotFoundError): + """插件元信息定义缺少错误""" + + def __init__(self, *args): + """插件元信息定义缺少错误""" + super().__init__("插件元信息未定义:", *args) + + +class PluginLoadError(MusicreaterOuterlyError): + """插件加载错误(外部相关)""" + + def __init__(self, *args): + """插件加载错误""" + super().__init__("插件加载错误 - ", *args) + + +class PluginConfigRelatedError(MusicreaterOuterlyError): + """插件配置相关错误""" + + def __init__(self, *args): + """插件配置相关错误""" + super().__init__("插件配置相关错误 - ", *args) + + +class PluginConfigLoadError(PluginLoadError, PluginConfigRelatedError): + """插件配置加载错误""" + + def __init__(self, *args): + """配置文件无法加载""" + super().__init__("插件配置文件加载错误:", *args) + + +class PluginConfigDumpError(PluginConfigRelatedError): + """插件配置保存错误""" + + def __init__(self, *args): + """配置文件无法保存""" + super().__init__("插件配置文件保存错误:", *args) diff --git a/Musicreater/experiment.py b/Musicreater/experiment.py index e359e50..64f1a76 100644 --- a/Musicreater/experiment.py +++ b/Musicreater/experiment.py @@ -18,8 +18,8 @@ Terms & Conditions: License.md in the root directory from typing import Dict, List, Tuple -from .exceptions import * -from .main import ( +from .old_exceptions import * +from .old_main import ( MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE, MM_CLASSIC_PITCHED_INSTRUMENT_TABLE, MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE, @@ -30,7 +30,7 @@ from .main import ( from .constants import MIDI_PAN, MIDI_PROGRAM, MIDI_VOLUME from .subclass import * -from .types import ChannelType, FittingFunctionType +from .old_types import ChannelType, FittingFunctionType from .utils import * diff --git a/Musicreater/old_exceptions.py b/Musicreater/old_exceptions.py new file mode 100644 index 0000000..787e3d8 --- /dev/null +++ b/Musicreater/old_exceptions.py @@ -0,0 +1,165 @@ +# -*- coding: utf-8 -*- + +""" +存放一些报错类型 +""" + +""" +版权所有 © 2025 金羿 & 诸葛亮与八卦阵 +Copyright © 2025 Eilles & bgArray + +开源相关声明请见 仓库根目录下的 License.md +Terms & Conditions: License.md in the root directory +""" + +# 睿乐组织 开发交流群 861684859 +# Email TriM-Organization@hotmail.com +# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md + + +class MSCTBaseException(Exception): + """音·创 的所有错误均继承于此""" + + def __init__(self, *args): + """音·创 的所有错误均继承于此""" + super().__init__("音·创", *args) + + def meow( + self, + ): + for i in self.args: + print(i + "喵!") + + def crash_it(self): + raise self + + +class MidiFormatException(MSCTBaseException): + """音·创 的所有MIDI格式错误均继承于此""" + + def __init__(self, *args): + """音·创 的所有MIDI格式错误均继承于此""" + super().__init__("MIDI 格式错误", *args) + + +class MidiDestroyedError(MSCTBaseException): + """Midi文件损坏""" + + def __init__(self, *args): + """Midi文件损坏""" + super().__init__("MIDI文件损坏:无法读取 MIDI 文件", *args) + + +# class MidiUnboundError(MSCTBaseException): +# """未定义Midi对象(无用)""" + +# def __init__(self, *args): +# """未绑定Midi对象""" +# super().__init__("未定义MidiFile对象:你甚至没有对象就想要生孩子?", *args) +# 此错误在本版本内已经不再使用 + + +class CommandFormatError(MSCTBaseException, RuntimeError): + """指令格式与目标格式不匹配而引起的错误""" + + def __init__(self, *args): + """指令格式与目标格式不匹配而引起的错误""" + super().__init__("指令格式不匹配", *args) + + +# class CrossNoteError(MidiFormatException): +# """同通道下同音符交叉出现所产生的错误""" + +# def __init__(self, *args): +# """同通道下同音符交叉出现所产生的错误""" +# super().__init__("同通道下同音符交叉", *args) +# 这TM是什么错误? +# 我什么时候写的这玩意? +# 我哪知道这说的是啥? +# !!! +# 我知道这是什么了 —— 金羿 2025 0401 +# 两个其他属性相同的音符在同一个通道,出现连续两个开音信息和连续两个停止信息 +# 那么这两个音符的音长无法判断。这是个好问题,但是不是我现在能解决的,也不是我们现在想解决的问题 + + +class NotDefineTempoError(MidiFormatException): + """没有Tempo设定导致时间无法计算的错误""" + + def __init__(self, *args): + """没有Tempo设定导致时间无法计算的错误""" + super().__init__("在曲目开始时没有声明 Tempo(未指定拍长)", *args) + + +class ChannelOverFlowError(MidiFormatException): + """一个midi中含有过多的通道""" + + def __init__(self, max_channel=16, *args): + """一个midi中含有过多的通道""" + super().__init__("含有过多的通道(数量应≤{})".format(max_channel), *args) + + +class NotDefineProgramError(MidiFormatException): + """没有Program设定导致没有乐器可以选择的错误""" + + def __init__(self, *args): + """没有Program设定导致没有乐器可以选择的错误""" + super().__init__("未指定演奏乐器", *args) + + +class NoteOnOffMismatchError(MidiFormatException): + """音符开音和停止不匹配的错误""" + + def __init__(self, *args): + """音符开音和停止不匹配的错误""" + super().__init__("音符不匹配", *args) + + +class LyricMismatchError(MSCTBaseException): + """歌词匹配解析错误""" + + def __init__(self, *args): + """有可能产生了错误的歌词解析""" + super().__init__("歌词解析错误", *args) + +# 已重构 +class ZeroSpeedError(MSCTBaseException, ZeroDivisionError): + """以0作为播放速度的错误""" + + def __init__(self, *args): + """以0作为播放速度的错误""" + super().__init__("播放速度为零", *args) + +# 已重构 +class IllegalMinimumVolumeError(MSCTBaseException, ValueError): + """最小播放音量有误的错误""" + + def __init__(self, *args): + """最小播放音量错误""" + super().__init__("最小播放音量超出范围", *args) + + +# 已重构 +class MusicSequenceDecodeError(MSCTBaseException): + """音乐序列解码错误""" + + def __init__(self, *args): + """音乐序列无法正确解码的错误""" + super().__init__("解码音符序列文件时出现问题", *args) + + +# 已重构 +class MusicSequenceTypeError(MSCTBaseException): + """音乐序列类型错误""" + + def __init__(self, *args): + """无法识别音符序列字节码的类型""" + super().__init__("错误的音符序列字节类型", *args) + + +# 已重构 +class MusicSequenceVerificationFailed(MusicSequenceDecodeError): + """音乐序列校验失败""" + + def __init__(self, *args): + """音符序列文件与其校验值不一致""" + super().__init__("音符序列文件校验失败", *args) diff --git a/Musicreater/main.py b/Musicreater/old_main.py similarity index 99% rename from Musicreater/main.py rename to Musicreater/old_main.py index 4d804ad..24f18fc 100644 --- a/Musicreater/main.py +++ b/Musicreater/old_main.py @@ -37,9 +37,9 @@ from itertools import chain import mido from .constants import * -from .exceptions import * +from .old_exceptions import * from .subclass import * -from .types import * +from .old_types import * from .utils import * """ diff --git a/Musicreater/plugin/__init__.py b/Musicreater/old_plugin/__init__.py similarity index 100% rename from Musicreater/plugin/__init__.py rename to Musicreater/old_plugin/__init__.py diff --git a/Musicreater/plugin/addonpack/__init__.py b/Musicreater/old_plugin/addonpack/__init__.py similarity index 100% rename from Musicreater/plugin/addonpack/__init__.py rename to Musicreater/old_plugin/addonpack/__init__.py diff --git a/Musicreater/plugin/addonpack/main.py b/Musicreater/old_plugin/addonpack/main.py similarity index 99% rename from Musicreater/plugin/addonpack/main.py rename to Musicreater/old_plugin/addonpack/main.py index 253cd3d..538b582 100644 --- a/Musicreater/plugin/addonpack/main.py +++ b/Musicreater/old_plugin/addonpack/main.py @@ -16,7 +16,7 @@ import os import shutil from typing import Literal, Optional, Tuple -from ...main import MidiConvert +from ...old_main import MidiConvert from ...subclass import ProgressBarStyle from ..archive import behavior_mcpack_manifest, compress_zipfile from ..mcstructure import ( diff --git a/Musicreater/plugin/archive.py b/Musicreater/old_plugin/archive.py similarity index 100% rename from Musicreater/plugin/archive.py rename to Musicreater/old_plugin/archive.py diff --git a/Musicreater/plugin/bdx.py b/Musicreater/old_plugin/bdx.py similarity index 100% rename from Musicreater/plugin/bdx.py rename to Musicreater/old_plugin/bdx.py diff --git a/Musicreater/plugin/bdxfile/__init__.py b/Musicreater/old_plugin/bdxfile/__init__.py similarity index 100% rename from Musicreater/plugin/bdxfile/__init__.py rename to Musicreater/old_plugin/bdxfile/__init__.py diff --git a/Musicreater/plugin/bdxfile/main.py b/Musicreater/old_plugin/bdxfile/main.py similarity index 99% rename from Musicreater/plugin/bdxfile/main.py rename to Musicreater/old_plugin/bdxfile/main.py index 8773cd9..1f99bec 100644 --- a/Musicreater/plugin/bdxfile/main.py +++ b/Musicreater/old_plugin/bdxfile/main.py @@ -17,7 +17,7 @@ from typing import Optional import brotli -from ...main import MidiConvert +from ...old_main import MidiConvert from ...subclass import MineCommand, ProgressBarStyle from ..bdx import ( bdx_move, diff --git a/Musicreater/plugin/common.py b/Musicreater/old_plugin/common.py similarity index 100% rename from Musicreater/plugin/common.py rename to Musicreater/old_plugin/common.py diff --git a/Musicreater/plugin/main.py b/Musicreater/old_plugin/main.py similarity index 100% rename from Musicreater/plugin/main.py rename to Musicreater/old_plugin/main.py diff --git a/Musicreater/plugin/mcstructfile/__init__.py b/Musicreater/old_plugin/mcstructfile/__init__.py similarity index 100% rename from Musicreater/plugin/mcstructfile/__init__.py rename to Musicreater/old_plugin/mcstructfile/__init__.py diff --git a/Musicreater/plugin/mcstructfile/main.py b/Musicreater/old_plugin/mcstructfile/main.py similarity index 99% rename from Musicreater/plugin/mcstructfile/main.py rename to Musicreater/old_plugin/mcstructfile/main.py index b2e159d..25c3d2e 100644 --- a/Musicreater/plugin/mcstructfile/main.py +++ b/Musicreater/old_plugin/mcstructfile/main.py @@ -14,7 +14,7 @@ Terms & Conditions: License.md in the root directory import os from typing import Literal -from ...main import MidiConvert +from ...old_main import MidiConvert from ...subclass import MineCommand from ..mcstructure import ( COMPABILITY_VERSION_117, diff --git a/Musicreater/plugin/mcstructure.py b/Musicreater/old_plugin/mcstructure.py similarity index 100% rename from Musicreater/plugin/mcstructure.py rename to Musicreater/old_plugin/mcstructure.py diff --git a/Musicreater/plugin/noteblock.py b/Musicreater/old_plugin/noteblock.py similarity index 97% rename from Musicreater/plugin/noteblock.py rename to Musicreater/old_plugin/noteblock.py index 7c2a4cb..6644d65 100644 --- a/Musicreater/plugin/noteblock.py +++ b/Musicreater/old_plugin/noteblock.py @@ -16,8 +16,8 @@ Terms & Conditions: License.md in the root directory # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md -from ..exceptions import NotDefineProgramError, ZeroSpeedError -from ..main import MidiConvert +from ..old_exceptions import NotDefineProgramError, ZeroSpeedError +from ..old_main import MidiConvert from ..subclass import MineCommand from ..utils import inst_to_sould_with_deviation, perc_inst_to_soundID_withX diff --git a/Musicreater/plugin/schematic.py b/Musicreater/old_plugin/schematic.py similarity index 100% rename from Musicreater/plugin/schematic.py rename to Musicreater/old_plugin/schematic.py diff --git a/Musicreater/plugin/schematic/__init__.py b/Musicreater/old_plugin/schematic/__init__.py similarity index 100% rename from Musicreater/plugin/schematic/__init__.py rename to Musicreater/old_plugin/schematic/__init__.py diff --git a/Musicreater/plugin/schematic/main.py b/Musicreater/old_plugin/schematic/main.py similarity index 100% rename from Musicreater/plugin/schematic/main.py rename to Musicreater/old_plugin/schematic/main.py diff --git a/Musicreater/plugin/websocket/__init__.py b/Musicreater/old_plugin/websocket/__init__.py similarity index 100% rename from Musicreater/plugin/websocket/__init__.py rename to Musicreater/old_plugin/websocket/__init__.py diff --git a/Musicreater/plugin/websocket/main.py b/Musicreater/old_plugin/websocket/main.py similarity index 99% rename from Musicreater/plugin/websocket/main.py rename to Musicreater/old_plugin/websocket/main.py index 95baf22..2cd60ab 100644 --- a/Musicreater/plugin/websocket/main.py +++ b/Musicreater/old_plugin/websocket/main.py @@ -19,7 +19,7 @@ from typing import List, Literal, Optional, Tuple import fcwslib -from ...main import MidiConvert +from ...old_main import MidiConvert from ...subclass import MineCommand, ProgressBarStyle diff --git a/Musicreater/old_types.py b/Musicreater/old_types.py new file mode 100644 index 0000000..16e6312 --- /dev/null +++ b/Musicreater/old_types.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- + +""" +存放数据类型的定义 +""" + +""" +版权所有 © 2025 金羿 & 诸葛亮与八卦阵 +Copyright © 2025 Eilles & bgArray + +开源相关声明请见 仓库根目录下的 License.md +Terms & Conditions: License.md in the root directory +""" + +# 睿乐组织 开发交流群 861684859 +# Email TriM-Organization@hotmail.com +# 若需转载或借鉴 许可声明请查看仓库目录下的 License.md + +from typing import Callable, Dict, List, Literal, Mapping, Tuple, Union + +from .subclass import MineNote + +MidiNoteNameTableType = Mapping[int, Tuple[str, ...]] +""" +Midi音符名称对照表类型 +""" + +MidiInstrumentTableType = Mapping[int, str] +""" +Midi乐器对照表类型 +""" + +FittingFunctionType = Callable[[float], float] +""" +拟合函数类型 +""" + +ChannelType = Dict[ + int, + Dict[ + int, + List[ + Union[ + Tuple[Literal["PgmC"], int, int], + Tuple[Literal["NoteS"], int, int, int], + Tuple[Literal["NoteE"], int, int], + ] + ], + ], +] +""" +以字典所标记的通道信息类型(已弃用) + +Dict[int,Dict[int,List[Union[Tuple[Literal["PgmC"], int, int],Tuple[Literal["NoteS"], int, int, int],Tuple[Literal["NoteE"], int, int],]],],] +""" + + +MineNoteChannelType = Mapping[ + int, + List[MineNote,], +] +""" +我的世界通道信息类型 + +Dict[int,Dict[int,List[MineNote,],],] +""" + +MineNoteTrackType = Mapping[ + int, + List[MineNote,], +] + + diff --git a/Musicreater/paramcurve.py b/Musicreater/paramcurve.py index 5667ccc..e7f70c5 100644 --- a/Musicreater/paramcurve.py +++ b/Musicreater/paramcurve.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- """ -存储音·创音轨所需的参数曲线 +存储 音·创 v3 内部数据使用的参数曲线 """ """ -版权所有 © 2025 金羿 -Copyright © 2025 Eilles +版权所有 © 2026 金羿 +Copyright © 2026 Eilles 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory @@ -17,7 +17,7 @@ Terms & Conditions: License.md in the root directory # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md -# WARNING 本文件中使用之功能尚未启用 +# WARNING 本文件所含之功能未经完整测试 # 鉴于白谭若佬给出的建议:本功能应是处于低优先级开发的 # 因此暂时用处不大,可以稍微放一会再进行开发 # 目前用人工智能生成了部分代码,只经过简单的测试 diff --git a/Musicreater/plugin.py b/Musicreater/plugin.py new file mode 100644 index 0000000..27b07b0 --- /dev/null +++ b/Musicreater/plugin.py @@ -0,0 +1,783 @@ +# -*- 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 diff --git a/Musicreater/plugins.py b/Musicreater/plugins.py new file mode 100644 index 0000000..c8ceb0e --- /dev/null +++ b/Musicreater/plugins.py @@ -0,0 +1,22 @@ +# -*- 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 + +from typing import List, Optional, Dict, Generator, Any +from pathlib import Path + +from .plugin import MusicInputPlugin, MusicOperatePlugin, MusicOutputPlugin, TrackInputPlugin, TrackOperatePlugin, TrackOutputPlugin, ServicePlugin, LibraryPlugin diff --git a/Musicreater/types.py b/Musicreater/types.py index 16e6312..8ebd4b8 100644 --- a/Musicreater/types.py +++ b/Musicreater/types.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- """ -存放数据类型的定义 +存储 音·创 v3 定义的一些数据类型,可以用于类型检查器 """ """ -版权所有 © 2025 金羿 & 诸葛亮与八卦阵 -Copyright © 2025 Eilles & bgArray +版权所有 © 2025 金羿 & 玉衡 +Copyright © 2025 Eilles & Alioth 开源相关声明请见 仓库根目录下的 License.md Terms & Conditions: License.md in the root directory @@ -16,58 +16,10 @@ Terms & Conditions: License.md in the root directory # Email TriM-Organization@hotmail.com # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md + from typing import Callable, Dict, List, Literal, Mapping, Tuple, Union -from .subclass import MineNote - -MidiNoteNameTableType = Mapping[int, Tuple[str, ...]] -""" -Midi音符名称对照表类型 -""" - -MidiInstrumentTableType = Mapping[int, str] -""" -Midi乐器对照表类型 -""" - FittingFunctionType = Callable[[float], float] """ 拟合函数类型 -""" - -ChannelType = Dict[ - int, - Dict[ - int, - List[ - Union[ - Tuple[Literal["PgmC"], int, int], - Tuple[Literal["NoteS"], int, int, int], - Tuple[Literal["NoteE"], int, int], - ] - ], - ], -] -""" -以字典所标记的通道信息类型(已弃用) - -Dict[int,Dict[int,List[Union[Tuple[Literal["PgmC"], int, int],Tuple[Literal["NoteS"], int, int, int],Tuple[Literal["NoteE"], int, int],]],],] -""" - - -MineNoteChannelType = Mapping[ - int, - List[MineNote,], -] -""" -我的世界通道信息类型 - -Dict[int,Dict[int,List[MineNote,],],] -""" - -MineNoteTrackType = Mapping[ - int, - List[MineNote,], -] - - +""" \ No newline at end of file diff --git a/Musicreater/utils.py b/Musicreater/utils.py index e4c647b..da39d25 100644 --- a/Musicreater/utils.py +++ b/Musicreater/utils.py @@ -40,9 +40,9 @@ from .constants import ( MM_INSTRUMENT_DEVIATION_TABLE, MM_INSTRUMENT_RANGE_TABLE, ) -from .exceptions import MusicSequenceDecodeError +from .old_exceptions import MusicSequenceDecodeError from .subclass import MineNote, mctick2timestr, SingleNoteBox -from .types import MidiInstrumentTableType, MineNoteChannelType, FittingFunctionType +from .old_types import MidiInstrumentTableType, MineNoteChannelType, FittingFunctionType def empty_midi_channels( diff --git a/Packer/MSCT_Packer.py b/Packer/MSCT_Packer.py index 4fea1c9..50e549f 100644 --- a/Packer/MSCT_Packer.py +++ b/Packer/MSCT_Packer.py @@ -1,15 +1,15 @@ import Musicreater import Musicreater.experiment -import Musicreater.plugin +import Musicreater.old_plugin # import Musicreater.previous -from Musicreater.plugin.addonpack import ( +from Musicreater.old_plugin.addonpack import ( to_addon_pack_in_delay, to_addon_pack_in_repeater, to_addon_pack_in_score, ) -from Musicreater.plugin.bdxfile import to_BDX_file_in_delay, to_BDX_file_in_score -from Musicreater.plugin.mcstructfile import ( +from Musicreater.old_plugin.bdxfile import to_BDX_file_in_delay, to_BDX_file_in_score +from Musicreater.old_plugin.mcstructfile import ( to_mcstructure_file_in_delay, to_mcstructure_file_in_repeater, to_mcstructure_file_in_score, @@ -21,7 +21,7 @@ MSCT_MAIN = ( # Musicreater.previous, ) -MSCT_PLUGIN = (Musicreater.plugin,) +MSCT_PLUGIN = (Musicreater.old_plugin,) MSCT_PLUGIN_FUNCTION = ( to_addon_pack_in_delay, diff --git a/example.py b/example.py index 581b11c..015daa6 100644 --- a/example.py +++ b/example.py @@ -19,18 +19,18 @@ Terms & Conditions: ./License.md import os import Musicreater -from Musicreater.plugin.addonpack import ( +from Musicreater.old_plugin.addonpack import ( to_addon_pack_in_delay, to_addon_pack_in_repeater, to_addon_pack_in_score, ) -from Musicreater.plugin.mcstructfile import ( +from Musicreater.old_plugin.mcstructfile import ( to_mcstructure_file_in_delay, to_mcstructure_file_in_repeater, to_mcstructure_file_in_score, ) -from Musicreater.plugin.bdxfile import to_BDX_file_in_delay, to_BDX_file_in_score +from Musicreater.old_plugin.bdxfile import to_BDX_file_in_delay, to_BDX_file_in_score # 获取midi列表 midi_path = input(f"请输入MIDI路径:") diff --git a/example_futureFunction.py b/example_futureFunction.py index dffdbae..3c9bbeb 100644 --- a/example_futureFunction.py +++ b/example_futureFunction.py @@ -1,9 +1,9 @@ import Musicreater.experiment -import Musicreater.plugin -import Musicreater.plugin.mcstructfile +import Musicreater.old_plugin +import Musicreater.old_plugin.mcstructfile print( - Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay( + Musicreater.old_plugin.mcstructfile.to_mcstructure_file_in_delay( Musicreater.experiment.FutureMidiConvertM4.from_midi_file( input("midi路径:"), old_exe_format=False ), diff --git a/example_singleConvert.py b/example_singleConvert.py index dd7757e..27098e1 100644 --- a/example_singleConvert.py +++ b/example_singleConvert.py @@ -1,9 +1,9 @@ import Musicreater -import Musicreater.plugin -import Musicreater.plugin.mcstructfile +import Musicreater.old_plugin +import Musicreater.old_plugin.mcstructfile print( - Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay( + Musicreater.old_plugin.mcstructfile.to_mcstructure_file_in_delay( Musicreater.MidiConvert.from_midi_file( input("midi路径:"), old_exe_format=False, diff --git a/example_websocket.py b/example_websocket.py index 011136b..7f5cdda 100644 --- a/example_websocket.py +++ b/example_websocket.py @@ -1,13 +1,13 @@ import Musicreater -import Musicreater.plugin -import Musicreater.plugin.websocket +import Musicreater.old_plugin +import Musicreater.old_plugin.websocket import os dire = input("midi目录:") print( - Musicreater.plugin.websocket.to_websocket_server( + Musicreater.old_plugin.websocket.to_websocket_server( [ Musicreater.MidiConvert.from_midi_file( os.path.join(dire, names), old_exe_format=False diff --git a/let_future_java.py b/let_future_java.py index 2ea0fe5..12ffb3c 100644 --- a/let_future_java.py +++ b/let_future_java.py @@ -11,7 +11,7 @@ import shutil from typing import Optional, Tuple import Musicreater.experiment -from Musicreater.plugin.archive import compress_zipfile +from Musicreater.old_plugin.archive import compress_zipfile from Musicreater.utils import guess_deviation, is_in_diapason diff --git a/pyproject.toml b/pyproject.toml index a5312e4..21f7052 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,6 +4,8 @@ requires-python = ">= 3.8, < 4.0" dependencies = [ "mido >= 1.3", + "tomli>=2.4.0; python_version < '3.11'", + "tomli-w>=1.0.0", "xxhash >= 3", ] @@ -47,7 +49,7 @@ full = [ "TrimMCStruct <= 0.0.5.9", "brotli >= 1.0.0", - "numpy" + "numpy", ] dev = [ "TrimMCStruct <= 0.0.5.9", diff --git a/test_future_kamires.py b/test_future_kamires.py index ac9f907..542c0bd 100644 --- a/test_future_kamires.py +++ b/test_future_kamires.py @@ -1,6 +1,6 @@ import Musicreater.experiment -import Musicreater.plugin -import Musicreater.plugin.mcstructfile +import Musicreater.old_plugin +import Musicreater.old_plugin.mcstructfile msct = Musicreater.experiment.FutureMidiConvertKamiRES.from_midi_file( input("midi路径:"), old_exe_format=False @@ -24,7 +24,7 @@ for name in sorted( print( "\n输出:", - Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay( + Musicreater.old_plugin.mcstructfile.to_mcstructure_file_in_delay( msct, opt, # Musicreater.plugin.ConvertConfig(input("输出路径:"),), diff --git a/test_future_lyric.py b/test_future_lyric.py index 0d31e15..9f1ee3e 100644 --- a/test_future_lyric.py +++ b/test_future_lyric.py @@ -1,6 +1,6 @@ import Musicreater.experiment -import Musicreater.plugin -import Musicreater.plugin.mcstructfile +import Musicreater.old_plugin +import Musicreater.old_plugin.mcstructfile msct = Musicreater.experiment.FutureMidiConvertLyricSupport.from_midi_file( input("midi路径:"), old_exe_format=False @@ -24,7 +24,7 @@ opt = input("输出路径:") print( "\n输出:", - Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay( + Musicreater.old_plugin.mcstructfile.to_mcstructure_file_in_delay( msct, opt, # Musicreater.plugin.ConvertConfig(input("输出路径:"),), diff --git a/uv.lock b/uv.lock index eb8bdf1..61e308d 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 1 +revision = 3 requires-python = ">=3.8, <4.0" resolution-markers = [ "python_full_version >= '3.10'", @@ -578,6 +578,9 @@ name = "musicreater" source = { editable = "." } dependencies = [ { name = "mido" }, + { name = "tomli" }, + { name = "tomli-w", version = "1.0.0", source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" }, marker = "python_full_version < '3.9'" }, + { name = "tomli-w", version = "1.2.0", source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" }, marker = "python_full_version >= '3.9'" }, { name = "xxhash" }, ] @@ -592,6 +595,9 @@ dev = [ ] full = [ { name = "brotli" }, + { name = "numpy", version = "1.24.4", source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" }, marker = "python_full_version < '3.9'" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" }, marker = "python_full_version == '3.9.*'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" }, marker = "python_full_version >= '3.10'" }, { name = "trimmcstruct" }, ] @@ -601,8 +607,11 @@ requires-dist = [ { name = "brotli", marker = "extra == 'full'", specifier = ">=1.0.0" }, { name = "dill", marker = "extra == 'dev'" }, { name = "mido", specifier = ">=1.3" }, + { name = "numpy", marker = "extra == 'full'" }, { name = "pyinstaller", marker = "extra == 'dev'" }, { name = "rich", marker = "extra == 'dev'" }, + { name = "tomli" }, + { name = "tomli-w" }, { name = "trimmcstruct", marker = "extra == 'dev'", specifier = "<=0.0.5.9" }, { name = "trimmcstruct", marker = "extra == 'full'", specifier = "<=0.0.5.9" }, { name = "twine", marker = "extra == 'dev'" }, @@ -1041,6 +1050,85 @@ wheels = [ { url = "https://mirror.nju.edu.cn/pypi/web/packages/58/29/93c53c098d301132196c3238c312825324740851d77a8500a2462c0fd888/setuptools-80.8.0-py3-none-any.whl", hash = "sha256:95a60484590d24103af13b686121328cc2736bee85de8936383111e421b9edc0" }, ] +[[package]] +name = "tomli" +version = "2.4.0" +source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirror.nju.edu.cn/pypi/web/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c" } +wheels = [ + { url = "https://mirror.nju.edu.cn/pypi/web/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4" }, + { url = "https://mirror.nju.edu.cn/pypi/web/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a" }, +] + +[[package]] +name = "tomli-w" +version = "1.0.0" +source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://mirror.nju.edu.cn/pypi/web/packages/49/05/6bf21838623186b91aedbda06248ad18f03487dc56fbc20e4db384abde6c/tomli_w-1.0.0.tar.gz", hash = "sha256:f463434305e0336248cac9c2dc8076b707d8a12d019dd349f5c1e382dd1ae1b9" } +wheels = [ + { url = "https://mirror.nju.edu.cn/pypi/web/packages/bb/01/1da9c66ecb20f31ed5aa5316a957e0b1a5e786a0d9689616ece4ceaf1321/tomli_w-1.0.0-py3-none-any.whl", hash = "sha256:9f2a07e8be30a0729e533ec968016807069991ae2fd921a78d42f429ae5f4463" }, +] + +[[package]] +name = "tomli-w" +version = "1.2.0" +source = { registry = "https://mirror.nju.edu.cn/pypi/web/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version == '3.9.*'", +] +sdist = { url = "https://mirror.nju.edu.cn/pypi/web/packages/19/75/241269d1da26b624c0d5e110e8149093c759b7a286138f4efd61a60e75fe/tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021" } +wheels = [ + { url = "https://mirror.nju.edu.cn/pypi/web/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90" }, +] + [[package]] name = "trimmcstruct" version = "0.0.5.9"