Skip to content

Instantly share code, notes, and snippets.

@mill1000
Last active May 10, 2023 04:00
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mill1000/e07de721d3e76874db5c6bf5c6dda473 to your computer and use it in GitHub Desktop.
Save mill1000/e07de721d3e76874db5c6bf5c6dda473 to your computer and use it in GitHub Desktop.
Custom Gerbera Import Scripts
// Helper to access first element of an array if it exists
function get(item, fallback) {
return (item && item[0]) || fallback;
}
// Custom audio import script
function addAudioCustom(obj) {
var title = get(obj.metaData[M_TITLE], obj.title);
var artist = get(obj.metaData[M_ARTIST], "Unknown");
var album = get(obj.metaData[M_ALBUM], "Unknown");
var date = get(obj.metaData[M_DATE]);
var decade = "Unknown";
if (/.*T.*/.test(date)) {
// Date appears to be a file modification time, ignore it
date = "Unknown"
}
else {
// Get year from full data
var year = getYear(date);
// Update metadata
obj.metaData[M_UPNP_DATE] = [year];
// Compute decade
decade = date.substring(0, 3) + "0s"
}
// Update object title
obj.title = title;
const chain = {
// Container for all artists
artists: { title: "Artists", objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
// Container for each artist
artist: { searchable: true, title: artist, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER_MUSIC_ARTIST, metaData: {}, res: obj.res, aux: obj.aux, refID: obj.id },
// Container for all albums
albums: { title: "Albums", objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
// Container for each album
album: { searchable: true, title: album, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER_MUSIC_ALBUM, metaData: {}, res: obj.res, aux: obj.aux, refID: obj.id },
// Root container for decades
decades: { title: "Decades", objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
// Container for each decade
decade: { title: decade, objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
allTracks: { title: "All Tracks", objectType: OBJECT_TYPE_CONTAINER, upnpclass: UPNP_CLASS_CONTAINER },
};
// Set metadata on the album container
chain.album.metaData[M_ARTIST] = [artist];
chain.album.metaData[M_CREATOR] = [artist];
chain.album.metaData[M_ALBUMARTIST] = [artist];
chain.album.metaData[M_DATE] = [date];
chain.album.metaData[M_ALBUM] = [album];
// Set metadata on the artist container
chain.artist.metaData[M_ARTIST] = [artist];
chain.artist.metaData[M_ALBUMARTIST] = [artist];
// Group by artist, then album
var container = addContainerTree([chain.artists, chain.artist, chain.album]);
addCdsObject(obj, container);
// Make the artist and album unsearchable so it only shows up once in results
chain.album.searchable = false;
chain.artist.searchable = false;
// Group by album
container = addContainerTree([chain.albums, chain.album]);
addCdsObject(obj, container);
// Group by decade, then album
container = addContainerTree([chain.decades, chain.decade, chain.album]);
addCdsObject(obj, container);
// All tracks
container = addContainerTree([chain.allTracks]);
addCdsObject(obj, container);
}
// Custom Gerbera audio import script
if (getPlaylistType(orig.mimetype) === "") {
// Create a copy of the original object
var obj = orig;
// All virtual objects must reference an object in the PC-Directory
obj.refID = orig.id;
var mime = orig.mimetype.split("/")[0];
if (mime === "video" && obj.onlineservice === ONLINE_SERVICE_APPLE_TRAILERS) {
mime = "trailer";
} else if (orig.mimetype === "application/ogg") {
mime = (orig.theora === 1) ? "video" : "audio";
}
var audioLayout = config["/import/scripting/virtual-layout/attribute::audio-layout"] || "Default";
switch (mime) {
case "audio":
switch (audioLayout) {
case "Structured":
addAudioStructured(obj);
break;
case "Custom":
addAudioCustom(obj);
break;
default:
addAudio(obj);
break;
}
break;
default:
print("Ignored " + obj.location + " of type " + orig.mimetype);
break;
}
}
@TheDiscoPotato
Copy link

Hi there. I appreciate you posting your custom scripts. This is exactly what I was looking for. However, I'm having trouble implementing the audio_custom.js. I might be adding the script location incorrectly in the config.xml file. Would you possibly be able to share yours?

@mill1000
Copy link
Author

mill1000 commented Mar 8, 2022

This should be the relevant bit you need

<import hidden-files="no">
    <scripting script-charset="UTF-8">
      <common-script>/usr/local/share/gerbera/js/common.js</common-script>
      <playlist-script>/usr/local/share/gerbera/js/playlists.js</playlist-script>
      <custom-script>/home/rock/gerbera/gerbera-scripts/audio_custom.js</custom-script>
      <virtual-layout type="js" audio-layout="Custom">
        <import-script>/home/rock/gerbera/gerbera-scripts/import.js</import-script>
      </virtual-layout>
    </scripting>
  </import>

@TheDiscoPotato
Copy link

TheDiscoPotato commented Mar 8, 2022

Much appreciated. Looks like the trick was adding audio-layout="Custom". Cheers!

@thebream
Copy link

thebream commented Mar 23, 2022

Awesome example!

Just been playing with this, and found that to make the "searchable" attribute work as intended in the above audio_custom.js:

  // Make the artsit and album unsearchable so it only shows up once in results
  chain.album.searchable = false;
  chain.artist.searchable = false;

You need to add this to the SERVER section in your config.xml file
<upnp searchable-container-flag="yes" />

Otherwise, when you search for, say, an album called "Thriller" in your UPNP controller (e.g. BubbleUPnP), it will find it three times from:

  • Albums / Thriller
  • Artists / Michael Jackson / Thriller
  • Decades / 1980s / Thiller

@mill1000
Copy link
Author

That's correct. Good catch. Perhaps I should add a snippet of my config.xml for future reference.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment