提醒:本文最后更新于2023-09-02 18:27,文中所关联的信息可能已发生改变,请知悉!
先来看看在pyqt5 designer中拖拽生成的界面:
下面我直接给出上面这个界面的ui文件的代码:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>955</width>
<height>657</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">阿甘·B站缓存合成器</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QGridLayout" name="gridLayout">
<property name="sizeConstraint">
<enum>QLayout::SetNoConstraint</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="cache_path_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>50</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string notr="true">缓存路径</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QPushButton" name="scan_button">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">扫描</string>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QPushButton" name="composer_button">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">开始合成</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QPushButton" name="go_to_website_button">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">点我下载最新版本,网站打不开的话需要你懂的</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="3">
<widget class="QPlainTextEdit" name="cache_path_text_edit">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>50</horstretch>
<verstretch>99</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="0" colspan="4">
<widget class="QListWidget" name="listWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
然后是上面这个ui文件转换成的python代码,注意下面转换完的py代码做了很多修改,不然不能实现功能的哈,
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '.\阿甘·B站缓存合成器.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import QIcon,QPixmap
from PyQt5.QtWidgets import QListWidget, QCheckBox,QListWidgetItem
from PyQt5.QtCore import Qt
import ctypes
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("Dialog")
class FilteredList(QListWidget):
#继承自列表控件
def __init__(self, parent=None):
super().__init__(parent)
self.selectAll_ch = QCheckBox("全选(selectAll)")
self.selectAll_ch.setCheckState(Qt.Checked)
self.selectAll_ch.stateChanged[int].connect(self.on_selectAll)#
item = QListWidgetItem(self)
self.setItemWidget(item, self.selectAll_ch )#列表控件的项设为 QCheckBox
self.boxes = set()
self.videos_title_dict = {}
def insert_item(self,videos_list):
if not self.boxes:
for _, text in enumerate(videos_list):
ch = QCheckBox(text)
ch.setCheckState(Qt.Unchecked)
ch.stateChanged[int].connect(self.on_stateChanged)
#item.setCheckState(Qt.Unchecked)#
item = QListWidgetItem(self)
self.setItemWidget(item, ch)
self.boxes.add(ch)
self.videos_title_dict[text]=ch
def on_selectAll(self,state):
if state == 2:
for ch in self.boxes:
ch.setCheckState(2)
if state == 0:
for ch in self.boxes:
ch.setCheckState(0)
def on_stateChanged(self,state):
ch = self.sender()
if state:
if len([ch for ch in self.boxes if ch.checkState()]) == self.count()-1:
#0 不选中, 1 部分选中,2 全选中 #Qt.Unchecked #Qt.PartiallyChecked #Qt.Checked
self.selectAll_ch.setCheckState(2)
else:
self.selectAll_ch.setCheckState(1)
else:
if len([k for k in self.boxes if k.checkState()]):
self.selectAll_ch.setCheckState(1)
else:
self.selectAll_ch.setCheckState(0)
def keyPressEvent(self, event):
#Ctrl+A 全选
if event.modifiers() & Qt.ControlModifier and event.key()==Qt.Key_A:
self.selectAll_ch.setCheckState(2)
class Ui_Dialog(object):
def get_icon(self):
import base64
icon_bytes = b'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAA6VJREFUWEftl9tvVFUUh791ZmwLmaY2k9Y2wgMdE4nCC5oqdDoWozGaGKMJ9kkTMfBiAkKgD/AfICBIlJQQU4OmCRqqqCVq1CYqpHOmXgIIJM10etNOK73Y69zONrvTyQymU87QU3lxP56s81vfWXut395HuMtL7nJ+/gfIW4FHLqr1RopjovApyVspS8GAAUNK0Y2Lc2ad/FrItuYFqP1RPVrqpu1JL2vWloDkiVQKonH4ZgTGElgCplIcNAPyrR2QJQFqVtG+30fFprLbS6UURGahfRjODDAK7DP9fICItdTbjgFkksQs+DwKzRH6xpNsCwUk+J8C6GSjCWjuRX0a5djqBE0dWyWZD6KgCiQUtP0Jh8NZuecqocELtffCalf6eVLB+Sic7qVzKM7zvwRkZMUAtLC3CJp88IQXDEABl8bg7R7GIrNsDvnlxooCaPG9NfBSFRRpAqBrAg51kwrP8lqoXs44CvBWeH7cmsx6OVLbqbwqTut+H0+/WAVuSVfg4igcCaMG5zgU9HMg3zTcaQ/o8b8soJtrzYZSKnUFNpTe2gMnIzCR4sPpcl7//WGJL1aFggD0rJvj0D2TltLVvr8E1nvgvuKs/HAMTkTgq5H5arR6kmzv2Cpzywa4vR3BVBLahqBlACaTWEpxasbLbkcqYAdAe8CXUXi/H6ZTJBAOmHUcdaQH7ADoGO2G2geae4lPJHg5FJDPHJ2CXCPSjfdsJfjLobokm6Z/Dk70YHXcZI9ZL++sGEBG+JkK2LUOKorST2ZS0NIPLYOcni7nDUd6YDErzgA85IF9vuwoxi04NwRHw/w2dg+B7sfl72VPQQYg14i0aO1PavNGDxd2r6Ns44IX5ACMWy4CXVvksmMAh3uwUHwiwtdKoR3gqbpyXthbg7F2VTrNbAo+GoRTfcSsFK90NcjHzgHknIZaVB9CjdWgLy7GgrXpcXw3Mn83SABvmvXy3rIBtMBkMj1merkEytzZpLkJfp6A4z1wbYoYij1mQE46AmDHC6IxaO6DL6KAoJtvh+mXsysOoC8iVyeh9Q/47q+FdEJEYFvQL6GCAaqKOd9YTfWDnvTBs9jSR6/elvAM/DAKVyZviVIozhpF7OwsdAz1f4EkOS7CA7qQAhUKPHa2IBOj4JohvJrv63WcrV+zhu9VybSLFoRGmwD66G23XBzs2iLXl3rHLoB7ys12AxoULFw9/yWrUMpgWCyCUsyF4GNy0w6sLQA7Qnca8w+WjtQwLG4EwgAAAABJRU5ErkJggg=='
icon_img = base64.b64decode(icon_bytes) # 解码
icon_pixmap = QPixmap() # 新建QPixmap对象
icon_pixmap.loadFromData(icon_img) # 往QPixmap中写入数据
return icon_pixmap
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(1049, 633)
Dialog.setWindowTitle("阿甘·B站缓存合成器 - V1.0.0")
Dialog.setWindowIcon(QIcon(self.get_icon()))
self.gridLayout_2 = QtWidgets.QGridLayout(Dialog)
self.gridLayout_2.setObjectName("gridLayout_2")
self.gridLayout = QtWidgets.QGridLayout()
self.gridLayout.setSizeConstraint(QtWidgets.QLayout.SetNoConstraint)
self.gridLayout.setObjectName("gridLayout")
self.cache_path_label = QtWidgets.QLabel(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(50)
sizePolicy.setHeightForWidth(self.cache_path_label.sizePolicy().hasHeightForWidth())
self.cache_path_label.setSizePolicy(sizePolicy)
font = QtGui.QFont()
font.setBold(False)
font.setWeight(50)
self.cache_path_label.setFont(font)
self.cache_path_label.setText("缓存路径")
self.cache_path_label.setObjectName("cache_path_label")
self.gridLayout.addWidget(self.cache_path_label, 0, 0, 1, 1)
self.scan_button = QtWidgets.QPushButton(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.scan_button.sizePolicy().hasHeightForWidth())
self.scan_button.setSizePolicy(sizePolicy)
self.scan_button.setText("扫描")
self.scan_button.setObjectName("scan_button")
self.scan_button.clicked.connect(Dialog.scan_videos_generate_list)
self.gridLayout.addWidget(self.scan_button, 2, 2, 1, 1)
self.composer_button = QtWidgets.QPushButton(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.composer_button.sizePolicy().hasHeightForWidth())
self.composer_button.setSizePolicy(sizePolicy)
self.composer_button.setText("开始合成")
self.composer_button.setObjectName("composer_button")
self.composer_button.clicked.connect(Dialog.videos_composer)
self.gridLayout.addWidget(self.composer_button, 2, 3, 1, 1)
self.listWidget = FilteredList()
self.gridLayout.addWidget(self.listWidget, 1, 0, 1, 4)
self.go_to_website_button = QtWidgets.QPushButton(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.go_to_website_button.sizePolicy().hasHeightForWidth())
self.go_to_website_button.setSizePolicy(sizePolicy)
self.go_to_website_button.setText("点我下载最新版本,网站打不开的话需要你懂的")
self.go_to_website_button.setObjectName("go_to_website_button")
self.go_to_website_button.clicked.connect(lambda: QtGui.QDesktopServices.openUrl(QtCore.QUrl('http://www.sharpgan.com/a-gan-bilibili-video-composer')))
self.gridLayout.addWidget(self.go_to_website_button, 2, 0, 1, 2)
self.cache_path_text_edit = QtWidgets.QPlainTextEdit(Dialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(50)
sizePolicy.setVerticalStretch(99)
sizePolicy.setHeightForWidth(self.cache_path_text_edit.sizePolicy().hasHeightForWidth())
self.cache_path_text_edit.setSizePolicy(sizePolicy)
self.cache_path_text_edit.setPlaceholderText("请输入B站客户端的设置-下载设置-缓存目录中的路径")
self.cache_path_text_edit.setObjectName("cache_path_text_edit")
self.gridLayout.addWidget(self.cache_path_text_edit, 0, 1, 1, 3)
self.gridLayout_2.addLayout(self.gridLayout, 0, 0, 1, 1)
QtCore.QMetaObject.connectSlotsByName(Dialog)
最后就是主代码了哈,
# coding:utf-8
import os
import sys
import json
import shutil
from bilibili_video_composer_ui import Ui_Dialog
from PyQt5.QtWidgets import QApplication, QDialog, QMessageBox
import subprocess
class MainDialog(QDialog):
def __init__(self, parent=None):
super(QDialog, self).__init__(parent)
self.ui = Ui_Dialog()
self.ui.setupUi(self)
def get_path_content(self):
path_content = self.ui.cache_path_text_edit.toPlainText()
return path_content
def get_videos_info_list(self, video_path, ext_name):
file_list = []
for root, _, files in os.walk(video_path):
for file in files:
if file.endswith(ext_name):
file_list.append(root+"\\"+file)
return file_list
def alert_dialog(self,content):
dlg = QMessageBox(self)
dlg.setWindowTitle("提示")
dlg.setText(content)
dlg.exec()
def check_path_is_empty(self):
path_content = self.get_path_content()
# print(path_content)
if not path_content:
self.alert_dialog("请在缓存路径中输入B站客户端的设置-下载设置-缓存目录中的路径!")
return True
else:
return False
def get_video_title(self, video_info_path):
info_content = open(video_info_path,encoding='UTF-8')
video_info_to_dict = json.loads(info_content.read())
info_content.close()
video_title = video_info_to_dict['title']
return video_title
def insert_video_title(self, videos_info_list):
video_title_list = []
for info in videos_info_list:
title = self.get_video_title(info)
video_title_list.append(title)
self.ui.listWidget.insert_item(video_title_list)
def scan_videos_generate_list(self):
path_content = self.get_path_content()
if not self.check_path_is_empty():
info_list = self.get_videos_info_list(path_content, ".videoInfo")
if not info_list:
self.alert_dialog("抱歉,您提供的缓存路径下未能扫描到任何视频!")
self.insert_video_title(info_list)
def is_dir_check(self, dir_path):
is_dir_path = os.path.isdir(dir_path)
return is_dir_path
def video_file_purify(self, video_file_path):
f_read=open(video_file_path,'rb')
cleaned_data = f_read.read()[9:]
f_read.close()
f_write = open(video_file_path + ".new",'wb')
f_write.write(cleaned_data)
f_write.close()
def videos_composer(self):
video_root_dir = self.get_path_content()
videos_info_list_from_file = self.get_videos_info_list(video_root_dir,".videoInfo")
videos_titles_list_from_widget = self.ui.listWidget.videos_title_dict
videos_titles_checked = [i for i in videos_titles_list_from_widget if videos_titles_list_from_widget[i].isChecked()]
videos_output_dir = video_root_dir + "\output"
video_tmp_dir_path = videos_output_dir + "\\tmp"
count = 0
self.alert_dialog("正在后台进行合成,请耐心等待合成完毕后的弹窗提醒...")
for checked in videos_titles_checked:
for info_path in videos_info_list_from_file:
title = self.get_video_title(info_path)
if checked == title:
video_random_dir_path = os.path.dirname(info_path)
video_base_random_name = os.path.basename(video_random_dir_path)
# 这里是B站客户端视频缓存文件夹下生成的随机数字文件夹
video_tmp_random_path = video_tmp_dir_path + "\\" + video_base_random_name
# print(video_tmp_random_path)
if not self.is_dir_check(video_tmp_random_path):
# 这里的源路径必须是以random文件夹结尾,目标路径不需要存在会自动创建,目标路径也必须以random文件夹结尾
shutil.copytree(video_random_dir_path, video_tmp_random_path)
m4s_videos_list = self.get_videos_info_list(video_tmp_random_path, ".m4s")
# print(m4s_videos_list)
for m4s in m4s_videos_list:
self.video_file_purify(m4s)
subprocess.check_call(".\\ffmpeg.exe -i {0} -i {1} -c:v copy -strict experimental {2}".format(m4s_videos_list[0]+".new",m4s_videos_list[1]+".new", videos_output_dir + "\\" + title + ".mp4"))
count = count + 1
shutil.rmtree(video_tmp_dir_path)
if count == len(videos_titles_checked):
self.alert_dialog("全部视频合成完毕,请打开B站缓存路径下的output文件夹进行查看!")
if __name__ == '__main__':
myapp = QApplication(sys.argv)
myDlg = MainDialog()
myDlg.show()
sys.exit(myapp.exec_())
如果你不想付费的话可以访问下面的链接直接下载体验一下哈:
2023-03-07 阿甘·B站缓存合成器 – V1.0.0发布
打包命令备注:
nuitka --show-memory --show-progress --enable-plugin=pyqt5 --output-dir=out --windows-icon-from-ico=.\bilibili.ico --standalone bilibili_video_composer_main.py