Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

MLM is both an auto downloader and a library organizer. Both parts are optional so either can be replaced with e.g. RSS or booktree if you prefer. And even if you use both, you can still add torrents manually and have them organized, and/or use booktree for collections or files that are not from MaM.

This allows you to automatically download for example bookmarks and have them hardlinked into an organized library folder for e.g. ABS. It also follows a list of preferred formats so that if you first download the mp3 version of a book and then later download an m4b, the mp3 will be automatically removed from your library and optionally moved to a different category or tagged in qbittorrent.

The auto downloader can both use configured searches similar to RSS, and Goodreads lists as input. It keeps track of your unsat slots and will by default always leave at least 10 open. It also keeps track of your library and avoids downloading e.g. an mp3 torrent if you already have the m4b.

The library organizer will only link one audio file type and one ebook file type per torrent. So e.g. an audiobook torrent with both m4b and pdf files will have both linked, but an ebook torrent with both and epub and mobi will only have the epub linked.

Limitations:

  • At the moment MLM only works with qbittorrent
  • MLM works with torrents, meaning collections (multiple books in a single torrents) will be treated as one book (however if you link these with booktree, MLM will not touch those files)
  • MLM works with torrents from MaM, meaning files not via a torrent from here can not be handled (however if you link these with booktree, MLM will not touch those files)
  • Metadata will only ever be as good as it is on MaM. Please submit updated information or contact staff on torrents to help improve metadata for everyone

Installation

Recommended installation methods are:

Docker

The recommended installation method on anything but Windows is to use docker compose.

Example docker compose file:

services:
  mlm:
    image: ghcr.io/stirlingmouse/mlm:main 
    volumes:
      - ./config:/config # folder for the config file, place it in config/config.toml
      - ./data:/data # folder where mlm will keep a database
      - /mnt/Data:/mnt/Data # folder where your downloaded files and library can be accessed from
    environment:
      TZ: Europe/London # https://en.wikipedia.org/wiki/List_of_tz_database_time_zones

Windows

For Windows, download the installer from: https://github.com/StirlingMouse/MLM/releases/latest

After starting MLM a small mouse icon will be placed in your tray icons. From here you can access the Web UI, the config file, and the logs directory.

If using the Windows qBittorrent version, also remember to enable its Web UI under settings.

Configure MLM to connect to qbittorent with a configuration like:

[[qbittorrent]]
url = "http://localhost:8080"
username = "qbittorent username"
password = "qbittorent password"

Make sure the port number (8080) matches the port configured in qBittorrent, as well as the username and password. Or leave those out if "Bypass authentication for clients on localhost" is checked.

Configuration

All configuration is done in a TOML file, see chapters for examples and explanations.

Basic Configuration

A good basic configuration would look something like this:

mam_id = "set mam_id here"

[[qbittorrent]]
url = "http://localhost:8011" # update this to your qbit web ui URL
username = "qbittorent username"
password = "qbittorent password"

[qbittorrent.on_cleaned]
category = "Seed" # change the qbittorrent category to Seed when a torrent gets replaced with a better one

[[autograb]] # you can skip the autograb sections if you don't want it to grab anything automatically
type = "bookmarks" # this grabs all your bookmarks that are freeleech (global, personal or VIP)
cost = "free" # cost defaults to free so you never accidentally ruin your ratio if you forget to set it

[[autograb]]
type = "bookmarks" # this grabs all your bookmarks smaller than 15 MiB even if they are not freeleech
cost = "all"
max_size = "15 MiB"

[[tag]] # you can skip the tag sections if you don't use tagging or categories in qbittorrent
categories = { audio = true, ebook = false } # this selects all audiobook torrents
category = "Audiobooks" # and sets the qbittorrent category to "Audiobooks"

[[tag]]
categories = { audio = false, ebook = true }
category = "Ebooks"

[[library]] # you can skip the library sections if you don't want MLM to organize a library folder for you
category = "Audiobooks" # this selects all torrents with the category Audiobooks
library_dir = "/mnt/Data/Library/Audiobooks" # this is where your nicely organized audiobooks will end up

[[library]]
category = "Ebooks"
library_dir = "/mnt/Data/Library/Ebooks"

MaM ID

The mam_id is a security session you create on https://www.myanonamouse.net/preferences/index.php?view=security

qBittorrent

One or more [[qbittorrent] blocks has to be configured. If multiple qBittorrent blocks are configured, torrents from all qBittorrent instances can be linked, but newly downloaded torrents will only be added to the first configured instance.

Basic configuration just requires an URL:

[[qbittorrent]]
url = "http://localhost:8011"

If your qBittorrent instance requires authentication, set it with:

username = "qbittorent username"
password = "qbittorent password"

Path Mapping

If your qBittorrent instance is set up so that it uses different paths to refer to a file than MLM, you'll need to configure path mapping.

For Example, if qBittorrent refers to a file with /downloads/My Audiobook.m4b but MLM will find it under /mnt/data/My Audiobook.m4b you need the setting:

path_mapping = { "/downloads" =  "/mnt/data" }

On Cleaned

If you download a better version of a torrent (e.g. an m4b torrent when you previously had an mp3), the older torrent will be "cleaned". By default this only means having its hardlinks removed. However you can also change the qBittorrent category and/or set tags.

[qbittorrent.on_cleaned]
category = "Seed"
tags = [ "superseded" ]

Library Organizer

The library organizer hardlinks files into a directory with the directory structure:

"Author/Series/Series #1 - Title {Narrator}/" # For audiobooks with a series
"Author/Title {Narrator}/" # For audiobooks without a series
"Author/Series/Series #1 - Title/" # For ebooks with a series
"Author/Title/" # For ebooks without a series

If you do not want to include narrator in the audiobook folder names, the top level option

exclude_narrator_in_library_dir = true

can be set. This makes the MLM directory structure match booktree which allows easier migration from booktree.

You can select either a category or a qBittorrent download directory to link to a library.

Link all torrents with category "Audiobooks" to "/mnt/Data/Library/Audiobooks":

[[library]]
category = "Audiobooks"
library_dir = "/mnt/Data/Library/Audiobooks"

Link all torrents with download directory/save location "/mnt/Data/Downloads/Ebooks" to "/mnt/Data/Library/Audiobooks":

[[library]]
download_dir = "/mnt/Data/Downloads/Ebooks"
library_dir = "/mnt/Data/Library/Ebooks"
Note that you can use either `category` or `download_dir` to select torrents for a library, not both.

It's possible to use tags to additionally filter down which torrents to link:

[[library]]
download_dir = "/mnt/Data/Uploads/Audiobooks"
library_dir = "/mnt/Data/Library/Audiobooks"
allow_tags = [ "library" ] # only links torrents with tag "library"
deny_tags = [ "skip" ] # but not if also having tag "skip"

When specifying multiple allow_tags, the torrent just need to have any of them to be linked. When specifying multiple deny_tags, the torrent just need to have any of them to be skipped.

Method

It's possible to instead copy or symlink files to the library if hardlinking does not work for you:

method = "hardlink_or_copy" # Try hardlinking but fallback to copy if required
method = "hardlink_or_symlink" # Try hardlinking but fallback to symlink if required
method = "copy" # Always copy files
method = "symlink" # Always symlink files

Example usage:

[[library]]
category = "Audiobooks"
library_dir = "/mnt/Data/Library/Audiobooks"
method = "copy"

File Types

A list of audio and ebook file types in order of preference that will be linked from this library, the default config are:

audio_types = ["m4b", "m4a", "mp4", "mp3", "ogg"]
ebook_types = ["cbz", "epub", "pdf", "mobi", "azw3", "azw", "cbr"]

Only one format from each list will be linked. This means that a multi-format ebook torrent will only have its best format linked. E.g. for a torrent with epub, pdf and mobi files, only the epub will be linked. But as one format from each list is selected, an audiobook torrent with a supplementary PDF will have both the audiofiles and the PDF linked.

Autograbbers

Autograbbers are predefined searches that are run periodically. Very similar to RSS. MLM monitors both your ratio and your unsat slots to try and avoid causing any problems for your account. By default MLM will never download a torrent that will take you below 2 in ratio, and always leave 10 unsats slots free.

A basic configuration for an autograbber look like this:

[[autograb]]
type = "bookmarks"
cost = "free"

Most of the fields you can set on an autograb block is shared by other blocks that select torrents, see them in Search Filters.

Type

type selects which torrents to search, the different options are:

type = "bookmarks"        # Searches your bookmarks
type = "freeleech"        # Searches the freeleech list
type = "new"              # Searches new/latest torrents
type = { uploader = 123 } # Searches uploads by user with id 123

Bookmarks and Freeleech will go through up to 50 pages of the search result, allowing you to find all of them. While New and Uploader will only ever see the 100 latest torrents matching your search filters.

Cost

cost selects which kind of torrents to grab. This is by default free but the different options are:

cost = "free"      # Free for you in any way, VIP, Personal Freeleeech or Global Freeleech
cost = "wedge"     # Apply a freeleech wedge before downloading
cost = "try_wedge" # Try to apply a freeleech wedge before downloading, but still download if that is not possible
cost = "ratio"     # Download the torrent even if you will take a ratio hit

Query and Search In

A search query, same as the search field on MaM, example:

query = '"Akwaeke Emezi"|"T J Klune"'
search_in = [ "author" ] # only search in authors

The search_in control what the query should match, here it only matchers author names but it can be one or more of:

  • author
  • description
  • filenames
  • filetypes
  • narrator
  • series
  • tags
  • title

The MaM search is very powerful, checkout the on-site search quide for what you can do with it: https://www.myanonamouse.net/guides/?gid=37729

Name

name = "My Bookmarks"

Name this autograbber, this allows you to see which autograbber is responsible for downloading a torrent in the event log.

Sort By

Normally results are sorted by newest first so that the New and Uploader types gets the latest torrents. The options are:

sort_by = "oldest_first" # Find oldest matching torrents instead of the newest ones
sort_by = "random"       # Randomize order
sort_by = "low_seeders"  # Find lowest seeded torrents
sort_by = "low_snatches" # Find lowest snatched torrents

Random is interesting as it can be used to effectively fill your unsat slots with something, example to grab random LGBT torrents if you have more than 50 unsat slots free:

[[autograb]]
name = "LGBT"
type = "new"
query = 'm4b|epub'
search_in = [ "filtetypes" ]
sort_by = "random"
flags = { lgbt = true }
unsat_buffer = 50

Max Pages

max_pages = 5

How many pages (of 100 torrents) of the search should be fetched. By default, type Bookmarks and Freeleech will go through up to 50 pages of the search result, while other types only fetch a single page.

Search Interval

search_interval = 60

In minutes, how often this search should be done

Unsat Buffer

unsat_buffer = 10

How many unsat slots that should be left open so that you have room to download torrents manually or with other autograb blocks.

Wedge Buffer

wedge_buffer = 10

How many wedges that should be left unused so that you have can download torrents manually or with other autograb blocks.

Max Active Downloads

name = "My Bookmarks"
max_active_downloads = 10

How many currently active downloads are allowed for this autograb block. For example useful if you grab low-seeded torrents. Requires that you also set a name so the active torrents can be identified.

Category

category = "bookmarks"

A qBittorrent category to set on all torrents downloaded by this autograbber. Overrides any [[tag]] blocks you might have.

Dry Run

dry_run = true

Prevents the autograbber from actually downloading anything. You can use this to look at the logfiles/docker logs for the searches, or use the search on MaM links on the config page, to help figure out if you are matching the torrents that you want.

Goodreads Import

Goodreads lists/bookshelves can be used as a source for autograbbing books. On the bookshelf page, use the RSS icon: to get the URL to give to MLM.

Example configuration:

[[goodreads_list]]
url = "https://www.goodreads.com/review/list_rss/..." # RSS feed of a Goodreads list

[[goodreads_list.grab]] # A block deciding what torrents to grab from the list,
                        # if a torrent matches multiple blocks, the first one is used
cost = "all"
languages = [ "english" ] # you can use the same search filters as for autograb blocks
max_size = "15 MiB"

[[goodreads_list.grab]]
cost = "wedge" # automatically wedge torrents before download, as we have an cost=all block
               # before this one with a max_size, this will only wedge torrents > 15 MiB
languages = [ "english" ]

Each list needs at least one goodreads_list.grab block that select what torrents to grab. To see how to select torrents and what fields you can set, see Search Filters.

Search Filters

The [[autograb]], [[goodreads_list.grab]], and [[tag]] blocks in the config take options that map the same search filters you find on MaM.

Categories

categories = { audio = false, ebook = [ "food" ] }

Selects torrents by category, audio = false filters out all audiobook torrents. audio = true would select all audiobook torrents. If you only want to select some categories of a main type, give an array of category names which are the same as you find in the search form.

The above example selects Ebook - Food only.

To select only audiobooks:

categories = { audio = true, ebook = false }

To select Fantasy and Romance from either main cat:

categories = { audio = [ "fantasy", "romance" ], ebook = [ "fantasy", "romance" ] }

Languages

languages = [ "English" ]

An array of languages to allow. Uses the same names as can be found in the search form.

To select both English and French torrents:

languages = [ "English", "French" ]

Flags

flags = {
    crude_language = false, # Exclude torrents with the "crude language" flag
    violence = false, # Exclude torrents with the "violence" flag
    some_explicit = false, # Exclude torrents with the "some explicit" flag
    explicit = true, # Only select torrents with the "explicit" flag
    abridged = true, # Only select torrents with the "abridged" flag
    lgbt = true, # Only select torrents with the "lgbt" flag
}

Flags are set to true or false to either require or exclude them. Skip a flag if you do not care about if it's set or not. Unlike the normal search form you can both exclude and select for at the same time.

To filter out explicit torrents when ignoring other flags:

flags = { explicit = false }

To only select abridged torrents when ignoring other flags:

flags = { abridged = true }

Exclude uploader

exclude_uploader = [ "username" ]

A list of uploader usernames to filter out, useful if you don't want to download your own uploads.

Size

min_size = "100 KiB"
max_size = "1.2 GiB"

Only select torrents above/below the specified size

Upload date

uploaded_after = "2020-06-01"
uploaded_before = "2025-01-01"

Only select torrents uploaded before/after the specified date. Inclusive so this will also select torrents uploaded on 2020-06-01 and 2025-01-01.

Peers

min_seeders = 10
max_seeders = 50
min_leechers = 10
max_leechers = 50
min_snatched = 10
max_snatched = 50

Only select torrents with seeders/leechers/snatches above or below the specified value. Inclusive so this also selects torrents with 10 or 50 seeders.

Tagging

[[tag]] blocks can be used to set a category or tags on torrents in qBittorrent.

Fields for selecting torrents are the same as for other blocks, see them in Search Filters.

Category

categories = { audio = false, ebook = true }
category = "Ebooks"

Sets the qBittorrent category of a torrent, in this example of all ebook torrents. If multiple [[tag]] blocks matches a torrent, only the first category is used.

Tags

[[tag]]
flags = { explicit = true }
tags = [ "explicit" ] # However tags from different tag section merge so a torrent can get both explicit, abridged and one of the categories

Sets qBittorrent tags on a torrent, in this example sets the tag explicit on all torrents that have the explicit flag. If multiple [[tag]] blocks matches a torrent, tags from all of them are set.

Audiobookshelf

MLM creates metadata.json files when linking which allows audiobookshelf to get great metadata without further configuration. However, you can configure it to also talk with audiobookshelf via its API for further integration.

[audiobookshelf]
url = "https://audiobookshelf.my.domain"
token = ""

token is an API Key that you create in Audiobookshelf > Settings > API Keys > Add API Key.

When this option is set, MLM will add ABS links on all torrents that have been picked up by ABS so that you can easily open them from MLM.

It will also update the metadata in ABS after linking, so if the uploader or torrent mods correct a torrent, that gets reflected in ABS.

Finally, if a torrents gets cleaned without a replacement (can be done manually in the WebUI) or is replaced under a different path, MLM will automatically remove the book from ABS where they otherwise would show up as "issues" with "missing files".

Search

MLM has a very basic torrent search interface for manually searching and downloading torrents. This will over time be extended to support all search options. For now the only configuration option for the search is:

[search]
wedge_over = "30 MiB"

Wedge Over

A size of torrents which over that will be automatically wedged when you select them for download in the WebUI (shown by the download arrow turning blue)

Full Example

An advanced example showcasing many options:

mam_id = "set mam_id here"

# Below top-level settings show the default values
web_host = "0.0.0.0" # What address to bind the web server to
web_port = 3157 # What port to bind the web server to
unsat_buffer = 10 # How many unsat slots to leave empty
wedge_buffer = 0  # How many wedges to leave unused
min_ratio = 2 # Lowest ratio MLM is allowed to use. If downloading a torrent would take you below this ratio, MLM will not download it.
add_torrents_stopped = false
exclude_narrator_in_library_dir = false
search_interval = 30 # in minutes, how often a search should be done for the autograbs
goodreads_interval = 60 # in minutes, how often the goodreads lists should be checked and books searched for
link_interval = 10 # in minutes, how often the library organizer should query qbittorent for new torrents
audio_types = ["m4b", "m4a", "mp4", "mp3", "ogg"] # order of preference for audiobook formats, formats not in this list will not be downloaded or linked
ebook_types = ["cbz", "epub", "pdf", "mobi", "azw3", "azw", "cbr"] # order of preference for ebook formats, formats not in this list will not be downloaded or linked

[[qbittorrent]]
url = "http://localhost:8011"
username = "qbittorent username"
password = "qbittorent password"

[qbittorrent.on_cleaned]
category = "Seed"
tags = [ "superseded" ]

[[qbittorrent]] # you can have multiple qbittorent instances. However autograbbed torrents will only be added to the first one
url = "http://localhost:8012"

[qbittorrent.on_cleaned] # Every qbit instance has their own on_cleaned rules
tags = [ "superseded" ]

[[autograb]]
type = "freeleech" # autograbs from the global freeleech list only, not PF or VIP
languages = [ "english" ] # you can filter by language
flags = { lgbt = true } # or flags, true requires the flag to exist
min_size = "50 MiB"
exclude_uploader = [ "Oriel" ] # and exclude certain uploaders (excluding yourself is a good idea if you use multiple clients!)
unsat_buffer = 50 # you can set a different unsat buffer for a specific autograb if you don't want it to overwhelm your slots

[[autograb]]
type = "new" # autograbs from newly uploaded torrents
query = '"Akwaeke Emezi"|"T J Klune"' # a normal search query, see the search guide for what you can do: https://www.myanonamouse.net/guides/?gid=37729
search_in = [ "author" ] # only search in authors
flags = { violence = false } # with false we forbid the flag
dry_run = true # this makes MLM not actually download the selected torrents, only log them, for testing your search

# Other filters you can use
# uploaded_after = "2020-06-01"
# uploaded_before = "2025-01-01"
# min_seeders = 10
# max_seeders = 10
# min_leechers = 10
# max_leechers = 10
# min_snatched = 10
# max_snatched = 10

[[goodreads_list]]
url = "https://www.goodreads.com/review/list_rss/..." # RSS feed of a Goodreads list

[[goodreads_list.grab]] # A block deciding what torrents to grab from the list, if a torrent matches multiple block the first one is used
cost = "all"
languages = [ "english" ] # you can use the same search filters as for autograb blocks
max_size = "15 MiB"

[[goodreads_list.grab]]
cost = "wedge" # automatically wedge torrents before download, as we have an cost=all block before with a max_size, this will only wedge torrents > 15 MiB
languages = [ "english" ]

[[goodreads_list]]
url = "other list"

[[goodreads_list.grab]] # each list has their own grab blocks to select torrents
cost = "free"
prefer_format = "audio" # If a torrent is available in both ebook and audio, only download the audiobook
                        # however this still allow downloading an ebook if no audiobook is available.
                        # leave this property out to download both formats.
                        # If you never want to download an ebook, use categories = { audio = true, ebook = false } instead

[[tag]]
categories = { audio = false, ebook = [ "food" ] }
category = "Cookbooks" # Cookbooks will win over Ebooks as it is defined first and a torrent can only have one category

[[tag]]
flags = { explicit = true }
tags = [ "explicit" ] # However tags from different tag section merge so a torrent can get both explicit, abridged and one of the categories

[[tag]]
flags = { abridged = true }
tags = [ "abridged" ]

[[tag]] # you can skip the tag sections if you don't use tagging or categories in qbittorrent
categories = { audio = true, ebook = false } # this selects all audiobook torrents
category = "Audiobooks" # and sets the qbittorrent category to "Audiobooks"

[[tag]]
categories = { audio = false, ebook = true }
category = "Ebooks"

[[library]]
category = "Audiobooks"
library_dir = "/mnt/Data/Library/Audiobooks"

[[library]]
download_dir = "/mnt/Data/Downloads/Ebooks" # you can also specify a library using the download_dir
library_dir = "/mnt/Data/Library/Ebooks"

[[library]]
download_dir = "/mnt/Data/Uploads/Audiobooks" # multiple libraries can contribute to the same library dir, for example if you keep your own uploads separate
library_dir = "/mnt/Data/Library/Audiobooks"
allow_tags = [ "library" ] # you can also require the torrents to have certain tags
deny_tags = [ "skip" ] # or disallow some