Jellyfin NFO File Format: How Spotify-Saver Structures Album Metadata

Spotify-Saver generates Jellyfin-compatible NFO files by serializing album metadata into a standardized XML document containing track listings, artist information, and external database identifiers.

The open-source tool spotify-saver creates structured metadata files for self-hosted media servers. When downloading albums with the --nfo flag enabled, it outputs an album.nfo file that follows the Jellyfin NFO file format specification, ensuring rich album information displays correctly in your media library.

XML Schema Overview

The NFO file uses XML with a root <album> element. According to the source code in spotifysaver/metadata/nfo_generator.py, the generator constructs this document using Python's minidom module with pretty-printing enabled via minidom.toprettyxml(). The file is saved as album.nfo in the destination folder (output_dir).

Album-Level Metadata Elements

The generator populates top-level elements using data from Spotify and optional TheAudioDB enrichment:

  • <title> – Album name sourced directly from Spotify data (line 61)
  • <year> – Four-digit release year derived from the release_date field (lines 62-63)
  • <premiered> and <releasedate> – Full ISO date string (YYYY-MM-DD) representing the official release date (lines 63-65)
  • <runtime> – Total album length in minutes, calculated by summing all track durations and dividing by 60 (lines 68-70)
  • <genre> – Repeatable element for each genre associated with the album (lines 71-74)
  • <artist> – Repeatable element listing individual artist names as they appear on the album (lines 75-76)
  • <albumartist> – Single element containing comma-separated artist names (line 77)

Track Listing Structure

Each track appears as a nested <track> element containing specific child nodes:

  • <position> – The track number on the album (line 82)
  • <title> – The track title (line 83)
  • <duration> – Track length formatted as MM:SS using the internal _format_duration helper (line 84)

External Identifiers and Descriptions

The NFO includes fields for third-party database integration and editing controls:

  • <review> and <outline> – Album description pulled from TheAudioDB (lines 92-95)
  • <audiodbalbumid> and <audiodbartistid> – TheAudioDB identifiers when available (lines 96-99)
  • <musicbrainzalbumid> and <musicbrainzalbumartistid> – MusicBrainz UUIDs for precise matching (lines 100-103)
  • <lockdata> – Fixed to "false" to allow metadata edits within Jellyfin's interface (line 106)
  • <dateadded> – Timestamp of NFO generation in YYYY-MM-DD HH:MM:SS format (lines 107-108)

How the NFO Generator Works

The NFOGenerator class in spotifysaver/metadata/nfo_generator.py handles the XML construction. It enriches base Spotify data with optional TheAudioDB metadata through the _get_theaudiodb_data method when a matching record is found. The generator accepts an Album model instance and an output_dir path, writing the final XML to album.nfo in the specified directory.

Generating NFO Files in Practice

To generate a Jellyfin-compatible NFO file programmatically:

from pathlib import Path
from spotifysaver.models.album import Album
from spotifysaver.metadata import NFOGenerator

# Assume we already have a fully-populated Album instance

album: Album = ...   # e.g. fetched via SpotifyApi and enriched with tracks

# Directory where the album folder resides (Jellyfin expects the .nfo next to the audio files)

output_dir = Path("/media/music/YourArtist/YourAlbum")

# Generate the NFO file

NFOGenerator.generate(album, output_dir)

print(f"NFO written to {output_dir / 'album.nfo'}")

This produces a file structured as follows:

<?xml version="1.0" ?>
<album>
  <title>My Album</title>
  <year>2023</year>
  <premiered>2023-07-14</premiered>
  <releasedate>2023-07-14</releasedate>
  <runtime>42</runtime>
  <genre>Rock</genre>
  <artist>My Artist</artist>
  <albumartist>My Artist</albumartist>

  <track>
    <position>1</position>
    <title>First Song</title>
    <duration>03:45</duration>
  </track>
  <!-- additional <track> entries … -->

  <review>...description from TheAudioDB...</review>
  <outline>...same description...</outline>
  <audiodbalbumid>12345</audiodbalbumid>
  <musicbrainzalbumid>abcdef-12345-...</musicbrainzalbumid>
  <lockdata>false</lockdata>
  <dateadded>2026-03-02 14:32:10</dateadded>
</album>

Summary

  • Root element: The Jellyfin NFO file uses an <album> root containing all metadata.
  • Track details: Individual <track> elements store position, title, and MM:SS duration.
  • External IDs: The generator includes TheAudioDB and MusicBrainz identifiers when available.
  • Editability: The <lockdata>false</lockdata> setting ensures Jellyfin allows user modifications.
  • Source location: All logic resides in spotifysaver/metadata/nfo_generator.py.

Frequently Asked Questions

What is the root element of a Jellyfin NFO file created by Spotify-Saver?

The root element is <album>, which serves as the container for all album-level information including title, year, genres, and track listings. This structure is defined at line 58 of spotifysaver/metadata/nfo_generator.py.

How does Spotify-Saver calculate the runtime value in the NFO?

The generator sums the total seconds of all tracks in the album, then divides by 60 to produce an integer value representing the total runtime in minutes. This calculation occurs at lines 68-70 of the source file.

Can Jellyfin edit metadata from these NFO files?

Yes. The <lockdata> element is explicitly set to "false" (line 106), which tells Jellyfin that the metadata is not locked and can be edited through the web interface. If set to "true", Jellyfin would treat the data as read-only.

What external databases does the NFO generator reference?

The generator references TheAudioDB for descriptions and album/artist identifiers, and MusicBrainz for UUIDs. These are stored in elements like <audiodbalbumid>, <audiodbartistid>, <musicbrainzalbumid>, and <musicbrainzalbumartistid>, providing stable identifiers for media server matching.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →