We provide some client python scripts as some examples about how to control the parameters
from a console or any application (see deefuzzer/scripts/).
+
Twitter (manual and optional)
================================
Your DeeFuzzer will now tweet the currently playing track and new tracks on your profile.
+
+Station Folders
+===============
+
+Station folders are a specific way of setting up your file system so that you can auto-create many stations
+based on only a few settings. The feature requires a single main folder, with one or more subfolders. Each
+subfolder is scanned for the presence of media files (audio-only at the moment). If files are found, then a
+station is created using the parameters in the <stationfolder> block. Substitution is performed to fill in
+some detail to the stationfolder parameters, and all stationdefaults are also applied.
+
+The base folder is specified by the <folder> block. No substitution is done on this parameter.
+
+Subsitution is done for [name] and [path] - [name] is replaced with the name of the subfolder, and [path] is
+replaced with the subfolder's complete path.
+
+Consider the following example. We have a block with the following settings:
+
+ <stationfolder>
+ <folder>/path/to/media</folder>
+ <infos>
+ <short_name>[name]</short_name>
+ <name>[name]</name>
+ <genre>[name]</genre>
+ </infos>
+ <media>
+ <dir>[path]</dir>
+ </media>
+ </stationfolder>
+
+The folder structure is as follows:
+
+ /path/to/media
+ + one
+ - song1.mp3
+ - song2.mp3
+ + two
+ - song3.ogg
+ + three
+ - presentation.pdf
+ + four
+ - song4.mp3
+
+In this case, three stations are created: one, two, and four. Each will have their short name (and thus their
+icecast mount point) set to their respective folder names. Subfolder three is skipped, as there are no audio files
+present - just a PDF file.
+
+
API
===
def __init__(self, conf_file):
Thread.__init__(self)
self.conf_file = conf_file
- self.conf = self.get_conf_dict()
-
+ self.conf = get_conf_dict(self.conf_file)
for key in self.conf['deefuzzer'].keys():
if key == 'log':
log_file = self.conf['deefuzzer']['log']
else:
setattr(self, key, self.conf['deefuzzer'][key])
- if isinstance(self.conf['deefuzzer']['station'], dict):
- # Fix wrong type data from xmltodict when one station (*)
- self.nb_stations = 1
+ # Fix wrong type data from xmltodict when one station (*)
+ if 'station' not in self.conf['deefuzzer']:
+ self.conf['deefuzzer']['station'] = []
else:
- self.nb_stations = len(self.conf['deefuzzer']['station'])
+ if not isinstance(self.conf['deefuzzer']['station'], list):
+ s = self.conf['deefuzzer']['station']
+ self.conf['deefuzzer']['station'] = []
+ self.conf['deefuzzer']['station'].append(s)
+
+ # Load additional station definitions from the requested folder
+ if 'stationconfig' in self.conf['deefuzzer']:
+ self.load_stations(self.conf['deefuzzer']['stationconfig'])
+
+ # Create stations automagically from a folder structure
+ if 'stationfolder' in self.conf['deefuzzer'].keys() and isinstance(self.conf['deefuzzer']['stationfolder'], dict):
+ self.create_stations_fromfolder(self.conf['deefuzzer']['stationfolder'])
+
+ self.nb_stations = len(self.conf['deefuzzer']['station'])
# Set the deefuzzer logger
self.logger.write_info('Starting DeeFuzzer')
self.stations = []
self.logger.write_info('Number of stations : ' + str(self.nb_stations))
- def get_conf_dict(self):
- mime_type = mimetypes.guess_type(self.conf_file)[0]
- confile = open(self.conf_file,'r')
- data = confile.read()
- confile.close()
-
- if 'xml' in mime_type:
- return xmltodict(data,'utf-8')
- elif 'yaml' in mime_type:
- import yaml
- return yaml.load(data)
- elif 'json' in mime_type:
- import json
- return json.loads(data)
-
def set_m3u_playlist(self):
m3u_dir = os.sep.join(self.m3u.split(os.sep)[:-1])
if not os.path.exists(m3u_dir) and m3u_dir:
m3u.close()
self.logger.write_info('Writing M3U file to : ' + self.m3u)
+ def create_stations_fromfolder(self, options):
+ if not 'folder' in options.keys():
+ return
+ folder = str(options['folder'])
+ self.logger.write_info('Scanning folder ' + folder + ' for stations...')
+ files = os.listdir(folder)
+ for file in files:
+ filepath = os.path.join(folder, file)
+ if os.path.isdir(filepath):
+ if folder_contains_music(filepath):
+ self.create_station(filepath, options)
+
+ def create_station(self, folder, options):
+ self.logger.write_info('Creating station for folder ' + folder)
+ s = {}
+ path, name = os.path.split(folder)
+ d = dict(path=folder,name=name)
+ for i in options.keys():
+ if not 'folder' in i:
+ s[i] = replace_all(options[i], d)
+ if not 'media' in s.keys():
+ s['media'] = {}
+ s['media']['dir'] = folder:
+ self.conf['deefuzzer']['station'].append(s)
+
+
+ def load_stations(self, folder):
+ if isinstance(folder, dict):
+ for f in folder:
+ self.load_stations(f)
+ return
+
+ if not os.path.isdir(folder):
+ return
+
+ self.logger.write_info('Loading station config files in ' + folder)
+ files = os.listdir(folder)
+ for file in files:
+ filepath = os.path.join(folder, file)
+ if os.path.isfile(filepath):
+ self.load_station_config(filepath)
+
+ def load_station_config(self, file):
+ self.logger.write_info('Loading station config file ' + file)
+ stationdef = get_conf_dict(file)
+ if isinstance(stationdef, dict):
+ if 'station' in stationdef.keys() and isinstance(stationdef['station'], dict):
+ self.conf['deefuzzer']['station'].append(stationdef['station'])
+
def run(self):
q = Queue.Queue(1)
for i in range(0,self.nb_stations):
- if isinstance(self.conf['deefuzzer']['station'], dict):
- station = self.conf['deefuzzer']['station']
- else:
- station = self.conf['deefuzzer']['station'][i]
+ station = self.conf['deefuzzer']['station'][i]
+
+ # Apply station defaults if they exist
+ if 'stationdefaults' in self.conf['deefuzzer']:
+ if isinstance(self.conf['deefuzzer']['stationdefaults'], dict):
+ station = merge_defaults(station, self.conf['deefuzzer']['stationdefaults'])
self.stations.append(Station(station, q, self.logger, self.m3u))
if self.m3u:
import os
import re
import string
+import mimetypes
+from itertools import chain
+from deefuzzer.tools import *
+
+mimetypes.add_type('application/x-yaml','.yaml')
def clean_word(word) :
""" Return the word without excessive blank spaces, underscores and
def is_absolute_path(path):
return os.sep == path[0]
+
+def merge_defaults(setting, default):
+ combined = {}
+ for key in set(chain(setting, default)):
+ if key in setting:
+ if key in default:
+ if isinstance(setting[key], dict) and isinstance(default[key], dict):
+ combined[key] = merge_defaults(setting[key], default[key])
+ else:
+ combined[key] = setting[key]
+ else:
+ combined[key] = setting[key]
+ else:
+ combined[key] = default[key]
+ return combined
+
+def replace_all(option, repl):
+ if isinstance(option, list):
+ r = []
+ for i in option:
+ r.append(replace_all(i, repl))
+ return r
+ elif isinstance(option, dict):
+ r = {}
+ for key in option.keys():
+ r[key] = replace_all(option[key], repl)
+ return r
+ elif isinstance(option, str):
+ r = option
+ for key in repl.keys():
+ r = r.replace('[' + key + ']', repl[key])
+ return r
+ return option
+
+def get_conf_dict(file):
+ mime_type = mimetypes.guess_type(file)[0]
+
+ # Do the type check first, so we don't load huge files that won't be used
+ if 'xml' in mime_type:
+ confile = open(file,'r')
+ data = confile.read()
+ confile.close()
+ return xmltodict(data,'utf-8')
+ elif 'yaml' in mime_type:
+ import yaml
+ confile = open(file,'r')
+ data = confile.read()
+ confile.close()
+ return yaml.load(data)
+ elif 'json' in mime_type:
+ import json
+ confile = open(file,'r')
+ data = confile.read()
+ confile.close()
+ return json.loads(data)
+
+ return False
+
+def folder_contains_music(folder):
+ files = os.listdir(folder)
+ for file in files:
+ filepath = os.path.join(folder, file)
+ if os.path.isfile(filepath):
+ mime_type = mimetypes.guess_type(filepath)[0]
+ if 'audio/mpeg' in mime_type or 'audio/ogg' in mime_type:
+ return True
+ return False
"deefuzzer": {
"log": "/path/to/station.log",
"m3u": "/path/to/station.m3u",
+ "stationdefaults": {
+ "control": {
+ "mode": 0,
+ "port": 16001
+ },
+ "jingles": {
+ "dir": "/path/to/jingles",
+ "mode": 0,
+ "shuffle": 1
+ }
+ },
"station": {
"control": {
"mode": 0,
<deefuzzer>
<log>/path/to/station.log</log>
<m3u>/path/to/station.m3u</m3u>
+ <stationdefaults>
+ <control>
+ <mode>0</mode>
+ <port>16001</port>
+ </control>
+ <jingles>
+ <dir>/path/to/jingles</dir>
+ <mode>0</mode>
+ <shuffle>1</shuffle>
+ </jingles>
+ </stationdefaults>
<station>
<control>
<mode>0</mode>
log: /path/to/station.log
m3u: /path/to/station.m3u
+ stationdefaults:
+ control: {mode: 0,
+ port: 16001}
+
+ jingles: {dir: /path/to/jingles,
+ mode: 0,
+ shuffle: 1}
+
station:
control: {mode: 0,
port: 16001}
The file is preferably accessible behind an url,
for example, http://mydomain.com/m3u/mystation.m3u -->
<m3u>/path/to/station.m3u</m3u>
+ <stationdefaults>
+ <!-- This tag allows a common default configuration to be set for all stations. This
+ is useful when defining many stations that will share many common configuration
+ settings. If a setting is specified here and in a station tag, the station tag
+ will override this one. Available options are the same as the station tag. -->
+ <control>
+ <!-- If '1', an OSC controller thread is started to allow external commands
+ See README for more info -->
+ <mode>0</mode>
+ <!-- The port of the OSC server -->
+ <port>16001</port>
+ </control>
+ <jingles>
+ <!-- A path to the directory containing jingles media files.
+ The files have to be of the same type of the main media files. -->
+ <dir>/path/to/jingles</dir>
+ <!-- If '1', some media will be played between each main track of the playlist. '0' does nothing. -->
+ <mode>0</mode>
+ <!-- If '1', the jingle playlist will be randomized. '0' for aphanumeric order -->
+ <shuffle>1</shuffle>
+ </jingles>
+ </stationdefaults>
<station>
<control>
<!-- If '1', an OSC controller thread is started to allow external commands
</station>
<!-- Note that you can add many different stations in the same config file, thanks to the multi-threaded architecure ! -->
-</deefuzzer>
-
+ <!-- The stationfolder option allows auto-creation of stations based on a folder structure. See the readme
+ for details. -->
+ <stationfolder>
+ <!-- REQUIRED: The base folder to use when auto-generating stations -->
+ <folder>/path/to/media</folder>
+ <!-- Station information to use. At a minimum, the following should be defined:
+ infos.short_name so that mount points will be unique.
+ media.dir so that the files are loaded from the right place (IMPROVEMENT: should be set in code!)
+ All the same options are available as the station setting, and all stations will also have the global
+ stationdefaults applied. -->
+ <infos>
+ <short_name>[name]</short_name>
+ <name>[name]</name>
+ <genre>[name]</genre>
+ </infos>
+ <media>
+ <dir>[path]</dir>
+ </media>
+ </stationfolder>
+
+ <!-- The stationfolder option allows specifying a folder to scan for additional configuration files. Applies only
+ those that are station blocks. Can specify multiple stationoption blocks. -->
+ <stationconfig>/path/to/configs</stationconfig>
+ <stationconfig>/path/to/configs2</stationconfig>
+</deefuzzer>
--- /dev/null
+<!-- This is an example of a station definition file loaded via the stationconfig option. One or more station
+ definitions can be defied here. No other blocks will be loaded if they are present. All stations will
+ have the global stationdefaults applied. -->
+<station>
+ <infos>
+ <description>My personal best funky playlist ever!</description>
+ <name>My best funky station</name>
+ <short_name>My_station</short_name>
+ <url>http://parisson.com</url>
+ <genre>Various Funk Groove</genre>
+ </infos>
+ <jingles>
+ <dir>/path/to/jingles</dir>
+ <mode>0</mode>
+ <shuffle>1</shuffle>
+ </jingles>
+ <media>
+ <bitrate>96</bitrate>
+ <dir>/path/to/mp3/</dir>
+ <format>mp3</format>
+ <m3u>/path/to/m3u_file</m3u>
+ <ogg_quality>4</ogg_quality>
+ <samplerate>48000</samplerate>
+ <shuffle>0</shuffle>
+ <voices>2</voices>
+ </media>
+ <server>
+ <host>127.0.0.1</host>
+ <mountpoint>monitor</mountpoint>
+ <port>8000</port>
+ <public>0</public>
+ <sourcepassword>icecast_source_password</sourcepassword>
+ <type>icecast</type>
+ <appendtype>1</appendtype>
+ </server>
+</station>