cadence/src/render.py

476 lines
16 KiB
Python
Executable File

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# JACK-Capture frontend, with freewheel and transport support
# Copyright (C) 2010-2018 Filipe Coelho <falktx@falktx.com>
#
# 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_())