有名な某サイト〇yaaをサムネイルで見やすくデザインしなおした。
このサイトを知ってる方は多いかと思いますが、なにせ文字だらけで大変
そこでbingからサムネを取得してくるように
ソフトの起動には組み込みでqtorrentが必要
import sys
import requests
import webbrowser
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QMutex, QSemaphore, QTimer, QDateTime
from PyQt5.QtGui import QImage, QPixmap, QIcon
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QTableWidget, QTableWidgetItem, QLabel, QPushButton, QComboBox, QLineEdit, QWidget, QHeaderView, QHBoxLayout, QStyleFactory, QStatusBar, QDialog, QScrollArea, QFileDialog, QAction, QMenu, QMenuBar, QMessageBox, QAbstractScrollArea, QProgressBar, QAbstractItemView
import json
from PIL import Image
import io
import os
from bs4 import BeautifulSoup
import qbittorrentapi
from datetime import datetime
MAX_THREADS = 4 # 一度に実行するスレッドの数を制限
ITEMS_PER_PAGE = 20 # 1ページあたりの表示アイテム数
ITEMS_PER_SCRAPE_PAGE = 100 # 1スクレイプページあたりの最大アイテム数
SETTINGS_FILE = "settings.json"
class ClickableLabel(QLabel):
def __init__(self, url, full_image, parent=None):
super().__init__(parent)
self.url = url
self.full_image = full_image
def mousePressEvent(self, event):
self.show_image_dialog(self.full_image)
@staticmethod
def show_image_dialog(image):
dialog = QDialog()
dialog.setWindowTitle("画像拡大表示")
dialog.setGeometry(100, 100, 800, 600) # ウィンドウサイズを設定
layout = QVBoxLayout()
label = QLabel()
pixmap = QPixmap.fromImage(image)
label.setPixmap(pixmap.scaled(pixmap.width() * 4, pixmap.height() * 4, Qt.KeepAspectRatio, Qt.SmoothTransformation)) # 2倍に拡大表示
scroll_area = QScrollArea()
scroll_area.setWidget(label)
layout.addWidget(scroll_area)
dialog.setLayout(layout)
dialog.exec_()
class ImageLoaderThread(QThread):
image_loaded = pyqtSignal(int, QImage, QImage, str, bool)
increment_queue = pyqtSignal()
decrement_queue = pyqtSignal()
def __init__(self, row_index, query, refer_url, semaphore, category=False):
super().__init__()
self.row_index = row_index
self.query = query
self.refer_url = refer_url
self.category = category
self.semaphore = semaphore
def run(self):
self.increment_queue.emit()
try:
if self.category:
response = requests.get(self.query, headers={'User-Agent': 'Mozilla/5.0'})
if 'image' in response.headers['Content-Type']:
image_data = response.content
with Image.open(io.BytesIO(image_data)) as img:
# フルサイズ画像を保存
byte_array_full = io.BytesIO()
img.save(byte_array_full, format='PNG')
q_image_full = QImage.fromData(byte_array_full.getvalue())
# サムネイル画像を作成
img.thumbnail((150, 150))
byte_array_thumb = io.BytesIO()
img.save(byte_array_thumb, format='PNG')
q_image_thumb = QImage.fromData(byte_array_thumb.getvalue())
self.image_loaded.emit(self.row_index, q_image_thumb, q_image_full, self.refer_url, self.category)
else:
search_url = f"https://www.bing.com/images/search?q={self.query}"
response = requests.get(search_url, headers={'User-Agent': 'Mozilla/5.0'})
soup = BeautifulSoup(response.content, 'html.parser')
img_tag = soup.find('img', class_='mimg')
if img_tag:
img_url = img_tag.get('src')
response = requests.get(img_url, headers={'User-Agent': 'Mozilla/5.0'})
if 'image' in response.headers['Content-Type']:
image_data = response.content
with Image.open(io.BytesIO(image_data)) as img:
# フルサイズ画像を保存
byte_array_full = io.BytesIO()
img.save(byte_array_full, format='PNG')
q_image_full = QImage.fromData(byte_array_full.getvalue())
# サムネイル画像を作成
img.thumbnail((150, 150))
byte_array_thumb = io.BytesIO()
img.save(byte_array_thumb, format='PNG')
q_image_thumb = QImage.fromData(byte_array_thumb.getvalue())
self.image_loaded.emit(self.row_index, q_image_thumb, q_image_full, self.refer_url, self.category)
except Exception as e:
print(f"Error loading image for row {self.row_index}: {e}")
finally:
self.decrement_queue.emit()
self.semaphore.release() # スレッドが完了したらセマフォを解放
class DownloadManagerWindow(QMainWindow):
def __init__(self, qb):
super().__init__()
self.qb = qb
self.setWindowTitle("ダウンロード進行状況")
self.setGeometry(150, 150, 600, 400)
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.layout = QVBoxLayout(self.central_widget)
self.table = QTableWidget()
self.table.setColumnCount(4)
self.table.setHorizontalHeaderLabels(['名前', '進行状況', '操作', '日付'])
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
self.table.setContextMenuPolicy(Qt.CustomContextMenu)
self.table.customContextMenuRequested.connect(self.show_context_menu)
self.layout.addWidget(self.table)
self.timer = QTimer(self)
self.timer.timeout.connect(self.update_progress)
self.timer.start(1000) # 1秒ごとに更新
def update_progress(self):
try:
for torrent in self.qb.torrents_info():
row_count = self.table.rowCount()
for row in range(row_count):
if self.table.item(row, 0).text() == torrent.name:
progress_bar = self.table.cellWidget(row, 1)
if progress_bar:
progress_bar.setValue(int(torrent.progress * 100))
if torrent.state == 'uploading':
pause_button = self.table.cellWidget(row, 2).findChild(QPushButton)
if pause_button:
pause_button.setText('再開')
break
else:
self.add_torrent(torrent)
except Exception as e:
print(f"Error updating progress: {e}")
def add_torrent(self, torrent):
row_count = self.table.rowCount()
self.table.insertRow(row_count)
self.table.setItem(row_count, 0, QTableWidgetItem(torrent.name))
progress_bar = QProgressBar()
progress_bar.setValue(int(torrent.progress * 100))
self.table.setCellWidget(row_count, 1, progress_bar)
pause_button = QPushButton('一時停止')
pause_button.clicked.connect(lambda: self.toggle_torrent(torrent))
widget = QWidget()
layout = QHBoxLayout(widget)
layout.addWidget(pause_button)
layout.setAlignment(Qt.AlignCenter)
widget.setLayout(layout)
self.table.setCellWidget(row_count, 2, widget)
self.table.setItem(row_count, 3, QTableWidgetItem(datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
def toggle_torrent(self, torrent):
if torrent.state == 'uploading' or torrent.state == 'stalledUP':
self.qb.torrents_pause(torrent_hashes=torrent.hash)
else:
self.qb.torrents_resume(torrent_hashes=torrent.hash)
def show_context_menu(self, pos):
context_menu = QMenu(self)
open_folder_action = context_menu.addAction("フォルダを開く")
delete_action = context_menu.addAction("削除")
pause_action = context_menu.addAction("一時停止/再開")
action = context_menu.exec_(self.table.viewport().mapToGlobal(pos))
if action == open_folder_action:
self.open_folder()
elif action == delete_action:
self.delete_torrent()
elif action == pause_action:
self.pause_resume_torrent()
def open_folder(self):
current_row = self.table.currentRow()
if current_row >= 0:
torrent_name = self.table.item(current_row, 0).text()
for torrent in self.qb.torrents_info():
if torrent.name == torrent_name:
os.startfile(torrent.save_path)
break
def delete_torrent(self):
current_row = self.table.currentRow()
if current_row >= 0:
torrent_name = self.table.item(current_row, 0).text()
for torrent in self.qb.torrents_info():
if torrent.name == torrent_name:
self.qb.torrents_delete(torrent_hashes=torrent.hash)
self.table.removeRow(current_row)
break
def pause_resume_torrent(self):
current_row = self.table.currentRow()
if current_row >= 0:
torrent_name = self.table.item(current_row, 0).text()
for torrent in self.qb.torrents_info():
if torrent.name == torrent_name:
if torrent.state == 'uploading' or torrent.state == 'stalledUP':
self.qb.torrents_pause(torrent_hashes=torrent.hash)
else:
self.qb.torrents_resume(torrent_hashes=torrent.hash)
break
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Nyaa.si スクレイパー")
self.setGeometry(100, 100, 1200, 900) # ウィンドウのサイズを調整
self.setWindowIcon(QIcon("avatar-U99s-Q.png"))
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.layout = QVBoxLayout(self.central_widget)
self.query_input = QLineEdit()
self.query_input.setPlaceholderText("検索")
self.query_input.returnPressed.connect(self.search) # Enterキーで検索
self.layout.addWidget(self.query_input)
self.category_combo = QComboBox()
self.category_combo.addItems([
'すべてのカテゴリー', # All categories
'本', # Books
'アニメ', # Anime
'MP3', # MP3
'FLAC', # FLAC
'実写番組', # Live Action
'字幕番組', # Subtitled Live Action
'ソフトウェア', # Software
'ゲーム' # Games
])
self.layout.addWidget(self.category_combo)
self.search_button = QPushButton("検索")
self.search_button.setStyleSheet("background-color: #337ab7; color: white; padding: 10px 24px; font-size: 16px;")
self.search_button.clicked.connect(self.search)
self.layout.addWidget(self.search_button)
self.table = QTableWidget()
self.table.setColumnCount(9)
self.table.setHorizontalHeaderLabels(['カテゴリー', '画像', '名前', 'サイズ', '日付', 'シーダー', 'リーチャー', '完了', 'マグネットリンク'])
self.table.horizontalHeader().sectionClicked.connect(self.header_clicked)
self.table.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents)
self.layout.addWidget(self.table)
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
self.table.setColumnWidth(0, 100) # Category
self.table.setColumnWidth(1, 150) # Image
self.table.setColumnWidth(2, 300) # Name
self.table.setColumnWidth(3, 100) # Size
self.table.setColumnWidth(4, 150) # Date
self.table.setColumnWidth(5, 80) # Seeders
self.table.setColumnWidth(6, 80) # Leechers
self.table.setColumnWidth(7, 100) # Completed
self.table.setColumnWidth(8, 150) # Magnet Link
self.table.verticalHeader().setDefaultSectionSize(50) # 行の高さを設定
self.pagination_layout = QHBoxLayout()
self.prev_button = QPushButton("前へ")
self.prev_button.setStyleSheet("background-color: #337ab7; color: white; padding: 10px 24px; font-size: 16px;")
self.prev_button.clicked.connect(self.prev_page)
self.next_button = QPushButton("次へ")
self.next_button.setStyleSheet("background-color: #337ab7; color: white; padding: 10px 24px; font-size: 16px;")
self.next_button.clicked.connect(self.next_page)
self.page_combo = QComboBox()
self.page_combo.addItems([str(i) for i in range(1, 101)])
self.page_combo.currentIndexChanged.connect(self.page_changed)
self.pagination_layout.addWidget(self.prev_button)
self.pagination_layout.addWidget(self.page_combo)
self.pagination_layout.addWidget(self.next_button)
self.layout.addLayout(self.pagination_layout)
self.current_page = 1
self.current_subpage = 1
self.threads = []
self.queue_count = 0
self.mutex = QMutex()
self.semaphore = QSemaphore(MAX_THREADS) # セマフォを使用してスレッドの数を制限
self.status_bar = QStatusBar()
self.setStatusBar(self.status_bar)
self.update_queue_count()
self.setStyle(QStyleFactory.create('Fusion'))
self.setStyleSheet("""
QMainWindow {
background-color: #f5f5f5;
}
QLineEdit, QComboBox, QTableWidget {
font-size: 16px;
padding: 5px;
}
QTableWidget::item {
padding: 10px;
}
QLabel, QPushButton {
font-size: 16px;
}
QPushButton {
background-color: #337ab7;
color: white;
padding: 10px 24px;
font-size: 16px;
}
""")
self.sort_order = Qt.AscendingOrder
self.download_folders = {
'すべてのカテゴリー': '',
'本': '', # Books
'アニメ': '', # Anime
'MP3': '', # MP3
'FLAC': '', # FLAC
'実写番組': '', # Live Action
'字幕番組': '', # Subtitled Live Action
'ソフトウェア': '', # Software
'ゲーム': '' # Games
}
self.load_settings() # 設定を読み込む
self.qb = qbittorrentapi.Client(host='localhost', port=8080) # qBittorrentに接続
try:
self.qb.auth_log_in(username='admin', password='あなたの') # 認証
except qbittorrentapi.LoginFailed as e:
print(f'ログインに失敗しました: {e}')
sys.exit(1)
self.timer = QTimer(self)
self.timer.timeout.connect(self.update_progress)
self.timer.start(1000) # 1秒ごとに更新
self.download_torrents = {} # ダウンロード中のトレントを管理する辞書
self.create_menu()
def create_menu(self):
menu_bar = QMenuBar(self)
self.setMenuBar(menu_bar)
file_menu = QMenu("ファイル", self)
menu_bar.addMenu(file_menu)
download_list_action = QAction("ダウンロードリスト", self)
download_list_action.triggered.connect(self.show_download_manager)
file_menu.addAction(download_list_action)
folder_action = QAction("ダウンロードフォルダ設定", self)
folder_action.triggered.connect(self.show_folder_dialog)
file_menu.addAction(folder_action)
settings_menu = QMenu("設定", self)
menu_bar.addMenu(settings_menu)
items_per_page_action = QAction("表示アイテム数", self)
items_per_page_action.triggered.connect(self.show_items_per_page_dialog)
settings_menu.addAction(items_per_page_action)
login_action = QAction("ログイン設定", self)
login_action.triggered.connect(self.show_login_dialog)
settings_menu.addAction(login_action)
options_menu = QMenu("オプション", self)
menu_bar.addMenu(options_menu)
open_qbittorrent_action = QAction("qBittorrentを開く", self)
open_qbittorrent_action.triggered.connect(lambda: webbrowser.open('http://localhost:8080'))
options_menu.addAction(open_qbittorrent_action)
def show_login_dialog(self):
dialog = QDialog(self)
dialog.setWindowTitle("ログイン情報の設定")
layout = QVBoxLayout()
username_input = QLineEdit()
username_input.setPlaceholderText("ユーザー名")
layout.addWidget(username_input)
password_input = QLineEdit()
password_input.setPlaceholderText("パスワード")
password_input.setEchoMode(QLineEdit.Password)
layout.addWidget(password_input)
save_button = QPushButton("保存")
save_button.clicked.connect(lambda: self.save_login_info(username_input.text(), password_input.text(), dialog))
layout.addWidget(save_button)
dialog.setLayout(layout)
dialog.exec_()
def save_login_info(self, username, password, dialog):
try:
self.qb.auth_log_in(username=username, password=password)
self.status_bar.showMessage("ログイン情報が保存されました。")
dialog.accept()
except qbittorrentapi.LoginFailed as e:
self.status_bar.showMessage(f"ログインに失敗しました: {e}")
def show_folder_dialog(self):
dialog = QDialog(self)
dialog.setWindowTitle("ダウンロードフォルダ設定")
layout = QVBoxLayout()
for category in self.download_folders.keys():
folder_input_layout = QHBoxLayout()
folder_label = QLabel(category)
folder_input_layout.addWidget(folder_label)
folder_button = QPushButton("フォルダを選択")
folder_button.clicked.connect(lambda _, cat=category: self.select_folder(cat))
folder_input_layout.addWidget(folder_button)
layout.addLayout(folder_input_layout)
save_button = QPushButton("保存")
save_button.clicked.connect(lambda: self.save_folders(dialog))
layout.addWidget(save_button)
dialog.setLayout(layout)
dialog.exec_()
def select_folder(self, category):
folder = QFileDialog.getExistingDirectory(self, f"{category}のフォルダを選択")
if folder:
self.download_folders[category] = folder
self.status_bar.showMessage(f"{category}のフォルダ: {folder}")
def save_folders(self, dialog):
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
json.dump(self.download_folders, f, ensure_ascii=False, indent=4)
self.status_bar.showMessage("フォルダ設定が保存されました。")
dialog.accept()
def load_settings(self):
if os.path.exists(SETTINGS_FILE):
with open(SETTINGS_FILE, 'r', encoding='utf-8') as f:
self.download_folders = json.load(f)
self.status_bar.showMessage("設定を読み込みました。")
def show_items_per_page_dialog(self):
dialog = QDialog(self)
dialog.setWindowTitle("表示アイテム数の設定")
layout = QVBoxLayout()
items_per_page_input = QLineEdit()
items_per_page_input.setPlaceholderText("表示アイテム数を入力")
layout.addWidget(items_per_page_input)
save_button = QPushButton("保存")
save_button.clicked.connect(lambda: self.save_items_per_page(items_per_page_input.text(), dialog))
layout.addWidget(save_button)
dialog.setLayout(layout)
dialog.exec_()
def save_items_per_page(self, items_per_page, dialog):
global ITEMS_PER_PAGE
try:
ITEMS_PER_PAGE = int(items_per_page)
self.status_bar.showMessage("表示アイテム数が設定されました。")
dialog.accept()
except ValueError:
self.status_bar.showMessage("無効な入力です。")
def show_download_manager(self):
self.download_manager_window = DownloadManagerWindow(self.qb)
self.download_manager_window.show()
def update_queue_count(self):
self.status_bar.showMessage(f"画像読み込み中: {self.queue_count}")
def increment_queue(self):
self.mutex.lock()
self.queue_count += 1
self.update_queue_count()
self.mutex.unlock()
def decrement_queue(self):
self.mutex.lock()
self.queue_count -= 1
self.update_queue_count()
self.mutex.unlock()
def search(self):
self.current_page = 1
self.current_subpage = 1
self.load_page()
def load_page(self):
query = self.query_input.text()
category_map = {
'すべてのカテゴリー': '0_0',
'本': '3_3',
'アニメ': '1_4',
'MP3': '2_2',
'FLAC': '2_1',
'実写番組': '4_4',
'字幕番組': '4_1',
'ソフトウェア': '6_1',
'ゲーム': '6_2'
}
category = category_map[self.category_combo.currentText()]
all_data = self.scrape_nyaa(query=query, category=category, page=self.current_page)
start_index = (self.current_subpage - 1) * ITEMS_PER_PAGE
end_index = start_index + ITEMS_PER_PAGE
data = all_data[start_index:end_index]
self.table.setRowCount(0)
self.images = []
for i, row in enumerate(data):
self.show_result(i, row)
for i, row in enumerate(data):
if row[0]: # カテゴリーアイコンのロード
category_icon_url = f"https://nyaa.si/static/img/icons/nyaa/{category}.png"
self.load_image_async(i, category_icon_url, row[0], category=True)
if row[9]:
self.load_image_async(i, row[9], row[1])
def prev_page(self):
if self.current_subpage > 1:
self.current_subpage -= 1
self.load_page()
elif self.current_page > 1:
self.current_page -= 1
self.current_subpage = (ITEMS_PER_SCRAPE_PAGE // ITEMS_PER_PAGE) # Set to last subpage
self.page_combo.setCurrentIndex(self.current_page - 1)
self.load_page()
def next_page(self):
if self.current_subpage < (ITEMS_PER_SCRAPE_PAGE // ITEMS_PER_PAGE):
self.current_subpage += 1
self.load_page()
elif self.current_page < 100:
self.current_page += 1
self.current_subpage = 1 # Reset to first subpage
self.page_combo.setCurrentIndex(self.current_page - 1)
self.load_page()
def page_changed(self):
self.current_page = int(self.page_combo.currentText())
self.current_subpage = 1 # Reset to first subpage
self.load_page()
def scrape_nyaa(self, query='', category='', page=1):
try:
url = f'https://nyaa.si/?c={category}&p={page}&q={query}'
response = requests.get(url, headers={'User-Agent': 'Mozilla/5.0'})
soup = BeautifulSoup(response.content, 'html.parser')
table = soup.find('table', class_='torrent-list')
rows = table.find_all('tr', class_='default') if table else []
data = []
for row in rows:
cells = row.find_all('td')
if len(cells) > 0:
magnet_link = cells[2].find_all('a')[1]['href'] if len(cells[2].find_all('a')) > 1 else ''
data.append([
cells[0].find('img')['alt'] if cells[0].find('img') else '', # カテゴリー名
'', # Placeholder for image
cells[1].get_text(strip=True), # 名前
cells[3].get_text(strip=True), # サイズ
cells[4].get_text(strip=True), # 日付
cells[5].get_text(strip=True), # シーダー
cells[6].get_text(strip=True), # リーチャー
cells[7].get_text(strip=True), # 完了
magnet_link, # マグネットリンク
cells[1].get_text(strip=True) # 画像検索用の名前
])
return data
except Exception as e:
print(f"Error during scraping: {e}")
return []
def show_result(self, row_index, data):
self.table.insertRow(row_index)
for col_index, item in enumerate(data[:9]): # データの数を9に変更
if col_index == 1: # 画像列を飛ばす
continue
self.table.setItem(row_index, col_index, QTableWidgetItem(item))
self.table.setItem(row_index, 1, QTableWidgetItem("読み込み中..."))
magnet_button = QPushButton("ダウンロード")
magnet_button.clicked.connect(lambda _, link=data[8], row=row_index: self.open_magnet(link, row))
self.table.setCellWidget(row_index, 8, magnet_button)
def open_magnet(self, link, row):
category = self.category_combo.currentText()
download_folder = self.download_folders.get(category, '')
if download_folder:
self.download_torrent(link, download_folder, row)
else:
webbrowser.open(link)
def download_torrent(self, magnet_link, download_folder, row):
try:
self.qb.torrents_add(urls=magnet_link, save_path=download_folder)
torrent_info = self.qb.torrents_info(urls=magnet_link)
if torrent_info:
torrent_hash = torrent_info[0].hash
self.download_torrents[torrent_hash] = row
self.status_bar.showMessage("ダウンロードを開始しました。")
else:
self.status_bar.showMessage("トレント情報の取得に失敗しました。")
except Exception as e:
self.status_bar.showMessage(f"ダウンロードに失敗しました: {e}")
def update_progress(self):
try:
for torrent in self.qb.torrents_info():
if torrent.hash in self.download_torrents:
if torrent.progress == 1.0 and torrent.state not in ('pausedDL', 'pausedUP'):
self.qb.torrents_pause(torrent_hashes=torrent.hash)
self.status_bar.showMessage("ダウンロードが完了し、一時停止しました。")
else:
row = self.download_torrents[torrent.hash]
progress_bar = self.table.cellWidget(row, 8)
if isinstance(progress_bar, QProgressBar):
progress_bar.setValue(int(torrent.progress * 100))
except Exception as e:
print(f"Error updating progress: {e}")
def load_image_async(self, row_index, query, refer_url, category=False):
self.semaphore.acquire() # セマフォを取得してスレッドの実行を制限
thread = ImageLoaderThread(row_index, query, refer_url, self.semaphore, category)
thread.image_loaded.connect(self.display_image)
thread.increment_queue.connect(self.increment_queue)
thread.decrement_queue.connect(self.decrement_queue)
self.threads.append(thread)
thread.start()
def display_image(self, row_index, q_image_thumb, q_image_full, refer_url, category):
pixmap = QPixmap.fromImage(q_image_thumb)
label = ClickableLabel(refer_url, q_image_full)
label.setPixmap(pixmap)
if category:
self.table.setCellWidget(row_index, 0, label) # カテゴリーアイコンの列
else:
self.table.setCellWidget(row_index, 1, label) # 画像の列
def closeEvent(self, event):
for thread in self.threads:
thread.quit()
thread.wait()
event.accept()
def header_clicked(self, index):
if index == 5: # シーダー列
self.sort_order = Qt.DescendingOrder if self.sort_order == Qt.AscendingOrder else Qt.AscendingOrder
self.table.sortItems(index, self.sort_order)
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setStyle(QStyleFactory.create('Fusion'))
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
Apple Rooms laboratoryの運営者