Esempio n. 1
0
class YTdownloader(QWidget):
    def __init__(self):
        super().__init__()
        # setup some flags
        self.isFetching = False
        self.isDownloading = False

        # default output path
        self.outputPath = f'{QDir.homePath()}/videos'

        # setup some window specific things
        self.setWindowTitle('YouTube Downloader')
        self.setWindowIcon(QIcon('assets/yt-icon.ico'))
        self.setFixedSize(705, 343)

        # parent layout
        layout = QVBoxLayout()
        layout.setContentsMargins(15, 15, 15, 10)
        self.setLayout(layout)

        # top bar layout
        topBar = QHBoxLayout()

        # detail section
        detailSec = QHBoxLayout()
        metaSec = QVBoxLayout()

        # download section
        downloadSec = QHBoxLayout()
        downloadBtn = QVBoxLayout()

        # output path link button
        self.outputBtn = QPushButton('📂  Output Path')
        self.outputBtn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
        self.outputBtn.setToolTip(self.outputPath)
        self.outputBtn.clicked.connect(self.setOutputPath)

        # status bar
        self.statusBar = QStatusBar()

        # message box
        self.message = QMessageBox()

        # setting up widgets
        self.urlBox = QLineEdit()
        self.urlBox.setFocusPolicy(Qt.FocusPolicy.ClickFocus or Qt.FocusPolicy.NoFocus)
        self.urlBox.setPlaceholderText('🔍 Enter or paste video URL...')
        self.button = QPushButton('Get')
        self.button.setDefault(True)
        self.button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
        self.button.clicked.connect(self.getDetails)

        # thumbnail
        pixmap = QPixmap('assets\placeholder.jpg')
        self.thumb = QLabel()
        self.thumb.setFixedSize(250, 141)
        self.thumb.setScaledContents(True)
        self.thumb.setPixmap(pixmap)

        # detail widgets
        self.title = QLabel('Title: ')
        self.author = QLabel('Author: ')
        self.length = QLabel('Duration: ')
        self.publish_date = QLabel('Published: ')

        # progress bar
        self.progress_bar = QProgressBar()
        
        # download options
        self.download = QComboBox()
        self.download.setPlaceholderText('Download Video')
        self.download.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
        self.download.activated.connect(lambda: self.getContent(0))
        self.download.setEnabled(False)

        # download audio button
        self.download_audio = QPushButton('Download Audio')
        self.download_audio.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
        self.download_audio.clicked.connect(lambda: self.getContent(1))
        self.download_audio.setEnabled(False)

        # add widgets and layouts
        topBar.addWidget(self.urlBox)
        topBar.addWidget(self.button)

        # detail section
        metaSec.addWidget(self.title)
        metaSec.addWidget(self.author)
        metaSec.addWidget(self.length)
        metaSec.addWidget(self.publish_date)
        detailSec.addWidget(self.thumb)
        detailSec.addSpacing(20)
        detailSec.addLayout(metaSec)

        # download section
        downloadBtn.addWidget(self.download)
        downloadBtn.addWidget(self.download_audio)
        downloadSec.addWidget(self.progress_bar)
        downloadSec.addSpacing(10)
        downloadSec.addLayout(downloadBtn)

        # status bar
        self.statusBar.setSizeGripEnabled(False)
        self.statusBar.addPermanentWidget(self.outputBtn)

        # add content to parent layout
        layout.addLayout(topBar)
        layout.addSpacing(20)
        layout.addLayout(detailSec)
        layout.addSpacing(5)
        layout.addLayout(downloadSec)
        layout.addWidget(self.statusBar)

        # setup a connection thread to keep checking internet connectivity
        self.connection = ConnectionThread()
        self.connection.start()

        # catch the connection response signal
        self.connection.con_response.connect(self.connection_slot)

    # connection slot
    def connection_slot(self, status):
        curMsg = self.statusBar.currentMessage()
        # connection succeeded
        if status:
            if curMsg == '🔴  Disconnected':
                self.statusBar.showMessage('🟢  Connection restored!', 3000)
            elif curMsg != '🟢  Connected':
                self.statusBar.showMessage('🟢  Connected')
        # connection failed
        elif curMsg == '🟢  Connected':
            self.statusBar.showMessage('🔴  Connection interrupted!', 3000)
        elif curMsg != '🔴  Disconnected': 
            self.statusBar.showMessage('🔴  Disconnected')

    # set output path slot
    def setOutputPath(self):
        # update the output path
        path = str(QFileDialog.getExistingDirectory(self, "Select Output Directory"))
        if path:
            self.outputPath = path
            # update tooltip
            self.outputBtn.setToolTip(path)

    # get button slot
    def getDetails(self):
        curMsg = self.statusBar.currentMessage()
        if curMsg == '🔴  Disconnected' or curMsg == '🔴  Connection interrupted!':
            self.message.critical(
                self,
                'Error',
                'Connection failed!\nAre you sure you\'re connected to the internet ? '
            )
        elif self.button.text() == 'Get':
            self.button.setText('Stop')
            # indicate progress bar as busy
            self.progress_bar.setRange(0, 0)
            # set fetching flag
            self.isFetching = True
            # setup a worker thread to keep UI responsive
            self.worker = WorkerThread(self.urlBox.text())
            self.worker.start()
            # catch the finished signal
            self.worker.finished.connect(self.finished_slot)
            # catch the response signal
            self.worker.worker_response.connect(self.response_slot)
            # catch the error signal
            self.worker.worker_err_response.connect(self.err_slot)
        elif self.button.text() == 'Stop':
            if self.isFetching:
                # stop worker thread
                self.worker.terminate()
                # set back the button text
                self.button.setText('Get')
            elif self.isDownloading:
                # stop download thread
                self.download_thread.terminate()
                # show the warning message
                self.message.information(
                    self,
                    'Interrupted',
                    'Download interrupted!\nThe process was aborted while the file was being downloaded... '
                )
                # reset pogress bar
                self.progress_bar.reset()

    # download options slot
    def getContent(self, id):
        if self.isFetching:
            # show the warning message
            self.message.warning(
                self,
                'Warning',
                'Please wait!\nWait while the details are being fetched... '
            )
        else:
            # disable the download options
            self.download.setDisabled(True)
            self.download_audio.setDisabled(True)
            # set downloading flag
            self.isDownloading = True
            # set button to stop 
            self.button.setText('Stop')
            # setup download thread
            if id == 0:
                self.download_thread = DownloadThread(self.yt, self.download.currentText()[:4], self.outputPath)
            else:
                self.download_thread = DownloadThread(self.yt, 'audio', self.outputPath)
            # start the thread
            self.download_thread.start()
            # catch the finished signal
            self.download_thread.finished.connect(self.download_finished_slot)
            # catch the response signal
            self.download_thread.download_response.connect(self.download_response_slot)
            # catch the complete signal
            self.download_thread.download_complete.connect(self.download_complete_slot)
            # catch the error signal
            self.download_thread.download_err.connect(self.download_err_slot)

    # finished slot
    def finished_slot(self):
        # remove progress bar busy indication
        self.progress_bar.setRange(0, 100)
        # unset fetching flag
        self.isFetching = False

    # response slot
    def response_slot(self, res):
        # set back the button text
        self.button.setText('Get')
        # save the yt object for speeding up download
        self.yt = res[0]
        # set the actual thumbnail of requested video
        self.thumb.setPixmap(res[1])
        # slice the title if it is more than the limit
        if len(res[2]) > 50:
            self.title.setText(f'Title:  {res[2][:50]}...')
        else:
            self.title.setText(f'Title:  {res[2]}')
        # set leftover details
        self.author.setText(f'Author:  {res[3]}')
        self.length.setText(f'Duration:  {timedelta(seconds=res[4])}')
        self.publish_date.setText(f'Published:  {res[5].strftime("%d/%m/%Y")}')
        # clear any previous items if any
        self.download.clear()
        # add resolutions as items to the download button and enable them
        self.download.addItems([item for item in res[6]])
        self.download.setDisabled(False)
        self.download_audio.setDisabled(False)

    # error slot
    def err_slot(self):
        # show the warning message
        self.message.warning(
            self,
            'Warning',
            'Something went wrong!\nProbably a broken link or some restricted content... '
        )
        # set back the button text
        self.button.setText('Get')

    # download finished slot
    def download_finished_slot(self):
        # set back the button text
        self.button.setText('Get')
        # now enable the download options
        self.download.setDisabled(False)
        self.download_audio.setDisabled(False)
        # unset downloading flag
        self.isDownloading = False
        # reset pogress bar
        self.progress_bar.reset()

    # download response slot
    def download_response_slot(self, per):
        # update progress bar
        self.progress_bar.setValue(per)
        # adjust the font color to maintain the contrast
        if per > 52:
            self.progress_bar.setStyleSheet('QProgressBar { color: #fff }')
        else:
            self.progress_bar.setStyleSheet('QProgressBar { color: #000 }')
    
    # download complete slot
    def download_complete_slot(self, location):
        # use native separators
        location = QDir.toNativeSeparators(location)
        # show the success message
        if self.message.information(
            self,
            'Downloaded',
            f'Download complete!\nFile was successfully downloaded to :\n{location}\n\nOpen the downloaded file now ?',
            QMessageBox.StandardButtons.Open,
            QMessageBox.StandardButtons.Cancel
        ) is QMessageBox.StandardButtons.Open: subprocess.Popen(f'explorer /select,{location}')

    # download error slot
    def download_err_slot(self):
        # show the error message
        self.message.critical(
            self,
            'Error',
            'Error!\nSomething unusual happened and was unable to download...'
        )
Esempio n. 2
0
class Madoka(QWidget):
    global serialPort, baud, fmt

    def __init__(self):
        global serialPort, baud, fmt
        self.serialLogState = False
        self.fmts = ['bin', 'oct', 'dec', 'hex', 'csv', 'csv+', 'ascii']
        self.baudList = [
            4800, 7200, 9600, 14400, 19200, 28800, 38400, 57600, 76800, 115200,
            230400, 460800, 921600, 1000000, 2000000, 4000000
        ]
        fmt = self.fmts.index(fmt)
        super().__init__()
        layoutA = QHBoxLayout()

        self.baudBox = QComboBox()
        self.getBaud()
        layoutA.addWidget(self.baudBox)

        self.formatBox = QComboBox()
        self.getFormat()
        layoutA.addWidget(self.formatBox)

        layoutB = QHBoxLayout()
        self.portBox = QComboBox()
        self.getPorts()
        layoutB.addWidget(self.portBox)

        parentLayout = QVBoxLayout()
        parentLayout.addLayout(layoutB)
        parentLayout.addLayout(layoutA)

        self.setLayout(parentLayout)

        self.portBox.currentTextChanged.connect(self.portSelected)
        self.portBox.setCurrentIndex(len(self.ports) - 1)

        self.baudBox.currentTextChanged.connect(self.baudSelected)
        try:
            self.baudBox.setCurrentIndex(self.baudList.index(baud))
        except:
            self.baudBox.setCurrentIndex(-1)

        self.formatBox.currentTextChanged.connect(self.formatSelected)
        self.formatBox.setCurrentIndex(fmt)

        self.button1 = QPushButton()
        self.button1.setText("start")
        self.button1.released.connect(self.btn1Clicked)
        layoutA.addWidget(self.button1)

    def portSelected(self, text):
        global serialPort
        if (text != ''):
            serialPort = self.ports[self.portsIndex.index(text)]
            # print(serialPort)
            # self.erandano.setText(self.port)

    def baudSelected(self, text):
        global baud
        if (text != ''):
            baud = text
            # print(baud)

    def formatSelected(self, text):
        global fmt
        if (text != ''):
            fmt = self.fmts.index(text)
            # print(text)

    def getPorts(self):
        global serialPort
        # sys.stderr.write("\n--- Available ports:\n")
        self.ports = []
        self.portsIndex = []
        for n, (port, desc, devid) in enumerate(sorted(comports()), 1):
            # sys.stderr.write("--- {:2}: {:20} {!r} \n".format(n, port, desc))
            self.ports.append(port)
            print(port)
            self.portsIndex.append("{:2}: {:20} {!r}".format(n, port, desc))
        try:
            serialPort = self.ports.index(serialPort)
        except:
            serialPort = self.ports[len(self.ports) - 1]
        for s in self.portsIndex:
            self.portBox.addItem(s)

    def getBaud(self):
        for b in self.baudList:
            self.baudBox.addItem(str(b))

    def getFormat(self):
        global fmt
        for f in self.fmts:
            self.formatBox.addItem(f)

    def btn1Clicked(self):
        if (self.button1.text() == 'start'):
            self.baudBox.setDisabled(True)
            self.formatBox.setDisabled(True)
            self.button1.setText('stop')
            self.serialLogState = True
            th = threading.Thread(target=printData, daemon=True)
            th.start()
        elif (self.button1.text() == 'stop'):
            self.button1.setText('start')
            self.baudBox.setDisabled(False)
            self.formatBox.setDisabled(False)
            self.serialLogState = False
Esempio n. 3
0
class B23Download(QWidget):
    def __init__(self):
        super(B23Download, self).__init__()
        # setup some flags
        self.is_fetching = False
        self.is_downloading = False

        # default output path
        basepath = os.path.dirname(os.path.abspath(__file__))
        path = os.path.join(basepath, "videos")
        self.output_path = path

        # setup some window specific things
        self.setWindowTitle("Bilibili Favorite Downloader")
        self.setWindowIcon(QIcon("images/icon_bilibili.ico"))
        self.setFixedSize(705, 343)

        # parent layout
        main_layout = QVBoxLayout()
        main_layout.setContentsMargins(15, 15, 15, 10)
        self.setLayout(main_layout)

        # top bar layout
        top_layout = QHBoxLayout()

        # detail section
        mid_main_layout = QHBoxLayout()
        mid_right_layout = QVBoxLayout()

        # download section
        bottom_main_layout = QHBoxLayout()
        bottom_right_layout = QVBoxLayout()

        # output path link button
        self.output_btn = QPushButton("📂  Output Path")
        self.output_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
        self.output_btn.setToolTip(self.output_path)
        self.output_btn.clicked.connect(self.set_output_path)

        # status bar
        self.status_bar = QStatusBar()

        # message box
        self.message_box = QMessageBox()

        # setting up widgets
        self.url_edit = QLineEdit()
        self.url_edit.setPlaceholderText("🔍 Enter or paste favorite URL...")
        self.get_btn = QPushButton("Get")
        self.get_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
        self.get_btn.clicked.connect(self.get_details)

        # thumbnail
        pixmap = QPixmap("images/placeholder.png")
        self.thumb = QLabel()
        self.thumb.setFixedSize(250, 141)
        self.thumb.setScaledContents(True)
        self.thumb.setPixmap(pixmap)

        # detail widgets
        self.title = QLabel("Title: ")
        self.author = QLabel("Author: ")
        self.length = QLabel("Videos: ")
        self.publish_date = QLabel("Published: ")

        # progress bar
        self.progress_bar = QProgressBar()

        # download options
        self.download_btn = QPushButton(" Download Videos ")
        self.download_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
        self.download_btn.clicked.connect(self.get_content)
        self.download_btn.setEnabled(False)
        self.download_btn.setShortcut("Ctrl+Return")
        self.download_btn.setMinimumWidth(200)

        # add widgets and layouts
        top_layout.addWidget(self.url_edit)
        top_layout.addWidget(self.get_btn)

        # detail section
        mid_right_layout.addWidget(self.title)
        mid_right_layout.addWidget(self.author)
        mid_right_layout.addWidget(self.length)
        mid_right_layout.addWidget(self.publish_date)
        mid_main_layout.addWidget(self.thumb)
        mid_main_layout.addSpacing(20)
        mid_main_layout.addLayout(mid_right_layout)

        # download section
        bottom_right_layout.addWidget(self.download_btn)
        bottom_main_layout.addWidget(self.progress_bar)
        bottom_main_layout.addSpacing(10)
        bottom_main_layout.addLayout(bottom_right_layout)

        # status bar
        self.status_bar.setSizeGripEnabled(False)
        self.status_bar.addPermanentWidget(self.output_btn)

        # add content to parent layout
        main_layout.addLayout(top_layout)
        main_layout.addSpacing(20)
        main_layout.addLayout(mid_main_layout)
        main_layout.addSpacing(5)
        main_layout.addLayout(bottom_main_layout)
        main_layout.addWidget(self.status_bar)

    # set output path slot
    def set_output_path(self):
        # update the output path
        path = str(
            QFileDialog.getExistingDirectory(self, "Select Output Directory"))
        if path:
            self.output_path = path
            # update tooltip
            self.output_btn.setToolTip(path)

    # get button slot
    def get_details(self):
        text = self.url_edit.text().strip()

        if not text:
            return

        if text.find("fid") < 0:
            self.message_box.warning(
                self,
                "Error",
                ("Input a correct favorite URL!\n"
                 "For example: https://space.bilibili.com/xxx/favlist?fid=xxx..."
                 ),
            )
            return

        if self.get_btn.text() == "Get":
            self.get_btn.setText("Stop")
            # indicate progress bar as busy
            self.progress_bar.setRange(0, 0)
            # set fetching flag
            self.is_fetching = True
            # setup a worker thread to keep UI responsive
            self.media_id = text.split("fid=")[-1].split("&")[0]
            self.worker = WorkerThread(self.media_id)
            self.worker.start()
            # catch the finished signal
            self.worker.finished.connect(self.finished_slot)
            # catch the response signal
            self.worker.worker_response.connect(self.response_slot)
            # catch the error signal
            self.worker.worker_err_response.connect(self.err_slot)
        elif self.get_btn.text() == "Stop":
            if self.is_fetching:
                # stop worker thread
                self.worker.terminate()
                # set back the get_btn text
                self.get_btn.setText("Get")
            elif self.is_downloading:
                # stop download thread
                self.download_thread.terminate()
                # show the warning message_box
                self.message_box.information(
                    self,
                    "Interrupted",
                    "Download interrupted!\nThe process was aborted while the file was being downloaded... ",
                )
                # reset progress bar
                self.progress_bar.reset()

    # download options slot
    def get_content(self):
        if self.is_fetching:
            # show the warning message
            self.message_box.critical(
                self,
                "Error",
                "Please wait!\nWait while the details are being fetched... ",
            )
        else:
            # disable the download options
            self.download_btn.setDisabled(True)
            # set downloading flag
            self.is_downloading = True
            # set button to stop
            self.get_btn.setText("Stop")
            self.download_thread = DownloadThread(
                self.media_id,
                self.media_counts,
                self.first_page_medias,
                self.output_path,
            )
            # start the thread
            self.download_thread.start()
            # catch the finished signal
            self.download_thread.finished.connect(self.download_finished_slot)
            # catch the response signal
            self.download_thread.download_response.connect(
                self.download_response_slot)
            # catch the complete signal
            self.download_thread.download_complete.connect(
                self.download_complete_slot)
            # catch the error signal
            self.download_thread.download_err.connect(self.download_err_slot)

    # handling enter key for get/stop button
    def keyPressEvent(self, event):
        self.url_edit.setFocus()
        if (event.key() == Qt.Key.Key_Enter.value
                or event.key() == Qt.Key.Key_Return.value):
            self.get_details()

    # finished slot
    def finished_slot(self):
        # remove progress bar busy indication
        self.progress_bar.setRange(0, 100)
        # unset fetching flag
        self.is_fetching = False

    # response slot
    def response_slot(self, res):
        # set back the button text
        self.get_btn.setText("Get")
        # set the actual thumbnail of requested video
        self.thumb.setPixmap(res.thumb_img)
        # slice the title if it is more than the limit
        if len(res.title) > 50:
            self.title.setText(f"Title: {res.title[:50]}...")
        else:
            self.title.setText(f"Title: {res.title}")
        # cache first page medias
        self.first_page_medias = res.medias
        self.media_counts = res.media_counts
        # set leftover details
        self.author.setText(f"Author: {res.author}")
        self.length.setText(f"Videos: {res.media_counts}")
        self.publish_date.setText(
            f'Published: {time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(res.publish_date))}'
        )
        self.download_btn.setDisabled(False)

    # error slot
    def err_slot(self):
        # show the warning message
        self.message_box.warning(
            self,
            "Warning",
            "Something went wrong!\nProbably a broken link or some restricted content... ",
        )
        # set back the button text
        self.get_btn.setText("Get")

    # download finished slot
    def download_finished_slot(self):
        # set back the button text
        self.get_btn.setText("Get")
        # now enable the download options
        self.download_btn.setDisabled(False)
        # unset downloading flag
        self.is_downloading = False
        # reset pogress bar
        self.progress_bar.reset()

    # download response slot
    def download_response_slot(self, per):
        # update progress bar
        self.progress_bar.setValue(per)
        # adjust the font color to maintain the contrast
        if per > 52:
            self.progress_bar.setStyleSheet("QProgressBar { color: #fff }")
        else:
            self.progress_bar.setStyleSheet("QProgressBar { color: #000 }")

    # download complete slot
    def download_complete_slot(self, location):
        # use native separators
        location = QDir.toNativeSeparators(location)
        # show the success message
        if (self.message_box.information(
                self,
                "Downloaded",
                f"Download complete!\nFile was successfully downloaded to :\n{location}\n\nOpen the downloaded file now ?",
                QMessageBox.StandardButtons.Open,
                QMessageBox.StandardButtons.Cancel,
        ) is QMessageBox.StandardButtons.Open):
            subprocess.Popen(f"explorer /select,{location}")

    # download error slot
    def download_err_slot(self):
        # show the error message
        self.message_box.critical(
            self,
            "Error",
            "Error!\nSomething unusual happened and was unable to download...",
        )
Esempio n. 4
0
class LoginDialog(QDialog):
    """登录对话框"""

    clicked_ok = pyqtSignal()

    def __init__(self, config):
        super().__init__()
        self._cwd = os.getcwd()
        self._config = config
        self._cookie_assister = 'login_assister.exe'
        self._user = ""
        self._pwd = ""
        self._cookie = {}
        self._del_user = ""
        self.initUI()
        self.setStyleSheet(dialog_qss_style)
        self.setMinimumWidth(560)
        self.name_ed.setFocus()
        # 信号
        self.name_ed.textChanged.connect(self.set_user)
        self.pwd_ed.textChanged.connect(self.set_pwd)
        self.cookie_ed.textChanged.connect(self.set_cookie)

    def update_selection(self, user):
        """显示已经保存的登录用户信息"""
        user_info = self._config.get_user_info(user)
        if user_info:
            self._user = user_info[0]
            self._pwd = user_info[1]
            self._cookie = user_info[2]
        # 更新控件显示内容
        self.name_ed.setText(self._user)
        self.pwd_ed.setText(self._pwd)
        try: text = ";".join([f'{k}={v}' for k, v in self._cookie.items()])
        except: text = ''
        self.cookie_ed.setPlainText(text)

    def initUI(self):
        self.setWindowTitle("登录蓝奏云")
        self.setWindowIcon(QIcon(SRC_DIR + "login.ico"))
        logo = QLabel()
        logo.setPixmap(QPixmap(SRC_DIR + "logo3.gif"))
        logo.setStyleSheet("background-color:rgb(0,153,255);")
        logo.setAlignment(Qt.AlignmentFlag.AlignCenter)

        self.tabs = QTabWidget()
        self.auto_tab = QWidget()
        self.hand_tab = QWidget()

        # Add tabs
        self.tabs.addTab(self.auto_tab,"自动获取Cookie")
        self.tabs.addTab(self.hand_tab,"手动输入Cookie")
        self.auto_get_cookie_ok = AutoResizingTextEdit("🔶点击👇自动获取浏览器登录信息👇")
        self.auto_get_cookie_ok.setReadOnly(True)
        self.auto_get_cookie_btn = QPushButton("自动读取浏览器登录信息")
        auto_cookie_notice = '支持浏览器:Chrome, Chromium, Opera, Edge, Firefox'
        self.auto_get_cookie_btn.setToolTip(auto_cookie_notice)
        self.auto_get_cookie_btn.clicked.connect(self.call_auto_get_cookie)
        self.auto_get_cookie_btn.setStyleSheet("QPushButton {min-width: 210px;max-width: 210px;}")

        self.name_lb = QLabel("&U 用户")
        self.name_lb.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.name_ed = QLineEdit()
        self.name_lb.setBuddy(self.name_ed)

        self.pwd_lb = QLabel("&P 密码")
        self.pwd_lb.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.pwd_ed = QLineEdit()
        self.pwd_ed.setEchoMode(QLineEdit.EchoMode.Password)
        self.pwd_lb.setBuddy(self.pwd_ed)

        self.cookie_lb = QLabel("&Cookie")
        self.cookie_ed = QTextEdit()
        notice = "由于滑动验证的存在,需要输入cookie,cookie请使用浏览器获取\n" + \
            "cookie会保存在本地,下次使用。其格式如下:\n ylogin=value1; phpdisk_info=value2"
        self.cookie_ed.setPlaceholderText(notice)
        self.cookie_lb.setBuddy(self.cookie_ed)

        self.show_input_cookie_btn = QPushButton("显示Cookie输入框")
        self.show_input_cookie_btn.setToolTip(notice)
        self.show_input_cookie_btn.setStyleSheet("QPushButton {min-width: 110px;max-width: 110px;}")
        self.show_input_cookie_btn.clicked.connect(self.change_show_input_cookie)
        self.ok_btn = QPushButton("登录")
        self.ok_btn.clicked.connect(self.change_ok_btn)
        self.cancel_btn = QPushButton("取消")
        self.cancel_btn.clicked.connect(self.change_cancel_btn)
        lb_line_1 = QLabel()
        lb_line_1.setText('<html><hr />切换用户</html>')
        lb_line_2 = QLabel()
        lb_line_2.setText('<html><hr /></html>')

        self.form = QFormLayout()
        self.form.setLabelAlignment(Qt.AlignmentFlag.AlignRight)
        self.form.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow)  # 覆盖MacOS的默认样式
        self.form.addRow(self.name_lb, self.name_ed)
        self.form.addRow(self.pwd_lb, self.pwd_ed)
        if is_windows:
            def set_assister_path():
                """设置辅助登录程序路径"""
                assister_path = QFileDialog.getOpenFileName(self, "选择辅助登录程序路径",
                                                            self._cwd, "EXE Files (*.exe)")
                if not assister_path[0]:
                    return None
                assister_path = os.path.normpath(assister_path[0])  # windows backslash
                if assister_path == self._cookie_assister:
                    return None
                self.assister_ed.setText(assister_path)
                self._cookie_assister = assister_path

            self.assister_lb = QLabel("登录辅助程序")
            self.assister_lb.setAlignment(Qt.AlignmentFlag.AlignCenter)
            self.assister_ed = MyLineEdit(self)
            self.assister_ed.setText(self._cookie_assister)
            self.assister_ed.clicked.connect(set_assister_path)
            self.assister_lb.setBuddy(self.assister_ed)
            self.form.addRow(self.assister_lb, self.assister_ed)

        hbox = QHBoxLayout()
        hbox.addWidget(self.show_input_cookie_btn)
        hbox.addStretch(1)
        hbox.addWidget(self.ok_btn)
        hbox.addWidget(self.cancel_btn)

        user_box = QHBoxLayout()
        self.user_num = 0
        self.user_btns = {}
        for user in self._config.users_name:
            user = str(user)  # TODO: 可能需要删掉
            self.user_btns[user] = QDoublePushButton(user)
            self.user_btns[user].setStyleSheet("QPushButton {border:none;}")
            if user == self._config.name:
                self.user_btns[user].setStyleSheet("QPushButton {background-color:rgb(0,153,2);}")
                self.tabs.setCurrentIndex(1)
            self.user_btns[user].setToolTip(f"点击选中,双击切换至用户:{user}")
            self.user_btns[user].doubleClicked.connect(self.choose_user)
            self.user_btns[user].clicked.connect(self.delete_chose_user)
            user_box.addWidget(self.user_btns[user])
            self.user_num += 1
            user_box.addStretch(1)

        self.layout = QVBoxLayout(self)
        self.layout.addWidget(logo)
        vbox = QVBoxLayout()
        if self._config.name:
            vbox.addWidget(lb_line_1)
            user_box.setAlignment(Qt.AlignmentFlag.AlignCenter)
            vbox.addLayout(user_box)
            vbox.addWidget(lb_line_2)
            if self.user_num > 1:
                self.del_user_btn = QPushButton("删除账户")
                self.del_user_btn.setIcon(QIcon(SRC_DIR + "delete.ico"))
                self.del_user_btn.setStyleSheet("QPushButton {min-width: 180px;max-width: 180px;}")
                self.del_user_btn.clicked.connect(self.call_del_chose_user)
                vbox.addWidget(self.del_user_btn)
            else:
                self.del_user_btn = None
            vbox.addStretch(1)

        vbox.addLayout(self.form)
        vbox.addStretch(1)
        vbox.addLayout(hbox)
        vbox.setAlignment(Qt.AlignmentFlag.AlignCenter)

        self.hand_tab.setLayout(vbox)
        auto_cookie_vbox = QVBoxLayout()
        auto_cookie_vbox.addWidget(self.auto_get_cookie_ok)
        auto_cookie_vbox.addWidget(self.auto_get_cookie_btn)
        auto_cookie_vbox.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.auto_tab.setLayout(auto_cookie_vbox)
        self.layout.addWidget(self.tabs)
        self.setLayout(self.layout)
        self.update_selection(self._config.name)

    def call_del_chose_user(self):
        if self._del_user:
            if self._del_user != self._config.name:
                self.user_num -= 1
                self._config.del_user(self._del_user)
                self.user_btns[self._del_user].close()
                self._del_user = ""
                if self.user_num <= 1:
                    self.del_user_btn.close()
                    self.del_user_btn = None
                return
            else:
                title = '不能删除'
                msg = '不能删除当前登录账户,请先切换用户!'
        else:
            title = '请选择账户'
            msg = '请单击选择需要删除的账户\n\n注意不能删除当前账户(绿色)'
        message_box = QMessageBox(self)
        message_box.setIcon(QMessageBox.Icon.Critical)
        message_box.setStyleSheet(btn_style)
        message_box.setWindowTitle(title)
        message_box.setText(msg)
        message_box.setStandardButtons(QMessageBox.StandardButton.Close)
        buttonC = message_box.button(QMessageBox.StandardButton.Close)
        buttonC.setText('关闭')
        message_box.exec()

    def delete_chose_user(self):
        """更改单击选中需要删除的用户"""
        user = str(self.sender().text())
        self._del_user = user
        if self.del_user_btn:
            self.del_user_btn.setText(f"删除 <{user}>")

    def choose_user(self):
        """切换用户"""
        user = self.sender().text()
        if user != self._config.name:
            self.ok_btn.setText("切换用户")
        else:
            self.ok_btn.setText("登录")
        self.update_selection(user)

    def change_show_input_cookie(self):
        row_c = 4 if is_windows else 3
        if self.form.rowCount() < row_c:
            self.org_height = self.height()
            self.form.addRow(self.cookie_lb, self.cookie_ed)
            self.show_input_cookie_btn.setText("隐藏Cookie输入框")
            self.change_height = None
            self.adjustSize()
        else:
            if not self.change_height:
                self.change_height = self.height()
            if self.cookie_ed.isVisible():
                self.cookie_lb.setVisible(False)
                self.cookie_ed.setVisible(False)
                self.show_input_cookie_btn.setText("显示Cookie输入框")
                start_height, end_height = self.change_height, self.org_height
            else:
                self.cookie_lb.setVisible(True)
                self.cookie_ed.setVisible(True)
                self.show_input_cookie_btn.setText("隐藏Cookie输入框")
                start_height, end_height = self.org_height, self.change_height
            gm = self.geometry()
            x, y = gm.x(), gm.y()
            wd = self.width()
            self.animation = QPropertyAnimation(self, b'geometry')
            self.animation.setDuration(400)
            self.animation.setStartValue(QRect(x, y, wd, start_height))
            self.animation.setEndValue(QRect(x, y, wd, end_height))
            self.animation.start()

    def set_user(self, user):
        self._user = user
        if not user:
            return None
        if user not in self._config.users_name:
            self.ok_btn.setText("添加用户")
            self.cookie_ed.setPlainText("")
        elif user != self._config.name:
            self.update_selection(user)
            self.ok_btn.setText("切换用户")
        else:
            self.update_selection(user)
            self.ok_btn.setText("登录")

    def set_pwd(self, pwd):
        if self._user in self._config.users_name:
            user_info = self._config.get_user_info(self._user)
            if pwd and pwd != user_info[1]:  # 改变密码,cookie作废
                self.cookie_ed.setPlainText("")
                self._cookie = None
            if not pwd:  # 输入空密码,表示删除对pwd的存储,并使用以前的cookie
                self._cookie = user_info[2]
                try: text = ";".join([f'{k}={v}' for k, v in self._cookie.items()])
                except: text = ''
                self.cookie_ed.setPlainText(text)
        self._pwd = pwd

    def set_cookie(self):
        cookies = self.cookie_ed.toPlainText()
        if cookies:
            try:
                self._cookie = {kv.split("=")[0].strip(" "): kv.split("=")[1].strip(" ") for kv in cookies.split(";") if kv.strip(" ") }
            except: self._cookie = None

    def change_cancel_btn(self):
        self.update_selection(self._config.name)
        self.close()

    def change_ok_btn(self):
        if self._user and self._pwd:
            if self._user not in self._config.users_name:
                self._cookie = None
        if self._cookie:
            up_info = {"name": self._user, "pwd": self._pwd, "cookie": self._cookie, "work_id": -1}
            if self.ok_btn.text() == "切换用户":
                self._config.change_user(self._user)
            else:
                self._config.set_infos(up_info)
            self.clicked_ok.emit()
            self.close()
        elif USE_WEB_ENG:
            self.web = LoginWindow(self._user, self._pwd)
            self.web.cookie.connect(self.get_cookie_by_web)
            self.web.setWindowModality(Qt.WindowModality.ApplicationModal)
            self.web.exec()
        elif os.path.isfile(self._cookie_assister):
            try:
                result = os.popen(f'{self._cookie_assister} {self._user} {self._pwd}')
                cookie = result.read()
                try:
                    self._cookie = {kv.split("=")[0].strip(" "): kv.split("=")[1].strip(" ") for kv in cookie.split(";")}
                except: self._cookie = None
                if not self._cookie:
                    return None
                up_info = {"name": self._user, "pwd": self._pwd, "cookie": self._cookie, "work_id": -1}
                self._config.set_infos(up_info)
                self.clicked_ok.emit()
                self.close()
            except: pass
        else:
            title = '请使用 Cookie 登录或是选择 登录辅助程序'
            msg = '没有输入 Cookie,或者没有找到登录辅助程序!\n\n' + \
                  '推荐使用浏览器获取 cookie 填入 cookie 输入框\n\n' + \
                  '如果不嫌文件体积大,请下载登录辅助程序:\n' + \
                  'https://github.com/rachpt/lanzou-gui/releases'
            message_box = QMessageBox(self)
            message_box.setIcon(QMessageBox.Icon.Critical)
            message_box.setStyleSheet(btn_style)
            message_box.setWindowTitle(title)
            message_box.setText(msg)
            message_box.setStandardButtons(QMessageBox.StandardButton.Close)
            buttonC = message_box.button(QMessageBox.StandardButton.Close)
            buttonC.setText('关闭')
            message_box.exec()

    def get_cookie_by_web(self, cookie):
        """使用辅助登录程序槽函数"""
        self._cookie = cookie
        self._close_dialog()

    def call_auto_get_cookie(self):
        """自动读取浏览器cookie槽函数"""
        try:
            self._cookie = get_cookie_from_browser()
        except Exception as e:
            logger.error(f"Browser_cookie3 Error: {e}")
            self.auto_get_cookie_ok.setPlainText(f"❌获取失败,错误信息\n{e}")
        else:
            if self._cookie:
                self._user = self._pwd = ''
                self.auto_get_cookie_ok.setPlainText("✅获取成功即将登录……")
                QTimer.singleShot(2000, self._close_dialog)
            else:
                self.auto_get_cookie_ok.setPlainText("❌获取失败\n请提前使用支持的浏览器登录蓝奏云,读取前完全退出浏览器!\n支持的浏览器与顺序:\nchrome, chromium, opera, edge, firefox")

    def _close_dialog(self):
        """关闭对话框"""
        up_info = {"name": self._user, "pwd": self._pwd, "cookie": self._cookie}
        self._config.set_infos(up_info)
        self.clicked_ok.emit()
        self.close()