#!/usr/bin/env python3 # -*- coding: utf-8 -*- # JACK-Capture frontend, with freewheel and transport support # Copyright (C) 2010-2018 Filipe Coelho # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # For a full copy of the GNU General Public License see the COPYING file # ------------------------------------------------------------------------------------------------------------ # Imports (Global) from time import sleep if True: from PyQt5.QtCore import pyqtSlot, QProcess, QTime, QTimer, QSettings from PyQt5.QtWidgets import QDialog else: from PyQt4.QtCore import pyqtSlot, QProcess, QTime, QTimer, QSettings from PyQt4.QtGui import QDialog # ------------------------------------------------------------------------------------------------------------ # Imports (Custom Stuff) import ui_render from shared import * from shared_i18n import * from jacklib_helpers import * # ------------------------------------------------------------------------------------------------------------ # Global variables global gJackCapturePath, gJackClient gJackCapturePath = "" gJackClient = None # ------------------------------------------------------------------------------------------------------------ # Find 'jack_capture' # Check for cxfreeze if sys.path[0].rsplit(os.sep, 1)[-1] in ("catia", "claudia", "cadence-render", "render"): name = sys.path[0].rsplit(os.sep, 1)[-1] cwd = sys.path[0].rsplit(name, 1)[0] if os.path.exists(os.path.join(cwd, "jack_capture")): gJackCapturePath = os.path.join(cwd, "jack_capture") del cwd, name # Check in PATH if not gJackCapturePath: for iPATH in PATH: if os.path.exists(os.path.join(iPATH, "jack_capture")): gJackCapturePath = os.path.join(iPATH, "jack_capture") break def canRender(): global gJackCapturePath return bool(gJackCapturePath) # ------------------------------------------------------------------------------------------------------------ # Render Window class RenderW(QDialog): def __init__(self, parent): QDialog.__init__(self, parent) self.ui = ui_render.Ui_RenderW() self.ui.setupUi(self) self.fFreewheel = False self.fLastTime = -1 self.fMaxTime = 180 self.fTimer = QTimer(self) self.fProcess = QProcess(self) # ------------------------------------------------------------- # Get JACK client and base information global gJackClient if gJackClient: self.fJackClient = gJackClient else: self.fJackClient = jacklib.client_open("Render-Dialog", jacklib.JackNoStartServer, None) self.fBufferSize = int(jacklib.get_buffer_size(self.fJackClient)) self.fSampleRate = int(jacklib.get_sample_rate(self.fJackClient)) for i in range(self.ui.cb_buffer_size.count()): if int(self.ui.cb_buffer_size.itemText(i)) == self.fBufferSize: self.ui.cb_buffer_size.setCurrentIndex(i) break else: self.ui.cb_buffer_size.addItem(str(self.fBufferSize)) self.ui.cb_buffer_size.setCurrentIndex(self.ui.cb_buffer_size.count() - 1) # ------------------------------------------------------------- # Set-up GUI stuff # Get List of formats self.fProcess.start(gJackCapturePath, ["-pf"]) self.fProcess.waitForFinished() formats = str(self.fProcess.readAllStandardOutput(), encoding="utf-8").split(" ") formatsList = [] for i in range(len(formats) - 1): iFormat = formats[i].strip() if iFormat: formatsList.append(iFormat) formatsList.sort() # Put all formats in combo-box, select 'wav' option for i in range(len(formatsList)): self.ui.cb_format.addItem(formatsList[i]) if formatsList[i] == "wav": self.ui.cb_format.setCurrentIndex(i) self.ui.cb_depth.setCurrentIndex(4) #Float self.ui.rb_stereo.setChecked(True) self.ui.te_end.setTime(QTime(0, 3, 0)) self.ui.progressBar.setFormat("") self.ui.progressBar.setMinimum(0) self.ui.progressBar.setMaximum(1) self.ui.progressBar.setValue(0) self.ui.b_render.setIcon(getIcon("media-record")) self.ui.b_stop.setIcon(getIcon("media-playback-stop")) self.ui.b_close.setIcon(getIcon("window-close")) self.ui.b_open.setIcon(getIcon("document-open")) self.ui.b_stop.setVisible(False) self.ui.le_folder.setText(HOME) # ------------------------------------------------------------- # Set-up connections self.ui.b_render.clicked.connect(self.slot_renderStart) self.ui.b_stop.clicked.connect(self.slot_renderStop) self.ui.b_open.clicked.connect(self.slot_getAndSetPath) self.ui.b_now_start.clicked.connect(self.slot_setStartNow) self.ui.b_now_end.clicked.connect(self.slot_setEndNow) self.ui.te_start.timeChanged.connect(self.slot_updateStartTime) self.ui.te_end.timeChanged.connect(self.slot_updateEndTime) self.ui.group_time.clicked.connect(self.slot_transportChecked) self.fTimer.timeout.connect(self.slot_updateProgressbar) # ------------------------------------------------------------- self.loadSettings() @pyqtSlot() def slot_renderStart(self): if not os.path.exists(self.ui.le_folder.text()): QMessageBox.warning(self, self.tr("Warning"), self.tr("The selected directory does not exist. Please choose a valid one.")) return timeStart = self.ui.te_start.time() timeEnd = self.ui.te_end.time() minTime = (timeStart.hour() * 3600) + (timeStart.minute() * 60) + (timeStart.second()) maxTime = (timeEnd.hour() * 3600) + (timeEnd.minute() * 60) + (timeEnd.second()) newBufferSize = int(self.ui.cb_buffer_size.currentText()) useTransport = self.ui.group_time.isChecked() self.fFreewheel = bool(self.ui.cb_render_mode.currentIndex() == 1) self.fLastTime = -1 self.fMaxTime = maxTime if self.fFreewheel: self.fTimer.setInterval(100) else: self.fTimer.setInterval(500) self.ui.group_render.setEnabled(False) self.ui.group_time.setEnabled(False) self.ui.group_encoding.setEnabled(False) self.ui.b_render.setVisible(False) self.ui.b_stop.setVisible(True) self.ui.b_close.setEnabled(False) if useTransport: self.ui.progressBar.setFormat("%p%") self.ui.progressBar.setMinimum(minTime) self.ui.progressBar.setMaximum(maxTime) self.ui.progressBar.setValue(minTime) else: self.ui.progressBar.setFormat("") self.ui.progressBar.setMinimum(0) self.ui.progressBar.setMaximum(0) self.ui.progressBar.setValue(0) self.ui.progressBar.update() arguments = [] # Filename prefix arguments.append("-fp") arguments.append(self.ui.le_prefix.text()) # Format arguments.append("-f") arguments.append(self.ui.cb_format.currentText()) # Bit depth arguments.append("-b") arguments.append(self.ui.cb_depth.currentText()) # Channels arguments.append("-c") if self.ui.rb_mono.isChecked(): arguments.append("1") elif self.ui.rb_stereo.isChecked(): arguments.append("2") else: arguments.append(str(self.ui.sb_channels.value())) # Controlled only by freewheel if self.fFreewheel: arguments.append("-jf") # Controlled by transport elif useTransport: arguments.append("-jt") # Silent mode arguments.append("-dc") arguments.append("-s") # Change current directory os.chdir(self.ui.le_folder.text()) if newBufferSize != int(jacklib.get_buffer_size(self.fJackClient)): print(self.tr("NOTICE: buffer size changed before render")) jacklib.set_buffer_size(self.fJackClient, newBufferSize) if useTransport: if jacklib.transport_query(self.fJackClient, None) > jacklib.JackTransportStopped: # rolling or starting jacklib.transport_stop(self.fJackClient) jacklib.transport_locate(self.fJackClient, minTime * self.fSampleRate) self.fProcess.start(gJackCapturePath, arguments) self.fProcess.waitForStarted() if self.fFreewheel: print(self.tr("NOTICE: rendering in freewheel mode")) sleep(1) jacklib.set_freewheel(self.fJackClient, 1) if useTransport: self.fTimer.start() jacklib.transport_start(self.fJackClient) @pyqtSlot() def slot_renderStop(self): useTransport = self.ui.group_time.isChecked() if useTransport: jacklib.transport_stop(self.fJackClient) if self.fFreewheel: jacklib.set_freewheel(self.fJackClient, 0) sleep(1) self.fProcess.terminate() #self.fProcess.waitForFinished(5000) if useTransport: self.fTimer.stop() self.ui.group_render.setEnabled(True) self.ui.group_time.setEnabled(True) self.ui.group_encoding.setEnabled(True) self.ui.b_render.setVisible(True) self.ui.b_stop.setVisible(False) self.ui.b_close.setEnabled(True) self.ui.progressBar.setFormat("") self.ui.progressBar.setMinimum(0) self.ui.progressBar.setMaximum(1) self.ui.progressBar.setValue(0) self.ui.progressBar.update() # Restore buffer size newBufferSize = int(jacklib.get_buffer_size(self.fJackClient)) if newBufferSize != self.fBufferSize: jacklib.set_buffer_size(self.fJackClient, newBufferSize) @pyqtSlot() def slot_getAndSetPath(self): getAndSetPath(self, self.ui.le_folder.text(), self.ui.le_folder) @pyqtSlot() def slot_setStartNow(self): time = int(jacklib.get_current_transport_frame(self.fJackClient) / self.fSampleRate) secs = time % 60 mins = int(time / 60) % 60 hrs = int(time / 3600) % 60 self.ui.te_start.setTime(QTime(hrs, mins, secs)) @pyqtSlot() def slot_setEndNow(self): time = int(jacklib.get_current_transport_frame(self.fJackClient) / self.fSampleRate) secs = time % 60 mins = int(time / 60) % 60 hrs = int(time / 3600) % 60 self.ui.te_end.setTime(QTime(hrs, mins, secs)) @pyqtSlot(QTime) def slot_updateStartTime(self, time): if time >= self.ui.te_end.time(): self.ui.te_end.setTime(time) renderEnabled = False else: renderEnabled = True if self.ui.group_time.isChecked(): self.ui.b_render.setEnabled(renderEnabled) @pyqtSlot(QTime) def slot_updateEndTime(self, time): if time <= self.ui.te_start.time(): self.ui.te_start.setTime(time) renderEnabled = False else: renderEnabled = True if self.ui.group_time.isChecked(): self.ui.b_render.setEnabled(renderEnabled) @pyqtSlot(bool) def slot_transportChecked(self, yesNo): if yesNo: renderEnabled = bool(self.ui.te_end.time() > self.ui.te_start.time()) else: renderEnabled = True self.ui.b_render.setEnabled(renderEnabled) @pyqtSlot() def slot_updateProgressbar(self): time = int(int(jacklib.get_current_transport_frame(self.fJackClient)) / self.fSampleRate) self.ui.progressBar.setValue(time) if time > self.fMaxTime or (self.fLastTime > time and not self.fFreewheel): self.slot_renderStop() self.fLastTime = time def saveSettings(self): settings = QSettings("Cadence", "Cadence-Render") if self.ui.rb_mono.isChecked(): channels = 1 elif self.ui.rb_stereo.isChecked(): channels = 2 else: channels = self.ui.sb_channels.value() settings.setValue("Geometry", self.saveGeometry()) settings.setValue("OutputFolder", self.ui.le_folder.text()) settings.setValue("FilenamePrefix", self.ui.le_prefix.text()) settings.setValue("EncodingFormat", self.ui.cb_format.currentText()) settings.setValue("EncodingDepth", self.ui.cb_depth.currentText()) settings.setValue("EncodingChannels", channels) settings.setValue("UseTransport", self.ui.group_time.isChecked()) settings.setValue("StartTime", self.ui.te_start.time()) settings.setValue("EndTime", self.ui.te_end.time()) def loadSettings(self): settings = QSettings("Cadence", "Cadence-Render") self.restoreGeometry(settings.value("Geometry", b"")) outputFolder = settings.value("OutputFolder", HOME) if os.path.exists(outputFolder): self.ui.le_folder.setText(outputFolder) self.ui.le_prefix.setText(settings.value("FilenamePrefix", "jack_capture_")) encFormat = settings.value("EncodingFormat", "Wav", type=str) for i in range(self.ui.cb_format.count()): if self.ui.cb_format.itemText(i) == encFormat: self.ui.cb_format.setCurrentIndex(i) break encDepth = settings.value("EncodingDepth", "Float", type=str) for i in range(self.ui.cb_depth.count()): if self.ui.cb_depth.itemText(i) == encDepth: self.ui.cb_depth.setCurrentIndex(i) break encChannels = settings.value("EncodingChannels", 2, type=int) if encChannels == 1: self.ui.rb_mono.setChecked(True) elif encChannels == 2: self.ui.rb_stereo.setChecked(True) else: self.ui.rb_outro.setChecked(True) self.ui.sb_channels.setValue(encChannels) self.ui.group_time.setChecked(settings.value("UseTransport", False, type=bool)) self.ui.te_start.setTime(settings.value("StartTime", self.ui.te_start.time(), type=QTime)) self.ui.te_end.setTime(settings.value("EndTime", self.ui.te_end.time(), type=QTime)) def closeEvent(self, event): self.saveSettings() if self.fJackClient: jacklib.client_close(self.fJackClient) QDialog.closeEvent(self, event) def done(self, r): QDialog.done(self, r) self.close() # ------------------------------------------------------------------------------------------------------------ # Allow to use this as a standalone app if __name__ == '__main__': # Additional imports from PyQt5.QtWidgets import QApplication # App initialization app = QApplication(sys.argv) app.setApplicationName("Cadence-Render") app.setApplicationVersion(VERSION) app.setOrganizationName("Cadence") app.setWindowIcon(QIcon(":/scalable/cadence.svg")) setup_i18n() if jacklib is None: QMessageBox.critical(None, app.translate("RenderW", "Error"), app.translate("RenderW", "JACK is not available in this system, cannot use this application.")) sys.exit(1) if not gJackCapturePath: QMessageBox.critical(None, app.translate("RenderW", "Error"), app.translate("RenderW", "The 'jack_capture' application is not available.\n" "Is not possible to render without it!")) sys.exit(2) jackStatus = jacklib.jack_status_t(0x0) gJackClient = jacklib.client_open("Render", jacklib.JackNoStartServer, jacklib.pointer(jackStatus)) if not gJackClient: errorString = get_jack_status_error_string(jackStatus) QMessageBox.critical(None, app.translate("RenderW", "Error"), app.translate("RenderW", "Could not connect to JACK, possible reasons:\n" "%s" % errorString)) sys.exit(1) # Show GUI gui = RenderW(None) gui.setWindowIcon(getIcon("media-record", 48)) gui.show() # App-Loop sys.exit(app.exec_())