Ingredients
minidlnadicecast2
ezstream
update-ezstream
ice2chrome
Description
minidlnad is a program to serve media files via DLNA protocolsicecast2 is the actual server that clients connect to to stream music
ezstream is the application which supplies media files to the icecast2 server
update-ezstream is an application which forces ezstream to re-read its source media files
ice2chrome is an application which pushes the icecast2 stream to a chromecast receiver
Method
Minidlnad Configuration
A modified version of minidlna can be found at www.vizier.uk. This version supports configurable layouts.
[config]
unknown=Unknown
searchdepth=4
strippath=/disk/media/Media
[replace]
genre=language courses=audiobook
genre=podcast=audiobook
genre=speech=audiobook
genre=blues-rock=blues
genre=books + spoken=audiobook
genre=international=pop-rock
genre=pop/rock=pop-rock
artist=Jean Michel Jarre=Jean-Michel Jarre
artist=r -e-m=Rem
artist=r-e-m=Rem
[rule]
comment=Ignore Index Tracks for Audiobooks etc
chainset=none
mediatype=audio
or=genre=vocal,genre=radio,genre=audiobook
and=comment=index
[rule]
chainset=audiobook
mediatype=audio
or=genre=audiobook
[rule]
chainset=radio
mediatype=audio
or=genre=radio
[rule]
chainset=legacyradio
mediatype=audio
or=genre=vocal
[rule]
chainset=musicindex
mediatype=audio
and=comment=index
[rule]
chainset=music
mediatype=audio
[rule]
chainset=video
mediatype=video
[rule]
chainset=image
mediatype=image
[rule]
chainset=catchall
[catchall]
chain=/Catchall/$TITLE
[image]
chain=/Folder/$PATH/$FILENAME
chain=/Photo/Location/$LOCNSEW/ - All Photos - /$TITLE
chain=/Photo/Location/$LOCNSEW/$YEAR/$TITLE
chain=/Photo/Date/$DECADE/ - All Photos - /$TITLE
chain=/Photo/Date/$DECADE/$YEAR/$TITLE
chain=/Photo/ - All Photos - /$TITLE
[video]
chain=/Folder/$PATH/$FILENAME
chain=/Video/Date/$DECADE/ - All Videos - /$FILEBASE
chain=/Video/Date/$DECADE/$YEAR/$FILEBASE
chain=/Video/ - All Videos - /$FILEBASE
[music]
chain=/Folder/$PATH/$FILENAME
chain=/Music/Track/ - All Tracks - /$TITLE
chain=/Music/Track/$ABCTITLE/$TITLE
chain=/Music/Album/ - All Albums - /$ALBUM/$TRACKNUM. $TITLE
chain=/Music/Album/$ABCALBUM/$ALBUM/$TRACKNUM. $TITLE
chain=/Music/Artist/ - All Artists - /$ARTIST/By Album/$SALBUM - $TRACKNUM. $TITLE
chain=/Music/Artist/ - All Artists - /$ARTIST/Shuffle/$RND. $TITLE
chain=/Music/Artist/$ABCARTIST/$ARTIST/By Album/$SALBUM - $TRACKNUM. $TITLE
chain=/Music/Artist/$ABCARTIST/$ARTIST/Shuffle/$RND. $TITLE ($SALBUM)
chain=/Music/Date/$DECADE/$YEAR/$TITLE
chain=/Music/Date/$DECADE/ - All Tracks - /$TITLE
chain=/Genre/$GENRE/Track/ - All Tracks - /$TITLE
chain=/Genre/$GENRE/Track/$ABCTITLE/$TITLE
chain=/Genre/$GENRE/Album/ - All Albums - /$ALBUM/$TRACKNUM. $TITLE
chain=/Genre/$GENRE/Album/$ABCALBUM/$ALBUM/$TRACKNUM. $TITLE
chain=/Genre/$GENRE/Artist/ - All Artists - /$ARTIST/By Album/$SALBUM - $TRACKNUM. $TITLE
chain=/Genre/$GENRE/Artist/ - All Artists - /$ARTIST/Shuffle/$RND. $TITLE
chain=/Genre/$GENRE/Artist/$ABCARTIST/$ARTIST/By Album/$SALBUM - $TRACKNUM. $TITLE
chain=/Genre/$GENRE/Artist/$ABCARTIST/$ARTIST/Shuffle/$RND. $TITLE ($SALBUM)
chain=/Genre/$GENRE/Date/$DECADE/$YEAR/$TITLE
chain=/Genre/$GENRE/Date/$DECADE/ - All Tracks - /$TITLE
chain=/Search/Music/$SEARCHALBUMARTIST/$TITLE
chain=/Search/Music/$SEARCHCOMPOSER/$TITLE
chain=/Search/Music/$SEARCHALBUM/$TITLE
chain=/Search/Music/$SEARCHTITLE/$TITLE
[musicindex]
chain=/Folder/$PATH/$FILENAME
chain=/Music/Album/ - All Albums - /$ALBUM/$TRACKNUM. $TITLE
chain=/Music/Album/$ABCALBUM/$ALBUM/$TRACKNUM. $TITLE
chain=/Genre/$GENRE/Album/ - All Albums - /$ALBUM/$TRACKNUM. $TITLE
chain=/Genre/$GENRE/Album/$ABCALBUM/$ALBUM/$TRACKNUM. $TITLE
[audiobook]
chain=/Folder/$PATH/$FILENAME
chain=/Audiobook/Author/ - All Authors - /$ARTIST/$ALBUM/$0TRACKNUM. $TITLE
chain=/Audiobook/Author/$ABCARTIST/$ALBUM/$0TRACKNUM. $TITLE
chain=/Audiobook/Book/ - All Titles - /$ALBUM/$0TRACKNUM. $TITLE
chain=/Audiobook/Book/$ABCALBUM/$ALBUM/$0TRACKNUM. $TITLE
chain=/Search/Audiobook Book Title/$SEARCHALBUM/$ALBUM/$0TRACKNUM. $TITLE
chain=/Search/Audiobook Author/$SEARCHARTIST/$ARTIST/$ALBUM/$0TRACKNUM. $TITLE
chain=/Search/Audiobook Author/$SEARCHCOMPOSER/$0TRACKNUM. $TITLE
chain=/Search/Audiobook Chapter/$SEARCHALBUM/$ALBUM/$0TRACKNUM. $TITLE
[radio]
chain=/Folder/$PATH/$FILENAME
chain=/Radio/ - All Programmes - /$ALBUMARTIST/$ALBUM/$0TRACKNUM. $TITLE
chain=/Radio/$ABCALBUMARTIST/$ALBUMARTIST/ - All Series - /$0TRACKNUM. $TITLE
chain=/Radio/$ABCALBUMARTIST/$ALBUMARTIST/$ALBUM/$0TRACKNUM. $TITLE
chain=/Search/Radio Programme/$SEARCHALBUMARTIST/$ALBUMARTIST/$0TRACKNUM. $TITLE
chain=/Search/Radio Episode/$SEARCHTITLE/$0TRACKNUM. $TITLE
Icecast Configuration
Install standard icecast2, and make the following modifications:
/etc/icecast2/icecast.xml
<authentication>
<source-password>PASS1234</source-password>
<relay-password>PASS1234</relay-password>
<admin-user>admin</admin-user>
<admin-password>ADMINPASS</admin-password>
</authentication>
<hostname>SERVERNAME</hostname>
<listen-socket>
<port>8000</port>
<shoutcast-mount>/channel1</shoutcast-mount>
<shoutcast-mount>/channel2</shoutcast-mount>
<shoutcast-mount>/channel3</shoutcast-mount>
<shoutcast-mount>/channel4</shoutcast-mount>
<shoutcast-mount>/channel5</shoutcast-mount>
<shoutcast-mount>/channel6</shoutcast-mount>
<shoutcast-mount>/channel7</shoutcast-mount>
<shoutcast-mount>/channel8</shoutcast-mount>
<shoutcast-mount>/channel9</shoutcast-mount>
</listen-socket>
Data File Area
Create an area for the media files and configuration. For this example, it is assumed that these files are installed on a mounted disk in /disk/media
Create the following folders:
bin/
conf.dlna/
ezstream/bin
ezstream/cfg
ezstream/channel
Media/
EZStream Configuration Management
Create a configuration file for each stream you wish to have:/disk/media/ezstream/cfg/N.cfg
<ezstream>
<url>http://localhost:8000/channelN</url>
<sourcepassword>PASS1234</sourcepassword>
<format>MP3</format>
<filename>/disk/media/ezstream/channel/N.m3u</filename>
<stream_once>0</stream_once>
<metadata_format>@s@</metadata_format>
<svrinfoname>Media Server</svrinfoname>
<svrinfourl>http://www.openserverproject.com</svrinfourl>
<svrinfogenre>Cantopop</svrinfogenre>
<svrinfodescription></svrinfodescription>
<svrinfobitrate>256</svrinfobitrate>
<svrinfochannels>2</svrinfochannels>
<svrinfosamplerate>44100</svrinfosamplerate>
<svrinfopublic>0</svrinfopublic>
</ezstream>
/disk/media/ezstream/channel/N.m3u
/disk/media/Media/full/path/to/mp3/file1.mp3Create a program to update the ezstream configuration based on configuration files in /disk/media/ezstream/cfg, and set its setuid/setgid bits such that other users can re-initialise the stream (most importantly, the web server user).
/disk/media/Media/full/path/to/mp3/file2.mp3
/disk/media/ezstream/bin/update-ezstream (.c)
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <linux/limits.h>
#include <libgen.h>
#include <signal.h>
#include <string.h>
int getprocesspid(char *procname, char *filter) ;
int main(int argc, char *argv[])
{
int pid ;
if (argc!=2 || argv[1][0]<'1' || argv[1][0]>'9') {
printf("%s 1-9\n", argv[0]) ;
exit(1) ;
}
// Find PID of running ezstream app
char cfg[16] ;
snprintf(cfg, sizeof(cfg), "/%c.xml", argv[1][0]) ;
pid = getprocesspid("ezstream", cfg) ;
// Change UID for ezstream user
printf("Launched with uid: %d, ", getuid()) ;
setreuid(geteuid(),geteuid()) ;
printf("running with uid: %d\n", getuid()) ;
// Send HUP signal, or launch server
if (pid>0) {
kill(pid, SIGHUP) ;
return 0 ;
} else {
if (fork()==0) {
char *thispath = dirname(argv[0]) ;
char path[PATH_MAX+1] ;
char *newargs[3] ;
snprintf(path, sizeof(path), "%s/../cfg/%c.xml",
thispath, argv[1][0]) ;
newargs[0] = "/usr/local/bin/ezstream" ;
newargs[1] = "-c" ;
newargs[2] = path ;
newargs[3] = NULL ;
execv(newargs[0], newargs) ;
printf("ERROR: Invalid program: %s %s %s\n",
newargs[0], newargs[1], newargs[2]) ;
return 1 ;
}
close(0) ;
close(1) ;
close(2) ;
return 0 ;
}
}
int getprocesspid(char *procname, char *filter)
{
int pid=-1 ;
char *args[5] ;
args[0]="/bin/ps" ;
args[1]="-fC" ;
args[2]=procname ;
args[3]=NULL ;
int pipefd[2] ;
pipe(pipefd) ;
if (fork() == 0) {
close(pipefd[0]) ;
dup2(pipefd[1],1) ;
close(pipefd[1]) ;
char *args[5] ;
args[0]="/bin/ps" ;
args[1]="-fC" ;
args[2]=procname ;
args[3]=NULL ;
execv(args[0], args) ;
fprintf(stderr, "ERROR: Invalid program: %s %s %s\n",
args[0], args[1], args[2]) ;
exit(1) ;
} else {
char line[1024] ;
int l=0 ;
char ch ;
int r ;
close(pipefd[1]) ;
do {
if (r=read(pipefd[0], &ch, 1)) {
line[l++]=ch ;
line[l]='\0' ;
if (ch=='\n' || ch=='\r') {
printf("line=%s\n", line) ;
if (strstr(line, filter) != NULL) {
sscanf(&line[9], "%d", &pid) ;
}
l=0 ;
}
}
} while (pid<0 && r==1 && l<sizeof(line)-1) ;
printf("\n pid=%d\n", pid) ;
return pid ;
}
}
Boot Configuration
Add a script to initialise the stream sources:/disk/media/bin/init-stream-sources.sh
#!/bin/sh
/disk/media/ezstream/bin/update-ezstream 1
/disk/media/ezstream/bin/update-ezstream 2
/disk/media/ezstream/bin/update-ezstream 3
/disk/media/ezstream/bin/update-ezstream 4
/disk/media/ezstream/bin/update-ezstream 5
/disk/media/ezstream/bin/update-ezstream 6
/disk/media/ezstream/bin/update-ezstream 7
/disk/media/ezstream/bin/update-ezstream 8
/disk/media/ezstream/bin/update-ezstream 9
Make the following modifications to ensure the dlna media server and ezstream/shoutcast server starts up:
/etc/rc.local
/usr/local/sbin/minidlnad -f /disk/media/conf.dlna/minidlna.conf
/disk/media/bin/init-stream-sources.sh > /tmp/mediastreams.log 2>&1