Skip to content

Fork Changes

o51r15 edited this page Jun 25, 2026 · 2 revisions

Fork Changes

What started as a targeted PostgreSQL swap has grown into a substantial independent rewrite. This page documents everything that differs from the upstream Pinchflat project.


Backend

PostgreSQL replaces SQLite

The upstream project uses SQLite. This fork uses PostgreSQL.

  • SQLite + Oban is a structural mismatch. Oban was built for Postgres and the SQLite adapter causes write contention under load, query timeouts, and crash loops as library size grows.
  • Postgres handles concurrent job queues (indexing, downloading, metadata, file sync) correctly and without file locking issues.
  • Deployment requires a Postgres sidecar container. See Installation.

Oban Lifeline plugin

Jobs that get stuck in executing state after a crash or container restart are automatically rescued and re-queued after 30 minutes. The upstream does not include this plugin.

Metadata reliability

  • Scoped yt-dlp output template prevents multi-MB JSON truncation on resource-constrained hosts
  • Graceful error handling in the metadata worker prevents fetch failures from causing noisy Oban retries and discards

UI

The entire UI has been redesigned with a Sonarr-style layout.

  • Source library — poster card grid with download progress bars and one-click monitored toggles
  • Source detail page — fanart banner header, poster overlay, granular stats bar (Downloaded / Pending / Failed / Prevented / Skipped), horizontal action bar
  • Episode list — grouped by year (season analog) with collapsible sections, per-item status badges, and monitored toggles
  • Activity page — dedicated page replaces the cluttered home dashboard
  • System Status page — Pinchfork version, yt-dlp version, PostgreSQL version and database size, Oban queue health, and live bgutil PO token server status with one-click test

Source management

Source type selector

Channel vs Playlist is now user-selectable on the new source form before entering the URL. This overrides yt-dlp's auto-detection, which can misidentify channel handles as playlists on some architectures. The URL field label changes dynamically (Channel URL / Playlist URL) and the URL field is locked until a type is selected.

Metadata editor

A dedicated Edit Metadata page (accessible from the source action bar) provides:

  • Editable Name and Description fields with per-field lock toggles — locked fields are not overwritten by metadata refreshes
  • Custom poster upload (file or URL) — once set, the custom poster takes priority over auto-fetched images and is locked until removed

Content availability filtering

Per-source checkboxes to control whether public videos, members-only videos, or both are downloaded. The availability field is captured from yt-dlp at index time and re-evaluated on rescan.

Cookie behaviour

Per-source control over when cookies are used: Disabled / When Needed / All Operations.

Video client override

Select the yt-dlp player client per source. Used to bypass SABR-corrupted downloads or work around authentication issues on specific content. See SABR Bypass and PO Tokens.


yt-dlp

PO token support

Integration with the bgutil sidecar for YouTube PO token generation. Required for reliable downloads on some content. See SABR Bypass and PO Tokens.

yt-dlp version management

YT_DLP_VERSION environment variable controls update behavior: stable (default), nightly, master, pinned/none to disable, or a specific version string like 2025.12.08.


Media tracking

Error classification

error_type field on media items distinguishes permanent failures (members-only, unavailable, geo-blocked, age-restricted, premium-only) from transient ones (network errors, rate limits). Permanent failures automatically set prevent_download: true to stop retry cycles.

Prevention reason tracking

download_prevented_reason field distinguishes between manual prevention (user turned off download), policy blocks (system rules), and error-stops. This prevents re-indexing from accidentally re-enabling intentionally blocked items.


Additional features

YouTube API key tester

One-click API key validation from the Settings page.

Local temp staging

Downloads can stage to a local disk path before moving to the final destination. Eliminates partial-file issues when /downloads is a network share. See Local Temp Staging.


What has not changed

The following upstream features are unchanged in this fork and are documented in the upstream wiki:

  • Media profiles
  • SponsorBlock integration
  • RSS/podcast feeds
  • Apprise notifications
  • Custom yt-dlp options
  • Lifecycle scripts
  • Jellyfin/Plex/Kodi setup and file naming
  • Retention periods
  • Title filter regex
  • Output path templates

Configuration differences from upstream

Environment variables added

Variable Default Notes
DATABASE_URL Required. Postgres connection string: ecto://user:pass@host/db. Replaces the SQLite DATABASE_PATH variable which does not exist in this fork.
POOL_SIZE 10 Postgres connection pool size. No equivalent in upstream.
YT_DLP_VERSION stable Controls yt-dlp update behavior: stable, nightly, master, pinned/none to disable, or a specific version like 2025.12.08. No equivalent in upstream.

Environment variables removed

Variable Reason
DATABASE_PATH SQLite-only. Not used in this fork.
JOURNAL_MODE SQLite-only workaround for network shares. Not used in this fork.

Oban job queue behavior

This fork uses Oban.Engines.Basic (Postgres native) instead of Oban.Engines.Lite (SQLite-only). This resolves write contention and crash loops that could occur on the upstream under load.

The Oban.Plugins.Lifeline plugin is enabled with a 30-minute rescue window. Any job stuck in executing state after a crash or container restart is automatically moved back to retryable and re-queued. Upstream does not include this plugin.

Source fields added

  • Source type — Channel or Playlist, now user-selectable. Overrides yt-dlp auto-detection.
  • Download public videos (default: on) — controls whether videos with availability: public are downloaded.
  • Download members-only videos (default: off) — controls whether subscriber_only, premium_only, and needs_auth videos are downloaded. Requires cookies.
  • Cookie behaviour — Disabled / When Needed / All Operations.
  • Video client override — yt-dlp player client selector per source.
  • Custom name locked — prevents metadata refresh from overwriting the source name.
  • Description locked — prevents metadata refresh from overwriting the source description.
  • Custom poster — user-uploaded or URL-fetched poster image that takes priority over auto-fetched images.

Unlisted and private videos are always skipped regardless of availability settings.

Media item fields added

  • availability — captured from yt-dlp at index time. Values: public, unlisted, subscriber_only, premium_only, needs_auth, private.
  • error_type — set during download failures. Values: transient (will retry), permanent (sets prevent_download: true, stops retrying).
  • download_prevented_reason — distinguishes manual prevention, policy blocks, and error-stops.

Permanent failures include: video unavailable, removed, private, members-only, age-restricted, geo-blocked, and premium-only errors.

Clone this wiki locally