Add get_screens.py
This commit is contained in:
parent
428b21e33e
commit
2e6d80c61c
232
get_screens.py
Normal file
232
get_screens.py
Normal file
@ -0,0 +1,232 @@
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import csv
|
||||
import cv2
|
||||
import xml.etree.ElementTree as ET
|
||||
from pyzbar.pyzbar import decode
|
||||
import shutil
|
||||
|
||||
# --- НАСТРОЙКИ ---
|
||||
ADB_PATH = "adb"
|
||||
OUTPUT_CSV = "stocard_export.csv"
|
||||
MANUAL_FOLDER = "pdf417_manual_cards"
|
||||
|
||||
# Увеличенные задержки для старых или медленных телефонов (в секундах)
|
||||
DELAY_OPEN_CARD = 1.0 # Сколько ждать, пока откроется карта и появится штрихкод
|
||||
DELAY_BACK = 0.5 # Сколько ждать после нажатия кнопки "Назад"
|
||||
DELAY_SCROLL = 0.5 # Сколько ждать после прокрутки списка
|
||||
# ------------------
|
||||
|
||||
def run_adb(command):
|
||||
return os.popen(f"{ADB_PATH} {command}").read()
|
||||
|
||||
def ensure_stocard_is_open():
|
||||
"""Проверяет, открыт ли Stocard, и если нет — запускает его MainActivity"""
|
||||
activity_info = run_adb("shell dumpsys window displays | grep mCurrentFocus")
|
||||
if "de.stocard.stocard" not in activity_info:
|
||||
print("[ЗАЩИТА] Похоже, мы вылетели из приложения. Возвращаю Stocard...")
|
||||
# Запуск через обход блокировки (MainActivity)
|
||||
run_adb("shell am start -n de.stocard.stocard/de.stocard.ui.main.MainActivity")
|
||||
time.sleep(3.0)
|
||||
|
||||
def get_current_xml():
|
||||
"""Стягивает актуальный снимок экрана XML с телефона"""
|
||||
ensure_stocard_is_open()
|
||||
run_adb("shell uiautomator dump /sdcard/current_dump.xml")
|
||||
run_adb("pull /sdcard/current_dump.xml .")
|
||||
run_adb("shell rm /sdcard/current_dump.xml")
|
||||
return "current_dump.xml"
|
||||
|
||||
def click_at(x, y):
|
||||
ensure_stocard_is_open()
|
||||
run_adb(f"shell input tap {x} {y}")
|
||||
time.sleep(DELAY_OPEN_CARD) # Медленное ожидание открытия штрихкода
|
||||
|
||||
def press_back():
|
||||
run_adb("shell input keyevent 4")
|
||||
time.sleep(DELAY_BACK)
|
||||
|
||||
def scroll_down():
|
||||
"""Листает список карт вниз, чтобы подгрузить новые"""
|
||||
ensure_stocard_is_open()
|
||||
print("Прокручиваю список вниз...")
|
||||
run_adb("shell input swipe 540 1800 540 600 500")
|
||||
time.sleep(DELAY_SCROLL)
|
||||
|
||||
def parse_card_title():
|
||||
"""Делает дамп экрана ОТКРЫТОЙ карты и пытается вытащить название магазина"""
|
||||
run_adb("shell uiautomator dump /sdcard/card_dump.xml")
|
||||
run_adb("pull /sdcard/card_dump.xml .")
|
||||
run_adb("shell rm /sdcard/card_dump.xml")
|
||||
|
||||
title = "Неизвестный магазин"
|
||||
if not os.path.exists("card_dump.xml"):
|
||||
return title
|
||||
|
||||
try:
|
||||
tree = ET.parse("card_dump.xml")
|
||||
root = tree.getroot()
|
||||
|
||||
# Собираем все текстовые строки с экрана
|
||||
possible_titles = []
|
||||
for elem in root.iter('node'):
|
||||
text = elem.get('text', '').strip()
|
||||
package = elem.get('package', '')
|
||||
|
||||
# Отсекаем служебные тексты, цифры (номера карт) и пустые строки
|
||||
if text and 'stocard' in package.lower():
|
||||
if not text.isdigit() and len(text) > 1 and text.lower() not in ['назад', 'back', 'share', 'поделиться', 'меню', 'menu']:
|
||||
possible_titles.append(text)
|
||||
|
||||
# Обычно название магазина — это самый первый или самый верхний текст на экране карточки
|
||||
if possible_titles:
|
||||
title = possible_titles[0]
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ОШИБКА парсинга названия]: {e}")
|
||||
|
||||
if os.path.exists("card_dump.xml"):
|
||||
os.remove("card_dump.xml")
|
||||
|
||||
return title
|
||||
|
||||
def capture_and_scan(card_number_id):
|
||||
"""Делает скриншот открытой карты, распознает штрихкод и вытаскивает название"""
|
||||
# 1. Сначала вытаскиваем название из XML открытой карты
|
||||
card_title = parse_card_title()
|
||||
|
||||
# 2. Делаем скриншот для распознавания штрихкода
|
||||
run_adb("shell screencap -p /sdcard/screen.png")
|
||||
run_adb("pull /sdcard/screen.png .")
|
||||
run_adb("shell rm /sdcard/screen.png")
|
||||
|
||||
img = cv2.imread("screen.png")
|
||||
if img is None:
|
||||
return card_title, None, None
|
||||
|
||||
# 3. Пробуем распознать стандартный штрихкод
|
||||
detected = decode(img)
|
||||
for barcode in detected:
|
||||
return card_title, barcode.data.decode('utf-8'), barcode.type
|
||||
|
||||
# 4. ПЛАН Б: Если код не распознан (например, PDF417), сохраняем скриншот
|
||||
# Используем название магазина в имени файла, чтобы было удобно смотреть глазами
|
||||
safe_title = "".join([c for c in card_title if c.isalpha() or c.isdigit() or c==' ']).rstrip()
|
||||
manual_screenshot_path = os.path.join(MANUAL_FOLDER, f"{card_number_id}_{safe_title}.png")
|
||||
shutil.copy("screen.png", manual_screenshot_path)
|
||||
|
||||
return card_title, f"РУЧНОЙ_ВВОД (См. скриншот {card_number_id}_{safe_title}.png)", "PDF417_OR_UNKNOWN"
|
||||
|
||||
def extract_coordinates(xml_path):
|
||||
"""Ищет координаты только видимых на экране контейнеров карт и фильтрует дубликаты"""
|
||||
centers = []
|
||||
try:
|
||||
tree = ET.parse(xml_path)
|
||||
root = tree.getroot()
|
||||
except Exception as e:
|
||||
return centers
|
||||
|
||||
for elem in root.iter('node'):
|
||||
package = elem.get('package', '')
|
||||
if 'stocard' in package.lower():
|
||||
bounds = elem.get('bounds', '')
|
||||
resource_id = elem.get('resource-id', '')
|
||||
|
||||
if bounds and ('card' in resource_id.lower() or 'item' in resource_id.lower() or 'container' in resource_id.lower()):
|
||||
match = re.findall(r'\d+', bounds)
|
||||
if match and len(match) == 4:
|
||||
x1, y1, x2, y2 = map(int, match)
|
||||
|
||||
if y1 > 250 and y2 < 2100:
|
||||
center_x = int((x1 + x2) / 2)
|
||||
center_y = int((y1 + y2) / 2)
|
||||
|
||||
# --- УМНАЯ ФИЛЬТРАЦИЯ ПО ДИСТАНЦИИ ---
|
||||
# Проверяем, нет ли уже в списке точки, которая находится слишком близко
|
||||
is_duplicate = False
|
||||
for (existing_x, existing_y) in centers:
|
||||
# Если расстояние по X меньше 100 и по Y меньше 200 — это та же самая карта
|
||||
if abs(existing_x - center_x) < 100 and abs(existing_y - center_y) < 200:
|
||||
is_duplicate = True
|
||||
break
|
||||
|
||||
if not is_duplicate:
|
||||
centers.append((center_x, center_y))
|
||||
return centers
|
||||
|
||||
def main():
|
||||
# Создаем папку для проблемных карт, если её нет
|
||||
if not os.path.exists(MANUAL_FOLDER):
|
||||
os.makedirs(MANUAL_FOLDER)
|
||||
|
||||
print("=== ЗАПУСК ПОЛНОЙ АВТОМАТИЗАЦИИ СБОРА КАРТ (МЕДЛЕННЫЙ РЕЖИМ) ===")
|
||||
print("Инструкция: Откройте самый верх главного списка в Stocard.")
|
||||
input("Нажмите Enter для старта...")
|
||||
|
||||
file_exists = os.path.isfile(OUTPUT_CSV)
|
||||
with open(OUTPUT_CSV, mode="a", encoding="utf-8", newline="") as csv_file:
|
||||
writer = csv.writer(csv_file)
|
||||
if not file_exists:
|
||||
writer.writerow(["Название", "Номер карты", "Тип штрихкода"])
|
||||
|
||||
saved_numbers = set()
|
||||
consecutive_duplicates = 0
|
||||
card_counter = 1
|
||||
|
||||
while True:
|
||||
xml_file = get_current_xml()
|
||||
coordinates = extract_coordinates(xml_file)
|
||||
|
||||
if not coordinates:
|
||||
print("[ВНИМАНИЕ] На этом экране карты не найдены. Пробую прокрутить...")
|
||||
scroll_down()
|
||||
continue
|
||||
|
||||
new_cards_on_screen = 0
|
||||
print(f"На текущем экране обнаружено точек для проверки: {len(coordinates)}")
|
||||
|
||||
for (x, y) in coordinates:
|
||||
print(f"Кликаю на карту в точку ({x}, {y})...")
|
||||
click_at(x, y)
|
||||
|
||||
# Получаем Название, Номер и Тип из обновленной функции
|
||||
card_title, card_number, card_type = capture_and_scan(card_counter)
|
||||
|
||||
if card_number:
|
||||
# Проверяем на дубликаты (для обычных карт) или сохраняем, если это PDF417
|
||||
if card_number not in saved_numbers or card_type == "PDF417_OR_UNKNOWN":
|
||||
saved_numbers.add(card_number)
|
||||
|
||||
# Записываем РЕАЛЬНОЕ НАЗВАНИЕ вместо "Карта Х"
|
||||
writer.writerow([card_title, card_number, card_type])
|
||||
csv_file.flush()
|
||||
|
||||
print(f"[СОХРАНЕНО] #{card_counter}: {card_title} -> {card_number} ({card_type})")
|
||||
card_counter += 1
|
||||
new_cards_on_screen += 1
|
||||
consecutive_duplicates = 0
|
||||
else:
|
||||
print(f"[ДУБЛИКАТ] Карта магазина {card_title} уже в базе.")
|
||||
else:
|
||||
print("[ПРОПУСК] Экран пустой или произошла ошибка чтения.")
|
||||
|
||||
print("Возвращаюсь обратно...")
|
||||
press_back()
|
||||
|
||||
if os.path.exists(xml_file):
|
||||
os.remove(xml_file)
|
||||
|
||||
if new_cards_on_screen == 0:
|
||||
consecutive_duplicates += 1
|
||||
if consecutive_duplicates >= 2:
|
||||
print("\n[УСПЕХ] Новые карты больше не появляются. Список полностью обработан!")
|
||||
break
|
||||
|
||||
scroll_down()
|
||||
|
||||
print(f"\nСбор завершен! Всего сохранено строк: {card_counter - 1}")
|
||||
print(f"Данные сохранены в: {os.path.abspath(OUTPUT_CSV)}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
x
Reference in New Issue
Block a user