import sys, io, os from PySide2 import QtCore, QtGui, QtWidgets from PySide2.QtGui import QPainter, QColor, QFont from PySide2.QtUiTools import QUiLoader from os.path import expanduser import subprocess as sp import numpy from PIL import Image, ImageDraw, ImageFont from PIL.ImageQt import ImageQt import atexit from queue import Queue from PySide2.QtCore import QSettings, QFile import signal import preview_thread, core, video_thread class Command(QtCore.QObject): videoTask = QtCore.Signal(str, str, QFont, int, int, int, int, tuple, tuple, str, str) def __init__(self): QtCore.QObject.__init__(self) import argparse self.parser = argparse.ArgumentParser(description='Create a visualization for an audio file') self.parser.add_argument('-i', '--input', dest='input', help='input audio file', required=True) self.parser.add_argument('-o', '--output', dest='output', help='output video file', required=True) self.parser.add_argument('-b', '--background', dest='bgimage', help='background image file', required=True) self.parser.add_argument('-t', '--text', dest='text', help='title text', required=True) self.parser.add_argument('-f', '--font', dest='font', help='title font', required=False) self.parser.add_argument('-s', '--fontsize', dest='fontsize', help='title font size', required=False) self.parser.add_argument('-c', '--textcolor', dest='textcolor', help='title text color in r,g,b format', required=False) self.parser.add_argument('-C', '--viscolor', dest='viscolor', help='visualization color in r,g,b format', required=False) self.parser.add_argument('-x', '--xposition', dest='xposition', help='x position', required=False) self.parser.add_argument('-y', '--yposition', dest='yposition', help='y position', required=False) self.parser.add_argument('-a', '--alignment', dest='alignment', help='title alignment', required=False, type=int, choices=[0, 1, 2]) self.args = self.parser.parse_args() self.settings = QSettings('settings.ini', QSettings.IniFormat) # load colours as tuples from comma-separated strings self.textColor = core.Core.RGBFromString(self.settings.value("textColor", '255, 255, 255')) self.visColor = core.Core.RGBFromString(self.settings.value("visColor", '255, 255, 255')) if self.args.textcolor: self.textColor = core.Core.RGBFromString(self.args.textcolor) if self.args.viscolor: self.visColor = core.Core.RGBFromString(self.args.viscolor) # font settings if self.args.font: self.font = QFont(self.args.font) else: self.font = QFont(self.settings.value("titleFont", QFont())) if self.args.fontsize: self.fontsize = int(self.args.fontsize) else: self.fontsize = int(self.settings.value("fontSize", 35)) if self.args.alignment: self.alignment = int(self.args.alignment) else: self.alignment = int(self.settings.value("alignment", 0)) if self.args.xposition: self.textX = int(self.args.xposition) else: self.textX = int(self.settings.value("xPosition", 70)) if self.args.yposition: self.textY = int(self.args.yposition) else: self.textY = int(self.settings.value("yPosition", 375)) ffmpeg_cmd = self.settings.value("ffmpeg_cmd", expanduser("~")) self.videoThread = QtCore.QThread(self) self.videoWorker = video_thread.Worker(self) self.videoWorker.moveToThread(self.videoThread) self.videoWorker.videoCreated.connect(self.videoCreated) self.videoThread.start() self.videoTask.emit(self.args.bgimage, self.args.text, self.font, self.fontsize, self.alignment, self.textX, self.textY, self.textColor, self.visColor, self.args.input, self.args.output) def videoCreated(self): self.videoThread.quit() self.videoThread.wait() self.cleanUp() def cleanUp(self): self.settings.setValue("titleFont", self.font.toString()) self.settings.setValue("alignment", str(self.alignment)) self.settings.setValue("fontSize", str(self.fontsize)) self.settings.setValue("xPosition", str(self.textX)) self.settings.setValue("yPosition", str(self.textY)) self.settings.setValue("visColor", '%s,%s,%s' % self.visColor) self.settings.setValue("textColor", '%s,%s,%s' % self.textColor) sys.exit(0) class Main(QtCore.QObject): newTask = QtCore.Signal(str, str, QFont, int, int, int, int, tuple, tuple) processTask = QtCore.Signal() videoTask = QtCore.Signal(str, str, QFont, int, int, int, int, tuple, tuple, str, str) def __init__(self, window): QtCore.QObject.__init__(self) # print('main thread id: {}'.format(QtCore.QThread.currentThreadId())) self.window = window self.core = core.Core() self.settings = QSettings('settings.ini', QSettings.IniFormat) # load colors as tuples from a comma-separated string self.textColor = core.Core.RGBFromString(self.settings.value("textColor", '255, 255, 255')) self.visColor = core.Core.RGBFromString(self.settings.value("visColor", '255, 255, 255')) self.previewQueue = Queue() self.previewThread = QtCore.QThread(self) self.previewWorker = preview_thread.Worker(self, self.previewQueue) self.previewWorker.moveToThread(self.previewThread) self.previewWorker.imageCreated.connect(self.showPreviewImage) self.previewThread.start() self.timer = QtCore.QTimer(self) self.timer.timeout.connect(self.processTask.emit) self.timer.start(500) window.pushButton_selectInput.clicked.connect(self.openInputFileDialog) window.pushButton_selectOutput.clicked.connect(self.openOutputFileDialog) window.pushButton_createVideo.clicked.connect(self.createAudioVisualisation) window.pushButton_selectBackground.clicked.connect(self.openBackgroundFileDialog) window.progressBar_create.setValue(0) window.setWindowTitle("Audio Visualizer") window.pushButton_selectInput.setText("Select Input Music File") window.pushButton_selectOutput.setText("Select Output Video File") window.pushButton_selectBackground.setText("Select Background Image") window.label_font.setText("Title Font") window.label_alignment.setText("Title Options") window.label_colorOptions.setText("Colors") window.label_fontsize.setText("Fontsize") window.label_title.setText("Title Text") window.label_textColor.setText("Text:") window.label_visColor.setText("Visualizer:") window.pushButton_createVideo.setText("Create Video") window.groupBox_create.setTitle("Create") window.groupBox_settings.setTitle("Settings") window.groupBox_preview.setTitle("Preview") window.alignmentComboBox.addItem("Left") window.alignmentComboBox.addItem("Middle") window.alignmentComboBox.addItem("Right") window.fontsizeSpinBox.setValue(35) window.textXSpinBox.setValue(70) window.textYSpinBox.setValue(375) window.lineEdit_textColor.setText('%s,%s,%s' % self.textColor) window.lineEdit_visColor.setText('%s,%s,%s' % self.visColor) window.pushButton_textColor.clicked.connect(lambda: self.pickColor('text')) window.pushButton_visColor.clicked.connect(lambda: self.pickColor('vis')) btnStyle = "QPushButton { background-color : %s; outline: none; }" % QColor(*self.textColor).name() window.pushButton_textColor.setStyleSheet(btnStyle) btnStyle = "QPushButton { background-color : %s; outline: none; }" % QColor(*self.visColor).name() window.pushButton_visColor.setStyleSheet(btnStyle) titleFont = self.settings.value("titleFont") if not titleFont == None: window.fontComboBox.setCurrentFont(QFont(titleFont)) alignment = self.settings.value("alignment") if not alignment == None: window.alignmentComboBox.setCurrentIndex(int(alignment)) fontSize = self.settings.value("fontSize") if not fontSize == None: window.fontsizeSpinBox.setValue(int(fontSize)) xPosition = self.settings.value("xPosition") if not xPosition == None: window.textXSpinBox.setValue(int(xPosition)) yPosition = self.settings.value("yPosition") if not yPosition == None: window.textYSpinBox.setValue(int(yPosition)) window.fontComboBox.currentFontChanged.connect(self.drawPreview) window.lineEdit_title.textChanged.connect(self.drawPreview) window.alignmentComboBox.currentIndexChanged.connect(self.drawPreview) window.textXSpinBox.valueChanged.connect(self.drawPreview) window.textYSpinBox.valueChanged.connect(self.drawPreview) window.fontsizeSpinBox.valueChanged.connect(self.drawPreview) window.lineEdit_textColor.textChanged.connect(self.drawPreview) window.lineEdit_visColor.textChanged.connect(self.drawPreview) self.drawPreview() window.show() def cleanUp(self): self.timer.stop() self.previewThread.quit() self.previewThread.wait() self.settings.setValue("titleFont", self.window.fontComboBox.currentFont().toString()) self.settings.setValue("alignment", str(self.window.alignmentComboBox.currentIndex())) self.settings.setValue("fontSize", str(self.window.fontsizeSpinBox.value())) self.settings.setValue("xPosition", str(self.window.textXSpinBox.value())) self.settings.setValue("yPosition", str(self.window.textYSpinBox.value())) self.settings.setValue("visColor", self.window.lineEdit_visColor.text()) self.settings.setValue("textColor", self.window.lineEdit_textColor.text()) def openInputFileDialog(self): inputDir = self.settings.value("inputDir", expanduser("~")) fileName, _ = QtWidgets.QFileDialog.getOpenFileName(self.window, "Open Music File", inputDir, "Music Files (*.mp3 *.wav *.ogg *.flac)"); if not fileName == "": self.settings.setValue("inputDir", os.path.dirname(fileName)) self.window.label_input.setText(fileName) def openOutputFileDialog(self): outputDir = self.settings.value("outputDir", expanduser("~")) fileName, _ = QtWidgets.QFileDialog.getSaveFileName(self.window, "Set Output Video File", outputDir, "Video Files (*.mkv)"); if not fileName == "": self.settings.setValue("outputDir", os.path.dirname(fileName)) self.window.label_output.setText(fileName) def openBackgroundFileDialog(self): backgroundDir = self.settings.value("backgroundDir", expanduser("~")) fileName, _ = QtWidgets.QFileDialog.getOpenFileName(self.window, "Open Background Image", backgroundDir, "Image Files (*.jpg *.jpeg *.png);; Video Files (*.mp4)"); if not fileName == "": self.settings.setValue("backgroundDir", os.path.dirname(fileName)) self.window.label_background.setText(fileName) self.drawPreview() def createAudioVisualisation(self): ffmpeg_cmd = self.settings.value("ffmpeg_cmd", expanduser("~")) self.videoThread = QtCore.QThread(self) self.videoWorker = video_thread.Worker(self) self.videoWorker.moveToThread(self.videoThread) self.videoWorker.videoCreated.connect(self.videoCreated) self.videoWorker.progressBarUpdate.connect(self.progressBarUpdated) self.videoWorker.progressBarSetText.connect(self.progressBarSetText) self.videoThread.start() self.videoTask.emit(self.window.label_background.text(), self.window.lineEdit_title.text(), self.window.fontComboBox.currentFont(), self.window.fontsizeSpinBox.value(), self.window.alignmentComboBox.currentIndex(), self.window.textXSpinBox.value(), self.window.textYSpinBox.value(), core.Core.RGBFromString(self.window.lineEdit_textColor.text()), core.Core.RGBFromString(self.window.lineEdit_visColor.text()), self.window.label_input.text(), self.window.label_output.text()) def progressBarUpdated(self, value): self.window.progressBar_create.setValue(value) def progressBarSetText(self, value): self.window.progressBar_create.setFormat(value) def videoCreated(self): self.videoThread.quit() self.videoThread.wait() def drawPreview(self): self.newTask.emit(self.window.label_background.text(), self.window.lineEdit_title.text(), self.window.fontComboBox.currentFont(), self.window.fontsizeSpinBox.value(), self.window.alignmentComboBox.currentIndex(), self.window.textXSpinBox.value(), self.window.textYSpinBox.value(), core.Core.RGBFromString(self.window.lineEdit_textColor.text()), core.Core.RGBFromString(self.window.lineEdit_visColor.text())) # self.processTask.emit() def showPreviewImage(self, image): self._scaledPreviewImage = image self._previewPixmap = QtGui.QPixmap.fromImage(self._scaledPreviewImage) self.window.label_preview.setPixmap(self._previewPixmap) def pickColor(self, colorTarget): color = QtWidgets.QColorDialog.getColor() if color.isValid(): RGBstring = '%s,%s,%s' % (str(color.red()), str(color.green()), str(color.blue())) btnStyle = "QPushButton { background-color : %s; outline: none; }" % color.name() if colorTarget == 'text': self.window.lineEdit_textColor.setText(RGBstring) window.pushButton_textColor.setStyleSheet(btnStyle) elif colorTarget == 'vis': self.window.lineEdit_visColor.setText(RGBstring) window.pushButton_visColor.setStyleSheet(btnStyle) if len(sys.argv) > 1: # command line mode app = QtWidgets.QApplication(sys.argv, False) command = Command() signal.signal(signal.SIGINT, command.cleanUp) sys.exit(app.exec_()) else: # gui mode if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) file = QFile("main.ui") file.open(QFile.ReadOnly) loader = QUiLoader() window = loader.load(file) # window.adjustSize() desc = QtWidgets.QDesktopWidget() dpi = desc.physicalDpiX() topMargin = 0 if (dpi == 96) else int(10 * (dpi / 96)) window.resize(window.width() * (dpi / 96), window.height() * (dpi / 96)) window.verticalLayout_2.setContentsMargins(0, topMargin, 0, 0) main = Main(window) signal.signal(signal.SIGINT, main.cleanUp) atexit.register(main.cleanUp) sys.exit(app.exec_())