{% extends "base.html" %} {% block title %}{{ _('Settings') }} — MediaForge{% endblock %} {% import "_settings_card_macro.html" as ext %} {% import "_settings_menu.html" as menu %} {% import '_start_page_form.html' as startpage %} {% block styles %} {% endblock %} {% set is_admin = not auth_enabled or (current_user and current_user.role == 'admin') %} {% block content %}
{% if is_admin %} {% endif %} {# In-page floating side menu (shared macro — single source of truth, also used by encoding.html). switchTab() (settings.js) is unchanged. "Encoding" links out to its own /encoding route (E2). Module-contributed settings tabs live on the Module Settings page now (see resolve_module_settings). #} {{ menu.settings_menu('tabs', is_admin=is_admin) }}
{{ _('General') }}
{{ _('General settings, language, system options and debug mode') }}
{{ _('Configure') }}
{% if is_admin %}
{{ _('Encoding') }}
{{ _('Configure FFmpeg encoding, hardware acceleration and upscaling profiles') }}
{{ _('Configure') }}
{% endif %} {# Start Page had a tab but no card here, so the overview claimed to be a complete map of Settings while silently skipping one page. #}
{{ _('Start Page') }}
{{ _('Which rows the home page shows, in which order, how many cards each holds and the default filters') }}
{{ _('Configure') }}
{{ _('Library') }}
{{ _('Media library scanning schedule, scan performance and statistics visibility') }}
{{ _('Configure') }}
{{ _('Design') }}
{{ _('Appearance themes, accent color, visual effects and layout options') }}
{{ _('Configure') }}
{% if is_admin %}
{{ _('Sources') }}
{{ _('Configure streaming providers, mirrors and search order') }}
{{ _('Configure') }}
{% endif %}
{{ _('Downloads') }}
{{ _('Download paths, naming template, language preferences and bandwidth limits') }}
{{ _('Configure') }}
Auto-Sync
{{ _('Automatic synchronization, schedules, weekly blocks and adaptive intervals') }}
{{ _('Configure') }}
{{ _('Network & Access') }}
{{ _('DNS server configuration, Captcha solver behavior and reverse proxy base URL') }}
{{ _('Configure') }}
{% if is_admin %}
{{ _('Authentication') }}
{{ _('User management, roles, passwords and Single Sign-On (SSO/OIDC)') }}
{{ _('Configure') }}
API
{{ _('External REST API key, endpoint reference and integration documentation') }}
{{ _('Configure') }}
{{ _('Privacy & Telemetry') }}
{{ _('Crash reports, diagnostic telemetry and data privacy controls') }}
{{ _('Configure') }}
{{ _('Backup') }}
{{ _('Create, download and restore full system backups') }}
{{ _('Configure') }}
{% endif %}
{{ _('Updates') }}
{{ _('Check for updates, switch release channels and configure automatic updates') }}
{{ _('Configure') }}
{# Module-contributed settings tabs no longer appear on the Settings overview -- they live on the Module Settings page now. #}
{# module cards moved to the Module Settings page #}

{{ _('Project & Support') }}

{{ _('MediaForge is an open-source project. For questions, bugs, or feature ideas please visit our GitHub repository.') }}
GitHub Repository Issue Tracker
{# Language moved to the profile page (panel-language), where the rest of the personal settings live. It is an ACCOUNT preference (users.language), so an instance page is the wrong home for it -- and having it in two places meant an admin changing "the language" here was only ever changing their own, which reads as an instance setting and is not one. The sidebar footer's flag dropdown reaches the same setting from every page. #} {% if is_admin %}

{{ _('Startup & Tray') }}

{{ _('Starts the application as an icon in the system tray instead of blocking a terminal window.') }}
{{ _('Automatically start MediaForge when you log in to your computer.') }}
{{ _('Whether to open the Web UI in your default browser when MediaForge starts.') }}

{{ _('System') }}

{{ _('Debug Mode') }}

{{ _('Writes detailed logs. Takes effect immediately — no restart needed.') }}
{% if is_admin %}

{{ _('Restart') }}

{{ _('Restarts MediaForge with the same startup arguments — without installing an update. Running downloads pause and resume afterwards.') }}
{% endif %}

{{ _('Web Console') }}

{{ _('Show the console output directly here in the Web UI') }}
{% endif %}
{% if is_admin %} {{ startpage.start_page_form('global', show_new_home_toggle=True, heading=_('Default for new accounts'), intro=_('What someone sees who has never opened these settings. Changing this does not overwrite what a user already chose for themselves.')) }} {% endif %}

{{ _('Media Statistics') }}

{{ _('Adds a Media category to the statistics page with movie, series and episode counts based on your media library.') }}
{{ _('Automatic library rescan') }}
{{ _('A location is rescanned once its cached scan is older than this.') }}
{{ _('Scan intensity') }}
{{ _('Parallel ffprobe workers used to read resolution and codec from files whose name does not reveal them.') }}
{{ _('More workers finish a scan sooner on fast local storage and will push the drive hard while it runs; on a network share they mostly multiply the timeouts. A scan always stops after two minutes of probing regardless, and continues where it left off a few minutes later.') }}
{{ _('MediaForge no longer rescans everything on every start — on a large library that costs minutes for a result that is almost always identical. While it runs, the file watcher picks up changes live; this interval covers what the watcher cannot, such as files added while MediaForge was closed. Locations that have never been scanned are always read in, and the refresh button on the Library page rescans on demand.') }}

{{ _('eBooks') }}

{{ _('Covers are always prepared: MediaForge reads the cover out of each book in the background and keeps a small copy, so the shelf shows pictures instead of blank tiles. An EPUB carries its cover inside it; MOBI and AZW3 have to be converted first, which is what the second cache below holds.') }}

{{ _('Cache') }}

{{ _('Cover cache') }}
{{ _('Converted books') }}
{{ _('Both caches hold derived data only — your book files are never touched. A cover is read again the next time the shelf is drawn, and a converted book is rebuilt the next time it is opened. These are separate from the comic caches above.') }}

{{ _('Comics') }}

{{ _('Covers are always prepared: MediaForge reads the first page of each series in the background and keeps it, so the shelf shows pictures instead of blank tiles. Only CBR and CBA have to be unpacked for this, which is what the option below is about.') }}
{{ _('Converts every CBR and CBA file in the library instead of doing it the first time an issue is opened. This takes a noticeable amount of time and disk space — the cache ends up holding a second copy of every affected comic — so it is only worthwhile for small collections.') }}

{{ _('Cache') }}

{{ _('Cover cache') }}
{{ _('Converted archives') }}
{{ _('Both caches hold derived data only — your comic files are never touched. A cover is read again the next time the shelf is drawn, and a converted archive is rebuilt the next time that issue is opened.') }}

{{ _('Diagnostics') }}

{{ _('Unpacker for CBR (RAR)') }}
{{ _('Unpacker for CBA (ACE)') }}
{{ _('Windows 10 and newer as well as macOS already bring bsdtar along as “tar”, and the MediaForge Docker image contains libarchive-tools.') }}

{{ _('Replacing originals') }}

{{ _('This option deletes data. Once an archive has been converted, the original CBR or CBA file is replaced by the new CBZ — the original is gone afterwards and MediaForge cannot bring it back. Leave this switched off unless you are certain you no longer need the original files.') }}
{# module cards moved to the Module Settings page #} {# The controls that used to sit here wrote to the ACCOUNT, so an admin "setting up the instance" changed nothing for anybody else. They are on the profile page now. What is left here are real instance defaults: what an account that has never picked anything is shown. #} {% if is_admin %}

{{ _('Default appearance for new accounts') }}

{{ _('What someone sees who has never chosen a look. Changing this does not touch accounts that already picked their own.') }}

{{ _('Dark or light') }}

{# Same shape as the accent block on the profile page: preset swatches first, custom colour below. The presets carry their own class so base.html's applyAccent() -- which marks every .accent-preset with the ACCOUNT's colour -- leaves this row alone. #}

{{ _('Accent Color') }}

{{ _('Custom Color') }}

{{ _('Language') }}

{{ _('The interface language a newly created account starts with. Everyone can change it later on their profile page.') }}
{% endif %}

{{ _('Theme Packs') }}

{{ _('Theme packs restyle the whole interface — fonts, animations, checkboxes, inputs, the calendar and more. New theme packs can be installed from the Module Store.') }}
{# "Your theme" moved to the profile page with the rest of the personal appearance settings. #} {% if is_admin %}

{{ _('Default for new accounts') }}

{{ _('Get more themes') }}
{{ _('The theme every account sees until it picks its own on the profile page. Installed and managed in the Module Manager.') }}
{% endif %}
{# "Advanced Settings" moved to the profile page. They were written to localStorage only, which made them per BROWSER -- neither an instance setting nor an account one, on a page that is otherwise entirely about the instance. They are account preferences now (ui_* keys). #}
{% if is_admin %}
{# module cards moved to the Module Settings page #}

{{ _('Sources') }}

{{ _('Enable or disable the available sources. Disabled sources are hidden on the start page and are no longer loaded there.') }}
{{ _('hanime.tv is an adult source (18+) for sexually explicit animated content (hentai). Disabled by default; enabling requires an age confirmation.') }}

{{ _('Source order (Start Page v1)') }}

{{ _('Set the order in which sources appear on the start page. Drag the rows or use the arrows. For each source you can also choose whether new or popular items are shown first.') }}

{{ _('Search') }}

{{ _('When enabled, disabled sources are also skipped in the search results.') }}

{{ _('Provider order') }}

{{ _('Order in which hosters are tried. If the hoster selected for a download fails (dead link, hoster not offered for that episode), MediaForge automatically continues with the next one in this list. Drag the rows or use the arrows.') }}
{{ _('When disabled, only the selected hoster is used and a download fails if it is unavailable.') }}

{{ _('Domain fallback (mirrors)') }}

{{ _('Alternative domains per site, tried top to bottom whenever a domain is unreachable or blocked (e.g. s.to → serienstream.to → 186.2.175.5). The first entry is the primary domain and cannot be removed. An IP address is called with the primary domain as its Host header. One entry per line.') }}
{% endif %}
{# module cards moved to the Module Settings page #} {% if is_admin %}

{{ _('Download Settings') }}

{{ _('Download Path') }}

{{ _('Changes take effect immediately for new downloads.') }}

{{ _('Media types') }}

{# The default root has no row in the custom-paths table to carry this, so it is a setting of its own (default_path_media_kinds). Only kinds with a scanner are offered here, same as in that table. #}
{{ _('Which libraries the default download path feeds. At least one has to stay selected.') }}

{{ _('Default Language') }}

{{ _('Used as default when adding a new download.') }}

{{ _('File Naming Template') }}

{{ _('Placeholders:') }} {title} {year} {imdbid} {season} {episode} {language}
{{ _('Downloads are sorted by language (e.g. german-dub/, english-sub/).') }}
{{ _('Hides English Sub from the language selection.') }}
{{ _('Each movie gets its own subfolder — recommended for Jellyfin.') }}
{{ _('Long-running shows are split into seasons on AniWorld but keep one continuous count, which the site appends to the episode title as “[Episode 062]”. With this on, that count is used for the file name: S02E062 instead of S02E001. The season stays the one AniWorld lists the episode in — only the number changes. Episodes without the marker keep their season-relative number. Only affects new downloads; files already on disk are not renamed.') }}

{{ _('Presets') }}

{{ _('Why this matters for Jellyfin') }}
{{ _('Jellyfin identifies an episode by its season and episode number. On a long-running show AniWorld\'s per-season count is not the number the episode is known by anywhere else: “Staffel 2, Folge 1” is episode 62 of the series, which is exactly what the site puts in the “[Episode 062]” marker. Absolute numbering writes that number into the file name.') }} {{ _('The preset also puts a series into one folder instead of per-season subfolders, and leaves language folders off — they split one series across several library entries.') }} {{ _('The Jellyfin preset keeps the language in the file name instead. Jellyfin reads whatever follows the SxxExx marker as the episode title, so it will show “(German Dub)” there until a Jellyfin NFO fills in the real one — set a TMDB key under Integrations if you want proper episode titles.') }}
{{ _('Both buttons write the same five settings at once: naming template, absolute episode numbers, language separation, English subtitle downloads and movie subfolders. The dialog asks whether the folder and file structure should be set too, so you can take the rest and keep your own naming template. Nothing else is touched, and files already on disk are never renamed.') }}

{{ _('Subtitles') }}

{{ _('Every subtitle track the source offers is downloaded and muxed into the video file as a selectable track. Only affects sources that provide real subtitle tracks — burned-in subtitles are part of the picture.') }}

{{ _('Duplicate Handling') }}

{{ _('If the episode already exists, the source is checked first. A higher resolution (or a clearly higher bitrate at the same resolution) is downloaded and replaces the old file.') }}
{{ _('An episode already downloaded as German Dub gets a new English Dub added as a second audio track instead of being saved a second time.') }}

{{ _('Custom Paths') }}

{{ _('Add Path') }}

{# Filled in by settings.js (renderNewPathKindSelect) from the kinds the server reports as scannable, so this markup does not have to list them and cannot drift from the registry. #}
{# The Auto-Sync default path lives on the Auto-Sync tab only. It used to be duplicated here as a radio column, which meant one setting with two controls that had to be kept in sync by hand -- and an Auto-Sync option on a page about downloads. #}
{{ _('Name') }} {{ _('Path') }} {{ _('Default for sites') }} {{ _('Media types') }}{{ _('Actions') }}
{{ _('Select the sites for which this path should be preselected as the download folder.') }}
{{ _('The media types decide which library a path shows up in — a folder is only scanned for what is ticked here.') }}

{{ _('Language Fallback Groups') }}

{{ _('An ordered list of languages that can be selected instead of a single language — in the download dialog and in auto-sync. Every episode is fetched in the first language of the list it is actually available in, so a series that switches from dub to sub mid-season no longer needs two jobs.') }} {{ _('Auto-sync also upgrades: as soon as an episode you already have becomes available in a language further up the list, it is downloaded again in that language. Whether the previous file is then deleted or kept alongside it is set per group.') }}

{{ _('Add Group') }}

{{ _('On: the episode exists only in the better language afterwards. Off: both language versions are kept, each in its own folder — your library then holds the episode twice.') }}
{{ _('Name') }} {{ _('Fallback order') }} {{ _('Delete old file') }} {{ _('Actions') }}
{{ _('A group needs at least two languages. Groups still used by a sync job or a waiting download cannot be deleted.') }}

{{ _('Bandwidth & Schedule') }}

{{ _('Bandwidth Limit') }}

{{ _('Throttles all downloads to this speed. 0 = unlimited. Applies to new downloads.') }}

{{ _('Download Time Window') }}

{{ _('New downloads only start within this window (server local time). Overnight windows like 22:00–06:00 are supported. Running downloads are not interrupted.') }}

{{ _('Download History') }}

{{ _('Automatically delete download-history entries older than the selected period. Runs about hourly.') }}
{% endif %}
{# module cards moved to the Module Settings page #} {% if is_admin %}

{{ _('Auto-Sync Defaults') }}

{# Where a NEW sync job downloads to. settings.js has always had the code for this dropdown (renderSyncDefaultPathOptions / saveSyncDefaultPath) but the {# First entry is deliberately not a custom path: "the global download folder" is a real answer the radio column in Downloads cannot express, because unticking every radio is not a choice. #}
{{ _('How often Auto-Sync checks for new episodes. Language and provider are defaults for new sync jobs.') }}
{{ _('Custom path unavailable:') }} {{ _('Defines what Auto-Sync should do when the assigned custom path is not available. This is the default for all new jobs but can be overridden per job.') }}

{{ _('Auto-Sync Error Handling') }}

{{ _('Defines how often and at what interval a failed Auto-Sync is retried before it is considered permanently failed.') }}

{{ _('Adaptive Auto-Sync') }}

{{ _('When an Auto-Sync job finds no new episodes for the selected period, it enters pause mode and is only re-checked at the wider interval below. As soon as a new episode is found again, it returns to the normal cycle.') }}
{% else %}
{{ _('Only administrators can change Auto-Sync settings.') }}
{% endif %}
{# module cards moved to the Module Settings page #}

{{ _('Network / DNS') }}

{% if is_admin %}
{% endif %}
{{ _('Defines which DNS server resolves domains. Useful if your ISP blocks AniWorld or SerienStream.') }}
{% if is_admin %}

{{ _('Captcha / Browser') }}

{{ _('Reuses one Chromium profile across captcha solves so a once-earned Cloudflare trust (cf_clearance) and browser fingerprint survive. This raises the Turnstile success rate. Applies on the next captcha; a restart guarantees a clean state.') }}

{{ _('Captcha options') }}

{{ _('If Turnstile keeps failing (red cross), delete the profile and solve one captcha manually in the web UI to seed a fresh trust.') }}
{% endif %} {% if is_admin %}

{{ _('Web Server') }}

{{ _('Base URL (Reverse Proxy)') }}

{{ _('Public URL when MediaForge runs behind a reverse proxy (e.g. Nginx, Traefik). Required for OIDC callbacks and absolute links. Leave empty if no proxy is used.') }} {{ _('Changes take effect after the next restart.') }}
{% endif %}
{% if is_admin %}
{# module cards moved to the Module Settings page #}

{{ _('User Management') }}

{% if not force_sso %}

{{ _('Add User') }}

{% endif %}
ID {{ _('Username') }} {{ _('Role') }} {{ _('Authentication') }} {{ _('Created') }} {{ _('Actions') }}

{{ _('Single Sign-On (SSO / OIDC)') }}

{{ _('Connect MediaForge with an external identity provider (Keycloak, Authentik, Google, etc.).') }} {{ _('Changes require a server restart.') }}
{{ _('The sub claim is more secure than the username. Found in server logs after the first SSO login.') }}
{% endif %} {% if is_admin %}
{# module cards moved to the Module Settings page #}

MediaForge API

{{ _('External REST API for Home Assistant & Co. Auth via X-Api-Key header or ?apikey=.') }}
{{ _('Base URL') }}
http://localhost:8080
{{ _('Endpoints') }}
GET /api/v1/status {{ _('Status & queue overview') }}
GET /api/v1/queue {{ _('Download queue') }} ?status=queued|running|completed|failed
GET /api/v1/queue/{id} {{ _('Single queue item') }}
GET /api/v1/library {{ _('Library — all titles') }}
GET /api/v1/library/series {{ _('Series only') }}
GET /api/v1/library/movies {{ _('Movies only') }}
GET /api/v1/stats {{ _('Download statistics') }}
GET /api/v1/autosync {{ _('AutoSync jobs — status overview (all users)') }}
GET /api/v1/uptime {{ _('UpTime monitor — current status per source') }}
GET /api/v1/update-status {{ _('Self-update progress / state') }}
GET /api/v1/mediascan {{ _('MediaScan run status') }}
GET /api/v1/upscale {{ _('Upscale queue, badge count & progress') }}
GET /api/v1/history {{ _('Download history — all users') }} ?limit=&offset=&status=&source=
{{ _('Example:') }}

{{ _('Your installation') }}

{{ _('This ID is generated once per installation and is pseudonymous, not anonymous, from stage 2 onward -- it lets usage over time be linked to this one installation. It is never linked to your MediaForge account or username.') }}

{{ _('Manage my data') }}

{{ _('Request deletion or export of any data already sent under your installation ID -- available whether or not telemetry is currently enabled. The request is sent from this app, with your installation ID attached automatically (never typed by hand).') }}

{{ _('Data collection stages') }}

{{ _('Nothing here is sent unless you actively enable it. Turning a stage on enables every data point in it at once; you can then switch individual points back off -- the stage switch always reflects the combined state of its data points.') }}
{{ _('Telemetry active at all') }}
{% endif %}
{# module cards moved to the Module Settings page #}

Updates

{{ _('Installed') }} {{ app_version or _('unknown') }}
{{ _('Available') }}
{% if is_admin %} {# Full & Selective Backup — admin-only tab (settings + user data). #}

{{ _('Create backup') }}

{{ _('Export your settings and data to a single backup file. Choose which categories to include. A password is required — it encrypts sensitive values such as API keys and passwords. Without the password the backup cannot be restored.') }}
{{ _('Loading…') }}

{{ _('Restore backup') }}

{{ _('Restore a backup file. Select the file and enter its password to preview the contents, then choose which categories to restore.') }}
{{ _('Backup file') }}
{% endif %} {# Module-contributed settings tabs render on the Module Settings page (registry.resolve_module_settings), not here anymore. #} {# Encoding is embedded here as a client-side panel (no reload) — same body the standalone /encoding route uses. Admin-only, matching encoding_page. #} {% if is_admin %}
{% include "_encoding_body.html" %}
{% endif %}
18+

{{ _('Enable hanime (18+)') }}

{{ _('hanime.tv is a source for adult, sexually explicit animated content (hentai) and is only permitted for users aged 18 or older.') }}

{{ _('Are you UNDER 18 years old?') }}

{% endblock %} {% block scripts %} {{ startpage.start_page_i18n() }} {% endblock %}