Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions docs/guides/filtering_library.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
---
title: Pseudo Branching in Library Processing
description: Use two Unmanic libraries over the same media library to process files differently based on ffprobe metadata
id: filtering_library
---

![Difficulty](https://img.shields.io/badge/Difficulty-moderate-orange?style=flat)
![Setup Time](https://img.shields.io/badge/Setup%20Time-15%20minutes-orange?style=flat)
[![Original Author](https://img.shields.io/badge/Original%20Author-yajrendrag-lightgrey?style=flat?style=plastic&logo=github)](https://github.com/yajrendrag)

Sometimes you need to process files in the same media library differently based on their properties. For example, you might want to encode HEVC files one way and H.264 files another way, or handle high-resolution files differently from standard-definition ones.

While Unmanic library processing does not implement branches, this can be approximated through the use of filtering plugins. This guide shows how to set up a **filtering library** pattern: two Unmanic libraries pointed at the same media folder, each using a JSONata-based file test plugin to claim its portion of the files. One library processes files that **match** certain ffprobe criteria, while the other processes files that **do not match** those same criteria.

## Concept

The filtering library approach uses two complementary plugins:

- **Limit Library Search by FFprobe Data** — includes only files whose ffprobe data matches a JSONata query. Files that do not match are skipped.
- **Ignore Files with Matching FFprobe Data** — excludes files whose ffprobe data matches a JSONata query. Files that do not match are processed.

By configuring both plugins with the **same JSONata query and search strings** but placing them in separate Unmanic libraries that point to the same media path, every file in the library is processed by exactly one of the two libraries.

## Requirements

- A running Unmanic installation.
- The **Limit Library Search by FFprobe Data** plugin installed from the official plugin repository.
- The **Ignore Files with Matching FFprobe Data** plugin installed from the [yajrendrag plugin repository](https://github.com/yajrendrag/unmanic-plugins). To add this repository, go to **Plugins** and click the repositories icon, then add `https://raw.githubusercontent.com/yajrendrag/unmanic-plugins/repo/repo.json` as a custom plugin repository.

## Instructions

### 1. Decide on a filtering criteria

Choose the ffprobe property you want to split on. You will express this as a JSONata query. For example, to split on video codec:

| JSONata Query | Search Strings | Matches |
|---|---|---|
| `$.streams[*].codec_name` | `hevc` | Files with an HEVC video stream |
| `$.streams[codec_type="video"].codec_name` | `h264,mpeg4` | Files with H.264 or MPEG-4 video |
| `[$.streams[codec_type="video" and (coded_height > 1000) and (coded_height < 2000)].codec_name]` | `hevc,h264` | 1080p HEVC or H.264 files |
| `[$.format.duration > 300 ? "true" : "false"]` | `true` | Files longer than 5 minutes |

For help building JSONata expressions, use the [JSONata Exerciser Tool](https://try.jsonata.org/pdNmg6BId) and the [JSONata Documentation](https://docs.jsonata.org/overview.html).

:::warning
**Quotes**

The Python library used to parse the JSONata query does not support single quotes. Always use double quotes in your query as in the examples above.
:::

### 2. Create Library A — files that match

Create a new library (or use an existing one) and set its path to your media folder.

1. Go to **Settings > Libraries** and add a library. Give it a descriptive name — for example, `Movies - HEVC`.
2. Set the library path to your media folder (e.g., `/library/movies`).
3. Go to the library's **Plugins** tab.
4. Enable the **Limit Library Search by FFprobe Data** plugin in the **Library Management File Test** section of the plugin flow. Place it at the **top** of the file test flow so it filters files before any other file test plugins run.
5. Configure the plugin:
- **The FFprobe field to match against** — enter your JSONata query (e.g., `$.streams[*].codec_name`).
- **Search strings** — enter the values to match (e.g., `hevc`).
- Optionally check **Add all matching files to pending tasks list** if you want matching files to skip any subsequent file test plugins and be added to the task list immediately.
6. Add and configure any additional worker plugins for this library to process matching files the way you want (e.g., transcode HEVC to AV1).

### 3. Create Library B — files that do not match

Create a second library pointed at the **same media folder**.

1. Add another library with a descriptive name — for example, `Movies - Non-HEVC`.
2. Set the library path to the **same media folder** as Library A (e.g., `/library/movies`).
3. Go to the library's **Plugins** tab.
4. Enable the **Ignore Files with Matching FFprobe Data** plugin in the **Library Management File Test** section of the plugin flow. Place it at the **top** of the file test flow so it filters files before any other file test plugins run.
5. Configure the plugin with the **same JSONata query and search strings** as Library A:
- **Field to match against** — enter the same JSONata query (e.g., `$.streams[*].codec_name`).
- **Search strings** — enter the same values (e.g., `hevc`).
- Optionally check **If checked, this plugin will force the plugin to immediately add the file to the task list when no disallowed values are found** if you want non-matching files to skip subsequent file test plugins.
6. Add and configure any additional worker plugins for this library to process non-matching files the way you want (e.g., transcode H.264 to AV1 with different settings).

### 4. Enable library scanning

For both libraries, enable **Library Scanner** and/or **File Monitor** as needed under the library settings. Both libraries will scan the same folder, but each will only pick up its respective portion of files based on the filtering plugin.

:::tip
If you are using **Library Scanner** on both libraries, consider staggering their scan schedules to avoid both libraries probing the same files simultaneously.
:::

## Example: splitting HDR and SDR content

Suppose you have a movie library at `/library/movies` with a mix of HDR and SDR content. The **Video Transcoder** plugin needs to be configured differently for each — HDR files require settings that preserve HDR metadata and tone mapping, while SDR files can use standard encoding settings.

HDR content is identified by the `color_transfer` property in ffprobe output. HDR10 files use the `smpte2084` transfer characteristic (also known as PQ — Perceptual Quantizer).

**Library A** — `Movies - HDR`:
- Path: `/library/movies`
- File test plugin: **Limit Library Search by FFprobe Data** (at the top of the file test flow)
- JSONata query: `$.streams[codec_type="video"].color_transfer`
- Search strings: `smpte2084`
- Worker plugin: **Video Transcoder** configured with HDR-appropriate settings (e.g., preserving HDR metadata, 10-bit encoding)

**Library B** — `Movies - SDR`:
- Path: `/library/movies`
- File test plugin: **Ignore Files with Matching FFprobe Data** (at the top of the file test flow)
- JSONata query: `$.streams[codec_type="video"].color_transfer`
- Search strings: `smpte2084`
- Worker plugin: **Video Transcoder** configured with standard SDR encoding settings

With this setup, every video file in `/library/movies` is handled by exactly one library. HDR files are processed with HDR-aware transcoding settings, and SDR files are processed with standard settings.

:::note
Both plugins evaluate the JSONata query against the full ffprobe JSON output of each file. The query results are then checked against the comma-separated search strings. A file "matches" if any of the search strings appear in the query result.
:::

## Tips

- **Keep the JSONata query and search strings identical** across both libraries. This ensures complete, non-overlapping coverage of your media files.
- You can extend this pattern to more than two libraries by using more specific JSONata queries in each. For example, one library could target `hevc`, another `h264`, and a third everything else.
- Test your JSONata queries using the [JSONata Exerciser Tool](https://try.jsonata.org/pdNmg6BId) with sample ffprobe JSON output from one of your files. You can generate sample output by running `ffprobe -show_format -show_streams -print_format json "yourfile.mkv"`.
153 changes: 153 additions & 0 deletions docs/guides/handling_colorspace_changes_hw_decode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
---
title: Handling Colorspace Changes with Hardware Decode
description: Resolve ffmpeg filter graph failures caused by colorspace changes when using hardware-accelerated decoding
id: handling_colorspace_changes_hw_decode
---

![Difficulty](https://img.shields.io/badge/Difficulty-moderate-orange?style=flat)
![Setup Time](https://img.shields.io/badge/Setup%20Time-20%20minutes-orange?style=flat)
[![Original Author](https://img.shields.io/badge/Original%20Author-yajrendrag-lightgrey?style=flat?style=plastic&logo=github)](https://github.com/yajrendrag)

When the **Video Transcoder** plugin is configured to use hardware-accelerated decoding (NVENC, QSV, or VAAPI), some video files can cause ffmpeg to fail mid-transcode. This happens when ffmpeg detects a colorspace change in the video stream and attempts to reconfigure its filter graph. Because the frames are on the GPU, ffmpeg cannot perform the filter change and the transcode fails.

This guide explains the problem, how to use the **Safe decode mode** option to resolve it, and how to automate the entire process using a pseudo branching library strategy.

## The Problem

All three hardware decoders supported by the Video Transcoder plugin — NVENC, QSV, and VAAPI — can encounter this failure. When a video stream contains a colorspace change (for example, a transition from BT.601 to BT.709 partway through the file), ffmpeg tries to reconfigure its filter graph on the fly. With hardware decode enabled, the frames reside in GPU memory where this reconfiguration is not supported, causing the transcode to fail.

### Error Messages

**QSV:**
```
[fc#0 @ 0x55853af42c40] Reconfiguring filter graph because hwaccel changed
Impossible to convert between the formats supported by the filter 'scaler_out_#0:0' and the filter 'auto_scale_0'
[fc#0 @ 0x55853af42c40] Error reinitializing filters!
[fc#0 @ 0x55853af42c40] Task finished with error code: -38 (Function not implemented)
[fc#0 @ 0x55853af42c40] Terminating thread with return code -38 (Function not implemented)
Conversion failed!
```

**NVENC:**
```
[fc#0 @ 0x561892fe7940] Reconfiguring filter graph because video parameters changed to yuv420p(tv, bt709), 1910x1070
Impossible to convert between the formats supported by the filter 'Parsed_hwupload_cuda_1' and the filter 'auto_scale_1'
[fc#0 @ 0x561892fe7940] Error reinitializing filters!
[fc#0 @ 0x561892fe7940] Task finished with error code: -38 (Function not implemented)
[fc#0 @ 0x561892fe7940] Terminating thread with return code -38 (Function not implemented)
Conversion failed!
```

VAAPI will produce similar messages referencing VAAPI-specific filter names.

## The Solution: Safe Decode Mode

The Video Transcoder plugin provides a **Safe decode mode** option for each hardware encoder. When enabled, this option downloads decoded frames from the GPU to the CPU, where ffmpeg can implement the option to ignore filter graph changes, and then uploads the frames back to the GPU for encoding. Both decode and encode still happen on the GPU — the CPU round-trip is only needed to enable the filter change bypass.

### Where to Find the Setting

1. Open the **Video Transcoder** plugin configuration.
2. Ensure the plugin is in **Standard** mode (not Basic or Advanced).
3. Under the encoder section (NVENC, QSV, or VAAPI), enable **HW Decoding** (set it to something other than "Disabled").
4. The **Safe decode mode** checkbox appears directly below the HW Decoding option.
5. Check the box to enable safe decode mode.

:::note
Safe decode mode is disabled by default. When disabled, frames stay entirely in GPU memory for maximum performance. The trade-off for enabling it is a small performance penalty due to the GPU-to-CPU-to-GPU round-trip of decoded frames.
:::

## Choosing an Approach

You have three options depending on how frequently you encounter this error.

### Option 1: Manual Reprocessing (Infrequent Failures)

If you rarely see this error, keep **Safe decode mode** disabled for maximum performance. When a file fails:

1. Go to the completed tasks list and identify the failed file.
2. Open the library's plugin configuration and enable **Safe decode mode** on the Video Transcoder plugin.
3. Reprocess the failed file.
4. Disable **Safe decode mode** again after the file completes.

This is the simplest approach but requires manual intervention each time.

### Option 2: Always-On Safe Mode (Frequent Failures)

If you see this error often, simply leave **Safe decode mode** enabled permanently. All files will take the GPU → CPU → GPU path for decoded frames, accepting the small performance penalty in exchange for never hitting this failure.

### Option 3: Automated Pseudo Branching (Recommended)

This approach uses the [Pseudo Branching in Library Processing](/docs/guides/filtering_library) strategy to automatically detect and reprocess failed files. No manual intervention is needed.

The setup uses two libraries pointed at the same media folder:
- **Library 1** — configured with Safe decode mode **disabled** (fast path) and the **Reprocess File** plugin to catch failures.
- **Library 2** — configured with Safe decode mode **enabled** (safe path) to handle the reprocessed files.

#### Requirements

- The **Video Transcoder** plugin installed from the official plugin repository.
- The **Reprocess File** plugin installed from the [yajrendrag plugin repository](https://github.com/yajrendrag/unmanic-plugins). To add this repository, go to **Plugins** and click the repositories icon, then add `https://raw.githubusercontent.com/yajrendrag/unmanic-plugins/repo/repo.json` as a custom plugin repository.

#### Step 1: Create the Diagnostic Script

The **Reprocess File** plugin can run a diagnostic shell script to determine whether a failed file should be reprocessed. The script receives the post-processor data object as JSON on stdin and should exit with code `0` to reprocess or `1` to skip.

A sample script is included in the Reprocess File plugin's `script.md` file. Create a script based on it and save it to a convenient location such as `/config/.unmanic/userdata/reprocess_files/`:

```bash
#!/usr/bin/env bash
DATA=$(cat)

TASK_ID=$(echo "$DATA" | jq -r '.task_id')

DB="/config/.unmanic/config/unmanic.db"

MATCH=$(sqlite3 "$DB" \
"SELECT COUNT(*) FROM tasks
WHERE id = ${TASK_ID}
AND (log LIKE '%Reconfiguring filter graph%'
OR log LIKE '%hwaccel changed%')")

[ "$MATCH" -gt 0 ] && exit 0 || exit 1
```

Make the script executable:

```bash
chmod +x /config/.unmanic/userdata/reprocess_files/detect_colorspace_failure.sh
```

The script queries the Unmanic database for the failed task's log output. If the log contains `Reconfiguring filter graph` or `hwaccel changed`, the script returns `0` (reprocess). Otherwise it returns `1` (do not reprocess).

#### Step 2: Configure Library 1 — Fast Path

1. Create a library (e.g., `Movies - HW Decode`) and set its path to your media folder.
2. Configure the **Video Transcoder** plugin in Standard mode:
- Enable HW Decoding for your encoder (NVENC, QSV, or VAAPI).
- Leave **Safe decode mode** unchecked (disabled).
3. Configure any other worker plugins as needed.
4. Enable the **Reprocess File** plugin in the **Post-processor Task Results** section of the plugin flow.
5. Configure the Reprocess File plugin:
- Set it to reprocess **failed** tasks.
- Enter the full path to the diagnostic script (e.g., `/config/.unmanic/userdata/reprocess_files/detect_colorspace_failure.sh`).
- Select **Library 2** (the safe path library created in the next step) as the target library for reprocessing.

#### Step 3: Configure Library 2 — Safe Path

1. Create a second library (e.g., `Movies - HW Decode Safe`) and set its path to the **same media folder**.
2. Configure the **Video Transcoder** plugin in Standard mode:
- Enable HW Decoding for your encoder.
- Check **Safe decode mode** (enabled).
3. Configure any other worker plugins to match Library 1.
4. Disable **Library Scanner** and **File Monitor** on this library — it should only process files sent to it by the Reprocess File plugin from Library 1.

#### How It Works

1. Library 1 scans the media folder and processes files using the fast GPU-only decode path.
2. If a file fails due to a colorspace change, the Reprocess File plugin's diagnostic script detects the specific error in the task log.
3. The Reprocess File plugin automatically adds the failed file to Library 2's task queue.
4. Library 2 reprocesses the file with Safe decode mode enabled, successfully handling the colorspace change.

:::tip
This approach gives you the best of both worlds: maximum performance for the majority of files that do not have colorspace changes, with automatic fallback for the ones that do.
:::
Loading