Skip to content

Commit 5ff997c

Browse files
committed
[finish] version 1.3.0
1 parent cb6ebc0 commit 5ff997c

File tree

4 files changed

+93
-16
lines changed

4 files changed

+93
-16
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ X Studio · 歌手 UI 自动化 | UI Automation for X Studio Singer
1313
- 启动和退出 X Studio
1414
- 新建、打开、保存、导出工程
1515
- 为某条轨道切换歌手
16+
- 静音、独奏某条轨道
1617

1718
可能的应用场景:
1819

1920
- 批量导出若干份工程
21+
- 分轨导出一个工程
2022
- 导出一个工程的若干版本
2123
- 批量编辑并另存为工程
2224
- **工程在线试听**(欢迎网站站长合作!)
@@ -70,6 +72,12 @@ UI 自动化执行的成功与否受到系统流畅度等客观因素影响。
7072
> - 切换歌手时将打印日志
7173
> - 重构部分代码,优化使用方式
7274
75+
#### 1.3.0 (2022.03.06)
76+
77+
> - 支持静音、取消静音、独奏、取消独奏
78+
> - 调整项目结构,优化部分代码
79+
> - Demo - 分轨导出一份工程文件
80+
7381

7482

7583
## 参考资料与相关链接 | References & Links

src/core/mouse.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
1+
import uiautomation as auto
12
import win32api
23
import win32con
34

45

56
def move_wheel(distance: int):
67
win32api.mouse_event(win32con.MOUSEEVENTF_WHEEL, 0, 0, distance)
8+
9+
10+
def scroll_inside(target: auto.Control, bound: auto.Control):
11+
while True:
12+
if target.BoundingRectangle.top < bound.BoundingRectangle.top:
13+
bound.MoveCursorToMyCenter(simulateMove=False)
14+
move_wheel(bound.BoundingRectangle.top - target.BoundingRectangle.top)
15+
elif target.BoundingRectangle.bottom > bound.BoundingRectangle.bottom:
16+
bound.MoveCursorToMyCenter(simulateMove=False)
17+
move_wheel(bound.BoundingRectangle.bottom - target.BoundingRectangle.bottom)
18+
else:
19+
break

src/core/tracks.py

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,75 @@
77
logger = log.logger
88

99

10+
all_tracks = []
11+
12+
13+
def enum_tracks() -> list:
14+
all_tracks.clear()
15+
index = 1
16+
track = Track(index)
17+
while track.exists():
18+
all_tracks.append(track)
19+
index += 1
20+
track = Track(index)
21+
return all_tracks
22+
23+
24+
def count_tracks() -> int:
25+
return len(enum_tracks())
26+
27+
1028
class Track:
1129
def __init__(self, index: int):
1230
if index < 1:
1331
logger.error('轨道编号最小为 1。')
1432
exit(1)
15-
self.track_window = auto.WindowControl(searchDepth=1, RegexName='X Studio .*').CustomControl(searchDepth=1, ClassName='TrackWin')
1633
self.index = index
17-
self.pane = None
34+
self.track_window = auto.WindowControl(searchDepth=1, RegexName='X Studio .*').CustomControl(searchDepth=1, ClassName='TrackWin')
35+
self.scroll_bound = self.track_window.PaneControl(searchDepth=1, ClassName='ScrollViewer')
36+
self.pane = self.track_window.CustomControl(searchDepth=2, foundIndex=self.index, ClassName='TrackChannelControlPanel')
37+
self.mute_button = self.pane.ButtonControl(searchDepth=1, Name='UnMute')
38+
self.unmute_button = self.pane.ButtonControl(searchDepth=1, Name='Mute')
39+
self.solo_button = self.pane.ButtonControl(searchDepth=1, Name='notSolo')
40+
self.notsolo_button = self.pane.ButtonControl(searchDepth=1, Name='Solo')
41+
self.switch_button = self.pane.ButtonControl(searchDepth=2, AutomationId='switchSingerButton')
1842

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

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

49+
def is_muted(self) -> bool:
50+
return self.unmute_button.Exists(maxSearchSeconds=0.5)
51+
52+
def is_solo(self) -> bool:
53+
return self.notsolo_button.Exists(maxSearchSeconds=0.5)
54+
55+
def set_muted(self, muted: bool):
56+
if muted and not self.is_muted():
57+
mouse.scroll_inside(target=self.mute_button, bound=self.scroll_bound)
58+
self.mute_button.Click(simulateMove=False)
59+
elif not muted and self.is_muted():
60+
mouse.scroll_inside(target=self.unmute_button, bound=self.scroll_bound)
61+
self.unmute_button.Click(simulateMove=False)
62+
if muted:
63+
logger.info('静音轨道 %d。' % self.index)
64+
else:
65+
logger.info('取消静音轨道 %d。' % self.index)
66+
67+
def set_solo(self, solo: bool):
68+
if solo and not self.is_solo():
69+
mouse.scroll_inside(target=self.solo_button, bound=self.scroll_bound)
70+
self.solo_button.Click(simulateMove=False)
71+
elif not solo and self.is_solo():
72+
mouse.scroll_inside(target=self.notsolo_button, bound=self.scroll_bound)
73+
self.notsolo_button.Click(simulateMove=False)
74+
if solo:
75+
logger.info('独奏轨道 %d。' % self.index)
76+
else:
77+
logger.info('取消独奏轨道 %d。' % self.index)
78+
2679
def switch_singer(self, singer: str):
2780
"""
2881
切换歌手。
@@ -34,18 +87,7 @@ def switch_singer(self, singer: str):
3487
if self.is_instrumental():
3588
logger.error('指定的轨道不是演唱轨。')
3689
exit(1)
37-
bound = self.track_window.PaneControl(searchDepth=1, ClassName='ScrollViewer').BoundingRectangle
38-
top, bottom = bound.top, bound.bottom
39-
switch_button = self.pane.ButtonControl(searchDepth=2, AutomationId='switchSingerButton')
40-
while True:
41-
if switch_button.BoundingRectangle.top < top:
42-
self.track_window.MoveCursorToMyCenter(simulateMove=False)
43-
mouse.move_wheel(top - switch_button.BoundingRectangle.top)
44-
elif switch_button.BoundingRectangle.bottom > bottom:
45-
self.track_window.MoveCursorToMyCenter(simulateMove=False)
46-
mouse.move_wheel(bottom - switch_button.BoundingRectangle.bottom)
47-
else:
48-
switch_button.DoubleClick(simulateMove=False)
49-
break
90+
mouse.scroll_inside(target=self.switch_button, bound=self.scroll_bound)
91+
self.switch_button.DoubleClick(simulateMove=False)
5092
singers.choose_singer(name=singer)
5193
logger.info('为轨道 %d 切换歌手:%s。' % (self.index, singer))

src/separate_tracks.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import sys
2+
3+
sys.path.append('core')
4+
5+
from core import engine, projects, tracks
6+
7+
if __name__ == '__main__':
8+
path = r'..\demo\separate_tracks\assets\示例.svip'
9+
prefix = '示例'
10+
engine.start_xstudio(engine=r'E:\YQ数据空间\YQ实验室\实验室:XStudioSinger\内测\XStudioSinger_2.0.0_beta2.exe', project=path)
11+
for track in tracks.enum_tracks():
12+
track.set_solo(True)
13+
projects.export_project(title=f'{prefix}_轨道{track.index}')
14+
engine.quit_xstudio()

0 commit comments

Comments
 (0)