Skip navigation

Sign up | Lost password? | Help

Widgets from Maurice v. k.

How to use the import.js with Mediatomb UPNP server

Well I bought myself a NSLU2 Linksys storage link and installed unslung (alternative firmware) on it to use it as a media upnp server and as a client my archos 605 wifi.
I decided as for upnp server to go for the mediatomb, because the interface can be easily configured using javascript. A few scripts and explanation can be found in this blog...

How to get started?

First locate the files config.xml and import.js, because these we have to change to alter the database view of the upnp server.
The config.xml is when on a standard install on unslung in the directory /opt/etc/mediatomb/config.xml there you can configure different settings for your server. Off course the location can change depending on the install and what linux distro you are using.

An example of the config.xml with no changes yet:

<?xml version="1.0" encoding="UTF-8"?>
<config version="1" xmlns="http://mediatomb.cc/config/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://mediatomb.cc/config/1 http://mediatomb.cc/config/1.xsd">
  <server>
    <ui enabled="yes">
      <accounts enabled="no" session-timeout="100">
        <account user="" password=""/>
      </accounts>
    </ui>
    <name>MediaTomb</name>
    <udn>uuid:e2fa5a80-7375-4ccf-9769-b3a083cee7e2</udn>
    <home>/root/.mediatomb</home>
    <webroot>/opt/share/mediatomb/web</webroot>
    <storage>
      <sqlite3 enabled="yes">
        <database-file>mediatomb.db</database-file>
      </sqlite3>
    </storage>
    <protocolInfo extend="no"/><!-- For PS3 support change to "yes" -->
    <!--
       Uncomment the lines below to get rid of jerky avi playback on the
       DSM320 or to enable subtitles support on the DSM units
    -->
    <!--
    <custom-http-headers>
      <add header="X-User-Agent: redsonic"/>
    </custom-http-headers>

    <manufacturerURL>redsonic.com</manufacturerURL>
    <modelNumber>105</modelNumber>
    -->
    <!-- Uncomment the line below if you have a Telegent TG100 -->
    <!--
       <upnp-string-limit>101</upnp-string-limit>
    -->
  </server>
  <import hidden-files="no">
    <scripting script-charset="UTF-8">
      <common-script>/opt/share/mediatomb/js/common.js</common-script>
      <playlist-script>/opt/share/mediatomb/js/playlists.js</playlist-script>
      <virtual-layout type="builtin">
        <import-script>/opt/share/mediatomb/js/import.js</import-script>
      </virtual-layout>
    </scripting>
    <mappings>
      <extension-mimetype ignore-unknown="no">
        <map from="mp3" to="audio/mpeg"/>
        <map from="ogg" to="application/ogg"/>
        <map from="asf" to="video/x-ms-asf"/>
        <map from="asx" to="video/x-ms-asf"/>
        <map from="wma" to="audio/x-ms-wma"/>
        <map from="wax" to="audio/x-ms-wax"/>
        <map from="wmv" to="video/x-ms-wmv"/>
        <map from="wvx" to="video/x-ms-wvx"/>
        <map from="wm" to="video/x-ms-wm"/>
        <map from="wmx" to="video/x-ms-wmx"/>
        <map from="m3u" to="audio/x-mpegurl"/>
        <map from="pls" to="audio/x-scpls"/>
        <map from="flv" to="video/x-flv"/>
        <!-- Uncomment the line below for PS3 divx support -->
        <!-- <map from="avi" to="video/divx"/> -->
        <!-- Uncomment the line below for D-Link DSM / ZyXEL DMA-1000 -->
        <!-- <map from="avi" to="video/avi"/> -->
      </extension-mimetype>
      <mimetype-upnpclass>
        <map from="audio/*" to="object.item.audioItem.musicTrack"/>
        <map from="video/*" to="object.item.videoItem"/>
        <map from="image/*" to="object.item.imageItem"/>
      </mimetype-upnpclass>
      <mimetype-contenttype>
        <treat mimetype="audio/mpeg" as="mp3"/>
        <treat mimetype="application/ogg" as="ogg"/>
        <treat mimetype="audio/x-flac" as="flac"/>
        <treat mimetype="image/jpeg" as="jpg"/>
        <treat mimetype="audio/x-mpegurl" as="playlist"/>
        <treat mimetype="audio/x-scpls" as="playlist"/>
        <treat mimetype="audio/x-wav" as="pcm"/>
        <treat mimetype="audio/L16" as="pcm"/>
        <treat mimetype="video/x-msvideo" as="avi"/>
      </mimetype-contenttype>
    </mappings>
  </import>
  <transcoding enabled="no">
    <mimetype-profile-mappings>
      <transcode mimetype="video/x-flv" using="vlcmpeg"/>
      <transcode mimetype="application/ogg" using="vlcmpeg"/>
      <transcode mimetype="application/ogg" using="oggflac2raw"/>
      <transcode mimetype="audio/x-flac" using="oggflac2raw"/>
    </mimetype-profile-mappings>
    <profiles>
      <profile name="oggflac2raw" enabled="no" type="external">
        <mimetype>audio/L16</mimetype>
        <accept-url>no</accept-url>
        <first-resource>yes</first-resource>
        <accept-ogg-theora>no</accept-ogg-theora>
        <agent command="ogg123" arguments="-d raw -f %out %in"/>
        <buffer size="1048576" chunk-size="131072" fill-size="262144"/>
      </profile>
      <profile name="vlcmpeg" enabled="no" type="external">
        <mimetype>video/mpeg</mimetype>
        <accept-url>yes</accept-url>
        <first-resource>yes</first-resource>
        <accept-ogg-theora>yes</accept-ogg-theora>
        <agent command="vlc" arguments="-I dummy %in --sout #transcode{venc=ffmpeg,vcodec=mp2v,vb=4096,fps=25,aenc=ffmpeg,acodec=mpga,ab=192,samplerate=44100,channels=2}:standard{access=file,mux=ps,dst=%out} vlc:quit"/>
        <buffer size="14400000" chunk-size="512000" fill-size="120000"/>
      </profile>
    </profiles>
  </transcoding>
</config>


More on this file can be found here http://mediatomb.cc/pages/documentation#id2536421
In this file is also the location of the import.js /opt/share/mediatomb/js/import.js

An example of the import.js with no changes yet:

// Default MediaTomb import script.
// see MediaTomb scripting documentation for more information

/*MT_F*
    
    MediaTomb - http://www.mediatomb.cc/
    
    import.js - this file is part of MediaTomb.
    
    Copyright (C) 2006-2008 Gena Batyan <bgeradz@mediatomb.cc>,
                            Sergey 'Jin' Bostandzhyan <jin@mediatomb.cc>,
                            Leonhard Wimmer <leo@mediatomb.cc>
    
    This file is free software; the copyright owners give unlimited permission
    to copy and/or redistribute it; with or without modifications, as long as
    this notice is preserved.
    
    This file 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.
    
    $Id: import.js 1720 2008-03-01 19:36:53Z jin_eld $
*/

function addAudio(obj)
{
 
    var desc = '';
    var artist_full;
    var album_full;
    
    // first gather data
    var title = obj.meta[M_TITLE];
    if (!title) title = obj.title;
    
    var artist = obj.meta[M_ARTIST];
    if (!artist) 
    {
        artist = 'Unknown';
        artist_full = null;
    }
    else
    {
        artist_full = artist;
        desc = artist;
    }
    
    var album = obj.meta[M_ALBUM];
    if (!album) 
    {
        album = 'Unknown';
        album_full = null;
    }
    else
    {
        desc = desc + ', ' + album;
        album_full = album;
    }
    
    if (desc)
        desc = desc + ', ';
    
    desc = desc + title;
    
    var date = obj.meta[M_DATE];
    if (!date)
    {
        date = 'Unknown';
    }
    else
    {
        date = getYear(date);
        desc = desc + ', ' + date;
    }
    
    var genre = obj.meta[M_GENRE];
    if (!genre)
    {
        genre = 'Unknown';
    }
    else
    {
        desc = desc + ', ' + genre;
    }
    
    var description = obj.meta[M_DESCRIPTION];
    if (!description) 
    {
        obj.meta[M_DESCRIPTION] = desc;
    }

// uncomment this if you want to have track numbers in front of the title
// in album view
    
/*    
    var track = obj.meta[M_TRACKNUMBER];
    if (!track)
        track = '';
    else
    {
        if (track.length == 1)
        {
            track = '0' + track;
        }
        track = track + ' ';
    }
*/
    // comment the following line out if you uncomment the stuff above  :)
    var track = '';

    var chain = new Array('Audio', 'All Audio');
    obj.title = title;
    addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER_MUSIC);
    
    chain = new Array('Audio', 'Artists', artist, 'All Songs');
    addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER_MUSIC);
    
    chain = new Array('Audio', 'All - full name');
    var temp = '';
    if (artist_full)
        temp = artist_full;
    
    if (album_full)
        temp = temp + ' - ' + album_full + ' - ';
    
    obj.title = temp + title;
    addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER_MUSIC);
    
    chain = new Array('Audio', 'Artists', artist, 'All - full name');
    addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER_MUSIC);
    
    chain = new Array('Audio', 'Artists', artist, album);
    obj.title = track + title;
    addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER_MUSIC_ALBUM);
    
    chain = new Array('Audio', 'Albums', album);
    obj.title = track + title; 
    addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER_MUSIC_ALBUM);
    
    chain = new Array('Audio', 'Genres', genre);
    addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER_MUSIC_GENRE);
    
    chain = new Array('Audio', 'Year', date);
    addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER_MUSIC);
}

// currently no video metadata supported
function addVideo(obj)
{
    var chain = new Array('Video');
    addCdsObject(obj, createContainerChain(chain));
}

function addImage(obj)
{
    var chain = new Array('Photos', 'All Photos');
    addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER);

    var date = obj.meta[M_DATE];
    if (date)
    {
        chain = new Array('Photos', 'Date', date);
        addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER);
    }
    var model = obj.aux[EXIF_TAG_MODEL]
    if (model) 
    {
        chain = new Array ('Photos', 'Camera', model);
        addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER);
    }
}

// main script part

if (getPlaylistType(orig.mimetype) == '')
{
    var arr = orig.mimetype.split('/');
    var mime = arr[0];
    
    // var obj = copyObject(orig);
    
    var obj = orig; 
    obj.refID = orig.id;
    
    if (mime == 'audio')
    {
        addAudio(obj);
    }
    
    if (mime == 'video')
    {
        addVideo(obj);
    }
    
    if (mime == 'image')
    {
        addImage(obj);
    }

    if (orig.mimetype == 'application/ogg')
    {
        if (orig.theora == 1)
            addVideo(obj);
        else
            addAudio(obj);
    }
}


More on this file can be found here http://mediatomb.cc/pages/scripting#id2535294

First I wanted to display my videos as a directory structure like on the drive, because there is no metadata support for videos yet.
I decided to make a script that makes a copy of the directory structure of my parent directory Videos and below. It has to detect the number of subdirs automaticly so I wouldn't have to change this script in the future.

Let's get started:

First of all we have to change the config.xml so it uses the import script to make our database.
We do that with a simple text editor like nano or whatever you prefer. You can also do it in windows notepad and copy the file back after the edit. Or for the lazy ones just scroll down this page and download the zip with the config.xml and import.js already changed.

I like using nano which I accessed with a SSH session on my NSLU2.
The line we have to change is
<virtual-layout type="builtin">

into the following
<virtual-layout type="js">


Then we will edit the import.js and add the new script.

The new script for directory structure for videos:

function addVideo(obj) 
{ 
//create an array of the directorys of the location of the file
var location = obj.location.split('/'); 
chain = new Array(); 
var dirList=new Array();
var j=0;
for (i=location.length-2;i>=0;i--)
{
dirList[j]=location[i];
//Find the parent dir Videos and break the loop
//If you have a different parent dir then you should change it here
if (location[i]=='Videos') {break;}
j++;

}
//Build the chain array by adding the parent and subdirs starting with the parent dir.
for (i=j;i>=0;i--)
{
chain.push(dirList[i]);
}
 
addCdsObject(obj, createContainerChain(chain)); 
}


Find the function addVideo(obj) in the import.js and replace it with the above.

The old addVideo(obj) function looks like this:

function addVideo(obj)
{
    var chain = new Array('Video');
    addCdsObject(obj, createContainerChain(chain));
}


If you now restart the server and add your parent directory to your database it should create something like in the following picture.



My second change was photos sorting them on camera model, subject info and keywords from the exif info.

Much metadata isn't available for photo's, but there is an option to parse the exif info and add them in an array aux to be used then by javascript.
To parse the exif info libexif library is used. If we want to add an exif entry to the aux array we have to do it in config.xml.
Add an extra child to the import tag with the following do this just below the end tag of mappings

<library-options>
<libexif>
<auxdata>
<add-data tag="EXIF_TAG_MODEL"/>
<add-data tag="EXIF_TAG_PIXEL_X_DIMENSION"/>
<add-data tag="EXIF_TAG_PIXEL_Y_DIMENSION"/>
<add-data tag="EXIF_TAG_IMAGE_DESCRIPTION"/>
<add-data tag="EXIF_TAG_USER_COMMENT"/>
</auxdata>
</libexif>


</library-options>

This reads the following exifdata in the aux array:

"EXIF_TAG_MODEL" --> in javascript to be called as aux["EXIF_TAG_MODEL"] and is the camera model.
"EXIF_TAG_PIXEL_X_DIMENSION" --> called as aux["EXIF_TAG_PIXEL_X_DIMENSION"] the number of pixels on the x axle
"EXIF_TAG_PIXEL_Y_DIMENSION" --> called as aux["EXIF_TAG_PIXEL_Y_DIMENSION"] the number of pixels on the y axle
"EXIF_TAG_IMAGE_DESCRIPTION" --> called as aux["EXIF_TAG_IMAGE_DESCRIPTION"] the image description
"EXIF_TAG_USER_COMMENT" --> called as aux["EXIF_TAG_USER_COMMENT"] the comment of the image

There is more exif data to collect and for a more complete list look here and here for the explanation of the variables

I want create the folowing structure with this info:




For doing this I changed the function addImage(obj) with the following code.

function addImage(obj)
{
    var chain = new Array('Photos', 'All Photos');
    addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER);
    var xpixel=obj.aux['EXIF_TAG_PIXEL_X_DIMENSION'];
    var ypixel=obj.aux['EXIF_TAG_PIXEL_Y_DIMENSION'];
    
  //check if there is x and y pixel 
  //and if both are below 800px add them only to small photos 
  if ( ((!xpixel) && (!ypixel)) || ((xpixel<800) && (ypixel<800)) )

    {
chain = new Array('Photos','Small photos');
addCdsObject(obj,createContainerChain(chain),UPNP_CLASS_CONTAINER);
    }
else
{   
    //If there is a date add them to the date folder
    var date = obj.meta[M_DATE];
    if (date)
    {
        chain = new Array('Photos', 'Date', date);
        addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER);
    }
    //camera directory with subject subdirectory
    var model = obj.aux['EXIF_TAG_MODEL'];
    if (model) 
    {
var subject=obj.aux['EXIF_TAG_IMAGE_DESCRIPTION'];
    if (subject)
    {
subject = firstUppperCase(subject);
        chain = new Array ('Photos','Camera',model,subject);
        addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER);
    }
    else
        {
        chain = new Array ('Photos', 'Camera',model,'Unknown description');
        addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER);
}
    }
    else
    {
var subject=obj.aux['EXIF_TAG_IMAGE_DESCRIPTION'];
 if (subject)
        {
subject = firstUppperCase(subject);
               chain = new Array ('Photos','Camera','Unknown camera',subject);
                addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER);
        }
        else
        {
                chain = new Array ('Photos', 'Camera','Unknown camera','Unknown description');
                addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER);
        }


    }
    //subject directory in the parent directory
    var subject=obj.aux['EXIF_TAG_IMAGE_DESCRIPTION'];
    if (subject)
    {
subject = firstUppperCase(subject);
        chain = new Array ('Photos', 'Subject', subject);
        addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER);
    }
    else
        {
        chain = new Array ('Photos', 'Subject','Unknown description');
        addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER);
        }
    //keyword directory in the parent directory to add this function I use the format
    //Keyword-1;Keyword-2;Keyword-3 in the exif tag user comment I also change all letters
    //to lower case and the first letter to upper of every keyword in the script
    //This to avoid getting multiple dirs with Holiday 2007 and holiday 2007. This with
    //the function firstUppperCase(str).
    var keyword=obj.aux['EXIF_TAG_USER_COMMENT'];
    if (keyword)    
    {
var keywordArray=new Array();
var keywordArray=obj.aux['EXIF_TAG_USER_COMMENT'].split(';');
for (i=0;i<keywordArray.length;i++)
{
keywordArray[i] = firstUppperCase(keywordArray[i])
        chain = new Array ('Photos', 'Keyword', keywordArray[i]);
        addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER);
}
    }
    else
        {
        chain = new Array ('Photos', 'Keyword','Unknown keyword');
        addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER);
        }

}
}
// script to turn string to lower case and the first to uppercase
function firstUppperCase(str)
{
var remainChar = str.substring(1);
remainChar = remainChar.toLowerCase();
var firstChar = str.substring(0,1);
firstChar = firstChar.toUpperCase();
return firstChar+remainChar
}


BTW the old function looked like this:

function addImage(obj)
{
    var chain = new Array('Photos', 'All Photos');
    addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER);

    var date = obj.meta[M_DATE];
    if (date)
    {
        chain = new Array('Photos', 'Date', date);
        addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER);
    }
    var model = obj.aux[EXIF_TAG_MODEL]
    if (model) 
    {
        chain = new Array ('Photos', 'Camera', model);
        addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER);
    }
}


The code can be shorter, but I did it like this so I can easily cut away parts if I don't want them anymore.

IMPORTANT:
When testing the code I came across a problem with reading the EXIF tags I changed in windows explorer. It seems windows explorer writes the tags in UNICODE 16 bit and libexif can't read that. Also if you changed the tags already there is no easy way to go back to ascii. At least the exif editors I tried kept also writing them in unicode 16 bit.
Now I use Exifer to edit the exif info and if it is not already changed by windows explorer it will write them in ascii.
The best way I think is to make a copy of your photos before changing any exif tags also don't use windows explorer to change them.


I hope you enjoyed reading this and if you find it usefull please leave a comment.

Maurice B)

Step by step tutorial to upload your address book backupLocal content portal for your Archos wifi (Favorit manager) update 16-10-2008

Comments

maddeals 23. September 2008, 17:39

Great tutorial but I'm having trouble getting mediatomb to create virtual folders for my Itunes m4a tracks. Everything goes in to the unknown virtual folders. Your tutorial shows how to do video and pictures, would you post an example how to configure the import.js for audio. I would would like to sort by Artist, Album and Genre. I appreciate any advice you might give. Thank you inadvance.

Mike D.

Write a comment

You must be logged in to write a comment. If you're not a registered member, please sign up.

July 2009
M T W T F S S
June 2009August 2009
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31