This commit is contained in:
2025-05-09 19:31:58 +08:00
parent b6799d04fa
commit 177d7e1cdb
5 changed files with 201 additions and 0 deletions

BIN
MaichartConverter Executable file

Binary file not shown.

27
MaichartConverter.py Normal file
View File

@@ -0,0 +1,27 @@
import subprocess
class MaichartConverterFailed(BaseException):
def __str__(self):
return "Maichart conversion failed."
def ma2tosimai(ma2path):
cmd = [
"/Users/bennett/Cookies_ToolsKit/mcc/MaichartConverter","CompileMa2","-p",
ma2path,
"-f","SimaiFes"
]
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
text = result.stdout
start_index = text.find("TargetFormat : SimaiFes")
# 如果找到了该字符串,提取后面的内容
if start_index != -1:
extracted_content = text[start_index + len("TargetFormat : SimaiFes"):].strip()
else:
raise MaichartConverterFailed(result.stderr)
return extracted_content

56
ReadOpt.py Normal file
View File

@@ -0,0 +1,56 @@
import xml.etree.ElementTree as ET
def parse_music_xml(file_path):
# 解析 XML 文件
tree = ET.parse(file_path)
root = tree.getroot()
# 获取音乐名称
name_elem = root.find("name/str")
music_name = name_elem.text if name_elem is not None else "?"
# 获取艺术家名称
artist_elem = root.find("artistName/str")
artist_name = artist_elem.text if artist_elem is not None else "?"
# 提取启用谱面信息(设计者 + 等级)
note_infos = []
notes_data = root.find("notesData")
if notes_data is not None:
for note in notes_data.findall("Notes"):
# 判断是否启用
is_enable_elem = note.find("isEnable")
if is_enable_elem is not None and is_enable_elem.text.lower() == "true":
# 提取设计者
designer_elem = note.find("notesDesigner/str")
designer = designer_elem.text if (designer_elem is not None and designer_elem.text) else "-"
# 提取等级
level_elem = note.find("level")
level_decimal_elem = note.find("levelDecimal")
level = level_elem.text if level_elem is not None else "0"
level_decimal = level_decimal_elem.text if level_decimal_elem is not None else "0"
# 组合信息
note_infos.append({
"designer": designer,
"level": f"{level}.{level_decimal}",
"levelshow": f"{level}{"+" if level_decimal > "6" else ""}",
})
return music_name, artist_name, note_infos
if __name__ == "__main__":
file_path = "/Users/bennett/Downloads/SDEZ/Package/Sinmai_Data/StreamingAssets/A000/music/music011663/Music.xml"
music_name, artist_name, note_infos = parse_music_xml(file_path)
print("音乐名称:", music_name)
print("艺术家名称:", artist_name)
print("启用谱面列表:")
for idx, info in enumerate(note_infos, 1):
print(f"{idx}个谱面:")
print(f" 定数: {info['level']}")
print(f" 难度: {info['levelshow']}")
print(f" 谱面设计者: {info['designer']}")

57
pv_convert.py Normal file
View File

@@ -0,0 +1,57 @@
import os
import subprocess
import sys
def get_video_resolution(video_path):
"""获取视频的宽高信息"""
cmd = [
"ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries",
"stream=width,height", "-of", "csv=p=0", video_path
]
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
width, height = map(int, result.stdout.strip().split(","))
return width, height
def calculate_padding(width, height):
"""计算填充黑边,使其变成 1:1正方形"""
max_side = max(width, height) # 以最大边长作为正方形边长
pad_x = (max_side - width) // 2
pad_y = (max_side - height) // 2
return max_side, pad_x, pad_y
def process_video(input_file, output_file):
"""填充黑色边框,调整为 1:1并转换为 H.264"""
width, height = get_video_resolution(input_file)
target_size, pad_x, pad_y = calculate_padding(width, height)
if pad_x == 0 and pad_y == 0:
print("视频已经是 1:1无需填充。")
ffmpeg_cmd = [
"ffmpeg", "-i", input_file,
"-c:v", "h264_videotoolbox",
"-b:v", "50M", "-maxrate", "70M", "-bufsize", "100M",
"-c:a", "aac", "-b:a", "320k",
output_file
]
else:
print(f"填充黑边,使视频变为 {target_size}x{target_size}")
ffmpeg_cmd = [
"ffmpeg", "-i", input_file,
"-vf", f"pad={target_size}:{target_size}:{pad_x}:{pad_y}:black",
"-c:v", "h264_videotoolbox",
"-b:v", "50M", "-maxrate", "70M", "-bufsize", "100M",
"-c:a", "aac", "-b:a", "320k",
output_file
]
subprocess.run(ffmpeg_cmd)
if __name__ == "__main__":
if len(sys.argv) < 3:
print("用法: python pv_convert.py 输入文件 输出文件")
sys.exit(1)
input_file = sys.argv[1]
output_file = sys.argv[2]
process_video(input_file, output_file)

61
pv_decode.py Normal file
View File

@@ -0,0 +1,61 @@
import os
import subprocess
from pathlib import Path
from pv_convert import process_video
# === 配置 ===
WANNACRI_PATH = "wannacri" # 需在 PATH 或填写完整路径
FFMPEG_PATH = "ffmpeg" # 需在 PATH 或填写完整路径
def extract_usm(usm_path: Path, output_dir: Path):
""" 提取 usm 文件内容 """
subprocess.run([WANNACRI_PATH, "extractusm", str(usm_path),"--key","0x7F4551499DF55E68"], cwd=output_dir, check=True)
def convert_ivf_to_mp4(ivf_path: Path, output_mp4_path: Path):
""" 将 ivf 文件转换为 mp4 """
subprocess.run([
FFMPEG_PATH,
"-y", "-i", str(ivf_path),
"-c:v", "copy",
str(output_mp4_path)
], check=True)
def dat_to_mp4(dat_file: str):
""" 将 .dat 文件当作 .usm 文件处理,提取并转换为 .mp4 """
dat_path = Path(dat_file).resolve()
base_name = dat_path.stem
work_dir = dat_path.parent / "work"
usm_path = work_dir / f"{base_name}.dat" # 直接将 .dat 文件当作 .usm
ivf_path = work_dir / "output" / f"{base_name}.dat" / "videos" / f"{base_name}.ivf"
mp4_path = work_dir / f"{base_name}.mp4"
# 直接将 .dat 当作 .usm 文件处理
print(f"[1/3] 提取 USM 内容 ...")
extract_usm(usm_path, work_dir)
if not ivf_path.exists():
print(f"❌ 提取失败,未找到 {ivf_path.name}")
return
print(f"[2/3] 转换为 MP4 ...")
convert_ivf_to_mp4(ivf_path, mp4_path)
print(f"[3/3] 成功生成:{mp4_path}")
return mp4_path
# === 示例用法 ===
if __name__ == "__main__":
import sys
if len(sys.argv) != 2:
print("用法: python dat_to_mp4.py <xxx.dat>")
exit(1)
dat_file = sys.argv[1]
mp4_output = dat_to_mp4(dat_file)
if mp4_output:
converted_mp4_path = f"{mp4_output.stem}(converted).mp4"
print(f"[4/4] 开始转换为新的 MP4 文件:{converted_mp4_path}")
process_video(mp4_output, converted_mp4_path)