-
Notifications
You must be signed in to change notification settings - Fork 0
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.
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.
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.
- 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
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
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.
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
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.
Per-source control over when cookies are used: Disabled / When Needed / All Operations.
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.
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 environment variable controls update behavior: stable (default), nightly, master, pinned/none to disable, or a specific version string like 2025.12.08.
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.
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.
One-click API key validation from the Settings page.
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.
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
| 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. |
| Variable | Reason |
|---|---|
DATABASE_PATH |
SQLite-only. Not used in this fork. |
JOURNAL_MODE |
SQLite-only workaround for network shares. Not used in this fork. |
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 type — Channel or Playlist, now user-selectable. Overrides yt-dlp auto-detection.
-
Download public videos (default: on) — controls whether videos with
availability: publicare downloaded. -
Download members-only videos (default: off) — controls whether
subscriber_only,premium_only, andneeds_authvideos 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.
-
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(setsprevent_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.