Thanks to visit codestin.com
Credit goes to github.com

Skip to content
forked from varthe/Defaulterr

Change the default audio and subtitle streams for items in Plex per user based on codec, language, keywords, and more

License

TJZine/Defaulter

 
 

Repository files navigation

Defaulterr

Change the default audio and subtitle streams for items in Plex per user based on codec, language, keywords and more. Customizable with filters and groups. Can run on a schedule or for newly added items using a Tautulli webhook.

Getting Started

Docker Compose

services:
  defaulterr:
    image: varthe/defaulterr:latest
    container_name: defaulterr
    hostname: defaulterr
    ports:
      - 3184:3184
    volumes:
      - /path/to/config:/config
      - /path/to/logs:/logs
    environment:
      - TZ=Europe/London
      - LOG_LEVEL=info

Automated Docker builds

Forks hosted on GitHub can use the included Build and publish Docker image workflow (.github/workflows/docker-publish.yml) to automatically produce and publish container images to the GitHub Container Registry (GHCR).

  1. Enable GitHub Actions for your fork (required for public forks) and ensure the repository has permission to publish packages (Settings → Actions → General → Workflow permissions → "Read and write" for the GITHUB_TOKEN).
  2. Optionally adjust the workflow's IMAGE_NAME environment variable if you prefer a different image name than defaulterr.
  3. Push to main, create a tag (e.g. v1.2.3), or run the workflow manually from the Actions tab—builds on branches and tags are automatically pushed to ghcr.io/<owner>/<image> with sensible tags (branch, tag, commit SHA, and latest for main).
  4. Update your deployment manifests to reference the published image (for example ghcr.io/<owner>/defaulterr:latest).

Pull requests still build the image but skip the push step, allowing verification without publishing.

Unraid Template

Click here to download the Unraid template.

Configuration Overview

Your configuration is defined in config.yaml. Below is a breakdown of the required settings and optional configurations. See config.yaml for an example of an implementation.

REQUIRED SETTINGS

  • plex_server_url: Your Plex server URL.
  • plex_owner_name: Used to identify the owner, allowing them to be included in groups.
  • plex_owner_token: The server owner's token.
  • plex_client_identifier: Find this value using the instructions below.

Obtaining the Client Identifier

  1. Go to https://plex.tv/api/resources?X-Plex-Token={your_admin_token} (replace {your_admin_token} with your token).
  2. Search for your server and find the clientIdentifier value. This HAS TO be the server's identifier, not the owner's.

RUN SETTINGS

  • dry_run: Set to True to test filters. This mode won't update users and is recommended to verify that your filters work correctly. It overwrites other run settings. You can also enable it at runtime with --dry-run or DRY_RUN=1 (CLI options override environment variables, which override config values).
  • partial_run_on_start: Set to True to do a partial run on application start.
    • WARNING: The first run may take a LONG time to complete as it will update all existing media. Subsequent runs will only update any new items added since the last run.
  • partial_run_cron_expression: Specify a cron expression (e.g., 0 3 * * * for daily at 3:00 AM) to do a partial run on a schedule. You can create and check cron expressions at https://crontab.guru/.
  • clean_run_on_start: Set to True to update all existing media on application start. Should only be used if you want to re-apply a new set of filters on your libraries.
  • skipInaccessibleItems: Set to True to skip per-user updates that return HTTP 403 because the user can't access the item. When enabled the run will continue, log skip counts per user, and finish with exit code 0. Defaults to False. You can also enable it via the SKIP_INACCESSIBLE_ITEMS environment variable.

Diagnostics & Observability

Diagnostics are opt-in and disabled by default so an upgrade preserves historic logging behaviour. Enable only what you need for a run—the CLI flag always wins over an environment variable, which in turn overrides the configuration file.

Flag precedence & defaults

CLI flag Environment variable Default Description
`--log-user-summary[=true false]` LOG_USER_SUMMARY false
`--log-json-user-summary[=true false]` LOG_JSON_USER_SUMMARY false
--audit-dir=<path> AUDIT_DIR disabled Write per-run audit reports to the given directory (JSON + CSV). Files are created as run-YYYYMMDD-HHMMSS.json / .csv.
--dry-run DRY_RUN false Shortcut to enable dry-run mode without editing the config file.

Logging modes

When summaries are enabled, two formats are available:

  • Human-readable summaries are compact and optimized for console review:

    [INFO]: User updates (group=noLossless, part=23614, title='1917'):
      keltonsnyder: audio='AC3 5.1' (id=12345) [success]
      rossni6: [skipped: HTTP 403]
    
  • JSON summaries are single-line payloads that parse cleanly with jq or any JSON parser:

    [INFO]: {"event":"user_updates","group":"noLossless","partId":23614,"title":"1917","library":"Movies Home","users":[{"name":"keltonsnyder","audio":{"id":12345,"label":"AC3 5.1"},"status":"success"},{"name":"rossni6","status":"skipped","reason":"HTTP 403"}]}
    

The JSON mode includes every field from the human summary plus HTTP status codes when available, and the owner account is omitted unless explicitly placed in a group. Startup also emits a digest and owner-safety notice so operators can confirm membership ahead of the run:

[INFO]: Group digest (library='Movies Home'):
  - noLossless: members=keltonsnyder,davidantillon,rossni6 (3)
    resolvedTokens: 3/3 ok; restrictedAccess: 0 (updates skipped for missing tokens)
[INFO]: Owner 'Tristan' is not included in any group; no owner updates will be performed.

Audit exports

Providing --audit-dir (or setting AUDIT_DIR) enables structured per-run audit exports. Each run writes two files—run-YYYYMMDD-HHMMSS.json and run-YYYYMMDD-HHMMSS.csv—with one row per (group × user × item × actionType) attempt.

Field Description
timestamp ISO timestamp when the attempt started.
libraryName Library containing the media item.
ratingKey / partId Plex identifiers for the item and part being updated.
title Human-friendly title of the item.
group / user Configured group and username being updated.
actionType audio, subtitles, or audio+subtitles depending on the streams touched.
fromStreamId / fromLabel Previous stream identifiers and labels (if known).
toStreamId / toLabel Target stream identifiers and labels requested.
status success, skipped, error, or dry_run.
reason Short diagnostic (e.g. HTTP 403, no_token).
httpStatus HTTP response code when available.
durationMs Time spent on the attempt.

The JSON export is a simple array of objects with these fields; the CSV uses the same header order for quick spreadsheet imports. Tokens are never written in full—any time a token surfaces in logs it is masked to ***…LAST6.

Managed user caveats

Managed accounts remain supported and keep the same configuration syntax as before. Diagnostics help confirm that Defaulterr updates only the accounts you intend, but Plex itself stores some preferences per underlying account. If multiple managed profiles reuse a single token, Plex may still apply changes across them. The audit trail and per-user headers make the script’s actions transparent so you can distinguish Defaulterr activity from Plex-side behaviour.

GROUPS

Groups define collections of users with shared filters:

  • Usernames must match exactly as they appear in Plex, including capitalization and special characters.
  • Managed accounts require additional setup. Read below.
  • Optionally, use $ALL in place of a username to include all users from your server.

Example:

groups:
  serialTranscoders: # Can be named anything
    - varthe
    - UserWithCapitalLetters # EXACTLY like in Plex
    - $ALL # Grabs all users from the server
  subtitleEnjoyers: # Can be named anything
    - varthe
  deafPeople: # Can be named anything
    - varthe
  weebs: # Can be named anything
    - varthe

MANAGED ACCOUNTS (optional)

To include managed accounts in groups you will need to supply their tokens manually. See this comment by Blind_Watchman on how to obtain their tokens. You have to do it like this because the regular tokens won't work.

Include them in the config file like below. Use the key (e.g user1) in groups.

managed_users:
  user1: token
  user2: token

Important: Plex keeps default audio/subtitle selections per Plex account, not per managed profile. If multiple entries share the same token (for example, the owner and the managed profiles underneath it), any update for one will affect all of them. Defaulterr logs a warning when it detects token reuse so you know shared preferences are in play. Some Plex clients may also display managed profile preferences as if they carry across profiles—double-check the audit trail if behaviour looks unexpected.

FILTERS

Filters define how audio and subtitle streams are updated based on specified criteria. The structure in config.yaml is as follows:

  • Library Name: The filter applies to a specific Plex library.
    • Group Name: Defines which group the filter targets.
      • Stream Type: Can be audio or subtitles.
        • include: Fields that MUST appear in the stream AND include the specified value
        • exclude: Fields that MUST NOT appear in the stream OR not be the specified value
        • on_match: Specifies filters for the other stream type if a match is found. For example, disable subtitles if a Spanish audio track is matched. Otherwise find Spanish subtitles.

Note: Any field (e.g., language, codec, extendedDisplayTitle) can either be a single value or an array of values. This allows flexibility in filtering criteria by matching multiple options when needed.

Multiple groups and filters can be defined per library, with the first matching filter being applied. If no filters match, the item remains unchanged in Plex. Filters can utilize any property in the stream object returned by Plex. See example.json for examples.

filters:
  Movies: # Library name
    serialTranscoders: # Group name
      audio:
        # Audio Filter 1 - First English audio track that's not TRUEHD/DTS and not a commentary
        - include:
            language: English # Needs to be in the original language, e.g Español for Spanish
            # languageCode: eng # Alternative to the above, e.g. jpn for Japanese
          exclude:
            codec:
              - truehd
              - dts
            extendedDisplayTitle: commentary
        # Audio Filter 2 - Any English track (fallback if the above filter doesn't match)
        - include:
            language: English
    subtitleEnjoyers:
      subtitles:
        # Subtitle Filter 1 - First English track that's not forced
        - include:
            language: English
          exclude:
            extendedDisplayTitle: forced
    deafPeople:
      subtitles:
        # Subtitle Filter 1 - First English SDH track
        - include:
            language: English
            hearingImpaired: true # SDH
  Anime: # Library name
    weebs: # Group name
      audio:
        # Audio Filter 1 - First English track with disabled subtitles
        - include:
            language: English
          on_match:
            subtitles: disabled # Set subtitles to "off" in Plex
        # Audio Filter 2 - Japenese track with English subtitles
        - include:
            languageCode: jpn # Japenese
            on_match:
              subtitles:
                # Full subtitles -> Dialogue subtitles -> Anything without the word "signs"
                - include:
                    language: English
                    extendedDisplayTitle: full
                - include:
                    language: English
                    extendedDisplayTitle: dialogue
                - include:
                    language: English
                  exclude:
                    extendedDisplayTitle: signs

Diagnostics Acceptance Criteria

These diagnostics are considered production-ready when the following conditions are met:

  • Upgrading without new flags preserves legacy logging output and runtime behaviour.
  • Enabling the logging flags produces the documented human and JSON summaries with valid JSON payloads.
  • Startup digests list the correct members, token resolution counts, and owner-safety notice, with missing tokens warned once per run.
  • Audit exports create JSON and CSV files with one row per attempt and the schema shown above.
  • All sensitive tokens are masked everywhere outside of configuration storage.
  • Per-user HTTP requests always include the intended token and never reuse a default/owner credential for another account.
  • Automated tests cover success, dry-run, missing token, HTTP 403, JSON validation, audit I/O, and per-request header isolation.

Manual Diagnostics Verification Plan

Operators who prefer a hands-on validation can follow this checklist after deploying an update:

  1. Baseline run – Execute a small partial run with diagnostics flags left at their defaults to confirm logs match the pre-upgrade format.
  2. Human summaries – Re-run with --log-user-summary to confirm the concise per-item summaries list the expected users and statuses.
  3. JSON summaries – Add --log-json-user-summary and pipe the log through jq '.' to validate that each JSON line parses cleanly and contains the documented keys.
  4. Audit exports – Enable --audit-dir=/tmp/audit-test, process a few items, and spot-check that the CSV/JSON row counts match the attempted updates and that tokens appear masked in any warnings.
  5. Session isolation – Temporarily supply an invalid token for a single user and confirm only that user is skipped (with HTTP 403 or no_token), while other users continue succeeding.

Tautulli Webhook Integration

To automate filter applications for newly added items:

  1. Go to Settings -> Notifications & Newsletters in Tautulli.
  2. Set Recently Added Notification Delay to 60 (increase if notifications are firing too early).
  3. Navigate to Settings -> Notification Agents.
  4. Add a new notification agent and select Webhook.
  5. Use the Defaulterr URL: http://defaulterr:3184/webhook.
  6. Choose POST for the Webhook Method.
  7. Enable the Recently Added trigger.
  8. Paste the following JSON data into JSON Data:
<movie>
{
  "type": "movie",
  "libraryId": "{section_id}",
  "mediaId": "{rating_key}"
}
</movie>

<show>
{
  "type": "show",
  "libraryId": "{section_id}",
  "mediaId": "{rating_key}"
}
</show>

<season>
{
  "type": "season",
  "libraryId": "{section_id}",
  "mediaId": "{rating_key}"
}
</season>

<episode>
{
  "type": "episode",
  "libraryId": "{section_id}",
  "mediaId": "{rating_key}"
}
</episode>

About

Change the default audio and subtitle streams for items in Plex per user based on codec, language, keywords, and more

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Languages

  • JavaScript 99.7%
  • Dockerfile 0.3%