## File: README.md

# Libation: Liberate your Library

**Libation** is a free, open-source application for downloading and managing your Audible audiobooks. It decrypts your library, removes DRM, and lets you own your audiobooks forever.

> [](https://getlibation.com) **[Which version should I download?](https://getlibation.com)** — get a recommended download for your system on our site.

## Features

- **Unlock Your Library**: Download and remove DRM from your audiobooks.
- **Cross-Platform**: Fully supported on Windows, macOS, and Linux.
- **Region Support**: Works with Audible regions US, UK, Canada, Germany, France, Australia, Japan, India, and Spain.
- **Advanced Organization**: Search, filter, and tag your books.
- **Fast & Efficient**: Powered by AAXClean for fast decryption without heavy dependencies like ffmpeg.
- **Import**: Easily import your existing library, including cover art.

## Getting started with Libation

All documentation has been moved to our new site: [getlibation.com](https://getlibation.com). Or jump to the important bits:

* [Getting Started](https://getlibation.com/docs/getting-started)
* [Download](https://github.com/rmcrackan/Libation/releases/latest)
* [Issues, bugs, and requests](https://github.com/rmcrackan/Libation/issues)
* [Documentation](https://getlibation.com/docs/index)

## Development

Grab the latest release for your platform from the [Releases Page](https://github.com/rmcrackan/Libation/releases/latest).

## Documentation

Comprehensive documentation is available in the `docs` directory and on our [Documentation Site](https://getlibation.com/docs).

- [Getting Started](https://getlibation.com/docs/getting-started)
- [FAQ](https://getlibation.com/docs/frequently-asked-questions)
- [Spatial Audio & DRM](https://getlibation.com/docs/advanced/spatial-audio)

## Development & Contributing

We welcome contributions!

- **[Development Getting Started](https://getlibation.com/docs/development/getting-started)**: Setup your environment.
- **[Contribute](https://getlibation.com/docs/development/contribute)**: How to contribute code.
- **[Testing Changes](https://getlibation.com/docs/development/testing)**: Run the tests and seed a demo library for UI testing.
- **[Website & Docs](https://getlibation.com/docs/development/website)**: How to run and improve the documentation.
- **[Linux Setup (Nix)](https://getlibation.com/docs/development/nix-linux-setup)**: Nix-based environment setup.

## Community & Support

- **[Issues](https://github.com/rmcrackan/Libation/issues)**: Report bugs or request features.
- **[PayPal](https://paypal.me/mcrackan?locale.x=en_us)**: Support the project if you find it useful.

## License

Libation is released under the GPL-3.0 License

---

If you found this useful, tell a friend. If you found this REALLY useful, you can [donate here](https://getlibation.com/donate)
...or just tell more friends. As long as I'm maintaining this software, it will remain free and open source.

---

## File: docs/features/audio-file-formats.md

# Audio Formats Produced by Libation

Libation will download audio in a number of different audio formats, depending on the settings you choose within Libation and the per-title availability of audio formats from Audible. The Libation settings which affect the format downloaded by Libation are shown in the Settings menu screenshot below.

> [!NOTE]
>
> - Audiobook file extensions are either `.m4b` or `.mp3`. Libation uses the `.m4b` file extension for all non-MP3 files, regardless of the audio codec contained therein. Some media players don't recognize the `.m4b` file extension and may require the extension be changed to `.m4a` or `.mp4`.
> - Most (but not all) podcasts are delivered by Audible as native MP3 files. None of the following audio formats and settings discussions pertain to those podcasts because MP3s have no DRM, and those episodes are copied directly to their output folders.



## Settings Summary

### Audio Quality to Request From Audible

Audiobooks can be requested from Audible as "Normal" quality or "High" quality, matching the settings in the Audible mobile apps. This setting affects the audio bitrate and, sometimes, the number of audio channels. This setting has no effect on the _audio codec_.

### Use Widevine DRM

When this setting is disabled, all audiobooks will be downloaded using Audible's in-house DRM (AAX(C)) in the [AAC-LC](#aac-lc) format.
When this setting is enabled, Libation will request audio files protected by Google's Widevine DRM scheme. This unlocks [Request xHE-AAC Codec](#request-xhe-aac-codec) for higher-quality stereo downloads on titles where Audible delivers them via Widevine L3.

If you don't enable **Request xHE-AAC Codec**, then enabling **Use Widevine DRM** will have no practical effect in nearly all circumstances. Audiobooks will be downloaded in the same [AAC-LC](#aac-lc) format with the same bitrate and the same number of audio channels. On rare occasions, enabling **Use Widevine DRM** without xHE-AAC will result in audio files with a different bitrate.

### Request xHE-AAC Codec

Enable this setting to request audiobooks in the [xHE-AAC](#xhe-aac) format. This codec is generally better quality than the [AAC-LC](#aac-lc) codec at the same bitrate, but it isn't as commonly supported by media players, so you may have some difficulty playing these audiobooks. The highest bitrate version of some audiobooks is only available as [xHE-AAC](#xhe-aac).

### Download My Books in the Original Audio Format (Lossless)

If selected, Audiobooks will be downloaded and saved in the format delivered by audible (which depends on the settings explained above). Libation will not change the audio.

### Download My Books as .MP3 Files (Transcode if Necessary)

If selected, Libation will decode [AAC-LC](#aac-lc), [xHE-AAC](#xhe-aac), and [E-AC-3](#e-ac-3) audiobooks and re-encode them as MP3s using the MP3 encoder settings ([read about LAME MP3 encoder settings](https://lame.sourceforge.io/lame_ui_example.php)). Note that Libation cannot convert [AC-4](#ac-4) audio to MP3.

## Audio Formats

### Traditional Mono and Stereo Formats

#### AAC-LC

**Full Name:** Advanced Audio Coding - Low Complexity

**Description:** This is the base profile for AAC audio and has existed since AAC's initial release in 1997. It enjoys wide support on nearly every conceivable platform capable of playing digital audio, as ubiquitous as MP3.
If Widevine support is not enabled, or if the book is not available in the more high-definition formats, Libation will download audiobooks in this format.

#### MP3

**Full Name:** MPEG-1 Audio Layer III or MPEG-2 Audio Layer III

**Description:**

An older (released in 1991) but still nearly universally supported audio codec. Its audio quality is generally worse than AAC-LC at similar bitrates. Audible delivers some podcasts in MP3 format, but no audiobooks are natively availble as MP3. Libation supports converting Audiobooks delivered in other audio formats to MP3. Note that the MP3 format supports a maximum of two audio channels, so multichannel E-AC-3 audio will be downsampled to stereo or mono (depending on the Libation's settings). [AC-4](#ac-4) cannot be converted to MP3.

#### xHE-AAC

**Full Name:** Extended High-Efficiency Advanced Audio Coding

**Description:**

This is a proprietary codec created by the [Fraunhofer Institute for Integrated Circuits IIS](https://www.iis.fraunhofer.de/en/ff/amm/broadcast-streaming/xheaac.html). It combines features of the HE-AAC v2 and the baseline USAC (Unified Speech and Audio Coding) profiles with the parts of the MPEG-D DRC Loudness Control Profile or Dynamic Range Control Profile. Therefore, USAC and xHE-AAC are not synonymous and should not be used interchangeably. A player capable of decoding USAC will not necessarily be able to decode xHE-AAC.

xHE-AAC boasts significantly higher quality audio at low bitrates. Though it has existed since at least 2016, playback support is still quite limited. FFmpeg has recently added partial decoder support for the USAC profiles, but it is insufficient to decode the xHE-AAC audio files acquired from Audible (due to FFmpeg's lack of support for MPEG Surround for Mono to Stereo Upmixing; ISO 23003-3:2012 §7.11)

Note that the xHE-AAC files authored by Audible have some USAC conformance errors including:

- Number of samples per frame not matching the UsacConfig coreCoderFrameLength value.
- Disagreement between stts and UsacFrame usacIndependencyFlag value.
- Stts indicating a frame is an immediate play-out frame, but USAC AudioPreRoll is absent.

## Supported Media Players

Below is an incomplete matrix of codec support across various media players and platforms.
| Player | [AAC-LC](#aac-lc) | [xHE-AAC](#xhe-aac) | [E-AC-3](#e-ac-3) | [AC-4](#ac-4) |
| :--- | :---: | :---: | :---: | :---: |
|Windows Native Support|Yes|Yes<sup>1</sup>|Yes<sup>2,3</sup>|Yes<sup>4</sup>|
|macOS Native Support|Yes|Yes|Yes<sup>3</sup>| |
|Android Native Support<sup>5</sup>|Yes|Yes| | |
|FFmpeg (all platforms)|Yes|Yes<sup>6</sup>|Yes<sup>3</sup>||
|[VLC](https://www.videolan.org/vlc/) (Windows)|Yes| |Yes<sup>3</sup> | |
|[foobar2000](https://www.foobar2000.org/components) (Windows and Mac)|Yes|Yes<sup>7</sup> | | |
|[PotPlayer](https://potplayer.daum.net/) (Windows)|Yes|Yes|Yes<sup>3</sup>| |
|[Samsung Media Player](https://play.google.com/store/apps/details?id=com.sec.android.app.music)<sup>8</sup> (Samsung devices) |Yes|Yes|Yes|Yes|

1. Windows 11 22H2 and later
2. On Windows [prior to Windows 11, version 24H2](https://support.microsoft.com/en-us/windows/codecs-in-media-player-d5c2cdcd-83a2-4805-abb0-c6888138e456). You can still get the codec by running the following command from a Windows PowerShell console: `winget install --id 9nvjqjbdkn97`
3. As mentioned in [Spatial Audio and Dolby Atmos](#spatial-audio-and-dolby-atmos), just because a media player can play a file does not mean it's rendering Atmos. _Dolby Digital Plus Atmos_ is backwards compatible with _Dolby Digital Plus_, so media players which only support _Dolby Digital Plus_ will play E-AC-3 audio files as regular 5.1 surround without rendering the Atmos spatial qualities. Additional software or hardware support may be required for Dolby Atmos playback.
4. You can download the AC-4 codec for Windows from 3rd party sites like [Major Geeks](https://www.majorgeeks.com/files/details/dolby_ac_3ac_4_installer.html) and [Free-Codecs](https://www.free-codecs.com/dolby-ac-4-decoder_download.htm). Once you install the codec bundle from one of those sources, the Windows store app will keep it updated. Read more about the process [in this comment](https://github.com/rmcrackan/Libation/pull/1331#discussion_r2268660524).
5. All Android devices will support AAC-LC and xHE-AAC. Some manufactures (such as Samsung) will include Dolby codecs for playing E-AC-3 and AC-4 audio.
6. requires FFmpeg to be [built with fdk-aac](https://trac.ffmpeg.org/wiki/Encode/AAC#fdk_aac). You will almost certainly not find pre-build binaries in the wild due to licensing restrictions.
7. Requires the [fdk-aac plugin](https://www.foobar2000.org/components/view/foo_pd_aac) (Windows only)
8. Requires audio file extensions to be `.m4a` or `.mp4`. Libation sets the file extensions to `.m4b`, so you must manually change it to `.m4a` by renaming the audio file.

## Spatial Audio and Dolby Atmos

> [!IMPORTANT]
> **Download status (2026):** The **Request Spatial Audio** and **Spatial Audio Codec** settings were removed in Libation 13.1.3. Audible now requires Widevine L1 (hardware-backed DRM) for many spatial titles, which Libation cannot use. Libation does not currently download new spatial/Atmos content. The sections below describe E-AC-3 and AC-4 for **playback** of files downloaded before the change. See [Spatial Audio, Dolby Atmos, and Widevine DRM](../advanced/spatial-audio.md) for full details and [issue #1553](https://github.com/rmcrackan/Libation/issues/1553).

Atmos is a surround sound technology that expands on existing surround sound systems by adding height channels as well as free-moving sound objects. Audible delivers Dolby Atmos in two formats: E-AC-3 and AC-4.

Your device's ability to play audio from these formats does not necessarily mean that the audio you are hearing is Atmos (spatial). For instance, downloading the AC-4 codec for Windows ([links in the [Supported media Players](#supported-media-players) section) will enable you to play AC-4 audiobooks, but you'll still need to download [Dolby Access](https://apps.microsoft.com/detail/9n0866fs04w8?hl=en-US&gl=US) and pay $15 to enable _Dolby Atmos For Headphones_. Please refer to [this comment](https://github.com/rmcrackan/Libation/pull/1331#discussion_r2268660524) for additional context.

### E-AC-3

**Full Name:** Dolby Digital Plus (a.k.a Enhanced AC-3, DDP, DD+, and EC-3)

**Description:**

A proprietary digital audio compression scheme developed by Dolby Digital for the transport and storage of multichannel audio. This format can be extended to add support for Atmos, making the codec _Dolby Digital Plus Atmos_. _Dolby Digital Plus Atmos_ is backwards compatible with Dolby Digital Plus, so any media player capable of playing Dolby Digital Plus can play _Dolby Digital Plus Atmos_. Audible spatial audiobooks downloaded in the E-AC-3 format are _Dolby Digital Plus Atmos_. If they are played by a media player that supports Atmos, they will play as Atmos audio. If they are played by a media player that does not support Atmos, they will be played as traditional 5.1 surround audio.

### AC-4

**Full Name:** Dolby AC-4

**Description:**

A proprietary audio compression technology developed by Dolby Digital for the transport and storage of audio channels and/or audio objects. Audible spatial audiobooks downloaded in the AC-4 format are 2-channel AC-4 Immersive Stereo (AC4-IMS) audio, intended for playback in headphones or earbuds (though apparently [not supported on Apple devices](https://github.com/rmcrackan/Libation/issues/996#issuecomment-3169574514)).

---

## File: docs/features/lucene.md

# Lucene Query Syntax

Lucene has a custom query syntax for querying its indexes. Here are some query examples demonstrating the query syntax.

## Keyword matching

Search for word "foo" in the title field.

`title:foo`

Search for phrase "foo bar" in the title field.

`title:"foo bar"`

Search for phrase "foo bar" in the title field AND the phrase "quick fox" in the body field.

`title:"foo bar" AND body:"quick fox"`

Search for either the phrase "foo bar" in the title field AND the phrase "quick fox" in the body field, or the word "fox" in the title field.

`(title:"foo bar" AND body:"quick fox") OR title:fox`

Search for word "foo" and not "bar" in the title field.

`title:foo -title:bar`

## Wildcard matching

Search for any word that starts with "foo" in the title field.

`title:foo*`

Search for any word that starts with "foo" and ends with bar in the title field.

`title:foo*bar`

Note that Lucene doesn't support using a * symbol as the first character of a search.

## Range searches

Range Queries allow one to match documents whose field(s) values are between the lower and upper bound specified by the Range Query. Range Queries can be inclusive or exclusive of the upper and lower bounds. Sorting is done lexicographically.


`mod_date:[20020101 TO 20030101]`

---

The above guide was helpfully provided by https://supermind.org

---

## File: docs/features/naming-templates.md

---
pageClass: naming-templates-mono
---

# Naming Templates

File and Folder names can be customized using Libation's built-in tag template naming engine. To edit how folder and file names are created, go to Settings \> Download/Decrypt and edit the naming templates. If you're splitting your audiobook into multiple files by chapter, you can also use a custom template to set each chapter's title metadata tag by editing the template in Settings \> Audio File Options.

These templates apply to both GUI and CLI.

::: tip Keep `` somewhere in your templates
Libation finds a title's existing files by looking for its ASIN in the path. Drop `` from both the folder
and the file template and Libation can no longer recognise its own output, so a later PDF, cover or metadata
download cannot be placed beside the audiobook it belongs to, and the grid cannot tell that a file is present.
:::

## Template Tags

These are the naming template tags currently supported by Libation.

### Property Tags

These tags will be replaced in the template with the audiobook's values.

| Tag                      | Description                                                    | Type                                   |
|--------------------------|----------------------------------------------------------------| -------------------------------------- |
| \ **†**             | Audible book ID (ASIN)                                         | Text                                   |
| \<title\>                | Full title with subtitle                                       | [Text](#text-formatters)               |
| \<title short\>          | Title. Stop at first colon                                     | [Text](#text-formatters)               |
| \<audible title\>        | Audible's title (does not include subtitle)                    | [Text](#text-formatters)               |
| \<audible subtitle\>     | Audible's subtitle                                             | [Text](#text-formatters)               |
| \<author\>               | Author(s)                                                      | [Name List](#name-list-formatters)     |
| \<first author\>         | First author                                                   | [Name](#name-formatters)               |
| \<narrator\>             | Narrator(s)                                                    | [Name List](#name-list-formatters)     |
| \<first narrator\>       | First narrator                                                 | [Name](#name-formatters)               |
| \<series\>               | All series to which the book belongs (if any)                  | [Series List](#series-list-formatters) |
| \<first series\>         | First series                                                   | [Series](#series-formatters)           |
| \<series#\>              | Number order in series (alias for \<first series[{#}]\>        | [Number](#number-formatters)           |
| \<minutes\>              | Duration of the audiobook in minutes                           | [TimeSpan](#timespan-formatters)       |
| \              | Bitrate (kbps) of the last downloaded audiobook                | [Number](#number-formatters)           |
| \<samplerate\>           | Sample rate (Hz) of the last downloaded audiobook              | [Number](#number-formatters)           |
| \<channels\>             | Number of audio channels in the last downloaded audiobook      | [Number](#number-formatters)           |
| \<codec\>                | Audio codec of the last downloaded audiobook                   | [Text](#text-formatters)               |
| \<file version\>         | Audible's file version number of the last downloaded audiobook | [Text](#text-formatters)               |
| \<libation version\>     | Libation version used during last download of the audiobook    | [Text](#text-formatters)               |
| \<account\>              | Audible account of this book                                   | [Text](#text-formatters)               |
| \<account nickname\>     | Audible account nickname of this book                          | [Text](#text-formatters)               |
| \<tag\>                  | Tag(s)                                                         | [Text List](#text-list-formatters)     |
| \<first tag\>            | First tag                                                      | [Text](#text-formatters)               |
| \<locale\>               | Region/country                                                 | [Region](#region-formatters)           |
| \<year\>                 | Year published                                                 | [Number](#number-formatters)           |
| \<language\>             | Book's language                                                | [Language](#language-formatters)       |
| \<language short\> **†** | Book's language abbreviated. Eg: ENG                           | Text                                   |
| \<os\>                   | Language currently set in the operating system                 | [Language](#language-formatters)       |
| \                   | User interface language                                        | [Language](#language-formatters)       |
| \<file date\>            | File creation date/time.                                       | [DateTime](#date-formatters)           |
| \             | Audiobook publication date                                     | [DateTime](#date-formatters)           |
| \<date added\>           | Date the book added to your Audible account                    | [DateTime](#date-formatters)           |
| \<ch count\> **‡**       | Number of chapters                                             | [Number](#number-formatters)           |
| \<ch title\> **‡**       | Chapter title                                                  | [Text](#text-formatters)               |
| \<ch#\> **‡**            | Chapter number                                                 | [Number](#number-formatters)           |
| \<ch# 0\> **‡**          | Chapter number with leading zeros                              | [Number](#number-formatters)           |

**†** Does not support custom formatting

**‡** Only valid for Chapter Filename and Chapter Tile Metadata

To change how these properties are displayed, [read about custom formatters](#tag-formatters)

`<title short>` stops at the first colon wherever that colon came from, so it also cuts titles such as "Star Trek: The Next Generation" that Audible sent without a subtitle. `<audible title>` leaves out Audible's subtitle without cutting the title. To see which of your books are affected either way, filter on `HasSubtitle` and `TitleHasColon`; see [searching and filtering](searching-and-filtering.md#subtitles-and-short-titles).

### Conditional Tags

Anything between the opening tag (`<tagname->`) and closing tag (`<-tagname>`) will only appear in the name if the condition evaluates to true.

| Tag                                                                | Description                                                                                     | Type        |
|--------------------------------------------------------------------|-------------------------------------------------------------------------------------------------| ----------- |
| \...\<-if series\>                                    | Only include if part of a book series or podcast                                                | Conditional |
| \...\<-if podcast\>                                  | Only include if part of a podcast                                                               | Conditional |
| \...\<-if bookseries\>                            | Only include if part of a book series                                                           | Conditional |
| \...\<-if podcastparent\> **†**                | Only include if item is a podcast series parent                                                 | Conditional |
| \...\<-if abridged\>                                | Only include if item is abridged                                                                | Conditional |
| \<has PROPERTY-\>...\<-has\>                                       | Only include if the PROPERTY has a value (i.e. not null or empty)                               | Conditional |
| \...\<-is\>                       | Only include if the PROPERTY or a single value of a list PROPERTY satisfies the CHECK           | Conditional |
| \...\<-is\>               | Only include if the formatted PROPERTY or a single value of a list PROPERTY satisfies the CHECK | Conditional |
| \...\<-is\> | Only include if the joined form of all formatted values of a list PROPERTY satisfies the CHECK  | Conditional |
| \<cmp 1st-PROPERTY [CHECK](#checks) 2nd-PROPERTY-\>...\<-cmp\>     | Only include if two given PROPERTIES satisfy the CHECK                                          | Conditional |

**†** Only affects the podcast series folder naming if "Save all podcast episodes to the series parent folder" option is checked.

For example, `<series><-if podcast>` will evaluate to the podcast's series name if the file is a podcast. For audiobooks that are not podcasts, that tag will be blank.

You can invert the condition (instead of displaying the text when the condition is true, display the text when it is false) by playing a `!` symbol before the opening tag name.

| Inverted Tag                                                    | Description                                                                                      | Type        |
|-----------------------------------------------------------------|--------------------------------------------------------------------------------------------------| ----------- |
| \<!if series-\>...\<-if series\>                                | Only include if _not_ part of a book series or podcast                                           | Conditional |
| \<!if podcast-\>...\<-if podcast\>                              | Only include if _not_ part of a podcast                                                          | Conditional |
| \<!if bookseries-\>...\<-if bookseries\>                        | Only include if _not_ part of a book series                                                      | Conditional |
| \<!if podcastparent-\>...\<-if podcastparent\> **†**            | Only include if item is _not_ a podcast series parent                                            | Conditional |
| \<!has PROPERTY-\>...\<-has\>                                   | Only include if the PROPERTY _does not_ have a value (i.e. is null or empty)                     | Conditional |
| \<!is PROPERTY[[CHECK](#checks)]-\>...\<-is\>                   | Only include if neither the whole PROPERTY nor the values of a list PROPERTY satisfies the CHECK | Conditional |
| \<!cmp 1st-PROPERTY [CHECK](#checks) 2nd-PROPERTY-\>...\<-cmp\> | Only include if two given PROPERTIES _do not_ satisfy the CHECK                                  | Conditional |

**†** Only affects the podcast series folder naming if "Save all podcast episodes to the series parent folder" option is checked.

As an example, this folder template will place all Liberated podcasts into a "Podcasts" folder and all liberated books (not podcasts) into a "Books" folder.

`Podcasts<-if podcast><!if podcast->Books<-if podcast><title>`

This example will add a number if the `<series#>` tag has a value:

`<has series#-><series#><-has>`

Conditionals may be nested. This folder template puts every book under its series, and prefixes the book folder with the series number only for the books that have one, so that books without a number don't end up in a folder starting with `.`:

`<first series>/<has series#-><series#>. <-has><-if series><title short>`

This example will put non-series books in a "Standalones" folder:

`<!if series->Standalones/<-if series>`

And this example will customize the title based on whether the book has a subtitle:

`<audible title><has audible subtitle->-<audible subtitle><-has>`

## Tag Formatters

**Text**, **Name**, **Series**, **Number**, **TimeSpan**, **DateTime**, **Region**, **Language** and their **List** tags can be optionally formatted using format text in square brackets after the tag name. Below is a list of supported formatters for each tag type.

### Text Formatters

Text formatting can change length and case of the text. Use \<#\>, \<#\>\<case\> or \<case\> to specify one or both of these.

| Formatter | Description                                                     | Example Usage      | Example Result                              |
| --------- | --------------------------------------------------------------- | ------------------ | ------------------------------------------- |
| #         | Cuts down the text to the specified number of characters        | \<title[14]\>      | A Study in Scar                             |
| L         | Converts text to lowercase                                      | \<title[L]\>       | a study in scarlet꞉ a sherlock holmes novel |
| U         | Converts text to uppercase                                      | \<title short[U]\> | A STUDY IN SCARLET                          |
| t         | Converts text to title case                                     | \<title[t]\>       | The Abc Murders                             |
| T         | Converts text to title case where uppercase words are preserved | \<title[T]\>       | The ABC Murders                             |
|           |                                                                 | \<title[6T]\>      | The AB                                      |

### Text List Formatters

| Formatter                                  | Description                                                                                                                                                                                     | Example Usage                                                     | Example Result                                   |
|--------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------|--------------------------------------------------|
| separator()                                | Specify the text used to joinmultiple entries.Default is ", "                                                                                                                       | `<tag[separator(_)]>`                                             | Tag1_Tag2_Tag3_Tag4_Tag5                         |
| format(\{S\}) **†**                        | Formats the entries by placing their values into the specified template.Use \{S:[Text formatters](#text-formatters)\} to place the entry and optionally apply a format.                     | `<tag[format(Tag={S:l})``separator(;)]>`                      | Tag=tag1;Tag=tag2;Tag=tag3;Tag=tag4;Tag=tag5     |
| unique(FMT) **†**                          | Reduce list members to a unique set. Entries are compared to each other after applying the given format. Duplicate entries (after format is applied) are removed, keeping the first occurrence. | `<tag[unique()]>`<hr>`<tag[unique({S:1L})``separator(;)]>`    | Tag1, Tag2, Tag3<hr>tag1                         |
| sort(S)                                    | Sorts the elements by their value.*Sorting direction:*uppercase = ascendinglowercase = descendingDefault is unsorted                                                    | `<tag[sort(s)``separator(;)]>`                                | Tag5;Tag4;Tag3;Tag2;Tag1                         |
| max(#)                                     | Only use the first # of entries                                                                                                                                                                 | `<tag[max(1)]>`                                                   | Tag1                                             |
| slice(#)                                   | Only use the nth entry of the list                                                                                                                                                              | `<tag[slice(2)]>`                                                 | Tag2                                             |
| slice(#..)                                 | Only use entries of the list starting from #                                                                                                                                                    | `<tag[slice(2..)]>`                                               | Tag2, Tag3, Tag4, Tag5                           |
| slice(..#)                                 | Like max(#). Only use the first # of entries                                                                                                                                                    | `<tag[slice(..1)]>`                                               | Tag1                                             |
| slice(#..#)                                | Only use entries of the list starting from # and ending at # (inclusive)                                                                                                                        | `<tag[slice(2..4)]>`                                              | Tag2, Tag3, Tag4                                 |
| slice(-#..-#)                              | Numbers may be specified negative. In that case positions ar counted from the end with -1 pointing at the last member                                                                           | `<tag[slice(-3..-2)]>`                                            | Tag3, Tag4                                       |
| count(FMT) **‡**                           | Instead of returning some or all members of the list, print out the number of entries using the specified [format](#number-formatters).                                                         | `<tag[count()]>`<hr>`<tag[count(00)]>`                            | 5<hr>05                                          |
| filter(FMT [CHECK](#checks) VALUE)  **†**  | Filter list entries based on a condition. Each item is first formatted using the specified text format (or the default format if FMT is omitted), then compared against VALUE using the specified [CHECK](#checks). Only matching entries are included in the output.**Syntax:** `filter(FORMAT CHECK VALUE)` or `filter(CHECK VALUE)`- `FORMAT`: Optional text format to apply to each entry (e.g., `{S}`, `{S:L}`, `{S:3}`); defaults to `{S}`- `CHECK`: Comparison operator (e.g., `=`, `!=`, `~`)- `VALUE`: The value to compare against | `<tag[filter({S} ~ '[3-9]')]>`<hr>`<tag[filter({S} != 'Ignore')]>` | Tag3, Tag4, Tag5<hr>Tag1, Tag2, Tag3, Tag4, Tag5 |

**†** For further information on format templates, please refer to the [Format templates](#format-templates) section.

**‡** When `count(FMT)` is used, only the number is output in the specified [number format](#number-formatters). Any other output-related specifications from `separator()` or `format()` are irrelevant here and are ignored.

### Series Formatters

| Formatter               | Description                                                                                                                                                                                                                                                                                                                                                                                                                               | Example Usage                                                                                                                | Example Result                                                                                              |
|-------------------------| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| \{N \| # \| ID\} **†**  | Formats the series usingthe series part tags.\{N:[Text_Formatter](#text-formatters)\} = Series Name\{#:[Number_Formatter](#number-formatters)\} = Number order in series\{ID:[Text_Formatter](#text-formatters)\} = Audible Series IDFormatter parts are optional and introduced by the colon. If specified the string will be used to format the part using the corresponding formatter.Default is \{N\} | `<first series>`<hr>`<first series[{N:l}]>`<hr>`<first series[{N}, {#}, {ID}]>`<hr>`<first series[{N:10U}, {ID}, {#:00.0}]>` | Sherlock Holmes<hr>sherlock holmes<hr>Sherlock Holmes, 1-6, B08376S3R2<hr>SHERLOCK H, B08376S3R2, 01.0-06.0 |

**†** For further information on format templates, please refer to the [Format templates](#format-templates) section.

### Series List Formatters

| Formatter                                 | Description                                                                                                                                                                                                                                                                                                  | Example Usage                                                                            | Example Result                                                                                                      |
|-------------------------------------------| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------|
| separator()                               | Specify the text used to joinmultiple series names.Default is ", "                                                                                                                                                                                                                               | `<series[separator(; )]>`                                                                | Sherlock Holmes; Some Other Series                                                                                  |
| format(\{N \| # \| ID\}) **†**            | Formats the series propertiesusing the name series tags.See [Series Formatter Usage](#series-formatters) above.                                                                                                                                                                                      | `<series[format({N}, {#})``separator(; )]>`<hr>`<series[format({ID}-{N}, {#:00.0})]>` | Sherlock Holmes, 1-6; Book Collection, 1<hr>B08376S3R2-Sherlock Holmes, 01.0-06.0, B000000000-Book Collection, 01.0 |
| unique(FMT) **†**                         | Reduce list members to a unique set. Entries are compared to each other after applying the given format. Duplicate entries (after format is applied) are removed, keeping the first occurrence.                                                                                                                 | `<series[unique()]>`<hr>`<series[unique({N:L})``separator(; )]>`                     | Sherlock Holmes; Some Other Series<hr>sherlock holmes; some other series                                            |
| sort(N \| # \| ID)                        | Sorts the series by name, number or ID.These terms define the primary, secondary, tertiary, … sorting order.You may combine multiple terms in sequence to specify multi‑level sorting.*Sorting direction:*uppercase = ascendinglowercase = descendingDefault is unsorted | `<series[sort(N)``separator(; )]>`                                                   | Book Collection, 1; Sherlock Holmes, 1-6                                                                            |
| max(#)                                    | Only use the first # of series                                                                                                                                                                                                                                                                               | `<series[max(1)]>`                                                                       | Sherlock Holmes                                                                                                     |
| slice(#..#)                               | Only use entries of the series list starting from # and ending at # (inclusive)See [Text List Formatter Usage](#text-list-formatters) above for details on all the variants of `slice()`                                                                                                             | `<series[slice(..-2)]>`                                                                  | Sherlock Holmes                                                                                                     |
| count(FMT) **‡**                          | Instead of returning some or all members of the list, print out the number of series using the specified [format](#number-formatters).                                                                                                                                                      | `<series[count()]>`<hr>`<series[count(00)]>`                                             | 2<hr>02                                                                                                             |
| filter(FMT [CHECK](#checks) VALUE)  **†** | Filter list entries based on a condition. Each series is first formatted using the specified [Series Format](#series-formatters) (or the default format if FMT is omitted), then compared against VALUE using the specified [CHECK](#checks). Only matching entries are included in the output.**Syntax:** `filter(FORMAT CHECK VALUE)` or `filter(CHECK VALUE)`- `FORMAT`: Optional series format to apply to each entry (e.g., `{N}`, `{N:L}`, `{#}`); defaults to `{N}`- `CHECK`: Comparison operator (e.g., `=`, `!=`, `~`)- `VALUE`: The value to compare against | `<series[filter({N} = 'Holmes')]>`<hr>`<series[filter(~ "Sherlock")]>`                    | Sherlock Holmes<hr>Sherlock Holmes                                                                                  |

**†** For further information on format templates, please refer to the [Format templates](#format-templates) section.

**‡** When `count(FMT)` is used, only the number is output in the specified [number format](#number-formatters). Any other output-related specifications from `separator()` or `format()` are irrelevant here and are ignored.

### Name Formatters

| Formatter                              | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | Example Usage                                                          | Example Result                          |
|----------------------------------------| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | --------------------------------------- |
| \{T \| F \| M \| L \| S \| ID\} **†**  | Formats the human name usingthe name part tags.\{T:[Text_Formatter](#text-formatters)\} = Title (e.g. "Dr.")\{F:[Text_Formatter](#text-formatters)\} = First name\{M:[Text_Formatter](#text-formatters)\} = Middle name\{L:[Text_Formatter](#text-formatters)\} = Last Name\{S:[Text_Formatter](#text-formatters)\} = Suffix (e.g. "PhD")\{ID:[Text_Formatter](#text-formatters)\} = Audible Contributor IDFormatter parts are optional and introduced by the colon. If specified the string will be used to format the part using the correspoing formatter.Default is \{T\} \{F\} \{M\} \{L\} \{S\} | `<first narrator[{L}, {F:1}.]>`<hr>`<first author[{L:u}, {F} _{ID}_]>` | Fry, S.<hr>DOYLE, Arthur \_B000AQ43GQ\_ |

**†** For further information on format templates, please refer to the [Format templates](#format-templates) section.

### Name List Formatters

| Formatter                                     | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | Example Usage                                                                                                  | Example Result                                                                                                                                           |
|-----------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|
| separator()                                   | Specify the text used to joinmultiple people's names.Default is ", "                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | `<author[separator(; )]>`                                                                                      | Arthur Conan Doyle; Stephen Fry                                                                                                                          |
| format(\{T \| F \| M \| L \| S \| ID\}) **†** | Formats the human name usingthe name part tags.See [Name Formatter Usage](#name-formatters) above.                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | `<author[format({L:u}, {F})``separator(; )]>`<hr>`<author[format({L}, {F:1}.``_{ID}_) separator(; )]>` | DOYLE, Arthur; FRY, Stephen<hr>Doyle, A. \_B000AQ43GQ\_;Fry, S. \_B000APAGVS\_                                                                       |
| unique(FMT) **†**                             | Reduce list members to a unique set. Entries are compared to each other after applying the given format. Duplicate entries (after format is applied) are removed, keeping the first occurrence.                                                                                                                                                                                                                                                                                                                                                                                     | `<author[unique()]>`<hr>`<author[unique({L:L})``separator(; )]>`                                           | Arthur Conan Doyle, Stephen Fry<hr>doyle; fry                                                                                                            |
| sort(T \| F \| M \| L \| S \| ID)             | Sorts the names by title, first, middle, or last name,suffix or Audible Contributor IDThese terms define the primary, secondary, tertiary, … sorting order.You may combine multiple terms in sequence to specify multi‑level sorting.*Sorting direction:*uppercase = ascendinglowercase = descendingDefault is unsorted                                                                                                                                                                                                                 | `<author[sort(M)]>`<hr>`<author[sort(Fl)]>`<hr>`<author[sort(L FM ID)]>`                                       | Stephen Fry, Arthur Conan Doyle<hr>Stephen King, Stephen Fry<hr>John P. Smith \_B000TTTBBB\_, John P. Smith \_B000TTTCCC\_, John S. Smith \_B000HHHVVV\_ |
| max(#)                                        | Only use the first # of namesDefault is all names                                                                                                                                                                                                                                                                                                           | `<author[max(1)]>`                                                                                             | Arthur Conan Doyle                                                                                                                                       |
| slice(#..#)                                   | Only use entries of the names list starting from # and ending at # (inclusive)See [Text List Formatter Usage](#text-list-formatters) above for details on all the variants of `slice()`                                                                                                                                                                     | `<author[slice(..-2)]>`                                                                                        | Arthur Conan Doyle                                                                                                                                       |
| count(FMT) **‡**                              | Instead of returning some or all members of the list, print out the number of names using the specified [format](#number-formatters).                                                                                                                                                                                                                               | `<author[count()]>`<hr>`<author[count(00)]>`                                                                   | 2<hr>02                                                                                                                                                  |
| filter(FMT [CHECK](#checks) VALUE)  **†**     | Filter list entries based on a condition. Each person is first formatted using the specified name format (or the default format if FMT is omitted), then compared against VALUE using the specified [CHECK](#checks). Only matching entries are included in the output.**Syntax:** `filter(FORMAT CHECK VALUE)` or `filter(CHECK VALUE)`- `FORMAT`: Optional name format to apply to each entry (e.g., `{L}`, `{M}`, `{L}, {F}`); defaults to `{T} {F} {M} {L} {S}`- `CHECK`: Comparison operator (e.g., `=`, `!=`, `~`)- `VALUE`: The value to compare against | `<author[filter({L} = 'Doyle')]>`<hr>`<author[filter({F} ~ '^Stephen')]>`<hr>`<narrator[filter(~ "Fry")]>`     | Conan Doyle<hr>Stephen Fry<hr>Stephen Fry |

**†** For further information on format templates, please refer to the [Format templates](#format-templates) section.

**‡** When `count(FMT)` is used, only the number is output in the specified [number format](#number-formatters). Any other output-related specifications from `separator()` or `format()` are irrelevant here and are ignored.

### TimeSpan Formatters
For more custom formatters and examples, [see this guide from Microsoft](https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-timespan-format-strings).

| Formatter | Description                                                                                                                                                                                                                                                                                                                                                                                                                                            | Example Usage             | Example Result |
|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------|----------------|
| d         | The "d" custom format specifier outputs the value of the TimeSpan.Days property, which represents the number of whole days in the time interval. It outputs the full number of days in a TimeSpan value, even if the value has more than one digit. If the value of the TimeSpan.Days property is zero, the specifier outputs "0".Use "dd"-"dddddddd" for zero padding up to the specified size.                                               | \<minutes[dd]\>             | 02             |
| h         | The "h" custom format specifier outputs the value of the TimeSpan.Hours property, which represents the number of whole hours in the time interval that isn't counted as part of its day component. It returns a one-digit string value if the value of the TimeSpan.Hours property is 0 through 9, and it returns a two-digit string value if the value of the TimeSpan.Hours property ranges from 10 to 23.Use "hh" for zero padding.         | \<minutes[hh]\>             | 14             |
| m         | The "m" custom format specifier outputs the value of the TimeSpan.Minutes property, which represents the number of whole minutes in the time interval that isn't counted as part of its day component. It returns a one-digit string value if the value of the TimeSpan.Minutes property is 0 through 9, and it returns a two-digit string value if the value of the TimeSpan.Minutes property ranges from 10 to 59.Use "mm" for zero padding. | \<minutes[m]\>              | 42             |
| 'string'  | Literal string delimiter.                                                                                                                                                                                                                                                                                                                                                                                                                              | \<minutes[d'd 'h'h 'm'm']\> | 2d 14h 42m     |
| \\        | The escape character.                                                                                                                                                                                                                                                                                                                                                                                                                                  | \<minutes[d\\d h\\h m\\m]\> | 2d 14h 42m     |

These formatters have been enhanced to allow the display of days, hours or months beyond their usual limits. For example, the total number of hours, even if it exceeds 23.
Here, a number format is inserted for the desired part in accordance with [Microsoft's instructions](https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-numeric-format-strings). Unlike standard number formats, however, the letters D, H or M (uppercase) are used instead of zeros.

| Formatter | Description                                                                                                                                         | Example Usage                  | Example Result    |
|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------|-------------------|
| D         | A number format with "D" instead of "0". Using this will output the total number of days and reduce the amount of minutes avalable for "H" and "M". | \<minutes[DD]\>                  | 02                |
| H         | A number format with "H" instead of "0". Using this will output the total number of hours and reduce the amount of minutes available for "M".       | \<minutes[HH]\>                  | 62                |
| M         | A number format with "M" instead of "0". Using this will output the total number of minutes.                                                        | \<minutes[#,#MM]\>               | 3,762             |
| D H M     | A combination of the above.                                                                                                                         | \<minutes[D'days 'MM'minutes']\> | 02days 882minutes |

### Number Formatters

For more custom formatters and examples, [see this guide from Microsoft](https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-numeric-format-strings).

|Formatter|Description|Example Usage|Example Result|
|-|-|-|-|
|\[integer\]|Zero-pads the number|\\<series#\[3\]\>\<samplerate\[6\]\>|0128001044100|
|0|Replaces the zero with the corresponding digit if oneis present; otherwise, zero appears in the result string.|\<series#\[000.0\]\>|001.0|
|#|Replaces the "#" symbol with the corresponding digit if one is present; otherwise, no digit appears in the result string|\<series#\[00.##\]\>|01|

### Date Formatters

For more standard formatters, [see this guide from Microsoft](https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings).

#### Standard DateTime Formatters

| Formatter | Description                 | Example Usage    | Example Result      |
| --------- | --------------------------- | ---------------- | ------------------- |
| s         | Sortable date/time pattern. | \<file date[s]\> | 2023-02-14T13:45:30 |
| Y         | Year month pattern.         | \<file date[Y]\> | February 2023       |

#### Custom DateTime Formatters

You can use custom formatters to construct customized DateTime string. For more custom formatters and examples, [see this guide from Microsoft](https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings).
|Formatter|Description|Example Usage|Example Result|
|-|-|-|-|
|yyyy|4-digit year|\<file date[yyyy]\>|2023|
|yy|2-digit year|\<file date[yy]\>|23|
|MM|2-digit month|\<file date[MM]\>|02|
|dd|2-digit day of the month|\<file date[yyyy-MM-dd]\>|2023-02-14|
|HHmm|The hour, using a 24-hour clock from 00 to 23The minute, from 00 through 59.|\<file date[HH:mm]\>|14:45|

### Region Formatters

You can specify which part of a region you are interested in.

| Formatter                                                   | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | Example Usage                                        | Example Result                        |
|-------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------|---------------------------------------|
| \{O \| I \| I2 \| I3 \| E \| N \| W \| L \| T \| ID\} **†** | Formats the region usingthe region part tags.\{O:[Text_Formatter](#text-formatters)\} = Region as used in Libation\{I:[Text_Formatter](#text-formatters)\} = Two letter ISO code\{I2:[Text_Formatter](#text-formatters)\} = Two letter ISO code\{I3:[Text_Formatter](#text-formatters)\} = Three letter ISO code\{E:[Text_Formatter](#text-formatters)\} = English name\{N:[Text_Formatter](#text-formatters)\} = Native name - OS dependent\{W:[Text_Formatter](#number-formatters)\} = Unique Windows code\{L:[Text_Formatter](#text-formatters)\} = Lang code used for this region/store\{T:[Text_Formatter](#number-formatters)\} = TLD under which the audible store is hosted\{ID:[Text_Formatter](#text-formatters)\} = Region code Formatter parts are optional and introduced by the colon. If specified the string will be used to format the part using the corresponding formatter.Default is \{O\} | `<locale[{I} ({E})]>`<hr>`www.audible.<locale[{T}]>` | US (United States)<hr>www.audible.com |
| \{D\}  **†**,**‡**                                          | Display name interpreted by the current language settings.To ensure output in a specific language the lang-code to use might be specified with a leading '@'.Formatter part is also optional and introduced by the colon.\{D@LANG:[Text_Formatter](#text-formatters)\}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | `<locale[{D@es:u}]>`                                 | ESTADOS UNIDOS                        |

**†** For further information on format templates, please refer to the [Format templates](#format-templates) section.

**‡** LANG may be any code from the [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) standard like `es` for Spanish, `en` for English, `de` for German, etc. or even a [IETF language tag](https://en.wikipedia.org/wiki/IETF_language_tag) like 'fr-CA'.

### Language Formatters

You can specify which part of a language you are interested in.

| Formatter                                         | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        | Example Usage              | Example Result |
|---------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------|----------------|
| \{O \| I \| I2 \| I3 \| E \| N \| W \| ID\} **†** | Formats the language usingthe language part tags.\{O:[Text_Formatter](#text-formatters)\} = Language as provided by audible\{I:[Text_Formatter](#text-formatters)\} = Two letter ISO code\{I2:[Text_Formatter](#text-formatters)\} = Two letter ISO code\{I3:[Text_Formatter](#text-formatters)\} = Three letter ISO code\{E:[Text_Formatter](#text-formatters)\} = English name\{N:[Text_Formatter](#text-formatters)\} = Native name - OS dependent\{W:[Text_Formatter](#number-formatters)\} = Unique Windows code\{ID:[Text_Formatter](#text-formatters)\} = Lang codeFormatter parts are optional and introduced by the colon. If specified the string will be used to format the part using the corresponding formatter.Default is \{O\} | `<language[{I3:l} ({E})]>` | fra (French)   |
| \{D\} **†**,**‡**                                 | Display name interpreted by the current language settings.To ensure output in a specific language the lang-code to use might be specified with a leading '@'.Formatter part is also optional and introduced by the colon.\{D@LANG:[Text_Formatter](#text-formatters)\}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | `<language[{D@es}]>`       | francés        |

**†** For further information on format templates, please refer to the [Format templates](#format-templates) section.

**‡** LANG may be any code from the [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) standard like `es` for Spanish, `en` for English, `de` for German, etc. or even a [IETF language tag](https://en.wikipedia.org/wiki/IETF_language_tag) like 'fr-CA'.

### Format Templates

Depending on which property is to be displayed, one or more placeholders can be used in a format template. The placeholders are defined in the form `{A}`:

`<first author[{F} {L}]>`

The format template must sometimes be enclosed in square brackets and sometimes in round brackets. In addition to placeholders, the format template may also contain arbitrary text. To prevent this text from being mistaken for a bracket at the end of the template or a placeholder, escapes can be used within the text:
* `\x` - Escapes the next character.
* `\\` - Escapes a backslash.
* `"text"` - encloses text that may contain special characters. To include a double quote in the text, escape it by doubling it: `"She said ""Hello"""` will output `She said "Hello"`.
* `'text'`- encloses text that may contain special characters. To include a single quote in the text, escape it by doubling it: `'It''s a test'` will output `It's a test`.

`<series[separator(,) format('{Series:' {N} ({#}\)\})]>`

Not all elements of a property are always present or have content. In this case, format templates would contain gaps after substitution. Groups of spaces are automatically merged. Other characters, however, remain unchanged. By doubling the curly brackets, you can specify text fragments before and after the placeholder, which are only used if the placeholder is replaced with content.


<code v-pre>&lt;narrator[format({{F} }{\({M}'\) '}{L})]&gt;</code> -> `Neil Gaiman, Christopher (Evan) Welch`

### Checks

There are two formats for checks, with slightly different syntax for specifying parameters:

`...<-is>`

`<cmp 1st-PROPERTY CHECK 2nd-PROPERTY->...<-cmp>`

For `CHECK-with-value`, the value (2nd parameter) is specified directly after the check operator (e.g., `=`). This may
be a number or a string with escaped characters like `\]`. Single backslashes must be doubled if they are part of the
string.

`1st-PROPERTY` and `2nd-PROPERTY` may be any of the properties listed in the [Property tags](#property-tags) section, or they
may be string or number literals. Use digits only for numbers. To specify a string literal, enclose it in single or
double quotes. If the string contains a single or double quote, escape it by doubling it. For example, to specify the
string literal `O'Reilly`, you can use either `'O''Reilly'` or `"O'Reilly"`.

| String Checks | Unicode Operator | Description                                                                                                        | Examples                                                  |
|---------------|------------------|--------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------|
| =             |                  | Matches if values are equal (case-insensitive)                                                                     | \\<author[filter({L} = 'Doyle')]> |
| !=            |                  | Matches if values are not equal (case-insensitive)                                                                 | \\<cmp "foo" != 'bar'-\>\<tag[filter({S} != 'Ignore')]> |
| ~             |                  | Matches if the first parameter matches the regular expression specified by the second parameter (case-insensitive) | \\<tag[filter({S} ~ '[3-9]')]>\<narrator[filter({F} ~ '^Stephen')]> |

| Number Checks | Unicode Operator | Description                                                    | Examples                                                  |
|---------------|------------------|----------------------------------------------------------------|-----------------------------------------------------------|
| #= **†**      |                  | Matches if the number value equals NUMBER                      | \\<cmp channels #= 1-\>           |
| #!= **†**     | ≠                | Matches if the number value does not equal NUMBER              | \                                      |
| #\>= **†**    | ≥                | Matches if the number value is greater than or equal to NUMBER | \=128]-\>\<cmp 44100 #>= samplerate-\> |
| #\> **†**     |                  | Matches if the number value is greater than NUMBER             | \30]-\>\<cmp minutes #\> 60-\>           |
| #\<= **†**    | ≤                | Matches if the number value is less than or equal to NUMBER    | \                |
| #\< **†**     |                  | Matches if the number value is less than NUMBER                | \                                      |

| List Checks              | Unicode Operator | Description                                                                                                                | Examples                                               |
|--------------------------|------------------|----------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------|
| ==  \| :equals:          | ≡                | Matches if both values contain the same elements                                                                           | \<cmp author[slice(-2..-1)] == narrator[slice(..2)]-\> |
| \>> \| :contains:        | ∋                | Matches if the first parameter contains the second parameter                                                               | \                     |
| !>> \| :not_contains:    | ∌                | Matches if the first parameter does not contain the second parameter                                                       | \<cmp series :not_contains: 'Harry Potter'-\>          |
| <<  \| :in:              | ∈                | Matches if the first parameter is contained in the second parameter                                                        | \<cmp first author << author[slice(2..)]-\>            |
| !<< \| :not_in:          | ∉                | Matches if the first parameter is not contained in the second parameter                                                    | \<cmp author[slice(1)] ∉ narrator-\>                   |
| &&  \| :overlaps:        | ⋂                | Matches if both values share at least one element                                                                          | \<cmp author && narrator-\>                            |
| &&! \| :disjoint:        | ⋂̸               | Matches if both values share no elements                                                                                   | \<cmp tag :disjoint: series-\>                         |
| <=< \| :subset:          | ⊆                | Matches if all elements from the first parameter are found in the second parameter                                         | \<cmp author[slice(-3..)] <=< author[slice(..-4)]-\>   |
| >=> \| :superset:        | ⊇                | Matches if the first parameter contains all elements from the second parameter                                             | \<cmp author ⊇ narrator-\>                             |
| <-< \| :proper_subset:   | ⊂                | Matches if all elements from the first parameter are found in the second parameter, and the second has additional elements | \<cmp author[slice(..2)] :proper_subset: author-\>     |
| >-> \| :proper_superset: | ⊃                | Matches if the first parameter contains all elements from the second parameter plus at least one additional element        | \<cmp author ⊃ narrator-\>                             |

**†** NUMBER checks on lists check the size of the list. For string values, the string length is used.

#### More Complex Examples

This example truncates the title to 4 characters and checks if its (trimmed) value equals "the" (case-insensitive):

``

This example takes tags 2 through 4, joins them with a colon, and checks if the result equals "Tag2:Tag3:Tag4":

``

---

## File: docs/features/searching-and-filtering.md

# Searching and Filtering

## Tags

To add tags to a title, click the tags button



Add as many tags as you'd like. Tags are separated by a space. Each tag can contain letters, numbers, and underscores



Tags are saved non-case specific for easy search. There is one special tag "hidden" which will also grey-out the book



To edit tags, just click the button again.

## Searches

Libation's advanced searching is built on the powerful Lucene search engine. Simple searches are effortless and powerful searches are simple. To search, just type and click Filter or press enter

- Type anything in the search box to search common fields: title, authors, narrators, and the book's audible id
- Use Lucene's "Query Parser Syntax" for advanced searching.
  - [Easy guide to searching](lucene.md)
  - [Full official guide](https://lucene.apache.org/core/2_9_4/queryparsersyntax.html)
- Tons of search fields, specific to audiobooks
- Synonyms so you don't have to memorize magic words. Eg: author and author**s** will both work
- Click [?] button for a full list of search fields and synonyms 
- Search by tag like \[this\]
- When tags have an underscore you can use part of the tag. This is useful for quick categories. The below examples make this more clear.

## Search Examples

Search for anything with the word potter



If you only want to see Harry Potter



If you only want to see potter except for Harry Potter. You can also use "-" instead of "NOT"




To see only books written by Neil Gaiman where he also narrates his own book. (If you don't include AND, you'll see everything written by Neil Gaiman and also all books in your library which are self-narrated.)



I tagged autobiographies as auto_bio and biographies written by someone else as bio. I can get only autobiographies with \[auto_bio\] or get both by searching \[bio\]




## Subtitles and short titles

The `<title short>` tag keeps everything before the first colon, which is what keeps the default folder name short. That is usually what you want, but not always. "A Book Series Omnibus: Volume One" and "A Book Series Omnibus: Volume Two" both shorten to "A Book Series Omnibus", so the two books land in the same folder and can no longer be told apart by name.

A colon is not something you can search for: the search engine throws punctuation away when it indexes your library, and Lucene reads a colon in a query as the separator between a field and its value. Two boolean fields find these books instead.

| Field                            | Matches                                                                                                       |
|----------------------------------|---------------------------------------------------------------------------------------------------------------|
| `HasSubtitle` (`HasSubtitles`)   | Audible sent a separate subtitle, which every title tag except `<title>` leaves out                            |
| `TitleHasColon` (`ColonInTitle`) | Audible's title itself contains a colon, so `<title short>` cuts into the title rather than dropping a subtitle |

Some searches worth keeping as quick filters:

- `TitleHasColon` - every book whose title `<title short>` cuts
- `TitleHasColon AND -IsLiberated` - the same, limited to books you have not downloaded yet
- `HasSubtitle OR TitleHasColon` - everything shortening changes in any way
- `-HasSubtitle AND -TitleHasColon` - the books shortening cannot change

If these fields find nothing at all, your search index was built before they existed. Scanning your library rebuilds it, as does closing Libation and deleting the `SearchEngine` folder in your Libation files folder. The index is only a cache of your library, so deleting it is safe.

Once you can see the affected books, you can decide what to do about them. If the only problem is colons inside Audible's titles, switching `<title short>` to `<audible title>` in Settings > Download/Decrypt fixes every one of them at once: it still leaves out Audible's subtitle, but it never cuts the title. If instead two books share a title and differ only by subtitle, use `<title>` for those books, or keep `` in the template so their names stay unique. Either way, filter to the books you want handled differently, liberate them with one template, then restore your usual template for the rest.

### Auditing titles in a spreadsheet

The filter tells you which books are shortened. It cannot tell you which ones actually collide, and a colon on its own is harmless - the damage is done when two books end up with the same name. Export (Export in the menu bar) writes `Title` and `Subtitle` as separate columns, where `Title` is Audible's title exactly as `<title short>` sees it, so a spreadsheet can answer the question the filter cannot.

Put this beside the Title column to see the name each book would be shortened to, then use `COUNTIF` on the result to find the ones that repeat:

`=LEFT(A2, IFERROR(FIND(":", A2) - 1, LEN(A2)))`

To carry the result back into Libation, paste the affected ids into the filter box joined by OR, eg. `id:B015D78L0U OR id:B01LYFDNZM`, which selects exactly those books in the grid.

## Filters

If you have a search you want to save, click Add To Quick Filters to save it in your Quick Filters list. To use it again, select it from the Quick Filters list.

To edit this list go to Quick Filters > Edit quick filters. Here you can re-order the list, delete filters, double-click a filter to edit it, or double-click the bottom blank box to add a new filter.

Check "Quick Filters > Start Libation with 1st filter Default" to have your top filter automatically applied when Libation starts. In this top example, I want to always start without these: at books I've tagged hidden, books I've tagged as free_audible_originals, and books which I have rated.

---

## File: docs/development/contribute.md

# Contribute to Libation

We welcome contributions! Whether it's fixing bugs, adding features, or improving documentation, your help is appreciated.

> [!WARNING]
> Read the [Development - Getting Started](/docs/development/getting-started) guide first.

## Getting Started

1.  **Fork the repository** on GitHub.
2.  **Clone your fork** locally.
3.  **Create a branch** for your feature or fix:
    ```bash
    git checkout -b feature/my-new-feature
    ```

## Code Style

- Follow standard C# coding conventions.
- Ensure your code builds and runs without errors.
- Clean up any unused dependencies or imports.

## Logging and secrets

We ask people to attach `Log.log` to public issue reports, so treat everything written there as published.

- Log `account.MaskedLogEntry`, never an account's id or name. `AccountCredentialStatus.FormatAccountLabel` gives the unmasked label and is for dialogs shown to the account's owner only.
- Never hang an `Account`, an `Identity`, or anything holding one off an exception. Serilog.Exceptions reflects over every public property of a logged exception and follows nested objects, so a live account on an exception publishes its address and activation bytes no matter what `ToString` says. Carry an `AccountSummary` instead. A test enforces this: see `ExceptionsCannotReachAnAccount`.
- Remember that an exception's `Message` gets logged too, so mask anything you interpolate into one.
- Wrap a new secret in `Dinah.Core.Security.SecretString`, which keeps the value behind `Reveal()` where reflection cannot find it and prints `[REDACTED length=N]` everywhere else. Implement `ILogMasked` on a type that needs a masked identity in logs.
- `Reveal()` at the point of use, and nowhere else. Interpolating a secret into a string is not a compile error, so a redaction can end up sent over the wire in place of the real value - cover any new call site with a test.

## Submitting a Pull Request

1.  **Commit your changes** with a clear message.
2.  **Push to your fork**:
    ```bash
    git push origin feature/my-new-feature
    ```
3.  **Open a Pull Request** on the main repository.
4.  Describe your changes and link any related issues.

## Reporting Issues

If you find a bug or have a feature request, please [open an issue](https://github.com/rmcrackan/Libation/issues) on GitHub.

---

## File: docs/development/getting-started.md

# Developing Libation

Libation is built using .NET and Avalonia UI. To get started with development, you'll need to set up your environment.

## Prerequisites

- **.NET SDK**: The project currently targets `.net10.0`. You will need the latest .NET SDK.
- **IDE**: We recommend [Visual Studio Code](https://code.visualstudio.com/), [JetBrains Rider](https://www.jetbrains.com/rider/), or Visual Studio.
- **Git**: For version control.

## Setup

1.  **Clone the repository**:

    ```bash
    git clone https://github.com/rmcrackan/Libation.git
    cd Libation
    ```

2.  **Restore dependencies**:
    ```bash
    dotnet restore
    ```

## Running Libation Locally

You can run Libation directly from the source code using the .NET CLI or your IDE.

### Using .NET CLI

To run the desktop application (Avalonia):

1.  Navigate to the `Source/LibationAvalonia` directory:

    ```bash
    cd Source/LibationAvalonia
    ```

2.  Run the application:
    ```bash
    dotnet run
    ```

### Using Visual Studio / Rider

1.  Open `Source/Libation.slnx` (or open the root folder).
2.  Set `LibationAvalonia` as the startup project.
3.  Press Run/Debug.

### Troubleshooting

- **Assets/Cover Art**: If you encounter issues with missing assets, ensure you have run `git submodule update --init --recursive` if applicable, although Libation typically manages assets within the project.
- **Port/Network**: Libation makes network requests to Audible and other services. Ensure your firewall allows the application to connect.

## Testing your changes

Once the app runs, see [Testing Changes](./testing.md) for how to run the automated tests and how to seed a demo library so UI changes can be checked by eye.

## Linux Specifics

For Linux users, we have a specific guide using Nix:

- [Linux Development Setup with Nix](./nix-linux-setup.md)

## Documentation Specifics

For Documentaion, we have a specific guide using VitePress:

- [Website & Docs Development](./website.md)

---

## File: docs/development/nix-linux-setup.md

# Development Environment Setup using Nix or Nix Flakes on Linux x86_64

[Nix flakes](https://nixos.wiki/wiki/Flakes) can be used to provide version controlled reproducible and cross-platform development environments. The key files are:

- `flake.nix`: Defines the flake inputs and outputs, including development shells.
- `shell.nix`: This file defines the dependencies and additionally adds support for the Impure `nix-shell` method. This is used by the flake to create the dev environment.
- `flake.lock`: Locks the versions of inputs for reproducibility.

## Prerequisites

- [Nix](https://nixos.org/download.html) the package manager or NixOs installed on Linux (x86_64-linux)
- Optional: flakes support enabled.

## Using the Development Shell

You have two primary ways to enter the development shell with Nix:

### 1. Using `nix develop` (flake-native command)

This is the recommended way if you have Nix with flakes support. Flake guarantee the versions of the dependencies and can be controlled through `flake.nix` and `flake.lock`.

```bash
nix develop
```

This will open a shell with all dependencies and environment configured as per the `flake.nix` for (`x86_64-linux`) systems only at this time.

### 2. Using `nix-shell` (that's why shell.nix is a separate file)

If you want to use traditional `nix-shell` tooling which uses the nixpkgs version of your system:

```bash
nix-shell
```

This will drop you into the shell environment defined in `shell.nix`. Note that this is not flake-native method and does not use the locked nixpkgs in `flake.lock` so exact versions of the dependancies is not guaranteed.

## What's Inside the Dev Shell?

- The environment variables and packages configured in `shell.nix` will be available.
- The package set (`pkgs`) used aligns with the versions locked in `flake.lock` to ensure reproducibility.

## Example Workflow Using Flakes

```bash
# Navigate to the project root folder which contains the flake.nix, flake.lock and shell.nix files.

cd /home/user/dev/Libation

# Enter the flake development shell (Linux x86_64)

nix develop

# run VSCode or VSCodium from the current shell environment

code .

# Run or Debug using VSCode and VSCodium using the linux Launch configuration.
```



You can also Build and run your application inside the shell.

```bash
dotnet build ./Source/LibationAvalonia/LibationAvalonia.csproj -p:TargetFrameworks=net9.0 -p:TargetFramework=net9.0 -p:RuntimeIdentifier=linux-x64
```

## Notes

- Leaving the current shell environemnt will drop all added dependancies and you will not be able to run or debug the program unless your system has those dependancies defined globally.
- To exit the shell environment voluntarily use `exit` inside the shell.
- Ensure you have no conflicting `nix.conf` or `global.json` that might affect SDK versions or runtime identifiers.
- Keep your `flake.lock` file committed to ensure builds are reproducible for all collaborators.

## References

- [Nix Flakes - NixOS Wiki](https://nixos.wiki/wiki/Flakes)
- [Nix.dev - Introduction to Nix flakes](https://nix.dev/manual/nix/2.28/command-ref/new-cli/nix3-flake-init)
- [Nix-shell Manual](https://nixos.org/manual/nix/stable/command-ref/nix-shell.html)

---

## File: docs/development/website.md

# Website & Docs

The [getlibation.com](https://getlibation.com) site is built with [VitePress](https://vitepress.dev/). Site content lives in this repo: the home page is `index.md` at the root, documentation pages are under `docs/`, and site config / theme files are in `.vitepress/`.

For VitePress details (markdown, routing, and more), see the [VitePress guide](https://vitepress.dev/guide).

## Prerequisites

- [Node.js](https://nodejs.org/) 18 or newer

## Run the site locally

From the **repository root** (`Libation/`, where `package.json` is):

### One-time setup

Install npm dependencies (needed once after cloning, or again after dependency changes):

```bash
npm install
```

### Start the dev server

```bash
npm run docs:dev
```

Then open `http://localhost:5173` in your browser. The server reloads when you edit markdown or theme files.

Stop it with `Ctrl+C` in that terminal.

### Other commands

```bash
# Production build (output: .vitepress/dist)
npm run docs:build

# Preview the production build locally
npm run docs:preview
```

## Adding pages

New markdown files are routed from their path automatically (for example `docs/getting-started.md` -> `/docs/getting-started`, or `donate.md` at the repo root -> `/donate`).

To show a page in the sidebar or top nav, add it in `.vitepress/config.js`. A page can exist and be reachable by URL without being linked in navigation.

When you add a docs page that belongs in the documentation index, update **both**:

- `docs/index.md` (Documentation Overview)
- the **Documentation** section of root `index.md` (home page)

Those two TOC lists should stay the same. The home page has extra marketing content above them; the link lists should match.

---

## File: docs/advanced/advanced.md

# Advanced

## Files and Folders

To make upgrades and reinstalls easier, Libation separates all of its responsibilities to a few different folders. If you don't want to mess with this stuff: ignore it. Read on if you like a little more control over your files.

- In Libation's initial folder are the files that make up the program. Since nothing else is here, just copy new files here to upgrade the program. Delete this folder to delete Libation.

- In a separate folder, Libation keeps track of all of the files it creates like settings and downloaded images. After an upgrade, Libation might think that's its being run for the first time. Just click ADVANCED SETUP and point to this folder. Libation will reload your library and settings.

- The last important folder is the "books location." This is where Libation looks for your downloaded and decrypted books. This is how it knows which books still need to be downloaded. The Audible id must be somewhere in the book's file or folder name for Libation to detect your downloaded book.

## Settings

- Allow Libation to fix up audiobook metadata. After decrypting a title, Libation attempts to fix details like chapters and cover art. Some power users and/or control freaks prefer to manage this themselves. By unchecking this setting, Libation will only decrypt the book and will leave metadata as-is, warts and all.

In addition to the options that are enabled if you allow Libation to "fix up" the audiobook, it does the following:

- Adds the `TCOM` (`@wrt` in M4B files) metadata tag for the narrators.
- Sets the `©gen` metadata tag for the genres.
- Unescapes the copyright symbol (replace `&#169;` with `©`)
- Replaces the recording copyright `(P)` string with `℗`
- Replaces the chapter markers embedded in the aax file with the chapter markers retrieved from Audible's API.
- Sets the embedded cover art image with the 500x500 px cover art retrieved from Audible

## Custom Theme Colors

In Libation Chardonnay (not Classic), you may adjust the app colors using the built-in theme editor. Open the Settings window (from the menu bar: Settings > Settings). On the "Important" settings tab, click "Edit Theme Colors".

### Theme Editor Window

The theme editor has a list of style names and their currently assigned colors. To change a style color, click on the color swatch in the left-hand column to open the color editor for that style. Observe the color changes in real-time on the built-in preview panel on the right-hand side of the theme editor.

You may import or export themes using the buttons at the bottom-left of the theme editor.
"Cancel" or closing the window will revert any changes you've made in the theme editor.
"Reset" will reset any changes you've made in the theme editor.
"Defaults" will restore the application default colors for the active theme ("Light" or "Dark")
"Save" will save the theme colors to the ChardonnayTheme.json file and close the editor.

> [!NOTE]
> You may only edit the currently applied theme ("Light" or
> "Dark").

### Video Walkthrough

The below video demonstrates using the theme editor to make changes to the Dark theme color pallet.

<video src="https://github.com/user-attachments/assets/05c0cb7f-578f-4465-9691-77d694111349" controls></video>