Skip to content

Plugin Developer Guide

Build integrations for media players like Plex, Jellyfin, Kodi, and more.

Overview

A typical OpenSkip integration:

  1. Detects when media starts playing
  2. Identifies the show, season, and episode
  3. Queries OpenSkip for timestamps
  4. Displays skip buttons or auto-skips

Getting Show Metadata

Most media players provide metadata about what's playing. Look for:

Field Use
TVDB ID Best option - query directly with tvdb_id
TMDB ID Query shows endpoint with tmdb_id
IMDB ID Query shows endpoint
Show name + Season + Episode Search by title, then get episode

Example: Plex

Plex provides GUID strings that often contain TVDB IDs:

# Plex GUID format: com.plexapp.agents.thetvdb://81189/1/1
def parse_plex_guid(guid: str):
    if "thetvdb" in guid:
        parts = guid.split("//")[1].split("/")
        return {
            "tvdb_id": int(parts[0]),
            "season": int(parts[1]),
            "episode": int(parts[2])
        }
    return None

Example: Jellyfin

Jellyfin stores provider IDs in the item metadata:

def get_jellyfin_ids(item):
    provider_ids = item.get("ProviderIds", {})
    return {
        "tvdb_id": provider_ids.get("Tvdb"),
        "tmdb_id": provider_ids.get("Tmdb"),
        "imdb_id": provider_ids.get("Imdb")
    }

Querying OpenSkip

Primary Method: TVDB ID

import requests

def get_timestamps(tvdb_id: int, season: int, episode: int, duration_ms: int = None):
    params = {
        "tvdb_id": tvdb_id,
        "season": season,
        "episode": episode
    }

    if duration_ms:
        params["duration_ms"] = duration_ms
        params["tolerance"] = 5000  # 5 second tolerance

    response = requests.get(
        "https://api.openskip.io/api/v1/timestamps",
        params=params,
        timeout=5
    )

    if response.status_code == 200:
        data = response.json()
        return data["items"][0] if data["total"] > 0 else None

    return None

Fallback: Search by Title

If you don't have a TVDB ID:

def find_show_by_title(title: str):
    response = requests.get(
        "https://api.openskip.io/api/v1/shows",
        params={"search": title}
    )
    data = response.json()

    if data["total"] > 0:
        return data["items"][0]
    return None

def get_timestamps_by_title(title: str, season: int, episode: int):
    show = find_show_by_title(title)
    if not show:
        return None

    # Get episode
    response = requests.get(
        f"https://api.openskip.io/api/v1/shows/{show['id']}/seasons/{season}/episodes/{episode}"
    )

    if response.status_code != 200:
        return None

    ep = response.json()

    # Get timestamps
    return get_timestamps_by_episode_id(ep["id"])

Implementing Skip Functionality

Skip Button Approach

Show a button when entering skippable region:

class SkipButtonController:
    def __init__(self, timestamps):
        self.timestamps = timestamps
        self.button_visible = False

    def on_playback_position(self, current_time: float):
        # Check if in intro region
        intro_start = self.timestamps.get("intro_start")
        intro_end = self.timestamps.get("intro_end")

        if intro_start and intro_end:
            if intro_start <= current_time < intro_end:
                if not self.button_visible:
                    self.show_skip_button("Skip Intro", intro_end)
                    self.button_visible = True
            else:
                if self.button_visible:
                    self.hide_skip_button()
                    self.button_visible = False

        # Similarly for outro, recap, etc.

Auto-Skip Approach

Automatically skip without user interaction:

class AutoSkipController:
    def __init__(self, timestamps, player):
        self.timestamps = timestamps
        self.player = player
        self.skipped_intro = False
        self.skipped_outro = False

    def on_playback_position(self, current_time: float):
        intro_start = self.timestamps.get("intro_start")
        intro_end = self.timestamps.get("intro_end")

        # Auto-skip intro
        if intro_start and intro_end and not self.skipped_intro:
            if intro_start <= current_time < intro_end:
                self.player.seek(intro_end)
                self.skipped_intro = True
                self.notify("Skipped intro")

Best Practices

Caching

Cache timestamp data to reduce API calls and improve responsiveness:

from functools import lru_cache
from datetime import datetime, timedelta

class TimestampCache:
    def __init__(self, ttl_hours=24):
        self.cache = {}
        self.ttl = timedelta(hours=ttl_hours)

    def get(self, key):
        if key in self.cache:
            data, timestamp = self.cache[key]
            if datetime.now() - timestamp < self.ttl:
                return data
            del self.cache[key]
        return None

    def set(self, key, data):
        self.cache[key] = (data, datetime.now())

Error Handling

Handle API failures gracefully:

def get_timestamps_safe(tvdb_id, season, episode):
    try:
        response = requests.get(
            "https://api.openskip.io/api/v1/timestamps",
            params={"tvdb_id": tvdb_id, "season": season, "episode": episode},
            timeout=5
        )
        response.raise_for_status()
        return response.json()
    except requests.exceptions.Timeout:
        logger.warning("OpenSkip API timeout")
        return None
    except requests.exceptions.RequestException as e:
        logger.error(f"OpenSkip API error: {e}")
        return None

Rate Limiting

Respect API rate limits (60 requests/minute for search):

import time
from threading import Lock

class RateLimiter:
    def __init__(self, requests_per_minute=50):
        self.min_interval = 60 / requests_per_minute
        self.last_request = 0
        self.lock = Lock()

    def wait(self):
        with self.lock:
            now = time.time()
            elapsed = now - self.last_request
            if elapsed < self.min_interval:
                time.sleep(self.min_interval - elapsed)
            self.last_request = time.time()

Configuration Options

Let users configure skip behavior:

class SkipConfig:
    # Skip behavior
    auto_skip_intro: bool = False
    auto_skip_outro: bool = False
    auto_skip_recap: bool = False
    show_skip_button: bool = True
    skip_button_duration: int = 10  # seconds to show button

    # API settings
    api_url: str = "https://api.openskip.io"
    cache_ttl_hours: int = 24
    timeout_seconds: int = 5

Testing Your Integration

Test Shows

Use these TVDB IDs for testing (when data is available):

Show TVDB ID
Breaking Bad 81189
Game of Thrones 121361
The Office (US) 73244

Mock API for Development

class MockOpenSkipAPI:
    def get_timestamps(self, tvdb_id, season, episode):
        return {
            "items": [{
                "intro_start": 0.0,
                "intro_end": 45.0,
                "outro_start": 3400.0,
                "outro_end": 3480.0,
            }],
            "total": 1
        }

Publishing Your Plugin

When your plugin is ready:

  1. Add OpenSkip attribution/link
  2. Document the OpenSkip integration
  3. Let us know! We'd love to list community plugins

Need Help?