mirror of
https://gitea.pjck.top/Cookies/CookiesChartConverter.git
synced 2025-12-14 10:46:55 +08:00
Init
This commit is contained in:
BIN
MaichartConverter
Executable file
BIN
MaichartConverter
Executable file
Binary file not shown.
27
MaichartConverter.py
Normal file
27
MaichartConverter.py
Normal 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
56
ReadOpt.py
Normal 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
57
pv_convert.py
Normal 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
61
pv_decode.py
Normal 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)
|
||||
|
||||
Reference in New Issue
Block a user