Skip to content

Commit

Permalink
[finish] version 1.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
yqzhishen committed Mar 6, 2022
1 parent 7a81c18 commit cb6ebc0
Show file tree
Hide file tree
Showing 13 changed files with 470 additions and 409 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ dmypy.json

# Others
/automation.py
/test.py
test.py
*.wav
*.mp3
*.mid
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ UI 自动化执行的成功与否受到系统流畅度等客观因素影响。
> - 缩短等待时间,提升运行速度
> - Demo - 为同一份工程文件导出不同歌手的演唱音频
#### 1.2.0 (2022.03.06)

> - 修复音轨操作不能向上滚动的问题
> - 切换歌手时将打印日志
> - 重构部分代码,优化使用方式


## 参考资料与相关链接 | References & Links
Expand Down
394 changes: 0 additions & 394 deletions src/core.py

This file was deleted.

82 changes: 82 additions & 0 deletions src/core/engine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import os
import winreg

import uiautomation as auto

import log
import singers
import verify

logger = log.logger


def find_xstudio() -> str:
"""
根据注册表查找 X Studio 主程序路径。
:return: XStudioSinger.exe 的路径
"""
key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Classes\\svipfile\\shell\\open\\command')
value = winreg.QueryValueEx(key, '')
return value[0].split('"')[1]


def start_xstudio(engine: str = None, project: str = None, singer: str = '陈水若'):
"""
启动 X Studio。
:param engine: 手动指定 X Studio 主程序路径
:param project: 启动时需要打开的工程文件路径,默认打开空白工程
:param singer: 若打开空白工程,可指定初始歌手名称
"""
if engine:
engine = os.path.abspath(engine)
if not os.path.exists(engine):
logger.error('指定的主程序路径不存在。')
exit(1)
if not os.path.isfile(engine) or not engine.endswith('.exe'):
logger.error('指定的主程序不是合法的可执行 (.exe) 文件。')
exit(1)
logger.info('指定的主程序:%s。' % engine)
if project:
project = os.path.abspath(project)
if not os.path.exists(project):
logger.error('工程文件不存在。')
exit(1)
if not os.path.isfile(project) or not project.endswith('.svip'):
logger.error('不是合法的 X Studio 工程 (.svip) 文件。')
exit(1)
if engine:
os.popen(f'"{engine}" "{project}"')
else:
os.popen(f'"{project}"')
verify.verify_startup()
verify.verify_opening(auto)
logger.info('启动 X Studio 并打开工程:%s。' % project)
verify.verify_updates()
else:
if engine:
os.popen(f'"{engine}"')
else:
os.popen(f'"{find_xstudio()}"')
verify.verify_startup()
auto.WindowControl(searchDepth=1, Name='X Studio').TextControl(searchDepth=2, Name='开始创作').Click(simulateMove=False)
singers.choose_singer(name=singer)
logger.info('启动 X Studio 并创建空白工程,初始歌手:%s。' % singer)
verify.verify_updates()


def quit_xstudio():
"""
退出 X Studio。
"""
auto.WindowControl(searchDepth=1, RegexName='X Studio .*').ButtonControl(searchDepth=1, AutomationId='btnClose').Click(simulateMove=False)
confirm_window = auto.WindowControl(searchDepth=1, Name='X Studio')
text = confirm_window.TextControl(searchDepth=1, AutomationId='Tbx').Name
if text.startswith('确认'):
confirm_window.ButtonControl(searchDepth=1, AutomationId='OkBtn').Click(simulateMove=False)
else:
confirm_window.ButtonControl(searchDepth=1, AutomationId='NoBtn').Click(simulateMove=False)
logger.info('退出 X Studio。')


if __name__ == '__main__':
start_xstudio(singer='陈水若')
18 changes: 18 additions & 0 deletions src/core/keybd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import time

import win32api
import win32con


def key_down(code: int):
win32api.keybd_event(code, win32api.MapVirtualKey(code, 0), 0, 0)


def key_up(code: int):
win32api.keybd_event(code, win32api.MapVirtualKey(code, 0), win32con.KEYEVENTF_KEYUP, 0)


def key_press(code: int):
key_down(code)
time.sleep(0.02)
key_up(code)
25 changes: 25 additions & 0 deletions src/core/log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import logging

import colorlog


logger = logging.getLogger()
log_colors_config = {
'DEBUG': 'white',
'INFO': 'green',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'bold_red',
}
console_handler = logging.StreamHandler()
logger.setLevel(logging.INFO)
console_handler.setLevel(logging.INFO)
console_formatter = colorlog.ColoredFormatter(
fmt='%(log_color)s[%(asctime)s.%(msecs)03d] %(filename)s -> %(funcName)s line:%(lineno)d [%(levelname)s] : %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
log_colors=log_colors_config
)
console_handler.setFormatter(console_formatter)
if not logger.handlers:
logger.addHandler(console_handler)
console_handler.close()
6 changes: 6 additions & 0 deletions src/core/mouse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import win32api
import win32con


def move_wheel(distance: int):
win32api.mouse_event(win32con.MOUSEEVENTF_WHEEL, 0, 0, distance)
169 changes: 169 additions & 0 deletions src/core/projects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import os

import uiautomation as auto

import keybd
import log
import verify
import singers

logger = log.logger


def new_project(singer: str = None):
"""
新建工程。X Studio 必须已处于启动状态。
:param singer: 可指定新工程的初始歌手
"""
keybd.key_down(17)
keybd.key_press(78)
keybd.key_up(17)
confirm_window = auto.WindowControl(searchDepth=1, Name='X Studio')
if confirm_window.Exists(maxSearchSeconds=1):
confirm_window.ButtonControl(searchDepth=1, AutomationId='NoBtn').Click(simulateMove=False)
if singer:
track_window = auto.WindowControl(searchDepth=1, RegexName='X Studio .*').CustomControl(searchDepth=1, ClassName='TrackWin')
track_window.CustomControl(searchDepth=2, ClassName='TrackChannelControlPanel').ButtonControl(searchDepth=2, AutomationId='switchSingerButton').DoubleClick(simulateMove=False)
singers.choose_singer(singer)
logger.info('创建新工程,初始歌手:%s。' % singer)
else:
logger.info('创建新工程。')


def open_project(filename: str, folder: str = None):
"""
打开工程。X Studio 必须已处于启动状态。
:param filename: 工程文件名
:param folder: 工程所处文件夹路径,默认为 X Studio 上一次打开工程的路径
"""
if folder:
project = os.path.abspath(os.path.join(folder, filename))
if not os.path.exists(project):
logger.error('工程文件不存在。')
exit(1)
else:
project = filename
if not filename.endswith('.svip'):
logger.error('不是一个可打开的 X Studio 工程 (.svip) 文件。')
exit(1)
keybd.key_down(17)
keybd.key_press(79)
keybd.key_up(17)
confirm_window = auto.WindowControl(searchDepth=1, Name='X Studio')
if confirm_window.Exists(maxSearchSeconds=1):
confirm_window.ButtonControl(searchDepth=1, AutomationId='NoBtn').Click(simulateMove=False)
main_window = auto.WindowControl(searchDepth=1, RegexName='X Studio .*')
open_window = main_window.WindowControl(searchDepth=1, Name='打开文件')
open_window.EditControl(searchDepth=3, Name='文件名(N):').GetValuePattern().SetValue(project)
open_window.ButtonControl(searchDepth=1, Name='打开(O)').Click(simulateMove=False)
warning_window = open_window.WindowControl(searchDepth=1, ClassName='#32770')
if warning_window.Exists(maxSearchSeconds=1):
warning = warning_window.TextControl(searchDepth=2).Name
warning_window.ButtonControl(searchDepth=2, Name='确定').Click(simulateMove=False)
open_window.ButtonControl(searchDepth=1, Name='取消').Click(simulateMove=False)
logger.error(warning.replace('\r\n', ' ').replace('。 ', '。'))
exit(1)
verify.verify_opening(main_window)
logger.info('打开工程:%s。' % project)


def export_project(title: str = None, folder: str = None, format: str = 'mp3', samplerate: int = 48000):
"""
导出当前打开的工程。
:param title: 目标文件名,默认与工程同名
:param folder: 目标文件夹路径,默认为工程所在文件夹
:param format: 导出格式 (mp3/wav/midi),默认为 mp3
:param samplerate: 采样率 (48000/44100),默认为 48000
"""
if format not in ['mp3', 'wav', 'midi']:
logger.error('只能保存为 mp3, wav 或 midi 格式。')
exit(1)
if format == 'midi':
samplerate = None
elif samplerate != 48000 and samplerate != 44100:
logger.error('采样率只能为 48000 或 44100。')
exit(1)
if folder and not os.path.exists(folder):
folder = folder.replace('/', '\\')
os.makedirs(folder)
auto.ButtonControl(searchDepth=2, Name='导出').Click(simulateMove=False)
setting_window = auto.WindowControl(searchDepth=2, Name='导出设置')
if title:
setting_window.EditControl(searchDepth=1, AutomationId='FileNameTbx').GetValuePattern().SetValue(title)
else:
title = setting_window.EditControl(searchDepth=1, AutomationId='FileNameTbx').GetValuePattern().Value
if folder:
logger.warning('当前尚不支持指定导出文件夹路径。')
setting_window.EditControl(searchDepth=1, AutomationId='DestTbx').SendKeys(folder, interval=0.05)
if format != 'mp3':
format_box = setting_window.ComboBoxControl(searchDepth=1, AutomationId='FormatComboBox')
format_box.Click(simulateMove=False)
if format == 'wav':
format_box.ListItemControl(searchDepth=1, Name='WAVE文件').Click(simulateMove=False)
else:
format_box.ListItemControl(searchDepth=1, Name='Midi文件').Click(simulateMove=False)
if samplerate == 44100:
samplerate_box = setting_window.ComboBoxControl(searchDepth=1, AutomationId='SampleRateComboBox')
samplerate_box.Click(simulateMove=False)
samplerate_box.ListItemControl(searchDepth=1, Name='44100HZ').Click(simulateMove=False)
setting_window.ButtonControl(searchDepth=1, Name='导出').Click(simulateMove=False)
export_window = auto.WindowControl(searchDepth=2, RegexName='导出.*')
label = export_window.TextControl(searchDepth=1, ClassName='TextBlock', AutomationId='label')
while True:
message_window = auto.WindowControl(searchDepth=2, ClassName='#32770')
if message_window.Exists(maxSearchSeconds=1):
message = message_window.TextControl(searchDepth=1, ClassName='Static').Name
message_window.ButtonControl(searchDepth=1, Name='确定').Click(simulateMove=False)
logger.error(message + '。')
exit(1)
if label.Name == '导出成功':
break
elif label.Name.startswith('导出失败'):
logger.error('导出失败,请稍后再试。')
exit(1)
export_window.ButtonControl(searchDepth=1, AutomationId='okBtn').Click(simulateMove=False)
logger.info('导出工程:%s, 格式 %s, 采样率 %d Hz。' % (title, format, samplerate))


def save_project(filename: str = None, folder: str = None):
"""
保存或另存为当前打开的工程。
:param filename: 另存为的工程文件名
:param folder: 另存为的文件夹路径,默认为工程所在文件夹
"""
if folder:
if not filename:
logger.error('另存为工程时必须指定文件名。')
exit(1)
if not os.path.exists(folder):
os.makedirs(folder)
folder = os.path.abspath(folder.replace('/', '\\'))
if not filename:
keybd.key_down(17)
keybd.key_press(83)
keybd.key_up(17)
logger.info('保存工程。')
else:
if folder:
project = os.path.join(folder, filename)
else:
project = filename
keybd.key_down(17)
keybd.key_down(16)
keybd.key_press(83)
keybd.key_up(16)
keybd.key_up(17)
save_window = auto.WindowControl(searchDepth=1, RegexName='X Studio .*').WindowControl(searchDepth=1, Name='另存为')
save_window.EditControl(searchDepth=6, Name='文件名:').GetValuePattern().SetValue(project)
save_window.ButtonControl(searchDepth=1, Name='保存(S)').Click(simulateMove=False)
confirm_window = save_window.WindowControl(searchDepth=1, ClassName='#32770')
if confirm_window.Exists(maxSearchSeconds=1):
warning = confirm_window.TextControl(searchDepth=2).Name
if warning.endswith('是否替换它?'):
confirm_window.ButtonControl(searchDepth=1, Name='是(Y)').Click(simulateMove=False)
else:
confirm_window.ButtonControl(searchDepth=2, Name='确定').Click(simulateMove=False)
save_window.ButtonControl(searchDepth=1, Name='取消').Click(simulateMove=False)
logger.error(warning.replace('\r\n', ' ').replace('。 ', '。'))
exit(1)
logger.info('另存为工程:%s。' % project)
38 changes: 38 additions & 0 deletions src/core/singers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import time

import uiautomation as auto

import log
import mouse

logger = log.logger


def choose_singer(name: str):
"""
选择一名歌手。歌手市场必须处于打开状态。
:param name: 歌手名字
"""
singer_market = auto.WindowControl(searchDepth=2, Name='歌手市场')
singer_market.HyperlinkControl(searchDepth=9, Name='全部歌手').Click(simulateMove=False)
browser_pane = singer_market.PaneControl(searchDepth=3, ClassName='CefBrowserWindow')
bottom = browser_pane.BoundingRectangle.bottom
while True:
singer_text = browser_pane.TextControl(searchDepth=14, Name=name)
bottom_text = browser_pane.TextControl(searchDepth=14, Name='已经到底了')
if singer_text.Exists(maxSearchSeconds=0.5) and 0 < singer_text.BoundingRectangle.bottom < bottom:
singer_text.Click(simulateMove=False)
break
elif bottom_text.Exists(maxSearchSeconds=0.5) and bottom_text.BoundingRectangle.bottom > 0:
singer_market.ButtonControl(searchDepth=1, AutomationId='btnClose').Click(simulateMove=False)
logger.error('指定的歌手“%s”不存在。' % name)
exit(1)
else:
browser_pane.MoveCursorToMyCenter(simulateMove=False)
mouse.move_wheel(-1500)
time.sleep(1)
if singer_market.ButtonControl(searchDepth=17, Name='待解锁').Exists(maxSearchSeconds=0.5):
singer_market.ImageControl(Depth=17).Click(simulateMove=False)
logger.error('指定的歌手“%s”未解锁。' % name)
exit(1)
singer_market.ButtonControl(searchDepth=17, Name='选中').Click(simulateMove=False)
Loading

0 comments on commit cb6ebc0

Please sign in to comment.