From adece83242e4687410c84fcd95683ef119f4cea5 Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Tue, 29 Aug 2017 11:22:40 -0400 Subject: [PATCH] 2017-08-29 Fred Gleason * Added '--bitrate=', '--format=', '--normalization-level=', '--quality=' and '--samplerate=' switches to rdrender(1). --- ChangeLog | 3 + docs/docbook/rdrender.xml | 273 ++++++++++++++++++++++++++++-------- utils/rdrender/mainloop.cpp | 76 ++++++++-- utils/rdrender/rdrender.cpp | 120 +++++++++++++++- utils/rdrender/rdrender.h | 10 ++ 5 files changed, 409 insertions(+), 73 deletions(-) diff --git a/ChangeLog b/ChangeLog index 5918c7d0..ac6fd794 100644 --- a/ChangeLog +++ b/ChangeLog @@ -15979,3 +15979,6 @@ * Updated the rdrender(1) man page. 2017-08-29 Fred Gleason * Added an '--ignore-stops' switch to rdrender(1). +2017-08-29 Fred Gleason + * Added '--bitrate=', '--format=', '--normalization-level=', + '--quality=' and '--samplerate=' switches to rdrender(1). diff --git a/docs/docbook/rdrender.xml b/docs/docbook/rdrender.xml index dc6cf18d..2897eb5f 100644 --- a/docs/docbook/rdrender.xml +++ b/docs/docbook/rdrender.xml @@ -30,7 +30,11 @@ rdrender - OPTIONS + GENERAL OPTS + RENDERING OPTS + AUDIO OPTS + logname + output-file @@ -38,12 +42,141 @@ Description rdrender1 can be used to render - an existing Rivendell log as a single audio WAV file. + an existing Rivendell log as a single audio file, thus enabling playback + of the rendered content independently of Rivendell. + + + Three different types of options can be given to + rdrender1: General, + which affect the operation of the program itself, Rendering, + which control how a log is virtually "played" and Audio, + which control the format of the resulting audio file. Each group of + options is documented separately below. - Options + General Options + + + + + + + Be verbose. Among other things, this will cause + rdrender1 to print + a disposition for each log event encountered to standard error. + + + + + + + + Rendering Options + + + + lineno + + + + Start rendering the log at line lineno. + This option is mutually exclusive with the + option, below. + + + + + + + HH:MM:SS + + + + Start rendering the log at the event having a hard-start time + of HH:MM:SS. This option is mutually + exclusive with the option, above. + + + + + + + + + + + Treat any STOP transitions encountered as if they were PLAY + transitions. If not given, a STOP transition will cause rendering + of the log to be terminated at the point where the STOP was + encountered. + + + + + + + lineno + + + + Stop rendering the log at line lineno-1. + This option is mutually exclusive with the + option, below. + + + + + + + HH:MM:SS + + + + Stop rendering the log at the event having a hard-start time of + HH:MM:SS. + This option is mutually exclusive with the + option, above. + + + + + + + HH:MM:SS + + + + Render the log as if it had been started at a time of + HH:MM:SS (useful for ensuring that + dayparted carts play as expected). If not given, the current + system time will be used. + + + + + + + + Audio Options + + + + rate + + + + Specify the bitrate to use, in rate + bits per second. Default value is 256000 bits/sec. + + + This setting is meaningful only when used with the + MP2 or MP3 formats. + + + + chans @@ -59,93 +192,103 @@ - lineno + format - Start the rendered file with line lineno. - This option is mutually exclusive with the - option, below. + Specify the file and audio encoding format to be used. The + following values for format are + recognized: + + + + FLAC + + Free Lossless Audio Codec (.flac) + + + + + MP2 + + MPEG-1 Layer 2 (.mp2) + + + + + MP3 + + MPEG-1 Layer 3 (.mp3) + + + + + PCM16 + + PCM16 audio in a WAV file format (.wav) + + + + + PCM24 + + PCM24 audio in a WAV file format (.wav) + + + + + VORBIS + + OggVorbis (.ogg) + + + + + Default value is PCM16. - HH:MM:SS + level - Start the rendered file with the event having a hard-start time - of HH:MM:SS. This option is mutually - exclusive with the option, above. + Peak normalize the audio to level dBFS. + Default value is 0, which will disable + peak normalization. - + qual - Treat any STOP transitions encountered as if they were PLAY. - If not given, a STOP transition will cause rendering of the log - to be terminated at that point. + Specify the quality level to use, in the range + -1 through 10, + inclusive. Default value is 3. + + + This setting is meaningful only when used with the + VORBIS format. - lineno + rate - End the rendered file with line lineno-1. - This option is mutually exclusive with the - option, below. - - - - - - - HH:MM:SS - - - - End the rendered file with the event immediately prior to the - one having a hard-start time of HH:MM:SS. - This option is mutually exclusive with the - option, above. - - - - - - - HH:MM:SS - - - - Render the log as if it had been started at a time of - HH:MM:SS. (Useful for ensuring that - dayparted carts play as expected). If not given, the current - system time will be used. - - - - - - - - - - - Be verbose. Among other things, this will cause - rdrender1 to print - a disposition for each log event encountered to standard error. + Specify the sample rate to use, in rate + samples per second. Default value is the sample rate configured + in System Settings in + rdadmin1. @@ -153,5 +296,15 @@ +Bugs + + rdrender1 makes no attempt to + interpret hard-start time values beyond allowing them to be + used to flag the start and end of rendering. (See the + and rendering + options). + + + diff --git a/utils/rdrender/mainloop.cpp b/utils/rdrender/mainloop.cpp index bd270291..693aff9d 100644 --- a/utils/rdrender/mainloop.cpp +++ b/utils/rdrender/mainloop.cpp @@ -18,6 +18,7 @@ // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. // +#include #include #include @@ -25,6 +26,7 @@ #include #include +#include #include #include #include @@ -38,7 +40,7 @@ int MainObject::MainLoop() QString warnings=""; // - // Open Endpoints + // Open Log // RDLog *log=new RDLog(render_logname); if(!log->exists()) { @@ -48,16 +50,53 @@ int MainObject::MainLoop() RDLogEvent *log_event=new RDLogEvent(RDLog::tableName(render_logname)); log_event->load(); + // + // Open Output File + // SF_INFO sf_info; + SNDFILE *sf_out; + FILE *f; + char tempdir[PATH_MAX]; memset(&sf_info,0,sizeof(sf_info)); sf_info.samplerate=render_system->sampleRate(); sf_info.channels=render_channels; - sf_info.format=SF_FORMAT_WAV|SF_FORMAT_PCM_16; - SNDFILE *sf_out=sf_open(render_output_filename,SFM_WRITE,&sf_info); - if(sf_out==NULL) { - fprintf(stderr,"rdrender: unable to open output file [%s]\n", - sf_strerror(sf_out)); - return 1; + if(render_settings.format()==RDSettings::Pcm16) { + sf_info.format=SF_FORMAT_WAV|SF_FORMAT_PCM_16; + } + else { + sf_info.format=SF_FORMAT_WAV|SF_FORMAT_PCM_24; + } + if(render_settings_modified) { + // + // 2nd pass will be needed, so just create a placeholder for now + // and then output to a temp file + // + Verbose("Pass 1 of 2"); + if((f=fopen(render_output_filename,"w"))==NULL) { + fprintf(stderr,"rdrender: unable to open output file [%s]\n", + strerror(errno)); + return 1; + } + fclose(f); + strncpy(tempdir,RDTempDir()+"/rdrenderXXXXXX",PATH_MAX); + render_temp_output_filename=QString(mkdtemp(tempdir))+"/log.wav"; + sf_out=sf_open(render_temp_output_filename,SFM_WRITE,&sf_info); + if(sf_out==NULL) { + fprintf(stderr,"rdrender: unable to open temporary file \"%s\" [%s]\n", + (const char *)render_temp_output_filename, + sf_strerror(sf_out)); + return 1; + } + Verbose("Using temporary file \""+render_temp_output_filename+"\"."); + } + else { + Verbose("Pass 1 of 1"); + sf_out=sf_open(render_output_filename,SFM_WRITE,&sf_info); + if(sf_out==NULL) { + fprintf(stderr,"rdrender: unable to open output file [%s]\n", + sf_strerror(sf_out)); + return 1; + } } // @@ -165,13 +204,28 @@ int MainObject::MainLoop() } } } + sf_close(sf_out); + + // + // Process 2nd Pass + // + if(render_settings_modified) { + QString err_msg; + bool ok=false; + Verbose("Pass 2 of 2"); + ok=ConvertAudio(render_temp_output_filename,render_output_filename, + &render_settings,&err_msg); + DeleteCutFile(render_temp_output_filename); + if(!ok) { + fprintf(stderr,"rdrender: unable to convert output [%s]\n", + (const char *)err_msg); + } + return 1; + } + fprintf(stderr,"%s",(const char *)warnings); fflush(stderr); - // - // Clean up - // - sf_close(sf_out); return 0; } diff --git a/utils/rdrender/rdrender.cpp b/utils/rdrender/rdrender.cpp index baeea92c..62a7a09f 100644 --- a/utils/rdrender/rdrender.cpp +++ b/utils/rdrender/rdrender.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -46,6 +47,17 @@ MainObject::MainObject(QObject *parent) render_last_line=-1; render_ignore_stops=false; + // + // Initialize Audio Settings + // + render_settings.setChannels(RDRENDER_DEFAULT_CHANNELS); + render_settings.setSampleRate(0); + render_settings.setFormat(RDRENDER_DEFAULT_FORMAT); + render_settings.setBitRate(RDRENDER_DEFAULT_BITRATE); + render_settings.setQuality(RDRENDER_DEFAULT_BITRATE); + render_settings.setNormalizationLevel(RDRENDER_DEFAULT_NORMALIZATION_LEVEL); + render_settings_modified=false; + // // Read Command Options // @@ -67,6 +79,15 @@ MainObject::MainObject(QObject *parent) render_verbose=true; cmd->setProcessed(i,true); } + if(cmd->key(i)=="--bitrate") { + render_settings.setBitRate(cmd->value(i).toUInt(&ok)); + if(!ok) { + fprintf(stderr,"rdrender: invalid --bitrate argument\n"); + exit(1); + } + render_settings_modified=true; + cmd->setProcessed(i,true); + } if(cmd->key(i)=="--channels") { render_channels=cmd->value(i).toUInt(&ok); if((!ok)||(render_channels>2)) { @@ -75,6 +96,44 @@ MainObject::MainObject(QObject *parent) } cmd->setProcessed(i,true); } + if(cmd->key(i)=="--format") { + QString format=cmd->value(i); + ok=false; + if(format.lower()=="flac") { + render_settings.setFormat(RDSettings::Flac); + render_settings_modified=true; + ok=true; + } + if(format.lower()=="mp2") { + render_settings.setFormat(RDSettings::MpegL2); + render_settings_modified=true; + ok=true; + } + if(format.lower()=="mp3") { + render_settings.setFormat(RDSettings::MpegL3); + render_settings_modified=true; + ok=true; + } + if(format.lower()=="pcm16") { + render_settings.setFormat(RDSettings::Pcm16); + ok=true; + } + if(format.lower()=="pcm24") { + render_settings.setFormat(RDSettings::Pcm24); + ok=true; + } + if(format.lower()=="vorbis") { + render_settings.setFormat(RDSettings::OggVorbis); + render_settings_modified=true; + ok=true; + } + if(!ok) { + fprintf(stderr,"rdrender: unknown --format \"%s\"\n", + (const char *)format); + exit(1); + } + cmd->setProcessed(i,true); + } if(cmd->key(i)=="--first-line") { render_first_line=cmd->value(i).toInt(&ok); if((!ok)|(render_first_line<0)) { @@ -111,6 +170,33 @@ MainObject::MainObject(QObject *parent) } cmd->setProcessed(i,true); } + if(cmd->key(i)=="--normalization-level") { + render_settings.setNormalizationLevel(cmd->value(i).toInt(&ok)); + if(!ok) { + fprintf(stderr,"rdrender: invalid --normalization-level argument\n"); + exit(1); + } + render_settings_modified=true; + cmd->setProcessed(i,true); + } + if(cmd->key(i)=="--quality") { + render_settings.setQuality(cmd->value(i).toUInt(&ok)); + if(!ok) { + fprintf(stderr,"rdrender: invalid --quality argument\n"); + exit(1); + } + render_settings_modified=true; + cmd->setProcessed(i,true); + } + if(cmd->key(i)=="--samplerate") { + render_settings.setSampleRate(cmd->value(i).toUInt(&ok)); + if(!ok) { + fprintf(stderr,"rdrender: invalid --samplerate argument\n"); + exit(1); + } + render_settings_modified=true; + cmd->setProcessed(i,true); + } if(cmd->key(i)=="--start-time") { render_start_time=QTime::fromString(cmd->value(i)); if(!render_start_time.isValid()) { @@ -126,9 +212,14 @@ MainObject::MainObject(QObject *parent) } if((render_last_line>=0)&&(render_first_line>=0)&& (render_last_linekey(cmd->keys()-2); render_output_filename=cmd->key(cmd->keys()-1); if(render_start_time.isNull()) { @@ -172,6 +263,9 @@ MainObject::MainObject(QObject *parent) // System Configuration // render_system=new RDSystem(); + if(render_settings.sampleRate()==0) { + render_settings.setSampleRate(render_system->sampleRate()); + } // // Station Configuration @@ -241,7 +335,12 @@ bool MainObject::GetCutFile(const QString &cutname,int start_pt,int end_pt, conv->setCartNumber(RDCut::cartNumber(cutname)); conv->setCutNumber(RDCut::cutNumber(cutname)); RDSettings s; - s.setFormat(RDSettings::Pcm16); + if(render_settings.format()==RDSettings::Pcm16) { + s.setFormat(RDSettings::Pcm16); + } + else { + s.setFormat(RDSettings::Pcm24); + } s.setSampleRate(render_system->sampleRate()); s.setChannels(render_channels); s.setNormalizationLevel(0); @@ -275,6 +374,23 @@ void MainObject::DeleteCutFile(const QString &dest_filename) const } +bool MainObject::ConvertAudio(const QString &srcfile,const QString &dstfile, + RDSettings *s,QString *err_msg) +{ + RDAudioConvert::ErrorCode err_code; + + RDAudioConvert *conv=new RDAudioConvert(render_station->name(),this); + conv->setSourceFile(srcfile); + conv->setDestinationFile(dstfile); + conv->setDestinationSettings(s); + err_code=conv->convert(); + *err_msg=RDAudioConvert::errorText(err_code); + delete conv; + + return err_code==RDAudioConvert::ErrorOk; +} + + int main(int argc,char *argv[]) { QApplication a(argc,argv,false); diff --git a/utils/rdrender/rdrender.h b/utils/rdrender/rdrender.h index 33fbc1e7..5577c9a3 100644 --- a/utils/rdrender/rdrender.h +++ b/utils/rdrender/rdrender.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,10 @@ #include "logline.h" #define RDRENDER_DEFAULT_CHANNELS 2 +#define RDRENDER_DEFAULT_FORMAT RDSettings::Pcm16 +#define RDRENDER_DEFAULT_BITRATE 256000 +#define RDRENDER_DEFAULT_QUALITY 3 +#define RDRENDER_DEFAULT_NORMALIZATION_LEVEL 0 #define RDRENDER_USAGE "[options] \n" class MainObject : public QObject @@ -59,9 +64,12 @@ class MainObject : public QObject bool GetCutFile(const QString &cutname,int start_pt,int end_pt, QString *dest_filename) const; void DeleteCutFile(const QString &dest_filename) const; + bool ConvertAudio(const QString &srcfile,const QString &dstfile, + RDSettings *s,QString *err_msg); bool render_verbose; QString render_logname; QString render_output_filename; + QString render_temp_output_filename; unsigned render_channels; QTime render_start_time; int render_first_line; @@ -69,6 +77,8 @@ class MainObject : public QObject QTime render_first_time; QTime render_last_time; bool render_ignore_stops; + RDSettings render_settings; + bool render_settings_modified; RDRipc *render_ripc; RDStation *render_station; RDSystem *render_system;