main.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. import sys, io, os
  2. from PyQt5 import QtCore, QtGui, uic, QtWidgets
  3. from PyQt5.QtGui import QPainter, QColor, QFont
  4. from os.path import expanduser
  5. import subprocess as sp
  6. import numpy
  7. from PIL import Image, ImageDraw, ImageFont
  8. from PIL.ImageQt import ImageQt
  9. import atexit
  10. from queue import Queue
  11. from PyQt5.QtCore import QSettings
  12. import signal
  13. import preview_thread, core, video_thread
  14. class Command(QtCore.QObject):
  15. videoTask = QtCore.pyqtSignal(str, str, QFont, int, int, int, int, tuple, tuple, str, str)
  16. def __init__(self):
  17. QtCore.QObject.__init__(self)
  18. import argparse
  19. self.parser = argparse.ArgumentParser(description='Create a visualization for an audio file')
  20. self.parser.add_argument('-i', '--input', dest='input', help='input audio file', required=True)
  21. self.parser.add_argument('-o', '--output', dest='output', help='output video file', required=True)
  22. self.parser.add_argument('-b', '--background', dest='bgimage', help='background image file', required=True)
  23. self.parser.add_argument('-t', '--text', dest='text', help='title text', required=True)
  24. self.parser.add_argument('-f', '--font', dest='font', help='title font', required=False)
  25. self.parser.add_argument('-s', '--fontsize', dest='fontsize', help='title font size', required=False)
  26. self.parser.add_argument('-c', '--textcolor', dest='textcolor', help='title text color in r,g,b format', required=False)
  27. self.parser.add_argument('-C', '--viscolor', dest='viscolor', help='visualization color in r,g,b format', required=False)
  28. self.parser.add_argument('-x', '--xposition', dest='xposition', help='x position', required=False)
  29. self.parser.add_argument('-y', '--yposition', dest='yposition', help='y position', required=False)
  30. self.parser.add_argument('-a', '--alignment', dest='alignment', help='title alignment', required=False, type=int, choices=[0, 1, 2])
  31. self.args = self.parser.parse_args()
  32. self.settings = QSettings('settings.ini', QSettings.IniFormat)
  33. # load colours as tuples from comma-separated strings
  34. self.textColor = core.Core.RGBFromString(self.settings.value("textColor", '255, 255, 255'))
  35. self.visColor = core.Core.RGBFromString(self.settings.value("visColor", '255, 255, 255'))
  36. if self.args.textcolor:
  37. self.textColor = core.Core.RGBFromString(self.args.textcolor)
  38. if self.args.viscolor:
  39. self.visColor = core.Core.RGBFromString(self.args.viscolor)
  40. # font settings
  41. if self.args.font:
  42. self.font = QFont(self.args.font)
  43. else:
  44. self.font = QFont(self.settings.value("titleFont", QFont()))
  45. if self.args.fontsize:
  46. self.fontsize = int(self.args.fontsize)
  47. else:
  48. self.fontsize = int(self.settings.value("fontSize", 35))
  49. if self.args.alignment:
  50. self.alignment = int(self.args.alignment)
  51. else:
  52. self.alignment = int(self.settings.value("alignment", 0))
  53. if self.args.xposition:
  54. self.textX = int(self.args.xposition)
  55. else:
  56. self.textX = int(self.settings.value("xPosition", 70))
  57. if self.args.yposition:
  58. self.textY = int(self.args.yposition)
  59. else:
  60. self.textY = int(self.settings.value("yPosition", 375))
  61. ffmpeg_cmd = self.settings.value("ffmpeg_cmd", expanduser("~"))
  62. self.videoThread = QtCore.QThread(self)
  63. self.videoWorker = video_thread.Worker(self)
  64. self.videoWorker.moveToThread(self.videoThread)
  65. self.videoWorker.videoCreated.connect(self.videoCreated)
  66. self.videoThread.start()
  67. self.videoTask.emit(self.args.bgimage,
  68. self.args.text,
  69. self.font,
  70. self.fontsize,
  71. self.alignment,
  72. self.textX,
  73. self.textY,
  74. self.textColor,
  75. self.visColor,
  76. self.args.input,
  77. self.args.output)
  78. def videoCreated(self):
  79. self.videoThread.quit()
  80. self.videoThread.wait()
  81. self.cleanUp()
  82. def cleanUp(self):
  83. self.settings.setValue("titleFont", self.font.toString())
  84. self.settings.setValue("alignment", str(self.alignment))
  85. self.settings.setValue("fontSize", str(self.fontsize))
  86. self.settings.setValue("xPosition", str(self.textX))
  87. self.settings.setValue("yPosition", str(self.textY))
  88. self.settings.setValue("visColor", '%s,%s,%s' % self.visColor)
  89. self.settings.setValue("textColor", '%s,%s,%s' % self.textColor)
  90. sys.exit(0)
  91. class Main(QtCore.QObject):
  92. newTask = QtCore.pyqtSignal(str, str, QFont, int, int, int, int, tuple, tuple)
  93. processTask = QtCore.pyqtSignal()
  94. videoTask = QtCore.pyqtSignal(str, str, QFont, int, int, int, int, tuple, tuple, str, str)
  95. def __init__(self, window):
  96. QtCore.QObject.__init__(self)
  97. # print('main thread id: {}'.format(QtCore.QThread.currentThreadId()))
  98. self.window = window
  99. self.core = core.Core()
  100. self.settings = QSettings('settings.ini', QSettings.IniFormat)
  101. # load colors as tuples from a comma-separated string
  102. self.textColor = core.Core.RGBFromString(self.settings.value("textColor", '255, 255, 255'))
  103. self.visColor = core.Core.RGBFromString(self.settings.value("visColor", '255, 255, 255'))
  104. self.previewQueue = Queue()
  105. self.previewThread = QtCore.QThread(self)
  106. self.previewWorker = preview_thread.Worker(self, self.previewQueue)
  107. self.previewWorker.moveToThread(self.previewThread)
  108. self.previewWorker.imageCreated.connect(self.showPreviewImage)
  109. self.previewThread.start()
  110. self.timer = QtCore.QTimer(self)
  111. self.timer.timeout.connect(self.processTask.emit)
  112. self.timer.start(500)
  113. window.pushButton_selectInput.clicked.connect(self.openInputFileDialog)
  114. window.pushButton_selectOutput.clicked.connect(self.openOutputFileDialog)
  115. window.pushButton_createVideo.clicked.connect(self.createAudioVisualisation)
  116. window.pushButton_selectBackground.clicked.connect(self.openBackgroundFileDialog)
  117. window.progressBar_create.setValue(0)
  118. window.setWindowTitle("Audio Visualizer")
  119. window.pushButton_selectInput.setText("Select Input Music File")
  120. window.pushButton_selectOutput.setText("Select Output Video File")
  121. window.pushButton_selectBackground.setText("Select Background Image")
  122. window.label_font.setText("Title Font")
  123. window.label_alignment.setText("Title Options")
  124. window.label_colorOptions.setText("Colors")
  125. window.label_fontsize.setText("Fontsize")
  126. window.label_title.setText("Title Text")
  127. window.label_textColor.setText("Text:")
  128. window.label_visColor.setText("Visualizer:")
  129. window.pushButton_createVideo.setText("Create Video")
  130. window.groupBox_create.setTitle("Create")
  131. window.groupBox_settings.setTitle("Settings")
  132. window.groupBox_preview.setTitle("Preview")
  133. window.alignmentComboBox.addItem("Left")
  134. window.alignmentComboBox.addItem("Middle")
  135. window.alignmentComboBox.addItem("Right")
  136. window.fontsizeSpinBox.setValue(35)
  137. window.textXSpinBox.setValue(70)
  138. window.textYSpinBox.setValue(375)
  139. window.lineEdit_textColor.setText('%s,%s,%s' % self.textColor)
  140. window.lineEdit_visColor.setText('%s,%s,%s' % self.visColor)
  141. window.pushButton_textColor.clicked.connect(lambda: self.pickColor('text'))
  142. window.pushButton_visColor.clicked.connect(lambda: self.pickColor('vis'))
  143. btnStyle = "QPushButton { background-color : %s; outline: none; }" % QColor(*self.textColor).name()
  144. window.pushButton_textColor.setStyleSheet(btnStyle)
  145. btnStyle = "QPushButton { background-color : %s; outline: none; }" % QColor(*self.visColor).name()
  146. window.pushButton_visColor.setStyleSheet(btnStyle)
  147. titleFont = self.settings.value("titleFont")
  148. if not titleFont == None:
  149. window.fontComboBox.setCurrentFont(QFont(titleFont))
  150. alignment = self.settings.value("alignment")
  151. if not alignment == None:
  152. window.alignmentComboBox.setCurrentIndex(int(alignment))
  153. fontSize = self.settings.value("fontSize")
  154. if not fontSize == None:
  155. window.fontsizeSpinBox.setValue(int(fontSize))
  156. xPosition = self.settings.value("xPosition")
  157. if not xPosition == None:
  158. window.textXSpinBox.setValue(int(xPosition))
  159. yPosition = self.settings.value("yPosition")
  160. if not yPosition == None:
  161. window.textYSpinBox.setValue(int(yPosition))
  162. window.fontComboBox.currentFontChanged.connect(self.drawPreview)
  163. window.lineEdit_title.textChanged.connect(self.drawPreview)
  164. window.alignmentComboBox.currentIndexChanged.connect(self.drawPreview)
  165. window.textXSpinBox.valueChanged.connect(self.drawPreview)
  166. window.textYSpinBox.valueChanged.connect(self.drawPreview)
  167. window.fontsizeSpinBox.valueChanged.connect(self.drawPreview)
  168. window.lineEdit_textColor.textChanged.connect(self.drawPreview)
  169. window.lineEdit_visColor.textChanged.connect(self.drawPreview)
  170. self.drawPreview()
  171. window.show()
  172. def cleanUp(self):
  173. self.timer.stop()
  174. self.previewThread.quit()
  175. self.previewThread.wait()
  176. self.settings.setValue("titleFont", self.window.fontComboBox.currentFont().toString())
  177. self.settings.setValue("alignment", str(self.window.alignmentComboBox.currentIndex()))
  178. self.settings.setValue("fontSize", str(self.window.fontsizeSpinBox.value()))
  179. self.settings.setValue("xPosition", str(self.window.textXSpinBox.value()))
  180. self.settings.setValue("yPosition", str(self.window.textYSpinBox.value()))
  181. self.settings.setValue("visColor", self.window.lineEdit_visColor.text())
  182. self.settings.setValue("textColor", self.window.lineEdit_textColor.text())
  183. def openInputFileDialog(self):
  184. inputDir = self.settings.value("inputDir", expanduser("~"))
  185. fileName = QtWidgets.QFileDialog.getOpenFileName(self.window,
  186. "Open Music File", inputDir, "Music Files (*.mp3 *.wav *.ogg *.flac)");
  187. if not fileName == "":
  188. print( 'filename :{} type:{}'.format(fileName, type(fileName)) )
  189. self.settings.setValue("inputDir", os.path.dirname(fileName[0]))
  190. self.window.label_input.setText(fileName[0])
  191. def openOutputFileDialog(self):
  192. outputDir = self.settings.value("outputDir", expanduser("~"))
  193. fileName = QtWidgets.QFileDialog.getSaveFileName(self.window,
  194. "Set Output Video File", outputDir, "Video Files (*.mkv)");
  195. if not fileName == "":
  196. self.settings.setValue("outputDir", os.path.dirname(fileName[0]))
  197. self.window.label_output.setText(fileName[0])
  198. def openBackgroundFileDialog(self):
  199. backgroundDir = self.settings.value("backgroundDir", expanduser("~"))
  200. fileName = QtWidgets.QFileDialog.getOpenFileName(self.window,
  201. "Open Background Image", backgroundDir, "Image Files (*.jpg *.png);; Video Files (*.mp4)");
  202. if not fileName == "":
  203. self.settings.setValue("backgroundDir", os.path.dirname(fileName[0]))
  204. self.window.label_background.setText(fileName[0])
  205. self.drawPreview()
  206. def createAudioVisualisation(self):
  207. ffmpeg_cmd = self.settings.value("ffmpeg_cmd", expanduser("~"))
  208. self.videoThread = QtCore.QThread(self)
  209. self.videoWorker = video_thread.Worker(self)
  210. self.videoWorker.moveToThread(self.videoThread)
  211. self.videoWorker.videoCreated.connect(self.videoCreated)
  212. self.videoWorker.progressBarUpdate.connect(self.progressBarUpdated)
  213. self.videoWorker.progressBarSetText.connect(self.progressBarSetText)
  214. self.videoThread.start()
  215. self.videoTask.emit(self.window.label_background.text(),
  216. self.window.lineEdit_title.text(),
  217. self.window.fontComboBox.currentFont(),
  218. self.window.fontsizeSpinBox.value(),
  219. self.window.alignmentComboBox.currentIndex(),
  220. self.window.textXSpinBox.value(),
  221. self.window.textYSpinBox.value(),
  222. core.Core.RGBFromString(self.window.lineEdit_textColor.text()),
  223. core.Core.RGBFromString(self.window.lineEdit_visColor.text()),
  224. self.window.label_input.text(),
  225. self.window.label_output.text())
  226. def progressBarUpdated(self, value):
  227. self.window.progressBar_create.setValue(value)
  228. def progressBarSetText(self, value):
  229. self.window.progressBar_create.setFormat(value)
  230. def videoCreated(self):
  231. self.videoThread.quit()
  232. self.videoThread.wait()
  233. def drawPreview(self):
  234. self.newTask.emit(self.window.label_background.text(),
  235. self.window.lineEdit_title.text(),
  236. self.window.fontComboBox.currentFont(),
  237. self.window.fontsizeSpinBox.value(),
  238. self.window.alignmentComboBox.currentIndex(),
  239. self.window.textXSpinBox.value(),
  240. self.window.textYSpinBox.value(),
  241. core.Core.RGBFromString(self.window.lineEdit_textColor.text()),
  242. core.Core.RGBFromString(self.window.lineEdit_visColor.text()))
  243. # self.processTask.emit()
  244. def showPreviewImage(self, image):
  245. self._scaledPreviewImage = image
  246. self._previewPixmap = QtGui.QPixmap.fromImage(self._scaledPreviewImage)
  247. self.window.label_preview.setPixmap(self._previewPixmap)
  248. def pickColor(self, colorTarget):
  249. color = QtGui.QColorDialog.getColor()
  250. if color.isValid():
  251. RGBstring = '%s,%s,%s' % (str(color.red()), str(color.green()), str(color.blue()))
  252. btnStyle = "QPushButton { background-color : %s; outline: none; }" % color.name()
  253. if colorTarget == 'text':
  254. self.window.lineEdit_textColor.setText(RGBstring)
  255. window.pushButton_textColor.setStyleSheet(btnStyle)
  256. elif colorTarget == 'vis':
  257. self.window.lineEdit_visColor.setText(RGBstring)
  258. window.pushButton_visColor.setStyleSheet(btnStyle)
  259. if len(sys.argv) > 1:
  260. # command line mode
  261. app = QtWidgets.QApplication(sys.argv, False)
  262. command = Command()
  263. signal.signal(signal.SIGINT, command.cleanUp)
  264. sys.exit(app.exec_())
  265. else:
  266. # gui mode
  267. if __name__ == "__main__":
  268. app = QtWidgets.QApplication(sys.argv)
  269. window = uic.loadUi("main.ui")
  270. # window.adjustSize()
  271. desc = QtWidgets.QDesktopWidget()
  272. dpi = desc.physicalDpiX()
  273. topMargin = 0 if (dpi == 96) else int(10 * (dpi / 96))
  274. window.resize(window.width() * (dpi / 96), window.height() * (dpi / 96))
  275. window.verticalLayout_2.setContentsMargins(0, topMargin, 0, 0)
  276. main = Main(window)
  277. signal.signal(signal.SIGINT, main.cleanUp)
  278. atexit.register(main.cleanUp)
  279. sys.exit(app.exec_())