MediaTomb dspl support¶
dspl format¶
The dspl (digitalSTROM playlist) file is simply the JSON-file that can be fetched from the dSS at http://[dSS]/json/apartment/getStructure.
Steps to enable dspl support¶
Several files have to be modified to enable dspl support in MediaTomb. I recommend to make a copy of each file before modifying it because this makes it very easy to enable and disable dspl support later on. The necessary modifications are described file by file. Copying the file will not be mentioned for each file, just do it if you need the possibility to enable/disable dspl-support. All the JavaScript files are located in the MediaTomb script directory, which is probably /usr/local/share/mediatomb/js/ (depending on the installation). The XML-files are by default located in ~/.mediatomb/.
common.js
The function getPlayListType in the common.js file in the Mediatomb script directory has to be replaced by
function getPlaylistType(mimetype)
{
if (mimetype == 'audio/x-mpegurl')
return 'm3u';
if (mimetype == 'audio/x-scpls')
return 'pls';
if (mimetype == 'playlist/dspl')
return 'dspl';
return '';
}
playlists.js
At the end of the playlists.js in the MediaTomb script directory (which should be the closing curly bracket of the else if (type == 'pls') branch) the following else-if-branch has to be added. Remember to replace all file paths with the paths of the corresponding files on your machine. The code for the call_scene_item_by_state.py, upnp_call_device and upnp_call_scene scripts (the latter two are called from call_scene_item_by_state.py) can be found at the end of this page.
// the following else if branch is added to process dspl playlists
else if (type == 'dspl')
{
// read whole json file
var json_content = "";
var line = readln();
do
{
json_content = json_content + line;
line = readln();
}
while (line);
// evaluate content of json file
apartment = eval('(' + json_content + ')').apartment;
// traverse structure and add appropriate active items
while(apartment.zones.length > 0)
{
zone = apartment.zones.shift();
// zone 0 is not displayed...
if(zone.id)
{
// show devices only if flag is set
if(SHOW_DEVICES)
{
// add every device in zone
while(zone.devices.length > 0)
{
device = zone.devices.shift();
addPlaylistItemOfDevice(zone.name, device);
}
}
addScenes(zone.id, zone.name);
}
}
}
var SHOW_DEVICES = false;
var prefix = "Automatisch gefundene Zonen"
function audioContainerChain(arr)
{
var base = new Array("Wohnung - Musik");
return createContainerChain(base.concat(arr));
}
function imageContainerChain(arr)
{
var base = new Array("Wohnung - Photos");
return createContainerChain(base.concat(arr));
}
var devPLOrder = 1;
function addPlaylistItemOfDevice(zone, device)
{
var item = new Object();
print("Adding device ``" + device.name + "'' in zone ``" + zone + "''");
// set general information of audio _and_ image items
item.objectType = OBJECT_TYPE_ACTIVE_ITEM;
if (device.name == '')
item.title = device.id;
else
item.title = device.name;
item.description = device.name;
item.action = "/home/demo/Desktop/call_scene_item_by_state.py";
item.state = "class=device;device="+device.id + ";";
// add audio item to database
audioItem = eval(uneval(item)); // clone object
audioItem.location = "/home/demo/Desktop/send.mp3";
audioItem.mimetype = 'audio/mpeg';
audioItem.upnpclass = UPNP_CLASS_ITEM_MUSIC_TRACK;
audioItemOn = eval(uneval(audioItem));
audioItemOn.title += " Ein";
audioItemOn.description = audioItemOn.title;
audioItemOn.state += "action=on;";
audioItemOn.playlistOrder = devPLOrder++;
addCdsObject(audioItemOn,
audioContainerChain(new Array(zone, "Devices",
audioItem.title,
"Ein")),
UPNP_CLASS_PLAYLIST_CONTAINER);
audioItemOff = eval(uneval(audioItem));
audioItemOff.title += " Aus";
audioItemOff.description = audioItemOff.title;
audioItemOff.state += "action=off";
audioItemOff.playlistOrder = devPLOrder++;
addCdsObject(audioItemOff,
audioContainerChain(new Array(zone, "Devices",
audioItem.title,
"Aus")),
UPNP_CLASS_PLAYLIST_CONTAINER);
// add image item to database
imageItem = eval(uneval(item));
imageItem.mimetype = 'image/jpeg';
imageItem.upnpclass = "object.item.imageItem";
imageItemOn = eval(uneval(imageItem));
imageItemOn.title += " Ein";
imageItemOn.description = imageItemOn.title;
imageItemOn.state += "action=on;"
imageItemOn.location = "/home/demo/Desktop/light_10.jpeg";
imageItemOn.playlistOrder = devPLOrder++;
addCdsObject(imageItemOn,
imageContainerChain(new Array(zone, "Devices",
imageItem.title,
"Ein")),
UPNP_CLASS_PLAYLIST_CONTAINER);
imageItemOff = eval(uneval(imageItem));
imageItemOff.title += " Aus";
imageItemOff.description = imageItemOff.title;
imageItemOff.state += "action=off;"
imageItemOff.location = "/home/demo/Desktop/light_01.jpeg";
imageItemOff.playlistOrder = devPLOrder++;
addCdsObject(imageItemOff,
imageContainerChain(new Array(zone, "Devices",
imageItemOff.title,
"Aus")),
UPNP_CLASS_PLAYLIST_CONTAINER);
}
// creator function for Scene object
function Scene(id, name)
{
this.id = id;
this.name = name;
}
// add scenes 1 to 4 and scene off for a zone to audio and image container
function addScenes(zoneid, zonename) {
var item = new Object();
// scene id -> scene name mapping
if(zoneid == 313) // Wohnen
{
scenes = new Array(new Scene(5, "Essen"),
new Scene(17, "Fernsehen"),
new Scene(18, "Lesen"),
new Scene(19, "Putzen"),
new Scene(13, "Alles aus"));
}
else if(zoneid == 357) // Garage
{
scenes = new Array(new Scene(5, "Scene 1"),
new Scene(17, "Scene 2"),
new Scene(18, "Scene 3"),
new Scene(19, "Scene 4"),
new Scene(13, "Alles aus"));
}
else if(zoneid == 324) // Schlafen
{
scenes = new Array(new Scene(5, "Scene 1"),
new Scene(17, "Scene 2"),
new Scene(18, "Scene 3"),
new Scene(19, "Scene 4"),
new Scene(13, "Alles aus"));
}
else if(zoneid == 1) // Bad
{
scenes = new Array(new Scene(5, "Scene 1"),
new Scene(17, "Scene 2"),
new Scene(18, "Scene 3"),
new Scene(19, "Scene 4"),
new Scene(13, "Alles aus"));
}
else
{
scenes = new Array(new Scene(5, "Scene 1"),
new Scene(17, "Scene 2"),
new Scene(18, "Scene 3"),
new Scene(19, "Scene 4"),
new Scene(13, "Alles aus"));
}
print("Adding Scenes in zone ``" + zonename + "''");
// set general information of audio _and_ image items
item.objectType = OBJECT_TYPE_ACTIVE_ITEM;
item.action = "/home/demo/Desktop/call_scene_item_by_state.py";
index = 1;
while(scenes.length > 0)
{
scene = scenes.shift();
// set scene dependant properties
item.title = scene.name;
item.description = scene.name;
item.state = "class=scene;zone="+zoneid+";scene="+scene.id;
item.playlistOrder = index++;
// create audio item
audioItem = eval(uneval(item));
audioItem.location = "/home/demo/Desktop/send.mp3";
audioItem.mimetype = 'audio/mpeg';
audioItem.upnpclass = UPNP_CLASS_ITEM_MUSIC_TRACK;
addCdsObject(audioItem,
audioContainerChain(new Array(zonename, audioItem.title)),
UPNP_CLASS_PLAYLIST_CONTAINER);
// create image item
imageItem = eval(uneval(item));
imageItem.location = "/home/demo/Desktop/sc" + scene.id + ".jpg";
imageItem.mimetype = 'image/jpeg';
imageItem.upnpclass = "object.item.imageItem";
addCdsObject(imageItem,
imageContainerChain(new Array(zonename, imageItem.title)),
UPNP_CLASS_PLAYLIST_CONTAINER);
}
}
config.xml
The following tags have to be added to the config.xml file:
<map from="dspl" to="playlist/dspl"/>
to the<extension-mimetype ...>
tag<treat mimetype="playlist/dspl" as="playlist"/>
to the<mimetype-contenttype>
tag
In case you copied the script files, the path to the new common.js file has to be set in the
<common-script>tag and the path to the new
playlists.js file has to be set in the <playlist-script>tag.
If you copied all files before modifying them, enabling and disabling dspl-support is as easy as executing MediaTomb with the corresponding config file (mediatomb -c ...).
Files¶
call_scene_item_by_state.py
#!/usr/bin/env python
# $Id: demo_toggle.py 1294 2007-05-13 16:28:24Z lww $
import sys
from xml.dom.minidom import *
import os
class Item:
def __init__(self, input):
self.xml = parseString(input)
def __getNodeData(self, data):
try:
title = self.xml.getElementsByTagName(data)[0]
if (len(title.childNodes) > 0):
if (title.childNodes[0].nodeType == title.childNodes[0].TEXT_NODE):
return title.childNodes[0].data
return ''
except:
return ''
def __getNode(self, data):
try:
title = self.xml.getElementsByTagName(data)[0]
if (len(title.childNodes) > 0):
if (title.childNodes[0].nodeType == title.childNodes[0].TEXT_NODE):
return title.childNodes[0]
else:
text = self.xml.createTextNode("")
title.appendChild(text)
return title.childNodes[0]
except:
return None
def getTitle(self):
return self.__getNodeData("dc:title")
def setTitle(self, data):
node = self.__getNode("dc:title")
node.data = data
def getClass(self):
return self.__getNodeData("upnp:class")
def setClass(self, data):
node = self.__getNode("upnp:class")
node.data = data
def getAction(self):
return self.__getNodeData("action")
def setAction(self, data):
node = self.__getNode("action")
node.data = data
def getState(self):
return self.__getNodeData("state")
def setState(self, data):
node = self.__getNode("state")
node.data = data
def getLocation(self):
return self.__getNodeData("location")
def setLocation(self, data):
node = self.__getNode("location")
node.data = data
def getMimeType(self):
return self.__getNodeData("mime-type")
def setMimeType(self, data):
node = self.__getNode("mime-type")
node.data = data
def render(self):
return self.xml.toxml()
def getDescription(self):
return self.__getNodeData("dc:description")
def setDescription(self, data):
node = self.__getNode("dc:description")
node.data = data
item = Item(sys.stdin.read())
#
# The following part of the file reads parameters from the state of the item
# and carries out an appropriate upnp call using the upnp_call_device script.
#
# Author: Sebastian Kohler (sebastian.kohler@aizo.com)
#
# split parameter list string into a list of parameters, i.e. a list name=value pairs
parameterList = item.getState().split(';');
# split each parameter into name and value
param = {}
for p in parameterList:
tmp = p.split('=', 1);
if len(tmp) == 2:
param[tmp[0]] = tmp[1]
# read parameters and finally carry out appropriate upnp call
if 'class' in param:
if param['class'] == 'device':
if 'action' in param:
if param['action'] == 'on':
on = 1;
else:
on = 0;
os.system("/home/demo/Desktop/upnp_call_device %s %i" % (param['device'], on))
else:
print "no action found"
elif param['class'] == 'scene':
if not 'zone' in param:
print "Error: no zone found"
elif not 'scene' in param:
print "Error: no scene found"
else:
zone = int(param['zone'])
scene = int(param['scene'])
# Scene 13 (Alles aus) is blocked if no other scene has been called inbetween.
# This is necessary because some devices send after rebooting the last sent request, which
# might scene 13, which would lead to a loop of shutting down and rebooting...
lockfile = "/home/demo/Desktop/zone"+str(zone)+".lock"
if scene == 13:
if os.path.exists(lockfile):
call = False;
else:
FILE = open(lockfile,"w")
FILE.write("DO NOT REMOVE THIS FILE! This file is a lock file used by MediaTomb to avoid some issues with calling scenes on the dSS...")
FILE.close();
call = True;
else:
if os.path.exists(lockfile):
os.remove(lockfile)
call = True;
if call:
os.system("/home/demo/Desktop/upnp_call_scene %i %i" % (zone, scene))
else:
print "Error: unknown class" + param['class']
else:
print "Error: no class found"
sys.stdout.write(item.render())
upnp_call_device
#!/bin/bash
#
# ask the dss to turn a device on/off
#
# Sebastian Kohler, sebastian.kohler@aizo.com
DSS_SERVER=localhost:8080
BASE_URL="http://$DSS_SERVER/json/device/"
if [ -z "$2" ]; then
echo "Usage: $(basename $0) <decive id> [0|1]"
exit 1
fi
set -x
if [ $2 -eq 0 ]; then
wget -O - "${BASE_URL}turnOff?dsid=$1" &> /dev/null
else
wget -O - "${BASE_URL}turnOn?dsid=$1" &> /dev/null
fi
upnp_call_scene
#!/bin/bash
#
# ask the dss to call a scene
#
# Johannes Winkelmann, johannes.winkelmann@aizo.com
DSS_SERVER=localhost:8080
ZONE_ID=0
BASE_URL="http://$DSS_SERVER/json/zone/callScene"
if [ -z "$2" ]; then
echo "Usage: $(basename $0) <zone id> <scene id>"
exit 1
fi
set -x
wget -O - "${BASE_URL}?id=$1&sceneNr=$2&groupID=1" &> /dev/null