Skip to content

Commit

Permalink
[finish] version 1.3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
yqzhishen committed Mar 6, 2022
1 parent cb6ebc0 commit 5ff997c
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 16 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ X Studio · 歌手 UI 自动化 | UI Automation for X Studio Singer
- 启动和退出 X Studio
- 新建、打开、保存、导出工程
- 为某条轨道切换歌手
- 静音、独奏某条轨道

可能的应用场景:

- 批量导出若干份工程
- 分轨导出一个工程
- 导出一个工程的若干版本
- 批量编辑并另存为工程
- **工程在线试听**(欢迎网站站长合作!)
Expand Down Expand Up @@ -70,6 +72,12 @@ UI 自动化执行的成功与否受到系统流畅度等客观因素影响。
> - 切换歌手时将打印日志
> - 重构部分代码,优化使用方式
#### 1.3.0 (2022.03.06)

> - 支持静音、取消静音、独奏、取消独奏
> - 调整项目结构,优化部分代码
> - Demo - 分轨导出一份工程文件


## 参考资料与相关链接 | References & Links
Expand Down
13 changes: 13 additions & 0 deletions src/core/mouse.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
import uiautomation as auto
import win32api
import win32con


def move_wheel(distance: int):
win32api.mouse_event(win32con.MOUSEEVENTF_WHEEL, 0, 0, distance)


def scroll_inside(target: auto.Control, bound: auto.Control):
while True:
if target.BoundingRectangle.top < bound.BoundingRectangle.top:
bound.MoveCursorToMyCenter(simulateMove=False)
move_wheel(bound.BoundingRectangle.top - target.BoundingRectangle.top)
elif target.BoundingRectangle.bottom > bound.BoundingRectangle.bottom:
bound.MoveCursorToMyCenter(simulateMove=False)
move_wheel(bound.BoundingRectangle.bottom - target.BoundingRectangle.bottom)
else:
break
74 changes: 58 additions & 16 deletions src/core/tracks.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,75 @@
logger = log.logger


all_tracks = []


def enum_tracks() -> list:
all_tracks.clear()
index = 1
track = Track(index)
while track.exists():
all_tracks.append(track)
index += 1
track = Track(index)
return all_tracks


def count_tracks() -> int:
return len(enum_tracks())


class Track:
def __init__(self, index: int):
if index < 1:
logger.error('轨道编号最小为 1。')
exit(1)
self.track_window = auto.WindowControl(searchDepth=1, RegexName='X Studio .*').CustomControl(searchDepth=1, ClassName='TrackWin')
self.index = index
self.pane = None
self.track_window = auto.WindowControl(searchDepth=1, RegexName='X Studio .*').CustomControl(searchDepth=1, ClassName='TrackWin')
self.scroll_bound = self.track_window.PaneControl(searchDepth=1, ClassName='ScrollViewer')
self.pane = self.track_window.CustomControl(searchDepth=2, foundIndex=self.index, ClassName='TrackChannelControlPanel')
self.mute_button = self.pane.ButtonControl(searchDepth=1, Name='UnMute')
self.unmute_button = self.pane.ButtonControl(searchDepth=1, Name='Mute')
self.solo_button = self.pane.ButtonControl(searchDepth=1, Name='notSolo')
self.notsolo_button = self.pane.ButtonControl(searchDepth=1, Name='Solo')
self.switch_button = self.pane.ButtonControl(searchDepth=2, AutomationId='switchSingerButton')

def exists(self) -> bool:
self.pane = self.track_window.CustomControl(searchDepth=2, foundIndex=self.index, ClassName='TrackChannelControlPanel')
return self.pane.Exists(maxSearchSeconds=0.5)

def is_instrumental(self) -> bool:
return self.pane.ComboBoxControl(searchDepth=1, ClassName='ComboBox').IsOffscreen

def is_muted(self) -> bool:
return self.unmute_button.Exists(maxSearchSeconds=0.5)

def is_solo(self) -> bool:
return self.notsolo_button.Exists(maxSearchSeconds=0.5)

def set_muted(self, muted: bool):
if muted and not self.is_muted():
mouse.scroll_inside(target=self.mute_button, bound=self.scroll_bound)
self.mute_button.Click(simulateMove=False)
elif not muted and self.is_muted():
mouse.scroll_inside(target=self.unmute_button, bound=self.scroll_bound)
self.unmute_button.Click(simulateMove=False)
if muted:
logger.info('静音轨道 %d。' % self.index)
else:
logger.info('取消静音轨道 %d。' % self.index)

def set_solo(self, solo: bool):
if solo and not self.is_solo():
mouse.scroll_inside(target=self.solo_button, bound=self.scroll_bound)
self.solo_button.Click(simulateMove=False)
elif not solo and self.is_solo():
mouse.scroll_inside(target=self.notsolo_button, bound=self.scroll_bound)
self.notsolo_button.Click(simulateMove=False)
if solo:
logger.info('独奏轨道 %d。' % self.index)
else:
logger.info('取消独奏轨道 %d。' % self.index)

def switch_singer(self, singer: str):
"""
切换歌手。
Expand All @@ -34,18 +87,7 @@ def switch_singer(self, singer: str):
if self.is_instrumental():
logger.error('指定的轨道不是演唱轨。')
exit(1)
bound = self.track_window.PaneControl(searchDepth=1, ClassName='ScrollViewer').BoundingRectangle
top, bottom = bound.top, bound.bottom
switch_button = self.pane.ButtonControl(searchDepth=2, AutomationId='switchSingerButton')
while True:
if switch_button.BoundingRectangle.top < top:
self.track_window.MoveCursorToMyCenter(simulateMove=False)
mouse.move_wheel(top - switch_button.BoundingRectangle.top)
elif switch_button.BoundingRectangle.bottom > bottom:
self.track_window.MoveCursorToMyCenter(simulateMove=False)
mouse.move_wheel(bottom - switch_button.BoundingRectangle.bottom)
else:
switch_button.DoubleClick(simulateMove=False)
break
mouse.scroll_inside(target=self.switch_button, bound=self.scroll_bound)
self.switch_button.DoubleClick(simulateMove=False)
singers.choose_singer(name=singer)
logger.info('为轨道 %d 切换歌手:%s。' % (self.index, singer))
14 changes: 14 additions & 0 deletions src/separate_tracks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import sys

sys.path.append('core')

from core import engine, projects, tracks

if __name__ == '__main__':
path = r'..\demo\separate_tracks\assets\示例.svip'
prefix = '示例'
engine.start_xstudio(engine=r'E:\YQ数据空间\YQ实验室\实验室:XStudioSinger\内测\XStudioSinger_2.0.0_beta2.exe', project=path)
for track in tracks.enum_tracks():
track.set_solo(True)
projects.export_project(title=f'{prefix}_轨道{track.index}')
engine.quit_xstudio()

0 comments on commit 5ff997c

Please sign in to comment.