main.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. import base64
  2. import datetime
  3. import os
  4. import threading
  5. from PyQt5 import QtCore
  6. from PyQt5.QtWidgets import *
  7. from PyQt5.QtGui import *
  8. import FileOperator
  9. import TestFileSystem
  10. from MainWindow import Ui_MainWindow
  11. from icon import img
  12. import TitleSpider
  13. def GetSeries(dataList):
  14. return int(dataList.split('_')[1])
  15. class MainApp(QMainWindow, Ui_MainWindow):
  16. def __init__(self):
  17. QMainWindow.__init__(self)
  18. self.setupUi(self)
  19. self.SetBaseInfo()
  20. self.InitMenuBar()
  21. self.HandleButtons()
  22. self.SetUI()
  23. self.setFixedSize(self.width(), self.height())
  24. self.SetLogText()
  25. self.progressBar.setValue(0)
  26. self.InitCheckBox()
  27. self.path = os.getcwd()
  28. self.saveName = "set.ini"
  29. self.joinedPath = os.path.join(self.path, self.saveName) # 配置文件的位置
  30. self.isTextFileExists = False
  31. self.isTextFirstColumnHaveContent = False
  32. self.isTextSecondColumnHaveContent = False
  33. self.InitOutPutPath() # 发布绿色版时注释
  34. def InitOutPutPath(self):
  35. self.isTextFileExists = FileOperator.SaveForOutput(self.path, self.saveName)
  36. if self.isTextFileExists is False: # 如果没有文件,在已经创建文件的前提下,等待用户输入手动填入OutputText. 相关事件在button事件实现
  37. # line = FileOperator.ReadForOutput() #
  38. pass
  39. else: # 存在文件 则读取文件,并自动赋给文本框
  40. self.lines = FileOperator.ReadForOutput(self.joinedPath)
  41. if len(self.lines) >= 2:
  42. if self.lines[0] != '':
  43. if os.path.isdir(self.lines[0].strip()):
  44. self.downloadDirEdit.setText(self.lines[0].strip())
  45. self.isTextFirstColumnHaveContent = True
  46. if self.lines[1] != '':
  47. if os.path.isdir(self.lines[1].strip()):
  48. self.outputDirEdit.setText(self.lines[1].strip())
  49. self.isTextSecondColumnHaveContent = True
  50. else:
  51. pass
  52. # self.Log("检测到配置文件set.ini 为空")
  53. # QMessageBox.critical(self, "错误", "配置文件set.ini !")
  54. def InitCheckBox(self):
  55. # self.txtFileCheckBox.setChecked(True) # 默认保存Txt
  56. # self.deleteFileCheckBox.setChecked(False) # 默认保留源目录
  57. # self.copyToOutput.setChecked(True) # 默认使用复制的方式
  58. self.txtFileCheckBox.setChecked(False) # 默认不保存Txt
  59. self.deleteFileCheckBox.setChecked(True) # 默认不保留源目录
  60. self.moveToOutput.setChecked(True) # 默认不使用复制的方式
  61. self.localMode.setChecked(True)
  62. def MutiThreadCopy(self, mp4List, outputPath):
  63. t = threading.Thread(target=FileOperator.CopyFile, args=(mp4List, outputPath))
  64. t.start()
  65. t.join()
  66. def MutiThreadMove(self, mp4List, outputPath):
  67. t = threading.Thread(target=FileOperator.MoveFile, args=(mp4List, outputPath))
  68. t.start()
  69. t.join()
  70. def CheckIsChecked(self): # 按下按钮的事件里调用,检查checkbox状态,并提示。最后给对应的bool变量赋值
  71. self.isSaveTxt = self.txtFileCheckBox.isChecked()
  72. self.isDeleteDir = self.deleteFileCheckBox.isChecked()
  73. if self.copyToOutput.isChecked() or self.moveToOutput.isChecked():
  74. pass
  75. else:
  76. QMessageBox.critical(self, "错误", "请至少勾选一种输出方式!")
  77. if self.localMode.isChecked() or self.spiderMode.isChecked():
  78. pass
  79. else:
  80. QMessageBox.critical(self, "错误", "请至少勾选一种处理模式(本地模式 或 爬虫模式)!")
  81. if self.copyToOutput.isChecked():
  82. self.isCopyOutput = True
  83. else:
  84. self.isCopyOutput = False
  85. if self.moveToOutput.isChecked():
  86. self.isMoveOutput = True
  87. else:
  88. self.isMoveOutput = False
  89. if self.localMode.isChecked():
  90. self.isLocalMode = True
  91. else:
  92. self.isLocalMode = False
  93. if self.spiderMode.isChecked():
  94. QMessageBox.warning(self, "警告", "爬虫模式依赖网络,关闭本窗口前请确保代理服务是关闭状态")
  95. self.isSpiderMode = True
  96. else:
  97. self.isSpiderMode = False
  98. def SetLogText(self):
  99. self.activityLogEdit.setReadOnly(True)
  100. def Log(self, msg):
  101. self.statusbar.showMessage(msg)
  102. self.activityLogEdit.appendPlainText('[{0}]'.format(str(datetime.datetime.now())[0:19]))
  103. self.activityLogEdit.appendPlainText(msg)
  104. self.activityLogEdit.appendPlainText('')
  105. def LogOnBar(self, msg):
  106. self.statusbar.showMessage(msg)
  107. def HandleButtons(self):
  108. self.downloadDirButton.clicked.connect(self.OpenDownloadDir)
  109. self.outputDirButton.clicked.connect(self.OpenOutputDir)
  110. self.renameButton.clicked.connect(self.RenameFile)
  111. self.copyToOutput.clicked.connect(self.DisableMove)
  112. self.moveToOutput.clicked.connect(self.DisableCopy)
  113. self.localMode.clicked.connect(self.DisableSpiderMode)
  114. self.spiderMode.clicked.connect(self.DisableLocalMode)
  115. # 处理checkbox冲突
  116. def DisableCopy(self):
  117. if self.copyToOutput.isChecked():
  118. self.copyToOutput.setChecked(False)
  119. def DisableMove(self):
  120. if self.moveToOutput.isChecked():
  121. self.moveToOutput.setChecked(False)
  122. def DisableSpiderMode(self):
  123. if self.spiderMode.isChecked():
  124. self.spiderMode.setChecked(False)
  125. self.renameButton.setText("一键解密+整理+重命名")
  126. def DisableLocalMode(self):
  127. if self.localMode.isChecked():
  128. self.localMode.setChecked(False)
  129. self.renameButton.setText("一键解密+爬取+整理+重命名")
  130. def InitMenuBar(self):
  131. # 添加menu“帮助”的事件
  132. aboutAction = QAction('&关于', self)
  133. # aboutAction.setStatusTip('关于')
  134. aboutAction.triggered.connect(self.ShowAboutDialog)
  135. # 已有菜单栏,此处只需要添加菜单
  136. mainPageMenu = self.menubar.addMenu('&主页')
  137. helpMenu = self.menubar.addMenu('&帮助')
  138. # 菜单绑定之前添加的事件
  139. helpMenu.addAction(aboutAction)
  140. # 设置UI
  141. def SetUI(self):
  142. tmp = open('tmp.png', "wb+")
  143. try:
  144. tmp.write(base64.b64decode(img))
  145. except Exception as e:
  146. QMessageBox.critical(self, "错误", "请关闭本程序并使用管理员权限运行本软件!或卸载本软件再重新安装至其它非系统盘 比如:D盘、E盘!" + str(e))
  147. return
  148. tmp.close()
  149. icon = QIcon('tmp.png')
  150. os.remove("tmp.png")
  151. self.setWindowIcon(icon)
  152. def ShowAboutDialog(self):
  153. about_text = "<p>描述:这是一款致力于解决BiliBili UWP版下载后的视频加密、命名信息丢失和存放位置不合理等痛点的软件</p><p>版本:4.3</p><p>@Author:LZY</p><p>@github:love" \
  154. "-in-cpp</p> "
  155. QMessageBox.about(self, '说明', about_text)
  156. def OpenDownloadDir(self):
  157. if self.isTextFirstColumnHaveContent is False:
  158. dName = QFileDialog.getExistingDirectory(self, '选择下载文件夹', '/')
  159. self.downloadDirEdit.setText(dName)
  160. else:
  161. dName = QFileDialog.getExistingDirectory(self, '选择下载文件夹', self.lines[0].strip())
  162. self.downloadDirEdit.setText(dName)
  163. def OpenOutputDir(self):
  164. if self.isTextSecondColumnHaveContent is False:
  165. dName = QFileDialog.getExistingDirectory(self, '选择输出文件夹', '/')
  166. self.outputDirEdit.setText(dName)
  167. else:
  168. dName = QFileDialog.getExistingDirectory(self, '选择输出文件夹', self.lines[1].strip())
  169. self.outputDirEdit.setText(dName)
  170. def SetBaseInfo(self):
  171. self.setWindowTitle('BiliBili UWP版视频下载整理工具')
  172. self.downloadDirEdit.setToolTip(r"例如:E:\BiliDownload\44938322")
  173. self.downloadDirEdit.setPlaceholderText("路径请具体到单个数字名称的文件夹,暂不支持文件夹的批量处理")
  174. self.outputDirEdit.setPlaceholderText("您希望处理后的文件被保存到的地方")
  175. # def FindFiles(self,downloadPath):
  176. def RenameFile(self):
  177. self.CheckIsChecked()
  178. self.progressBar.setValue(0)
  179. # 进入目录查找dvi文件
  180. downloadPath = self.downloadDirEdit.toPlainText()
  181. outputPath = self.outputDirEdit.toPlainText()
  182. if os.path.isdir(downloadPath) is False or os.path.isdir(self.downloadDirEdit.toPlainText().strip()) is False:
  183. self.Log('UWP下载目录的路径存在非法输入!')
  184. else:
  185. self.Log("进入目录:{0}".format(downloadPath))
  186. dviInfoList = FileOperator.GetDviInfo(downloadPath) # 获取dvi文件信息
  187. if dviInfoList[0] is False:
  188. self.Log('没有找到.dvi文件!请检查下载目录后重试!')
  189. else:
  190. # 在outputDir下新建名为dvi[3]文件夹
  191. try:
  192. outputPath = FileOperator.MakeDir(outputPath, dviInfoList[3])
  193. except Exception as e:
  194. QMessageBox.critical(self, "错误", "已经存在同名文件夹! Error:" + str(e))
  195. return
  196. if self.isSpiderMode:
  197. self.Log("开始爬取BV:{0}, 标题:{1} 的所有视频标题,请稍后...".format(dviInfoList[1], dviInfoList[3]))
  198. try:
  199. TitleSpider.GetTxt(dviInfoList[1], outputPath)
  200. except Exception as e:
  201. QMessageBox.critical(self, "错误", "请检查网络后重试 Error:" + str(e))
  202. return
  203. # 调用爬虫产生.txt
  204. global fileName
  205. fileName = TitleSpider.fileName
  206. self.LogOnBar('已成功爬取文件: {0} ! 注:只显示部分文件名'.format(fileName[0:35]))
  207. self.Log('已成功爬取文件: {0} !'.format(fileName))
  208. elif self.isLocalMode:
  209. self.Log("开始遍历获取BV:{0}, 标题:{1} 的所有视频标题,请稍后...".format(dviInfoList[1], dviInfoList[3]))
  210. localVideoTitleList = FileOperator.GetLocalVideoTitle(downloadPath, dviInfoList[2])
  211. fileName = FileOperator.GetTxt(localVideoTitleList, dviInfoList[3], outputPath)
  212. self.Log('已成功获取文件: {0} !'.format(fileName))
  213. else:
  214. self.Log("impossible")
  215. # 找到所有downloadPath的.mp4文件
  216. mp4List = FileOperator.FindSpecialMp4Files(downloadPath, dviInfoList[2])[0] # mp4真正在的地方
  217. # Log
  218. mp4nameList = FileOperator.FindSpecialMp4Files(downloadPath, dviInfoList[2])[1]
  219. try:
  220. mp4nameList.sort(key=GetSeries)
  221. except Exception as e:
  222. QMessageBox.critical(self, "错误", "存在干扰文件!排序错误,请联系作者!" + str(e))
  223. return
  224. s = "查询到以下mp4文件:\n"
  225. for item in mp4nameList:
  226. s += (item + '\n')
  227. self.Log(s)
  228. if os.path.isdir(outputPath) is False or os.path.isdir(self.outputDirEdit.toPlainText().strip()) is False:
  229. self.Log('输出目录的路径存在非法输入!')
  230. else:
  231. # 记忆输出目录
  232. FileOperator.WriteForOutput(self.joinedPath, os.path.dirname(downloadPath), self.outputDirEdit.toPlainText()) # 发布绿色版时注释
  233. # 解密
  234. self.Log("开始解密...")
  235. FileOperator.DecryptMp4(downloadPath, dviInfoList[2])
  236. self.Log("解密完毕!")
  237. # 复制
  238. self.CopyOrMove(self.isCopyOutput, mp4List, outputPath)
  239. # 重命名
  240. self.Log("开始重命名...")
  241. FileOperator.DoRename(outputPath, fileName, dviInfoList[2], self.isLocalMode)
  242. self.Log("重命名完毕!")
  243. # 进度条100%
  244. self.progressBar.setValue(100)
  245. # 是否保存.txt文件
  246. if self.isSaveTxt is True:
  247. pass
  248. else:
  249. self.Log("正在删除程序运行过程中产生的.txt文件")
  250. FileOperator.DeleteTxt(outputPath, fileName)
  251. self.Log("删除.txt文件成功!")
  252. # 是否删除源文件夹
  253. if self.isDeleteDir is True:
  254. self.Log("正在删除源文件夹")
  255. FileOperator.DeleteDir(downloadPath)
  256. self.Log("删除源文件夹成功!")
  257. else:
  258. pass
  259. # 重命名输出文件夹 搁置
  260. # 输出方式:复制或移动
  261. def CopyOrMove(self, isCopyTo, mp4List, outputPath):
  262. if isCopyTo is True:
  263. self.Log("进入目录:{0}".format(outputPath))
  264. self.Log("开始复制... 这可能需要一段时间...")
  265. self.MutiThreadCopy(mp4List, outputPath) # 多线程复制
  266. self.Log("复制完毕!")
  267. else:
  268. self.Log("进入目录:{0}".format(outputPath))
  269. self.Log("开始移动... 这可能需要一段时间...")
  270. self.MutiThreadMove(mp4List, outputPath) # 多线程移动
  271. self.Log("移动完毕!")
  272. def DSpiderMode(self):
  273. pass
  274. def DoLocalMode(self):
  275. pass
  276. if __name__ == '__main__':
  277. QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
  278. app = QApplication([])
  279. window = MainApp()
  280. window.show()
  281. app.exec_()