Quick update to the RLM module, added a check for duplicates so that
you don't resend track info by rearranging the play list (and triggering
a new now/next update). Also cleaned up the code a bit.
On 2013-04-09 17:02, Wayne Merricks wrote:
Hi all,
Just in case this is of interest to people I've made a Live 365 RLM
module for Rivendell. Its based on the icecast2 module but obviously
updates the URL Live365 uses instead via curl.
The only thing I'm not happy about is that Live365 make you specify
the username/password as part of the URL string which potentially
means plain text transmission. If anyones interested this is the
recommended URL:
http://tools.live365.com/cgi-bin/add_song.cgi?version=2&pass=mypassword&handle=myusername&title=#title#&artist=#artist#&album=#album#&seconds=#ss#&fileName=#basename#
The reason I made this is to get around the pesky DMCA violations and
station delistings you get if you don't update your play list
manually
while in relay/live mode.
I've attached the rlm c file, a sample conf file and a compiled rlm
but please note you'll probably have to recompile yourself depending
on your system setup.
If anyone wants any more info feel free to reply,
Wayne
/* rlm_live365.c
*
* (C) Copyright 2008 Fred Gleason <[email protected]>
*
* 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.
*
* This is a Rivendell Loadable Module. It uses Now&Next PAD data
* to update the metadata on a Live365 account specified in the
* configuration file pointed to by the plugin argument.
*
* This module requires the curl(1) network transfer tool, included with
* most Linux distros. It is also available at http://curl.haxx.se/.
*
* This is heavily based on the rlm_icecast2 module and slightly tweaked
* to send the required information to Live 365 instead.
*
* To compile this module, just do:
*
* gcc -shared -o rlm_live365.rlm rlm_live365.c
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <unistd.h>
#include <rlm/rlm.h>
//Global Vars
char rlm_live365_lastTitleUpdate[256];
int rlm_live365_serverCount;
char *rlm_live365_usernames;
char *rlm_live365_passwords;
char *rlm_live365_hostnames;
char *rlm_live365_formats;
int *rlm_live365_versions;
int *rlm_live365_masters;
int *rlm_live365_aux1s;
int *rlm_live365_aux2s;
/** Helper function to check if a string will fit in its destination */
int rlm_live365_BufferDiff(char *sString,int dOrigin,int dDiff,int dMaxSize)
{
int dOldSize,dNewSize;
int i;
/*
* Will it fit?
*/
dOldSize=strlen(sString);
if((dOldSize+dDiff)>=dMaxSize) {
return -1;
}
dNewSize=dOldSize+dDiff;
/*
* Adding characters
*/
if(dDiff>0) {
for(i=dOldSize;i>dOrigin;i--) {
sString[i+dDiff]=sString[i];
}
return dNewSize;
}
/*
* No Change
*/
if(dDiff==0) {
return dNewSize;
}
/*
* Deleting Characters
*/
if(dDiff<0) {
for(i=dOrigin;i<dOldSize;i++) {
sString[i]=sString[i-dDiff];
}
return dNewSize;
}
return -1;
}
//URL Encode string with a max size given by dMaxSize
int rlm_live365_EncodeString(char *sString,int dMaxSize)
{
int i; /* General Purpose Counter */
char sAccum[4]; /* General String Buffer */
i=0;//While String != 0 indicating end of string
while(sString[i]!=0) {
//if string != * - _ . and it is 0 - 9 or A - Z (a - z too)
if(((sString[i]!='*') && (sString[i]!='-') && (sString[i]!='_')
&& (sString[i]!='.')) && ((sString[i]<'0') || ((sString[i]>'9')
&& (sString[i]<'A')) || ((sString[i]>'Z') && (sString[i]<'a'))
|| (sString[i]>'z'))) {
//if we can't fit this new string inside its destination
if(rlm_live365_BufferDiff(sString,i,2,dMaxSize)<0) {
fprintf(stderr,"rlm_live365: BufferDiff() failed, maxsize: %d\n",
dMaxSize);
return -1;//quit and return in fail
}
//format the string for web
sprintf(sAccum,"%%%2x",sString[i]);
sString[i++]=sAccum[0];//sString[0] = first formatted char
sString[i++]=sAccum[1];//sString[1] = 2nd formatted char
sString[i]=sAccum[2];//sString[2] = final formatted char
}
if(sString[i]==' ') {//if sString[2] = ' '
sString[i]='+';//change it to +
}
i++;
if(i>=dMaxSize) {
fprintf(stderr,"rlm_live365: offset exceeded limit, maxsize: %d\n",
dMaxSize);//print to stderr buffer can't fit string
return -1;//return in error
}
}
return strlen(sString);//return the pointer
}
/** Get the Active status of the given log
* 0 = Inactive
* 1 = Active
* 2 = Active but only if On Air */
int rlm_live365_GetLogStatus(void *ptr,const char *arg,const char *section,
const char *logname)
{
const char *tag=RLMGetStringValue(ptr,arg,section,logname,"");
if(strcasecmp(tag,"yes")==0) {
return 1;
}
if(strcasecmp(tag,"on")==0) {
return 1;
}
if(strcasecmp(tag,"true")==0) {
return 1;
}
if(strcasecmp(tag,"no")==0) {
return 0;
}
if(strcasecmp(tag,"off")==0) {
return 0;
}
if(strcasecmp(tag,"false")==0) {
return 0;
}
if(strcasecmp(tag,"onair")==0) {
return 2;
}
return 0;
}
/** Called when the RLM starts, this function loads the Live365 details from the
* Conf file (kind of a constructor for this RLM) */
void rlm_live365_RLMStart(void *ptr,const char *arg)
{
//Set defaults
char username[256];
char section[256];
char errtext[256];
int i=1;
rlm_live365_serverCount=0;
//rlm_live365_lastTitleUpdate="";
rlm_live365_usernames=NULL;
rlm_live365_passwords=NULL;
rlm_live365_hostnames=NULL;
rlm_live365_versions=NULL;
rlm_live365_masters=NULL;
rlm_live365_aux1s=NULL;
rlm_live365_aux2s=NULL;
sprintf(section,"Live365-%d",i++);//section = Live365-1
/** RLMGetStringValue
* Function retrieves the line in the argument config file for this rlm
* ptr = ptr to riv control?
* arg = config file to read
* section = section of config file to look for, in this case Live365-1
* Username = field to get
* "" = Value if not found? */
//username = username from conf file padded to 255 in size with 0s
strncpy(username,RLMGetStringValue(ptr,arg,section,"Username",""),255);
username[255]=0;//Make sure the end of username = 0, could be other things if
//the user name was really long
if(strlen(username)==0) {//If length of username == 0
//Log to Rivendell log
RLMLog(ptr,LOG_WARNING,"rlm_live365: no live365 details specified");
return;//quit
}
while(strlen(username)>0) {//while we have a username we can use
//Reallocate memory for rlm_live365_usernames
//We're taking 256 bytes (not sure why we're squaring)
rlm_live365_usernames=realloc(rlm_live365_usernames,
(rlm_live365_serverCount+1)*256);
//Copy username into newly reallocated memory
strcpy(rlm_live365_usernames+256*rlm_live365_serverCount,username);
//As above for the password except we're reading the file again here too
rlm_live365_passwords=realloc(rlm_live365_passwords,
(rlm_live365_serverCount+1)*256);
strcpy(rlm_live365_passwords+256*rlm_live365_serverCount,
RLMGetStringValue(ptr,arg,section,"Password",""));
//As above for the hostname
rlm_live365_hostnames=realloc(rlm_live365_hostnames,
(rlm_live365_serverCount+1)*256);
strcpy(rlm_live365_hostnames+256*rlm_live365_serverCount,
RLMGetStringValue(ptr,arg,section,"Hostname",""));
//As above for the versions, int so no need for strcpy
rlm_live365_versions=realloc(rlm_live365_versions,
(rlm_live365_serverCount+1)*sizeof(int));
rlm_live365_versions[rlm_live365_serverCount]=
RLMGetIntegerValue(ptr,arg,section,"Version",0);
//As above for the Master Log
rlm_live365_masters=realloc(rlm_live365_masters,
(rlm_live365_serverCount+1)*sizeof(int));
//Call GetLogStatus for MasterLog (0 inactive, 1 for active, 2 for onair)
rlm_live365_masters[rlm_live365_serverCount]=
rlm_live365_GetLogStatus(ptr,arg,section,"MasterLog");
//As above for AUX 1
rlm_live365_aux1s=realloc(rlm_live365_aux1s,
(rlm_live365_serverCount+1)*sizeof(int));
rlm_live365_aux1s[rlm_live365_serverCount]=
rlm_live365_GetLogStatus(ptr,arg,section,"Aux1Log");
//As Above for AUX 2
rlm_live365_aux2s=realloc(rlm_live365_aux2s,
(rlm_live365_serverCount+1)*sizeof(int));
rlm_live365_aux2s[rlm_live365_serverCount]=
rlm_live365_GetLogStatus(ptr,arg,section,"Aux2Log");
//Print into errtext Live365 Details that we read
sprintf(errtext,"rlm_live365: configured Live365 for %s",
rlm_live365_usernames+256*rlm_live365_serverCount);
rlm_live365_serverCount++;//Increment devs (number of Live365 servers)
RLMLog(ptr,LOG_INFO,errtext);//Log the Live365 details
sprintf(section,"Live365-%d",i++);//Increment section to read details
//Get the next username
strncpy(username,RLMGetStringValue(ptr,arg,section,"Username",""),255);
username[255]=0;//Make sure it ends in 0
}
}
//Called to free memory when done all globals must be freed here
void rlm_live365_RLMFree(void *ptr)
{
free(rlm_live365_lastTitleUpdate);
free(rlm_live365_usernames);
free(rlm_live365_passwords);
free(rlm_live365_hostnames);
free(rlm_live365_versions);
free(rlm_live365_masters);
free(rlm_live365_aux1s);
free(rlm_live365_aux2s);
}
//Called when the RLM actually executes
void rlm_live365_RLMPadDataSent(void *ptr,const struct rlm_svc *svc,
const struct rlm_log *log,
const struct rlm_pad *now,
const struct rlm_pad *next)
{
int i;
int flag=0;
//int duplicateFlag=0;
int lengthInSeconds = 0;
char artist[256];
char title[256];
char album[256];
char username[256];
char password[256];
int version;
char url[1024];//URL
char msg[1500];//Error/Success message?
//Check for duplicate title and discard
strncpy(title, now->rlm_title, 256);
if(strcmp(title, rlm_live365_lastTitleUpdate) == 0){
//duplicateFlag=1;
snprintf(msg, 1500, "rlm_live365: Skipping duplicate title %s", title);
RLMLog(ptr,LOG_INFO,msg);
}else{
strncpy(rlm_live365_lastTitleUpdate, title, 256);
//snprintf(msg, 1500, "rlm_live365: No Duplicate %s", title);
//RLMLog(ptr,LOG_INFO,msg);
//Loop through all the Live365 servers we found
for(i=0;i<rlm_live365_serverCount;i++) {
switch(log->log_mach) {
case 0:
flag=rlm_live365_masters[i];
break;
case 1:
flag=rlm_live365_aux1s[i];
break;
case 2:
flag=rlm_live365_aux2s[i];
break;
}//Get the flag for the given log
//If we're active or onair then fire the update
if((flag==1)||((flag==2)&&(log->log_onair!=0))) {
//Copy now->title into title then URL encode
//strncpy(title, now->rlm_title, 256);//already copied earlier
rlm_live365_EncodeString(title, 255);
//Copy now->artist into artist then URL encode
strncpy(artist, now->rlm_artist, 256);
rlm_live365_EncodeString(artist, 255);
//Copy now->album into album then URL encode
strncpy(album, now->rlm_album, 256);
rlm_live365_EncodeString(album, 255);
//Length in seconds
lengthInSeconds = now->rlm_len / 1000; //Riv reports in ms
//Version
version = rlm_live365_versions[i];
//Username then URL encode
strncpy(username, rlm_live365_usernames+256*i, 256);
rlm_live365_EncodeString(username, 255);
//Password then URL encode
strncpy(password, rlm_live365_passwords+256*i, 256);
rlm_live365_EncodeString(password, 255);
//Format URL for Curl
/*E.g. http://tools.live365.com/cgi-bin/add_song.cgi?
version=2&pass=password&handle=username&
title=Brilliant+Song&artist=The+Best+Artist&
album=Good+Album&seconds=60&fileName=null */
snprintf(url, 1024,
"%s?version=%d&pass=%s&handle=%s&title=%s&artist=%s&album=%s&seconds=%d&fileName=null",
rlm_live365_hostnames+256*i, version, password, username,
title, artist, album, lengthInSeconds);
//If now -> title isn't empty
if(strlen(now->rlm_title)!=0) {
if(fork()==0) {//Spawn a process to run curl for the above url
//If execlp returns then an error occurred so log it
execlp("curl","curl","-o","/dev/null","-s",
url,(char *)NULL);
RLMLog(ptr,LOG_WARNING,"rlm_live365: unable to execute curl(1)");
exit(0);//Quit the RLM upon error
}
}
//Set msg to str ready for logging, max 1500
snprintf(msg,1500,"rlm_live365: sending pad update: \"%s\"",
now->rlm_title);
RLMLog(ptr,LOG_INFO,msg);//Log it
}
}
}
}
_______________________________________________
Rivendell-dev mailing list
[email protected]
http://lists.rivendellaudio.org/mailman/listinfo/rivendell-dev