Merge pull request #2310 from rcorre/master

Add CLI equivalent to GUI's "export tracks"
This commit is contained in:
Colin Wallace
2015-09-13 22:36:03 +00:00
9 changed files with 373 additions and 179 deletions

View File

@@ -42,8 +42,14 @@ LMMS features components such as a Song Editor, a Beat+Bassline Editor, a Piano
.SH OPTIONS
.IP "\fB\-r, --render\fP \fIproject-file\fP
Render given file to either a wav\- or ogg\-file. See \fB\-f\fP for details
.IP "\fB\-o, --output\fP \fIfile\fP
render into \fIfile\fP
.IP "\fB\-r, --rendertracks\fP \fIproject-file\fP
Render each track into a separate wav\- or ogg\-file. See \fB\-f\fP for details
.IP "\fB\-o, --output\fP \fIpath\fP
Render into \fIpath\fP
.br
For --render, this is interpreted as a file path.
.br
For --render-tracks, this is interpreted as a path to an existing directory.
.IP "\fB\-f, --format\fP \fIformat\fP
Specify format of render-output where \fIformat\fP is either 'wav' or 'ogg'
.IP "\fB\-s, --samplerate\fP \fIsamplerate\fP

View File

@@ -32,7 +32,7 @@
#include "ui_export_project.h"
#include "ProjectRenderer.h"
#include "RenderManager.h"
class ExportProjectDialog : public QDialog, public Ui::ExportProjectDialog
{
@@ -50,26 +50,17 @@ protected:
private slots:
void startBtnClicked( void );
void updateTitleBar( int );
void render(ProjectRenderer* renderer);
void multiRender();
ProjectRenderer* prepRender();
void popRender();
void accept();
void startExport();
private:
QString m_fileName;
QString m_dirName;
QString m_fileExtension;
typedef QVector<ProjectRenderer*> RenderVector;
RenderVector m_renderers;
bool m_multiExport;
typedef QVector<Track*> TrackVector;
TrackVector m_unmuted;
TrackVector m_unmutedBB;
ProjectRenderer::ExportFileFormats m_ft;
TrackVector m_tracksToRender;
ProjectRenderer* m_activeRenderer;
RenderManager* m_renderManager;
} ;
#endif

View File

@@ -87,8 +87,9 @@ public:
static ExportFileFormats getFileFormatFromExtension(
const QString & _ext );
static const FileEncodeDevice fileEncodeDevices[];
static QString getFileExtensionFromFormat( ExportFileFormats fmt );
static const FileEncodeDevice fileEncodeDevices[];
public slots:
void startProcessing();

76
include/RenderManager.h Normal file
View File

@@ -0,0 +1,76 @@
/*
* RenderManager.h - Provides a uniform interface for rendering the project or
* individual tracks for the CLI and GUI.
*
* Copyright (c) 2015 Ryan Roden-Corrent <ryan/at/rcorre.net>
*
* This file is part of LMMS - http://lmms.io
*
* 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 (at your option) 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.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#ifndef RENDER_MANAGER_H
#define RENDER_MANAGER_H
#include <vector>
#include "ProjectRenderer.h"
class RenderManager : public QObject
{
Q_OBJECT
public:
RenderManager(
const Mixer::qualitySettings & qualitySettings,
const ProjectRenderer::OutputSettings & outputSettings,
ProjectRenderer::ExportFileFormats fmt,
QString outputPath);
virtual ~RenderManager();
/// Export all unmuted tracks into a single file
void renderProject();
/// Export all unmuted tracks into individual file
void renderTracks();
void abortProcessing();
signals:
void progressChanged( int );
void finished();
private slots:
void renderNextTrack();
void updateConsoleProgress();
private:
QString pathForTrack( const Track *track, int num );
void restoreMutedState();
const Mixer::qualitySettings m_qualitySettings;
const ProjectRenderer::OutputSettings m_outputSettings;
ProjectRenderer::ExportFileFormats m_format;
QString m_outputPath;
ProjectRenderer* m_activeRenderer;
QVector<Track*> m_tracksToRender;
QVector<Track*> m_unmuted;
} ;
#endif

View File

@@ -51,6 +51,7 @@ set(LMMS_SRCS
core/ProjectRenderer.cpp
core/ProjectVersion.cpp
core/RemotePlugin.cpp
core/RenderManager.cpp
core/RingBuffer.cpp
core/SampleBuffer.cpp
core/SamplePlayHandle.cpp

View File

@@ -124,6 +124,15 @@ ProjectRenderer::ExportFileFormats ProjectRenderer::getFileFormatFromExtension(
QString ProjectRenderer::getFileExtensionFromFormat(
ExportFileFormats fmt )
{
return fileEncodeDevices[fmt].m_extension;
}
void ProjectRenderer::startProcessing()
{

214
src/core/RenderManager.cpp Normal file
View File

@@ -0,0 +1,214 @@
/*
* RenderManager - exporting logic common between the CLI and GUI.
*
* Copyright (c) 2015 Ryan Roden-Corrent <ryan/at/rcorre.net>
*
* This file is part of LMMS - http://lmms.io
*
* 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 (at your option) 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.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include <QFileInfo>
#include <QDebug>
#include <QDir>
#include "RenderManager.h"
#include "Song.h"
#include "BBTrackContainer.h"
#include "BBTrack.h"
#include "debug.h"
RenderManager::RenderManager(
const Mixer::qualitySettings & qualitySettings,
const ProjectRenderer::OutputSettings & outputSettings,
ProjectRenderer::ExportFileFormats fmt,
QString outputPath) :
m_qualitySettings(qualitySettings),
m_outputSettings(outputSettings),
m_format(fmt),
m_outputPath(outputPath),
m_activeRenderer(NULL)
{
}
RenderManager::~RenderManager()
{
delete m_activeRenderer;
}
void RenderManager::abortProcessing()
{
if ( m_activeRenderer ) {
m_activeRenderer->abortProcessing();
}
restoreMutedState();
}
// Called to render each new track when rendering tracks individually.
void RenderManager::renderNextTrack()
{
delete m_activeRenderer;
m_activeRenderer = NULL;
if( m_tracksToRender.isEmpty() )
{
// nothing left to render
restoreMutedState();
emit finished();
}
else
{
// pop the next track from our rendering queue
Track* renderTrack = m_tracksToRender.back();
m_tracksToRender.pop_back();
// mute everything but the track we are about to render
for( auto it = m_unmuted.begin(); it != m_unmuted.end(); ++it )
{
(*it)->setMuted( (*it) != renderTrack );
}
// for multi-render, prefix each output file with a different number
int trackNum = m_tracksToRender.size() + 1;
// create a renderer for this track
m_activeRenderer = new ProjectRenderer(
m_qualitySettings,
m_outputSettings,
m_format,
pathForTrack(renderTrack, trackNum));
if ( m_activeRenderer->isReady() )
{
// pass progress signals through
connect( m_activeRenderer, SIGNAL( progressChanged( int ) ),
this, SIGNAL( progressChanged( int ) ) );
// when it is finished, render the next track
connect( m_activeRenderer, SIGNAL( finished() ),
this, SLOT( renderNextTrack() ) );
m_activeRenderer->startProcessing();
}
else
{
qDebug( "Renderer failed to acquire a file device!" );
renderNextTrack();
}
}
}
// Render the song into individual tracks
void RenderManager::renderTracks()
{
const TrackContainer::TrackList & tl = Engine::getSong()->tracks();
// find all currently unnmuted tracks -- we want to render these.
for( auto it = tl.begin(); it != tl.end(); ++it )
{
Track* tk = (*it);
Track::TrackTypes type = tk->type();
// Don't mute automation tracks
if ( tk->isMuted() == false &&
( type == Track::InstrumentTrack || type == Track::SampleTrack ) )
{
m_unmuted.push_back(tk);
}
}
const TrackContainer::TrackList t2 = Engine::getBBTrackContainer()->tracks();
for( auto it = t2.begin(); it != t2.end(); ++it )
{
Track* tk = (*it);
if ( tk->isMuted() == false )
{
m_unmuted.push_back(tk);
}
}
// copy the list of unmuted tracks into our rendering queue.
// we need to remember which tracks were unmuted to restore state at the end.
m_tracksToRender = m_unmuted;
renderNextTrack();
}
// Render the song into a single track
void RenderManager::renderProject()
{
m_activeRenderer = new ProjectRenderer(
m_qualitySettings,
m_outputSettings,
m_format,
m_outputPath);
if( m_activeRenderer->isReady() )
{
// pass progress signals through
connect( m_activeRenderer, SIGNAL( progressChanged( int ) ),
this, SIGNAL( progressChanged( int ) ) );
// as we have not queued any tracks, renderNextTrack will just clean up
connect( m_activeRenderer, SIGNAL( finished() ),
this, SLOT( renderNextTrack() ) );
m_activeRenderer->startProcessing();
}
else
{
qDebug( "Renderer failed to acquire a file device!" );
emit finished();
}
}
// Unmute all tracks that were muted while rendering tracks
void RenderManager::restoreMutedState()
{
while( !m_unmuted.isEmpty() )
{
Track* restoreTrack = m_unmuted.back();
m_unmuted.pop_back();
restoreTrack->setMuted( false );
}
}
// Determine the output path for a track when rendering tracks individually
QString RenderManager::pathForTrack(const Track *track, int num)
{
QString extension = ProjectRenderer::getFileExtensionFromFormat( m_format );
QString name = track->name();
name = name.remove(QRegExp("[^a-zA-Z]"));
name = QString( "%1_%2%3" ).arg( num ).arg( name ).arg( extension );
return QDir(m_outputPath).filePath(name);
}
void RenderManager::updateConsoleProgress()
{
if ( m_activeRenderer )
{
m_activeRenderer->updateConsoleProgress();
int totalNum = m_unmuted.size();
if ( totalNum > 0 )
{
// we are rendering multiple tracks, append a track counter to the output
int trackNum = totalNum - m_tracksToRender.size();
fprintf( stderr, "(%d/%d)", trackNum, totalNum );
}
}
}

View File

@@ -66,6 +66,7 @@
#include "ImportFilter.h"
#include "MainWindow.h"
#include "ProjectRenderer.h"
#include "RenderManager.h"
#include "DataFile.h"
#include "Song.h"
@@ -126,7 +127,10 @@ void printHelp()
" [ -h ]\n"
" [ <file to load> ]\n\n"
"-r, --render <project file> Render given project file\n"
"-o, --output <file> Render into <file>\n"
" --rendertracks <project> Render each track to a different file\n"
"-o, --output <path> Render into <path>\n"
" For --render, provide a file path\n"
" For --rendertracks, provide a directory path\n"
"-f, --format <format> Specify format of render-output where\n"
" Format is either 'wav' or 'ogg'.\n"
"-s, --samplerate <samplerate> Specify output samplerate in Hz\n"
@@ -172,6 +176,7 @@ int main( int argc, char * * argv )
bool exitAfterImport = false;
bool allowRoot = false;
bool renderLoop = false;
bool renderTracks = false;
QString fileToLoad, fileToImport, renderOut, profilerOutputFile;
// first of two command-line parsing stages
@@ -185,6 +190,11 @@ int main( int argc, char * * argv )
{
coreOnly = true;
}
else if( arg == "--rendertracks" )
{
coreOnly = true;
renderTracks = true;
}
else if( arg == "--allowroot" )
{
allowRoot = true;
@@ -288,7 +298,7 @@ int main( int argc, char * * argv )
return EXIT_SUCCESS;
}
else if( arg == "--render" || arg == "-r" )
else if( arg == "--render" || arg == "-r" || arg == "--rendertracks" )
{
++i;
@@ -301,7 +311,7 @@ int main( int argc, char * * argv )
fileToLoad = QString::fromLocal8Bit( argv[i] );
renderOut = baseName( fileToLoad ) + ".";
renderOut = fileToLoad;
}
else if( arg == "--loop" || arg == "-l" )
{
@@ -319,7 +329,7 @@ int main( int argc, char * * argv )
}
renderOut = baseName( QString::fromLocal8Bit( argv[i] ) ) + ".";
renderOut = QString::fromLocal8Bit( argv[i] );
}
else if( arg == "--format" || arg == "-f" )
{
@@ -588,9 +598,16 @@ int main( int argc, char * * argv )
Engine::getSong()->setExportLoop( renderLoop );
// when rendering multiple tracks, renderOut is a directory
// otherwise, it is a file, so we need to append the file extension
if ( !renderTracks )
{
renderOut = baseName( renderOut ) +
ProjectRenderer::getFileExtensionFromFormat(eff);
}
// create renderer
QString extension = ( eff == ProjectRenderer::WaveFile ) ? "wav" : "ogg";
ProjectRenderer * r = new ProjectRenderer( qs, os, eff, renderOut + extension );
RenderManager * r = new RenderManager( qs, os, eff, renderOut );
QCoreApplication::instance()->connect( r,
SIGNAL( finished() ), SLOT( quit() ) );
@@ -606,7 +623,14 @@ int main( int argc, char * * argv )
}
// start now!
r->startProcessing();
if ( renderTracks )
{
r->renderTracks();
}
else
{
r->renderProject();
}
}
else // otherwise, start the GUI
{

View File

@@ -25,6 +25,7 @@
#include <QFileInfo>
#include <QDir>
#include <QMessageBox>
#include <QDebug>
#include "ExportProjectDialog.h"
#include "Song.h"
@@ -41,10 +42,10 @@ ExportProjectDialog::ExportProjectDialog( const QString & _file_name,
m_fileName( _file_name ),
m_fileExtension(),
m_multiExport( multi_export ),
m_activeRenderer( NULL )
m_renderManager( NULL )
{
setupUi( this );
setWindowTitle( tr( "Export project to %1" ).arg(
setWindowTitle( tr( "Export project to %1" ).arg(
QFileInfo( _file_name ).fileName() ) );
// get the extension of the chosen file
@@ -80,7 +81,6 @@ ExportProjectDialog::ExportProjectDialog( const QString & _file_name,
connect( startButton, SIGNAL( clicked() ),
this, SLOT( startBtnClicked() ) );
}
@@ -88,12 +88,7 @@ ExportProjectDialog::ExportProjectDialog( const QString & _file_name,
ExportProjectDialog::~ExportProjectDialog()
{
for( RenderVector::ConstIterator it = m_renderers.begin();
it != m_renderers.end(); ++it )
{
delete (*it);
}
delete m_renderManager;
}
@@ -101,14 +96,12 @@ ExportProjectDialog::~ExportProjectDialog()
void ExportProjectDialog::reject()
{
for( RenderVector::ConstIterator it = m_renderers.begin(); it != m_renderers.end(); ++it )
{
(*it)->abortProcessing();
if( m_renderManager ) {
m_renderManager->abortProcessing();
}
if( m_activeRenderer ) {
m_activeRenderer->abortProcessing();
}
delete m_renderManager;
m_renderManager = NULL;
QDialog::reject();
}
@@ -117,24 +110,10 @@ void ExportProjectDialog::reject()
void ExportProjectDialog::accept()
{
// If more to render, kick off next render job
if( m_renderers.isEmpty() == false )
{
popRender();
}
else
{
// If done, then reset mute states
while( m_unmuted.isEmpty() == false )
{
Track* restoreTrack = m_unmuted.back();
m_unmuted.pop_back();
restoreTrack->setMuted( false );
}
delete m_renderManager;
m_renderManager = NULL;
QDialog::accept();
}
QDialog::accept();
}
@@ -142,16 +121,8 @@ void ExportProjectDialog::accept()
void ExportProjectDialog::closeEvent( QCloseEvent * _ce )
{
for( RenderVector::ConstIterator it = m_renderers.begin(); it != m_renderers.end(); ++it )
{
if( (*it)->isRunning() )
{
(*it)->abortProcessing();
}
}
if( m_activeRenderer && m_activeRenderer->isRunning() ) {
m_activeRenderer->abortProcessing();
if( m_renderManager ) {
m_renderManager->abortProcessing();
}
QDialog::closeEvent( _ce );
@@ -159,94 +130,8 @@ void ExportProjectDialog::closeEvent( QCloseEvent * _ce )
void ExportProjectDialog::popRender()
{
if( m_multiExport && m_tracksToRender.isEmpty() == false )
{
Track* renderTrack = m_tracksToRender.back();
m_tracksToRender.pop_back();
// Set must states for song tracks
for( TrackVector::ConstIterator it = m_unmuted.begin(); it != m_unmuted.end(); ++it )
{
if( (*it) == renderTrack )
{
(*it)->setMuted( false );
}
else
{
(*it)->setMuted( true );
}
}
}
// Pop next render job and start
m_activeRenderer = m_renderers.back();
m_renderers.pop_back();
render( m_activeRenderer );
}
void ExportProjectDialog::multiRender()
{
m_dirName = m_fileName;
QString path = QDir(m_fileName).filePath("text.txt");
int x = 1;
const TrackContainer::TrackList & tl = Engine::getSong()->tracks();
// Check for all unmuted tracks. Remember list.
for( TrackContainer::TrackList::ConstIterator it = tl.begin();
it != tl.end(); ++it )
{
Track* tk = (*it);
Track::TrackTypes type = tk->type();
// Don't mute automation tracks
if ( tk->isMuted() == false &&
( type == Track::InstrumentTrack || type == Track::SampleTrack ) )
{
m_unmuted.push_back(tk);
QString nextName = tk->name();
nextName = nextName.remove(QRegExp("[^a-zA-Z]"));
QString name = QString( "%1_%2%3" ).arg( x++ ).arg( nextName ).arg( m_fileExtension );
m_fileName = QDir(m_dirName).filePath(name);
prepRender();
}
else if (! tk->isMuted() && type == Track::BBTrack )
{
m_unmutedBB.push_back(tk);
}
}
const TrackContainer::TrackList t2 = Engine::getBBTrackContainer()->tracks();
for( TrackContainer::TrackList::ConstIterator it = t2.begin(); it != t2.end(); ++it )
{
Track* tk = (*it);
if ( tk->isMuted() == false )
{
m_unmuted.push_back(tk);
QString nextName = tk->name();
nextName = nextName.remove(QRegExp("[^a-zA-Z]"));
QString name = QString( "%1_%2%3" ).arg( x++ ).arg( nextName ).arg( m_fileExtension );
m_fileName = QDir(m_dirName).filePath(name);
prepRender();
}
}
m_tracksToRender = m_unmuted;
popRender();
}
ProjectRenderer* ExportProjectDialog::prepRender()
void ExportProjectDialog::startExport()
{
Mixer::qualitySettings qs =
Mixer::qualitySettings(
@@ -262,38 +147,33 @@ ProjectRenderer* ExportProjectDialog::prepRender()
bitrates[ bitrateCB->currentIndex() ],
static_cast<ProjectRenderer::Depths>( depthCB->currentIndex() ) );
m_renderManager = new RenderManager( qs, os, m_ft, m_fileName );
Engine::getSong()->setExportLoop( exportLoopCB->isChecked() );
Engine::getSong()->setRenderBetweenMarkers( renderMarkersCB->isChecked() );
ProjectRenderer* renderer = new ProjectRenderer( qs, os, m_ft, m_fileName );
connect( m_renderManager, SIGNAL( progressChanged( int ) ),
progressBar, SLOT( setValue( int ) ) );
connect( m_renderManager, SIGNAL( progressChanged( int ) ),
this, SLOT( updateTitleBar( int ) )) ;
connect( m_renderManager, SIGNAL( finished() ),
this, SLOT( accept() ) );
connect( m_renderManager, SIGNAL( finished() ),
gui->mainWindow(), SLOT( resetWindowTitle() ) );
m_renderers.push_back(renderer);
return renderer;
}
void ExportProjectDialog::render( ProjectRenderer* renderer )
{
if( renderer->isReady() )
if ( m_multiExport )
{
connect( renderer, SIGNAL( progressChanged( int ) ), progressBar, SLOT( setValue( int ) ) );
connect( renderer, SIGNAL( progressChanged( int ) ), this, SLOT( updateTitleBar( int ) )) ;
connect( renderer, SIGNAL( finished() ), this, SLOT( accept() ) );
connect( renderer, SIGNAL( finished() ), gui->mainWindow(), SLOT( resetWindowTitle() ) );
renderer->startProcessing();
m_renderManager->renderTracks();
}
else
{
accept();
m_renderManager->renderProject();
}
}
void ExportProjectDialog::startBtnClicked()
{
m_ft = ProjectRenderer::NumFileFormats;
@@ -325,15 +205,7 @@ void ExportProjectDialog::startBtnClicked()
updateTitleBar( 0 );
if (m_multiExport==true)
{
multiRender();
}
else
{
prepRender();
popRender();
}
startExport();
}