Showing preview only (755K chars total). Download the full file or copy to clipboard to get everything.
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
<p align="center">
<a href="https://github.com/windhide/SkyMusicPlay-for-Windows"><img src="https://files.superbed.cc/store/images/7e/7a/67bfcd95d0e0a243d4067e7a.png" width="256" height="256" alt="SkyMusicPlay-for-Windows"></a>
<h1 align = "center">星星弹琴软件</h1>
<div align = "center">
<a href="https://www.kdocs.cn/l/cpTkEdhxIRob" target="_blank">教程文档</a> ·
<a href="https://github.com/windhide/SkyMusicPlay-for-Windows/releases">点我下载</a>
</div>
## 丨安装提醒
>
> **安装请`关闭杀毒软件`进行**
>
> 支持PC端,也支持模拟器端
>
> 🚧目前还在施工中,功能快速迭代中...🚧
>
>
> ✨如果需要相关创意功能欢迎在issues中提出✨
>
</details>
## 丨项目开发环境
- **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 = """
<!DOCTYPE html>
<html lang="en_US">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>{{ sheet_name }}</title>
<style type="text/css">
@media (prefers-reduced-motion: reduce){html { scroll-behavior: auto; }}
html { scroll-behavior: smooth; }
#navigation { border: none; }
#transcript { margin: 0 0; }
body{font-family: "Noto Sans", "Noto Sans CJK JP", "Noto Sans CJK KR", "Noto Sans CJK SC", "Noto Sans CJK TC", "Avenir", "Arial", "sans-serif";font-size: 12pt;}
h1{font-size: 1.3em;font-weight: bold;}
.header{font-size: 0.8em;line-height: 50%;}
.line{display: flex;flex-direction: row;flex-wrap: wrap;}
.lyrics{margin-left: 0.5rem;margin-bottom: 0.35rem;border: 0px black solid;width: 8.11em;font-size: 0.8em;text-align: center;}
hr{border: none;margin-left: 0;padding: 0;margin-right: 1.8em;width: 100vw;}
hr.sep { border-top: thin solid lightgray; width: 93vw; }
hr.solid { border-top: thin solid black; }
hr.double { border-top: medium double black; }
hr.dashed { border-top: thin dashed black; }
.instr.silent, .instr.broken{border-width: 0;background-color: transparent;}
.repeat, .num{font-size: 0.8em;align-self: flex-end;margin: 2px;}
.num{color: grey;padding-left: 2em;}
.broken{color: red;font-weight: bold;}
@media (prefers-color-scheme: dark){body { background-color: #282828; }.instr.harp { border-color: white; }p, body, td, text.headers { color: white; }}
.instr{margin-left: 0.5rem;margin-bottom: 0.35rem;border-radius: 5px;border: 1px black solid;display: grid;width: fit-content;}
.instr.harp {grid-template-columns: repeat(5, 1.3em); grid-template-rows: repeat(3, 1.4em);}
.instr.drum {grid-template-columns: repeat(4, 1.3em); grid-template-rows: repeat(2, 1.4em);}
.r1, .r2, .r3, [class^="q"]{border-radius: 4px;margin: 2px;width: 1em;height: 1em;justify-self: center;align-self: center;}
d1, d2, d3, silence { background-position: 50%; }
.r1 { background-color: limegreen; }
.r2 { background-color: deepskyblue; }
.r3 { background-color: deeppink; }
crc { background-image: url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 91 91'%3E%3Ccircle fill='transparent' stroke='white' stroke-width='5px' cx='45.4' cy='45.4' r='25.5'%3E%3C/circle%3E%3C/svg%3E"); }
dmn { background-image: url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 123.40601 123.40601'%0A%3E%3Cg transform='translate(-52.444184,-69.21989)'%0A%3E%3Crect style='fill:none;stroke:white ;stroke-width:5px;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:100;stroke-dasharray:none;' width='64.633926' height='64.633926' x='140.97375' y='-20.454748' transform='rotate(45)' /%3E%3C/g%3E%3C/svg%3E"); }
crdm { background-image: url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 123.40601 123.40601'%0A%3E%3Cg transform='translate(-52.444184,-69.21989)' %3E%3Crect style='fill:none;stroke:white;stroke-width:5px;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1' width='64.633926' height='64.633926' x='140.97375' y='-20.454748' transform='rotate(45)' /%3E%3Ccircle style='fill:none;stroke:white;stroke-width:5px;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1' cx='114.12409' cy='130.82843' r='31.938707' /%3E%3C/g%3E%3C/svg%3E"); }
d1 { background-image: url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 123.40601 123.40601'%3E%3Ccircle stroke='none' fill='rgb(194,240,194)' cx='61.5' cy='61.5' r='14'%3E%3C/circle%3E%3C/svg%3E"); }
d2 { background-image: url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 123.40601 123.40601'%3E%3Ccircle stroke='none' fill='rgb(179,236,255)' cx='61.5' cy='61.5' r='14'%3E%3C/circle%3E%3C/svg%3E"); }
d3 { background-image: url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 123.40601 123.40601'%3E%3Ccircle stroke='none' fill='rgb(255,185,223)' cx='61.5' cy='61.5' r='14'%3E%3C/circle%3E%3C/svg%3E"); }
dn { background-image: url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 123.40601 123.40601'%3E%3Ccircle stroke='none' fill='rgb(167,167,167)' cx='61.5' cy='61.5' r='14'%3E%3C/circle%3E%3C/svg%3E"); }
silence { background-image: url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 123.40601 123.40601'%3E%3Ccircle stroke='none' fill='rgb(167,167,167)' cx='61.5' cy='61.5' r='28'%3E%3C/circle%3E%3C/svg%3E"); }
</style></head>
<body>
<h1>{{ sheet_name }}</h1>
<p class="header"><b>文件由:WindHide's Sky Music弹琴软件创建</b> <a href="https://github.com/windhide/SkyMusicPlay-for-Windows" style="color: pink;">https://github.com/windhide/SkyMusicPlay-for-Windows</a></p>
<p class="header"><b></b></p>
</div>
<div id="transcript">
{% for line in content %}
<hr class="sep">
<div class="line" id="{{ line.id }}">
{% for instr in line.instruments %}
<div class="instr harp" id="{{ instr.id }}">
{% for note in instr.notes %}
<{{ note.type }} class="{{ note.class }}"></{{ note.type }}>
{% endfor %}
</div>
{% endfor %}
</div>
{% endfor %}
</div>
</body></html>
"""
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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
</dict>
</plist>
================================================
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
================================================
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Sky_Music</title>
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0); /* 半透明背景 */
border-radius: 25px; /* 圆角设置 */
overflow: hidden;
border: .5px solid #eeeeee30;
box-sizing: border-box;
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
================================================
FILE: sky-music-web/src/renderer/src/App.vue
================================================
<template>
<n-config-provider :theme="darkTheme" :style="{ opacity : transparency_number}" >
<n-flex id="drag-area" justify="end" style="position: fixed; z-index: 200; right: 18px" :style="{ width: collapsed ? '90%' : '80%'}">
<n-button text type="warning" size="large" style="margin-top: 12px; font-size: 20px;" @click="syncSheetName">
<n-icon size="25px">
<ArrowSync16Filled />
</n-icon>
</n-button>
<n-popselect v-model:value="nowLang" :options="options" trigger="click" @update:value="changeLang" scrollable>
<n-button text size="large" color="#A3F6EC" style="margin-top: 12px; font-size: 20px;" :round="false">
<n-icon size="25px">
<Language />
</n-icon>
</n-button>
</n-popselect>
<n-popover style="border-radius: 17px; --n-color: rgba(47,47,55,1)" trigger="click">
<template #trigger>
<n-button text size="large" color="#A3F6EC" style="margin-top: 12px; font-size: 20px;" :round="false">
<n-icon size="25px">
<ColorPaletteOutline />
</n-icon>
</n-button>
</template>
<n-gradient-text gradient="linear-gradient(90deg, rgb(242,201,196), rgb(221,242,196))">{{ t('main.transparency') }}</n-gradient-text>
<n-slider v-model:value="transparency_number" :step="0.01" :max="1" :min="0" style="width: 200px;"/>
<n-gradient-text gradient="linear-gradient(90deg, rgb(242,201,196), rgb(221,242,196))">{{ t('main.color') }}</n-gradient-text>
<n-color-picker :show-alpha="false" v-model:value="colorPick"/>
</n-popover>
<n-popover style="border-radius: 17px; --n-color: rgba(47,47,55,1)" trigger="click">
<template #trigger>
<n-button text size="large" color="#D0BDF4" style="margin-top: 12px; font-size: 20px;" :round="false">
<n-icon size="25px">
<Settings48Regular />
</n-icon>
</n-button>
</template>
<n-switch size="small" v-model:value="is_compatibility_mode" @update:value="CompatibilityModeChange" :rail-style="railStyle" :round="false">
<template #checked>
<p style="color: rgba(94, 104, 81, 0.75);">{{ t('main.compatible') }}</p>
</template>
<template #unchecked>
<p style="color: rgba(94, 104, 81, 0.75);">{{ t('main.backend') }}</p>
</template>
</n-switch>
<br>
<n-switch size="small" v-model:value="isPostW" @update:value="PostWChange" :rail-style="railStyle" v-show="is_compatibility_mode != true" :round="false">
<template #checked>
<p style="color: rgba(94, 104, 81, 0.75);">{{ t('main.queue') }}</p>
</template>
<template #unchecked>
<p style="color: rgba(94, 104, 81, 0.75);">{{ t('main.cut') }}</p>
</template>
</n-switch>
<br>
<n-switch size="small" v-model:value="isRunnable" @update:value="RunnableChange" :rail-style="railStyle" v-show="is_compatibility_mode != true" :round="false">
<template #checked>
<p style="color: rgba(94, 104, 81, 0.75);">{{ t('main.multi_core') }}</p>
</template>
<template #unchecked>
<p style="color: rgba(94, 104, 81, 0.75);">{{ t('main.single_core') }}</p>
</template>
</n-switch>
</n-popover>
<n-button text :dashed="fixDashed" size="large" color="#F2C9C4" style="margin-top: 12px; font-size: 20px;" @click="fixHandle">
<n-icon size="25px">
<Pin48Regular v-if="fixDashed" />
<Pin48Filled v-else />
</n-icon>
</n-button>
<n-button text type="warning" size="large" style="margin-top: 12px; font-size: 20px;" @click="openFileHandle">
<n-icon size="25px">
<ImageOutline />
</n-icon>
</n-button>
<n-button text type="info" size="large" style="margin-top: 12px; font-size: 25px;" @click="miniHandle">
<n-icon>
<RemoveSharp />
</n-icon>
</n-button>
<n-button text type="error" size="large" style="margin-top: 12px; font-size: 25px;" @click="closeHandle">
<n-icon>
<Close />
</n-icon>
</n-button>
</n-flex>
<n-spin :show="show" :size="90" stroke="#F2E8C4">
<n-message-provider>
<n-dialog-provider>
<n-space vertical>
<n-layout>
<n-layout has-sider>
<n-layout-sider v-show="route.fullPath.indexOf('keyboard') == -1" bordered show-trigger
collapse-mode="width" :collapsed-width="64" :width="150" :native-scrollbar="false" @update:collapsed="collapsedHandle"
:style="{
height: '100vh',
backgroundColor: colorPick+ ' !important'
}"
>
<n-menu :collapsed-width="64" :collapsed-icon-size="23" :options="menuOptions"
@update:value="clickMenu" />
</n-layout-sider>
<n-layout
:style="{
padding: '30px 20px 0px 20px',
backgroundColor: colorPick+' !important'
}"
>
<router-view />
</n-layout>
</n-layout>
</n-layout>
</n-space>
</n-dialog-provider>
</n-message-provider>
<template #description> <div style="color: #F2E8C4;">{{ t('main.loading') }}</div> </template>
</n-spin>
</n-config-provider>
</template>
<script lang="ts" setup>
import type { Component, CSSProperties } from 'vue'
import { h, onMounted, ref, nextTick, computed } from 'vue'
import { NIcon, darkTheme, NMessageProvider } from 'naive-ui'
import { getData, sendData } from '@renderer/utils/fetchUtils'
import {
CubeSharp,
Home,
MusicalNotes,
GameController,
Close,
RemoveSharp,
Flask,
ImageOutline,
Settings,
PlanetSharp,
ColorPaletteOutline,
PulseSharp,
Language,
LogoGoogle
} from '@vicons/ionicons5'
import {
Pin48Regular,
Pin48Filled,
Settings48Regular,
Compose24Filled,
ArrowSync16Filled
} from '@vicons/fluent'
import router from '@renderer/router'
import { useRoute } from 'vue-router'
import { useI18n } from "vue-i18n";
const { t, locale } = useI18n();
const route = useRoute()
const collapsed = ref(false)
const is_compatibility_mode = ref(false)
const isPostW = ref(true)
const isRunnable = ref(true)
const transparency_number = ref(1.00)
const colorPick = ref("#101014")
const nowLang = ref('zh_cn')
const options = [
{
label: "简体中文",
value: "zh_cn"
},
{
label: "文言文",
value: "zh_classical"
},
{
label: "繁體中文",
value: "zh_tw"
},
{
label: "English",
value: "en"
},
{
label: "日本語",
value: "jp"
},
{
label: "한국어",
value: "ko"
}
];
function fixHandle() {
if (fixDashed.value) {
window.api.setAlwaysOnTop();
fixDashed.value = false
} else {
window.api.setAlwaysOnTop();
fixDashed.value = true
}
}
function railStyle({ focused, checked }){
const style: CSSProperties = {}
if (checked) {
style.background = '#F2C9C4'
if (focused) {
style.boxShadow = '0 0 0 2px #F2C9C440'
}
}
else {
style.background = '#F2E8C4'
if (focused) {
style.boxShadow = '0 0 0 2px #F2E8C440'
}
}
return style
}
function openFileHandle() {
sendData('openFiles',{
"operate":"images"
})
}
function syncSheetName() {
getData('syncSheetName').then(_res=>{
window.api.sync_sheet_2_el()
})
}
function miniHandle() {
window.api.mini()
}
function closeHandle() {
window.api.close()
}
function renderIcon(icon: Component) {
return () => h(NIcon, null, { default: () => h(icon) });
}
const collapsedHandle = (isCoolapsed: boolean) => {
console.log(isCoolapsed)
collapsed.value = isCoolapsed
}
let show = ref(true)
let fixDashed = ref(true)
const menuOptions = computed(() => [
{
label: t('main.menu.home'),
key: "home",
icon: renderIcon(Home),
},
{
label: t('main.menu.music'),
key: "music",
icon: renderIcon(MusicalNotes),
},
{
label: t('main.menu.tutorial'),
key: "tutorial",
icon: renderIcon(GameController),
},
{
label: t('main.menu.kube'),
key: "kube",
icon: renderIcon(CubeSharp),
},
{
label: t('main.menu.shortcut'),
key: "shortcut",
icon: renderIcon(PlanetSharp),
},
{
label: t('main.menu.musicEdit'),
key: "musicEdit",
icon: renderIcon(Compose24Filled),
},
{
key: "kube",
type: "divider"
},
{
label: t("main.menu.aisetting"),
key: "aiSetting",
icon: renderIcon(LogoGoogle)
},
{
label: t('main.menu.setting'),
key: "setting",
icon: renderIcon(Settings),
},
{
label: t('main.menu.hwndHandle'),
key: "hwndHandle",
icon: renderIcon(PulseSharp),
},
{
label: t('main.menu.magicTools'),
key: "magicTools",
icon: renderIcon(Flask),
},
]);
const clickMenu = (key: string) => {
router.push({ name: key });
};
let checkInterval = setInterval(() => {
getData("check").then(res => {
if (res === undefined) return
show.value = !res
clearInterval(checkInterval)
LoadData()
window.api.sync_el_2_sheet()
router.push({ name: 'home', query: { show: 1 } });
});
}, 500)
function CompatibilityModeChange(value: boolean){
sendData("config_operate",{
operate:"set",
name:"compatibility_mode",
value
})
}
function PostWChange(value: boolean){
sendData("config_operate",{
operate:"set",
name:"is_post_w",
value
})
}
function RunnableChange(value: boolean){
sendData("config_operate",{
operate: "set",
name: "cpu_type",
value
})
}
function changeLang(value: string) {
// 更新locale值并保存到localStorage
locale.value = value
localStorage.setItem('sky-music-language', value)
show.value = true
nextTick(async () => {
const newMenuOptions = [
{
label: t('main.menu.home'),
key: "home",
icon: renderIcon(Home),
},
{
label: t('main.menu.music'),
key: "music",
icon: renderIcon(MusicalNotes),
},
{
label: t('main.menu.tutorial'),
key: "tutorial",
icon: renderIcon(GameController),
},
{
label: t('main.menu.kube'),
key: "kube",
icon: renderIcon(CubeSharp),
},
{
label: t('main.menu.shortcut'),
key: "shortcut",
icon: renderIcon(PlanetSharp),
},
{
label: t('main.menu.musicEdit'),
key: "musicEdit",
icon: renderIcon(Compose24Filled),
},
{
key: "kube",
type: "divider"
},
{
label: t("main.menu.aisetting"),
key: "aiSetting",
icon: renderIcon(LogoGoogle)
},
{
label: t('main.menu.setting'),
key: "setting",
icon: renderIcon(Settings),
},
{
label: t('main.menu.hwndHandle'),
key: "hwndHandle",
icon: renderIcon(PulseSharp),
},
{
label: t('main.menu.magicTools'),
key: "magicTools",
icon: renderIcon(Flask),
}
]
Object.assign(menuOptions, newMenuOptions)
const currentRoute = route.name
// 跳转到设置页再跳回原页面,切换过程中显示Loading
let target = currentRoute
await router.push({ name: 'setting' })
await nextTick()
await router.push({ name: target })
show.value = false
})
}
function LoadData(){
sendData("config_operate",{
operate: "cpu_type"
}).then(res=>{
isRunnable.value = res
})
}
onMounted(() => {
// 从localStorage获取保存的语言设置,初始化当前语言
const savedLanguage = localStorage.getItem('sky-music-language')
if (savedLanguage) {
nowLang.value = savedLanguage
locale.value = savedLanguage
}
const dragArea = document.getElementById('drag-area')
if (dragArea) {
let startX, startY
dragArea.addEventListener('mousedown', (event) => {
startX = event.clientX
startY = event.clientY
window.electron.onMouseDown(startX, startY)
const onMouseMove = (moveEvent) => {
const deltaX = moveEvent.clientX - startX
const deltaY = moveEvent.clientY - startY
window.electron.onMouseMove(deltaX, deltaY)
}
const onMouseUp = () => {
document.removeEventListener('mousemove', onMouseMove)
document.removeEventListener('mouseup', onMouseUp)
window.electron.onMouseUp()
}
document.addEventListener('mousemove', onMouseMove)
document.addEventListener('mouseup', onMouseUp)
})
} else {
console.error('drag-area element not found')
}
})
</script>
<style scoped>
:deep(.n-menu--vertical){
--n-border-radius: 21px !important;
--n-item-text-color-active: rgb(242,232,196) !important;
--n-item-icon-color-active: rgb(242,232,196) !important;
--n-item-text-color-active-hover: rgb(242,232,196) !important;
--n-item-icon-color: rgba(221,242,196, 0.82) !important;
--n-item-icon-color-hover: rgb(242,201,196) !important;
--n-item-icon-color-active: rgb(242,232,196) !important;
--n-item-icon-color-active-hover: rgb(242,232,196) !important;
--n-item-icon-color-child-active: rgb(242,232,196) !important;
--n-item-icon-color-child-active-hover: rgb(242,232,196) !important;
--n-item-icon-color-collapsed: rgba(255, 255, 255, 0.9);
--n-item-color-active: rgba(242,232,196,0.15) !important;
--n-item-color-active-hover: rgba(242,232,196,0.3) !important;
--n-item-icon-color-collapsed: rgba(221,242,196, 0.82) !important;
}
:deep(.n-menu-item-content){
--n-item-text-color: rgba(221,242,196, 0.82) !important;
--n-item-text-color-hover: rgb(242,201,196) !important;
color: rgba(94, 104, 81, 0.82);
}
:deep(.n-slider-rail__fill){
--n-fill-color-hover: #A3F6EC !important;
background-color: #A3F6EC !important;
}
</style>
<style>
.n-card {
background-color: rgba(242, 201, 196, 0) !important;
}
.n-menu-divider {
margin-bottom: 275px;
background-color: rgba(242, 201, 196, 0);
}
.n-drawer-mask,.n-drawer{
border-radius: 26px !important;
}
</style>
================================================
FILE: sky-music-web/src/renderer/src/component/svg/cr.vue
================================================
<template><svg version="0.0" width="63.876953" height="63.876953" id="svg3639" xmlns="http://www.w3.org/2000/svg"
><path d="M 31.939164,9.4384766 A 22.500162,22.500162 0 0 0 9.4384767,31.939165 22.500162,22.500162 0 0 0 31.939164,54.438477 22.500162,22.500162 0 0 0 54.438476,31.939165 22.500162,22.500162 0 0 0 31.939164,9.4384766 Z m 0,2.5303484 A 19.969496,19.969496 0 0 1 51.908129,31.939165 19.969496,19.969496 0 0 1 31.939164,51.90813 19.969496,19.969496 0 0 1 11.968824,31.939165 19.969496,19.969496 0 0 1 31.939164,11.968825 Z" id="path3629" style="stroke-width:0.983987"/><defs id="defs3633"/></svg></template><script setup lang="ts" />
================================================
FILE: sky-music-web/src/renderer/src/component/svg/dm.vue
================================================
<template><svg version="0.0" width="63.876953" height="63.876953" id="svg3639" xmlns="http://www.w3.org/2000/svg"
><path d="M 30.878793,1.0626133 1.0626133,30.878793 a 1.4999541,1.4999541 89.999124 0 0 3.24e-5,2.121288 L 30.87876,62.814372 a 1.5000459,1.5000459 179.99912 0 0 2.121353,-3.2e-5 L 62.81434,33.000113 a 1.5000459,1.5000459 90.000876 0 0 3.2e-5,-2.121353 L 33.000081,1.0626457 a 1.4999541,1.4999541 8.7588976e-4 0 0 -2.121288,-3.24e-5 z m 2.121284,3.400427 26.413908,26.4157167 a 1.5000513,1.5000513 90.00098 0 1 -3.6e-5,2.121356 L 33.000113,59.413949 a 1.5000513,1.5000513 179.99902 0 1 -2.121356,3.6e-5 L 4.4630403,33.000077 A 1.4999487,1.4999487 89.99902 0 1 4.463004,30.878793 L 30.878793,4.463004 a 1.4999487,1.4999487 9.804247e-4 0 1 2.121284,3.63e-5 z"/></svg></template><script setup lang="ts" />
================================================
FILE: sky-music-web/src/renderer/src/component/svg/dmcr.vue
================================================
<template><svg version="0.0" width="63.876953" height="63.876953" id="svg3639" xmlns="http://www.w3.org/2000/svg"
><path d="M 31.939164,9.4384766 A 22.500162,22.500162 0 0 0 9.4384767,31.939165 22.500162,22.500162 0 0 0 31.939164,54.438477 22.500162,22.500162 0 0 0 54.438476,31.939165 22.500162,22.500162 0 0 0 31.939164,9.4384766 Z m 0,2.5303484 A 19.969496,19.969496 0 0 1 51.908129,31.939165 19.969496,19.969496 0 0 1 31.939164,51.90813 19.969496,19.969496 0 0 1 11.968824,31.939165 19.969496,19.969496 0 0 1 31.939164,11.968825 Z" id="path3629" style="stroke-width:0.983987"/><defs id="defs3633"/><path id="path3635" d="M 30.878793,1.0626133 1.0626133,30.878793 a 1.4999541,1.4999541 89.999124 0 0 3.24e-5,2.121288 L 30.87876,62.814372 a 1.5000459,1.5000459 179.99912 0 0 2.121353,-3.2e-5 L 62.81434,33.000113 a 1.5000459,1.5000459 90.000876 0 0 3.2e-5,-2.121353 L 33.000081,1.0626457 a 1.4999541,1.4999541 8.7588976e-4 0 0 -2.121288,-3.24e-5 z m 2.121284,3.400427 26.413908,26.4157167 a 1.5000513,1.5000513 90.00098 0 1 -3.6e-5,2.121356 L 33.000113,59.413949 a 1.5000513,1.5000513 179.99902 0 1 -2.121356,3.6e-5 L 4.4630403,33.000077 A 1.4999487,1.4999487 89.99902 0 1 4.463004,30.878793 L 30.878793,4.463004 a 1.4999487,1.4999487 9.804247e-4 0 1 2.121284,3.63e-5 z"/></svg></template><script setup lang="ts" />
================================================
FILE: sky-music-web/src/renderer/src/env.d.ts
================================================
/// <reference types="vite/client" />
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
}
========
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
SYMBOL INDEX (195 symbols across 39 files)
FILE: draw-follow-window/draw_server.py
function draw_box_api (line 19) | def draw_box_api(canvas, width, height, position_x, position_y, box_id=N...
function delete_box_api (line 36) | def delete_box_api(canvas, box_id, box_pool=None):
class TransparentBoxWindow (line 47) | class TransparentBoxWindow:
method __init__ (line 48) | def __init__(self, root, width, height, position_x, position_y):
method draw_red_border (line 68) | def draw_red_border(self, width, height, border_thickness=10):
method start_server (line 76) | def start_server(self):
method handle_client (line 88) | def handle_client(self, client):
method process_command_batch (line 117) | def process_command_batch(self, commands):
method change_window_geometry (line 199) | def change_window_geometry(self, width, height, position_x, position_y):
method exit_program (line 226) | def exit_program(self):
FILE: draw-follow-window/draw_socket_demo.py
function send_command (line 8) | def send_command(command):
FILE: sky-music-server/music_compare_repeat.py
function get_file_hash (line 8) | def get_file_hash(file_path, block_size=65536):
function get_file_similarity (line 17) | def get_file_similarity(file1, file2):
function find_similar_files (line 25) | def find_similar_files(input_folder, threshold=0.8):
function process_files (line 66) | def process_files(input_folder, output_folder, threshold=0.8):
FILE: sky-music-server/music_file_process.py
function sanitize_filename (line 10) | def sanitize_filename(name):
function process_file (line 15) | def process_file(file_path, normal_output_folder, encrypted_folder, keyw...
function process_files (line 75) | def process_files(input_folder, normal_output_folder, encrypted_folder, ...
FILE: sky-music-server/sky_music_apis.py
function get_list (line 39) | async def get_list(listName: str, searchStr: str):
function play_operate (line 42) | def play_operate(request: dict):
function get_progress (line 53) | def get_progress():
function create_upload_files (line 63) | async def create_upload_files(file: UploadFile):
function create_upload_files_user (line 70) | async def create_upload_files_user(file: UploadFile):
function translate (line 101) | def translate(request: dict):
function config_operate (line 109) | def config_operate(request: dict):
function follow (line 139) | def follow(request: dict):
function check (line 146) | def check():
function open_browser (line 149) | def open_browser(url: str):
function open_files (line 153) | def open_files(request: dict):
function get_update (line 161) | def get_update():
function auto (line 168) | def auto(request: dict):
function get_path (line 175) | def get_path(request: dict):
function test (line 178) | def test(request: dict):
function aigc (line 201) | def aigc(request: dict):
function register_routes (line 208) | def register_routes(app: FastAPI):
FILE: sky-music-server/sky_music_server.py
function lifespan (line 31) | async def lifespan(app: FastAPI):
FILE: sky-music-server/windhide/auto/auto_thread.py
class HeartFireThread (line 20) | class HeartFireThread(threading.Thread):
method __init__ (line 21) | def __init__(self):
method run (line 26) | def run(self):
method stop (line 84) | def stop(self):
method check_running (line 90) | def check_running(self):
function press_left (line 97) | def press_left():
FILE: sky-music-server/windhide/musicToSheet/aigc_handler_sheet.py
function general_ai (line 25) | def general_ai(model, filename, type_, platform):
function parse_segment_info (line 110) | def parse_segment_info(text):
function match_to_sheet (line 122) | def match_to_sheet(text, filename, bpm, model):
function loadSheetFile (line 144) | def loadSheetFile(type, fileName):
function saveSheetFile (line 158) | def saveSheetFile(song_notes, fileName, bpm, model):
FILE: sky-music-server/windhide/musicToSheet/music2html.py
function generatorSheetHtml (line 72) | def generatorSheetHtml(sheet_name, convert_sheet):
FILE: sky-music-server/windhide/musicToSheet/process_audio.py
function get_dynamic_time_merge_threshold (line 18) | def get_dynamic_time_merge_threshold(bpm):
function get_bpm_from_midi (line 22) | def get_bpm_from_midi(midi_file_path):
function merge_keys (line 28) | def merge_keys(keys):
function process_midi_to_txt (line 33) | def process_midi_to_txt(input_path, output_path, version):
function process_directory_with_progress (line 83) | def process_directory_with_progress(typeStr, output_dir=getResourcesPath...
FILE: sky-music-server/windhide/musicToSheet/transfer_MID.py
function inference (line 4) | def inference(input_path):
FILE: sky-music-server/windhide/musicToSheet/vocals_split.py
function split_vocals (line 8) | def split_vocals(musicFilePath):
FILE: sky-music-server/windhide/playRobot/amd_robot.py
function send_single_key_to_window_task (line 25) | def send_single_key_to_window_task(key, duration):
function send_multiple_key_to_window_task (line 31) | def send_multiple_key_to_window_task(keys, duration):
function send_multiple_key_press (line 41) | def send_multiple_key_press(keys):
function send_multiple_key_release (line 46) | def send_multiple_key_release(keys):
function send_single_key_to_window_follow (line 52) | def send_single_key_to_window_follow(key, duration):
function send_multiple_key_to_window_follow (line 59) | def send_multiple_key_to_window_follow(keys, duration):
function execute_in_thread (line 67) | def execute_in_thread(target, *args, **kwargs):
function send_single_key_to_window (line 74) | def send_single_key_to_window(key, duration):
function send_multiple_key_to_window (line 81) | def send_multiple_key_to_window(keys, duration):
function playMusic (line 88) | def playMusic(fileName, type):
function playMusic_edit (line 96) | def playMusic_edit(text):
function resume (line 104) | def resume():
function pause (line 109) | def pause():
function stop (line 114) | def stop():
function mouse_move_to (line 124) | def mouse_move_to(x: int, y: int):
function key_press (line 134) | def key_press(key: str):
function key_down (line 154) | def key_down(key: str):
function key_up (line 171) | def key_up(key: str):
function mouse_wheel_scroll (line 188) | def mouse_wheel_scroll(operator):
function set_us_keyboard_layout (line 206) | def set_us_keyboard_layout():
FILE: sky-music-server/windhide/playRobot/intel_robot.py
function send_single_key_to_window_task (line 23) | def send_single_key_to_window_task(key, duration):
function send_multiple_key_to_window_task (line 29) | def send_multiple_key_to_window_task(keys, duration):
function send_single_key_to_window (line 37) | def send_single_key_to_window(key, duration):
function send_multiple_key_to_window (line 44) | def send_multiple_key_to_window(keys, duration):
function send_single_key_to_window_follow (line 51) | def send_single_key_to_window_follow(key, duration):
function send_multiple_key_to_window_follow (line 57) | def send_multiple_key_to_window_follow(keys, duration):
function playMusic (line 65) | def playMusic(fileName, type):
function playMusic_edit (line 73) | def playMusic_edit(text):
function resume (line 81) | def resume():
function pause (line 86) | def pause():
function stop (line 91) | def stop():
function mouse_move_to (line 99) | def mouse_move_to(x: int, y: int):
function key_press (line 112) | def key_press(key: str):
function key_down (line 132) | def key_down(key: str):
function key_up (line 149) | def key_up(key: str):
function mouse_wheel_scroll (line 165) | def mouse_wheel_scroll(operator):
function set_us_keyboard_layout (line 182) | def set_us_keyboard_layout():
FILE: sky-music-server/windhide/static/global_variable.py
class GlobalVariable (line 1) | class GlobalVariable:
FILE: sky-music-server/windhide/thread/amd_play_thread.py
class ControlledThread (line 9) | class ControlledThread:
method __init__ (line 10) | def __init__(self):
method _run (line 16) | def _run(self):
method format_time (line 58) | def format_time(self, milliseconds):
method start (line 71) | def start(self):
method pause (line 80) | def pause(self):
method resume (line 83) | def resume(self):
method stop (line 86) | def stop(self):
FILE: sky-music-server/windhide/thread/follow_process_thread.py
function run_follow_process (line 13) | def run_follow_process():
function stop_follow_process (line 31) | def stop_follow_process():
FILE: sky-music-server/windhide/thread/follow_thread.py
function get_key_string (line 20) | def get_key_string(key):
function on_press (line 28) | def on_press(key):
function key_release (line 62) | def key_release(key):
function get_next_sheet_demo (line 107) | def get_next_sheet_demo(operator):
function startThread (line 144) | def startThread():
FILE: sky-music-server/windhide/thread/frame_alive_thread.py
function is_process_running (line 7) | def is_process_running(process_name):
function monitor_process (line 12) | def monitor_process(process_name):
FILE: sky-music-server/windhide/thread/hwnd_check_thread.py
function safe_enum_windows (line 16) | def safe_enum_windows(callback):
function get_exe_name_from_hwnd (line 36) | def get_exe_name_from_hwnd(hwnd):
function find_window_by_class (line 49) | def find_window_by_class(class_name):
function update_window_handle (line 54) | def update_window_handle():
function start_thread (line 81) | def start_thread():
FILE: sky-music-server/windhide/thread/intel_play_thread.py
class ControlledThread (line 9) | class ControlledThread:
method __init__ (line 10) | def __init__(self):
method _run (line 16) | def _run(self):
method format_time (line 58) | def format_time(self, milliseconds):
method start (line 71) | def start(self):
method pause (line 80) | def pause(self):
method resume (line 83) | def resume(self):
method stop (line 86) | def stop(self):
FILE: sky-music-server/windhide/thread/queue_thread.py
function music_start_tasks (line 12) | def music_start_tasks():
FILE: sky-music-server/windhide/thread/shortcut_thread.py
function get_key_string (line 14) | def get_key_string(key):
function on_press (line 24) | def on_press(key):
function on_client_connect (line 32) | def on_client_connect(client, server):
function on_client_disconnect (line 35) | def on_client_disconnect(client, server):
function startThread (line 39) | def startThread():
FILE: sky-music-server/windhide/utils/auto_util.py
function auto_click_fire (line 6) | def auto_click_fire():
function shutdown (line 13) | def shutdown():
FILE: sky-music-server/windhide/utils/command_util.py
function start_process (line 19) | def start_process():
function add_window_key (line 25) | def add_window_key(key):
function del_window_key (line 38) | def del_window_key(key):
function clear_window_key (line 41) | def clear_window_key(keys):
function update_key (line 45) | def update_key():
function resize_and_reload_key (line 48) | def resize_and_reload_key():
function quit_window (line 90) | def quit_window():
function wait_for_server (line 97) | def wait_for_server(host, port, max_retries=30, delay=2):
function send_command (line 109) | def send_command(command):
FILE: sky-music-server/windhide/utils/config_util.py
function set_config (line 9) | def set_config(request: dict):
function get_config (line 52) | def get_config(request: dict):
function favorite_music (line 56) | def favorite_music(request: dict):
function convert_sheet (line 62) | def convert_sheet(request: dict):
function drop_file (line 69) | def drop_file(request: dict):
FILE: sky-music-server/windhide/utils/hook_util.py
function sout_null (line 9) | def sout_null():
function cpu_check (line 15) | def cpu_check():
FILE: sky-music-server/windhide/utils/hwnd_utils.py
function get_running_apps (line 10) | def get_running_apps():
function get_running_apps_by_struct (line 37) | def get_running_apps_by_struct(check_struct):
FILE: sky-music-server/windhide/utils/ocr_follow_util.py
function set_next_sheet (line 25) | def set_next_sheet(request: dict):
function get_next_sheet (line 36) | def get_next_sheet(request: dict):
function load_key_model (line 52) | def load_key_model():
function get_key_position (line 62) | def get_key_position(conf, threshold=10):
function test_key_model_position (line 141) | def test_key_model_position(conf):
function open_follow (line 156) | def open_follow():
FILE: sky-music-server/windhide/utils/ocr_heart_utils.py
function load_model (line 22) | def load_model():
function merge_boxes (line 31) | def merge_boxes(boxes, confs, max_distance=50):
function get_friend_model_position (line 75) | def get_friend_model_position(conf, isTest=False, max_distance=50):
function get_window_screenshot_friend (line 135) | def get_window_screenshot_friend():
FILE: sky-music-server/windhide/utils/ocr_normal_utils.py
function resetGameFrame (line 12) | def resetGameFrame():
function get_window_screenshot (line 18) | def get_window_screenshot():
function get_system_dpi (line 28) | def get_system_dpi():
function get_game_position (line 34) | def get_game_position():
FILE: sky-music-server/windhide/utils/path_util.py
function matchKey (line 11) | def matchKey(key):
function convert_notes_to_delayed_format (line 15) | def convert_notes_to_delayed_format(fileName, type):
function convert_json_to_play (line 49) | def convert_json_to_play(song_notes):
function getResourcesPath (line 71) | def getResourcesPath(file):
function getTypeMusicList (line 84) | def getTypeMusicList(type, searchStr=None):
function process_sheet_rename_time (line 109) | def process_sheet_rename_time(isImportOrTranslate = False):
function format_time (line 167) | def format_time(milliseconds):
function detect_encoding (line 180) | def detect_encoding(file_path):
FILE: sky-music-server/windhide/utils/play_util.py
function detect_encoding (line 7) | def detect_encoding(file_path):
function start (line 12) | def start(request: dict):
function pause (line 15) | def pause():
function stop (line 27) | def stop():
function resume (line 40) | def resume():
FILE: sky-music-server/windhide/utils/sheet_decrypt_util.py
function decrypt_sheet (line 4) | def decrypt_sheet(data):
FILE: sky-music-web/src/main/index.ts
constant MAX_CONCURRENT_COPIES (line 12) | const MAX_CONCURRENT_COPIES = 20;
function createWindow (line 31) | function createWindow(): void {
function launchBackend (line 301) | function launchBackend() {
function isBackendRunning (line 328) | function isBackendRunning(processName: string): boolean {
FILE: sky-music-web/src/preload/index.d.ts
type Window (line 4) | interface Window {
FILE: sky-music-web/src/renderer/src/store/index.ts
method getPlayList (line 8) | getPlayList(state) {
method getNextPlayMusic (line 11) | getNextPlayMusic(state){
method setPlayList (line 18) | setPlayList(state, datas) {
method addPlayList (line 21) | addPlayList(state:any, data) {
method removePlayList (line 32) | removePlayList(state:any, index:number){
method clearPlayList (line 36) | clearPlayList(state:any){
FILE: sky-music-web/src/renderer/src/utils/configStore.ts
type CONFIG_TYPE (line 2) | enum CONFIG_TYPE {
type CONFIG_STATUS_TYPE (line 17) | enum CONFIG_STATUS_TYPE {
FILE: sky-music-web/src/renderer/src/utils/fetchUtils.ts
function getList (line 3) | function getList(listName,searchStr){
function getData (line 10) | function getData(url){
function sendData (line 17) | function sendData(url, data) {
function setConfig (line 30) | function setConfig(name,value){
function getWWWData (line 47) | function getWWWData(url){
Condensed preview — 103 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (768K chars).
[
{
"path": ".gitattributes",
"chars": 84,
"preview": "*.pth filter=lfs diff=lfs merge=lfs -text\n*.exe filter=lfs diff=lfs merge=lfs -text\n"
},
{
"path": ".github/FUNDING.yml",
"chars": 883,
"preview": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [u"
},
{
"path": ".gitignore",
"chars": 636,
"preview": ".DS_Store\nnode_modules\n/dist\n\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyar"
},
{
"path": ".version",
"chars": 404,
"preview": "{\n \"version\": \"2.6.5\",\n \"title\": \"更新啦~新年快乐🔔\",\n \"content\": \"🍉新版本v2.6.6\\\\n🍊更新群-1007672060\\\\n💡版本更新协助\\\\n🥝 更新日志\\\\n\\\\"
},
{
"path": "README.md",
"chars": 1236,
"preview": "# SkyMusicPlay-for-Windows\r\n\r\n<p align=\"center\">\r\n <a href=\"https://github.com/windhide/SkyMusicPlay-for-Windows\"><img"
},
{
"path": "buildProject.ps1",
"chars": 2620,
"preview": "# =========================\n# 自动管理员提权\n# =========================\n$principal = New-Object Security.Principal.WindowsPrin"
},
{
"path": "draw-follow-window/README.md",
"chars": 175,
"preview": "```shell\n pyinstaller --onefile --noconsole --clean --strip --name draw_server draw_server.py\n\n```\n\n```shell\n draw_ser"
},
{
"path": "draw-follow-window/draw_server.py",
"chars": 8465,
"preview": "import tkinter as tk\nimport socket\nimport threading\nimport sys\nimport argparse\nimport os\n\n# 针对 Windows 系统设置 DPI Awarenes"
},
{
"path": "draw-follow-window/draw_socket_demo.py",
"chars": 1150,
"preview": "import socket\nimport time\n\nclient = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\nclient.connect((\"localhost\", 12345"
},
{
"path": "sky-music-server/README.md",
"chars": 591,
"preview": "打包指令\n```shell\n# 有命令调试打包\npyinstaller --uac-admin sky_music_server.py -i icon.ico --upx-dir D:\\Desktop\\upx-4.2.2-win64\\ --"
},
{
"path": "sky-music-server/music_compare_repeat.py",
"chars": 2754,
"preview": "import difflib\nimport hashlib\nimport os\nimport shutil\nfrom collections import defaultdict\n\n\ndef get_file_hash(file_path,"
},
{
"path": "sky-music-server/music_file_process.py",
"chars": 3402,
"preview": "import json\nimport os\nimport re\nimport shutil # 用于移动文件\nfrom concurrent.futures import ThreadPoolExecutor\n\nimport charde"
},
{
"path": "sky-music-server/music_translate.py",
"chars": 582,
"preview": "import os\n\nfrom basic_pitch import ICASSP_2022_MODEL_PATH\nfrom basic_pitch.inference import predict_and_save\n\nif __name_"
},
{
"path": "sky-music-server/requirements.txt",
"chars": 538,
"preview": "plyer==2.1.0\npynput==1.7.7\ncharset-normalizer==3.4.1\nchardet==5.2.0\nultralytics==8.3.61\nopencv-python==4.10.0.84\nnumpy=="
},
{
"path": "sky-music-server/sky_music_apis.py",
"chars": 7686,
"preview": "import json\nimport os\nimport time\nimport webbrowser\n\nimport psutil\nimport requests\nfrom fastapi import FastAPI, UploadFi"
},
{
"path": "sky-music-server/sky_music_server.py",
"chars": 2271,
"preview": "import logging\nimport os\nimport queue\nfrom contextlib import asynccontextmanager\n\nimport sys\nimport threading\nimport psu"
},
{
"path": "sky-music-server/version.txt",
"chars": 819,
"preview": "# version.txt\nVSVersionInfo(\n ffi=FixedFileInfo(\n filevers=(1, 0, 0, 0),\n prodvers=(1, 0, 0, 0),\n mask=0x3f,\n "
},
{
"path": "sky-music-server/windhide/auto/auto_thread.py",
"chars": 3112,
"preview": "import threading\nfrom os import path\n\nimport time\n\nimport plyer\nfrom pynput.keyboard import Controller, Key\n\nfrom windhi"
},
{
"path": "sky-music-server/windhide/musicToSheet/aigc_handler_sheet.py",
"chars": 6064,
"preview": "import json\nimport os\nimport re\n\n# from google import genai\nfrom openai import OpenAI\n\nfrom windhide.static.global_varia"
},
{
"path": "sky-music-server/windhide/musicToSheet/music2html.py",
"chars": 7218,
"preview": "import os\n\nfrom jinja2 import Template\nfrom platformdirs import user_desktop_dir\n\nhtml_template = \"\"\"\n<!DOCTYPE html>\n<h"
},
{
"path": "sky-music-server/windhide/musicToSheet/process_audio.py",
"chars": 26883,
"preview": "import json\nimport os\n\nimport pretty_midi\n\nfrom windhide.musicToSheet.transfer_MID import inference\nfrom windhide.musicT"
},
{
"path": "sky-music-server/windhide/musicToSheet/transfer_MID.py",
"chars": 490,
"preview": "from windhide.utils.path_util import getResourcesPath\n\n\ndef inference(input_path):\n from basic_pitch import ICASSP_20"
},
{
"path": "sky-music-server/windhide/musicToSheet/vocals_split.py",
"chars": 1406,
"preview": "import os\nimport shutil\n\nimport demucs.separate\n\nfrom windhide.utils.path_util import getResourcesPath\n\ndef split_vocals"
},
{
"path": "sky-music-server/windhide/playRobot/amd_robot.py",
"chars": 7472,
"preview": "import ctypes\nimport threading\nimport time\nfrom ctypes import windll\n\nimport keyboard\nimport pyautogui\nimport win32con\ni"
},
{
"path": "sky-music-server/windhide/playRobot/intel_robot.py",
"chars": 7033,
"preview": "import ctypes\nimport time\n\nimport keyboard\nimport pyautogui\nimport win32con\nimport win32gui\n\nfrom windhide.static.global"
},
{
"path": "sky-music-server/windhide/static/global_variable.py",
"chars": 7184,
"preview": "class GlobalVariable:\n # 运行环境配置\n isProd = False\n cpu_type = None\n compatibility_mode = False # 是否虚拟机\n is"
},
{
"path": "sky-music-server/windhide/thread/amd_play_thread.py",
"chars": 3149,
"preview": "import math\nimport threading\nimport time\n\nfrom windhide.playRobot import amd_robot\nfrom windhide.static.global_variable "
},
{
"path": "sky-music-server/windhide/thread/follow_process_thread.py",
"chars": 1564,
"preview": "import os\nimport subprocess\n\nimport psutil\n\nfrom windhide.static.global_variable import GlobalVariable\nfrom windhide.uti"
},
{
"path": "sky-music-server/windhide/thread/follow_thread.py",
"chars": 6192,
"preview": "import time\nfrom os import path\n\nimport plyer\nfrom pynput import keyboard\n\nfrom windhide.playRobot.amd_robot import send"
},
{
"path": "sky-music-server/windhide/thread/frame_alive_thread.py",
"chars": 478,
"preview": "import os\nimport time\n\nimport psutil\n\n\ndef is_process_running(process_name):\n \"\"\"检查目标进程是否运行\"\"\"\n return any(proc.in"
},
{
"path": "sky-music-server/windhide/thread/hwnd_check_thread.py",
"chars": 2743,
"preview": "import os\nimport time\nimport traceback\n\nimport psutil\nimport pywintypes\nimport win32gui\nimport win32process\n\nfrom windhi"
},
{
"path": "sky-music-server/windhide/thread/intel_play_thread.py",
"chars": 3144,
"preview": "import math\nimport threading\nimport time\n\nfrom windhide.playRobot import intel_robot\nfrom windhide.static.global_variabl"
},
{
"path": "sky-music-server/windhide/thread/queue_thread.py",
"chars": 1670,
"preview": "import queue\nimport re\nfrom os import path\n\nimport plyer\n\nfrom windhide.playRobot import intel_robot, amd_robot\nfrom win"
},
{
"path": "sky-music-server/windhide/thread/shortcut_thread.py",
"chars": 1317,
"preview": "import urllib\n\nfrom pynput import keyboard\nfrom websocket_server import WebsocketServer\n\nfrom windhide.static.global_var"
},
{
"path": "sky-music-server/windhide/utils/auto_util.py",
"chars": 417,
"preview": "\nfrom windhide.auto.auto_thread import HeartFireThread\nfrom windhide.static.global_variable import GlobalVariable\n\n\ndef "
},
{
"path": "sky-music-server/windhide/utils/command_util.py",
"chars": 4774,
"preview": "import socket\nimport threading\nimport time\nfrom os import path\nfrom time import sleep\n\nimport plyer\n\nfrom windhide.stati"
},
{
"path": "sky-music-server/windhide/utils/config_util.py",
"chars": 3176,
"preview": "import os\nimport shutil\n\nfrom windhide.musicToSheet.music2html import generatorSheetHtml\nfrom windhide.static.global_var"
},
{
"path": "sky-music-server/windhide/utils/hook_util.py",
"chars": 436,
"preview": "import builtins\nimport platform\n\nfrom windhide.static.global_variable import GlobalVariable\n\n\n# 重定向 print 到空函数\n\ndef sout"
},
{
"path": "sky-music-server/windhide/utils/hwnd_utils.py",
"chars": 1555,
"preview": "import os\n\nimport psutil\nimport win32gui\nimport win32process\n\nfrom windhide.static.global_variable import GlobalVariable"
},
{
"path": "sky-music-server/windhide/utils/ocr_follow_util.py",
"chars": 5808,
"preview": "import logging\nimport os\nimport threading\n\nimport keyboard\nimport time\n\n\nfrom ultralytics import YOLO\n\nfrom windhide.sta"
},
{
"path": "sky-music-server/windhide/utils/ocr_heart_utils.py",
"chars": 4867,
"preview": "import os\n\nimport cv2\nimport numpy as np\nimport pyautogui\nimport win32con\nimport win32gui\nfrom sklearn.cluster import DB"
},
{
"path": "sky-music-server/windhide/utils/ocr_normal_utils.py",
"chars": 1673,
"preview": "import ctypes\n\nimport cv2\nimport numpy as np\nimport pyautogui\nimport win32con\nimport win32gui\n\nfrom windhide.static.glob"
},
{
"path": "sky-music-server/windhide/utils/path_util.py",
"chars": 6848,
"preview": "import json\nimport os\nimport re\n\nimport chardet\nimport plyer\n\nfrom windhide.static.global_variable import GlobalVariable"
},
{
"path": "sky-music-server/windhide/utils/play_util.py",
"chars": 1311,
"preview": "import chardet\n\nfrom windhide.playRobot import intel_robot, amd_robot\nfrom windhide.static.global_variable import Global"
},
{
"path": "sky-music-server/windhide/utils/sheet_decrypt_util.py",
"chars": 965,
"preview": "import json\n\n\ndef decrypt_sheet(data):\n # 加密密钥和签名(来自 CEN.cs)\n KEY = \"TB,R&Q}-ULFXF7={nU7v?fy#Khr9Mhuu\"\n SIGNATU"
},
{
"path": "sky-music-web/.editorconfig",
"chars": 146,
"preview": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert_final_newline = true\ntrim_"
},
{
"path": "sky-music-web/.eslintignore",
"chars": 33,
"preview": "node_modules\ndist\nout\n.gitignore\n"
},
{
"path": "sky-music-web/.eslintrc.cjs",
"chars": 574,
"preview": "/* eslint-env node */\nrequire('@rushstack/eslint-patch/modern-module-resolution')\n\nmodule.exports = {\n extends: [\n '"
},
{
"path": "sky-music-web/.gitignore",
"chars": 45,
"preview": "node_modules\ndist\nout\n*.txt\n.DS_Store\n*.log*\n"
},
{
"path": "sky-music-web/.npmrc",
"chars": 146,
"preview": "electron_mirror=https://npmmirror.com/mirrors/electron/\nelectron_builder_binaries_mirror=https://npmmirror.com/mirrors/e"
},
{
"path": "sky-music-web/.prettierignore",
"chars": 65,
"preview": "out\ndist\npnpm-lock.yaml\nLICENSE.md\ntsconfig.json\ntsconfig.*.json\n"
},
{
"path": "sky-music-web/.prettierrc.yaml",
"chars": 66,
"preview": "singleQuote: true\nsemi: false\nprintWidth: 100\ntrailingComma: none\n"
},
{
"path": "sky-music-web/README.md",
"chars": 712,
"preview": "# electron-app\n\nAn Electron application with Vue and TypeScript\n\n## Recommended IDE Setup\n\n- [VSCode](https://code.visua"
},
{
"path": "sky-music-web/build/entitlements.mac.plist",
"chars": 415,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "sky-music-web/dev-app-update.yml",
"chars": 98,
"preview": "provider: generic\nurl: https://example.com/auto-updates\nupdaterCacheDirName: electron-app-updater\n"
},
{
"path": "sky-music-web/electron-builder.yml",
"chars": 1327,
"preview": "appId: 星星弹琴\nproductName: Sky_Music\ndirectories:\n buildResources: build\nfiles:\n - '!**/.vscode/*'\n - '!src/*'\n - '!el"
},
{
"path": "sky-music-web/electron.vite.config.ts",
"chars": 414,
"preview": "import { resolve } from 'path'\nimport { defineConfig, externalizeDepsPlugin } from 'electron-vite'\nimport vue from '@vit"
},
{
"path": "sky-music-web/package.json",
"chars": 1921,
"preview": "{\n \"name\": \"sky-music-web\",\n \"version\": \"v2.6.6\",\n \"description\": \"WindHide\",\n \"main\": \"./out/main/index.js\",\n \"aut"
},
{
"path": "sky-music-web/src/main/index.ts",
"chars": 10439,
"preview": "import { app, shell, BrowserWindow, ipcMain, screen, Notification, powerSaveBlocker } from 'electron'\nimport { join } fr"
},
{
"path": "sky-music-web/src/preload/index.d.ts",
"chars": 617,
"preview": "import { ElectronAPI } from '@electron-toolkit/preload'\n\ndeclare global {\n interface Window {\n api: {\n setAlway"
},
{
"path": "sky-music-web/src/preload/index.ts",
"chars": 2066,
"preview": "import { contextBridge, ipcRenderer } from 'electron'\nimport { electronAPI } from '@electron-toolkit/preload'\n// Custom "
},
{
"path": "sky-music-web/src/renderer/index.html",
"chars": 508,
"preview": "<!doctype html>\n<html>\n <head>\n <meta charset=\"UTF-8\" />\n <title>Sky_Music</title>\n <style>\n html, body {\n "
},
{
"path": "sky-music-web/src/renderer/src/App.vue",
"chars": 14331,
"preview": "<template>\n <n-config-provider :theme=\"darkTheme\" :style=\"{ opacity : transparency_number}\" >\n <n-flex id=\"drag-area"
},
{
"path": "sky-music-web/src/renderer/src/component/svg/cr.vue",
"chars": 645,
"preview": "<template><svg version=\"0.0\" width=\"63.876953\" height=\"63.876953\" id=\"svg3639\" xmlns=\"http://www.w3.org/2000/svg\"\n><path"
},
{
"path": "sky-music-web/src/renderer/src/component/svg/dm.vue",
"chars": 816,
"preview": "<template><svg version=\"0.0\" width=\"63.876953\" height=\"63.876953\" id=\"svg3639\" xmlns=\"http://www.w3.org/2000/svg\"\n><path"
},
{
"path": "sky-music-web/src/renderer/src/component/svg/dmcr.vue",
"chars": 1317,
"preview": "<template><svg version=\"0.0\" width=\"63.876953\" height=\"63.876953\" id=\"svg3639\" xmlns=\"http://www.w3.org/2000/svg\"\n><path"
},
{
"path": "sky-music-web/src/renderer/src/env.d.ts",
"chars": 281,
"preview": "/// <reference types=\"vite/client\" />\n\ndeclare module '*.vue' {\n import type { DefineComponent } from 'vue'\n // eslint"
},
{
"path": "sky-music-web/src/renderer/src/i18n/index.ts",
"chars": 601,
"preview": "import { createI18n } from 'vue-i18n'\nimport zh_cn from './locales/zh-cn.json'\nimport zh_classical from './locales/zh-cl"
},
{
"path": "sky-music-web/src/renderer/src/i18n/locales/en.json",
"chars": 11588,
"preview": "{\n \"columns\": {\n \"name\": \"Song title\",\n \"operation\": \"operate\",\n \"total_duration\": \"Duration\"\n },\n \"home\": {"
},
{
"path": "sky-music-web/src/renderer/src/i18n/locales/jp.json",
"chars": 8002,
"preview": "{\n \"main\": {\n \"loading\": \"🍉アプリケーションをロード中、少々お待ちください~\",\n \"compatible\": \"互換モード\",\n \"backend\": \"バックエンドモード\",\n \"qu"
},
{
"path": "sky-music-web/src/renderer/src/i18n/locales/ko.json",
"chars": 8764,
"preview": "{\n \"columns\": {\n \"name\": \"노래 제목\",\n \"operation\": \"작동하다\",\n \"total_duration\": \"지속\"\n },\n \"home\": {\n \"head_tex"
},
{
"path": "sky-music-web/src/renderer/src/i18n/locales/zh-classical.json",
"chars": 6839,
"preview": "{\n \"main\": {\n \"loading\": \"🍉应用载入中,少待片刻~\",\n \"compatible\": \"兼容之制\",\n \"backend\": \"後臺之制\",\n \"queue\": \"列隊之制\",\n \""
},
{
"path": "sky-music-web/src/renderer/src/i18n/locales/zh-cn.json",
"chars": 7534,
"preview": "{\n \"main\": {\n \"loading\": \"🍉应用加载中,请稍等~\",\n \"compatible\": \"兼容模式\",\n \"backend\": \"后台模式\",\n \"queue\": \"队列模式\",\n \"c"
},
{
"path": "sky-music-web/src/renderer/src/i18n/locales/zh-tw.json",
"chars": 7390,
"preview": "{\n \"columns\": {\n \"name\": \"歌名\",\n \"operation\": \"操作\",\n \"total_duration\": \"時長\"\n },\n \"home\": {\n \"head_text\": \""
},
{
"path": "sky-music-web/src/renderer/src/main.ts",
"chars": 279,
"preview": "import { createApp } from 'vue'\nimport App from './App.vue'\nimport router from './router'\nimport store from './store'\nim"
},
{
"path": "sky-music-web/src/renderer/src/router/index.ts",
"chars": 1402,
"preview": "import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'\n\nconst routes: Array<RouteRecordRaw> = [\n {"
},
{
"path": "sky-music-web/src/renderer/src/store/index.ts",
"chars": 957,
"preview": "import { createStore } from 'vuex'\n\nexport default createStore({\n state: {\n playList:[],\n },\n getters: {\n getPl"
},
{
"path": "sky-music-web/src/renderer/src/utils/configStore.ts",
"chars": 2511,
"preview": "import { sendData } from \"@renderer/utils/fetchUtils\";\nexport enum CONFIG_TYPE {\n DELAY_STATUS = 'DELAY_STATUS', //间隔延迟"
},
{
"path": "sky-music-web/src/renderer/src/utils/fetchUtils.ts",
"chars": 1354,
"preview": "let baseUrl = \"http://127.0.0.1:9899/\"\n\nexport function getList(listName,searchStr){\n return fetch(baseUrl+\"?listName=\""
},
{
"path": "sky-music-web/src/renderer/src/views/ai_setting.vue",
"chars": 13998,
"preview": "<template>\n <div class=\"father\">\n <n-divider style=\"\">\n <n-gradient-text\n :gradient=\"{\n fro"
},
{
"path": "sky-music-web/src/renderer/src/views/home.vue",
"chars": 145619,
"preview": "<template>\n <div id=\"father\">\n <n-divider>\n <img\n id=\"avatar\"\n src=\"data:image/png;base64,iVBORw0"
},
{
"path": "sky-music-web/src/renderer/src/views/home_loader.vue",
"chars": 141772,
"preview": "<template>\n <div id=\"father\">\n <n-divider>\n <img\n id=\"avatar\"\n src=\"data:image/png;base64,iVBORw0"
},
{
"path": "sky-music-web/src/renderer/src/views/hwndHandle.vue",
"chars": 5115,
"preview": "<template>\n <div id=\"father\">\n <n-gradient-text\n :gradient=\"{\n from: '\trgb(254, 1, 1)',\n to: 'rgb"
},
{
"path": "sky-music-web/src/renderer/src/views/kube.vue",
"chars": 25431,
"preview": "<template>\n <n-flex align=\"center\" style=\"margin-top: 15px;\">\n <n-switch size=\"medium\" v-model:value=\"is_singular\" @"
},
{
"path": "sky-music-web/src/renderer/src/views/magicTools.vue",
"chars": 7951,
"preview": "<template>\n <div class=\"father\">\n <n-divider>\n ⚠\n </n-divider>\n <div class=\"father\">\n <n-highlight"
},
{
"path": "sky-music-web/src/renderer/src/views/music.vue",
"chars": 38343,
"preview": "<template>\n <n-flex align=\"center\" style=\"margin-left: 6px;\">\n <n-gradient-text :size=\"20\" type=\"success\" style=\"wid"
},
{
"path": "sky-music-web/src/renderer/src/views/music_edit.vue",
"chars": 43984,
"preview": "<template>\n <!-- 状态显示区域 -->\n <n-flex>\n <n-gradient-text gradient=\"linear-gradient(90deg, rgb(242,201,196), rgb(221,"
},
{
"path": "sky-music-web/src/renderer/src/views/setting.vue",
"chars": 10000,
"preview": "<template>\n <div id=\"headText\">\n <n-highlight style=\"margin-bottom: 5px; color: #DDF2C4;\" :text=\"headText\" :patterns"
},
{
"path": "sky-music-web/src/renderer/src/views/shortcutKeys.vue",
"chars": 9976,
"preview": "<template>\n <div id=\"father\">\n <n-highlight style=\"margin-bottom: 5px; color: #DDF2C4;\" :text=\"headText\" :patterns=\""
},
{
"path": "sky-music-web/src/renderer/src/views/tutorial.vue",
"chars": 10166,
"preview": "<template>\n <n-flex align=\"center\">\n <n-gradient-text :size=\"20\" type=\"success\" style=\"width: 100%; color: #f2e8c4\">"
},
{
"path": "sky-music-web/tsconfig.json",
"chars": 107,
"preview": "{\n \"files\": [],\n \"references\": [{ \"path\": \"./tsconfig.node.json\" }, { \"path\": \"./tsconfig.web.json\" }]\n}\n"
},
{
"path": "sky-music-web/tsconfig.node.json",
"chars": 230,
"preview": "{\n \"extends\": \"@electron-toolkit/tsconfig/tsconfig.node.json\",\n \"include\": [\"electron.vite.config.*\", \"src/main/**/*\","
},
{
"path": "sky-music-web/tsconfig.web.json",
"chars": 384,
"preview": "{\n \"extends\": \"@electron-toolkit/tsconfig/tsconfig.web.json\",\n \"include\": [\n \"src/renderer/src/env.d.ts\",\n \"src/"
},
{
"path": "template-resources/myFavorite/.keep",
"chars": 0,
"preview": ""
},
{
"path": "template-resources/myImport/.keep",
"chars": 0,
"preview": ""
},
{
"path": "template-resources/myTranslate/.keep",
"chars": 0,
"preview": ""
},
{
"path": "template-resources/systemTools/drawTool/.keep",
"chars": 0,
"preview": ""
},
{
"path": "template-resources/systemTools/modelData/demoScheenshot/.keep",
"chars": 0,
"preview": ""
},
{
"path": "template-resources/systemTools/scriptTemplate/.keep",
"chars": 0,
"preview": ""
},
{
"path": "template-resources/translateMID/.keep",
"chars": 0,
"preview": ""
},
{
"path": "template-resources/translateOriginalMusic/.keep",
"chars": 0,
"preview": ""
}
]
// ... and 2 more files (download for full content)
About this extraction
This page contains the full source code of the windhide/SkyMusicPlay-for-Windows GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 103 files (684.1 KB), approximately 324.4k tokens, and a symbol index with 195 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.