From 687a8c6fc89f0b33784605bf7e2cbbbe250ed807 Mon Sep 17 00:00:00 2001 From: Fred Gleason Date: Wed, 27 Apr 2016 19:46:39 -0400 Subject: [PATCH] 2016-04-27 Brian McGlynn * Added a 'bitRate' field in the return for the AudioInfo web method. * Added an rdexport(1) utility in 'utils/rdexport/'. --- .gitignore | 1 + ChangeLog | 3 + configure.ac | 3 +- docs/docbook/Makefile.am | 3 + docs/docbook/rdexport.xml | 220 ++++++++++++++++++++++ docs/metadata_wildcards.txt | 2 +- lib/rdaudioinfo.cpp | 8 + lib/rdaudioinfo.h | 2 + rivendell.spec.in | 1 + utils/Makefile.am | 1 + utils/rdexport/Makefile.am | 48 +++++ utils/rdexport/rdexport.cpp | 352 ++++++++++++++++++++++++++++++++++++ utils/rdexport/rdexport.h | 66 +++++++ web/rdxport/audioinfo.cpp | 1 + 14 files changed, 709 insertions(+), 2 deletions(-) create mode 100644 docs/docbook/rdexport.xml create mode 100644 utils/rdexport/Makefile.am create mode 100644 utils/rdexport/rdexport.cpp create mode 100644 utils/rdexport/rdexport.h diff --git a/.gitignore b/.gitignore index 66ffb456..316b998a 100644 --- a/.gitignore +++ b/.gitignore @@ -98,6 +98,7 @@ utils/rddelete/rddelete utils/rdgpimon/rdgpimon utils/rdimport/rdimport utils/rdcollect/rdcollect +utils/rdexport/rdexport utils/rdmaint/rdmaint utils/rdgen/rdgen utils/rdrevert/rdrevert diff --git a/ChangeLog b/ChangeLog index 4b05be01..2344bdc2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -15101,3 +15101,6 @@ 2016-04-26 Brian McGlynn * Updated Cart Scheduler to favor weighted playback for cuts expiring first following the number of plays +2016-04-27 Brian McGlynn + * Added a 'bitRate' field in the return for the AudioInfo web method. + * Added an rdexport(1) utility in 'utils/rdexport/'. diff --git a/configure.ac b/configure.ac index d928786a..e52bf27e 100644 --- a/configure.ac +++ b/configure.ac @@ -458,9 +458,10 @@ AC_CONFIG_FILES([rivendell.spec \ utils/rddelete/Makefile \ utils/rddgimport/Makefile \ utils/rddiscimport/Makefile \ + utils/rdexport/Makefile \ utils/rdgen/Makefile \ - utils/rdhpiinfo/Makefile \ utils/rdgpimon/Makefile \ + utils/rdhpiinfo/Makefile \ utils/rdimport/Makefile \ utils/rdmaint/Makefile \ utils/rdmarkerset/Makefile \ diff --git a/docs/docbook/Makefile.am b/docs/docbook/Makefile.am index 0f64ec23..2433e8c8 100644 --- a/docs/docbook/Makefile.am +++ b/docs/docbook/Makefile.am @@ -34,6 +34,7 @@ all-local: cae.html\ cae.pdf\ rdrevert.8\ + rdexport.1\ rml.html\ rml.pdf\ web_api.html\ @@ -46,6 +47,8 @@ EXTRA_DIST = cae.html\ cae.xml\ rdrevert.8\ rdrevert.xml\ + rdexport.8\ + rdexport.xml\ rml.html\ rml.pdf\ rml.xml\ diff --git a/docs/docbook/rdexport.xml b/docs/docbook/rdexport.xml new file mode 100644 index 00000000..e330bf74 --- /dev/null +++ b/docs/docbook/rdexport.xml @@ -0,0 +1,220 @@ + + + + + rdexport + 1 + April 2016 + Linux Audio Manual + + + rdexport + Export audio from a Rivendell audio store + + + + + Fred + Gleason + fredg@paravelsystems.com + + Application Author + + + + + + + rdexport + OPTIONS + output-dir + + + + + Description + + rdexport1 can be used to export + audio from a Rivendell audio store. + + + + Options + + + + start-cart:end-cart + + + + Specify a range of cart numbers to be exported, from + start-cart to + end-cart (both inclusive). This option + may be given multiple times. + + + + + + + group-name + + + + Specify a group to be exported. This option may be given multiple + times. + + + + + + + pattern + + + + Specify a pattern to define how output files should be named. + Patterns consist of characters interspersed with zero or more + wildcard characters as follows: + + + + %a + + Artist + + + + %b + + Record Label + + + + + %c + + Client + + + + + %e + + Agency + + + + + %g + + Rivendell group name + + + + + %h + + Event length in milliseconds + + + + + %i + + Cut Description + + + + + %j + + Cut Number + + + + + %l + + Album + + + + + %m + + Composer + + + + + %n + + Cart Number + + + + + %o + + Outcue + + + + + %p + + Publisher + + + + + %r + + Conductor + + + + + %s + + Song ID + + + + + %t + + Title + + + + + %u + + User Defined + + + + + %y + + Release Year + + + + + + + + Default value is %n_%j. + + + + + + diff --git a/docs/metadata_wildcards.txt b/docs/metadata_wildcards.txt index 062df577..978417d6 100644 --- a/docs/metadata_wildcards.txt +++ b/docs/metadata_wildcards.txt @@ -16,7 +16,7 @@ Now Next Field %g %G The Rivendell group name %h %H Event length (in milliseconds) %i %I Cut Description - %j %J [Unassigned] + %j %J Cut Number %k %K [Unassigned] %l %L Album %m %M Composer diff --git a/lib/rdaudioinfo.cpp b/lib/rdaudioinfo.cpp index b7f1d163..d6f91429 100644 --- a/lib/rdaudioinfo.cpp +++ b/lib/rdaudioinfo.cpp @@ -58,6 +58,7 @@ RDAudioInfo::RDAudioInfo(RDStation *station,RDConfig *config, conv_format=RDWaveFile::Pcm16; conv_channels=0; conv_sample_rate=0; + conv_bit_rate=0; conv_frames=0; conv_length=0; } @@ -81,6 +82,12 @@ unsigned RDAudioInfo::sampleRate() const } +unsigned RDAudioInfo::bitRate() const +{ + return conv_bit_rate; +} + + unsigned RDAudioInfo::frames() const { return conv_frames; @@ -183,6 +190,7 @@ RDAudioInfo::ErrorCode RDAudioInfo::runInfo(const QString &username, conv_format=(RDWaveFile::Format)ParseInt("format",conv_xml); conv_channels=ParseInt("channels",conv_xml); conv_sample_rate=ParseInt("sampleRate",conv_xml); + conv_bit_rate=ParseInt("bitRate",conv_xml); conv_frames=ParseInt("frames",conv_xml); conv_length=ParseInt("length",conv_xml); diff --git a/lib/rdaudioinfo.h b/lib/rdaudioinfo.h index 1d94005b..a7306b57 100644 --- a/lib/rdaudioinfo.h +++ b/lib/rdaudioinfo.h @@ -40,6 +40,7 @@ class RDAudioInfo : public QObject RDWaveFile::Format format() const; unsigned channels() const; unsigned sampleRate() const; + unsigned bitRate() const; unsigned frames() const; unsigned length() const; void setCartNumber(unsigned cartnum); @@ -57,6 +58,7 @@ class RDAudioInfo : public QObject RDWaveFile::Format conv_format; unsigned conv_channels; unsigned conv_sample_rate; + unsigned conv_bit_rate; unsigned conv_frames; unsigned conv_length; QString conv_xml; diff --git a/rivendell.spec.in b/rivendell.spec.in index 43e6110e..87a58837 100644 --- a/rivendell.spec.in +++ b/rivendell.spec.in @@ -285,6 +285,7 @@ rm -rf $RPM_BUILD_ROOT @WIN32_PATH@ /etc/pam.d/rdalsaconfig-root /etc/security/console.apps/rdalsaconfig-root +%{_mandir}/man1/rdexport.1.gz %{_mandir}/man1/rdimport.1.gz %{_mandir}/man8/rdmarkerset.8.gz %{_mandir}/man8/rdrevert.8.gz diff --git a/utils/Makefile.am b/utils/Makefile.am index 5a57984a..93de2ec7 100644 --- a/utils/Makefile.am +++ b/utils/Makefile.am @@ -36,6 +36,7 @@ SUBDIRS = $(ALSACONFIG_RD_OPT)\ rdcollect\ rddelete\ rddiscimport\ + rdexport\ rdgen\ rdgpimon\ rdimport\ diff --git a/utils/rdexport/Makefile.am b/utils/rdexport/Makefile.am new file mode 100644 index 00000000..2e9dec31 --- /dev/null +++ b/utils/rdexport/Makefile.am @@ -0,0 +1,48 @@ +## automake.am +## +## Automake.am for rivendell/utils/rdexport +## +## (C) Copyright 2016 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. +## +## Use automake to process this into a Makefile.in + +AM_CPPFLAGS = -Wall -DPREFIX=\"$(prefix)\" -DQTDIR=\"@QT_DIR@\" @QT_CXXFLAGS@ -I$(top_srcdir)/lib +LIBS = @QT_LIBS@ -L$(top_srcdir)/lib +MOC = @QT_MOC@ + +# The dependency for qt's Meta Object Compiler (moc) +moc_%.cpp: %.h + $(MOC) $< -o $@ + +bin_PROGRAMS = rdexport + +dist_rdexport_SOURCES = rdexport.cpp rdexport.h + +nodist_rdexport_SOURCES = moc_rdexport.cpp + +rdexport_LDADD = @LIB_RDLIBS@ @LIBVORBIS@ + +CLEANFILES = *~\ + *.idb\ + *ilk\ + *.obj\ + *.pdb\ + *.qm\ + moc_* + +MAINTAINERCLEANFILES = *~\ + Makefile.in\ + moc_* diff --git a/utils/rdexport/rdexport.cpp b/utils/rdexport/rdexport.cpp new file mode 100644 index 00000000..8d4d8c80 --- /dev/null +++ b/utils/rdexport/rdexport.cpp @@ -0,0 +1,352 @@ +// rdexport.cpp +// +// A Batch Exporter for Rivendell. +// +// (C) Copyright 2016 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 + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rdexport.h" + +MainObject::MainObject(QObject *parent,const char *name) + :QObject(parent,name) +{ + export_metadata_pattern="%n_%j"; + + // + // Read Command Options + // + RDCmdSwitch *cmd= + new RDCmdSwitch(qApp->argc(),qApp->argv(),"rdimport",RDEXPORT_USAGE); + if(cmd->keys()<1) { + fprintf(stderr,"rdexport: you must specify an output directory\n"); + exit(256); + } + for(int i=0;i<(int)cmd->keys()-1;i++) { + if(cmd->key(i)=="--carts") { + bool ok=false; + bool valid=false; + QStringList f0=f0.split(":",cmd->value(i)); + if(f0.size()==2) { + int start=f0[0].toUInt(&valid); + if(valid&&(start>0)&&(valid<=RD_MAX_CART_NUMBER)) { + int end=f0[1].toUInt(&valid); + if(valid&&(start>0)&&(valid<=RD_MAX_CART_NUMBER)&&(end>=start)) { + export_start_carts.push_back(start); + export_end_carts.push_back(end); + ok=true; + } + } + } + if(!ok) { + fprintf(stderr,"rdexport: invalid --carts argument\n"); + exit(256); + } + cmd->setProcessed(i,true); + } + if(cmd->key(i)=="--group") { + export_groups.push_back(cmd->value(i)); + cmd->setProcessed(i,true); + } + if(cmd->key(i)=="--metadata-pattern") { + export_metadata_pattern=cmd->value(i); + cmd->setProcessed(i,true); + } + if(cmd->key(i)=="--verbose") { + export_verbose=true; + cmd->setProcessed(i,true); + } + if(!cmd->processed(i)) { + fprintf(stderr,"rdexport: unrecognized option\n"); + exit(256); + } + } + export_output_to=cmd->key(cmd->keys()-1); + + // + // Read Configuration + // + export_config=new RDConfig(); + export_config->load(); + + // + // Open Database + // + QSqlDatabase *db=QSqlDatabase::addDatabase(export_config->mysqlDriver()); + if(!db) { + fprintf(stderr,"rdexport: unable to initialize connection to database\n"); + exit(256); + } + db->setDatabaseName(export_config->mysqlDbname()); + db->setUserName(export_config->mysqlUsername()); + db->setPassword(export_config->mysqlPassword()); + db->setHostName(export_config->mysqlHostname()); + if(!db->open()) { + fprintf(stderr,"rdimport: unable to connect to database\n"); + db->removeDatabase(export_config->mysqlDbname()); + exit(256); + } + new RDDbHeartbeat(export_config->mysqlHeartbeatInterval(),this); + + // + // Validate Group List + // + std::vector bad_groups; + for(unsigned i=0;iexists()) { + bad_groups.push_back(export_groups[i]); + } + delete grp; + } + if(bad_groups.size()>0) { + QString str="no such group(s): "; + for(unsigned i=0;istationName()); + connect(export_ripc,SIGNAL(userChanged()),this,SLOT(userData())); + export_ripc-> + connectHost("localhost",RIPCD_TCP_PORT,export_config->password()); + + // + // Station Configuration + // + export_station=new RDStation(export_config->stationName()); + + // + // User + // + export_user=NULL; +} + + +void MainObject::userData() +{ + // + // Get User Context + // + disconnect(export_ripc,SIGNAL(userChanged()),this,SLOT(userData())); + if(export_user!=NULL) { + delete export_user; + } + export_user=new RDUser(export_ripc->user()); + + // + // Verify Permissions + // + if(!export_user->editAudio()) { + fprintf(stderr,"rdexport: user \"%s\" has no edit audio permission\n", + (const char *)export_user->name()); + exit(256); + } + + // + // Process Groups + // + for(unsigned i=0;inext()) { + ExportCart(q->value(0).toUInt()); + } + delete q; +} + + +void MainObject::ExportCart(unsigned cartnum) +{ + RDCart *cart=new RDCart(cartnum); + QString sql; + RDSqlQuery *q; + + if(cart->exists()&&(cart->type()==RDCart::Audio)) { + sql=QString().sprintf("select CUT_NAME from CUTS where CART_NUMBER=%u", + cartnum); + q=new RDSqlQuery(sql); + while(q->next()) { + RDCut *cut=new RDCut(q->value(0).toString()); + ExportCut(cart,cut); + } + delete q; + } + delete cart; +} + + +void MainObject::ExportCut(RDCart *cart,RDCut *cut) +{ + RDAudioExport *conv=new RDAudioExport(export_station,export_config,this); + RDAudioExport::ErrorCode export_err; + RDAudioConvert::ErrorCode conv_err; + RDAudioInfo::ErrorCode info_err; + + // + // Get Audio Parameters + // + RDAudioInfo *info=new RDAudioInfo(export_station,export_config); + info->setCartNumber(cart->number()); + info->setCutNumber(RDCut::cutNumber(cut->cutName())); + if((info_err=info->runInfo(export_user->name(),export_user->password()))!= + RDAudioInfo::ErrorOk) { + fprintf(stderr,"rdexport: error getting cut info [%s]\n", + (const char *)RDAudioInfo::errorText(info_err)); + exit(256); + } + RDSettings settings; + switch(info->format()) { + case RDWaveFile::Pcm16: + settings.setFormat(RDSettings::Pcm16); + break; + + case RDWaveFile::Pcm24: + settings.setFormat(RDSettings::Pcm24); + break; + + case RDWaveFile::MpegL2: + settings.setFormat(RDSettings::MpegL2); + break; + + default: + fprintf(stderr,"rdexport: unsupported source audio format\n"); + exit(256); + } + settings.setChannels(info->channels()); + settings.setSampleRate(info->sampleRate()); + settings.setBitRate(info->bitRate()); + + Verbose(QString("exporting cart/cut ")+ + QString().sprintf("%06u/%03d",RDCut::cartNumber(cut->cutName()), + RDCut::cutNumber(cut->cutName()))+" ["+cart->title()+"]"); + conv->setCartNumber(cart->number()); + conv->setCutNumber(RDCut::cutNumber(cut->cutName())); + conv->setDestinationSettings(&settings); + conv->setDestinationFile(ResolveOutputName(cart,cut, + RDSettings::defaultExtension(export_station->name(),settings.format()))); + conv->setEnableMetadata(true); + + if((export_err=conv->runExport(export_user->name(),export_user->password(), + &conv_err))!=RDAudioExport::ErrorOk) { + fprintf(stderr,"rdexport: exporter error [%s]\n", + (const char *)RDAudioExport::errorText(export_err,conv_err)); + exit(256); + } + + delete conv; + delete info; +} + + +QString MainObject::ResolveOutputName(RDCart *cart,RDCut *cut, + const QString &exten) +{ + QString name=export_metadata_pattern; + + name.replace("%a",cart->artist()); + name.replace("%b",cart->label()); + name.replace("%c",cart->client()); + name.replace("%e",cart->agency()); + name.replace("%g",cart->groupName()); + name.replace("%h",QString().sprintf("%d",cut->length())); + name.replace("%i",cut->description()); + name.replace("%j",QString().sprintf("%03d",RDCut::cutNumber(cut->cutName()))); + name.replace("%l",cart->album()); + name.replace("%m",cart->composer()); + name.replace("%n",QString().sprintf("%06u",cart->number())); + name.replace("%o",cut->outcue()); + name.replace("%p",cart->publisher()); + name.replace("%r",cart->conductor()); + name.replace("%s",cart->songId()); + name.replace("%t",cart->title()); + name.replace("%u",cart->userDefined()); + name.replace("%y",QString().sprintf("%d",cart->year())); + + QString ret=name; + int count=1; + while(QFile::exists(export_output_to+"/"+ret+"."+exten)) { + ret=name+QString().sprintf("[%d]",count++); + } + + return export_output_to+"/"+ret+"."+exten; +} + + +void MainObject::Verbose(const QString &msg) +{ + if(export_verbose) { + fprintf(stderr,"%s\n",(const char *)msg); + } +} + + +int main(int argc,char *argv[]) +{ + QApplication a(argc,argv,false); + new MainObject(NULL); + return a.exec(); +} diff --git a/utils/rdexport/rdexport.h b/utils/rdexport/rdexport.h new file mode 100644 index 00000000..819e4615 --- /dev/null +++ b/utils/rdexport/rdexport.h @@ -0,0 +1,66 @@ +// rdexport.h +// +// A Batch Exporter for Rivendell. +// +// (C) Copyright 2016 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 RDEXPORT_H +#define RDEXPORT_H + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#define RDEXPORT_USAGE "[options] \n" + +class MainObject : public QObject +{ + Q_OBJECT; + public: + MainObject(QObject *parent=0,const char *name=0); + + private slots: + void userData(); + + private: + void ExportGroup(const QString &groupname); + void ExportCart(unsigned cartnum); + void ExportCut(RDCart *cart,RDCut *cut); + QString ResolveOutputName(RDCart *cart,RDCut *cut,const QString &exten); + void Verbose(const QString &msg); + std::vector export_start_carts; + std::vector export_end_carts; + std::vector export_groups; + QString export_metadata_pattern; + QString export_output_to; + RDConfig *export_config; + RDRipc *export_ripc; + RDStation *export_station; + RDUser *export_user; + bool export_verbose; +}; + + +#endif // RDEXPORT_H diff --git a/web/rdxport/audioinfo.cpp b/web/rdxport/audioinfo.cpp index 0749d825..96732bab 100644 --- a/web/rdxport/audioinfo.cpp +++ b/web/rdxport/audioinfo.cpp @@ -106,6 +106,7 @@ void Xport::AudioInfo() printf(" %d\n",format); printf(" %d\n",wave->getChannels()); printf(" %d\n",wave->getSamplesPerSec()); + printf(" %d\n",wave->getHeadBitRate()); printf(" %u\n",wave->getSampleLength()); printf(" %u\n",wave->getExtTimeLength()); printf("\n");