diff --git a/ChangeLog b/ChangeLog index 90e0d9bb..d804cefa 100644 --- a/ChangeLog +++ b/ChangeLog @@ -15984,3 +15984,7 @@ '--quality=' and '--samplerate=' switches to rdrender(1). 2017-08-29 Fred Gleason * Added '--to-cart=' and '--to-file=' switches to rdrender(1). +2017-08-30 Fred Gleason + * Added an 'RDRenderer' class in 'lib/rdrenderer.cpp' and + 'lib/rdrenderer.h'. + * Refactored rdrender(1) to use the 'RDRenderer' class. diff --git a/lib/Makefile.am b/lib/Makefile.am index f6c8a6f8..ea9ca0d7 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -195,6 +195,7 @@ dist_librd_la_SOURCES = dbversion.h\ rdpushbutton.cpp rdpushbutton.h\ rdrecording.cpp rdrecording.h\ rdrehash.cpp rdrehash.h\ + rdrenderer.cpp rdrenderer.h\ rdreplicator.cpp rdreplicator.h\ rdreport.cpp rdreport.h\ rdringbuffer.cpp rdringbuffer.h\ @@ -312,6 +313,7 @@ nodist_librd_la_SOURCES = moc_rdadd_cart.cpp\ moc_rdsegmeter.cpp\ moc_rdsimpleplayer.cpp\ moc_rdrehash.cpp\ + moc_rdrenderer.cpp\ moc_rdslider.cpp\ moc_rdsocket.cpp\ moc_rdsound_panel.cpp\ diff --git a/lib/lib.pro b/lib/lib.pro index 42e0b5a4..acb17cec 100644 --- a/lib/lib.pro +++ b/lib/lib.pro @@ -163,6 +163,7 @@ x11 { SOURCES += rdplay_deck.cpp SOURCES += rdrecording.cpp SOURCES += rdrehash.cpp + SOURCES += rdrenderer.cpp SOURCES += rdsimpleplayer.cpp SOURCES += rdsound_panel.cpp SOURCES += rdstatus.cpp @@ -300,6 +301,7 @@ x11 { HEADERS += rdplay_deck.h HEADERS += rdrecording.h HEADERS += rdrehash.h + HEADERS += rdrenderer.h HEADERS += rdsimpleplayer.h HEADERS += rdsound_panel.h HEADERS += rdstatus.h diff --git a/lib/librd_cs.ts b/lib/librd_cs.ts index b623933a..7997f687 100644 --- a/lib/librd_cs.ts +++ b/lib/librd_cs.ts @@ -2148,6 +2148,89 @@ Zkuste to, prosím, znovu! + + RDRenderer + + no such cart + + + + no such cut + + + + Using temporary file + + + + Pass 2 of 2 + + + + Writing output file + + + + Pass 1 of 1 + + + + Pass 1 of 2 + + + + Importing cart + + + + first-time event not found + + + + last-time event not found + + + + STOP + + + + FAIL + + + + NO AUDIO AVAILABLE + + + + SKIP + + + + --- end of log --- + + + + unable to open output file + + + + log render halted at line + + + + due to STOP + + + + at line + + + + failed to play (NO AUDIO AVAILABLE) + + + RDReport diff --git a/lib/librd_de.ts b/lib/librd_de.ts index cbee76ab..16f30df0 100644 --- a/lib/librd_de.ts +++ b/lib/librd_de.ts @@ -2139,6 +2139,89 @@ bitte erneut versuchen! + + RDRenderer + + no such cart + + + + no such cut + + + + Using temporary file + + + + Pass 2 of 2 + + + + Writing output file + + + + Pass 1 of 1 + + + + Pass 1 of 2 + + + + Importing cart + + + + first-time event not found + + + + last-time event not found + + + + STOP + STOP + + + FAIL + + + + NO AUDIO AVAILABLE + + + + SKIP + + + + --- end of log --- + + + + unable to open output file + + + + log render halted at line + + + + due to STOP + + + + at line + + + + failed to play (NO AUDIO AVAILABLE) + + + RDReport diff --git a/lib/librd_es.ts b/lib/librd_es.ts index 9f958152..73a0e374 100644 --- a/lib/librd_es.ts +++ b/lib/librd_es.ts @@ -2141,6 +2141,89 @@ please try again! + + RDRenderer + + no such cart + + + + no such cut + + + + Using temporary file + + + + Pass 2 of 2 + + + + Writing output file + + + + Pass 1 of 1 + + + + Pass 1 of 2 + + + + Importing cart + + + + first-time event not found + + + + last-time event not found + + + + STOP + DETENER + + + FAIL + + + + NO AUDIO AVAILABLE + + + + SKIP + + + + --- end of log --- + + + + unable to open output file + + + + log render halted at line + + + + due to STOP + + + + at line + + + + failed to play (NO AUDIO AVAILABLE) + + + RDReport diff --git a/lib/librd_fr.ts b/lib/librd_fr.ts index f7339be8..194bf7fa 100644 --- a/lib/librd_fr.ts +++ b/lib/librd_fr.ts @@ -1800,6 +1800,89 @@ please try again! + + RDRenderer + + no such cart + + + + no such cut + + + + Using temporary file + + + + Pass 2 of 2 + + + + Writing output file + + + + Pass 1 of 1 + + + + Pass 1 of 2 + + + + Importing cart + + + + first-time event not found + + + + last-time event not found + + + + STOP + + + + FAIL + + + + NO AUDIO AVAILABLE + + + + SKIP + + + + --- end of log --- + + + + unable to open output file + + + + log render halted at line + + + + due to STOP + + + + at line + + + + failed to play (NO AUDIO AVAILABLE) + + + RDSoundPanel diff --git a/lib/librd_nb.ts b/lib/librd_nb.ts index d5877f33..8b9195a9 100644 --- a/lib/librd_nb.ts +++ b/lib/librd_nb.ts @@ -2089,6 +2089,89 @@ prøv ein gong til! + + RDRenderer + + no such cart + + + + no such cut + + + + Using temporary file + + + + Pass 2 of 2 + + + + Writing output file + + + + Pass 1 of 1 + + + + Pass 1 of 2 + + + + Importing cart + + + + first-time event not found + + + + last-time event not found + + + + STOP + STOPP + + + FAIL + + + + NO AUDIO AVAILABLE + + + + SKIP + + + + --- end of log --- + + + + unable to open output file + + + + log render halted at line + + + + due to STOP + + + + at line + + + + failed to play (NO AUDIO AVAILABLE) + + + RDReport diff --git a/lib/librd_nn.ts b/lib/librd_nn.ts index d5877f33..8b9195a9 100644 --- a/lib/librd_nn.ts +++ b/lib/librd_nn.ts @@ -2089,6 +2089,89 @@ prøv ein gong til! + + RDRenderer + + no such cart + + + + no such cut + + + + Using temporary file + + + + Pass 2 of 2 + + + + Writing output file + + + + Pass 1 of 1 + + + + Pass 1 of 2 + + + + Importing cart + + + + first-time event not found + + + + last-time event not found + + + + STOP + STOPP + + + FAIL + + + + NO AUDIO AVAILABLE + + + + SKIP + + + + --- end of log --- + + + + unable to open output file + + + + log render halted at line + + + + due to STOP + + + + at line + + + + failed to play (NO AUDIO AVAILABLE) + + + RDReport diff --git a/lib/librd_pt_BR.ts b/lib/librd_pt_BR.ts index 847b6a0c..9886958e 100644 --- a/lib/librd_pt_BR.ts +++ b/lib/librd_pt_BR.ts @@ -2088,6 +2088,89 @@ por favor, tente novamente! + + RDRenderer + + no such cart + + + + no such cut + + + + Using temporary file + + + + Pass 2 of 2 + + + + Writing output file + + + + Pass 1 of 1 + + + + Pass 1 of 2 + + + + Importing cart + + + + first-time event not found + + + + last-time event not found + + + + STOP + PARE + + + FAIL + + + + NO AUDIO AVAILABLE + + + + SKIP + + + + --- end of log --- + + + + unable to open output file + + + + log render halted at line + + + + due to STOP + + + + at line + + + + failed to play (NO AUDIO AVAILABLE) + + + RDReport diff --git a/lib/rdrenderer.cpp b/lib/rdrenderer.cpp new file mode 100644 index 00000000..4ab96a1f --- /dev/null +++ b/lib/rdrenderer.cpp @@ -0,0 +1,607 @@ +// rdrenderer.cpp +// +// Render a Rivendell log to a single audio object. +// +// (C) Copyright 2017 Fred Gleason +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. +// +// 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; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#include +#include + +#include "rdaudioconvert.h" +#include "rdaudioexport.h" +#include "rdaudioimport.h" +#include "rdcart.h" +#include "rdconf.h" +#include "rdcut.h" + +#include "rdrenderer.h" + +__RDRenderLogLine::__RDRenderLogLine(RDLogLine *ll,RDUser *user, + RDStation *station,RDSystem *sys, + RDConfig *config,unsigned chans) + : RDLogLine(*ll) +{ + ll_cart=NULL; + ll_cut=NULL; + ll_handle=NULL; + ll_user=user; + ll_station=station; + ll_system=sys; + ll_config=config; + ll_channels=chans; + ll_ramp_level=0.0; + ll_ramp_rate=0.0; +} + + +RDCart *__RDRenderLogLine::cart() const +{ + return ll_cart; +} + + +RDCut *__RDRenderLogLine::cut() const +{ + return ll_cut; +} + + +SNDFILE *__RDRenderLogLine::handle() const +{ + return ll_handle; +} + + +double __RDRenderLogLine::rampLevel() const +{ + return ll_ramp_level; +} + + +void __RDRenderLogLine::setRampLevel(double lvl) +{ + ll_ramp_level=lvl; +} + + +double __RDRenderLogLine::rampRate() const +{ + return ll_ramp_rate; +} + + +void __RDRenderLogLine::setRampRate(double lvl) +{ + ll_ramp_rate=lvl; +} + + +void __RDRenderLogLine::setRamp(RDLogLine::TransType next_trans) +{ + if((next_trans==RDLogLine::Segue)&& + (ll_cut->segueStartPoint()>=0)&&(ll_cut->segueEndPoint()>=0)) { + ll_ramp_rate=((double)RD_FADE_DEPTH)/ + ((double)FramesFromMsec(ll_cut->segueEndPoint()-ll_cut->segueStartPoint())); + } +} + + +bool __RDRenderLogLine::open(const QTime &time) +{ + QString cutname; + SF_INFO sf_info; + + if(type()==RDLogLine::Cart) { + ll_cart=new RDCart(cartNumber()); + if(ll_cart->exists()&&(ll_cart->type()==RDCart::Audio)) { + if(ll_cart->selectCut(&cutname,time)) { + ll_cut=new RDCut(cutname); + QString filename; + if(GetCutFile(cutname,ll_cut->startPoint(),ll_cut->endPoint(), + &filename)) { + ll_handle=sf_open(filename,SFM_READ,&sf_info); + if(ll_handle!=NULL) { + DeleteCutFile(filename); + return true; + } + } + } + } + } + return false; +} + + +void __RDRenderLogLine::close() +{ + sf_close(ll_handle); + ll_handle=NULL; +} + + +QString __RDRenderLogLine::summary() const +{ + QString ret=QString().sprintf("unknown event [type: %d]",type()); + switch(type()) { + case RDLogLine::Cart: + ret=QString().sprintf("cart %06u [",cartNumber())+title()+"]"; + break; + + case RDLogLine::Marker: + ret="marker ["+markerComment()+"]"; + break; + + case RDLogLine::Macro: + ret="macro cart ["+title()+"]"; + break; + + case RDLogLine::Chain: + ret="chain-to ["+markerLabel()+"]"; + break; + + case RDLogLine::Track: + ret="track marker ["+markerComment()+"]"; + break; + + case RDLogLine::MusicLink: + ret="music link"; + break; + + case RDLogLine::TrafficLink: + ret="traffic link"; + break; + + case RDLogLine::OpenBracket: + case RDLogLine::CloseBracket: + case RDLogLine::UnknownType: + break; + } + return ret; +} + + +bool __RDRenderLogLine::GetCutFile(const QString &cutname,int start_pt, + int end_pt,QString *dest_filename) const +{ + bool ret=false; + RDAudioConvert::ErrorCode conv_err; + RDAudioExport::ErrorCode export_err; + char tempdir[PATH_MAX]; + + strncpy(tempdir,RDTempDir()+"/rdrenderXXXXXX",PATH_MAX); + *dest_filename=QString(mkdtemp(tempdir))+"/"+cutname+".wav"; + RDAudioExport *conv=new RDAudioExport(ll_station,ll_config); + conv->setDestinationFile(*dest_filename); + conv->setCartNumber(RDCut::cartNumber(cutname)); + conv->setCutNumber(RDCut::cutNumber(cutname)); + RDSettings s; + s.setFormat(RDSettings::Pcm16); + s.setSampleRate(ll_system->sampleRate()); + s.setChannels(ll_channels); + s.setNormalizationLevel(0); + conv->setDestinationSettings(&s); + conv->setRange(start_pt,end_pt); + conv->setEnableMetadata(false); + switch(export_err=conv->runExport(ll_user->name(), + ll_user->password(),&conv_err)) { + case RDAudioExport::ErrorOk: + ret=true; + break; + + default: + ret=false; + printf("export err %d [%s]\n",export_err, + (const char *)RDAudioExport::errorText(export_err,conv_err)); + break; + } + + delete conv; + return ret; +} + + +void __RDRenderLogLine::DeleteCutFile(const QString &dest_filename) const +{ + unlink(dest_filename); + QStringList f0=f0.split("/",dest_filename); + f0.erase(f0.fromLast()); + rmdir("/"+f0.join("/")); +} + + +uint64_t __RDRenderLogLine::FramesFromMsec(uint64_t msec) +{ + return msec*ll_system->sampleRate()/1000; +} + + + + +RDRenderer::RDRenderer(RDUser *user,RDStation *station,RDSystem *system, + RDConfig *config,QObject *parent) + : QObject(parent) +{ + render_user=user; + render_station=station; + render_system=system; + render_config=config; +} + + +RDRenderer::~RDRenderer() +{ +} + + +bool RDRenderer::renderToFile(const QString &outfile,RDLogEvent *log, + unsigned chans,RDSettings *s, + const QTime &start_time,bool ignore_stops, + QString *err_msg,int first_line,int last_line, + const QTime &first_time,const QTime &last_time) +{ + QString temp_output_filename; + char tempdir[PATH_MAX]; + bool ok=false; + FILE *f=NULL; + + // + // Verify Destination + // + if((f=fopen(outfile,"w"))==NULL) { + *err_msg=tr("unable to open output file")+" ["+strerror(errno)+"]"; + return false; + } + fclose(f); + + if(((s->format()!=RDSettings::Pcm16)&&(s->format()!=RDSettings::Pcm24))|| + (s->normalizationLevel()!=0)) { + ProgressMessage("Pass 1 of 2"); + // + // Get Temporary File + // + strncpy(tempdir,RDTempDir()+"/rdrenderXXXXXX",PATH_MAX); + temp_output_filename=QString(mkdtemp(tempdir))+"/log.wav"; + ProgressMessage(tr("Using temporary file")+" \""+temp_output_filename+"\"."); + + // + // Render It + // + if(!Render(temp_output_filename,log,chans,s,start_time,ignore_stops,err_msg, + first_line,last_line,first_time,last_time)) { + return false; + } + + // + // Convert It + // + ProgressMessage(tr("Pass 2 of 2")); + ProgressMessage(tr("Writing output file")); + ok=ConvertAudio(temp_output_filename,outfile,s,err_msg); + DeleteTempFile(temp_output_filename); + if(!ok) { + return false; + } + } + else { + ProgressMessage(tr("Pass 1 of 1")); + return Render(outfile,log,chans,s,start_time,ignore_stops,err_msg, + first_line,last_line,first_time,last_time); + } + return true; +} + + +bool RDRenderer::renderToCart(unsigned cartnum,int cutnum,RDLogEvent *log, + unsigned chans,RDSettings *s, + const QTime &start_time,bool ignore_stops, + QString *err_msg,int first_line,int last_line, + const QTime &first_time,const QTime &last_time) +{ + QString temp_output_filename; + char tempdir[PATH_MAX]; + bool ok=false; + + ProgressMessage(tr("Pass 1 of 2")); + // + // Verify Destination + // + if(!RDCart::exists(cartnum)) { + *err_msg=tr("no such cart"); + return false; + } + if(!RDCut::exists(cartnum,cutnum)) { + *err_msg=tr("no such cut"); + return false; + } + + // + // Get Temporary File + // + strncpy(tempdir,RDTempDir()+"/rdrenderXXXXXX",PATH_MAX); + temp_output_filename=QString(mkdtemp(tempdir))+"/log.wav"; + ProgressMessage(tr("Using temporary file")+" \""+temp_output_filename+"\"."); + + // + // Render It + // + if(!Render(temp_output_filename,log,chans,s,start_time,ignore_stops,err_msg, + first_line,last_line,first_time,last_time)) { + return false; + } + + // + // Convert It + // + ProgressMessage(tr("Pass 2 of 2")); + ProgressMessage(tr("Importing cart")); + ok=ImportCart(temp_output_filename,cartnum,cutnum,err_msg); + DeleteTempFile(temp_output_filename); + if(!ok) { + return false; + } + + return true; +} + + +QStringList RDRenderer::warnings() const +{ + return render_warnings; +} + + +bool RDRenderer::Render(const QString &outfile,RDLogEvent *log,unsigned chans, + RDSettings *s,const QTime &start_time,bool ignore_stops, + QString *err_msg,int first_line,int last_line, + const QTime &first_time,const QTime &last_time) +{ + float *pcm=NULL; + QTime current_time=start_time; + QString temp_output_filename; + + render_warnings.clear(); + + // + // Open Output File + // + SF_INFO sf_info; + SNDFILE *sf_out; + + memset(&sf_info,0,sizeof(sf_info)); + sf_info.samplerate=render_system->sampleRate(); + sf_info.channels=chans; + if(s->format()==RDSettings::Pcm16) { + sf_info.format=SF_FORMAT_WAV|SF_FORMAT_PCM_16; + } + else { + sf_info.format=SF_FORMAT_WAV|SF_FORMAT_PCM_24; + } + sf_out=sf_open(outfile,SFM_WRITE,&sf_info); + if(sf_out==NULL) { + fprintf(stderr,"rdrender: unable to open output file [%s]\n", + sf_strerror(sf_out)); + return 1; + } + + // + // Initialize the log + // + std::vector<__RDRenderLogLine *> lls; + for(int i=0;isize();i++) { + lls.push_back(new __RDRenderLogLine(log->logLine(i),render_user, + render_station,render_system, + render_config,chans)); + if(ignore_stops&&(lls.back()->transType()==RDLogLine::Stop)) { + lls.back()->setTransType(RDLogLine::Play); + } + if((!first_time.isNull())&& + (lls.back()->timeType()==RDLogLine::Hard)&& + (first_line==-1)&& + (lls.back()->startTime(RDLogLine::Imported)==first_time)) { + first_line=i; + } + if((!last_time.isNull())&& + (lls.back()->timeType()==RDLogLine::Hard)&& + (last_line==-1)&& + (lls.back()->startTime(RDLogLine::Imported)==last_time)) { + last_line=i; + } + } + if((!first_time.isNull())&&(first_line==-1)) { + *err_msg+=tr("first-time event not found"); + } + if((!last_time.isNull())&&(last_line==-1)) { + if(!err_msg->isEmpty()) { + *err_msg+=", "; + } + *err_msg+=tr("last-time event not found"); + } + if(!err_msg->isEmpty()) { + return false; + } + lls.push_back(new __RDRenderLogLine(new RDLogLine(),render_user, + render_station,render_system, + render_config,chans)); + lls.back()->setTransType(RDLogLine::Play); + if((!first_time.isNull())&&(first_line==-1)) { + first_line=log->size(); + } + + // + // Iterate through it + // + for(unsigned i=0;i(int)i))) { + if(lls.at(i)->transType()==RDLogLine::Stop) { + ProgressMessage(current_time,i,tr("STOP")+" ",lls.at(i)->summary()); + render_warnings. + push_back(tr("log render halted at line")+QString().sprintf(" %d ",i)+ + tr("due to STOP")); + break; + } + if(lls.at(i)->open(current_time)) { + ProgressMessage(current_time,i, + RDLogLine::transText(lls.at(i)->transType()), + QString().sprintf(" cart %06u [",lls.at(i)->cartNumber())+ + lls.at(i)->title()+"]"); + sf_count_t frames=0; + if((lls.at(i+1)->transType()==RDLogLine::Segue)&& + (lls.at(i)->cut()->segueStartPoint()>=0)) { + frames=FramesFromMsec(lls.at(i)->cut()->segueStartPoint()- + lls.at(i)->cut()->startPoint()); + current_time= + current_time.addMSecs(lls.at(i)->cut()->segueStartPoint()- + lls.at(i)->cut()->startPoint()); + } + else { + frames=FramesFromMsec(lls.at(i)->cut()->endPoint()- + lls.at(i)->cut()->startPoint()); + current_time=current_time.addMSecs(lls.at(i)->cut()->endPoint()- + lls.at(i)->cut()->startPoint()); + } + pcm=new float[frames*chans]; + memset(pcm,0,frames*chans); + + for(unsigned j=0;jsetRamp(lls.at(i+1)->transType()); + } + else { + if(i<(lls.size()-1)) { + if(lls.at(i)->type()==RDLogLine::Cart) { + ProgressMessage(current_time,i,tr("FAIL"),lls.at(i)->summary()+ + " ("+tr("NO AUDIO AVAILABLE")+")"); + render_warnings. + push_back(lls.at(i)->summary()+tr("at line")+ + QString().sprintf(" %d ",i)+ + tr("failed to play (NO AUDIO AVAILABLE)")); + } + else { + ProgressMessage(current_time,i,tr("SKIP"),lls.at(i)->summary()); + } + } + else { + ProgressMessage(current_time,lls.size()-1, + tr("STOP"),tr("--- end of log ---")); + } + } + } + } + sf_close(sf_out); + + return true; +} + + +void RDRenderer::Sum(float *pcm_out,__RDRenderLogLine *ll,sf_count_t frames, + unsigned chans) +{ + if(ll->handle()!=NULL) { + float *pcm=new float[frames*chans]; + + memset(pcm,0,frames*chans); + sf_count_t n=sf_readf_float(ll->handle(),pcm,frames); + for(sf_count_t i=0;irampRate()+ll->rampLevel())/2000.0); + for(sf_count_t j=0;jsetRampLevel((double)n*ll->rampRate()+ll->rampLevel()); + if(nclose(); + } + delete pcm; + } +} + + +bool RDRenderer::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; +} + + +bool RDRenderer::ImportCart(const QString &srcfile,unsigned cartnum,int cutnum, + QString *err_msg) +{ + RDAudioImport::ErrorCode err_import_code; + RDAudioConvert::ErrorCode err_conv_code; + RDSettings settings; + + settings.setNormalizationLevel(0); + + RDAudioImport *conv=new RDAudioImport(render_station,render_config,this); + conv->setCartNumber(cartnum); + conv->setCutNumber(cutnum); + conv->setSourceFile(srcfile); + conv->setUseMetadata(false); + conv->setDestinationSettings(&settings); + err_import_code= + conv->runImport(render_user->name(),render_user->password(),&err_conv_code); + *err_msg=RDAudioImport::errorText(err_import_code,err_conv_code); + delete conv; + return err_import_code==RDAudioImport::ErrorOk; +} + + +void RDRenderer::DeleteTempFile(const QString &filename) const +{ + unlink(filename); + QStringList f0=f0.split("/",filename); + f0.erase(f0.fromLast()); + rmdir("/"+f0.join("/")); +} + + +uint64_t RDRenderer::FramesFromMsec(uint64_t msec) const +{ + return msec*render_system->sampleRate()/1000; +} + + +void RDRenderer::ProgressMessage(const QString &msg) +{ + emit progressMessageSent(msg); +} + + +void RDRenderer::ProgressMessage(const QTime &time,int line, + const QString &trans,const QString &msg) +{ + QString str=QString().sprintf("%04d : ",line)+ + time.toString("hh:mm:ss")+" : "+ + QString().sprintf("%-5s",(const char *)trans)+msg; + emit progressMessageSent(str); +} diff --git a/lib/rdrenderer.h b/lib/rdrenderer.h new file mode 100644 index 00000000..f4aa7742 --- /dev/null +++ b/lib/rdrenderer.h @@ -0,0 +1,122 @@ +// rdrenderer.h +// +// Render a Rivendell log to a single audio object. +// +// (C) Copyright 2017 Fred Gleason +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. +// +// 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; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// + +#ifndef RDRENDERER_H +#define RDRENDERER_H + +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +class __RDRenderLogLine : public RDLogLine +{ + public: + __RDRenderLogLine(RDLogLine *ll,RDUser *user,RDStation *station,RDSystem *sys, + RDConfig *config,unsigned chans); + RDCart *cart() const; + RDCut *cut() const; + SNDFILE *handle() const; + double rampLevel() const; + void setRampLevel(double lvl); + double rampRate() const; + void setRampRate(double lvl); + void setRamp(RDLogLine::TransType next_trans); + bool open(const QTime &time); + void close(); + QString summary() const; + + private: + bool GetCutFile(const QString &cutname,int start_pt,int end_pt, + QString *dest_filename) const; + void DeleteCutFile(const QString &dest_filename) const; + uint64_t FramesFromMsec(uint64_t msec); + RDCart *ll_cart; + RDCut *ll_cut; + SNDFILE *ll_handle; + RDLogLine *ll_logline; + RDUser *ll_user; + RDStation *ll_station; + RDSystem *ll_system; + RDConfig *ll_config; + unsigned ll_channels; + double ll_ramp_level; + double ll_ramp_rate; +}; + + + + +class RDRenderer : public QObject +{ + Q_OBJECT; + public: + RDRenderer(RDUser *user,RDStation *station,RDSystem *system,RDConfig *config, + QObject *parent=0); + ~RDRenderer(); + bool renderToFile(const QString &outfile,RDLogEvent *log,unsigned chans, + RDSettings *s,const QTime &start_time,bool ignore_stops, + QString *err_msg,int first_line,int last_line, + const QTime &first_time=QTime(), + const QTime &last_time=QTime()); + bool renderToCart(unsigned cartnum,int cutnum,RDLogEvent *log,unsigned chans, + RDSettings *s,const QTime &start_time,bool ignore_stops, + QString *err_msg,int first_line,int last_line, + const QTime &first_time=QTime(), + const QTime &last_time=QTime()); + QStringList warnings() const; + + signals: + void progressMessageSent(const QString &msg); + + private: + bool Render(const QString &outfile,RDLogEvent *log,unsigned chans, + RDSettings *s,const QTime &start_time,bool ignore_stops, + QString *err_msg,int first_line,int last_line, + const QTime &first_time,const QTime &last_time); + void Sum(float *pcm_out,__RDRenderLogLine *ll,sf_count_t frames, + unsigned chans); + bool ConvertAudio(const QString &srcfile,const QString &dstfile, + RDSettings *s,QString *err_msg); + bool ImportCart(const QString &srcfile,unsigned cartnum,int cutnum, + QString *err_msg); + uint64_t FramesFromMsec(uint64_t msec) const; + void DeleteTempFile(const QString &filename) const; + void ProgressMessage(const QString &msg); + void ProgressMessage(const QTime &time,int line,const QString &trans, + const QString &msg); + RDUser *render_user; + RDStation *render_station; + RDSystem *render_system; + RDConfig *render_config; + QStringList render_warnings; +}; + + +#endif // RDRENDERER_H diff --git a/utils/rdrender/Makefile.am b/utils/rdrender/Makefile.am index d5a795f6..57f18185 100644 --- a/utils/rdrender/Makefile.am +++ b/utils/rdrender/Makefile.am @@ -29,9 +29,7 @@ moc_%.cpp: %.h bin_PROGRAMS = rdrender -dist_rdrender_SOURCES = logline.cpp logline.h\ - mainloop.cpp\ - rdrender.cpp rdrender.h +dist_rdrender_SOURCES = rdrender.cpp rdrender.h nodist_rdrender_SOURCES = moc_rdrender.cpp diff --git a/utils/rdrender/logline.cpp b/utils/rdrender/logline.cpp deleted file mode 100644 index 6355403f..00000000 --- a/utils/rdrender/logline.cpp +++ /dev/null @@ -1,222 +0,0 @@ -// logline.cpp -// -// Container class for Rivendell Log Line. -// -// (C) Copyright 2017 Fred Gleason -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License version 2 as -// published by the Free Software Foundation. -// -// 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; if not, write to the Free Software -// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -// - -#include "logline.h" - -#include -#include -#include -#include - -LogLine::LogLine(RDLogLine *ll,RDUser *user,RDStation *station,RDSystem *sys, - RDConfig *config,unsigned chans) - : RDLogLine(*ll) -{ - ll_cart=NULL; - ll_cut=NULL; - ll_handle=NULL; - ll_user=user; - ll_station=station; - ll_system=sys; - ll_config=config; - ll_channels=chans; - ll_ramp_level=0.0; - ll_ramp_rate=0.0; -} - - -RDCart *LogLine::cart() const -{ - return ll_cart; -} - - -RDCut *LogLine::cut() const -{ - return ll_cut; -} - - -SNDFILE *LogLine::handle() const -{ - return ll_handle; -} - - -double LogLine::rampLevel() const -{ - return ll_ramp_level; -} - - -void LogLine::setRampLevel(double lvl) -{ - ll_ramp_level=lvl; -} - - -double LogLine::rampRate() const -{ - return ll_ramp_rate; -} - - -void LogLine::setRampRate(double lvl) -{ - ll_ramp_rate=lvl; -} - - -void LogLine::setRamp(RDLogLine::TransType next_trans) -{ - if((next_trans==RDLogLine::Segue)&& - (ll_cut->segueStartPoint()>=0)&&(ll_cut->segueEndPoint()>=0)) { - ll_ramp_rate=((double)RD_FADE_DEPTH)/((double)FramesFromMsec(ll_cut->segueEndPoint()-ll_cut->segueStartPoint())); - } -} - - -bool LogLine::open(const QTime &time) -{ - QString cutname; - SF_INFO sf_info; - - if(type()==RDLogLine::Cart) { - ll_cart=new RDCart(cartNumber()); - if(ll_cart->exists()&&(ll_cart->type()==RDCart::Audio)) { - if(ll_cart->selectCut(&cutname,time)) { - ll_cut=new RDCut(cutname); - QString filename; - if(GetCutFile(cutname,ll_cut->startPoint(),ll_cut->endPoint(), - &filename)) { - ll_handle=sf_open(filename,SFM_READ,&sf_info); - if(ll_handle!=NULL) { - DeleteCutFile(filename); - return true; - } - } - } - } - } - return false; -} - - -void LogLine::close() -{ - sf_close(ll_handle); - ll_handle=NULL; -} - - -QString LogLine::summary() const -{ - QString ret=QString().sprintf("unknown event [type: %d]",type()); - switch(type()) { - case RDLogLine::Cart: - ret=QString().sprintf("cart %06u [",cartNumber())+title()+"]"; - break; - - case RDLogLine::Marker: - ret="marker ["+markerComment()+"]"; - break; - - case RDLogLine::Macro: - ret="macro cart ["+title()+"]"; - break; - - case RDLogLine::Chain: - ret="chain-to ["+markerLabel()+"]"; - break; - - case RDLogLine::Track: - ret="track marker ["+markerComment()+"]"; - break; - - case RDLogLine::MusicLink: - ret="music link"; - break; - - case RDLogLine::TrafficLink: - ret="traffic link"; - break; - - case RDLogLine::OpenBracket: - case RDLogLine::CloseBracket: - case RDLogLine::UnknownType: - break; - } - return ret; -} - - -bool LogLine::GetCutFile(const QString &cutname,int start_pt,int end_pt, - QString *dest_filename) const -{ - bool ret=false; - RDAudioConvert::ErrorCode conv_err; - RDAudioExport::ErrorCode export_err; - char tempdir[PATH_MAX]; - - strncpy(tempdir,RDTempDir()+"/rdrenderXXXXXX",PATH_MAX); - *dest_filename=QString(mkdtemp(tempdir))+"/"+cutname+".wav"; - RDAudioExport *conv=new RDAudioExport(ll_station,ll_config); - conv->setDestinationFile(*dest_filename); - conv->setCartNumber(RDCut::cartNumber(cutname)); - conv->setCutNumber(RDCut::cutNumber(cutname)); - RDSettings s; - s.setFormat(RDSettings::Pcm16); - s.setSampleRate(ll_system->sampleRate()); - s.setChannels(ll_channels); - s.setNormalizationLevel(0); - conv->setDestinationSettings(&s); - conv->setRange(start_pt,end_pt); - conv->setEnableMetadata(false); - switch(export_err=conv->runExport(ll_user->name(), - ll_user->password(),&conv_err)) { - case RDAudioExport::ErrorOk: - ret=true; - break; - - default: - ret=false; - printf("export err %d [%s]\n",export_err, - (const char *)RDAudioExport::errorText(export_err,conv_err)); - break; - } - - delete conv; - return ret; -} - - -void LogLine::DeleteCutFile(const QString &dest_filename) const -{ - unlink(dest_filename); - QStringList f0=f0.split("/",dest_filename); - f0.erase(f0.fromLast()); - rmdir("/"+f0.join("/")); -} - - -uint64_t LogLine::FramesFromMsec(uint64_t msec) -{ - return msec*ll_system->sampleRate()/1000; -} diff --git a/utils/rdrender/logline.h b/utils/rdrender/logline.h deleted file mode 100644 index 610aa836..00000000 --- a/utils/rdrender/logline.h +++ /dev/null @@ -1,71 +0,0 @@ -// logline.h -// -// Container class for Rivendell Log Line. -// -// (C) Copyright 2017 Fred Gleason -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License version 2 as -// published by the Free Software Foundation. -// -// 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; if not, write to the Free Software -// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -// - -#ifndef LOGLINE_H -#define LOGLINE_H - -#include - -#include - -#include -#include -#include -#include -#include -#include - -class LogLine : public RDLogLine -{ - public: - LogLine(RDLogLine *ll,RDUser *user,RDStation *station,RDSystem *sys, - RDConfig *config,unsigned chans); - RDCart *cart() const; - RDCut *cut() const; - SNDFILE *handle() const; - double rampLevel() const; - void setRampLevel(double lvl); - double rampRate() const; - void setRampRate(double lvl); - void setRamp(RDLogLine::TransType next_trans); - bool open(const QTime &time); - void close(); - QString summary() const; - - private: - bool GetCutFile(const QString &cutname,int start_pt,int end_pt, - QString *dest_filename) const; - void DeleteCutFile(const QString &dest_filename) const; - uint64_t FramesFromMsec(uint64_t msec); - RDCart *ll_cart; - RDCut *ll_cut; - SNDFILE *ll_handle; - RDLogLine *ll_logline; - RDUser *ll_user; - RDStation *ll_station; - RDSystem *ll_system; - RDConfig *ll_config; - unsigned ll_channels; - double ll_ramp_level; - double ll_ramp_rate; -}; - - -#endif // LOGLINE_H diff --git a/utils/rdrender/rdrender.cpp b/utils/rdrender/rdrender.cpp index 08477dc9..295c5c7a 100644 --- a/utils/rdrender/rdrender.cpp +++ b/utils/rdrender/rdrender.cpp @@ -36,6 +36,8 @@ #include #include #include +#include +#include #include #include "rdrender.h" @@ -60,7 +62,6 @@ MainObject::MainObject(QObject *parent) 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 @@ -84,7 +85,6 @@ MainObject::MainObject(QObject *parent) fprintf(stderr,"rdrender: invalid --bitrate argument\n"); exit(1); } - render_settings_modified=true; cmd->setProcessed(i,true); } if(cmd->key(i)=="--channels") { @@ -100,17 +100,14 @@ MainObject::MainObject(QObject *parent) 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") { @@ -123,7 +120,6 @@ MainObject::MainObject(QObject *parent) } if(format.lower()=="vorbis") { render_settings.setFormat(RDSettings::OggVorbis); - render_settings_modified=true; ok=true; } if(!ok) { @@ -175,7 +171,6 @@ MainObject::MainObject(QObject *parent) fprintf(stderr,"rdrender: invalid --normalization-level argument\n"); exit(1); } - render_settings_modified=true; cmd->setProcessed(i,true); } if(cmd->key(i)=="--quality") { @@ -184,7 +179,6 @@ MainObject::MainObject(QObject *parent) fprintf(stderr,"rdrender: invalid --quality argument\n"); exit(1); } - render_settings_modified=true; cmd->setProcessed(i,true); } if(cmd->key(i)=="--samplerate") { @@ -193,7 +187,6 @@ MainObject::MainObject(QObject *parent) fprintf(stderr,"rdrender: invalid --samplerate argument\n"); exit(1); } - render_settings_modified=true; cmd->setProcessed(i,true); } if(cmd->key(i)=="--start-time") { @@ -290,15 +283,6 @@ MainObject::MainObject(QObject *parent) if(render_settings.sampleRate()==0) { render_settings.setSampleRate(render_system->sampleRate()); } - if((render_cart_number>0)&&(!RDCart::exists(render_cart_number))) { - fprintf(stderr,"rdrender: no such cart\n"); - exit(1); - } - if((render_cut_number>0)&& - (!RDCut::exists(render_cart_number,render_cut_number))) { - fprintf(stderr,"rdrender: no such cut\n"); - exit(1); - } // // Station Configuration @@ -323,17 +307,54 @@ void MainObject::userData() } render_user=new RDUser(render_ripc->user()); - exit(MainLoop()); + // + // Open Log + // + RDLog *log=new RDLog(render_logname); + if(!log->exists()) { + fprintf(stderr,"rdrender: no such log\n"); + exit(1); + } + RDLogEvent *log_event=new RDLogEvent(RDLog::tableName(render_logname)); + log_event->load(); + + // + // Render It + // + QString err_msg; + RDRenderer *r=new RDRenderer(render_user,render_station,render_system, + render_config,this); + connect(r,SIGNAL(progressMessageSent(const QString &)), + this,SLOT(printProgressMessage(const QString &))); + if(render_to_file.isEmpty()) { + if(!r->renderToCart(render_cart_number,render_cut_number,log_event, + render_channels,&render_settings,render_start_time, + render_ignore_stops,&err_msg,render_first_line, + render_last_line,render_first_time,render_last_time)) { + fprintf(stderr,"rdrender: %s\n",(const char *)err_msg); + exit(1); + } + } + else { + if(!r->renderToFile(render_to_file,log_event,render_channels, + &render_settings,render_start_time,render_ignore_stops, + &err_msg,render_first_line,render_last_line, + render_first_time,render_last_time)) { + fprintf(stderr,"rdrender: %s\n",(const char *)err_msg); + exit(1); + } + } + QStringList warnings=r->warnings(); + for(unsigned i=0;isampleRate()/1000; -} - - -void MainObject::Verbose(const QString &msg) +void MainObject::printProgressMessage(const QString &msg) { if(render_verbose) { fprintf(stderr,"%s\n",(const char *)msg); @@ -341,112 +362,6 @@ void MainObject::Verbose(const QString &msg) } -void MainObject::Verbose(const QTime &time,int line,const QString &trans, - const QString &msg) -{ - if(render_verbose) { - fprintf(stderr,"%s\n", - (const char *)(QString().sprintf("%04d : ",line)+ - time.toString("hh:mm:ss")+" : "+ - QString().sprintf("%-5s",(const char *)trans)+msg)); - } -} - - -bool MainObject::GetCutFile(const QString &cutname,int start_pt,int end_pt, - QString *dest_filename) const -{ - bool ret=false; - RDAudioConvert::ErrorCode conv_err; - RDAudioExport::ErrorCode export_err; - char tempdir[PATH_MAX]; - - strncpy(tempdir,RDTempDir()+"/rdrenderXXXXXX",PATH_MAX); - *dest_filename=QString(mkdtemp(tempdir))+"/"+cutname+".wav"; - RDAudioExport *conv=new RDAudioExport(render_station,render_config); - conv->setDestinationFile(*dest_filename); - conv->setCartNumber(RDCut::cartNumber(cutname)); - conv->setCutNumber(RDCut::cutNumber(cutname)); - RDSettings s; - 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); - conv->setDestinationSettings(&s); - conv->setRange(start_pt,end_pt); - conv->setEnableMetadata(false); - switch(export_err=conv->runExport(render_user->name(), - render_user->password(),&conv_err)) { - case RDAudioExport::ErrorOk: - ret=true; - break; - - default: - ret=false; - printf("export err %d [%s]\n",export_err, - (const char *)RDAudioExport::errorText(export_err,conv_err)); - break; - } - - delete conv; - return ret; -} - - -void MainObject::DeleteCutFile(const QString &dest_filename) const -{ - unlink(dest_filename); - QStringList f0=f0.split("/",dest_filename); - f0.erase(f0.fromLast()); - rmdir("/"+f0.join("/")); -} - - -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; -} - - -bool MainObject::ImportCart(const QString &srcfile,unsigned cartnum,int cutnum, - QString *err_msg) -{ - RDAudioImport::ErrorCode err_import_code; - RDAudioConvert::ErrorCode err_conv_code; - RDSettings settings; - - settings.setNormalizationLevel(0); - - RDAudioImport *conv=new RDAudioImport(render_station,render_config,this); - conv->setCartNumber(cartnum); - conv->setCutNumber(cutnum); - conv->setSourceFile(srcfile); - conv->setUseMetadata(false); - conv->setDestinationSettings(&settings); - err_import_code= - conv->runImport(render_user->name(),render_user->password(),&err_conv_code); - *err_msg=RDAudioImport::errorText(err_import_code,err_conv_code); - delete conv; - return err_import_code==RDAudioImport::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 eed703bd..f12aed46 100644 --- a/utils/rdrender/rdrender.h +++ b/utils/rdrender/rdrender.h @@ -36,14 +36,12 @@ #include #include -#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" +#define RDRENDER_USAGE "[options] \n" class MainObject : public QObject { @@ -53,21 +51,9 @@ class MainObject : public QObject private slots: void userData(); + void printProgressMessage(const QString &msg); private: - int MainLoop(); - void Sum(float *pcm_out,LogLine *ll,sf_count_t frames); - uint64_t FramesFromMsec(uint64_t msec); - void Verbose(const QString &msg); - void Verbose(const QTime &time,int line,const QString &trans, - const QString &msg); - 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 ImportCart(const QString &srcfile,unsigned cartnum,int cutnum, - QString *err_msg); bool render_verbose; QString render_logname; QString render_to_file; @@ -82,7 +68,6 @@ class MainObject : public QObject QTime render_last_time; bool render_ignore_stops; RDSettings render_settings; - bool render_settings_modified; RDRipc *render_ripc; RDStation *render_station; RDSystem *render_system;