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

> Explore the Jellyfin NFO file format Spotify-Saver creates. Learn how album metadata, track listings, and artist info are structured in this XML document for seamless integration.

- Repository: [Gabriel Baute/spotify-saver](https://github.com/gabrielbaute/spotify-saver)
- Tags: api-reference
- Published: 2026-03-02

---

**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`](https://github.com/gabrielbaute/spotify-saver/blob/main/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`](https://github.com/gabrielbaute/spotify-saver/blob/main/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:

```python
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
<?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`](https://github.com/gabrielbaute/spotify-saver/blob/main/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`](https://github.com/gabrielbaute/spotify-saver/blob/main/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.