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

;Rivendell Config file for rlm_live365 module
;This specifies what Live365 address and account we should use for the now and
;next information.
;
;NOTES:
;You need to have curl installed (apt-get install curl)
;
;The Voice Asia 08/04/2013

[Live365-1]

;Username: Live 365 account user name (same one you use to login to the
;          broadcast console
Username=you_username

;Password: Live 365 password
Password=your_password

;Hostname: Live365 base URL for add_song.cgi, this should be the same for
;          each server unless Live365 changes things in future
Hostname=http://tools.live365.com/cgi-bin/add_song.cgi

;Version: Live 365 CGI version, currently this should be set to 2.  This
;         may change in future.
Version=2

;What logs to trigger now and next for
;Onair will only trigger if this hosts onair flag is set (very useful)
MasterLog=Yes
Aux1Log=No
Aux2Log=No

;You can have more than one Live365 account if you wish just increment the 
;number.
;[Live365-2]
;
;Username=;
;Password=t
;Hostname=http://tools.live365.com/cgi-bin/add_song.cgi
;Version=2
;MasterLog=Yes
;Aux1Log=No
;Aux2Log=No
/* 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
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 Icecast 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_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 Icecast1
   * 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_icecast2_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_icecast2_RLMFree(void *ptr)
{
  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 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?
  
  //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->artist into artist then URL encode
      strncpy(artist, now->rlm_artist, 256);
      rlm_live365_EncodeString(artist, 255);

      //Copy now->title into title then URL encode
      strncpy(title, now->rlm_title, 256);
      rlm_live365_EncodeString(title, 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
    }
  }
}

Attachment: rlm_live365.rlm
Description: Binary data

_______________________________________________
Rivendell-dev mailing list
[email protected]
http://lists.rivendellaudio.org/mailman/listinfo/rivendell-dev

Reply via email to