Repository: windhide/SkyMusicPlay-for-Windows
Branch: main
Commit: 8446e7c36849
Files: 103
Total size: 684.1 KB
Directory structure:
gitextract_xsmzwekj/
├── .gitattributes
├── .github/
│ └── FUNDING.yml
├── .gitignore
├── .version
├── README.md
├── buildProject.ps1
├── draw-follow-window/
│ ├── README.md
│ ├── draw_server.py
│ └── draw_socket_demo.py
├── sky-music-server/
│ ├── README.md
│ ├── music_compare_repeat.py
│ ├── music_file_process.py
│ ├── music_translate.py
│ ├── requirements.txt
│ ├── sky_music_apis.py
│ ├── sky_music_server.py
│ ├── version.txt
│ └── windhide/
│ ├── auto/
│ │ └── auto_thread.py
│ ├── musicToSheet/
│ │ ├── aigc_handler_sheet.py
│ │ ├── music2html.py
│ │ ├── process_audio.py
│ │ ├── transfer_MID.py
│ │ └── vocals_split.py
│ ├── playRobot/
│ │ ├── amd_robot.py
│ │ └── intel_robot.py
│ ├── static/
│ │ └── global_variable.py
│ ├── thread/
│ │ ├── amd_play_thread.py
│ │ ├── follow_process_thread.py
│ │ ├── follow_thread.py
│ │ ├── frame_alive_thread.py
│ │ ├── hwnd_check_thread.py
│ │ ├── intel_play_thread.py
│ │ ├── queue_thread.py
│ │ └── shortcut_thread.py
│ └── utils/
│ ├── auto_util.py
│ ├── command_util.py
│ ├── config_util.py
│ ├── hook_util.py
│ ├── hwnd_utils.py
│ ├── ocr_follow_util.py
│ ├── ocr_heart_utils.py
│ ├── ocr_normal_utils.py
│ ├── path_util.py
│ ├── play_util.py
│ └── sheet_decrypt_util.py
├── sky-music-web/
│ ├── .editorconfig
│ ├── .eslintignore
│ ├── .eslintrc.cjs
│ ├── .gitignore
│ ├── .npmrc
│ ├── .prettierignore
│ ├── .prettierrc.yaml
│ ├── README.md
│ ├── build/
│ │ └── entitlements.mac.plist
│ ├── dev-app-update.yml
│ ├── electron-builder.yml
│ ├── electron.vite.config.ts
│ ├── package.json
│ ├── src/
│ │ ├── main/
│ │ │ └── index.ts
│ │ ├── preload/
│ │ │ ├── index.d.ts
│ │ │ └── index.ts
│ │ └── renderer/
│ │ ├── index.html
│ │ └── src/
│ │ ├── App.vue
│ │ ├── component/
│ │ │ └── svg/
│ │ │ ├── cr.vue
│ │ │ ├── dm.vue
│ │ │ └── dmcr.vue
│ │ ├── env.d.ts
│ │ ├── i18n/
│ │ │ ├── index.ts
│ │ │ └── locales/
│ │ │ ├── en.json
│ │ │ ├── jp.json
│ │ │ ├── ko.json
│ │ │ ├── zh-classical.json
│ │ │ ├── zh-cn.json
│ │ │ └── zh-tw.json
│ │ ├── main.ts
│ │ ├── router/
│ │ │ └── index.ts
│ │ ├── store/
│ │ │ └── index.ts
│ │ ├── utils/
│ │ │ ├── configStore.ts
│ │ │ └── fetchUtils.ts
│ │ └── views/
│ │ ├── ai_setting.vue
│ │ ├── home.vue
│ │ ├── home_loader.vue
│ │ ├── hwndHandle.vue
│ │ ├── kube.vue
│ │ ├── magicTools.vue
│ │ ├── music.vue
│ │ ├── music_edit.vue
│ │ ├── setting.vue
│ │ ├── shortcutKeys.vue
│ │ └── tutorial.vue
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── tsconfig.web.json
└── template-resources/
├── myFavorite/
│ └── .keep
├── myImport/
│ └── .keep
├── myTranslate/
│ └── .keep
├── systemTools/
│ ├── drawTool/
│ │ └── .keep
│ ├── modelData/
│ │ ├── check_key_model.pt
│ │ ├── demoScheenshot/
│ │ │ └── .keep
│ │ └── friend_model.pt
│ └── scriptTemplate/
│ └── .keep
├── translateMID/
│ └── .keep
└── translateOriginalMusic/
└── .keep
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
*.pth filter=lfs diff=lfs merge=lfs -text
*.exe filter=lfs diff=lfs merge=lfs -text
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
thanks_dev: # Replace with a single thanks.dev username
custom: ['https://afdian.com/a/WindHdie']
================================================
FILE: .gitignore
================================================
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
#Electron-builder output
/dist_electron
*.pyc
sky-music-server/.idea/
sky-music-server/node_modules/
sky-music-web/node_modules/
sky-music-web/.idea/
sky-music-server/build/
sky-music-web/web2/
sky-music-web/dist_electron/
sky-music-web/backend_dist
sky-music-web/out
draw-follow-window/build
draw-follow-window/dist
web2/
**/__pycache__/**
resources/
*.spec
ffmpeg.exe
template-resources/systemMusic/**
================================================
FILE: .version
================================================
{
"version": "2.6.5",
"title": "更新啦~新年快乐🔔",
"content": "🍉新版本v2.6.6\\n🍊更新群-1007672060\\n💡版本更新协助\\n🥝 更新日志\\n\\t⭐️ 演奏、转谱界面,已转换歌曲进行细分分类\\n\\t⭐️ 转谱添加人声分离功能\\n\\t🔧 修复同步歌单的时候带来的问题 \\n\\t (如果有需要 群里也有单独解密的软件进行下载)",
"downloadUrl": "https://github.com/windhide/SkyMusicPlay-for-Windows/releases/download/v2.6.6/v2.6.6_x64_windows.exe",
"positiveText": "新年快乐🎇",
"negativeText": "朕知道了,退下吧。🥲"
}
================================================
FILE: README.md
================================================
# SkyMusicPlay-for-Windows
星星弹琴软件
## 丨安装提醒
>
> **安装请`关闭杀毒软件`进行**
>
> 支持PC端,也支持模拟器端
>
> 🚧目前还在施工中,功能快速迭代中...🚧
>
>
> ✨如果需要相关创意功能欢迎在issues中提出✨
>
## 丨项目开发环境
- **Python**:3.11
- **Node**:16.20.2
## 丨技术栈
- **Electron**
- **TypeScript**
- **Vite**
- **Naive-UI**
- **Python**
- **YOLO**
- **WebSocket**
## 丨其他
- 项目引用或代码引用请注明原作者出处,禁止商业用途
- [Creative Commons Attribution-NonCommercial (CC BY-NC) 许可协议](https://creativecommons.org/licenses/by-nc/4.0/deed.zh-hans)
## 丨联系方式
- 如有任何问题或建议,欢迎通过以下方式与我联系:
- [Email](mailto:WindHide520@gmail.com)
- [GitHub](https://github.com/windhide)
## 致谢
- [pianotrans](https://github.com/azuwis/pianotrans) 音乐转钢琴谱
- [basic-pitch](https://github.com/spotify/) 音乐转MIDI
================================================
FILE: buildProject.ps1
================================================
# =========================
# 自动管理员提权
# =========================
$principal = New-Object Security.Principal.WindowsPrincipal(
[Security.Principal.WindowsIdentity]::GetCurrent()
)
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Host "This script requires administrator privileges."
Write-Host "Attempting to restart with administrator rights..."
Start-Process powershell `
-Verb RunAs `
-ArgumentList "-NoExit -ExecutionPolicy Bypass -File `"$PSCommandPath`""
exit
}
# =========================
# 脚本所在目录
# =========================
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
Set-Location $ScriptDir
Write-Host "Current script directory: $ScriptDir"
# =========================
# 删除旧构建目录
# =========================
$pathsToRemove = @(
"$ScriptDir\sky-music-web\dist",
"$ScriptDir\sky-music-server\build",
"$ScriptDir\sky-music-web\backend_dist"
)
foreach ($path in $pathsToRemove) {
if (Test-Path $path) {
Write-Host "Removing: $path"
Remove-Item -Recurse -Force -Path $path
}
}
# =========================
# 构建 Python 服务器
# =========================
Write-Host "`n=== Building Python Server ==="
Set-Location "$ScriptDir\sky-music-server"
& ".\.venv\Scripts\python.exe" -m PyInstaller `
-i icon.ico `
sky_music_server.py `
--distpath "$ScriptDir\sky-music-web\backend_dist" `
--version-file "$ScriptDir\sky-music-server\version.txt" `
--hidden-import main `
--collect-all sklearn `
--collect-all basic_pitch `
--collect-all plyer `
--collect-all torch `
--collect-all torchvision `
--hidden-import=torch `
--hidden-import demucs `
--collect-all demucs `
--hidden-import=torchvision `
--uac-admin
if ($LASTEXITCODE -ne 0) {
Write-Error "PyInstaller build failed."
exit 1
}
# =========================
# 复制 ffmpeg.exe
# =========================
Write-Host "`nCopying ffmpeg.exe"
$ffmpegSrc = "$ScriptDir\ffmpeg.exe"
$ffmpegDst = "$ScriptDir\sky-music-web\backend_dist\sky_music_server\ffmpeg.exe"
if (Test-Path $ffmpegSrc) {
Copy-Item $ffmpegSrc $ffmpegDst -Force
} else {
Write-Warning "ffmpeg.exe not found: $ffmpegSrc"
}
# =========================
# 构建 Electron 应用
# =========================
Write-Host "`n=== Building Electron App ==="
Set-Location "$ScriptDir\sky-music-web"
& npm run build:win
if ($LASTEXITCODE -ne 0) {
Write-Error "Electron build failed."
exit 1
}
# =========================
# 完成提示
# =========================
Write-Host "`nAll tasks completed successfully!"
Pause
================================================
FILE: draw-follow-window/README.md
================================================
```shell
pyinstaller --onefile --noconsole --clean --strip --name draw_server draw_server.py
```
```shell
draw_server.exe --width 1024 --height 768 --x 200 --y 300
```
================================================
FILE: draw-follow-window/draw_server.py
================================================
import tkinter as tk
import socket
import threading
import sys
import argparse
import os
# 针对 Windows 系统设置 DPI Awareness(适用于 Windows 8.1 及以上)
if os.name == "nt":
try:
from ctypes import windll
windll.shcore.SetProcessDpiAwareness(1)
except Exception as e:
print("无法设置 DPI Awareness,可能影响窗口几何尺寸的准确性。")
# 绘制和删除方框的 API
def draw_box_api(canvas, width, height, position_x, position_y, box_id=None):
# 在 Canvas 上绘制一个指定大小和位置的方框
if box_id is not None:
# 重用已有的方框对象
canvas.coords(box_id,
position_x, position_y,
position_x + width, position_y + height)
return box_id
else:
# 创建新的方框对象
return canvas.create_rectangle(
position_x, position_y,
position_x + width, position_y + height,
outline="#00ffff", width=3
)
def delete_box_api(canvas, box_id, box_pool=None):
# 从 Canvas 上删除指定 ID 的方框
if box_pool is not None and len(box_pool) < 100:
# 将方框对象添加到对象池中
canvas.itemconfig(box_id, state='hidden')
box_pool.append(box_id)
else:
# 对象池已满或未使用对象池,直接删除
canvas.delete(box_id)
# 主窗口类
class TransparentBoxWindow:
def __init__(self, root, width, height, position_x, position_y):
self.root = root
self.port = 12345 # 固定端口
# 设置 Canvas
self.canvas = tk.Canvas(root, width=width, height=height, bg="white", highlightthickness=0)
self.canvas.pack()
# 绘制红色边框
# self.draw_red_border(width, height)
# 存储用户自定义 ID 与 Tkinter 方框 ID 的映射
self.boxes = {}
# 对象池 - 存储可重用的方框对象
self.box_pool = []
self.max_pool_size = 100 # 对象池最大容量
# 启动 Socket 服务器
threading.Thread(target=self.start_server, daemon=True).start()
def draw_red_border(self, width, height, border_thickness=10):
"""在 Canvas 上绘制一个红色的边框"""
self.canvas.create_rectangle(
border_thickness // 2, border_thickness // 2,
width - border_thickness // 2, height - border_thickness // 2,
outline="red", width=border_thickness, tags="red_border"
)
def start_server(self):
# 创建 Socket 服务器
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("localhost", self.port))
server.listen(5)
print(f"服务器已启动,监听端口:{self.port},等待连接...")
while True:
client, addr = server.accept()
print(f"收到连接来自 {addr}")
threading.Thread(target=self.handle_client, args=(client,), daemon=True).start()
def handle_client(self, client):
try:
buffer = ""
command_batch = []
while True:
data = client.recv(1024).decode("utf-8")
if not data:
break
buffer += data
# 按换行符分割命令
commands = buffer.split("\n")
# 收集完整命令到批处理列表
for command in commands[:-1]:
print(f"收到命令: {command}")
command_batch.append(command)
# 当积累了足够的命令或遇到特殊命令时执行批处理
if len(command_batch) >= 30 or any(cmd.startswith(("resize", "exit", "update")) for cmd in command_batch):
self.process_command_batch(command_batch)
command_batch = []
# 将最后一个不完整的命令保留到下一次处理
buffer = commands[-1]
# 处理剩余的命令
if command_batch:
self.process_command_batch(command_batch)
except ConnectionResetError:
print("客户端断开连接")
finally:
client.close()
def process_command_batch(self, commands):
# 创建命令分类字典
command_groups = {
"draw": [],
"delete": [],
"update": None,
"resize": None,
"exit": False
}
# 对命令进行分类
for command in commands:
parts = command.split()
cmd_type = parts[0]
if cmd_type == "draw":
command_groups["draw"].append({
"id": parts[1],
"width": int(parts[2]),
"height": int(parts[3]),
"x": int(parts[4]),
"y": int(parts[5])
})
elif cmd_type == "delete":
command_groups["delete"].append(parts[1])
elif cmd_type == "update":
# 将update命令作为触发批处理的信号
command_groups["update"] = True
elif cmd_type == "resize":
command_groups["resize"] = {
"width": int(parts[1]),
"height": int(parts[2]),
"x": int(parts[3]),
"y": int(parts[4])
}
elif cmd_type == "exit":
command_groups["exit"] = True
# 批量处理删除命令
for box_id in command_groups["delete"]:
if box_id in self.boxes:
tkinter_id = self.boxes[box_id]
delete_box_api(self.canvas, tkinter_id, self.box_pool)
del self.boxes[box_id]
# 批量处理绘制命令
for box in command_groups["draw"]:
if box["id"] not in self.boxes:
# 尝试从对象池中获取方框对象
reused_box_id = None
if self.box_pool:
reused_box_id = self.box_pool.pop()
self.canvas.itemconfig(reused_box_id, state='normal')
tkinter_id = draw_box_api(
self.canvas,
box["width"],
box["height"],
box["x"],
box["y"],
reused_box_id
)
self.boxes[box["id"]] = tkinter_id
# 处理调整窗口大小命令
if command_groups["resize"]:
resize = command_groups["resize"]
self.change_window_geometry(
resize["width"],
resize["height"],
resize["x"],
resize["y"]
)
# 处理退出命令
if command_groups["exit"]:
print("接收到退出指令,正在退出程序...")
self.exit_program()
# 更新Canvas
self.root.update_idletasks()
def change_window_geometry(self, width, height, position_x, position_y):
print(f"更改窗口尺寸和位置为:{width}x{height}, 坐标: ({position_x}, {position_y})")
# 隐藏所有方框而不是删除它们
for tkinter_id in self.boxes.values():
self.canvas.itemconfig(tkinter_id, state='hidden')
if len(self.box_pool) < self.max_pool_size:
self.box_pool.append(tkinter_id)
self.boxes.clear()
# 修改窗口和Canvas大小
self.root.geometry(f"{width}x{height}+{position_x}+{position_y}")
self.canvas.config(width=width, height=height)
# 一次性更新UI
self.root.update_idletasks()
# 输出调整后的实际窗口大小
actual_width = self.root.winfo_width()
actual_height = self.root.winfo_height()
print(f"调整后实际窗口尺寸: {actual_width}x{actual_height}")
# 输出调整后的实际窗口大小
actual_width = self.root.winfo_width()
actual_height = self.root.winfo_height()
print(f"调整后实际窗口尺寸: {actual_width}x{actual_height}")
def exit_program(self):
self.root.destroy()
sys.exit(0)
if __name__ == "__main__":
# 解析命令行参数
parser = argparse.ArgumentParser(description="Transparent Box GUI")
parser.add_argument("--width", type=int, default=800, help="Window width") # 窗口宽度
parser.add_argument("--height", type=int, default=600, help="Window height") # 窗口高度
parser.add_argument("--x", type=int, default=100, help="Window position X") # 窗口 X 坐标
parser.add_argument("--y", type=int, default=100, help="Window position Y") # 窗口 Y 坐标
args = parser.parse_args()
width = args.width
height = args.height
x = args.x
y = args.y
root = tk.Tk()
# 尽管已在 Windows 下尝试设置 DPI Awareness,仍保持 scaling 为 1.0
root.tk.call('tk', 'scaling', 1.0)
# 先设置 geometry,再调用 update_idletasks 确保尺寸生效
root.geometry(f"{width}x{height}+{x}+{y}")
root.update_idletasks()
root.overrideredirect(True)
root.attributes("-transparentcolor", "white")
root.attributes("-topmost", True)
app = TransparentBoxWindow(root, width, height, x, y)
root.mainloop()
================================================
FILE: draw-follow-window/draw_socket_demo.py
================================================
import socket
import time
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("localhost", 12345)) # 连接到服务器
def send_command(command):
try:
client.sendall(command.encode("utf-8")) # 发送命令
print(f"发送命令: {command}")
except ConnectionRefusedError:
print("无法连接到服务器,请确保服务端已启动。")
# 示例指令序列
send_command("draw box1 100 50 200 200\n") # 绘制第一个方框
time.sleep(1) # 等待 1 秒
send_command("draw box2 150 100 300 300\n") # 绘制第二个方框
time.sleep(1) # 等待 1 秒
# 调整窗口尺寸和位置,同时清空所有已绘制的方框
send_command("resize 2560 1080 50 10\n") # 更改窗口为 1000x800 大小,位置 (50, 50)
# time.sleep(2) # 等待 2 秒
send_command("draw box2 150 100 1000 1000\n") # 绘制第二个方框
time.sleep(1) # 等待 1 秒
# 再次绘制新的方框
send_command("draw box3 120 80 400 100\n") # 绘制第三个方框
time.sleep(1) # 等待 1 秒
send_command("draw box4 200 150 500 400\n") # 绘制第四个方框
time.sleep(2) # 等待 2 秒
# 删除某些方框
send_command("delete box3\n") # 删除第三个方框
time.sleep(2) # 等待 2 秒
send_command("delete box4\n") # 删除第四个方框
time.sleep(2) # 等待 2 秒
send_command("update\n") # 删除第四个方框
time.sleep(2) # 等待 2 秒
# 添加退出指令
send_command("exit \n") # 发送退出指令到服务器
print("发送退出指令,客户端结束运行。")
================================================
FILE: sky-music-server/README.md
================================================
打包指令
```shell
# 有命令调试打包
pyinstaller --uac-admin sky_music_server.py -i icon.ico --upx-dir D:\Desktop\upx-4.2.2-win64\ --distpath D:\Desktop\SkyMusicPlay-for-Windows\sky-music-web\dist\win-unpacked\backend_dist --hidden-import=main --collect-all=sklearn --collect-all=basic_pitch
# 无命令调试打包
pyinstaller --uac-admin -w sky_music_server.py -i icon.ico --upx-dir D:\Desktop\upx-4.2.2-win64\ --distpath D:\Desktop\SkyMusicPlay-for-Windows\sky-music-web\dist\win-unpacked\backend_dist --hidden-import=main --collect-all=sklearn --collect-all=basic_pitch
```
> ffmpeg.exe 放在和 sky_windows_music.exe平级
================================================
FILE: sky-music-server/music_compare_repeat.py
================================================
import difflib
import hashlib
import os
import shutil
from collections import defaultdict
def get_file_hash(file_path, block_size=65536):
"""计算文件的 SHA-256 哈希值"""
hasher = hashlib.sha256()
with open(file_path, 'rb') as f:
while chunk := f.read(block_size):
hasher.update(chunk)
return hasher.hexdigest()
def get_file_similarity(file1, file2):
"""计算两个文件的内容相似度"""
with open(file1, 'r', errors='ignore') as f1, open(file2, 'r', errors='ignore') as f2:
text1 = f1.readlines()
text2 = f2.readlines()
return difflib.SequenceMatcher(None, text1, text2).ratio()
def find_similar_files(input_folder, threshold=0.8):
"""查找相似文件并标记需要删除的文件"""
files = [os.path.join(input_folder, f) for f in os.listdir(input_folder) if
os.path.isfile(os.path.join(input_folder, f))]
# 用哈希分组
hash_groups = defaultdict(list)
for file in files:
file_hash = get_file_hash(file)
hash_groups[file_hash].append(file)
to_delete = set()
checked_pairs = set()
for group in hash_groups.values():
# 如果 MD5 相同的文件超过 1 个,则这些都属于重复文件
if len(group) > 1:
# 保留最大(或最老)的一个,其余删除
group_sorted = sorted(group, key=lambda f: os.path.getsize(f), reverse=True)
keep = group_sorted[0]
duplicates = group_sorted[1:]
to_delete.update(duplicates)
continue
# 其它情况下才继续相似度比对(不同 hash)
for i, file1 in enumerate(group):
for file2 in group[i + 1:]:
if (file1, file2) in checked_pairs:
continue
checked_pairs.add((file1, file2))
similarity = get_file_similarity(file1, file2)
if similarity >= threshold:
smaller_file = min(file1, file2, key=lambda f: os.path.getsize(f))
to_delete.add(smaller_file)
return to_delete
def process_files(input_folder, output_folder, threshold=0.8):
"""处理文件夹,删除重复文件并移动剩余文件"""
if not os.path.exists(output_folder):
os.makedirs(output_folder)
to_delete = find_similar_files(input_folder, threshold)
# 删除重复文件
deleted_files = list(to_delete)
for file in to_delete:
os.remove(file)
# 移动剩余文件
for file in os.listdir(input_folder):
full_path = os.path.join(input_folder, file)
if os.path.isfile(full_path):
shutil.move(full_path, os.path.join(output_folder, file))
print(f"删除了 {len(deleted_files)} 个文件:")
for file in deleted_files:
print(file)
if __name__ == "__main__":
input_folder = r"D:\Desktop\处理好的" # 输入文件夹路径
output_folder = r"D:\Desktop\二次处理的" # 处理后正常输出路径
process_files(input_folder, output_folder)
================================================
FILE: sky-music-server/music_file_process.py
================================================
import json
import os
import re
import shutil # 用于移动文件
from concurrent.futures import ThreadPoolExecutor
import chardet # 用于检测文件编码
def sanitize_filename(name):
"""清理文件名中的非法字符"""
return re.sub(r'[<>:"/\\\\|?*]', '_', name)
def process_file(file_path, normal_output_folder, encrypted_folder, keyword="1Key"):
try:
# 自动检测文件编码
with open(file_path, 'rb') as file:
raw_data = file.read()
detected = chardet.detect(raw_data)
encoding = detected.get('encoding', 'utf-8') or 'utf-8' # 避免 None
# 用检测到的编码读取文件
with open(file_path, 'r', encoding=encoding) as file:
content = file.read()
# 解析 JSON
try:
json_data = json.loads(content)
except json.JSONDecodeError as e:
print(f"❌ 解析 JSON 失败: {file_path}, 错误: {e}")
return
# 确保 JSON 是列表并且不为空
if not isinstance(json_data, list) or len(json_data) == 0:
print(f"❌ 无效的 JSON 结构: {file_path}")
return
# **检查是否包含关键词 "1Key"**
if keyword in content:
first_name = json_data[0].get('name', '未命名文件')
sanitized_name = sanitize_filename(first_name)
new_file_name = f"{sanitized_name}.txt"
new_file_path = os.path.join(normal_output_folder, new_file_name)
# 避免文件名冲突
counter = 1
while os.path.exists(new_file_path):
new_file_name = f"{sanitized_name}_{counter}.txt"
new_file_path = os.path.join(normal_output_folder, new_file_name)
counter += 1
# **将内容转换为 UTF-8 并保存**
with open(new_file_path, 'w', encoding='utf-8') as new_file:
new_file.write(content)
print(f"✅ 文件 {os.path.basename(file_path)} 已重命名并保存到 {normal_output_folder}")
return
# **检查是否包含 isEncrypted: true**
for item in json_data:
if isinstance(item, dict) and item.get("isEncrypted") is True:
# **直接移动文件到加密文件夹**
shutil.move(file_path, os.path.join(encrypted_folder, os.path.basename(file_path)))
print(f"✅ 文件 {os.path.basename(file_path)} 已移动到 {encrypted_folder}")
return
# **如果既不是加密文件,也不包含关键词 "1Key",则跳过**
print(f"⚠️ 文件 {os.path.basename(file_path)} 不包含 'isEncrypted': true 或 '{keyword}',跳过处理")
except (KeyError, IOError, UnicodeDecodeError) as e:
print(f"❌ 处理文件 {os.path.basename(file_path)} 时出错: {e}")
def process_files(input_folder, normal_output_folder, encrypted_folder, keyword="1Key"):
# **确保目标文件夹存在**
os.makedirs(normal_output_folder, exist_ok=True)
os.makedirs(encrypted_folder, exist_ok=True)
# **使用线程池并行处理文件**
with ThreadPoolExecutor() as executor:
for root, _, files in os.walk(input_folder):
for file_name in files:
file_path = os.path.join(root, file_name)
executor.submit(process_file, file_path, normal_output_folder, encrypted_folder, keyword)
print("✅ 所有文件处理完成。")
if __name__ == '__main__':
# **文件夹路径**
input_folder = r"D:\SoftWareStorage\Tencent Download\额外的谱子_倒卖死妈_一辈子都是垃圾" # 输入文件夹路径
normal_output_folder = r"D:\Desktop\处理好的" # 处理后正常输出路径
encrypted_folder = r"D:\Desktop\加密的" # 加密文件存放路径
process_files(input_folder, normal_output_folder, encrypted_folder)
================================================
FILE: sky-music-server/music_translate.py
================================================
import os
from basic_pitch import ICASSP_2022_MODEL_PATH
from basic_pitch.inference import predict_and_save
if __name__ == '__main__':
# 定义输入文件和输出路径
desktop_path = os.path.join(os.path.expanduser("~"), "Desktop") # 获取桌面路径
output_midi_path = "D:\\Desktop"
try:
predict_and_save(
[
r"D:\Desktop\孙燕姿 - 第一天.mp3"
],
output_midi_path,
True,
False,
False,
False,
ICASSP_2022_MODEL_PATH
)
except Exception as e:
print(f"处理失败:{e}")
================================================
FILE: sky-music-server/requirements.txt
================================================
plyer==2.1.0
pynput==1.7.7
charset-normalizer==3.4.1
chardet==5.2.0
ultralytics==8.3.61
opencv-python==4.10.0.84
numpy==1.23.5
PyAutoGUI==0.9.54
scikit-learn==1.5.1
psutil==6.1.1
keyboard==0.13.5
Jinja2==3.1.6
platformdirs==4.3.6
requests==2.32.3
uvicorn==0.34.0
fastapi==0.115.6
torch==2.5.1
matplotlib==3.10.0
torchlibrosa==0.1.0
librosa==0.10.2.post1
audioread==3.0.1
mido==1.3.3
pretty_midi
pywin32
python-multipart
pyinstaller
websocket_server
basic_pitch
coremltools
scikit-learn==1.5.1
protobuf==4.25.6
openai==1.85.0
demucs==4.0.1
================================================
FILE: sky-music-server/sky_music_apis.py
================================================
import json
import os
import time
import webbrowser
import psutil
import requests
from fastapi import FastAPI, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from windhide.musicToSheet.aigc_handler_sheet import general_ai
from windhide.playRobot import amd_robot, intel_robot
from windhide.static.global_variable import GlobalVariable
from windhide.utils.auto_util import auto_click_fire, shutdown
from windhide.utils.config_util import set_config, get_config, favorite_music, convert_sheet, drop_file
from windhide.utils.hwnd_utils import get_running_apps, get_running_apps_by_struct
from windhide.utils.ocr_follow_util import set_next_sheet, get_key_position, test_key_model_position, \
open_follow
from windhide.utils.ocr_heart_utils import get_friend_model_position
from windhide.utils.path_util import getTypeMusicList, getResourcesPath, process_sheet_rename_time
from windhide.utils.play_util import start, pause, resume, stop
from windhide.utils.sheet_decrypt_util import decrypt_sheet
# 避开与光遇相同核心运行
process = psutil.Process(os.getpid())
all_cores = list(range(psutil.cpu_count()))
cores_to_use = [core for core in all_cores if core != 0]
process.cpu_affinity(cores_to_use)
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 允许的源,可根据需求设置特定地址或使用 ["*"] 允许所有
allow_credentials=True, # 允许携带认证信息(如 Cookies)
allow_methods=["*"], # 允许的 HTTP 方法(如 GET、POST)
allow_headers=["*"], # 允许的 HTTP 请求头
)
async def get_list(listName: str, searchStr: str):
return getTypeMusicList(listName, searchStr)
def play_operate(request: dict):
match request["operate"]:
case 'start':
start(request)
case 'pause':
pause()
case 'resume':
resume()
case 'stop':
stop()
def get_progress():
return {
"overall_progress": f"{GlobalVariable.overall_progress:.1f}",
"now_progress": f"{GlobalVariable.now_progress:.1f}",
"now_translate_text": GlobalVariable.now_translate_text,
"now_play_music": GlobalVariable.now_play_music,
"now_total_time": GlobalVariable.now_total_time,
"now_current_time": GlobalVariable.now_current_time
}
async def create_upload_files(file: UploadFile):
path = os.path.join(getResourcesPath("translateOriginalMusic"), f'{file.filename}')
with open(path, 'wb') as f:
for chunk in iter(lambda: file.file.read(1024), b''):
f.write(chunk)
return "ok"
async def create_upload_files_user(file: UploadFile):
# 读取文件内容
file_content = await file.read()
import chardet
detected = chardet.detect(file_content)
encoding = detected.get('encoding', 'utf-8')
text_content = file_content.decode(encoding)
data = json.loads(text_content)
is_encrypted = data[0].get("isEncrypted", False)
if is_encrypted:
print("解密触发")
data = decrypt_sheet(data)
# 提取 songNotes 并计算时间戳
song_notes = data[0].get("songNotes", [])
if not song_notes:
raise ValueError("No songNotes found in the file")
sum_time = int(song_notes[-1]["time"]) + int(song_notes[-1].get("duration", 0))
# 生成新的文件名
name, ext = os.path.splitext(file.filename)
new_filename = f"{name}-#{sum_time}{ext}"
path = os.path.join(getResourcesPath("myImport"), new_filename)
# 保存文件
with open(path, 'wb') as f:
f.write(json.dumps(data).encode())
return "ok"
def translate(request: dict):
from windhide.musicToSheet.process_audio import process_directory_with_progress
match request["operate"]:
case 'translate':
process_directory_with_progress(request["value"])
process_sheet_rename_time(isImportOrTranslate=True)
return "ok"
def config_operate(request: dict):
match request["operate"]:
case 'set':
set_config(request)
case 'get':
return get_config(request)
case 'favorite_music':
favorite_music(request)
case 'convert_sheet':
return convert_sheet(request)
case 'drop_file':
drop_file(request)
case 'get_key_position':
return get_key_position(float(request["conf"]))
case 'cpu_type':
return True if GlobalVariable.cpu_type == "AMD" else False
case 'hwnd_get':
return get_running_apps()
case 'hwnd_get_now':
if GlobalVariable.window["hWnd"] is None:
return "Nothing"
else:
return GlobalVariable.hwnd_title
case 'hwnd_set':
if request["value"] == 'reset':
GlobalVariable.is_custom_hwnd = False
else:
return get_running_apps_by_struct(request["value"])
return "ok"
def follow(request: dict):
match request["operate"]:
case 'setSheet':
set_next_sheet(request)
case 'openFollow':
open_follow()
def check():
return 'True'
def open_browser(url: str):
webbrowser.open(url)
return 'ok'
def open_files(request: dict):
match request["operate"]:
case 'images':
appdata_path = os.getenv('APPDATA')
os.startfile(os.path.join(appdata_path, 'ThatGameCompany', 'com.netease.sky', 'images'))
case 'files':
os.startfile(os.path.join(getResourcesPath(None), request["type"]))
def get_update():
response = requests.get('https://gitee.com/WindHide/SkyMusicPlay-for-Windows/raw/main/.version')
if response.status_code == 200:
return json.loads(response.text)
return "404"
# 下面放识别相关的调用
def auto(request: dict):
match request["operate"]:
case 'click_fire':
auto_click_fire()
case 'shutdown':
shutdown()
def get_path(request: dict):
return getResourcesPath(request["type"])
def test(request: dict):
match request["operate"]:
case 'image':
return get_friend_model_position(float(request["conf"]), isTest=True)
case 'key':
test_key_model_position(float(request["conf"]))
return None
case 'press':
match GlobalVariable.cpu_type:
case 'Intel':
intel_robot.key_down(request["key"])
time.sleep(0.01)
intel_robot.key_up(request["key"])
return None
case 'AMD':
amd_robot.key_down(request["key"])
time.sleep(0.01)
amd_robot.key_up(request["key"])
return None
return None
return None
def aigc(request: dict):
print(f"okay now is running aigc {request['ai']}")
# if request["ai"] == "Gemini":
# return gemini_ai(model=request["model"],filename=request["filename"],type_=request["type"])
# else:
return general_ai(model=request["model"],filename=request["filename"],type_=request["type"], platform=request["ai"])
def register_routes(app: FastAPI):
app.get("/")(get_list)
app.get("/check")(check)
app.get("/update")(get_update)
app.get("/getProgress")(get_progress)
app.get("/openBrowser")(open_browser)
app.get("/syncSheetName")(process_sheet_rename_time)
app.post("/play_operate")(play_operate)
app.post("/userMusicUpload")(create_upload_files_user)
app.post("/config_operate")(config_operate)
app.post("/fileUpload")(create_upload_files)
app.post("/openFiles")(open_files)
app.post("/translate")(translate)
app.post("/follow")(follow)
app.post("/auto")(auto)
app.post("/aigc")(aigc)
app.post("/path")(get_path)
app.post("/test")(test)
================================================
FILE: sky-music-server/sky_music_server.py
================================================
import logging
import os
import queue
from contextlib import asynccontextmanager
import sys
import threading
import psutil
import uvicorn
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from sky_music_apis import register_routes
from windhide.static.global_variable import GlobalVariable
from windhide.thread.frame_alive_thread import monitor_process
from windhide.thread.hwnd_check_thread import start_thread as hwnd_check_thread
from windhide.thread.queue_thread import music_start_tasks
from windhide.thread.shortcut_thread import startThread as shortcut_thread
os.environ["TORCHAUDIO_USE_TORCHCODEC"] = "0"
# 设置 CPU 亲和性,避开与光遇相同核心运行
process = psutil.Process(os.getpid())
all_cores = list(range(psutil.cpu_count()))
# 避开光遇核心(假设是 0)和系统核心(可选避开 1)
available_cores = [core for core in all_cores if core not in [0, 1]]
# 选择前两个空闲核心
cores_to_use = available_cores[:2]
process.cpu_affinity(cores_to_use)
@asynccontextmanager
async def lifespan(app: FastAPI):
# 检查是否为生产模式
if "--prod" in sys.argv:
GlobalVariable.isProd = True
else:
GlobalVariable.isProd = False
print("当前为开发模式")
# 启动快捷键监听线程
shortcut_websocket_thread = threading.Thread(target=shortcut_thread, daemon=True)
shortcut_websocket_thread.start()
# 启动目标进程监控线程(仅在生产模式下)
if GlobalVariable.isProd:
target_process = "Sky_Music.exe"
process_monitor_thread = threading.Thread(target=monitor_process, args=(target_process,), daemon=True)
process_monitor_thread.start()
# 启动窗口监听线程
hwnd_thread = threading.Thread(target=hwnd_check_thread, daemon=True)
hwnd_thread.start()
# 初始化播放任务队列
GlobalVariable.task_queue = queue.Queue()
task_thread = threading.Thread(target=music_start_tasks, daemon=True)
task_thread.start()
yield # 👈 此处之后 FastAPI 正式启动
# 关闭时的清理逻辑(可选)
print("应用正在关闭...")
app = FastAPI(lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
register_routes(app)
if __name__ == '__main__':
try:
uvicorn.run(app, host="localhost", port=9899, log_config=None)
logging.info("服务启动完成")
except Exception as e:
logging.error(e)
================================================
FILE: sky-music-server/version.txt
================================================
# version.txt
VSVersionInfo(
ffi=FixedFileInfo(
filevers=(1, 0, 0, 0),
prodvers=(1, 0, 0, 0),
mask=0x3f,
flags=0x0,
OS=0x40004,
fileType=0x1,
subtype=0x0,
date=(0, 0)
),
kids=[
StringFileInfo([
StringTable(
'040904B0',
[
StringStruct('CompanyName', 'WindHide'),
StringStruct('FileDescription', '小星弹琴软件'),
StringStruct('FileVersion', '1.0.0.0'),
StringStruct('InternalName', 'sky-music-server'),
StringStruct('LegalCopyright', '© 2025 WindHide'),
StringStruct('OriginalFilename', 'sky-music-server.exe'),
StringStruct('ProductName', '小星弹琴软件'),
StringStruct('ProductVersion', '1.0.0.0')
]
)
]),
VarFileInfo([VarStruct('Translation', [1033, 1200])])
]
)
================================================
FILE: sky-music-server/windhide/auto/auto_thread.py
================================================
import threading
from os import path
import time
import plyer
from pynput.keyboard import Controller, Key
from windhide.static.global_variable import GlobalVariable
from windhide.utils.ocr_heart_utils import get_friend_model_position
from windhide.utils.ocr_normal_utils import resetGameFrame
from windhide.utils.path_util import getResourcesPath
if GlobalVariable.cpu_type == 'Intel':
from windhide.playRobot.intel_robot import mouse_move_to, key_press
else:
from windhide.playRobot.amd_robot import mouse_move_to, key_press
keyboard = Controller()
class HeartFireThread(threading.Thread):
def __init__(self):
super().__init__()
self._running = False # 控制线程运行的标志位
self._lock = threading.Lock() # 保证线程安全
def run(self):
"""线程启动后执行的主逻辑"""
self._running = True
resetGameFrame()
# mouse_wheel_scroll("down")
# time.sleep(2)
# key_press("g")
time.sleep(2)
# 先判断是不是第一页
while self._running:
friend_button = get_friend_model_position(GlobalVariable.sheld)["button"]
if len(friend_button) >= 2:
break
else:
key_press("z")
self.check_running()
time.sleep(3)
key_press("c")
time.sleep(2)
# 来到第一页
while True:
if not self._running:
break
results = get_friend_model_position(GlobalVariable.sheld)
button = results["button"]
friend = results["friend"]
if len(friend) != 0:
for position in friend:
if not self._running:
break
time.sleep(1)
mouse_move_to(position[0], position[1])
key_press("space")
time.sleep(0.1)
key_press("space")
time.sleep(1.5)
key_press("f")
time.sleep(1)
key_press("ESC")
# 下一页
time.sleep(1.5)
key_press("c")
time.sleep(3)
else:
# 如果没有,显示别是不是到第一页去了,否则直接下一页
if len(button) < 2:
key_press("c")
time.sleep(3)
else:
plyer.notification.notify(
app_name='小星弹琴软件',
app_icon=path.join(getResourcesPath("systemTools"), "icon.ico"),
title='🔥🔥🔥🔥🔥🔥🔥🔥',
message='点火结束🔥🔥🔥🔥🔥',
timeout=1
)
return "点火结束"
def stop(self):
"""安全停止线程"""
with self._lock:
self._running = False
GlobalVariable.auto_thread = None
def check_running(self):
"""检查线程是否正在运行,若未运行则安全退出"""
with self._lock:
if not self._running:
raise StopIteration("线程已停止")
def press_left():
keyboard.press(Key.left)
time.sleep(0.1)
keyboard.release(Key.left)
================================================
FILE: sky-music-server/windhide/musicToSheet/aigc_handler_sheet.py
================================================
import json
import os
import re
# from google import genai
from openai import OpenAI
from windhide.static.global_variable import GlobalVariable
from windhide.utils.path_util import getResourcesPath
from windhide.utils.play_util import detect_encoding
# def gemini_ai(model,filename, type_):
# client = genai.Client(api_key=GlobalVariable.ai_token["Gemini"])
# song_data = loadSheetFile(type_,filename)
# contents = GlobalVariable.duration_prompt if model == 'duration' else GlobalVariable.translate_prompt
# contents = contents.replace("{input}",json.dumps(song_data["songNotes"]))
# print(contents)
# response = client.models.generate_content(
# model="gemini-2.5-flash-preview-05-20", contents=contents
# )
# return match_to_sheet(response.text, filename+"Gemini", song_data["bpm"], model)
# noinspection PyTypeChecker
def general_ai(model, filename, type_, platform):
try:
client = OpenAI(
api_key=GlobalVariable.general_ai[platform]["key"],
base_url=GlobalVariable.general_ai[platform]["url"]
)
# 加载原始乐谱
song_data = loadSheetFile(type_, filename)
target_length = len(song_data["songNotes"])
# 准备初始提示词
contents = GlobalVariable.duration_prompt if model == 'duration' else GlobalVariable.translate_prompt
# 初始化消息上下文
messages = [
{"role": "system", "content": contents},
{"role": "user", "content": json.dumps(song_data["songNotes"])},
]
# 初始化状态变量
full_objects = []
max_retry = 20
retry_count = 0
total_segments = None
current_segment = 1
print("🎼 开始生成 JSON 音符数据...")
while retry_count < max_retry:
# 向模型发起请求
response = client.chat.completions.create(
model=GlobalVariable.general_ai[platform]["model"],
messages=messages,
stream=True,
)
# 逐步拼接响应内容
partial = ""
for chunk in response:
if chunk.choices:
delta = chunk.choices[0].delta
if hasattr(delta, "content") and delta.content:
partial += delta.content
print(delta.content, end="", flush=True)
# 提取段落信息
seg_cur, seg_tot = parse_segment_info(partial)
if seg_cur and seg_tot:
current_segment = seg_cur
total_segments = seg_tot
allow_extra_retry = 5
max_retry = total_segments + allow_extra_retry
retry_count = current_segment
print(f"\n🔍 当前第 {current_segment} 段 / 共 {total_segments} 段")
# 提取 JSON 对象
new_objs = re.findall(r'\{[^{}]*?"time"\s*:\s*\d+[^{}]*?\}', partial)
full_objects.extend(new_objs)
print(f"\n📦 已收集 JSON 元素数:{len(full_objects)} / 目标 {target_length}")
# ✅ 判断是否完成(段数或数量)
if (total_segments and current_segment >= total_segments) or len(full_objects) >= target_length:
json_text = f"[{','.join(full_objects)}]"
saveSheetFile(json_text, filename + platform, song_data["bpm"], model)
print("✅ JSON 输出已完成,已保存")
return "ok"
# 🚫 若未完成,续问 —— 精简上下文避免 token 超限
retry_count += 1
print(f"⚠️ 第 {current_segment} 段未完成,开始第 {retry_count} 次续问...")
messages = [
{"role": "system", "content": contents},
{"role": "user", "content": json.dumps(song_data["songNotes"])},
{"role": "assistant", "content": partial},
{"role": "user", "content": "请继续上次未完成的 JSON 输出。"}
]
print("❌ 达到最大重试次数,输出失败")
return "output incomplete"
except Exception as e:
print("❗ 异常:", e)
return "Nothing to do"
def parse_segment_info(text):
"""
解析格式如“第 N 段(共预计 M 段)”,返回 (N, M)
"""
pattern = r'第\s*(\d+)\s*段(共预计\s*(\d+)\s*段)'
match = re.search(pattern, text)
if match:
current_segment = int(match.group(1))
total_segments = int(match.group(2))
return current_segment, total_segments
return None, None
def match_to_sheet(text, filename, bpm, model):
start_idx = text.find('[')
end_idx = text.rfind(']')
if start_idx == -1 or end_idx == -1 or end_idx <= start_idx:
return "error"
json_str = text[start_idx:end_idx + 1]
try:
parsed = json.loads(json_str)
if isinstance(parsed, list) and len(parsed) >= 20:
print("✅ 成功解析 JSON 数组,长度:", len(parsed))
saveSheetFile(json.dumps(parsed), filename, bpm, model)
return "ok"
else:
return "error"
except json.JSONDecodeError as e:
print("⚠️ JSON 解码失败:", e)
return "error"
def loadSheetFile(type, fileName):
# 优化了文件路径构建
file_path = os.path.join(getResourcesPath(type), fileName + ".txt")
with open(file_path, 'r', encoding=detect_encoding(file_path)) as file:
data = json.load(file)
song_notes = data[0].get("songNotes", [])
bpm = data[0].get("bpm", [])
if not song_notes:
return []
return {
"songNotes": song_notes,
"bpm": bpm
}
def saveSheetFile(song_notes, fileName, bpm, model):
if isinstance(song_notes, str):
song_notes = json.loads(song_notes)
output_file_name = fileName + "_AIGC_Handler_"+ ("延音" if model == "duration" else "间隔优化")
output = [{
"name": output_file_name,
"author": "skyMusic-WindHide",
"transcribedBy": "WindHide's Software",
"bpm": bpm,
"bitsPerPage": 15,
"pitchLevel": 0,
"isComposed": True,
"songNotes": song_notes,
"isEncrypted": False,
}]
file_output_path =os.path.join(getResourcesPath("myTranslate"), f"{output_file_name}.txt")
with open(file_output_path, 'w') as f:
json.dump(output, f, indent=4, ensure_ascii=False)
================================================
FILE: sky-music-server/windhide/musicToSheet/music2html.py
================================================
import os
from jinja2 import Template
from platformdirs import user_desktop_dir
html_template = """
{{ sheet_name }}
{{ sheet_name }}
{% for line in content %}
{% for instr in line.instruments %}
{% for note in instr.notes %}
<{{ note.type }} class="{{ note.class }}">{{ note.type }}>
{% endfor %}
{% endfor %}
{% endfor %}
"""
def generatorSheetHtml(sheet_name, convert_sheet):
context = []
instr_index = 0
line_index = 0
line_data = {"id": f"line-{line_index}", "instruments": []}
max_instruments_per_line = 13
switch_limit = {13: 11, 11: 13} # 交替限制
# 映射按键到音符类型
note_mapping = {
"y": ("crdm", "r1"),
"u": ("dmn", "r1"),
"i": ("crc", "r1"),
"o": ("dmn", "r1"),
"p": ("crc", "r1"),
"h": ("crc", "r2"),
"j": ("dmn", "r2"),
"k": ("crdm", "r2"),
"l": ("dmn", "r2"),
";": ("crc", "r2"),
"n": ("crc", "r3"),
"m": ("dmn", "r3"),
",": ("crc", "r3"),
".": ("dmn", "r3"),
"/": ("crdm", "r3"),
}
for index, key in enumerate(convert_sheet):
notes = []
for char in "yuiophjkl;nm,./":
if char in key:
note_type, note_class = note_mapping[char]
else:
note_type, note_class = "d1", "" # 默认值
notes.append({"type": note_type, "class": note_class})
instrument = {"id": f"instr-{instr_index}", "notes": notes}
line_data["instruments"].append(instrument)
instr_index += 1
# 判断是否换行
if instr_index == max_instruments_per_line:
context.append(line_data)
line_index += 1
line_data = {"id": f"line-{line_index}", "instruments": []}
instr_index = 0
max_instruments_per_line = switch_limit[max_instruments_per_line]
# 处理最后一行
if line_data["instruments"]:
context.append(line_data)
# 渲染模板
template = Template(html_template)
final_html = template.render(sheet_name=sheet_name, content=context)
desktop_path = os.path.join(user_desktop_dir(), f"{sheet_name}.html")
with open(desktop_path, "w", encoding="utf-8") as file:
file.write(final_html)
return "ok"
================================================
FILE: sky-music-server/windhide/musicToSheet/process_audio.py
================================================
import json
import os
import pretty_midi
from windhide.musicToSheet.transfer_MID import inference
from windhide.musicToSheet.vocals_split import split_vocals
from windhide.static.global_variable import GlobalVariable
from windhide.utils.path_util import getResourcesPath
# 15个音符与键盘按键的映射
note_to_key = {"_C_c¹":{36:'1Key0',38:'1Key1',40:'1Key2',41:'1Key3',43:'1Key4',45:'1Key5',47:'1Key6',48:'1Key7',50:'1Key8',52:'1Key9',53:'1Key10',55:'1Key11',57:'1Key12',59:'1Key13',60:'1Key14'},"c_c²":{48:'1Key0',50:'1Key1',52:'1Key2',53:'1Key3',55:'1Key4',57:'1Key5',59:'1Key6',60:'1Key7',62:'1Key8',64:'1Key9',65:'1Key10',67:'1Key11',69:'1Key12',71:'1Key13',72:'1Key14'},'c¹_c³':{60:'1Key0',62:'1Key1',64:'1Key2',65:'1Key3',67:'1Key4',69:'1Key5',71:'1Key6',72:'1Key7',74:'1Key8',76:'1Key9',77:'1Key10',79:'1Key11',81:'1Key12',83:'1Key13',84:'1Key14'},"c²_c⁴":{72:'1Key0',74:'1Key1',76:'1Key2',77:'1Key3',79:'1Key4',81:'1Key5',83:'1Key6',84:'1Key7',86:'1Key8',88:'1Key9',89:'1Key10',91:'1Key11',93:'1Key12',95:'1Key13',96:'1Key14'},"c³_c⁵":{84:'1Key0',86:'1Key1',88:'1Key2',89:'1Key3',91:'1Key4',93:'1Key5',95:'1Key6',96:'1Key7',98:'1Key8',100:'1Key9',101:'1Key10',103:'1Key11',105:'1Key12',107:'1Key13',108:'1Key14'},"_C_c²":{36:'1Key-7',38:'1Key-6',40:'1Key-5',41:'1Key-4',43:'1Key-3',45:'1Key-2',47:'1Key-1',48:'1Key0',50:'1Key1',52:'1Key2',53:'1Key3',55:'1Key4',57:'1Key5',59:'1Key6',60:'1Key7',62:'1Key8',64:'1Key9',65:'1Key10',67:'1Key11',69:'1Key12',71:'1Key13',72:'1Key14'},"c_c³":{48:'1Key-7',50:'1Key-6',52:'1Key-5',53:'1Key-4',55:'1Key-3',57:'1Key-2',59:'1Key-1',60:'1Key0',62:'1Key1',64:'1Key2',65:'1Key3',67:'1Key4',69:'1Key5',71:'1Key6',72:'1Key7',74:'1Key8',76:'1Key9',77:'1Key10',79:'1Key11',81:'1Key12',83:'1Key13',84:'1Key14'},"c¹_c⁴":{60:'1Key-7',62:'1Key-6',64:'1Key-5',65:'1Key-4',67:'1Key-3',69:'1Key-2',71:'1Key-1',72:'1Key0',74:'1Key1',76:'1Key2',77:'1Key3',79:'1Key4',81:'1Key5',83:'1Key6',84:'1Key7',86:'1Key8',88:'1Key9',89:'1Key10',91:'1Key11',93:'1Key12',95:'1Key13',96:'1Key14'},"c²_c⁵":{72:'1Key-7',74:'1Key-6',76:'1Key-5',77:'1Key-4',79:'1Key-3',81:'1Key-2',83:'1Key-1',84:'1Key0',86:'1Key1',88:'1Key2',89:'1Key3',91:'1Key4',93:'1Key5',95:'1Key6',96:'1Key7',98:'1Key8',100:'1Key9',101:'1Key10',103:'1Key11',105:'1Key12',107:'1Key13',108:'1Key14'},"_C_c³":{36:'1Key-14',38:'1Key-13',40:'1Key-12',41:'1Key-11',43:'1Key-10',45:'1Key-9',47:'1Key-8',48:'1Key-7',50:'1Key-6',52:'1Key-5',53:'1Key-4',55:'1Key-3',57:'1Key-2',59:'1Key-1',60:'1Key0',62:'1Key1',64:'1Key2',65:'1Key3',67:'1Key4',69:'1Key5',71:'1Key6',72:'1Key7',74:'1Key8',76:'1Key9',77:'1Key10',79:'1Key11',81:'1Key12',83:'1Key13',84:'1Key14'},"c_c⁴":{48:'1Key-14',50:'1Key-13',52:'1Key-12',53:'1Key-11',55:'1Key-10',57:'1Key-9',59:'1Key-8',60:'1Key-7',62:'1Key-6',64:'1Key-5',65:'1Key-4',67:'1Key-3',69:'1Key-2',71:'1Key-1',72:'1Key0',74:'1Key1',76:'1Key2',77:'1Key3',79:'1Key4',81:'1Key5',83:'1Key6',84:'1Key7',86:'1Key8',88:'1Key9',89:'1Key10',91:'1Key11',93:'1Key12',95:'1Key13',96:'1Key14'},"c¹_c⁵":{60:'1Key-14',62:'1Key-13',64:'1Key-12',65:'1Key-11',67:'1Key-10',69:'1Key-9',71:'1Key-8',72:'1Key-7',74:'1Key-6',76:'1Key-5',77:'1Key-4',79:'1Key-3',81:'1Key-2',83:'1Key-1',84:'1Key0',86:'1Key1',88:'1Key2',89:'1Key3',91:'1Key4',93:'1Key5',95:'1Key6',96:'1Key7',98:'1Key8',100:'1Key9',101:'1Key10',103:'1Key11',105:'1Key12',107:'1Key13',108:'1Key14'},"_C_c⁴":{36:'1Key-14',38:'1Key-13',40:'1Key-12',41:'1Key-11',43:'1Key-10',45:'1Key-9',47:'1Key-8',48:'1Key-7',50:'1Key-6',52:'1Key-5',53:'1Key-4',55:'1Key-3',57:'1Key-2',59:'1Key-1',60:'1Key0',62:'1Key1',64:'1Key2',65:'1Key3',67:'1Key4',69:'1Key5',71:'1Key6',72:'1Key7',74:'1Key8',76:'1Key9',77:'1Key10',79:'1Key11',81:'1Key12',83:'1Key13',84:'1Key14',86:'1Key15',88:'1Key16',89:'1Key17',91:'1Key18',93:'1Key19',95:'1Key20',96:'1Key21',},"c_c⁵":{48:'1Key-14',50:'1Key-13',52:'1Key-12',53:'1Key-11',55:'1Key-10',57:'1Key-9',59:'1Key-8',60:'1Key-7',62:'1Key-6',64:'1Key-5',65:'1Key-4',67:'1Key-3',69:'1Key-2',71:'1Key-1',72:'1Key0',74:'1Key1',76:'1Key2',77:'1Key3',79:'1Key4',81:'1Key5',83:'1Key6',84:'1Key7',86:'1Key8',88:'1Key9',89:'1Key10',91:'1Key11',93:'1Key12',95:'1Key13',96:'1Key14',98:'1Key15',100:'1Key16',101:'1Key17',103:'1Key18',105:'1Key19',107:'1Key20',108:'1Key21',},"_C_c⁵":{36:'1Key-14',38:'1Key-13',40:'1Key-12',41:'1Key-11',43:'1Key-10',45:'1Key-9',47:'1Key-8',48:'1Key-7',50:'1Key-6',52:'1Key-5',53:'1Key-4',55:'1Key-3',57:'1Key-2',59:'1Key-1',60:"1Key0",62:"1Key1",64:"1Key2",65:"1Key3",67:"1Key4",69:"1Key5",71:"1Key6",72:"1Key7",74:"1Key8",76:"1Key9",77:"1Key10",79:"1Key11",81:"1Key12",83:"1Key13",84:"1Key14",86:"1Key15",88:"1Key16",89:"1Key17",91:"1Key18",93:"1Key19",95:"1Key20",96:"1Key21",98:"1Key22",100:"1Key23",101:"1Key24",103:"1Key25",105:"1Key26",107:"1Key27",108:"1Key28"},"_C_b":{36:'1Key0',38:'1Key1',40:'1Key2',41:'1Key3',43:'1Key4',45:'1Key5',47:'1Key6',48:'1Key7',50:'1Key8',52:'1Key9',53:'1Key10',55:'1Key11',57:'1Key12',59:'1Key13'},"c_b¹":{48:'1Key0',50:'1Key1',52:'1Key2',53:'1Key3',55:'1Key4',57:'1Key5',59:'1Key6',60:'1Key7',62:'1Key8',64:'1Key9',65:'1Key10',67:'1Key11',69:'1Key12',71:'1Key13'},'c¹_b²':{60:'1Key0',62:'1Key1',64:'1Key2',65:'1Key3',67:'1Key4',69:'1Key5',71:'1Key6',72:'1Key7',74:'1Key8',76:'1Key9',77:'1Key10',79:'1Key11',81:'1Key12',83:'1Key13'},"c²_b³":{72:'1Key0',74:'1Key1',76:'1Key2',77:'1Key3',79:'1Key4',81:'1Key5',83:'1Key6',84:'1Key7',86:'1Key8',88:'1Key9',89:'1Key10',91:'1Key11',93:'1Key12',95:'1Key13'},"c³_b⁴":{84:'1Key0',86:'1Key1',88:'1Key2',89:'1Key3',91:'1Key4',93:'1Key5',95:'1Key6',96:'1Key7',98:'1Key8',100:'1Key9',101:'1Key10',103:'1Key11',105:'1Key12',107:'1Key13'},"_C_b¹":{36:'1Key-7',38:'1Key-6',40:'1Key-5',41:'1Key-4',43:'1Key-3',45:'1Key-2',47:'1Key-1',48:'1Key0',50:'1Key1',52:'1Key2',53:'1Key3',55:'1Key4',57:'1Key5',59:'1Key6',60:'1Key7',62:'1Key8',64:'1Key9',65:'1Key10',67:'1Key11',69:'1Key12',71:'1Key13'},"c_b²":{48:'1Key-7',50:'1Key-6',52:'1Key-5',53:'1Key-4',55:'1Key-3',57:'1Key-2',59:'1Key-1',60:'1Key0',62:'1Key1',64:'1Key2',65:'1Key3',67:'1Key4',69:'1Key5',71:'1Key6',72:'1Key7',74:'1Key8',76:'1Key9',77:'1Key10',79:'1Key11',81:'1Key12',83:'1Key13'},"c¹_b³":{60:'1Key-7',62:'1Key-6',64:'1Key-5',65:'1Key-4',67:'1Key-3',69:'1Key-2',71:'1Key-1',72:'1Key0',74:'1Key1',76:'1Key2',77:'1Key3',79:'1Key4',81:'1Key5',83:'1Key6',84:'1Key7',86:'1Key8',88:'1Key9',89:'1Key10',91:'1Key11',93:'1Key12',95:'1Key13'},"c²_b⁴":{72:'1Key-7',74:'1Key-6',76:'1Key-5',77:'1Key-4',79:'1Key-3',81:'1Key-2',83:'1Key-1',84:'1Key0',86:'1Key1',88:'1Key2',89:'1Key3',91:'1Key4',93:'1Key5',95:'1Key6',96:'1Key7',98:'1Key8',100:'1Key9',101:'1Key10',103:'1Key11',105:'1Key12',107:'1Key13'},"_C_b²":{36:'1Key-14',38:'1Key-13',40:'1Key-12',41:'1Key-11',43:'1Key-10',45:'1Key-9',47:'1Key-8',48:'1Key-7',50:'1Key-6',52:'1Key-5',53:'1Key-4',55:'1Key-3',57:'1Key-2',59:'1Key-1',60:'1Key0',62:'1Key1',64:'1Key2',65:'1Key3',67:'1Key4',69:'1Key5',71:'1Key6',72:'1Key7',74:'1Key8',76:'1Key9',77:'1Key10',79:'1Key11',81:'1Key12',83:'1Key13'},"c_b³":{48:'1Key-14',50:'1Key-13',52:'1Key-12',53:'1Key-11',55:'1Key-10',57:'1Key-9',59:'1Key-8',60:'1Key-7',62:'1Key-6',64:'1Key-5',65:'1Key-4',67:'1Key-3',69:'1Key-2',71:'1Key-1',72:'1Key0',74:'1Key1',76:'1Key2',77:'1Key3',79:'1Key4',81:'1Key5',83:'1Key6',84:'1Key7',86:'1Key8',88:'1Key9',89:'1Key10',91:'1Key11',93:'1Key12',95:'1Key13'},"c¹_b⁴":{60:'1Key-14',62:'1Key-13',64:'1Key-12',65:'1Key-11',67:'1Key-10',69:'1Key-9',71:'1Key-8',72:'1Key-7',74:'1Key-6',76:'1Key-5',77:'1Key-4',79:'1Key-3',81:'1Key-2',83:'1Key-1',84:'1Key0',86:'1Key1',88:'1Key2',89:'1Key3',91:'1Key4',93:'1Key5',95:'1Key6',96:'1Key7',98:'1Key8',100:'1Key9',101:'1Key10',103:'1Key11',105:'1Key12',107:'1Key13'},"_C_b³":{36:'1Key-14',38:'1Key-13',40:'1Key-12',41:'1Key-11',43:'1Key-10',45:'1Key-9',47:'1Key-8',48:'1Key-7',50:'1Key-6',52:'1Key-5',53:'1Key-4',55:'1Key-3',57:'1Key-2',59:'1Key-1',60:'1Key0',62:'1Key1',64:'1Key2',65:'1Key3',67:'1Key4',69:'1Key5',71:'1Key6',72:'1Key7',74:'1Key8',76:'1Key9',77:'1Key10',79:'1Key11',81:'1Key12',83:'1Key13',84:'1Key14',86:'1Key15',88:'1Key16',89:'1Key17',91:'1Key18',93:'1Key19',95:'1Key20'},"c_b⁴":{48:'1Key-14',50:'1Key-13',52:'1Key-12',53:'1Key-11',55:'1Key-10',57:'1Key-9',59:'1Key-8',60:'1Key-7',62:'1Key-6',64:'1Key-5',65:'1Key-4',67:'1Key-3',69:'1Key-2',71:'1Key-1',72:'1Key0',74:'1Key1',76:'1Key2',77:'1Key3',79:'1Key4',81:'1Key5',83:'1Key6',84:'1Key7',86:'1Key8',88:'1Key9',89:'1Key10',91:'1Key11',93:'1Key12',95:'1Key13',96:'1Key14',98:'1Key15',100:'1Key16',101:'1Key17',103:'1Key18',105:'1Key19',107:'1Key20'},"_C_b⁴":{36:'1Key-14',38:'1Key-13',40:'1Key-12',41:'1Key-11',43:'1Key-10',45:'1Key-9',47:'1Key-8',48:'1Key-7',50:'1Key-6',52:'1Key-5',53:'1Key-4',55:'1Key-3',57:'1Key-2',59:'1Key-1',60:"1Key0",62:"1Key1",64:"1Key2",65:"1Key3",67:"1Key4",69:"1Key5",71:"1Key6",72:"1Key7",74:"1Key8",76:"1Key9",77:"1Key10",79:"1Key11",81:"1Key12",83:"1Key13",84:"1Key14",86:"1Key15",88:"1Key16",89:"1Key17",91:"1Key18",93:"1Key19",95:"1Key20",96:"1Key21",98:"1Key22",100:"1Key23",101:"1Key24",103:"1Key25",105:"1Key26",107:"1Key27"}}
extra_note_to_key = {"_C_c¹":{37:['1Key0','1Key1'],39:['1Key1','1Key2'],42:['1Key3','1Key4'],44:['1Key4','1Key5'],46:['1Key5','1Key6'],49:['1Key6','1Key7'],51:['1Key8','1Key9'],54:['1Key10','1Key11'],56:['1Key11','1Key12'],58:['1Key12','1Key13'],},"c_c²":{49:['1Key0','1Key1'],51:['1Key1','1Key2'],54:['1Key3','1Key4'],56:['1Key4','1Key5'],58:['1Key5','1Key6'],61:['1Key6','1Key7'],63:['1Key8','1Key9'],66:['1Key10','1Key11'],68:['1Key11','1Key12'],70:['1Key12','1Key13'],},'c¹_c³':{61:['1Key0','1Key1'],63:['1Key1','1Key2'],66:['1Key3','1Key4'],68:['1Key4','1Key5'],70:['1Key5','1Key6'],73:['1Key6','1Key7'],75:['1Key8','1Key9'],78:['1Key10','1Key11'],80:['1Key11','1Key12'],82:['1Key12','1Key13'],},"c²_c⁴":{73:['1Key0','1Key1'],75:['1Key1','1Key2'],78:['1Key3','1Key4'],80:['1Key4','1Key5'],82:['1Key5','1Key6'],85:['1Key6','1Key7'],87:['1Key8','1Key9'],90:['1Key10','1Key11'],92:['1Key11','1Key12'],94:['1Key12','1Key13'],},"c³_c⁵":{85:['1Key0','1Key1'],87:['1Key1','1Key2'],90:['1Key3','1Key4'],92:['1Key4','1Key5'],94:['1Key5','1Key6'],97:['1Key6','1Key7'],99:['1Key8','1Key9'],102:['1Key10','1Key11'],104:['1Key11','1Key12'],106:['1Key12','1Key13'],},"_C_c²":{37:['1Key-7','1Key-6'],39:['1Key-6','1Key-5'],42:['1Key-4','1Key-3'],44:['1Key-3','1Key-2'],46:['1Key-2','1Key-1'],49:['1Key0','1Key1'],51:['1Key1','1Key2'],54:['1Key3','1Key4'],56:['1Key4','1Key5'],58:['1Key5','1Key6'],61:['1Key6','1Key7'],63:['1Key8','1Key9'],66:['1Key10','1Key11'],68:['1Key11','1Key12'],70:['1Key12','1Key13'],},"c_c³":{49:['1Key-7','1Key-6'],51:['1Key-6','1Key-5'],54:['1Key-4','1Key-3'],56:['1Key-3','1Key-2'],58:['1Key-2','1Key-1'],61:['1Key0','1Key1'],63:['1Key1','1Key2'],66:['1Key3','1Key4'],68:['1Key4','1Key5'],70:['1Key5','1Key6'],73:['1Key6','1Key7'],75:['1Key8','1Key9'],78:['1Key10','1Key11'],80:['1Key11','1Key12'],82:['1Key12','1Key13'],},"c¹_c⁴":{61:['1Key-7','1Key-6'],63:['1Key-6','1Key-5'],66:['1Key-4','1Key-3'],68:['1Key-3','1Key-2'],70:['1Key-2','1Key-1'],73:['1Key0','1Key1'],75:['1Key1','1Key2'],78:['1Key3','1Key4'],80:['1Key4','1Key5'],82:['1Key5','1Key6'],85:['1Key6','1Key7'],87:['1Key8','1Key9'],90:['1Key10','1Key11'],92:['1Key11','1Key12'],94:['1Key12','1Key13'],},"c²_c⁵":{73:['1Key-7','1Key-6'],75:['1Key-6','1Key-5'],78:['1Key-4','1Key-3'],80:['1Key-3','1Key-2'],82:['1Key-2','1Key-1'],85:['1Key0','1Key1'],87:['1Key1','1Key2'],90:['1Key3','1Key4'],92:['1Key4','1Key5'],94:['1Key5','1Key6'],97:['1Key6','1Key7'],99:['1Key8','1Key9'],102:['1Key10','1Key11'],104:['1Key11','1Key12'],106:['1Key12','1Key13'],},"_C_c³":{37:['1Key-14','1Key-13'],39:['1Key-13','1Key-12'],42:['1Key-11','1Key-10'],44:['1Key-10','1Key-9'],46:['1Key-9','1Key-8'],49:['1Key-7','1Key-6'],51:['1Key-6','1Key-5'],54:['1Key-4','1Key-3'],56:['1Key-3','1Key-2'],58:['1Key-2','1Key-1'],61:['1Key0','1Key1'],63:['1Key1','1Key2'],66:['1Key3','1Key4'],68:['1Key4','1Key5'],70:['1Key5','1Key6'],73:['1Key6','1Key7'],75:['1Key8','1Key9'],78:['1Key10','1Key11'],80:['1Key11','1Key12'],82:['1Key12','1Key13'],},"c_c⁴":{49:['1Key-14','1Key-13'],51:['1Key-13','1Key-12'],54:['1Key-11','1Key-10'],56:['1Key-10','1Key-9'],58:['1Key-9','1Key-8'],61:['1Key-7','1Key-6'],63:['1Key-6','1Key-5'],66:['1Key-4','1Key-3'],68:['1Key-3','1Key-2'],70:['1Key-2','1Key-1'],73:['1Key0','1Key1'],75:['1Key1','1Key2'],78:['1Key3','1Key4'],80:['1Key4','1Key5'],82:['1Key5','1Key6'],85:['1Key6','1Key7'],87:['1Key8','1Key9'],90:['1Key10','1Key11'],92:['1Key11','1Key12'],94:['1Key12','1Key13'],},"c¹_c⁵":{61:['1Key-14','1Key-13'],63:['1Key-13','1Key-12'],66:['1Key-11','1Key-10'],68:['1Key-10','1Key-9'],70:['1Key-9','1Key-8'],73:['1Key-7','1Key-6'],75:['1Key-6','1Key-5'],78:['1Key-4','1Key-3'],80:['1Key-3','1Key-2'],82:['1Key-2','1Key-1'],85:['1Key0','1Key1'],87:['1Key1','1Key2'],90:['1Key3','1Key4'],92:['1Key4','1Key5'],94:['1Key5','1Key6'],97:['1Key6','1Key7'],99:['1Key8','1Key9'],102:['1Key10','1Key11'],104:['1Key11','1Key12'],106:['1Key12','1Key13'],},"_C_c⁴":{37:['1Key-14','1Key-13'],39:['1Key-13','1Key-12'],42:['1Key-11','1Key-10'],44:['1Key-10','1Key-9'],46:['1Key-9','1Key-8'],49:['1Key-7','1Key-6'],51:['1Key-6','1Key-5'],54:['1Key-4','1Key-3'],56:['1Key-3','1Key-2'],58:['1Key-2','1Key-1'],61:['1Key0','1Key1'],63:['1Key1','1Key2'],66:['1Key3','1Key4'],68:['1Key4','1Key5'],70:['1Key5','1Key6'],73:['1Key6','1Key7'],75:['1Key8','1Key9'],78:['1Key10','1Key11'],80:['1Key11','1Key12'],82:['1Key12','1Key13'],85:['1Key14','1Key15'],87:['1Key15','1Key16'],90:['1Key17','1Key18'],92:['1Key18','1Key19'],94:['1Key19','1Key20'],},"c_c⁵":{49:['1Key-14','1Key-13'],51:['1Key-13','1Key-12'],54:['1Key-11','1Key-10'],56:['1Key-10','1Key-9'],58:['1Key-9','1Key-8'],61:['1Key-7','1Key-6'],63:['1Key-6','1Key-5'],66:['1Key-4','1Key-3'],68:['1Key-3','1Key-2'],70:['1Key-2','1Key-1'],73:['1Key0','1Key1'],75:['1Key1','1Key2'],78:['1Key3','1Key4'],80:['1Key4','1Key5'],82:['1Key5','1Key6'],85:['1Key6','1Key7'],87:['1Key8','1Key9'],90:['1Key10','1Key11'],92:['1Key11','1Key12'],94:['1Key12','1Key13'],97:['1Key14','1Key15'],99:['1Key15','1Key16'],102:['1Key17','1Key18'],104:['1Key18','1Key19'],106:['1Key19','1Key20'],},"_C_c⁵":{37:['1Key-14','1Key-13'],39:['1Key-13','1Key-12'],42:['1Key-11','1Key-10'],44:['1Key-10','1Key-9'],46:['1Key-9','1Key-8'],49:['1Key-7','1Key-6'],51:['1Key-6','1Key-5'],54:['1Key-4','1Key-3'],56:['1Key-3','1Key-2'],58:['1Key-2','1Key-1'],61:['1Key0','1Key1'],63:['1Key1','1Key2'],66:['1Key3','1Key4'],68:['1Key4','1Key5'],70:['1Key5','1Key6'],73:['1Key6','1Key7'],75:['1Key8','1Key9'],78:['1Key10','1Key11'],80:['1Key11','1Key12'],82:['1Key12','1Key13'],85:['1Key14','1Key15'],87:['1Key15','1Key16'],90:['1Key17','1Key18'],92:['1Key18','1Key19'],94:['1Key19','1Key20'],97:['1Key21','1Key22'],99:['1Key22','1Key23'],102:['1Key24','1Key25'],104:['1Key25','1Key26'],106:['1Key26','1Key27'],},"_C_b":{37:['1Key0','1Key1'],39:['1Key1','1Key2'],42:['1Key3','1Key4'],44:['1Key4','1Key5'],46:['1Key5','1Key6'],49:['1Key6','1Key7'],51:['1Key8','1Key9'],54:['1Key10','1Key11'],56:['1Key11','1Key12'],58:['1Key12','1Key13'],},"c_b¹":{49:['1Key0','1Key1'],51:['1Key1','1Key2'],54:['1Key3','1Key4'],56:['1Key4','1Key5'],58:['1Key5','1Key6'],61:['1Key6','1Key7'],63:['1Key8','1Key9'],66:['1Key10','1Key11'],68:['1Key11','1Key12'],70:['1Key12','1Key13'],},'c¹_b²':{61:['1Key0','1Key1'],63:['1Key1','1Key2'],66:['1Key3','1Key4'],68:['1Key4','1Key5'],70:['1Key5','1Key6'],73:['1Key6','1Key7'],75:['1Key8','1Key9'],78:['1Key10','1Key11'],80:['1Key11','1Key12'],82:['1Key12','1Key13'],},"c²_b³":{73:['1Key0','1Key1'],75:['1Key1','1Key2'],78:['1Key3','1Key4'],80:['1Key4','1Key5'],82:['1Key5','1Key6'],85:['1Key6','1Key7'],87:['1Key8','1Key9'],90:['1Key10','1Key11'],92:['1Key11','1Key12'],94:['1Key12','1Key13'],},"c³_b⁴":{85:['1Key0','1Key1'],87:['1Key1','1Key2'],90:['1Key3','1Key4'],92:['1Key4','1Key5'],94:['1Key5','1Key6'],97:['1Key6','1Key7'],99:['1Key8','1Key9'],102:['1Key10','1Key11'],104:['1Key11','1Key12'],106:['1Key12','1Key13'],},"_C_b¹":{37:['1Key-7','1Key-6'],39:['1Key-6','1Key-5'],42:['1Key-4','1Key-3'],44:['1Key-3','1Key-2'],46:['1Key-2','1Key-1'],49:['1Key0','1Key1'],51:['1Key1','1Key2'],54:['1Key3','1Key4'],56:['1Key4','1Key5'],58:['1Key5','1Key6'],61:['1Key6','1Key7'],63:['1Key8','1Key9'],66:['1Key10','1Key11'],68:['1Key11','1Key12'],70:['1Key12','1Key13'],},"c_b²":{49:['1Key-7','1Key-6'],51:['1Key-6','1Key-5'],54:['1Key-4','1Key-3'],56:['1Key-3','1Key-2'],58:['1Key-2','1Key-1'],61:['1Key0','1Key1'],63:['1Key1','1Key2'],66:['1Key3','1Key4'],68:['1Key4','1Key5'],70:['1Key5','1Key6'],73:['1Key6','1Key7'],75:['1Key8','1Key9'],78:['1Key10','1Key11'],80:['1Key11','1Key12'],82:['1Key12','1Key13'],},"c¹_b³":{61:['1Key-7','1Key-6'],63:['1Key-6','1Key-5'],66:['1Key-4','1Key-3'],68:['1Key-3','1Key-2'],70:['1Key-2','1Key-1'],73:['1Key0','1Key1'],75:['1Key1','1Key2'],78:['1Key3','1Key4'],80:['1Key4','1Key5'],82:['1Key5','1Key6'],85:['1Key6','1Key7'],87:['1Key8','1Key9'],90:['1Key10','1Key11'],92:['1Key11','1Key12'],94:['1Key12','1Key13'],},"c²_b⁴":{73:['1Key-7','1Key-6'],75:['1Key-6','1Key-5'],78:['1Key-4','1Key-3'],80:['1Key-3','1Key-2'],82:['1Key-2','1Key-1'],85:['1Key0','1Key1'],87:['1Key1','1Key2'],90:['1Key3','1Key4'],92:['1Key4','1Key5'],94:['1Key5','1Key6'],97:['1Key6','1Key7'],99:['1Key8','1Key9'],102:['1Key10','1Key11'],104:['1Key11','1Key12'],106:['1Key12','1Key13'],},"_C_b²":{37:['1Key-14','1Key-13'],39:['1Key-13','1Key-12'],42:['1Key-11','1Key-10'],44:['1Key-10','1Key-9'],46:['1Key-9','1Key-8'],49:['1Key-7','1Key-6'],51:['1Key-6','1Key-5'],54:['1Key-4','1Key-3'],56:['1Key-3','1Key-2'],58:['1Key-2','1Key-1'],61:['1Key0','1Key1'],63:['1Key1','1Key2'],66:['1Key3','1Key4'],68:['1Key4','1Key5'],70:['1Key5','1Key6'],73:['1Key6','1Key7'],75:['1Key8','1Key9'],78:['1Key10','1Key11'],80:['1Key11','1Key12'],82:['1Key12','1Key13'],},"c_b³":{49:['1Key-14','1Key-13'],51:['1Key-13','1Key-12'],54:['1Key-11','1Key-10'],56:['1Key-10','1Key-9'],58:['1Key-9','1Key-8'],61:['1Key-7','1Key-6'],63:['1Key-6','1Key-5'],66:['1Key-4','1Key-3'],68:['1Key-3','1Key-2'],70:['1Key-2','1Key-1'],73:['1Key0','1Key1'],75:['1Key1','1Key2'],78:['1Key3','1Key4'],80:['1Key4','1Key5'],82:['1Key5','1Key6'],85:['1Key6','1Key7'],87:['1Key8','1Key9'],90:['1Key10','1Key11'],92:['1Key11','1Key12'],94:['1Key12','1Key13'],},"c¹_b⁴":{61:['1Key-14','1Key-13'],63:['1Key-13','1Key-12'],66:['1Key-11','1Key-10'],68:['1Key-10','1Key-9'],70:['1Key-9','1Key-8'],73:['1Key-7','1Key-6'],75:['1Key-6','1Key-5'],78:['1Key-4','1Key-3'],80:['1Key-3','1Key-2'],82:['1Key-2','1Key-1'],85:['1Key0','1Key1'],87:['1Key1','1Key2'],90:['1Key3','1Key4'],92:['1Key4','1Key5'],94:['1Key5','1Key6'],97:['1Key6','1Key7'],99:['1Key8','1Key9'],102:['1Key10','1Key11'],104:['1Key11','1Key12'],106:['1Key12','1Key13'],},"_C_b³":{37:['1Key-14','1Key-13'],39:['1Key-13','1Key-12'],42:['1Key-11','1Key-10'],44:['1Key-10','1Key-9'],46:['1Key-9','1Key-8'],49:['1Key-7','1Key-6'],51:['1Key-6','1Key-5'],54:['1Key-4','1Key-3'],56:['1Key-3','1Key-2'],58:['1Key-2','1Key-1'],61:['1Key0','1Key1'],63:['1Key1','1Key2'],66:['1Key3','1Key4'],68:['1Key4','1Key5'],70:['1Key5','1Key6'],73:['1Key6','1Key7'],75:['1Key8','1Key9'],78:['1Key10','1Key11'],80:['1Key11','1Key12'],82:['1Key12','1Key13'],85:['1Key14','1Key15'],87:['1Key15','1Key16'],90:['1Key17','1Key18'],92:['1Key18','1Key19'],94:['1Key19','1Key20'],},"c_b⁴":{49:['1Key-14','1Key-13'],51:['1Key-13','1Key-12'],54:['1Key-11','1Key-10'],56:['1Key-10','1Key-9'],58:['1Key-9','1Key-8'],61:['1Key-7','1Key-6'],63:['1Key-6','1Key-5'],66:['1Key-4','1Key-3'],68:['1Key-3','1Key-2'],70:['1Key-2','1Key-1'],73:['1Key0','1Key1'],75:['1Key1','1Key2'],78:['1Key3','1Key4'],80:['1Key4','1Key5'],82:['1Key5','1Key6'],85:['1Key6','1Key7'],87:['1Key8','1Key9'],90:['1Key10','1Key11'],92:['1Key11','1Key12'],94:['1Key12','1Key13'],97:['1Key14','1Key15'],99:['1Key15','1Key16'],102:['1Key17','1Key18'],104:['1Key18','1Key19'],106:['1Key19','1Key20'],},"_C_b⁴":{37:['1Key-14','1Key-13'],39:['1Key-13','1Key-12'],42:['1Key-11','1Key-10'],44:['1Key-10','1Key-9'],46:['1Key-9','1Key-8'],49:['1Key-7','1Key-6'],51:['1Key-6','1Key-5'],54:['1Key-4','1Key-3'],56:['1Key-3','1Key-2'],58:['1Key-2','1Key-1'],61:['1Key0','1Key1'],63:['1Key1','1Key2'],66:['1Key3','1Key4'],68:['1Key4','1Key5'],70:['1Key5','1Key6'],73:['1Key6','1Key7'],75:['1Key8','1Key9'],78:['1Key10','1Key11'],80:['1Key11','1Key12'],82:['1Key12','1Key13'],85:['1Key14','1Key15'],87:['1Key15','1Key16'],90:['1Key17','1Key18'],92:['1Key18','1Key19'],94:['1Key19','1Key20'],97:['1Key21','1Key22'],99:['1Key22','1Key23'],102:['1Key24','1Key25'],104:['1Key25','1Key26'],106:['1Key26','1Key27'],}}
# 特殊音符的映射规则
special_note_mapping = {'_C_c¹':{62:57,64:59,65:60,},'c_c²':{74:69,76:71,77:72,},'c¹_c³':{86:81,88:83,89:84,},'c²_c⁴':{98:93,100:95,101:96,},'c³_c⁵':{110:105,112:107,113:108,},"_C_c²":{74:69,76:71,77:72,},"c_c³":{86:81,88:83,89:84,},"c¹_c⁴":{98:93,100:95,101:96,},"c²_c⁵":{110:105,112:107,113:108,},"_C_c³":{86:81,88:83,89:84,},"c_c⁴":{98:93,100:95,101:96,},"c¹_c⁵":{110:105,112:107,113:108,},"_C_c⁴":{98:93,100:95,101:96,},"c_c⁵":{110:105,112:107,113:108,},"_C_c⁵":{110:105,112:107,113:108,},"_C_b":{62:57,64:59,},"c_b¹":{74:69,76:71,},"c¹_b²":{86:81,88:83,},"c²_b³":{98:93,100:95,},"c³_b⁴":{110:105,112:107,},"_C_b¹":{74:69,76:71,},"c_b²":{86:81,88:83,},"c¹_b³":{98:93,100:95,},"c²_b⁴":{110:105,112:107,},"_C_b²":{86:81,88:83,},"c_b³":{98:93,100:95,},"c¹_b⁴":{110:105,112:107,},"_C_b³":{98:93,100:95,},"c_b⁴":{110:105,112:107,},"_C_b⁴":{110:105,112:107,}}
# 根据 BPM 动态调整时间合并阈值
def get_dynamic_time_merge_threshold(bpm):
return max(GlobalVariable.merge_min, min(GlobalVariable.merge_max, int(60000 / bpm / 4))) # 限制阈值在 范围区间
# 15 个音符与键盘按键的映射
def get_bpm_from_midi(midi_file_path):
midi = pretty_midi.PrettyMIDI(midi_file_path)
tempos = midi.get_tempo_changes()
return tempos[1][0] if len(tempos[1]) > 0 else 120
def merge_keys(keys):
key_count = len(keys)
return [f"{key_count}Key{key.replace('1Key', '')}" for key in keys]
def process_midi_to_txt(input_path, output_path, version):
midi = pretty_midi.PrettyMIDI(input_path)
bpm = get_bpm_from_midi(input_path)
time_merge_threshold = get_dynamic_time_merge_threshold(bpm)
notes = []
for instrument in midi.instruments:
if not instrument.is_drum:
for note in instrument.notes:
pitch, time, velocity= note.pitch, int(note.start * 1000), note.velocity
if velocity < GlobalVariable.velocity_filter:
continue
if pitch in note_to_key[version]:
notes.append({'time': time, 'key': note_to_key[version][pitch]})
elif GlobalVariable.semitone_switch is True and pitch in extra_note_to_key[version]:
for extra_key in extra_note_to_key[version][pitch]:
notes.append({'time': time, 'key': extra_key})
elif GlobalVariable.detail_switch is True and pitch in special_note_mapping[version]:
notes.append({'time': time, 'key': note_to_key[version][special_note_mapping[version][pitch]]})
notes.sort(key=lambda x: x['time'])
merged_notes, last_time, temp_keys = [], None, []
for note in notes:
if last_time is None or note['time'] - last_time <= time_merge_threshold:
temp_keys.append(note['key'])
else:
merged_notes.extend({'time': last_time, 'key': k} for k in merge_keys(temp_keys))
temp_keys = [note['key']]
last_time = note['time']
merged_notes.extend({'time': last_time, 'key': k} for k in merge_keys(temp_keys))
output = [{
"name": os.path.basename(input_path).replace("_basic_pitch.mid","_Range") + version,
"author": "skyMusic-WindHide",
"transcribedBy": "WindHide's Software",
"bpm": bpm,
"bitsPerPage": 15,
"pitchLevel": 0,
"isComposed": True,
"songNotes": merged_notes,
"isEncrypted": False,
}]
with open(output_path, 'w') as f:
json.dump(output, f, indent=4)
return 100
def process_directory_with_progress(typeStr, output_dir=getResourcesPath("myTranslate")):
GlobalVariable.overall_progress = 0
os.makedirs(output_dir, exist_ok=True)
files_to_process = [f for f in os.listdir(getResourcesPath("translateOriginalMusic"))
if f.endswith(('.mp3', '.ogg', '.wav', '.flac', '.mid', '.m4a'))]
total_files = len(files_to_process)
tranMap = []
if GlobalVariable.is_singular:
mapping = {
"2": ["_C_c¹", "c_c²", "c¹_c³", "c²_c⁴", "c³_c⁵"],
"3": ["_C_c²", "c_c³", "c¹_c⁴", "c²_c⁵"],
"4": ["_C_c³", "c_c⁴", "c¹_c⁵"],
"5": ["_C_c⁴", "c_c⁵"],
"6": ["_C_c⁵"]
}
else:
mapping = {
"2": ["_C_b", "c_b¹", "c¹_b²", "c²_b³", "c³_b⁴"],
"3": ["_C_b¹", "c_b²", "c¹_b³", "c²_b⁴"],
"4": ["_C_b²", "c_b³", "c¹_b⁴"],
"5": ["_C_b³", "c_b⁴"],
"6": ["_C_b⁴"]
}
for key in mapping:
if key in typeStr:
tranMap.extend(mapping[key])
if not total_files:
print("没有找到需要处理的文件")
return
if GlobalVariable.split_switch:
for idx, file in enumerate(files_to_process):
if "_ok" in file or "_vocals.flac" in file or "_beat.flac" in file:
continue
musicFilePath = os.path.join(getResourcesPath("translateOriginalMusic"), file)
split_vocals(musicFilePath) # 处理人声分离
files_to_process = [f for f in os.listdir(getResourcesPath("translateOriginalMusic"))
if f.endswith(('.mp3', '.ogg', '.wav', '.flac', '.mid', '.m4a'))]
for idx, file in enumerate(files_to_process):
if "_ok" in file:
continue
GlobalVariable.now_translate_text = [f"{idx + 1}/{total_files}", file]
fileNameNoEnd = file.rsplit('.', 1)[0]
midFilePath = os.path.join(getResourcesPath("translateMID"), f"{fileNameNoEnd}_basic_pitch")
musicFilePath = os.path.join(getResourcesPath("translateOriginalMusic"), file)
if not file.endswith(".mid"):
inference(input_path=musicFilePath)
else:
midFilePath = os.path.join(getResourcesPath("translateOriginalMusic"), f"{fileNameNoEnd}")
for version in tranMap:
process_midi_to_txt(midFilePath + ".mid", os.path.join(output_dir, f"{fileNameNoEnd}_Range_{version}.txt"),
version)
new_file_path = os.path.join(getResourcesPath("translateOriginalMusic"),
f"{fileNameNoEnd}_ok.{file.split('.')[-1]}")
os.rename(os.path.join(getResourcesPath("translateOriginalMusic"), file), new_file_path)
print(f"已将文件 {file} 重命名为 {new_file_path}")
GlobalVariable.overall_progress = ((idx + 1) / total_files) * 100
GlobalVariable.overall_progress = 100
================================================
FILE: sky-music-server/windhide/musicToSheet/transfer_MID.py
================================================
from windhide.utils.path_util import getResourcesPath
def inference(input_path):
from basic_pitch import ICASSP_2022_MODEL_PATH
from basic_pitch.inference import predict_and_save
output_midi_path = getResourcesPath("translateMID")
try:
predict_and_save([ input_path ],
output_midi_path,
True,
False,
False,
False,
ICASSP_2022_MODEL_PATH
)
except Exception as e:
print(e)
================================================
FILE: sky-music-server/windhide/musicToSheet/vocals_split.py
================================================
import os
import shutil
import demucs.separate
from windhide.utils.path_util import getResourcesPath
def split_vocals(musicFilePath):
ffmpeg_dir = os.path.join(getResourcesPath("systemTools"), "ffmpeg")
if not os.path.exists(os.path.join(ffmpeg_dir, "ffmpeg.exe")):
raise RuntimeError("FFmpeg 未找到,请确认路径正确")
# 注入 PATH
os.environ["PATH"] = ffmpeg_dir + os.pathsep + os.environ.get("PATH", "")
os.environ['TORCH_HOME'] = os.path.join(getResourcesPath("systemTools"), "modelData")
demucs.separate.main([
"--flac",
"--two-stems",
"vocals",
"-o", getResourcesPath("splitMusic"),
"-n", "mdx_extra",
musicFilePath
])
# 转换完成后
# 文件移动到translateOriginalMusic
filename = musicFilePath.split("\\")[-1].split(".")[0]
os.path.join(getResourcesPath("splitMusic"), "mdx_extra", filename)
no_vocals_path = os.path.join(getResourcesPath("splitMusic"), "mdx_extra", filename, "no_vocals.flac")
vocals_path = os.path.join(getResourcesPath("splitMusic"), "mdx_extra", filename, "vocals.flac")
# 移动文件
targetFolder = getResourcesPath("translateOriginalMusic")
shutil.move(no_vocals_path, f"{targetFolder}/{filename + '_beat.flac'}")
shutil.move(vocals_path, f"{targetFolder}/{filename + '_vocals.flac'}")
shutil.rmtree(os.path.join(getResourcesPath("splitMusic"), "mdx_extra", filename)) # 删除子文件夹
================================================
FILE: sky-music-server/windhide/playRobot/amd_robot.py
================================================
import ctypes
import threading
import time
from ctypes import windll
import keyboard
import pyautogui
import win32con
import win32gui
from windhide.static.global_variable import GlobalVariable
from windhide.thread.amd_play_thread import ControlledThread
from windhide.utils.path_util import convert_notes_to_delayed_format, convert_json_to_play
PostMessageW = windll.user32.PostMessageW # ok 消息队列
SendMessageW = windll.user32.SendMessageW # ok 立即处理
MapVirtualKeyW = windll.user32.MapVirtualKeyW
VkKeyScanW = windll.user32.VkKeyScanW
user32 = ctypes.windll.user32
WM_KEYDOWN = 0x100
WM_KEYUP = 0x101
pyautogui.FAILSAFE = False
def send_single_key_to_window_task(key, duration):
"""发送单个按键,减少延迟"""
key_down(key)
time.sleep(duration/1000 + GlobalVariable.duration)
key_up(key)
def send_multiple_key_to_window_task(keys, duration):
"""发送组合按键,减少延迟"""
for key in keys:
key_down(key)
time.sleep(duration/1000 + GlobalVariable.duration)
for key in keys:
key_up(key)
# 这里是跟弹独立出来的
def send_multiple_key_press(keys):
for key in keys:
keyboard.press(key)
def send_multiple_key_release(keys):
for key in keys:
keyboard.release(key)
# 现在给模拟器和跟弹在用了
def send_single_key_to_window_follow(key, duration):
"""发送单个按键,减少延迟"""
keyboard.press(key)
time.sleep(duration/1000 + GlobalVariable.duration)
keyboard.release(key)
# 现在给模拟器和跟弹在用了
def send_multiple_key_to_window_follow(keys, duration):
"""发送组合按键,减少延迟"""
for key in keys:
keyboard.press(key)
time.sleep(duration/1000 + GlobalVariable.duration)
for key in keys:
keyboard.release(key)
def execute_in_thread(target, *args, **kwargs):
"""通用线程执行器,采用线程池管理"""
thread = threading.Thread(target=target, args=args, kwargs=kwargs)
thread.daemon = True # 将线程设置为守护线程,程序退出时自动结束线程
thread.start()
return thread
def send_single_key_to_window(key, duration):
"""发送单个按键(新线程中执行)"""
if GlobalVariable.compatibility_mode:
execute_in_thread(send_single_key_to_window_follow, key,duration)
else:
execute_in_thread(send_single_key_to_window_task, key,duration)
def send_multiple_key_to_window(keys, duration):
"""发送组合按键(新线程中执行)"""
if GlobalVariable.compatibility_mode:
execute_in_thread(send_multiple_key_to_window_follow, keys,duration)
else:
execute_in_thread(send_multiple_key_to_window_task, keys,duration)
def playMusic(fileName, type):
"""优化音乐播放逻辑,只加载乐谱数据一次"""
convert_notes_to_delayed_format(fileName, type)
if GlobalVariable.thread is not None:
stop()
GlobalVariable.thread = ControlledThread()
GlobalVariable.thread.start()
def playMusic_edit(text):
"""优化音乐播放逻辑,只加载乐谱数据一次"""
convert_json_to_play(text)
if GlobalVariable.thread is not None:
stop()
GlobalVariable.thread = ControlledThread()
GlobalVariable.thread.start()
def resume():
"""恢复播放"""
if GlobalVariable.thread:
GlobalVariable.thread.resume()
def pause():
"""暂停播放"""
if GlobalVariable.thread:
GlobalVariable.thread.pause()
def stop():
"""停止播放"""
if GlobalVariable.thread:
GlobalVariable.thread.stop()
GlobalVariable.set_progress = 0
GlobalVariable.thread = None
# 点击,按下
def mouse_move_to(x: int, y: int):
# 获取窗口的屏幕位置
window_rect = win32gui.GetWindowRect(GlobalVariable.window["hWnd"]) # 返回 (left, top, right, bottom)
window_x, window_y = window_rect[0], window_rect[1]
client_x = window_x + x
client_y = window_y + y
win32gui.SendMessage(GlobalVariable.window["hWnd"], win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
pyautogui.moveTo(client_x, client_y, duration=0)
# 核心
def key_press(key: str):
key = key.lower()
if key in special_keys:
vk_code, scan_code = special_keys[key]
else:
# 普通按键的处理
vk_code = VkKeyScanW(ctypes.c_wchar(key))
scan_code = keyboard.key_to_scan_codes(key)[0] if key != '/' else keyboard.key_to_scan_codes(key)[1]
lparam = (scan_code << 16) | 1
if GlobalVariable.is_post_w:
PostMessageW(GlobalVariable.window["hWnd"], win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
PostMessageW(GlobalVariable.window["hWnd"], WM_KEYDOWN, vk_code, lparam)
time.sleep(0.01)
PostMessageW(GlobalVariable.window["hWnd"], WM_KEYUP, vk_code, lparam)
else:
SendMessageW(GlobalVariable.window["hWnd"], win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
SendMessageW(GlobalVariable.window["hWnd"], WM_KEYDOWN, vk_code, lparam)
time.sleep(0.01)
SendMessageW(GlobalVariable.window["hWnd"], WM_KEYUP, vk_code, lparam)
def key_down(key: str):
set_us_keyboard_layout()
key = key.lower()
if key in special_keys:
vk_code, scan_code = special_keys[key]
else:
# 普通按键的处理
vk_code = VkKeyScanW(ctypes.c_wchar(key))
scan_code = keyboard.key_to_scan_codes(key)[0] if key != '/' else keyboard.key_to_scan_codes(key)[1]
lparam = (scan_code << 16) | 1
if GlobalVariable.is_post_w:
PostMessageW(GlobalVariable.window["hWnd"], win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
PostMessageW(GlobalVariable.window["hWnd"], WM_KEYDOWN, vk_code, lparam)
else:
SendMessageW(GlobalVariable.window["hWnd"], win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
SendMessageW(GlobalVariable.window["hWnd"], WM_KEYDOWN, vk_code, lparam)
def key_up(key: str):
set_us_keyboard_layout()
key = key.lower()
if key in special_keys:
vk_code, scan_code = special_keys[key]
else:
# 普通按键的处理
vk_code = VkKeyScanW(ctypes.c_wchar(key))
scan_code = keyboard.key_to_scan_codes(key)[0] if key != '/' else keyboard.key_to_scan_codes(key)[1]
lparam = (scan_code << 16) | 0XC0000001
if GlobalVariable.is_post_w:
PostMessageW(GlobalVariable.window["hWnd"], win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
PostMessageW(GlobalVariable.window["hWnd"], WM_KEYUP, vk_code, lparam)
else:
SendMessageW(GlobalVariable.window["hWnd"], win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
SendMessageW(GlobalVariable.window["hWnd"], WM_KEYUP, vk_code, lparam)
def mouse_wheel_scroll(operator):
match operator:
case 'up':
delta = 3000
case 'down':
delta = -3000
window_rect = win32gui.GetWindowRect(GlobalVariable.window["hWnd"]) # 返回 (left, top, right, bottom)
# 窗口中心
window_x, window_y = window_rect[0] + window_rect[0] / 2, window_rect[1] + window_rect[1] / 2
# 激活窗口
if GlobalVariable.is_post_w:
PostMessageW(GlobalVariable.window["hWnd"], win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
else:
SendMessageW(GlobalVariable.window["hWnd"], win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
pyautogui.moveTo(window_x, window_y)
pyautogui.scroll(delta)
def set_us_keyboard_layout():
# LoadKeyboardLayoutW 函数的定义
user32.LoadKeyboardLayoutW.argtypes = [ctypes.c_wchar_p, ctypes.c_uint]
user32.LoadKeyboardLayoutW.restype = ctypes.c_void_p
user32.LoadKeyboardLayoutW("00000409", 1) # 0409 是美国键盘布局标识符,1 表示激活
special_keys = {
'space': (0x20, 0x39), # 空格键
'tab': (0x09, 0x0F), # Tab 键
'esc': (0x1B, 0x01), # Escape 键
'shift': (0x10, 0x2A), # 左 Shift 键
'right': (0x27, 0x4D), # 方向键右
'left': (0x25, 0x4B), # 方向键左
'up': (0x26, 0x48), # 方向键上
'down': (0x28, 0x50) # 方向键下
}
================================================
FILE: sky-music-server/windhide/playRobot/intel_robot.py
================================================
import ctypes
import time
import keyboard
import pyautogui
import win32con
import win32gui
from windhide.static.global_variable import GlobalVariable
from windhide.thread.intel_play_thread import ControlledThread
from windhide.utils.path_util import convert_notes_to_delayed_format, convert_json_to_play
PostMessageW = ctypes.windll.user32.PostMessageW # 消息队列
SendMessageW = ctypes.windll.user32.SendMessageW # 立即处理
MapVirtualKeyW = ctypes.windll.user32.MapVirtualKeyW
VkKeyScanW = ctypes.windll.user32.VkKeyScanW
user32 = ctypes.windll.user32
WM_KEYDOWN = 0x100
WM_KEYUP = 0x101
pyautogui.FAILSAFE = False
def send_single_key_to_window_task(key, duration):
"""发送单个按键,减少延迟"""
key_down(key)
time.sleep(duration/1000 + GlobalVariable.duration)
key_up(key)
def send_multiple_key_to_window_task(keys, duration):
"""发送组合按键,减少延迟"""
for key in keys:
key_down(key)
time.sleep(duration/1000 + GlobalVariable.duration)
for key in keys:
key_up(key)
def send_single_key_to_window(key, duration):
"""发送单个按键(单线程)"""
if GlobalVariable.compatibility_mode:
send_single_key_to_window_follow(key, duration)
else:
send_single_key_to_window_task(key, duration)
def send_multiple_key_to_window(keys, duration):
"""发送组合按键(单线程)"""
if GlobalVariable.compatibility_mode:
send_multiple_key_to_window_follow(keys, duration)
else:
send_multiple_key_to_window_task(keys, duration)
def send_single_key_to_window_follow(key, duration):
"""发送单个按键,减少延迟(单线程)"""
keyboard.press(key)
time.sleep(duration/1000 + GlobalVariable.duration)
keyboard.release(key)
def send_multiple_key_to_window_follow(keys, duration):
"""发送组合按键,减少延迟(单线程)"""
for key in keys:
keyboard.press(key)
time.sleep(duration/1000 + GlobalVariable.duration)
for key in keys:
keyboard.release(key)
def playMusic(fileName, type):
"""优化音乐播放逻辑,只加载乐谱数据一次"""
convert_notes_to_delayed_format(fileName, type)
if GlobalVariable.thread is not None:
stop()
GlobalVariable.thread = ControlledThread()
GlobalVariable.thread.start()
def playMusic_edit(text):
"""优化音乐播放逻辑,只加载乐谱数据一次"""
convert_json_to_play(text)
if GlobalVariable.thread is not None:
stop()
GlobalVariable.thread = ControlledThread()
GlobalVariable.thread.start()
def resume():
"""恢复播放"""
if GlobalVariable.thread:
GlobalVariable.thread.resume()
def pause():
"""暂停播放"""
if GlobalVariable.thread:
GlobalVariable.thread.pause()
def stop():
"""停止播放"""
if GlobalVariable.thread:
GlobalVariable.thread.stop()
GlobalVariable.set_progress = 0
GlobalVariable.thread = None
# 点击,按下
def mouse_move_to(x: int, y: int):
# 获取窗口的屏幕位置
window_rect = win32gui.GetWindowRect(GlobalVariable.window["hWnd"]) # 返回 (left, top, right, bottom)
window_x, window_y = window_rect[0], window_rect[1]
client_x = window_x + x
client_y = window_y + y
if GlobalVariable.is_post_w:
PostMessageW(GlobalVariable.window["hWnd"], win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
else:
SendMessageW(GlobalVariable.window["hWnd"], win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
pyautogui.moveTo(client_x, client_y, duration=0)
# 核心
def key_press(key: str):
key = key.lower()
if key in special_keys:
vk_code, scan_code = special_keys[key]
else:
# 普通按键的处理
vk_code = VkKeyScanW(ctypes.c_wchar(key))
scan_code = keyboard.key_to_scan_codes(key)[0] if key != '/' else keyboard.key_to_scan_codes(key)[1]
lparam = (scan_code << 16) | 1
if GlobalVariable.is_post_w:
PostMessageW(GlobalVariable.window["hWnd"], win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
PostMessageW(GlobalVariable.window["hWnd"], WM_KEYDOWN, vk_code, lparam)
time.sleep(0.01)
PostMessageW(GlobalVariable.window["hWnd"], WM_KEYUP, vk_code, lparam)
else:
SendMessageW(GlobalVariable.window["hWnd"], win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
SendMessageW(GlobalVariable.window["hWnd"], WM_KEYDOWN, vk_code, lparam)
time.sleep(0.01)
SendMessageW(GlobalVariable.window["hWnd"], WM_KEYUP, vk_code, lparam)
def key_down(key: str):
set_us_keyboard_layout()
key = key.lower()
if key in special_keys:
vk_code, scan_code = special_keys[key]
else:
# 普通按键的处理
vk_code = VkKeyScanW(ctypes.c_wchar(key))
scan_code = keyboard.key_to_scan_codes(key)[0] if key != '/' else keyboard.key_to_scan_codes(key)[1]
lparam = (scan_code << 16) | 1
if GlobalVariable.is_post_w:
PostMessageW(GlobalVariable.window["hWnd"], win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
PostMessageW(GlobalVariable.window["hWnd"], WM_KEYDOWN, vk_code, lparam)
else:
SendMessageW(GlobalVariable.window["hWnd"], win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
SendMessageW(GlobalVariable.window["hWnd"], WM_KEYDOWN, vk_code, lparam)
def key_up(key: str):
key = key.lower()
if key in special_keys:
vk_code, scan_code = special_keys[key]
else:
# 普通按键的处理
vk_code = VkKeyScanW(ctypes.c_wchar(key))
scan_code = keyboard.key_to_scan_codes(key)[0] if key != '/' else keyboard.key_to_scan_codes(key)[1]
lparam = (scan_code << 16) | 0XC0000001
if GlobalVariable.is_post_w:
PostMessageW(GlobalVariable.window["hWnd"], win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
PostMessageW(GlobalVariable.window["hWnd"], WM_KEYUP, vk_code, lparam)
else:
SendMessageW(GlobalVariable.window["hWnd"], win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
SendMessageW(GlobalVariable.window["hWnd"], WM_KEYUP, vk_code, lparam)
def mouse_wheel_scroll(operator):
match operator:
case 'up':
delta = 3000
case 'down':
delta = -3000
window_rect = win32gui.GetWindowRect(GlobalVariable.window["hWnd"]) # 返回 (left, top, right, bottom)
# 窗口中心
window_x, window_y = window_rect[0] + window_rect[0] / 2, window_rect[1] + window_rect[1] / 2
# 激活窗口
if GlobalVariable.is_post_w:
PostMessageW(GlobalVariable.window["hWnd"], win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
else:
SendMessageW(GlobalVariable.window["hWnd"], win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
pyautogui.moveTo(window_x, window_y)
pyautogui.scroll(delta)
def set_us_keyboard_layout():
# LoadKeyboardLayoutW 函数的定义
user32.LoadKeyboardLayoutW.argtypes = [ctypes.c_wchar_p, ctypes.c_uint]
user32.LoadKeyboardLayoutW.restype = ctypes.c_void_p
user32.LoadKeyboardLayoutW("00000409", 1) # 0409 是美国键盘布局标识符,1 表示激活
special_keys = {
'space': (0x20, 0x39), # 空格键
'tab': (0x09, 0x0F), # Tab 键
'esc': (0x1B, 0x01), # Escape 键
'shift': (0x10, 0x2A), # 左 Shift 键
'right': (0x27, 0x4D), # 方向键右
'left': (0x25, 0x4B), # 方向键左
'up': (0x26, 0x48), # 方向键上
'down': (0x28, 0x50) # 方向键下
}
================================================
FILE: sky-music-server/windhide/static/global_variable.py
================================================
class GlobalVariable:
# 运行环境配置
isProd = False
cpu_type = None
compatibility_mode = False # 是否虚拟机
is_post_w = False # 是否插队模式
duration_prompt = """你现在是一个音乐数据生成器,不是聊天助手。
🎼 输入是一首歌曲的音符数据,格式为一个 JSON 数组,每个元素如下:
{"time":0,"key":"1Key10","duration":400}
解释:
- `time` 表示时间,单位为毫秒。
- `key` 表示音符键名,格式为 `{轨道号}Key{音高编号}`。
- `轨道号` 是数字,如 `1` 或 `2`,表示旋律或和声的分轨。
- `duration` 表示这个音要持续的时长(单位毫秒,最少 300 毫秒)。
- `音高编号` 必须从下表中选择:
| 编号 | keyN | 对应音高 |
|------|--------|-----------------|
| 0 | Key0 | C4 (MIDI 60) |
| 1 | Key1 | D4 (62) |
| 2 | Key2 | E4 (64) |
| 3 | Key3 | F4 (65) |
| 4 | Key4 | G4 (67) |
| 5 | Key5 | A4 (69) |
| 6 | Key6 | B4 (71) |
| 7 | Key7 | C5 (72) |
| 8 | Key8 | D5 (74) |
| 9 | Key9 | E5 (76) |
| 10 | Key10 | F5 (77) |
| 11 | Key11 | G5 (79) |
| 12 | Key12 | A5 (81) |
| 13 | Key13 | B5 (83) |
| 14 | Key14 | C6 (84) |
🧠 你的任务:
- 对原始旋律进行改编,使旋律更自然流畅。
- 添加或修改 `duration` 字段。
- **允许自由发挥,增加音符数量不得少于原始音符总数,鼓励适度创新和变化。**
- 避免长时间重复相同旋律,保证整体音乐连贯性。
- 生成的 `time` 应合理反映节奏,避免毫秒级零散断裂,建议保持符合歌曲节拍风格。
- 轨道号代表不同声部,1号轨道为主旋律,2号轨道为和声伴奏,可适当调整和声层次。
- 同一时间点最多同时按下3个不同音符,保持和声自然。
- 每次输出最多 40 个完整 JSON 元素,不允许跨元素截断。
- 每个元素结构严格为:`{"time":x,"key":"xKeyx","duration":x},`
- 所有 `key` 必须严格来自上方表格(含轨道前缀,如 `"1Key4"`)。
- 如果多个音符同时按下,使用相同的 `time` 和不同的 `key`。
- 允许对不合理音节进行改造,使其更符合音乐逻辑。
🧩 输出格式要求:
- 每次输出仅包含中间段 JSON 元素,不输出 `[` 或 `]`。
- 每次输出第一行必须注明:`第 N 段(共预计 M 段)`
- 输出不得包含除 JSON 结构外的任何注释、说明或总结语。
- 不得说“已完成”、“以下是输出”、“JSON 完整”等总结语。
🪛 响应规范:
- 保证每个 JSON 对象都闭合(不能残缺或中断)。
- 禁止输出非 `{"time":,"key":}` 格式的字段。
- 当我回复“继续”,你只输出**上一次后的 JSON 段落**,禁止重复。
- 你不能停止任务,除非我显式让你“停止”。
🎵 准备开始时,我会提供原始 JSON 数据,你必须根据以上规范对其改编并分段输出。
示例输出片段:
第 1 段(共预计 10 段)
{"time":0,"key":"1Key10","duration":400},{"time":250,"key":"1Key12","duration":120},{"time":250,"key":"2Key4","duration":200},{"time":500,"key":"1Key14","duration":300},
{"time":750,"key":"1Key13","duration":312},{"time":1000,"key":"1Key11","duration":313},{"time":1250,"key":"1Key9","duration":421},{"time":1250,"key":"2Key7","duration":436},
{"time":1500,"key":"1Key10","duration":332},{"time":1750,"key":"1Key12","duration":315}"""
translate_prompt = """你现在是一个音乐数据生成器,不是聊天助手。
🎼 输入是一首歌曲的音符数据,格式为一个 JSON 数组,每个元素如下:
{"time":0,"key":"1Key10"}
解释:
- `time` 表示时间,单位为毫秒。
- `key` 表示音符键名,格式为 `{轨道号}Key{音高编号}`。
- `轨道号` 是数字,如 `1` 或 `2`,表示旋律或和声的分轨。
- `音高编号` 必须从下表中选择:
| 编号 | keyN | 对应音高 |
|------|--------|-----------------|
| 0 | Key0 | C4 (MIDI 60) |
| 1 | Key1 | D4 (62) |
| 2 | Key2 | E4 (64) |
| 3 | Key3 | F4 (65) |
| 4 | Key4 | G4 (67) |
| 5 | Key5 | A4 (69) |
| 6 | Key6 | B4 (71) |
| 7 | Key7 | C5 (72) |
| 8 | Key8 | D5 (74) |
| 9 | Key9 | E5 (76) |
| 10 | Key10 | F5 (77) |
| 11 | Key11 | G5 (79) |
| 12 | Key12 | A5 (81) |
| 13 | Key13 | B5 (83) |
| 14 | Key14 | C6 (84) |
🧠 你的任务:
- 对原始旋律进行改编,使旋律更自然流畅。
- **允许自由发挥,增加音符数量不得少于原始音符总数,鼓励适度创新和变化。**
- 避免长时间重复相同旋律,保证整体音乐连贯性。
- 生成的 `time` 应合理反映节奏,避免毫秒级零散断裂,建议保持符合歌曲节拍风格。
- 轨道号代表不同声部,1号轨道为主旋律,2号轨道为和声伴奏,可适当调整和声层次。
- 同一时间点最多同时按下3个不同音符,保持和声自然。
- 每次输出最多 40 个完整 JSON 元素,不允许跨元素截断。
- 每个元素结构严格为:`{"time":1234,"key":"xKeyN"},`
- 所有 `key` 必须严格来自上方表格(含轨道前缀,如 `"1Key4"`)。
- 如果多个音符同时按下,使用相同的 `time` 和不同的 `key`。
- 允许对不合理音节进行改造,使其更符合音乐逻辑。
🧩 输出格式要求:
- 每次输出仅包含中间段 JSON 元素,不输出 `[` 或 `]`。
- 每次输出第一行必须注明:`第 N 段(共预计 M 段)`
- 输出不得包含除 JSON 结构外的任何注释、说明或总结语。
- 不得说“已完成”、“以下是输出”、“JSON 完整”等总结语。
🪛 响应规范:
- 保证每个 JSON 对象都闭合(不能残缺或中断)。
- 禁止输出非 `{"time":,"key":}` 格式的字段。
- 当我回复“继续”,你只输出**上一次后的 JSON 段落**,禁止重复。
- 你不能停止任务,除非我显式让你“停止”。
🎵 准备开始时,我会提供原始 JSON 数据,你必须根据以上规范对其改编并分段输出。
示例输出片段:
第 1 段(共预计 10 段)
{"time":0,"key":"1Key10"},{"time":250,"key":"1Key12"},{"time":250,"key":"2Key4"},{"time":500,"key":"1Key14"},
{"time":750,"key":"1Key13"},{"time":1000,"key":"1Key11"},{"time":1250,"key":"1Key9"},{"time":1250,"key":"2Key7"},
{"time":1500,"key":"1Key10"},{"time":1750,"key":"1Key12"}"""
general_ai = {
# "Kimi":{
# "key": "sk-sfpR8uS6S0puwi3d758viFNLrPmXTwDyhoecDEEIorX49bbr",
# "url": "https://api.moonshot.cn/v1",
# "model": "moonshot-v1-128k",
# } ,
# "Qwen":{
# "key": "sk-99dae6996fb24d62a85a4e5b68c5619e",
# "url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
# "model": "qwen-turbo-latest",
# }
}
# 窗口相关
is_custom_hwnd = False
hwnd_title = "Sky.exe"
hwnd_select_struct = {}
window = {
"hWnd": None,
"width": 0,
"height": 0,
"position_x": 0,
"position_y": 0,
"is_change": False,
"key_position": None,
"wait": False
}
# 快捷键相关
shortcutStruct = {
"follow_key":{
"tap_key": "yuiophjkl;nm,./",
"string": "-=q",
"repeat": "-",
"repeat_next": '=',
"resize": "q",
"exit": "esc"
},
"music_key":{
"string": "f2f5f6f7f8updownleftrightpage_uppage_down",
"next": "f2",
"start": "f5",
"resume": "f6",
"pause": "f7",
"stop": "f8",
"add_duration": "up",
"reduce_duration": "down",
"add_delay": "right",
"reduce_delay": "left",
"add_speed": "page_up",
"reduce_speed": "page_down",
}
}
# 线程相关
thread = None
auto_thread = None
task_queue = None
# 乐谱相关
music_sheet = [] # 乐谱
# 进度条相关
now_progress = 0 # 进度条
set_progress = -0.01 # 进度条
now_play_music = "没有正在播放的歌曲哦"
now_total_time = ""
now_current_time = ""
# 音乐转换进度条
overall_progress = 0 # 总体进度
now_translate_text = []
merge_min = 20
merge_max = 30
velocity_filter = 10
is_singular = True
semitone_switch = True # 半音转换开关
detail_switch = True # 超3音转换开关
split_switch = False # 人声分离开关
# 演奏相关
play_speed = 1 # 倍速
delay_interval = 0 # 0
duration = 0 # 20毫秒
# 跟弹相关
follow_music = ""
follow_sheet = []
nowClientKey = ""
nowRobotKey = ""
follow_client = None
isNowAutoPlaying = False
follow_thread = None # 跟弹按键处理相关线程
exit_flag = False
draw_process = None
window_offset_x = 0
window_offset_y = 0
follow_process = None
sheld = 0.7
# 乐谱映射
keyMap = {
'Key-14': '', 'Key-13': '', 'Key-12': '', 'Key-11': '', 'Key-10': '', 'Key-9': '', 'Key-8': '',
'Key-7': '', 'Key-6': '', 'Key-5': '', 'Key-4': '', 'Key-3': '', 'Key-2': '', 'Key-1': '',
'Key0': 'y', 'Key1': 'u', 'Key2': 'i', 'Key3': 'o', 'Key4': 'p', 'Key5': 'h', 'Key6': 'j',
'Key7': 'k', 'Key8': 'l', 'Key9': ';', 'Key10': 'n', 'Key11': 'm', 'Key12': ',', 'Key13': '.',
'Key14': '/', 'Key15': '', 'Key16': '', 'Key17': '', 'Key18': '', 'Key19': '', 'Key20': '',
'Key21': '', 'Key22': '', 'Key23': '', 'Key24': '', 'Key25': '', 'Key26': '', 'Key27': '',
'Key28': ''
}
================================================
FILE: sky-music-server/windhide/thread/amd_play_thread.py
================================================
import math
import threading
import time
from windhide.playRobot import amd_robot
from windhide.static.global_variable import GlobalVariable
class ControlledThread:
def __init__(self):
self._pause_event = threading.Event()
self._pause_event.set() # 初始状态允许运行
self._thread = None
self._running = False
def _run(self):
if not GlobalVariable.music_sheet:
self.stop()
return
local_music_sheet = GlobalVariable.music_sheet[:]
total_length = 1 if len(local_music_sheet) == 1 else len(local_music_sheet) - 1
index = 0
# 在播放开始时计算总时长(单位为毫秒)
total_time = sum(sheet["delay"] for sheet in local_music_sheet)
GlobalVariable.now_total_time = self.format_time(total_time)
current_time = 0 # 当前时间(单位为毫秒)
while self._running and index < len(local_music_sheet):
self._pause_event.wait() # 等待恢复运行
if GlobalVariable.set_progress != -0.01:
index = math.floor(total_length * GlobalVariable.set_progress)
GlobalVariable.set_progress = -0.01
current_time = total_time * (index / total_length) # 调整当前时间
continue
sheet = local_music_sheet[index]
keys = sheet["key"]
delay = sheet["delay"] / GlobalVariable.play_speed
if total_length == 0:
GlobalVariable.now_progress = 100
else:
GlobalVariable.now_progress = (index / total_length) * 100
if keys:
(amd_robot.send_single_key_to_window if len(keys) == 1
else amd_robot.send_multiple_key_to_window)(keys, sheet["duration"] if "duration" in sheet else 0)
time.sleep(delay / 1000 + GlobalVariable.delay_interval)
# 格式化当前时间/总时间
# formatted_time = self.format_time(current_time) + " / " + self.format_time(total_time)
# print(f"播放时间:{formatted_time}", end="\r")
GlobalVariable.now_current_time = self.format_time(current_time)
current_time += delay # 更新当前时间
index += 1
def format_time(self, milliseconds):
"""
将毫秒数格式化为时分秒格式(HH:MM:SS 或 MM:SS)
"""
seconds = milliseconds / 1000
hours, remainder = divmod(seconds, 3600)
minutes, seconds = divmod(remainder, 60)
if hours > 0:
return f"{int(hours):02}:{int(minutes):02}:{int(seconds):02}"
else:
return f"{int(minutes):02}:{int(seconds):02}"
def start(self):
if self._running:
return
self._running = True
if self._thread and self._thread.is_alive():
self._thread.join()
self._thread = threading.Thread(target=self._run, daemon=True)
self._thread.start()
def pause(self):
self._pause_event.clear()
def resume(self):
self._pause_event.set()
def stop(self):
self._running = False
self.resume() # 避免暂停状态导致的死锁
if self._thread and self._thread.is_alive():
self._thread.join()
self._thread = None
================================================
FILE: sky-music-server/windhide/thread/follow_process_thread.py
================================================
import os
import subprocess
import psutil
from windhide.static.global_variable import GlobalVariable
from windhide.utils.ocr_normal_utils import get_game_position
from windhide.utils.path_util import getResourcesPath
GlobalVariable.draw_process = None # 进程对象
def run_follow_process():
try:
x1, y1, x2, y2 = get_game_position()
width, height = x2 - x1, y2 - y1
GlobalVariable.draw_process = subprocess.Popen(
[
os.path.join(getResourcesPath("systemTools"), "drawTool", "draw_server.exe"),
f"--width={width}",
f"--height={height}",
f"--x={x1}",
f"--y={y1}",
],
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP, # 独立进程组,便于管理
)
except (FileNotFoundError, subprocess.SubprocessError):
pass # 直接忽略异常,避免冗余输出
def stop_follow_process():
"""彻底终止 draw_server.exe 及其所有子进程"""
process = GlobalVariable.draw_process
if process and process.poll() is None:
try:
parent = psutil.Process(process.pid)
children = parent.children(recursive=True)
for child in children:
child.terminate()
_, still_alive = psutil.wait_procs(children, timeout=5)
for child in still_alive:
child.kill()
parent.terminate()
parent.wait(5)
GlobalVariable.draw_process = None # 清理进程记录
except psutil.NoSuchProcess:
pass
except Exception:
pass
================================================
FILE: sky-music-server/windhide/thread/follow_thread.py
================================================
import time
from os import path
import plyer
from pynput import keyboard
from windhide.playRobot.amd_robot import send_multiple_key_press, send_multiple_key_release
from windhide.static.global_variable import GlobalVariable
from windhide.utils import hook_util
from windhide.utils.command_util import resize_and_reload_key, clear_window_key, add_window_key, \
quit_window, update_key
from windhide.utils.path_util import getResourcesPath
hook_util.sout_null()
GlobalVariable.exit_flag = False # 添加全局退出标志
originalKeys = "None"
pressedKeys = set()
def get_key_string(key):
if hasattr(key, "char") and key.char is not None: # 处理普通字符按键
return key.char
elif hasattr(key, "name"): # 处理特殊按键
return key.name
else:
return str(key) # 兜底方案,转换为字符串
def on_press(key):
global pressedKeys, originalKeys
if GlobalVariable.exit_flag:
return False # 终止监听
if GlobalVariable.follow_music != "":
try:
key = get_key_string(key)
if key in GlobalVariable.shortcutStruct["follow_key"]["string"] or key in GlobalVariable.shortcutStruct["follow_key"]["tap_key"]:
if GlobalVariable.isNowAutoPlaying:
if key not in GlobalVariable.shortcutStruct["follow_key"]["repeat"]:
GlobalVariable.nowRobotKey += key
if len(GlobalVariable.nowRobotKey) == len(GlobalVariable.nowClientKey):
GlobalVariable.isNowAutoPlaying = False
GlobalVariable.nowRobotKey = ''
else:
if key in GlobalVariable.shortcutStruct["follow_key"]["repeat"]:
GlobalVariable.isNowAutoPlaying = True
send_multiple_key_press(GlobalVariable.nowClientKey)
if key in GlobalVariable.shortcutStruct["follow_key"]["repeat_next"]:
send_multiple_key_press(GlobalVariable.nowClientKey)
except Exception as e:
print(f"发生错误: {e.__doc__} , 开始重新加载")
# 处理 Esc 退出监听
if key == GlobalVariable.shortcutStruct["follow_key"]["exit"] or key == "alt_l":
print("检测到 Esc 键,退出监听...")
GlobalVariable.exit_flag = True # 设置全局标志
try:
quit_window()
except Exception as e:
return False
return False # 停止监听
def key_release(key):
global pressedKeys, originalKeys
if GlobalVariable.exit_flag:
return False # 终止监听
if GlobalVariable.follow_music != "":
try:
key = get_key_string(key)
if key in GlobalVariable.shortcutStruct["follow_key"]["string"] or key in GlobalVariable.shortcutStruct["follow_key"]["tap_key"]:
if GlobalVariable.isNowAutoPlaying:
if key not in GlobalVariable.shortcutStruct["follow_key"]["repeat"]:
GlobalVariable.nowRobotKey += key
if len(GlobalVariable.nowRobotKey) == len(GlobalVariable.nowClientKey):
GlobalVariable.isNowAutoPlaying = False
GlobalVariable.nowRobotKey = ''
else:
if key in GlobalVariable.shortcutStruct["follow_key"]["repeat"]:
GlobalVariable.isNowAutoPlaying = True
send_multiple_key_release(GlobalVariable.nowClientKey)
if key in GlobalVariable.shortcutStruct["follow_key"]["repeat_next"]:
send_multiple_key_release(GlobalVariable.nowClientKey)
if key in GlobalVariable.shortcutStruct["follow_key"]["resize"]:
resize_and_reload_key()
else:
if key in originalKeys:
pressedKeys.add(key)
if len(pressedKeys) == len(originalKeys):
pressedKeys.clear()
maybe_next_key = get_next_sheet_demo("ok")
if not maybe_next_key:
return
else:
originalKeys = set(maybe_next_key)
except Exception as e:
print(f"发生错误: {e.__doc__} , 开始重新加载")
# 处理 Esc 退出监听
if key == GlobalVariable.shortcutStruct["follow_key"]["exit"] or key == "alt_l":
print("检测到 Esc 键,退出监听...")
GlobalVariable.exit_flag = True # 设置全局标志
try:
quit_window()
except Exception as e:
return False
return False # 停止监听
def get_next_sheet_demo(operator):
if len(GlobalVariable.follow_sheet) == 0:
return ""
try:
if operator != "load":
global originalKeys
clear_window_key(originalKeys)
if operator == "ok" or operator == "load":
sheet = GlobalVariable.follow_sheet[0]
GlobalVariable.nowClientKey = sheet
GlobalVariable.follow_sheet = GlobalVariable.follow_sheet[1:]
if len(GlobalVariable.follow_sheet) == 0:
GlobalVariable.exit_flag = True # 设置全局标志
quit_window()
plyer.notification.notify(
app_name='小星弹琴软件',
app_icon=path.join(getResourcesPath("systemTools"), "icon.ico"),
title='已经结束啦',
message='这首歌已经弹完了哦',
timeout=1
)
send_multiple_key_press("z")
send_multiple_key_release("z")
return False
for key in sheet:
add_window_key(key)
update_key()
return sheet
else:
GlobalVariable.nowClientKey = GlobalVariable.follow_sheet[0]
return GlobalVariable.follow_sheet[0]
except IndexError:
print("空数组")
return ""
def startThread():
global originalKeys, pressedKeys
time.sleep(2)
originalKeys = set(get_next_sheet_demo("load"))
pressedKeys = set()
GlobalVariable.exit_flag = False # 确保每次启动时标志位重置
with keyboard.Listener(on_press=on_press, on_release=key_release) as listener:
listener.join()
================================================
FILE: sky-music-server/windhide/thread/frame_alive_thread.py
================================================
import os
import time
import psutil
def is_process_running(process_name):
"""检查目标进程是否运行"""
return any(proc.info["name"] == process_name for proc in psutil.process_iter(["name"]))
def monitor_process(process_name):
"""监听目标进程的状态,如果退出则结束主程序"""
print(f"🔍 监听进程: {process_name}")
while True:
if not is_process_running(process_name):
print(f"⚠️ {process_name} 已退出,关闭主程序。")
os._exit(0) # 强制退出主进程
time.sleep(1) # 每秒检查一次
================================================
FILE: sky-music-server/windhide/thread/hwnd_check_thread.py
================================================
import os
import time
import traceback
import psutil
import pywintypes
import win32gui
import win32process
from windhide.static.global_variable import GlobalVariable
from windhide.utils import hook_util
from windhide.utils.ocr_follow_util import get_key_position
hook_util.sout_null()
def safe_enum_windows(callback):
"""封装 EnumWindows 调用,捕获特定异常,防止异常中断调用链"""
try:
win32gui.EnumWindows(callback, None)
except pywintypes.error as e:
error_code = e.args[0] # 获取错误码
error_message = e.args[1] # 获取错误消息
error_details = e.args[2] # 获取错误详细信息
if error_code == 2: # 系统找不到指定文件,忽略
print(f"[DEBUG] EnumWindows 错误码 2:{error_message},详细信息:{error_details}")
else:
# 打印错误码、消息和详细信息,同时也捕获堆栈跟踪
print(f"[DEBUG] EnumWindows 异常:")
print(f" 错误码: {error_code}")
print(f" 错误消息: {error_message}")
print(f" 错误详细信息: {error_details}")
print("[DEBUG] 错误堆栈跟踪:")
traceback.print_exc() # 打印堆栈跟踪
def get_exe_name_from_hwnd(hwnd):
# 获取窗口的 PID
_, pid = win32process.GetWindowThreadProcessId(hwnd)
try:
# 使用 psutil 获取进程的 exe 路径
process = psutil.Process(pid)
exe_path = process.exe() # 获取 exe 路径
exe_name = os.path.basename(exe_path) # 只获取文件名
return exe_name
except (psutil.NoSuchProcess, psutil.AccessDenied):
return None
def find_window_by_class(class_name):
"""根据窗口类名查找窗口句柄"""
hwnd = win32gui.FindWindow(class_name, None)
return hwnd
def update_window_handle():
"""查找窗口句柄,并更新全局变量"""
if GlobalVariable.is_custom_hwnd is False:
class_name = "TgcMainWindow"
hwnd = find_window_by_class(class_name)
if hwnd:
left, top, right, bottom = win32gui.GetWindowRect(hwnd)
width, height = right - left, bottom - top
window = GlobalVariable.window
window["hWnd"], window["width"], window["height"] = hwnd, width, height
window["position_x"], window["position_y"] = left, top
# 仅在窗口信息发生变化时设置 is_change
if any([
window["width"] != width,
window["height"] != height,
window["position_x"] != left,
window["position_y"] != top,
]):
window["is_change"] = True
GlobalVariable.hwnd_title = get_exe_name_from_hwnd(hwnd)
get_key_position(0.88)
else:
GlobalVariable.window["hWnd"] = None
else:
GlobalVariable.hwnd_title = get_exe_name_from_hwnd(GlobalVariable.window["hWnd"])
def start_thread():
"""后台线程循环更新窗口句柄"""
while True:
update_window_handle()
time.sleep(2)
================================================
FILE: sky-music-server/windhide/thread/intel_play_thread.py
================================================
import math
import threading
import time
from windhide.playRobot import intel_robot
from windhide.static.global_variable import GlobalVariable
class ControlledThread:
def __init__(self):
self._pause_event = threading.Event()
self._pause_event.set() # 初始状态为运行
self._thread = None
self._running = False
def _run(self):
if not GlobalVariable.music_sheet:
self.stop()
return
local_music_sheet = GlobalVariable.music_sheet[:]
total_length = 1 if len(local_music_sheet) == 1 else len(local_music_sheet) - 1
index = 0
# 在播放开始时计算总时长(单位为毫秒)
total_time = sum(sheet["delay"] for sheet in local_music_sheet)
GlobalVariable.now_total_time = self.format_time(total_time)
current_time = 0 # 当前时间(单位为毫秒)
while self._running and index < len(local_music_sheet):
self._pause_event.wait() # 等待恢复
if GlobalVariable.set_progress != -0.01:
index = math.floor(total_length * GlobalVariable.set_progress)
GlobalVariable.set_progress = -0.01
current_time = total_time * (index / total_length) # 调整当前时间
continue
sheet = local_music_sheet[index]
keys = sheet["key"]
delay = sheet["delay"] / GlobalVariable.play_speed
if total_length == 0:
GlobalVariable.now_progress = 100
else:
GlobalVariable.now_progress = (index / total_length) * 100
if keys:
(intel_robot.send_single_key_to_window if len(keys) == 1
else intel_robot.send_multiple_key_to_window)(keys, sheet["duration"] if "duration" in sheet else 0)
time.sleep(delay / 1000 + GlobalVariable.delay_interval)
# 格式化当前时间/总时间
# formatted_time = self.format_time(current_time) + " / " + self.format_time(total_time)
# print(f"播放时间:{formatted_time}", end="\r")
GlobalVariable.now_current_time = self.format_time(current_time)
current_time += delay # 更新当前时间
index += 1
def format_time(self, milliseconds):
"""
将毫秒数格式化为时分秒格式(HH:MM:SS 或 MM:SS)
"""
seconds = milliseconds / 1000
hours, remainder = divmod(seconds, 3600)
minutes, seconds = divmod(remainder, 60)
if hours > 0:
return f"{int(hours):02}:{int(minutes):02}:{int(seconds):02}"
else:
return f"{int(minutes):02}:{int(seconds):02}"
def start(self):
if self._running:
return
self._running = True
if self._thread and self._thread.is_alive():
self._thread.join()
self._thread = threading.Thread(target=self._run, daemon=True)
self._thread.start()
def pause(self):
self._pause_event.clear()
def resume(self):
self._pause_event.set()
def stop(self):
self._running = False
self.resume() # 防止死锁
if self._thread and self._thread.is_alive():
self._thread.join()
self._thread = None
================================================
FILE: sky-music-server/windhide/thread/queue_thread.py
================================================
import queue
import re
from os import path
import plyer
from windhide.playRobot import intel_robot, amd_robot
from windhide.static.global_variable import GlobalVariable
from windhide.utils.path_util import getResourcesPath
def music_start_tasks():
while True:
try:
request = GlobalVariable.task_queue.get(block=True, timeout=0.5) # 最多等待1秒
except queue.Empty:
continue
try:
if GlobalVariable.window["hWnd"] is None and GlobalVariable.compatibility_mode is False:
plyer.notification.notify(
app_name='小星弹琴软件',
app_icon=path.join(getResourcesPath("systemTools"),"icon.ico"),
title='🔥🔥🔥🔥🔥',
message='未检测到游戏窗口,请打开游戏或者去句柄页面进行指定!本次播放操作释放',
timeout=1
)
GlobalVariable.now_progress = 100
GlobalVariable.task_queue.task_done()
continue
GlobalVariable.now_play_music = ("" if 'sheet' in request else re.sub(r"-#(\d+)(?=\.\w+)?", "", request["fileName"].replace(".txt", "")))
match GlobalVariable.cpu_type:
case "Intel":
intel_robot.playMusic_edit(request["sheet"]) if 'sheet' in request else intel_robot.playMusic(request["fileName"], request["type"])
case "AMD":
amd_robot.playMusic_edit(request["sheet"]) if 'sheet' in request else amd_robot.playMusic(request["fileName"], request["type"])
except Exception as e:
print(f"Error in processing task: {str(e)}")
GlobalVariable.task_queue.task_done() # 标记任务完成
================================================
FILE: sky-music-server/windhide/thread/shortcut_thread.py
================================================
import urllib
from pynput import keyboard
from websocket_server import WebsocketServer
from windhide.static.global_variable import GlobalVariable
from windhide.utils import hook_util
hook_util.sout_null()
server = WebsocketServer("127.0.0.1", 11452)
def get_key_string(key):
if hasattr(key, "char") and key.char is not None: # 处理普通字符按键
return key.char
elif hasattr(key, "name"): # 处理特殊按键
return key.name
else:
return str(key) # 兜底方案,转换为字符串
# 键盘按键事件处理
def on_press(key):
key = get_key_string(key)
if key in GlobalVariable.shortcutStruct["music_key"]["string"]:
try:
server.send_message_to_all(urllib.parse.quote(key))
except Exception as e:
print(f"发送消息时发生错误: {e}")
# 客户端事件处理
def on_client_connect(client, server):
print(f"客户端 {client['id']} 已连接")
def on_client_disconnect(client, server):
print(f"客户端 {client['id']} 已断开连接")
# 启动 WebSocket 服务
def startThread():
try:
server.set_fn_new_client(on_client_connect)
server.set_fn_client_left(on_client_disconnect)
listener = keyboard.Listener(on_press=on_press)
listener.start()
print("WebSocket 服务正在启动,监听 127.0.0.1:11452...")
server.run_forever()
except Exception as e:
print(f"WebSocket 服务器启动失败: {e}")
================================================
FILE: sky-music-server/windhide/utils/auto_util.py
================================================
from windhide.auto.auto_thread import HeartFireThread
from windhide.static.global_variable import GlobalVariable
def auto_click_fire():
if GlobalVariable.auto_thread is not None:
GlobalVariable.auto_thread.stop()
GlobalVariable.auto_thread = HeartFireThread()
GlobalVariable.auto_thread.daemon = True
GlobalVariable.auto_thread.start()
def shutdown():
GlobalVariable.auto_thread.stop()
================================================
FILE: sky-music-server/windhide/utils/command_util.py
================================================
import socket
import threading
import time
from os import path
from time import sleep
import plyer
from windhide.static.global_variable import GlobalVariable
from windhide.thread.follow_process_thread import run_follow_process, stop_follow_process
from windhide.utils.ocr_normal_utils import get_game_position
from windhide.utils.path_util import getResourcesPath
# 全局变量,存储客户端连接对象
GlobalVariable.follow_client = None
def start_process():
# 创建并启动线程,传递参数
thread = threading.Thread(target=run_follow_process)
thread.daemon = True # 设置为守护线程,主线程退出时自动退出
thread.start()
def add_window_key(key):
try:
width = GlobalVariable.window['key_position'][key]['width']
height = GlobalVariable.window['key_position'][key]['height']
position_x = GlobalVariable.window['key_position'][key]['position_x'] - GlobalVariable.window_offset_x
position_y = GlobalVariable.window['key_position'][key]['position_y'] - GlobalVariable.window_offset_y
send_command(f"draw {key} {width} {height} {position_x} {position_y} \n") # 绘制
except KeyError as e:
print("按键识别不完全,重新调用 => ", e, e.__doc__)
GlobalVariable.window["is_change"] = True
time.sleep(2)
resize_and_reload_key()
def del_window_key(key):
send_command(f"delete {key} \n") # 绘制
def clear_window_key(keys):
for key in keys:
send_command(f"delete {key} \n")
def update_key():
send_command(f"update \n")
def resize_and_reload_key():
if GlobalVariable.window["wait"] is not True:
GlobalVariable.window["is_change"] = True
GlobalVariable.window["wait"] = True
plyer.notification.notify(
app_name='小星弹琴软件',
app_icon=path.join(getResourcesPath("systemTools"), "icon.ico"),
title='已经在很努力的检测了',
message='如果长时间未检测完成请换个位置打开琴键',
timeout=0.3
)
while True:
sleep(1)
if len(GlobalVariable.window['key_position']) == 15 and GlobalVariable.window["wait"] == False:
GlobalVariable.window["wait"] = False
position = get_game_position()
position_x, position_y = position[0], position[1]
width, height = position[2] - position[0], position[3] - position[1]
# 检查窗口大小是否发生变化
if width != GlobalVariable.window.get('width', 0) or height != GlobalVariable.window.get('height', 0):
GlobalVariable.window['width'] = width
GlobalVariable.window['height'] = height
# 重新计算按键位置
from windhide.utils.ocr_follow_util import get_key_position
get_key_position(None) # 重新获取按键位置
send_command(f"resize {width} {height} {position_x} {position_y} \n")
clear_window_key(GlobalVariable.nowClientKey)
# 重新获取新的15个按键
if len(GlobalVariable.follow_sheet) > 0:
GlobalVariable.nowClientKey = GlobalVariable.follow_sheet[0]
for key in GlobalVariable.nowClientKey:
add_window_key(key)
update_key()
break
plyer.notification.notify(
app_name='小星弹琴软件',
app_icon=path.join(getResourcesPath("systemTools"), "icon.ico"),
title='检测完毕',
message='OK',
timeout=1
)
def quit_window():
send_command(f"exit\n")
GlobalVariable.follow_client.shutdown(socket.SHUT_RDWR) # 关闭套接字的读写
GlobalVariable.follow_client.close()
GlobalVariable.follow_client = None
stop_follow_process()
def wait_for_server(host, port, max_retries=30, delay=2):
"""等待服务器启动并可连接"""
retries = 0
while retries < max_retries:
try:
with socket.create_connection((host, port), timeout=1) as sock:
return True # 服务器已启动
except (ConnectionRefusedError, OSError):
retries += 1
time.sleep(delay)
return False # 超时
def send_command(command):
try:
if GlobalVariable.follow_client is None:
if not wait_for_server("localhost", 12345):
print("无法连接到 draw_server.exe,超时退出。")
return
GlobalVariable.follow_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
GlobalVariable.follow_client.connect(("localhost", 12345)) # 连接到服务器
GlobalVariable.follow_client.send(command.encode("utf-8"))
print(f"发送命令: {command}")
except WindowsError as e:
GlobalVariable.exit_flag = True
print("send Command错误", e.__doc__)
except Exception as e:
print("send Command错误", e.__doc__)
GlobalVariable.exit_flag = True
# GlobalVariable.follow_client.shutdown(socket.SHUT_RDWR) # 关闭套接字的读写
# GlobalVariable.follow_client.close()
# GlobalVariable.follow_client = None
================================================
FILE: sky-music-server/windhide/utils/config_util.py
================================================
import os
import shutil
from windhide.musicToSheet.music2html import generatorSheetHtml
from windhide.static.global_variable import GlobalVariable
from windhide.utils.path_util import getResourcesPath, convert_notes_to_delayed_format
def set_config(request: dict):
match request["name"]:
case 'delay_interval':
GlobalVariable.delay_interval = float(request["value"])
case 'duration':
GlobalVariable.duration = float(request["value"])
case 'set_progress':
GlobalVariable.set_progress = float(request["value"])
case 'play_speed':
GlobalVariable.play_speed = float(request["value"])
case 'compatibility_mode':
GlobalVariable.compatibility_mode = request["value"]
case 'is_post_w':
GlobalVariable.is_post_w = request["value"]
case 'cpu_type':
GlobalVariable.cpu_type = "AMD" if request["value"] else "Intel"
case 'shortcutStruct':
GlobalVariable.shortcutStruct = request["value"]
case 'keyMap':
GlobalVariable.keyMap = request["value"]
case 'merge_min':
GlobalVariable.merge_min = int(request["value"])
case 'merge_max':
GlobalVariable.merge_max = int(request["value"])
case 'velocity_filter':
GlobalVariable.velocity_filter = int(request["value"])
case 'is_singular':
GlobalVariable.is_singular = request["value"]
case 'semitone_switch':
GlobalVariable.semitone_switch = request["value"]
case 'detail_switch':
GlobalVariable.detail_switch = request["value"]
case 'split_switch':
GlobalVariable.split_switch = request["value"]
case 'ai_token':
GlobalVariable.ai_token[request["value"]["type"]] = request["value"]["token"]
case 'translate_prompt':
GlobalVariable.translate_prompt = request["value"]
case 'duration_prompt':
GlobalVariable.duration_prompt = request["value"]
case 'sheld':
GlobalVariable.sheld = float(request["value"])
def get_config(request: dict):
configValue = eval("GlobalVariable." + request["name"])
return configValue
def favorite_music(request: dict):
src = os.path.join(getResourcesPath(request['type']), request['fileName'] + ".txt")
dst = os.path.join(getResourcesPath('myFavorite'), request['fileName'] + ".txt")
shutil.copy(src, dst, follow_symlinks=False)
def convert_sheet(request: dict):
convert_notes_to_delayed_format(request["fileName"], request["type"])
generatorSheetHtml(request["fileName"], list(map(lambda item: item['key'], GlobalVariable.music_sheet)))
GlobalVariable.music_sheet = []
return "ok"
def drop_file(request: dict):
file_name = request["fileName"]
if file_name is None:
return '不ok'
if request.get('suffix', None) is None:
drop_path = os.path.join(getResourcesPath(request['type']), file_name + '.txt')
else:
drop_path = os.path.join(getResourcesPath(request['type']), file_name + request['suffix'])
os.remove(drop_path)
return 'ok'
================================================
FILE: sky-music-server/windhide/utils/hook_util.py
================================================
import builtins
import platform
from windhide.static.global_variable import GlobalVariable
# 重定向 print 到空函数
def sout_null():
if GlobalVariable.isProd:
builtins.print = lambda *args, **kwargs: None
cpu_check()
return None
def cpu_check():
cpu_info = platform.processor()
if "Intel" in cpu_info:
GlobalVariable.cpu_type = 'Intel'
elif "AMD" in cpu_info:
GlobalVariable.cpu_type = 'AMD'
================================================
FILE: sky-music-server/windhide/utils/hwnd_utils.py
================================================
import os
import psutil
import win32gui
import win32process
from windhide.static.global_variable import GlobalVariable
def get_running_apps():
apps = []
# 获取当前系统运行的所有进程
for proc in psutil.process_iter(['pid', 'name']):
try:
pid = proc.info['pid']
name = proc.info['name']
exe_path = proc.exe()
if 'System' in name or 'svchost' in name:
continue
def enum_windows(hwnd, lParam):
if win32gui.IsWindowVisible(hwnd):
window_title = win32gui.GetWindowText(hwnd)
if window_title:
_, window_pid = win32process.GetWindowThreadProcessId(hwnd)
if window_pid == pid:
apps.append({
'title': window_title,
'exe_name': os.path.basename(exe_path),
'pid': pid,
'hwnd': hwnd
})
win32gui.EnumWindows(enum_windows, None)
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
pass
return apps
def get_running_apps_by_struct(check_struct):
if any(item == check_struct for item in get_running_apps()) is True:
GlobalVariable.hwnd_select_struct = check_struct
GlobalVariable.window["hWnd"] = check_struct["hwnd"]
GlobalVariable.is_custom_hwnd = True
else:
GlobalVariable.is_custom_hwnd = False
================================================
FILE: sky-music-server/windhide/utils/ocr_follow_util.py
================================================
import logging
import os
import threading
import keyboard
import time
from ultralytics import YOLO
from windhide.static.global_variable import GlobalVariable
from windhide.thread.follow_thread import startThread as follow_thread_demo
from windhide.utils.command_util import start_process
from windhide.utils.ocr_normal_utils import get_window_screenshot
from windhide.utils.path_util import getResourcesPath, convert_notes_to_delayed_format
if GlobalVariable.cpu_type == 'Intel':
from windhide.playRobot.intel_robot import mouse_move_to, key_press
else:
from windhide.playRobot.amd_robot import mouse_move_to, key_press
global_button_model = None
logging.getLogger("ultralytics").setLevel(logging.WARNING)
def set_next_sheet(request: dict):
try:
print(f"Setting follow sheet for file: {request['fileName']}")
convert_notes_to_delayed_format(request["fileName"], request["type"])
GlobalVariable.follow_sheet = list(map(lambda item: item['key'], GlobalVariable.music_sheet))
GlobalVariable.music_sheet = []
GlobalVariable.follow_music = request["fileName"]
except Exception as e:
print(f"Error in /followSheet: {str(e)}")
def get_next_sheet(request: dict):
if len(GlobalVariable.follow_sheet) == 0:
return ""
try:
if request["type"] == "ok":
sheet = GlobalVariable.follow_sheet[0]
GlobalVariable.nowClientKey = sheet
GlobalVariable.follow_sheet = GlobalVariable.follow_sheet[1:]
return sheet
else:
GlobalVariable.nowClientKey = GlobalVariable.follow_sheet[0]
return GlobalVariable.follow_sheet[0]
except IndexError:
print("空数组")
return ""
def load_key_model():
"""加载模型并保存在全局变量中"""
global global_button_model
if global_button_model is None:
button_model_path = os.path.join(getResourcesPath("systemTools"), "modelData", "check_key_model.pt")
global_button_model = YOLO(button_model_path) # 加载模型并保存在 global_friend_model 中
print("模型加载完成")
return global_button_model
def get_key_position(conf, threshold=10):
if GlobalVariable.window["key_position"] is not None:
if len(GlobalVariable.window["key_position"]) == 15 and not GlobalVariable.window["is_change"]:
return GlobalVariable.window["key_position"]
print("开始检测按键布局")
GlobalVariable.window["key_position"] = None
image = None
try:
bgr_image = get_window_screenshot()
height, width = bgr_image.shape[:2]
crop_width = int(width * 0.15) # 右边10%的宽度
crop_height = int(height * 0.1) # 底部 10% 的高度
image = bgr_image[: -crop_height, : -crop_width]
except Exception as e:
print(e)
model = load_key_model()
results = model(image, conf=conf) # 替换为你的图片路径
boxes = results[0].boxes # 检测到的所有框
xyxy = boxes.xyxy.cpu().numpy() # 获取每个框的绝对坐标 (x1, y1, x2, y2)
all_boxes = []
for box in xyxy:
x1, y1, x2, y2 = box
width = int(x2 - x1)
height = int(y2 - y1)
all_boxes.append({
"left": int(x1),
"top": int(y1),
"right": int(x2),
"bottom": int(y2),
"width": width,
"height": height,
"position_x": int(x1), # 添加 position_x
"position_y": int(y1) # 添加 position_y
})
result_dict = {}
for box in all_boxes:
y_key = box["top"] # 使用上边距作为分组依据
added = False
for key in result_dict:
if abs(key - y_key) <= threshold:
result_dict[key].append(box)
added = True
break
if not added:
result_dict[y_key] = [box]
sorted_result = {}
sorted_keys = sorted(result_dict)
for key in sorted_keys:
group = result_dict[key]
sorted_group = sorted(group, key=lambda b: b["position_x"])
sorted_result[key] = [{
"width": box["width"],
"height": box["height"],
"position_x": box["position_x"],
"position_y": box["position_y"]
} for box in sorted_group]
key_mapping = {
0: ["y", "u", "i", "o", "p"], # 第一组
1: ["h", "j", "k", "l", ";"], # 第二组
2: ["n", "m", ",", ".", "/"] # 第三组
}
final_result = {}
for idx, (group_key, group_boxes) in enumerate(zip(sorted_keys, sorted_result.values())):
if idx == 3:
break
# 避免越界
keys = key_mapping[idx] # 获取当前分组的键名列表
for key_name, box in zip(keys, group_boxes):
final_result[key_name] = box # 使用键名作为最终结果的 key
GlobalVariable.window["key_position"] = final_result
if len(final_result) == 15:
GlobalVariable.window["is_change"] = False
GlobalVariable.window["wait"] = False
print("final_result",final_result)
print("final_result len =>",len(final_result))
return final_result
def test_key_model_position(conf):
image = None
time.sleep(1)
try:
bgr_image = get_window_screenshot()
height, width = bgr_image.shape[:2]
crop_width = int(width * 0.15) # 右边10%的宽度
crop_height = int(height * 0.1) # 底部 10% 的高度
image = bgr_image[: -crop_height, : -crop_width]
except Exception as e:
print(e)
model = load_key_model()
results = model(image, conf=conf) # 替换为你的图片路径
results[0].show()
def open_follow():
if GlobalVariable.exit_flag == False:
keyboard.press('left alt')
time.sleep(0.05)
keyboard.press('left alt')
time.sleep(1.3)
start_process()
GlobalVariable.follow_thread = threading.Thread(target=follow_thread_demo)
GlobalVariable.follow_thread.daemon = True # 设置为守护线程,主线程退出时自动退出
GlobalVariable.follow_thread.start()
================================================
FILE: sky-music-server/windhide/utils/ocr_heart_utils.py
================================================
import os
import cv2
import numpy as np
import pyautogui
import win32con
import win32gui
from sklearn.cluster import DBSCAN
from ultralytics import YOLO
from windhide.static.global_variable import GlobalVariable
from windhide.utils.path_util import getResourcesPath
if GlobalVariable.cpu_type == 'AMD':
from windhide.playRobot.amd_robot import PostMessageW
else:
from windhide.playRobot.intel_robot import PostMessageW
# 全局变量,保存模型
global_friend_model = None
def load_model():
"""加载模型并保存在全局变量中"""
global global_friend_model
if global_friend_model is None:
model_path = os.path.join(getResourcesPath("systemTools"), "modelData", "friend_model.pt")
global_friend_model = YOLO(model_path) # 加载模型并保存在 global_friend_model 中
print("模型加载完成")
return global_friend_model
def merge_boxes(boxes, confs, max_distance=50):
"""
合并距离相近的识别框,合并后的框为置信度高的框。
:param boxes: 识别框的坐标 [x1, y1, x2, y2] (形状为 [num_boxes, 4])
:param confs: 每个框的置信度 (形状为 [num_boxes, ])
:param max_distance: 合并框时的最大距离,单位为像素
:return: 合并后的框的坐标和置信度
"""
# 使用 DBSCAN 聚类算法来找出距离相近的框
clustering = DBSCAN(eps=max_distance, min_samples=1, metric='euclidean').fit(boxes[:, :2]) # 只考虑框的中心点进行聚类
# 聚类标签
labels = clustering.labels_
# 合并结果
merged_boxes = []
merged_confs = []
for label in np.unique(labels):
# 获取同一类框的索引
indices = np.where(labels == label)[0]
# 获取该组框的坐标和置信度
group_boxes = boxes[indices]
group_confs = confs[indices]
# 选择置信度最高的框作为合并后的框
max_conf_index = np.argmax(group_confs)
# best_box = group_boxes[max_conf_index]
# 计算合并后的框,使用最小外接矩形
x1 = np.min(group_boxes[:, 0])
y1 = np.min(group_boxes[:, 1])
x2 = np.max(group_boxes[:, 2])
y2 = np.max(group_boxes[:, 3])
# 将合并后的框添加到结果列表
merged_boxes.append([x1, y1, x2, y2])
merged_confs.append(group_confs[max_conf_index]) # 置信度为该类中的最大置信度
return np.array(merged_boxes), np.array(merged_confs)
def get_friend_model_position(conf, isTest=False, max_distance=50):
# 加载已经训练好的模型
image = get_window_screenshot_friend()
model = load_model()
results = model(image, conf=conf) # 替换为你的图片路径
boxes = results[0].boxes # 这是检测到的所有框
xyxy = boxes.xyxy.cpu().numpy() # 获取每个框的绝对坐标 (x1, y1, x2, y2)
classes = boxes.cls.cpu().numpy() # 获取每个框对应的分类号
confs = boxes.conf.cpu().numpy() # 获取每个框的置信度
class_names = ["button", "friend"] # 获取所有类别的名称
merged_boxes, merged_confs = merge_boxes(xyxy, confs, max_distance)
# 获取截图的偏移量
screenshot_offset_x = 150
screenshot_offset_y = 110
# 创建结果字典,专门存储合并框的中心点坐标
result_dict = {
"button": [],
"friend": [],
}
# 填充结果字典:仅保存修正后的中心点坐标
for i, box in enumerate(merged_boxes):
class_id = int(classes[i]) # 获取当前合并框的类别ID
label = class_names[class_id] # 获取类别名称
x1, y1, x2, y2 = box
# 修正坐标:加上截图的偏移量
x1 += screenshot_offset_x
y1 += screenshot_offset_y
x2 += screenshot_offset_x
y2 += screenshot_offset_y
# 计算修正后的中心点坐标
center_x = (x1 + x2) / 2
center_y = (y1 + y2) / 2
result_dict[label].append((center_x, center_y))
# 如果需要可视化
image_with_boxes = image.copy()
for i, box in enumerate(merged_boxes):
x1, y1, x2, y2 = box
# 同样修正坐标
x1 += screenshot_offset_x
y1 += screenshot_offset_y
x2 += screenshot_offset_x
y2 += screenshot_offset_y
cv2.rectangle(image_with_boxes, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2)
cv2.putText(image_with_boxes, f"{merged_confs[i]:.2f}", (int(x1), int(y1) - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
if isTest:
results[0].show()
return result_dict
def get_window_screenshot_friend():
png_path = os.path.join(getResourcesPath("systemTools"), 'modelData', 'demoScheenshot', 'demo.png')
saturation_scale = 2.4 # >1增加饱和度,<1降低饱和度
PostMessageW(GlobalVariable.window["hWnd"], win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
client_rect = win32gui.GetClientRect(GlobalVariable.window["hWnd"])
# 将客户区矩形转换为屏幕坐标
client_x1, client_y1 = win32gui.ClientToScreen(GlobalVariable.window["hWnd"], (client_rect[0], client_rect[1]))
client_x2, client_y2 = win32gui.ClientToScreen(GlobalVariable.window["hWnd"], (client_rect[2], client_rect[3]))
# 截取窗口客户区范围内的图像
screenshot = pyautogui.screenshot(
region=(client_x1 + 150, client_y1 + 80, client_x2 - client_x1 - 240, client_y2 - client_y1 - 150))
_, s_channel, _ = cv2.split(cv2.cvtColor(np.array(screenshot), cv2.COLOR_BGR2HSV))
d = np.clip(s_channel * saturation_scale, 0, 220).astype(np.uint8)
cv2.imwrite(png_path, d)
image = cv2.imread(png_path)
return image
================================================
FILE: sky-music-server/windhide/utils/ocr_normal_utils.py
================================================
import ctypes
import cv2
import numpy as np
import pyautogui
import win32con
import win32gui
from windhide.static.global_variable import GlobalVariable
def resetGameFrame():
win32gui.ShowWindow(GlobalVariable.window["hWnd"], win32con.SW_RESTORE) # 先恢复窗口,以防最小化
rect = win32gui.GetWindowRect(GlobalVariable.window["hWnd"])
x, y = rect[0], rect[1] # 保持窗口的左上角位置不变
win32gui.MoveWindow(GlobalVariable.window["hWnd"], x, y, 1280, 720, True)
def get_window_screenshot():
"""获取指定窗口的截图"""
# 获取窗口位置和大小
# PostMessageW(GlobalVariable.window["hWnd"], win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
rect = win32gui.GetWindowRect(GlobalVariable.window["hWnd"])
x1, y1, x2, y2 = rect
# 截取窗口范围内的图像
screenshot = pyautogui.screenshot(region=(x1, y1, x2 - x1, y2 - y1))
return cv2.cvtColor(np.array(screenshot), cv2.COLOR_RGB2BGR)
def get_system_dpi():
hdc = ctypes.windll.user32.GetDC(0) # 获取屏幕设备上下文
dpi = ctypes.windll.gdi32.GetDeviceCaps(hdc, 88) # LOGPIXELSX = 88
ctypes.windll.user32.ReleaseDC(0, hdc) # 释放设备上下文
return dpi
def get_game_position():
hwnd = GlobalVariable.window["hWnd"]
# 获取窗口物理坐标
rect = win32gui.GetWindowRect(hwnd)
client_rect = win32gui.GetClientRect(hwnd)
# 获取窗口 DPI 缩放比例
border_x = (rect[2] - rect[0] - client_rect[2]) // 2
border_y = (rect[3] - rect[1] - client_rect[3] - border_x)
GlobalVariable.window_offset_x = border_x
GlobalVariable.window_offset_y = border_y
# 转换物理坐标为逻辑坐标,去掉边框和标题栏
x1 = int((rect[0] + border_x))
y1 = int((rect[1] + border_y))
x2 = int((rect[2] - border_x))
y2 = int((rect[3] - border_y))
return x1, y1, x2, y2
================================================
FILE: sky-music-server/windhide/utils/path_util.py
================================================
import json
import os
import re
import chardet
import plyer
from windhide.static.global_variable import GlobalVariable
def matchKey(key):
match = re.search(r'(Key-?\d+)', key)
return match.group(1)
def convert_notes_to_delayed_format(fileName, type):
# 优化了文件路径构建
file_path = os.path.join(getResourcesPath(type), fileName + ".txt")
with open(file_path, 'r', encoding=detect_encoding(file_path)) as file:
data = json.load(file)
song_notes = data[0].get("songNotes", [])
if not song_notes:
return []
result = []
combined_keys = "" # 按键累积
combined_time = None # 当前时间
for i, note in enumerate(song_notes):
current_time = note["time"]
key = note["key"]
# 处理新时间点
if current_time != combined_time:
if combined_keys:
next_time = song_notes[i]["time"] if i < len(song_notes) else current_time
delay = next_time - combined_time
result.append({"key": combined_keys, "delay": delay, "duration": note["duration"] if "duration" in note else 0})
combined_time = current_time
combined_keys = GlobalVariable.keyMap.get(matchKey(key), '') # 获取按键,默认空字符串
else:
combined_keys += GlobalVariable.keyMap.get(matchKey(key), '') # 如果时间相同,合并按键
# 处理最后的累积按键
if combined_keys:
result.append({"key": combined_keys, "delay": 0, 'duration':song_notes[len(song_notes) - 1]['duration'] if "duration" in note else 0}) # 最后条目的延迟为0
GlobalVariable.music_sheet = result
def convert_json_to_play(song_notes):
result = []
combined_keys = "" # 按键累积
combined_time = None # 当前时间
for i, note in enumerate(song_notes):
current_time = note["time"]
key = note["key"]
# 处理新时间点
if current_time != combined_time:
if combined_keys:
next_time = song_notes[i]["time"] if i < len(song_notes) else current_time
delay = next_time - combined_time
result.append({"key": combined_keys, "delay": delay, "duration": note["duration"]})
combined_time = current_time
combined_keys = GlobalVariable.keyMap.get(matchKey(key), '') # 获取按键,默认空字符串
else:
combined_keys += GlobalVariable.keyMap.get(matchKey(key), '') # 如果时间相同,合并按键
if combined_keys:
result.append({"key": combined_keys, "delay": 0, "duration": song_notes[len(song_notes) - 1]['duration']}) # 最后条目的延迟为0
GlobalVariable.music_sheet = result
def getResourcesPath(file):
nowPath = os.path.dirname(os.path.abspath(__file__))
resources_path = os.path.dirname(os.path.dirname(nowPath))
target_subpath = os.path.join("backend_dist", "sky_music_server")
resources_path = os.path.abspath(
os.path.join(resources_path, "..", "..", "..")) if target_subpath in resources_path else os.path.abspath(
os.path.join(resources_path, ".."))
if file is None:
return os.path.join(resources_path, 'resources')
else:
return os.path.join(resources_path, 'resources', file)
def getTypeMusicList(type, searchStr=None):
# 获取资源目录路径
resources_dir = os.path.join(getResourcesPath(None), type)
# 获取目录下所有文件名
file_names = [
file for file in os.listdir(resources_dir)
if os.path.isfile(os.path.join(resources_dir, file))
and file != ".keep"
and not re.search(r"-#\d+(?=\.\w+)?$", file) # 排除匹配 -#数字 的文件
]
# 如果 searchStr 不为空,过滤包含 searchStr 的文件名,忽略大小写
if searchStr and searchStr.strip():
file_names = [file for file in file_names if searchStr.lower() in file.lower()]
# 构建返回的音乐列表,包含文件名和总时长
music_list = []
for file in file_names:
music_list.append({
"name": re.sub(r"-#(\d+)(?=\.\w+)?", "", file.replace(".txt", "")),
"total_duration": format_time(int(re.search(r"-#(\d+)(?=\.\w+)?", file).group(1) if re.search(r"-#(\d+)(?=\.\w+)?", file) else "0")),
"truthName": f"{file.replace('.txt', '')}"
})
return music_list
def process_sheet_rename_time(isImportOrTranslate = False):
plyer.notification.notify(
app_name='小星弹琴软件',
app_icon=os.path.join(getResourcesPath("systemTools"), "icon.ico"),
title='开始同步乐谱时长操作',
message='时间可能会比较长,耐心等待,执行过程中可能会影响正常演奏。',
timeout=1
)
if isImportOrTranslate:
resource_dirs = [
os.path.join(getResourcesPath(None), "myImport"),
os.path.join(getResourcesPath(None), "myTranslate"),
]
else:
resource_dirs = [
os.path.join(getResourcesPath(None), "systemMusic"),
os.path.join(getResourcesPath(None), "myTranslate"),
os.path.join(getResourcesPath(None), "myImport"),
os.path.join(getResourcesPath(None), "myFavorite")
]
all_file_paths = []
for resources_dir in resource_dirs:
if not os.path.exists(resources_dir):
continue
file_paths = [
os.path.abspath(os.path.join(resources_dir, file))
for file in os.listdir(resources_dir)
if os.path.isfile(os.path.join(resources_dir, file)) and file != ".keep"
]
all_file_paths.extend(file_paths)
for file_path in all_file_paths:
if re.search(r"-#(\d+)(?=\.\w+)?", file_path):
continue
try:
with open(file_path, 'r', encoding=detect_encoding(file_path)) as file:
data = json.load(file)
song_notes = data[0].get("songNotes", [])
if not song_notes:
continue
sumTime = int(song_notes[-1]["time"]) + int(song_notes[-1].get("duration", 0))
directory, filename = os.path.split(file_path)
name, ext = os.path.splitext(filename)
new_filename = f"{name}-#{sumTime}{ext}"
new_file_path = os.path.join(directory, new_filename)
# 重命名文件
os.rename(file_path, new_file_path)
except Exception as e:
continue
plyer.notification.notify(
app_name='小星弹琴软件',
app_icon=os.path.join(getResourcesPath("systemTools"), "icon.ico"),
title='开始同步乐谱时长操作',
message='操作完成',
timeout=1
)
return "ok"
def format_time(milliseconds):
"""
将毫秒数格式化为时分秒格式(HH:MM:SS 或 MM:SS)
"""
seconds = milliseconds / 1000
hours, remainder = divmod(seconds, 3600)
minutes, seconds = divmod(remainder, 60)
if hours > 0:
return f"{int(hours):02}:{int(minutes):02}:{int(seconds):02}"
else:
return f"{int(minutes):02}:{int(seconds):02}"
def detect_encoding(file_path):
with open(file_path, 'rb') as file:
raw_data = file.read(32000) # 读取文件的前32KB进行编码检测
return chardet.detect(raw_data)['encoding'] # 直接返回编码
================================================
FILE: sky-music-server/windhide/utils/play_util.py
================================================
import chardet
from windhide.playRobot import intel_robot, amd_robot
from windhide.static.global_variable import GlobalVariable
def detect_encoding(file_path):
with open(file_path, 'rb') as file:
raw_data = file.read(32000) # 读取文件的前32KB进行编码检测
return chardet.detect(raw_data)['encoding'] # 直接返回编码
def start(request: dict):
GlobalVariable.task_queue.put(request) # 将请求放入队列
def pause():
try:
print("Pausing music")
match GlobalVariable.cpu_type:
case "Intel":
intel_robot.pause()
case "AMD":
amd_robot.pause()
except Exception as e:
print(f"Error in /pause: {str(e)}")
def stop():
try:
print("Stopping music")
GlobalVariable.now_play_music = "没有正在播放的歌曲哦"
match GlobalVariable.cpu_type:
case "Intel":
intel_robot.stop()
case "AMD":
amd_robot.stop()
except Exception as e:
print(f"Error in /stop: {str(e)}")
def resume():
try:
print("Resuming music")
match GlobalVariable.cpu_type:
case "Intel":
intel_robot.resume()
case "AMD":
amd_robot.resume()
except Exception as e:
print(f"Error in /resume: {str(e)}")
================================================
FILE: sky-music-server/windhide/utils/sheet_decrypt_util.py
================================================
import json
def decrypt_sheet(data):
# 加密密钥和签名(来自 CEN.cs)
KEY = "TB,R&Q}-ULFXF7={nU7v?fy#Khr9Mhuu"
SIGNATURE = "ztB_kaFeQe/wa8Kq{r_jz!r=P])hQL(f"
sheet_data = data[0]
# 获取加密的 songNotes
encrypted_notes = sheet_data.get('songNotes', [])
if not encrypted_notes:
print("错误:songNotes 为空")
return
print(f"找到 {len(encrypted_notes)} 个加密字符")
print("开始解密...")
# 解密算法(基于 CEN.ToString)
decrypted_chars = []
for i, short_val in enumerate(encrypted_notes):
# decrypted = (short - key[i % keyLength] + 100)
key_char = KEY[i % len(KEY)]
decrypted_char = chr(short_val - ord(key_char) + 100)
decrypted_chars.append(decrypted_char)
# 组合解密后的字符串
decrypted_str = ''.join(decrypted_chars)
# 移除尾部签名
decrypted_str = decrypted_str.replace(SIGNATURE, '')
print(f"解密后字符串长度: {len(decrypted_str)}")
data[0]['songNotes'] = json.loads(decrypted_str)
return data
================================================
FILE: sky-music-web/.editorconfig
================================================
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
================================================
FILE: sky-music-web/.eslintignore
================================================
node_modules
dist
out
.gitignore
================================================
FILE: sky-music-web/.eslintrc.cjs
================================================
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
extends: [
'eslint:recommended',
'plugin:vue/vue3-recommended',
'@electron-toolkit',
'@electron-toolkit/eslint-config-ts/eslint-recommended',
'@vue/eslint-config-typescript/recommended',
'@vue/eslint-config-prettier'
],
rules: {
'vue/require-default-prop': 'off',
'vue/multi-word-component-names': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'no-debugger': 'off',
}
}
================================================
FILE: sky-music-web/.gitignore
================================================
node_modules
dist
out
*.txt
.DS_Store
*.log*
================================================
FILE: sky-music-web/.npmrc
================================================
electron_mirror=https://npmmirror.com/mirrors/electron/
electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/
================================================
FILE: sky-music-web/.prettierignore
================================================
out
dist
pnpm-lock.yaml
LICENSE.md
tsconfig.json
tsconfig.*.json
================================================
FILE: sky-music-web/.prettierrc.yaml
================================================
singleQuote: true
semi: false
printWidth: 100
trailingComma: none
================================================
FILE: sky-music-web/README.md
================================================
# electron-app
An Electron application with Vue and TypeScript
## Recommended IDE Setup
- [VSCode](https://code.visualstudio.com/) + [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin)
## Project Setup
### Install
```bash
$ yarn
```
### Development
```bash
$ yarn dev
```
### Build
```bash
# For windows
$ yarn build:win
# For macOS
$ yarn build:mac
# For Linux
$ yarn build:linux
```
================================================
FILE: sky-music-web/build/entitlements.mac.plist
================================================
com.apple.security.cs.allow-jit
com.apple.security.cs.allow-unsigned-executable-memory
com.apple.security.cs.allow-dyld-environment-variables
================================================
FILE: sky-music-web/dev-app-update.yml
================================================
provider: generic
url: https://example.com/auto-updates
updaterCacheDirName: electron-app-updater
================================================
FILE: sky-music-web/electron-builder.yml
================================================
appId: 星星弹琴
productName: Sky_Music
directories:
buildResources: build
files:
- '!**/.vscode/*'
- '!src/*'
- '!electron.vite.config.{js,ts,mjs,cjs}'
- '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
- '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}'
- '!**/backend_dist/**'
- '!**/template-resources/**'
- '!**/ffmpeg.exe'
- '!*.keep'
win:
executableName: Sky_Music
icon: build/icon.ico
requestedExecutionLevel: requireAdministrator
nsis:
artifactName: 小星弹琴软件v${version}_x64_windows.${ext}
shortcutName: "小星弹琴软件"
uninstallDisplayName: '卸载 Uninstaller'
perMachine: true
installerIcon: build/icon.ico
uninstallerIcon: build/icon.ico
installerHeaderIcon: build/icon.ico
oneClick: false # 允许自定义安装目录和其他设置
allowToChangeInstallationDirectory: true # 允许用户选择安装目录
createDesktopShortcut: true # 允许创建桌面快捷方式
createStartMenuShortcut: true # 允许创建开始菜单快捷方式
deleteAppDataOnUninstall: true # 卸载时删除软件缓存数据
extraFiles:
- from: ".\\backend_dist"
to: ".\\backend_dist"
- from: "..\\template-resources"
to: ".\\resources"
npmRebuild: false
publish:
provider: generic
url: https://example.com/auto-updates
electronDownload:
mirror: https://npmmirror.com/mirrors/electron/
================================================
FILE: sky-music-web/electron.vite.config.ts
================================================
import { resolve } from 'path'
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
main: {
plugins: [externalizeDepsPlugin()]
},
preload: {
plugins: [externalizeDepsPlugin()]
},
renderer: {
resolve: {
alias: {
'@renderer': resolve('src/renderer/src')
}
},
plugins: [vue()]
}
})
================================================
FILE: sky-music-web/package.json
================================================
{
"name": "sky-music-web",
"version": "v2.6.6",
"description": "WindHide",
"main": "./out/main/index.js",
"author": "WindHide",
"homepage": "https://github.com/windhide",
"scripts": {
"format": "prettier --write .",
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts,.vue --fix",
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
"typecheck:web": "vue-tsc --noEmit -p tsconfig.web.json --composite false",
"typecheck": "npm run typecheck:node && npm run typecheck:web",
"start": "electron-vite preview",
"dev": "electron-vite dev",
"build": "npm run typecheck && electron-vite build",
"build:win": "npm run build && electron-builder --win"
},
"dependencies": {
"@electron-toolkit/preload": "^3.0.0",
"@electron-toolkit/utils": "^3.0.0",
"@vicons/fluent": "^0.13.0",
"@vicons/ionicons5": "^0.12.0",
"electron-store": "^8.0.0",
"electron-updater": "^6.1.7",
"hotkeys-js": "^3.13.9",
"html2canvas": "^1.4.1",
"iconv-lite": "^0.6.3",
"lodash-es": "^4.17.21",
"naive-ui": "^2.41.0",
"node-cmd": "^5.0.0",
"p-limit": "^4.0.0",
"vfonts": "^0.0.3",
"vue-i18n": "^11.1.3",
"vue-router": "^4.0.3",
"vuex": "^4.0.0"
},
"devDependencies": {
"@electron-toolkit/eslint-config": "^1.0.2",
"@electron-toolkit/eslint-config-ts": "^2.0.0",
"@electron-toolkit/tsconfig": "^1.0.1",
"@rushstack/eslint-patch": "^1.10.3",
"@types/node": "^20.14.8",
"@vitejs/plugin-vue": "^5.0.5",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^13.0.0",
"electron": "^31.0.2",
"electron-builder": "^24.13.3",
"electron-vite": "^2.3.0",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.26.0",
"prettier": "^3.3.2",
"typescript": "^5.5.2",
"vite": "^5.3.1",
"vue": "^3.4.30",
"vue-tsc": "^2.0.22"
}
}
================================================
FILE: sky-music-web/src/main/index.ts
================================================
import { app, shell, BrowserWindow, ipcMain, screen, Notification, powerSaveBlocker } from 'electron'
import { join } from 'path'
import { electronApp, is } from '@electron-toolkit/utils'
import icon from '../../build/icon.png?asset'
import { spawn, execSync } from 'child_process'
import Store from 'electron-store';
Store.initRenderer()
const path = require('path')
const fs = require('fs');
const iconv = require('iconv-lite'); // 用于支持多种编码格式
const elStore = new Store()
const MAX_CONCURRENT_COPIES = 20; // 限制最大并行任务数
let limit;
(async () => {
const { default: pLimit } = await import('p-limit');
limit = pLimit(MAX_CONCURRENT_COPIES);
})();
let mainWindow: BrowserWindow | null = null;
app.commandLine.appendSwitch('no-sandbox');
// 启动电源保护
powerSaveBlocker.start('prevent-display-sleep');
powerSaveBlocker.start('prevent-app-suspension');
app.commandLine.appendSwitch('enable-gpu-rasterization'); // 强制 GPU 光栅化
app.commandLine.appendSwitch('ignore-gpu-blacklist'); // 忽略 Electron GPU 黑名单
app.commandLine.appendSwitch('force-color-profile', 'srgb'); // 统一颜色配置
app.commandLine.appendSwitch('disable-background-timer-throttling'); // 关闭后台定时器节流
// 允许 GPU 但在必要时自动回退到软件渲染
app.commandLine.appendSwitch('disable-software-rasterizer');
const syncFolders = ["myImport", "myTranslate", "myFavorite"]
function createWindow(): void {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 800,
height: 774,
resizable: false,
autoHideMenuBar: true,
frame: false,
transparent: true,
alwaysOnTop: false,
icon,
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false,
nodeIntegration: false,
contextIsolation: true
}
})
mainWindow.on('ready-to-show', () => {
mainWindow?.show()
})
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
// Menu.setApplicationMenu(null)
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
}
// 窗口拖动逻辑
let isMousePressed = false
let offsetX = 0
let offsetY = 0
let nowHeight = 774
let nowWidth = 800
ipcMain.on('mousedown', (_event, { }) => {
console.log(_event)
const cursorPoint = screen.getCursorScreenPoint()
let bounds:any = null
bounds = mainWindow?.getBounds()
if (bounds) {
isMousePressed = true
offsetX = cursorPoint.x - bounds.x // 精确鼠标偏移量
offsetY = cursorPoint.y - bounds.y
}
})
ipcMain.on('mousemove', (event) => {
console.log(event)
if (isMousePressed && mainWindow) {
const cursorPoint = screen.getCursorScreenPoint()
// 实时更新窗口位置
mainWindow.setBounds({
x: cursorPoint.x - offsetX,
y: cursorPoint.y - offsetY,
width: nowWidth,
height: nowHeight
})
}
})
ipcMain.on('mouseup', () => {
isMousePressed = false
})
ipcMain.on('window-min', (event) => {
console.log(event)
mainWindow?.minimize()
})
ipcMain.on('window-close', (event) => {
console.log(event)
ipcMain.removeAllListeners('mousedown')
ipcMain.removeAllListeners('mousemove')
ipcMain.removeAllListeners('mouseup')
mainWindow?.close()
app.quit()
})
ipcMain.on('set-always-on-top', (event) => {
console.log(event)
mainWindow?.setAlwaysOnTop(!mainWindow?.isAlwaysOnTop())
})
ipcMain.on('send_system_notification', (_event, title:string, body: string) => {
const notification = new Notification({title,body});
notification.show(); // 显示通知
setTimeout(()=>{
notification.close()
},1500)
})
ipcMain.on('window_size', (_event, height:number, width: number) => {
console.log(_event)
let bounds:any = null
bounds = mainWindow?.getBounds()
nowHeight = height === 0 ? 774 : height
nowWidth = width === 0 ? 800 : width
mainWindow?.setBounds({
x: bounds.x,
y: bounds.y,
width:nowWidth,
height:nowHeight
})
})
ipcMain.handle('read-file', async (_event, filePath:string, needData: boolean) => {
try {
let fileContent = await fs.promises.readFile(filePath, 'utf8');
const encodingList = ['utf8', 'utf16le', 'gbk']; // 支持的编码
for (const encoding of encodingList) {
try {
const buffer = await fs.promises.readFile(filePath);
fileContent = iconv.decode(buffer, encoding);
// 测试解析(假设文件是 JSON 格式)
JSON.parse(fileContent); // 尝试解析
break; // 成功解析则退出循环
} catch (err) {
// 如果解析失败,继续尝试下一个编码
fileContent = undefined;
}
}
if (needData){
return JSON.parse(fileContent)
}
return fileContent.includes("songNotes")
} catch (err) {
console.error('Error loading JSON:', err);
return false;
}
});
ipcMain.handle('getVersion', (_event) => {
return app.getVersion()
});
// console.log('当前缓存存储位置',app.getPath('userData'))
ipcMain.on('setElStore', (_event, key, value) => {
elStore.set(key, value)
})
ipcMain.on('getElStore', (event, key) => {
const result = elStore.get(key)
event.returnValue = result
})
ipcMain.on('sync_sheet_2_el', async (_event,) =>{
let cachePath = elStore.path.replaceAll("\\config.json", "");
for (const folder of syncFolders) {
fetch("http://127.0.0.1:9899/path", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"type": folder
}),
}).then(response => response.json())
.then(data =>{
copyFolderIncremental(data, path.join(cachePath, folder));
})
.catch(error => console.error(error))
}
})
ipcMain.on('sync_el_2_sheet', async (_event,) =>{
let cachePath = elStore.path.replaceAll("\\config.json", "");
for (const folder of syncFolders) {
fetch("http://127.0.0.1:9899/path", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"type": folder
}),
}).then(response => response.json()).then(data =>{
copyFolderIncremental(path.join(cachePath, folder), data);
}).catch(error => console.error(error))
}
})
async function copyFolderIncremental(source, target) {
try {
target = path.resolve(target);
source = path.resolve(source);
console.log("target=>", target);
console.log("source=>", source);
// 检查源文件夹是否存在
let sourceExists = true;
try {
await fs.promises.access(source, fs.constants.F_OK);
} catch (err) {
sourceExists = false;
await fs.promises.mkdir(source, { recursive: true });
}
// 检查目标文件夹是否存在
let targetExists = true;
try {
await fs.promises.access(target, fs.constants.F_OK);
} catch (err) {
targetExists = false;
}
// 如果源文件夹不存在且目标文件夹存在,删除目标文件夹
if (!sourceExists && targetExists) {
await fs.promises.rm(target, { recursive: true, force: true });
}
// 确保目标文件夹存在
await fs.promises.mkdir(target, { recursive: true });
// 获取源文件夹中的文件列表
const sourceFiles = await fs.promises.readdir(source);
const sourceFilesSet = new Set(sourceFiles);
// 如果目标文件夹存在,检查需要删除的文件
if (targetExists) {
const targetFiles = await fs.promises.readdir(target);
const deletePromises = targetFiles
.filter(file => !sourceFilesSet.has(file))
.map(file => {
const targetPath = path.join(target, file);
return fs.promises.rm(targetPath, { recursive: true, force: true });
});
await Promise.all(deletePromises);
}
// 复制或更新文件
const copyTasks = sourceFiles.map(file => limit(async () => {
try {
const sourcePath = path.join(source, file);
const targetPath = path.join(target, file);
const sourceStats = await fs.promises.stat(sourcePath);
if (sourceStats.isFile()) {
let shouldCopy = true;
try {
const targetStats = await fs.promises.stat(targetPath);
shouldCopy = targetStats.mtimeMs < sourceStats.mtimeMs;
} catch (err) {
// 目标文件不存在,需要复制
}
if (shouldCopy) {
await fs.promises.copyFile(sourcePath, targetPath);
}
} else if (sourceStats.isDirectory()) {
await copyFolderIncremental(sourcePath, targetPath);
}
} catch (err) {
console.error(`Error processing file ${file}:`, err);
}
}));
await Promise.all(copyTasks);
} catch (err) {
console.error("Error copying folder:", err);
console.log("Continuing with other operations...");
}
}
}
app.on('window-all-closed', () => {
app.quit();
})
function launchBackend() {
const args = ['--prod']
const exeName = 'sky_music_server.exe'
let runPath = __dirname.replace("resources\\app.asar\\out\\main","")
const exePath = path.join(runPath, 'backend_dist/sky_music_server/sky_music_server.exe')
if (!fs.existsSync(exePath)) {
console.error('[server] server File not found:', exePath)
return
}
if (isBackendRunning(exeName)) {
console.log('[server] is running, do Nothiong')
return
}
console.log('[server] starting:', exePath)
const subprocess = spawn(exePath, args, {
detached: true,
stdio: 'ignore',
windowsHide: true
})
subprocess.unref() // 让它在主进程退出后仍能运行
}
function isBackendRunning(processName: string): boolean {
try {
const stdout = execSync('tasklist', { encoding: 'utf-8' })
return stdout.toLowerCase().includes(processName.toLowerCase())
} catch (err) {
console.error('[check server] faild:', err)
return false
}
}
app.whenReady().then(() => {
electronApp.setAppUserModelId('com.windhide')
createWindow()
launchBackend()
})
================================================
FILE: sky-music-web/src/preload/index.d.ts
================================================
import { ElectronAPI } from '@electron-toolkit/preload'
declare global {
interface Window {
api: {
setAlwaysOnTop: () => void;
close: () => void;
mini: () => void;
async readFile: (filePath:string, needData: boolean) => any;
system_notification: (title,body) => void;
getVersion: () => any;
window_size: (height:number, width: number) => void;
syncElToSheetFiles
sync_el_2_sheet:() => void;
sync_sheet_2_el:() => void;
};
electron:{
onMouseDown: (x, y) => void;
onMouseMove: (x, y) => void;
onMouseUp: ()=> void;
}
}
}
================================================
FILE: sky-music-web/src/preload/index.ts
================================================
import { contextBridge, ipcRenderer } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
// Custom APIs for renderer
const api = {}
// @ts-ignore (define in dts)
const elStore = {}
// Use `contextBridge` APIs to expose Electron APIs to
// renderer only if context isolation is enabled, otherwise
// just add to the DOM global.
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('api', {
setAlwaysOnTop: () => {
ipcRenderer.send('set-always-on-top');
},
close: () => {
ipcRenderer.send('window-close');
},
mini: () => {
ipcRenderer.send('window-min');
},
readFile: async (filePath: string, needData: boolean) => {
return await ipcRenderer.invoke('read-file', filePath, needData);
},
getVersion: () => {
return ipcRenderer.invoke('getVersion');
},
system_notification: async (title,body) => {
ipcRenderer.send('send_system_notification', title, body);
},
window_size: (height:number, width: number) => {
ipcRenderer.send('window_size', height, width);
},
sync_el_2_sheet:() => {
ipcRenderer.send('sync_el_2_sheet');
},
sync_sheet_2_el:() => {
ipcRenderer.send('sync_sheet_2_el');
}
});// 用于向主进程发送拖动事件
contextBridge.exposeInMainWorld('electron', {
onMouseDown: (x, y) => ipcRenderer.send('mousedown', { x, y }),
onMouseMove: (x, y) => ipcRenderer.send('mousemove', { x, y }),
onMouseUp: () => ipcRenderer.send('mouseup')
})
// 用于数据持久化
contextBridge.exposeInMainWorld('elStore', {
setElStore:(key, value)=>{
ipcRenderer.send('setElStore', key, value);
},
getElStore:(key)=>{
return ipcRenderer.sendSync('getElStore', key);
}
})
} catch (error) {
console.error(error)
}
} else {
// @ts-ignore (define in dts)
window.electron = electronAPI
// @ts-ignore (define in dts)
window.api = api
// @ts-ignore (define in dts)
window.elStore = elStore
}
================================================
FILE: sky-music-web/src/renderer/index.html
================================================
Sky_Music
================================================
FILE: sky-music-web/src/renderer/src/App.vue
================================================
{{ t('main.transparency') }}
{{ t('main.color') }}
{{ t('main.compatible') }}
{{ t('main.backend') }}
{{ t('main.queue') }}
{{ t('main.cut') }}
{{ t('main.multi_core') }}
{{ t('main.single_core') }}
{{ t('main.loading') }}
================================================
FILE: sky-music-web/src/renderer/src/component/svg/cr.vue
================================================
================================================
FILE: sky-music-web/src/renderer/src/component/svg/dm.vue
================================================
================================================
FILE: sky-music-web/src/renderer/src/component/svg/dmcr.vue
================================================
================================================
FILE: sky-music-web/src/renderer/src/env.d.ts
================================================
///
declare module '*.vue' {
import type { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}
================================================
FILE: sky-music-web/src/renderer/src/i18n/index.ts
================================================
import { createI18n } from 'vue-i18n'
import zh_cn from './locales/zh-cn.json'
import zh_classical from './locales/zh-classical.json'
import zh_tw from './locales/zh-tw.json'
import en from './locales/en.json'
import jp from './locales/jp.json'
import ko from './locales/ko.json'
// 从localStorage获取保存的语言设置,如果没有则使用默认值'zh_cn'
const savedLanguage = localStorage.getItem('sky-music-language') || 'zh_cn'
const i18n = createI18n({
legacy: false,
locale: savedLanguage,
fallbackLocale: 'zh_cn',
messages: {
zh_cn,
zh_classical,
zh_tw,
en,
jp,
ko
}
})
export default i18n
================================================
FILE: sky-music-web/src/renderer/src/i18n/locales/en.json
================================================
{
"columns": {
"name": "Song title",
"operation": "operate",
"total_duration": "Duration"
},
"home": {
"head_text": "If you think it's easy to use, you can enjoy a cup of coffee☕",
"patterns0": "completely free",
"patterns1": "Being cheated",
"patterns2": "Coffee☕",
"qq": "Q group",
"qq_sb": "channel",
"text": "Welcome to use. This software is completely free",
"tutorial": "tutorial",
"update": "It's the latest version",
"update_error": "Update detection error",
"update_info": "Can't detect the new version of the software, try to repair the network"
},
"hwnd_handle": {
"columns": {
"exe_name": "Process name",
"hwnd": "Handle",
"operation": "Select",
"pid": "PID",
"title": "Software title"
},
"head_text": "Here is the target sent by the background. No games except light encounters have been tested and will not be repaired.",
"head_text2": "Because of this function, other games have been blocked, don’t look for me. \nDepend on the game's anti-cheating mechanism",
"now_hwnd": "Now handle >",
"patterns0": "Change the destination sent in the background",
"patterns1": "Light encounter",
"patterns2": "Not tested",
"patterns3": "Won't",
"patterns4": "Don't look for me",
"patterns5": "lead to",
"patterns6": "title",
"patterns7": "Anti-cheating mechanism",
"refresh": "Refresh the list",
"reset": "Reset",
"reset_sky": "Reset to light encounter window"
},
"kube": {
"Streng_filter": "Force filtering (less than milliseconds)",
"all_t": "Full sound conversion only",
"chose_music": "Select",
"dual": "Even keys",
"dynamic_merge": "Dynamic merge threshold range division",
"get_fail": "Failed to get {value}",
"half_t": "Convert semitone",
"odd": "Singular keys",
"other_key": "Super 3 tone change",
"range_key": "Range conversion only",
"scale": {
"scale2": "Sky range_2 sets",
"scale3": "3 sets",
"scale4": "4 sets",
"scale5": "5 sets",
"scale6": "6 sets"
},
"star_transfer": "conversion",
"title": "Convert songs",
"transfer_fail": "Conversion failed",
"transfer_progress": "Conversion progress",
"transfer_success": "Conversion successfully"
},
"magic_tools": {
"buttons": {
"abaaba": "Scream",
"autoFire": "Automatic ignition",
"check": "examine",
"developer": "Developer customization",
"heart_fire": "Heart fire",
"key": "button",
"shutdown": "Terminate thread"
},
"fileButtons": {
"myFavorite": "love",
"myImport": "Imported",
"myTranslate": "Converted",
"systemMusic": "System music",
"translateMID": "MIDI"
},
"head_text": "Here is the beta function, please use it with caution, no memory modification is involved.",
"head_text2": "This function is for learning and communication only and is strictly prohibited for commercial purposes. Please delete it within 24 hours",
"head_text3": "🚫Simulator players are prohibited from using all the following functions🚫",
"messeage": {
"start": "Now start automatically clicking on Heart Fire",
"stop": "termination! \n! \n! \n! \n!"
},
"patterns0": "Use with caution 🌶",
"patterns1": "No memory modification involved 🌶",
"patterns2": "This function is for learning and communication only and is strictly prohibited for commercial purposes. Please delete it within 24 hours",
"patterns3": "🚫Simulator players are prohibited from using all the following functions🚫"
},
"main": {
"backend": "Background mode",
"color": "Background color",
"compatible": "Compatibility mode",
"cut": "Cut-in mode",
"loading": "🍉Application is loading, please wait~",
"menu": {
"home": "introduce",
"hwndHandle": "Handle",
"kube": "TranSheet",
"magicTools": "Developer",
"music": "Music",
"musicEdit": "Edit",
"setting": "Music Set",
"shortcut": "Shortcut",
"tutorial": "Follow",
"aisetting": "AI setting"
},
"multi_core": "Multi-core mode",
"queue": "Queue mode",
"single_core": "Single-core mode",
"transparency": "transparency"
},
"messeage": {
"choose_plz": "Choose a song and play it with the handsome guy",
"progress_fail": "Failed to obtain progress",
"remove_fail": "Deletion failed",
"remove_success": "Removal successfully",
"double_click": "Double-click the song to play!",
"msg1": "Only allow start",
"msg10": "The maximum sustain sound is 2",
"msg11": "Sustain 0.01",
"msg12": "The lowest sustain sound is 0",
"msg13": "Sustain -0.01",
"msg14": "The maximum interval is 2",
"msg15": "Interval 0.01",
"msg16": "The minimum interval is 0",
"msg17": "Interval-0.01",
"msg18": "The maximum speed is 5",
"msg19": "Speed 0.1",
"msg2": "Choose a song and play it. Beautiful",
"msg20": "The lowest speed is 0.1",
"msg21": "Speed -0.1",
"msg3": "start",
"msg4": "continue",
"msg5": "Only allow for continued",
"msg6": "pause",
"msg7": "Pause is allowed only when it is playing",
"msg8": "stop",
"msg9": "Next",
"no_import": "Import failed",
"no_music": "There are no songs playing",
"no_now": "Nothing, kid, really nothing 😭, try another category",
"ok_import": "Complete the import",
"order_ok": "The song on the list is finished",
"sheet": "Score👉",
"unknow_music": "Unknown songs"
},
"music_edit": {
"dialog": {
"content": "Are you sure you want to clear all scores in the current work area? \nUnsaved changes will be lost.",
"content2": "Are you sure you want to leave the score editing page? \nUnsaved changes will be lost.",
"negativeText": "If I don't clear, I'll save it first",
"negativeText2": "I'll save it first if I don't leave",
"positiveText": "Just clear it, clear it",
"positiveText2": "Just leave",
"title": "A warm tip from the developer ⭐"
},
"sheet": {
"error": {
"data": "Error in spectral data format",
"sheet_data": "Spectral Note Data Format Error"
},
"load_fail": "Spectrum loading failed",
"load_success": "The score loaded successfully"
},
"tips": {
"afer_add_column": "Insert a new column after the current highlighted column",
"clear_sheet": "Clear the score of the current workspace",
"copy_now_to_end": "Copy a copy to the end of the current highlighted column",
"copy_now_to_head": "Copy a copy to the beginning of the current highlighted column",
"copy_now_to_next": "Copy a copy of the current highlighted column to the next column",
"copy_now_to_pre": "Copy a copy of the current highlighted column to the previous column",
"delete_now_column": "Delete the current highlighted column",
"next_column": "Next column",
"pause": "pause",
"play": "Play",
"play_now_column": "The highlighted column is in the game and press",
"pre_column": "Previous column",
"save_sheet": "Save the spectrum",
"select_in_music": "Select songs from the song list",
"tip1": "The long press interval needs to be less than the column and wait for the delay, and it has been automatically adjusted (currently)",
"tip2": "Copy to the beginning",
"tip3": "Copyed to previous column",
"tip4": "Copyed to the next column",
"tip5": "Copyed to the last column",
"tip6": "The long press interval needs to be less than the waiting interval, and the waiting delay after the column has been automatically increased (currently)",
"tip7": "The waiting delay after column cannot be less than 10, and has been automatically adjusted (new globally)",
"tip8": "The waiting delay after long pressing is less than the column, 10ms, has been automatically adjusted (new globally)",
"upend": "Put it upside down",
"upload_sheet": "Upload score edit"
},
"title": {
"add_new_after_column": "Waiting delay after added column",
"add_new_count_column": "Number of new columns",
"add_new_druation_column": "New long press interval",
"change_send_key_in_game": "Send keys to the game via column",
"columnAfterDuration": "Waiting delay after column",
"columnDownDuration": "long press column",
"func_area_tip": "Ribbon prompts",
"global_after": "Delay after glo column",
"global_down": "Long press area",
"set": "set up",
"sheet_area_color": "Alternating colors in the spectrum area",
"sheet_name": "Song name",
"sheet_name_placeholder": "Song Name/File Name"
},
"total_column": "Total column count: {length}",
"total_time": "Total length of music score: {length}"
},
"setting": {
"clearAll": "Shortcut keys have been reset",
"clearNow": "This key has been cleared",
"head_text": "The piano map takes effect for this operation. Restarting the software requires reset. If you do not adapt, please adapt as soon as possible.",
"key_map": "Key Mapping",
"ok": "Set",
"patterns0": "This run",
"patterns1": "again",
"patterns2": "Restart",
"patterns3": "Adapt to it as soon as possible",
"repeat": "There are duplicate shortcut key values, please check the configuration.",
"reset_map": "Reset piano shooting"
},
"shortcutKeys": {
"button_title": {
"add_delay": "interval",
"add_duration": "+ Suspension",
"add_speed": "+ speed",
"exit": "quit",
"next": "Next",
"pause": "pause",
"reduce_delay": "- Interval",
"reduce_duration": "- Suspension",
"reduce_speed": "- speed",
"repeat": "repeat",
"repeat_next": "Repeat & step",
"resize": "Reload keys",
"resume": "continue",
"start": "Play",
"stop": "stop"
},
"divider1": "Music shortcut keys",
"divider2": "Follow the shortcut key",
"head_text": "The shortcut keys are effective for this operation. Restarting the software requires reset. If you do not adapt, please adapt as soon as possible.",
"patterns0": "This run",
"patterns1": "again",
"patterns2": "Restart",
"patterns3": "Adapt to it as soon as possible",
"title": "Reset shortcut key"
},
"tab": {
"love_success": "Successfully collected",
"myFavorite": "love",
"myImport": "Import",
"myTranslate": "Converted",
"remove_success": "Removal successfully",
"search": "search",
"systemMusic": "systemMusic",
"translateOriginalMusic": "Unconverted songs",
"clear": "Clear",
"title": "Playlist"
},
"tutorial": {
"chose_music": "Please select a song first",
"error": "The conversion failed, please try again",
"error_console": "Conversion failed",
"follow": "Start following the bullet",
"no_music": "No songs",
"now": "Current: {music}",
"save": "Save visual scores to desktop",
"save_desktop": "Saved on desktop"
},
"controller": {
"no_music": "No songs"
},
"music": {
"chose": "Play:",
"placeholder1": "interval",
"placeholder2": "Sustaining sound",
"play": "choose:",
"space": {
"chose0": "System comes with",
"chose1": "random",
"chose2": "Customize",
"mult_speed": "Double the speed",
"title": "Interval delay",
"title1": "Sustain Settings"
}
},
"rule": {
"cycle": "cycle",
"order": "order",
"random": "random"
}
}
================================================
FILE: sky-music-web/src/renderer/src/i18n/locales/jp.json
================================================
{
"main": {
"loading": "🍉アプリケーションをロード中、少々お待ちください~",
"compatible": "互換モード",
"backend": "バックエンドモード",
"queue": "キュー・モード",
"cut": "割り込みモード",
"multi_core": "マルチコアモード",
"single_core": "シングルコアモード",
"transparency": "透明度",
"color": "背景色",
"menu": {
"home": "紹介",
"music": "演奏",
"tutorial": "練習",
"kube": "譜面作成",
"shortcut": "ショートカット",
"musicEdit": "楽譜編集",
"setting": "設定",
"hwndHandle": "ハンドル",
"magicTools": "開発者"
}
},
"shortcutKeys": {
"title": "ショートカットキーをリセット",
"divider1": "音楽ショートカット",
"divider2": "練習ショートカット",
"head_text": "ショートカットは今回の実行中のみ有効です。ソフトを再起動する場合は再設定が必要です。",
"button_title": {
"start": "再生",
"resume": "続き",
"pause": "一時停止",
"stop": "停止",
"add_duration": "+ サステイン",
"reduce_duration": "- サステイン",
"add_delay": "+ 間隔",
"reduce_delay": "- 間隔",
"add_speed": "+ 速度",
"reduce_speed": "- 速度",
"next": "次曲",
"repeat": "リピート",
"repeat_next": "リピートしてスキップ",
"exit": "終了",
"resize": "キー再読み込み"
},
"patterns0": "今回の実行",
"patterns1": "再設定",
"patterns2": "再起動",
"patterns3": "早めに慣れてください"
},
"tab": {
"systemMusic": "システム音楽",
"myImport": "インポート音楽",
"myFavorite": "お気に入り",
"myTranslate": "変換済み音楽",
"translateOriginalMusic": "未変換音楽",
"search": "検索",
"love_success": "お気に入りに追加しました",
"remove_success": "削除しました",
"title": "プレイリスト",
"clear": "クリア"
},
"music_edit": {
"total_column": "総列数:{length}",
"total_time": "楽譜総時間:{length}",
"tips": {
"pause": "一時停止",
"play": "再生",
"upend": "逆再生",
"pre_column": "前の列",
"next_column": "次の列",
"afer_add_column": "現在のハイライト列の後に新しい列を挿入",
"delete_now_column": "現在のハイライト列を削除",
"play_now_column": "現在のハイライト列をゲームで押す",
"copy_now_to_head": "現在のハイライト列を先頭にコピー",
"copy_now_to_pre": "現在のハイライト列を前の列にコピー",
"copy_now_to_next": "現在のハイライト列を次の列にコピー",
"copy_now_to_end": "現在のハイライト列を最後にコピー",
"select_in_music": "プレイリストから選択",
"save_sheet": "楽譜を保存",
"clear_sheet": "現在の作業領域の楽譜をクリア",
"upload_sheet": "楽譜をアップロードして編集",
"tip1": "長押し間隔は列後の待機遅延より短くする必要があります(現在)",
"tip2": "先頭にコピーしました",
"tip3": "前の列にコピーしました",
"tip4": "次の列にコピーしました",
"tip5": "最後にコピーしました",
"tip6": "長押し間隔は待機間隔より短くする必要があります(現在)",
"tip7": "列後の待機遅延は10以上にする必要があります(グローバル追加)",
"tip8": "長押し間隔は列後の待機遅延より10ms短くする必要があります(グローバル追加)"
},
"title": {
"change_send_key_in_game": "列を過ぎたらゲームにキーを送信",
"func_area_tip": "機能エリアのヒント",
"sheet_area_color": "楽譜エリアの交互色",
"columnDownDuration": "列長押し間隔",
"columnAfterDuration": "列後待機遅延",
"sheet_name": "曲名",
"sheet_name_placeholder": "曲名/ファイル名",
"add_new_count_column": "新しい列の数",
"add_new_druation_column": "新しい長押し間隔",
"add_new_after_column": "新しい列後待機遅延",
"global_down": "グローバル長押し間隔",
"set": "設定",
"global_after": "グローバル列後遅延"
},
"dialog": {
"title": "開発者からの暖かいヒント⭐",
"content": "現在の作業領域のすべての楽譜をクリアしますか?保存されていない変更は失われます。",
"content2": "楽譜編集ページを離れますか?保存されていない変更は失われます。",
"positiveText": "クリアする",
"negativeText": "保存してからクリア",
"positiveText2": "離れる",
"negativeText2": "保存してから離れる"
},
"sheet": {
"error": {
"sheet_data": "楽譜データ形式エラー",
"data": "楽譜データ形式エラー"
},
"load_success": "楽譜の読み込みに成功",
"load_fail": "楽譜の読み込みに失敗"
}
},
"columns": {
"name": "曲名",
"operation": "操作",
"total_duration": "時間"
},
"messeage": {
"remove_success": "削除しました",
"remove_fail": "削除失敗",
"progress_fail": "進捗取得失敗",
"choose_plz": "曲を選んでから練習してください",
"double_click": "曲をダブルクリックで再生!",
"unknow_music": "不明な曲",
"order_ok": "プレイリストの曲が終了しました",
"no_music": "再生中の曲はありません",
"sheet": "楽譜👉",
"no_import": "インポート失敗",
"ok_import": "インポート完了",
"no_now": "ありません、他のカテゴリを試してください",
"msg1": "停止状態でのみ開始可能",
"msg2": "曲を選んでから再生してください",
"msg3": "開始",
"msg4": "続き",
"msg5": "一時停止状態でのみ続き可能",
"msg6": "一時停止",
"msg7": "再生中のみ一時停止可能",
"msg8": "停止",
"msg9": "次曲",
"msg10": "サステイン最大2",
"msg11": "サステイン+0.01",
"msg12": "サステイン最小0",
"msg13": "サステイン-0.01",
"msg14": "間隔最大2",
"msg15": "間隔+0.01",
"msg16": "間隔最小0",
"msg17": "間隔-0.01",
"msg18": "速度最大5",
"msg19": "速度+0.1",
"msg20": "速度最小0.1",
"msg21": "速度-0.1"
},
"tutorial": {
"error_console": "変換失敗",
"error": "変換失敗、再試行してください",
"save_desktop": "デスクトップに保存しました",
"now": "現在: {music} ",
"save": "可視化楽譜をデスクトップに保存",
"chose_music": "まず曲を選択してください",
"no_music": "曲がありません",
"follow": "練習開始"
},
"setting": {
"key_map": "キーマッピング",
"reset_map": "キーマッピングをリセット",
"head_text": "キーマッピングは今回の実行中のみ有効です。ソフトを再起動する場合は再設定が必要です。",
"repeat": "重複するショートカット値があります。設定を確認してください。",
"ok": "設定済み",
"clearNow": "このキーをクリア",
"clearAll": "ショートカットをリセット",
"patterns0": "今回の実行",
"patterns1": "再設定",
"patterns2": "再起動",
"patterns3": "早めに慣れてください"
},
"home": {
"head_text": "気に入ったらコーヒー一杯☕をお願いします",
"text": "このソフトウェアへようこそ。このソフトウェアは完全無料です。もし購入した場合は騙されています",
"update": "現在最新バージョンです",
"update_error": "更新チェックエラー",
"update_info": "ソフトウェアの新バージョンを検出できません。ネットワークを修復してみてください",
"tutorial": "チュートリアル",
"qq": "QQグループ",
"qq_sb": "チャンネル",
"patterns0": "完全無料",
"patterns1": "騙されています",
"patterns2": "コーヒー☕"
},
"kube": {
"title": "楽曲変換",
"chose_music": "音楽を選択",
"star_transfer": "変換開始",
"transfer_success": "変換成功",
"transfer_fail": "変換失敗",
"get_fail": "{value} の取得に失敗しました",
"odd": "奇数キー",
"dual": "偶数キー",
"half_t": "半音変換を含む",
"all_t": "全音のみ変換",
"range_key": "範囲のみ変換",
"other_key": "3音以上の転調",
"Streng_filter": "強度フィルター(ミリ秒未満)",
"dynamic_merge": "動的マージしきい値範囲(BPM計算に依存)",
"scale": {
"scale2": "Sky範囲_2オクターブ",
"scale3": "3オクターブ",
"scale4": "4オクターブ",
"scale5": "5オクターブ",
"scale6": "6オクターブ"
},
"transfer_progress": "変換進捗"
},
"magic_tools": {
"head_text": "ここはベータ機能です。慎重にご利用ください🌶、メモリ改変は含みません🌶。",
"head_text2": "この機能は学習・交流のみを目的としています。商用利用は禁止、24時間以内に削除してください",
"head_text3": "🚫エミュレータープレイヤーは以下の機能を使用禁止🚫",
"buttons": {
"check": "チェック",
"abaaba": "強く叫ぶ",
"heart_fire": "心火",
"key": "キー",
"autoFire": "自動点火",
"developer": "開発者カスタム",
"shutdown": "スレッド終了"
},
"fileButtons": {
"systemMusic": "システム音楽",
"myImport": "インポート音楽",
"myTranslate": "変換済み音楽",
"myFavorite": "お気に入り音楽",
"translateMID": "変換MIDI"
},
"messeage": {
"start": "今から心火を自動クリックします",
"stop": "終了!!!!!"
},
"patterns0": "慎重にご利用ください🌶",
"patterns1": "メモリ改変は含みません🌶",
"patterns2": "この機能は学習・交流のみを目的としています。商用利用は禁止、24時間以内に削除してください",
"patterns3": "🚫エミュレータープレイヤーは以下の機能を使用禁止🚫"
},
"hwnd_handle": {
"head_text": "ここはバックグラウンド送信先を変更する場所です。Sky以外のゲームは未検証、修正は行いません",
"head_text2": "この機能で他ゲームがBANされても責任は負いません。ゲームのアンチチート次第です",
"refresh": "リストを更新",
"reset_sky": "Skyウィンドウにリセット",
"now_hwnd": "現在のハンドル >",
"columns": {
"title": "ソフトタイトル",
"exe_name": "プロセス名",
"pid": "PID",
"hwnd": "ハンドル",
"operation": "選択"
},
"reset": "リセット",
"patterns0": "バックグラウンド送信先を変更",
"patterns1": "Sky",
"patterns2": "未検証",
"patterns3": "修正しません",
"patterns4": "責任は負いません",
"patterns5": "BANされる",
"patterns6": "アンチチート",
"patterns7": "不正行為防止メカニズム"
},
"music": {
"play": "選 択: ",
"chose": "再 生: ",
"space": {
"title": "間隔遅延",
"chose0": "システム標準",
"chose1": "ランダム",
"chose2": "カスタム",
"title1": "サステイン設定",
"mult_speed": "倍速"
},
"placeholder1": "間隔",
"placeholder2": "サステイン"
},
"controller": {
"no_music": "曲がありません"
},
"rule": {
"order": "順番",
"random": "ランダム",
"cycle": "ループ"
}
}
================================================
FILE: sky-music-web/src/renderer/src/i18n/locales/ko.json
================================================
{
"columns": {
"name": "노래 제목",
"operation": "작동하다",
"total_duration": "지속"
},
"home": {
"head_text": "사용하기 쉽다고 생각되면 커피 한 잔을 즐길 수 있습니다.",
"patterns0": "완전히 무료",
"patterns1": "속임수",
"patterns2": "커피 ☕",
"qq": "Q 그룹을 추가하십시오",
"qq_sb": "채널을 추가하십시오",
"text": "이 소프트웨어 사용에 오신 것을 환영합니다. 이 소프트웨어는 완전히 무료입니다. 이 소프트웨어를 구입하면 속임수를 썼습니다.",
"tutorial": "튜토리얼을 참조하십시오",
"update": "최신 버전입니다",
"update_error": "감지 오류 업데이트",
"update_info": "새 버전의 소프트웨어를 감지 할 수없고 네트워크를 수리하십시오."
},
"hwnd_handle": {
"columns": {
"exe_name": "프로세스 이름",
"hwnd": "핸들",
"operation": "선택하다",
"pid": "PID",
"title": "소프트웨어 제목"
},
"head_text": "다음은 배경에서 보낸 대상입니다. 가벼운 만남을 제외한 게임은 테스트되지 않았으며 수리되지 않습니다.",
"head_text2": "이 기능으로 인해 다른 게임이 차단되었으며 나를 찾지 마십시오. 게임의 체쇄 방지 메커니즘에 따라 다릅니다",
"now_hwnd": "이제 처리>",
"patterns0": "백그라운드에서 보낸 목적지를 변경하십시오",
"patterns1": "가벼운 만남",
"patterns2": "테스트되지 않았습니다",
"patterns3": "습관",
"patterns4": "나를 찾지 마세요",
"patterns5": "이어",
"patterns6": "제목",
"patterns7": "방지 메커니즘",
"refresh": "목록을 새로 고치십시오",
"reset": "다시 놓기",
"reset_sky": "라이트 만남 창으로 재설정하십시오"
},
"kube": {
"Streng_filter": "강제 필터링 (밀리 초 미만)",
"all_t": "완전한 사운드 변환 만",
"chose_music": "파일 선택",
"dual": "열쇠조차도",
"dynamic_merge": "동적 병합 임계 값 범위 부서 (BMP 계산에 대한 동적 의존성)",
"get_fail": "{value}를 얻지 못했습니다.",
"half_t": "Semitone을 변환합니다",
"odd": "단일 키",
"other_key": "슈퍼 3 톤 변경",
"range_key": "범위 변환 만",
"scale": {
"scale2": "빛이 발생합니다 range_2 스케일 세트",
"scale3": "3 개의 스케일 세트",
"scale4": "4 개의 스케일 세트",
"scale5": "5 세트의 저울",
"scale6": "6 개의 스케일 세트"
},
"star_transfer": "변환 시작",
"title": "노래를 변환합니다",
"transfer_fail": "변환이 실패했습니다",
"transfer_progress": "전환 진행",
"transfer_success": "성공적으로 변환"
},
"magic_tools": {
"buttons": {
"abaaba": "외치다",
"autoFire": "자동 점화",
"check": "조사하다",
"developer": "개발자 사용자 정의",
"heart_fire": "심장 불",
"key": "단추",
"shutdown": "스레드 종료"
},
"fileButtons": {
"myFavorite": "음악 모음",
"myImport": "수입 된 음악",
"myTranslate": "개조 된 음악",
"systemMusic": "시스템 음악",
"translateMID": "스펙트럼의 미디"
},
"head_text": "여기 베타 함수는 다음과 같습니다.주의해서 사용하십시오. 메모리 수정은 관련이 없습니다.",
"head_text2": "이 기능은 학습과 의사 소통을위한 것이며 상업적 목적으로 엄격하게 금지됩니다. 24 시간 이내에 삭제하십시오",
"head_text3": "simulator 플레이어는 다음 모든 기능을 사용하지 않습니다.",
"messeage": {
"start": "이제 Heart Fire를 자동으로 클릭하십시오",
"stop": "종료! ! ! ! !"
},
"patterns0": "주의해서 사용하십시오",
"patterns1": "메모리 수정과 관련이 없습니다",
"patterns2": "이 기능은 학습과 의사 소통을위한 것이며 상업적 목적으로 엄격하게 금지됩니다. 24 시간 이내에 삭제하십시오",
"patterns3": "simulator 플레이어는 다음 모든 기능을 사용하지 않습니다."
},
"main": {
"backend": "배경 모드",
"color": "배경색",
"compatible": "호환성 모드",
"cut": "컷 인 모드",
"loading": "Application이로드 중입니다. 기다려주세요 ~",
"menu": {
"home": "소개하다",
"hwndHandle": "핸들",
"kube": "비틀기",
"magicTools": "개발자",
"music": "성능",
"musicEdit": "편집",
"setting": "설정",
"shortcut": "단축키",
"tutorial": "추종탄"
},
"multi_core": "멀티 코어 모드",
"queue": "대기열 모드",
"single_core": "단일 코어 모드",
"transparency": "투명도"
},
"messeage": {
"choose_plz": "노래를 선택하고 잘 생긴 남자와 함께 연주하십시오.",
"progress_fail": "진전을 얻지 못했습니다",
"remove_fail": "삭제가 실패했습니다",
"remove_success": "성공적으로 제거",
"double_click": "노래를 두 번 클릭하십시오!",
"msg1": "시작 만 허용합니다",
"msg10": "최대 지속 사운드는 2입니다",
"msg11": "0.01을 유지하십시오",
"msg12": "가장 낮은 지속 사운드는 0입니다",
"msg13": "지속 -0.01",
"msg14": "최대 간격은 2입니다",
"msg15": "간격 0.01",
"msg16": "최소 간격은 0입니다",
"msg17": "간격 -0.01",
"msg18": "최대 속도는 5입니다",
"msg19": "속도 0.1",
"msg2": "노래를 선택하고 재생하십시오. 아름다운",
"msg20": "가장 낮은 속도는 0.1입니다",
"msg21": "속도 -0.1",
"msg3": "시작",
"msg4": "계속하다",
"msg5": "계속할 수 있습니다",
"msg6": "정지시키다",
"msg7": "일시 중지는 재생 중일 때만 허용됩니다",
"msg8": "멈추다",
"msg9": "다음",
"no_import": "가져 오기 실패",
"no_music": "노래가 재생되지 않습니다",
"no_now": "아무것도, 아이, 정말 아무것도,, 다른 범주를 시도하십시오",
"ok_import": "수입을 완료하십시오",
"order_ok": "목록의 노래가 완성되었습니다",
"sheet": "스코어 👉",
"unknow_music": "알 수없는 노래"
},
"music_edit": {
"dialog": {
"content": "현재 작업 영역의 모든 점수를 지우고 싶습니까? 구축되지 않은 변경 사항이 손실됩니다.",
"content2": "점수 편집 페이지를 남겨두고 싶습니까? 구축되지 않은 변경 사항이 손실됩니다.",
"negativeText": "내가 명확하지 않으면 먼저 저장하겠습니다",
"negativeText2": "떠나지 않으면 먼저 저장하겠습니다",
"positiveText": "그냥 지우십시오",
"positiveText2": "그냥 떠나십시오",
"title": "개발자의 따뜻한 팁 ⭐"
},
"sheet": {
"error": {
"data": "스펙트럼 데이터 형식의 오류",
"sheet_data": "스펙트럼 참고 데이터 형식 오류"
},
"load_fail": "스펙트럼 로딩이 실패했습니다",
"load_success": "점수가 성공적으로로드되었습니다"
},
"tips": {
"afer_add_column": "현재 강조 표시된 열에 새 열을 삽입하십시오",
"clear_sheet": "현재 작업 공간의 점수를 지우십시오",
"copy_now_to_end": "현재 강조 표시된 열의 끝에 사본을 복사하십시오.",
"copy_now_to_head": "현재 강조 표시된 열의 시작 부분에 사본을 복사합니다.",
"copy_now_to_next": "현재 강조 표시된 열의 사본을 다음 열에 복사합니다.",
"copy_now_to_pre": "현재 강조 표시된 열의 사본을 이전 열에 복사합니다.",
"delete_now_column": "현재 강조 표시된 열을 삭제하십시오",
"next_column": "다음 열",
"pause": "정지시키다",
"play": "놀다",
"play_now_column": "강조 표시된 열은 게임에 있습니다",
"pre_column": "이전 열",
"save_sheet": "스펙트럼을 저장하십시오",
"select_in_music": "노래 목록에서 노래를 선택하십시오",
"tip1": "긴 프레스 간격은 열보다 작고 지연을 기다려야하며 자동으로 조정되었습니다 (현재)",
"tip2": "처음에 복사하십시오",
"tip3": "이전 열로 복사되었습니다",
"tip4": "다음 열에 복사되었습니다",
"tip5": "마지막 열에 복사되었습니다",
"tip6": "긴 프레스 간격은 대기 간격보다 작아야하며 열이 자동으로 증가한 후 대기 지연 (현재)",
"tip7": "열 후 대기 지연은 10 미만일 수 없으며 자동으로 조정되었습니다 (새로운 글로벌)",
"tip8": "긴 압박 후 대기 지연은 10ms 컬럼보다 작습니다.",
"upend": "거꾸로 두십시오",
"upload_sheet": "업로드 점수 편집"
},
"title": {
"add_new_after_column": "추가 열 후 대기 지연",
"add_new_count_column": "새 열의 수",
"add_new_druation_column": "새로운 긴 프레스 간격",
"change_send_key_in_game": "열을 통해 키를 게임에 보내십시오",
"columnAfterDuration": "열 후 대기 지연",
"columnDownDuration": "길이가",
"func_area_tip": "리본 프롬프트",
"global_after": "글로벌 칼럼 후 지연",
"global_down": "전체 지역을 길게 누릅니다",
"set": "설정",
"sheet_area_color": "스펙트럼 영역의 교대 색상",
"sheet_name": "노래 이름",
"sheet_name_placeholder": "노래 이름/파일 이름"
},
"total_column": "총 열 수 : {길이}",
"total_time": "음악 점수의 총 길이 : {길이}"
},
"setting": {
"clearAll": "바로 가기 키를 재설정하십시오",
"clearNow": "이 키가 지워졌습니다",
"head_text": "피아노지도는이 작업에 적용됩니다. 소프트웨어를 다시 시작하려면 재설정이 필요합니다. 적응하지 않으면 가능한 빨리 적응하십시오.",
"key_map": "키 매핑",
"ok": "세트",
"patterns0": "이 실행",
"patterns1": "다시",
"patterns2": "다시 시작하십시오",
"patterns3": "가능한 빨리 적응하십시오",
"repeat": "중복 단축키 키 값이 있습니다. 구성을 확인하십시오.",
"reset_map": "피아노 촬영을 재설정하십시오"
},
"shortcutKeys": {
"button_title": {
"add_delay": "간격",
"add_duration": "사운드 유지",
"add_speed": "속도의 두 배",
"exit": "그만두다",
"next": "다음",
"pause": "정지시키다",
"reduce_delay": "- 간격",
"reduce_duration": "- 보류",
"reduce_speed": "- 이중 속도",
"repeat": "반복하다",
"repeat_next": "단계별로 반복하고",
"resize": "키를 다시로드하십시오",
"resume": "계속하다",
"start": "놀다",
"stop": "멈추다"
},
"divider1": "음악 바로 가기 키",
"divider2": "바로 가기 키를 따르십시오",
"head_text": "바로 가기 키는이 작업에 효과적입니다. 소프트웨어를 다시 시작하려면 재설정이 필요합니다. 적응하지 않으면 가능한 빨리 적응하십시오.",
"patterns0": "이 실행",
"patterns1": "다시",
"patterns2": "다시 시작하십시오",
"patterns3": "가능한 빨리 적응하십시오",
"title": "바로 가기 키를 재설정합니다"
},
"tab": {
"love_success": "성공적으로 수집되었습니다",
"myFavorite": "모으다",
"myImport": "노래 가져 오기",
"myTranslate": "변환 된 노래",
"remove_success": "성공적으로 제거",
"search": "찾다",
"systemMusic": "시스템 노래",
"translateOriginalMusic": "비 변환되지 않은 노래",
"clear": "분명한",
"title": "재생 목록"
},
"tutorial": {
"chose_music": "먼저 노래를 선택하십시오",
"error": "변환이 실패했습니다. 다시 시도하십시오",
"error_console": "변환이 실패했습니다",
"follow": "총알을 따라 가기 시작하십시오",
"no_music": "노래가 없습니다",
"now": "현재 : {music}",
"save": "비주얼 점수를 데스크탑에 저장하십시오",
"save_desktop": "데스크탑에 저장"
},
"controller": {
"no_music": "노래가 없습니다"
},
"music": {
"chose": "놀다:",
"placeholder1": "간격",
"placeholder2": "사운드 유지",
"play": "선택하다:",
"space": {
"chose0": "시스템과 함께 제공됩니다",
"chose1": "무작위의",
"chose2": "사용자 정의하십시오",
"mult_speed": "속도의 두 배",
"title": "간격 지연",
"title1": "설정 설정을 유지하십시오"
}
},
"rule": {
"cycle": "주기",
"order": "주문하다",
"random": "무작위의"
}
}
================================================
FILE: sky-music-web/src/renderer/src/i18n/locales/zh-classical.json
================================================
{
"main": {
"loading": "🍉应用载入中,少待片刻~",
"compatible": "兼容之制",
"backend": "後臺之制",
"queue": "列隊之制",
"cut": "插隊之制",
"multi_core": "多核之制",
"single_core": "單核之制",
"transparency": "透明度",
"color": "底色",
"menu": {
"home": "序",
"music": "奏樂",
"tutorial": "習彈",
"kube": "譜析",
"shortcut": "捷鍵",
"musicEdit": "譜編",
"setting": "設置",
"hwndHandle": "柄",
"magicTools": "開發者"
}
},
"shortcutKeys": {
"title": "重設捷鍵",
"divider1": "樂捷鍵",
"divider2": "習彈捷鍵",
"head_text": "捷鍵僅本次運行有效,重啟需復設,若不適,請速適之。",
"button_title": {
"start": "奏",
"resume": "續",
"pause": "止",
"stop": "罷",
"add_duration": "+ 延音",
"reduce_duration": "- 延音",
"add_delay": "+ 間隔",
"reduce_delay": "- 間隔",
"add_speed": "+ 倍速",
"reduce_speed": "- 倍速",
"next": "次曲",
"repeat": "復奏",
"repeat_next": "復並過",
"exit": "退",
"resize": "重載鍵"
},
"patterns0": "本次運行",
"patterns1": "復設",
"patterns2": "重啟",
"patterns3": "速適之"
},
"tab": {
"systemMusic": "自帶樂",
"myImport": "導入樂",
"myFavorite": "所好",
"myTranslate": "已轉樂",
"translateOriginalMusic": "未轉樂",
"search": "尋",
"love_success": "所好成",
"remove_success": "去除成",
"title": "曲單",
"clear": "盡清"
},
"music_edit": {
"total_column": "總列:{length}",
"total_time": "譜總時:{length}",
"tips": {
"pause": "止",
"play": "奏",
"upend": "倒奏",
"pre_column": "上列",
"next_column": "下列",
"afer_add_column": "於高亮列後增新列",
"delete_now_column": "去高亮列",
"play_now_column": "高亮列按於戲中",
"copy_now_to_head": "高亮列複首",
"copy_now_to_pre": "高亮列複上列",
"copy_now_to_next": "高亮列複下列",
"copy_now_to_end": "高亮列複末",
"select_in_music": "自曲單選曲",
"save_sheet": "存譜",
"clear_sheet": "清作業區譜",
"upload_sheet": "上傳譜編",
"tip1": "長按間隔須小於列後延,已自調(當下)",
"tip2": "已複首",
"tip3": "已複上列",
"tip4": "已複下列",
"tip5": "已複末列",
"tip6": "長按間隔須小於待延,已自增列後延(當下)",
"tip7": "列後延不得小於十,已自調(全局新)",
"tip8": "長按間隔須小於列後延十毫秒,已自調(全局新)"
},
"title": {
"change_send_key_in_game": "過列發鍵於戲",
"func_area_tip": "功能區示",
"sheet_area_color": "譜區間色",
"columnDownDuration": "列長按間",
"columnAfterDuration": "列後延",
"sheet_name": "曲名",
"sheet_name_placeholder": "曲名/檔名",
"add_new_count_column": "增新列數",
"add_new_druation_column": "新長按間",
"add_new_after_column": "新列後延",
"global_down": "全局長按間",
"set": "設",
"global_after": "全局列後延"
},
"dialog": {
"title": "開發者溫言⭐",
"content": "確清作業區諸譜乎?未存者將失。",
"content2": "確離譜編頁乎?未存者將失。",
"positiveText": "清之可也",
"negativeText": "且存不清",
"positiveText2": "去之可也",
"negativeText2": "且存不去"
},
"sheet": {
"error": {
"sheet_data": "譜音數據謬",
"data": "譜數據謬"
},
"load_success": "譜載成",
"load_fail": "譜載敗"
}
},
"columns": {
"name": "曲名",
"operation": "操作",
"total_duration": "時長"
},
"messeage": {
"remove_success": "去除成",
"remove_fail": "去除敗",
"progress_fail": "取進度敗",
"choose_plz": "選曲習彈之",
"double_click": "雙擊奏曲!",
"unknow_music": "曲未詳",
"order_ok": "曲盡於列矣",
"no_music": "無曲正奏也",
"sheet": "譜👉",
"no_import": "導入敗",
"ok_import": "導入成",
"no_now": "無也,誠無也,換類再試之",
"msg1": "止時乃可始",
"msg2": "選曲乃可奏",
"msg3": "始",
"msg4": "續",
"msg5": "止時乃可續",
"msg6": "止",
"msg7": "奏時乃可止",
"msg8": "罷",
"msg9": "次曲",
"msg10": "延音至二為極",
"msg11": "延音+0.01",
"msg12": "延音至零為下",
"msg13": "延音-0.01",
"msg14": "間隔至二為極",
"msg15": "間隔+0.01",
"msg16": "間隔至零為下",
"msg17": "間隔-0.01",
"msg18": "速至五為極",
"msg19": "速+0.1",
"msg20": "速至0.1為下",
"msg21": "速-0.1"
},
"tutorial": {
"error_console": "轉換敗",
"error": "轉換敗,請再試",
"save_desktop": "已存於桌",
"now": "今: {music} ",
"save": "存可視譜於桌",
"chose_music": "先選曲",
"no_music": "無曲",
"follow": "始習彈"
},
"setting": {
"key_map": "琴鍵映射",
"reset_map": "重設彈琴映射",
"head_text": "彈琴映射僅本次運行有效,重啟需復設,若不適,請速適之。",
"repeat": "捷鍵值有復,請檢之。",
"ok": "已設",
"clearNow": "已清此鍵",
"clearAll": "已復捷鍵",
"patterns0": "本次運行",
"patterns1": "復設",
"patterns2": "重啟",
"patterns3": "速適之"
},
"home": {
"head_text": "若覺好用,賞我一杯咖啡☕",
"text": "歡迎用此軟,軟全免,若購之,則受騙矣",
"update": "已是新矣",
"update_error": "檢新敗",
"update_info": "新未可檢,試修網絡",
"tutorial": "觀教程",
"qq": "加Q群",
"qq_sb": "加頻道",
"patterns0": "全免",
"patterns1": "受騙了",
"patterns2": "咖啡☕"
},
"kube": {
"title": "轉曲",
"chose_music": "選樂",
"star_transfer": "始轉",
"transfer_success": "轉成",
"transfer_fail": "轉敗",
"get_fail": "取{value}敗",
"odd": "單鍵",
"dual": "雙鍵",
"half_t": "含半音轉",
"all_t": "全音轉",
"range_key": "僅範圍轉",
"other_key": "超三音變",
"Streng_filter": "力度濾(毫秒下)",
"dynamic_merge": "動合閾值分(依bmp計)",
"scale": {
"scale2": "光遇二階",
"scale3": "三階",
"scale4": "四階",
"scale5": "五階",
"scale6": "六階"
},
"transfer_progress": "轉進度"
},
"magic_tools": {
"head_text": "此測試功能,慎用🌶,不涉內存🌶。",
"head_text2": "僅供學習,禁商用,廿四時內刪",
"head_text3": "🚫模擬器者禁用下列諸功能🚫",
"buttons": {
"check": "檢",
"abaaba": "大叫",
"heart_fire": "心火",
"key": "鍵",
"autoFire": "自點火",
"developer": "開發自定",
"shutdown": "終線"
},
"fileButtons": {
"systemMusic": "系樂",
"myImport": "導樂",
"myTranslate": "轉樂",
"myFavorite": "所好樂",
"translateMID": "轉譜MIDI"
},
"messeage": {
"start": "今自點心火",
"stop": "終止!!!!!"
},
"patterns0": "慎用🌶",
"patterns1": "不涉內存🌶",
"patterns2": "僅供學習,禁商用,廿四時內刪",
"patterns3": "🚫模擬器者禁用下列諸功能🚫"
},
"hwnd_handle": {
"head_text": "此更改後臺發送目標,光遇外諸戲未測,不修",
"head_text2": "因是致他戲封號,勿問我。視戲反作弊制而定",
"refresh": "新列",
"reset_sky": "復光遇窗",
"now_hwnd": "今柄 >",
"columns": {
"title": "軟標",
"exe_name": "程名",
"pid": "PID",
"hwnd": "柄",
"operation": "定"
},
"reset": "復",
"patterns0": "更改後臺發送目標",
"patterns1": "光遇",
"patterns2": "未測",
"patterns3": "不修",
"patterns4": "勿問我",
"patterns5": "致",
"patterns6": "封號",
"patterns7": "反作弊制"
},
"music": {
"play": "選: ",
"chose": "奏: ",
"space": {
"title": "間隔延",
"chose0": "系自帶",
"chose1": "隨機",
"chose2": "自定",
"title1": "延音設",
"mult_speed": "倍速"
},
"placeholder1": "間隔",
"placeholder2": "延音"
},
"controller": {
"no_music": "無曲"
},
"rule": {
"order": "順",
"random": "隨",
"cycle": "循"
}
}
================================================
FILE: sky-music-web/src/renderer/src/i18n/locales/zh-cn.json
================================================
{
"main": {
"loading": "🍉应用加载中,请稍等~",
"compatible": "兼容模式",
"backend": "后台模式",
"queue": "队列模式",
"cut": "插队模式",
"multi_core": "多核模式",
"single_core": "单核模式",
"transparency": "透明度",
"color": "背景颜色",
"menu": {
"home": "介绍",
"music": "演奏",
"tutorial": "跟弹",
"kube": "扒谱",
"shortcut": "快捷键",
"musicEdit": "乐谱编辑",
"setting": "设置",
"hwndHandle": "句柄",
"magicTools": "开发者",
"aisetting": "AI设置"
}
},
"shortcutKeys": {
"title": "重 置 快 捷 键",
"divider1": "音乐快捷键",
"divider2": "跟弹快捷键",
"head_text": "快捷键为本次运行生效,重启软件需要重新设置,如不适应请尽快适应",
"button_title": {
"start": "播放",
"resume": "继续",
"pause": "暂停",
"stop": "停止",
"add_duration": "+ 延音",
"reduce_duration": "- 延音",
"add_delay": "+ 间隔",
"reduce_delay": "- 间隔",
"add_speed": "+ 倍速",
"reduce_speed": "- 倍速",
"next": "下一首",
"repeat": "重复",
"repeat_next": "重复并步过",
"exit": "退出",
"resize": "重载按键"
},
"patterns0": "本次运行",
"patterns1": "重新",
"patterns2": "重启",
"patterns3": "尽快适应"
},
"tab": {
"systemMusic": "自带歌曲",
"myImport": "导入歌曲",
"myFavorite": "收藏",
"myTranslate": "已转换歌曲",
"translateOriginalMusic": "未转换歌曲",
"search": "搜索",
"love_success": "收藏成功",
"remove_success": "移除成功",
"title": "播放列表",
"clear": "清空"
},
"music_edit": {
"total_column": "总列数:{length}",
"total_time": "乐谱总时长:{length}",
"tips": {
"pause": "暂停",
"play": "播放",
"upend": "倒放",
"pre_column": "上一列",
"next_column": "下一列",
"afer_add_column": "在当前高亮列后插入新列",
"delete_now_column": "删除当前高亮列",
"play_now_column": "当前高亮列到游戏里面按下",
"copy_now_to_head": "当前高亮列复制一份到开头",
"copy_now_to_pre": "当前高亮列复制一份到上一列",
"copy_now_to_next": "当前高亮列复制一份到下一列",
"copy_now_to_end": "当前高亮列复制一份到末尾",
"select_in_music": "从歌单里面选歌",
"save_sheet": "保存谱子",
"clear_sheet": "清空当前工作区的谱子",
"upload_sheet": "上传谱子编辑",
"tip1": "长按间隔需要小于列后等待延迟,已自动调整(当下)",
"tip2": "已复制到开头",
"tip3": "已复制到上一列",
"tip4": "已复制到下一列",
"tip5": "已复制到最后一列",
"tip6": "长按间隔需要小于等待间隔,已自动增加列后等待延迟(当下)",
"tip7": "列后等待延迟不能小于 10,已自动调整(全局新增)",
"tip8": "长按间隔需要小于列后等待延迟 10ms,已自动调整(全局新增)"
},
"title": {
"change_send_key_in_game": "过列发送按键到游戏",
"func_area_tip": "功能区提示",
"sheet_area_color": "谱区交替色",
"columnDownDuration": "当列长按间隔",
"columnAfterDuration": "列后等待延迟",
"sheet_name": "歌曲名字",
"sheet_name_placeholder": "歌曲名字/文件名字",
"add_new_count_column": "新增列数量",
"add_new_druation_column": "新增的长按间隔",
"add_new_after_column": "新增的列后等待延迟",
"global_down": "全局长按间隔",
"set": "设置",
"global_after": "全局列后延迟"
},
"dialog": {
"title": "一个来自开发者的温馨小提示⭐",
"content": "确定要清空当前工作区域的所有谱子吗?未保存的更改将丢失。",
"content2": "确定要离开乐谱编辑页面吗?未保存的更改将丢失。",
"positiveText": "就清空就清空",
"negativeText": "不清空了我先保存吧",
"positiveText2": "就走就走",
"negativeText2": "不走了我先保存吧"
},
"sheet": {
"error": {
"sheet_data": "谱子音符数据格式错误",
"data": "谱子数据格式错误"
},
"load_success": "谱子加载成功",
"load_fail": "谱子加载失败"
}
},
"columns": {
"name": "歌名",
"operation": "操作",
"total_duration": "时长"
},
"messeage": {
"remove_success": "移除成功",
"remove_fail": "删除失败",
"progress_fail": "获取进度失败",
"choose_plz": "选个歌再跟弹吧靓仔",
"double_click": "双击歌曲播放!",
"unknow_music": "未知歌曲",
"order_ok": "列表的歌放完咯",
"no_music": "没有正在播放的歌曲哦",
"sheet": "谱子👉",
"no_import": "导入失败",
"ok_import": "完成导入",
"no_now": "没得啊孩子,真的没得😭,切别的分类再试试吧",
"msg1": "仅停止状态下允许开始",
"msg2": "选个歌再播放吧靓仔",
"msg3": "开始",
"msg4": "继续",
"msg5": "仅暂停状态下允许继续",
"msg6": "暂停",
"msg7": "仅正在播放时允许暂停",
"msg8": "停止",
"msg9": "下一首",
"msg10": "延音最高为2",
"msg11": "延音+0.01",
"msg12": "延音最低为0",
"msg13": "延音-0.01",
"msg14": "间隔最高为2",
"msg15": "间隔+0.01",
"msg16": "间隔最低为0",
"msg17": "间隔-0.01",
"msg18": "速度最高为5",
"msg19": "速度+0.1",
"msg20": "速度最低为0.1",
"msg21": "速度-0.1"
},
"tutorial": {
"error_console": "转换失败",
"error": "转换失败,请重试",
"save_desktop": "已保存在桌面",
"now": "当前: {music} ",
"save": "保存可视化乐谱到桌面",
"chose_music": "请先选择一首歌曲",
"no_music": "没有歌曲",
"follow": "开始跟弹"
},
"setting": {
"key_map": "琴键映射",
"reset_map": "重 置 弹 琴 映 射",
"head_text": "弹琴映射为本次运行生效,重启软件需要重新设置,如不适应请尽快适应",
"repeat": "存在重复的快捷键值,请检查配置。",
"ok": "已设置",
"clearNow": "已清除本键",
"clearAll": "已重置快捷键",
"patterns0": "本次运行",
"patterns1": "重新",
"patterns2": "重启",
"patterns3": "尽快适应"
},
"home": {
"head_text": "如果您觉得好用可以赏我一杯咖啡☕",
"text": "欢迎使用本软件,本软件完全免费,如果您是买的本软件就是被骗了",
"update": "当前已经是最新版了",
"update_error": "更新检测错误",
"update_info": "无法检测软件新版本,尝试下修复网络呢",
"tutorial": "看教程",
"qq": "加Q群",
"qq_sb": "加频道",
"patterns0": "完全免费",
"patterns1": "被骗了",
"patterns2": "咖啡☕"
},
"kube": {
"title": "转换歌曲",
"chose_music": "选择音乐",
"star_transfer": "开始转换",
"transfer_success": "转换成功",
"transfer_fail": "转换失败",
"get_fail": "获取 {value} 失败",
"odd": "单数键",
"dual": "双数键",
"half_t": "含半音转换",
"all_t": "仅全音转换",
"range_key": "仅范围转换",
"other_key": "超3音变调",
"Streng_filter": "力度过滤(低于毫秒)",
"dynamic_merge": "动态合并阈值范围划分(动态取决于bmp计算)",
"scale": {
"scale2": "光遇范围_2组音阶",
"scale3": "3组音阶",
"scale4": "4组音阶",
"scale5": "5组音阶",
"scale6": "6组音阶"
},
"transfer_progress": "转换进度"
},
"magic_tools": {
"head_text": "此处是测试版功能请谨慎使用🌶,不涉及内存修改🌶。",
"head_text2": "此处功能仅供学习交流,严禁用于商业用途,请于24小时内删除",
"head_text3": "🚫模拟器玩家禁止使用下面的所有功能🚫",
"buttons": {
"check": "检查",
"abaaba": "狠狠的叫",
"heart_fire": "心火",
"key": "按键",
"autoFire": "自动点火",
"developer": "开发者自定义",
"shutdown": "终止线程"
},
"fileButtons": {
"systemMusic": "系统的音乐",
"myImport": "导入的音乐",
"myTranslate": "转换的音乐",
"myFavorite": "收藏的音乐",
"translateMID": "转谱的MIDI"
},
"messeage": {
"start": "现在开始自动点击心火",
"stop": "终止!!!!!"
},
"patterns0": "谨慎使用🌶",
"patterns1": "不涉及内存修改🌶",
"patterns2": "此处功能仅供学习交流,严禁用于商业用途,请于24小时内删除",
"patterns3": "🚫模拟器玩家禁止使用下面的所有功能🚫"
},
"hwnd_handle": {
"head_text": "此处为更改后台发送的目标,除了光遇以外的游戏均未测试,不会进行修复",
"head_text2": "因为此功能导致其他游戏被封号,不要找我。取决于游戏的反作弊机制",
"refresh": "刷新列表",
"reset_sky": "重置为光遇窗口",
"now_hwnd": "现在的句柄 >",
"columns": {
"title": "软件标题",
"exe_name": "进程名字",
"pid": "PID",
"hwnd": "句柄",
"operation": "选定"
},
"reset": "重置",
"patterns0": "更改后台发送的目标",
"patterns1": "光遇",
"patterns2": "未测试",
"patterns3": "不会",
"patterns4": "不要找我",
"patterns5": "导致",
"patterns6": "封号",
"patterns7": "反作弊机制"
},
"music": {
"play": "选 择: ",
"chose": "播 放: ",
"space": {
"title": "间隔延迟",
"chose0": "系统自带",
"chose1": "随机",
"chose2": "自定义",
"title1": "延音设置",
"mult_speed": "倍速"
},
"placeholder1": "间隔",
"placeholder2": "延音"
},
"controller": {
"no_music": "没有歌曲"
},
"rule": {
"order": "顺序",
"random": "随机",
"cycle": "循环"
},
"ai_setting": {
"head_text": "此处功能测试中",
"patterns1": "此处功能测试中",
"buttons": {
"set_token": "保存"
}
}
}
================================================
FILE: sky-music-web/src/renderer/src/i18n/locales/zh-tw.json
================================================
{
"columns": {
"name": "歌名",
"operation": "操作",
"total_duration": "時長"
},
"home": {
"head_text": "如果您覺得好用可以賞我一杯咖啡☕",
"patterns0": "完全免費",
"patterns1": "被騙了",
"patterns2": "咖啡☕",
"qq": "加Q群",
"qq_sb": "加頻道",
"text": "歡迎使用本軟件,本軟件完全免費,如果您是買的本軟件就是被騙了",
"tutorial": "看教程",
"update": "當前已經是最新版了",
"update_error": "更新檢測錯誤",
"update_info": "無法檢測軟件新版本,嘗試下修復網絡呢"
},
"hwnd_handle": {
"columns": {
"exe_name": "進程名字",
"hwnd": "句柄",
"operation": "選定",
"pid": "PID",
"title": "軟件標題"
},
"head_text": "此處為更改後台發送的目標,除了光遇以外的遊戲均未測試,不會進行修復",
"head_text2": "因為此功能導致其他遊戲被封號,不要找我。\n取決於遊戲的反作弊機制",
"now_hwnd": "現在的句柄 >",
"patterns0": "更改後台發送的目標",
"patterns1": "光遇",
"patterns2": "未測試",
"patterns3": "不會",
"patterns4": "不要找我",
"patterns5": "導致",
"patterns6": "封號",
"patterns7": "反作弊機制",
"refresh": "刷新列表",
"reset": "重置",
"reset_sky": "重置為光遇窗口"
},
"kube": {
"Streng_filter": "力度過濾(低於毫秒)",
"all_t": "僅全音轉換",
"chose_music": "選擇音樂",
"dual": "雙數鍵",
"dynamic_merge": "動態合併閾值範圍劃分(動態取決於bmp計算)",
"get_fail": "獲取 {value} 失敗",
"half_t": "含半音轉換",
"odd": "單數鍵",
"other_key": "超3音變調",
"range_key": "僅範圍轉換",
"scale": {
"scale2": "光遇範圍_2組音階",
"scale3": "3組音階",
"scale4": "4組音階",
"scale5": "5組音階",
"scale6": "6組音階"
},
"star_transfer": "開始轉換",
"title": "轉換歌曲",
"transfer_fail": "轉換失敗",
"transfer_progress": "轉換進度",
"transfer_success": "轉換成功"
},
"magic_tools": {
"buttons": {
"abaaba": "狠狠的叫",
"autoFire": "自動點火",
"check": "檢查",
"developer": "開發者自定義",
"heart_fire": "心火",
"key": "按鍵",
"shutdown": "終止線程"
},
"fileButtons": {
"myFavorite": "收藏的音樂",
"myImport": "導入的音樂",
"myTranslate": "轉換的音樂",
"systemMusic": "系統的音樂",
"translateMID": "轉譜的MIDI"
},
"head_text": "此處是測試版功能請謹慎使用🌶,不涉及內存修改🌶。",
"head_text2": "此處功能僅供學習交流,嚴禁用於商業用途,請於24小時內刪除",
"head_text3": "🚫模擬器玩家禁止使用下面的所有功能🚫",
"messeage": {
"start": "現在開始自動點擊心火",
"stop": "終止! \n! \n! \n! \n!"
},
"patterns0": "謹慎使用🌶",
"patterns1": "不涉及內存修改🌶",
"patterns2": "此處功能僅供學習交流,嚴禁用於商業用途,請於24小時內刪除",
"patterns3": "🚫模擬器玩家禁止使用下面的所有功能🚫"
},
"main": {
"backend": "後台模式",
"color": "背景顏色",
"compatible": "兼容模式",
"cut": "插隊模式",
"loading": "🍉應用加載中,請稍等~",
"menu": {
"home": "介紹",
"hwndHandle": "句柄",
"kube": "扒譜",
"magicTools": "開發者",
"music": "演奏",
"musicEdit": "樂譜編輯",
"setting": "設定",
"shortcut": "快速鍵",
"tutorial": "跟彈"
},
"multi_core": "多核模式",
"queue": "隊列模式",
"single_core": "單核模式",
"transparency": "透明度"
},
"messeage": {
"choose_plz": "選個歌再跟彈吧靚仔",
"progress_fail": "獲取進度失敗",
"remove_fail": "刪除失敗",
"remove_success": "移除成功",
"double_click": "雙擊歌曲播放!",
"msg1": "僅停止狀態下允許開始",
"msg10": "延音最高為2",
"msg11": "延音 0.01",
"msg12": "延音最低為0",
"msg13": "延音-0.01",
"msg14": "間隔最高為2",
"msg15": "間隔 0.01",
"msg16": "間隔最低為0",
"msg17": "間隔-0.01",
"msg18": "速度最高為5",
"msg19": "速度 0.1",
"msg2": "選個歌再播放吧靚仔",
"msg20": "速度最低為0.1",
"msg21": "速度-0.1",
"msg3": "開始",
"msg4": "繼續",
"msg5": "僅暫停狀態下允許繼續",
"msg6": "暫停",
"msg7": "僅正在播放時允許暫停",
"msg8": "停止",
"msg9": "下一首",
"no_import": "導入失敗",
"no_music": "沒有正在播放的歌曲哦",
"no_now": "沒得啊孩子,真的沒得😭,切別的分類再試試吧",
"ok_import": "完成導入",
"order_ok": "列表的歌放完咯",
"sheet": "譜子👉",
"unknow_music": "未知歌曲"
},
"music_edit": {
"dialog": {
"content": "確定要清空當前工作區域的所有譜子嗎?\n未保存的更改將丟失。",
"content2": "確定要離開樂譜編輯頁面嗎?\n未保存的更改將丟失。",
"negativeText": "不清空了我先保存吧",
"negativeText2": "不走了我先保存吧",
"positiveText": "就清空就清空",
"positiveText2": "就走就走",
"title": "一個來自開發者的溫馨小提示⭐"
},
"sheet": {
"error": {
"data": "譜子數據格式錯誤",
"sheet_data": "譜子音符數據格式錯誤"
},
"load_fail": "譜子加載失敗",
"load_success": "譜子加載成功"
},
"tips": {
"afer_add_column": "在當前高亮列後插入新列",
"clear_sheet": "清空當前工作區的譜子",
"copy_now_to_end": "當前高亮列複製一份到末尾",
"copy_now_to_head": "當前高亮列複製一份到開頭",
"copy_now_to_next": "當前高亮列複製一份到下一列",
"copy_now_to_pre": "當前高亮列複製一份到上一列",
"delete_now_column": "刪除當前高亮列",
"next_column": "下一列",
"pause": "暫停",
"play": "播放",
"play_now_column": "當前高亮列到遊戲裡面按下",
"pre_column": "上一列",
"save_sheet": "保存譜子",
"select_in_music": "從歌單裡面選歌",
"tip1": "長按間隔需要小於列後等待延遲,已自動調整(當下)",
"tip2": "已復製到開頭",
"tip3": "已復製到上一列",
"tip4": "已復製到下一列",
"tip5": "已復製到最後一列",
"tip6": "長按間隔需要小於等待間隔,已自動增加列後等待延遲(當下)",
"tip7": "列後等待延遲不能小於 10,已自動調整(全局新增)",
"tip8": "長按間隔需要小於列後等待延遲 10ms,已自動調整(全局新增)",
"upend": "倒放",
"upload_sheet": "上傳譜子編輯"
},
"title": {
"add_new_after_column": "新增的列後等待延遲",
"add_new_count_column": "新增列數量",
"add_new_druation_column": "新增的長按間隔",
"change_send_key_in_game": "過列發送按鍵到遊戲",
"columnAfterDuration": "列後等待延遲",
"columnDownDuration": "當列長按間隔",
"func_area_tip": "功能區提示",
"global_after": "全局列後延遲",
"global_down": "全局長按間隔",
"set": "設定",
"sheet_area_color": "譜區交替色",
"sheet_name": "歌曲名字",
"sheet_name_placeholder": "歌曲名字/文件名字"
},
"total_column": "總列數:{length}",
"total_time": "樂譜總時長:{length}"
},
"setting": {
"clearAll": "已重置快捷鍵",
"clearNow": "已清除本鍵",
"head_text": "彈琴映射為本次運行生效,重啟軟件需要重新設置,如不適應請盡快適應",
"key_map": "琴鍵映射",
"ok": "已設置",
"patterns0": "本次運行",
"patterns1": "重新",
"patterns2": "重啟",
"patterns3": "盡快適應",
"repeat": "存在重複的快捷鍵值,請檢查配置。",
"reset_map": "重 置 彈 琴 映 射"
},
"shortcutKeys": {
"button_title": {
"add_delay": "間隔",
"add_duration": "延音",
"add_speed": "倍速",
"exit": "退出",
"next": "下一首",
"pause": "暫停",
"reduce_delay": "- 間隔",
"reduce_duration": "- 延音",
"reduce_speed": "- 倍速",
"repeat": "重複",
"repeat_next": "重複併步過",
"resize": "重載按鍵",
"resume": "繼續",
"start": "播放",
"stop": "停止"
},
"divider1": "音樂快捷鍵",
"divider2": "跟彈快捷鍵",
"head_text": "快捷鍵為本次運行生效,重啟軟件需要重新設置,如不適應請盡快適應",
"patterns0": "本次運行",
"patterns1": "重新",
"patterns2": "重啟",
"patterns3": "盡快適應",
"title": "重 置 快 捷 鍵"
},
"tab": {
"love_success": "收藏成功",
"myFavorite": "收藏",
"myImport": "導入歌曲",
"myTranslate": "已轉換歌曲",
"remove_success": "移除成功",
"search": "搜尋",
"systemMusic": "自帶歌曲",
"translateOriginalMusic": "未轉換歌曲",
"clear": "清空",
"title": "播放列表"
},
"tutorial": {
"chose_music": "請先選擇一首歌曲",
"error": "轉換失敗,請重試",
"error_console": "轉換失敗",
"follow": "開始跟彈",
"no_music": "沒有歌曲",
"now": "當前: {music}",
"save": "保存可視化樂譜到桌面",
"save_desktop": "已保存在桌面"
},
"controller": {
"no_music": "沒有歌曲"
},
"music": {
"chose": "播 放:",
"placeholder1": "間隔",
"placeholder2": "延音",
"play": "選 擇:",
"space": {
"chose0": "系統自帶",
"chose1": "隨機",
"chose2": "自定義",
"mult_speed": "倍速",
"title": "間隔延遲",
"title1": "延音設置"
}
},
"rule": {
"cycle": "循環",
"order": "順序",
"random": "隨機"
}
}
================================================
FILE: sky-music-web/src/renderer/src/main.ts
================================================
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import naive from 'naive-ui'
import i18n from './i18n'
const app = createApp(App)
app.use(store)
app.use(router)
app.use(naive)
app.use(i18n)
app.mount('#app')
================================================
FILE: sky-music-web/src/renderer/src/router/index.ts
================================================
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
const routes: Array = [
{
path: '/',
name: 'home_loader',
component: () => import('../views/home_loader.vue')
},
{
path: '/home',
name: 'home',
component: () => import('../views/home.vue')
},
{
path: '/music',
name: 'music',
component: () => import('../views/music.vue')
},
{
path: '/kube',
name: 'kube',
component: () => import('../views/kube.vue')
},
{
path: '/tutorial',
name: 'tutorial',
component: () => import('../views/tutorial.vue')
},
{
path: '/magicTools',
name: 'magicTools',
component: () => import('../views/magicTools.vue')
},
{
path: '/shortcut',
name: 'shortcut',
component: () => import('../views/shortcutKeys.vue')
},
{
path: '/setting',
name: 'setting',
component: () => import('../views/setting.vue')
},
{
path: '/hwndHandle',
name: 'hwndHandle',
component: () => import('../views/hwndHandle.vue')
},
{
path: '/musicEdit',
name: 'musicEdit',
component: () => import('../views/music_edit.vue')
},
{
path: "/aiSetting",
name: "aiSetting",
component: () => import('../views/ai_setting.vue')
}
]
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes
})
export default router
================================================
FILE: sky-music-web/src/renderer/src/store/index.ts
================================================
import { createStore } from 'vuex'
export default createStore({
state: {
playList:[],
},
getters: {
getPlayList(state) {
return state.playList;
},
getNextPlayMusic(state){
const needReturn = state.playList[0]
state.playList.splice(0, 1)
return needReturn
}
},
mutations: {
setPlayList(state, datas) {
state.playList = datas;
},
addPlayList(state:any, data) {
let addFlag = true
state.playList.forEach(element => {
if(element.truthName === data.truthName){
addFlag = false;
return;
}
});
if(addFlag)
state.playList.push({'name': data.name ,'truthName':data.truthName, 'type': data.type})
},
removePlayList(state:any, index:number){
console.log("删除下标",index)
state.playList.splice(index, 1)
},
clearPlayList(state:any){
state.playList = []
}
},
actions: {
},
modules: {}
})
================================================
FILE: sky-music-web/src/renderer/src/utils/configStore.ts
================================================
import { sendData } from "@renderer/utils/fetchUtils";
export enum CONFIG_TYPE {
DELAY_STATUS = 'DELAY_STATUS', //间隔延迟状态
DELAY_SPEED = 'DELAY_SPEED', //延迟设置
DELAY_RANDOM_START = 'DELAY_RANDOM_START',
DELAY_RANDOM_END = 'DELAY_RANDOM_END',
DURATION_STATUS = 'URATION_STATUS', //延音设置状态
DURATION_SPEED = 'DURATION_SPEED', //延音设置
DURATION_RANDOM_START = 'DURATION_RANDOM_START',
DURATION_RANDOM_END = 'DURATION_RANDOM_END',
PLAY_SPEED = 'PLAY_SPEED', //播放速度
SHORTCUT_KEY = 'SHORTCUT_KEY' //快捷键配置
}
export enum CONFIG_STATUS_TYPE {
SYSTEM = 'system',
RANDOM = 'random',
CUSTOM = 'custom',
}
const configStore = {
setItem: (key, value) => {
// @ts-ignore (define in dts)
window.elStore.setElStore(key, value)
},
getItem: (key) => {
// @ts-ignore (define in dts)
const value = window.elStore.getElStore(key)
return value
}
}
export default configStore
// 进入程序后,设置缓存快捷键配置
export const setConfigShortcutKey = ()=>{
if(!configStore.getItem(CONFIG_TYPE.SHORTCUT_KEY)) {
return
}
const shortcutKeyValues = configStore.getItem(CONFIG_TYPE.SHORTCUT_KEY);
const follow = shortcutKeyValues.follow_key;
const music = shortcutKeyValues.music_key;
const followString = (follow.repeat + follow.repeat_next + follow.resize);
const musicString = (music.next + music.pause + music.resume + music.start + music.stop + music.add_duration + music.reduce_duration + music.add_delay + music.reduce_delay + music.add_speed + music.reduce_speed);
const followKey = {
tap_key: "yuiophjkl;nm,./",
repeat: follow.repeat.toLowerCase(),
repeat_next: follow.repeat_next.toLowerCase(),
resize: follow.resize.toLowerCase(),
exit: follow.exit.toLowerCase(),
// 使用计算后的字符串
string: followString.toLowerCase()
};
const musicKey = {
next: music.next.toLowerCase(),
pause: music.pause.toLowerCase(),
resume: music.resume.toLowerCase(),
start: music.start.toLowerCase(),
stop: music.stop.toLowerCase(),
string: musicString.toLowerCase(),
add_duration: music.add_duration.toLowerCase(),
reduce_duration: music.reduce_duration.toLowerCase(),
add_delay: music.add_delay.toLowerCase(),
reduce_delay: music.reduce_delay.toLowerCase(),
add_speed: music.add_speed.toLowerCase(),
reduce_speed: music.reduce_speed.toLowerCase(),
};
sendData("config_operate", {
"operate": "set",
"name": "shortcutStruct",
"value": {
"follow_key": followKey,
"music_key": musicKey
}
})
}
================================================
FILE: sky-music-web/src/renderer/src/utils/fetchUtils.ts
================================================
let baseUrl = "http://127.0.0.1:9899/"
export function getList(listName,searchStr){
return fetch(baseUrl+"?listName="+listName+"&searchStr="+searchStr)
.then(response => response.json())
.then(data =>{return data})
.catch(error => console.error(error));
}
export function getData(url){
return fetch(baseUrl+url)
.then(response => response.json())
.then(data =>{return data})
.catch(error => console.error(error));
}
export function sendData(url, data) {
console.log("data",data)
return fetch(baseUrl + url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.then(response => response.json())
.then(data =>{return data})
.catch(error => console.error(error));}
export function setConfig(name,value){
return fetch(baseUrl + "config_operate", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name":name,
"value":value,
"operate":'set'
}),
})
.then(response => response.json())
.then(data =>{return data})
.catch(error => console.error(error));
}
export function getWWWData(url){
return fetch(url)
.then(response => response.text())
.then(data =>{return data})
.catch(error => console.error(error));
}
================================================
FILE: sky-music-web/src/renderer/src/views/ai_setting.vue
================================================
AIGC Settings
{{ t('ai_setting.buttons.set_token') }}
:3
================================================
FILE: sky-music-web/src/renderer/src/views/home.vue
================================================
Wind Hide
{{ t("home.tutorial") }}
{{ t("home.qq") }}
{{ t("home.qq_sb") }}
================================================
FILE: sky-music-web/src/renderer/src/views/home_loader.vue
================================================
================================================
FILE: sky-music-web/src/renderer/src/views/hwndHandle.vue
================================================
W A R N I N G
{{t("hwnd_handle.refresh")}}
{{t("hwnd_handle.reset_sky")}}
{{t("hwnd_handle.now_hwnd")}} {{ nowHwnd == null ? 'Nothing' : nowHwnd }}
================================================
FILE: sky-music-web/src/renderer/src/views/kube.vue
================================================
🤔
🧐
{{ t("kube.odd") }}
{{ t("kube.dual") }}
🤔
🧐
{{ t("kube.half_t") }}
{{ t("kube.all_t") }}
🤔
🧐
{{ t("kube.other_key") }}
{{ t("kube.range_key") }}
🤔
🧐
人声分离
原版转换
Tips:人声分离速度会很慢
{{ t("kube.Streng_filter") }}
{{ t("kube.dynamic_merge") }}
-
{{ t("kube.transfer_progress") }}
{{ t("kube.chose_music") }}
{{ t("kube.star_transfer") }}
================================================
FILE: sky-music-web/src/renderer/src/views/magicTools.vue
================================================
⚠
Y
U
I
O
P
H
J
K
L
;
N
M
,
.
/
{{ button.context }}
{{ button.context }}
{{ t('magic_tools.buttons.check') }}
{{ t('magic_tools.buttons.abaaba') }}
置信度变更
{{ button.context }}
================================================
FILE: sky-music-web/src/renderer/src/views/music.vue
================================================
{{ t("music.chose") + nowPlayMusic + '' }}
{{ t("music.play") + nowSelectMusic + '' }}
{{nowCurrentTime}} / {{nowTotalTime}}
{{t("music.space.title") }}
{{t("music.space.chose0") }}
{{t("music.space.chose1") }}
{{t("music.space.chose2") }}
-
{{t("music.space.title1") }}
{{t("music.space.chose0") }}
{{t("music.space.chose1") }}
{{t("music.space.chose2") }}
-
{{t("music.space.mult_speed") }}
{{t('tab.clear')}}
================================================
FILE: sky-music-web/src/renderer/src/views/music_edit.vue
================================================
{{ t("music_edit.total_column", {length: notes.length}) }}
{{ t("music_edit.total_time", {length: `${Math.floor(timeNotes.reduce((acc, currentValue) => acc + currentValue, 0) / 60000)}分 ${Math.floor((timeNotes.reduce((acc, currentValue) => acc + currentValue, 0) % 60000) / 1000)}秒`}) }}
{{ t("music_edit.tips.pause") }}
{{ t("music_edit.tips.play") }}
{{ t("music_edit.tips.pre_column") }}
{{ t("music_edit.tips.next_column") }}
{{ t("music_edit.tips.afer_add_column") }}
{{ t("music_edit.tips.delete_now_column") }}
{{ t("music_edit.tips.pause") }}
{{ t("music_edit.tips.upend") }}
{{ t("music_edit.tips.play_now_column") }}
{{ t("music_edit.tips.copy_now_to_head") }}
{{ t("music_edit.tips.copy_now_to_pre") }}
{{ t("music_edit.tips.copy_now_to_next") }}
{{ t("music_edit.tips.copy_now_to_end") }}
{{ t("music_edit.tips.select_in_music") }}
{{ t("music_edit.tips.save_sheet") }}
{{ t("music_edit.tips.clear_sheet") }}
{{ t("music_edit.tips.upload_sheet") }}
{{ t("music_edit.title.change_send_key_in_game") }}
{{ t("music_edit.title.func_area_tip") }}
{{ t("music_edit.title.sheet_area_color") }}
{{ t("music_edit.title.columnDownDuration") }}
{{ t("music_edit.title.columnAfterDuration") }}
{{ t("music_edit.title.sheet_name") }}
{{ t("music_edit.title.add_new_count_column") }}
{{ t("music_edit.title.add_new_druation_column") }}
{{ t("music_edit.title.add_new_after_column") }}
{{ t("music_edit.title.global_down") }}
{{ t("music_edit.title.set") }}
{{ t("music_edit.title.global_after") }}
{{ t("music_edit.title.set") }}
================================================
FILE: sky-music-web/src/renderer/src/views/setting.vue
================================================
{{t('setting.reset_map')}}
{{"清空所有映射"}}
{{t('setting.key_map')}}
{{ key.name }}
{{ key.name }}
================================================
FILE: sky-music-web/src/renderer/src/views/shortcutKeys.vue
================================================
{{ t("shortcutKeys.title") }}
{{ t("shortcutKeys.divider1") }}
{{ shortcut.name }}
{{ t("shortcutKeys.divider2") }}
{{ shortcut.name }}
================================================
FILE: sky-music-web/src/renderer/src/views/tutorial.vue
================================================
{{ t('tutorial.now', { music:nowPlayMusic }) }}
{{ t('tutorial.follow') }}
{{ t('tutorial.save') }}
================================================
FILE: sky-music-web/tsconfig.json
================================================
{
"files": [],
"references": [{ "path": "./tsconfig.node.json" }, { "path": "./tsconfig.web.json" }]
}
================================================
FILE: sky-music-web/tsconfig.node.json
================================================
{
"extends": "@electron-toolkit/tsconfig/tsconfig.node.json",
"include": ["electron.vite.config.*", "src/main/**/*", "src/preload/**/*"],
"compilerOptions": {
"composite": true,
"types": ["electron-vite/node"]
}
}
================================================
FILE: sky-music-web/tsconfig.web.json
================================================
{
"extends": "@electron-toolkit/tsconfig/tsconfig.web.json",
"include": [
"src/renderer/src/env.d.ts",
"src/renderer/src/**/*",
"src/renderer/src/**/*.vue",
"src/preload/*.d.ts"
],
"compilerOptions": {
"composite": true,
"baseUrl": ".",
"paths": {
"@renderer/*": [
"src/renderer/src/*"
]
},
"noUnusedLocals":false
}
}
================================================
FILE: template-resources/myFavorite/.keep
================================================
================================================
FILE: template-resources/myImport/.keep
================================================
================================================
FILE: template-resources/myTranslate/.keep
================================================
================================================
FILE: template-resources/systemTools/drawTool/.keep
================================================
================================================
FILE: template-resources/systemTools/modelData/demoScheenshot/.keep
================================================
================================================
FILE: template-resources/systemTools/scriptTemplate/.keep
================================================
================================================
FILE: template-resources/translateMID/.keep
================================================
================================================
FILE: template-resources/translateOriginalMusic/.keep
================================================