MCPcopy Index your code
hub / github.com/cedric-marcoux/dispatcharr_timeshift

github.com/cedric-marcoux/dispatcharr_timeshift @v1.3.1

Chat with this repo
repository ↗ · DeepWiki ↗ · release v1.3.1 ↗ · + Follow
42 symbols 145 edges 5 files 31 documented · 74% updated 2mo agov1.3.1 · 2026-05-04★ 3810 open issues
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Dispatcharr Timeshift Plugin

Timeshift/catch-up TV plugin for Dispatcharr. Watch past TV programs (up to 7 days) via Xtream Codes providers.

Version: 1.1.9 GitHub: https://github.com/cedric-marcoux/dispatcharr_timeshift License: MIT


⚠️ IMPORTANT: Installation Methods

Method 1: Git Clone (Recommended)

The easiest and most reliable way to install:

cd /path/to/dispatcharr/data/plugins/
git clone https://github.com/cedric-marcoux/dispatcharr_timeshift.git
docker compose restart dispatcharr

Then enable the plugin in Dispatcharr Settings → Plugins.

Method 2: Download Release Asset

Download dispatcharr_timeshift.zip from the Releases page, then import via Settings → Plugins → Import.

Method 3: Manual ZIP (Advanced)

⚠️ WARNING: GitHub's default "Download ZIP" includes the branch/tag name in the folder (e.g., dispatcharr_timeshift-1.1.8/ or dispatcharr_timeshift-main/), which breaks Python imports. You must rename the folder:

cd /path/to/dispatcharr/data/plugins/

# If downloaded from a release tag (v1.1.8):
unzip dispatcharr_timeshift-1.1.8.zip
mv dispatcharr_timeshift-1.1.8 dispatcharr_timeshift

# If downloaded from main branch:
unzip dispatcharr_timeshift-main.zip
mv dispatcharr_timeshift-main dispatcharr_timeshift

# Fix permissions
chmod 644 dispatcharr_timeshift/*.py
chown 1000:1000 dispatcharr_timeshift/*
docker compose restart dispatcharr

Timeshift Not Working?

If timeshift features don't appear after installation, your provider may not support timeshift (tv_archive). Check if your Xtream Codes provider offers catch-up/replay functionality.


Changelog

v1.3.1

  • [FIX] Catch-up icons missing in TiviMate / IPTVnator after EPG cache clear — the XMLTV stream served at /xmltv.php did not include past programmes, so clients had nothing to attach has_archive icons to once their local cache was wiped
  • Root cause: Dispatcharr core generate_epg honors a prev_days parameter (URL query or user.custom_properties.epg_prev_days) that controls how many days of past EPG to include. Default is 0. The plugin's generate_epg patch only converted timestamps to local timezone; it never injected prev_days. Catch-up clients consume the XMLTV grid for the rewind UI, so when their cache was cleared they saw zero past programmes and lost all catch-up icons
  • Fix: the patch now auto-injects prev_days based on each provider's tv_archive_duration (max across all XC streams with tv_archive=1). The value is cached for 5 minutes per worker so XMLTV downloads stay fast. URL ?prev_days= and per-user epg_prev_days keep precedence
  • New setting xmltv_prev_days_override (default: 0 = auto from provider): set a positive integer (1-30) to force a fixed lookback regardless of provider metadata
  • This bug pre-dated v1.3.0 but was masked by clients' persistent EPG cache; clearing TiviMate cache after upgrade exposed it

v1.3.0

  • Major refactor for Dispatcharr v0.24 compatibility + idempotency
  • [NEW] uWSGI worker warm-up (Dispatcharr v0.24+): hooks were not being installed in uWSGI workers
  • Root cause: Dispatcharr v0.24 (commit ddb0328) introduced should_skip_initialization() which skips plugin discovery in worker processes (parent.name in ['uwsgi', 'gunicorn']). With lazy-apps=true, each uWSGI worker initializes independently and never imports plugin.py → monkey-patches never applied → tv_archive=0 returned
  • Fix: new _warmup_workers() helper. When the plugin gets imported in any worker (triggered by a Connect event firing — e.g. a client playing any channel), the helper fires 20 sequential HTTP loopback requests to /live/...ts. Each request hits a different uWSGI worker (round-robin), which fires channel_start, which causes that worker to import the plugin and install hooks. A Redis NX lock prevents multiple workers from running warmup simultaneously
  • Defense in depth: new _keepalive action subscribed to channel_start, channel_stop, client_connect, client_disconnect, epg_refresh events. Any worker that handles a Connect event (even not a stream play) loads the plugin
  • New setting warmup_on_enable (default: true): disable to skip the auto-warmup HTTP loopback (~12 KB total bandwidth) and rely solely on natural event firing
  • Cold-start behavior: immediately after Dispatcharr restart, no uWSGI worker has the plugin loaded (Dispatcharr v0.24 design). The first client action — playing any channel — triggers channel_start in one worker, which discovers the plugin, installs hooks, and runs warmup to propagate to remaining workers. Within ~2 seconds all workers are patched
  • Plugin remains 100% independent — no Dispatcharr core modifications required
  • [FIX] Issue #14 — RecursionError on plugin re-discover: the URLResolver.resolve patch was not idempotent. Calling _patch_url_resolver() twice (e.g. via pm.discover_plugins(force_reload=True) from another plugin's development cycle) captured the already-patched function as "original", recursing one level per re-discover until Python's recursion limit blew up every API request. Fixed using the same _is_timeshift_patch + _native_func marker pattern already used by the other four patches in this module
  • [FIX] Issue #8 — Fallback chain not working in timeshift_proxy: when a channel's first stream lacks tv_archive=1 but a later stream has it, the plugin returned "Timeshift not supported for this channel" instead of using the catch-up-enabled fallback stream. The fallback chain logic was originally added in v1.1.9 (community contribution) but was accidentally reverted in v1.2.0 during a refactor. Restored in v1.3.0

v1.1.9

  • New feature: Catch-up Fallback Chain - Channels with multiple streams now support catch-up even when top-priority stream lacks it
  • Previously: If top-priority stream (e.g., UHD) had no catch-up, entire channel was marked as unavailable
  • Now: Plugin searches all streams in priority order to find one with tv_archive=1
  • IPTV clients now correctly see "Catch-up available" even when highest-quality stream lacks it
  • Timeshift requests automatically use the catch-up-enabled stream (transparent to user)
  • Maintains stream priority for live TV (top stream still preferred for live playback)
  • Example: BBC One has Sky (no catch-up) and IPTV (with catch-up) → Users can now watch catch-up from IPTV stream
  • Modified 3 functions across 2 files: _patch_xc_get_live_streams(), _patch_xc_get_epg(), timeshift_proxy()
  • Backward compatible: Single-stream channels work identically

v1.1.8

  • Documentation: Improved installation instructions
  • Method 1: Git clone (recommended) - most reliable method
  • Method 2: Download release asset dispatcharr_timeshift.zip (correct folder name)
  • Method 3: Manual ZIP with folder rename warning
  • Explained why GitHub's default ZIP breaks Python imports (folder name includes version/branch)

v1.1.7

  • Bug fix: Export Plugin class in __init__.py
  • Dispatcharr requires the Plugin class to be exported from __init__.py
  • Without this, the plugin fails with "invalid plugin: missing plugin class"
  • Added from .plugin import Plugin and __all__ = ['Plugin']

v1.1.6

  • New feature: Debug Mode - Toggle in plugin settings to enable ultra-verbose logging
  • Normal mode: Minimal logging (1 line per timeshift request + errors only)
  • Debug mode: Detailed logs for every step (config loading, channel lookup, timestamp conversion, URL building, provider response)
  • New feature: URL Format Selection - Choose between timeshift URL formats:
  • Auto-detect (default): Tries Format A, falls back to Format B if 400 error
  • Format A: Query string (/streaming/timeshift.php?username=X&...)
  • Format B: Path-based (/timeshift/user/pass/duration/time/id.ts)
  • Custom: User-defined template with placeholders
  • New feature: Custom URL Template - For exotic providers with non-standard URLs
  • Placeholders: {server_url}, {username}, {password}, {stream_id}, {timestamp}, {duration}
  • Only used when "Custom template" is selected in URL Format
  • New feature: Timezone Dropdown - Provider timezone now uses a dropdown with 120 IANA timezone options
  • Organized by region: UTC, Europe, Americas, Asia, Africa, Australia/Pacific
  • Prevents typos and invalid timezone entries
  • Code cleanup: Added .strip() to all config values to prevent whitespace issues
  • Reduced log noise: Production logs now minimal, detailed info only in debug mode
  • Bug fix: XMLTV EPG compatibility with Dispatcharr v0.14 (handles both HttpResponse and StreamingHttpResponse)

v1.1.5

  • Bug fix: Re-enabled UTC→Local timezone conversion for timeshift timestamps
  • v1.1.4 incorrectly removed the conversion, causing wrong content to play
  • Root cause: IPTV clients (iPlayTV, TiviMate, Televizo) use start_timestamp (UTC unix timestamp) from EPG to construct timeshift URLs
  • The timestamp in the URL is therefore in UTC, but XC providers expect LOCAL time
  • Now views.py correctly converts the timestamp from UTC to the configured timezone
  • Example: User selects 18:00 Toronto show → Client sends 23:00 UTC → Plugin converts to 18:00 local → Provider plays correct content

v1.1.4 (BROKEN - DO NOT USE)

  • ~~Bug fix: Removed double timezone conversion~~ - This was incorrect
  • This version broke timeshift for all non-UTC timezones
  • Users experienced wrong content playing (offset by their timezone difference)
  • Fixed in v1.1.5

v1.1.3

  • Bug fix: Timezone setting was not being read from database
  • Plugin was using wrong attribute config.config instead of config.settings
  • Timezone always defaulted to "Europe/Brussels" regardless of user setting
  • Now correctly reads from Dispatcharr's PluginConfig.settings field
  • Affects both timeshift URL conversion and EPG timestamp conversion

v1.1.2

  • Code cleanup: Removed dead code (uninstall_hooks() and _restore_*() functions)
  • Dispatcharr never calls plugin.run("disable"), so these functions were never executed
  • Optimized diagnostics: Expensive DB queries in 404 handler now only run in DEBUG mode
  • Reduces overhead on production systems
  • Basic warning still logged at INFO level
  • Minor fix: Removed unnecessary if chunk: check in stream generator
  • Tested with Dispatcharr v0.14.0

v1.1.1

  • Dynamic EPG-based duration: Timeshift requests now use the actual programme duration from EPG
  • Calculates duration from programme's end_time - start_time
  • Adds 5-minute buffer for stream startup
  • Falls back to 120 minutes if programme not found in EPG
  • Caps at 8 hours maximum to prevent issues
  • Prevents long movies from being cut off (was hardcoded to 2h)
  • More efficient for short programmes (30-45 min)

v1.1.0

  • URL format fallback: Automatic detection and fallback for providers using different timeshift URL formats
  • Format A (default): /streaming/timeshift.php?username=X&password=Y&stream=Z&start=T&duration=N
  • Format B (fallback): /timeshift/{username}/{password}/N/{timestamp}/{stream_id}.ts
  • Automatically tries Format B if Format A returns HTTP 400
  • Caches working format per M3U account for session (no restart needed)
  • Fixes "Provider returned 400" error for providers using path-based timeshift URLs

v1.0.5

  • Enhanced diagnostics: Improved "Channel not found" logging with detailed troubleshooting info
  • Shows if stream exists but with wrong account type (need 'XC')
  • Shows if stream has no channels assigned
  • Shows total XC streams count (helps detect sync issues)
  • Shows if channel exists but user lacks access level

v1.0.4

  • Bug fix: Fixed AssertionError: .accepted_renderer not set on Response error
  • Replaced Django REST Framework Response with Django JsonResponse in patched_stream_xc()
  • This error occurred when channel lookup failed (404) or credentials were invalid (401)
  • The issue was that DRF's Response requires a renderer, but the patched function is called from Django URL patterns, not through DRF's APIView

v1.0.3

  • Enhanced logging: Improved error diagnostics with detailed context for troubleshooting
  • API requests now log channel enhancement stats (e.g., "Enhanced 36/36 channels")
  • EPG requests log channel lookups and program generation counts
  • Authentication failures now specify the exact reason (missing xc_password, wrong password, unknown user)
  • Provider errors include status code, content-type, and response body preview
  • All errors include actionable diagnostic hints

v1.0.2

Based on fixes from Lesthat's fork - thanks for the contributions!

  • Multi-client support: Added compatibility for Snappier iOS and IPTVX
  • EPG 404 fix: Fixed "not found" errors when clients request EPG data using provider stream IDs
  • Data type fixes: Corrected JSON types for strict validation (Snappier iOS compatibility)
  • EPG timezone fix: Programs now display at correct times (fixed +2h offset issue)
  • XMLTV timezone: Converted timestamps for IPTVX compatibility
  • Language setting: Configurable EPG language (27 European languages)
  • Unique program IDs: Each program now has a timestamp-based unique ID

v1.0.1

  • User-Agent fix: Now uses the User-Agent configured in M3U account settings (TiviMate, VLC, etc.) instead of a hardcoded value

v1.0.0

  • Initial release

IMPORTANT: After enabling or disabling this plugin, you must refresh your source in your IPTV player (e.g., iPlayTV) for it to detect the timeshift/replay availability on channels.

Features

  • 100% Plugin Solution - No modification to Dispatcharr

Core symbols most depended-on inside this repo

_get_plugin_config
called by 7
hooks.py
_is_plugin_enabled
called by 5
hooks.py
_build_timeshift_url_format_b
called by 3
views.py
install_hooks
called by 2
hooks.py
uninstall_hooks
called by 2
hooks.py
_auto_install_hooks
called by 2
plugin.py
_read_plugin_version
called by 2
plugin.py
_build_timeshift_url_format_a
called by 2
views.py

Shape

Function 36
Method 5
Class 1

Languages

Python100%

Modules by API surface

hooks.py18 symbols
plugin.py11 symbols
views.py10 symbols
version_check.py3 symbols

For agents

$ claude mcp add dispatcharr_timeshift \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact