MediaTomb

Scripting

This is the place to share your import and playlist scripts. Try to not paste in code, rather upload complete scripts. Also provide a short description and a screenshot, showing how your script organizes the content.

File System Structured Import Script

If you want your videos displayed as they are structured in your file system, like in the following Example:

Movies--+-Movie1
        +-Movie2

TVShows-+-TVShow1
        +-TVShow2

etc..

Modify your import.js as follows:

Replace:

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

With:

function addVideo(obj)
{
        var chain, show, season;
        var location = obj.location.split('/');
        chain = new Array();
        chain.push(location[location.length-3]); //genre name  (Movies, TVShows, etc)
        chain.push(location[location.length-2]); //series/movie name (Movie1, TVShow1,)
        addCdsObject(obj, createContainerChain(chain));
}

Download the full import.js script.

File Name Structured Import Script

This is how you can organise your videos into folders that correspond to the first letter of the file name. It looks like this:

-VIDEO-
        --
        -ABC-
        -ALL-
        -DEF-
        -GHI-
        -JKL-
        -MNO-
        -PQRS-
        -T-
        -UVW-
        -XYZ-

I've given T its own folder because there seem to be a lot of videos that start with T. Also, every video goes in the -ALL- folder as well as the folder that corresponds to the first letter of its file name.

1. Make a copy of the import.js file so you can go back to it if you make a big mistake. I name mine import.js.original

2. Open the import.js file in your favourite text editor and replace the addVideo function with this:

function addVideo(obj)
{
    // first title data
    var title = obj.meta[M_TITLE];
    if (!title) title = obj.title;
 
    // always add to the ALL section
    var chain = new Array('-VIDEO-', '-ALL-');
    addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER);
 
    // create a chain that matches the first letter of the file name
    var c = title.charAt(0);
    if (c == 'a' || c == 'A' || c == 'b' || c == 'B' || c == 'c' || c == 'C')
    {
    	chain = new Array('-VIDEO-', '-ABC-');
    }
    else if (c == 'd' || c == 'D' || c == 'e' || c == 'E' || c == 'f' || c == 'F')
    {
        chain = new Array('-VIDEO-', '-DEF-');
    }
    else if (c == 'g' || c == 'G' || c == 'h' || c == 'H' || c == 'i' || c == 'I')
    {
        chain = new Array('-VIDEO-', '-GHI-');
    }
    else if (c == 'j' || c == 'J' || c == 'k' || c == 'K' || c == 'l' || c == 'L')
    {
        chain = new Array('-VIDEO-', '-JKL-');
    }
    else if (c == 'm' || c == 'M' || c == 'n' || c == 'N' || c == 'o' || c == 'O')
    {
        chain = new Array('-VIDEO-', '-MNO-');
    }
    else if (c == 'p' || c == 'P' || c == 'q' || c == 'Q' || c == 'r' || c == 'R' || c == 's' || c == 'S')
    {
        chain = new Array('-VIDEO-', '-PQRS-');
    }
    else if (c == 't' || c == 'T')
    {
        chain = new Array('-VIDEO-', '-T-');
    }
    else if (c == 'u' || c == 'U' || c == 'v' || c == 'V' || c == 'w' || c == 'W')
    {
        chain = new Array('-VIDEO-', '-UVW-');
    }
    else if (c == 'x' || c == 'X' || c == 'y' || c == 'Y' || c == 'z' || c == 'Z')
    {
        chain = new Array('-VIDEO-', '-XYZ-');
    }
    else 
    {
        chain = new Array('-VIDEO-', '--');
    }
 
    // add the video
    addCdsObject(obj, createContainerChain(chain), UPNP_CLASS_CONTAINER);
}

3. Save your modified import.js file

4. Re-import all your video

Wishlist: I'd really like to be able to automatically re-organise the folders depending on the number of items that it contains. For example, if there were lots and lots of videos in the -ABC- folder I would like to break it up into -A-, -B-, -C- automatically. I'd also like to put the number of videos that are contained in the folder in the name of the folder; for example, -ABC (235)-. Unfortunately I don't think this is possible without some big changes to mediatomb.

Parsing Of File Names For TV Shows

This is how you can automagically organize your tv shows in folders that correspond to the name of the show and the season. For turing:

Videos\my_name_is_earl.2x19.harassed_a_reporter.dvdrip_xvid-fov.avi
Videos\Heroes.S02E07.Out of Time.HDTV.XviD-LOL.[TvT].avi
Videos\house.320.hdtv-lol

to

Videos\TV Shows\My Name Is Earl\Season 02\Episode 19 - Harassed A Reporter
Videos\TV Shows\Heroes\Season 02\Episode 07 - Out Of Time
Videos\TV Shows\House\Season 03\Episode 20

It looks like this:

Note: Do make a backup of your import script if something went wrong!

Video
        TV Shows
                My Show That I Like
                        Season 01
                                Episode 01 - Things That Matters
                                Episode 02
                                Episode 03 - Another Title

Add the following code in the top of the function addVideo in your import script Replace:

    if(checkIfSerieAndAdd(obj))
				return;

If you are using the original script, the result should look like this:

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

Now it is time to add the function checkIfSerieAndAdd and the required functions. Add the following code to the end of the file:

function checkIfSerieAndAdd(obj)
{
  var excludeSamples = true;
  var fixEpisodeNames = true;
 
  var patterns = [  /(.*)s(\d\d)e(\d\d)(\D.*)/i|>,
					/(.*)s(\d\d)(\D)(.*)/i|>,
					/(\D*)[\.|\-|_](\d)(\d\d)([\.|\-|_]\D.*)/i|>,
					/(\D*)(\d)[^0-9](\d\d)(\D.*)/i|>  ];
  for(var i=0;i<patterns.length;i++){
	  var match = patterns[i].exec(obj.title);
	  if(match){
		var name = fixSerieName(match[1]);
		if(name.length == 0)
			continue;
		var season = parseFloat(match[2]);
		if(season == 0)
			continue;
 
		var episode = parseFloat(match[3]);
		var leftover = match[4];
		var r = new RegExp();
 
		if(excludeSamples && /\Wsample\W/i.test(leftover))
			return true;		
 
		if(episode == 0){
			// Some malformed string
			leftover = obj.title;			
		}
 
		var chain = new Array('Video', 'TV Shows');
		chain.push(name);
		var paddedSeason = 'Season ' + padLeft(season, '0', 2);
		chain.push(paddedSeason);
 
		var paddedEpisode = padLeft(episode, '0', 2);
		if(paddedEpisode == 'NaN')
			paddedEpisode = '';
		else
			paddedEpisode = ' ' + paddedEpisode;
 
		if(fixEpisodeNames){
			var commonabbr = [ 'divx', 'xvid', 'dvdrip', 'hdtv', 'lol', 'axxo', 'repack', 'xor', 
								'pdtv', 'real', 'vtv', 'caph', '2hd', 'proper', 'fqm', 'uncut', 
								'topaz', 'tvt', 'notv', 'fpn', 'fov', 'orenji', '0tv', 'omicron', 
								'dsr', 'ws', 'sys', 'crimson', 'wat', 'hiqt', 'internal']
			for(var j=0;j<commonabbr.length;j++){
				var re = new RegExp("[\\W|_]" + commonabbr[j] + "[\\W|_]", 'gi');
				leftover = leftover.replace(re, '.');
			}
			while(leftover.indexOf('..')!=-1)
				leftover = leftover.replace(/\.\./g, '.');
 
			leftover = leftover.replace(/\.\w*$/, ' ');
 
			leftover = fixSerieName(leftover);
			if(leftover.length > 0)
				leftover = ' - ' + leftover;
		}
 
		obj.title = 'Episode' + paddedEpisode + leftover;
 
		addCdsObject(obj, createContainerChain(chain));
 
		return true;
	  }
  }
 
  return false;
}
 
function fixSerieName(name){
	name = name.replace(/_/g, ' ');
	name = name.replace(/\./g, ' ');
	name = name.replace(/  /g, ' ');
	name = removeStartingDashesAndSpaces(name);
	name = removeEndingDashesAndSpaces(name);
 
	return toProperCase(name);
}
function removeStartingDashesAndSpaces(name){
	if(name.length == 0)
		return name;
 
	while(name.indexOf(' ') == 0 || name.indexOf('-') == 0){
		name = name.replace(/^ /g, '');
		name = name.replace(/^-/g, '');
	}
	return name;
}
function removeEndingDashesAndSpaces(name){
	if(name.length == 0)
		return name;
 
	while(name.lastIndexOf(' ') == name.length - 1 || name.lastIndexOf('-') == name.length - 1){
		name = name.replace(/ $/g, '');
		name = name.replace(/-$/g, '');
	}
	return name;
}
function toProperCase(s)
{
  return s.toLowerCase().replace(/^(.)|\s(.)/g|>, 
          function($1) { return $1.toUpperCase(); });
}
 
function padLeft(str, pad, count) {
	str += '';
	while(str.length < count){
		str = pad + str;
	}
	return str;
}

Save your modified import.js and re-import all your videos.

Importing Tracks From flac+cue CD Image

This allows you to import CD images in flac format with an external cue sheet to your media server. The script assumes that the whole album is contained in one flac encoded file with an accompanying text format cue file describing the track layout in within the file and the meta data for the whole disc and individual tracks. The tracks are imported as “dummy” urls containing only the meta data, the flac file name, and the track location within the file. The urls are then provided through a transcoding script which produces the audio in wav format for the clients.

Changes to config.xml defining some mappings

* <import><mappings><extension-mimetype> add: <map from=“cue” to=“audio/x-cue”/>

* <import><mappings><mimetype-contenttype> add: <treat mimetype=“audio/x-cue” as=“playlist”/>

* <transcoding><mimetype-profile-mappings> add: <transcode mimetype=“audio/x-cue+flac” using=“flac2raw”/>

* <transcoding><profiles> add:

<profile name="flac2raw" enabled="yes" type="external">
  <mimetype>audio/x-wav</mimetype>
  <accept-url>yes</accept-url>
  <first-resource>yes</first-resource>
  <accept-ogg-theora>no</accept-ogg-theora>
  <agent command="flacNcue2wav.sh" arguments="%in %out"/>
  <buffer size="1048576" chunk-size="131072" fill-size="262144"/>
</profile>

Changes to import.js and common.js

To be able to call addAudio() function from playlist.js, it must be transferred from import.js to common.js. Also, the .cue extension must be specified to be a known playlist extension by changing getPlaylistType() function in common.js to be

function getPlaylistType(mimetype)
{
    if (mimetype == 'audio/x-mpegurl')
        return 'm3u';
    if (mimetype == 'audio/x-scpls')
        return 'pls';
    if (mimetype == 'audio/x-cue')
        return 'cue';
    return '';
}

Cue sheet parsing

The majority of the required scripting is done in playlist.js. The cue sheet parser is added to the main function as an own branch in addition to .m3u and .pls handling. After the parser has collected enough information of a track, it calls a new createSubItem() function which creates an object with the correct meta data, assigns type OBJECT_TYPE_ITEM_EXTERNAL_URL to it, and encodes the track location in the url in the form

http://cue2flac/params?skip=1:23.45&until=3:45.67&filename=/absolute/path/to/image.flac

This tells that the track location in disc is the range 1:23.45-3:45.67 in the given file. After all metadata is assigned to the newly created track object, it is added to the data base with addAudio() function (remember, the one transferred from import.js to common.js).

The full playlists.js script.

The transcoding script decoding the url for an external program is given in the transcoding section.

 
scripting/scripting.txt · Last modified: 2008/06/30 22:10 by loraderon