2026-03-24 15:20:01 +01:00
# 📖 Metadata Update & FFprobe (`metadata_update`)
2026-03-25 22:31:21 +01:00
This chapter covers the `metadata_update` block inside `config.yml` , which determines how aggressively or gently
Tuliprox
2026-03-24 15:20:01 +01:00
manages background tasks for resolving metadata and technical stream properties.
2026-03-25 22:31:21 +01:00
Tuliprox utilizes three distinct mechanisms to ensure perfect library quality (especially for Plex/Jellyfin
compatibility):
2026-03-24 15:20:01 +01:00
1. **Resolve (Xtream API):** Fetching missing VOD details (Cast, Director, Plot) directly from the provider's API.
2. **TMDB:** Supplementing missing Release Years and high-resolution Covers/Backdrops via The Movie Database.
3. **Probe (FFprobe):** Physically opening the stream to analyze the exact A/V codecs (HEVC, H264) and resolution.
## Top-level entries
```yaml
metadata_update :
cache_path : metadata
retry_delay : 2s
worker_idle_timeout : 1m
max_queue_size : 100000
no_change_cache_ttl_secs : 3600
probe_fairness_resolve_burst : 200
log :
resolve :
probe :
tmdb :
ffprobe :
```
### Global Options (Flat Keys)
2026-03-25 22:31:21 +01:00
| Parameter | Type | Default | Technical Impact & Background |
|:-------------------------------|:---------|:-------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `cache_path` | String | `"metadata"` | Directory where TMDB cache files and metadata are stored. Relative paths are resolved against `storage_dir` . Used by all metadata resolution paths (Xtream VOD/Series, local library). |
| `retry_delay` | Duration | `"2s"` | General minimum wait time when the worker encounters temporary runtime errors (e.g. socket timeout). Prevents very fast retry loops in case of transient network issues. |
| `worker_idle_timeout` | Duration | `"1m"` | Time of inactivity (empty queue) after which the background worker kills itself to free RAM/CPU resources. |
| `max_queue_size` | Int | `100000` | RAM Safety Limit: Maximum number of metadata tasks kept in memory per input simultaneously. New tasks are rejected once this limit is reached. |
| `no_change_cache_ttl_secs` | Int | `3600` | How long (seconds) a "No Change" status is cached to avoid unnecessary DB checks across subsequent playlist updates. Identical reason sets are skipped while valid. |
| `probe_fairness_resolve_burst` | Int | `200` | After 200 consecutive Resolve tasks (API), 1 Probe task (FFprobe) is forcibly prioritized so probes don't starve in large libraries. |
> **Note on Durations:** Duration fields support `s`, `m`, `h`, `d` or plain seconds (e.g. `30s`, `10m`, `1h`, `7d`).
> Exception: `ffprobe.analyze_duration` and `ffprobe.live_analyze_duration` require explicit unit suffix.
> Size fields support `B`, `KB`, `MB`, `GB`, `TB` or plain bytes.
2026-03-24 15:20:01 +01:00
---
## 1. Logging (`log`)
2026-03-25 22:31:21 +01:00
Controls background worker logging verbosity to monitor the metadata queue status.
2026-03-24 15:20:01 +01:00
```yaml
metadata_update :
log :
queue_interval : 30s
progress_interval : 15s
```
2026-03-25 22:31:21 +01:00
| Parameter | Type | Default | Description |
|:--------------------|:---------|:--------|:--------------------------------------------------------------------------------------------|
| `queue_interval` | Duration | `"30s"` | Interval to log the current queue size and pending task status of the metadata worker. |
| `progress_interval` | Duration | `"15s"` | Interval for progress reports while tasks are being processed (successful/failed resolves). |
2026-03-24 15:20:01 +01:00
---
## 2. API Resolve Limits (`resolve`)
Controls API requests for pure metadata (Xtream Info / TMDB).
```yaml
metadata_update :
resolve :
max_retry_backoff : 1h
min_retry_base : 5s
max_attempts : 3
exhaustion_reset_gap : 1h
```
2026-03-25 22:31:21 +01:00
| Parameter | Type | Default | Description |
|:-----------------------|:---------|:--------|:-------------------------------------------------------------------------------------------|
| `max_retry_backoff` | Duration | `"1h"` | Upper limit for exponential wait time between repeated API failures. |
| `min_retry_base` | Duration | `"5s"` | Initial wait time on the very first failure before exponential backoff kicks in. |
| `max_attempts` | Int (u8) | `3` | Max attempts per cycle before a resolve task is marked as "exhausted" for the current run. |
| `exhaustion_reset_gap` | Duration | `"1h"` | Time window after a cycle completes before "exhausted" states are reset for the next run. |
2026-03-24 15:20:01 +01:00
---
## 3. FFprobe Retries & Limits (`probe`)
Controls technical stream probing retries via FFprobe.
```yaml
metadata_update :
probe :
cooldown : 7d
retry_load_retry_delay : 1m
retry_backoff_step_1 : 10m
retry_backoff_step_2 : 30m
retry_backoff_step_3 : 1h
max_attempts : 3
backoff_jitter_percent : 20
user_priority : 127
```
2026-03-25 22:31:21 +01:00
| Parameter | Type | Default | Description |
|:-------------------------|:---------|:--------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `cooldown` | Duration | `"7d"` | Hard lock time (cooldown) after probe attempts are exhausted. The stream is ignored for this period to protect the provider. |
| `retry_load_retry_delay` | Duration | `"1m"` | Wait time before re-attempting to load the internal `metadata_retry_state.db` after a load failure. |
| `retry_backoff_step_1` | Duration | `"10m"` | Wait time after the 1st FFprobe failure. |
| `retry_backoff_step_2` | Duration | `"30m"` | Wait time after the 2nd FFprobe failure. |
| `retry_backoff_step_3` | Duration | `"1h"` | Wait time from the 3rd FFprobe failure onwards. |
| `max_attempts` | Int (u8) | `3` | Max failures to probe a stream before it enters global long-term cooldown. |
| `backoff_jitter_percent` | Int (u8) | `20` | Random time deviation in percent (Jitter) so parallel retries don't hit the provider at the same second. |
| `user_priority` | Int (i8) | `127` | Priority of the probe task on the Unix Nice-Scale. `127` is the absolute lowest. Probes at 127 are immediately cancelled/preempted if a real user needs the slot. |
2026-03-24 15:20:01 +01:00
---
## 4. TMDB API Integration (`tmdb`)
2026-03-25 22:31:21 +01:00
Controls how Tuliprox interacts with The Movie Database API.
2026-03-24 15:20:01 +01:00
```yaml
metadata_update :
tmdb :
enabled : false
api_key : "YOUR_KEY"
rate_limit_ms : 250
cache_duration_days : 30
language : en-US
cooldown : 7d
match_threshold : 86
```
2026-03-25 22:31:21 +01:00
| Parameter | Type | Default | Description |
|:----------------------|:----------|:-------------|:-----------------------------------------------------------------------------------------------|
| `enabled` | Bool | `false` | Global master switch for TMDB resolution. |
| `api_key` | String | *(Internal)* | Your own TMDB API Key. If omitted, Tuliprox uses a built-in default placeholder. |
| `rate_limit_ms` | Int (u64) | `250` | Minimum wait between TMDB API calls to prevent IP rate-limiting or bans (milliseconds). |
| `cache_duration_days` | Int (u32) | `30` | How long successful TMDB results are kept in the cache. Use `0` for permanent caching. |
| `language` | String | `"en-US"` | Preferred metadata language (e.g., `"de-DE"` ) for plot and titles. |
| `cooldown` | Duration | `"7d"` | Lock time for a movie if the TMDB search was successful but returned "no match" for the title. |
| `match_threshold` | Int (u16) | `86` | Minimum similarity score (Jaro-Winkler) for a result to be accepted as a valid "Match". |
2026-03-24 15:20:01 +01:00
---
## 5. FFprobe Process Rules (`ffprobe`)
2026-03-25 22:31:21 +01:00
Detailed settings for the technical stream analysis process.
2026-03-24 15:20:01 +01:00
```yaml
metadata_update :
ffprobe :
enabled : true
timeout : 60
analyze_duration : 10s
probe_size : 10MB
live_analyze_duration : 5s
live_probe_size : 5MB
```
2026-03-25 22:31:21 +01:00
| Parameter | Type | Default | Description |
|:------------------------|:----------|:---------|:-------------------------------------------------------------------------------------------------------------------|
| `enabled` | Bool | `false` | Global master switch for ALL stream probing. Must be `true` for input flags like `probe_vod` to work. |
| `timeout` | Int (u64) | `60` | Hard timeout (in seconds) for the OS FFprobe process. Prevents zombie processes. |
| `analyze_duration` | Duration | `"10s"` | Passes `-analyzeduration` to FFprobe for VODs/Series. *Warning: Requires unit suffix (`s`, `m`)!* |
| `probe_size` | Size | `"10MB"` | Passes `-probesize` to FFprobe for VODs/Series (Data Limit). Supports units like KB, MB, GB. |
| `live_analyze_duration` | Duration | `"5s"` | Stricter time limit for Live-TV streams to minimize latency and provider traffic. *Warning: Requires unit suffix!* |
| `live_probe_size` | Size | `"5MB"` | Stricter data limit for Live-TV streams. |
**Why are there 4 FFprobe fields?**
`ffprobe.analyze_duration` + `ffprobe.probe_size` are the default pair for non-live probes (VOD/Series).
`ffprobe.live_analyze_duration` + `ffprobe.live_probe_size` are the live-specific pair for all live probes.
This split is intentional because live probing usually needs lower values (less provider load / lower latency),
while VOD/Series can use higher values for better metadata extraction quality.
2026-03-24 15:20:01 +01:00
---
2026-03-25 22:31:21 +01:00
## Global Activation of Video Analysis & TMDB API Fallback
While `metadata_update` section in config.yml configures the global behavior, you must explicitly activate analysis per
input.
**Input Config (`source.yml`):**
```yaml
inputs :
- name : my-provider
type : xtream
options :
# Attempts to resolve missing TMDB IDs and Release Date via TMDB API based on title
resolve_tmdb : true
# Probes stream if video/audio info is missing in provider data
probe_stream : true
```
**Target Config (`source.yml`):**
If `add_quality_to_filename` is set for STRM output, the analyzed quality tags are used in filenames.
```yaml
targets :
- name : my-library
output :
- type : strm
directory : /media/strm
style : jellyfin
# Adds tags like [2160p 4K HEVC HDR] to the filename
add_quality_to_filename : true
# Groups different versions of the same movie into one folder (based on TMDB ID)
flat : true
```
> **Note on Probing:** Probing respects the `max_connections` limit of your provider input.
> If no connection slot is available, the item is skipped and retried during the next update cycle.
### Glossary & Data Types
To ensure precise configuration, the following terms and data formats are used throughout
the metadata module:
#### Special Data Types (Strings)
* **Duration:** Specifies time intervals. Supported units are `s` (seconds), `m` (minutes), `h` (hours), and `d` (days).
* *Example:* `3600` (plain seconds) or `1h` (suffixed).
* *Note:* `ffprobe.analyze_duration` strictly requires a unit suffix.
* **Size:** Specifies data volumes for stream analysis. Supported units are `B` , `KB` , `MB` , `GB` , and `TB` .
* *Example:* `10485760` (plain bytes) or `10MB` (suffixed).
#### Terminology
* `Resolve task` : A metadata job that fetches or enriches item metadata (e.g., VOD/Series details, TMDB IDs, or release
dates) via provider APIs or TMDB.
* `Probe task` : A technical analysis job that physically inspects stream properties (codecs, resolution, audio tracks)
using the FFprobe engine.
* `TMDB cooldown` : A per-item lock set when a TMDB lookup completes successfully but returns "no match."
This prevents redundant API calls for items not found on TMDB.
* `Attempt` : A single execution try of a task. If it fails, the attempt counter for that specific item is incremented.
* `Retry` : A subsequent re-attempt of a failed task after a waiting period.
* `Backoff delay` : The waiting time before the next retry is allowed after a failure.
* `Exponential backoff` : A strategy where the `backoff delay` grows with each failed attempt (e.g., 5s, 10s, 20s) up to
a configured `max_retry_backoff` .
* `Jitter` : A small random variation added to the backoff delay to prevent "thundering herd" issues, where hundreds of
tasks retry at the exact same millisecond.
* `Transient error` : A temporary failure (e.g., socket timeout, 502 Bad Gateway) that is likely to resolve itself in a
future attempt.
* `Exhausted` : The state reached when `max_attempts` for a task type are met. The task will not be retried within the
current cycle.
* `Cooldown`: A mandatory skip period applied after a task is * *Exhausted** (primarily used for Probes). No retries are
allowed until this period expires.
* `Update cycle` : A full metadata processing run for a specific input, spanning from the first queued item to the point
where the queue is idle.
* `Resolve exhaustion reset gap` : The time window after a cycle completion after which "Exhausted" states for resolve
tasks are cleared for the next run.
* `Pending queue` : The in-memory list of metadata tasks currently waiting for an available background worker slot.
* `Worker idle timeout` : The duration a metadata worker stays active without work before shutting down to release system
resources.
> **Persistence:** All retry, exhaustion, and cooldown states are persisted per input in the `metadata_retry_state.db`.
> This ensures that Tuliprox remembers the status of broken streams across server restarts.
2026-03-24 15:20:01 +01:00
## Additional Information
2026-03-25 22:31:21 +01:00
Tuliprox is not just a proxy; it is a highly intelligent **Playlist Processing Engine** . A core part of this is the
asynchronous
update and metadata process that loads information from the provider, updates local databases, and fully automatically
supplements
2026-03-24 15:20:01 +01:00
missing metadata.
2026-03-25 22:31:21 +01:00
### 1. The Complete Processing Pipeline
2026-03-24 15:20:01 +01:00
When a playlist update starts (via Scheduler, API, or Boot), Tuliprox runs this pipeline:
2026-03-25 22:31:21 +01:00
1. **Download & Cache Check:** For each configured `input` , it checks if provider data needs re-downloading (controlled
by
2026-03-24 15:20:01 +01:00
`cache_duration` ).
2026-03-25 22:31:21 +01:00
2. **Input Storage (B+Tree):** The raw data (M3U, Xtream categories) is written to a local, extremely fast B+Tree
database
2026-03-24 15:20:01 +01:00
(`input_name.db` ). This drastically saves RAM.
2026-03-25 22:31:21 +01:00
3. **Target Processing:** For each defined `target` (output playlist), data is loaded from the input and routed through
the
2026-03-24 15:20:01 +01:00
pipeline (`processing_order` , e.g., Filter ➔ Rename ➔ Map).
2026-03-25 22:31:21 +01:00
4. **Metadata Resolve & Probe:** Tuliprox analyzes the filtered entries. If data is missing (e.g., TMDB IDs, Video
Codecs),
2026-03-24 15:20:01 +01:00
these are dispatched as "Jobs" to the `MetadataUpdateManager` .
2026-03-25 22:31:21 +01:00
5. **Target Storage & EPG:** The finished playlist is written to the Target databases. Only then is XMLTV EPG data
matched and
2026-03-24 15:20:01 +01:00
assigned.
---
2026-03-25 22:31:21 +01:00
### 2. The `MetadataUpdateManager` (Architecture)
2026-03-24 15:20:01 +01:00
2026-03-25 22:31:21 +01:00
The `MetadataUpdateManager` is an asynchronous background engine (if `resolve_background: true` is set on the input)
that
2026-03-24 15:20:01 +01:00
prevents blocking the main playlist update.
2026-03-25 22:31:21 +01:00
#### Architecture & Logic
2026-03-24 15:20:01 +01:00
2026-03-25 22:31:21 +01:00
* **Per-Input Worker:** A dedicated, isolated *Tokio Task (Worker)* is started for each Provider-Input. This prevents a
slow
2026-03-24 15:20:01 +01:00
provider from blocking another.
* * *Task-Merging:** If a stream requires both TMDB info and an FFprobe, they are merged into a single Task.
2026-03-25 22:31:21 +01:00
* * *Rate-Limiting & Connection-Locks:** The manager strictly respects the `max_connections` of your input. An FFprobe (
Stream
2026-03-24 15:20:01 +01:00
Analysis) is *only* initiated if a provider connection is free. A probe task runs at the absolute lowest priority
2026-03-25 22:31:21 +01:00
(`user_priority: 127` ). If a real user starts streaming, the FFprobe process is **immediately aborted/preempted** to
free the
2026-03-24 15:20:01 +01:00
slot for the user!
2026-03-25 22:31:21 +01:00
* * *Smart Retry & Cooldown:** If a fetch or probe fails (e.g., HTTP 502), an exponential backoff with Jitter (random
deviation)
2026-03-24 15:20:01 +01:00
kicks in. If the max attempts (`max_attempts` ) are reached, the task enters a global cooldown (e.g., 7 days) to stop
harassing the provider.
2026-03-25 22:31:21 +01:00
* * *Persistence (Retry State):** The status of failed tasks is stored locally in `metadata_retry_state.db` . A server
restart
2026-03-24 15:20:01 +01:00
does not cause Tuliprox to immediately bombard the provider with requests for broken streams.
2026-03-25 22:31:21 +01:00
* * *Cascading Updates:** Once a worker collects a batch of metadata, it saves it in the Input DB and immediately
*cascades*
2026-03-24 15:20:01 +01:00
(inherits) the updates into all Target DBs, without requiring a full playlist rebuild.
---
2026-03-25 22:31:21 +01:00
### 3. Metadata Collection Mechanisms
2026-03-24 15:20:01 +01:00
2026-03-25 22:31:21 +01:00
#### How is a stream queued for analysis?
2026-03-24 15:20:01 +01:00
2026-03-25 22:31:21 +01:00
Tuliprox checks every stream for completeness (`has_details()` ). A task is queued if the switches in `inputs.options`
are
2026-03-24 15:20:01 +01:00
active **and** one of these conditions is met:
* **Info-Resolve (VOD/Series):** Missing provider info (Cast, Plot, Director) retrievable via Xtream API
(`get_vod_info` / `get_series_info` ).
* * *TMDB/Date-Resolve:** Missing `tmdb_id` or `release_date` .
2026-03-25 22:31:21 +01:00
* * *Probing (FFprobe):** * VOD/Series: Missing technical A/V parameters (`video_codec` , `audio_codec` , `resolution` ).
2026-03-24 15:20:01 +01:00
* Live-TV: The `last_probed_timestamp` is older than `probe_live_interval_hours` .
2026-03-25 22:31:21 +01:00
#### Collection Engines
2026-03-24 15:20:01 +01:00
2026-03-25 22:31:21 +01:00
* **Release Year / Date (PTT):** Tuliprox uses a highly optimized internal parser
(`PTT` - Parse Torrent Title). It locally analyzes the stream name and extracts the year
(e.g., from *"My Movie (2023)"* ). If this fails, it queries the TMDB API.
2026-03-24 15:20:01 +01:00
* * *TMDB Information:**
2026-03-25 22:31:21 +01:00
Via TMDB API and a Jaro-Winkler distance comparison (similarity scoring),
it fetches IDs, release years, covers, backdrops, genres, directors, and actors.
2026-03-24 15:20:01 +01:00
* * *Video & Audio (FFprobe):**
Tuliprox briefly opens the stream via `ffprobe` . It extracts and normalizes:
* *Resolution:* SD, 720p, 1080p, 1440p, 4K, 8K
* *Video:* Codec (H264, HEVC, AV1), Bit-Depth (8bit, 10bit), Dynamic Range (HDR10, Dolby Vision, HLG)
* *Audio:* Codec (AAC, AC3, EAC3, DTS, TrueHD) and Channels (2.0, 5.1, 7.1)
* Tuliprox uses these tags later for the `add_quality_to_filename` target feature
(e.g., `My Movie [2160p 4K HEVC HDR].strm` ).
* * *Seasons & Episodes:**
2026-03-25 22:31:21 +01:00
For series, the Xtream API delivers a structure of seasons and episodes.
Tuliprox "flattens" these into individually playable streams (`PlaylistItemType::Series` ).
Each episode is treated **individually** during probing, as codecs and resolutions
can change from episode to episode.