mirror of
https://github.com/TheFunny/ArisuAutoSweeper
synced 2026-06-09 20:04:52 +00:00
Upload code
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
import os
|
||||
import re
|
||||
import typing as t
|
||||
from dataclasses import dataclass
|
||||
|
||||
import numpy as np
|
||||
from tqdm import tqdm
|
||||
|
||||
from module.base.code_generator import CodeGenerator
|
||||
from module.base.utils import SelectedGrids, area_limit, area_pad, get_bbox, get_color, image_size, load_image
|
||||
from module.config.config_manual import ManualConfig as AzurLaneConfig
|
||||
from module.config.server import VALID_LANG
|
||||
from module.config.utils import deep_get, deep_iter, deep_set, iter_folder
|
||||
from module.logger import logger
|
||||
|
||||
SHARE_SERVER = 'share'
|
||||
ASSET_SERVER = [SHARE_SERVER] + VALID_LANG
|
||||
|
||||
|
||||
class AssetsImage:
|
||||
REGEX_ASSETS = re.compile(
|
||||
f'^{AzurLaneConfig.ASSETS_FOLDER}/'
|
||||
f'(?P<server>{"|".join(ASSET_SERVER).lower()})/'
|
||||
f'(?P<module>[a-zA-Z0-9_/]+?)/'
|
||||
f'(?P<assets>\w+)'
|
||||
f'(?P<frame>\.\d+)?'
|
||||
f'(?P<attr>\.AREA|\.SEARCH|\.COLOR|\.BUTTON)?'
|
||||
f'\.png$'
|
||||
)
|
||||
|
||||
def __init__(self, file: str):
|
||||
"""
|
||||
Args:
|
||||
file: ./assets/<server>/<module>/<assets>.<frame>.<attr>.png
|
||||
Example: ./assets/cn/ui/login/LOGIN_CONFIRM.2.BUTTON.png
|
||||
then, server="cn", module="ui/login", assets="LOGIN_CONFIRM", frame=2, attr="BUTTON"
|
||||
<frame> and <attr> are optional.
|
||||
"""
|
||||
self.file: str = file
|
||||
prefix = AzurLaneConfig.ASSETS_FOLDER
|
||||
res = AssetsImage.REGEX_ASSETS.match(file)
|
||||
|
||||
self.valid = False
|
||||
self.server = ''
|
||||
self.module = ''
|
||||
self.assets = ''
|
||||
self.frame = 1
|
||||
self.attr = ''
|
||||
|
||||
if res:
|
||||
self.valid = True
|
||||
self.server = res.group('server')
|
||||
self.module = res.group('module')
|
||||
self.assets = res.group('assets')
|
||||
if res.group('frame'):
|
||||
self.frame = int(res.group('frame').strip('.'))
|
||||
else:
|
||||
self.frame = 1
|
||||
if res.group('attr'):
|
||||
self.attr = res.group('attr').strip('.')
|
||||
else:
|
||||
self.attr = ''
|
||||
self.parent_file = f'{prefix}{res.group(1)}.png'
|
||||
else:
|
||||
logger.info(f'Invalid assets name: {self.file}')
|
||||
|
||||
self.bbox: t.Tuple = ()
|
||||
self.mean: t.Tuple = ()
|
||||
|
||||
def parse(self):
|
||||
image = load_image(self.file)
|
||||
|
||||
size = image_size(image)
|
||||
if size != AzurLaneConfig.ASSETS_RESOLUTION:
|
||||
logger.warning(f'{self.file} has wrong resolution: {size}')
|
||||
# self.valid = False
|
||||
bbox = get_bbox(image)
|
||||
mean = get_color(image=image, area=bbox)
|
||||
mean = tuple(np.rint(mean).astype(int))
|
||||
self.bbox = bbox
|
||||
self.mean = mean
|
||||
return bbox, mean
|
||||
|
||||
def __str__(self):
|
||||
if self.valid:
|
||||
return f'AssetsImage(module={self.module}, assets={self.assets}, server={self.server}, frame={self.frame}, attr={self.attr})'
|
||||
else:
|
||||
return f'AssetsImage(file={self.file}, valid={self.valid})'
|
||||
|
||||
|
||||
def iter_images():
|
||||
for server in ASSET_SERVER:
|
||||
for path, folders, files in os.walk(os.path.join(AzurLaneConfig.ASSETS_FOLDER, server)):
|
||||
for file in files:
|
||||
file = os.path.join(path, file).replace('\\', '/')
|
||||
yield AssetsImage(file)
|
||||
|
||||
|
||||
@dataclass
|
||||
class DataAssets:
|
||||
module: str
|
||||
assets: str
|
||||
server: str
|
||||
frame: int
|
||||
file: str = ''
|
||||
area: t.Tuple[int, int, int, int] = ()
|
||||
search: t.Tuple[int, int, int, int] = ()
|
||||
color: t.Tuple[int, int, int] = ()
|
||||
button: t.Tuple[int, int, int, int] = ()
|
||||
|
||||
@staticmethod
|
||||
def area_to_search(area):
|
||||
area = area_pad(area, pad=-20)
|
||||
area = area_limit(area, (0, 0, *AzurLaneConfig.ASSETS_RESOLUTION))
|
||||
return area
|
||||
|
||||
@classmethod
|
||||
def product(cls, image: AssetsImage):
|
||||
"""
|
||||
Product DataAssets from AssetsImage with attr=""
|
||||
"""
|
||||
data = cls(module=image.module, assets=image.assets, server=image.server, frame=image.frame, file=image.file)
|
||||
data.load_image(image)
|
||||
return data
|
||||
|
||||
def load_image(self, image: AssetsImage):
|
||||
if image.attr == '':
|
||||
self.file = image.file
|
||||
self.area = image.bbox
|
||||
self.color = image.mean
|
||||
self.button = image.bbox
|
||||
elif image.attr == 'AREA':
|
||||
self.area = image.bbox
|
||||
elif image.attr == 'SEARCH':
|
||||
self.search = image.bbox
|
||||
elif image.attr == 'COLOR':
|
||||
self.color = image.mean
|
||||
elif image.attr == 'BUTTON':
|
||||
self.button = image.bbox
|
||||
else:
|
||||
logger.warning(f'Trying to load an image with unknown attribute: {image}')
|
||||
|
||||
def generate_code(self):
|
||||
return f'Assets(file="{self.file}", area={self.area}, search={self.search}, color={self.color}, button={self.button})'
|
||||
|
||||
|
||||
def iter_assets():
|
||||
images = list(iter_images())
|
||||
|
||||
# parse images, this may take a while
|
||||
for image in tqdm(images):
|
||||
image.parse()
|
||||
|
||||
# Validate images
|
||||
images = SelectedGrids(images).select(valid=True)
|
||||
images.create_index('module', 'assets', 'server', 'frame', 'attr')
|
||||
for image in images.filter(lambda x: bool(x.attr)):
|
||||
image: AssetsImage = image
|
||||
if not images.indexed_select(image.module, image.assets, image.server, image.frame, ''):
|
||||
logger.warning(f'Attribute assets has no parent assets: {image.file}')
|
||||
image.valid = False
|
||||
if not images.indexed_select(image.module, image.assets, image.server, 1, ''):
|
||||
logger.warning(f'Attribute assets has no first frame: {image.file}')
|
||||
image.valid = False
|
||||
if image.attr == 'SEARCH' and image.frame > 1:
|
||||
logger.warning(f'Attribute SEARCH with frame > 1 is not allowed: {image.file}')
|
||||
image.valid = False
|
||||
images = images.select(valid=True).sort('module', 'assets', 'server', 'frame')
|
||||
|
||||
# Convert to DataAssets
|
||||
data = {}
|
||||
for image in images:
|
||||
if image.attr == '':
|
||||
row = DataAssets.product(image)
|
||||
row.load_image(image)
|
||||
deep_set(data, keys=[image.module, image.assets, image.server, image.frame], value=row)
|
||||
# Load attribute images
|
||||
for image in images:
|
||||
if image.attr != '':
|
||||
row = deep_get(data, keys=[image.module, image.assets, image.server, image.frame])
|
||||
row.load_image(image)
|
||||
# Apply `search` of the first frame to all
|
||||
for path, frames in deep_iter(data, depth=3):
|
||||
print(path, frames)
|
||||
first = frames[1]
|
||||
search = first.search if first.search else DataAssets.area_to_search(first.area)
|
||||
for frame in frames.values():
|
||||
frame.search = search
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def generate_code():
|
||||
all = iter_assets()
|
||||
for module, module_data in all.items():
|
||||
path = os.path.join(AzurLaneConfig.ASSETS_MODULE, module.split('/', maxsplit=1)[0])
|
||||
output = os.path.join(path, 'assets.py')
|
||||
if os.path.exists(output):
|
||||
os.remove(output)
|
||||
output = os.path.join(path, 'assets')
|
||||
os.makedirs(output, exist_ok=True)
|
||||
for prev in iter_folder(output, ext='.py'):
|
||||
if os.path.basename(prev) == '__init__.py':
|
||||
continue
|
||||
os.remove(prev)
|
||||
|
||||
for module, module_data in all.items():
|
||||
path = os.path.join(AzurLaneConfig.ASSETS_MODULE, module.split('/', maxsplit=1)[0])
|
||||
output = os.path.join(path, 'assets')
|
||||
gen = CodeGenerator()
|
||||
gen.Import("""
|
||||
from module.base.button import Button, ButtonWrapper
|
||||
""")
|
||||
gen.CommentAutoGenerage('dev_tools.button_extract')
|
||||
for assets, assets_data in module_data.items():
|
||||
has_share = SHARE_SERVER in assets_data
|
||||
with gen.Object(key=assets, object_class='ButtonWrapper'):
|
||||
gen.ObjectAttr(key='name', value=assets)
|
||||
if has_share:
|
||||
servers = assets_data.keys()
|
||||
else:
|
||||
servers = VALID_LANG
|
||||
for server in servers:
|
||||
frames = list(assets_data.get(server, {}).values())
|
||||
if len(frames) > 1:
|
||||
with gen.ObjectAttr(key=server, value=gen.List()):
|
||||
for index, frame in enumerate(frames):
|
||||
with gen.ListItem(gen.Object(object_class='Button')):
|
||||
gen.ObjectAttr(key='file', value=frame.file)
|
||||
gen.ObjectAttr(key='area', value=frame.area)
|
||||
gen.ObjectAttr(key='search', value=frame.search)
|
||||
gen.ObjectAttr(key='color', value=frame.color)
|
||||
gen.ObjectAttr(key='button', value=frame.button)
|
||||
elif len(frames) == 1:
|
||||
frame = frames[0]
|
||||
with gen.ObjectAttr(key=server, value=gen.Object(object_class='Button')):
|
||||
gen.ObjectAttr(key='file', value=frame.file)
|
||||
gen.ObjectAttr(key='area', value=frame.area)
|
||||
gen.ObjectAttr(key='search', value=frame.search)
|
||||
gen.ObjectAttr(key='color', value=frame.color)
|
||||
gen.ObjectAttr(key='button', value=frame.button)
|
||||
else:
|
||||
gen.ObjectAttr(key=server, value=None)
|
||||
gen.write(os.path.join(output, f'assets_{module.replace("/", "_")}.py'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
generate_code()
|
||||
@@ -0,0 +1,172 @@
|
||||
import os
|
||||
import re
|
||||
import typing as t
|
||||
from functools import cached_property
|
||||
|
||||
from module.base.code_generator import CodeGenerator
|
||||
from module.config.utils import read_file
|
||||
from module.logger import logger
|
||||
|
||||
UI_LANGUAGES = ['jp']
|
||||
|
||||
|
||||
def text_to_variable(text):
|
||||
text = re.sub("'s |s' ", '_', text)
|
||||
text = re.sub('[ \-—:\'/•.]+', '_', text)
|
||||
text = re.sub(r'[(),#"?!&]|</?\w+>', '', text)
|
||||
# text = re.sub(r'[#_]?\d+(_times?)?', '', text)
|
||||
return text
|
||||
|
||||
|
||||
class TextMap:
|
||||
DATA_FOLDER = ''
|
||||
|
||||
def __init__(self, lang: str):
|
||||
self.lang = lang
|
||||
|
||||
@cached_property
|
||||
def data(self) -> dict[int, str]:
|
||||
if not os.path.exists(TextMap.DATA_FOLDER):
|
||||
logger.critical('`TextMap.DATA_FOLDER` does not exist, please set it to your path to StarRailData')
|
||||
exit(1)
|
||||
file = os.path.join(TextMap.DATA_FOLDER, 'TextMap', f'TextMap{self.lang.upper()}.json')
|
||||
data = {}
|
||||
for id_, text in read_file(file).items():
|
||||
text = text.replace('\u00A0', '')
|
||||
text = text.replace(r'{NICKNAME}', 'Trailblazer')
|
||||
data[int(id_)] = text
|
||||
return data
|
||||
|
||||
def find(self, name: t.Union[int, str]) -> tuple[int, str]:
|
||||
"""
|
||||
Args:
|
||||
name:
|
||||
|
||||
Returns:
|
||||
text id (hash in TextMap)
|
||||
text
|
||||
"""
|
||||
if isinstance(name, int) or (isinstance(name, str) and name.isdigit()):
|
||||
name = int(name)
|
||||
try:
|
||||
return name, self.data[name]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
name = str(name)
|
||||
for row_id, row_name in self.data.items():
|
||||
if row_id >= 0 and row_name == name:
|
||||
return row_id, row_name
|
||||
for row_id, row_name in self.data.items():
|
||||
if row_name == name:
|
||||
return row_id, row_name
|
||||
logger.error(f'Cannot find name: "{name}" in language {self.lang}')
|
||||
return 0, ''
|
||||
|
||||
|
||||
def replace_templates(text: str) -> str:
|
||||
"""
|
||||
Replace templates in data to make sure it equals to what is shown in game
|
||||
|
||||
Examples:
|
||||
replace_templates("Complete Echo of War #4 time(s)")
|
||||
== "Complete Echo of War 1 time(s)"
|
||||
"""
|
||||
text = re.sub(r'#4', '1', text)
|
||||
text = re.sub(r'</?\w+>', '', text)
|
||||
return text
|
||||
|
||||
|
||||
class KeywordExtract:
|
||||
def __init__(self):
|
||||
self.text_map: dict[str, TextMap] = {lang: TextMap(lang) for lang in UI_LANGUAGES}
|
||||
self.text_map['cn'] = TextMap('chs')
|
||||
self.keywords_id: list[int] = []
|
||||
|
||||
def find_keyword(self, keyword, lang) -> tuple[int, str]:
|
||||
"""
|
||||
Args:
|
||||
keyword: text string or text id
|
||||
lang: Language to find
|
||||
|
||||
Returns:
|
||||
text id (hash in TextMap)
|
||||
text
|
||||
"""
|
||||
text_map = self.text_map[lang]
|
||||
return text_map.find(keyword)
|
||||
|
||||
def load_keywords(self, keywords: list[str | int], lang='cn'):
|
||||
text_map = self.text_map[lang]
|
||||
keywords_id = [text_map.find(keyword)[0] for keyword in keywords]
|
||||
self.keywords_id = [keyword for keyword in keywords_id if keyword != 0]
|
||||
|
||||
def clear_keywords(self):
|
||||
self.keywords_id = []
|
||||
|
||||
def write_keywords(
|
||||
self,
|
||||
keyword_class,
|
||||
output_file: str = '',
|
||||
text_convert=text_to_variable,
|
||||
generator: CodeGenerator = None,
|
||||
extra_attrs: dict[str, dict] = None
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
keyword_class:
|
||||
output_file:
|
||||
text_convert:
|
||||
generator: Reuse an existing code generator
|
||||
extra_attrs: Extra attributes write in keywords
|
||||
"""
|
||||
if generator is None:
|
||||
gen = CodeGenerator()
|
||||
gen.Import(f"""
|
||||
from .classes import {keyword_class}
|
||||
""")
|
||||
gen.CommentAutoGenerage('dev_tools.keyword_extract')
|
||||
else:
|
||||
gen = generator
|
||||
|
||||
last_id = getattr(gen, 'last_id', 0)
|
||||
if extra_attrs:
|
||||
keyword_num = len(self.keywords_id)
|
||||
for attr_key, attr_value in extra_attrs.items():
|
||||
if len(attr_value) != keyword_num:
|
||||
print(f"Extra attribute {attr_key} does not match the size of keywords")
|
||||
return
|
||||
for index, keyword in enumerate(self.keywords_id):
|
||||
_, name = self.find_keyword(keyword, lang='en')
|
||||
name = text_convert(replace_templates(name))
|
||||
with gen.Object(key=name, object_class=keyword_class):
|
||||
gen.ObjectAttr(key='id', value=index + last_id + 1)
|
||||
gen.ObjectAttr(key='name', value=name)
|
||||
for lang in UI_LANGUAGES:
|
||||
gen.ObjectAttr(key=lang, value=replace_templates(self.find_keyword(keyword, lang=lang)[1]))
|
||||
if extra_attrs:
|
||||
for attr_key, attr_value in extra_attrs.items():
|
||||
gen.ObjectAttr(key=attr_key, value=attr_value[keyword])
|
||||
gen.last_id = index + last_id + 1
|
||||
|
||||
if output_file:
|
||||
print(f'Write {output_file}')
|
||||
gen.write(output_file)
|
||||
self.clear_keywords()
|
||||
return gen
|
||||
|
||||
def generate(self):
|
||||
self.load_keywords(['模拟宇宙', '拟造花萼(金)', '拟造花萼(赤)', '凝滞虚影', '侵蚀隧洞', '历战余响', '忘却之庭'])
|
||||
self.write_keywords(keyword_class='DungeonNav', output_file='./tasks/dungeon/keywords/nav.py')
|
||||
self.load_keywords(['行动摘要', '生存索引', '每日实训'])
|
||||
self.write_keywords(keyword_class='DungeonTab', output_file='./tasks/dungeon/keywords/tab.py')
|
||||
self.load_keywords(['前往', '领取', '进行中', '已领取', '本日活跃度已满'])
|
||||
self.write_keywords(keyword_class='DailyQuestState', output_file='./tasks/daily/keywords/daily_quest_state.py')
|
||||
self.load_keywords(['领取', '追踪'])
|
||||
self.write_keywords(keyword_class='BattlePassQuestState',
|
||||
output_file='./tasks/battle_pass/keywords/quest_state.py')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
TextMap.DATA_FOLDER = '../StarRailData'
|
||||
KeywordExtract().generate()
|
||||
@@ -0,0 +1,91 @@
|
||||
import os
|
||||
import re
|
||||
from dataclasses import dataclass, fields
|
||||
|
||||
from module.base.code_generator import CodeGenerator
|
||||
|
||||
|
||||
@dataclass
|
||||
class RouteData:
|
||||
name: str
|
||||
route: str
|
||||
plane: str
|
||||
floor: str = 'F1'
|
||||
position: tuple = None
|
||||
|
||||
|
||||
class RouteExtract:
|
||||
def __init__(self, folder):
|
||||
self.folder = folder
|
||||
|
||||
def iter_files(self):
|
||||
for path, folders, files in os.walk(self.folder):
|
||||
path = path.replace('\\', '/')
|
||||
for file in files:
|
||||
if file.endswith('.py'):
|
||||
yield f'{path}/{file}'
|
||||
|
||||
def extract_route(self, file):
|
||||
print(f'Extract {file}')
|
||||
with open(file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
"""
|
||||
def route_item_enemy(self):
|
||||
self.enter_himeko_trial()
|
||||
self.map_init(plane=Jarilo_BackwaterPass, position=(519.9, 361.5))
|
||||
"""
|
||||
regex = re.compile(
|
||||
r'def (?P<func>[a-zA-Z0-9_]*?)\(self\):.*?'
|
||||
r'self\.map_init\((.*?)\)'
|
||||
, re.DOTALL)
|
||||
file = file.replace(self.folder, '').replace('.py', '').replace('/', '_').strip('_')
|
||||
module = f"{self.folder.strip('./').replace('/', '.')}.{file.replace('_', '.')}"
|
||||
|
||||
for result in regex.findall(content):
|
||||
func, data = result
|
||||
|
||||
res = re.search(r'plane=([a-zA-Z_]*)', data)
|
||||
if res:
|
||||
plane = res.group(1)
|
||||
else:
|
||||
# Must contain plane
|
||||
continue
|
||||
res = re.search(r'floor=([\'"a-zA-Z0-9_]*)', data)
|
||||
if res:
|
||||
floor = res.group(1).strip('"\'')
|
||||
else:
|
||||
floor = 'F1'
|
||||
res = re.search(r'position=\(([0-9.]*)[, ]+([0-9.]*)', data)
|
||||
if res:
|
||||
position = (float(res.group(1)), float(res.group(2)))
|
||||
else:
|
||||
position = None
|
||||
|
||||
yield RouteData(
|
||||
name=f'{file}__{func}',
|
||||
route=f'{module}:{func}',
|
||||
plane=plane,
|
||||
floor=floor,
|
||||
position=position,
|
||||
)
|
||||
|
||||
def write(self, file):
|
||||
gen = CodeGenerator()
|
||||
gen.Import("""
|
||||
from tasks.map.route.base import RouteData
|
||||
""")
|
||||
gen.CommentAutoGenerage('dev_tools.route_extract')
|
||||
|
||||
for f in self.iter_files():
|
||||
for row in self.extract_route(f):
|
||||
with gen.Object(key=row.name, object_class='RouteData'):
|
||||
for key in fields(row):
|
||||
value = getattr(row, key.name)
|
||||
gen.ObjectAttr(key.name, value)
|
||||
gen.write(file)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
os.chdir(os.path.join(os.path.dirname(__file__), '../'))
|
||||
RouteExtract('./route/daily').write('./tasks/map/route/route/daily.py')
|
||||
@@ -0,0 +1,108 @@
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from pynput import keyboard
|
||||
from module.config.config import AzurLaneConfig
|
||||
from module.config.utils import alas_instance
|
||||
from module.device.connection import Connection, ConnectionAttr
|
||||
from module.device.device import Device
|
||||
from module.logger import logger
|
||||
|
||||
"""
|
||||
A tool to take screenshots on device
|
||||
|
||||
Usage:
|
||||
python -m dev_tools.screenshot
|
||||
"""
|
||||
|
||||
|
||||
class EmptyConnection(Connection):
|
||||
def __init__(self):
|
||||
ConnectionAttr.__init__(self, AzurLaneConfig('template'))
|
||||
|
||||
logger.hr('Detect device')
|
||||
print()
|
||||
print('这里是你本机可用的模拟器serial:')
|
||||
devices = self.list_device()
|
||||
|
||||
# Show available devices
|
||||
available = devices.select(status='device')
|
||||
for device in available:
|
||||
print(device.serial)
|
||||
if not len(available):
|
||||
print('No available devices')
|
||||
|
||||
# Show unavailable devices if having any
|
||||
unavailable = devices.delete(available)
|
||||
if len(unavailable):
|
||||
print('Here are the devices detected but unavailable')
|
||||
for device in unavailable:
|
||||
print(f'{device.serial} ({device.status})')
|
||||
|
||||
|
||||
def handle_sensitive_info(image):
|
||||
# Paint UID to black
|
||||
image[680:720, 0:180, :] = 0
|
||||
return image
|
||||
|
||||
|
||||
_ = EmptyConnection()
|
||||
name = input(
|
||||
'输入alas配置文件名称,或者模拟器serial,或者模拟器端口号: (默认输入 "alas"):\n'
|
||||
'例如:"alas", "127.0.0.1:16384", "7555"\n'
|
||||
)
|
||||
name = name.strip().strip('"').strip()
|
||||
if not name:
|
||||
name = 'alas'
|
||||
if name.isdigit():
|
||||
name = f'127.0.0.1:{name}'
|
||||
if name in alas_instance():
|
||||
print(f'{name} is an existing config file')
|
||||
device = Device(name)
|
||||
else:
|
||||
print(f'{name} is a device serial')
|
||||
config = AzurLaneConfig('template')
|
||||
config.override(
|
||||
Emulator_Serial=name,
|
||||
Emulator_PackageName='com.miHoYo.hkrpg',
|
||||
Emulator_ScreenshotMethod='adb_nc',
|
||||
)
|
||||
device = Device(config)
|
||||
|
||||
output = './screenshots/dev_screenshots'
|
||||
os.makedirs(output, exist_ok=True)
|
||||
device.disable_stuck_detection()
|
||||
device.screenshot_interval_set(0.)
|
||||
print('')
|
||||
print(f'截图将保存到: {output}')
|
||||
|
||||
|
||||
def screenshot():
|
||||
print(f'截图中...')
|
||||
image = device.screenshot()
|
||||
now = datetime.strftime(datetime.now(), '%Y-%m-%d_%H-%M-%S-%f')
|
||||
file = f'{output}/{now}.png'
|
||||
image = handle_sensitive_info(image)
|
||||
print(f'截图中...')
|
||||
Image.fromarray(image).save(file)
|
||||
print(f'截图已保存到: {file}')
|
||||
|
||||
|
||||
# Bind global shortcut
|
||||
GLOBAL_KEY = 'F3'
|
||||
|
||||
|
||||
def on_press(key):
|
||||
if str(key) == f'Key.{GLOBAL_KEY.lower()}':
|
||||
screenshot()
|
||||
|
||||
|
||||
listener = keyboard.Listener(on_press=on_press)
|
||||
listener.start()
|
||||
|
||||
while 1:
|
||||
print()
|
||||
_ = input(f'按 <回车键> 或者按快捷键 <{GLOBAL_KEY}> 截一张图(快捷键全局生效):')
|
||||
screenshot()
|
||||
Reference in New Issue
Block a user