You are not logged in.
I got many virtual containers defined in default config.xml file
<virtual-layout type="js">
<boxlayout>
<box key="Root" title="Root" class="object.container" sort-key="00000" />
<box key="PCDirectory" title="PC Directory" class="object.container" sort-key="00000" />
<box key="Audio/allAlbums" title="Albums" class="object.container" upnp-shortcut="MUSIC_ALBUMS" />
<box key="Audio/allArtists" title="Artists" class="object.container" upnp-shortcut="MUSIC_ARTISTS" />
<box key="Audio/allAudio" title="All Audio" class="object.container" />
<box key="Audio/allComposers" title="Composers" class="object.container" />
<box key="Audio/allDirectories" title="Directories" class="object.container" upnp-shortcut="MUSIC_FOLDER_STRUCTURE" />
<box key="Audio/allGenres" title="Genres" class="object.container" upnp-shortcut="MUSIC_GENRES" />
<box key="Audio/allSongs" title="All Songs" class="object.container" />
<box key="Audio/allTracks" title="All - full name" class="object.container" upnp-shortcut="MUSIC_ALL" />
<box key="Audio/allYears" title="Year" class="object.container" />
<box key="Audio/audioRoot" title="Audio" class="object.container" />
<box key="Audio/artistChronology" title="Album Chronology" class="object.container" />
<box key="AudioInitial/abc" title="ABC" class="object.container" />
<box key="AudioInitial/allArtistTracks" title="000 All" class="object.container" />
<box key="AudioInitial/allBooks" title="Books" class="object.container" />
<box key="AudioInitial/audioBookRoot" title="AudioBooks" class="object.container" upnp-shortcut="MUSIC_AUDIOBOOKS" />
<box key="AudioStructured/allAlbums" title="-Album-" class="object.container" upnp-shortcut="MUSIC_ALBUMS" size="6" />
<box key="AudioStructured/allArtistTracks" title="all" class="object.container" />
<box key="AudioStructured/allArtists" title="-Artist-" class="object.container" upnp-shortcut="MUSIC_ARTISTS" size="9" />
<box key="AudioStructured/allGenres" title="-Genre-" class="object.container" upnp-shortcut="MUSIC_GENRES" size="6" />
<box key="AudioStructured/allTracks" title="-Track-" class="object.container" upnp-shortcut="MUSIC_ALL" size="6" />
<box key="AudioStructured/allYears" title="-Year-" class="object.container" />
<box key="Video/allDates" title="Date" class="object.container" upnp-shortcut="VIDEOS_YEARS_MONTH" />
<box key="Video/allDirectories" title="Directories" class="object.container" upnp-shortcut="VIDEOS_FOLDER_STRUCTURE" />
<box key="Video/allVideo" title="All Video" class="object.container" upnp-shortcut="VIDEOS_ALL" />
<box key="Video/allYears" title="Year" class="object.container" upnp-shortcut="VIDEOS_YEARS" />
<box key="Video/unknown" title="Unknown" class="object.container" />
<box key="Video/videoRoot" title="Video" class="object.container" upnp-shortcut="VIDEOS" />
<box key="Image/allDates" title="Date" class="object.container" upnp-shortcut="IMAGES_YEARS_MONTH" />
<box key="Image/allDirectories" title="Directories" class="object.container" upnp-shortcut="IMAGES_FOLDER_STRUCTURE" />
<box key="Image/allImages" title="All Photos" class="object.container" upnp-shortcut="IMAGES_ALL" />
<box key="Image/allYears" title="Year" class="object.container" upnp-shortcut="IMAGES_YEARS" />
<box key="Image/imageRoot" title="Photos" class="object.container" upnp-shortcut="IMAGES" />
<box key="Image/unknown" title="Unknown" class="object.container" />
<box key="ImageDetail/allModels" title="All Models" class="object.container" />
<box key="ImageDetail/yearMonth" title="Year+Month" class="object.container" />
<box key="ImageDetail/yearDate" title="Year+Date" class="object.container" />
<box key="Topic/topicRoot" title="Topics" class="object.container" />
<box key="Topic/topic" title="Topic" class="object.container" />
<box key="Topic/topicExtra" title="Extra" class="object.container" />
<box key="Playlist/playlistRoot" title="Playlists" class="object.container" />
<box key="Playlist/allPlaylists" title="All Playlists" class="object.container" />
<box key="Playlist/allDirectories" title="Directories" class="object.container" upnp-shortcut="MUSIC_PLAYLISTS" />
</boxlayout>
</virtual-layout>I need the directories of Audio <box key="Audio/allDirectories" title="Directories" class="object.container" upnp-shortcut="MUSIC_FOLDER_STRUCTURE" /> displayed.
Anyone has any solutions please.
Last edited by Jodi42 (2026-08-20 13:39:46)
Offline
I made a custom script through claude. On the official github page of gerbera, many users have offered change but developers reject merging it.
Use this audio.js in /usr/share/gerbera/js
/*GRB*
Gerbera - https://gerbera.io/
audio.js - this file is part of Gerbera.
Copyright (C) 2023-2026 Gerbera Contributors
Gerbera is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.
Gerbera 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. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Gerbera. If not, see <http://www.gnu.org/licenses/>.
$Id$
*/
// doc-add-audio-begin
function importAudio(obj, cont, rootPath, autoscanId, containerType) {
const audio = getAudioDetails(obj);
obj.sortKey = '';
const scriptOptions = config['/import/scripting/virtual-layout/script-options/script-option'];
// We finally gathered all data that we need, so let's create a
// nice layout for our audio files.
obj.title = audio.title;
// set option in config if you want to have track numbers in front of the title
if (!scriptOptions || scriptOptions['trackNumbers'] !== 'show')
audio.track = '';
// set option in config if you want to have channel numbers in front of the title
if (!scriptOptions || scriptOptions['channelCount'] !== 'show')
audio.channels = '';
audio.genre = mapGenre(audio.genre).value;
// The UPnP class argument to addCdsObject() is optional, if it is
// not supplied the default UPnP class will be used. However, it
// is suggested to correctly set UPnP classes of containers and
// objects - this information may be used by some renderers to
// identify the type of the container and present the content in a
// different manner.
// Remember, the server will sort all items by ID3 track if the
// container class is set to UPNP_CLASS_CONTAINER_MUSIC_ALBUM.
const parentCount = intFromConfig('/import/resources/container/attribute::parentCount', 1);
const containerResource = parentCount > 1 ? cont.res : undefined;
const containerRefID = cont.res.count > 0 ? cont.id : obj.id;
const boxSetup = config['/import/scripting/virtual-layout/boxlayout/box'];
const chainSetup = config['/import/scripting/virtual-layout/boxlayout/chain/audio'];
const boxes = [
BK_audioRoot,
BK_audioAll,
BK_audioAllArtists,
BK_audioAllGenres,
BK_audioAllAlbums,
BK_audioAllYears,
BK_audioAllComposers,
BK_audioAllSongs,
BK_audioAllTracks,
BK_audioArtistChronology,
BK_audioAllDirectories
];
const _Chain = prepareChains(boxes, boxSetup, chainSetup);
const chain = {
audio: _Chain[BK_audioRoot],
allAudio: _Chain[BK_audioAll],
allArtists: _Chain[BK_audioAllArtists],
allGenres: _Chain[BK_audioAllGenres],
allAlbums: _Chain[BK_audioAllAlbums],
allYears: _Chain[BK_audioAllYears],
allComposers: _Chain[BK_audioAllComposers],
allSongs: _Chain[BK_audioAllSongs],
allFull: _Chain[BK_audioAllTracks],
allDirectories: _Chain[BK_audioAllDirectories],
allFullArtist: {
id: boxSetup[BK_audioAllTracks].id,
title: boxSetup[BK_audioAllTracks].title,
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: boxSetup[BK_audioAllTracks].class,
metaData: {},
},
artistChronology: _Chain[BK_audioArtistChronology],
artist: {
title: audio.artists[0],
location: audio.artists[0],
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: UPNP_CLASS_CONTAINER_MUSIC_ARTIST,
metaData: {},
res: containerResource,
aux: obj.aux,
refID: containerRefID
},
album: {
searchable: false,
title: audio.album,
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: UPNP_CLASS_CONTAINER_MUSIC_ALBUM,
metaData: {},
res: containerResource,
aux: obj.aux,
refID: containerRefID
},
genre: {
title: audio.genre,
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: UPNP_CLASS_CONTAINER_MUSIC_GENRE,
metaData: {},
res: containerResource,
aux: obj.aux,
refID: containerRefID
},
year: {
title: audio.date,
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: UPNP_CLASS_CONTAINER,
metaData: {},
res: containerResource,
aux: obj.aux,
refID: containerRefID
},
composer: {
title: audio.composer,
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: UPNP_CLASS_CONTAINER_MUSIC_COMPOSER,
metaData: {},
res: containerResource,
aux: obj.aux,
refID: containerRefID
},
};
chain.audio.metaData[M_CONTENT_CLASS] = [UPNP_CLASS_AUDIO_ITEM];
chain.album.metaData[M_ARTIST] = audio.artists;
chain.album.metaData[M_ALBUMARTIST] = [audio.albumArtist];
chain.album.metaData[M_GENRE] = [audio.genre];
chain.album.metaData[M_DATE] = obj.metaData[M_DATE];
chain.album.metaData[M_UPNP_DATE] = obj.metaData[M_UPNP_DATE];
chain.album.metaData[M_ALBUM] = [audio.album];
chain.artist.metaData[M_ARTIST] = audio.artists;
chain.artist.metaData[M_ALBUMARTIST] = [audio.albumArtist];
chain.genre.metaData[M_GENRE] = [audio.genre];
chain.year.metaData[M_DATE] = [audio.date];
chain.year.metaData[M_UPNP_DATE] = [audio.date];
chain.composer.metaData[M_COMPOSER] = [audio.composer];
const result = [];
createUserChain(obj, audio, _Chain, boxSetup, chainSetup, result, rootPath);
var albumSearch = false;
var artistSearch = false;
var genreSearch = false;
var container;
if (boxSetup[BK_audioAll].enabled) {
container = addContainerTree([chain.audio, chain.allAudio]);
result.push(addCdsObject(obj, container, rootPath));
}
// Directories
if (boxSetup[BK_audioAllDirectories].enabled) {
var dir = getRootPath(rootPath, obj.location);
if (dir && dir.length > 0) {
var tree = [chain.audio, chain.allDirectories];
for (var i = 0; i < dir.length; i++) {
tree = tree.concat({ title: dir[i], objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER });
}
tree[tree.length - 1].upnpclass = containerType;
tree[tree.length - 1].metaData = {};
tree[tree.length - 1].res = containerResource;
tree[tree.length - 1].aux = obj.aux;
tree[tree.length - 1].refID = containerRefID;
result.push(addCdsObject(obj, addContainerTree(tree), rootPath));
}
}
const artCnt = audio.artists.length;
if (boxSetup[BK_audioAllSongs].enabled && boxSetup[BK_audioAllArtists].enabled) {
chain.artist.searchable = boxSetup[BK_audioAllArtists].searchable && !artistSearch;
artistSearch = artistSearch || chain.artist.searchable;
for (var i = 0; i < artCnt; i++) {
chain.artist.title = audio.artists[i];
container = addContainerTree([chain.audio, chain.allArtists, chain.artist, chain.allSongs]);
result.push(addCdsObject(obj, container, rootPath));
}
}
if (boxSetup[BK_audioAllTracks].enabled) {
const titleBackup = obj.title;
var prefix = '';
if (audio.artistFull) {
prefix = audio.artistFull;
}
if (audio.albumFull) {
prefix = prefix + ' - ' + audio.albumFull + ' - ';
} else {
prefix = prefix + ' - ';
}
obj.title = prefix + audio.title;
container = addContainerTree([chain.audio, chain.allFull]);
result.push(addCdsObject(obj, container, rootPath));
if (boxSetup[BK_audioAllArtists].enabled) {
for (var i = 0; i < artCnt; i++) {
chain.artist.title = audio.artists[i];
container = addContainerTree([chain.audio, chain.allArtists, chain.artist, chain.allFullArtist]);
result.push(addCdsObject(obj, container, rootPath));
}
}
obj.title = titleBackup; // Restore the title
}
// 'chain.album' is NOT searchable by default.
// it shall be enabled at least in one of the following trees:
// - chain.audio/chain.allArtists/chain.artist/chain.album (boxSetup[BK_audioAllArtists].enabled)
// - chain.audio/chain.allAlbums/chain.album (boxSetup[BK_audioAllAlbums].enabled)
//
// If you change the sequence of if's below, you might get into issues!
if (boxSetup[BK_audioAllArtists].enabled) {
const titleBackup = obj.title;
obj.title = audio.track + audio.title;
chain.album.searchable = boxSetup[BK_audioAllArtists].searchable && !albumSearch;
albumSearch = albumSearch || chain.album.searchable;
for (var i = 0; i < artCnt; i++) {
chain.artist.title = audio.artists[i];
container = addContainerTree([chain.audio, chain.allArtists, chain.artist, chain.album]);
result.push(addCdsObject(obj, container, rootPath));
}
obj.title = titleBackup; // Restore the title
}
if (boxSetup[BK_audioAllAlbums].enabled) {
const titleBackup = obj.title;
obj.title = audio.track + audio.title;
chain.album.searchable = boxSetup[BK_audioAllAlbums].searchable && !albumSearch;
albumSearch = albumSearch || chain.album.searchable;
chain.album.location = getRootPath(rootPath, obj.location).join('_');
container = addContainerTree([chain.audio, chain.allAlbums, chain.album]);
chain.album.location = '';
result.push(addCdsObject(obj, container, rootPath));
chain.album.searchable = false;
obj.title = titleBackup; // Restore the title
}
if (boxSetup[BK_audioAllGenres].enabled) {
chain.genre.searchable = boxSetup[BK_audioAllGenres].searchable && !genreSearch;
genreSearch = genreSearch || chain.genre.searchable;
const genCnt = audio.genres.length;
for (var j = 0; j < genCnt; j++) {
chain.genre.title = audio.genres[j];
chain.genre.metaData[M_GENRE] = [audio.genres[j]];
container = addContainerTree([chain.audio, chain.allGenres, chain.genre]);
result.push(addCdsObject(obj, container, rootPath));
}
chain.genre.searchable = false;
}
if (boxSetup[BK_audioAllYears].enabled) {
container = addContainerTree([chain.audio, chain.allYears, chain.year]);
result.push(addCdsObject(obj, container, rootPath));
}
if (boxSetup[BK_audioAllComposers].enabled) {
container = addContainerTree([chain.audio, chain.allComposers, chain.composer]);
result.push(addCdsObject(obj, container, rootPath));
}
if (boxSetup[BK_audioArtistChronology].enabled && boxSetup[BK_audioAllArtists].enabled) {
chain.album.searchable = boxSetup[BK_audioArtistChronology].searchable && !albumSearch;
albumSearch = albumSearch || chain.album.searchable;
chain.album.title = audio.date + " - " + audio.album;
for (var i = 0; i < artCnt; i++) {
chain.artist.title = audio.artists[i];
container = addContainerTree([chain.audio, chain.allArtists, chain.artist, chain.artistChronology, chain.album]);
result.push(addCdsObject(obj, container, rootPath));
}
chain.album.title = audio.album; // Restore the title;
}
return result;
}
// doc-add-audio-end
function importAudioStructured(obj, cont, rootPath, autoscanId, containerType) {
// first gather data
const audio = getAudioDetails(obj);
obj.sortKey = '';
const scriptOptions = config['/import/scripting/virtual-layout/script-options/script-option'];
// Extra code for correct display of albums with various artists (usually collections)
var tracktitle = audio.track + audio.title;
audio.albumArtist = audio.album + ' - ' + audio.artists.join(' / ');
if (audio.description) {
if (audio.description.toUpperCase() === 'VARIOUS') {
audio.albumArtist = audio.album + ' - Various';
tracktitle = tracktitle + ' - ' + audio.artists.join(' / ');
}
}
// set option in config if you want to have track numbers in front of the title
if (!scriptOptions || scriptOptions['trackNumbers'] !== 'show')
audio.track = '';
// set option in config if you want to have channel numbers in front of the title
if (!scriptOptions || scriptOptions['channelCount'] !== 'show')
audio.channels = '';
audio.genre = mapGenre(audio.genre).value;
const boxConfig = {
divChar: stringFromConfig('/import/scripting/virtual-layout/structured-layout/attribute::div-char', '-'),
};
boxConfig.singleLetterBoxSize = 2 * boxConfig.divChar.length + 1;
const boxSetup = config['/import/scripting/virtual-layout/boxlayout/box'];
const chainSetup = config['/import/scripting/virtual-layout/boxlayout/chain/audio'];
const parentCount = intFromConfig('/import/resources/container/attribute::parentCount', 1);
const containerResource = parentCount > 1 ? cont.res : undefined;
const containerRefID = cont.res.count > 0 ? cont.id : obj.id;
const boxes = [
BK_audioStructuredAllArtists,
BK_audioStructuredAllGenres,
BK_audioStructuredAllAlbums,
BK_audioStructuredAllYears,
BK_audioStructuredAllTracks
];
const _Chain = prepareChains(boxes, boxSetup, chainSetup);
const chain = {
allArtists: _Chain[BK_audioStructuredAllArtists],
allGenres: _Chain[BK_audioStructuredAllGenres],
allAlbums: _Chain[BK_audioStructuredAllAlbums],
allYears: _Chain[BK_audioStructuredAllYears],
allTracks: _Chain[BK_audioStructuredAllTracks],
entryAllLevel1: {
title: allbox(boxSetup[BK_audioStructuredAllArtistTracks].title, 6, boxConfig.divChar),
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: UPNP_CLASS_CONTAINER
},
entryAllLevel2: {
title: allbox(boxSetup[BK_audioStructuredAllArtistTracks].title, 9, boxConfig.divChar),
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: UPNP_CLASS_CONTAINER
},
entryAllLevel3: {
title: allbox(boxSetup[BK_audioStructuredAllArtistTracks].title, 9, boxConfig.divChar),
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: UPNP_CLASS_CONTAINER_MUSIC_ARTIST
},
abc: {
title: abcbox(audio.album, boxSetup[BK_audioStructuredAllAlbums].size, boxConfig.divChar),
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: UPNP_CLASS_CONTAINER
},
init: {
title: mapInitial(audio.album.charAt(0)),
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: UPNP_CLASS_CONTAINER
},
artist: {
title: audio.artists[0],
searchable: false,
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: UPNP_CLASS_CONTAINER_MUSIC_ARTIST,
metaData: [],
res: containerResource,
aux: obj.aux,
refID: containerRefID
},
album_artist: {
title: audio.albumArtist,
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: UPNP_CLASS_CONTAINER_MUSIC_ARTIST,
metaData: [],
res: containerResource,
aux: obj.aux,
refID: containerRefID
},
album: {
title: audio.album,
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: containerType,
metaData: [],
res: containerResource,
aux: obj.aux,
refID: containerRefID
},
genre: {
title: audio.genre,
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: UPNP_CLASS_CONTAINER_MUSIC_GENRE,
metaData: [],
res: containerResource,
aux: obj.aux,
refID: containerRefID
},
decade: {
title: audio.decade,
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: UPNP_CLASS_CONTAINER
},
date: {
title: audio.date,
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: UPNP_CLASS_CONTAINER
},
};
chain.allArtists.metaData[M_CONTENT_CLASS] = [UPNP_CLASS_AUDIO_ITEM];
chain.album.metaData[M_ARTIST] = [audio.albumArtist];
chain.album.metaData[M_ALBUMARTIST] = [audio.albumArtist];
chain.album.metaData[M_GENRE] = [audio.genre];
chain.album.metaData[M_DATE] = obj.metaData[M_DATE];
chain.album.metaData[M_UPNP_DATE] = obj.metaData[M_UPNP_DATE];
chain.album.metaData[M_ALBUM] = [audio.album];
chain.artist.metaData[M_ARTIST] = audio.artists;
chain.artist.metaData[M_ALBUMARTIST] = audio.artists;
chain.album_artist.metaData[M_ARTIST] = [audio.albumArtist];
chain.album_artist.metaData[M_ALBUMARTIST] = [audio.albumArtist];
var isSingleCharBox = boxConfig.singleLetterBoxSize >= chain.abc.title.length;
const result = [];
createUserChain(obj, audio, _Chain, boxSetup, chainSetup, result, rootPath);
var albumSearch = false;
var artistSearch = false;
var genreSearch = false;
obj.title = tracktitle;
chain.album.searchable = boxSetup[BK_audioStructuredAllAlbums].searchable && !artistSearch;
albumSearch = albumSearch || chain.album.searchable;
var container = addContainerTree(isSingleCharBox ? [chain.allAlbums, chain.abc, chain.album_artist] : [chain.allAlbums, chain.abc, chain.init, chain.album_artist]);
result.push(addCdsObject(obj, container, rootPath));
container = addContainerTree([chain.allAlbums, chain.abc, chain.entryAllLevel2, chain.album_artist]);
result.push(addCdsObject(obj, container, rootPath));
container = addContainerTree([chain.allAlbums, chain.entryAllLevel1, chain.album_artist]);
result.push(addCdsObject(obj, container, rootPath));
// Artist
const artCnt = audio.artists.length;
var i;
// Ensure the ARTIST entries in entryAllLevel1 are searchable and nowhere else
chain.artist.searchable = boxSetup[BK_audioStructuredAllArtists].searchable && !artistSearch;
artistSearch = artistSearch || chain.artist.searchable;
const entryAllLevel1ArtistSearch = chain.artist.searchable;
// A track may be interpreted by more than one artist
for (i = 0; i < artCnt; i++) {
obj.title = audio.title + ' (' + audio.album + ', ' + audio.date + ')';
chain.artist.title = audio.artists[i];
chain.artist.searchable = entryAllLevel1ArtistSearch;
container = addContainerTree([chain.allArtists, chain.entryAllLevel1, chain.artist]);
result.push(addCdsObject(obj, container, rootPath));
chain.artist.searchable = false;
chain.abc.title = abcbox(audio.artists[i], boxSetup[BK_audioStructuredAllArtists].size, boxConfig.divChar);
isSingleCharBox = boxConfig.singleLetterBoxSize >= chain.abc.title.length;
container = addContainerTree([chain.allArtists, chain.abc, chain.entryAllLevel2, chain.artist]);
result.push(addCdsObject(obj, container, rootPath));
chain.init.title = mapInitial(audio.artists[i].charAt(0));
container = addContainerTree(isSingleCharBox ? [chain.allArtists, chain.abc, chain.artist, chain.entryAllLevel3] : [chain.allArtists, chain.abc, chain.init, chain.artist, chain.entryAllLevel3]);
result.push(addCdsObject(obj, container, rootPath));
obj.title = tracktitle;
chain.album.title = audio.album + ' (' + audio.date + ')';
container = addContainerTree(isSingleCharBox ? [chain.allArtists, chain.abc, chain.artist, chain.album] : [chain.allArtists, chain.abc, chain.init, chain.artist, chain.album]);
result.push(addCdsObject(obj, container, rootPath));
}
// Genre
if (boxSetup[BK_audioStructuredAllGenres].enabled) {
obj.title = audio.title + ' - ' + audio.artistFull;
chain.genre.searchable = boxSetup[BK_audioStructuredAllGenres].searchable && !genreSearch;
genreSearch = genreSearch || chain.genre.searchable;
chain.entryAllLevel2.upnpclass = UPNP_CLASS_CONTAINER_MUSIC_GENRE;
const genCnt = audio.genres.length;
for (var j = 0; j < genCnt; j++) {
chain.genre.title = audio.genres[j];
chain.genre.metaData[M_GENRE] = [audio.genres[j]];
container = addContainerTree([chain.allGenres, chain.genre, chain.entryAllLevel1]);
result.push(addCdsObject(obj, container, rootPath));
}
chain.album_artist.searchable = !artistSearch;
artistSearch = artistSearch || chain.album_artist.searchable;
chain.abc.title = abcbox(audio.album, boxSetup[BK_audioStructuredAllGenres].size, boxConfig.divChar);
isSingleCharBox = boxConfig.singleLetterBoxSize >= chain.abc.title.length;
for (var j = 0; j < genCnt; j++) {
chain.genre.title = audio.genres[j];
chain.genre.metaData[M_GENRE] = [audio.genres[j]];
container = addContainerTree(isSingleCharBox ? [chain.allGenres, chain.genre, chain.abc, chain.album_artist] : [chain.allGenres, chain.genre, chain.abc, chain.init, chain.album_artist]);
result.push(addCdsObject(obj, container, rootPath));
}
}
chain.album_artist.searchable = false;
// Tracks
if (boxSetup[BK_audioStructuredAllTracks].enabled) {
obj.title = audio.title + ' - ' + audio.artists.join(' / ') + ' (' + audio.album + ', ' + audio.date + ')';
chain.abc.title = abcbox(audio.title, boxSetup[BK_audioStructuredAllTracks].size, boxConfig.divChar);
isSingleCharBox = boxConfig.singleLetterBoxSize >= chain.abc.title.length;
chain.init.title = mapInitial(audio.title.charAt(0));
chain.init.upnpclass = UPNP_CLASS_CONTAINER_MUSIC_ARTIST;
container = addContainerTree(isSingleCharBox ? [chain.allTracks, chain.abc] : [chain.allTracks, chain.abc, chain.init]);
result.push(addCdsObject(obj, container, rootPath));
obj.title = audio.title + ' - ' + audio.artistFull;
container = addContainerTree([chain.allTracks, chain.entryAllLevel1]);
result.push(addCdsObject(obj, container, rootPath));
}
// Sort years into decades
if (boxSetup[BK_audioStructuredAllYears].enabled) {
obj.title = audio.title + ' - ' + audio.artistFull;
container = addContainerTree([chain.allYears, chain.decade, chain.entryAllLevel3]);
result.push(addCdsObject(obj, container, rootPath));
container = addContainerTree([chain.allYears, chain.decade, chain.date, chain.entryAllLevel3]);
result.push(addCdsObject(obj, container, rootPath));
obj.title = tracktitle;
chain.album.title = audio.album;
container = addContainerTree([chain.allYears, chain.decade, chain.date, chain.artist, chain.album]);
result.push(addCdsObject(obj, container, rootPath));
}
return result;
}
// Create layout that has a slot for each initial of the album artist
function importAudioInitial(obj, cont, rootPath, autoscanId, containerType) {
const audio = getAudioDetails(obj);
const scriptOptions = config['/import/scripting/virtual-layout/script-options/script-option'];
obj.title = audio.title;
obj.sortKey = '';
// set option in config if you want to remove track numbers in front of the title
if (scriptOptions && scriptOptions['trackNumbers'] === 'hide')
audio.track = '';
const specialGenre = new RegExp(scriptOptions && scriptOptions['specialGenre'] ? scriptOptions['specialGenre'] : 'NotDefinedGenre');
const spokenGenre = new RegExp(scriptOptions && scriptOptions['spokenGenre'] ? scriptOptions['spokenGenre'] : 'Audio Book|H.*rspiel|Stories|Gesprochen|Comedy|Spoken');
if (audio.desc) {
obj.description = audio.desc;
}
// We finally gathered all data that we need, so let's create a
// nice layout for our audio files.
// The UPnP class argument to addCdsObject() is optional, if it is
// not supplied the default UPnP class will be used. However, it
// is suggested to correctly set UPnP classes of containers and
// objects - this information may be used by some renderers to
// identify the type of the container and present the content in a
// different manner.
const parentCount = intFromConfig('/import/resources/container/attribute::parentCount', 1);
const containerResource = parentCount > 1 ? cont.res : undefined;
const containerRefID = cont.res.count > 0 ? cont.id : obj.id;
const boxSetup = config['/import/scripting/virtual-layout/boxlayout/box'];
const chainSetup = config['/import/scripting/virtual-layout/boxlayout/chain/audio'];
const boxes = [
BK_audioRoot,
BK_audioAll,
BK_audioAllArtists,
BK_audioAllGenres,
BK_audioAllAlbums,
BK_audioAllYears,
BK_audioAllComposers,
BK_audioAllSongs,
BK_audioAllTracks,
BK_audioArtistChronology,
BK_audioInitialAllArtistTracks,
BK_audioInitialAbc,
BK_audioInitialAudioBookRoot,
BK_audioInitialAllBooks];
const _Chain = prepareChains(boxes, boxSetup, chainSetup);
const chain = {
audio: _Chain[BK_audioRoot],
allAudio: _Chain[BK_audioAll],
allArtists: _Chain[BK_audioAllArtists],
allGenres: _Chain[BK_audioAllGenres],
allAlbums: _Chain[BK_audioAllAlbums],
allYears: _Chain[BK_audioAllYears],
allComposers: _Chain[BK_audioAllComposers],
allSongs: _Chain[BK_audioAllSongs],
allFull: _Chain[BK_audioAllTracks],
allFullArtist: {
id: boxSetup[BK_audioAllTracks].id,
title: boxSetup[BK_audioAllTracks].title,
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: boxSetup[BK_audioAllTracks].class,
metaData: {},
},
artistChronology: _Chain[BK_audioArtistChronology],
all000: {
id: boxSetup[BK_audioInitialAllArtistTracks].id,
title: boxSetup[BK_audioInitialAllArtistTracks].title,
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: boxSetup[BK_audioInitialAllArtistTracks].class,
upnpShortcut: boxSetup[BK_audioInitialAllArtistTracks].upnpShortcut,
metaData: {},
res: parentCount > 0 ? cont.res : undefined,
aux: obj.aux,
refID: containerRefID
},
abc: _Chain[BK_audioInitialAbc],
init: {
title: '_',
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: UPNP_CLASS_CONTAINER
},
artist: {
title: audio.albumArtist,
location: audio.albumArtist,
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: UPNP_CLASS_CONTAINER_MUSIC_ARTIST,
metaData: {},
res: containerResource,
aux: obj.aux,
refID: containerRefID
},
album: {
title: audio.album,
location: getRootPath(rootPath, obj.location).join('_'),
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: containerType,
metaData: {},
res: parentCount > 0 ? cont.res : undefined,
aux: obj.aux,
refID: containerRefID
},
genre: {
title: audio.genre,
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: UPNP_CLASS_CONTAINER_MUSIC_GENRE,
metaData: {},
res: containerResource,
aux: obj.aux,
refID: containerRefID
},
year: {
title: audio.date,
objectType: OBJECT_TYPE_CONTAINER,
searchable: true,
upnpclass: UPNP_CLASS_CONTAINER,
metaData: {},
res: containerResource,
aux: obj.aux,
refID: containerRefID
},
composer: {
title: audio.composer,
objectType: OBJECT_TYPE_CONTAINER,
searchable: true,
upnpclass: UPNP_CLASS_CONTAINER_MUSIC_COMPOSER,
metaData: {},
res: containerResource,
aux: obj.aux,
refID: containerRefID
},
};
const isAudioBook = obj.upnpclass === UPNP_CLASS_AUDIO_BOOK;
chain.audio.metaData[M_CONTENT_CLASS] = [UPNP_CLASS_AUDIO_ITEM];
chain.album.metaData[M_ARTIST] = [audio.albumArtist];
chain.album.metaData[M_ALBUMARTIST] = [audio.albumArtist];
chain.album.metaData[M_GENRE] = [audio.genre];
chain.album.metaData[M_DATE] = obj.metaData[M_DATE];
chain.album.metaData[M_UPNP_DATE] = obj.metaData[M_UPNP_DATE];
chain.album.metaData[M_ALBUM] = [audio.album];
chain.artist.metaData[M_ARTIST] = [audio.albumArtist];
chain.artist.metaData[M_ALBUMARTIST] = [audio.albumArtist];
chain.all000.metaData[M_ARTIST] = [audio.albumArtist];
chain.all000.metaData[M_ALBUMARTIST] = [audio.albumArtist];
chain.year.metaData[M_DATE] = [audio.date];
chain.year.metaData[M_UPNP_DATE] = [audio.date];
chain.composer.metaData[M_COMPOSER] = [audio.composer];
const result = [];
createUserChain(obj, audio, _Chain, boxSetup, chainSetup, result, rootPath);
var albumSearch = false;
var artistSearch = false;
var genreSearch = false;
var container;
var allAlbumsKey;
if (isAudioBook) {
allAlbumsKey = BK_audioInitialAudioBookRoot;
chain.audio = {
id: boxSetup[BK_audioInitialAudioBookRoot].id,
title: boxSetup[BK_audioInitialAudioBookRoot].title,
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: boxSetup[BK_audioInitialAudioBookRoot].class
};
chain.allAlbums = {
id: boxSetup[BK_audioInitialAllBooks].id,
title: boxSetup[BK_audioInitialAllBooks].title,
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: boxSetup[BK_audioInitialAllBooks].class
};
} else {
allAlbumsKey = BK_audioAllAlbums;
if (boxSetup[BK_audioAll].enabled) {
container = addContainerTree([chain.audio, chain.allAudio]);
result.push(addCdsObject(obj, container, rootPath));
}
if (boxSetup[BK_audioAllArtists].enabled) {
container = addContainerTree([chain.audio, chain.allArtists, chain.artist, chain.allSongs]);
result.push(addCdsObject(obj, container, rootPath));
}
}
var temp = '';
if (audio.artistFull) {
temp = audio.artistFull;
}
if (audio.albumFull) {
temp = temp + ' - ' + audio.albumFull + ' - ';
} else {
temp = temp + ' - ';
}
if (!isAudioBook) {
obj.title = temp + audio.title;
if (boxSetup[BK_audioAllTracks].enabled) {
container = addContainerTree([chain.audio, chain.allFull]);
result.push(addCdsObject(obj, container, rootPath));
if (boxSetup[BK_audioAllArtists].enabled) {
container = addContainerTree([chain.audio, chain.allArtists, chain.artist, chain.allFullArtist]);
result.push(addCdsObject(obj, container, rootPath));
}
}
}
// only searchable if not active for initials section
chain.artist.searchable = !boxSetup[BK_audioInitialAbc].searchable && boxSetup[BK_audioAllArtists].searchable && !artistSearch;
artistSearch = artistSearch || chain.artist.searchable;
obj.title = audio.track + audio.title;
if (boxSetup[BK_audioAllArtists].enabled) {
container = addContainerTree([chain.audio, chain.allArtists, chain.artist, chain.album]);
result.push(addCdsObject(obj, container, rootPath));
}
if (boxSetup[BK_audioAllAlbums].enabled) {
// only searchable if not active for initials section
chain.album.searchable = !boxSetup[BK_audioInitialAbc].searchable && boxSetup[allAlbumsKey].searchable && !albumSearch;
albumSearch = albumSearch || chain.album.searchable;
if (!isAudioBook && boxSetup[BK_audioInitialAllArtistTracks].enabled) {
obj.title = audio.album + " - " + audio.track + audio.title;
container = addContainerTree([chain.audio, chain.allAlbums, chain.artist, chain.all000]);
result.push(addCdsObject(obj, container, rootPath));
}
obj.title = audio.track + audio.title;
container = addContainerTree([chain.audio, chain.allAlbums, chain.artist, chain.album]);
result.push(addCdsObject(obj, container, rootPath));
chain.album.searchable = false;
obj.title = audio.track + audio.title;
// Remember, the server will sort all items by ID3 track if the
// container class is set to UPNP_CLASS_CONTAINER_MUSIC_ALBUM.
chain.all000.metaData[M_ARTIST] = [];
chain.all000.metaData[M_ALBUMARTIST] = [];
if (!isAudioBook && boxSetup[BK_audioInitialAllArtistTracks].enabled) {
chain.album.location = getRootPath(rootPath, obj.location).join('_');
container = addContainerTree([chain.audio, chain.allAlbums, chain.all000, chain.album]);
chain.album.location = '';
result.push(addCdsObject(obj, container, rootPath));
}
}
if (audio.disknr != '') {
if (specialGenre.exec(audio.genre)) {
if (audio.disknr.length == 1) {
chain.album.title = '0' + audio.disknr + ' - ' + audio.album;
} else {
chain.album.title = audio.disknr + ' - ' + audio.album;
}
}
}
var init = mapInitial(audio.albumArtist.charAt(0));
if (init && init !== '') {
chain.init.title = init;
}
const spokenMatch = spokenGenre.exec(audio.genre) ? true : false;
chain.artist.searchable = boxSetup[BK_audioInitialAbc].searchable && !artistSearch;
artistSearch = artistSearch || chain.artist.searchable;
chain.album.searchable = boxSetup[BK_audioInitialAbc].searchable && !albumSearch;
albumSearch = albumSearch || chain.album.searchable;
obj.title = audio.track + audio.title;
if (!isAudioBook) {
container = addContainerTree([chain.audio, chain.abc, chain.init, chain.artist, chain.album]);
result.push(addCdsObject(obj, container, rootPath));
}
chain.artist.searchable = false;
chain.album.searchable = false;
if (spokenMatch === false && !isAudioBook && boxSetup[BK_audioInitialAllArtistTracks].enabled) {
var part = '';
if (obj.metaData[M_UPNP_DATE] && obj.metaData[M_UPNP_DATE][0])
part = obj.metaData[M_UPNP_DATE][0] + ' - ';
obj.title = part + chain.album.title + " - " + audio.track + audio.title;
container = addContainerTree([chain.audio, chain.abc, chain.init, chain.artist, chain.all000]);
result.push(addCdsObject(obj, container, rootPath));
}
chain.album.title = audio.album;
if (boxSetup[BK_audioAllGenres].enabled) {
const genCnt = audio.genres.length;
for (var j = 0; j < genCnt; j++) {
var mapResult = mapGenre(audio.genres[j]);
var gMatch = 0;
if (mapResult.mapped) {
obj.title = temp + audio.track + audio.title;
gMatch = 1;
chain.genre.title = mapResult.value;
chain.all000.upnpclass = UPNP_CLASS_CONTAINER_MUSIC_GENRE;
chain.all000.metaData[M_GENRE] = [mapResult.value];
chain.genre.metaData[M_GENRE] = [mapResult.value];
if (spokenMatch === false && !isAudioBook && boxSetup[BK_audioInitialAllArtistTracks].enabled) {
container = addContainerTree([chain.audio, chain.allGenres, chain.genre, chain.all000]);
result.push(addCdsObject(obj, container, rootPath));
}
if (specialGenre.exec(chain.genre.title)) {
if (audio.disknr.length === 1) {
chain.album.title = '0' + audio.disknr + ' - ' + audio.album;
} else {
chain.album.title = audio.disknr + ' - ' + audio.album;
}
}
container = addContainerTree([chain.audio, chain.allGenres, chain.genre, chain.artist, chain.album]);
result.push(addCdsObject(obj, container, rootPath));
}
}
obj.title = temp + audio.title;
if (gMatch === 0) {
chain.genre.title = 'Other';
chain.genre.metaData[M_GENRE] = ['Other'];
container = addContainerTree([chain.audio, chain.allGenres, chain.genre]);
result.push(addCdsObject(obj, container, rootPath));
}
chain.genre.title = audio.genre;
chain.genre.metaData[M_GENRE] = [audio.genre];
chain.all000.upnpclass = UPNP_CLASS_CONTAINER;
chain.all000.metaData[M_GENRE] = [];
chain.genre.searchable = boxSetup[BK_audioAllGenres].searchable && !genreSearch;
genreSearch = genreSearch || chain.genre.searchable;
if (!isAudioBook && boxSetup[BK_audioInitialAllArtistTracks].enabled) {
container = addContainerTree([chain.audio, chain.allGenres, chain.all000, chain.genre]);
result.push(addCdsObject(obj, container, rootPath));
}
}
if (boxSetup[BK_audioAllYears].enabled) {
container = addContainerTree([chain.audio, chain.allYears, chain.year]);
result.push(addCdsObject(obj, container, rootPath));
}
if (boxSetup[BK_audioAllComposers].enabled && audio.composer !== 'None') {
container = addContainerTree([chain.audio, chain.allComposers, chain.composer]);
result.push(addCdsObject(obj, container, rootPath));
}
if (boxSetup[BK_audioArtistChronology].enabled && boxSetup[BK_audioAllArtists].enabled) {
chain.artist.searchable = boxSetup[BK_audioArtistChronology].searchable && !artistSearch;
artistSearch = artistSearch || chain.artist.searchable;
chain.album.searchable = boxSetup[BK_audioArtistChronology].searchable && !albumSearch;
albumSearch = albumSearch || chain.album.searchable;
chain.album.title = audio.date + " - " + audio.album;
container = addContainerTree([chain.audio, chain.allArtists, chain.artist, chain.artistChronology, chain.album]);
result.push(addCdsObject(obj, container, rootPath));
chain.album.title = audio.album; // Restore the title;
}
return result;
}
/*
AMENDMENTS:
make artist upnpclass = UPNP_CLASS_CONTAINER_MUSIC_ALBUM to get correct sort order for tracks
add function 'AlphaGroup' and chain.abc1/abc2 to extract and store group-by letter
add abc, Genre, Album to composer object
add composer to artists, albums objects
add abc to artists objects
*/
// based on https://github.com/gerbera/gerbera/discussions/3697#discussioncomment-15084809
function importAudioClassical(obj, cont, rootPath, autoscanId, containerType) {
const audio = getAudioDetails(obj);
obj.sortKey = '';
const scriptOptions = config['/import/scripting/virtual-layout/script-options/script-option'];
obj.title = audio.title;
// set option in config if you want to have track numbers in front of the title
if (!scriptOptions || scriptOptions['trackNumbers'] !== 'show')
audio.track = '';
// set option in config if you want to have channel numbers in front of the title
if (!scriptOptions || scriptOptions['channelCount'] !== 'show')
audio.channels = '';
audio.genre = mapGenre(audio.genre).value;
const parentCount = intFromConfig('/import/resources/container/attribute::parentCount', 1);
const containerResource = parentCount > 1 ? cont.res : undefined;
const containerRefID = cont.res.count > 0 ? cont.id : obj.id;
const boxSetup = config['/import/scripting/virtual-layout/boxlayout/box'];
const chainSetup = config['/import/scripting/virtual-layout/boxlayout/chain/audio'];
const boxes = [
BK_audioRoot,
BK_audioAllArtists,
BK_audioAllGenres,
BK_audioAllAlbums,
BK_audioAllComposers,
];
const _Chain = prepareChains(boxes, boxSetup, chainSetup);
const chain = {
audio: _Chain[BK_audioRoot],
allArtists: _Chain[BK_audioAllArtists],
allGenres: _Chain[BK_audioAllGenres],
allAlbums: _Chain[BK_audioAllAlbums],
allComposers: _Chain[BK_audioAllComposers],
artist: {
title: audio.artists[0],
location: audio.artists[0],
objectType: OBJECT_TYPE_CONTAINER,
/* upnpclass: UPNP_CLASS_CONTAINER_MUSIC_ARTIST, AMENDED TO GET CORRECT SORT ORDER FOR COMPOSER*/
upnpclass: UPNP_CLASS_CONTAINER_MUSIC_ALBUM,
metaData: {},
res: containerResource,
aux: obj.aux,
refID: containerRefID
},
album: {
searchable: false,
title: audio.album,
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: UPNP_CLASS_CONTAINER_MUSIC_ALBUM,
metaData: {},
res: containerResource,
aux: obj.aux,
refID: containerRefID
},
genre: {
title: audio.genre,
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: UPNP_CLASS_CONTAINER_MUSIC_GENRE,
metaData: {},
res: containerResource,
aux: obj.aux,
refID: containerRefID
},
composer: {
title: audio.composer,
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: UPNP_CLASS_CONTAINER_MUSIC_COMPOSER,
metaData: {},
res: containerResource,
aux: obj.aux,
refID: containerRefID
},
abc1: {
title: '',
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: UPNP_CLASS_CONTAINER,
metaData: {},
res: containerResource,
aux: obj.aux,
refID: containerRefID
},
abc2: {
title: '',
objectType: OBJECT_TYPE_CONTAINER,
upnpclass: UPNP_CLASS_CONTAINER,
metaData: {},
res: containerResource,
aux: obj.aux,
refID: containerRefID
},
};
chain.audio.metaData[M_CONTENT_CLASS] = [UPNP_CLASS_AUDIO_ITEM];
chain.album.metaData[M_ARTIST] = audio.artists;
chain.album.metaData[M_ALBUMARTIST] = [audio.albumArtist];
chain.album.metaData[M_GENRE] = [audio.genre];
chain.album.metaData[M_DATE] = obj.metaData[M_DATE];
chain.album.metaData[M_UPNP_DATE] = obj.metaData[M_UPNP_DATE];
chain.album.metaData[M_ALBUM] = [audio.album];
chain.artist.metaData[M_ARTIST] = audio.artists;
chain.artist.metaData[M_ALBUMARTIST] = [audio.albumArtist];
chain.genre.metaData[M_GENRE] = [audio.genre];
chain.composer.metaData[M_COMPOSER] = [audio.composer];
const result = [];
createUserChain(obj, audio, _Chain, boxSetup, chainSetup, result, rootPath);
var albumSearch = false;
var genreSearch = false;
var container;
const artCnt = audio.artists.length;
if (boxSetup[BK_audioAllArtists].enabled) {
const titleBackup = obj.title;
obj.title = audio.track + audio.title;
chain.album.searchable = boxSetup[BK_audioAllArtists].searchable && !albumSearch;
albumSearch = albumSearch || chain.album.searchable;
for (var i = 0; i < artCnt; i++) {
chain.artist.title = audio.artists[i];
chain.abc1.title = chain.artist.title.toUpperCase().substring(0, 1);
chain.abc2.title = chain.artist.title.toUpperCase().substring(0, 2);
container = addContainerTree([chain.audio, chain.allArtists, chain.abc1, chain.abc2, chain.artist, chain.composer, chain.album]);
result.push(addCdsObject(obj, container, rootPath));
}
obj.title = titleBackup; // Restore the title
}
if (boxSetup[BK_audioAllAlbums].enabled) {
const titleBackup = obj.title;
obj.title = audio.track + audio.title;
chain.album.searchable = boxSetup[BK_audioAllAlbums].searchable && !albumSearch;
albumSearch = albumSearch || chain.album.searchable;
chain.album.location = getRootPath(rootPath, obj.location).join('_');
container = addContainerTree([chain.audio, chain.allAlbums, chain.album, chain.composer]);
chain.album.location = '';
result.push(addCdsObject(obj, container, rootPath));
chain.album.searchable = !albumSearch;
obj.title = titleBackup; // Restore the title
}
if (boxSetup[BK_audioAllGenres].enabled) {
chain.genre.searchable = boxSetup[BK_audioAllGenres].searchable && !genreSearch;
genreSearch = genreSearch || chain.genre.searchable;
const genCnt = audio.genres.length;
for (var j = 0; j < genCnt; j++) {
chain.genre.title = audio.genres[j];
chain.genre.metaData[M_GENRE] = [audio.genres[j]];
container = addContainerTree([chain.audio, chain.allGenres, chain.genre, chain.composer, chain.album]);
result.push(addCdsObject(obj, container, rootPath));
}
chain.genre.searchable = !genreSearch;
}
if (boxSetup[BK_audioAllComposers].enabled) {
chain.abc1.title = chain.composer.title.toUpperCase().substring(0, 1);
container = addContainerTree([chain.audio, chain.allComposers, chain.abc1, chain.composer, chain.genre, chain.album, chain.artist]);
result.push(addCdsObject(obj, container, rootPath));
}
return result;
}Offline